1 /* 2 * CRace.cpp 3 * OpenLieroX 4 * 5 * Created by Albert Zeyer on 10.05.09. 6 * code under LGPL 7 * 8 */ 9 10 #include <algorithm> 11 #include "CGameMode.h" 12 #include "CServer.h" 13 #include "CWorm.h" 14 #include "FlagInfo.h" 15 #include "CServerConnection.h" 16 #include "CServerNetEngine.h" 17 18 struct Race : public CGameMode { 19 NameRace20 virtual std::string Name() { 21 return "Race"; 22 } 23 getGameInfoGroupInOptionsRace24 virtual GameInfoGroup getGameInfoGroupInOptions() { return GIG_Race; } 25 MinNeededVersionRace26 virtual Version MinNeededVersion() { return OLXBetaVersion(0,58,1); } 27 resetRace28 virtual void reset() { 29 wayPoints.clear(); 30 wormSpawnPoints.clear(); 31 nextGoals.clear(); 32 } 33 RaceRace34 Race() { reset(); } 35 36 static const unsigned int WPNUM = 4; 37 std::vector<CVec> wayPoints; 38 flagNameRace39 std::string flagName(int t) { 40 return "way point " + itoa(t + 1); 41 } 42 sendWormScoreUpdateRace43 void sendWormScoreUpdate(CWorm* w) { 44 for(int ii = 0; ii < MAX_CLIENTS; ii++) { 45 if(cServer->getClients()[ii].getStatus() != NET_CONNECTED) continue; 46 if(cServer->getClients()[ii].getNetEngine() == NULL) continue; 47 cServer->getClients()[ii].getNetEngine()->SendWormScore( w ); 48 } 49 } 50 PrepareGameRace51 virtual void PrepareGame() { 52 CGameMode::PrepareGame(); 53 reset(); 54 55 wayPoints.resize(WPNUM); 56 for(int x = 0; x <= 1; x++) 57 for(int y = 0; y <= 1; y++) { 58 std::list<CVec> goodPos; 59 goodPos.push_back(CVec( 60 (cServer->getMap()->GetWidth() * 0.8f * x + cServer->getMap()->GetWidth() * 0.2f), 61 (cServer->getMap()->GetHeight() * 0.8f * y + cServer->getMap()->GetHeight() * 0.2f))); 62 std::list<CVec> badPos; 63 badPos.push_back(CVec( 64 (cServer->getMap()->GetWidth() * 0.2f * x + cServer->getMap()->GetWidth() * 0.8f), 65 (cServer->getMap()->GetHeight() * 0.2f * y + cServer->getMap()->GetHeight() * 0.8f))); 66 int t = (y == 0) ? x : (3 - x); 67 wayPoints[t] = cServer->FindSpotCloseToPos(goodPos, badPos, false); 68 69 if(!tLXOptions->tGameInfo.features[FT_InstantAirJump]) 70 // set the place to the ground 71 wayPoints[t] = cServer->getMap()->groundPos(wayPoints[t]) - CVec(0, (float)(cServer->flagInfo()->getHeight()/4)); 72 } 73 } 74 getWormFlagRace75 virtual int getWormFlag(CWorm* worm) { 76 return worm->getID(); 77 } 78 79 typedef int WormID; 80 std::map<WormID,CVec> wormSpawnPoints; 81 typedef int FlagID; 82 typedef int WayPointID; 83 std::map<FlagID,WayPointID> nextGoals; 84 getNextGoalRace85 WayPointID getNextGoal(FlagID id) { 86 std::map<FlagID,WayPointID>::iterator f = nextGoals.find(id); 87 if(f == nextGoals.end()) return 1; 88 else return f->second; 89 } 90 SpawnRace91 virtual bool Spawn(CWorm* worm, CVec pos) { 92 if(wayPoints.size() == 0) { 93 errors << "Race::Spawn: not prepared yet" << endl; 94 return true; 95 } 96 97 std::map<WormID,CVec>::iterator sp = wormSpawnPoints.find(worm->getID()); 98 if(sp == wormSpawnPoints.end()) 99 pos = wayPoints[0]; 100 else 101 pos = sp->second; 102 103 if(!cServer->flagInfo()->getFlag(getWormFlag(worm))) { 104 // we have to create the new flag, there isn't any yet 105 cServer->flagInfo()->applyInitFlag(getWormFlag(worm), wayPoints[1]); 106 nextGoals[getWormFlag(worm)] = 1; 107 } 108 109 worm->Spawn(pos); 110 111 return true; 112 } 113 KillRace114 virtual void Kill(CWorm* victim, CWorm* killer) { 115 // Victim is out of the game 116 if(victim->Kill() && networkTexts->sPlayerOut != "<none>") 117 cServer->SendGlobalText(replacemax(networkTexts->sPlayerOut, "<player>", 118 victim->getName(), 1), TXT_NORMAL); 119 120 wormSpawnPoints[victim->getID()] = victim->getPos(); 121 } 122 addScoreRace123 virtual void addScore(CWorm* w) { 124 w->addKill(); 125 sendWormScoreUpdate(w); 126 cServer->SendGlobalText(TeamNameForWorm(w) + " is in round " + itoa(Round(w)), TXT_NORMAL); 127 } 128 TeamNameForWormRace129 virtual std::string TeamNameForWorm(CWorm* w) { 130 return w->getName(); 131 } 132 RoundRace133 virtual int Round(CWorm* w) { 134 return w->getKills(); 135 } 136 hitFlagSpawnPointRace137 virtual void hitFlagSpawnPoint(CWorm* worm, Flag* flag) { 138 if(getWormFlag(worm) == flag->id) { 139 if(flag->holderWorm == worm->getID()) 140 cServer->flagInfo()->applySetBack(flag); 141 else 142 cServer->flagInfo()->applyHolderWorm(flag, worm->getID()); 143 144 nextGoals[getWormFlag(worm)] = (nextGoals[getWormFlag(worm)] + 1) % WPNUM; 145 cServer->flagInfo()->applySpawnPos(flag, wayPoints[nextGoals[getWormFlag(worm)]]); 146 147 if(nextGoals[getWormFlag(worm)] == 1) { 148 // we made one round 149 addScore(worm); 150 cServer->RecheckGame(); 151 } 152 } 153 } 154 FlagPointRadiusRace155 virtual float FlagPointRadius() { return tLXOptions->tGameInfo.features[FT_Race_CheckPointRadius]; } FlagRadiusRace156 virtual float FlagRadius() { return tLXOptions->tGameInfo.features[FT_Race_CheckPointRadius]; } 157 ShootRace158 virtual bool Shoot(CWorm* worm) { 159 // get that information from client because both client&server can check this 160 return cClient->getGameLobby()->features[FT_Race_AllowWeapons]; 161 } 162 CheckGameOverRace163 virtual bool CheckGameOver() { 164 if(int(tLXOptions->tGameInfo.features[FT_Race_Rounds]) > 0) { 165 for(int i = 0; i < MAX_WORMS; ++i) { 166 CWorm* w = &cServer->getWorms()[i]; 167 if(!w->isUsed()) continue; 168 if(w->getKills() >= int(tLXOptions->tGameInfo.features[FT_Race_Rounds])) { 169 return true; 170 } 171 } 172 } 173 174 bool allOut = true; 175 for(int i = 0; i < MAX_WORMS; i++) 176 if(cServer->getWorms()[i].isUsed()) { 177 if(cServer->getWorms()[i].getLives() != WRM_OUT || cServer->getWorms()[i].getAlive()) { 178 allOut = false; 179 break; 180 } 181 } 182 if(allOut) return true; 183 184 // Check if the timelimit has been reached 185 if(TimeLimit() > 0) { 186 if (cServer->getServerTime() > TimeLimit()) { 187 if(networkTexts->sTimeLimit != "<none>") 188 cServer->SendGlobalText(networkTexts->sTimeLimit, TXT_NORMAL); 189 notes << "time limit (" << (tLXOptions->tGameInfo.fTimeLimit*60.0f) << ") reached with current time " << cServer->getServerTime().seconds(); 190 notes << " -> game over" << endl; 191 return true; 192 } 193 } 194 195 return false; 196 } 197 198 struct CompareGoals { 199 Race* race; CompareGoalsRace::CompareGoals200 CompareGoals(Race* r) : race(r) {} 201 compRace::CompareGoals202 int comp(CWorm* w1, CWorm* w2) { 203 // this is if we want to do the compare but the game has not started yet 204 if(race->wayPoints.size() == 0) return 0; 205 206 { 207 // compare next goals 208 int lastGoal1 = (race->getNextGoal(race->getWormFlag(w1)) + WPNUM - 1) % WPNUM; 209 int lastGoal2 = (race->getNextGoal(race->getWormFlag(w2)) + WPNUM - 1) % WPNUM; 210 if(lastGoal1 > lastGoal2) return 1; 211 if(lastGoal1 < lastGoal2) return -1; 212 } 213 214 { 215 // compare distance to next goals (they should be the same for both worms) 216 float dist1 = (race->wayPoints[race->getNextGoal(race->getWormFlag(w1))] - w1->getPos()).GetLength2(); 217 float dist2 = (race->wayPoints[race->getNextGoal(race->getWormFlag(w2))] - w2->getPos()).GetLength2(); 218 long distdiff = (long) (dist2 - dist1); 219 if(distdiff >= 1) return 1; // it's better to be more close 220 if(distdiff <= -1) return -1; 221 } 222 223 return 0; 224 } 225 operator ()Race::CompareGoals226 bool operator()(CWorm* w1, CWorm* w2) { 227 return comp(w1, w2) > 0; 228 } 229 }; 230 CompareWormsScoreRace231 int CompareWormsScore(CWorm* w1, CWorm* w2) { 232 // Kills very first (that is the amount of rounds) 233 if (w1->getKills() > w2->getKills()) return 1; 234 if (w1->getKills() < w2->getKills()) return -1; 235 236 { 237 // compare goals; we can only do that if we are server, because we don't have the waypoints otherwise 238 int d = (cServer && cServer->isServerRunning()) ? CompareGoals(this).comp(w1, w2) : 0; 239 if(d != 0) return d; 240 } 241 242 // fallback (very unprobable that this will be used in game) 243 return CGameMode::CompareWormsScore(w1, w2); 244 } 245 }; 246 247 struct TeamRace : public Race { 248 NameTeamRace249 virtual std::string Name() { 250 return "Team Race"; 251 } 252 getGameInfoGroupInOptionsTeamRace253 virtual GameInfoGroup getGameInfoGroupInOptions() { return GIG_Race; } 254 255 static const int MAXTEAMS = 4; 256 int teamScore[MAXTEAMS]; 257 GameTeamsTeamRace258 virtual int GameTeams() { 259 return MAXTEAMS; 260 } 261 resetTeamRace262 void reset() { 263 Race::reset(); 264 for(int i = 0; i < MAXTEAMS; ++i) 265 teamScore[i] = 0; 266 } 267 TeamRaceTeamRace268 TeamRace() { reset(); } 269 PrepareGameTeamRace270 virtual void PrepareGame() { 271 Race::PrepareGame(); 272 } 273 addScoreTeamRace274 virtual void addScore(CWorm* w) { 275 teamScore[CLAMP(w->getTeam(),0,MAXTEAMS-1)]++; 276 cServer->SendTeamScoreUpdate(); 277 // also add score for this specifc worm 278 Race::addScore(w); 279 } 280 getWormFlagTeamRace281 virtual int getWormFlag(CWorm* worm) { 282 return worm->getTeam(); 283 } 284 isTeamUsedTeamRace285 bool isTeamUsed(int t) { 286 return !cServer->isTeamEmpty(t); 287 } 288 TeamNameForWormTeamRace289 virtual std::string TeamNameForWorm(CWorm* w) { 290 return TeamName(w->getTeam()); 291 } 292 RoundTeamRace293 virtual int Round(CWorm* w) { 294 return TeamScores(w->getTeam()); 295 } 296 TeamScoresTeamRace297 virtual int TeamScores(int t) { 298 if(t >= 0 && t < MAXTEAMS) return teamScore[t]; 299 return -1; 300 } 301 getTeamWormsTeamRace302 std::vector<CWorm*> getTeamWorms(int t) { 303 std::vector<CWorm*> worms; 304 if(cServer->getWorms() == NULL) return worms; 305 for(int i = 0; i < MAX_WORMS; i++) 306 if(cServer->getWorms()[i].isUsed()) 307 if(cServer->getWorms()[i].getTeam() != t) 308 worms.push_back(&cServer->getWorms()[i]); 309 return worms; 310 } 311 getTeamBestWormTeamRace312 CWorm* getTeamBestWorm(int t) { 313 std::vector<CWorm*> teamWorms = getTeamWorms(t); 314 if(teamWorms.size() == 0) return NULL; 315 std::sort(teamWorms.begin(), teamWorms.end(), CompareGoals(this)); 316 return teamWorms.front(); 317 } 318 CompareTeamScoreTeamRace319 virtual int CompareTeamScore(int t1, int t2) { 320 // team score is most important in CTF, more important than left lifes 321 { 322 int d = TeamScores(t1) - TeamScores(t2); 323 if(d != 0) return d; 324 } 325 326 { 327 // compare the goals of the best worms of each team 328 CWorm* w1 = getTeamBestWorm(t1); 329 CWorm* w2 = getTeamBestWorm(t2); 330 331 int d = (w1 && w2) ? CompareGoals(this).comp(w1, w2) : 0; 332 if(d != 0) return d; 333 } 334 335 // ok, fallback to default 336 return CGameMode::CompareTeamsScore(t1, t2); 337 } 338 CheckGameOverTeamRace339 virtual bool CheckGameOver() { 340 if(int(tLXOptions->tGameInfo.features[FT_Race_Rounds]) > 0) { 341 for(int i = 0; i < MAXTEAMS; ++i) { 342 if(teamScore[i] >= int(tLXOptions->tGameInfo.features[FT_Race_Rounds])) { 343 return true; 344 } 345 } 346 } 347 348 bool allOut = true; 349 for(int i = 0; i < MAX_WORMS; i++) 350 if(cServer->getWorms()[i].isUsed()) { 351 if(cServer->getWorms()[i].getLives() != WRM_OUT || cServer->getWorms()[i].getAlive()) { 352 allOut = false; 353 break; 354 } 355 } 356 if(allOut) return true; 357 358 // Check if the timelimit has been reached 359 if(TimeLimit() > 0) { 360 if (cServer->getServerTime() > TimeLimit()) { 361 if(networkTexts->sTimeLimit != "<none>") 362 cServer->SendGlobalText(networkTexts->sTimeLimit, TXT_NORMAL); 363 notes << "time limit (" << (tLXOptions->tGameInfo.fTimeLimit*60.0f) << ") reached with current time " << cServer->getServerTime().seconds(); 364 notes << " -> game over" << endl; 365 return true; 366 } 367 } 368 369 if(GameTeams() > 1) { 370 // Only one team left? 371 int teams = 0; 372 for(int i = 0; i < GameTeams(); i++) { 373 if(WormsAliveInTeam(i)) { 374 teams++; 375 } 376 } 377 378 // Only one team left 379 if(teams <= 1) { 380 return true; 381 } 382 } 383 384 return false; 385 } 386 387 388 389 }; 390 391 static Race _gameMode_Race; 392 static TeamRace _gameMode_TeamRace; 393 CGameMode* gameMode_Race = &_gameMode_Race; 394 CGameMode* gameMode_TeamRace = &_gameMode_TeamRace; 395 396