1316: code improvement on gameclient.cpp r=def- a=Ryozuki



1341: Mark unused envelopes r=def- a=Learath2

Thought I'd do this one as well while looking at the editor code.

1349: Don't ignore CONNECT packets with data that we don't know r=def- a=heinrich5991

This specifically affects 0.6.5. Just treat them the same way as those
without any data.

Co-authored-by: Ryozuki <edgar@ryobyte.com>
Co-authored-by: Learath <learath2@gmail.com>
Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
bors[bot] 2018-10-29 14:31:27 +00:00
commit 1b0b36c6eb
4 changed files with 144 additions and 95 deletions

View file

@ -508,45 +508,38 @@ void CNetServer::OnConnCtrlMsg(NETADDR &Addr, int ClientID, int ControlMsg, cons
void CNetServer::OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet) void CNetServer::OnTokenCtrlMsg(NETADDR &Addr, int ControlMsg, const CNetPacketConstruct &Packet)
{ {
if (ClientExists(Addr)) if(ClientExists(Addr))
return; // silently ignore return; // silently ignore
if (Addr.type == NETTYPE_WEBSOCKET_IPV4) if(Addr.type == NETTYPE_WEBSOCKET_IPV4)
{ {
// websocket client doesn't send token // websocket client doesn't send token
// direct accept // direct accept
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), NET_SECURITY_TOKEN_UNSUPPORTED); SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), NET_SECURITY_TOKEN_UNSUPPORTED);
TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED); TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED);
} }
else if (ControlMsg == NET_CTRLMSG_CONNECT) else if(ControlMsg == NET_CTRLMSG_CONNECT)
{ {
bool SupportsToken = Packet.m_DataSize >= // response connection request with token
(int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(SECURITY_TOKEN)) && SECURITY_TOKEN Token = GetToken(Addr);
!mem_comp(&Packet.m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)); SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token);
if (SupportsToken)
{
// response connection request with token
SECURITY_TOKEN Token = GetToken(Addr);
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC), Token);
}
} }
else if (ControlMsg == NET_CTRLMSG_ACCEPT && Packet.m_DataSize == 1 + sizeof(SECURITY_TOKEN)) else if(ControlMsg == NET_CTRLMSG_ACCEPT)
{ {
SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]); SECURITY_TOKEN Token = ToSecurityToken(&Packet.m_aChunkData[1]);
if (Token == GetToken(Addr)) if(Token == GetToken(Addr))
{ {
// correct token // correct token
// try to accept client // try to accept client
if (g_Config.m_Debug) if(g_Config.m_Debug)
dbg_msg("security", "new client (ddnet token)"); dbg_msg("security", "new client (ddnet token)");
TryAcceptClient(Addr, Token); TryAcceptClient(Addr, Token);
} }
else else
{ {
// invalid token // invalid token
if (g_Config.m_Debug) if(g_Config.m_Debug)
dbg_msg("security", "invalid token"); dbg_msg("security", "invalid token");
} }
} }
@ -570,6 +563,29 @@ int CNetServer::GetClientSlot(const NETADDR &Addr)
return Slot; return Slot;
} }
static bool IsDDNetControlMsg(const CNetPacketConstruct *pPacket)
{
if(!(pPacket->m_Flags&NET_PACKETFLAG_CONTROL)
|| pPacket->m_DataSize < 1)
{
return false;
}
if(pPacket->m_aChunkData[0] == NET_CTRLMSG_CONNECT
&& pPacket->m_DataSize >= (int)(1 + sizeof(SECURITY_TOKEN_MAGIC) + sizeof(SECURITY_TOKEN))
&& mem_comp(&pPacket->m_aChunkData[1], SECURITY_TOKEN_MAGIC, sizeof(SECURITY_TOKEN_MAGIC)) == 0)
{
// DDNet CONNECT
return true;
}
if(pPacket->m_aChunkData[0] == NET_CTRLMSG_ACCEPT
&& pPacket->m_DataSize >= 1 + (int)sizeof(SECURITY_TOKEN))
{
// DDNet ACCEPT
return true;
}
return false;
}
/* /*
TODO: chopp up this function into smaller working parts TODO: chopp up this function into smaller working parts
*/ */
@ -643,9 +659,8 @@ int CNetServer::Recv(CNetChunk *pChunk)
{ {
// not found, client that wants to connect // not found, client that wants to connect
if(m_RecvUnpacker.m_Data.m_Flags&NET_PACKETFLAG_CONTROL && if(IsDDNetControlMsg(&m_RecvUnpacker.m_Data))
m_RecvUnpacker.m_Data.m_DataSize > 1) // got ddnet control msg
// got control msg with extra size (should support token)
OnTokenCtrlMsg(Addr, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data); OnTokenCtrlMsg(Addr, m_RecvUnpacker.m_Data.m_aChunkData[0], m_RecvUnpacker.m_Data);
else else
// got connection-less ctrl or sys msg // got connection-less ctrl or sys msg

View file

@ -345,9 +345,9 @@ void CGameClient::OnInit()
for(unsigned int i = 0; i < 16; i++) for(unsigned int i = 0; i < 16; i++)
{ {
if(rand() % 2) if(rand() % 2)
g_Config.m_ClTimeoutCode[i] = (rand() % 26) + 97; g_Config.m_ClTimeoutCode[i] =(char)((rand() % 26) + 97);
else else
g_Config.m_ClTimeoutCode[i] = (rand() % 26) + 65; g_Config.m_ClTimeoutCode[i] = (char)((rand() % 26) + 65);
} }
} }
@ -356,9 +356,9 @@ void CGameClient::OnInit()
for(unsigned int i = 0; i < 16; i++) for(unsigned int i = 0; i < 16; i++)
{ {
if(rand() % 2) if(rand() % 2)
g_Config.m_ClDummyTimeoutCode[i] = (rand() % 26) + 97; g_Config.m_ClDummyTimeoutCode[i] = (char)((rand() % 26) + 97);
else else
g_Config.m_ClDummyTimeoutCode[i] = (rand() % 26) + 65; g_Config.m_ClDummyTimeoutCode[i] = (char)((rand() % 26) + 65);
} }
} }
} }
@ -446,8 +446,8 @@ int CGameClient::OnSnapInput(int *pData, bool Dummy, bool Force)
vec2 Main = m_LocalCharacterPos; vec2 Main = m_LocalCharacterPos;
vec2 Dummy = m_aClients[m_LocalIDs[!g_Config.m_ClDummy]].m_Predicted.m_Pos; vec2 Dummy = m_aClients[m_LocalIDs[!g_Config.m_ClDummy]].m_Predicted.m_Pos;
vec2 Dir = Main - Dummy; vec2 Dir = Main - Dummy;
m_HammerInput.m_TargetX = Dir.x; m_HammerInput.m_TargetX = (int)(Dir.x);
m_HammerInput.m_TargetY = Dir.y; m_HammerInput.m_TargetY = (int)(Dir.y);
mem_copy(pData, &m_HammerInput, sizeof(m_HammerInput)); mem_copy(pData, &m_HammerInput, sizeof(m_HammerInput));
return sizeof(m_HammerInput); return sizeof(m_HammerInput);
@ -1019,7 +1019,7 @@ void CGameClient::OnNewSnapshot()
char aMessage[64]; char aMessage[64];
int MsgLen = rand()%(sizeof(aMessage)-1); int MsgLen = rand()%(sizeof(aMessage)-1);
for(int i = 0; i < MsgLen; i++) for(int i = 0; i < MsgLen; i++)
aMessage[i] = 'a'+(rand()%('z'-'a')); aMessage[i] = (char)('a' + (rand() % ('z' - 'a')));
aMessage[MsgLen] = 0; aMessage[MsgLen] = 0;
CNetMsg_Cl_Say Msg; CNetMsg_Cl_Say Msg;
@ -1135,7 +1135,7 @@ void CGameClient::OnNewSnapshot()
static bool s_GameOver = 0; static bool s_GameOver = 0;
static bool s_GamePaused = 0; static bool s_GamePaused = 0;
m_Snap.m_pGameInfoObj = (const CNetObj_GameInfo *)pData; m_Snap.m_pGameInfoObj = (const CNetObj_GameInfo *)pData;
bool CurrentTickGameOver = m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER; bool CurrentTickGameOver = (bool)(m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER);
if(!s_GameOver && CurrentTickGameOver) if(!s_GameOver && CurrentTickGameOver)
OnGameOver(); OnGameOver();
else if(s_GameOver && !CurrentTickGameOver) else if(s_GameOver && !CurrentTickGameOver)
@ -1150,7 +1150,7 @@ void CGameClient::OnNewSnapshot()
m_pStatboard->OnReset(); m_pStatboard->OnReset();
m_LastRoundStartTick = m_Snap.m_pGameInfoObj->m_RoundStartTick; m_LastRoundStartTick = m_Snap.m_pGameInfoObj->m_RoundStartTick;
s_GameOver = CurrentTickGameOver; s_GameOver = CurrentTickGameOver;
s_GamePaused = m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED; s_GamePaused = (bool)(m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED);
} }
else if(Item.m_Type == NETOBJTYPE_GAMEDATA) else if(Item.m_Type == NETOBJTYPE_GAMEDATA)
{ {
@ -1225,22 +1225,17 @@ void CGameClient::OnNewSnapshot()
} }
} }
// update friend state
for(int i = 0; i < MAX_CLIENTS; ++i) for(int i = 0; i < MAX_CLIENTS; ++i)
{ {
if(i == m_Snap.m_LocalClientID || !m_Snap.m_paPlayerInfos[i] || !Friends()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true)) // update friend state
m_aClients[i].m_Friend = false; m_aClients[i].m_Friend = !(i == m_Snap.m_LocalClientID
else || !m_Snap.m_paPlayerInfos[i]
m_aClients[i].m_Friend = true; || !Friends()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true));
}
// update foe state // update foe state
for(int i = 0; i < MAX_CLIENTS; ++i) m_aClients[i].m_Foe = !(i == m_Snap.m_LocalClientID
{ || !m_Snap.m_paPlayerInfos[i]
if(i == m_Snap.m_LocalClientID || !m_Snap.m_paPlayerInfos[i] || !Foes()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true)) || !Foes()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true));
m_aClients[i].m_Foe = false;
else
m_aClients[i].m_Foe = true;
} }
// sort player infos by name // sort player infos by name
@ -1414,7 +1409,6 @@ void CGameClient::OnPredict()
class CLocalProjectile PredictedProjectiles[MaxProjectiles]; class CLocalProjectile PredictedProjectiles[MaxProjectiles];
int NumProjectiles = 0; int NumProjectiles = 0;
int ReloadTimer = 0; int ReloadTimer = 0;
vec2 PrevPos;
if(AntiPingWeapons()) if(AntiPingWeapons())
{ {
@ -1518,53 +1512,43 @@ void CGameClient::OnPredict()
bool WeaponFired = false; bool WeaponFired = false;
bool NewPresses = false; bool NewPresses = false;
// handle weapons
do
{
if(ReloadTimer)
break;
if(!World.m_apCharacters[m_Snap.m_LocalClientID])
break;
if(!pInput || !pPrevInput)
break;
// handle weapons
if(!ReloadTimer && World.m_apCharacters[m_Snap.m_LocalClientID] && (pInput && pPrevInput))
{
bool FullAuto = false; bool FullAuto = false;
if(Local->m_ActiveWeapon == WEAPON_GRENADE || Local->m_ActiveWeapon == WEAPON_SHOTGUN || Local->m_ActiveWeapon == WEAPON_RIFLE) if(Local->m_ActiveWeapon == WEAPON_GRENADE || Local->m_ActiveWeapon == WEAPON_SHOTGUN || Local->m_ActiveWeapon == WEAPON_RIFLE)
FullAuto = true; FullAuto = true;
bool WillFire = false; bool WillFire = false;
if(CountInput(PrevInput.m_Fire, Input.m_Fire).m_Presses) if(CountInput(PrevInput.m_Fire, Input.m_Fire).m_Presses)
{ {
WillFire = true; WillFire = true;
NewPresses = true; NewPresses = true;
} }
if(FullAuto && (Input.m_Fire&1))
if(FullAuto && (Input.m_Fire & 1))
WillFire = true; WillFire = true;
if(!WillFire)
break;
if(!IsRace(&Info) && !m_Snap.m_pLocalCharacter->m_AmmoCount && Local->m_ActiveWeapon != WEAPON_HAMMER)
break;
int ExpectedStartTick = Tick-1; if(WillFire && ((IsRace(&Info) || m_Snap.m_pLocalCharacter->m_AmmoCount) || Local->m_ActiveWeapon == WEAPON_HAMMER)) {
ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000; int ExpectedStartTick = Tick - 1;
ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000;
bool DirectInput = Client()->InputExists(Tick); bool DirectInput = Client()->InputExists(Tick);
if(!DirectInput) if(!DirectInput)
{ {
ReloadTimer++; ReloadTimer++;
ExpectedStartTick++; ExpectedStartTick++;
} }
switch(Local->m_ActiveWeapon)
switch(Local->m_ActiveWeapon) {
{ case WEAPON_RIFLE:
case WEAPON_RIFLE: case WEAPON_SHOTGUN:
case WEAPON_SHOTGUN: case WEAPON_GUN:
case WEAPON_GUN:
{ {
WeaponFired = true; WeaponFired = true;
} break; } break;
case WEAPON_GRENADE: case WEAPON_GRENADE:
{ {
if(NumProjectiles >= MaxProjectiles) if(NumProjectiles >= MaxProjectiles)
break; break;
@ -1580,7 +1564,7 @@ void CGameClient::OnPredict()
NumProjectiles++; NumProjectiles++;
WeaponFired = true; WeaponFired = true;
} break; } break;
case WEAPON_HAMMER: case WEAPON_HAMMER:
{ {
vec2 ProjPos = ProjStartPos; vec2 ProjPos = ProjStartPos;
float Radius = ProximityRadius*0.5f; float Radius = ProximityRadius*0.5f;
@ -1595,7 +1579,7 @@ void CGameClient::OnPredict()
continue; continue;
if(i == m_Snap.m_LocalClientID) if(i == m_Snap.m_LocalClientID)
continue; continue;
if(!(distance(World.m_apCharacters[i]->m_Pos, ProjPos) < Radius+ProximityRadius)) if(distance(World.m_apCharacters[i]->m_Pos, ProjPos) >= Radius + ProximityRadius)
continue; continue;
CCharacterCore *pTarget = World.m_apCharacters[i]; CCharacterCore *pTarget = World.m_apCharacters[i];
@ -1627,14 +1611,15 @@ void CGameClient::OnPredict()
WeaponFired = true; WeaponFired = true;
} }
} break; } break;
}
if(!ReloadTimer)
{
ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000;
if(!DirectInput)
ReloadTimer++;
}
} }
if(!ReloadTimer) }
{
ReloadTimer = g_pData->m_Weapons.m_aId[Local->m_ActiveWeapon].m_Firedelay * SERVER_TICK_SPEED / 1000;
if(!DirectInput)
ReloadTimer++;
}
} while(false);
if(ReloadTimer) if(ReloadTimer)
ReloadTimer--; ReloadTimer--;
@ -1693,10 +1678,7 @@ void CGameClient::OnPredict()
{ {
if(!World.m_apCharacters[c]) if(!World.m_apCharacters[c])
continue; continue;
if(m_Snap.m_LocalClientID == c) World.m_apCharacters[c]->Tick(m_Snap.m_LocalClientID == c, true);
World.m_apCharacters[c]->Tick(true, true);
else
World.m_apCharacters[c]->Tick(false, true);
} }
} }
@ -2103,7 +2085,7 @@ void CLocalProjectile::Init(CGameClient *pGameClient, CWorldCore *pWorld, CColli
{ {
bool StandardVel = (fabs(1.0f - length(m_Direction)) < 0.015); bool StandardVel = (fabs(1.0f - length(m_Direction)) < 0.015);
m_Owner = -1; m_Owner = -1;
m_Explosive = ((m_Type == WEAPON_GRENADE && StandardVel) ? true : false); m_Explosive = m_Type == WEAPON_GRENADE && StandardVel;
m_Bouncing = 0; m_Bouncing = 0;
m_Freeze = 0; m_Freeze = 0;
m_ExtraInfo = false; m_ExtraInfo = false;
@ -2155,8 +2137,8 @@ vec2 CLocalProjectile::GetPos(float Time)
bool CLocalProjectile::GameLayerClipped(vec2 CheckPos) bool CLocalProjectile::GameLayerClipped(vec2 CheckPos)
{ {
return round_to_int(CheckPos.x)/32 < -200 || round_to_int(CheckPos.x)/32 > m_pCollision->GetWidth()+200 || return round_to_int(CheckPos.x) / 32 < -200 || round_to_int(CheckPos.x) / 32 > m_pCollision->GetWidth() + 200 ||
round_to_int(CheckPos.y)/32 < -200 || round_to_int(CheckPos.y)/32 > m_pCollision->GetHeight()+200 ? true : false; round_to_int(CheckPos.y)/32 < -200 || round_to_int(CheckPos.y)/32 > m_pCollision->GetHeight()+200;
} }
void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientID) void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientID)
@ -2183,7 +2165,7 @@ void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientI
bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking); bool OwnerCanProbablyHitOthers = (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision || m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking);
if(((Target >= 0 && (m_Owner >= 0 ? OwnerCanProbablyHitOthers : 1 || Target == m_Owner)) || Collide || GameLayerClipped(CurPos)) && !IsWeaponCollide) if(((Target >= 0 && (m_Owner >= 0 ? OwnerCanProbablyHitOthers : true)) || Collide || GameLayerClipped(CurPos)) && !IsWeaponCollide)
{ {
if(m_Explosive && (Target < 0 || (Target >= 0 && (!m_Freeze || (m_Weapon == WEAPON_SHOTGUN && Collide))))) if(m_Explosive && (Target < 0 || (Target >= 0 && (!m_Freeze || (m_Weapon == WEAPON_SHOTGUN && Collide)))))
CreateExplosion(ColPos, m_Owner); CreateExplosion(ColPos, m_Owner);
@ -2209,11 +2191,11 @@ void CLocalProjectile::Tick(int CurrentTick, int GameTickSpeed, int LocalClientI
{ {
int Lifetime = 0; int Lifetime = 0;
if(m_Weapon == WEAPON_GRENADE) if(m_Weapon == WEAPON_GRENADE)
Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED; Lifetime = (int)(m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED);
else if(m_Weapon == WEAPON_GUN) else if(m_Weapon == WEAPON_GUN)
Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED; Lifetime = (int)(m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeLifetime * SERVER_TICK_SPEED);
else if(m_Weapon == WEAPON_SHOTGUN) else if(m_Weapon == WEAPON_SHOTGUN)
Lifetime = m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunLifetime * SERVER_TICK_SPEED; Lifetime = (int)(m_pGameClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunLifetime * SERVER_TICK_SPEED);
int LifeSpan = Lifetime - (CurrentTick - m_StartTick); int LifeSpan = Lifetime - (CurrentTick - m_StartTick);
if(LifeSpan == -1) if(LifeSpan == -1)
{ {

View file

@ -4591,6 +4591,47 @@ void CEditor::RenderUndoList(CUIRect View)
} }
} }
bool CEditor::IsEnvelopeUsed(int EnvelopeIndex)
{
for(int i = 0; i < m_Map.m_lGroups.size(); i++)
{
for(int j = 0; j < m_Map.m_lGroups[i]->m_lLayers.size(); j++)
{
if(m_Map.m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_QUADS)
{
CLayerQuads *pQuadLayer = (CLayerQuads *)m_Map.m_lGroups[i]->m_lLayers[j];
for(int k = 0; k < pQuadLayer->m_lQuads.size(); k++)
{
if(pQuadLayer->m_lQuads[k].m_PosEnv == EnvelopeIndex
|| pQuadLayer->m_lQuads[k].m_ColorEnv == EnvelopeIndex)
{
return true;
}
}
}
else if(m_Map.m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_SOUNDS)
{
CLayerSounds *pSoundLayer = (CLayerSounds *)m_Map.m_lGroups[i]->m_lLayers[j];
for(int k = 0; k < pSoundLayer->m_lSources.size(); k++)
{
if(pSoundLayer->m_lSources[k].m_PosEnv == EnvelopeIndex
|| pSoundLayer->m_lSources[k].m_SoundEnv == EnvelopeIndex)
{
return true;
}
}
}
else if(m_Map.m_lGroups[i]->m_lLayers[j]->m_Type == LAYERTYPE_TILES)
{
CLayerTiles *pTileLayer = (CLayerTiles *)m_Map.m_lGroups[i]->m_lLayers[j];
if(pTileLayer->m_ColorEnv == EnvelopeIndex)
return true;
}
}
}
return false;
}
void CEditor::RenderEnvelopeEditor(CUIRect View) void CEditor::RenderEnvelopeEditor(CUIRect View)
{ {
if(m_SelectedEnvelope < 0) m_SelectedEnvelope = 0; if(m_SelectedEnvelope < 0) m_SelectedEnvelope = 0;
@ -4680,7 +4721,16 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
Shifter.VSplitLeft(15.0f, &Dec, &Shifter); Shifter.VSplitLeft(15.0f, &Dec, &Shifter);
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf),"%d/%d", m_SelectedEnvelope+1, m_Map.m_lEnvelopes.size()); str_format(aBuf, sizeof(aBuf),"%d/%d", m_SelectedEnvelope+1, m_Map.m_lEnvelopes.size());
RenderTools()->DrawUIRect(&Shifter, vec4(1,1,1,0.5f), 0, 0.0f);
vec4 EnvColor = vec4(1, 1, 1, 0.5f);
if(m_Map.m_lEnvelopes.size())
{
EnvColor = IsEnvelopeUsed(m_SelectedEnvelope) ?
vec4(0.7f, 1, 0.7f, 0.5f) :
vec4(1, 0.7f, 0.7f, 0.5f);
}
RenderTools()->DrawUIRect(&Shifter, EnvColor, 0, 0.0f);
UI()->DoLabel(&Shifter, aBuf, 10.0f, 0, -1); UI()->DoLabel(&Shifter, aBuf, 10.0f, 0, -1);
static int s_PrevButton = 0; static int s_PrevButton = 0;

View file

@ -1018,6 +1018,8 @@ public:
static void AddImage(const char *pFilename, int StorageType, void *pUser); static void AddImage(const char *pFilename, int StorageType, void *pUser);
static void AddSound(const char *pFileName, int StorageType, void *pUser); static void AddSound(const char *pFileName, int StorageType, void *pUser);
bool IsEnvelopeUsed(int EnvelopeIndex);
void RenderImages(CUIRect Toolbox, CUIRect View); void RenderImages(CUIRect Toolbox, CUIRect View);
void RenderLayers(CUIRect Toolbox, CUIRect View); void RenderLayers(CUIRect Toolbox, CUIRect View);
void RenderSounds(CUIRect Toolbox, CUIRect View); void RenderSounds(CUIRect Toolbox, CUIRect View);