| 1 | /* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ |
| 2 | /* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ |
| 3 | #include "DDRace.h" |
| 4 | |
| 5 | #include <engine/server.h> |
| 6 | #include <engine/shared/config.h> |
| 7 | |
| 8 | #include <game/mapitems.h> |
| 9 | #include <game/server/entities/character.h> |
| 10 | #include <game/server/gamecontext.h> |
| 11 | #include <game/server/player.h> |
| 12 | #include <game/server/score.h> |
| 13 | #include <game/version.h> |
| 14 | |
| 15 | #define GAME_TYPE_NAME "DDraceNetwork" |
| 16 | #define TEST_TYPE_NAME "TestDDraceNetwork" |
| 17 | |
| 18 | CGameControllerDDRace::CGameControllerDDRace(class CGameContext *pGameServer) : |
| 19 | IGameController(pGameServer) |
| 20 | { |
| 21 | m_pGameType = g_Config.m_SvTestingCommands ? TEST_TYPE_NAME : GAME_TYPE_NAME; |
| 22 | m_GameFlags = protocol7::GAMEFLAG_RACE; |
| 23 | } |
| 24 | |
| 25 | CGameControllerDDRace::~CGameControllerDDRace() = default; |
| 26 | |
| 27 | CScore *CGameControllerDDRace::Score() |
| 28 | { |
| 29 | return GameServer()->Score(); |
| 30 | } |
| 31 | |
| 32 | void CGameControllerDDRace::HandleCharacterTiles(CCharacter *pChr, int MapIndex) |
| 33 | { |
| 34 | CPlayer *pPlayer = pChr->GetPlayer(); |
| 35 | const int ClientId = pPlayer->GetCid(); |
| 36 | |
| 37 | int TileIndex = GameServer()->Collision()->GetTileIndex(Index: MapIndex); |
| 38 | int TileFIndex = GameServer()->Collision()->GetFrontTileIndex(Index: MapIndex); |
| 39 | |
| 40 | //Sensitivity |
| 41 | int S1 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x + pChr->GetProximityRadius() / 3.f, pChr->GetPos().y - pChr->GetProximityRadius() / 3.f)); |
| 42 | int S2 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x + pChr->GetProximityRadius() / 3.f, pChr->GetPos().y + pChr->GetProximityRadius() / 3.f)); |
| 43 | int S3 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x - pChr->GetProximityRadius() / 3.f, pChr->GetPos().y - pChr->GetProximityRadius() / 3.f)); |
| 44 | int S4 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x - pChr->GetProximityRadius() / 3.f, pChr->GetPos().y + pChr->GetProximityRadius() / 3.f)); |
| 45 | int Tile1 = GameServer()->Collision()->GetTileIndex(Index: S1); |
| 46 | int Tile2 = GameServer()->Collision()->GetTileIndex(Index: S2); |
| 47 | int Tile3 = GameServer()->Collision()->GetTileIndex(Index: S3); |
| 48 | int Tile4 = GameServer()->Collision()->GetTileIndex(Index: S4); |
| 49 | int FTile1 = GameServer()->Collision()->GetFrontTileIndex(Index: S1); |
| 50 | int FTile2 = GameServer()->Collision()->GetFrontTileIndex(Index: S2); |
| 51 | int FTile3 = GameServer()->Collision()->GetFrontTileIndex(Index: S3); |
| 52 | int FTile4 = GameServer()->Collision()->GetFrontTileIndex(Index: S4); |
| 53 | |
| 54 | const ERaceState PlayerDDRaceState = pChr->m_DDRaceState; |
| 55 | bool IsOnStartTile = (TileIndex == TILE_START) || (TileFIndex == TILE_START) || FTile1 == TILE_START || FTile2 == TILE_START || FTile3 == TILE_START || FTile4 == TILE_START || Tile1 == TILE_START || Tile2 == TILE_START || Tile3 == TILE_START || Tile4 == TILE_START; |
| 56 | // start |
| 57 | if(IsOnStartTile && PlayerDDRaceState != ERaceState::CHEATED) |
| 58 | { |
| 59 | const int Team = GameServer()->GetDDRaceTeam(ClientId); |
| 60 | if(Teams().GetSaving(TeamId: Team)) |
| 61 | { |
| 62 | GameServer()->SendStartWarning(ClientId, pMessage: "You can't start while loading/saving of team is in progress" ); |
| 63 | pChr->Die(Killer: ClientId, Weapon: WEAPON_WORLD); |
| 64 | return; |
| 65 | } |
| 66 | if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && (Team == TEAM_FLOCK || Teams().Count(Team) <= 1)) |
| 67 | { |
| 68 | GameServer()->SendStartWarning(ClientId, pMessage: "You have to be in a team with other tees to start" ); |
| 69 | pChr->Die(Killer: ClientId, Weapon: WEAPON_WORLD); |
| 70 | return; |
| 71 | } |
| 72 | if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team != TEAM_FLOCK && Teams().IsValidTeamNumber(Team) && Teams().Count(Team) < g_Config.m_SvMinTeamSize && !Teams().TeamFlock(Team)) |
| 73 | { |
| 74 | char aBuf[128]; |
| 75 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Your team has fewer than %d players, so your team rank won't count" , g_Config.m_SvMinTeamSize); |
| 76 | GameServer()->SendStartWarning(ClientId, pMessage: aBuf); |
| 77 | } |
| 78 | if(g_Config.m_SvResetPickups) |
| 79 | { |
| 80 | pChr->ResetPickups(); |
| 81 | } |
| 82 | |
| 83 | Teams().OnCharacterStart(ClientId); |
| 84 | pChr->m_LastTimeCp = -1; |
| 85 | pChr->m_LastTimeCpBroadcasted = -1; |
| 86 | for(float &CurrentTimeCp : pChr->m_aCurrentTimeCp) |
| 87 | { |
| 88 | CurrentTimeCp = 0.0f; |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | // finish |
| 93 | if(((TileIndex == TILE_FINISH) || (TileFIndex == TILE_FINISH) || FTile1 == TILE_FINISH || FTile2 == TILE_FINISH || FTile3 == TILE_FINISH || FTile4 == TILE_FINISH || Tile1 == TILE_FINISH || Tile2 == TILE_FINISH || Tile3 == TILE_FINISH || Tile4 == TILE_FINISH) && PlayerDDRaceState == ERaceState::STARTED) |
| 94 | Teams().OnCharacterFinish(ClientId); |
| 95 | |
| 96 | // unlock team |
| 97 | else if(((TileIndex == TILE_UNLOCK_TEAM) || (TileFIndex == TILE_UNLOCK_TEAM)) && Teams().TeamLocked(Team: GameServer()->GetDDRaceTeam(ClientId))) |
| 98 | { |
| 99 | Teams().SetTeamLock(Team: GameServer()->GetDDRaceTeam(ClientId), Lock: false); |
| 100 | GameServer()->SendChatTeam(Team: GameServer()->GetDDRaceTeam(ClientId), pText: "Your team was unlocked by an unlock team tile" ); |
| 101 | } |
| 102 | |
| 103 | // solo part |
| 104 | if(((TileIndex == TILE_SOLO_ENABLE) || (TileFIndex == TILE_SOLO_ENABLE)) && !Teams().m_Core.GetSolo(ClientId)) |
| 105 | { |
| 106 | GameServer()->SendChatTarget(To: ClientId, pText: "You are now in a solo part" ); |
| 107 | pChr->SetSolo(true); |
| 108 | } |
| 109 | else if(((TileIndex == TILE_SOLO_DISABLE) || (TileFIndex == TILE_SOLO_DISABLE)) && Teams().m_Core.GetSolo(ClientId)) |
| 110 | { |
| 111 | GameServer()->SendChatTarget(To: ClientId, pText: "You are now out of the solo part" ); |
| 112 | pChr->SetSolo(false); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | void CGameControllerDDRace::SetArmorProgress(CCharacter *pCharacter, int Progress) |
| 117 | { |
| 118 | pCharacter->SetArmor(std::clamp(val: 10 - (Progress / 15), lo: 0, hi: 10)); |
| 119 | } |
| 120 | |
| 121 | void CGameControllerDDRace::OnPlayerConnect(CPlayer *pPlayer) |
| 122 | { |
| 123 | IGameController::OnPlayerConnect(pPlayer); |
| 124 | int ClientId = pPlayer->GetCid(); |
| 125 | |
| 126 | // init the player |
| 127 | Score()->PlayerData(Id: ClientId)->Reset(); |
| 128 | |
| 129 | // Can't set score here as LoadScore() is threaded, run it in |
| 130 | // LoadScoreThreaded() instead |
| 131 | Score()->LoadPlayerData(ClientId); |
| 132 | |
| 133 | if(!Server()->ClientPrevIngame(ClientId)) |
| 134 | { |
| 135 | char aBuf[512]; |
| 136 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' entered and joined the %s" , Server()->ClientName(ClientId), GetTeamName(Team: pPlayer->GetTeam())); |
| 137 | GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf, SpamProtectionClientId: -1, VersionFlags: CGameContext::FLAG_SIX); |
| 138 | |
| 139 | GameServer()->SendChatTarget(To: ClientId, pText: "DDraceNetwork Mod. Version: " GAME_VERSION); |
| 140 | GameServer()->SendChatTarget(To: ClientId, pText: "please visit DDNet.org or say /info and make sure to read our /rules" ); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | void CGameControllerDDRace::OnPlayerDisconnect(CPlayer *pPlayer, const char *pReason) |
| 145 | { |
| 146 | int ClientId = pPlayer->GetCid(); |
| 147 | bool WasModerator = pPlayer->m_Moderating && Server()->ClientIngame(ClientId); |
| 148 | |
| 149 | IGameController::OnPlayerDisconnect(pPlayer, pReason); |
| 150 | |
| 151 | if(!GameServer()->PlayerModerating() && WasModerator) |
| 152 | GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: "Server kick/spec votes are no longer actively moderated." ); |
| 153 | |
| 154 | if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO) |
| 155 | Teams().SetForceCharacterTeam(ClientId, Team: TEAM_FLOCK); |
| 156 | |
| 157 | for(int Team = TEAM_FLOCK + 1; Team < TEAM_SUPER; Team++) |
| 158 | if(Teams().IsInvited(Team, ClientId)) |
| 159 | Teams().SetClientInvited(Team, ClientId, Invited: false); |
| 160 | } |
| 161 | |
| 162 | void CGameControllerDDRace::OnReset() |
| 163 | { |
| 164 | IGameController::OnReset(); |
| 165 | Teams().Reset(); |
| 166 | } |
| 167 | |
| 168 | void CGameControllerDDRace::Tick() |
| 169 | { |
| 170 | IGameController::Tick(); |
| 171 | Teams().ProcessSaveTeam(); |
| 172 | Teams().Tick(); |
| 173 | } |
| 174 | |
| 175 | void CGameControllerDDRace::DoTeamChange(class CPlayer *pPlayer, int Team, bool DoChatMsg) |
| 176 | { |
| 177 | if(!IsValidTeam(Team)) |
| 178 | return; |
| 179 | |
| 180 | if(Team == pPlayer->GetTeam()) |
| 181 | return; |
| 182 | |
| 183 | CCharacter *pCharacter = pPlayer->GetCharacter(); |
| 184 | |
| 185 | if(Team == TEAM_SPECTATORS) |
| 186 | { |
| 187 | if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && pCharacter) |
| 188 | { |
| 189 | Teams().OnCharacterDeath(ClientId: pPlayer->GetCid(), Weapon: WEAPON_GAME); |
| 190 | // Joining spectators should not kill a locked team, but should still |
| 191 | // check if the team finished by you leaving it. |
| 192 | int DDRTeam = pCharacter->Team(); |
| 193 | Teams().SetForceCharacterTeam(ClientId: pPlayer->GetCid(), Team: TEAM_FLOCK); |
| 194 | Teams().CheckTeamFinished(Team: DDRTeam); |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | IGameController::DoTeamChange(pPlayer, Team, DoChatMsg); |
| 199 | } |
| 200 | |