5063: Reworked Draggers and Turrets AND show always hooks if they are in the field of view  r=def- a=C0D3D3V

Fixes #3622, #4723 #4798 and #5054

Here you can find a comparison video with 16.0.3: https://youtu.be/bYwLB1cEMI8

This now also includes the changes from https://github.com/ddnet/ddnet/pull/4980 
So please also see my monologue here: https://github.com/ddnet/ddnet/pull/4980

Physic that got changed by this PR:
- Solo players are not included in the calculation of the next team players to the dragger/turrets. This does not affect any map.
- Turrets get switched correctly if players are solo
- Plasma bullets fired by turrets on solo player only explode for solo players and the other way around (except if they unsolo)
- Plasma bullets can no longer be intercepted by other teams 
- Turrets can shot now independently at the speed of sv_plasma_per_sec for  every team and every solo player


This should be tested by some more players before it get merged!

This reduces the use of snap id's tested here: https://youtu.be/G3nVtdH0--Q 
from 16000 to 69
test with the new dragger: https://youtu.be/mzNrDHP7HQs
I did not add the terminal of the server because you basically see no change in the used snap ids 
The videos are with cl_predict off

Comparison of the new turrets: https://streamable.com/8us8lk
left old turrets, right the new ones

We should delete ranks on (that where made after 13 Jun 2019):
https://ddnet.tw/maps/Increase-32-Your-32-Speed
https://ddnet.tw/maps/Fall-32-into-32-the-32-Future
https://ddnet.tw/maps/turboSeks

## Checklist

- [x] Tested the change ingame
- [x] 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
- [x] Considered possible null pointers and out of bounds array indexing
- [only on 3 maps] Changed no physics that affect existing maps
- [x] 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: c0d3d3v <c0d3d3v@mag-keinen-spam.de>
This commit is contained in:
bors[bot] 2022-05-24 18:26:08 +00:00 committed by GitHub
commit 03f95108b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 772 additions and 531 deletions

View file

@ -2280,6 +2280,8 @@ if(SERVER)
entities/door.h
entities/dragger.cpp
entities/dragger.h
entities/dragger_beam.cpp
entities/dragger_beam.h
entities/flag.cpp
entities/flag.h
entities/gun.cpp

View file

@ -145,16 +145,16 @@ typedef vector2_base<bool> bvec2;
typedef vector2_base<int> ivec2;
template<typename T>
inline bool closest_point_on_line(vector2_base<T> line_point0, vector2_base<T> line_point1, vector2_base<T> target_point, vector2_base<T> &out_pos)
inline bool closest_point_on_line(vector2_base<T> line_pointA, vector2_base<T> line_pointB, vector2_base<T> target_point, vector2_base<T> &out_pos)
{
vector2_base<T> c = target_point - line_point0;
vector2_base<T> v = (line_point1 - line_point0);
T d = length(line_point0 - line_point1);
if(d > 0)
vector2_base<T> AB = line_pointB - line_pointA;
T SquaredMagnitudeAB = dot(AB, AB);
if(SquaredMagnitudeAB > 0)
{
v = normalize_pre_length<T>(v, d);
T t = dot(v, c) / d;
out_pos = mix(line_point0, line_point1, clamp(t, (T)0, (T)1));
vector2_base<T> AP = target_point - line_pointA;
T APdotAB = dot(AP, AB);
T t = APdotAB / SquaredMagnitudeAB;
out_pos = line_pointA + AB * clamp(t, (T)0, (T)1);
return true;
}
else

View file

@ -65,7 +65,7 @@ void CSnapIDPool::Reset()
for(int i = 0; i < MAX_IDS; i++)
{
m_aIDs[i].m_Next = i + 1;
m_aIDs[i].m_State = 0;
m_aIDs[i].m_State = ID_FREE;
}
m_aIDs[MAX_IDS - 1].m_Next = -1;
@ -82,7 +82,7 @@ void CSnapIDPool::RemoveFirstTimeout()
// add it to the free list
m_aIDs[m_FirstTimed].m_Next = m_FirstFree;
m_aIDs[m_FirstTimed].m_State = 0;
m_aIDs[m_FirstTimed].m_State = ID_FREE;
m_FirstFree = m_FirstTimed;
// remove it from the timed list
@ -108,7 +108,7 @@ int CSnapIDPool::NewID()
return ID;
}
m_FirstFree = m_aIDs[m_FirstFree].m_Next;
m_aIDs[ID].m_State = 1;
m_aIDs[ID].m_State = ID_ALLOCATED;
m_Usage++;
m_InUsage++;
return ID;
@ -125,10 +125,10 @@ void CSnapIDPool::FreeID(int ID)
{
if(ID < 0)
return;
dbg_assert(m_aIDs[ID].m_State == 1, "id is not allocated");
dbg_assert(m_aIDs[ID].m_State == ID_ALLOCATED, "id is not allocated");
m_InUsage--;
m_aIDs[ID].m_State = 2;
m_aIDs[ID].m_State = ID_TIMED;
m_aIDs[ID].m_Timeout = time_get() + time_freq() * 5;
m_aIDs[ID].m_Next = -1;

View file

@ -43,6 +43,14 @@ class CSnapIDPool
MAX_IDS = 32 * 1024,
};
// State of a Snap ID
enum
{
ID_FREE = 0,
ID_ALLOCATED = 1,
ID_TIMED = 2,
};
class CID
{
public:

View file

@ -519,14 +519,6 @@ void CHud::RenderWarmupTimer()
}
}
void CHud::MapscreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup)
{
float Points[4];
RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY,
pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), 1.0f, Points);
Graphics()->MapScreen(Points[0], Points[1], Points[2], Points[3]);
}
void CHud::RenderTextInfo()
{
if(g_Config.m_ClShowfps)
@ -660,7 +652,7 @@ void CHud::RenderCursor()
if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK)
return;
MapscreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
// render cursor
int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;

View file

@ -65,8 +65,6 @@ class CHud : public CComponent
void RenderWarmupTimer();
void RenderLocalTime(float x);
void MapscreenToGroup(float CenterX, float CenterY, struct CMapItemGroup *PGroup);
static constexpr float MOVEMENT_INFORMATION_LINE_HEIGHT = 8.0f;
public:

View file

@ -55,14 +55,6 @@ void CMapLayers::EnvelopeUpdate()
}
}
void CMapLayers::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom)
{
float Points[4];
RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY,
pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), Zoom, Points);
Graphics()->MapScreen(Points[0], Points[1], Points[2], Points[3]);
}
void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, float *pChannels, void *pUser)
{
CMapLayers *pThis = (CMapLayers *)pUser;
@ -1567,7 +1559,7 @@ void CMapLayers::OnRender()
{
// set clipping
float Points[4];
MapScreenToGroup(Center.x, Center.y, m_pLayers->GameGroup(), GetCurCamera()->m_Zoom);
RenderTools()->MapScreenToGroup(Center.x, Center.y, m_pLayers->GameGroup(), GetCurCamera()->m_Zoom);
Graphics()->GetScreen(&Points[0], &Points[1], &Points[2], &Points[3]);
float x0 = (pGroup->m_ClipX - Points[0]) / (Points[2] - Points[0]);
float y0 = (pGroup->m_ClipY - Points[1]) / (Points[3] - Points[1]);
@ -1587,10 +1579,10 @@ void CMapLayers::OnRender()
if((!g_Config.m_ClZoomBackgroundLayers || m_Type == TYPE_FULL_DESIGN) && !pGroup->m_ParallaxX && !pGroup->m_ParallaxY)
{
MapScreenToGroup(Center.x, Center.y, pGroup, 1.0f);
RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, 1.0f);
}
else
MapScreenToGroup(Center.x, Center.y, pGroup, GetCurCamera()->m_Zoom);
RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, GetCurCamera()->m_Zoom);
for(int l = 0; l < pGroup->m_NumLayers; l++)
{

View file

@ -35,8 +35,6 @@ class CMapLayers : public CComponent
bool m_OnlineOnly;
void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom = 1.0f);
struct STileLayerVisuals
{
STileLayerVisuals() :

View file

@ -14,13 +14,6 @@
#include "players.h"
void CNamePlates::MapscreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup)
{
float Points[4];
RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY, pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), 1.0f, Points);
Graphics()->MapScreen(Points[0], Points[1], Points[2], Points[3]);
}
void CNamePlates::RenderNameplate(
const CNetObj_Character *pPrevChar,
const CNetObj_Character *pPlayerChar,
@ -109,7 +102,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP
// create nameplates at standard zoom
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
MapscreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
m_aNamePlates[ClientID].m_NameTextWidth = TextRender()->TextWidth(0, FontSize, pName, -1, -1.0f);
@ -135,7 +128,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP
// create nameplates at standard zoom
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
MapscreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
m_aNamePlates[ClientID].m_ClanNameTextWidth = TextRender()->TextWidth(0, FontSizeClan, pClan, -1, -1.0f);

View file

@ -37,8 +37,6 @@ struct SPlayerNamePlate
class CNamePlates : public CComponent
{
void MapscreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup);
void RenderNameplate(
const CNetObj_Character *pPrevChar,
const CNetObj_Character *pPlayerChar,

View file

@ -2341,7 +2341,7 @@ void CGameClient::UpdatePrediction()
pCopy->m_FreezeTime = 0;
if(pCopy->Core()->m_HookedPlayer > 0)
{
pCopy->Core()->m_HookedPlayer = -1;
pCopy->Core()->SetHookedPlayer(-1);
pCopy->Core()->m_HookState = HOOK_IDLE;
}
}

View file

@ -1152,7 +1152,7 @@ void CCharacter::ResetPrediction()
}
if(m_Core.m_HookedPlayer >= 0)
{
m_Core.m_HookedPlayer = -1;
m_Core.SetHookedPlayer(-1);
m_Core.m_HookState = HOOK_IDLE;
}
m_LastWeaponSwitchTick = 0;

View file

@ -274,7 +274,7 @@ void CGameWorld::ReleaseHooked(int ClientID)
CCharacterCore *Core = pChr->Core();
if(Core->m_HookedPlayer == ClientID)
{
Core->m_HookedPlayer = -1;
Core->SetHookedPlayer(-1);
Core->m_HookState = HOOK_RETRACTED;
Core->m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
}

View file

@ -752,7 +752,7 @@ void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *w, float *h
*h *= Zoom;
}
void CRenderTools::MapscreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints)
{
float Width, Height;
@ -764,3 +764,11 @@ void CRenderTools::MapscreenToWorld(float CenterX, float CenterY, float Parallax
pPoints[2] = pPoints[0] + Width;
pPoints[3] = pPoints[1] + Height;
}
void CRenderTools::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom)
{
float Points[4];
MapScreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY,
pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), Zoom, Points);
Graphics()->MapScreen(Points[0], Points[1], Points[2], Points[3]);
}

View file

@ -119,8 +119,9 @@ public:
// helpers
void CalcScreenParams(float Aspect, float Zoom, float *w, float *h);
void MapscreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
void MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints);
void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom = 1.0f);
// DDRace

View file

@ -128,7 +128,7 @@ void CLayerGroup::Convert(CUIRect *pRect)
void CLayerGroup::Mapping(float *pPoints)
{
m_pMap->m_pEditor->RenderTools()->MapscreenToWorld(
m_pMap->m_pEditor->RenderTools()->MapScreenToWorld(
m_pMap->m_pEditor->m_WorldOffsetX, m_pMap->m_pEditor->m_WorldOffsetY,
m_ParallaxX, m_ParallaxY, m_OffsetX, m_OffsetY,
m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->m_WorldZoom, pPoints);
@ -2864,7 +2864,7 @@ void CEditor::DoMapEditor(CUIRect View)
float aPoints[4];
float Aspect = Start + (End - Start) * (i / (float)NumSteps);
RenderTools()->MapscreenToWorld(
RenderTools()->MapScreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
@ -2906,7 +2906,7 @@ void CEditor::DoMapEditor(CUIRect View)
float aAspects[] = {4.0f / 3.0f, 16.0f / 10.0f, 5.0f / 4.0f, 16.0f / 9.0f};
float Aspect = aAspects[i];
RenderTools()->MapscreenToWorld(
RenderTools()->MapScreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
@ -6156,7 +6156,7 @@ void CEditor::ZoomMouseTarget(float ZoomFactor)
// zoom to the current mouse position
// get absolute mouse position
float aPoints[4];
RenderTools()->MapscreenToWorld(
RenderTools()->MapScreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
100.0f, 100.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints);

View file

@ -79,7 +79,8 @@ void CCharacterCore::Reset()
m_HookDir = vec2(0, 0);
m_HookTick = 0;
m_HookState = HOOK_IDLE;
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_AttachedPlayers.clear();
m_Jumped = 0;
m_JumpedTotal = 0;
m_Jumps = 2;
@ -197,14 +198,14 @@ void CCharacterCore::Tick(bool UseInput)
m_HookState = HOOK_FLYING;
m_HookPos = m_Pos + TargetDirection * PhysSize * 1.5f;
m_HookDir = TargetDirection;
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_HookTick = (float)SERVER_TICK_SPEED * (1.25f - m_Tuning.m_HookDuration);
m_TriggeredEvents |= COREEVENT_HOOK_LAUNCH;
}
}
else
{
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_HookState = HOOK_IDLE;
m_HookPos = m_Pos;
}
@ -230,7 +231,7 @@ void CCharacterCore::Tick(bool UseInput)
// do hook
if(m_HookState == HOOK_IDLE)
{
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_HookState = HOOK_IDLE;
m_HookPos = m_Pos;
}
@ -292,7 +293,7 @@ void CCharacterCore::Tick(bool UseInput)
{
m_TriggeredEvents |= COREEVENT_HOOK_ATTACH_PLAYER;
m_HookState = HOOK_GRABBED;
m_HookedPlayer = i;
SetHookedPlayer(i);
Distance = distance(m_HookPos, pCharCore->m_Pos);
}
}
@ -317,7 +318,7 @@ void CCharacterCore::Tick(bool UseInput)
if(GoingThroughTele && m_pWorld && m_pTeleOuts && !m_pTeleOuts->empty() && !(*m_pTeleOuts)[teleNr - 1].empty())
{
m_TriggeredEvents = 0;
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_NewHook = true;
int RandomOut = m_pWorld->RandomOr0((*m_pTeleOuts)[teleNr - 1].size());
@ -342,7 +343,7 @@ void CCharacterCore::Tick(bool UseInput)
else
{
// release hook
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_HookState = HOOK_RETRACTED;
m_HookPos = m_Pos;
}
@ -379,7 +380,7 @@ void CCharacterCore::Tick(bool UseInput)
m_HookTick++;
if(m_HookedPlayer != -1 && (m_HookTick > SERVER_TICK_SPEED + SERVER_TICK_SPEED / 5 || (m_pWorld && !m_pWorld->m_apCharacters[m_HookedPlayer])))
{
m_HookedPlayer = -1;
SetHookedPlayer(-1);
m_HookState = HOOK_RETRACTED;
m_HookPos = m_Pos;
}
@ -549,7 +550,7 @@ void CCharacterCore::ReadCharacterCore(const CNetObj_CharacterCore *pObjCore)
m_HookPos.y = pObjCore->m_HookY;
m_HookDir.x = pObjCore->m_HookDx / 256.0f;
m_HookDir.y = pObjCore->m_HookDy / 256.0f;
m_HookedPlayer = pObjCore->m_HookedPlayer;
SetHookedPlayer(pObjCore->m_HookedPlayer);
m_Jumped = pObjCore->m_Jumped;
m_Direction = pObjCore->m_Direction;
m_Angle = pObjCore->m_Angle;
@ -617,6 +618,30 @@ void CCharacterCore::Quantize()
ReadCharacterCore(&Core);
}
void CCharacterCore::SetHookedPlayer(int HookedPlayer)
{
if(HookedPlayer != m_HookedPlayer)
{
if(m_HookedPlayer != -1 && m_Id != -1 && m_pWorld)
{
CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer];
if(pCharCore)
{
pCharCore->m_AttachedPlayers.erase(m_Id);
}
}
if(HookedPlayer != -1 && m_Id != -1 && m_pWorld)
{
CCharacterCore *pCharCore = m_pWorld->m_apCharacters[HookedPlayer];
if(pCharCore)
{
pCharCore->m_AttachedPlayers.insert(m_Id);
}
}
m_HookedPlayer = HookedPlayer;
}
}
// DDRace
void CCharacterCore::SetTeamsCore(CTeamsCore *pTeams)

View file

@ -7,6 +7,7 @@
#include <base/system.h>
#include <map>
#include <set>
#include <vector>
#include "collision.h"
@ -221,6 +222,8 @@ public:
int m_HookTick;
int m_HookState;
int m_HookedPlayer;
std::set<int> m_AttachedPlayers;
void SetHookedPlayer(int HookedPlayer);
int m_ActiveWeapon;
struct WeaponStat

View file

@ -68,6 +68,7 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
m_Core.Init(&GameServer()->m_World.m_Core, GameServer()->Collision());
m_Core.m_ActiveWeapon = WEAPON_GUN;
m_Core.m_Pos = m_Pos;
m_Core.m_Id = m_pPlayer->GetCID();
GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = &m_Core;
m_ReckoningTick = 0;
@ -107,7 +108,7 @@ void CCharacter::SetWeapon(int W)
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()));
GameServer()->CreateSound(m_Pos, SOUND_WEAPON_SWITCH, TeamMask());
if(m_Core.m_ActiveWeapon < 0 || m_Core.m_ActiveWeapon >= NUM_WEAPONS)
m_Core.m_ActiveWeapon = 0;
@ -204,7 +205,7 @@ void CCharacter::HandleNinja()
if(NinjaTime % Server()->TickSpeed() == 0 && NinjaTime / Server()->TickSpeed() <= 5)
{
GameServer()->CreateDamageInd(m_Pos, 0, NinjaTime / Server()->TickSpeed(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateDamageInd(m_Pos, 0, NinjaTime / Server()->TickSpeed(), TeamMask());
}
m_Armor = clamp(10 - (NinjaTime / 15), 0, 10);
@ -270,7 +271,7 @@ void CCharacter::HandleNinja()
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()));
GameServer()->CreateSound(aEnts[i]->m_Pos, SOUND_NINJA_HIT, TeamMask());
// set his velocity to fast upward (for now)
if(m_NumObjectsHit < 10)
m_apHitObjects[m_NumObjectsHit++] = aEnts[i];
@ -385,7 +386,7 @@ void CCharacter::FireWeapon()
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()));
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_PAIN_LONG, TeamMask());
}
return;
}
@ -407,7 +408,7 @@ void CCharacter::FireWeapon()
{
// reset objects Hit
m_NumObjectsHit = 0;
GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, TeamMask());
Antibot()->OnHammerFire(m_pPlayer->GetCID());
@ -429,9 +430,9 @@ void CCharacter::FireWeapon()
// 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()));
GameServer()->CreateHammerHit(pTarget->m_Pos - normalize(pTarget->m_Pos - ProjStartPos) * GetProximityRadius() * 0.5f, TeamMask());
else
GameServer()->CreateHammerHit(ProjStartPos, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateHammerHit(ProjStartPos, TeamMask());
vec2 Dir;
if(length(pTarget->m_Pos - m_Pos) > 0.0f)
@ -498,7 +499,7 @@ void CCharacter::FireWeapon()
-1 //SoundImpact
);
GameServer()->CreateSound(m_Pos, SOUND_GUN_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateSound(m_Pos, SOUND_GUN_FIRE, TeamMask());
}
}
break;
@ -530,7 +531,7 @@ void CCharacter::FireWeapon()
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()));
GameServer()->CreateSound(m_Pos, SOUND_SHOTGUN_FIRE, TeamMask());
}
break;
@ -555,7 +556,7 @@ void CCharacter::FireWeapon()
SOUND_GRENADE_EXPLODE //SoundImpact
); //SoundImpact
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateSound(m_Pos, SOUND_GRENADE_FIRE, TeamMask());
}
break;
@ -568,7 +569,7 @@ void CCharacter::FireWeapon()
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()));
GameServer()->CreateSound(m_Pos, SOUND_LASER_FIRE, TeamMask());
}
break;
@ -581,7 +582,7 @@ void CCharacter::FireWeapon()
m_Core.m_Ninja.m_CurrentMoveTime = g_pData->m_Weapons.m_Ninja.m_Movetime * Server()->TickSpeed() / 1000;
m_Core.m_Ninja.m_OldVelAmount = length(m_Core.m_Vel);
GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateSound(m_Pos, SOUND_NINJA_FIRE, TeamMask());
}
break;
}
@ -632,7 +633,7 @@ void CCharacter::GiveNinja()
m_Core.m_ActiveWeapon = WEAPON_NINJA;
if(!m_Core.m_aWeapons[WEAPON_NINJA].m_Got)
GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateSound(m_Pos, SOUND_PICKUP_NINJA, TeamMask());
}
void CCharacter::RemoveNinja()
@ -690,7 +691,7 @@ void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
void CCharacter::ResetHook()
{
m_Core.m_HookedPlayer = -1;
m_Core.SetHookedPlayer(-1);
m_Core.m_HookState = HOOK_RETRACTED;
m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
m_Core.m_HookPos = m_Core.m_Pos;
@ -912,7 +913,7 @@ void CCharacter::Die(int Killer, int Weapon)
Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
// a nice sound
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_DIE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateSound(m_Pos, SOUND_PLAYER_DIE, TeamMask());
// this is to rate limit respawning to 3 secs
m_pPlayer->m_PreviousDieTick = m_pPlayer->m_DieTick;
@ -923,7 +924,7 @@ void CCharacter::Die(int Killer, int Weapon)
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()));
GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCID(), TeamMask());
Teams()->OnCharacterDeath(GetPlayer()->GetCID(), Weapon);
}
@ -1191,8 +1192,33 @@ void CCharacter::Snap(int SnappingClient)
if(!Server()->Translate(ID, SnappingClient))
return;
if(NetworkClipped(SnappingClient) || !CanSnapCharacter(SnappingClient))
if(!CanSnapCharacter(SnappingClient))
{
return;
}
// A player may not be clipped away if his hook or a hook attached to him is in the field of view
bool PlayerAndHookNotInView = NetworkClippedLine(SnappingClient, m_Pos, m_Core.m_HookPos);
bool AttachedHookInView = false;
if(PlayerAndHookNotInView)
{
for(const auto &AttachedPlayerID : m_Core.m_AttachedPlayers)
{
CCharacter *OtherPlayer = GameServer()->GetPlayerChar(AttachedPlayerID);
if(OtherPlayer && OtherPlayer->m_Core.m_HookedPlayer == ID)
{
if(!NetworkClippedLine(SnappingClient, m_Pos, OtherPlayer->m_Pos))
{
AttachedHookInView = true;
break;
}
}
}
}
if(PlayerAndHookNotInView && !AttachedHookInView)
{
return;
}
SnapCharacter(SnappingClient, ID);
@ -2092,7 +2118,7 @@ void CCharacter::DDRaceTick()
{
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()));
GameServer()->CreateDamageInd(m_Pos, 0, (m_FreezeTime + 1) / Server()->TickSpeed(), TeamMask());
}
if(m_FreezeTime > 0)
m_FreezeTime--;
@ -2201,12 +2227,12 @@ void CCharacter::DDRacePostCoreTick()
// teleport gun
if(m_TeleGunTeleport)
{
GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCID(), Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCID(), TeamMask());
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()));
GameServer()->CreateDeath(m_TeleGunPos, m_pPlayer->GetCID(), TeamMask());
GameServer()->CreateSound(m_TeleGunPos, SOUND_WEAPON_SPAWN, TeamMask());
m_TeleGunTeleport = false;
m_IsBlueTeleGunTeleport = false;
}
@ -2402,5 +2428,5 @@ int64_t CCharacter::TeamMask()
void CCharacter::SwapClients(int Client1, int Client2)
{
m_Core.m_HookedPlayer = m_Core.m_HookedPlayer == Client1 ? Client2 : m_Core.m_HookedPlayer == Client2 ? Client1 : m_Core.m_HookedPlayer;
m_Core.SetHookedPlayer(m_Core.m_HookedPlayer == Client1 ? Client2 : m_Core.m_HookedPlayer == Client2 ? Client1 : m_Core.m_HookedPlayer);
}

View file

@ -1,5 +1,4 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
#include "dragger.h"
#include <engine/config.h>
#include <engine/server.h>
#include <engine/shared/config.h>
@ -11,144 +10,150 @@
#include <game/version.h>
#include "character.h"
#include "dragger.h"
#include "dragger_beam.h"
CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
int CaughtTeam, int Layer, int Number) :
CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool IgnoreWalls, int Layer, int Number) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
{
m_TargetID = -1;
m_Layer = Layer;
m_Number = Number;
m_Pos = Pos;
m_Strength = Strength;
m_IgnoreWalls = IgnoreWalls;
m_Layer = Layer;
m_Number = Number;
m_EvalTick = Server()->Tick();
m_NW = NW;
m_CaughtTeam = CaughtTeam;
for(auto &TargetId : m_TargetIdInTeam)
{
TargetId = -1;
}
mem_zero(m_apDraggerBeam, sizeof(m_apDraggerBeam));
GameWorld()->InsertEntity(this);
for(int &SoloID : m_SoloIDs)
{
SoloID = -1;
}
for(int &SoloEntID : m_SoloEntIDs)
{
SoloEntID = -1;
}
}
void CDragger::Move()
void CDragger::Tick()
{
if(m_TargetID >= 0)
if(Server()->Tick() % int(Server()->TickSpeed() * 0.15f) == 0)
{
CCharacter *pTarget = GameServer()->GetPlayerChar(m_TargetID);
if(!pTarget || pTarget->m_Super || pTarget->IsPaused() || (m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pTarget->Team()]))
int Flags;
m_EvalTick = Server()->Tick();
int index = GameServer()->Collision()->IsMover(m_Pos.x, m_Pos.y, &Flags);
if(index)
{
m_TargetID = -1;
m_Core = GameServer()->Collision()->CpSpeed(index, Flags);
}
}
m_Pos += m_Core;
CCharacter *TempEnts[MAX_CLIENTS];
mem_zero(TempEnts, sizeof(TempEnts));
for(int &SoloEntID : m_SoloEntIDs)
{
SoloEntID = -1;
}
int Num = GameServer()->m_World.FindEntities(m_Pos, g_Config.m_SvDraggerRange,
(CEntity **)TempEnts, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
int Id = -1;
int MinLen = 0;
CCharacter *Temp;
for(int i = 0; i < Num; i++)
{
Temp = TempEnts[i];
m_SoloEntIDs[i] = Temp->GetPlayer()->GetCID();
if(Temp->Team() != m_CaughtTeam)
// Adopt the new position for all outgoing laser beams
for(auto &DraggerBeam : m_apDraggerBeam)
{
m_SoloEntIDs[i] = -1;
continue;
}
if(m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Temp->Team()])
{
m_SoloEntIDs[i] = -1;
continue;
}
int Res =
m_NW ?
GameServer()->Collision()->IntersectNoLaserNW(m_Pos, Temp->m_Pos, 0, 0) :
GameServer()->Collision()->IntersectNoLaser(m_Pos, Temp->m_Pos, 0, 0);
if(Res == 0)
{
int Len = length(Temp->m_Pos - m_Pos);
if(MinLen == 0 || MinLen > Len)
if(DraggerBeam != nullptr)
{
MinLen = Len;
Id = i;
DraggerBeam->SetPos(m_Pos);
}
if(!Temp->Teams()->m_Core.GetSolo(Temp->GetPlayer()->GetCID()))
m_SoloEntIDs[i] = -1;
}
else
LookForPlayersToDrag();
}
}
void CDragger::LookForPlayersToDrag()
{
// Create a list of players who are in the range of the dragger
CCharacter *pPlayersInRange[MAX_CLIENTS];
mem_zero(pPlayersInRange, sizeof(pPlayersInRange));
int NumPlayersInRange = GameServer()->m_World.FindEntities(m_Pos,
g_Config.m_SvDraggerRange - CCharacter::ms_PhysSize,
(CEntity **)pPlayersInRange, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
// The closest player (within range) in a team is selected as the target
int ClosestTargetIdInTeam[MAX_CLIENTS];
bool CanStillBeTeamTarget[MAX_CLIENTS];
bool IsTarget[MAX_CLIENTS];
int MinDistInTeam[MAX_CLIENTS];
mem_zero(CanStillBeTeamTarget, sizeof(CanStillBeTeamTarget));
mem_zero(MinDistInTeam, sizeof(MinDistInTeam));
mem_zero(IsTarget, sizeof(IsTarget));
for(int &TargetId : ClosestTargetIdInTeam)
{
TargetId = -1;
}
for(int i = 0; i < NumPlayersInRange; i++)
{
CCharacter *pTarget = pPlayersInRange[i];
const int &TargetTeam = pTarget->Team();
// Do not create a dragger beam for super player
if(TargetTeam == TEAM_SUPER)
{
m_SoloEntIDs[i] = -1;
continue;
}
// If the dragger is disabled for the target's team, no dragger beam will be generated
if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
!GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[TargetTeam])
{
continue;
}
// Dragger beams can be created only for reachable, alive players
int IsReachable =
m_IgnoreWalls ?
!GameServer()->Collision()->IntersectNoLaserNW(m_Pos, pTarget->m_Pos, 0, 0) :
!GameServer()->Collision()->IntersectNoLaser(m_Pos, pTarget->m_Pos, 0, 0);
if(IsReachable && pTarget->IsAlive())
{
const int &TargetClientId = pTarget->GetPlayer()->GetCID();
// Solo players are dragged independently from the rest of the team
if(pTarget->Teams()->m_Core.GetSolo(TargetClientId))
{
IsTarget[TargetClientId] = true;
}
else
{
int Distance = distance(pTarget->m_Pos, m_Pos);
if(MinDistInTeam[TargetTeam] == 0 || MinDistInTeam[TargetTeam] > Distance)
{
MinDistInTeam[TargetTeam] = Distance;
ClosestTargetIdInTeam[TargetTeam] = TargetClientId;
}
CanStillBeTeamTarget[TargetClientId] = true;
}
}
}
if(m_TargetID < 0)
m_TargetID = Id != -1 ? TempEnts[Id]->GetPlayer()->GetCID() : -1;
if(m_TargetID >= 0)
// Set the closest player for each team as a target if the team does not have a target player yet
for(int i = 0; i < MAX_CLIENTS; i++)
{
const CCharacter *pTarget = GameServer()->GetPlayerChar(m_TargetID);
for(auto &SoloEntID : m_SoloEntIDs)
if((m_TargetIdInTeam[i] != -1 && !CanStillBeTeamTarget[m_TargetIdInTeam[i]]) || m_TargetIdInTeam[i] == -1)
{
if(GameServer()->GetPlayerChar(SoloEntID) == pTarget)
SoloEntID = -1;
m_TargetIdInTeam[i] = ClosestTargetIdInTeam[i];
}
if(m_TargetIdInTeam[i] != -1)
{
IsTarget[m_TargetIdInTeam[i]] = true;
}
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
// Create Dragger Beams which have not been created yet
if(IsTarget[i] && m_apDraggerBeam[i] == nullptr)
{
m_apDraggerBeam[i] = new CDraggerBeam(&GameServer()->m_World, this, m_Pos, m_Strength, m_IgnoreWalls, i);
}
// Remove dragger beams that have not yet been deleted
else if(!IsTarget[i] && m_apDraggerBeam[i] != nullptr)
{
m_apDraggerBeam[i]->Reset();
}
}
}
void CDragger::Drag()
void CDragger::RemoveDraggerBeam(int ClientID)
{
if(m_TargetID < 0)
return;
CCharacter *pTarget = GameServer()->GetPlayerChar(m_TargetID);
for(int i = -1; i < MAX_CLIENTS; i++)
{
if(i >= 0)
pTarget = GameServer()->GetPlayerChar(m_SoloEntIDs[i]);
if(!pTarget)
continue;
int Res = 0;
if(!m_NW)
Res = GameServer()->Collision()->IntersectNoLaser(m_Pos,
pTarget->m_Pos, 0, 0);
else
Res = GameServer()->Collision()->IntersectNoLaserNW(m_Pos,
pTarget->m_Pos, 0, 0);
if(Res || length(m_Pos - pTarget->m_Pos) > g_Config.m_SvDraggerRange)
{
pTarget = 0;
if(i == -1)
m_TargetID = -1;
else
m_SoloEntIDs[i] = -1;
}
else if(length(m_Pos - pTarget->m_Pos) > 28)
{
vec2 Temp = pTarget->Core()->m_Vel + (normalize(m_Pos - pTarget->m_Pos) * m_Strength);
pTarget->Core()->m_Vel = ClampVel(pTarget->m_MoveRestrictions, Temp);
}
}
m_apDraggerBeam[ClientID] = nullptr;
}
void CDragger::Reset()
@ -156,157 +161,80 @@ void CDragger::Reset()
m_MarkedForDestroy = true;
}
void CDragger::Tick()
{
if(((CGameControllerDDRace *)GameServer()->m_pController)->m_Teams.GetTeamState(m_CaughtTeam) == CGameTeams::TEAMSTATE_EMPTY)
return;
if(Server()->Tick() % int(Server()->TickSpeed() * 0.15f) == 0)
{
int Flags;
m_EvalTick = Server()->Tick();
int index = GameServer()->Collision()->IsMover(m_Pos.x, m_Pos.y,
&Flags);
if(index)
{
m_Core = GameServer()->Collision()->CpSpeed(index, Flags);
}
m_Pos += m_Core;
Move();
}
Drag();
}
void CDragger::Snap(int SnappingClient)
{
if(((CGameControllerDDRace *)GameServer()->m_pController)->m_Teams.GetTeamState(m_CaughtTeam) == CGameTeams::TEAMSTATE_EMPTY)
// Only players with the dragger in their field of view or who want to see everything will receive the snap
if(NetworkClipped(SnappingClient))
return;
if(NetworkClipped(SnappingClient, m_Pos))
return;
// Send the dragger in its resting position if the player would not otherwise see a dragger beam
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apDraggerBeam[i] != nullptr)
{
CCharacter *pChar = GameServer()->GetPlayerChar(i);
if(pChar && pChar->CanSnapCharacter(SnappingClient))
{
return;
}
}
}
CCharacter *pChar = GameServer()->GetPlayerChar(SnappingClient);
if(SnappingClient != SERVER_DEMO_CLIENT && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == TEAM_SPECTATORS || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) && GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
pChar = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
if(pChar && pChar->Team() != m_CaughtTeam)
return;
int SnappingClientVersion = SnappingClient != SERVER_DEMO_CLIENT ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
int SnappingClientVersion = SnappingClient != SERVER_DEMO_CLIENT ?
GameServer()->GetClientVersion(SnappingClient) :
CLIENT_VERSIONNR;
CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH)
{
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(),
sizeof(CNetObj_EntityEx)));
if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData->m_EntityClass = clamp(ENTITYCLASS_DRAGGER_WEAK + round_to_int(m_Strength) - 1, (int)ENTITYCLASS_DRAGGER_WEAK, (int)ENTITYCLASS_DRAGGER_STRONG);
pEntData->m_EntityClass = clamp(ENTITYCLASS_DRAGGER_WEAK + round_to_int(m_Strength) - 1,
(int)ENTITYCLASS_DRAGGER_WEAK, (int)ENTITYCLASS_DRAGGER_STRONG);
}
}
CCharacter *pTarget = m_TargetID < 0 ? 0 : GameServer()->GetPlayerChar(m_TargetID);
for(int &SoloID : m_SoloIDs)
else
{
if(SoloID == -1)
break;
// Emulate turned off blinking dragger for old clients
CCharacter *pChar = GameServer()->GetPlayerChar(SnappingClient);
if(SnappingClient != SERVER_DEMO_CLIENT &&
(GameServer()->m_apPlayers[SnappingClient]->GetTeam() == TEAM_SPECTATORS ||
GameServer()->m_apPlayers[SnappingClient]->IsPaused()) &&
GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
pChar = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
Server()->SnapFreeID(SoloID);
SoloID = -1;
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(pChar && m_Layer == LAYER_SWITCH && m_Number > 0 &&
!GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pChar->Team()] && (!Tick))
return;
}
int pos = 0;
CNetObj_Laser *obj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
for(int i = -1; i < MAX_CLIENTS; i++)
if(!obj)
return;
obj->m_X = (int)m_Pos.x;
obj->m_Y = (int)m_Pos.y;
obj->m_FromX = (int)m_Pos.x;
obj->m_FromY = (int)m_Pos.y;
if(pEntData)
{
if(i >= 0)
{
pTarget = GameServer()->GetPlayerChar(m_SoloEntIDs[i]);
if(!pTarget)
continue;
}
if(pTarget && NetworkClipped(SnappingClient, pTarget->m_Pos))
continue;
if(i != -1 || SnappingClientVersion < VERSION_DDNET_SWITCH)
{
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(pChar && m_Layer == LAYER_SWITCH && m_Number && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pChar->Team()] && (!Tick))
continue;
}
// send to spectators only active draggers and some inactive from team 0
if(!pChar && !pTarget && m_CaughtTeam != 0)
continue;
if(pChar && pTarget && pTarget->GetPlayer()->GetCID() != pChar->GetPlayer()->GetCID() && ((pChar->GetPlayer()->m_ShowOthers == SHOW_OTHERS_OFF && (pChar->Teams()->m_Core.GetSolo(SnappingClient) || pChar->Teams()->m_Core.GetSolo(pTarget->GetPlayer()->GetCID()))) || (pChar->GetPlayer()->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM && !pTarget->SameTeam(SnappingClient))))
{
continue;
}
CNetObj_Laser *obj;
if(i == -1)
{
obj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
}
else
{
m_SoloIDs[pos] = Server()->SnapNewID();
obj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem( // TODO: Have to free IDs again?
NETOBJTYPE_LASER, m_SoloIDs[pos], sizeof(CNetObj_Laser)));
pos++;
}
if(!obj)
continue;
obj->m_X = (int)m_Pos.x;
obj->m_Y = (int)m_Pos.y;
if(pTarget)
{
obj->m_FromX = (int)pTarget->m_Pos.x;
obj->m_FromY = (int)pTarget->m_Pos.y;
}
else
{
obj->m_FromX = (int)m_Pos.x;
obj->m_FromY = (int)m_Pos.y;
}
if(pEntData && i == -1)
{
obj->m_StartTick = 0;
}
else
{
int StartTick = m_EvalTick;
if(StartTick < Server()->Tick() - 4)
StartTick = Server()->Tick() - 4;
else if(StartTick > Server()->Tick())
StartTick = Server()->Tick();
obj->m_StartTick = StartTick;
}
obj->m_StartTick = 0;
}
else
{
int StartTick = m_EvalTick;
if(StartTick < Server()->Tick() - 4)
StartTick = Server()->Tick() - 4;
else if(StartTick > Server()->Tick())
StartTick = Server()->Tick();
obj->m_StartTick = StartTick;
}
}
CDraggerTeam::CDraggerTeam(CGameWorld *pGameWorld, vec2 Pos, float Strength,
bool NW, int Layer, int Number)
{
for(int i = 0; i < MAX_CLIENTS; ++i)
{
m_Draggers[i] = new CDragger(pGameWorld, Pos, Strength, NW, i, Layer, Number);
}
}
//CDraggerTeam::~CDraggerTeam()
//{
// for (int i = 0; i < MAX_CLIENTS; ++i)
// {
// delete m_Draggers[i];
// }
//}

View file

@ -3,38 +3,43 @@
#define GAME_SERVER_ENTITIES_DRAGGER_H
#include <game/server/entity.h>
class CCharacter;
class CDraggerBeam;
/**
* Draggers generate dragger beams which pull players towards their center similar to a tractor beam
*
* A dragger will only generate one dragger beam per team for the closest player for whom the following criteria are met:
* - The player is within the dragger range (sv_dragger_range).
* - The player is not a super player
* - The dragger is activated
* - The dragger beam to be generated is not blocked by laser stoppers (or solid blocks if IgnoreWalls is set to false)
* With the exception of solo players, for whom a dragger beam is always generated, regardless of the rest of the team,
* if the above criteria are met. Solo players have no influence on the generation of the dragger beam for the rest
* of the team.
* A created dragger beam remains for the selected player until one of the criteria is no longer fulfilled. Only then
* can a new dragger beam be created for that team, which may drag another team partner.
*/
class CDragger : public CEntity
{
// m_Core is the direction vector by which a dragger is shifted at each movement tick (every 150ms)
vec2 m_Core;
float m_Strength;
bool m_IgnoreWalls;
int m_EvalTick;
void Move();
void Drag();
int m_TargetID;
bool m_NW;
int m_CaughtTeam;
int m_SoloEntIDs[MAX_CLIENTS];
int m_SoloIDs[MAX_CLIENTS];
int m_TargetIdInTeam[MAX_CLIENTS];
CDraggerBeam *m_apDraggerBeam[MAX_CLIENTS];
void LookForPlayersToDrag();
public:
CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
int CaughtTeam, int Layer = 0, int Number = 0);
CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool IgnoreWalls, int Layer = 0, int Number = 0);
void RemoveDraggerBeam(int ClientID);
void Reset() override;
void Tick() override;
void Snap(int SnappingClient) override;
};
class CDraggerTeam
{
CDragger *m_Draggers[MAX_CLIENTS];
public:
CDraggerTeam(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW = false, int Layer = 0, int Number = 0);
//~CDraggerTeam();
};
#endif // GAME_SERVER_ENTITIES_DRAGGER_H

View file

@ -0,0 +1,115 @@
/* See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
#include "dragger_beam.h"
#include "character.h"
#include "dragger.h"
#include <engine/config.h>
#include <engine/server.h>
#include <game/generated/protocol.h>
#include <game/server/gamecontext.h>
#include <game/server/gamemodes/DDRace.h>
#include <game/server/player.h>
#include <game/server/teams.h>
CDraggerBeam::CDraggerBeam(CGameWorld *pGameWorld, CDragger *pDragger, vec2 Pos, float Strength, bool IgnoreWalls,
int ForClientID) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
{
m_pDragger = pDragger;
m_Pos = Pos;
m_Strength = Strength;
m_IgnoreWalls = IgnoreWalls;
m_ForClientID = ForClientID;
m_Active = true;
m_EvalTick = Server()->Tick();
GameWorld()->InsertEntity(this);
}
void CDraggerBeam::Tick()
{
if(!m_Active)
{
return;
}
// Drag only if the player is reachable and alive
CCharacter *pTarget = GameServer()->GetPlayerChar(m_ForClientID);
if(!pTarget)
{
Reset();
return;
}
int IsReachable =
m_IgnoreWalls ?
!GameServer()->Collision()->IntersectNoLaserNW(m_Pos, pTarget->m_Pos, 0, 0) :
!GameServer()->Collision()->IntersectNoLaser(m_Pos, pTarget->m_Pos, 0, 0);
// This check is necessary because the check in CDragger::LookForPlayersToDrag only happens every 150ms
if(!IsReachable ||
distance(pTarget->m_Pos, m_Pos) >= g_Config.m_SvDraggerRange || !pTarget->IsAlive())
{
Reset();
return;
}
// In the center of the dragger a tee does not experience speed-up
else if(distance(pTarget->m_Pos, m_Pos) > 28)
{
vec2 Temp = pTarget->Core()->m_Vel + (normalize(m_Pos - pTarget->m_Pos) * m_Strength);
pTarget->Core()->m_Vel = ClampVel(pTarget->m_MoveRestrictions, Temp);
}
}
void CDraggerBeam::SetPos(vec2 Pos)
{
m_Pos = Pos;
}
void CDraggerBeam::Reset()
{
m_MarkedForDestroy = true;
m_Active = false;
m_pDragger->RemoveDraggerBeam(m_ForClientID);
}
void CDraggerBeam::Snap(int SnappingClient)
{
if(!m_Active)
{
return;
}
// Only players who can see the player attached to the dragger can see the dragger beam
CCharacter *pTarget = GameServer()->GetPlayerChar(m_ForClientID);
if(!pTarget->CanSnapCharacter(SnappingClient))
{
return;
}
// Only players with the dragger beam in their field of view or who want to see everything will receive the snap
vec2 TargetPos = vec2(pTarget->m_Pos.x, pTarget->m_Pos.y);
if(NetworkClippedLine(SnappingClient, m_Pos, TargetPos))
{
return;
}
CNetObj_Laser *obj = static_cast<CNetObj_Laser *>(
Server()->SnapNewItem(NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
if(!obj)
{
return;
}
obj->m_X = (int)m_Pos.x;
obj->m_Y = (int)m_Pos.y;
obj->m_FromX = (int)TargetPos.x;
obj->m_FromY = (int)TargetPos.y;
int StartTick = m_EvalTick;
if(StartTick < Server()->Tick() - 4)
{
StartTick = Server()->Tick() - 4;
}
else if(StartTick > Server()->Tick())
{
StartTick = Server()->Tick();
}
obj->m_StartTick = StartTick;
}

View file

@ -0,0 +1,41 @@
/* See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
#ifndef GAME_SERVER_ENTITIES_DRAGGER_BEAM_H
#define GAME_SERVER_ENTITIES_DRAGGER_BEAM_H
#include "dragger.h"
#include <game/server/entity.h>
/**
* Dragger beams pull a selected player towards their center
*
* Dragger beams are generated by a particular dragger for the player closest to it and for whom certain criteria are
* met. Dragger beams exist until these criteria are no longer met. Dragger beams dissolve and automatically
* de-register from their dragger source as soon as the player for whom they were created:
* - is no longer alive
* - is no longer in range (sv_dragger_range)
* - can no longer be dragged because the beam is intercepted by a laser stopper (or if !IgnoreWalls by solid blocks)
*
* Dragger beams accelerate the selected player every tick towards their center. The length of the speed vector, which
* is added to that of the player, depends only on the strength of the dragger and is between 1 and 3 units long. If
* the player is in the center of the dragger, it will not accelerate.
*/
class CDraggerBeam : public CEntity
{
CDragger *m_pDragger;
float m_Strength;
bool m_IgnoreWalls;
int m_ForClientID;
int m_EvalTick;
bool m_Active;
public:
CDraggerBeam(CGameWorld *pGameWorld, CDragger *pDragger, vec2 Pos, float Strength, bool IgnoreWalls, int ForClientID);
void SetPos(vec2 Pos);
void Reset() override;
void Tick() override;
void Snap(int SnappingClient) override;
};
#endif // GAME_SERVER_ENTITIES_DRAGGER_BEAM_H

View file

@ -11,88 +11,21 @@
#include "gun.h"
#include "plasma.h"
//////////////////////////////////////////////////
// CGun
//////////////////////////////////////////////////
CGun::CGun(CGameWorld *pGameWorld, vec2 Pos, bool Freeze, bool Explosive, int Layer, int Number) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
{
m_Layer = Layer;
m_Number = Number;
m_LastFire = Server()->Tick();
m_Pos = Pos;
m_EvalTick = Server()->Tick();
m_Freeze = Freeze;
m_Explosive = Explosive;
m_Layer = Layer;
m_Number = Number;
m_EvalTick = Server()->Tick();
mem_zero(m_LastFireTeam, sizeof(m_LastFireTeam));
mem_zero(m_LastFireSolo, sizeof(m_LastFireSolo));
GameWorld()->InsertEntity(this);
}
void CGun::Fire()
{
CCharacter *Ents[MAX_CLIENTS];
int IdInTeam[MAX_CLIENTS];
int LenInTeam[MAX_CLIENTS];
for(int i = 0; i < MAX_CLIENTS; i++)
{
IdInTeam[i] = -1;
LenInTeam[i] = 0;
}
int Num = -1;
Num = GameServer()->m_World.FindEntities(m_Pos, g_Config.m_SvPlasmaRange, (CEntity **)Ents, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
for(int i = 0; i < Num; i++)
{
CCharacter *Target = Ents[i];
//now gun doesn't affect on super
if(Target->Team() == TEAM_SUPER)
continue;
if(m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Target->Team()])
continue;
int res = GameServer()->Collision()->IntersectLine(m_Pos, Target->m_Pos, 0, 0);
if(!res)
{
int Len = length(Target->m_Pos - m_Pos);
if(LenInTeam[Target->Team()] == 0 || LenInTeam[Target->Team()] > Len)
{
LenInTeam[Target->Team()] = Len;
IdInTeam[Target->Team()] = i;
}
}
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(IdInTeam[i] != -1)
{
CCharacter *Target = Ents[IdInTeam[i]];
new CPlasma(&GameServer()->m_World, m_Pos, normalize(Target->m_Pos - m_Pos), m_Freeze, m_Explosive, i);
m_LastFire = Server()->Tick();
}
}
for(int i = 0; i < Num; i++)
{
CCharacter *Target = Ents[i];
if(Target->IsAlive() && Target->Teams()->m_Core.GetSolo(Target->GetPlayer()->GetCID()))
{
if(IdInTeam[Target->Team()] != i)
{
int res = GameServer()->Collision()->IntersectLine(m_Pos, Target->m_Pos, 0, 0);
if(!res)
{
new CPlasma(&GameServer()->m_World, m_Pos, normalize(Target->m_Pos - m_Pos), m_Freeze, m_Explosive, Target->Team());
m_LastFire = Server()->Tick();
}
}
}
}
}
void CGun::Reset()
{
m_MarkedForDestroy = true;
}
void CGun::Tick()
{
if(Server()->Tick() % int(Server()->TickSpeed() * 0.15f) == 0)
@ -106,8 +39,105 @@ void CGun::Tick()
}
m_Pos += m_Core;
}
if(g_Config.m_SvPlasmaPerSec > 0 && m_LastFire + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec <= Server()->Tick())
if(g_Config.m_SvPlasmaPerSec > 0)
{
Fire();
}
}
void CGun::Fire()
{
// Create a list of players who are in the range of the turret
CCharacter *pPlayersInRange[MAX_CLIENTS];
mem_zero(pPlayersInRange, sizeof(pPlayersInRange));
int NumPlayersInRange = GameServer()->m_World.FindEntities(m_Pos, g_Config.m_SvPlasmaRange,
(CEntity **)pPlayersInRange, MAX_CLIENTS, CGameWorld::ENTTYPE_CHARACTER);
// The closest player (within range) in a team is selected as the target
int TargetIdInTeam[MAX_CLIENTS];
bool IsTarget[MAX_CLIENTS];
int MinDistInTeam[MAX_CLIENTS];
mem_zero(MinDistInTeam, sizeof(MinDistInTeam));
mem_zero(IsTarget, sizeof(IsTarget));
for(int &TargetId : TargetIdInTeam)
{
TargetId = -1;
}
for(int i = 0; i < NumPlayersInRange; i++)
{
CCharacter *pTarget = pPlayersInRange[i];
const int &TargetTeam = pTarget->Team();
// Do not fire at super players
if(TargetTeam == TEAM_SUPER)
{
continue;
}
// If the turret is disabled for the target's team, the turret will not fire
if(m_Layer == LAYER_SWITCH && m_Number > 0 &&
!GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[TargetTeam])
{
continue;
}
// Turrets can only shoot at a speed of sv_plasma_per_sec
const int &TargetClientId = pTarget->GetPlayer()->GetCID();
const bool &TargetIsSolo = pTarget->Teams()->m_Core.GetSolo(TargetClientId);
if((TargetIsSolo &&
m_LastFireSolo[TargetClientId] + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec > Server()->Tick()) ||
(!TargetIsSolo &&
m_LastFireTeam[TargetTeam] + Server()->TickSpeed() / g_Config.m_SvPlasmaPerSec > Server()->Tick()))
{
continue;
}
// Turrets can shoot only at reachable, alive players
int IsReachable = !GameServer()->Collision()->IntersectLine(m_Pos, pTarget->m_Pos, 0, 0);
if(IsReachable && pTarget->IsAlive())
{
// Turrets fire on solo players regardless of the rest of the team
if(TargetIsSolo)
{
IsTarget[TargetClientId] = true;
m_LastFireSolo[TargetClientId] = Server()->Tick();
}
else
{
int Distance = distance(pTarget->m_Pos, m_Pos);
if(MinDistInTeam[TargetTeam] == 0 || MinDistInTeam[TargetTeam] > Distance)
{
MinDistInTeam[TargetTeam] = Distance;
TargetIdInTeam[TargetTeam] = TargetClientId;
}
}
}
}
// Set the closest player for each team as a target
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(TargetIdInTeam[i] != -1)
{
IsTarget[TargetIdInTeam[i]] = true;
m_LastFireTeam[i] = Server()->Tick();
}
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
// Fire at each target
if(IsTarget[i])
{
CCharacter *pTarget = GameServer()->GetPlayerChar(i);
new CPlasma(&GameServer()->m_World, m_Pos, normalize(pTarget->m_Pos - m_Pos), m_Freeze, m_Explosive, i);
}
}
}
void CGun::Reset()
{
m_MarkedForDestroy = true;
}
void CGun::Snap(int SnappingClient)
@ -115,40 +145,49 @@ void CGun::Snap(int SnappingClient)
if(NetworkClipped(SnappingClient))
return;
CCharacter *Char = GameServer()->GetPlayerChar(SnappingClient);
if(SnappingClient != SERVER_DEMO_CLIENT && (GameServer()->m_apPlayers[SnappingClient]->GetTeam() == TEAM_SPECTATORS || GameServer()->m_apPlayers[SnappingClient]->IsPaused()) &&
GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
Char = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
int SnappingClientVersion = SnappingClient != SERVER_DEMO_CLIENT ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR;
int SnappingClientVersion = SnappingClient != SERVER_DEMO_CLIENT ?
GameServer()->GetClientVersion(SnappingClient) :
CLIENT_VERSIONNR;
CNetObj_EntityEx *pEntData = 0;
if(SnappingClientVersion >= VERSION_DDNET_SWITCH)
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(), sizeof(CNetObj_EntityEx)));
if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
pEntData = static_cast<CNetObj_EntityEx *>(Server()->SnapNewItem(NETOBJTYPE_ENTITYEX, GetID(),
sizeof(CNetObj_EntityEx)));
if(pEntData)
{
pEntData->m_SwitchNumber = m_Number;
pEntData->m_Layer = m_Layer;
if(m_Explosive && !m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_NORMAL;
else if(m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_EXPLOSIVE;
else if(!m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_FREEZE;
else
pEntData->m_EntityClass = ENTITYCLASS_GUN_UNFREEZE;
if(m_Explosive && !m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_NORMAL;
else if(m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_EXPLOSIVE;
else if(!m_Explosive && m_Freeze)
pEntData->m_EntityClass = ENTITYCLASS_GUN_FREEZE;
else
pEntData->m_EntityClass = ENTITYCLASS_GUN_UNFREEZE;
}
}
else
{
// Emulate turned off blinking turret for old clients
CCharacter *pChar = GameServer()->GetPlayerChar(SnappingClient);
if(SnappingClient != SERVER_DEMO_CLIENT &&
(GameServer()->m_apPlayers[SnappingClient]->GetTeam() == TEAM_SPECTATORS ||
GameServer()->m_apPlayers[SnappingClient]->IsPaused()) &&
GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID != SPEC_FREEVIEW)
pChar = GameServer()->GetPlayerChar(GameServer()->m_apPlayers[SnappingClient]->m_SpectatorID);
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(Char && Char->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) && (!Tick))
if(pChar && m_Layer == LAYER_SWITCH && m_Number > 0 &&
!GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[pChar->Team()] && (!Tick))
return;
}
CNetObj_Laser *pObj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
CNetObj_Laser *pObj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
if(!pObj)
return;

View file

@ -6,18 +6,30 @@
#include <game/gamecore.h>
#include <game/server/entity.h>
class CCharacter;
/**
* Turrets (also referred to as Gun) fire plasma bullets at the nearest player
*
* A turret fires plasma bullets with a certain firing rate (sv_plasma_per_sec) at the closest player of a team for whom
* the following criteria are met:
* - The player is within the turret range (sv_plasma_range)
* - The player is not a super player
* - The turret is activated
* - The initial trajectory of the plasma bullet to be generated would not be stopped by any solid block
* With the exception of solo players, for whom plasma bullets will always be fired, regardless of the rest of the team,
* if the above criteria are met. Solo players do not affect the generation of plasma bullets for the rest of the team.
* The shooting rate of sv_plasma_per_sec is independent for each team and solo player and starts with the first tick
* when a target player is selected.
*/
class CGun : public CEntity
{
int m_EvalTick;
vec2 m_Core;
bool m_Freeze;
bool m_Explosive;
int m_EvalTick;
int m_LastFireTeam[MAX_CLIENTS];
int m_LastFireSolo[MAX_CLIENTS];
void Fire();
int m_LastFire;
public:
CGun(CGameWorld *pGameWorld, vec2 Pos, bool Freeze, bool Explosive, int Layer = 0, int Number = 0);

View file

@ -25,7 +25,7 @@ CLaser::CLaser(CGameWorld *pGameWorld, vec2 Pos, vec2 Direction, float StartEner
m_IsBlueTeleport = false;
m_TuneZone = GameServer()->Collision()->IsTune(GameServer()->Collision()->GetMapIndex(m_Pos));
CCharacter *pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
m_TeamMask = pOwnerChar ? pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner) : 0;
m_TeamMask = pOwnerChar ? pOwnerChar->TeamMask() : 0;
m_BelongsToPracticeTeam = pOwnerChar && pOwnerChar->Teams()->IsPractice(pOwnerChar->Team());
GameWorld()->InsertEntity(this);
@ -277,7 +277,7 @@ void CLaser::Snap(int SnappingClient)
pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
if(pOwnerChar && pOwnerChar->IsAlive())
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
TeamMask = pOwnerChar->TeamMask();
if(SnappingClient != SERVER_DEMO_CLIENT && !CmaskIsSet(TeamMask, SnappingClient))
return;

View file

@ -13,35 +13,40 @@
const float PLASMA_ACCEL = 1.1f;
CPlasma::CPlasma(CGameWorld *pGameWorld, vec2 Pos, vec2 Dir, bool Freeze,
bool Explosive, int ResponsibleTeam) :
bool Explosive, int ForClientID) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
{
m_Pos = Pos;
m_Core = Dir;
m_Freeze = Freeze;
m_Explosive = Explosive;
m_ForClientID = ForClientID;
m_EvalTick = Server()->Tick();
m_LifeTime = Server()->TickSpeed() * 1.5f;
m_ResponsibleTeam = ResponsibleTeam;
GameWorld()->InsertEntity(this);
}
bool CPlasma::HitCharacter()
void CPlasma::Tick()
{
vec2 To2;
CCharacter *Hit = GameServer()->m_World.IntersectCharacter(m_Pos,
m_Pos + m_Core, 0.0f, To2);
if(!Hit)
return false;
if(Hit->Team() != m_ResponsibleTeam)
return false;
m_Freeze ? Hit->Freeze() : Hit->UnFreeze();
if(m_Explosive)
GameServer()->CreateExplosion(m_Pos, -1, WEAPON_GRENADE, true,
m_ResponsibleTeam, Hit->Teams()->TeamMask(m_ResponsibleTeam));
m_MarkedForDestroy = true;
return true;
// A plasma bullet has only a limited lifetime
if(m_LifeTime == 0)
{
Reset();
return;
}
CCharacter *pTarget = GameServer()->GetPlayerChar(m_ForClientID);
// Without a target, a plasma bullet has no reason to live
if(!pTarget)
{
Reset();
return;
}
m_LifeTime--;
Move();
HitCharacter(pTarget);
// Plasma bullets may explode twice if they would hit both a player and an obstacle in the next move step
HitObstacle(pTarget);
}
void CPlasma::Move()
@ -50,62 +55,72 @@ void CPlasma::Move()
m_Core *= PLASMA_ACCEL;
}
bool CPlasma::HitCharacter(CCharacter *pTarget)
{
vec2 IntersectPos;
CCharacter *HitPlayer = GameServer()->m_World.IntersectCharacter(
m_Pos, m_Pos + m_Core, 0.0f, IntersectPos, 0, m_ForClientID);
if(!HitPlayer)
{
return false;
}
// Super player should not be able to stop the plasma bullets
if(HitPlayer->Team() == TEAM_SUPER)
{
return false;
}
m_Freeze ? HitPlayer->Freeze() : HitPlayer->UnFreeze();
if(m_Explosive)
{
// Plasma Turrets are very precise weapons only one tee gets speed from it,
// other tees near the explosion remain unaffected
GameServer()->CreateExplosion(
m_Pos, m_ForClientID, WEAPON_GRENADE, true, pTarget->Team(), pTarget->TeamMask());
}
Reset();
return true;
}
bool CPlasma::HitObstacle(CCharacter *pTarget)
{
// Check if the plasma bullet is stopped by a solid block or a laser stopper
int HasIntersection = GameServer()->Collision()->IntersectNoLaser(m_Pos, m_Pos + m_Core, 0, 0);
if(HasIntersection)
{
if(m_Explosive)
{
// Even in the case of an explosion due to a collision with obstacles, only one player is affected
GameServer()->CreateExplosion(
m_Pos, m_ForClientID, WEAPON_GRENADE, true, pTarget->Team(), pTarget->TeamMask());
}
Reset();
return true;
}
return false;
}
void CPlasma::Reset()
{
m_MarkedForDestroy = true;
}
void CPlasma::Tick()
{
if(m_LifeTime == 0)
{
Reset();
return;
}
m_LifeTime--;
Move();
HitCharacter();
int Res = 0;
Res = GameServer()->Collision()->IntersectNoLaser(m_Pos, m_Pos + m_Core, 0,
0);
if(Res)
{
if(m_Explosive)
GameServer()->CreateExplosion(
m_Pos,
-1,
WEAPON_GRENADE,
true,
m_ResponsibleTeam,
((CGameControllerDDRace *)GameServer()->m_pController)->m_Teams.TeamMask(m_ResponsibleTeam));
Reset();
}
}
void CPlasma::Snap(int SnappingClient)
{
// Only players who can see the targeted player can see the plasma bullet
CCharacter *pTarget = GameServer()->GetPlayerChar(m_ForClientID);
if(!pTarget->CanSnapCharacter(SnappingClient))
{
return;
}
// Only players with the plasma bullet in their field of view or who want to see everything will receive the snap
if(NetworkClipped(SnappingClient))
return;
CCharacter *SnapChar = GameServer()->GetPlayerChar(SnappingClient);
CPlayer *SnapPlayer = SnappingClient != SERVER_DEMO_CLIENT ? GameServer()->m_apPlayers[SnappingClient] : 0;
int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if(SnapChar && SnapChar->IsAlive() && (m_Layer == LAYER_SWITCH && m_Number > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[SnapChar->Team()]) && (!Tick))
return;
if(SnapPlayer && (SnapPlayer->GetTeam() == TEAM_SPECTATORS || SnapPlayer->IsPaused()) && SnapPlayer->m_SpectatorID != SPEC_FREEVIEW && GameServer()->GetPlayerChar(SnapPlayer->m_SpectatorID) && GameServer()->GetPlayerChar(SnapPlayer->m_SpectatorID)->Team() != m_ResponsibleTeam && SnapPlayer->m_ShowOthers != SHOW_OTHERS_ON)
return;
if(SnapPlayer && SnapPlayer->GetTeam() != TEAM_SPECTATORS && !SnapPlayer->IsPaused() && SnapChar && SnapChar->Team() != m_ResponsibleTeam && SnapPlayer->m_ShowOthers != SHOW_OTHERS_ON)
return;
if(SnapPlayer && (SnapPlayer->GetTeam() == TEAM_SPECTATORS || SnapPlayer->IsPaused()) && SnapPlayer->m_SpectatorID == SPEC_FREEVIEW && SnapChar && SnapChar->Team() != m_ResponsibleTeam && SnapPlayer->m_SpecTeam)
return;
CNetObj_Laser *pObj = static_cast<CNetObj_Laser *>(Server()->SnapNewItem(
NETOBJTYPE_LASER, GetID(), sizeof(CNetObj_Laser)));
if(!pObj)
return;

View file

@ -4,24 +4,38 @@
#include <game/server/entity.h>
class CGun;
/**
* Plasma Bullets are projectiles fired from turrets at a specific target
*
* When hitting a tee, plasma bullets can either freeze or unfreeze the player
* Also, plasma projectiles can explode on impact. However, the player affected by the explosion is not necessarily the
* one the plasma collided with, but if the affected player is not a solo player, then the team-mate with the lowest
* ClientId within the explosion range. Furthermore, the affected player does not feel the explosion at the point of
* impact but at the last position of the plasma bullet. The same applies if a plasma bullet explodes due to a collision
* with a laser stopper or a solid block
* Plasma bullets move every tick in the assigned direction and then accelerate by the factor PLASMA_ACCEL
* Plasma bullets can explode twice if they would hit both a player and an obstacle in the next movement step
* Plasma bullets will stop existing as soon as:
* - The player they were created for do no longer exist
* - They have had a collision with a player, a solid block or a laser stopper
* - Their life time of 1.5 seconds has expired
*/
class CPlasma : public CEntity
{
vec2 m_Core;
int m_Freeze;
bool m_Explosive;
int m_ForClientID;
int m_EvalTick;
int m_LifeTime;
int m_ResponsibleTeam;
int m_Freeze;
bool m_Explosive;
bool HitCharacter();
void Move();
bool HitCharacter(CCharacter *pTarget);
bool HitObstacle(CCharacter *pTarget);
public:
CPlasma(CGameWorld *pGameWorld, vec2 Pos, vec2 Dir, bool Freeze,
bool Explosive, int ResponsibleTeam);
bool Explosive, int ForClientId);
void Reset() override;
void Tick() override;

View file

@ -143,7 +143,7 @@ void CProjectile::Tick()
}
if(pOwnerChar && pOwnerChar->IsAlive())
{
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
TeamMask = pOwnerChar->TeamMask();
}
else if(m_Owner >= 0 && (m_Type != WEAPON_GRENADE || g_Config.m_SvDestroyBulletsOnDeath || m_BelongsToPracticeTeam))
{
@ -257,7 +257,7 @@ void CProjectile::Tick()
TeamMask = -1LL;
if(pOwnerChar && pOwnerChar->IsAlive())
{
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
TeamMask = pOwnerChar->TeamMask();
}
GameServer()->CreateExplosion(ColPos, m_Owner, m_Type, m_Owner == -1, (!pOwnerChar ? -1 : pOwnerChar->Team()),
@ -333,7 +333,7 @@ void CProjectile::Snap(int SnappingClient)
pOwnerChar = GameServer()->GetPlayerChar(m_Owner);
if(pOwnerChar && pOwnerChar->IsAlive())
TeamMask = pOwnerChar->Teams()->TeamMask(pOwnerChar->Team(), -1, m_Owner);
TeamMask = pOwnerChar->TeamMask();
if(SnappingClient != SERVER_DEMO_CLIENT && m_Owner != -1 && !CmaskIsSet(TeamMask, SnappingClient))
return;

View file

@ -39,6 +39,11 @@ bool CEntity::NetworkClipped(int SnappingClient, vec2 CheckPos) const
return ::NetworkClipped(m_pGameWorld->GameServer(), SnappingClient, CheckPos);
}
bool CEntity::NetworkClippedLine(int SnappingClient, vec2 StartPos, vec2 EndPos) const
{
return ::NetworkClippedLine(m_pGameWorld->GameServer(), SnappingClient, StartPos, EndPos);
}
bool CEntity::GameLayerClipped(vec2 CheckPos)
{
return round_to_int(CheckPos.x) / 32 < -200 || round_to_int(CheckPos.x) / 32 > GameServer()->Collision()->GetWidth() + 200 ||
@ -93,3 +98,25 @@ bool NetworkClipped(const CGameContext *pGameServer, int SnappingClient, vec2 Ch
float dy = pGameServer->m_apPlayers[SnappingClient]->m_ViewPos.y - CheckPos.y;
return absolute(dy) > pGameServer->m_apPlayers[SnappingClient]->m_ShowDistance.y;
}
bool NetworkClippedLine(const CGameContext *pGameServer, int SnappingClient, vec2 StartPos, vec2 EndPos)
{
if(SnappingClient == SERVER_DEMO_CLIENT || pGameServer->m_apPlayers[SnappingClient]->m_ShowAll)
return false;
vec2 &ViewPos = pGameServer->m_apPlayers[SnappingClient]->m_ViewPos;
vec2 &ShowDistance = pGameServer->m_apPlayers[SnappingClient]->m_ShowDistance;
vec2 DistanceToLine, ClosestPoint;
if(closest_point_on_line(StartPos, EndPos, ViewPos, ClosestPoint))
{
DistanceToLine = ViewPos - ClosestPoint;
}
else
{
// No line section was passed but two equal points
DistanceToLine = ViewPos - StartPos;
}
float ClippDistance = maximum(ShowDistance.x, ShowDistance.y);
return (absolute(DistanceToLine.x) > ClippDistance || absolute(DistanceToLine.y) > ClippDistance);
}

View file

@ -139,6 +139,7 @@ public: // TODO: Maybe make protected
*/
bool NetworkClipped(int SnappingClient) const;
bool NetworkClipped(int SnappingClient, vec2 CheckPos) const;
bool NetworkClippedLine(int SnappingClient, vec2 StartPos, vec2 EndPos) const;
bool GameLayerClipped(vec2 CheckPos);
@ -152,5 +153,6 @@ public: // TODO: Maybe make protected
};
bool NetworkClipped(const CGameContext *pGameServer, int SnappingClient, vec2 CheckPos);
bool NetworkClippedLine(const CGameContext *pGameServer, int SnappingClient, vec2 StartPos, vec2 EndPos);
#endif

View file

@ -290,7 +290,7 @@ void CGameContext::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamag
if(!(int)Dmg)
continue;
if((GetPlayerChar(Owner) ? !(GetPlayerChar(Owner)->m_Hit & CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit || NoDamage) || Owner == apEnts[i]->GetPlayer()->GetCID())
if((GetPlayerChar(Owner) ? !(GetPlayerChar(Owner)->m_Hit & CCharacter::DISABLE_HIT_GRENADE) : g_Config.m_SvHit) || NoDamage || Owner == apEnts[i]->GetPlayer()->GetCID())
{
if(Owner != -1 && apEnts[i]->IsAlive() && !apEnts[i]->CanCollide(Owner))
continue;
@ -298,8 +298,8 @@ void CGameContext::CreateExplosion(vec2 Pos, int Owner, int Weapon, bool NoDamag
continue;
// Explode at most once per team
int PlayerTeam = ((CGameControllerDDRace *)m_pController)->m_Teams.m_Core.Team(apEnts[i]->GetPlayer()->GetCID());
if(GetPlayerChar(Owner) ? GetPlayerChar(Owner)->m_Hit & CCharacter::DISABLE_HIT_GRENADE : !g_Config.m_SvHit || NoDamage)
int PlayerTeam = apEnts[i]->Team();
if((GetPlayerChar(Owner) ? GetPlayerChar(Owner)->m_Hit & CCharacter::DISABLE_HIT_GRENADE : !g_Config.m_SvHit) || NoDamage)
{
if(!CmaskIsSet(TeamMask, PlayerTeam))
continue;

View file

@ -15,7 +15,6 @@
#include "entities/dragger.h"
#include "entities/gun.h"
#include "entities/light.h"
#include "entities/plasma.h"
#include "entities/projectile.h"
#include <game/layers.h>
@ -366,11 +365,11 @@ bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Nu
}
else if(Index >= ENTITY_DRAGGER_WEAK && Index <= ENTITY_DRAGGER_STRONG)
{
CDraggerTeam(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK + 1, false, Layer, Number);
new CDragger(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK + 1, false, Layer, Number);
}
else if(Index >= ENTITY_DRAGGER_WEAK_NW && Index <= ENTITY_DRAGGER_STRONG_NW)
{
CDraggerTeam(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK_NW + 1, true, Layer, Number);
new CDragger(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK_NW + 1, true, Layer, Number);
}
else if(Index == ENTITY_PLASMAE)
{

View file

@ -416,7 +416,7 @@ void CGameWorld::ReleaseHooked(int ClientID)
CCharacterCore *Core = pChr->Core();
if(Core->m_HookedPlayer == ClientID && !pChr->m_Super)
{
Core->m_HookedPlayer = -1;
Core->SetHookedPlayer(-1);
Core->m_TriggeredEvents |= COREEVENT_HOOK_RETRACT;
Core->m_HookState = HOOK_RETRACTED;
}

View file

@ -186,12 +186,12 @@ void CSaveTee::Load(CCharacter *pChr, int Team, bool IsSwap)
pChr->m_Core.m_HookState = m_HookState;
if(m_HookedPlayer != -1 && pChr->Teams()->m_Core.Team(m_HookedPlayer) != Team)
{
pChr->m_Core.m_HookedPlayer = -1;
pChr->m_Core.SetHookedPlayer(-1);
pChr->m_Core.m_HookState = HOOK_FLYING;
}
else
{
pChr->m_Core.m_HookedPlayer = m_HookedPlayer;
pChr->m_Core.SetHookedPlayer(m_HookedPlayer);
}
pChr->m_Core.m_NewHook = m_NewHook;
pChr->m_SavedInput.m_Direction = m_InputDirection;