ddnet/src/game/client/gameclient.cpp

3337 lines
119 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (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. */
2022-05-18 16:00:05 +00:00
#include <chrono>
#include <limits>
#include <engine/client/checksum.h>
#include <engine/demo.h>
#include <engine/editor.h>
2011-02-27 16:56:03 +00:00
#include <engine/engine.h>
#include <engine/favorites.h>
2011-03-23 12:06:35 +00:00
#include <engine/friends.h>
2010-05-29 07:25:38 +00:00
#include <engine/graphics.h>
#include <engine/map.h>
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
#include <engine/sound.h>
#include <engine/storage.h>
#include <engine/textrender.h>
#include <engine/updater.h>
2010-05-29 07:25:38 +00:00
#include <game/generated/client_data.h>
#include <game/generated/client_data7.h>
#include <game/generated/protocol.h>
2010-05-29 07:25:38 +00:00
2013-08-23 23:50:35 +00:00
#include <base/math.h>
2022-05-29 16:33:38 +00:00
#include <base/system.h>
2013-08-23 23:50:35 +00:00
#include <base/vmath.h>
#include "gameclient.h"
#include "race.h"
#include "render.h"
2010-05-29 07:25:38 +00:00
#include <game/localization.h>
#include <game/mapitems.h>
2010-05-29 07:25:38 +00:00
#include <game/version.h>
2015-08-25 00:11:04 +00:00
#include "components/background.h"
2010-05-29 07:25:38 +00:00
#include "components/binds.h"
#include "components/broadcast.h"
#include "components/camera.h"
#include "components/chat.h"
#include "components/console.h"
#include "components/controls.h"
#include "components/countryflags.h"
2010-05-29 07:25:38 +00:00
#include "components/damageind.h"
#include "components/debughud.h"
#include "components/effects.h"
#include "components/emoticon.h"
2022-03-25 11:54:11 +00:00
#include "components/freezebars.h"
2022-05-29 16:33:38 +00:00
#include "components/ghost.h"
2010-05-29 07:25:38 +00:00
#include "components/hud.h"
#include "components/items.h"
#include "components/killmessages.h"
#include "components/mapimages.h"
#include "components/maplayers.h"
2014-10-10 17:10:57 +00:00
#include "components/mapsounds.h"
2020-09-18 16:45:42 +00:00
#include "components/menu_background.h"
2010-05-29 07:25:38 +00:00
#include "components/menus.h"
#include "components/motd.h"
2020-09-18 16:45:42 +00:00
#include "components/nameplates.h"
2010-05-29 07:25:38 +00:00
#include "components/particles.h"
#include "components/players.h"
2022-05-29 16:33:38 +00:00
#include "components/race_demo.h"
2010-05-29 07:25:38 +00:00
#include "components/scoreboard.h"
#include "components/skins.h"
#include "components/sounds.h"
#include "components/spectator.h"
2015-05-21 09:41:59 +00:00
#include "components/statboard.h"
2010-05-29 07:25:38 +00:00
#include "components/voting.h"
2022-05-29 16:33:38 +00:00
#include "prediction/entities/character.h"
#include "prediction/entities/projectile.h"
2011-08-31 11:56:04 +00:00
2022-05-18 16:00:05 +00:00
using namespace std::chrono_literals;
const char *CGameClient::Version() const { return GAME_VERSION; }
const char *CGameClient::NetVersion() const { return GAME_NETVERSION; }
int CGameClient::DDNetVersion() const { return CLIENT_VERSIONNR; }
const char *CGameClient::DDNetVersionStr() const { return m_aDDNetVersionStr; }
const char *CGameClient::GetItemName(int Type) const { return m_NetObjHandler.GetObjName(Type); }
2009-06-15 06:45:44 +00:00
2010-05-29 07:25:38 +00:00
void CGameClient::OnConsoleInit()
{
2011-02-27 16:56:03 +00:00
m_pEngine = Kernel()->RequestInterface<IEngine>();
2010-05-29 07:25:38 +00:00
m_pClient = Kernel()->RequestInterface<IClient>();
m_pTextRender = Kernel()->RequestInterface<ITextRender>();
m_pSound = Kernel()->RequestInterface<ISound>();
m_pConfig = Kernel()->RequestInterface<IConfigManager>()->Values();
2010-05-29 07:25:38 +00:00
m_pInput = Kernel()->RequestInterface<IInput>();
m_pConsole = Kernel()->RequestInterface<IConsole>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_pDemoPlayer = Kernel()->RequestInterface<IDemoPlayer>();
m_pServerBrowser = Kernel()->RequestInterface<IServerBrowser>();
m_pEditor = Kernel()->RequestInterface<IEditor>();
m_pFavorites = Kernel()->RequestInterface<IFavorites>();
2011-03-23 12:06:35 +00:00
m_pFriends = Kernel()->RequestInterface<IFriends>();
2015-07-22 20:16:49 +00:00
m_pFoes = Client()->Foes();
#if defined(CONF_AUTOUPDATE)
m_pUpdater = Kernel()->RequestInterface<IUpdater>();
2015-03-13 14:13:19 +00:00
#endif
2021-07-12 09:43:56 +00:00
m_Menus.SetMenuBackground(&m_MenuBackground);
m_NamePlates.SetPlayers(&m_Players);
2015-05-19 22:51:02 +00:00
// make a list of all the systems, make sure to add them in the correct render order
m_vpAll.insert(m_vpAll.end(), {&m_Skins,
&m_CountryFlags,
&m_MapImages,
&m_Effects, // doesn't render anything, just updates effects
&m_Binds,
&m_Binds.m_SpecialBinds,
&m_Controls,
&m_Camera,
&m_Sounds,
&m_Voting,
&m_Particles, // doesn't render anything, just updates all the particles
&m_RaceDemo,
&m_MapSounds,
&m_BackGround, // render instead of m_MapLayersBackGround when g_Config.m_ClOverlayEntities == 100
&m_MapLayersBackGround, // first to render
&m_Particles.m_RenderTrail,
&m_Items,
&m_Players,
&m_Ghost,
&m_MapLayersForeGround,
&m_Particles.m_RenderExplosions,
&m_NamePlates,
2022-06-14 17:28:51 +00:00
&m_Particles.m_RenderExtra,
&m_Particles.m_RenderGeneral,
&m_FreezeBars,
&m_DamageInd,
&m_Hud,
&m_Spectator,
&m_Emoticon,
&m_KillMessages,
&m_Chat,
&m_Broadcast,
&m_DebugHud,
&m_Scoreboard,
&m_Statboard,
&m_Motd,
&m_Menus,
&m_Tooltips,
&CMenus::m_Binder,
&m_GameConsole,
&m_MenuBackground});
2020-09-18 16:45:42 +00:00
// build the input stack
m_vpInput.insert(m_vpInput.end(), {&CMenus::m_Binder, // this will take over all input when we want to bind a key
&m_Binds.m_SpecialBinds,
&m_GameConsole,
&m_Chat, // chat has higher prio due to tha you can quit it by pressing esc
&m_Motd, // for pressing esc to remove it
&m_Menus,
&m_Spectator,
&m_Emoticon,
&m_Controls,
&m_Binds});
2008-09-04 21:36:44 +00:00
// add the some console commands
Console()->Register("team", "i[team-id]", CFGFLAG_CLIENT, ConTeam, this, "Switch team");
2020-09-01 13:35:17 +00:00
Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself to restart");
// register server dummy commands for tab completion
Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value");
Console()->Register("tune_reset", "", CFGFLAG_SERVER, 0, 0, "Reset tuning");
Console()->Register("tune_dump", "", CFGFLAG_SERVER, 0, 0, "Dump tuning");
Console()->Register("change_map", "?r[map]", CFGFLAG_SERVER, 0, 0, "Change map");
Console()->Register("restart", "?i[seconds]", CFGFLAG_SERVER, 0, 0, "Restart in x seconds");
Console()->Register("broadcast", "r[message]", CFGFLAG_SERVER, 0, 0, "Broadcast message");
Console()->Register("say", "r[message]", CFGFLAG_SERVER, 0, 0, "Say in chat");
Console()->Register("set_team", "i[id] i[team-id] ?i[delay in minutes]", CFGFLAG_SERVER, 0, 0, "Set team of player to team");
Console()->Register("set_team_all", "i[team-id]", CFGFLAG_SERVER, 0, 0, "Set team of all players to team");
Console()->Register("add_vote", "s[name] r[command]", CFGFLAG_SERVER, 0, 0, "Add a voting option");
Console()->Register("remove_vote", "s[name]", CFGFLAG_SERVER, 0, 0, "remove a voting option");
Console()->Register("force_vote", "s[name] s[command] ?r[reason]", CFGFLAG_SERVER, 0, 0, "Force a voting option");
Console()->Register("clear_votes", "", CFGFLAG_SERVER, 0, 0, "Clears the voting options");
Console()->Register("add_map_votes", "", CFGFLAG_SERVER, 0, 0, "Automatically adds voting options for all maps");
Console()->Register("vote", "r['yes'|'no']", CFGFLAG_SERVER, 0, 0, "Force a vote to yes/no");
2011-09-04 09:13:30 +00:00
Console()->Register("swap_teams", "", CFGFLAG_SERVER, 0, 0, "Swap the current teams");
Console()->Register("shuffle_teams", "", CFGFLAG_SERVER, 0, 0, "Shuffle the current teams");
2010-05-29 07:25:38 +00:00
2019-09-08 22:53:07 +00:00
// register tune zone command to allow the client prediction to load tunezones from the map
Console()->Register("tune_zone", "i[zone] s[tuning] i[value]", CFGFLAG_CLIENT | CFGFLAG_GAME, ConTuneZone, this, "Tune in zone a variable to value");
for(auto &pComponent : m_vpAll)
pComponent->m_pClient = this;
2008-09-23 07:43:41 +00:00
// let all the other components register their console commands
for(auto &pComponent : m_vpAll)
pComponent->OnConsoleInit();
//
2010-05-29 07:25:38 +00:00
Console()->Chain("player_name", ConchainSpecialInfoupdate, this);
Console()->Chain("player_clan", ConchainSpecialInfoupdate, this);
Console()->Chain("player_country", ConchainSpecialInfoupdate, this);
2010-05-29 07:25:38 +00:00
Console()->Chain("player_use_custom_color", ConchainSpecialInfoupdate, this);
Console()->Chain("player_color_body", ConchainSpecialInfoupdate, this);
Console()->Chain("player_color_feet", ConchainSpecialInfoupdate, this);
Console()->Chain("player_skin", ConchainSpecialInfoupdate, this);
2014-04-28 13:19:57 +00:00
Console()->Chain("dummy_name", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("dummy_clan", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("dummy_country", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("dummy_use_custom_color", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("dummy_color_body", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("dummy_color_feet", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("dummy_skin", ConchainSpecialDummyInfoupdate, this);
Console()->Chain("cl_dummy", ConchainSpecialDummy, this);
Console()->Chain("cl_text_entities_size", ConchainClTextEntitiesSize, this);
2020-09-18 16:45:42 +00:00
Console()->Chain("cl_menu_map", ConchainMenuMap, this);
//
2010-05-29 07:25:38 +00:00
m_SuppressEvents = false;
2008-09-04 21:36:44 +00:00
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnInit()
2008-09-04 21:36:44 +00:00
{
Client()->SetMapLoadingCBFunc([this]() {
m_Menus.RenderLoading(DemoPlayer()->IsPlaying() ? Localize("Preparing demo playback") : Localize("Connected"), Localize("Loading map file from storage"), 0, false);
});
m_pGraphics = Kernel()->RequestInterface<IGraphics>();
m_pGraphics->AddWindowResizeListener(OnWindowResizeCB, this);
// propagate pointers
m_UI.Init(Input(), Graphics(), TextRender());
m_RenderTools.Init(Graphics(), TextRender());
2021-06-23 05:05:49 +00:00
int64_t Start = time_get();
2011-02-13 12:58:59 +00:00
if(GIT_SHORTREV_HASH)
{
str_format(m_aDDNetVersionStr, sizeof(m_aDDNetVersionStr), "%s %s (%s)", GAME_NAME, GAME_RELEASE_VERSION, GIT_SHORTREV_HASH);
}
else
{
str_format(m_aDDNetVersionStr, sizeof(m_aDDNetVersionStr), "%s %s", GAME_NAME, GAME_RELEASE_VERSION);
}
2010-05-29 07:25:38 +00:00
// set the language
2010-10-06 21:07:35 +00:00
g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console());
2011-02-27 16:56:03 +00:00
// TODO: this should be different
2008-08-30 22:38:56 +00:00
// setup item sizes
for(int i = 0; i < NUM_NETOBJTYPES; i++)
2010-05-29 07:25:38 +00:00
Client()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i));
2011-02-27 16:56:03 +00:00
Client()->LoadFont();
// update and swap after font loading, they are quite huge
Client()->UpdateAndSwap();
const char *pLoadingDDNetCaption = Localize("Loading DDNet Client");
2011-02-27 16:56:03 +00:00
// init all components
int SkippedComps = 0;
int CompCounter = 0;
for(int i = m_vpAll.size() - 1; i >= 0; --i)
{
m_vpAll[i]->OnInit();
// try to render a frame after each component, also flushes GPU uploads
if(m_Menus.IsInit())
{
char aBuff[256];
str_format(aBuff, std::size(aBuff), "%s [%d/%d]", Localize("Initializing components"), (CompCounter + 1), (int)ComponentCount());
m_Menus.RenderLoading(pLoadingDDNetCaption, aBuff, 1 + SkippedComps);
SkippedComps = 0;
}
else
{
++SkippedComps;
}
++CompCounter;
}
2011-02-27 16:56:03 +00:00
2014-04-27 22:41:19 +00:00
char aBuf[256];
m_GameSkinLoaded = false;
m_ParticlesSkinLoaded = false;
m_EmoticonsSkinLoaded = false;
m_HudSkinLoaded = false;
// setup load amount, load textures
2010-05-29 07:25:38 +00:00
for(int i = 0; i < g_pData->m_NumImages; i++)
2008-08-30 22:38:56 +00:00
{
2020-09-26 07:37:35 +00:00
if(i == IMAGE_GAME)
LoadGameSkin(g_Config.m_ClAssetGame);
else if(i == IMAGE_EMOTICONS)
LoadEmoticonsSkin(g_Config.m_ClAssetEmoticons);
else if(i == IMAGE_PARTICLES)
LoadParticlesSkin(g_Config.m_ClAssetParticles);
else if(i == IMAGE_HUD)
LoadHudSkin(g_Config.m_ClAssetHud);
2022-06-14 17:28:51 +00:00
else if(i == IMAGE_EXTRAS)
LoadExtrasSkin(g_Config.m_ClAssetExtras);
2020-09-26 07:37:35 +00:00
else
g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
m_Menus.RenderLoading(pLoadingDDNetCaption, Localize("Initializing assets"), 1);
2010-05-29 07:25:38 +00:00
}
for(auto &pComponent : m_vpAll)
pComponent->OnReset();
2010-05-29 07:25:38 +00:00
m_ServerMode = SERVERMODE_PURE;
2011-01-06 03:46:10 +00:00
m_aDDRaceMsgSent[0] = false;
m_aDDRaceMsgSent[1] = false;
m_aShowOthers[0] = SHOW_OTHERS_NOT_SET;
m_aShowOthers[1] = SHOW_OTHERS_NOT_SET;
m_aSwitchStateTeam[0] = -1;
m_aSwitchStateTeam[1] = -1;
2011-04-17 17:14:49 +00:00
2021-01-21 16:07:07 +00:00
m_LastZoom = .0;
m_LastScreenAspect = .0;
m_LastDummyConnected = false;
2011-04-17 17:14:49 +00:00
// Set free binds to DDRace binds if it's active
2021-07-12 09:29:59 +00:00
m_Binds.SetDDRaceBinds(true);
2014-08-10 10:54:01 +00:00
if(g_Config.m_ClTimeoutCode[0] == '\0' || str_comp(g_Config.m_ClTimeoutCode, "hGuEYnfxicsXGwFq") == 0)
{
for(unsigned int i = 0; i < 16; i++)
{
if(rand() % 2)
g_Config.m_ClTimeoutCode[i] = (char)((rand() % 26) + 97);
else
2018-10-02 18:52:21 +00:00
g_Config.m_ClTimeoutCode[i] = (char)((rand() % 26) + 65);
}
}
if(g_Config.m_ClDummyTimeoutCode[0] == '\0' || str_comp(g_Config.m_ClDummyTimeoutCode, "hGuEYnfxicsXGwFq") == 0)
{
for(unsigned int i = 0; i < 16; i++)
{
if(rand() % 2)
2018-10-02 18:52:21 +00:00
g_Config.m_ClDummyTimeoutCode[i] = (char)((rand() % 26) + 97);
else
2018-10-02 18:52:21 +00:00
g_Config.m_ClDummyTimeoutCode[i] = (char)((rand() % 26) + 65);
}
}
2021-06-23 05:05:49 +00:00
int64_t End = time_get();
str_format(aBuf, sizeof(aBuf), "initialisation finished after %.2fms", ((End - Start) * 1000) / (float)time_freq());
Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "gameclient", aBuf);
m_GameWorld.m_GameTickSpeed = SERVER_TICK_SPEED;
m_GameWorld.m_pCollision = Collision();
2019-09-08 22:53:07 +00:00
m_GameWorld.m_pTuningList = m_aTuningList;
2021-07-12 09:43:56 +00:00
m_MapImages.SetTextureScale(g_Config.m_ClTextEntitiesSize);
// Agressively try to grab window again since some Windows users report
// window not being focussed after starting client.
Graphics()->SetWindowGrab(true);
CChecksumData *pChecksum = Client()->ChecksumData();
pChecksum->m_SizeofGameClient = sizeof(*this);
pChecksum->m_NumComponents = m_vpAll.size();
for(size_t i = 0; i < m_vpAll.size(); i++)
{
if(i >= std::size(pChecksum->m_aComponentsChecksum))
{
break;
}
int Size = m_vpAll[i]->Sizeof();
pChecksum->m_aComponentsChecksum[i] = Size;
}
}
void CGameClient::OnUpdate()
{
// handle mouse movement
float x = 0.0f, y = 0.0f;
IInput::ECursorType CursorType = Input()->CursorRelative(&x, &y);
if(CursorType != IInput::CURSOR_NONE)
{
for(auto &pComponent : m_vpInput)
2008-09-12 07:20:26 +00:00
{
if(pComponent->OnCursorMove(x, y, CursorType))
2008-09-12 07:20:26 +00:00
break;
}
}
// handle key presses
2010-05-29 07:25:38 +00:00
for(int i = 0; i < Input()->NumEvents(); i++)
{
2010-05-29 07:25:38 +00:00
IInput::CEvent e = Input()->GetEvent(i);
if(!Input()->IsEventValid(&e))
continue;
for(auto &pComponent : m_vpInput)
{
if(pComponent->OnInput(e))
break;
}
}
}
void CGameClient::OnDummySwap()
{
if(g_Config.m_ClDummyResetOnSwitch)
{
int PlayerOrDummy = (g_Config.m_ClDummyResetOnSwitch == 2) ? g_Config.m_ClDummy : (!g_Config.m_ClDummy);
2021-07-12 09:43:56 +00:00
m_Controls.ResetInput(PlayerOrDummy);
m_Controls.m_aInputData[PlayerOrDummy].m_Hook = 0;
}
int tmp = m_DummyInput.m_Fire;
m_DummyInput = m_Controls.m_aInputData[!g_Config.m_ClDummy];
m_Controls.m_aInputData[g_Config.m_ClDummy].m_Fire = tmp;
m_IsDummySwapping = 1;
}
int CGameClient::OnSnapInput(int *pData, bool Dummy, bool Force)
{
if(!Dummy)
{
2021-07-12 09:43:56 +00:00
return m_Controls.SnapInput(pData);
}
if(!g_Config.m_ClDummyHammer)
{
if(m_DummyFire != 0)
{
m_DummyInput.m_Fire = (m_HammerInput.m_Fire + 1) & ~1;
m_DummyFire = 0;
}
if(!Force && (!m_DummyInput.m_Direction && !m_DummyInput.m_Jump && !m_DummyInput.m_Hook))
{
return 0;
}
mem_copy(pData, &m_DummyInput, sizeof(m_DummyInput));
return sizeof(m_DummyInput);
}
else
{
if(m_DummyFire % 25 != 0)
{
m_DummyFire++;
return 0;
}
m_DummyFire++;
m_HammerInput.m_Fire = (m_HammerInput.m_Fire + 1) | 1;
m_HammerInput.m_WantedWeapon = WEAPON_HAMMER + 1;
if(!g_Config.m_ClDummyRestoreWeapon)
{
m_DummyInput.m_WantedWeapon = WEAPON_HAMMER + 1;
}
vec2 MainPos = m_LocalCharacterPos;
vec2 DummyPos = m_aClients[m_aLocalIDs[!g_Config.m_ClDummy]].m_Predicted.m_Pos;
vec2 Dir = MainPos - DummyPos;
2018-10-02 18:52:21 +00:00
m_HammerInput.m_TargetX = (int)(Dir.x);
m_HammerInput.m_TargetY = (int)(Dir.y);
mem_copy(pData, &m_HammerInput, sizeof(m_HammerInput));
return sizeof(m_HammerInput);
}
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnConnected()
{
const char *pConnectCaption = DemoPlayer()->IsPlaying() ? Localize("Preparing demo playback") : Localize("Connected");
const char *pLoadMapContent = Localize("Initializing map logic");
// render loading before skip is calculated
m_Menus.RenderLoading(pConnectCaption, pLoadMapContent, 0, false);
2010-05-29 07:25:38 +00:00
m_Layers.Init(Kernel());
m_Collision.Init(Layers());
m_GameWorld.m_Core.InitSwitchers(m_Collision.m_HighestSwitchNumber);
CRaceHelper::ms_aFlagIndex[0] = -1;
CRaceHelper::ms_aFlagIndex[1] = -1;
CTile *pGameTiles = static_cast<CTile *>(Layers()->Map()->GetData(Layers()->GameLayer()->m_Data));
// get flag positions
for(int i = 0; i < m_Collision.GetWidth() * m_Collision.GetHeight(); i++)
{
if(pGameTiles[i].m_Index - ENTITY_OFFSET == ENTITY_FLAGSTAND_RED)
CRaceHelper::ms_aFlagIndex[TEAM_RED] = i;
else if(pGameTiles[i].m_Index - ENTITY_OFFSET == ENTITY_FLAGSTAND_BLUE)
CRaceHelper::ms_aFlagIndex[TEAM_BLUE] = i;
i += pGameTiles[i].m_Skip;
}
// render loading before going through all components
m_Menus.RenderLoading(pConnectCaption, pLoadMapContent, 0, false);
for(auto &pComponent : m_vpAll)
{
pComponent->OnMapLoad();
pComponent->OnReset();
}
Client()->SetLoadingStateDetail(IClient::LOADING_STATE_DETAIL_GETTING_READY);
m_Menus.RenderLoading(pConnectCaption, Localize("Sending initial client info"), 0, false);
2010-05-29 07:25:38 +00:00
m_ServerMode = SERVERMODE_PURE;
2018-02-04 15:00:47 +00:00
// send the initial info
2010-05-29 07:25:38 +00:00
SendInfo(true);
2014-02-13 18:53:30 +00:00
// we should keep this in for now, because otherwise you can't spectate
// people at start as the other info 64 packet is only sent after the first
// snap
2014-06-22 13:30:15 +00:00
Client()->Rcon("crashmeplx");
m_GameWorld.Clear();
m_GameWorld.m_WorldConfig.m_InfiniteAmmo = true;
mem_zero(&m_GameInfo, sizeof(m_GameInfo));
m_PredictedDummyID = -1;
2019-09-08 22:53:07 +00:00
LoadMapSettings();
if(Client()->State() != IClient::STATE_DEMOPLAYBACK && g_Config.m_ClAutoDemoOnConnect)
Client()->DemoRecorder_HandleAutoStart();
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnReset()
{
m_aLastNewPredictedTick[0] = -1;
m_aLastNewPredictedTick[1] = -1;
m_aLocalTuneZone[0] = 0;
m_aLocalTuneZone[1] = 0;
2021-05-12 16:57:50 +00:00
m_aExpectingTuningForZone[0] = -1;
m_aExpectingTuningForZone[1] = -1;
2021-05-12 16:57:50 +00:00
m_aReceivedTuning[0] = false;
m_aReceivedTuning[1] = false;
2021-05-12 16:57:50 +00:00
InvalidateSnapshot();
2020-10-26 14:14:07 +00:00
for(auto &Client : m_aClients)
Client.Reset();
for(auto &pComponent : m_vpAll)
pComponent->OnReset();
2011-01-06 03:46:10 +00:00
m_DemoSpecID = SPEC_FOLLOW;
m_aFlagDropTick[TEAM_RED] = 0;
m_aFlagDropTick[TEAM_BLUE] = 0;
m_LastRoundStartTick = -1;
m_LastFlagCarrierRed = -4;
m_LastFlagCarrierBlue = -4;
m_aTuning[g_Config.m_ClDummy] = CTuningParams();
m_Teams.Reset();
m_aDDRaceMsgSent[0] = false;
m_aDDRaceMsgSent[1] = false;
m_aShowOthers[0] = SHOW_OTHERS_NOT_SET;
m_aShowOthers[1] = SHOW_OTHERS_NOT_SET;
2021-01-21 16:07:07 +00:00
m_LastZoom = .0;
m_LastScreenAspect = .0;
m_LastDummyConnected = false;
m_ReceivedDDNetPlayer = false;
}
void CGameClient::UpdatePositions()
{
// local character position
2010-05-29 07:25:38 +00:00
if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK)
{
if(!AntiPingPlayers())
{
if(!m_Snap.m_pLocalCharacter || (m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER))
2013-10-09 14:02:23 +00:00
{
// don't use predicted
}
else
m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick(g_Config.m_ClDummy));
}
else
2013-10-09 14:02:23 +00:00
{
if(!(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER))
2013-10-09 14:02:23 +00:00
{
if(m_Snap.m_pLocalCharacter)
m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick(g_Config.m_ClDummy));
2013-10-09 14:02:23 +00:00
}
// else
// m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick(g_Config.m_ClDummy));
2013-10-09 14:02:23 +00:00
}
}
2010-05-29 07:25:38 +00:00
else if(m_Snap.m_pLocalCharacter && m_Snap.m_pLocalPrevCharacter)
{
2010-05-29 07:25:38 +00:00
m_LocalCharacterPos = mix(
vec2(m_Snap.m_pLocalPrevCharacter->m_X, m_Snap.m_pLocalPrevCharacter->m_Y),
vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y), Client()->IntraGameTick(g_Config.m_ClDummy));
}
// spectator position
if(m_Snap.m_SpecInfo.m_Active)
{
if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID != SPEC_FOLLOW && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
{
m_Snap.m_SpecInfo.m_Position = mix(
vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_Y),
vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Cur.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Cur.m_Y),
Client()->IntraGameTick(g_Config.m_ClDummy));
m_Snap.m_SpecInfo.m_UsePosition = true;
}
else if(m_Snap.m_pSpectatorInfo && ((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID == SPEC_FOLLOW) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)))
{
if(m_Snap.m_pPrevSpectatorInfo && m_Snap.m_pPrevSpectatorInfo->m_SpectatorID == m_Snap.m_pSpectatorInfo->m_SpectatorID)
m_Snap.m_SpecInfo.m_Position = mix(vec2(m_Snap.m_pPrevSpectatorInfo->m_X, m_Snap.m_pPrevSpectatorInfo->m_Y),
vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y), Client()->IntraGameTick(g_Config.m_ClDummy));
else
m_Snap.m_SpecInfo.m_Position = vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y);
m_Snap.m_SpecInfo.m_UsePosition = true;
}
}
UpdateRenderedCharacters();
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnRender()
{
// update the local character and spectate position
UpdatePositions();
2021-02-10 22:02:34 +00:00
// display gfx & client warnings
for(SWarning *pWarning : {Graphics()->GetCurWarning(), Client()->GetCurWarning()})
{
2021-07-12 09:43:56 +00:00
if(pWarning != NULL && m_Menus.CanDisplayWarning())
{
2022-05-18 16:00:05 +00:00
m_Menus.PopupWarning(Localize("Warning"), pWarning->m_aWarningMsg, "Ok", 10s);
pWarning->m_WasShown = true;
}
}
// render all systems
for(auto &pComponent : m_vpAll)
pComponent->OnRender();
// clear all events/input for this frame
Input()->Clear();
// clear new tick flags
2010-05-29 07:25:38 +00:00
m_NewTick = false;
m_NewPredictedTick = false;
if(g_Config.m_ClDummy && !Client()->DummyConnected())
g_Config.m_ClDummy = 0;
// resend player and dummy info if it was filtered by server
2021-07-12 09:43:56 +00:00
if(Client()->State() == IClient::STATE_ONLINE && !m_Menus.IsActive())
{
if(m_aCheckInfo[0] == 0)
{
if(
str_comp(m_aClients[m_aLocalIDs[0]].m_aName, Client()->PlayerName()) ||
str_comp(m_aClients[m_aLocalIDs[0]].m_aClan, g_Config.m_PlayerClan) ||
m_aClients[m_aLocalIDs[0]].m_Country != g_Config.m_PlayerCountry ||
str_comp(m_aClients[m_aLocalIDs[0]].m_aSkinName, g_Config.m_ClPlayerSkin) ||
m_aClients[m_aLocalIDs[0]].m_UseCustomColor != g_Config.m_ClPlayerUseCustomColor ||
m_aClients[m_aLocalIDs[0]].m_ColorBody != (int)g_Config.m_ClPlayerColorBody ||
m_aClients[m_aLocalIDs[0]].m_ColorFeet != (int)g_Config.m_ClPlayerColorFeet)
2014-04-28 13:19:57 +00:00
SendInfo(false);
else
m_aCheckInfo[0] = -1;
}
if(m_aCheckInfo[0] > 0)
m_aCheckInfo[0]--;
if(Client()->DummyConnected())
{
if(m_aCheckInfo[1] == 0)
{
if(
str_comp(m_aClients[m_aLocalIDs[1]].m_aName, Client()->DummyName()) ||
str_comp(m_aClients[m_aLocalIDs[1]].m_aClan, g_Config.m_ClDummyClan) ||
m_aClients[m_aLocalIDs[1]].m_Country != g_Config.m_ClDummyCountry ||
str_comp(m_aClients[m_aLocalIDs[1]].m_aSkinName, g_Config.m_ClDummySkin) ||
m_aClients[m_aLocalIDs[1]].m_UseCustomColor != g_Config.m_ClDummyUseCustomColor ||
m_aClients[m_aLocalIDs[1]].m_ColorBody != (int)g_Config.m_ClDummyColorBody ||
m_aClients[m_aLocalIDs[1]].m_ColorFeet != (int)g_Config.m_ClDummyColorFeet)
SendDummyInfo(false);
else
m_aCheckInfo[1] = -1;
}
if(m_aCheckInfo[1] > 0)
m_aCheckInfo[1]--;
2010-05-29 07:25:38 +00:00
}
}
}
2014-04-28 14:47:44 +00:00
void CGameClient::OnDummyDisconnect()
{
m_aDDRaceMsgSent[1] = false;
m_aShowOthers[1] = SHOW_OTHERS_NOT_SET;
m_aLastNewPredictedTick[1] = -1;
m_PredictedDummyID = -1;
2014-04-28 14:47:44 +00:00
}
int CGameClient::GetLastRaceTick()
{
2021-07-12 09:43:56 +00:00
return m_Ghost.GetLastRaceTick();
}
void CGameClient::OnRelease()
{
// release all systems
for(auto &pComponent : m_vpAll)
pComponent->OnRelease();
}
void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dummy)
{
2008-08-29 05:34:18 +00:00
// special messages
2020-12-06 18:58:04 +00:00
if(MsgId == NETMSGTYPE_SV_TUNEPARAMS)
2008-08-29 05:34:18 +00:00
{
// unpack the new tuning
2010-05-29 07:25:38 +00:00
CTuningParams NewTuning;
int *pParams = (int *)&NewTuning;
// No jetpack on DDNet incompatible servers:
NewTuning.m_JetpackStrength = 0;
for(unsigned i = 0; i < sizeof(CTuningParams) / sizeof(int); i++)
2014-04-12 09:12:29 +00:00
{
2014-12-27 11:05:02 +00:00
int value = pUnpacker->GetInt();
2008-08-29 05:34:18 +00:00
2014-04-12 09:12:29 +00:00
// check for unpacking errors
if(pUnpacker->Error())
break;
2014-12-27 11:05:02 +00:00
pParams[i] = value;
2014-04-12 09:12:29 +00:00
}
2010-05-29 07:25:38 +00:00
m_ServerMode = SERVERMODE_PURE;
m_aReceivedTuning[Conn] = true;
2008-08-29 05:34:18 +00:00
// apply new tuning
m_aTuning[Conn] = NewTuning;
2008-08-29 05:34:18 +00:00
return;
}
2010-05-29 07:25:38 +00:00
void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker);
if(!pRawMsg)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn());
Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
return;
}
if(Dummy)
{
if(MsgId == NETMSGTYPE_SV_CHAT && m_aLocalIDs[0] >= 0 && m_aLocalIDs[1] >= 0)
{
CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg;
if((pMsg->m_Team == 1 && (m_aClients[m_aLocalIDs[0]].m_Team != m_aClients[m_aLocalIDs[1]].m_Team || m_Teams.Team(m_aLocalIDs[0]) != m_Teams.Team(m_aLocalIDs[1]))) || pMsg->m_Team > 1)
{
2021-07-12 09:43:56 +00:00
m_Chat.OnMessage(MsgId, pRawMsg);
}
}
return; // no need of all that stuff for the dummy
}
// TODO: this should be done smarter
for(auto &pComponent : m_vpAll)
pComponent->OnMessage(MsgId, pRawMsg);
2010-05-29 07:25:38 +00:00
if(MsgId == NETMSGTYPE_SV_READYTOENTER)
{
Client()->EnterGame(Conn);
}
else if(MsgId == NETMSGTYPE_SV_EMOTICON)
{
2010-05-29 07:25:38 +00:00
CNetMsg_Sv_Emoticon *pMsg = (CNetMsg_Sv_Emoticon *)pRawMsg;
// apply
m_aClients[pMsg->m_ClientID].m_Emoticon = pMsg->m_Emoticon;
m_aClients[pMsg->m_ClientID].m_EmoticonStartTick = Client()->GameTick(Conn);
m_aClients[pMsg->m_ClientID].m_EmoticonStartFraction = Client()->IntraGameTickSincePrev(Conn);
}
2010-05-29 07:25:38 +00:00
else if(MsgId == NETMSGTYPE_SV_SOUNDGLOBAL)
{
2010-05-29 07:25:38 +00:00
if(m_SuppressEvents)
return;
// don't enqueue pseudo-global sounds from demos (created by PlayAndRecord)
2010-05-29 07:25:38 +00:00
CNetMsg_Sv_SoundGlobal *pMsg = (CNetMsg_Sv_SoundGlobal *)pRawMsg;
if(pMsg->m_SoundID == SOUND_CTF_DROP || pMsg->m_SoundID == SOUND_CTF_RETURN ||
pMsg->m_SoundID == SOUND_CTF_CAPTURE || pMsg->m_SoundID == SOUND_CTF_GRAB_EN ||
pMsg->m_SoundID == SOUND_CTF_GRAB_PL)
{
if(g_Config.m_SndGame)
2021-07-12 09:43:56 +00:00
m_Sounds.Enqueue(CSounds::CHN_GLOBAL, pMsg->m_SoundID);
}
else
{
if(g_Config.m_SndGame)
2021-07-12 09:43:56 +00:00
m_Sounds.Play(CSounds::CHN_GLOBAL, pMsg->m_SoundID, 1.0f);
}
}
else if(MsgId == NETMSGTYPE_SV_TEAMSSTATE || MsgId == NETMSGTYPE_SV_TEAMSSTATELEGACY)
{
2014-01-21 23:08:30 +00:00
unsigned int i;
for(i = 0; i < MAX_CLIENTS; i++)
{
2014-02-08 22:41:12 +00:00
int Team = pUnpacker->GetInt();
bool WentWrong = false;
2014-01-21 23:08:30 +00:00
if(pUnpacker->Error())
2014-02-08 22:41:12 +00:00
WentWrong = true;
if(!WentWrong && Team >= TEAM_FLOCK && Team <= TEAM_SUPER)
2014-02-08 22:41:12 +00:00
m_Teams.Team(i, Team);
else
2014-02-08 22:41:12 +00:00
WentWrong = true;
if(WentWrong)
2014-01-21 23:08:30 +00:00
{
m_Teams.Team(i, 0);
break;
}
}
if(i <= 16)
2014-01-21 23:08:30 +00:00
m_Teams.m_IsDDRace16 = true;
2021-07-12 09:43:56 +00:00
m_Ghost.m_AllowRestart = true;
m_RaceDemo.m_AllowRestart = true;
}
else if(MsgId == NETMSGTYPE_SV_KILLMSG)
{
CNetMsg_Sv_KillMsg *pMsg = (CNetMsg_Sv_KillMsg *)pRawMsg;
// reset character prediction
if(!(m_GameWorld.m_WorldConfig.m_IsFNG && pMsg->m_Weapon == WEAPON_LASER))
{
m_CharOrder.GiveWeak(pMsg->m_Victim);
2019-05-04 01:48:17 +00:00
if(CCharacter *pChar = m_GameWorld.GetCharacterByID(pMsg->m_Victim))
pChar->ResetPrediction();
m_GameWorld.ReleaseHooked(pMsg->m_Victim);
}
}
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnStateChange(int NewState, int OldState)
{
2010-05-29 07:25:38 +00:00
// reset everything when not already connected (to keep gathered stuff)
if(NewState < IClient::STATE_ONLINE)
OnReset();
// then change the state
for(auto &pComponent : m_vpAll)
pComponent->OnStateChange(NewState, OldState);
}
void CGameClient::OnShutdown()
{
for(auto &pComponent : m_vpAll)
pComponent->OnShutdown();
}
void CGameClient::OnEnterGame()
{
2021-07-12 09:43:56 +00:00
m_Effects.ResetDamageIndicator();
}
void CGameClient::OnGameOver()
{
if(Client()->State() != IClient::STATE_DEMOPLAYBACK && g_Config.m_ClEditor == 0)
Client()->AutoScreenshot_Start();
}
void CGameClient::OnStartGame()
{
if(Client()->State() != IClient::STATE_DEMOPLAYBACK && !g_Config.m_ClAutoDemoOnConnect)
Client()->DemoRecorder_HandleAutoStart();
2021-07-12 09:43:56 +00:00
m_Statboard.OnReset();
}
void CGameClient::OnFlagGrab(int TeamID)
{
if(TeamID == TEAM_RED)
m_aStats[m_Snap.m_pGameDataObj->m_FlagCarrierRed].m_FlagGrabs++;
else
m_aStats[m_Snap.m_pGameDataObj->m_FlagCarrierBlue].m_FlagGrabs++;
}
void CGameClient::OnWindowResize()
{
for(auto &pComponent : m_vpAll)
pComponent->OnWindowResize();
2020-10-12 10:29:47 +00:00
UI()->OnWindowResize();
TextRender()->OnWindowResize();
}
void CGameClient::OnWindowResizeCB(void *pUser)
{
CGameClient *pClient = (CGameClient *)pUser;
pClient->OnWindowResize();
}
2020-10-12 10:29:47 +00:00
void CGameClient::OnLanguageChange()
{
UI()->OnLanguageChange();
}
void CGameClient::OnRconType(bool UsernameReq)
{
2021-07-12 09:43:56 +00:00
m_GameConsole.RequireUsername(UsernameReq);
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnRconLine(const char *pLine)
{
2021-07-12 09:43:56 +00:00
m_GameConsole.PrintLine(CGameConsole::CONSOLETYPE_REMOTE, pLine);
2010-05-29 07:25:38 +00:00
}
2010-05-29 07:25:38 +00:00
void CGameClient::ProcessEvents()
{
2010-05-29 07:25:38 +00:00
if(m_SuppressEvents)
return;
2010-05-29 07:25:38 +00:00
int SnapType = IClient::SNAP_CURRENT;
int Num = Client()->SnapNumItems(SnapType);
for(int Index = 0; Index < Num; Index++)
{
2010-05-29 07:25:38 +00:00
IClient::CSnapItem Item;
const void *pData = Client()->SnapGetItem(SnapType, Index, &Item);
2010-05-29 07:25:38 +00:00
if(Item.m_Type == NETEVENTTYPE_DAMAGEIND)
{
CNetEvent_DamageInd *pEvent = (CNetEvent_DamageInd *)pData;
m_Effects.DamageIndicator(vec2(pEvent->m_X, pEvent->m_Y), direction(pEvent->m_Angle / 256.0f));
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETEVENTTYPE_EXPLOSION)
{
CNetEvent_Explosion *pEvent = (CNetEvent_Explosion *)pData;
m_Effects.Explosion(vec2(pEvent->m_X, pEvent->m_Y));
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETEVENTTYPE_HAMMERHIT)
{
CNetEvent_HammerHit *pEvent = (CNetEvent_HammerHit *)pData;
m_Effects.HammerHit(vec2(pEvent->m_X, pEvent->m_Y));
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETEVENTTYPE_SPAWN)
{
CNetEvent_Spawn *pEvent = (CNetEvent_Spawn *)pData;
m_Effects.PlayerSpawn(vec2(pEvent->m_X, pEvent->m_Y));
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETEVENTTYPE_DEATH)
{
CNetEvent_Death *pEvent = (CNetEvent_Death *)pData;
m_Effects.PlayerDeath(vec2(pEvent->m_X, pEvent->m_Y), pEvent->m_ClientID);
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETEVENTTYPE_SOUNDWORLD)
{
CNetEvent_SoundWorld *pEvent = (CNetEvent_SoundWorld *)pData;
if(!Config()->m_SndGame)
continue;
if(m_GameInfo.m_RaceSounds && ((pEvent->m_SoundID == SOUND_GUN_FIRE && !g_Config.m_SndGun) || (pEvent->m_SoundID == SOUND_PLAYER_PAIN_LONG && !g_Config.m_SndLongPain)))
continue;
m_Sounds.PlayAt(CSounds::CHN_WORLD, pEvent->m_SoundID, 1.0f, vec2(pEvent->m_X, pEvent->m_Y));
}
}
}
static CGameInfo GetGameInfo(const CNetObj_GameInfoEx *pInfoEx, int InfoExSize, CServerInfo *pFallbackServerInfo)
{
int Version = -1;
2020-09-25 06:17:14 +00:00
if(InfoExSize >= 12)
{
2019-06-13 22:24:50 +00:00
Version = pInfoEx->m_Version;
}
2020-09-25 06:17:14 +00:00
else if(InfoExSize >= 8)
{
Version = minimum(pInfoEx->m_Version, 4);
}
2019-06-13 22:24:50 +00:00
else if(InfoExSize >= 4)
{
2019-06-13 22:24:50 +00:00
Version = 0;
}
int Flags = 0;
if(Version >= 0)
{
Flags = pInfoEx->m_Flags;
}
int Flags2 = 0;
if(Version >= 5)
{
Flags2 = pInfoEx->m_Flags2;
}
2019-06-13 22:24:50 +00:00
bool Race;
bool FastCap;
bool FNG;
bool DDRace;
bool DDNet;
bool BlockWorlds;
2020-09-24 10:01:12 +00:00
bool City;
2019-06-13 22:24:50 +00:00
bool Vanilla;
bool Plus;
2020-12-21 15:45:19 +00:00
bool FDDrace;
2019-06-13 22:24:50 +00:00
if(Version < 1)
{
Race = IsRace(pFallbackServerInfo);
FastCap = IsFastCap(pFallbackServerInfo);
FNG = IsFNG(pFallbackServerInfo);
DDRace = IsDDRace(pFallbackServerInfo);
DDNet = IsDDNet(pFallbackServerInfo);
BlockWorlds = IsBlockWorlds(pFallbackServerInfo);
2020-09-24 10:01:12 +00:00
City = IsCity(pFallbackServerInfo);
2019-06-13 22:24:50 +00:00
Vanilla = IsVanilla(pFallbackServerInfo);
Plus = IsPlus(pFallbackServerInfo);
2020-12-21 15:45:19 +00:00
FDDrace = false;
2019-06-13 22:24:50 +00:00
}
else
{
2020-09-24 10:01:54 +00:00
Race = Flags & GAMEINFOFLAG_GAMETYPE_RACE;
FastCap = Flags & GAMEINFOFLAG_GAMETYPE_FASTCAP;
FNG = Flags & GAMEINFOFLAG_GAMETYPE_FNG;
DDRace = Flags & GAMEINFOFLAG_GAMETYPE_DDRACE;
DDNet = Flags & GAMEINFOFLAG_GAMETYPE_DDNET;
BlockWorlds = Flags & GAMEINFOFLAG_GAMETYPE_BLOCK_WORLDS;
Vanilla = Flags & GAMEINFOFLAG_GAMETYPE_VANILLA;
Plus = Flags & GAMEINFOFLAG_GAMETYPE_PLUS;
2020-09-25 06:17:14 +00:00
City = Version >= 5 && Flags2 & GAMEINFOFLAG2_GAMETYPE_CITY;
2020-12-21 15:45:19 +00:00
FDDrace = Version >= 6 && Flags2 & GAMEINFOFLAG2_GAMETYPE_FDDRACE;
2019-06-13 22:24:50 +00:00
// Ensure invariants upheld by the server info parsing business.
2020-12-21 15:45:19 +00:00
DDRace = DDRace || DDNet || FDDrace;
Race = Race || FastCap || DDRace;
}
CGameInfo Info;
Info.m_FlagStartsRace = FastCap;
Info.m_TimeScore = Race;
Info.m_UnlimitedAmmo = Race;
2019-07-19 09:28:08 +00:00
Info.m_DDRaceRecordMessage = DDRace && !DDNet;
Info.m_RaceRecordMessage = DDNet || (Race && !DDRace);
2022-04-16 17:36:25 +00:00
Info.m_RaceSounds = DDRace || FNG || BlockWorlds;
2020-09-24 10:01:12 +00:00
Info.m_AllowEyeWheel = DDRace || BlockWorlds || City || Plus;
Info.m_AllowHookColl = DDRace;
2020-09-24 10:01:12 +00:00
Info.m_AllowZoom = Race || BlockWorlds || City;
Info.m_BugDDRaceGhost = DDRace;
Info.m_BugDDRaceInput = DDRace;
Info.m_BugFNGLaserRange = FNG;
Info.m_BugVanillaBounce = Vanilla;
Info.m_PredictFNG = FNG;
Info.m_PredictDDRace = DDRace;
Info.m_PredictDDRaceTiles = DDRace && !BlockWorlds;
Info.m_PredictVanilla = Vanilla || FastCap;
Info.m_EntitiesDDNet = DDNet;
Info.m_EntitiesDDRace = DDRace;
Info.m_EntitiesRace = Race;
Info.m_EntitiesFNG = FNG;
Info.m_EntitiesVanilla = Vanilla;
2020-05-19 14:49:28 +00:00
Info.m_EntitiesBW = BlockWorlds;
2019-07-19 09:28:08 +00:00
Info.m_Race = Race;
Info.m_DontMaskEntities = !DDNet;
2020-09-25 06:17:14 +00:00
Info.m_AllowXSkins = false;
2020-12-21 15:45:19 +00:00
Info.m_EntitiesFDDrace = FDDrace;
Info.m_HudHealthArmor = true;
Info.m_HudAmmo = true;
Info.m_HudDDRace = false;
if(Version >= 0)
{
Info.m_TimeScore = Flags & GAMEINFOFLAG_TIMESCORE;
}
if(Version >= 2)
{
Info.m_FlagStartsRace = Flags & GAMEINFOFLAG_FLAG_STARTS_RACE;
Info.m_UnlimitedAmmo = Flags & GAMEINFOFLAG_UNLIMITED_AMMO;
Info.m_DDRaceRecordMessage = Flags & GAMEINFOFLAG_DDRACE_RECORD_MESSAGE;
Info.m_RaceRecordMessage = Flags & GAMEINFOFLAG_RACE_RECORD_MESSAGE;
Info.m_AllowEyeWheel = Flags & GAMEINFOFLAG_ALLOW_EYE_WHEEL;
Info.m_AllowHookColl = Flags & GAMEINFOFLAG_ALLOW_HOOK_COLL;
Info.m_AllowZoom = Flags & GAMEINFOFLAG_ALLOW_ZOOM;
Info.m_BugDDRaceGhost = Flags & GAMEINFOFLAG_BUG_DDRACE_GHOST;
Info.m_BugDDRaceInput = Flags & GAMEINFOFLAG_BUG_DDRACE_INPUT;
Info.m_BugFNGLaserRange = Flags & GAMEINFOFLAG_BUG_FNG_LASER_RANGE;
Info.m_BugVanillaBounce = Flags & GAMEINFOFLAG_BUG_VANILLA_BOUNCE;
Info.m_PredictFNG = Flags & GAMEINFOFLAG_PREDICT_FNG;
Info.m_PredictDDRace = Flags & GAMEINFOFLAG_PREDICT_DDRACE;
Info.m_PredictDDRaceTiles = Flags & GAMEINFOFLAG_PREDICT_DDRACE_TILES;
Info.m_PredictVanilla = Flags & GAMEINFOFLAG_PREDICT_VANILLA;
Info.m_EntitiesDDNet = Flags & GAMEINFOFLAG_ENTITIES_DDNET;
Info.m_EntitiesDDRace = Flags & GAMEINFOFLAG_ENTITIES_DDRACE;
Info.m_EntitiesRace = Flags & GAMEINFOFLAG_ENTITIES_RACE;
Info.m_EntitiesFNG = Flags & GAMEINFOFLAG_ENTITIES_FNG;
Info.m_EntitiesVanilla = Flags & GAMEINFOFLAG_ENTITIES_VANILLA;
}
2019-07-19 08:21:32 +00:00
if(Version >= 3)
{
Info.m_Race = Flags & GAMEINFOFLAG_RACE;
Info.m_DontMaskEntities = Flags & GAMEINFOFLAG_DONT_MASK_ENTITIES;
2019-07-19 08:21:32 +00:00
}
2020-09-25 06:17:14 +00:00
if(Version >= 4)
2020-05-19 15:13:34 +00:00
{
Info.m_EntitiesBW = Flags & GAMEINFOFLAG_ENTITIES_BW;
2020-05-19 15:13:34 +00:00
}
2020-09-25 06:17:14 +00:00
if(Version >= 5)
{
Info.m_AllowXSkins = Flags2 & GAMEINFOFLAG2_ALLOW_X_SKINS;
}
2020-12-21 15:45:19 +00:00
if(Version >= 6)
{
Info.m_EntitiesFDDrace = Flags2 & GAMEINFOFLAG2_ENTITIES_FDDRACE;
}
if(Version >= 7)
{
Info.m_HudHealthArmor = Flags2 & GAMEINFOFLAG2_HUD_HEALTH_ARMOR;
Info.m_HudAmmo = Flags2 & GAMEINFOFLAG2_HUD_AMMO;
Info.m_HudDDRace = Flags2 & GAMEINFOFLAG2_HUD_DDRACE;
}
return Info;
}
void CGameClient::InvalidateSnapshot()
{
// clear all pointers
2021-07-12 09:43:56 +00:00
mem_zero(&m_Snap, sizeof(m_Snap));
m_Snap.m_LocalClientID = -1;
SnapCollectEntities();
}
void CGameClient::OnNewSnapshot()
{
auto &&Evolve = [this](CNetObj_Character *pCharacter, int Tick) {
2021-07-12 10:04:45 +00:00
CWorldCore TempWorld;
CCharacterCore TempCore = CCharacterCore();
CTeamsCore TempTeams = CTeamsCore();
2021-07-12 10:04:45 +00:00
TempCore.Init(&TempWorld, Collision(), &TempTeams);
TempCore.Read(pCharacter);
TempCore.m_ActiveWeapon = pCharacter->m_Weapon;
2021-07-12 10:04:45 +00:00
while(pCharacter->m_Tick < Tick)
{
pCharacter->m_Tick++;
TempCore.Tick(false);
TempCore.Move();
TempCore.Quantize();
}
TempCore.Write(pCharacter);
};
InvalidateSnapshot();
m_NewTick = true;
2010-05-29 07:25:38 +00:00
ProcessEvents();
#ifdef CONF_DEBUG
2010-05-29 07:25:38 +00:00
if(g_Config.m_DbgStress)
{
if((Client()->GameTick(g_Config.m_ClDummy) % 100) == 0)
{
2010-05-29 07:25:38 +00:00
char aMessage[64];
int MsgLen = rand() % (sizeof(aMessage) - 1);
2010-05-29 07:25:38 +00:00
for(int i = 0; i < MsgLen; i++)
2018-10-02 18:52:21 +00:00
aMessage[i] = (char)('a' + (rand() % ('z' - 'a')));
2010-05-29 07:25:38 +00:00
aMessage[MsgLen] = 0;
2010-05-29 07:25:38 +00:00
CNetMsg_Cl_Say Msg;
Msg.m_Team = rand() & 1;
2010-05-29 07:25:38 +00:00
Msg.m_pMessage = aMessage;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
}
}
#endif
bool FoundGameInfoEx = false;
bool GotSwitchStateTeam = false;
m_aSwitchStateTeam[g_Config.m_ClDummy] = -1;
2020-10-26 14:14:07 +00:00
for(auto &Client : m_aClients)
{
2020-10-26 14:14:07 +00:00
Client.m_SpecCharPresent = false;
}
2008-09-23 07:43:41 +00:00
// go trough all the items in the snapshot and gather the info we want
{
2011-01-03 11:50:38 +00:00
m_Snap.m_aTeamSize[TEAM_RED] = m_Snap.m_aTeamSize[TEAM_BLUE] = 0;
2010-05-29 07:25:38 +00:00
int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT);
for(int i = 0; i < Num; i++)
{
2010-05-29 07:25:38 +00:00
IClient::CSnapItem Item;
const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item);
2010-05-29 07:25:38 +00:00
if(Item.m_Type == NETOBJTYPE_CLIENTINFO)
{
2010-05-29 07:25:38 +00:00
const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)pData;
int ClientID = Item.m_ID;
if(ClientID < MAX_CLIENTS)
{
CClientData *pClient = &m_aClients[ClientID];
IntsToStr(&pInfo->m_Name0, 4, pClient->m_aName);
IntsToStr(&pInfo->m_Clan0, 3, pClient->m_aClan);
pClient->m_Country = pInfo->m_Country;
IntsToStr(&pInfo->m_Skin0, 6, pClient->m_aSkinName);
pClient->m_UseCustomColor = pInfo->m_UseCustomColor;
pClient->m_ColorBody = pInfo->m_ColorBody;
pClient->m_ColorFeet = pInfo->m_ColorFeet;
// prepare the info
if(!m_GameInfo.m_AllowXSkins && (pClient->m_aSkinName[0] == 'x' && pClient->m_aSkinName[1] == '_'))
2022-07-09 16:14:56 +00:00
str_copy(pClient->m_aSkinName, "default");
pClient->m_SkinInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(pClient->m_ColorBody).UnclampLighting());
pClient->m_SkinInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(pClient->m_ColorFeet).UnclampLighting());
pClient->m_SkinInfo.m_Size = 64;
// find new skin
2021-07-12 09:43:56 +00:00
const CSkin *pSkin = m_Skins.Get(m_Skins.Find(pClient->m_aSkinName));
pClient->m_SkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
pClient->m_SkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
2020-11-08 05:39:16 +00:00
pClient->m_SkinInfo.m_SkinMetrics = pSkin->m_Metrics;
pClient->m_SkinInfo.m_BloodColor = pSkin->m_BloodColor;
pClient->m_SkinInfo.m_CustomColoredSkin = pClient->m_UseCustomColor;
if(!pClient->m_UseCustomColor)
{
pClient->m_SkinInfo.m_ColorBody = ColorRGBA(1, 1, 1);
pClient->m_SkinInfo.m_ColorFeet = ColorRGBA(1, 1, 1);
}
2021-07-12 10:04:45 +00:00
pClient->UpdateRenderInfo(IsTeamPlay());
}
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETOBJTYPE_PLAYERINFO)
{
2010-05-29 07:25:38 +00:00
const CNetObj_PlayerInfo *pInfo = (const CNetObj_PlayerInfo *)pData;
if(pInfo->m_ClientID < MAX_CLIENTS)
{
m_aClients[pInfo->m_ClientID].m_Team = pInfo->m_Team;
m_aClients[pInfo->m_ClientID].m_Active = true;
m_Snap.m_apPlayerInfos[pInfo->m_ClientID] = pInfo;
m_Snap.m_NumPlayers++;
if(pInfo->m_Local)
{
m_Snap.m_LocalClientID = Item.m_ID;
m_Snap.m_pLocalInfo = pInfo;
if(pInfo->m_Team == TEAM_SPECTATORS)
{
m_Snap.m_SpecInfo.m_Active = true;
}
}
// calculate team-balance
if(pInfo->m_Team != TEAM_SPECTATORS)
{
m_Snap.m_aTeamSize[pInfo->m_Team]++;
if(!m_aStats[pInfo->m_ClientID].IsActive())
m_aStats[pInfo->m_ClientID].JoinGame(Client()->GameTick(g_Config.m_ClDummy));
}
else if(m_aStats[pInfo->m_ClientID].IsActive())
m_aStats[pInfo->m_ClientID].JoinSpec(Client()->GameTick(g_Config.m_ClDummy));
2015-05-19 22:51:02 +00:00
}
}
2019-05-14 23:03:30 +00:00
else if(Item.m_Type == NETOBJTYPE_DDNETPLAYER)
{
m_ReceivedDDNetPlayer = true;
2019-05-14 23:03:30 +00:00
const CNetObj_DDNetPlayer *pInfo = (const CNetObj_DDNetPlayer *)pData;
if(Item.m_ID < MAX_CLIENTS)
{
m_aClients[Item.m_ID].m_AuthLevel = pInfo->m_AuthLevel;
m_aClients[Item.m_ID].m_Afk = pInfo->m_Flags & EXPLAYERFLAG_AFK;
m_aClients[Item.m_ID].m_Paused = pInfo->m_Flags & EXPLAYERFLAG_PAUSED;
m_aClients[Item.m_ID].m_Spec = pInfo->m_Flags & EXPLAYERFLAG_SPEC;
2022-03-14 12:25:47 +00:00
if(Item.m_ID == m_Snap.m_LocalClientID && (m_aClients[Item.m_ID].m_Paused || m_aClients[Item.m_ID].m_Spec))
{
m_Snap.m_SpecInfo.m_Active = true;
}
}
2019-05-14 23:03:30 +00:00
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETOBJTYPE_CHARACTER)
{
if(Item.m_ID < MAX_CLIENTS)
2008-09-23 07:43:41 +00:00
{
const void *pOld = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, Item.m_ID);
m_Snap.m_aCharacters[Item.m_ID].m_Cur = *((const CNetObj_Character *)pData);
if(pOld)
2019-11-09 21:49:53 +00:00
{
m_Snap.m_aCharacters[Item.m_ID].m_Active = true;
m_Snap.m_aCharacters[Item.m_ID].m_Prev = *((const CNetObj_Character *)pOld);
// limit evolving to 3 seconds
bool EvolvePrev = Client()->PrevGameTick(g_Config.m_ClDummy) - m_Snap.m_aCharacters[Item.m_ID].m_Prev.m_Tick <= 3 * Client()->GameTickSpeed();
bool EvolveCur = Client()->GameTick(g_Config.m_ClDummy) - m_Snap.m_aCharacters[Item.m_ID].m_Cur.m_Tick <= 3 * Client()->GameTickSpeed();
// reuse the result from the previous evolve if the snapped character didn't change since the previous snapshot
if(EvolveCur && m_aClients[Item.m_ID].m_Evolved.m_Tick == Client()->PrevGameTick(g_Config.m_ClDummy))
{
if(mem_comp(&m_Snap.m_aCharacters[Item.m_ID].m_Prev, &m_aClients[Item.m_ID].m_Snapped, sizeof(CNetObj_Character)) == 0)
m_Snap.m_aCharacters[Item.m_ID].m_Prev = m_aClients[Item.m_ID].m_Evolved;
if(mem_comp(&m_Snap.m_aCharacters[Item.m_ID].m_Cur, &m_aClients[Item.m_ID].m_Snapped, sizeof(CNetObj_Character)) == 0)
m_Snap.m_aCharacters[Item.m_ID].m_Cur = m_aClients[Item.m_ID].m_Evolved;
}
if(EvolvePrev && m_Snap.m_aCharacters[Item.m_ID].m_Prev.m_Tick)
Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Prev, Client()->PrevGameTick(g_Config.m_ClDummy));
if(EvolveCur && m_Snap.m_aCharacters[Item.m_ID].m_Cur.m_Tick)
Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Cur, Client()->GameTick(g_Config.m_ClDummy));
m_aClients[Item.m_ID].m_Snapped = *((const CNetObj_Character *)pData);
m_aClients[Item.m_ID].m_Evolved = m_Snap.m_aCharacters[Item.m_ID].m_Cur;
}
else
{
m_aClients[Item.m_ID].m_Evolved.m_Tick = -1;
2019-11-09 21:49:53 +00:00
}
2008-09-23 07:43:41 +00:00
}
}
else if(Item.m_Type == NETOBJTYPE_DDNETCHARACTER)
{
const CNetObj_DDNetCharacter *pCharacterData = (const CNetObj_DDNetCharacter *)pData;
if(Item.m_ID < MAX_CLIENTS)
{
m_Snap.m_aCharacters[Item.m_ID].m_ExtendedData = *pCharacterData;
m_Snap.m_aCharacters[Item.m_ID].m_PrevExtendedData = (const CNetObj_DDNetCharacter *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_DDNETCHARACTER, Item.m_ID);
m_Snap.m_aCharacters[Item.m_ID].m_HasExtendedData = true;
m_Snap.m_aCharacters[Item.m_ID].m_HasExtendedDisplayInfo = false;
if(pCharacterData->m_JumpedTotal != -1)
{
m_Snap.m_aCharacters[Item.m_ID].m_HasExtendedDisplayInfo = true;
}
CClientData *pClient = &m_aClients[Item.m_ID];
// Collision
pClient->m_Solo = pCharacterData->m_Flags & CHARACTERFLAG_SOLO;
2021-08-08 10:36:25 +00:00
pClient->m_Jetpack = pCharacterData->m_Flags & CHARACTERFLAG_JETPACK;
pClient->m_NoCollision = pCharacterData->m_Flags & CHARACTERFLAG_NO_COLLISION;
pClient->m_NoHammerHit = pCharacterData->m_Flags & CHARACTERFLAG_NO_HAMMER_HIT;
pClient->m_NoGrenadeHit = pCharacterData->m_Flags & CHARACTERFLAG_NO_GRENADE_HIT;
pClient->m_NoLaserHit = pCharacterData->m_Flags & CHARACTERFLAG_NO_LASER_HIT;
pClient->m_NoShotgunHit = pCharacterData->m_Flags & CHARACTERFLAG_NO_SHOTGUN_HIT;
pClient->m_NoHookHit = pCharacterData->m_Flags & CHARACTERFLAG_NO_HOOK;
pClient->m_Super = pCharacterData->m_Flags & CHARACTERFLAG_SUPER;
// Endless
pClient->m_EndlessHook = pCharacterData->m_Flags & CHARACTERFLAG_ENDLESS_HOOK;
pClient->m_EndlessJump = pCharacterData->m_Flags & CHARACTERFLAG_ENDLESS_JUMP;
// Freeze
pClient->m_FreezeEnd = pCharacterData->m_FreezeEnd;
pClient->m_DeepFrozen = pCharacterData->m_FreezeEnd == -1;
pClient->m_LiveFrozen = (pCharacterData->m_Flags & CHARACTERFLAG_NO_MOVEMENTS) != 0;
// Telegun
pClient->m_HasTelegunGrenade = pCharacterData->m_Flags & CHARACTERFLAG_TELEGUN_GRENADE;
pClient->m_HasTelegunGun = pCharacterData->m_Flags & CHARACTERFLAG_TELEGUN_GUN;
pClient->m_HasTelegunLaser = pCharacterData->m_Flags & CHARACTERFLAG_TELEGUN_LASER;
pClient->m_Predicted.ReadDDNet(pCharacterData);
}
}
2020-06-24 17:01:01 +00:00
else if(Item.m_Type == NETOBJTYPE_SPECCHAR)
{
const CNetObj_SpecChar *pSpecCharData = (const CNetObj_SpecChar *)pData;
if(Item.m_ID < MAX_CLIENTS)
{
CClientData *pClient = &m_aClients[Item.m_ID];
pClient->m_SpecCharPresent = true;
pClient->m_SpecChar.x = pSpecCharData->m_X;
pClient->m_SpecChar.y = pSpecCharData->m_Y;
}
2020-06-24 17:01:01 +00:00
}
else if(Item.m_Type == NETOBJTYPE_SPECTATORINFO)
{
m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *)pData;
m_Snap.m_pPrevSpectatorInfo = (const CNetObj_SpectatorInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECTATORINFO, Item.m_ID);
m_Snap.m_SpecInfo.m_SpectatorID = m_Snap.m_pSpectatorInfo->m_SpectatorID;
}
2011-03-04 16:08:10 +00:00
else if(Item.m_Type == NETOBJTYPE_GAMEINFO)
{
2022-02-14 23:12:52 +00:00
static bool s_GameOver = false;
static bool s_GamePaused = false;
2011-03-04 16:08:10 +00:00
m_Snap.m_pGameInfoObj = (const CNetObj_GameInfo *)pData;
2018-10-02 18:52:21 +00:00
bool CurrentTickGameOver = (bool)(m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER);
if(!s_GameOver && CurrentTickGameOver)
OnGameOver();
else if(s_GameOver && !CurrentTickGameOver)
OnStartGame();
// Reset statboard when new round is started (RoundStartTick changed)
// New round is usually started after `restart` on server
if(m_Snap.m_pGameInfoObj->m_RoundStartTick != m_LastRoundStartTick
// In GamePaused or GameOver state RoundStartTick is updated on each tick
// hence no need to reset stats until player leaves GameOver
// and it would be a mistake to reset stats after or during the pause
&& !(CurrentTickGameOver || m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED || s_GamePaused))
2021-07-12 09:43:56 +00:00
m_Statboard.OnReset();
m_LastRoundStartTick = m_Snap.m_pGameInfoObj->m_RoundStartTick;
s_GameOver = CurrentTickGameOver;
2018-10-02 18:52:21 +00:00
s_GamePaused = (bool)(m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED);
2011-03-04 16:08:10 +00:00
}
else if(Item.m_Type == NETOBJTYPE_GAMEINFOEX)
2019-05-21 08:11:02 +00:00
{
if(FoundGameInfoEx)
{
continue;
}
FoundGameInfoEx = true;
CServerInfo ServerInfo;
Client()->GetServerInfo(&ServerInfo);
m_GameInfo = GetGameInfo((const CNetObj_GameInfoEx *)pData, Client()->SnapItemSize(IClient::SNAP_CURRENT, i), &ServerInfo);
2019-05-21 08:11:02 +00:00
}
2011-03-04 16:08:10 +00:00
else if(Item.m_Type == NETOBJTYPE_GAMEDATA)
{
m_Snap.m_pGameDataObj = (const CNetObj_GameData *)pData;
m_Snap.m_GameDataSnapID = Item.m_ID;
if(m_Snap.m_pGameDataObj->m_FlagCarrierRed == FLAG_TAKEN)
{
if(m_aFlagDropTick[TEAM_RED] == 0)
m_aFlagDropTick[TEAM_RED] = Client()->GameTick(g_Config.m_ClDummy);
}
else if(m_aFlagDropTick[TEAM_RED] != 0)
m_aFlagDropTick[TEAM_RED] = 0;
if(m_Snap.m_pGameDataObj->m_FlagCarrierBlue == FLAG_TAKEN)
{
if(m_aFlagDropTick[TEAM_BLUE] == 0)
m_aFlagDropTick[TEAM_BLUE] = Client()->GameTick(g_Config.m_ClDummy);
}
else if(m_aFlagDropTick[TEAM_BLUE] != 0)
m_aFlagDropTick[TEAM_BLUE] = 0;
if(m_LastFlagCarrierRed == FLAG_ATSTAND && m_Snap.m_pGameDataObj->m_FlagCarrierRed >= 0)
OnFlagGrab(TEAM_RED);
else if(m_LastFlagCarrierBlue == FLAG_ATSTAND && m_Snap.m_pGameDataObj->m_FlagCarrierBlue >= 0)
OnFlagGrab(TEAM_BLUE);
m_LastFlagCarrierRed = m_Snap.m_pGameDataObj->m_FlagCarrierRed;
m_LastFlagCarrierBlue = m_Snap.m_pGameDataObj->m_FlagCarrierBlue;
}
2010-05-29 07:25:38 +00:00
else if(Item.m_Type == NETOBJTYPE_FLAG)
m_Snap.m_apFlags[Item.m_ID % 2] = (const CNetObj_Flag *)pData;
2021-08-15 10:53:14 +00:00
else if(Item.m_Type == NETOBJTYPE_SWITCHSTATE)
{
2022-06-02 13:40:55 +00:00
if(Item.m_DataSize < 36)
{
continue;
}
2021-08-15 10:53:14 +00:00
const CNetObj_SwitchState *pSwitchStateData = (const CNetObj_SwitchState *)pData;
int Team = clamp(Item.m_ID, (int)TEAM_FLOCK, (int)TEAM_SUPER - 1);
2021-08-15 10:53:14 +00:00
int HighestSwitchNumber = clamp(pSwitchStateData->m_HighestSwitchNumber, 0, 255);
if(HighestSwitchNumber != maximum(0, (int)Switchers().size() - 1))
{
m_GameWorld.m_Core.InitSwitchers(HighestSwitchNumber);
Collision()->m_HighestSwitchNumber = HighestSwitchNumber;
}
2021-08-15 10:53:14 +00:00
for(int j = 0; j < (int)Switchers().size(); j++)
2021-08-15 10:53:14 +00:00
{
Switchers()[j].m_aStatus[Team] = (pSwitchStateData->m_aStatus[j / 32] >> (j % 32)) & 1;
}
2022-06-02 13:40:55 +00:00
if(Item.m_DataSize >= 68)
{
// update the endtick of up to four timed switchers
for(int j = 0; j < (int)std::size(pSwitchStateData->m_aEndTicks); j++)
{
int SwitchNumber = pSwitchStateData->m_aSwitchNumbers[j];
int EndTick = pSwitchStateData->m_aEndTicks[j];
if(EndTick > 0 && in_range(SwitchNumber, 0, (int)Switchers().size()))
{
Switchers()[SwitchNumber].m_aEndTick[Team] = EndTick;
}
}
}
// update switch types
for(auto &Switcher : Switchers())
{
if(Switcher.m_aStatus[Team])
Switcher.m_aType[Team] = Switcher.m_aEndTick[Team] ? TILE_SWITCHTIMEDOPEN : TILE_SWITCHOPEN;
else
Switcher.m_aType[Team] = Switcher.m_aEndTick[Team] ? TILE_SWITCHTIMEDCLOSE : TILE_SWITCHCLOSE;
2021-08-15 10:53:14 +00:00
}
if(!GotSwitchStateTeam)
m_aSwitchStateTeam[g_Config.m_ClDummy] = Team;
else
m_aSwitchStateTeam[g_Config.m_ClDummy] = -1;
GotSwitchStateTeam = true;
2021-08-15 10:53:14 +00:00
}
2008-09-23 07:43:41 +00:00
}
}
if(!FoundGameInfoEx)
{
CServerInfo ServerInfo;
Client()->GetServerInfo(&ServerInfo);
m_GameInfo = GetGameInfo(0, 0, &ServerInfo);
}
2008-09-23 07:43:41 +00:00
// setup local pointers
if(m_Snap.m_LocalClientID >= 0)
2008-09-23 07:43:41 +00:00
{
m_aLocalIDs[g_Config.m_ClDummy] = m_Snap.m_LocalClientID;
2020-06-16 15:19:53 +00:00
CSnapState::CCharacterInfo *pChr = &m_Snap.m_aCharacters[m_Snap.m_LocalClientID];
if(pChr->m_Active)
2008-09-23 07:43:41 +00:00
{
2016-09-20 14:34:49 +00:00
if(!m_Snap.m_SpecInfo.m_Active)
{
m_Snap.m_pLocalCharacter = &pChr->m_Cur;
m_Snap.m_pLocalPrevCharacter = &pChr->m_Prev;
2016-09-20 14:34:49 +00:00
m_LocalCharacterPos = vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y);
}
}
else if(Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, m_Snap.m_LocalClientID))
2010-08-10 11:54:13 +00:00
{
// player died
2021-07-12 09:43:56 +00:00
m_Controls.OnPlayerDeath();
2010-08-10 11:54:13 +00:00
}
}
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
{
if(m_DemoSpecID != SPEC_FOLLOW)
{
m_Snap.m_SpecInfo.m_Active = true;
m_Snap.m_SpecInfo.m_SpectatorID = m_Snap.m_LocalClientID;
if(m_DemoSpecID > SPEC_FREEVIEW && m_Snap.m_aCharacters[m_DemoSpecID].m_Active)
m_Snap.m_SpecInfo.m_SpectatorID = m_DemoSpecID;
else
m_Snap.m_SpecInfo.m_SpectatorID = SPEC_FREEVIEW;
}
}
// clear out unneeded client data
for(int i = 0; i < MAX_CLIENTS; ++i)
{
if(!m_Snap.m_apPlayerInfos[i] && m_aClients[i].m_Active)
{
m_aClients[i].Reset();
m_aStats[i].Reset();
}
}
2011-03-23 12:06:35 +00:00
for(int i = 0; i < MAX_CLIENTS; ++i)
{
// update friend state
m_aClients[i].m_Friend = !(i == m_Snap.m_LocalClientID || !m_Snap.m_apPlayerInfos[i] || !Friends()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true));
// update foe state
m_aClients[i].m_Foe = !(i == m_Snap.m_LocalClientID || !m_Snap.m_apPlayerInfos[i] || !Foes()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true));
2015-07-22 20:16:49 +00:00
}
2014-02-08 21:24:57 +00:00
// sort player infos by name
mem_copy(m_Snap.m_apInfoByName, m_Snap.m_apPlayerInfos, sizeof(m_Snap.m_apInfoByName));
std::stable_sort(m_Snap.m_apInfoByName, m_Snap.m_apInfoByName + MAX_CLIENTS,
[this](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool {
if(!p2)
return static_cast<bool>(p1);
if(!p1)
return false;
return str_comp_nocase(m_aClients[p1->m_ClientID].m_aName, m_aClients[p2->m_ClientID].m_aName) < 0;
});
bool TimeScore = m_GameInfo.m_TimeScore;
2014-02-08 21:24:57 +00:00
// sort player infos by score
mem_copy(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByName, sizeof(m_Snap.m_apInfoByScore));
std::stable_sort(m_Snap.m_apInfoByScore, m_Snap.m_apInfoByScore + MAX_CLIENTS,
[TimeScore](const CNetObj_PlayerInfo *p1, const CNetObj_PlayerInfo *p2) -> bool {
if(!p2)
return static_cast<bool>(p1);
if(!p1)
return false;
return (((TimeScore && p1->m_Score == -9999) ? std::numeric_limits<int>::min() : p1->m_Score) >
((TimeScore && p2->m_Score == -9999) ? std::numeric_limits<int>::min() : p2->m_Score));
});
2018-02-04 15:00:47 +00:00
// sort player infos by DDRace Team (and score between)
int Index = 0;
for(int Team = TEAM_FLOCK; Team <= TEAM_SUPER; ++Team)
2014-01-22 00:39:18 +00:00
{
for(int i = 0; i < MAX_CLIENTS && Index < MAX_CLIENTS; ++i)
{
if(m_Snap.m_apInfoByScore[i] && m_Teams.Team(m_Snap.m_apInfoByScore[i]->m_ClientID) == Team)
m_Snap.m_apInfoByDDTeamScore[Index++] = m_Snap.m_apInfoByScore[i];
}
}
// sort player infos by DDRace Team (and name between)
Index = 0;
for(int Team = TEAM_FLOCK; Team <= TEAM_SUPER; ++Team)
{
for(int i = 0; i < MAX_CLIENTS && Index < MAX_CLIENTS; ++i)
{
if(m_Snap.m_apInfoByName[i] && m_Teams.Team(m_Snap.m_apInfoByName[i]->m_ClientID) == Team)
m_Snap.m_apInfoByDDTeamName[Index++] = m_Snap.m_apInfoByName[i];
2014-01-22 00:39:18 +00:00
}
}
CServerInfo CurrentServerInfo;
Client()->GetServerInfo(&CurrentServerInfo);
2010-05-29 07:25:38 +00:00
CTuningParams StandardTuning;
if(CurrentServerInfo.m_aGameType[0] != '0')
{
2010-05-29 07:25:38 +00:00
if(str_comp(CurrentServerInfo.m_aGameType, "DM") != 0 && str_comp(CurrentServerInfo.m_aGameType, "TDM") != 0 && str_comp(CurrentServerInfo.m_aGameType, "CTF") != 0)
m_ServerMode = SERVERMODE_MOD;
else if(mem_comp(&StandardTuning, &m_aTuning[g_Config.m_ClDummy], 33) == 0)
2010-05-29 07:25:38 +00:00
m_ServerMode = SERVERMODE_PURE;
else
2010-05-29 07:25:38 +00:00
m_ServerMode = SERVERMODE_PUREMOD;
}
2011-01-06 03:46:10 +00:00
2012-01-08 23:49:20 +00:00
// add tuning to demo
bool AnyRecording = false;
for(int i = 0; i < RECORDER_MAX; i++)
if(DemoRecorder(i)->IsRecording())
{
AnyRecording = true;
break;
}
if(AnyRecording && mem_comp(&StandardTuning, &m_aTuning[g_Config.m_ClDummy], sizeof(CTuningParams)) != 0)
2012-01-08 23:49:20 +00:00
{
CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS);
int *pParams = (int *)&m_aTuning[g_Config.m_ClDummy];
for(unsigned i = 0; i < sizeof(m_aTuning[0]) / sizeof(int); i++)
2012-01-08 23:49:20 +00:00
Msg.AddInt(pParams[i]);
Client()->SendMsgActive(&Msg, MSGFLAG_RECORD | MSGFLAG_NOSEND);
2012-01-08 23:49:20 +00:00
}
for(int i = 0; i < 2; i++)
{
if(m_aDDRaceMsgSent[i] || !m_Snap.m_pLocalInfo)
{
continue;
}
if(i == IClient::CONN_DUMMY && !Client()->DummyConnected())
{
continue;
}
CMsgPacker Msg(NETMSGTYPE_CL_ISDDNETLEGACY, false);
2014-02-19 21:29:46 +00:00
Msg.AddInt(CLIENT_VERSIONNR);
Client()->SendMsg(i, &Msg, MSGFLAG_VITAL);
m_aDDRaceMsgSent[i] = true;
}
2011-02-23 20:22:05 +00:00
if(m_aShowOthers[g_Config.m_ClDummy] == SHOW_OTHERS_NOT_SET || (m_aShowOthers[g_Config.m_ClDummy] != SHOW_OTHERS_NOT_SET && m_aShowOthers[g_Config.m_ClDummy] != g_Config.m_ClShowOthers))
{
{
CNetMsg_Cl_ShowOthers Msg;
2014-05-24 22:59:52 +00:00
Msg.m_Show = g_Config.m_ClShowOthers;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
}
// update state
m_aShowOthers[g_Config.m_ClDummy] = g_Config.m_ClShowOthers;
}
2017-10-06 20:01:33 +00:00
2021-07-12 09:43:56 +00:00
float ZoomToSend = m_Camera.m_Zoom;
2021-09-10 22:14:02 +00:00
if(m_Camera.m_Zooming)
2021-02-20 21:51:36 +00:00
{
2021-07-12 09:43:56 +00:00
if(m_Camera.m_ZoomSmoothingTarget > m_Camera.m_Zoom) // Zooming out
ZoomToSend = m_Camera.m_ZoomSmoothingTarget;
else if(m_Camera.m_ZoomSmoothingTarget < m_Camera.m_Zoom && m_LastZoom > 0) // Zooming in
2021-02-20 21:51:36 +00:00
ZoomToSend = m_LastZoom;
}
2021-01-21 16:07:07 +00:00
if(ZoomToSend != m_LastZoom || Graphics()->ScreenAspect() != m_LastScreenAspect || (Client()->DummyConnected() && !m_LastDummyConnected))
{
CNetMsg_Cl_ShowDistance Msg;
float x, y;
2021-01-21 16:07:07 +00:00
RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), ZoomToSend, &x, &y);
Msg.m_X = x;
Msg.m_Y = y;
Client()->ChecksumData()->m_Zoom = ZoomToSend;
2020-07-08 21:25:07 +00:00
CMsgPacker Packer(Msg.MsgID(), false);
Msg.Pack(&Packer);
2021-01-21 16:07:07 +00:00
if(ZoomToSend != m_LastZoom)
Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL);
2020-07-08 21:25:07 +00:00
if(Client()->DummyConnected())
Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL);
2021-01-21 16:07:07 +00:00
m_LastZoom = ZoomToSend;
m_LastScreenAspect = Graphics()->ScreenAspect();
}
2021-01-21 16:07:07 +00:00
m_LastDummyConnected = Client()->DummyConnected();
2021-07-12 09:43:56 +00:00
m_Ghost.OnNewSnapshot();
m_RaceDemo.OnNewSnapshot();
2021-04-22 12:52:49 +00:00
// detect air jump for other players
for(int i = 0; i < MAX_CLIENTS; i++)
if(m_Snap.m_aCharacters[i].m_Active && (m_Snap.m_aCharacters[i].m_Cur.m_Jumped & 2) && !(m_Snap.m_aCharacters[i].m_Prev.m_Jumped & 2))
2021-04-22 12:52:49 +00:00
if(!Predict() || (i != m_Snap.m_LocalClientID && (!AntiPingPlayers() || i != m_PredictedDummyID)))
{
vec2 Pos = mix(vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y),
vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y),
Client()->IntraGameTick(g_Config.m_ClDummy));
2021-07-12 09:43:56 +00:00
m_Effects.AirJump(Pos);
}
static int PrevLocalID = -1;
if(m_Snap.m_LocalClientID != PrevLocalID)
m_PredictedDummyID = PrevLocalID;
PrevLocalID = m_Snap.m_LocalClientID;
m_IsDummySwapping = 0;
SnapCollectEntities(); // creates a collection that associates EntityEx snap items with the entities they belong to
// update prediction data
if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
UpdatePrediction();
}
2010-05-29 07:25:38 +00:00
void CGameClient::OnPredict()
{
2008-09-23 07:43:41 +00:00
// store the previous values so we can detect prediction errors
2010-05-29 07:25:38 +00:00
CCharacterCore BeforePrevChar = m_PredictedPrevChar;
CCharacterCore BeforeChar = m_PredictedChar;
2008-09-23 07:43:41 +00:00
// we can't predict without our own id or own character
if(m_Snap.m_LocalClientID == -1 || !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active)
2008-09-23 07:43:41 +00:00
return;
// don't predict anything if we are paused
if(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)
{
2010-05-29 07:25:38 +00:00
if(m_Snap.m_pLocalCharacter)
2014-04-14 08:56:14 +00:00
{
m_PredictedChar.Read(m_Snap.m_pLocalCharacter);
m_PredictedChar.m_ActiveWeapon = m_Snap.m_pLocalCharacter->m_Weapon;
2014-04-14 08:56:14 +00:00
}
2010-05-29 07:25:38 +00:00
if(m_Snap.m_pLocalPrevCharacter)
2014-04-14 08:56:14 +00:00
{
m_PredictedPrevChar.Read(m_Snap.m_pLocalPrevCharacter);
m_PredictedPrevChar.m_ActiveWeapon = m_Snap.m_pLocalPrevCharacter->m_Weapon;
2014-04-14 08:56:14 +00:00
}
return;
}
2008-09-23 07:43:41 +00:00
vec2 aBeforeRender[MAX_CLIENTS];
for(int i = 0; i < MAX_CLIENTS; i++)
aBeforeRender[i] = GetSmoothPos(i);
2015-01-11 16:36:38 +00:00
// init
bool Dummy = g_Config.m_ClDummy ^ m_IsDummySwapping;
m_PredictedWorld.CopyWorld(&m_GameWorld);
2021-02-07 19:51:28 +00:00
// don't predict inactive players, or entities from other teams
2008-09-23 07:43:41 +00:00
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i))
2021-02-07 19:51:28 +00:00
if((!m_Snap.m_aCharacters[i].m_Active && pChar->m_SnapTicks > 10) || IsOtherTeam(i))
pChar->Destroy();
2021-02-07 19:51:28 +00:00
CProjectile *pProjNext = 0;
for(CProjectile *pProj = (CProjectile *)m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = pProjNext)
{
pProjNext = (CProjectile *)pProj->TypeNext();
if(IsOtherTeam(pProj->GetOwner()))
{
pProj->Destroy();
}
2021-02-07 19:51:28 +00:00
}
CCharacter *pLocalChar = m_PredictedWorld.GetCharacterByID(m_Snap.m_LocalClientID);
if(!pLocalChar)
return;
CCharacter *pDummyChar = 0;
if(PredictDummy())
pDummyChar = m_PredictedWorld.GetCharacterByID(m_PredictedDummyID);
// predict
for(int Tick = Client()->GameTick(g_Config.m_ClDummy) + 1; Tick <= Client()->PredGameTick(g_Config.m_ClDummy); Tick++)
{
// fetch the previous characters
if(Tick == Client()->PredGameTick(g_Config.m_ClDummy))
{
m_PrevPredictedWorld.CopyWorld(&m_PredictedWorld);
m_PredictedPrevChar = pLocalChar->GetCore();
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i))
m_aClients[i].m_PrevPredicted = pChar->GetCore();
}
// optionally allow some movement in freeze by not predicting freeze the last one to two ticks
if(g_Config.m_ClPredictFreeze == 2 && Client()->PredGameTick(g_Config.m_ClDummy) - 1 - Client()->PredGameTick(g_Config.m_ClDummy) % 2 <= Tick)
pLocalChar->m_CanMoveInFreeze = true;
// apply inputs and tick
CNetObj_PlayerInput *pInputData = (CNetObj_PlayerInput *)Client()->GetInput(Tick, m_IsDummySwapping);
CNetObj_PlayerInput *pDummyInputData = !pDummyChar ? 0 : (CNetObj_PlayerInput *)Client()->GetInput(Tick, m_IsDummySwapping ^ 1);
bool DummyFirst = pInputData && pDummyInputData && pDummyChar->GetCID() < pLocalChar->GetCID();
if(DummyFirst)
pDummyChar->OnDirectInput(pDummyInputData);
if(pInputData)
pLocalChar->OnDirectInput(pInputData);
if(pDummyInputData && !DummyFirst)
pDummyChar->OnDirectInput(pDummyInputData);
m_PredictedWorld.m_GameTick = Tick;
if(pInputData)
pLocalChar->OnPredictedInput(pInputData);
if(pDummyInputData)
pDummyChar->OnPredictedInput(pDummyInputData);
m_PredictedWorld.Tick();
// fetch the current characters
if(Tick == Client()->PredGameTick(g_Config.m_ClDummy))
{
m_PredictedChar = pLocalChar->GetCore();
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i))
m_aClients[i].m_Predicted = pChar->GetCore();
}
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i))
{
m_aClients[i].m_aPredPos[Tick % 200] = pChar->Core()->m_Pos;
m_aClients[i].m_aPredTick[Tick % 200] = Tick;
}
// check if we want to trigger effects
if(Tick > m_aLastNewPredictedTick[Dummy])
{
m_aLastNewPredictedTick[Dummy] = Tick;
m_NewPredictedTick = true;
vec2 Pos = pLocalChar->Core()->m_Pos;
int Events = pLocalChar->Core()->m_TriggeredEvents;
if(g_Config.m_ClPredict)
if(Events & COREEVENT_AIR_JUMP)
2021-07-12 09:43:56 +00:00
m_Effects.AirJump(Pos);
if(g_Config.m_SndGame)
{
if(Events & COREEVENT_GROUND_JUMP)
2021-07-12 09:43:56 +00:00
m_Sounds.PlayAndRecord(CSounds::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, Pos);
if(Events & COREEVENT_HOOK_ATTACH_GROUND)
2021-07-12 09:43:56 +00:00
m_Sounds.PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, Pos);
if(Events & COREEVENT_HOOK_HIT_NOHOOK)
2021-07-12 09:43:56 +00:00
m_Sounds.PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos);
}
}
2021-04-22 12:52:49 +00:00
// check if we want to trigger predicted airjump for dummy
if(AntiPingPlayers() && pDummyChar && Tick > m_aLastNewPredictedTick[!Dummy])
2021-04-22 12:52:49 +00:00
{
m_aLastNewPredictedTick[!Dummy] = Tick;
2021-04-22 12:52:49 +00:00
vec2 Pos = pDummyChar->Core()->m_Pos;
2021-04-22 12:58:30 +00:00
int Events = pDummyChar->Core()->m_TriggeredEvents;
2021-04-22 12:52:49 +00:00
if(g_Config.m_ClPredict)
if(Events & COREEVENT_AIR_JUMP)
2021-07-12 09:43:56 +00:00
m_Effects.AirJump(Pos);
2021-04-22 12:52:49 +00:00
}
}
// detect mispredictions of other players and make corrections smoother when possible
static vec2 s_aLastPos[MAX_CLIENTS] = {{0, 0}};
2022-02-14 23:12:52 +00:00
static bool s_aLastActive[MAX_CLIENTS] = {false};
if(g_Config.m_ClAntiPingSmooth && Predict() && AntiPingPlayers() && m_NewTick && abs(m_PredictedTick - Client()->PredGameTick(g_Config.m_ClDummy)) <= 1 && abs(Client()->GameTick(g_Config.m_ClDummy) - Client()->PrevGameTick(g_Config.m_ClDummy)) <= 2)
{
int PredTime = clamp(Client()->GetPredictionTime(), 0, 800);
float SmoothPace = 4 - 1.5f * PredTime / 800.f; // smoothing pace (a lower value will make the smoothing quicker)
2021-06-23 05:05:49 +00:00
int64_t Len = 1000 * PredTime * SmoothPace;
2018-10-02 18:45:44 +00:00
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!m_Snap.m_aCharacters[i].m_Active || i == m_Snap.m_LocalClientID || !s_aLastActive[i])
continue;
vec2 NewPos = (m_PredictedTick == Client()->PredGameTick(g_Config.m_ClDummy)) ? m_aClients[i].m_Predicted.m_Pos : m_aClients[i].m_PrevPredicted.m_Pos;
vec2 PredErr = (s_aLastPos[i] - NewPos) / (float)minimum(Client()->GetPredictionTime(), 200);
if(in_range(length(PredErr), 0.05f, 5.f))
2018-10-02 18:45:44 +00:00
{
vec2 PredPos = mix(m_aClients[i].m_PrevPredicted.m_Pos, m_aClients[i].m_Predicted.m_Pos, Client()->PredIntraGameTick(g_Config.m_ClDummy));
vec2 CurPos = mix(
vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y),
vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y),
Client()->IntraGameTick(g_Config.m_ClDummy));
vec2 RenderDiff = PredPos - aBeforeRender[i];
vec2 PredDiff = PredPos - CurPos;
float aMixAmount[2];
for(int j = 0; j < 2; j++)
2018-10-02 18:45:44 +00:00
{
aMixAmount[j] = 1.0f;
if(fabs(PredErr[j]) > 0.05f)
2018-10-02 18:45:44 +00:00
{
aMixAmount[j] = 0.0f;
if(fabs(RenderDiff[j]) > 0.01f)
2018-10-02 18:45:44 +00:00
{
aMixAmount[j] = 1.f - clamp(RenderDiff[j] / PredDiff[j], 0.f, 1.f);
aMixAmount[j] = 1.f - powf(1.f - aMixAmount[j], 1 / 1.2f);
}
2018-10-02 18:45:44 +00:00
}
int64_t TimePassed = time_get() - m_aClients[i].m_aSmoothStart[j];
2021-06-23 05:05:49 +00:00
if(in_range(TimePassed, (int64_t)0, Len - 1))
aMixAmount[j] = minimum(aMixAmount[j], (float)(TimePassed / (double)Len));
}
for(int j = 0; j < 2; j++)
if(fabs(RenderDiff[j]) < 0.01f && fabs(PredDiff[j]) < 0.01f && fabs(m_aClients[i].m_PrevPredicted.m_Pos[j] - m_aClients[i].m_Predicted.m_Pos[j]) < 0.01f && aMixAmount[j] > aMixAmount[j ^ 1])
aMixAmount[j] = aMixAmount[j ^ 1];
for(int j = 0; j < 2; j++)
{
int64_t Remaining = minimum((1.f - aMixAmount[j]) * Len, minimum(time_freq() * 0.700f, (1.f - aMixAmount[j ^ 1]) * Len + time_freq() * 0.300f)); // don't smooth for longer than 700ms, or more than 300ms longer along one axis than the other axis
2021-06-23 05:05:49 +00:00
int64_t Start = time_get() - (Len - Remaining);
if(!in_range(Start + Len, m_aClients[i].m_aSmoothStart[j], m_aClients[i].m_aSmoothStart[j] + Len))
{
m_aClients[i].m_aSmoothStart[j] = Start;
m_aClients[i].m_aSmoothLen[j] = Len;
}
}
}
}
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Snap.m_aCharacters[i].m_Active)
2015-05-04 15:53:07 +00:00
{
s_aLastPos[i] = m_aClients[i].m_Predicted.m_Pos;
s_aLastActive[i] = true;
2015-05-04 15:53:07 +00:00
}
else
s_aLastActive[i] = false;
}
if(g_Config.m_Debug && g_Config.m_ClPredict && m_PredictedTick == Client()->PredGameTick(g_Config.m_ClDummy))
{
2010-05-29 07:25:38 +00:00
CNetObj_CharacterCore Before = {0}, Now = {0}, BeforePrev = {0}, NowPrev = {0};
BeforeChar.Write(&Before);
BeforePrevChar.Write(&BeforePrev);
m_PredictedChar.Write(&Now);
m_PredictedPrevChar.Write(&NowPrev);
2010-05-29 07:25:38 +00:00
if(mem_comp(&Before, &Now, sizeof(CNetObj_CharacterCore)) != 0)
{
Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "prediction error");
for(unsigned i = 0; i < sizeof(CNetObj_CharacterCore) / sizeof(int); i++)
2010-05-29 07:25:38 +00:00
if(((int *)&Before)[i] != ((int *)&Now)[i])
2008-09-23 07:43:41 +00:00
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), " %d %d %d (%d %d)", i, ((int *)&Before)[i], ((int *)&Now)[i], ((int *)&BeforePrev)[i], ((int *)&NowPrev)[i]);
Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
2008-09-23 07:43:41 +00:00
}
}
}
m_PredictedTick = Client()->PredGameTick(g_Config.m_ClDummy);
2017-10-06 20:01:33 +00:00
if(m_NewPredictedTick)
2021-07-12 09:43:56 +00:00
m_Ghost.OnNewPredictedSnapshot();
}
void CGameClient::OnActivateEditor()
{
OnRelease();
}
2015-05-19 22:51:02 +00:00
CGameClient::CClientStats::CClientStats()
{
Reset();
2015-05-19 22:51:02 +00:00
}
void CGameClient::CClientStats::Reset()
{
m_JoinTick = 0;
m_IngameTicks = 0;
2015-05-19 22:51:02 +00:00
m_Active = false;
m_Frags = 0;
m_Deaths = 0;
m_Suicides = 0;
m_BestSpree = 0;
m_CurrentSpree = 0;
for(int j = 0; j < NUM_WEAPONS; j++)
{
m_aFragsWith[j] = 0;
m_aDeathsFrom[j] = 0;
}
m_FlagGrabs = 0;
m_FlagCaptures = 0;
}
2021-07-12 10:04:45 +00:00
void CGameClient::CClientData::UpdateRenderInfo(bool IsTeamPlay)
{
2010-05-29 07:25:38 +00:00
m_RenderInfo = m_SkinInfo;
// force team colors
2021-07-12 10:04:45 +00:00
if(IsTeamPlay)
{
m_RenderInfo.m_CustomColoredSkin = true;
const int aTeamColors[2] = {65461, 10223541};
2011-01-03 11:50:38 +00:00
if(m_Team >= TEAM_RED && m_Team <= TEAM_BLUE)
{
m_RenderInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(aTeamColors[m_Team]));
m_RenderInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(aTeamColors[m_Team]));
}
2011-03-27 09:52:16 +00:00
else
{
2019-05-11 16:04:32 +00:00
m_RenderInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(12829350));
m_RenderInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(12829350));
2011-03-27 09:52:16 +00:00
}
}
}
void CGameClient::CClientData::Reset()
{
m_aName[0] = 0;
m_aClan[0] = 0;
m_Country = -1;
m_Team = 0;
m_Angle = 0;
m_Emoticon = 0;
m_EmoticonStartTick = -1;
m_EmoticonStartFraction = 0;
m_Active = false;
m_ChatIgnore = false;
m_EmoticonIgnore = false;
m_Friend = false;
m_Foe = false;
m_AuthLevel = AUTHED_NO;
2019-08-01 21:07:40 +00:00
m_Afk = false;
2019-08-02 16:16:58 +00:00
m_Paused = false;
m_Spec = false;
m_SkinInfo.m_BloodColor = ColorRGBA(1, 1, 1);
m_SkinInfo.m_ColorableRenderSkin.Reset();
m_SkinInfo.m_OriginalRenderSkin.Reset();
m_SkinInfo.m_CustomColoredSkin = false;
m_SkinInfo.m_ColorBody = ColorRGBA(1, 1, 1);
m_SkinInfo.m_ColorFeet = ColorRGBA(1, 1, 1);
2020-11-08 05:39:16 +00:00
m_SkinInfo.m_SkinMetrics.Reset();
m_Solo = false;
m_Jetpack = false;
m_NoCollision = false;
m_EndlessHook = false;
m_EndlessJump = false;
m_NoHammerHit = false;
m_NoGrenadeHit = false;
m_NoLaserHit = false;
m_NoShotgunHit = false;
m_NoHookHit = false;
m_Super = false;
m_HasTelegunGun = false;
m_HasTelegunGrenade = false;
m_HasTelegunLaser = false;
m_FreezeEnd = 0;
m_DeepFrozen = false;
m_LiveFrozen = false;
2019-11-09 21:49:53 +00:00
m_Evolved.m_Tick = -1;
m_SpecChar = vec2(0, 0);
m_SpecCharPresent = false;
2020-06-24 17:01:01 +00:00
mem_zero(m_aSwitchStates, sizeof(m_aSwitchStates));
2021-08-15 10:53:14 +00:00
2021-07-12 10:04:45 +00:00
UpdateRenderInfo(false);
}
2010-05-29 07:25:38 +00:00
void CGameClient::SendSwitchTeam(int Team)
{
2010-05-29 07:25:38 +00:00
CNetMsg_Cl_SetTeam Msg;
Msg.m_Team = Team;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
2013-07-11 15:25:51 +00:00
if(Team != TEAM_SPECTATORS)
2021-07-12 09:43:56 +00:00
m_Camera.OnReset();
}
2010-05-29 07:25:38 +00:00
void CGameClient::SendInfo(bool Start)
{
2010-05-29 07:25:38 +00:00
if(Start)
{
2010-05-29 07:25:38 +00:00
CNetMsg_Cl_StartInfo Msg;
Msg.m_pName = Client()->PlayerName();
Msg.m_pClan = g_Config.m_PlayerClan;
Msg.m_Country = g_Config.m_PlayerCountry;
Msg.m_pSkin = g_Config.m_ClPlayerSkin;
Msg.m_UseCustomColor = g_Config.m_ClPlayerUseCustomColor;
2019-05-01 20:35:29 +00:00
Msg.m_ColorBody = g_Config.m_ClPlayerColorBody;
Msg.m_ColorFeet = g_Config.m_ClPlayerColorFeet;
CMsgPacker Packer(Msg.MsgID(), false);
2014-04-28 13:19:57 +00:00
Msg.Pack(&Packer);
Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL);
m_aCheckInfo[0] = -1;
}
else
{
2010-05-29 07:25:38 +00:00
CNetMsg_Cl_ChangeInfo Msg;
Msg.m_pName = Client()->PlayerName();
Msg.m_pClan = g_Config.m_PlayerClan;
Msg.m_Country = g_Config.m_PlayerCountry;
Msg.m_pSkin = g_Config.m_ClPlayerSkin;
Msg.m_UseCustomColor = g_Config.m_ClPlayerUseCustomColor;
2019-05-01 20:35:29 +00:00
Msg.m_ColorBody = g_Config.m_ClPlayerColorBody;
Msg.m_ColorFeet = g_Config.m_ClPlayerColorFeet;
CMsgPacker Packer(Msg.MsgID(), false);
2014-04-28 13:19:57 +00:00
Msg.Pack(&Packer);
Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL);
m_aCheckInfo[0] = Client()->GameTickSpeed();
}
}
2014-04-28 13:19:57 +00:00
void CGameClient::SendDummyInfo(bool Start)
{
if(Start)
{
CNetMsg_Cl_StartInfo Msg;
Msg.m_pName = Client()->DummyName();
Msg.m_pClan = g_Config.m_ClDummyClan;
Msg.m_Country = g_Config.m_ClDummyCountry;
Msg.m_pSkin = g_Config.m_ClDummySkin;
Msg.m_UseCustomColor = g_Config.m_ClDummyUseCustomColor;
2019-05-01 20:35:29 +00:00
Msg.m_ColorBody = g_Config.m_ClDummyColorBody;
Msg.m_ColorFeet = g_Config.m_ClDummyColorFeet;
CMsgPacker Packer(Msg.MsgID(), false);
2014-04-28 13:19:57 +00:00
Msg.Pack(&Packer);
Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL);
m_aCheckInfo[1] = -1;
2014-04-28 13:19:57 +00:00
}
else
{
CNetMsg_Cl_ChangeInfo Msg;
Msg.m_pName = Client()->DummyName();
Msg.m_pClan = g_Config.m_ClDummyClan;
Msg.m_Country = g_Config.m_ClDummyCountry;
Msg.m_pSkin = g_Config.m_ClDummySkin;
Msg.m_UseCustomColor = g_Config.m_ClDummyUseCustomColor;
2019-05-01 20:35:29 +00:00
Msg.m_ColorBody = g_Config.m_ClDummyColorBody;
Msg.m_ColorFeet = g_Config.m_ClDummyColorFeet;
CMsgPacker Packer(Msg.MsgID(), false);
2014-04-28 13:19:57 +00:00
Msg.Pack(&Packer);
Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL);
m_aCheckInfo[1] = Client()->GameTickSpeed();
2014-04-28 13:19:57 +00:00
}
}
void CGameClient::SendKill(int ClientID)
{
2010-05-29 07:25:38 +00:00
CNetMsg_Cl_Kill Msg;
Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL);
2015-02-18 13:23:25 +00:00
if(g_Config.m_ClDummyCopyMoves)
{
CMsgPacker MsgP(NETMSGTYPE_CL_KILL, false);
Client()->SendMsg(!g_Config.m_ClDummy, &MsgP, MSGFLAG_VITAL);
2015-02-18 13:23:25 +00:00
}
}
void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData)
{
((CGameClient *)pUserData)->SendSwitchTeam(pResult->GetInteger(0));
}
void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData)
{
((CGameClient *)pUserData)->SendKill(-1);
}
2010-05-29 07:25:38 +00:00
void CGameClient::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
2010-05-29 07:25:38 +00:00
if(pResult->NumArguments())
((CGameClient *)pUserData)->SendInfo(false);
2009-10-27 14:38:53 +00:00
}
2014-04-28 13:19:57 +00:00
void CGameClient::ConchainSpecialDummyInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
if(pResult->NumArguments())
((CGameClient *)pUserData)->SendDummyInfo(false);
2014-04-28 13:19:57 +00:00
}
void CGameClient::ConchainSpecialDummy(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
if(pResult->NumArguments())
if(g_Config.m_ClDummy && !((CGameClient *)pUserData)->Client()->DummyConnected())
g_Config.m_ClDummy = 0;
}
void CGameClient::ConchainClTextEntitiesSize(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
if(pResult->NumArguments())
{
CGameClient *pGameClient = (CGameClient *)pUserData;
2021-07-12 09:43:56 +00:00
pGameClient->m_MapImages.SetTextureScale(g_Config.m_ClTextEntitiesSize);
}
}
2010-05-29 07:25:38 +00:00
IGameClient *CreateGameClient()
2009-10-27 14:38:53 +00:00
{
2021-07-12 10:04:45 +00:00
return new CGameClient();
}
2013-08-23 23:50:35 +00:00
int CGameClient::IntersectCharacter(vec2 HookPos, vec2 NewPos, vec2 &NewPos2, int ownID)
2013-08-23 23:50:35 +00:00
{
2014-01-27 04:06:23 +00:00
float Distance = 0.0f;
2013-08-23 23:50:35 +00:00
int ClosestID = -1;
CClientData &OwnClientData = m_aClients[ownID];
2014-02-08 20:55:08 +00:00
2019-04-21 17:35:07 +00:00
for(int i = 0; i < MAX_CLIENTS; i++)
2013-08-23 23:50:35 +00:00
{
2019-06-09 17:26:20 +00:00
if(i == ownID)
continue;
CClientData &cData = m_aClients[i];
2019-06-09 17:26:20 +00:00
if(!cData.m_Active)
continue;
2014-01-11 23:45:25 +00:00
CNetObj_Character Prev = m_Snap.m_aCharacters[i].m_Prev;
CNetObj_Character Player = m_Snap.m_aCharacters[i].m_Cur;
2013-08-23 23:50:35 +00:00
vec2 Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), Client()->IntraGameTick(g_Config.m_ClDummy));
2013-08-23 23:50:35 +00:00
2019-06-09 17:26:20 +00:00
bool IsOneSuper = cData.m_Super || OwnClientData.m_Super;
bool IsOneSolo = cData.m_Solo || OwnClientData.m_Solo;
2019-04-21 17:35:07 +00:00
2019-06-09 17:26:20 +00:00
if(!IsOneSuper && (!m_Teams.SameTeam(i, ownID) || IsOneSolo || OwnClientData.m_NoHookHit))
2014-01-11 23:45:25 +00:00
continue;
2013-08-23 23:50:35 +00:00
vec2 ClosestPoint;
if(closest_point_on_line(HookPos, NewPos, Position, ClosestPoint))
2013-08-23 23:50:35 +00:00
{
if(distance(Position, ClosestPoint) < CCharacterCore::PhysicalSize() + 2.0f)
2013-08-23 23:50:35 +00:00
{
if(ClosestID == -1 || distance(HookPos, Position) < Distance)
{
NewPos2 = ClosestPoint;
ClosestID = i;
Distance = distance(HookPos, Position);
}
2013-08-23 23:50:35 +00:00
}
}
}
2014-10-23 15:31:29 +00:00
2013-08-23 23:50:35 +00:00
return ClosestID;
}
2019-04-25 16:49:27 +00:00
ColorRGBA CalculateNameColor(ColorHSLA TextColorHSL)
{
2019-04-25 16:49:27 +00:00
return color_cast<ColorRGBA>(ColorHSLA(TextColorHSL.h, TextColorHSL.s * 0.68f, TextColorHSL.l * 0.81f));
}
2019-04-21 17:35:07 +00:00
void CGameClient::UpdatePrediction()
{
m_GameWorld.m_WorldConfig.m_IsVanilla = m_GameInfo.m_PredictVanilla;
m_GameWorld.m_WorldConfig.m_IsDDRace = m_GameInfo.m_PredictDDRace;
m_GameWorld.m_WorldConfig.m_IsFNG = m_GameInfo.m_PredictFNG;
2022-04-25 13:57:10 +00:00
m_GameWorld.m_WorldConfig.m_PredictDDRace = m_GameInfo.m_PredictDDRace;
m_GameWorld.m_WorldConfig.m_PredictTiles = m_GameInfo.m_PredictDDRace && m_GameInfo.m_PredictDDRaceTiles;
m_GameWorld.m_WorldConfig.m_UseTuneZones = m_GameInfo.m_PredictDDRaceTiles;
m_GameWorld.m_WorldConfig.m_PredictFreeze = g_Config.m_ClPredictFreeze;
m_GameWorld.m_WorldConfig.m_PredictWeapons = AntiPingWeapons();
m_GameWorld.m_WorldConfig.m_BugDDRaceInput = m_GameInfo.m_BugDDRaceInput;
2021-09-15 20:14:16 +00:00
// always update default tune zone, even without character
if(!m_GameWorld.m_WorldConfig.m_UseTuneZones)
m_GameWorld.TuningList()[0] = m_aTuning[g_Config.m_ClDummy];
2021-09-15 20:14:16 +00:00
if(!m_Snap.m_pLocalCharacter)
{
if(CCharacter *pLocalChar = m_GameWorld.GetCharacterByID(m_Snap.m_LocalClientID))
pLocalChar->Destroy();
return;
}
if(m_Snap.m_pLocalCharacter->m_AmmoCount > 0 && m_Snap.m_pLocalCharacter->m_Weapon != WEAPON_NINJA)
m_GameWorld.m_WorldConfig.m_InfiniteAmmo = false;
m_GameWorld.m_WorldConfig.m_IsSolo = !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_HasExtendedData && !m_aTuning[g_Config.m_ClDummy].m_PlayerCollision && !m_aTuning[g_Config.m_ClDummy].m_PlayerHooking;
// update the tuning/tunezone at the local character position with the latest tunings received before the new snapshot
2021-05-12 16:57:50 +00:00
vec2 LocalCharPos = vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y);
m_GameWorld.m_Core.m_aTuning[g_Config.m_ClDummy] = m_aTuning[g_Config.m_ClDummy];
2021-05-12 16:57:50 +00:00
int TuneZone = 0;
if(m_GameWorld.m_WorldConfig.m_UseTuneZones)
2021-05-12 16:57:50 +00:00
{
TuneZone = Collision()->IsTune(Collision()->GetMapIndex(LocalCharPos));
if(TuneZone != m_aLocalTuneZone[g_Config.m_ClDummy])
2021-05-12 16:57:50 +00:00
{
// our tunezone changed, expecting tuning message
m_aLocalTuneZone[g_Config.m_ClDummy] = m_aExpectingTuningForZone[g_Config.m_ClDummy] = TuneZone;
m_aExpectingTuningSince[g_Config.m_ClDummy] = 0;
2021-05-12 16:57:50 +00:00
}
if(m_aExpectingTuningForZone[g_Config.m_ClDummy] >= 0)
2021-05-12 16:57:50 +00:00
{
if(m_aReceivedTuning[g_Config.m_ClDummy])
2021-05-12 16:57:50 +00:00
{
dbg_msg("tunezone", "got tuning for zone %d", m_aExpectingTuningForZone[g_Config.m_ClDummy]);
m_GameWorld.TuningList()[m_aExpectingTuningForZone[g_Config.m_ClDummy]] = m_aTuning[g_Config.m_ClDummy];
m_aReceivedTuning[g_Config.m_ClDummy] = false;
m_aExpectingTuningForZone[g_Config.m_ClDummy] = -1;
2021-05-12 16:57:50 +00:00
}
else if(m_aExpectingTuningSince[g_Config.m_ClDummy] >= 5)
2021-05-12 16:57:50 +00:00
{
// if we are expecting tuning for more than 10 snaps (less than a quarter of a second)
// it is probably dropped or it was received out of order
// or applied to another tunezone.
// we need to fallback to current tuning to fix ourselves.
m_aExpectingTuningForZone[g_Config.m_ClDummy] = -1;
m_aExpectingTuningSince[g_Config.m_ClDummy] = 0;
m_aReceivedTuning[g_Config.m_ClDummy] = false;
2021-05-12 16:57:50 +00:00
dbg_msg("tunezone", "the tuning was missed");
}
else
{
// if we are expecting tuning and have not received one yet.
// do not update any tuning, so we don't apply it to the wrong tunezone.
dbg_msg("tunezone", "waiting for tuning for zone %d", m_aExpectingTuningForZone[g_Config.m_ClDummy]);
m_aExpectingTuningSince[g_Config.m_ClDummy]++;
2021-05-12 16:57:50 +00:00
}
}
else
{
// if we have processed what we need, and the tuning is still wrong due to out of order messege
// fix our tuning by using the current one
m_GameWorld.TuningList()[TuneZone] = m_aTuning[g_Config.m_ClDummy];
m_aExpectingTuningSince[g_Config.m_ClDummy] = 0;
m_aReceivedTuning[g_Config.m_ClDummy] = false;
2021-05-12 16:57:50 +00:00
}
}
// if ddnetcharacter is available, ignore server-wide tunings for hook and collision
if(m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_HasExtendedData)
{
m_GameWorld.m_Core.m_aTuning[g_Config.m_ClDummy].m_PlayerCollision = 1;
m_GameWorld.m_Core.m_aTuning[g_Config.m_ClDummy].m_PlayerHooking = 1;
}
CCharacter *pLocalChar = m_GameWorld.GetCharacterByID(m_Snap.m_LocalClientID);
CCharacter *pDummyChar = 0;
if(PredictDummy())
pDummyChar = m_GameWorld.GetCharacterByID(m_PredictedDummyID);
// update strong and weak hook
if(pLocalChar && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK && (m_aTuning[g_Config.m_ClDummy].m_PlayerCollision || m_aTuning[g_Config.m_ClDummy].m_PlayerHooking))
{
if(m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_HasExtendedData)
{
int aIDs[MAX_CLIENTS];
2020-10-26 14:14:07 +00:00
for(int &ID : aIDs)
ID = -1;
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i))
aIDs[pChar->GetStrongWeakID()] = i;
2020-10-26 14:14:07 +00:00
for(int ID : aIDs)
if(ID >= 0)
m_CharOrder.GiveStrong(ID);
}
else
{
// manual detection
DetectStrongHook();
}
for(int i : m_CharOrder.m_IDs)
{
if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i))
{
m_GameWorld.RemoveEntity(pChar);
m_GameWorld.InsertEntity(pChar);
}
}
}
// advance the gameworld to the current gametick
if(pLocalChar && abs(m_GameWorld.GameTick() - Client()->GameTick(g_Config.m_ClDummy)) < SERVER_TICK_SPEED)
{
for(int Tick = m_GameWorld.GameTick() + 1; Tick <= Client()->GameTick(g_Config.m_ClDummy); Tick++)
{
CNetObj_PlayerInput *pInput = (CNetObj_PlayerInput *)Client()->GetInput(Tick);
CNetObj_PlayerInput *pDummyInput = 0;
if(pDummyChar)
pDummyInput = (CNetObj_PlayerInput *)Client()->GetInput(Tick, 1);
if(pInput)
pLocalChar->OnDirectInput(pInput);
if(pDummyInput)
pDummyChar->OnDirectInput(pDummyInput);
m_GameWorld.m_GameTick = Tick;
if(pInput)
pLocalChar->OnPredictedInput(pInput);
if(pDummyInput)
pDummyChar->OnPredictedInput(pDummyInput);
m_GameWorld.Tick();
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i))
{
m_aClients[i].m_aPredPos[Tick % 200] = pChar->Core()->m_Pos;
m_aClients[i].m_aPredTick[Tick % 200] = Tick;
}
}
}
else
{
// skip to current gametick
m_GameWorld.m_GameTick = Client()->GameTick(g_Config.m_ClDummy);
if(pLocalChar)
if(CNetObj_PlayerInput *pInput = (CNetObj_PlayerInput *)Client()->GetInput(Client()->GameTick(g_Config.m_ClDummy)))
pLocalChar->SetInput(pInput);
if(pDummyChar)
if(CNetObj_PlayerInput *pInput = (CNetObj_PlayerInput *)Client()->GetInput(Client()->GameTick(g_Config.m_ClDummy), 1))
pDummyChar->SetInput(pInput);
}
for(int i = 0; i < MAX_CLIENTS; i++)
if(CCharacter *pChar = m_GameWorld.GetCharacterByID(i))
{
m_aClients[i].m_aPredPos[Client()->GameTick(g_Config.m_ClDummy) % 200] = pChar->Core()->m_Pos;
m_aClients[i].m_aPredTick[Client()->GameTick(g_Config.m_ClDummy) % 200] = Client()->GameTick(g_Config.m_ClDummy);
}
// update the local gameworld with the new snapshot
m_GameWorld.m_Teams = m_Teams;
m_GameWorld.NetObjBegin();
for(int i = 0; i < MAX_CLIENTS; i++)
if(m_Snap.m_aCharacters[i].m_Active)
{
bool IsLocal = (i == m_Snap.m_LocalClientID || (PredictDummy() && i == m_PredictedDummyID));
int GameTeam = (m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) ? m_aClients[i].m_Team : i;
m_GameWorld.NetCharAdd(i, &m_Snap.m_aCharacters[i].m_Cur,
m_Snap.m_aCharacters[i].m_HasExtendedData ? &m_Snap.m_aCharacters[i].m_ExtendedData : 0,
GameTeam, IsLocal);
}
2021-02-07 19:51:28 +00:00
for(const CSnapEntities &EntData : SnapEntities())
m_GameWorld.NetObjAdd(EntData.m_Item.m_ID, EntData.m_Item.m_Type, EntData.m_pData, EntData.m_pDataEx);
m_GameWorld.NetObjEnd(m_Snap.m_LocalClientID);
}
void CGameClient::UpdateRenderedCharacters()
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!m_Snap.m_aCharacters[i].m_Active)
continue;
m_aClients[i].m_RenderCur = m_Snap.m_aCharacters[i].m_Cur;
m_aClients[i].m_RenderPrev = m_Snap.m_aCharacters[i].m_Prev;
m_aClients[i].m_IsPredicted = false;
2019-05-12 23:45:49 +00:00
m_aClients[i].m_IsPredictedLocal = false;
vec2 UnpredPos = mix(
vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y),
vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y),
Client()->IntraGameTick(g_Config.m_ClDummy));
vec2 Pos = UnpredPos;
2021-02-07 19:51:28 +00:00
if(Predict() && (i == m_Snap.m_LocalClientID || (AntiPingPlayers() && !IsOtherTeam(i))))
{
m_aClients[i].m_Predicted.Write(&m_aClients[i].m_RenderCur);
m_aClients[i].m_PrevPredicted.Write(&m_aClients[i].m_RenderPrev);
m_aClients[i].m_IsPredicted = true;
Pos = mix(
vec2(m_aClients[i].m_RenderPrev.m_X, m_aClients[i].m_RenderPrev.m_Y),
vec2(m_aClients[i].m_RenderCur.m_X, m_aClients[i].m_RenderCur.m_Y),
m_aClients[i].m_IsPredicted ? Client()->PredIntraGameTick(g_Config.m_ClDummy) : Client()->IntraGameTick(g_Config.m_ClDummy));
if(i == m_Snap.m_LocalClientID)
2019-05-12 23:45:49 +00:00
{
m_aClients[i].m_IsPredictedLocal = true;
2019-05-12 23:45:49 +00:00
CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i);
if(pChar && AntiPingGunfire() && ((pChar->m_NinjaJetpack && pChar->m_FreezeTime == 0) || m_Snap.m_aCharacters[i].m_Cur.m_Weapon != WEAPON_NINJA || m_Snap.m_aCharacters[i].m_Cur.m_Weapon == m_aClients[i].m_Predicted.m_ActiveWeapon))
2019-05-12 23:45:49 +00:00
{
m_aClients[i].m_RenderCur.m_AttackTick = pChar->GetAttackTick();
if(m_Snap.m_aCharacters[i].m_Cur.m_Weapon != WEAPON_NINJA && !(pChar->m_NinjaJetpack && pChar->Core()->m_ActiveWeapon == WEAPON_GUN))
m_aClients[i].m_RenderCur.m_Weapon = m_aClients[i].m_Predicted.m_ActiveWeapon;
}
}
else
{
// use unpredicted values for other players
m_aClients[i].m_RenderPrev.m_Angle = m_Snap.m_aCharacters[i].m_Prev.m_Angle;
m_aClients[i].m_RenderCur.m_Angle = m_Snap.m_aCharacters[i].m_Cur.m_Angle;
if(g_Config.m_ClAntiPingSmooth)
Pos = GetSmoothPos(i);
}
}
m_Snap.m_aCharacters[i].m_Position = Pos;
m_aClients[i].m_RenderPos = Pos;
if(Predict() && i == m_Snap.m_LocalClientID)
m_LocalCharacterPos = Pos;
}
}
void CGameClient::DetectStrongHook()
2015-01-11 16:36:38 +00:00
{
static int s_aLastUpdateTick[MAX_CLIENTS] = {0};
// attempt to detect strong/weak between players
for(int FromPlayer = 0; FromPlayer < MAX_CLIENTS; FromPlayer++)
2015-01-11 16:36:38 +00:00
{
if(!m_Snap.m_aCharacters[FromPlayer].m_Active)
continue;
int ToPlayer = m_Snap.m_aCharacters[FromPlayer].m_Prev.m_HookedPlayer;
if(ToPlayer < 0 || ToPlayer >= MAX_CLIENTS || !m_Snap.m_aCharacters[ToPlayer].m_Active || ToPlayer != m_Snap.m_aCharacters[FromPlayer].m_Cur.m_HookedPlayer)
continue;
if(abs(minimum(s_aLastUpdateTick[ToPlayer], s_aLastUpdateTick[FromPlayer]) - Client()->GameTick(g_Config.m_ClDummy)) < SERVER_TICK_SPEED / 4)
continue;
if(m_Snap.m_aCharacters[FromPlayer].m_Prev.m_Direction != m_Snap.m_aCharacters[FromPlayer].m_Cur.m_Direction || m_Snap.m_aCharacters[ToPlayer].m_Prev.m_Direction != m_Snap.m_aCharacters[ToPlayer].m_Cur.m_Direction)
continue;
CCharacter *pFromCharWorld = m_GameWorld.GetCharacterByID(FromPlayer);
CCharacter *pToCharWorld = m_GameWorld.GetCharacterByID(ToPlayer);
if(!pFromCharWorld || !pToCharWorld)
continue;
s_aLastUpdateTick[ToPlayer] = s_aLastUpdateTick[FromPlayer] = Client()->GameTick(g_Config.m_ClDummy);
float aPredictErr[2];
CCharacterCore ToCharCur;
ToCharCur.Read(&m_Snap.m_aCharacters[ToPlayer].m_Cur);
CWorldCore World;
World.m_aTuning[g_Config.m_ClDummy] = m_aTuning[g_Config.m_ClDummy];
2015-01-11 16:36:38 +00:00
for(int dir = 0; dir < 2; dir++)
{
CCharacterCore ToChar = pFromCharWorld->GetCore();
ToChar.Init(&World, Collision(), &m_Teams);
World.m_apCharacters[ToPlayer] = &ToChar;
ToChar.Read(&m_Snap.m_aCharacters[ToPlayer].m_Prev);
2015-01-11 16:36:38 +00:00
CCharacterCore FromChar = pFromCharWorld->GetCore();
FromChar.Init(&World, Collision(), &m_Teams);
World.m_apCharacters[FromPlayer] = &FromChar;
FromChar.Read(&m_Snap.m_aCharacters[FromPlayer].m_Prev);
2015-01-11 16:36:38 +00:00
for(int Tick = Client()->PrevGameTick(g_Config.m_ClDummy); Tick < Client()->GameTick(g_Config.m_ClDummy); Tick++)
2015-01-11 16:36:38 +00:00
{
if(dir == 0)
{
FromChar.Tick(false);
ToChar.Tick(false);
2015-01-11 16:36:38 +00:00
}
else
{
ToChar.Tick(false);
FromChar.Tick(false);
2015-01-11 16:36:38 +00:00
}
FromChar.Move();
FromChar.Quantize();
ToChar.Move();
ToChar.Quantize();
2015-01-11 16:36:38 +00:00
}
aPredictErr[dir] = distance(ToChar.m_Vel, ToCharCur.m_Vel);
2015-01-11 16:36:38 +00:00
}
const float LOW = 0.0001f;
const float HIGH = 0.07f;
if(aPredictErr[1] < LOW && aPredictErr[0] > HIGH)
{
if(m_CharOrder.HasStrongAgainst(ToPlayer, FromPlayer))
{
if(ToPlayer != m_Snap.m_LocalClientID)
m_CharOrder.GiveWeak(ToPlayer);
else
m_CharOrder.GiveStrong(FromPlayer);
}
}
else if(aPredictErr[0] < LOW && aPredictErr[1] > HIGH)
{
if(m_CharOrder.HasStrongAgainst(FromPlayer, ToPlayer))
{
if(ToPlayer != m_Snap.m_LocalClientID)
m_CharOrder.GiveStrong(ToPlayer);
else
m_CharOrder.GiveWeak(FromPlayer);
}
}
2015-01-11 16:36:38 +00:00
}
}
2017-03-06 13:04:09 +00:00
vec2 CGameClient::GetSmoothPos(int ClientID)
2017-03-06 13:04:09 +00:00
{
vec2 Pos = mix(m_aClients[ClientID].m_PrevPredicted.m_Pos, m_aClients[ClientID].m_Predicted.m_Pos, Client()->PredIntraGameTick(g_Config.m_ClDummy));
2021-06-23 05:05:49 +00:00
int64_t Now = time_get();
for(int i = 0; i < 2; i++)
{
int64_t Len = clamp(m_aClients[ClientID].m_aSmoothLen[i], (int64_t)1, time_freq());
int64_t TimePassed = Now - m_aClients[ClientID].m_aSmoothStart[i];
2021-06-23 05:05:49 +00:00
if(in_range(TimePassed, (int64_t)0, Len - 1))
{
float MixAmount = 1.f - powf(1.f - TimePassed / (float)Len, 1.2f);
int SmoothTick;
float SmoothIntra;
Client()->GetSmoothTick(&SmoothTick, &SmoothIntra, MixAmount);
if(SmoothTick > 0 && m_aClients[ClientID].m_aPredTick[(SmoothTick - 1) % 200] >= Client()->PrevGameTick(g_Config.m_ClDummy) && m_aClients[ClientID].m_aPredTick[SmoothTick % 200] <= Client()->PredGameTick(g_Config.m_ClDummy))
Pos[i] = mix(m_aClients[ClientID].m_aPredPos[(SmoothTick - 1) % 200][i], m_aClients[ClientID].m_aPredPos[SmoothTick % 200][i], SmoothIntra);
}
}
return Pos;
2017-03-06 13:04:09 +00:00
}
void CGameClient::Echo(const char *pString)
{
2021-07-12 09:43:56 +00:00
m_Chat.Echo(pString);
}
2019-07-16 20:06:57 +00:00
bool CGameClient::IsOtherTeam(int ClientID)
{
bool Local = m_Snap.m_LocalClientID == ClientID;
if(m_Snap.m_LocalClientID < 0)
return false;
2022-03-14 12:25:47 +00:00
else if((m_Snap.m_SpecInfo.m_Active && m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) || ClientID < 0)
2019-07-16 20:06:57 +00:00
return false;
else if(m_Snap.m_SpecInfo.m_Active && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
{
if(m_Teams.Team(ClientID) == TEAM_SUPER || m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID) == TEAM_SUPER)
return false;
2019-07-16 20:06:57 +00:00
return m_Teams.Team(ClientID) != m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID);
}
2019-07-16 20:06:57 +00:00
else if((m_aClients[m_Snap.m_LocalClientID].m_Solo || m_aClients[ClientID].m_Solo) && !Local)
return true;
if(m_Teams.Team(ClientID) == TEAM_SUPER || m_Teams.Team(m_Snap.m_LocalClientID) == TEAM_SUPER)
return false;
2019-07-16 20:06:57 +00:00
return m_Teams.Team(ClientID) != m_Teams.Team(m_Snap.m_LocalClientID);
}
2019-09-08 22:53:07 +00:00
int CGameClient::SwitchStateTeam()
{
if(m_aSwitchStateTeam[g_Config.m_ClDummy] >= 0)
return m_aSwitchStateTeam[g_Config.m_ClDummy];
else if(m_Snap.m_LocalClientID < 0)
return 0;
else if(m_Snap.m_SpecInfo.m_Active && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
return m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID);
return m_Teams.Team(m_Snap.m_LocalClientID);
}
bool CGameClient::IsLocalCharSuper()
{
if(m_Snap.m_LocalClientID < 0)
2022-02-14 23:12:52 +00:00
return false;
return m_aClients[m_Snap.m_LocalClientID].m_Super;
}
2020-09-26 07:37:35 +00:00
void CGameClient::LoadGameSkin(const char *pPath, bool AsDir)
{
if(m_GameSkinLoaded)
2020-09-26 07:37:35 +00:00
{
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteHealthFull);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteHealthEmpty);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteArmorFull);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteArmorEmpty);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponHammerCursor);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponGunCursor);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponShotgunCursor);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponGrenadeCursor);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponNinjaCursor);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponLaserCursor);
for(auto &SpriteWeaponCursor : m_GameSkin.m_aSpriteWeaponCursors)
{
2020-10-26 14:14:07 +00:00
SpriteWeaponCursor = IGraphics::CTextureHandle();
}
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteHookChain);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteHookHead);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponHammer);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponGun);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponShotgun);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponGrenade);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponNinja);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponLaser);
for(auto &SpriteWeapon : m_GameSkin.m_aSpriteWeapons)
{
2020-10-26 14:14:07 +00:00
SpriteWeapon = IGraphics::CTextureHandle();
}
for(auto &SpriteParticle : m_GameSkin.m_aSpriteParticles)
{
Graphics()->UnloadTexture(&SpriteParticle);
}
for(auto &SpriteStar : m_GameSkin.m_aSpriteStars)
{
Graphics()->UnloadTexture(&SpriteStar);
}
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponGunProjectile);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponShotgunProjectile);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponGrenadeProjectile);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponHammerProjectile);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponNinjaProjectile);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteWeaponLaserProjectile);
for(auto &SpriteWeaponProjectile : m_GameSkin.m_aSpriteWeaponProjectiles)
{
2020-10-26 14:14:07 +00:00
SpriteWeaponProjectile = IGraphics::CTextureHandle();
}
for(int i = 0; i < 3; ++i)
{
Graphics()->UnloadTexture(&m_GameSkin.m_aSpriteWeaponGunMuzzles[i]);
Graphics()->UnloadTexture(&m_GameSkin.m_aSpriteWeaponShotgunMuzzles[i]);
Graphics()->UnloadTexture(&m_GameSkin.m_aaSpriteWeaponNinjaMuzzles[i]);
for(auto &SpriteWeaponsMuzzle : m_GameSkin.m_aaSpriteWeaponsMuzzles)
{
2020-10-26 14:14:07 +00:00
SpriteWeaponsMuzzle[i] = IGraphics::CTextureHandle();
}
}
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupHealth);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupArmor);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupArmorShotgun);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupArmorGrenade);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupArmorLaser);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupArmorNinja);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupGrenade);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupShotgun);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupLaser);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupNinja);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupGun);
Graphics()->UnloadTexture(&m_GameSkin.m_SpritePickupHammer);
for(auto &SpritePickupWeapon : m_GameSkin.m_aSpritePickupWeapons)
{
2020-10-26 14:14:07 +00:00
SpritePickupWeapon = IGraphics::CTextureHandle();
}
for(auto &SpritePickupWeaponArmor : m_GameSkin.m_aSpritePickupWeaponArmor)
{
SpritePickupWeaponArmor = IGraphics::CTextureHandle();
}
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteFlagBlue);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteFlagRed);
if(m_GameSkin.IsSixup())
{
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteNinjaBarFullLeft);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteNinjaBarFull);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteNinjaBarEmpty);
Graphics()->UnloadTexture(&m_GameSkin.m_SpriteNinjaBarEmptyRight);
}
m_GameSkinLoaded = false;
2020-09-26 07:37:35 +00:00
}
char aPath[IO_MAX_PATH_LENGTH];
2020-09-26 07:37:35 +00:00
bool IsDefault = false;
if(str_comp(pPath, "default") == 0)
{
str_format(aPath, sizeof(aPath), "%s", g_pData->m_aImages[IMAGE_GAME].m_pFilename);
IsDefault = true;
}
else
{
if(AsDir)
str_format(aPath, sizeof(aPath), "assets/game/%s/%s", pPath, g_pData->m_aImages[IMAGE_GAME].m_pFilename);
else
str_format(aPath, sizeof(aPath), "assets/game/%s.png", pPath);
}
CImageInfo ImgInfo;
bool PngLoaded = Graphics()->LoadPNG(&ImgInfo, aPath, IStorage::TYPE_ALL);
if(!PngLoaded && !IsDefault)
{
if(AsDir)
LoadGameSkin("default");
else
LoadGameSkin(pPath, true);
}
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_HEALTH_FULL].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_HEALTH_FULL].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRGBA(aPath, ImgInfo))
2020-09-26 07:37:35 +00:00
{
m_GameSkin.m_SpriteHealthFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HEALTH_FULL]);
m_GameSkin.m_SpriteHealthEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HEALTH_EMPTY]);
m_GameSkin.m_SpriteArmorFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_ARMOR_FULL]);
m_GameSkin.m_SpriteArmorEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_ARMOR_EMPTY]);
m_GameSkin.m_SpriteWeaponHammerCursor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_HAMMER_CURSOR]);
m_GameSkin.m_SpriteWeaponGunCursor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GUN_CURSOR]);
m_GameSkin.m_SpriteWeaponShotgunCursor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_SHOTGUN_CURSOR]);
m_GameSkin.m_SpriteWeaponGrenadeCursor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GRENADE_CURSOR]);
m_GameSkin.m_SpriteWeaponNinjaCursor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_NINJA_CURSOR]);
m_GameSkin.m_SpriteWeaponLaserCursor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_LASER_CURSOR]);
m_GameSkin.m_aSpriteWeaponCursors[0] = m_GameSkin.m_SpriteWeaponHammerCursor;
m_GameSkin.m_aSpriteWeaponCursors[1] = m_GameSkin.m_SpriteWeaponGunCursor;
m_GameSkin.m_aSpriteWeaponCursors[2] = m_GameSkin.m_SpriteWeaponShotgunCursor;
m_GameSkin.m_aSpriteWeaponCursors[3] = m_GameSkin.m_SpriteWeaponGrenadeCursor;
m_GameSkin.m_aSpriteWeaponCursors[4] = m_GameSkin.m_SpriteWeaponLaserCursor;
m_GameSkin.m_aSpriteWeaponCursors[5] = m_GameSkin.m_SpriteWeaponNinjaCursor;
// weapons and hook
m_GameSkin.m_SpriteHookChain = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HOOK_CHAIN]);
m_GameSkin.m_SpriteHookHead = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HOOK_HEAD]);
m_GameSkin.m_SpriteWeaponHammer = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_HAMMER_BODY]);
m_GameSkin.m_SpriteWeaponGun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GUN_BODY]);
m_GameSkin.m_SpriteWeaponShotgun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_SHOTGUN_BODY]);
m_GameSkin.m_SpriteWeaponGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GRENADE_BODY]);
m_GameSkin.m_SpriteWeaponNinja = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_NINJA_BODY]);
m_GameSkin.m_SpriteWeaponLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_LASER_BODY]);
m_GameSkin.m_aSpriteWeapons[0] = m_GameSkin.m_SpriteWeaponHammer;
m_GameSkin.m_aSpriteWeapons[1] = m_GameSkin.m_SpriteWeaponGun;
m_GameSkin.m_aSpriteWeapons[2] = m_GameSkin.m_SpriteWeaponShotgun;
m_GameSkin.m_aSpriteWeapons[3] = m_GameSkin.m_SpriteWeaponGrenade;
m_GameSkin.m_aSpriteWeapons[4] = m_GameSkin.m_SpriteWeaponLaser;
m_GameSkin.m_aSpriteWeapons[5] = m_GameSkin.m_SpriteWeaponNinja;
// particles
for(int i = 0; i < 9; ++i)
{
m_GameSkin.m_aSpriteParticles[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART1 + i]);
}
// stars
for(int i = 0; i < 3; ++i)
{
m_GameSkin.m_aSpriteStars[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_STAR1 + i]);
}
// projectiles
m_GameSkin.m_SpriteWeaponGunProjectile = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GUN_PROJ]);
m_GameSkin.m_SpriteWeaponShotgunProjectile = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_SHOTGUN_PROJ]);
m_GameSkin.m_SpriteWeaponGrenadeProjectile = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GRENADE_PROJ]);
// these weapons have no projectiles
m_GameSkin.m_SpriteWeaponHammerProjectile = IGraphics::CTextureHandle();
m_GameSkin.m_SpriteWeaponNinjaProjectile = IGraphics::CTextureHandle();
m_GameSkin.m_SpriteWeaponLaserProjectile = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_LASER_PROJ]);
m_GameSkin.m_aSpriteWeaponProjectiles[0] = m_GameSkin.m_SpriteWeaponHammerProjectile;
m_GameSkin.m_aSpriteWeaponProjectiles[1] = m_GameSkin.m_SpriteWeaponGunProjectile;
m_GameSkin.m_aSpriteWeaponProjectiles[2] = m_GameSkin.m_SpriteWeaponShotgunProjectile;
m_GameSkin.m_aSpriteWeaponProjectiles[3] = m_GameSkin.m_SpriteWeaponGrenadeProjectile;
m_GameSkin.m_aSpriteWeaponProjectiles[4] = m_GameSkin.m_SpriteWeaponLaserProjectile;
m_GameSkin.m_aSpriteWeaponProjectiles[5] = m_GameSkin.m_SpriteWeaponNinjaProjectile;
// muzzles
for(int i = 0; i < 3; ++i)
{
m_GameSkin.m_aSpriteWeaponGunMuzzles[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_GUN_MUZZLE1 + i]);
m_GameSkin.m_aSpriteWeaponShotgunMuzzles[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_SHOTGUN_MUZZLE1 + i]);
m_GameSkin.m_aaSpriteWeaponNinjaMuzzles[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_WEAPON_NINJA_MUZZLE1 + i]);
m_GameSkin.m_aaSpriteWeaponsMuzzles[1][i] = m_GameSkin.m_aSpriteWeaponGunMuzzles[i];
m_GameSkin.m_aaSpriteWeaponsMuzzles[2][i] = m_GameSkin.m_aSpriteWeaponShotgunMuzzles[i];
m_GameSkin.m_aaSpriteWeaponsMuzzles[5][i] = m_GameSkin.m_aaSpriteWeaponNinjaMuzzles[i];
}
// pickups
m_GameSkin.m_SpritePickupHealth = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_HEALTH]);
m_GameSkin.m_SpritePickupArmor = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR]);
m_GameSkin.m_SpritePickupHammer = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_HAMMER]);
m_GameSkin.m_SpritePickupGun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_GUN]);
m_GameSkin.m_SpritePickupShotgun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_SHOTGUN]);
m_GameSkin.m_SpritePickupGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_GRENADE]);
m_GameSkin.m_SpritePickupLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_LASER]);
m_GameSkin.m_SpritePickupNinja = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_NINJA]);
m_GameSkin.m_SpritePickupArmorShotgun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_SHOTGUN]);
m_GameSkin.m_SpritePickupArmorGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_GRENADE]);
m_GameSkin.m_SpritePickupArmorNinja = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_NINJA]);
m_GameSkin.m_SpritePickupArmorLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PICKUP_ARMOR_LASER]);
m_GameSkin.m_aSpritePickupWeapons[0] = m_GameSkin.m_SpritePickupHammer;
m_GameSkin.m_aSpritePickupWeapons[1] = m_GameSkin.m_SpritePickupGun;
m_GameSkin.m_aSpritePickupWeapons[2] = m_GameSkin.m_SpritePickupShotgun;
m_GameSkin.m_aSpritePickupWeapons[3] = m_GameSkin.m_SpritePickupGrenade;
m_GameSkin.m_aSpritePickupWeapons[4] = m_GameSkin.m_SpritePickupLaser;
m_GameSkin.m_aSpritePickupWeapons[5] = m_GameSkin.m_SpritePickupNinja;
m_GameSkin.m_aSpritePickupWeaponArmor[0] = m_GameSkin.m_SpritePickupArmorShotgun;
m_GameSkin.m_aSpritePickupWeaponArmor[1] = m_GameSkin.m_SpritePickupArmorGrenade;
m_GameSkin.m_aSpritePickupWeaponArmor[2] = m_GameSkin.m_SpritePickupArmorNinja;
m_GameSkin.m_aSpritePickupWeaponArmor[3] = m_GameSkin.m_SpritePickupArmorLaser;
// flags
m_GameSkin.m_SpriteFlagBlue = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_FLAG_BLUE]);
m_GameSkin.m_SpriteFlagRed = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_FLAG_RED]);
// ninja bar (0.7)
if(!Graphics()->IsSpriteTextureFullyTransparent(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_FULL_LEFT]) ||
!Graphics()->IsSpriteTextureFullyTransparent(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_FULL]) ||
!Graphics()->IsSpriteTextureFullyTransparent(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_EMPTY]) ||
!Graphics()->IsSpriteTextureFullyTransparent(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_EMPTY_RIGHT]))
{
m_GameSkin.m_SpriteNinjaBarFullLeft = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_FULL_LEFT]);
m_GameSkin.m_SpriteNinjaBarFull = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_FULL]);
m_GameSkin.m_SpriteNinjaBarEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_EMPTY]);
m_GameSkin.m_SpriteNinjaBarEmptyRight = Graphics()->LoadSpriteTexture(ImgInfo, &client_data7::g_pData->m_aSprites[client_data7::SPRITE_NINJA_BAR_EMPTY_RIGHT]);
}
m_GameSkinLoaded = true;
2020-11-25 12:05:53 +00:00
Graphics()->FreePNG(&ImgInfo);
2020-09-26 07:37:35 +00:00
}
}
void CGameClient::LoadEmoticonsSkin(const char *pPath, bool AsDir)
{
if(m_EmoticonsSkinLoaded)
2020-09-26 07:37:35 +00:00
{
for(auto &SpriteEmoticon : m_EmoticonsSkin.m_aSpriteEmoticons)
Graphics()->UnloadTexture(&SpriteEmoticon);
m_EmoticonsSkinLoaded = false;
2020-09-26 07:37:35 +00:00
}
char aPath[IO_MAX_PATH_LENGTH];
2020-09-26 07:37:35 +00:00
bool IsDefault = false;
if(str_comp(pPath, "default") == 0)
{
str_format(aPath, sizeof(aPath), "%s", g_pData->m_aImages[IMAGE_EMOTICONS].m_pFilename);
IsDefault = true;
}
else
{
if(AsDir)
str_format(aPath, sizeof(aPath), "assets/emoticons/%s/%s", pPath, g_pData->m_aImages[IMAGE_EMOTICONS].m_pFilename);
else
str_format(aPath, sizeof(aPath), "assets/emoticons/%s.png", pPath);
}
CImageInfo ImgInfo;
bool PngLoaded = Graphics()->LoadPNG(&ImgInfo, aPath, IStorage::TYPE_ALL);
if(!PngLoaded && !IsDefault)
{
if(AsDir)
LoadEmoticonsSkin("default");
else
LoadEmoticonsSkin(pPath, true);
}
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_OOP].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_OOP].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRGBA(aPath, ImgInfo))
2020-09-26 07:37:35 +00:00
{
for(int i = 0; i < 16; ++i)
m_EmoticonsSkin.m_aSpriteEmoticons[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_OOP + i]);
m_EmoticonsSkinLoaded = true;
2020-11-25 12:05:53 +00:00
Graphics()->FreePNG(&ImgInfo);
2020-09-26 07:37:35 +00:00
}
}
void CGameClient::LoadParticlesSkin(const char *pPath, bool AsDir)
{
if(m_ParticlesSkinLoaded)
2020-09-26 07:37:35 +00:00
{
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleSlice);
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleBall);
for(auto &SpriteParticleSplat : m_ParticlesSkin.m_aSpriteParticleSplat)
Graphics()->UnloadTexture(&SpriteParticleSplat);
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleSmoke);
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleShell);
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleExpl);
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleAirJump);
Graphics()->UnloadTexture(&m_ParticlesSkin.m_SpriteParticleHit);
for(auto &SpriteParticle : m_ParticlesSkin.m_aSpriteParticles)
2020-10-26 14:14:07 +00:00
SpriteParticle = IGraphics::CTextureHandle();
m_ParticlesSkinLoaded = false;
2020-09-26 07:37:35 +00:00
}
char aPath[IO_MAX_PATH_LENGTH];
2020-09-26 07:37:35 +00:00
bool IsDefault = false;
if(str_comp(pPath, "default") == 0)
{
str_format(aPath, sizeof(aPath), "%s", g_pData->m_aImages[IMAGE_PARTICLES].m_pFilename);
IsDefault = true;
}
else
{
if(AsDir)
str_format(aPath, sizeof(aPath), "assets/particles/%s/%s", pPath, g_pData->m_aImages[IMAGE_PARTICLES].m_pFilename);
else
str_format(aPath, sizeof(aPath), "assets/particles/%s.png", pPath);
}
CImageInfo ImgInfo;
bool PngLoaded = Graphics()->LoadPNG(&ImgInfo, aPath, IStorage::TYPE_ALL);
if(!PngLoaded && !IsDefault)
{
if(AsDir)
LoadParticlesSkin("default");
else
LoadParticlesSkin(pPath, true);
}
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_PART_SLICE].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_PART_SLICE].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRGBA(aPath, ImgInfo))
2020-09-26 07:37:35 +00:00
{
m_ParticlesSkin.m_SpriteParticleSlice = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SLICE]);
m_ParticlesSkin.m_SpriteParticleBall = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_BALL]);
for(int i = 0; i < 3; ++i)
m_ParticlesSkin.m_aSpriteParticleSplat[i] = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SPLAT01 + i]);
m_ParticlesSkin.m_SpriteParticleSmoke = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SMOKE]);
m_ParticlesSkin.m_SpriteParticleShell = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SHELL]);
m_ParticlesSkin.m_SpriteParticleExpl = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_EXPL01]);
m_ParticlesSkin.m_SpriteParticleAirJump = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_AIRJUMP]);
m_ParticlesSkin.m_SpriteParticleHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_HIT01]);
m_ParticlesSkin.m_aSpriteParticles[0] = m_ParticlesSkin.m_SpriteParticleSlice;
m_ParticlesSkin.m_aSpriteParticles[1] = m_ParticlesSkin.m_SpriteParticleBall;
for(int i = 0; i < 3; ++i)
m_ParticlesSkin.m_aSpriteParticles[2 + i] = m_ParticlesSkin.m_aSpriteParticleSplat[i];
m_ParticlesSkin.m_aSpriteParticles[5] = m_ParticlesSkin.m_SpriteParticleSmoke;
m_ParticlesSkin.m_aSpriteParticles[6] = m_ParticlesSkin.m_SpriteParticleShell;
m_ParticlesSkin.m_aSpriteParticles[7] = m_ParticlesSkin.m_SpriteParticleExpl;
m_ParticlesSkin.m_aSpriteParticles[8] = m_ParticlesSkin.m_SpriteParticleAirJump;
m_ParticlesSkin.m_aSpriteParticles[9] = m_ParticlesSkin.m_SpriteParticleHit;
m_ParticlesSkinLoaded = true;
2020-09-30 21:42:00 +00:00
free(ImgInfo.m_pData);
2020-09-26 07:37:35 +00:00
}
}
void CGameClient::LoadHudSkin(const char *pPath, bool AsDir)
{
if(m_HudSkinLoaded)
{
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudAirjump);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudAirjumpEmpty);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudSolo);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoCollision);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudEndlessJump);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudEndlessHook);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudJetpack);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarFullLeft);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarFull);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarEmpty);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudFreezeBarEmptyRight);
2022-03-29 21:24:39 +00:00
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarFullLeft);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarFull);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarEmpty);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNinjaBarEmptyRight);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoHookHit);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoHammerHit);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoShotgunHit);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoGrenadeHit);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoLaserHit);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudNoGunHit);
2022-03-25 09:09:13 +00:00
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudDeepFrozen);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudLiveFrozen);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudTeleportGrenade);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudTeleportGun);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudTeleportLaser);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudPracticeMode);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudDummyHammer);
Graphics()->UnloadTexture(&m_HudSkin.m_SpriteHudDummyCopy);
m_HudSkinLoaded = false;
}
char aPath[IO_MAX_PATH_LENGTH];
bool IsDefault = false;
if(str_comp(pPath, "default") == 0)
{
str_format(aPath, sizeof(aPath), "%s", g_pData->m_aImages[IMAGE_HUD].m_pFilename);
IsDefault = true;
}
else
{
if(AsDir)
str_format(aPath, sizeof(aPath), "assets/hud/%s/%s", pPath, g_pData->m_aImages[IMAGE_HUD].m_pFilename);
else
str_format(aPath, sizeof(aPath), "assets/hud/%s.png", pPath);
}
CImageInfo ImgInfo;
bool PngLoaded = Graphics()->LoadPNG(&ImgInfo, aPath, IStorage::TYPE_ALL);
if(!PngLoaded && !IsDefault)
{
if(AsDir)
LoadHudSkin("default");
else
LoadHudSkin(pPath, true);
}
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_HUD_AIRJUMP].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_HUD_AIRJUMP].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRGBA(aPath, ImgInfo))
{
m_HudSkin.m_SpriteHudAirjump = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_AIRJUMP]);
m_HudSkin.m_SpriteHudAirjumpEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_AIRJUMP_EMPTY]);
m_HudSkin.m_SpriteHudSolo = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_SOLO]);
m_HudSkin.m_SpriteHudNoCollision = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_COLLISION]);
m_HudSkin.m_SpriteHudEndlessJump = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_ENDLESS_JUMP]);
m_HudSkin.m_SpriteHudEndlessHook = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_ENDLESS_HOOK]);
m_HudSkin.m_SpriteHudJetpack = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_JETPACK]);
m_HudSkin.m_SpriteHudFreezeBarFullLeft = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_FULL_LEFT]);
m_HudSkin.m_SpriteHudFreezeBarFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_FULL]);
m_HudSkin.m_SpriteHudFreezeBarEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_EMPTY]);
m_HudSkin.m_SpriteHudFreezeBarEmptyRight = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_FREEZE_BAR_EMPTY_RIGHT]);
2022-03-29 21:24:39 +00:00
m_HudSkin.m_SpriteHudNinjaBarFullLeft = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_FULL_LEFT]);
m_HudSkin.m_SpriteHudNinjaBarFull = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_FULL]);
m_HudSkin.m_SpriteHudNinjaBarEmpty = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_EMPTY]);
m_HudSkin.m_SpriteHudNinjaBarEmptyRight = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NINJA_BAR_EMPTY_RIGHT]);
m_HudSkin.m_SpriteHudNoHookHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_HOOK_HIT]);
m_HudSkin.m_SpriteHudNoHammerHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_HAMMER_HIT]);
m_HudSkin.m_SpriteHudNoShotgunHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_SHOTGUN_HIT]);
m_HudSkin.m_SpriteHudNoGrenadeHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_GRENADE_HIT]);
m_HudSkin.m_SpriteHudNoLaserHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_LASER_HIT]);
m_HudSkin.m_SpriteHudNoGunHit = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_NO_GUN_HIT]);
2022-03-25 09:09:13 +00:00
m_HudSkin.m_SpriteHudDeepFrozen = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_DEEP_FROZEN]);
m_HudSkin.m_SpriteHudLiveFrozen = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_LIVE_FROZEN]);
m_HudSkin.m_SpriteHudTeleportGrenade = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_TELEPORT_GRENADE]);
m_HudSkin.m_SpriteHudTeleportGun = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_TELEPORT_GUN]);
m_HudSkin.m_SpriteHudTeleportLaser = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_TELEPORT_LASER]);
m_HudSkin.m_SpriteHudPracticeMode = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_PRACTICE_MODE]);
m_HudSkin.m_SpriteHudDummyHammer = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_DUMMY_HAMMER]);
m_HudSkin.m_SpriteHudDummyCopy = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_HUD_DUMMY_COPY]);
m_HudSkinLoaded = true;
free(ImgInfo.m_pData);
}
}
2022-06-14 17:28:51 +00:00
void CGameClient::LoadExtrasSkin(const char *pPath, bool AsDir)
{
if(m_ExtrasSkinLoaded)
{
Graphics()->UnloadTexture(&m_ExtrasSkin.m_SpriteParticleSnowflake);
for(auto &SpriteParticle : m_ExtrasSkin.m_aSpriteParticles)
2022-06-14 17:28:51 +00:00
SpriteParticle = IGraphics::CTextureHandle();
m_ExtrasSkinLoaded = false;
}
char aPath[IO_MAX_PATH_LENGTH];
bool IsDefault = false;
if(str_comp(pPath, "default") == 0)
{
str_format(aPath, sizeof(aPath), "%s", g_pData->m_aImages[IMAGE_EXTRAS].m_pFilename);
IsDefault = true;
}
else
{
if(AsDir)
str_format(aPath, sizeof(aPath), "assets/extras/%s/%s", pPath, g_pData->m_aImages[IMAGE_EXTRAS].m_pFilename);
else
str_format(aPath, sizeof(aPath), "assets/extras/%s.png", pPath);
}
CImageInfo ImgInfo;
bool PngLoaded = Graphics()->LoadPNG(&ImgInfo, aPath, IStorage::TYPE_ALL);
if(!PngLoaded && !IsDefault)
{
if(AsDir)
LoadExtrasSkin("default");
else
LoadExtrasSkin(pPath, true);
}
else if(PngLoaded && Graphics()->CheckImageDivisibility(aPath, ImgInfo, g_pData->m_aSprites[SPRITE_PART_SNOWFLAKE].m_pSet->m_Gridx, g_pData->m_aSprites[SPRITE_PART_SNOWFLAKE].m_pSet->m_Gridy, true) && Graphics()->IsImageFormatRGBA(aPath, ImgInfo))
{
m_ExtrasSkin.m_SpriteParticleSnowflake = Graphics()->LoadSpriteTexture(ImgInfo, &g_pData->m_aSprites[SPRITE_PART_SNOWFLAKE]);
m_ExtrasSkin.m_aSpriteParticles[0] = m_ExtrasSkin.m_SpriteParticleSnowflake;
2022-06-14 17:28:51 +00:00
m_ExtrasSkinLoaded = true;
free(ImgInfo.m_pData);
}
}
void CGameClient::RefindSkins()
{
2020-10-26 14:14:07 +00:00
for(auto &Client : m_aClients)
{
2020-11-08 05:39:16 +00:00
Client.m_SkinInfo.m_OriginalRenderSkin.Reset();
Client.m_SkinInfo.m_ColorableRenderSkin.Reset();
2020-10-26 14:14:07 +00:00
if(Client.m_aSkinName[0] != '\0')
{
2021-07-12 09:43:56 +00:00
const CSkin *pSkin = m_Skins.Get(m_Skins.Find(Client.m_aSkinName));
2020-10-26 14:14:07 +00:00
Client.m_SkinInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
Client.m_SkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
2021-07-12 10:04:45 +00:00
Client.UpdateRenderInfo(IsTeamPlay());
}
}
2021-07-12 09:43:56 +00:00
m_Ghost.RefindSkin();
m_Chat.RefindSkins();
2021-07-12 09:29:59 +00:00
m_KillMessages.RefindSkins();
}
2019-09-08 22:53:07 +00:00
void CGameClient::LoadMapSettings()
{
// Reset Tunezones
CTuningParams TuningParams;
for(int i = 0; i < NUM_TUNEZONES; i++)
{
TuningList()[i] = TuningParams;
TuningList()[i].Set("gun_curvature", 0);
TuningList()[i].Set("gun_speed", 1400);
TuningList()[i].Set("shotgun_curvature", 0);
TuningList()[i].Set("shotgun_speed", 500);
TuningList()[i].Set("shotgun_speeddiff", 0);
2019-09-08 22:53:07 +00:00
}
// Load map tunings
IMap *pMap = Kernel()->RequestInterface<IMap>();
int Start, Num;
pMap->GetType(MAPITEMTYPE_INFO, &Start, &Num);
for(int i = Start; i < Start + Num; i++)
{
int ItemID;
CMapItemInfoSettings *pItem = (CMapItemInfoSettings *)pMap->GetItem(i, 0, &ItemID);
int ItemSize = pMap->GetItemSize(i);
if(!pItem || ItemID != 0)
continue;
if(ItemSize < (int)sizeof(CMapItemInfoSettings))
break;
if(!(pItem->m_Settings > -1))
break;
int Size = pMap->GetDataSize(pItem->m_Settings);
char *pSettings = (char *)pMap->GetData(pItem->m_Settings);
char *pNext = pSettings;
dbg_msg("tune", "%s", pNext);
2019-09-08 22:53:07 +00:00
while(pNext < pSettings + Size)
{
int StrSize = str_length(pNext) + 1;
Console()->ExecuteLine(pNext, IConsole::CLIENT_ID_GAME);
pNext += StrSize;
}
pMap->UnloadData(pItem->m_Settings);
break;
}
}
void CGameClient::ConTuneZone(IConsole::IResult *pResult, void *pUserData)
{
CGameClient *pSelf = (CGameClient *)pUserData;
int List = pResult->GetInteger(0);
const char *pParamName = pResult->GetString(1);
float NewValue = pResult->GetFloat(2);
if(List >= 0 && List < NUM_TUNEZONES)
pSelf->TuningList()[List].Set(pParamName, NewValue);
}
2020-09-18 16:45:42 +00:00
void CGameClient::ConchainMenuMap(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
CGameClient *pSelf = (CGameClient *)pUserData;
if(pResult->NumArguments())
{
if(str_comp(g_Config.m_ClMenuMap, pResult->GetString(0)) != 0)
{
str_format(g_Config.m_ClMenuMap, sizeof(g_Config.m_ClMenuMap), "%s", pResult->GetString(0));
2021-07-12 09:43:56 +00:00
pSelf->m_MenuBackground.LoadMenuBackground();
2020-09-18 16:45:42 +00:00
}
}
else
pfnCallback(pResult, pCallbackUserData);
}
2021-03-17 15:09:39 +00:00
void CGameClient::DummyResetInput()
{
if(!Client()->DummyConnected())
return;
if((m_DummyInput.m_Fire & 1) != 0)
m_DummyInput.m_Fire++;
2021-07-12 09:43:56 +00:00
m_Controls.ResetInput(!g_Config.m_ClDummy);
m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Hook = 0;
m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Fire = m_DummyInput.m_Fire;
2021-03-17 15:09:39 +00:00
m_DummyInput = m_Controls.m_aInputData[!g_Config.m_ClDummy];
2021-03-17 15:09:39 +00:00
}
bool CGameClient::CanDisplayWarning()
{
2021-07-12 09:43:56 +00:00
return m_Menus.CanDisplayWarning();
}
bool CGameClient::IsDisplayingWarning()
{
2021-07-12 09:43:56 +00:00
return m_Menus.GetCurPopup() == CMenus::POPUP_WARNING;
}
CNetObjHandler *CGameClient::GetNetObjHandler()
{
return &m_NetObjHandler;
}
void CGameClient::SnapCollectEntities()
{
int NumSnapItems = Client()->SnapNumItems(IClient::SNAP_CURRENT);
2022-06-15 17:34:41 +00:00
std::vector<CSnapEntities> vItemData;
std::vector<CSnapEntities> vItemEx;
2021-11-07 10:00:48 +00:00
for(int Index = 0; Index < NumSnapItems; Index++)
{
IClient::CSnapItem Item;
const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item);
2021-11-07 10:00:48 +00:00
if(Item.m_Type == NETOBJTYPE_ENTITYEX)
2022-06-15 17:34:41 +00:00
vItemEx.push_back({Item, pData, 0});
2021-11-07 10:00:48 +00:00
else if(Item.m_Type == NETOBJTYPE_PICKUP || Item.m_Type == NETOBJTYPE_LASER || Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE)
2022-06-15 17:34:41 +00:00
vItemData.push_back({Item, pData, 0});
}
2021-11-07 10:00:48 +00:00
// sort by id
class CEntComparer
{
public:
bool operator()(const CSnapEntities &lhs, const CSnapEntities &rhs) const
{
return lhs.m_Item.m_ID < rhs.m_Item.m_ID;
}
};
2022-06-15 17:34:41 +00:00
std::sort(vItemData.begin(), vItemData.end(), CEntComparer());
std::sort(vItemEx.begin(), vItemEx.end(), CEntComparer());
// merge extended items with items they belong to
m_vSnapEntities.clear();
2021-11-07 10:00:48 +00:00
size_t IndexEx = 0;
2022-06-15 17:34:41 +00:00
for(const CSnapEntities &Ent : vItemData)
2021-11-07 10:00:48 +00:00
{
const CNetObj_EntityEx *pDataEx = 0;
2022-06-15 17:34:41 +00:00
while(IndexEx < vItemEx.size() && vItemEx[IndexEx].m_Item.m_ID < Ent.m_Item.m_ID)
2021-11-07 10:00:48 +00:00
IndexEx++;
2022-06-15 17:34:41 +00:00
if(IndexEx < vItemEx.size() && vItemEx[IndexEx].m_Item.m_ID == Ent.m_Item.m_ID)
pDataEx = (const CNetObj_EntityEx *)vItemEx[IndexEx].m_pData;
m_vSnapEntities.push_back({Ent.m_Item, Ent.m_pData, pDataEx});
}
}