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