1 /*
2 * CGameMode.cpp
3 * OpenLieroX
4 *
5 * Created by Albert Zeyer on 02.03.09.
6 * code under LGPL
7 *
8 */
9
10 #include "CGameMode.h"
11 #include "CServer.h"
12 #include "Options.h"
13 #include "CWorm.h"
14 #include "Protocol.h"
15
GeneralGameType()16 int CGameMode::GeneralGameType() {
17 if(GameTeams() == 1)
18 return GMT_NORMAL;
19 else
20 return GMT_TEAMS;
21 }
22
23
GameTeams()24 int CGameMode::GameTeams() {
25 return 1;
26 }
27
TimeLimit()28 float CGameMode::TimeLimit() {
29 return tLXOptions->tGameInfo.fTimeLimit * 60.0f;
30 }
31
TeamName(int t)32 std::string CGameMode::TeamName(int t) {
33 static const std::string teamnames[4] = { "blue", "red", "green", "yellow" };
34 if(t >= 0 && t < 4) return teamnames[t];
35 return itoa(t);
36 }
37
PrepareGame()38 void CGameMode::PrepareGame()
39 {
40 bFirstBlood = true;
41 for(int i = 0; i < MAX_WORMS; i++) {
42 iKillsInRow[i] = 0;
43 iDeathsInRow[i] = 0;
44 }
45 }
46
Spawn(CWorm * worm,CVec pos)47 bool CGameMode::Spawn(CWorm* worm, CVec pos) {
48 worm->Spawn(pos);
49 return true;
50 }
51
Kill(CWorm * victim,CWorm * killer)52 void CGameMode::Kill(CWorm* victim, CWorm* killer)
53 {
54 // Kill or suicide message
55 std::string buf;
56 if( killer )
57 {
58 if( killer == victim )
59 {
60 // Suicide
61 // TODO: Restore the suicide count message
62 if( networkTexts->sCommitedSuicide != "<none>" )
63 replacemax(networkTexts->sCommitedSuicide, "<player>", victim->getName(), buf, 1);
64 killer->addSuicide();
65 }
66 else if( GameTeams() > 1 && killer->getTeam() == victim->getTeam() )
67 {
68 if( networkTexts->sTeamkill != "<none>" )
69 replacemax(networkTexts->sTeamkill, "<player>", killer->getName(), buf, 1);
70 killer->addTeamkill();
71 }
72 else
73 {
74 if(networkTexts->sKilled != "<none>")
75 {
76 replacemax(networkTexts->sKilled, "<killer>", killer->getName(), buf, 1);
77 replacemax(buf, "<victim>", victim->getName(), buf, 1);
78 }
79 killer->addKill();
80 }
81 }
82 else
83 {
84 // TODO: message if no killer
85 }
86 if( buf != "" )
87 cServer->SendGlobalText(buf, TXT_NORMAL);
88
89 // First blood
90 if(bFirstBlood && killer && killer != victim && networkTexts->sFirstBlood != "<none>") {
91 bFirstBlood = false;
92 cServer->SendGlobalText(replacemax(networkTexts->sFirstBlood, "<player>",
93 killer->getName(), 1), TXT_NORMAL);
94 }
95
96 // Kills & deaths in row
97 if(killer && killer != victim) {
98 iKillsInRow[killer->getID()]++;
99 iDeathsInRow[killer->getID()] = 0;
100 }
101 iKillsInRow[victim->getID()] = 0;
102 iDeathsInRow[victim->getID()]++;
103
104 // Killing spree message -- now with configurable thresholds
105 if(killer && killer != victim) {
106 if(iKillsInRow[killer->getID()] == tLXOptions->iSpreeThreshold1) {
107 if(networkTexts->sSpree1 != "<none>")
108 cServer->SendGlobalText(replacemax(networkTexts->sSpree1, "<player>", killer->getName(), 1), TXT_NORMAL); }
109
110 else if(iKillsInRow[killer->getID()] == tLXOptions->iSpreeThreshold2) {
111 if(networkTexts->sSpree2 != "<none>")
112 cServer->SendGlobalText(replacemax(networkTexts->sSpree2, "<player>", killer->getName(), 1), TXT_NORMAL); }
113
114 else if(iKillsInRow[killer->getID()] == tLXOptions->iSpreeThreshold3) {
115 if(networkTexts->sSpree3 != "<none>")
116 cServer->SendGlobalText(replacemax(networkTexts->sSpree3, "<player>", killer->getName(), 1), TXT_NORMAL); }
117
118 else if(iKillsInRow[killer->getID()] == tLXOptions->iSpreeThreshold4) {
119 if(networkTexts->sSpree4 != "<none>")
120 cServer->SendGlobalText(replacemax(networkTexts->sSpree4, "<player>", killer->getName(), 1), TXT_NORMAL); }
121
122 else if(iKillsInRow[killer->getID()] == tLXOptions->iSpreeThreshold5) {
123 if(networkTexts->sSpree5 != "<none>")
124 cServer->SendGlobalText(replacemax(networkTexts->sSpree5, "<player>", killer->getName(), 1), TXT_NORMAL); }
125
126 }
127
128 // Dying spree message -- now with configurable thresholds
129 if(iDeathsInRow[victim->getID()] == tLXOptions->iDyingSpreeThreshold1){
130 if(networkTexts->sDSpree1 != "<none>")
131 cServer->SendGlobalText(replacemax(networkTexts->sDSpree1, "<player>", victim->getName(), 1), TXT_NORMAL); }
132
133 else if(iDeathsInRow[victim->getID()] == tLXOptions->iDyingSpreeThreshold2){
134 if(networkTexts->sDSpree2 != "<none>")
135 cServer->SendGlobalText(replacemax(networkTexts->sDSpree2, "<player>", victim->getName(), 1), TXT_NORMAL); }
136
137 else if(iDeathsInRow[victim->getID()] == tLXOptions->iDyingSpreeThreshold3){
138 if(networkTexts->sDSpree3 != "<none>")
139 cServer->SendGlobalText(replacemax(networkTexts->sDSpree3, "<player>", victim->getName(), 1), TXT_NORMAL); }
140
141 else if(iDeathsInRow[victim->getID()] == tLXOptions->iDyingSpreeThreshold4){
142 if(networkTexts->sDSpree4 != "<none>")
143 cServer->SendGlobalText(replacemax(networkTexts->sDSpree4, "<player>", victim->getName(), 1), TXT_NORMAL); }
144
145 else if(iDeathsInRow[victim->getID()] == tLXOptions->iDyingSpreeThreshold5){
146 if(networkTexts->sDSpree5 != "<none>")
147 cServer->SendGlobalText(replacemax(networkTexts->sDSpree5, "<player>", victim->getName(), 1), TXT_NORMAL); }
148
149
150 // Victim is out of the game
151 if(victim->Kill() && networkTexts->sPlayerOut != "<none>")
152 cServer->SendGlobalText(replacemax(networkTexts->sPlayerOut, "<player>",
153 victim->getName(), 1), TXT_NORMAL);
154
155 if( GameTeams() > 1 )
156 {
157 int worms[4] = { 0, 0, 0, 0 };
158 for(int i = 0; i < MAX_WORMS; i++)
159 if(cServer->getWorms()[i].isUsed() && cServer->getWorms()[i].getLives() != WRM_OUT)
160 if(cServer->getWorms()[i].getTeam() >= 0 && cServer->getWorms()[i].getTeam() < 4)
161 worms[cServer->getWorms()[i].getTeam()]++;
162
163 // Victim's team is out of the game
164 if(worms[victim->getTeam()] == 0 && networkTexts->sTeamOut != "<none>")
165 cServer->SendGlobalText(replacemax(networkTexts->sTeamOut, "<team>",
166 TeamName(victim->getTeam()), 1), TXT_NORMAL);
167 }
168 }
169
Drop(CWorm * worm)170 void CGameMode::Drop(CWorm* worm)
171 {
172 if (!worm || worm->getID() < 0 || worm->getID() >= MAX_WORMS) {
173 errors << "Dropped an invalid worm" << endl;
174 return;
175 }
176
177 iKillsInRow[worm->getID()] = 0;
178 iDeathsInRow[worm->getID()] = 0;
179 }
180
getWormHitKillLimit()181 static int getWormHitKillLimit() {
182 if(tLXOptions->tGameInfo.iKillLimit <= 0) return -1;
183
184 for(int i = 0; i < MAX_WORMS; ++i) {
185 CWorm* w = &cServer->getWorms()[i];
186 if(!w->isUsed()) continue;
187 if(w->getScore() >= tLXOptions->tGameInfo.iKillLimit)
188 return i;
189 }
190
191 return -1;
192 }
193
CheckGameOver()194 bool CGameMode::CheckGameOver() {
195 // In game?
196 if (!cServer || cServer->getState() == SVS_LOBBY || cServer->getGameOver())
197 return true;
198
199 // check for teamscorelimit
200 if(GameTeams() > 1 && (int)tLXOptions->tGameInfo.features[FT_TeamScoreLimit] > 0) {
201 for(int i = 0; i < GameTeams(); ++i) {
202 if(TeamScores(i) >= (int)tLXOptions->tGameInfo.features[FT_TeamScoreLimit]) {
203 // TODO: make configureable
204 cServer->SendGlobalText("Team score limit " + itoa((int)tLXOptions->tGameInfo.features[FT_TeamScoreLimit]) + " hit by " + TeamName(i), TXT_NORMAL);
205 notes << "team score limit hit by team " << i << endl;
206 return true;
207 }
208 }
209 }
210
211 // check for maxkills
212 if(tLXOptions->tGameInfo.iKillLimit > 0) {
213 int w = getWormHitKillLimit();
214 if(w >= 0) {
215 // TODO: make configureable
216 cServer->SendGlobalText("Kill limit " + itoa(tLXOptions->tGameInfo.iKillLimit) + " hit by worm " + cServer->getWorms()[w].getName(), TXT_NORMAL);
217 notes << "worm " << w << " hit the kill limit" << endl;
218 return true;
219 }
220 }
221
222 // Check if the timelimit has been reached
223 if(TimeLimit() > 0) {
224 if (cServer->getServerTime() > TimeLimit()) {
225 if(networkTexts->sTimeLimit != "<none>")
226 cServer->SendGlobalText(networkTexts->sTimeLimit, TXT_NORMAL);
227 notes << "time limit (" << TimeLimit() << ") reached with current time " << cServer->getServerTime().seconds();
228 notes << " -> game over" << endl;
229 return true;
230 }
231 }
232
233 bool allowEmptyGames =
234 tLXOptions->tGameInfo.features[FT_AllowEmptyGames] &&
235 tLXOptions->tGameInfo.bAllowConnectDuringGame &&
236 tLX->iGameType != GME_LOCAL &&
237 tLXOptions->tGameInfo.iLives < 0;
238
239 if(!allowEmptyGames) {
240 // TODO: move that worms-num calculation to GameServer
241 int worms = 0;
242 for(int i = 0; i < MAX_WORMS; i++)
243 if(cServer->getWorms()[i].isUsed()) {
244 if (cServer->getWorms()[i].getLives() != WRM_OUT)
245 worms++;
246 }
247
248 int minWormsNeeded = 2;
249 if(tLX->iGameType == GME_LOCAL && cServer->getNumPlayers() == 1) minWormsNeeded = 1;
250 if(worms < minWormsNeeded) {
251 // TODO: make configureable
252 // HINT: thins is kinda spammy, because in this kind of games everybody knows why it ended
253 //cServer->SendGlobalText("Too few players in game", TXT_NORMAL);
254 notes << "only " << worms << " players left in game -> game over" << endl;
255 return true;
256 }
257 }
258
259 if(!allowEmptyGames && GameTeams() > 1) {
260 // Only one team left?
261 int teams = 0;
262 for(int i = 0; i < GameTeams(); i++) {
263 if(WormsAliveInTeam(i)) {
264 teams++;
265 }
266 }
267
268 // Only one team left
269 if(teams <= 1) {
270 // TODO: make configureable
271 // HINT: this is kinda spammy because everyone with IQ > 50 knows why the game ended
272 //cServer->SendGlobalText("Too few teams in game", TXT_NORMAL);
273 notes << "Game has finished, all non-winning teams are out!" << endl;
274 return true;
275 }
276 }
277
278 return false;
279 }
280
Winner()281 int CGameMode::Winner() {
282 // In case of last man standing, that one must win
283 CWorm *alive = cServer->getFirstAliveWorm();
284 if (cServer->getAliveWormCount() < 2 && alive) {
285 return alive->getID();
286 }
287
288 return HighestScoredWorm(); // For other cases (max kills etc.)
289 }
290
HighestScoredWorm()291 int CGameMode::HighestScoredWorm() {
292 int wormid = -1;
293 for(int i = 0; i < MAX_WORMS; i++)
294 if(cServer->getWorms()[i].isUsed()) {
295 if(wormid < 0) wormid = i;
296 else
297 wormid = CompareWormsScore(&cServer->getWorms()[i], &cServer->getWorms()[wormid]) > 0 ? i : wormid; // Take the worm with the best score
298 }
299
300 return wormid;
301 }
302
CompareWormsScore(CWorm * w1,CWorm * w2)303 int CGameMode::CompareWormsScore(CWorm* w1, CWorm* w2) {
304
305 // Lives first
306 if(cClient->getGameLobby()->iLives >= 0) {
307 if (w1->getLives() > w2->getLives()) return 1;
308 if (w1->getLives() < w2->getLives()) return -1;
309 }
310
311 // Kills
312 if (w1->getScore() > w2->getScore()) return 1;
313 if (w1->getScore() < w2->getScore()) return -1;
314
315 // Damage
316 return Round(w1->getDamage() - w2->getDamage());
317 }
318
WinnerTeam()319 int CGameMode::WinnerTeam() {
320 if(GameTeams() > 1) {
321 // Only one team left, that one must be the winner
322 if (cServer->getAliveTeamCount() < 2 && tLXOptions->tGameInfo.iLives >= 0) {
323 CWorm *w = cServer->getWorms();
324 for (int i = 0; i < MAX_WORMS; i++, w++)
325 if (w->isUsed() && w->getLives() != WRM_OUT && w->getTeam() >= 0 && w->getTeam() < 4)
326 return w->getTeam();
327 }
328
329 return HighestScoredTeam();
330 } else
331 return -1;
332 }
333
HighestScoredTeam()334 int CGameMode::HighestScoredTeam() {
335 int team = -1;
336 for(int i = 0; i < GameTeams(); i++)
337 if(team < 0) team = i;
338 else
339 team = CompareTeamsScore(i, team) > 0 ? i : team; // Take the team with the best score
340
341 return team;
342 }
343
WormsAliveInTeam(int t)344 int CGameMode::WormsAliveInTeam(int t) {
345 int c = 0;
346 for(int i = 0; i < MAX_WORMS; i++)
347 if(cServer->getWorms()[i].isUsed() && cServer->getWorms()[i].getLives() != WRM_OUT && cServer->getWorms()[i].getTeam() == t)
348 c++;
349 return c;
350 }
351
TeamScores(int t)352 int CGameMode::TeamScores(int t) {
353 return TeamKills(t);
354 }
355
TeamKills(int t)356 int CGameMode::TeamKills(int t) {
357 int c = 0;
358 for(int i = 0; i < MAX_WORMS; i++)
359 if(cServer->getWorms()[i].isUsed() && cServer->getWorms()[i].getTeam() == t)
360 c += cServer->getWorms()[i].getScore();
361 return c;
362 }
363
TeamDamage(int t)364 float CGameMode::TeamDamage(int t) {
365 float c = 0;
366 for(int i = 0; i < MAX_WORMS; i++)
367 if(cServer->getWorms()[i].isUsed() && cServer->getWorms()[i].getTeam() == t)
368 c += cServer->getWorms()[i].getDamage();
369 return c;
370 }
371
372
CompareTeamsScore(int t1,int t2)373 int CGameMode::CompareTeamsScore(int t1, int t2) {
374 // Lives first
375 if(tLXOptions->tGameInfo.iLives >= 0) {
376 int d = WormsAliveInTeam(t1) - WormsAliveInTeam(t2);
377 if(d != 0) return d;
378 }
379
380 // if there is a custom teamscore function
381 {
382 int d = TeamScores(t1) - TeamScores(t2);
383 if(d != 0) return d;
384 }
385
386 // Kills
387 {
388 int d = TeamKills(t1) - TeamKills(t2);
389 if(d != 0) return d;
390 }
391
392 // Damage
393 return Round(TeamDamage(t1) - TeamDamage(t2));
394 }
395
396
397 extern CGameMode* gameMode_DeathMatch;
398 extern CGameMode* gameMode_TeamDeathMatch;
399 extern CGameMode* gameMode_Tag;
400 extern CGameMode* gameMode_Demolitions;
401 extern CGameMode* gameMode_HideAndSeek;
402 extern CGameMode* gameMode_CaptureTheFlag;
403 extern CGameMode* gameMode_Race;
404 extern CGameMode* gameMode_TeamRace;
405
406
407 static std::vector<CGameMode*> gameModes;
408
InitGameModes()409 void InitGameModes() {
410 gameModes.resize(8);
411 gameModes[0] = gameMode_DeathMatch;
412 gameModes[1] = gameMode_TeamDeathMatch;
413 gameModes[2] = gameMode_Tag;
414 gameModes[3] = gameMode_Demolitions;
415 gameModes[4] = gameMode_HideAndSeek;
416 gameModes[5] = gameMode_CaptureTheFlag;
417 gameModes[6] = gameMode_Race;
418 gameModes[7] = gameMode_TeamRace;
419 }
420
GameMode(GameModeIndex i)421 CGameMode* GameMode(GameModeIndex i) {
422 if(i < 0 || (uint)i >= gameModes.size()) {
423 errors << "gamemode " << i << " requested, we don't have such one" << endl;
424 return NULL;
425 }
426
427 return gameModes[i];
428 }
429
GameMode(const std::string & name)430 CGameMode* GameMode(const std::string& name) {
431 for(std::vector<CGameMode*>::iterator i = gameModes.begin(); i != gameModes.end(); ++i) {
432 if(name == (*i)->Name())
433 return *i;
434 }
435 warnings << "gamemode " << name << " requested, we don't have such one" << endl;
436 return NULL;
437 }
438
GetGameModeIndex(CGameMode * gameMode)439 GameModeIndex GetGameModeIndex(CGameMode* gameMode) {
440 if(gameMode == NULL) return GM_DEATHMATCH;
441 int index = 0;
442 for(std::vector<CGameMode*>::iterator i = gameModes.begin(); i != gameModes.end(); ++i, ++index) {
443 if(gameMode == *i)
444 return (GameModeIndex)index;
445 }
446 return GM_DEATHMATCH;
447 }
448
449
450
GameModeIterator()451 Iterator<CGameMode* const&>::Ref GameModeIterator() {
452 return GetConstIterator(gameModes);
453 }
454
guessGeneralGameTypeName(int iGeneralGameType)455 std::string guessGeneralGameTypeName(int iGeneralGameType)
456 {
457 if( iGeneralGameType == GMT_NORMAL )
458 return "Death Match";
459 if( iGeneralGameType == GMT_TEAMS )
460 return "Team Death Match";
461 if( iGeneralGameType == GMT_TIME )
462 return "Tag";
463 if( iGeneralGameType == GMT_DIRT )
464 return "Demolitions";
465 return "";
466 };
467