-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGameManager.ts
201 lines (161 loc) · 6.86 KB
/
GameManager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
* Controls the overall game state of the world, listening to events occurring and transiting the game state accordingly
*/
import * as hz from 'horizon/core';
import { Events } from "Events";
import { timedIntervalActionFunction, GameState, PlayerGameStatus } from 'GameUtils';
import { MatchManager } from 'MatchManager';
export class GameManager extends hz.Component<typeof GameManager> {
static propsDefinition = {
startLineGameStateUI: { type: hz.PropTypes.Entity },
finishLineGameStateUI: { type: hz.PropTypes.Entity },
timeToMatchStartMS: { type: hz.PropTypes.Number, default: 3000 },
timeToMatchEndMS: { type: hz.PropTypes.Number, default: 3000 },
timeNewMatchReadyMS: { type: hz.PropTypes.Number, default: 3000 },
minTimeToShowStartPopupsMS: { type: hz.PropTypes.Number, default: 3000 },
minTimeToShowEndPopupsMS: { type: hz.PropTypes.Number, default: 10000 },
playersNeededForMatch: { type: hz.PropTypes.Number, default: 1 },
};
private currentGameState = GameState.ReadyForMatch;
private startMatchTimerID = 0;
private endMatchTimerID = 0;
private newMatchTimerID = 0;
private startLineGameStateUI: hz.TextGizmo | null = null;
private finishLineGameStateUI: hz.TextGizmo | null = null;
static s_instance: GameManager
constructor() {
super();
if (GameManager.s_instance === undefined) {
GameManager.s_instance = this;
}
else {
console.error(`There are two ${this.constructor.name} in the world!`)
return;
}
}
preStart() {
this.currentGameState = GameState.ReadyForMatch;
this.startLineGameStateUI = this.props.startLineGameStateUI!.as(hz.TextGizmo)!;
this.finishLineGameStateUI = this.props.finishLineGameStateUI!.as(hz.TextGizmo)!;
this.connectLocalBroadcastEvent(Events.onPlayerJoinedStandby,
() => {
const totalPlayerStandby = MatchManager.getInstance().getPlayersWithStatus(PlayerGameStatus.Standby).length;
if (totalPlayerStandby >= this.props.playersNeededForMatch) {
this.transitFromReadyToStarting();
}
});
//If players leave the match and are in standby, if there are too little players to start the match, we need to transit to ready
this.connectLocalBroadcastEvent(Events.onPlayerLeftStandby,
() => {
const totalPlayerInStandby = MatchManager.getInstance().getPlayersWithStatus(PlayerGameStatus.Standby).length;
if (totalPlayerInStandby < this.props.playersNeededForMatch) {
if (this.currentGameState === GameState.StartingMatch) {
this.transitFromStartingToReady();
}
else {
console.error("invalid state to transition from");
}
}
});
//handle the case where there the last player leaves the world
this.connectCodeBlockEvent(this.entity, hz.CodeBlockEvents.OnPlayerExitWorld, (player: hz.Player) => {
if (this.world.getPlayers().length === 0) {
this.sendNetworkBroadcastEvent(Events.onResetWorld, {});
console.warn("All players left, resetting world");
}
this.reset();
});
this.connectLocalBroadcastEvent(Events.onPlayerReachedGoal,
() => {
this.transitFromPlayingToEnding();
});
}
start() { }
private transitGameState(fromState: GameState, toState: GameState) {
if (fromState === toState) {
console.warn(`Trying to transit to the same state ${GameState[fromState]}, skipping`)
return false;
}
else if (fromState !== this.currentGameState) {
console.warn(`Trying to transit from ${GameState[fromState]} when Current state is ${GameState[this.currentGameState]} `)
return false;
}
else {
console.log(`transiting from ${GameState[fromState]} to ${GameState[toState]}`)
this.currentGameState = toState;
this.sendLocalBroadcastEvent(Events.onGameStateChanged, { fromState, toState });
return true;
}
}
private transitFromStartingToReady(): void {
const transited = this.transitGameState(GameState.StartingMatch, GameState.ReadyForMatch);
if (!transited) return;
this.reset();
}
private transitFromCompletedToReady(): void {
const transited = this.transitGameState(GameState.CompletedMatch, GameState.ReadyForMatch);
if (!transited) return;
this.reset();
}
private transitFromReadyToStarting(): void {
const transited = this.transitGameState(GameState.ReadyForMatch, GameState.StartingMatch);
if (!transited) return;
this.startMatchTimerID = timedIntervalActionFunction(this.props.timeToMatchStartMS, this,
(timerMS) => {
const infoStr = `Match Starting in ${timerMS / 1000}!`;
this.updateGameStateUI(infoStr);
this.sendLocalBroadcastEvent(Events.onGameStartTimeLeft, { timeLeftMS: timerMS });
if (timerMS < this.props.minTimeToShowStartPopupsMS) {
this.world.ui.showPopupForEveryone(infoStr, 1);
}
},
this.transitFromStartingToPlaying.bind(this)
);
}
private transitFromStartingToPlaying(): void {
const transited = this.transitGameState(GameState.StartingMatch, GameState.PlayingMatch);
if (!transited) return;
this.updateGameStateUI(`Game On!`);
}
private transitFromPlayingToEnding(): void {
const transited = this.transitGameState(GameState.PlayingMatch, GameState.EndingMatch);
if (!transited) return;
this.endMatchTimerID = timedIntervalActionFunction(this.props.timeToMatchEndMS, this,
(timerMS) => {
const infoStr = `Match Ending in ${timerMS / 1000}!`;
this.updateGameStateUI(infoStr);
if (timerMS < this.props.minTimeToShowEndPopupsMS) {
this.world.ui.showPopupForEveryone(infoStr, 1);
}
this.sendLocalBroadcastEvent(Events.onGameEndTimeLeft, { timeLeftMS: timerMS });
},
this.transitFromEndingToCompleted.bind(this)
);
}
private transitFromEndingToCompleted(): void {
const transited = this.transitGameState(GameState.EndingMatch, GameState.CompletedMatch);
if (!transited) return;
//now transit from Completed to Ready
this.newMatchTimerID = timedIntervalActionFunction(this.props.timeNewMatchReadyMS, this,
(timerMS) => {
const infoStr = `New Match Available in ${timerMS / 1000}!`;
this.updateGameStateUI(infoStr);
this.world.ui.showPopupForEveryone(infoStr, this.props.timeNewMatchReadyMS / 1000);
},
this.transitFromCompletedToReady.bind(this)
);
}
private updateGameStateUI(text: string): void {
this.startLineGameStateUI?.text.set(text);
this.finishLineGameStateUI?.text.set(text);
}
private reset() {
this.currentGameState = GameState.ReadyForMatch;
this.updateGameStateUI('Ready');
this.async.clearInterval(this.startMatchTimerID);
this.async.clearInterval(this.endMatchTimerID);
this.async.clearInterval(this.newMatchTimerID);
}
dispose() { this.reset(); }
}
hz.Component.register(GameManager);