mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-11 02:28:18 +00:00
f31e081bd4
OK, maybe not actually remove because it is kept for fallback when the new method isn't available. The whole gametype parsing business had the same downsides as user agent parsing on the web, hence I removed it while keeping behavior the same. This allows servers to explicitly opt in or out of certain bug workarounds and other client behavior. This increases the complexity of different configurations that are available in the client (which is a bad thing).
752 lines
26 KiB
C++
752 lines
26 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 <base/tl/sorted_array.h>
|
|
|
|
#include <engine/demo.h>
|
|
#include <engine/engine.h>
|
|
#include <engine/graphics.h>
|
|
#include <engine/shared/config.h>
|
|
#include <engine/serverbrowser.h>
|
|
#include <game/generated/protocol.h>
|
|
#include <game/generated/client_data.h>
|
|
|
|
#include <game/gamecore.h> // get_angle
|
|
#include <game/client/animstate.h>
|
|
#include <game/client/gameclient.h>
|
|
#include <game/client/ui.h>
|
|
#include <game/client/render.h>
|
|
|
|
#include <game/client/components/flow.h>
|
|
#include <game/client/components/skins.h>
|
|
#include <game/client/components/effects.h>
|
|
#include <game/client/components/sounds.h>
|
|
#include <game/client/components/controls.h>
|
|
|
|
#include <engine/textrender.h>
|
|
|
|
#include "players.h"
|
|
#include <stdio.h>
|
|
|
|
void CPlayers::RenderHand(CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha)
|
|
{
|
|
vec2 HandPos = CenterPos + Dir;
|
|
float Angle = GetAngle(Dir);
|
|
if(Dir.x < 0)
|
|
Angle -= AngleOffset;
|
|
else
|
|
Angle += AngleOffset;
|
|
|
|
vec2 DirX = Dir;
|
|
vec2 DirY(-Dir.y,Dir.x);
|
|
|
|
if(Dir.x < 0)
|
|
DirY = -DirY;
|
|
|
|
HandPos += DirX * PostRotOffset.x;
|
|
HandPos += DirY * PostRotOffset.y;
|
|
|
|
//Graphics()->TextureSet(data->m_aImages[IMAGE_CHAR_DEFAULT].id);
|
|
Graphics()->TextureSet(pInfo->m_Texture);
|
|
Graphics()->SetColor(pInfo->m_ColorBody.r, pInfo->m_ColorBody.g, pInfo->m_ColorBody.b, Alpha);
|
|
|
|
// two passes
|
|
for(int i = 0; i < 2; i++)
|
|
{
|
|
int QuadOffset = NUM_WEAPONS * 2 + i;
|
|
Graphics()->QuadsSetRotation(Angle);
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, HandPos.x, HandPos.y);
|
|
}
|
|
|
|
}
|
|
|
|
inline float NormalizeAngular(float f)
|
|
{
|
|
return fmod(f+pi*2, pi*2);
|
|
}
|
|
|
|
inline float AngularMixDirection (float Src, float Dst) { return sinf(Dst-Src) >0?1:-1; }
|
|
inline float AngularDistance(float Src, float Dst) { return asinf(sinf(Dst-Src)); }
|
|
|
|
inline float AngularApproach(float Src, float Dst, float Amount)
|
|
{
|
|
float d = AngularMixDirection (Src, Dst);
|
|
float n = Src + Amount*d;
|
|
if(AngularMixDirection (n, Dst) != d)
|
|
return Dst;
|
|
return n;
|
|
}
|
|
|
|
bool CPlayers::IsOtherTeam(int ClientID)
|
|
{
|
|
bool Local = m_pClient->m_Snap.m_LocalClientID == ClientID;
|
|
|
|
if((m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Team == TEAM_SPECTATORS && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW) || ClientID < 0)
|
|
return false;
|
|
else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
|
|
return m_pClient->m_Teams.Team(ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_SpecInfo.m_SpectatorID);
|
|
else if((m_pClient->m_aClients[m_pClient->m_Snap.m_LocalClientID].m_Solo || m_pClient->m_aClients[ClientID].m_Solo) && !Local)
|
|
return true;
|
|
|
|
return m_pClient->m_Teams.Team(ClientID) != m_pClient->m_Teams.Team(m_pClient->m_Snap.m_LocalClientID);
|
|
}
|
|
|
|
void CPlayers::RenderHook(
|
|
const CNetObj_Character *pPrevChar,
|
|
const CNetObj_Character *pPlayerChar,
|
|
const CTeeRenderInfo *pRenderInfo,
|
|
int ClientID,
|
|
float Intra
|
|
)
|
|
{
|
|
CNetObj_Character Prev;
|
|
CNetObj_Character Player;
|
|
Prev = *pPrevChar;
|
|
Player = *pPlayerChar;
|
|
|
|
CTeeRenderInfo RenderInfo = *pRenderInfo;
|
|
|
|
// don't render hooks to not active character cores
|
|
if(pPlayerChar->m_HookedPlayer != -1 && !m_pClient->m_Snap.m_aCharacters[pPlayerChar->m_HookedPlayer].m_Active)
|
|
return;
|
|
|
|
float IntraTick = Intra;
|
|
if(ClientID >= 0)
|
|
IntraTick = (m_pClient->m_aClients[ClientID].m_IsPredicted) ? Client()->PredIntraGameTick() : Client()->IntraGameTick();
|
|
|
|
bool OtherTeam = IsOtherTeam(ClientID);
|
|
|
|
RenderInfo.m_Size = 64.0f;
|
|
|
|
vec2 Position;
|
|
if(in_range(ClientID, MAX_CLIENTS-1))
|
|
Position = m_pClient->m_aClients[ClientID].m_RenderPos;
|
|
else
|
|
Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick);
|
|
|
|
// draw hook
|
|
if(Prev.m_HookState>0 && Player.m_HookState>0)
|
|
{
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
|
|
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
if(ClientID < 0)
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
|
|
|
|
vec2 Pos = Position;
|
|
vec2 HookPos;
|
|
|
|
if(in_range(pPlayerChar->m_HookedPlayer, MAX_CLIENTS-1))
|
|
HookPos = m_pClient->m_aClients[pPlayerChar->m_HookedPlayer].m_RenderPos;
|
|
else
|
|
HookPos = mix(vec2(Prev.m_HookX, Prev.m_HookY), vec2(Player.m_HookX, Player.m_HookY), IntraTick);
|
|
|
|
float d = distance(Pos, HookPos);
|
|
vec2 Dir = normalize(Pos-HookPos);
|
|
|
|
Graphics()->QuadsSetRotation(GetAngle(Dir)+pi);
|
|
// render head
|
|
int QuadOffset = NUM_WEAPONS * 2 + 2;
|
|
if(OtherTeam)
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, g_Config.m_ClShowOthersAlpha / 100.0f);
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, HookPos.x, HookPos.y);
|
|
|
|
// render chain
|
|
++QuadOffset;
|
|
static IGraphics::SRenderSpriteInfo s_HookChainRenderInfo[1024];
|
|
int HookChainCount = 0;
|
|
for(float f = 24; f < d && HookChainCount < 1024; f += 24, ++HookChainCount)
|
|
{
|
|
vec2 p = HookPos + Dir*f;
|
|
s_HookChainRenderInfo[HookChainCount].m_Pos[0] = p.x;
|
|
s_HookChainRenderInfo[HookChainCount].m_Pos[1] = p.y;
|
|
|
|
s_HookChainRenderInfo[HookChainCount].m_Scale = 1;
|
|
s_HookChainRenderInfo[HookChainCount].m_Rotation = GetAngle(Dir) + pi;
|
|
++HookChainCount;
|
|
}
|
|
Graphics()->RenderQuadContainerAsSpriteMultiple(m_WeaponEmoteQuadContainerIndex, QuadOffset, HookChainCount, s_HookChainRenderInfo);
|
|
|
|
Graphics()->QuadsSetRotation(0);
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
RenderHand(&RenderInfo, Position, normalize(HookPos-Pos), -pi/2, vec2(20, 0), OtherTeam ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f);
|
|
}
|
|
}
|
|
|
|
void CPlayers::RenderPlayer(
|
|
const CNetObj_Character *pPrevChar,
|
|
const CNetObj_Character *pPlayerChar,
|
|
const CTeeRenderInfo *pRenderInfo,
|
|
int ClientID,
|
|
float Intra
|
|
)
|
|
{
|
|
CNetObj_Character Prev;
|
|
CNetObj_Character Player;
|
|
Prev = *pPrevChar;
|
|
Player = *pPlayerChar;
|
|
|
|
CTeeRenderInfo RenderInfo = *pRenderInfo;
|
|
|
|
bool Local = m_pClient->m_Snap.m_LocalClientID == ClientID;
|
|
bool OtherTeam = IsOtherTeam(ClientID);
|
|
float Alpha = OtherTeam ? g_Config.m_ClShowOthersAlpha / 100.0f : 1.0f;
|
|
|
|
// set size
|
|
RenderInfo.m_Size = 64.0f;
|
|
|
|
float IntraTick = Intra;
|
|
if(ClientID >= 0)
|
|
IntraTick = m_pClient->m_aClients[ClientID].m_IsPredicted ? Client()->PredIntraGameTick() : Client()->IntraGameTick();
|
|
|
|
static float s_LastGameTickTime = Client()->GameTickTime();
|
|
static float s_LastPredIntraTick = Client()->PredIntraGameTick();
|
|
if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED))
|
|
{
|
|
s_LastGameTickTime = Client()->GameTickTime();
|
|
s_LastPredIntraTick = Client()->PredIntraGameTick();
|
|
}
|
|
|
|
bool PredictLocalWeapons = false;
|
|
float AttackTime = (Client()->PrevGameTick() - Player.m_AttackTick) / (float)SERVER_TICK_SPEED + Client()->GameTickTime();
|
|
float LastAttackTime = (Client()->PrevGameTick() - Player.m_AttackTick) / (float)SERVER_TICK_SPEED + s_LastGameTickTime;
|
|
if(Local && m_pClient->m_aClients[ClientID].m_IsPredicted && m_pClient->AntiPingWeapons() && m_pClient->AntiPingGrenade())
|
|
{
|
|
PredictLocalWeapons = true;
|
|
AttackTime = (Client()->PredIntraGameTick() + (Client()->PredGameTick() - 1 - Player.m_AttackTick)) / (float)SERVER_TICK_SPEED;
|
|
LastAttackTime = (s_LastPredIntraTick + (Client()->PredGameTick() - 1 - Player.m_AttackTick)) / (float)SERVER_TICK_SPEED;
|
|
}
|
|
float AttackTicksPassed = AttackTime*SERVER_TICK_SPEED;
|
|
|
|
float Angle;
|
|
if(Local && Client()->State() != IClient::STATE_DEMOPLAYBACK)
|
|
{
|
|
// just use the direct input if it's the local player we are rendering
|
|
Angle = GetAngle(m_pClient->m_pControls->m_MousePos[g_Config.m_ClDummy]);
|
|
}
|
|
else
|
|
{
|
|
float AngleIntraTick = IntraTick;
|
|
// using unpredicted angle when rendering other players in-game
|
|
if(ClientID >= 0)
|
|
AngleIntraTick = Client()->IntraGameTick();
|
|
// If the player moves their weapon through top, then change
|
|
// the end angle by 2*Pi, so that the mix function will use the
|
|
// short path and not the long one.
|
|
if(Player.m_Angle > (256.0f * pi) && Prev.m_Angle < 0)
|
|
Player.m_Angle -= 256.0f * 2 * pi;
|
|
else if(Player.m_Angle < 0 && Prev.m_Angle > (256.0f * pi))
|
|
Player.m_Angle += 256.0f * 2 * pi;
|
|
|
|
Angle = mix((float)Prev.m_Angle, (float)Player.m_Angle, AngleIntraTick)/256.0f;
|
|
}
|
|
|
|
vec2 Direction = GetDirection((int)(Angle*256.0f));
|
|
vec2 Position;
|
|
if(in_range(ClientID, MAX_CLIENTS-1))
|
|
Position = m_pClient->m_aClients[ClientID].m_RenderPos;
|
|
else
|
|
Position = mix(vec2(Prev.m_X, Prev.m_Y), vec2(Player.m_X, Player.m_Y), IntraTick);
|
|
vec2 Vel = mix(vec2(Prev.m_VelX/256.0f, Prev.m_VelY/256.0f), vec2(Player.m_VelX/256.0f, Player.m_VelY/256.0f), IntraTick);
|
|
|
|
m_pClient->m_pFlow->Add(Position, Vel*100.0f, 10.0f);
|
|
|
|
RenderInfo.m_GotAirJump = Player.m_Jumped&2?0:1;
|
|
|
|
bool Stationary = Player.m_VelX <= 1 && Player.m_VelX >= -1;
|
|
bool InAir = !Collision()->CheckPoint(Player.m_X, Player.m_Y+16);
|
|
bool WantOtherDir = (Player.m_Direction == -1 && Vel.x > 0) || (Player.m_Direction == 1 && Vel.x < 0);
|
|
|
|
// evaluate animation
|
|
float WalkTime = fmod(absolute(Position.x), 100.0f)/100.0f;
|
|
CAnimState State;
|
|
State.Set(&g_pData->m_aAnimations[ANIM_BASE], 0);
|
|
|
|
if(InAir)
|
|
State.Add(&g_pData->m_aAnimations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here
|
|
else if(Stationary)
|
|
State.Add(&g_pData->m_aAnimations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here
|
|
else if(!WantOtherDir)
|
|
State.Add(&g_pData->m_aAnimations[ANIM_WALK], WalkTime, 1.0f);
|
|
|
|
if(Player.m_Weapon == WEAPON_HAMMER)
|
|
State.Add(&g_pData->m_aAnimations[ANIM_HAMMER_SWING], clamp(LastAttackTime*5.0f,0.0f,1.0f), 1.0f);
|
|
if(Player.m_Weapon == WEAPON_NINJA)
|
|
State.Add(&g_pData->m_aAnimations[ANIM_NINJA_SWING], clamp(LastAttackTime*2.0f,0.0f,1.0f), 1.0f);
|
|
|
|
// do skidding
|
|
if(!InAir && WantOtherDir && length(Vel*50) > 500.0f)
|
|
{
|
|
static int64 SkidSoundTime = 0;
|
|
if(time_get()-SkidSoundTime > time_freq()/10)
|
|
{
|
|
if(g_Config.m_SndGame)
|
|
m_pClient->m_pSounds->PlayAt(CSounds::CHN_WORLD, SOUND_PLAYER_SKID, 0.25f, Position);
|
|
SkidSoundTime = time_get();
|
|
}
|
|
|
|
m_pClient->m_pEffects->SkidTrail(
|
|
Position+vec2(-Player.m_Direction*6,12),
|
|
vec2(-Player.m_Direction*100*length(Vel),-50)
|
|
);
|
|
}
|
|
|
|
// draw gun
|
|
{
|
|
if(ClientID >= 0 && ((GameClient()->m_GameInfo.m_AllowHookColl && g_Config.m_ClShowHookCollAlways) || (Player.m_PlayerFlags&PLAYERFLAG_AIM && ((!Local && g_Config.m_ClShowHookCollOther) || (Local && g_Config.m_ClShowHookCollOwn)))))
|
|
{
|
|
vec2 ExDirection = Direction;
|
|
|
|
if(Local && Client()->State() != IClient::STATE_DEMOPLAYBACK)
|
|
ExDirection = normalize(vec2(m_pClient->m_pControls->m_InputData[g_Config.m_ClDummy].m_TargetX, m_pClient->m_pControls->m_InputData[g_Config.m_ClDummy].m_TargetY));
|
|
|
|
Graphics()->TextureSet(-1);
|
|
vec2 InitPos = Position;
|
|
vec2 FinishPos = InitPos + ExDirection * (m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength-42.0f);
|
|
|
|
Graphics()->LinesBegin();
|
|
Graphics()->SetColor(1.00f, 0.0f, 0.0f, Alpha);
|
|
|
|
float PhysSize = 28.0f;
|
|
|
|
vec2 OldPos = InitPos + ExDirection * PhysSize * 1.5f;
|
|
vec2 NewPos = OldPos;
|
|
|
|
bool DoBreak = false;
|
|
int Hit = 0;
|
|
|
|
do {
|
|
OldPos = NewPos;
|
|
NewPos = OldPos + ExDirection * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookFireSpeed;
|
|
|
|
if(distance(InitPos, NewPos) > m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength)
|
|
{
|
|
NewPos = InitPos + normalize(NewPos-InitPos) * m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength;
|
|
DoBreak = true;
|
|
}
|
|
|
|
int TeleNr = 0;
|
|
Hit = Collision()->IntersectLineTeleHook(OldPos, NewPos, &FinishPos, 0x0, &TeleNr);
|
|
|
|
if(!DoBreak && Hit)
|
|
{
|
|
if(Hit != TILE_NOHOOK)
|
|
Graphics()->SetColor(130.0f/255.0f, 232.0f/255.0f, 160.0f/255.0f, Alpha);
|
|
}
|
|
|
|
if(m_pClient->IntersectCharacter(OldPos, FinishPos, FinishPos, ClientID) != -1)
|
|
{
|
|
Graphics()->SetColor(1.0f, 1.0f, 0.0f, Alpha);
|
|
break;
|
|
}
|
|
|
|
if(Hit)
|
|
break;
|
|
|
|
NewPos.x = round_to_int(NewPos.x);
|
|
NewPos.y = round_to_int(NewPos.y);
|
|
|
|
if(OldPos == NewPos)
|
|
break;
|
|
|
|
ExDirection.x = round_to_int(ExDirection.x*256.0f) / 256.0f;
|
|
ExDirection.y = round_to_int(ExDirection.y*256.0f) / 256.0f;
|
|
} while (!DoBreak);
|
|
|
|
IGraphics::CLineItem LineItem(InitPos.x, InitPos.y, FinishPos.x, FinishPos.y);
|
|
Graphics()->LinesDraw(&LineItem, 1);
|
|
Graphics()->LinesEnd();
|
|
}
|
|
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
Graphics()->QuadsSetRotation(State.GetAttach()->m_Angle*pi*2+Angle);
|
|
|
|
if(ClientID < 0)
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
|
|
|
|
// normal weapons
|
|
int iw = clamp(Player.m_Weapon, 0, NUM_WEAPONS-1);
|
|
int QuadOffset = iw * 2 + (Direction.x < 0 ? 1 : 0);
|
|
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
|
|
|
|
vec2 Dir = Direction;
|
|
float Recoil = 0.0f;
|
|
vec2 p;
|
|
if(Player.m_Weapon == WEAPON_HAMMER)
|
|
{
|
|
// Static position for hammer
|
|
p = Position + vec2(State.GetAttach()->m_X, State.GetAttach()->m_Y);
|
|
p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety;
|
|
// if attack is under way, bash stuffs
|
|
if(Direction.x < 0)
|
|
{
|
|
Graphics()->QuadsSetRotation(-pi/2-State.GetAttach()->m_Angle*pi*2);
|
|
p.x -= g_pData->m_Weapons.m_aId[iw].m_Offsetx;
|
|
}
|
|
else
|
|
{
|
|
Graphics()->QuadsSetRotation(-pi/2+State.GetAttach()->m_Angle*pi*2);
|
|
}
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, p.x, p.y);
|
|
}
|
|
else if(Player.m_Weapon == WEAPON_NINJA)
|
|
{
|
|
p = Position;
|
|
p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety;
|
|
|
|
if(Direction.x < 0)
|
|
{
|
|
Graphics()->QuadsSetRotation(-pi/2-State.GetAttach()->m_Angle*pi*2);
|
|
p.x -= g_pData->m_Weapons.m_aId[iw].m_Offsetx;
|
|
m_pClient->m_pEffects->PowerupShine(p+vec2(32,0), vec2(32,12));
|
|
}
|
|
else
|
|
{
|
|
Graphics()->QuadsSetRotation(-pi/2+State.GetAttach()->m_Angle*pi*2);
|
|
m_pClient->m_pEffects->PowerupShine(p-vec2(32,0), vec2(32,12));
|
|
}
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, p.x, p.y);
|
|
|
|
// HADOKEN
|
|
if(AttackTime <= 1/6.f && g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles)
|
|
{
|
|
int IteX = rand() % g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles;
|
|
static int s_LastIteX = IteX;
|
|
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
|
{
|
|
const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
|
|
if(pInfo->m_Paused)
|
|
IteX = s_LastIteX;
|
|
else
|
|
s_LastIteX = IteX;
|
|
}
|
|
else
|
|
{
|
|
if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
|
|
IteX = s_LastIteX;
|
|
else
|
|
s_LastIteX = IteX;
|
|
}
|
|
if(g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX])
|
|
{
|
|
vec2 Dir;
|
|
if(PredictLocalWeapons)
|
|
Dir = vec2(pPlayerChar->m_X,pPlayerChar->m_Y) - vec2(pPrevChar->m_X, pPrevChar->m_Y);
|
|
else
|
|
Dir = vec2(m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_X, m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_Y) - vec2(m_pClient->m_Snap.m_aCharacters[ClientID].m_Prev.m_X, m_pClient->m_Snap.m_aCharacters[ClientID].m_Prev.m_Y);
|
|
Dir = normalize(Dir);
|
|
float HadOkenAngle = GetAngle(Dir);
|
|
Graphics()->QuadsSetRotation(HadOkenAngle);
|
|
int QuadOffset = IteX * 2;
|
|
vec2 DirY(-Dir.y,Dir.x);
|
|
p = Position;
|
|
float OffsetX = g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsetx;
|
|
p -= Dir * OffsetX;
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponSpriteMuzzleQuadContainerIndex[iw], QuadOffset, p.x, p.y);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: should be an animation
|
|
Recoil = 0;
|
|
float a = AttackTicksPassed/5.0f;
|
|
if(a < 1)
|
|
Recoil = sinf(a*pi);
|
|
p = Position + Dir * g_pData->m_Weapons.m_aId[iw].m_Offsetx - Dir*Recoil*10.0f;
|
|
p.y += g_pData->m_Weapons.m_aId[iw].m_Offsety;
|
|
if(Player.m_Weapon == WEAPON_GUN && g_Config.m_ClOldGunPosition)
|
|
p.y -= 8;
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, p.x, p.y);
|
|
}
|
|
|
|
if(Player.m_Weapon == WEAPON_GUN || Player.m_Weapon == WEAPON_SHOTGUN)
|
|
{
|
|
// check if we're firing stuff
|
|
if(g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles)//prev.attackticks)
|
|
{
|
|
float Alpha = 0.0f;
|
|
if(AttackTicksPassed < g_pData->m_Weapons.m_aId[iw].m_Muzzleduration + 3)
|
|
{
|
|
float t = AttackTicksPassed/g_pData->m_Weapons.m_aId[iw].m_Muzzleduration;
|
|
Alpha = mix(2.0f, 0.0f, minimum(1.0f,maximum(0.0f,t)));
|
|
}
|
|
|
|
int IteX = rand() % g_pData->m_Weapons.m_aId[iw].m_NumSpriteMuzzles;
|
|
static int s_LastIteX = IteX;
|
|
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
|
{
|
|
const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo();
|
|
if(pInfo->m_Paused)
|
|
IteX = s_LastIteX;
|
|
else
|
|
s_LastIteX = IteX;
|
|
}
|
|
else
|
|
{
|
|
if(m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
|
|
IteX = s_LastIteX;
|
|
else
|
|
s_LastIteX = IteX;
|
|
}
|
|
if(Alpha > 0.0f && g_pData->m_Weapons.m_aId[iw].m_aSpriteMuzzles[IteX])
|
|
{
|
|
float OffsetY = -g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsety;
|
|
int QuadOffset = IteX * 2 + (Direction.x < 0 ? 1 : 0);
|
|
if(Direction.x < 0)
|
|
OffsetY = -OffsetY;
|
|
|
|
vec2 DirY(-Dir.y,Dir.x);
|
|
vec2 MuzzlePos = p + Dir * g_pData->m_Weapons.m_aId[iw].m_Muzzleoffsetx + DirY * OffsetY;
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponSpriteMuzzleQuadContainerIndex[iw], QuadOffset, MuzzlePos.x, MuzzlePos.y);
|
|
}
|
|
}
|
|
}
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
Graphics()->QuadsSetRotation(0);
|
|
|
|
switch (Player.m_Weapon)
|
|
{
|
|
case WEAPON_GUN: RenderHand(&RenderInfo, p, Direction, -3*pi/4, vec2(-15, 4), Alpha); break;
|
|
case WEAPON_SHOTGUN: RenderHand(&RenderInfo, p, Direction, -pi/2, vec2(-5, 4), Alpha); break;
|
|
case WEAPON_GRENADE: RenderHand(&RenderInfo, p, Direction, -pi/2, vec2(-4, 7), Alpha); break;
|
|
}
|
|
|
|
}
|
|
|
|
// render the "shadow" tee
|
|
if(Local && (g_Config.m_Debug || g_Config.m_ClUnpredictedShadow))
|
|
{
|
|
vec2 GhostPosition = Position;
|
|
if(ClientID >= 0)
|
|
GhostPosition = mix(
|
|
vec2(m_pClient->m_Snap.m_aCharacters[ClientID].m_Prev.m_X, m_pClient->m_Snap.m_aCharacters[ClientID].m_Prev.m_Y),
|
|
vec2(m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_X, m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur.m_Y),
|
|
Client()->IntraGameTick());
|
|
|
|
CTeeRenderInfo Ghost = RenderInfo;
|
|
RenderTools()->RenderTee(&State, &Ghost, Player.m_Emote, Direction, GhostPosition, 0.5f); // render ghost
|
|
}
|
|
|
|
RenderInfo.m_Size = 64.0f; // force some settings
|
|
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
|
|
Graphics()->QuadsSetRotation(0);
|
|
if(g_Config.m_ClShowDirection && ClientID >= 0 && (!Local || DemoPlayer()->IsPlaying()))
|
|
{
|
|
if(Player.m_Direction == -1)
|
|
{
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id);
|
|
Graphics()->QuadsSetRotation(pi);
|
|
Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, Position.x - 30.f, Position.y - 70.f);
|
|
}
|
|
else if(Player.m_Direction == 1)
|
|
{
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id);
|
|
Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, Position.x + 30.f, Position.y - 70.f);
|
|
}
|
|
if(Player.m_Jumped&1)
|
|
{
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_ARROW].m_Id);
|
|
Graphics()->QuadsSetRotation(pi * 3 / 2);
|
|
Graphics()->RenderQuadContainerAsSprite(m_DirectionQuadContainerIndex, 0, Position.x, Position.y - 70.f);
|
|
}
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
Graphics()->QuadsSetRotation(0);
|
|
}
|
|
|
|
if(OtherTeam || ClientID < 0)
|
|
RenderTools()->RenderTee(&State, &RenderInfo, Player.m_Emote, Direction, Position, g_Config.m_ClShowOthersAlpha / 100.0f);
|
|
else
|
|
RenderTools()->RenderTee(&State, &RenderInfo, Player.m_Emote, Direction, Position);
|
|
|
|
int QuadOffsetToEmoticon = NUM_WEAPONS * 2 + 2 + 2;
|
|
if(Player.m_PlayerFlags&PLAYERFLAG_CHATTING)
|
|
{
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id);
|
|
int QuadOffset = QuadOffsetToEmoticon + (SPRITE_DOTDOT - SPRITE_OOP);
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, Alpha);
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, Position.x + 24.f, Position.y - 40.f);
|
|
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
Graphics()->QuadsSetRotation(0);
|
|
}
|
|
|
|
if(ClientID < 0)
|
|
return;
|
|
|
|
if(g_Config.m_ClShowEmotes && m_pClient->m_aClients[ClientID].m_EmoticonStart != -1 && m_pClient->m_aClients[ClientID].m_EmoticonStart + 2 * Client()->GameTickSpeed() > Client()->GameTick())
|
|
{
|
|
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id);
|
|
|
|
int SinceStart = Client()->GameTick() - m_pClient->m_aClients[ClientID].m_EmoticonStart;
|
|
int FromEnd = m_pClient->m_aClients[ClientID].m_EmoticonStart + 2 * Client()->GameTickSpeed() - Client()->GameTick();
|
|
|
|
float a = 1;
|
|
|
|
if(FromEnd < Client()->GameTickSpeed() / 5)
|
|
a = FromEnd / (Client()->GameTickSpeed() / 5.0);
|
|
|
|
float h = 1;
|
|
if(SinceStart < Client()->GameTickSpeed() / 10)
|
|
h = SinceStart / (Client()->GameTickSpeed() / 10.0);
|
|
|
|
float Wiggle = 0;
|
|
if(SinceStart < Client()->GameTickSpeed() / 5)
|
|
Wiggle = SinceStart / (Client()->GameTickSpeed() / 5.0);
|
|
|
|
float WiggleAngle = sinf(5*Wiggle);
|
|
|
|
Graphics()->QuadsSetRotation(pi/6*WiggleAngle);
|
|
|
|
Graphics()->SetColor(1.0f,1.0f,1.0f,a * Alpha);
|
|
// client_datas::emoticon is an offset from the first emoticon
|
|
int QuadOffset = QuadOffsetToEmoticon + m_pClient->m_aClients[ClientID].m_Emoticon;
|
|
Graphics()->RenderQuadContainerAsSprite(m_WeaponEmoteQuadContainerIndex, QuadOffset, Position.x, Position.y - 23.f - 32.f * h, 1.f, (64.f*h) / 64.f);
|
|
|
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
Graphics()->QuadsSetRotation(0);
|
|
}
|
|
}
|
|
|
|
void CPlayers::OnRender()
|
|
{
|
|
// update RenderInfo for ninja
|
|
bool IsTeamplay = false;
|
|
if(m_pClient->m_Snap.m_pGameInfoObj)
|
|
IsTeamplay = (m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) != 0;
|
|
for(int i = 0; i < MAX_CLIENTS; ++i)
|
|
{
|
|
m_aRenderInfo[i] = m_pClient->m_aClients[i].m_RenderInfo;
|
|
if(m_pClient->m_Snap.m_aCharacters[i].m_Cur.m_Weapon == WEAPON_NINJA && g_Config.m_ClShowNinja)
|
|
{
|
|
// change the skin for the player to the ninja
|
|
int Skin = m_pClient->m_pSkins->Find("x_ninja");
|
|
if(Skin != -1)
|
|
{
|
|
if(IsTeamplay)
|
|
m_aRenderInfo[i].m_Texture = m_pClient->m_pSkins->Get(Skin)->m_ColorTexture;
|
|
else
|
|
{
|
|
m_aRenderInfo[i].m_Texture = m_pClient->m_pSkins->Get(Skin)->m_OrgTexture;
|
|
m_aRenderInfo[i].m_ColorBody = ColorRGBA(1,1,1);
|
|
m_aRenderInfo[i].m_ColorFeet = ColorRGBA(1,1,1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// render other players in two passes, first pass we render the other, second pass we render our self
|
|
for(int p = 0; p < 4; p++)
|
|
{
|
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
// only render active characters
|
|
if(!m_pClient->m_Snap.m_aCharacters[i].m_Active)
|
|
continue;
|
|
|
|
const void *pPrevInfo = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_PLAYERINFO, i);
|
|
const void *pInfo = Client()->SnapFindItem(IClient::SNAP_CURRENT, NETOBJTYPE_PLAYERINFO, i);
|
|
|
|
if(pPrevInfo && pInfo)
|
|
{
|
|
//
|
|
bool Local = m_pClient->m_Snap.m_LocalClientID == i;
|
|
if((p % 2) == 0 && Local) continue;
|
|
if((p % 2) == 1 && !Local) continue;
|
|
|
|
CNetObj_Character PrevChar = m_pClient->m_aClients[i].m_RenderPrev;
|
|
CNetObj_Character CurChar = m_pClient->m_aClients[i].m_RenderCur;
|
|
|
|
if(p<2)
|
|
{
|
|
RenderHook(
|
|
&PrevChar,
|
|
&CurChar,
|
|
&m_aRenderInfo[i],
|
|
i
|
|
);
|
|
}
|
|
else
|
|
{
|
|
RenderPlayer(
|
|
&PrevChar,
|
|
&CurChar,
|
|
&m_aRenderInfo[i],
|
|
i
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPlayers::OnInit()
|
|
{
|
|
m_WeaponEmoteQuadContainerIndex = Graphics()->CreateQuadContainer();
|
|
|
|
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
|
|
|
|
for(int i = 0; i < NUM_WEAPONS; ++i)
|
|
{
|
|
RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[i].m_pSpriteBody, 0);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, g_pData->m_Weapons.m_aId[i].m_VisualSize);
|
|
RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[i].m_pSpriteBody, SPRITE_FLAG_FLIP_Y);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, g_pData->m_Weapons.m_aId[i].m_VisualSize);
|
|
}
|
|
|
|
// at the end the hand
|
|
RenderTools()->SelectSprite(SPRITE_TEE_HAND_OUTLINE, 0, 0, 0);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, 20.f, false);
|
|
RenderTools()->SelectSprite(SPRITE_TEE_HAND, 0, 0, 0);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, 20.f, false);
|
|
|
|
RenderTools()->SelectSprite(SPRITE_HOOK_HEAD);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, -12.f, -8.f, 24.f, 16.f);
|
|
RenderTools()->SelectSprite(SPRITE_HOOK_CHAIN);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, -12.f, -8.f, 24.f, 16.f);
|
|
|
|
for(int i = 0; i < NUM_EMOTICONS; ++i)
|
|
{
|
|
RenderTools()->SelectSprite(SPRITE_OOP + i);
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponEmoteQuadContainerIndex, 64.f, false);
|
|
}
|
|
|
|
for(int i = 0; i < NUM_WEAPONS; ++i)
|
|
{
|
|
m_WeaponSpriteMuzzleQuadContainerIndex[i] = Graphics()->CreateQuadContainer();
|
|
for(int n = 0; n < g_pData->m_Weapons.m_aId[i].m_NumSpriteMuzzles; ++n)
|
|
{
|
|
if(g_pData->m_Weapons.m_aId[i].m_aSpriteMuzzles[n])
|
|
{
|
|
RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[i].m_aSpriteMuzzles[n], 0);
|
|
}
|
|
if(WEAPON_NINJA == i)
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponSpriteMuzzleQuadContainerIndex[i], 160.f);
|
|
else
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponSpriteMuzzleQuadContainerIndex[i], g_pData->m_Weapons.m_aId[i].m_VisualSize);
|
|
|
|
if(g_pData->m_Weapons.m_aId[i].m_aSpriteMuzzles[n])
|
|
{
|
|
RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[i].m_aSpriteMuzzles[n], SPRITE_FLAG_FLIP_Y);
|
|
}
|
|
if(WEAPON_NINJA == i)
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponSpriteMuzzleQuadContainerIndex[i], 160.f);
|
|
else
|
|
RenderTools()->QuadContainerAddSprite(m_WeaponSpriteMuzzleQuadContainerIndex[i], g_pData->m_Weapons.m_aId[i].m_VisualSize);
|
|
}
|
|
}
|
|
|
|
Graphics()->QuadsSetSubset(0.f, 0.f, 1.f, 1.f);
|
|
Graphics()->QuadsSetRotation(0.f);
|
|
// the direction
|
|
m_DirectionQuadContainerIndex = Graphics()->CreateQuadContainer();
|
|
|
|
IGraphics::CQuadItem QuadItem(0.f, 0.f, 22.f, 22.f);
|
|
Graphics()->QuadContainerAddQuads(m_DirectionQuadContainerIndex, &QuadItem, 1);
|
|
|
|
}
|