Topic: Tutorial: How to create a custom tune system per client
I post here a little tutorial about how to send custom parameters to each client/ This can be used for freezing effect, lock position of some tees, water physics, ...
In teeworlds, physics is done by the client and the server simultaneously. This allows the client to predict the position of each tee between two snapshots of the server. However, only the tee that you control is predicted. Other tees are simply interpolated between last position and next position. Constants used to compute the physics are called "Tune Parameters". If you want to change the physics in your mod, you usually change some of these tune parameters. When you do that, a message containing the new constants is sent to all clients.
If you want to change these constants to only one client, then things get more complicated. You must send the new tune parameter to only one client and in same time, change how the physics is computed in your server to allows one set of constants per tee. We will start with this point.
Change the core
The game core is the place where the physics is done. As we said, we want to use different tune parameters depending of the situation. The simplest way to do it is to change the file src/game/gamecore.h and gamecore.cpp
src/game/gamecore.h
void Tick(bool UseInput);
void Move();
We will add a pointer to a CTuningParams in order to let the core know which parameters must be used
void Tick(bool UseInput, const CTuningParams* pTuningParams);
void Move(const CTuningParams* pTuningParams);
src/game/gamecore.cpp
Then, the implementation of both functions must be changed . Just replace all occurrences of "m_pWorld->m_Tuning." by "pTuningParams->" in Move() and Tick(). For exemple,
void CCharacterCore::Move()
{
float RampValue = VelocityRamp(length(m_Vel)*50, m_pWorld->m_Tuning.m_VelrampStart, m_pWorld->m_Tuning.m_VelrampRange, m_pWorld->m_Tuning.m_VelrampCurvature);
...
will become
void CCharacterCore::Move(const CTuningParams* pTuningParams)
{
float RampValue = VelocityRamp(length(m_Vel)*50, pTuningParams->m_VelrampStart, pTuningParams->m_VelrampRange, pTuningParams->m_VelrampCurvature);
...
src/game/server/entities/character.cpp
Since the header of Tick() and Move() is different, we need to fix all calls of these function. Simply replace all occurrences of m_Core.Tick(xxx) by m_Core.Tick(xxx, GameServer()->Tuning()) and m_Core.Move() by m_Core.Move(GameServer()->Tuning()). The only exception is in the function TickDefered where you need to use &TempWorld.m_Tuning unstead of GameServer()->Tuning() to move the dummy (this is very important, otherwise, your mod will be unplayable).
void CCharacter::TickDefered()
{
// advance the dummy
{
CWorldCore TempWorld;
m_ReckoningCore.Init(&TempWorld, GameServer()->Collision());
m_ReckoningCore.Tick(false);
m_ReckoningCore.Move();
m_ReckoningCore.Quantize();
}
//lastsentcore
vec2 StartPos = m_Core.m_Pos;
vec2 StartVel = m_Core.m_Vel;
bool StuckBefore = GameServer()->Collision()->TestBox(m_Core.m_Pos, vec2(28.0f, 28.0f));
m_Core.Move();
...
will become
void CCharacter::TickDefered()
{
// advance the dummy
{
CWorldCore TempWorld;
m_ReckoningCore.Init(&TempWorld, GameServer()->Collision());
m_ReckoningCore.Tick(false, &TempWorld.m_Tuning); // <--- Changes
m_ReckoningCore.Move(&TempWorld.m_Tuning); // <--- Changes
m_ReckoningCore.Quantize();
}
//lastsentcore
vec2 StartPos = m_Core.m_Pos;
vec2 StartVel = m_Core.m_Vel;
bool StuckBefore = GameServer()->Collision()->TestBox(m_Core.m_Pos, vec2(28.0f, 28.0f));
m_Core.Move(GameServer()->Tuning()); // <--- Changes
...
Fix the NetVersion
Since you edited a file used by both server and client, the NetVersion of your server will be different, meaning that no one will be able to join your server. To fix that, you need to fake your NetVersion.
src/engine/server/server.cpp
Search for "NetVersion" in server.cpp
if(str_comp(pVersion, GameServer()->NetVersion()) != 0))
and replace this line by this one
if((str_comp(pVersion, GameServer()->NetVersion()) != 0) && (str_comp(pVersion, "0.6 626fce9a778df4d4") != 0))
Here, "0.6 626fce9a778df4d4" is the default NetVersion of TeeWorlds 0.6.3.
Create a simple system to maintain custom tune parameters
The second part is to send the new tune parameters to the client. The problematic here is to never forget to send the new tune parameters that you use. It became tricky when you have tune zones, weapons with particular tunes and some other effects in same time. To avoid any mistakes, we will attach two CTuningParams to each players. One for the current tune parameters that we want to use in the core, and one to store the tune parameters used in the last Tick. The idea is to send automatically tune parameters to the client if we detect a difference between the current and the previous CTuningParams.
src/game/server/player.h
Add these lines at the end of the class CPlayer
private:
CTuningParams m_PrevTuningParams;
CTuningParams m_NextTuningParams;
void HandleTuningParams(); //This function will send the new parameters if needed
public:
CTuningParams* GetNextTuningParams() { return &m_NextTuningParams; };
src/game/server/player.cpp
In the constructor, add these lines
m_PrevTuningParams = *pGameServer->Tuning();
m_NextTuningParams = m_PrevTuningParams;
At the end of Tick(), call HandleTuningParams(); The implementation of HandleTuningParams is given by
if(!(m_PrevTuningParams == m_NextTuningParams))
{
if(m_IsReady)
{
CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS);
int *pParams = (int *)&m_NextTuningParams;
for(unsigned i = 0; i < sizeof(m_NextTuningParams)/sizeof(int); i++)
Msg.AddInt(pParams[i]);
Server()->SendMsg(&Msg, MSGFLAG_VITAL, GetCID());
}
m_PrevTuningParams = m_NextTuningParams;
}
m_NextTuningParams = *GameServer()->Tuning();
src/game/gamecore.h
In the game core (header), add this function in the class CTuningParams to allow comparisons
bool operator==(const CTuningParams& TuningParams)
{
#define MACRO_TUNING_PARAM(Name,ScriptName,Value) if(m_##Name != TuningParams.m_##Name) return false;
#include "tuning.h"
#undef MACRO_TUNING_PARAM
return true;
}
src/game/server/entities/character.cpp
Replace all occurrences of m_Core.Tick(xxx, GameServer()->Tuning()) by m_Core.Tick(xxx, m_pPlayer->GetNextTuningParams()) and m_Core.Move(GameServer()->Tuning()) by m_Core.Move(m_pPlayer->GetNextTuningParams()). Don't do it for the dummy! It still must use &TempWorld.m_Tuning.
Example of use
src/game/server/entities/character.cpp
In the function Tick, just before the call to CCharacterCore::Tick(), change the gravity of one player:
if(m_pPlayer->GetCID() == 0)
m_pPlayer->GetNextTuningParams()->m_Gravity = 0.1f;
m_Core.Tick(true, m_pPlayer->GetNextTuningParams());
Try to connect 2 clients to your server: the first one should have lower gravity!