mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-14 03:58:18 +00:00
47d63317aa
3826: Respect TuneZone for characters r=def- a=TsFreddie *I might be totally insane for doing this. *The effect is only truly noticeable if you have high ping AND dropping packets.* *clang-tidy's warning doesn't seems to relate to anything I've changed* Now characters will try to respect TuneZones when ~~evolving and~~ predicting (like projectiles do). Since it is really important to keep TuningList as accurate as possible, I made a TuningList update logic so we try our best to update tunings only for the correct TuneZone (instead of constantly shoving current tuning into TuningList) And each CCharacterCore now has its own Tunings to avoid any characters influencing GameWorld's tuning. Some server code has to be updated accordingly. I checked the code path, didn't notice anything that would make our server behave differently. And I guess I should tag @trml myself this time. ## Checklist - [x] Tested the change ingame - [ ] Provided screenshots if it is a visual change - [ ] Tested in combination with possibly related configuration options - [ ] Written a unit test if it works standalone, system.c especially - [ ] Considered possible null pointers and out of bounds array indexing - [x] Changed no physics that affect existing maps (as far as I could tell) - [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: Freddie Wang <tsfreddiewang@gmail.com> Co-authored-by: def <dennis@felsin9.de>
2358 lines
71 KiB
C++
2358 lines
71 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
|
#include <antibot/antibot_data.h>
|
|
#include <engine/shared/config.h>
|
|
#include <game/generated/server_data.h>
|
|
#include <game/mapitems.h>
|
|
#include <game/server/gamecontext.h>
|
|
#include <game/server/player.h>
|
|
#include <new>
|
|
|
|
#include "character.h"
|
|
#include "laser.h"
|
|
#include "projectile.h"
|
|
|
|
#include "light.h"
|
|
#include <game/server/score.h>
|
|
#include <game/server/teams.h>
|
|
|
|
MACRO_ALLOC_POOL_ID_IMPL(CCharacter, MAX_CLIENTS)
|
|
|
|
// Character, "physical" player's part
|
|
CCharacter::CCharacter(CGameWorld *pWorld) :
|
|
CEntity(pWorld, CGameWorld::ENTTYPE_CHARACTER, vec2(0, 0), ms_PhysSize)
|
|
{
|
|
m_Health = 0;
|
|
m_Armor = 0;
|
|
m_StrongWeakID = 0;
|
|
|
|
// never intilize both to zero
|
|
m_Input.m_TargetX = 0;
|
|
m_Input.m_TargetY = -1;
|
|
|
|
m_LatestPrevPrevInput = m_LatestPrevInput = m_LatestInput = m_PrevInput = m_SavedInput = m_Input;
|
|
}
|
|
|
|
void CCharacter::Reset()
|
|
{
|
|
Destroy();
|
|
}
|
|
|
|
bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
|
|
{
|
|
m_EmoteStop = -1;
|
|
m_LastAction = -1;
|
|
m_LastNoAmmoSound = -1;
|
|
m_LastWeapon = WEAPON_HAMMER;
|
|
m_QueuedWeapon = -1;
|
|
m_LastRefillJumps = false;
|
|
m_LastPenalty = false;
|
|
m_LastBonus = false;
|
|
|
|
m_TeleGunTeleport = false;
|
|
m_IsBlueTeleGunTeleport = false;
|
|
m_Solo = false;
|
|
|
|
m_pPlayer = pPlayer;
|
|
m_Pos = Pos;
|
|
|
|
mem_zero(&m_LatestPrevPrevInput, sizeof(m_LatestPrevPrevInput));
|
|
m_LatestPrevPrevInput.m_TargetY = -1;
|
|
m_SpawnTick = Server()->Tick();
|
|
m_WeaponChangeTick = Server()->Tick();
|
|
Antibot()->OnSpawn(m_pPlayer->GetCID());
|
|
|
|
m_Core.Reset();
|
|
m_Core.Init(&GameServer()->m_World.m_Core, GameServer()->Collision());
|
|
m_Core.m_ActiveWeapon = WEAPON_GUN;
|
|
m_Core.m_Pos = m_Pos;
|
|
GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = &m_Core;
|
|
|
|
m_ReckoningTick = 0;
|
|
mem_zero(&m_SendCore, sizeof(m_SendCore));
|
|
mem_zero(&m_ReckoningCore, sizeof(m_ReckoningCore));
|
|
|
|
GameServer()->m_World.InsertEntity(this);
|
|
m_Alive = true;
|
|
|
|
GameServer()->m_pController->OnCharacterSpawn(this);
|
|
|
|
DDRaceInit();
|
|
|
|
m_TuneZone = GameServer()->Collision()->IsTune(GameServer()->Collision()->GetMapIndex(Pos));
|
|
m_TuneZoneOld = -1; // no zone leave msg on spawn
|
|
m_NeededFaketuning = 0; // reset fake tunings on respawn and send the client
|
|
SendZoneMsgs(); // we want a entermessage also on spawn
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone);
|
|
|
|
Server()->StartRecord(m_pPlayer->GetCID());
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCharacter::Destroy()
|
|
{
|
|
GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = 0;
|
|
m_Alive = false;
|
|
m_Solo = false;
|
|
}
|
|
|
|
void CCharacter::SetWeapon(int W)
|
|
{
|
|
if(W == m_Core.m_ActiveWeapon)
|
|
return;
|
|
|
|
m_LastWeapon = m_Core.m_ActiveWeapon;
|
|
m_QueuedWeapon = -1;
|
|
m_Core.m_ActiveWeapon = W;
|
|
GameServer()->CreateSound(m_Pos, SOUND_WEAPON_SWITCH, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
|
|
if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS)
|
|
m_Core.m_ActiveWeapon = 0;
|
|
}
|
|
|
|
void CCharacter::SetSolo(bool Solo)
|
|
{
|
|
m_Solo = Solo;
|
|
m_Core.m_Solo = Solo;
|
|
Teams()->m_Core.SetSolo(m_pPlayer->GetCID(), Solo);
|
|
|
|
if(Solo)
|
|
m_NeededFaketuning |= FAKETUNE_SOLO;
|
|
else
|
|
m_NeededFaketuning &= ~FAKETUNE_SOLO;
|
|
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
|
|
bool CCharacter::IsGrounded()
|
|
{
|
|
if(GameServer()->Collision()->CheckPoint(m_Pos.x + GetProximityRadius() / 2, m_Pos.y + GetProximityRadius() / 2 + 5))
|
|
return true;
|
|
if(GameServer()->Collision()->CheckPoint(m_Pos.x - GetProximityRadius() / 2, m_Pos.y + GetProximityRadius() / 2 + 5))
|
|
return true;
|
|
|
|
int MoveRestrictionsBelow = GameServer()->Collision()->GetMoveRestrictions(m_Pos + vec2(0, GetProximityRadius() / 2 + 4), 0.0f);
|
|
if(MoveRestrictionsBelow & CANTMOVE_DOWN)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CCharacter::HandleJetpack()
|
|
{
|
|
vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
|
|
|
|
bool FullAuto = false;
|
|
if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_LASER)
|
|
FullAuto = true;
|
|
if(m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
|
|
FullAuto = true;
|
|
|
|
// check if we gonna fire
|
|
bool WillFire = false;
|
|
if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses)
|
|
WillFire = true;
|
|
|
|
if(FullAuto && (m_LatestInput.m_Fire & 1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
WillFire = true;
|
|
|
|
if(!WillFire)
|
|
return;
|
|
|
|
// check for ammo
|
|
if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo || m_FreezeTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch(m_Core.m_ActiveWeapon)
|
|
{
|
|
case WEAPON_GUN:
|
|
{
|
|
if(m_Jetpack)
|
|
{
|
|
float Strength;
|
|
if(!m_TuneZone)
|
|
Strength = GameServer()->Tuning()->m_JetpackStrength;
|
|
else
|
|
Strength = GameServer()->TuningList()[m_TuneZone].m_JetpackStrength;
|
|
TakeDamage(Direction * -1.0f * (Strength / 100.0f / 6.11f), 0, m_pPlayer->GetCID(), m_Core.m_ActiveWeapon);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCharacter::HandleNinja()
|
|
{
|
|
if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
|
|
return;
|
|
|
|
if((Server()->Tick() - m_Ninja.m_ActivationTick) > (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000))
|
|
{
|
|
// time's up, return
|
|
RemoveNinja();
|
|
return;
|
|
}
|
|
|
|
int NinjaTime = m_Ninja.m_ActivationTick + (g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000) - Server()->Tick();
|
|
|
|
if(NinjaTime % Server()->TickSpeed() == 0 && NinjaTime / Server()->TickSpeed() <= 5)
|
|
{
|
|
GameServer()->CreateDamageInd(m_Pos, 0, NinjaTime / Server()->TickSpeed(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
|
|
m_Armor = clamp(10 - (NinjaTime / 15), 0, 10);
|
|
|
|
// force ninja Weapon
|
|
SetWeapon(WEAPON_NINJA);
|
|
|
|
m_Ninja.m_CurrentMoveTime--;
|
|
|
|
if(m_Ninja.m_CurrentMoveTime == 0)
|
|
{
|
|
// reset velocity
|
|
m_Core.m_Vel = m_Ninja.m_ActivationDir * m_Ninja.m_OldVelAmount;
|
|
}
|
|
|
|
if(m_Ninja.m_CurrentMoveTime > 0)
|
|
{
|
|
// Set velocity
|
|
m_Core.m_Vel = m_Ninja.m_ActivationDir * g_pData->m_Weapons.m_Ninja.m_Velocity;
|
|
vec2 OldPos = m_Pos;
|
|
GameServer()->Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(GetProximityRadius(), GetProximityRadius()), 0.f);
|
|
|
|
// reset velocity so the client doesn't predict stuff
|
|
m_Core.m_Vel = vec2(0.f, 0.f);
|
|
|
|
// check if we Hit anything along the way
|
|
{
|
|
CCharacter *aEnts[MAX_CLIENTS];
|
|
vec2 Dir = m_Pos - OldPos;
|
|
float Radius = GetProximityRadius() * 2.0f;
|
|
vec2 Center = OldPos + Dir * 0.5f;
|
|
int Num = GameServer()->m_World.FindEntities(Center, Radius, (CEntity **)aEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
|
|
|
|
// check that we're not in solo part
|
|
if(Teams()->m_Core.GetSolo(m_pPlayer->GetCID()))
|
|
return;
|
|
|
|
for(int i = 0; i < Num; ++i)
|
|
{
|
|
if(aEnts[i] == this)
|
|
continue;
|
|
|
|
// Don't hit players in other teams
|
|
if(Team() != aEnts[i]->Team())
|
|
continue;
|
|
|
|
// Don't hit players in solo parts
|
|
if(Teams()->m_Core.GetSolo(aEnts[i]->m_pPlayer->GetCID()))
|
|
return;
|
|
|
|
// make sure we haven't Hit this object before
|
|
bool bAlreadyHit = false;
|
|
for(int j = 0; j < m_NumObjectsHit; j++)
|
|
{
|
|
if(m_apHitObjects[j] == aEnts[i])
|
|
bAlreadyHit = true;
|
|
}
|
|
if(bAlreadyHit)
|
|
continue;
|
|
|
|
// check so we are sufficiently close
|
|
if(distance(aEnts[i]->m_Pos, m_Pos) > (GetProximityRadius() * 2.0f))
|
|
continue;
|
|
|
|
// Hit a player, give him damage and stuffs...
|
|
GameServer()->CreateSound(aEnts[i]->m_Pos, SOUND_NINJA_HIT, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
// set his velocity to fast upward (for now)
|
|
if(m_NumObjectsHit < 10)
|
|
m_apHitObjects[m_NumObjectsHit++] = aEnts[i];
|
|
|
|
aEnts[i]->TakeDamage(vec2(0, -10.0f), g_pData->m_Weapons.m_Ninja.m_pBase->m_Damage, m_pPlayer->GetCID(), WEAPON_NINJA);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void CCharacter::DoWeaponSwitch()
|
|
{
|
|
// make sure we can switch
|
|
if(m_ReloadTimer != 0 || m_QueuedWeapon == -1 || m_aWeapons[WEAPON_NINJA].m_Got || !m_aWeapons[m_QueuedWeapon].m_Got)
|
|
return;
|
|
|
|
// switch Weapon
|
|
SetWeapon(m_QueuedWeapon);
|
|
}
|
|
|
|
void CCharacter::HandleWeaponSwitch()
|
|
{
|
|
int WantedWeapon = m_Core.m_ActiveWeapon;
|
|
if(m_QueuedWeapon != -1)
|
|
WantedWeapon = m_QueuedWeapon;
|
|
|
|
bool Anything = false;
|
|
for(int i = 0; i < NUM_WEAPONS - 1; ++i)
|
|
if(m_aWeapons[i].m_Got)
|
|
Anything = true;
|
|
if(!Anything)
|
|
return;
|
|
// select Weapon
|
|
int Next = CountInput(m_LatestPrevInput.m_NextWeapon, m_LatestInput.m_NextWeapon).m_Presses;
|
|
int Prev = CountInput(m_LatestPrevInput.m_PrevWeapon, m_LatestInput.m_PrevWeapon).m_Presses;
|
|
|
|
if(Next < 128) // make sure we only try sane stuff
|
|
{
|
|
while(Next) // Next Weapon selection
|
|
{
|
|
WantedWeapon = (WantedWeapon + 1) % NUM_WEAPONS;
|
|
if(m_aWeapons[WantedWeapon].m_Got)
|
|
Next--;
|
|
}
|
|
}
|
|
|
|
if(Prev < 128) // make sure we only try sane stuff
|
|
{
|
|
while(Prev) // Prev Weapon selection
|
|
{
|
|
WantedWeapon = (WantedWeapon - 1) < 0 ? NUM_WEAPONS - 1 : WantedWeapon - 1;
|
|
if(m_aWeapons[WantedWeapon].m_Got)
|
|
Prev--;
|
|
}
|
|
}
|
|
|
|
// Direct Weapon selection
|
|
if(m_LatestInput.m_WantedWeapon)
|
|
WantedWeapon = m_Input.m_WantedWeapon - 1;
|
|
|
|
// check for insane values
|
|
if(WantedWeapon >= 0 && WantedWeapon < NUM_WEAPONS && WantedWeapon != m_Core.m_ActiveWeapon && m_aWeapons[WantedWeapon].m_Got)
|
|
m_QueuedWeapon = WantedWeapon;
|
|
|
|
DoWeaponSwitch();
|
|
}
|
|
|
|
void CCharacter::FireWeapon()
|
|
{
|
|
if(m_ReloadTimer != 0)
|
|
{
|
|
if(m_LatestInput.m_Fire & 1)
|
|
{
|
|
Antibot()->OnHammerFireReloading(m_pPlayer->GetCID());
|
|
}
|
|
return;
|
|
}
|
|
|
|
DoWeaponSwitch();
|
|
vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
|
|
|
|
bool FullAuto = false;
|
|
if(m_Core.m_ActiveWeapon == WEAPON_GRENADE || m_Core.m_ActiveWeapon == WEAPON_SHOTGUN || m_Core.m_ActiveWeapon == WEAPON_LASER)
|
|
FullAuto = true;
|
|
if(m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN)
|
|
FullAuto = true;
|
|
// allow firing directly after coming out of freeze or being unfrozen
|
|
// by something
|
|
if(m_FrozenLastTick)
|
|
FullAuto = true;
|
|
|
|
// don't fire hammer when player is deep and sv_deepfly is disabled
|
|
if(!g_Config.m_SvDeepfly && m_Core.m_ActiveWeapon == WEAPON_HAMMER && m_DeepFreeze)
|
|
return;
|
|
|
|
// check if we gonna fire
|
|
bool WillFire = false;
|
|
if(CountInput(m_LatestPrevInput.m_Fire, m_LatestInput.m_Fire).m_Presses)
|
|
WillFire = true;
|
|
|
|
if(FullAuto && (m_LatestInput.m_Fire & 1) && m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
WillFire = true;
|
|
|
|
if(!WillFire)
|
|
return;
|
|
|
|
if(m_FreezeTime)
|
|
{
|
|
// Timer stuff to avoid shrieking orchestra caused by unfreeze-plasma
|
|
if(m_PainSoundTimer <= 0 && !(m_LatestPrevInput.m_Fire & 1))
|
|
{
|
|
m_PainSoundTimer = 1 * Server()->TickSpeed();
|
|
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
return;
|
|
}
|
|
|
|
// check for ammo
|
|
if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo)
|
|
{
|
|
/*// 125ms is a magical limit of how fast a human can click
|
|
m_ReloadTimer = 125 * Server()->TickSpeed() / 1000;
|
|
GameServer()->CreateSound(m_Pos, SOUND_WEAPON_NOAMMO);*/
|
|
return;
|
|
}
|
|
|
|
vec2 ProjStartPos = m_Pos + Direction * GetProximityRadius() * 0.75f;
|
|
|
|
switch(m_Core.m_ActiveWeapon)
|
|
{
|
|
case WEAPON_HAMMER:
|
|
{
|
|
// reset objects Hit
|
|
m_NumObjectsHit = 0;
|
|
GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
|
|
Antibot()->OnHammerFire(m_pPlayer->GetCID());
|
|
|
|
if(m_Hit & DISABLE_HIT_HAMMER)
|
|
break;
|
|
|
|
CCharacter *apEnts[MAX_CLIENTS];
|
|
int Hits = 0;
|
|
int Num = GameServer()->m_World.FindEntities(ProjStartPos, GetProximityRadius() * 0.5f, (CEntity **)apEnts,
|
|
MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
|
|
|
|
for(int i = 0; i < Num; ++i)
|
|
{
|
|
CCharacter *pTarget = apEnts[i];
|
|
|
|
//if ((pTarget == this) || GameServer()->Collision()->IntersectLine(ProjStartPos, pTarget->m_Pos, NULL, NULL))
|
|
if((pTarget == this || (pTarget->IsAlive() && !CanCollide(pTarget->GetPlayer()->GetCID()))))
|
|
continue;
|
|
|
|
// set his velocity to fast upward (for now)
|
|
if(length(pTarget->m_Pos - ProjStartPos) > 0.0f)
|
|
GameServer()->CreateHammerHit(pTarget->m_Pos - normalize(pTarget->m_Pos - ProjStartPos) * GetProximityRadius() * 0.5f, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
else
|
|
GameServer()->CreateHammerHit(ProjStartPos, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
|
|
vec2 Dir;
|
|
if(length(pTarget->m_Pos - m_Pos) > 0.0f)
|
|
Dir = normalize(pTarget->m_Pos - m_Pos);
|
|
else
|
|
Dir = vec2(0.f, -1.f);
|
|
/*pTarget->TakeDamage(vec2(0.f, -1.f) + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f, g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage,
|
|
m_pPlayer->GetCID(), m_Core.m_ActiveWeapon);*/
|
|
|
|
float Strength;
|
|
if(!m_TuneZone)
|
|
Strength = GameServer()->Tuning()->m_HammerStrength;
|
|
else
|
|
Strength = GameServer()->TuningList()[m_TuneZone].m_HammerStrength;
|
|
|
|
vec2 Temp = pTarget->m_Core.m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f;
|
|
Temp = ClampVel(pTarget->m_MoveRestrictions, Temp);
|
|
Temp -= pTarget->m_Core.m_Vel;
|
|
pTarget->TakeDamage((vec2(0.f, -1.0f) + Temp) * Strength, g_pData->m_Weapons.m_Hammer.m_pBase->m_Damage,
|
|
m_pPlayer->GetCID(), m_Core.m_ActiveWeapon);
|
|
pTarget->UnFreeze();
|
|
|
|
if(m_FreezeHammer)
|
|
pTarget->Freeze();
|
|
|
|
Antibot()->OnHammerHit(m_pPlayer->GetCID());
|
|
|
|
Hits++;
|
|
}
|
|
|
|
// if we Hit anything, we have to wait for the reload
|
|
if(Hits)
|
|
{
|
|
float FireDelay;
|
|
if(!m_TuneZone)
|
|
FireDelay = GameServer()->Tuning()->m_HammerHitFireDelay;
|
|
else
|
|
FireDelay = GameServer()->TuningList()[m_TuneZone].m_HammerHitFireDelay;
|
|
m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WEAPON_GUN:
|
|
{
|
|
if(!m_Jetpack || !m_pPlayer->m_NinjaJetpack || m_Core.m_HasTelegunGun)
|
|
{
|
|
int Lifetime;
|
|
if(!m_TuneZone)
|
|
Lifetime = (int)(Server()->TickSpeed() * GameServer()->Tuning()->m_GunLifetime);
|
|
else
|
|
Lifetime = (int)(Server()->TickSpeed() * GameServer()->TuningList()[m_TuneZone].m_GunLifetime);
|
|
|
|
CProjectile *pProj = new CProjectile(
|
|
GameWorld(),
|
|
WEAPON_GUN, //Type
|
|
m_pPlayer->GetCID(), //Owner
|
|
ProjStartPos, //Pos
|
|
Direction, //Dir
|
|
Lifetime, //Span
|
|
0, //Freeze
|
|
0, //Explosive
|
|
0, //Force
|
|
-1 //SoundImpact
|
|
);
|
|
|
|
// pack the Projectile and send it to the client Directly
|
|
CNetObj_Projectile p;
|
|
pProj->FillInfo(&p);
|
|
|
|
CMsgPacker Msg(NETMSGTYPE_SV_EXTRAPROJECTILE);
|
|
Msg.AddInt(1);
|
|
for(unsigned i = 0; i < sizeof(CNetObj_Projectile) / sizeof(int); i++)
|
|
Msg.AddInt(((int *)&p)[i]);
|
|
|
|
Server()->SendMsg(&Msg, MSGFLAG_VITAL, m_pPlayer->GetCID());
|
|
GameServer()->CreateSound(m_Pos, SOUND_GUN_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WEAPON_SHOTGUN:
|
|
{
|
|
/*int ShotSpread = 2;
|
|
|
|
CMsgPacker Msg(NETMSGTYPE_SV_EXTRAPROJECTILE);
|
|
Msg.AddInt(ShotSpread*2+1);
|
|
|
|
for(int i = -ShotSpread; i <= ShotSpread; ++i)
|
|
{
|
|
float Spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f};
|
|
float a = angle(Direction);
|
|
a += Spreading[i+2];
|
|
float v = 1-(absolute(i)/(float)ShotSpread);
|
|
float Speed = mix((float)GameServer()->Tuning()->m_ShotgunSpeeddiff, 1.0f, v);
|
|
CProjectile *pProj = new CProjectile(GameWorld(), WEAPON_SHOTGUN,
|
|
m_pPlayer->GetCID(),
|
|
ProjStartPos,
|
|
vec2(cosf(a), sinf(a))*Speed,
|
|
(int)(Server()->TickSpeed()*GameServer()->Tuning()->m_ShotgunLifetime),
|
|
1, 0, 0, -1);
|
|
|
|
// pack the Projectile and send it to the client Directly
|
|
CNetObj_Projectile p;
|
|
pProj->FillInfo(&p);
|
|
|
|
for(unsigned i = 0; i < sizeof(CNetObj_Projectile)/sizeof(int); i++)
|
|
Msg.AddInt(((int *)&p)[i]);
|
|
}
|
|
|
|
Server()->SendMsg(&Msg, MSGFLAG_VITAL, m_pPlayer->GetCID());
|
|
|
|
GameServer()->CreateSound(m_Pos, SOUND_SHOTGUN_FIRE);*/
|
|
float LaserReach;
|
|
if(!m_TuneZone)
|
|
LaserReach = GameServer()->Tuning()->m_LaserReach;
|
|
else
|
|
LaserReach = GameServer()->TuningList()[m_TuneZone].m_LaserReach;
|
|
|
|
new CLaser(&GameServer()->m_World, m_Pos, Direction, LaserReach, m_pPlayer->GetCID(), WEAPON_SHOTGUN);
|
|
GameServer()->CreateSound(m_Pos, SOUND_SHOTGUN_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
break;
|
|
|
|
case WEAPON_GRENADE:
|
|
{
|
|
int Lifetime;
|
|
if(!m_TuneZone)
|
|
Lifetime = (int)(Server()->TickSpeed() * GameServer()->Tuning()->m_GrenadeLifetime);
|
|
else
|
|
Lifetime = (int)(Server()->TickSpeed() * GameServer()->TuningList()[m_TuneZone].m_GrenadeLifetime);
|
|
|
|
CProjectile *pProj = new CProjectile(
|
|
GameWorld(),
|
|
WEAPON_GRENADE, //Type
|
|
m_pPlayer->GetCID(), //Owner
|
|
ProjStartPos, //Pos
|
|
Direction, //Dir
|
|
Lifetime, //Span
|
|
0, //Freeze
|
|
true, //Explosive
|
|
0, //Force
|
|
SOUND_GRENADE_EXPLODE //SoundImpact
|
|
); //SoundImpact
|
|
|
|
// pack the Projectile and send it to the client Directly
|
|
CNetObj_Projectile p;
|
|
pProj->FillInfo(&p);
|
|
|
|
CMsgPacker Msg(NETMSGTYPE_SV_EXTRAPROJECTILE);
|
|
Msg.AddInt(1);
|
|
for(unsigned i = 0; i < sizeof(CNetObj_Projectile) / sizeof(int); i++)
|
|
Msg.AddInt(((int *)&p)[i]);
|
|
Server()->SendMsg(&Msg, MSGFLAG_VITAL, m_pPlayer->GetCID());
|
|
|
|
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
break;
|
|
|
|
case WEAPON_LASER:
|
|
{
|
|
float LaserReach;
|
|
if(!m_TuneZone)
|
|
LaserReach = GameServer()->Tuning()->m_LaserReach;
|
|
else
|
|
LaserReach = GameServer()->TuningList()[m_TuneZone].m_LaserReach;
|
|
|
|
new CLaser(GameWorld(), m_Pos, Direction, LaserReach, m_pPlayer->GetCID(), WEAPON_LASER);
|
|
GameServer()->CreateSound(m_Pos, SOUND_LASER_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
break;
|
|
|
|
case WEAPON_NINJA:
|
|
{
|
|
// reset Hit objects
|
|
m_NumObjectsHit = 0;
|
|
|
|
m_Ninja.m_ActivationDir = Direction;
|
|
m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000;
|
|
m_Ninja.m_OldVelAmount = length(m_Core.m_Vel);
|
|
|
|
GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
break;
|
|
}
|
|
|
|
m_AttackTick = Server()->Tick();
|
|
|
|
/*if(m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0) // -1 == unlimited
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo--;*/
|
|
|
|
if(!m_ReloadTimer)
|
|
{
|
|
float FireDelay;
|
|
if(!m_TuneZone)
|
|
GameServer()->Tuning()->Get(38 + m_Core.m_ActiveWeapon, &FireDelay);
|
|
else
|
|
GameServer()->TuningList()[m_TuneZone].Get(38 + m_Core.m_ActiveWeapon, &FireDelay);
|
|
m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000;
|
|
}
|
|
}
|
|
|
|
void CCharacter::HandleWeapons()
|
|
{
|
|
//ninja
|
|
HandleNinja();
|
|
HandleJetpack();
|
|
|
|
if(m_PainSoundTimer > 0)
|
|
m_PainSoundTimer--;
|
|
|
|
// check reload timer
|
|
if(m_ReloadTimer)
|
|
{
|
|
m_ReloadTimer--;
|
|
return;
|
|
}
|
|
|
|
// fire Weapon, if wanted
|
|
FireWeapon();
|
|
/*
|
|
// ammo regen
|
|
int AmmoRegenTime = g_pData->m_Weapons.m_aId[m_Core.m_ActiveWeapon].m_Ammoregentime;
|
|
if(AmmoRegenTime)
|
|
{
|
|
// If equipped and not active, regen ammo?
|
|
if (m_ReloadTimer <= 0)
|
|
{
|
|
if (m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart < 0)
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart = Server()->Tick();
|
|
|
|
if ((Server()->Tick() - m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart) >= AmmoRegenTime * Server()->TickSpeed() / 1000)
|
|
{
|
|
// Add some ammo
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo = minimum(m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo + 1, 10);
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart = -1;
|
|
}
|
|
}*/
|
|
|
|
return;
|
|
}
|
|
|
|
void CCharacter::GiveNinja()
|
|
{
|
|
m_Ninja.m_ActivationTick = Server()->Tick();
|
|
m_aWeapons[WEAPON_NINJA].m_Got = true;
|
|
m_aWeapons[WEAPON_NINJA].m_Ammo = -1;
|
|
if(m_Core.m_ActiveWeapon != WEAPON_NINJA)
|
|
m_LastWeapon = m_Core.m_ActiveWeapon;
|
|
m_Core.m_ActiveWeapon = WEAPON_NINJA;
|
|
|
|
if(!m_aWeapons[WEAPON_NINJA].m_Got)
|
|
GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
|
|
void CCharacter::RemoveNinja()
|
|
{
|
|
m_Ninja.m_CurrentMoveTime = 0;
|
|
m_aWeapons[WEAPON_NINJA].m_Got = false;
|
|
m_Core.m_ActiveWeapon = m_LastWeapon;
|
|
|
|
SetWeapon(m_Core.m_ActiveWeapon);
|
|
}
|
|
|
|
void CCharacter::SetEmote(int Emote, int Tick)
|
|
{
|
|
m_EmoteType = Emote;
|
|
m_EmoteStop = Tick;
|
|
}
|
|
|
|
void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput)
|
|
{
|
|
// check for changes
|
|
if(mem_comp(&m_SavedInput, pNewInput, sizeof(CNetObj_PlayerInput)) != 0)
|
|
m_LastAction = Server()->Tick();
|
|
|
|
// copy new input
|
|
mem_copy(&m_Input, pNewInput, sizeof(m_Input));
|
|
m_NumInputs++;
|
|
|
|
// it is not allowed to aim in the center
|
|
if(m_Input.m_TargetX == 0 && m_Input.m_TargetY == 0)
|
|
m_Input.m_TargetY = -1;
|
|
|
|
mem_copy(&m_SavedInput, &m_Input, sizeof(m_SavedInput));
|
|
}
|
|
|
|
void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
|
|
{
|
|
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
|
|
mem_copy(&m_LatestInput, pNewInput, sizeof(m_LatestInput));
|
|
|
|
// it is not allowed to aim in the center
|
|
if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
|
|
m_LatestInput.m_TargetY = -1;
|
|
|
|
Antibot()->OnDirectInput(m_pPlayer->GetCID());
|
|
|
|
if(m_NumInputs > 2 && m_pPlayer->GetTeam() != TEAM_SPECTATORS)
|
|
{
|
|
HandleWeaponSwitch();
|
|
FireWeapon();
|
|
}
|
|
|
|
mem_copy(&m_LatestPrevPrevInput, &m_LatestPrevInput, sizeof(m_LatestInput));
|
|
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
|
|
}
|
|
|
|
void CCharacter::ResetHook()
|
|
{
|
|
m_Core.m_HookedPlayer = -1;
|
|
m_Core.m_HookState = HOOK_RETRACTED;
|
|
m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
|
|
m_Core.m_HookPos = m_Core.m_Pos;
|
|
}
|
|
|
|
void CCharacter::ResetInput()
|
|
{
|
|
m_Input.m_Direction = 0;
|
|
//m_Input.m_Hook = 0;
|
|
// simulate releasing the fire button
|
|
if((m_Input.m_Fire & 1) != 0)
|
|
m_Input.m_Fire++;
|
|
m_Input.m_Fire &= INPUT_STATE_MASK;
|
|
m_Input.m_Jump = 0;
|
|
m_LatestPrevInput = m_LatestInput = m_Input;
|
|
}
|
|
|
|
void CCharacter::Tick()
|
|
{
|
|
/*if(m_pPlayer->m_ForceBalanced)
|
|
{
|
|
char Buf[128];
|
|
str_format(Buf, sizeof(Buf), "You were moved to %s due to team balancing", GameServer()->m_pController->GetTeamName(m_pPlayer->GetTeam()));
|
|
GameServer()->SendBroadcast(Buf, m_pPlayer->GetCID());
|
|
|
|
m_pPlayer->m_ForceBalanced = false;
|
|
}*/
|
|
|
|
if(m_Paused)
|
|
return;
|
|
|
|
// set emote
|
|
if(m_EmoteStop < Server()->Tick())
|
|
{
|
|
m_EmoteType = m_pPlayer->GetDefaultEmote();
|
|
m_EmoteStop = -1;
|
|
}
|
|
|
|
DDRaceTick();
|
|
|
|
Antibot()->OnCharacterTick(m_pPlayer->GetCID());
|
|
|
|
m_Core.m_Input = m_Input;
|
|
m_Core.Tick(true);
|
|
|
|
if(!m_PrevInput.m_Hook && m_Input.m_Hook && !(m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER))
|
|
{
|
|
Antibot()->OnHookAttach(m_pPlayer->GetCID(), false);
|
|
}
|
|
|
|
// handle Weapons
|
|
HandleWeapons();
|
|
|
|
DDRacePostCoreTick();
|
|
|
|
if(m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER)
|
|
{
|
|
if(m_Core.m_HookedPlayer != -1 && GameServer()->m_apPlayers[m_Core.m_HookedPlayer]->GetTeam() != -1)
|
|
{
|
|
Antibot()->OnHookAttach(m_pPlayer->GetCID(), true);
|
|
}
|
|
}
|
|
|
|
// Previnput
|
|
m_PrevInput = m_Input;
|
|
|
|
m_PrevPos = m_Core.m_Pos;
|
|
return;
|
|
}
|
|
|
|
void CCharacter::TickDefered()
|
|
{
|
|
// advance the dummy
|
|
{
|
|
CWorldCore TempWorld;
|
|
m_ReckoningCore.Init(&TempWorld, GameServer()->Collision(), &Teams()->m_Core, m_pTeleOuts);
|
|
m_ReckoningCore.m_Id = m_pPlayer->GetCID();
|
|
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.m_Id = m_pPlayer->GetCID();
|
|
m_Core.Move();
|
|
bool StuckAfterMove = GameServer()->Collision()->TestBox(m_Core.m_Pos, vec2(28.0f, 28.0f));
|
|
m_Core.Quantize();
|
|
bool StuckAfterQuant = GameServer()->Collision()->TestBox(m_Core.m_Pos, vec2(28.0f, 28.0f));
|
|
m_Pos = m_Core.m_Pos;
|
|
|
|
if(!StuckBefore && (StuckAfterMove || StuckAfterQuant))
|
|
{
|
|
// Hackish solution to get rid of strict-aliasing warning
|
|
union
|
|
{
|
|
float f;
|
|
unsigned u;
|
|
} StartPosX, StartPosY, StartVelX, StartVelY;
|
|
|
|
StartPosX.f = StartPos.x;
|
|
StartPosY.f = StartPos.y;
|
|
StartVelX.f = StartVel.x;
|
|
StartVelY.f = StartVel.y;
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x",
|
|
StuckBefore,
|
|
StuckAfterMove,
|
|
StuckAfterQuant,
|
|
StartPos.x, StartPos.y,
|
|
StartVel.x, StartVel.y,
|
|
StartPosX.u, StartPosY.u,
|
|
StartVelX.u, StartVelY.u);
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
|
|
}
|
|
|
|
{
|
|
int Events = m_Core.m_TriggeredEvents;
|
|
int CID = m_pPlayer->GetCID();
|
|
|
|
int64 TeamMask = Teams()->TeamMask(Team(), -1, CID);
|
|
// Some sounds are triggered client-side for the acting player
|
|
// so we need to avoid duplicating them
|
|
int64 TeamMaskExceptSelf = Teams()->TeamMask(Team(), CID, CID);
|
|
// Some are triggered client-side but only on Sixup
|
|
int64 TeamMaskExceptSelfIfSixup = Server()->IsSixup(CID) ? TeamMaskExceptSelf : TeamMask;
|
|
|
|
if(Events & COREEVENT_GROUND_JUMP)
|
|
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_JUMP, TeamMaskExceptSelf);
|
|
|
|
if(Events & COREEVENT_HOOK_ATTACH_PLAYER)
|
|
GameServer()->CreateSound(m_Pos, SOUND_HOOK_ATTACH_PLAYER, TeamMaskExceptSelfIfSixup);
|
|
|
|
if(Events & COREEVENT_HOOK_ATTACH_GROUND)
|
|
GameServer()->CreateSound(m_Pos, SOUND_HOOK_ATTACH_GROUND, TeamMaskExceptSelf);
|
|
|
|
if(Events & COREEVENT_HOOK_HIT_NOHOOK)
|
|
GameServer()->CreateSound(m_Pos, SOUND_HOOK_NOATTACH, TeamMaskExceptSelf);
|
|
}
|
|
|
|
if(m_pPlayer->GetTeam() == TEAM_SPECTATORS)
|
|
{
|
|
m_Pos.x = m_Input.m_TargetX;
|
|
m_Pos.y = m_Input.m_TargetY;
|
|
}
|
|
|
|
// update the m_SendCore if needed
|
|
{
|
|
CNetObj_Character Predicted;
|
|
CNetObj_Character Current;
|
|
mem_zero(&Predicted, sizeof(Predicted));
|
|
mem_zero(&Current, sizeof(Current));
|
|
m_ReckoningCore.Write(&Predicted);
|
|
m_Core.Write(&Current);
|
|
|
|
// only allow dead reackoning for a top of 3 seconds
|
|
if(m_Core.m_pReset || m_ReckoningTick + Server()->TickSpeed() * 3 < Server()->Tick() || mem_comp(&Predicted, &Current, sizeof(CNetObj_Character)) != 0)
|
|
{
|
|
m_ReckoningTick = Server()->Tick();
|
|
m_SendCore = m_Core;
|
|
m_ReckoningCore = m_Core;
|
|
m_Core.m_pReset = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCharacter::TickPaused()
|
|
{
|
|
++m_AttackTick;
|
|
++m_DamageTakenTick;
|
|
++m_Ninja.m_ActivationTick;
|
|
++m_ReckoningTick;
|
|
if(m_LastAction != -1)
|
|
++m_LastAction;
|
|
if(m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart > -1)
|
|
++m_aWeapons[m_Core.m_ActiveWeapon].m_AmmoRegenStart;
|
|
if(m_EmoteStop > -1)
|
|
++m_EmoteStop;
|
|
}
|
|
|
|
bool CCharacter::IncreaseHealth(int Amount)
|
|
{
|
|
if(m_Health >= 10)
|
|
return false;
|
|
m_Health = clamp(m_Health + Amount, 0, 10);
|
|
return true;
|
|
}
|
|
|
|
bool CCharacter::IncreaseArmor(int Amount)
|
|
{
|
|
if(m_Armor >= 10)
|
|
return false;
|
|
m_Armor = clamp(m_Armor + Amount, 0, 10);
|
|
return true;
|
|
}
|
|
|
|
void CCharacter::Die(int Killer, int Weapon)
|
|
{
|
|
if(Server()->IsRecording(m_pPlayer->GetCID()))
|
|
Server()->StopRecord(m_pPlayer->GetCID());
|
|
|
|
int ModeSpecial = GameServer()->m_pController->OnCharacterDeath(this, GameServer()->m_apPlayers[Killer], Weapon);
|
|
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d",
|
|
Killer, Server()->ClientName(Killer),
|
|
m_pPlayer->GetCID(), Server()->ClientName(m_pPlayer->GetCID()), Weapon, ModeSpecial);
|
|
GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
|
|
|
|
// send the kill message
|
|
CNetMsg_Sv_KillMsg Msg;
|
|
Msg.m_Killer = Killer;
|
|
Msg.m_Victim = m_pPlayer->GetCID();
|
|
Msg.m_Weapon = Weapon;
|
|
Msg.m_ModeSpecial = ModeSpecial;
|
|
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
|
|
|
|
// a nice sound
|
|
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_DIE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
|
|
// this is to rate limit respawning to 3 secs
|
|
m_pPlayer->m_PreviousDieTick = m_pPlayer->m_DieTick;
|
|
m_pPlayer->m_DieTick = Server()->Tick();
|
|
|
|
m_Alive = false;
|
|
m_Solo = false;
|
|
|
|
GameServer()->m_World.RemoveEntity(this);
|
|
GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = 0;
|
|
GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCID(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
Teams()->OnCharacterDeath(GetPlayer()->GetCID(), Weapon);
|
|
}
|
|
|
|
bool CCharacter::TakeDamage(vec2 Force, int Dmg, int From, int Weapon)
|
|
{
|
|
/*m_Core.m_Vel += Force;
|
|
|
|
if(GameServer()->m_pController->IsFriendlyFire(m_pPlayer->GetCID(), From) && !g_Config.m_SvTeamdamage)
|
|
return false;
|
|
|
|
// m_pPlayer only inflicts half damage on self
|
|
if(From == m_pPlayer->GetCID())
|
|
Dmg = maximum(1, Dmg/2);
|
|
|
|
m_DamageTaken++;
|
|
|
|
// create healthmod indicator
|
|
if(Server()->Tick() < m_DamageTakenTick+25)
|
|
{
|
|
// make sure that the damage indicators doesn't group together
|
|
GameServer()->CreateDamageInd(m_Pos, m_DamageTaken*0.25f, Dmg);
|
|
}
|
|
else
|
|
{
|
|
m_DamageTaken = 0;
|
|
GameServer()->CreateDamageInd(m_Pos, 0, Dmg);
|
|
}
|
|
|
|
if(Dmg)
|
|
{
|
|
if(m_Armor)
|
|
{
|
|
if(Dmg > 1)
|
|
{
|
|
m_Health--;
|
|
Dmg--;
|
|
}
|
|
|
|
if(Dmg > m_Armor)
|
|
{
|
|
Dmg -= m_Armor;
|
|
m_Armor = 0;
|
|
}
|
|
else
|
|
{
|
|
m_Armor -= Dmg;
|
|
Dmg = 0;
|
|
}
|
|
}
|
|
|
|
m_Health -= Dmg;
|
|
}
|
|
|
|
m_DamageTakenTick = Server()->Tick();
|
|
|
|
// do damage Hit sound
|
|
if(From >= 0 && From != m_pPlayer->GetCID() && GameServer()->m_apPlayers[From])
|
|
{
|
|
int64 Mask = CmaskOne(From);
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() == TEAM_SPECTATORS && GameServer()->m_apPlayers[i]->m_SpectatorID == From)
|
|
Mask |= CmaskOne(i);
|
|
}
|
|
GameServer()->CreateSound(GameServer()->m_apPlayers[From]->m_ViewPos, SOUND_HIT, Mask);
|
|
}
|
|
|
|
// check for death
|
|
if(m_Health <= 0)
|
|
{
|
|
Die(From, Weapon);
|
|
|
|
// set attacker's face to happy (taunt!)
|
|
if (From >= 0 && From != m_pPlayer->GetCID() && GameServer()->m_apPlayers[From])
|
|
{
|
|
CCharacter *pChr = GameServer()->m_apPlayers[From]->GetCharacter();
|
|
if (pChr)
|
|
{
|
|
pChr->m_EmoteType = EMOTE_HAPPY;
|
|
pChr->m_EmoteStop = Server()->Tick() + Server()->TickSpeed();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (Dmg > 2)
|
|
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG);
|
|
else
|
|
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_SHORT);*/
|
|
|
|
if(Dmg)
|
|
{
|
|
m_EmoteType = EMOTE_PAIN;
|
|
m_EmoteStop = Server()->Tick() + 500 * Server()->TickSpeed() / 1000;
|
|
}
|
|
|
|
vec2 Temp = m_Core.m_Vel + Force;
|
|
m_Core.m_Vel = ClampVel(m_MoveRestrictions, Temp);
|
|
|
|
return true;
|
|
}
|
|
|
|
//TODO: Move the emote stuff to a function
|
|
void CCharacter::SnapCharacter(int SnappingClient, int ID)
|
|
{
|
|
CCharacterCore *pCore;
|
|
int Tick, Emote = m_EmoteType, Weapon = m_Core.m_ActiveWeapon, AmmoCount = 0,
|
|
Health = 0, Armor = 0;
|
|
if(!m_ReckoningTick || GameServer()->m_World.m_Paused)
|
|
{
|
|
Tick = 0;
|
|
pCore = &m_Core;
|
|
}
|
|
else
|
|
{
|
|
Tick = m_ReckoningTick;
|
|
pCore = &m_SendCore;
|
|
}
|
|
|
|
// change eyes and use ninja graphic if player is frozen
|
|
if(m_DeepFreeze || m_FreezeTime > 0 || m_FreezeTime == -1)
|
|
{
|
|
if(Emote == EMOTE_NORMAL)
|
|
Emote = m_DeepFreeze ? EMOTE_PAIN : EMOTE_BLINK;
|
|
|
|
Weapon = WEAPON_NINJA;
|
|
}
|
|
|
|
// This could probably happen when m_Jetpack changes instead
|
|
// jetpack and ninjajetpack prediction
|
|
if(m_pPlayer->GetCID() == SnappingClient)
|
|
{
|
|
if(m_Jetpack && Weapon != WEAPON_NINJA)
|
|
{
|
|
if(!(m_NeededFaketuning & FAKETUNE_JETPACK))
|
|
{
|
|
m_NeededFaketuning |= FAKETUNE_JETPACK;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(m_NeededFaketuning & FAKETUNE_JETPACK)
|
|
{
|
|
m_NeededFaketuning &= ~FAKETUNE_JETPACK;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone);
|
|
}
|
|
}
|
|
}
|
|
|
|
// change eyes, use ninja graphic and set ammo count if player has ninjajetpack
|
|
if(m_pPlayer->m_NinjaJetpack && m_Jetpack && m_Core.m_ActiveWeapon == WEAPON_GUN && !m_DeepFreeze && !(m_FreezeTime > 0 || m_FreezeTime == -1) && !m_Core.m_HasTelegunGun)
|
|
{
|
|
if(Emote == EMOTE_NORMAL)
|
|
Emote = EMOTE_HAPPY;
|
|
Weapon = WEAPON_NINJA;
|
|
AmmoCount = 10;
|
|
}
|
|
|
|
if(m_pPlayer->GetCID() == SnappingClient || SnappingClient == -1 ||
|
|
(!g_Config.m_SvStrictSpectateMode && m_pPlayer->GetCID() == GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID))
|
|
{
|
|
Health = m_Health;
|
|
Armor = m_Armor;
|
|
if(m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo > 0)
|
|
AmmoCount = (!m_FreezeTime) ? m_aWeapons[m_Core.m_ActiveWeapon].m_Ammo : 0;
|
|
}
|
|
|
|
if(GetPlayer()->m_Afk || GetPlayer()->IsPaused())
|
|
{
|
|
if(m_FreezeTime > 0 || m_FreezeTime == -1 || m_DeepFreeze)
|
|
Emote = EMOTE_NORMAL;
|
|
else
|
|
Emote = EMOTE_BLINK;
|
|
}
|
|
|
|
if(Emote == EMOTE_NORMAL)
|
|
{
|
|
if(250 - ((Server()->Tick() - m_LastAction) % (250)) < 5)
|
|
Emote = EMOTE_BLINK;
|
|
}
|
|
|
|
if(!Server()->IsSixup(SnappingClient))
|
|
{
|
|
CNetObj_Character *pCharacter = static_cast<CNetObj_Character *>(Server()->SnapNewItem(NETOBJTYPE_CHARACTER, ID, sizeof(CNetObj_Character)));
|
|
if(!pCharacter)
|
|
return;
|
|
|
|
pCore->Write(pCharacter);
|
|
|
|
pCharacter->m_Tick = Tick;
|
|
pCharacter->m_Emote = Emote;
|
|
|
|
if(pCharacter->m_HookedPlayer != -1)
|
|
{
|
|
if(!Server()->Translate(pCharacter->m_HookedPlayer, SnappingClient))
|
|
pCharacter->m_HookedPlayer = -1;
|
|
}
|
|
|
|
pCharacter->m_AttackTick = m_AttackTick;
|
|
pCharacter->m_Direction = m_Input.m_Direction;
|
|
pCharacter->m_Weapon = Weapon;
|
|
pCharacter->m_AmmoCount = AmmoCount;
|
|
pCharacter->m_Health = Health;
|
|
pCharacter->m_Armor = Armor;
|
|
pCharacter->m_PlayerFlags = GetPlayer()->m_PlayerFlags;
|
|
}
|
|
else
|
|
{
|
|
protocol7::CNetObj_Character *pCharacter = static_cast<protocol7::CNetObj_Character *>(Server()->SnapNewItem(NETOBJTYPE_CHARACTER, ID, sizeof(protocol7::CNetObj_Character)));
|
|
if(!pCharacter)
|
|
return;
|
|
|
|
pCore->Write(reinterpret_cast<CNetObj_CharacterCore *>(static_cast<protocol7::CNetObj_CharacterCore *>(pCharacter)));
|
|
|
|
pCharacter->m_Tick = Tick;
|
|
pCharacter->m_Emote = Emote;
|
|
pCharacter->m_AttackTick = m_AttackTick;
|
|
pCharacter->m_Direction = m_Input.m_Direction;
|
|
pCharacter->m_Weapon = Weapon;
|
|
pCharacter->m_AmmoCount = AmmoCount;
|
|
|
|
if(m_FreezeTime > 0 || m_FreezeTime == -1 || m_DeepFreeze)
|
|
pCharacter->m_AmmoCount = m_FreezeTick + g_Config.m_SvFreezeDelay * Server()->TickSpeed();
|
|
else if(Weapon == WEAPON_NINJA)
|
|
pCharacter->m_AmmoCount = m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Server()->TickSpeed() / 1000;
|
|
|
|
pCharacter->m_Health = Health;
|
|
pCharacter->m_Armor = Armor;
|
|
pCharacter->m_TriggeredEvents = 0;
|
|
}
|
|
}
|
|
|
|
void CCharacter::Snap(int SnappingClient)
|
|
{
|
|
int ID = m_pPlayer->GetCID();
|
|
|
|
if(SnappingClient > -1 && !Server()->Translate(ID, SnappingClient))
|
|
return;
|
|
|
|
if(NetworkClipped(SnappingClient))
|
|
return;
|
|
|
|
if(SnappingClient > -1)
|
|
{
|
|
CCharacter *pSnapChar = GameServer()->GetPlayerChar(SnappingClient);
|
|
CPlayer *pSnapPlayer = GameServer()->m_apPlayers[SnappingClient];
|
|
|
|
if(pSnapPlayer->GetTeam() == TEAM_SPECTATORS || pSnapPlayer->IsPaused())
|
|
{
|
|
if(pSnapPlayer->m_SpectatorID != -1 && !CanCollide(pSnapPlayer->m_SpectatorID) && (pSnapPlayer->m_ShowOthers == 0 || (pSnapPlayer->m_ShowOthers == 2 && !SameTeam(pSnapPlayer->m_SpectatorID))))
|
|
return;
|
|
else if(pSnapPlayer->m_SpectatorID == -1 && !CanCollide(SnappingClient) && pSnapPlayer->m_SpecTeam)
|
|
return;
|
|
}
|
|
else if(pSnapChar && !pSnapChar->m_Super && !CanCollide(SnappingClient) && (pSnapPlayer->m_ShowOthers == 0 || (pSnapPlayer->m_ShowOthers == 2 && !SameTeam(SnappingClient))))
|
|
return;
|
|
}
|
|
|
|
if(m_Paused)
|
|
return;
|
|
|
|
SnapCharacter(SnappingClient, ID);
|
|
|
|
CNetObj_DDNetCharacter *pDDNetCharacter = static_cast<CNetObj_DDNetCharacter *>(Server()->SnapNewItem(NETOBJTYPE_DDNETCHARACTER, ID, sizeof(CNetObj_DDNetCharacter)));
|
|
if(!pDDNetCharacter)
|
|
return;
|
|
|
|
pDDNetCharacter->m_Flags = 0;
|
|
if(m_Solo)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_SOLO;
|
|
if(m_Super)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_SUPER;
|
|
if(m_EndlessHook)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_HOOK;
|
|
if(!m_Core.m_Collision || !GameServer()->Tuning()->m_PlayerCollision)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_NO_COLLISION;
|
|
if(!m_Core.m_Hook || !GameServer()->Tuning()->m_PlayerHooking)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_NO_HOOK;
|
|
if(m_SuperJump)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_JUMP;
|
|
if(m_Jetpack)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_JETPACK;
|
|
if(m_Hit & DISABLE_HIT_GRENADE)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_NO_GRENADE_HIT;
|
|
if(m_Hit & DISABLE_HIT_HAMMER)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_NO_HAMMER_HIT;
|
|
if(m_Hit & DISABLE_HIT_LASER)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_NO_LASER_HIT;
|
|
if(m_Hit & DISABLE_HIT_SHOTGUN)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_NO_SHOTGUN_HIT;
|
|
if(m_Core.m_HasTelegunGun)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_GUN;
|
|
if(m_Core.m_HasTelegunGrenade)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_GRENADE;
|
|
if(m_Core.m_HasTelegunLaser)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_TELEGUN_LASER;
|
|
if(m_aWeapons[WEAPON_HAMMER].m_Got)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_HAMMER;
|
|
if(m_aWeapons[WEAPON_GUN].m_Got)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_GUN;
|
|
if(m_aWeapons[WEAPON_SHOTGUN].m_Got)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_SHOTGUN;
|
|
if(m_aWeapons[WEAPON_GRENADE].m_Got)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_GRENADE;
|
|
if(m_aWeapons[WEAPON_LASER].m_Got)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_LASER;
|
|
if(m_Core.m_ActiveWeapon == WEAPON_NINJA)
|
|
pDDNetCharacter->m_Flags |= CHARACTERFLAG_WEAPON_NINJA;
|
|
|
|
pDDNetCharacter->m_FreezeEnd = m_DeepFreeze ? -1 : m_FreezeTime == 0 ? 0 : Server()->Tick() + m_FreezeTime;
|
|
pDDNetCharacter->m_Jumps = m_Core.m_Jumps;
|
|
pDDNetCharacter->m_TeleCheckpoint = m_TeleCheckpoint;
|
|
pDDNetCharacter->m_StrongWeakID = m_StrongWeakID;
|
|
}
|
|
|
|
// DDRace
|
|
|
|
bool CCharacter::CanCollide(int ClientID)
|
|
{
|
|
return Teams()->m_Core.CanCollide(GetPlayer()->GetCID(), ClientID);
|
|
}
|
|
bool CCharacter::SameTeam(int ClientID)
|
|
{
|
|
return Teams()->m_Core.SameTeam(GetPlayer()->GetCID(), ClientID);
|
|
}
|
|
|
|
int CCharacter::Team()
|
|
{
|
|
return Teams()->m_Core.Team(m_pPlayer->GetCID());
|
|
}
|
|
|
|
void CCharacter::SetTeleports(std::map<int, std::vector<vec2>> *pTeleOuts, std::map<int, std::vector<vec2>> *pTeleCheckOuts)
|
|
{
|
|
m_pTeleOuts = pTeleOuts;
|
|
m_pTeleCheckOuts = pTeleCheckOuts;
|
|
m_Core.m_pTeleOuts = pTeleOuts;
|
|
}
|
|
|
|
void CCharacter::FillAntibot(CAntibotCharacterData *pData)
|
|
{
|
|
pData->m_Pos = m_Pos;
|
|
pData->m_Vel = m_Core.m_Vel;
|
|
pData->m_Angle = m_Core.m_Angle;
|
|
pData->m_HookedPlayer = m_Core.m_HookedPlayer;
|
|
pData->m_SpawnTick = m_SpawnTick;
|
|
pData->m_WeaponChangeTick = m_WeaponChangeTick;
|
|
pData->m_aLatestInputs[0].m_TargetX = m_LatestInput.m_TargetX;
|
|
pData->m_aLatestInputs[0].m_TargetY = m_LatestInput.m_TargetY;
|
|
pData->m_aLatestInputs[1].m_TargetX = m_LatestPrevInput.m_TargetX;
|
|
pData->m_aLatestInputs[1].m_TargetY = m_LatestPrevInput.m_TargetY;
|
|
pData->m_aLatestInputs[2].m_TargetX = m_LatestPrevPrevInput.m_TargetX;
|
|
pData->m_aLatestInputs[2].m_TargetY = m_LatestPrevPrevInput.m_TargetY;
|
|
}
|
|
|
|
void CCharacter::HandleBroadcast()
|
|
{
|
|
CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID());
|
|
|
|
if(m_DDRaceState == DDRACE_STARTED && m_CpLastBroadcast != m_CpActive &&
|
|
m_CpActive > -1 && m_CpTick > Server()->Tick() && m_pPlayer->GetClientVersion() == VERSION_VANILLA &&
|
|
pData->m_BestTime && pData->m_aBestCpTime[m_CpActive] != 0)
|
|
{
|
|
char aBroadcast[128];
|
|
float Diff = m_CpCurrent[m_CpActive] - pData->m_aBestCpTime[m_CpActive];
|
|
str_format(aBroadcast, sizeof(aBroadcast), "Checkpoint | Diff : %+5.2f", Diff);
|
|
GameServer()->SendBroadcast(aBroadcast, m_pPlayer->GetCID());
|
|
m_CpLastBroadcast = m_CpActive;
|
|
m_LastBroadcast = Server()->Tick();
|
|
}
|
|
else if((m_pPlayer->m_TimerType == CPlayer::TIMERTYPE_BROADCAST || m_pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && m_DDRaceState == DDRACE_STARTED && m_LastBroadcast + Server()->TickSpeed() * g_Config.m_SvTimeInBroadcastInterval <= Server()->Tick())
|
|
{
|
|
char aBuf[32];
|
|
int Time = (int64)100 * ((float)(Server()->Tick() - m_StartTime) / ((float)Server()->TickSpeed()));
|
|
str_time(Time, TIME_HOURS, aBuf, sizeof(aBuf));
|
|
GameServer()->SendBroadcast(aBuf, m_pPlayer->GetCID(), false);
|
|
m_CpLastBroadcast = m_CpActive;
|
|
m_LastBroadcast = Server()->Tick();
|
|
}
|
|
}
|
|
|
|
void CCharacter::HandleSkippableTiles(int Index)
|
|
{
|
|
// handle death-tiles and leaving gamelayer
|
|
if((GameServer()->Collision()->GetCollisionAt(m_Pos.x + GetProximityRadius() / 3.f, m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
|
|
GameServer()->Collision()->GetCollisionAt(m_Pos.x + GetProximityRadius() / 3.f, m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH ||
|
|
GameServer()->Collision()->GetCollisionAt(m_Pos.x - GetProximityRadius() / 3.f, m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
|
|
GameServer()->Collision()->GetFCollisionAt(m_Pos.x + GetProximityRadius() / 3.f, m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
|
|
GameServer()->Collision()->GetFCollisionAt(m_Pos.x + GetProximityRadius() / 3.f, m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH ||
|
|
GameServer()->Collision()->GetFCollisionAt(m_Pos.x - GetProximityRadius() / 3.f, m_Pos.y - GetProximityRadius() / 3.f) == TILE_DEATH ||
|
|
GameServer()->Collision()->GetCollisionAt(m_Pos.x - GetProximityRadius() / 3.f, m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH) &&
|
|
!m_Super && !(Team() && Teams()->TeeFinished(m_pPlayer->GetCID())))
|
|
{
|
|
Die(m_pPlayer->GetCID(), WEAPON_WORLD);
|
|
return;
|
|
}
|
|
|
|
if(GameLayerClipped(m_Pos))
|
|
{
|
|
Die(m_pPlayer->GetCID(), WEAPON_WORLD);
|
|
return;
|
|
}
|
|
|
|
if(Index < 0)
|
|
return;
|
|
|
|
// handle speedup tiles
|
|
if(GameServer()->Collision()->IsSpeedup(Index))
|
|
{
|
|
vec2 Direction, TempVel = m_Core.m_Vel;
|
|
int Force, MaxSpeed = 0;
|
|
float TeeAngle, SpeederAngle, DiffAngle, SpeedLeft, TeeSpeed;
|
|
GameServer()->Collision()->GetSpeedup(Index, &Direction, &Force, &MaxSpeed);
|
|
if(Force == 255 && MaxSpeed)
|
|
{
|
|
m_Core.m_Vel = Direction * (MaxSpeed / 5);
|
|
}
|
|
else
|
|
{
|
|
if(MaxSpeed > 0 && MaxSpeed < 5)
|
|
MaxSpeed = 5;
|
|
if(MaxSpeed > 0)
|
|
{
|
|
if(Direction.x > 0.0000001f)
|
|
SpeederAngle = -atan(Direction.y / Direction.x);
|
|
else if(Direction.x < 0.0000001f)
|
|
SpeederAngle = atan(Direction.y / Direction.x) + 2.0f * asin(1.0f);
|
|
else if(Direction.y > 0.0000001f)
|
|
SpeederAngle = asin(1.0f);
|
|
else
|
|
SpeederAngle = asin(-1.0f);
|
|
|
|
if(SpeederAngle < 0)
|
|
SpeederAngle = 4.0f * asin(1.0f) + SpeederAngle;
|
|
|
|
if(TempVel.x > 0.0000001f)
|
|
TeeAngle = -atan(TempVel.y / TempVel.x);
|
|
else if(TempVel.x < 0.0000001f)
|
|
TeeAngle = atan(TempVel.y / TempVel.x) + 2.0f * asin(1.0f);
|
|
else if(TempVel.y > 0.0000001f)
|
|
TeeAngle = asin(1.0f);
|
|
else
|
|
TeeAngle = asin(-1.0f);
|
|
|
|
if(TeeAngle < 0)
|
|
TeeAngle = 4.0f * asin(1.0f) + TeeAngle;
|
|
|
|
TeeSpeed = sqrt(pow(TempVel.x, 2) + pow(TempVel.y, 2));
|
|
|
|
DiffAngle = SpeederAngle - TeeAngle;
|
|
SpeedLeft = MaxSpeed / 5.0f - cos(DiffAngle) * TeeSpeed;
|
|
if(abs((int)SpeedLeft) > Force && SpeedLeft > 0.0000001f)
|
|
TempVel += Direction * Force;
|
|
else if(abs((int)SpeedLeft) > Force)
|
|
TempVel += Direction * -Force;
|
|
else
|
|
TempVel += Direction * SpeedLeft;
|
|
}
|
|
else
|
|
TempVel += Direction * Force;
|
|
|
|
m_Core.m_Vel = ClampVel(m_MoveRestrictions, TempVel);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CCharacter::IsSwitchActiveCb(int Number, void *pUser)
|
|
{
|
|
CCharacter *pThis = (CCharacter *)pUser;
|
|
CCollision *pCollision = pThis->GameServer()->Collision();
|
|
return pCollision->m_pSwitchers && pThis->Team() != TEAM_SUPER && pCollision->m_pSwitchers[Number].m_Status[pThis->Team()];
|
|
}
|
|
|
|
void CCharacter::HandleTiles(int Index)
|
|
{
|
|
int MapIndex = Index;
|
|
//int PureMapIndex = GameServer()->Collision()->GetPureMapIndex(m_Pos);
|
|
m_TileIndex = GameServer()->Collision()->GetTileIndex(MapIndex);
|
|
m_TileFIndex = GameServer()->Collision()->GetFTileIndex(MapIndex);
|
|
m_MoveRestrictions = GameServer()->Collision()->GetMoveRestrictions(IsSwitchActiveCb, this, m_Pos, 18.0f, MapIndex);
|
|
if(Index < 0)
|
|
{
|
|
m_LastRefillJumps = false;
|
|
m_LastPenalty = false;
|
|
m_LastBonus = false;
|
|
return;
|
|
}
|
|
int cp = GameServer()->Collision()->IsCheckpoint(MapIndex);
|
|
if(cp != -1 && m_DDRaceState == DDRACE_STARTED && cp > m_CpActive)
|
|
{
|
|
m_CpActive = cp;
|
|
m_CpCurrent[cp] = m_Time;
|
|
m_CpTick = Server()->Tick() + Server()->TickSpeed() * 2;
|
|
if(m_pPlayer->GetClientVersion() >= VERSION_DDRACE)
|
|
{
|
|
CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID());
|
|
CNetMsg_Sv_DDRaceTime Msg;
|
|
Msg.m_Time = (int)m_Time;
|
|
Msg.m_Check = 0;
|
|
Msg.m_Finish = 0;
|
|
|
|
if(m_CpActive != -1 && m_CpTick > Server()->Tick())
|
|
{
|
|
if(pData->m_BestTime && pData->m_aBestCpTime[m_CpActive] != 0)
|
|
{
|
|
float Diff = (m_CpCurrent[m_CpActive] - pData->m_aBestCpTime[m_CpActive]) * 100;
|
|
Msg.m_Check = (int)Diff;
|
|
}
|
|
}
|
|
|
|
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, m_pPlayer->GetCID());
|
|
}
|
|
}
|
|
int cpf = GameServer()->Collision()->IsFCheckpoint(MapIndex);
|
|
if(cpf != -1 && m_DDRaceState == DDRACE_STARTED && cpf > m_CpActive)
|
|
{
|
|
m_CpActive = cpf;
|
|
m_CpCurrent[cpf] = m_Time;
|
|
m_CpTick = Server()->Tick() + Server()->TickSpeed() * 2;
|
|
if(m_pPlayer->GetClientVersion() >= VERSION_DDRACE)
|
|
{
|
|
CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID());
|
|
CNetMsg_Sv_DDRaceTime Msg;
|
|
Msg.m_Time = (int)m_Time;
|
|
Msg.m_Check = 0;
|
|
Msg.m_Finish = 0;
|
|
|
|
if(m_CpActive != -1 && m_CpTick > Server()->Tick())
|
|
{
|
|
if(pData->m_BestTime && pData->m_aBestCpTime[m_CpActive] != 0)
|
|
{
|
|
float Diff = (m_CpCurrent[m_CpActive] - pData->m_aBestCpTime[m_CpActive]) * 100;
|
|
Msg.m_Check = (int)Diff;
|
|
}
|
|
}
|
|
|
|
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, m_pPlayer->GetCID());
|
|
}
|
|
}
|
|
int tcp = GameServer()->Collision()->IsTCheckpoint(MapIndex);
|
|
if(tcp)
|
|
m_TeleCheckpoint = tcp;
|
|
|
|
GameServer()->m_pController->HandleCharacterTiles(this, Index);
|
|
|
|
// freeze
|
|
if(((m_TileIndex == TILE_FREEZE) || (m_TileFIndex == TILE_FREEZE)) && !m_Super && !m_DeepFreeze)
|
|
Freeze();
|
|
else if(((m_TileIndex == TILE_UNFREEZE) || (m_TileFIndex == TILE_UNFREEZE)) && !m_DeepFreeze)
|
|
UnFreeze();
|
|
|
|
// deep freeze
|
|
if(((m_TileIndex == TILE_DFREEZE) || (m_TileFIndex == TILE_DFREEZE)) && !m_Super && !m_DeepFreeze)
|
|
m_DeepFreeze = true;
|
|
else if(((m_TileIndex == TILE_DUNFREEZE) || (m_TileFIndex == TILE_DUNFREEZE)) && !m_Super && m_DeepFreeze)
|
|
m_DeepFreeze = false;
|
|
|
|
// endless hook
|
|
if(((m_TileIndex == TILE_EHOOK_ENABLE) || (m_TileFIndex == TILE_EHOOK_ENABLE)))
|
|
{
|
|
SetEndlessHook(true);
|
|
}
|
|
else if(((m_TileIndex == TILE_EHOOK_DISABLE) || (m_TileFIndex == TILE_EHOOK_DISABLE)))
|
|
{
|
|
SetEndlessHook(false);
|
|
}
|
|
|
|
// hit others
|
|
if(((m_TileIndex == TILE_HIT_DISABLE) || (m_TileFIndex == TILE_HIT_DISABLE)) && m_Hit != (DISABLE_HIT_GRENADE | DISABLE_HIT_HAMMER | DISABLE_HIT_LASER | DISABLE_HIT_SHOTGUN))
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't hit others");
|
|
m_Hit = DISABLE_HIT_GRENADE | DISABLE_HIT_HAMMER | DISABLE_HIT_LASER | DISABLE_HIT_SHOTGUN;
|
|
m_Core.m_NoShotgunHit = true;
|
|
m_Core.m_NoGrenadeHit = true;
|
|
m_Core.m_NoHammerHit = true;
|
|
m_Core.m_NoLaserHit = true;
|
|
m_NeededFaketuning |= FAKETUNE_NOHAMMER;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
else if(((m_TileIndex == TILE_HIT_ENABLE) || (m_TileFIndex == TILE_HIT_ENABLE)) && m_Hit != HIT_ALL)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can hit others");
|
|
m_Hit = HIT_ALL;
|
|
m_Core.m_NoShotgunHit = false;
|
|
m_Core.m_NoGrenadeHit = false;
|
|
m_Core.m_NoHammerHit = false;
|
|
m_Core.m_NoLaserHit = false;
|
|
m_NeededFaketuning &= ~FAKETUNE_NOHAMMER;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
|
|
// collide with others
|
|
if(((m_TileIndex == TILE_NPC_DISABLE) || (m_TileFIndex == TILE_NPC_DISABLE)) && m_Core.m_Collision)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't collide with others");
|
|
m_Core.m_Collision = false;
|
|
m_Core.m_NoCollision = true;
|
|
m_NeededFaketuning |= FAKETUNE_NOCOLL;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
else if(((m_TileIndex == TILE_NPC_ENABLE) || (m_TileFIndex == TILE_NPC_ENABLE)) && !m_Core.m_Collision)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can collide with others");
|
|
m_Core.m_Collision = true;
|
|
m_Core.m_NoCollision = false;
|
|
m_NeededFaketuning &= ~FAKETUNE_NOCOLL;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
|
|
// hook others
|
|
if(((m_TileIndex == TILE_NPH_DISABLE) || (m_TileFIndex == TILE_NPH_DISABLE)) && m_Core.m_Hook)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't hook others");
|
|
m_Core.m_Hook = false;
|
|
m_Core.m_NoHookHit = true;
|
|
m_NeededFaketuning |= FAKETUNE_NOHOOK;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
else if(((m_TileIndex == TILE_NPH_ENABLE) || (m_TileFIndex == TILE_NPH_ENABLE)) && !m_Core.m_Hook)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can hook others");
|
|
m_Core.m_Hook = true;
|
|
m_Core.m_NoHookHit = false;
|
|
m_NeededFaketuning &= ~FAKETUNE_NOHOOK;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
|
|
// unlimited air jumps
|
|
if(((m_TileIndex == TILE_UNLIMITED_JUMPS_ENABLE) || (m_TileFIndex == TILE_UNLIMITED_JUMPS_ENABLE)) && !m_SuperJump)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You have unlimited air jumps");
|
|
m_SuperJump = true;
|
|
m_Core.m_EndlessJump = true;
|
|
if(m_Core.m_Jumps == 0)
|
|
{
|
|
m_NeededFaketuning &= ~FAKETUNE_NOJUMP;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
}
|
|
else if(((m_TileIndex == TILE_UNLIMITED_JUMPS_DISABLE) || (m_TileFIndex == TILE_UNLIMITED_JUMPS_DISABLE)) && m_SuperJump)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You don't have unlimited air jumps");
|
|
m_SuperJump = false;
|
|
m_Core.m_EndlessJump = false;
|
|
if(m_Core.m_Jumps == 0)
|
|
{
|
|
m_NeededFaketuning |= FAKETUNE_NOJUMP;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
}
|
|
|
|
// walljump
|
|
if((m_TileIndex == TILE_WALLJUMP) || (m_TileFIndex == TILE_WALLJUMP))
|
|
{
|
|
if(m_Core.m_Vel.y > 0 && m_Core.m_Colliding && m_Core.m_LeftWall)
|
|
{
|
|
m_Core.m_LeftWall = false;
|
|
m_Core.m_JumpedTotal = m_Core.m_Jumps - 1;
|
|
m_Core.m_Jumped = 1;
|
|
}
|
|
}
|
|
|
|
// jetpack gun
|
|
if(((m_TileIndex == TILE_JETPACK_ENABLE) || (m_TileFIndex == TILE_JETPACK_ENABLE)) && !m_Jetpack)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You have a jetpack gun");
|
|
m_Jetpack = true;
|
|
m_Core.m_Jetpack = true;
|
|
}
|
|
else if(((m_TileIndex == TILE_JETPACK_DISABLE) || (m_TileFIndex == TILE_JETPACK_DISABLE)) && m_Jetpack)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You lost your jetpack gun");
|
|
m_Jetpack = false;
|
|
m_Core.m_Jetpack = false;
|
|
}
|
|
|
|
// refill jumps
|
|
if(((m_TileIndex == TILE_REFILL_JUMPS) || (m_TileFIndex == TILE_REFILL_JUMPS)) && !m_LastRefillJumps)
|
|
{
|
|
m_Core.m_JumpedTotal = 0;
|
|
m_Core.m_Jumped = 0;
|
|
m_LastRefillJumps = true;
|
|
}
|
|
if((m_TileIndex != TILE_REFILL_JUMPS) && (m_TileFIndex != TILE_REFILL_JUMPS))
|
|
{
|
|
m_LastRefillJumps = false;
|
|
}
|
|
|
|
// Teleport gun
|
|
if(((m_TileIndex == TILE_TELE_GUN_ENABLE) || (m_TileFIndex == TILE_TELE_GUN_ENABLE)) && !m_Core.m_HasTelegunGun)
|
|
{
|
|
m_Core.m_HasTelegunGun = true;
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Teleport gun enabled");
|
|
}
|
|
else if(((m_TileIndex == TILE_TELE_GUN_DISABLE) || (m_TileFIndex == TILE_TELE_GUN_DISABLE)) && m_Core.m_HasTelegunGun)
|
|
{
|
|
m_Core.m_HasTelegunGun = false;
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Teleport gun disabled");
|
|
}
|
|
|
|
if(((m_TileIndex == TILE_TELE_GRENADE_ENABLE) || (m_TileFIndex == TILE_TELE_GRENADE_ENABLE)) && !m_Core.m_HasTelegunGrenade)
|
|
{
|
|
m_Core.m_HasTelegunGrenade = true;
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Teleport grenade enabled");
|
|
}
|
|
else if(((m_TileIndex == TILE_TELE_GRENADE_DISABLE) || (m_TileFIndex == TILE_TELE_GRENADE_DISABLE)) && m_Core.m_HasTelegunGrenade)
|
|
{
|
|
m_Core.m_HasTelegunGrenade = false;
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Teleport grenade disabled");
|
|
}
|
|
|
|
if(((m_TileIndex == TILE_TELE_LASER_ENABLE) || (m_TileFIndex == TILE_TELE_LASER_ENABLE)) && !m_Core.m_HasTelegunLaser)
|
|
{
|
|
m_Core.m_HasTelegunLaser = true;
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Teleport laser enabled");
|
|
}
|
|
else if(((m_TileIndex == TILE_TELE_LASER_DISABLE) || (m_TileFIndex == TILE_TELE_LASER_DISABLE)) && m_Core.m_HasTelegunLaser)
|
|
{
|
|
m_Core.m_HasTelegunLaser = false;
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Teleport laser disabled");
|
|
}
|
|
|
|
// stopper
|
|
if(m_Core.m_Vel.y > 0 && (m_MoveRestrictions & CANTMOVE_DOWN))
|
|
{
|
|
m_Core.m_Jumped = 0;
|
|
m_Core.m_JumpedTotal = 0;
|
|
}
|
|
m_Core.m_Vel = ClampVel(m_MoveRestrictions, m_Core.m_Vel);
|
|
|
|
// handle switch tiles
|
|
if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_SWITCHOPEN && Team() != TEAM_SUPER && GameServer()->Collision()->GetSwitchNumber(MapIndex) > 0)
|
|
{
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = true;
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = 0;
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHOPEN;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_SWITCHTIMEDOPEN && Team() != TEAM_SUPER && GameServer()->Collision()->GetSwitchNumber(MapIndex) > 0)
|
|
{
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = true;
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = Server()->Tick() + 1 + GameServer()->Collision()->GetSwitchDelay(MapIndex) * Server()->TickSpeed();
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHTIMEDOPEN;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_SWITCHTIMEDCLOSE && Team() != TEAM_SUPER && GameServer()->Collision()->GetSwitchNumber(MapIndex) > 0)
|
|
{
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = false;
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = Server()->Tick() + 1 + GameServer()->Collision()->GetSwitchDelay(MapIndex) * Server()->TickSpeed();
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHTIMEDCLOSE;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_SWITCHCLOSE && Team() != TEAM_SUPER && GameServer()->Collision()->GetSwitchNumber(MapIndex) > 0)
|
|
{
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()] = false;
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_EndTick[Team()] = 0;
|
|
GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Type[Team()] = TILE_SWITCHCLOSE;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_FREEZE && Team() != TEAM_SUPER)
|
|
{
|
|
if(GameServer()->Collision()->GetSwitchNumber(MapIndex) == 0 || GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()])
|
|
Freeze(GameServer()->Collision()->GetSwitchDelay(MapIndex));
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_DFREEZE && Team() != TEAM_SUPER)
|
|
{
|
|
if(GameServer()->Collision()->GetSwitchNumber(MapIndex) == 0 || GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()])
|
|
m_DeepFreeze = true;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_DUNFREEZE && Team() != TEAM_SUPER)
|
|
{
|
|
if(GameServer()->Collision()->GetSwitchNumber(MapIndex) == 0 || GameServer()->Collision()->m_pSwitchers[GameServer()->Collision()->GetSwitchNumber(MapIndex)].m_Status[Team()])
|
|
m_DeepFreeze = false;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_ENABLE && m_Hit & DISABLE_HIT_HAMMER && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_HAMMER)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can hammer hit others");
|
|
m_Hit &= ~DISABLE_HIT_HAMMER;
|
|
m_NeededFaketuning &= ~FAKETUNE_NOHAMMER;
|
|
m_Core.m_NoHammerHit = false;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_DISABLE && !(m_Hit & DISABLE_HIT_HAMMER) && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_HAMMER)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't hammer hit others");
|
|
m_Hit |= DISABLE_HIT_HAMMER;
|
|
m_NeededFaketuning |= FAKETUNE_NOHAMMER;
|
|
m_Core.m_NoHammerHit = true;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_ENABLE && m_Hit & DISABLE_HIT_SHOTGUN && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_SHOTGUN)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can shoot others with shotgun");
|
|
m_Hit &= ~DISABLE_HIT_SHOTGUN;
|
|
m_Core.m_NoShotgunHit = false;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_DISABLE && !(m_Hit & DISABLE_HIT_SHOTGUN) && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_SHOTGUN)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't shoot others with shotgun");
|
|
m_Hit |= DISABLE_HIT_SHOTGUN;
|
|
m_Core.m_NoShotgunHit = true;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_ENABLE && m_Hit & DISABLE_HIT_GRENADE && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_GRENADE)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can shoot others with grenade");
|
|
m_Hit &= ~DISABLE_HIT_GRENADE;
|
|
m_Core.m_NoGrenadeHit = false;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_DISABLE && !(m_Hit & DISABLE_HIT_GRENADE) && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_GRENADE)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't shoot others with grenade");
|
|
m_Hit |= DISABLE_HIT_GRENADE;
|
|
m_Core.m_NoGrenadeHit = true;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_ENABLE && m_Hit & DISABLE_HIT_LASER && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_LASER)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can shoot others with laser");
|
|
m_Hit &= ~DISABLE_HIT_LASER;
|
|
m_Core.m_NoLaserHit = false;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_HIT_DISABLE && !(m_Hit & DISABLE_HIT_LASER) && GameServer()->Collision()->GetSwitchDelay(MapIndex) == WEAPON_LASER)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You can't shoot others with laser");
|
|
m_Hit |= DISABLE_HIT_LASER;
|
|
m_Core.m_NoLaserHit = true;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_JUMP)
|
|
{
|
|
int newJumps = GameServer()->Collision()->GetSwitchDelay(MapIndex);
|
|
|
|
if(newJumps != m_Core.m_Jumps)
|
|
{
|
|
char aBuf[256];
|
|
if(newJumps == 1)
|
|
str_format(aBuf, sizeof(aBuf), "You can jump %d time", newJumps);
|
|
else
|
|
str_format(aBuf, sizeof(aBuf), "You can jump %d times", newJumps);
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), aBuf);
|
|
|
|
if(newJumps == 0 && !m_SuperJump)
|
|
{
|
|
m_NeededFaketuning |= FAKETUNE_NOJUMP;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
else if(m_Core.m_Jumps == 0)
|
|
{
|
|
m_NeededFaketuning &= ~FAKETUNE_NOJUMP;
|
|
GameServer()->SendTuningParams(m_pPlayer->GetCID(), m_TuneZone); // update tunings
|
|
}
|
|
|
|
m_Core.m_Jumps = newJumps;
|
|
}
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_ADD_TIME && !m_LastPenalty)
|
|
{
|
|
int min = GameServer()->Collision()->GetSwitchDelay(MapIndex);
|
|
int sec = GameServer()->Collision()->GetSwitchNumber(MapIndex);
|
|
int Team = Teams()->m_Core.Team(m_Core.m_Id);
|
|
|
|
m_StartTime -= (min * 60 + sec) * Server()->TickSpeed();
|
|
|
|
if((g_Config.m_SvTeam == 3 || Team != TEAM_FLOCK) && Team != TEAM_SUPER)
|
|
{
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if(Teams()->m_Core.Team(i) == Team && i != m_Core.m_Id && GameServer()->m_apPlayers[i])
|
|
{
|
|
CCharacter *pChar = GameServer()->m_apPlayers[i]->GetCharacter();
|
|
|
|
if(pChar)
|
|
pChar->m_StartTime = m_StartTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_LastPenalty = true;
|
|
}
|
|
else if(GameServer()->Collision()->IsSwitch(MapIndex) == TILE_SUBTRACT_TIME && !m_LastBonus)
|
|
{
|
|
int min = GameServer()->Collision()->GetSwitchDelay(MapIndex);
|
|
int sec = GameServer()->Collision()->GetSwitchNumber(MapIndex);
|
|
int Team = Teams()->m_Core.Team(m_Core.m_Id);
|
|
|
|
m_StartTime += (min * 60 + sec) * Server()->TickSpeed();
|
|
if(m_StartTime > Server()->Tick())
|
|
m_StartTime = Server()->Tick();
|
|
|
|
if((g_Config.m_SvTeam == 3 || Team != TEAM_FLOCK) && Team != TEAM_SUPER)
|
|
{
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if(Teams()->m_Core.Team(i) == Team && i != m_Core.m_Id && GameServer()->m_apPlayers[i])
|
|
{
|
|
CCharacter *pChar = GameServer()->m_apPlayers[i]->GetCharacter();
|
|
|
|
if(pChar)
|
|
pChar->m_StartTime = m_StartTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_LastBonus = true;
|
|
}
|
|
|
|
if(GameServer()->Collision()->IsSwitch(MapIndex) != TILE_ADD_TIME)
|
|
{
|
|
m_LastPenalty = false;
|
|
}
|
|
|
|
if(GameServer()->Collision()->IsSwitch(MapIndex) != TILE_SUBTRACT_TIME)
|
|
{
|
|
m_LastBonus = false;
|
|
}
|
|
|
|
int z = GameServer()->Collision()->IsTeleport(MapIndex);
|
|
if(!g_Config.m_SvOldTeleportHook && !g_Config.m_SvOldTeleportWeapons && z && (*m_pTeleOuts)[z - 1].size())
|
|
{
|
|
if(m_Super)
|
|
return;
|
|
int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleOuts)[z - 1].size());
|
|
m_Core.m_Pos = (*m_pTeleOuts)[z - 1][TeleOut];
|
|
if(!g_Config.m_SvTeleportHoldHook)
|
|
{
|
|
ResetHook();
|
|
}
|
|
if(g_Config.m_SvTeleportLoseWeapons)
|
|
ResetPickups();
|
|
return;
|
|
}
|
|
int evilz = GameServer()->Collision()->IsEvilTeleport(MapIndex);
|
|
if(evilz && (*m_pTeleOuts)[evilz - 1].size())
|
|
{
|
|
if(m_Super)
|
|
return;
|
|
int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleOuts)[evilz - 1].size());
|
|
m_Core.m_Pos = (*m_pTeleOuts)[evilz - 1][TeleOut];
|
|
if(!g_Config.m_SvOldTeleportHook && !g_Config.m_SvOldTeleportWeapons)
|
|
{
|
|
m_Core.m_Vel = vec2(0, 0);
|
|
|
|
if(!g_Config.m_SvTeleportHoldHook)
|
|
{
|
|
ResetHook();
|
|
GameWorld()->ReleaseHooked(GetPlayer()->GetCID());
|
|
}
|
|
if(g_Config.m_SvTeleportLoseWeapons)
|
|
{
|
|
ResetPickups();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if(GameServer()->Collision()->IsCheckEvilTeleport(MapIndex))
|
|
{
|
|
if(m_Super)
|
|
return;
|
|
// first check if there is a TeleCheckOut for the current recorded checkpoint, if not check previous checkpoints
|
|
for(int k = m_TeleCheckpoint - 1; k >= 0; k--)
|
|
{
|
|
if((*m_pTeleCheckOuts)[k].size())
|
|
{
|
|
int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleCheckOuts)[k].size());
|
|
m_Core.m_Pos = (*m_pTeleCheckOuts)[k][TeleOut];
|
|
m_Core.m_Vel = vec2(0, 0);
|
|
|
|
if(!g_Config.m_SvTeleportHoldHook)
|
|
{
|
|
ResetHook();
|
|
GameWorld()->ReleaseHooked(GetPlayer()->GetCID());
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
// if no checkpointout have been found (or if there no recorded checkpoint), teleport to start
|
|
vec2 SpawnPos;
|
|
if(GameServer()->m_pController->CanSpawn(m_pPlayer->GetTeam(), &SpawnPos))
|
|
{
|
|
m_Core.m_Pos = SpawnPos;
|
|
m_Core.m_Vel = vec2(0, 0);
|
|
|
|
if(!g_Config.m_SvTeleportHoldHook)
|
|
{
|
|
ResetHook();
|
|
GameWorld()->ReleaseHooked(GetPlayer()->GetCID());
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if(GameServer()->Collision()->IsCheckTeleport(MapIndex))
|
|
{
|
|
if(m_Super)
|
|
return;
|
|
// first check if there is a TeleCheckOut for the current recorded checkpoint, if not check previous checkpoints
|
|
for(int k = m_TeleCheckpoint - 1; k >= 0; k--)
|
|
{
|
|
if((*m_pTeleCheckOuts)[k].size())
|
|
{
|
|
int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleCheckOuts)[k].size());
|
|
m_Core.m_Pos = (*m_pTeleCheckOuts)[k][TeleOut];
|
|
|
|
if(!g_Config.m_SvTeleportHoldHook)
|
|
{
|
|
ResetHook();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
// if no checkpointout have been found (or if there no recorded checkpoint), teleport to start
|
|
vec2 SpawnPos;
|
|
if(GameServer()->m_pController->CanSpawn(m_pPlayer->GetTeam(), &SpawnPos))
|
|
{
|
|
m_Core.m_Pos = SpawnPos;
|
|
|
|
if(!g_Config.m_SvTeleportHoldHook)
|
|
{
|
|
ResetHook();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CCharacter::HandleTuneLayer()
|
|
{
|
|
m_TuneZoneOld = m_TuneZone;
|
|
int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_Pos);
|
|
m_TuneZone = GameServer()->Collision()->IsTune(CurrentIndex);
|
|
|
|
if(m_TuneZone)
|
|
m_Core.m_Tuning = GameServer()->TuningList()[m_TuneZone]; // throw tunings from specific zone into gamecore
|
|
else
|
|
m_Core.m_Tuning = *GameServer()->Tuning();
|
|
|
|
if(m_TuneZone != m_TuneZoneOld) // don't send tunigs all the time
|
|
{
|
|
// send zone msgs
|
|
SendZoneMsgs();
|
|
}
|
|
}
|
|
|
|
void CCharacter::SendZoneMsgs()
|
|
{
|
|
// send zone leave msg
|
|
// (m_TuneZoneOld >= 0: avoid zone leave msgs on spawn)
|
|
if(m_TuneZoneOld >= 0 && GameServer()->m_aaZoneLeaveMsg[m_TuneZoneOld])
|
|
{
|
|
const char *pCur = GameServer()->m_aaZoneLeaveMsg[m_TuneZoneOld];
|
|
const char *pPos;
|
|
while((pPos = str_find(pCur, "\\n")))
|
|
{
|
|
char aBuf[256];
|
|
str_copy(aBuf, pCur, pPos - pCur + 1);
|
|
aBuf[pPos - pCur + 1] = '\0';
|
|
pCur = pPos + 2;
|
|
GameServer()->SendChatTarget(m_pPlayer->GetCID(), aBuf);
|
|
}
|
|
GameServer()->SendChatTarget(m_pPlayer->GetCID(), pCur);
|
|
}
|
|
// send zone enter msg
|
|
if(GameServer()->m_aaZoneEnterMsg[m_TuneZone])
|
|
{
|
|
const char *pCur = GameServer()->m_aaZoneEnterMsg[m_TuneZone];
|
|
const char *pPos;
|
|
while((pPos = str_find(pCur, "\\n")))
|
|
{
|
|
char aBuf[256];
|
|
str_copy(aBuf, pCur, pPos - pCur + 1);
|
|
aBuf[pPos - pCur + 1] = '\0';
|
|
pCur = pPos + 2;
|
|
GameServer()->SendChatTarget(m_pPlayer->GetCID(), aBuf);
|
|
}
|
|
GameServer()->SendChatTarget(m_pPlayer->GetCID(), pCur);
|
|
}
|
|
}
|
|
|
|
IAntibot *CCharacter::Antibot()
|
|
{
|
|
return GameServer()->Antibot();
|
|
}
|
|
|
|
void CCharacter::SetTeams(CGameTeams *pTeams)
|
|
{
|
|
m_pTeams = pTeams;
|
|
m_Core.SetTeamsCore(&m_pTeams->m_Core);
|
|
}
|
|
|
|
void CCharacter::SetRescue()
|
|
{
|
|
m_RescueTee.Save(this);
|
|
m_SetSavePos = true;
|
|
}
|
|
|
|
void CCharacter::DDRaceTick()
|
|
{
|
|
mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input));
|
|
m_Armor = (m_FreezeTime >= 0) ? 10 - (m_FreezeTime / 15) : 0;
|
|
if(m_Input.m_Direction != 0 || m_Input.m_Jump != 0)
|
|
m_LastMove = Server()->Tick();
|
|
|
|
if(m_FreezeTime > 0 || m_FreezeTime == -1)
|
|
{
|
|
if(m_FreezeTime % Server()->TickSpeed() == Server()->TickSpeed() - 1 || m_FreezeTime == -1)
|
|
{
|
|
GameServer()->CreateDamageInd(m_Pos, 0, (m_FreezeTime + 1) / Server()->TickSpeed(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
}
|
|
if(m_FreezeTime > 0)
|
|
m_FreezeTime--;
|
|
else
|
|
m_Ninja.m_ActivationTick = Server()->Tick();
|
|
m_Input.m_Direction = 0;
|
|
m_Input.m_Jump = 0;
|
|
m_Input.m_Hook = 0;
|
|
if(m_FreezeTime == 1)
|
|
UnFreeze();
|
|
}
|
|
|
|
HandleTuneLayer(); // need this before coretick
|
|
|
|
// look for save position for rescue feature
|
|
if(g_Config.m_SvRescue || ((g_Config.m_SvTeam == 3 || Team() > TEAM_FLOCK) && Team() >= TEAM_FLOCK && Team() < TEAM_SUPER))
|
|
{
|
|
int index = GameServer()->Collision()->GetPureMapIndex(m_Pos);
|
|
int tile = GameServer()->Collision()->GetTileIndex(index);
|
|
int ftile = GameServer()->Collision()->GetFTileIndex(index);
|
|
if(IsGrounded() && tile != TILE_FREEZE && tile != TILE_DFREEZE && ftile != TILE_FREEZE && ftile != TILE_DFREEZE && !m_DeepFreeze)
|
|
{
|
|
SetRescue();
|
|
}
|
|
}
|
|
|
|
m_Core.m_Id = GetPlayer()->GetCID();
|
|
}
|
|
|
|
void CCharacter::DDRacePostCoreTick()
|
|
{
|
|
m_Time = (float)(Server()->Tick() - m_StartTime) / ((float)Server()->TickSpeed());
|
|
|
|
if(m_EndlessHook || (m_Super && g_Config.m_SvEndlessSuperHook))
|
|
m_Core.m_HookTick = 0;
|
|
|
|
m_FrozenLastTick = false;
|
|
|
|
if(m_DeepFreeze && !m_Super)
|
|
Freeze();
|
|
|
|
if(m_Core.m_Jumps == 0 && !m_Super)
|
|
m_Core.m_Jumped = 3;
|
|
else if(m_Core.m_Jumps == 1 && m_Core.m_Jumped > 0)
|
|
m_Core.m_Jumped = 3;
|
|
else if(m_Core.m_JumpedTotal < m_Core.m_Jumps - 1 && m_Core.m_Jumped > 1)
|
|
m_Core.m_Jumped = 1;
|
|
|
|
if((m_Super || m_SuperJump) && m_Core.m_Jumped > 1)
|
|
m_Core.m_Jumped = 1;
|
|
|
|
int CurrentIndex = GameServer()->Collision()->GetMapIndex(m_Pos);
|
|
HandleSkippableTiles(CurrentIndex);
|
|
if(!m_Alive)
|
|
return;
|
|
|
|
// handle Anti-Skip tiles
|
|
std::list<int> Indices = GameServer()->Collision()->GetMapIndices(m_PrevPos, m_Pos);
|
|
if(!Indices.empty())
|
|
{
|
|
for(int &Index : Indices)
|
|
{
|
|
HandleTiles(Index);
|
|
if(!m_Alive)
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HandleTiles(CurrentIndex);
|
|
if(!m_Alive)
|
|
return;
|
|
}
|
|
|
|
// teleport gun
|
|
if(m_TeleGunTeleport)
|
|
{
|
|
GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCID(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
m_Core.m_Pos = m_TeleGunPos;
|
|
if(!m_IsBlueTeleGunTeleport)
|
|
m_Core.m_Vel = vec2(0, 0);
|
|
GameServer()->CreateDeath(m_TeleGunPos, m_pPlayer->GetCID(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
GameServer()->CreateSound(m_TeleGunPos, SOUND_WEAPON_SPAWN, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
|
|
m_TeleGunTeleport = false;
|
|
m_IsBlueTeleGunTeleport = false;
|
|
}
|
|
|
|
HandleBroadcast();
|
|
}
|
|
|
|
bool CCharacter::Freeze(int Seconds)
|
|
{
|
|
if((Seconds <= 0 || m_Super || m_FreezeTime == -1 || m_FreezeTime > Seconds * Server()->TickSpeed()) && Seconds != -1)
|
|
return false;
|
|
if(m_FreezeTick < Server()->Tick() - Server()->TickSpeed() || Seconds == -1)
|
|
{
|
|
m_Armor = 0;
|
|
m_FreezeTime = Seconds == -1 ? Seconds : Seconds * Server()->TickSpeed();
|
|
m_FreezeTick = Server()->Tick();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CCharacter::Freeze()
|
|
{
|
|
return Freeze(g_Config.m_SvFreezeDelay);
|
|
}
|
|
|
|
bool CCharacter::UnFreeze()
|
|
{
|
|
if(m_FreezeTime > 0)
|
|
{
|
|
m_Armor = 10;
|
|
if(!m_aWeapons[m_Core.m_ActiveWeapon].m_Got)
|
|
m_Core.m_ActiveWeapon = WEAPON_GUN;
|
|
m_FreezeTime = 0;
|
|
m_FreezeTick = 0;
|
|
m_FrozenLastTick = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CCharacter::GiveWeapon(int Weapon, bool Remove)
|
|
{
|
|
if(Weapon == WEAPON_NINJA)
|
|
{
|
|
if(Remove)
|
|
RemoveNinja();
|
|
else
|
|
GiveNinja();
|
|
return;
|
|
}
|
|
|
|
if(Remove)
|
|
{
|
|
if(GetActiveWeapon() == Weapon)
|
|
SetActiveWeapon(WEAPON_GUN);
|
|
}
|
|
else
|
|
{
|
|
m_aWeapons[Weapon].m_Ammo = -1;
|
|
}
|
|
|
|
m_aWeapons[Weapon].m_Got = !Remove;
|
|
}
|
|
|
|
void CCharacter::GiveAllWeapons()
|
|
{
|
|
for(int i = WEAPON_GUN; i < NUM_WEAPONS - 1; i++)
|
|
{
|
|
GiveWeapon(i);
|
|
}
|
|
}
|
|
|
|
void CCharacter::ResetPickups()
|
|
{
|
|
for(int i = WEAPON_SHOTGUN; i < NUM_WEAPONS - 1; i++)
|
|
{
|
|
m_aWeapons[i].m_Got = false;
|
|
if(m_Core.m_ActiveWeapon == i)
|
|
m_Core.m_ActiveWeapon = WEAPON_GUN;
|
|
}
|
|
}
|
|
|
|
void CCharacter::SetEndlessHook(bool Enable)
|
|
{
|
|
if(m_EndlessHook == Enable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), Enable ? "Endless hook has been activated" : "Endless hook has been deactived");
|
|
m_EndlessHook = Enable;
|
|
m_Core.m_EndlessHook = Enable;
|
|
}
|
|
|
|
void CCharacter::Pause(bool Pause)
|
|
{
|
|
m_Paused = Pause;
|
|
if(Pause)
|
|
{
|
|
GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = 0;
|
|
GameServer()->m_World.RemoveEntity(this);
|
|
|
|
if(m_Core.m_HookedPlayer != -1) // Keeping hook would allow cheats
|
|
{
|
|
ResetHook();
|
|
GameWorld()->ReleaseHooked(GetPlayer()->GetCID());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Core.m_Vel = vec2(0, 0);
|
|
GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = &m_Core;
|
|
GameServer()->m_World.InsertEntity(this);
|
|
}
|
|
}
|
|
|
|
void CCharacter::DDRaceInit()
|
|
{
|
|
m_Paused = false;
|
|
m_DDRaceState = DDRACE_NONE;
|
|
m_PrevPos = m_Pos;
|
|
m_SetSavePos = false;
|
|
m_LastBroadcast = 0;
|
|
m_TeamBeforeSuper = 0;
|
|
m_Core.m_Id = GetPlayer()->GetCID();
|
|
m_TeleCheckpoint = 0;
|
|
m_EndlessHook = g_Config.m_SvEndlessDrag;
|
|
m_Hit = g_Config.m_SvHit ? HIT_ALL : DISABLE_HIT_GRENADE | DISABLE_HIT_HAMMER | DISABLE_HIT_LASER | DISABLE_HIT_SHOTGUN;
|
|
m_SuperJump = false;
|
|
m_Jetpack = false;
|
|
m_Core.m_Jumps = 2;
|
|
m_FreezeHammer = false;
|
|
|
|
int Team = Teams()->m_Core.Team(m_Core.m_Id);
|
|
|
|
if(Teams()->TeamLocked(Team))
|
|
{
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if(Teams()->m_Core.Team(i) == Team && i != m_Core.m_Id && GameServer()->m_apPlayers[i])
|
|
{
|
|
CCharacter *pChar = GameServer()->m_apPlayers[i]->GetCharacter();
|
|
|
|
if(pChar)
|
|
{
|
|
m_DDRaceState = pChar->m_DDRaceState;
|
|
m_StartTime = pChar->m_StartTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(g_Config.m_SvTeam == 2 && Team == TEAM_FLOCK)
|
|
{
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "Please join a team before you start");
|
|
m_LastStartWarning = Server()->Tick();
|
|
}
|
|
}
|
|
|
|
void CCharacter::Rescue()
|
|
{
|
|
if(m_SetSavePos && !m_Super)
|
|
{
|
|
if(m_LastRescue + (int64)g_Config.m_SvRescueDelay * Server()->TickSpeed() > Server()->Tick())
|
|
{
|
|
char aBuf[256];
|
|
str_format(aBuf, sizeof(aBuf), "You have to wait %d seconds until you can rescue yourself", (int)((m_LastRescue + (int64)g_Config.m_SvRescueDelay * Server()->TickSpeed() - Server()->Tick()) / Server()->TickSpeed()));
|
|
GameServer()->SendChatTarget(GetPlayer()->GetCID(), aBuf);
|
|
return;
|
|
}
|
|
|
|
float StartTime = m_StartTime;
|
|
m_RescueTee.Load(this, Team());
|
|
// Don't load these from saved tee:
|
|
m_Core.m_Vel = vec2(0, 0);
|
|
m_Core.m_HookState = HOOK_IDLE;
|
|
m_StartTime = StartTime;
|
|
m_SavedInput.m_Direction = 0;
|
|
m_SavedInput.m_Jump = 0;
|
|
// simulate releasing the fire button
|
|
if((m_SavedInput.m_Fire & 1) != 0)
|
|
m_SavedInput.m_Fire++;
|
|
m_SavedInput.m_Fire &= INPUT_STATE_MASK;
|
|
m_SavedInput.m_Hook = 0;
|
|
m_pPlayer->Pause(CPlayer::PAUSE_NONE, true);
|
|
}
|
|
}
|