mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-14 03:58:18 +00:00
f8fca99e62
5642: Option to remove weak hook & bounce r=def- a=Jupeyy Fixes #5641 server side only ## Checklist - [x] Tested the change ingame - [ ] Provided screenshots if it is a visual change - [ ] Tested in combination with possibly related configuration options - [ ] Written a unit test (especially base/) or added coverage to integration test - [ ] Considered possible null pointers and out of bounds array indexing - [ ] Changed no physics that affect existing maps - [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: Jupeyy <jupjopjap@gmail.com>
763 lines
22 KiB
C++
763 lines
22 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
|
#include <engine/shared/config.h>
|
|
|
|
#include <game/generated/protocol.h>
|
|
#include <game/mapitems.h>
|
|
#include <game/teamscore.h>
|
|
|
|
#include "gamecontext.h"
|
|
#include "gamecontroller.h"
|
|
#include "player.h"
|
|
|
|
#include "entities/character.h"
|
|
#include "entities/door.h"
|
|
#include "entities/dragger.h"
|
|
#include "entities/gun.h"
|
|
#include "entities/light.h"
|
|
#include "entities/pickup.h"
|
|
#include "entities/projectile.h"
|
|
|
|
IGameController::IGameController(class CGameContext *pGameServer)
|
|
{
|
|
m_pGameServer = pGameServer;
|
|
m_pConfig = m_pGameServer->Config();
|
|
m_pServer = m_pGameServer->Server();
|
|
m_pGameType = "unknown";
|
|
|
|
//
|
|
DoWarmup(g_Config.m_SvWarmup);
|
|
m_GameOverTick = -1;
|
|
m_SuddenDeath = 0;
|
|
m_RoundStartTick = Server()->Tick();
|
|
m_RoundCount = 0;
|
|
m_GameFlags = 0;
|
|
m_aMapWish[0] = 0;
|
|
|
|
m_UnbalancedTick = -1;
|
|
m_ForceBalanced = false;
|
|
|
|
m_aNumSpawnPoints[0] = 0;
|
|
m_aNumSpawnPoints[1] = 0;
|
|
m_aNumSpawnPoints[2] = 0;
|
|
|
|
m_CurrentRecord = 0;
|
|
}
|
|
|
|
IGameController::~IGameController() = default;
|
|
|
|
void IGameController::DoActivityCheck()
|
|
{
|
|
if(g_Config.m_SvInactiveKickTime == 0)
|
|
return;
|
|
|
|
for(int i = 0; i < MAX_CLIENTS; ++i)
|
|
{
|
|
#ifdef CONF_DEBUG
|
|
if(g_Config.m_DbgDummies)
|
|
{
|
|
if(i >= MAX_CLIENTS - g_Config.m_DbgDummies)
|
|
break;
|
|
}
|
|
#endif
|
|
if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && Server()->GetAuthedState(i) == AUTHED_NO)
|
|
{
|
|
if(Server()->Tick() > GameServer()->m_apPlayers[i]->m_LastActionTick + g_Config.m_SvInactiveKickTime * Server()->TickSpeed() * 60)
|
|
{
|
|
switch(g_Config.m_SvInactiveKick)
|
|
{
|
|
case 0:
|
|
{
|
|
// move player to spectator
|
|
DoTeamChange(GameServer()->m_apPlayers[i], TEAM_SPECTATORS);
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
// move player to spectator if the reserved slots aren't filled yet, kick him otherwise
|
|
int Spectators = 0;
|
|
for(auto &pPlayer : GameServer()->m_apPlayers)
|
|
if(pPlayer && pPlayer->GetTeam() == TEAM_SPECTATORS)
|
|
++Spectators;
|
|
if(Spectators >= g_Config.m_SvSpectatorSlots)
|
|
Server()->Kick(i, "Kicked for inactivity");
|
|
else
|
|
DoTeamChange(GameServer()->m_apPlayers[i], TEAM_SPECTATORS);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
// kick the player
|
|
Server()->Kick(i, "Kicked for inactivity");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float IGameController::EvaluateSpawnPos(CSpawnEval *pEval, vec2 Pos, int DDTeam)
|
|
{
|
|
float Score = 0.0f;
|
|
CCharacter *pC = static_cast<CCharacter *>(GameServer()->m_World.FindFirst(CGameWorld::ENTTYPE_CHARACTER));
|
|
for(; pC; pC = (CCharacter *)pC->TypeNext())
|
|
{
|
|
// ignore players in other teams
|
|
if(GameServer()->GetDDRaceTeam(pC->GetPlayer()->GetCID()) != DDTeam)
|
|
continue;
|
|
|
|
float d = distance(Pos, pC->m_Pos);
|
|
Score += d == 0 ? 1000000000.0f : 1.0f / d;
|
|
}
|
|
|
|
return Score;
|
|
}
|
|
|
|
void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type, int DDTeam)
|
|
{
|
|
// j == 0: Find an empty slot, j == 1: Take any slot if no empty one found
|
|
for(int j = 0; j < 2 && !pEval->m_Got; j++)
|
|
{
|
|
// get spawn point
|
|
for(int i = 0; i < m_aNumSpawnPoints[Type]; i++)
|
|
{
|
|
vec2 P = m_aaSpawnPoints[Type][i];
|
|
|
|
if(j == 0)
|
|
{
|
|
// check if the position is occupado
|
|
CCharacter *apEnts[MAX_CLIENTS];
|
|
int Num = GameServer()->m_World.FindEntities(m_aaSpawnPoints[Type][i], 64, (CEntity **)apEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
|
|
vec2 aPositions[5] = {vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f)}; // start, left, up, right, down
|
|
int Result = -1;
|
|
for(int Index = 0; Index < 5 && Result == -1; ++Index)
|
|
{
|
|
Result = Index;
|
|
if(!GameServer()->m_World.m_Core.m_aTuning[0].m_PlayerCollision)
|
|
break;
|
|
for(int c = 0; c < Num; ++c)
|
|
if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints[Type][i] + aPositions[Index]) ||
|
|
distance(apEnts[c]->m_Pos, m_aaSpawnPoints[Type][i] + aPositions[Index]) <= apEnts[c]->GetProximityRadius())
|
|
{
|
|
Result = -1;
|
|
break;
|
|
}
|
|
}
|
|
if(Result == -1)
|
|
continue; // try next spawn point
|
|
|
|
P += aPositions[Result];
|
|
}
|
|
|
|
float S = EvaluateSpawnPos(pEval, P, DDTeam);
|
|
if(!pEval->m_Got || (j == 0 && pEval->m_Score > S))
|
|
{
|
|
pEval->m_Got = true;
|
|
pEval->m_Score = S;
|
|
pEval->m_Pos = P;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IGameController::CanSpawn(int Team, vec2 *pOutPos, int DDTeam)
|
|
{
|
|
CSpawnEval Eval;
|
|
|
|
// spectators can't spawn
|
|
if(Team == TEAM_SPECTATORS)
|
|
return false;
|
|
|
|
EvaluateSpawnType(&Eval, 0, DDTeam);
|
|
EvaluateSpawnType(&Eval, 1, DDTeam);
|
|
EvaluateSpawnType(&Eval, 2, DDTeam);
|
|
|
|
*pOutPos = Eval.m_Pos;
|
|
return Eval.m_Got;
|
|
}
|
|
|
|
bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Number)
|
|
{
|
|
if(Index < 0)
|
|
return false;
|
|
|
|
int x, y;
|
|
x = (Pos.x - 16.0f) / 32.0f;
|
|
y = (Pos.y - 16.0f) / 32.0f;
|
|
int aSides[8];
|
|
aSides[0] = GameServer()->Collision()->Entity(x, y + 1, Layer);
|
|
aSides[1] = GameServer()->Collision()->Entity(x + 1, y + 1, Layer);
|
|
aSides[2] = GameServer()->Collision()->Entity(x + 1, y, Layer);
|
|
aSides[3] = GameServer()->Collision()->Entity(x + 1, y - 1, Layer);
|
|
aSides[4] = GameServer()->Collision()->Entity(x, y - 1, Layer);
|
|
aSides[5] = GameServer()->Collision()->Entity(x - 1, y - 1, Layer);
|
|
aSides[6] = GameServer()->Collision()->Entity(x - 1, y, Layer);
|
|
aSides[7] = GameServer()->Collision()->Entity(x - 1, y + 1, Layer);
|
|
|
|
if(Index >= ENTITY_SPAWN && Index <= ENTITY_SPAWN_BLUE)
|
|
{
|
|
int Type = Index - ENTITY_SPAWN;
|
|
m_aaSpawnPoints[Type][m_aNumSpawnPoints[Type]] = Pos;
|
|
m_aNumSpawnPoints[Type] = minimum(m_aNumSpawnPoints[Type] + 1, (int)std::size(m_aaSpawnPoints[0]));
|
|
}
|
|
|
|
else if(Index == ENTITY_DOOR)
|
|
{
|
|
for(int i = 0; i < 8; i++)
|
|
{
|
|
if(aSides[i] >= ENTITY_LASER_SHORT && aSides[i] <= ENTITY_LASER_LONG)
|
|
{
|
|
new CDoor(
|
|
&GameServer()->m_World, //GameWorld
|
|
Pos, //Pos
|
|
pi / 4 * i, //Rotation
|
|
32 * 3 + 32 * (aSides[i] - ENTITY_LASER_SHORT) * 3, //Length
|
|
Number //Number
|
|
);
|
|
}
|
|
}
|
|
}
|
|
else if(Index == ENTITY_CRAZY_SHOTGUN_EX)
|
|
{
|
|
int Dir;
|
|
if(!Flags)
|
|
Dir = 0;
|
|
else if(Flags == ROTATION_90)
|
|
Dir = 1;
|
|
else if(Flags == ROTATION_180)
|
|
Dir = 2;
|
|
else
|
|
Dir = 3;
|
|
float Deg = Dir * (pi / 2);
|
|
CProjectile *pBullet = new CProjectile(
|
|
&GameServer()->m_World,
|
|
WEAPON_SHOTGUN, //Type
|
|
-1, //Owner
|
|
Pos, //Pos
|
|
vec2(sin(Deg), cos(Deg)), //Dir
|
|
-2, //Span
|
|
true, //Freeze
|
|
true, //Explosive
|
|
0, //Force
|
|
(g_Config.m_SvShotgunBulletSound) ? SOUND_GRENADE_EXPLODE : -1, //SoundImpact
|
|
Layer,
|
|
Number);
|
|
pBullet->SetBouncing(2 - (Dir % 2));
|
|
}
|
|
else if(Index == ENTITY_CRAZY_SHOTGUN)
|
|
{
|
|
int Dir;
|
|
if(!Flags)
|
|
Dir = 0;
|
|
else if(Flags == (TILEFLAG_ROTATE))
|
|
Dir = 1;
|
|
else if(Flags == (TILEFLAG_VFLIP | TILEFLAG_HFLIP))
|
|
Dir = 2;
|
|
else
|
|
Dir = 3;
|
|
float Deg = Dir * (pi / 2);
|
|
CProjectile *pBullet = new CProjectile(
|
|
&GameServer()->m_World,
|
|
WEAPON_SHOTGUN, //Type
|
|
-1, //Owner
|
|
Pos, //Pos
|
|
vec2(sin(Deg), cos(Deg)), //Dir
|
|
-2, //Span
|
|
true, //Freeze
|
|
false, //Explosive
|
|
0,
|
|
SOUND_GRENADE_EXPLODE,
|
|
Layer,
|
|
Number);
|
|
pBullet->SetBouncing(2 - (Dir % 2));
|
|
}
|
|
|
|
int Type = -1;
|
|
int SubType = 0;
|
|
|
|
if(Index == ENTITY_ARMOR_1)
|
|
Type = POWERUP_ARMOR;
|
|
else if(Index == ENTITY_ARMOR_SHOTGUN)
|
|
Type = POWERUP_ARMOR_SHOTGUN;
|
|
else if(Index == ENTITY_ARMOR_GRENADE)
|
|
Type = POWERUP_ARMOR_GRENADE;
|
|
else if(Index == ENTITY_ARMOR_NINJA)
|
|
Type = POWERUP_ARMOR_NINJA;
|
|
else if(Index == ENTITY_ARMOR_LASER)
|
|
Type = POWERUP_ARMOR_LASER;
|
|
else if(Index == ENTITY_HEALTH_1)
|
|
Type = POWERUP_HEALTH;
|
|
else if(Index == ENTITY_WEAPON_SHOTGUN)
|
|
{
|
|
Type = POWERUP_WEAPON;
|
|
SubType = WEAPON_SHOTGUN;
|
|
}
|
|
else if(Index == ENTITY_WEAPON_GRENADE)
|
|
{
|
|
Type = POWERUP_WEAPON;
|
|
SubType = WEAPON_GRENADE;
|
|
}
|
|
else if(Index == ENTITY_WEAPON_LASER)
|
|
{
|
|
Type = POWERUP_WEAPON;
|
|
SubType = WEAPON_LASER;
|
|
}
|
|
else if(Index == ENTITY_POWERUP_NINJA)
|
|
{
|
|
Type = POWERUP_NINJA;
|
|
SubType = WEAPON_NINJA;
|
|
}
|
|
else if(Index >= ENTITY_LASER_FAST_CCW && Index <= ENTITY_LASER_FAST_CW)
|
|
{
|
|
int aSides2[8];
|
|
aSides2[0] = GameServer()->Collision()->Entity(x, y + 2, Layer);
|
|
aSides2[1] = GameServer()->Collision()->Entity(x + 2, y + 2, Layer);
|
|
aSides2[2] = GameServer()->Collision()->Entity(x + 2, y, Layer);
|
|
aSides2[3] = GameServer()->Collision()->Entity(x + 2, y - 2, Layer);
|
|
aSides2[4] = GameServer()->Collision()->Entity(x, y - 2, Layer);
|
|
aSides2[5] = GameServer()->Collision()->Entity(x - 2, y - 2, Layer);
|
|
aSides2[6] = GameServer()->Collision()->Entity(x - 2, y, Layer);
|
|
aSides2[7] = GameServer()->Collision()->Entity(x - 2, y + 2, Layer);
|
|
|
|
float AngularSpeed = 0.0f;
|
|
int Ind = Index - ENTITY_LASER_STOP;
|
|
int M;
|
|
if(Ind < 0)
|
|
{
|
|
Ind = -Ind;
|
|
M = 1;
|
|
}
|
|
else if(Ind == 0)
|
|
M = 0;
|
|
else
|
|
M = -1;
|
|
|
|
if(Ind == 0)
|
|
AngularSpeed = 0.0f;
|
|
else if(Ind == 1)
|
|
AngularSpeed = pi / 360;
|
|
else if(Ind == 2)
|
|
AngularSpeed = pi / 180;
|
|
else if(Ind == 3)
|
|
AngularSpeed = pi / 90;
|
|
AngularSpeed *= M;
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
{
|
|
if(aSides[i] >= ENTITY_LASER_SHORT && aSides[i] <= ENTITY_LASER_LONG)
|
|
{
|
|
CLight *pLight = new CLight(&GameServer()->m_World, Pos, pi / 4 * i, 32 * 3 + 32 * (aSides[i] - ENTITY_LASER_SHORT) * 3, Layer, Number);
|
|
pLight->m_AngularSpeed = AngularSpeed;
|
|
if(aSides2[i] >= ENTITY_LASER_C_SLOW && aSides2[i] <= ENTITY_LASER_C_FAST)
|
|
{
|
|
pLight->m_Speed = 1 + (aSides2[i] - ENTITY_LASER_C_SLOW) * 2;
|
|
pLight->m_CurveLength = pLight->m_Length;
|
|
}
|
|
else if(aSides2[i] >= ENTITY_LASER_O_SLOW && aSides2[i] <= ENTITY_LASER_O_FAST)
|
|
{
|
|
pLight->m_Speed = 1 + (aSides2[i] - ENTITY_LASER_O_SLOW) * 2;
|
|
pLight->m_CurveLength = 0;
|
|
}
|
|
else
|
|
pLight->m_CurveLength = pLight->m_Length;
|
|
}
|
|
}
|
|
}
|
|
else if(Index >= ENTITY_DRAGGER_WEAK && Index <= ENTITY_DRAGGER_STRONG)
|
|
{
|
|
new CDragger(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK + 1, false, Layer, Number);
|
|
}
|
|
else if(Index >= ENTITY_DRAGGER_WEAK_NW && Index <= ENTITY_DRAGGER_STRONG_NW)
|
|
{
|
|
new CDragger(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK_NW + 1, true, Layer, Number);
|
|
}
|
|
else if(Index == ENTITY_PLASMAE)
|
|
{
|
|
new CGun(&GameServer()->m_World, Pos, false, true, Layer, Number);
|
|
}
|
|
else if(Index == ENTITY_PLASMAF)
|
|
{
|
|
new CGun(&GameServer()->m_World, Pos, true, false, Layer, Number);
|
|
}
|
|
else if(Index == ENTITY_PLASMA)
|
|
{
|
|
new CGun(&GameServer()->m_World, Pos, true, true, Layer, Number);
|
|
}
|
|
else if(Index == ENTITY_PLASMAU)
|
|
{
|
|
new CGun(&GameServer()->m_World, Pos, false, false, Layer, Number);
|
|
}
|
|
|
|
if(Type != -1)
|
|
{
|
|
CPickup *pPickup = new CPickup(&GameServer()->m_World, Type, SubType, Layer, Number);
|
|
pPickup->m_Pos = Pos;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void IGameController::OnPlayerConnect(CPlayer *pPlayer)
|
|
{
|
|
int ClientID = pPlayer->GetCID();
|
|
pPlayer->Respawn();
|
|
|
|
if(!Server()->ClientPrevIngame(ClientID))
|
|
{
|
|
char aBuf[128];
|
|
str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' team=%d", ClientID, Server()->ClientName(ClientID), pPlayer->GetTeam());
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
|
|
}
|
|
}
|
|
|
|
void IGameController::OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason)
|
|
{
|
|
pPlayer->OnDisconnect();
|
|
int ClientID = pPlayer->GetCID();
|
|
if(Server()->ClientIngame(ClientID))
|
|
{
|
|
char aBuf[512];
|
|
if(pReason && *pReason)
|
|
str_format(aBuf, sizeof(aBuf), "'%s' has left the game (%s)", Server()->ClientName(ClientID), pReason);
|
|
else
|
|
str_format(aBuf, sizeof(aBuf), "'%s' has left the game", Server()->ClientName(ClientID));
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, -1, CGameContext::CHAT_SIX);
|
|
|
|
str_format(aBuf, sizeof(aBuf), "leave player='%d:%s'", ClientID, Server()->ClientName(ClientID));
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "game", aBuf);
|
|
}
|
|
}
|
|
|
|
void IGameController::EndRound()
|
|
{
|
|
if(m_Warmup) // game can't end when we are running warmup
|
|
return;
|
|
|
|
GameServer()->m_World.m_Paused = true;
|
|
m_GameOverTick = Server()->Tick();
|
|
m_SuddenDeath = 0;
|
|
}
|
|
|
|
void IGameController::ResetGame()
|
|
{
|
|
GameServer()->m_World.m_ResetRequested = true;
|
|
}
|
|
|
|
const char *IGameController::GetTeamName(int Team)
|
|
{
|
|
if(Team == 0)
|
|
return "game";
|
|
return "spectators";
|
|
}
|
|
|
|
//static bool IsSeparator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; }
|
|
|
|
void IGameController::StartRound()
|
|
{
|
|
ResetGame();
|
|
|
|
m_RoundStartTick = Server()->Tick();
|
|
m_SuddenDeath = 0;
|
|
m_GameOverTick = -1;
|
|
GameServer()->m_World.m_Paused = false;
|
|
m_ForceBalanced = false;
|
|
Server()->DemoRecorder_HandleAutoStart();
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags & GAMEFLAG_TEAMS);
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
|
|
}
|
|
|
|
void IGameController::ChangeMap(const char *pToMap)
|
|
{
|
|
Server()->ChangeMap(pToMap);
|
|
}
|
|
|
|
void IGameController::OnReset()
|
|
{
|
|
for(auto &pPlayer : GameServer()->m_apPlayers)
|
|
if(pPlayer)
|
|
pPlayer->Respawn();
|
|
}
|
|
|
|
int IGameController::OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int Weapon)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void IGameController::OnCharacterSpawn(class CCharacter *pChr)
|
|
{
|
|
// default health
|
|
pChr->IncreaseHealth(10);
|
|
|
|
// give default weapons
|
|
pChr->GiveWeapon(WEAPON_HAMMER);
|
|
pChr->GiveWeapon(WEAPON_GUN);
|
|
}
|
|
|
|
void IGameController::HandleCharacterTiles(CCharacter *pChr, int MapIndex)
|
|
{
|
|
// Do nothing by default
|
|
}
|
|
|
|
void IGameController::DoWarmup(int Seconds)
|
|
{
|
|
if(Seconds < 0)
|
|
m_Warmup = 0;
|
|
else
|
|
m_Warmup = Seconds * Server()->TickSpeed();
|
|
}
|
|
|
|
bool IGameController::IsForceBalanced()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool IGameController::CanBeMovedOnBalance(int ClientID)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void IGameController::Tick()
|
|
{
|
|
// do warmup
|
|
if(m_Warmup)
|
|
{
|
|
m_Warmup--;
|
|
if(!m_Warmup)
|
|
StartRound();
|
|
}
|
|
|
|
if(m_GameOverTick != -1)
|
|
{
|
|
// game over.. wait for restart
|
|
if(Server()->Tick() > m_GameOverTick + Server()->TickSpeed() * 10)
|
|
{
|
|
StartRound();
|
|
m_RoundCount++;
|
|
}
|
|
}
|
|
|
|
DoActivityCheck();
|
|
}
|
|
|
|
void IGameController::Snap(int SnappingClient)
|
|
{
|
|
CNetObj_GameInfo *pGameInfoObj = (CNetObj_GameInfo *)Server()->SnapNewItem(NETOBJTYPE_GAMEINFO, 0, sizeof(CNetObj_GameInfo));
|
|
if(!pGameInfoObj)
|
|
return;
|
|
|
|
pGameInfoObj->m_GameFlags = m_GameFlags;
|
|
pGameInfoObj->m_GameStateFlags = 0;
|
|
if(m_GameOverTick != -1)
|
|
pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_GAMEOVER;
|
|
if(m_SuddenDeath)
|
|
pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_SUDDENDEATH;
|
|
if(GameServer()->m_World.m_Paused)
|
|
pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_PAUSED;
|
|
pGameInfoObj->m_RoundStartTick = m_RoundStartTick;
|
|
pGameInfoObj->m_WarmupTimer = m_Warmup;
|
|
|
|
pGameInfoObj->m_RoundNum = 0;
|
|
pGameInfoObj->m_RoundCurrent = m_RoundCount + 1;
|
|
|
|
CCharacter *pChr;
|
|
CPlayer *pPlayer = SnappingClient != SERVER_DEMO_CLIENT ? GameServer()->m_apPlayers[SnappingClient] : 0;
|
|
CPlayer *pPlayer2;
|
|
|
|
if(pPlayer && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK)
|
|
{
|
|
if((pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorID != SPEC_FREEVIEW && (pPlayer2 = GameServer()->m_apPlayers[pPlayer->m_SpectatorID]))
|
|
{
|
|
if((pChr = pPlayer2->GetCharacter()) && pChr->m_DDRaceState == DDRACE_STARTED)
|
|
{
|
|
pGameInfoObj->m_WarmupTimer = -pChr->m_StartTime;
|
|
pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_RACETIME;
|
|
}
|
|
}
|
|
else if((pChr = pPlayer->GetCharacter()) && pChr->m_DDRaceState == DDRACE_STARTED)
|
|
{
|
|
pGameInfoObj->m_WarmupTimer = -pChr->m_StartTime;
|
|
pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_RACETIME;
|
|
}
|
|
}
|
|
|
|
CNetObj_GameInfoEx *pGameInfoEx = (CNetObj_GameInfoEx *)Server()->SnapNewItem(NETOBJTYPE_GAMEINFOEX, 0, sizeof(CNetObj_GameInfoEx));
|
|
if(!pGameInfoEx)
|
|
return;
|
|
|
|
pGameInfoEx->m_Flags =
|
|
GAMEINFOFLAG_TIMESCORE |
|
|
GAMEINFOFLAG_GAMETYPE_RACE |
|
|
GAMEINFOFLAG_GAMETYPE_DDRACE |
|
|
GAMEINFOFLAG_GAMETYPE_DDNET |
|
|
GAMEINFOFLAG_UNLIMITED_AMMO |
|
|
GAMEINFOFLAG_RACE_RECORD_MESSAGE |
|
|
GAMEINFOFLAG_ALLOW_EYE_WHEEL |
|
|
GAMEINFOFLAG_ALLOW_HOOK_COLL |
|
|
GAMEINFOFLAG_ALLOW_ZOOM |
|
|
GAMEINFOFLAG_BUG_DDRACE_GHOST |
|
|
GAMEINFOFLAG_BUG_DDRACE_INPUT |
|
|
GAMEINFOFLAG_PREDICT_DDRACE |
|
|
GAMEINFOFLAG_PREDICT_DDRACE_TILES |
|
|
GAMEINFOFLAG_ENTITIES_DDNET |
|
|
GAMEINFOFLAG_ENTITIES_DDRACE |
|
|
GAMEINFOFLAG_ENTITIES_RACE |
|
|
GAMEINFOFLAG_RACE;
|
|
pGameInfoEx->m_Flags2 = GAMEINFOFLAG2_HUD_DDRACE;
|
|
if(g_Config.m_SvNoWeakHookAndBounce)
|
|
pGameInfoEx->m_Flags2 |= GAMEINFOFLAG2_NO_WEAK_HOOK_AND_BOUNCE;
|
|
pGameInfoEx->m_Version = GAMEINFO_CURVERSION;
|
|
|
|
if(Server()->IsSixup(SnappingClient))
|
|
{
|
|
protocol7::CNetObj_GameData *pGameData = static_cast<protocol7::CNetObj_GameData *>(Server()->SnapNewItem(-protocol7::NETOBJTYPE_GAMEDATA, 0, sizeof(protocol7::CNetObj_GameData)));
|
|
if(!pGameData)
|
|
return;
|
|
|
|
pGameData->m_GameStartTick = m_RoundStartTick;
|
|
pGameData->m_GameStateFlags = 0;
|
|
if(m_GameOverTick != -1)
|
|
pGameData->m_GameStateFlags |= protocol7::GAMESTATEFLAG_GAMEOVER;
|
|
if(m_SuddenDeath)
|
|
pGameData->m_GameStateFlags |= protocol7::GAMESTATEFLAG_SUDDENDEATH;
|
|
if(GameServer()->m_World.m_Paused)
|
|
pGameData->m_GameStateFlags |= protocol7::GAMESTATEFLAG_PAUSED;
|
|
|
|
pGameData->m_GameStateEndTick = 0;
|
|
|
|
protocol7::CNetObj_GameDataRace *pRaceData = static_cast<protocol7::CNetObj_GameDataRace *>(Server()->SnapNewItem(-protocol7::NETOBJTYPE_GAMEDATARACE, 0, sizeof(protocol7::CNetObj_GameDataRace)));
|
|
if(!pRaceData)
|
|
return;
|
|
|
|
pRaceData->m_BestTime = round_to_int(m_CurrentRecord * 1000);
|
|
pRaceData->m_Precision = 0;
|
|
pRaceData->m_RaceFlags = protocol7::RACEFLAG_HIDE_KILLMSG | protocol7::RACEFLAG_KEEP_WANTED_WEAPON;
|
|
}
|
|
|
|
if(!GameServer()->Switchers().empty())
|
|
{
|
|
int Team = pPlayer && pPlayer->GetCharacter() ? pPlayer->GetCharacter()->Team() : 0;
|
|
|
|
if(pPlayer && (pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorID != SPEC_FREEVIEW && GameServer()->m_apPlayers[pPlayer->m_SpectatorID] && GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter())
|
|
Team = GameServer()->m_apPlayers[pPlayer->m_SpectatorID]->GetCharacter()->Team();
|
|
|
|
if(Team == TEAM_SUPER)
|
|
return;
|
|
|
|
CNetObj_SwitchState *pSwitchState = static_cast<CNetObj_SwitchState *>(Server()->SnapNewItem(NETOBJTYPE_SWITCHSTATE, Team, sizeof(CNetObj_SwitchState)));
|
|
if(!pSwitchState)
|
|
return;
|
|
|
|
pSwitchState->m_HighestSwitchNumber = clamp((int)GameServer()->Switchers().size() - 1, 0, 255);
|
|
mem_zero(pSwitchState->m_aStatus, sizeof(pSwitchState->m_aStatus));
|
|
|
|
std::vector<std::pair<int, int>> vEndTicks; // <EndTick, SwitchNumber>
|
|
|
|
for(int i = 0; i <= pSwitchState->m_HighestSwitchNumber; i++)
|
|
{
|
|
int Status = (int)GameServer()->Switchers()[i].m_aStatus[Team];
|
|
pSwitchState->m_aStatus[i / 32] |= (Status << (i % 32));
|
|
|
|
int EndTick = GameServer()->Switchers()[i].m_aEndTick[Team];
|
|
if(EndTick > 0 && EndTick < Server()->Tick() + 3 * Server()->TickSpeed() && GameServer()->Switchers()[i].m_aLastUpdateTick[Team] < Server()->Tick())
|
|
{
|
|
// only keep track of EndTicks that have less than three second left and are not currently being updated by a player being present on a switch tile, to limit how often these are sent
|
|
vEndTicks.emplace_back(std::pair<int, int>(GameServer()->Switchers()[i].m_aEndTick[Team], i));
|
|
}
|
|
}
|
|
|
|
// send the endtick of switchers that are about to toggle back (up to four, prioritizing those with the earliest endticks)
|
|
mem_zero(pSwitchState->m_aSwitchNumbers, sizeof(pSwitchState->m_aSwitchNumbers));
|
|
mem_zero(pSwitchState->m_aEndTicks, sizeof(pSwitchState->m_aEndTicks));
|
|
|
|
std::sort(vEndTicks.begin(), vEndTicks.end());
|
|
const int NumTimedSwitchers = minimum((int)vEndTicks.size(), (int)std::size(pSwitchState->m_aEndTicks));
|
|
|
|
for(int i = 0; i < NumTimedSwitchers; i++)
|
|
{
|
|
pSwitchState->m_aSwitchNumbers[i] = vEndTicks[i].second;
|
|
pSwitchState->m_aEndTicks[i] = vEndTicks[i].first;
|
|
}
|
|
}
|
|
}
|
|
|
|
int IGameController::GetAutoTeam(int NotThisID)
|
|
{
|
|
// this will force the auto balancer to work overtime as well
|
|
#ifdef CONF_DEBUG
|
|
if(g_Config.m_DbgStress)
|
|
return 0;
|
|
#endif
|
|
|
|
int aNumplayers[2] = {0, 0};
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if(GameServer()->m_apPlayers[i] && i != NotThisID)
|
|
{
|
|
if(GameServer()->m_apPlayers[i]->GetTeam() >= TEAM_RED && GameServer()->m_apPlayers[i]->GetTeam() <= TEAM_BLUE)
|
|
aNumplayers[GameServer()->m_apPlayers[i]->GetTeam()]++;
|
|
}
|
|
}
|
|
|
|
int Team = 0;
|
|
|
|
if(CanJoinTeam(Team, NotThisID))
|
|
return Team;
|
|
return -1;
|
|
}
|
|
|
|
bool IGameController::CanJoinTeam(int Team, int NotThisID)
|
|
{
|
|
if(Team == TEAM_SPECTATORS || (GameServer()->m_apPlayers[NotThisID] && GameServer()->m_apPlayers[NotThisID]->GetTeam() != TEAM_SPECTATORS))
|
|
return true;
|
|
|
|
int aNumplayers[2] = {0, 0};
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if(GameServer()->m_apPlayers[i] && i != NotThisID)
|
|
{
|
|
if(GameServer()->m_apPlayers[i]->GetTeam() >= TEAM_RED && GameServer()->m_apPlayers[i]->GetTeam() <= TEAM_BLUE)
|
|
aNumplayers[GameServer()->m_apPlayers[i]->GetTeam()]++;
|
|
}
|
|
}
|
|
|
|
return (aNumplayers[0] + aNumplayers[1]) < Server()->MaxClients() - g_Config.m_SvSpectatorSlots;
|
|
}
|
|
|
|
int IGameController::ClampTeam(int Team)
|
|
{
|
|
if(Team < 0)
|
|
return TEAM_SPECTATORS;
|
|
return 0;
|
|
}
|
|
|
|
int64_t IGameController::GetMaskForPlayerWorldEvent(int Asker, int ExceptID)
|
|
{
|
|
// Send all world events to everyone by default
|
|
return CmaskAllExceptOne(ExceptID);
|
|
}
|
|
|
|
void IGameController::DoTeamChange(CPlayer *pPlayer, int Team, bool DoChatMsg)
|
|
{
|
|
Team = ClampTeam(Team);
|
|
if(Team == pPlayer->GetTeam())
|
|
return;
|
|
|
|
pPlayer->SetTeam(Team);
|
|
int ClientID = pPlayer->GetCID();
|
|
|
|
char aBuf[128];
|
|
DoChatMsg = false;
|
|
if(DoChatMsg)
|
|
{
|
|
str_format(aBuf, sizeof(aBuf), "'%s' joined the %s", Server()->ClientName(ClientID), GameServer()->m_pController->GetTeamName(Team));
|
|
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf);
|
|
}
|
|
|
|
str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' m_Team=%d", ClientID, Server()->ClientName(ClientID), Team);
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
|
|
|
|
// OnPlayerInfoChange(pPlayer);
|
|
}
|