5 changes: 5 additions & 0 deletions src/game/client/components/controls.cpp
Expand Up @@ -533,12 +533,17 @@ void CControls::ClampMousePos()
else
{
float CameraMaxDistance = 200.0f;
float CameraMinDistance = 0.0f;
float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f;
float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone;
float MaxDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMaxDistance : g_Config.m_ClMouseMaxDistance;
float MouseMax = min(CameraMaxDistance/FollowFactor + DeadZone, MaxDistance);
float MinDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMinDistance : g_Config.m_ClMouseMinDistance;
float MouseMin = min(CameraMinDistance/FollowFactor + DeadZone, MinDistance);

if(length(m_MousePos[g_Config.m_ClDummy]) > MouseMax)
m_MousePos[g_Config.m_ClDummy] = normalize(m_MousePos[g_Config.m_ClDummy])*MouseMax;
if(length(m_MousePos[g_Config.m_ClDummy]) < MouseMin)
m_MousePos[g_Config.m_ClDummy] = normalize(m_MousePos[g_Config.m_ClDummy])*MouseMin;
}
}
24 changes: 12 additions & 12 deletions src/game/client/components/hud.cpp
Expand Up @@ -91,7 +91,7 @@ void CHud::OnInit()
RenderTools()->SelectSprite(g_pData->m_Weapons.m_aId[i].m_pSpriteCursor);
RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 64.f);
}

// the flags
RenderTools()->SelectSprite(SPRITE_FLAG_RED);
RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 8.f, 16.f);
Expand Down Expand Up @@ -196,8 +196,8 @@ void CHud::RenderScoreHud()

bool RecreateTeamScore[2] = { str_comp(aScoreTeam[0], m_aScoreInfo[0].m_aScoreText) != 0, str_comp(aScoreTeam[1], m_aScoreInfo[1].m_aScoreText) != 0 };

int FlagCarrier[2] = {
m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed,
int FlagCarrier[2] = {
m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed,
m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue
};

Expand All @@ -220,7 +220,7 @@ void CHud::RenderScoreHud()
{
// draw box
if(RecreateRect)
{
{
if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1)
Graphics()->DeleteQuadContainer(m_aScoreInfo[t].m_RoundRectQuadContainerIndex);

Expand Down Expand Up @@ -277,8 +277,8 @@ void CHud::RenderScoreHud()
if(m_aScoreInfo[t].m_OptionalNameTextContainerIndex != -1)
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex);

float w = TextRender()->TextWidth(0, 8.0f, pName, -1);
float w = TextRender()->TextWidth(0, 8.0f, pName, -1);

CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, min(Whole - w - 1.0f, Whole - ScoreWidthMax - ImageSize - 2 * Split), StartY + (t + 1)*20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER);
Cursor.m_LineWidth = -1;
Expand Down Expand Up @@ -435,7 +435,7 @@ void CHud::RenderScoreHud()
int ID = apPlayerInfo[t]->m_ClientID;
if(ID >= 0 && ID < MAX_CLIENTS)
{
const char *pName = m_pClient->m_aClients[ID].m_aName;
const char *pName = m_pClient->m_aClients[ID].m_aName;
if(RecreateRect)
{
mem_copy(m_aScoreInfo[t].m_aPlayerNameText, pName, sizeof(m_aScoreInfo[t].m_aPlayerNameText));
Expand Down Expand Up @@ -479,7 +479,7 @@ void CHud::RenderScoreHud()

if(m_aScoreInfo[t].m_TextRankContainerIndex != -1)
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextRankContainerIndex);

CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Whole - ScoreWidthMax - ImageSize - Split - PosSize, StartY + t * 20 + (18.f - 10.f) / 2.f, 10.0f, TEXTFLAG_RENDER);
Cursor.m_LineWidth = -1;
Expand Down Expand Up @@ -521,7 +521,7 @@ void CHud::RenderWarmupTimer()
void CHud::MapscreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup)
{
float Points[4];
RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX/100.0f, pGroup->m_ParallaxY/100.0f,
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]);
}
Expand Down Expand Up @@ -705,7 +705,7 @@ void CHud::RenderCursor()

MapscreenToGroup(m_pClient->m_pCamera->m_Center.x, m_pClient->m_pCamera->m_Center.y, Layers()->GameGroup());
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);

// render cursor
int QuadOffset = NUM_WEAPONS * 10 + 40 + (m_pClient->m_Snap.m_pLocalCharacter->m_Weapon%NUM_WEAPONS);
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
Expand Down Expand Up @@ -759,13 +759,13 @@ void CHud::RenderHealthAndAmmo(const CNetObj_Character *pCharacter)
// render gui stuff

Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id);

int QuadOffset = pCharacter->m_Weapon%NUM_WEAPONS * 10;
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, min(pCharacter->m_AmmoCount, 10));

QuadOffset = NUM_WEAPONS * 10;
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, min(pCharacter->m_Health, 10));

QuadOffset += 10 + min(pCharacter->m_Health, 10);
if(min(pCharacter->m_Health, 10) < 10)
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, QuadOffset, 10 - min(pCharacter->m_Health, 10));
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/maplayers.cpp
Expand Up @@ -49,7 +49,7 @@ void CMapLayers::EnvelopeUpdate()
void CMapLayers::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom)
{
float Points[4];
RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX/100.0f, pGroup->m_ParallaxY/100.0f,
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]);
}
Expand Down
2 changes: 0 additions & 2 deletions src/game/client/components/menus.cpp
Expand Up @@ -1058,8 +1058,6 @@ int CMenus::Render()
RenderServerControl(MainView);
else if(m_GamePage == PAGE_SETTINGS)
RenderSettings(MainView);
else if(m_GamePage == PAGE_GHOST)
RenderGhost(MainView);
}
else if(g_Config.m_UiPage == PAGE_NEWS)
RenderNews(MainView);
Expand Down
8 changes: 4 additions & 4 deletions src/game/client/components/menus_browser.cpp
Expand Up @@ -1320,7 +1320,7 @@ void CMenus::RenderServerbrowser(CUIRect MainView)
bool NeedUpdate = str_comp(Client()->LatestVersion(), "0");
if(State == IUpdater::CLEAN && NeedUpdate)
{
str_format(aBuf, sizeof(aBuf), "DDNet %s is out!", Client()->LatestVersion());
str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out!"), Client()->LatestVersion());
TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f);
}
else if(State == IUpdater::CLEAN)
Expand All @@ -1331,16 +1331,16 @@ void CMenus::RenderServerbrowser(CUIRect MainView)
{
char aCurrentFile[64];
Updater()->GetCurrentFile(aCurrentFile, sizeof(aCurrentFile));
str_format(aBuf, sizeof(aBuf), "Downloading %s:", aCurrentFile);
str_format(aBuf, sizeof(aBuf), Localize("Downloading %s:"), aCurrentFile);
}
else if(State == IUpdater::FAIL)
{
str_format(aBuf, sizeof(aBuf), "Failed to download a file! Restart client to retry...");
str_format(aBuf, sizeof(aBuf), Localize("Update failed! Check log..."));
TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f);
}
else if(State == IUpdater::NEED_RESTART)
{
str_format(aBuf, sizeof(aBuf), "DDNet Client updated!");
str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!"));
TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f);
}
UI()->DoLabelScaled(&Button, aBuf, 14.0f, -1);
Expand Down
1 change: 1 addition & 0 deletions src/game/client/components/menus_demo.cpp
Expand Up @@ -690,6 +690,7 @@ int CMenus::DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int
if(IsDir)
{
str_format(Item.m_aName, sizeof(Item.m_aName), "%s/", pName);
Item.m_InfosLoaded = false;
Item.m_Valid = false;
}
else
Expand Down
1 change: 0 additions & 1 deletion src/game/client/components/menus_ingame.cpp
Expand Up @@ -27,7 +27,6 @@

#include <base/tl/string.h>
#include <engine/keys.h>
#include <engine/graphics.h>
#include <engine/storage.h>
#include "ghost.h"

Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/nameplates.cpp
Expand Up @@ -17,7 +17,7 @@
void CNamePlates::MapscreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup)
{
float Points[4];
RenderTools()->MapscreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX / 100.0f, pGroup->m_ParallaxY / 100.0f, pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), 1.0f, Points);
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]);
}

Expand Down
25 changes: 21 additions & 4 deletions src/game/client/components/scoreboard.cpp
Expand Up @@ -473,26 +473,43 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch
Cursor.m_LineWidth = NameLength;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1);
}
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);

// clan
tw = TextRender()->TextWidth(0, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1);
TextRender()->SetCursor(&Cursor, ClanOffset+ClanLength/2-tw/2, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
if(str_comp(m_pClient->m_aClients[pInfo->m_ClientID].m_aClan,
m_pClient->m_aClients[GameClient()->m_LocalIDs[0]].m_aClan) == 0)
{
vec4 Color = m_pClient->m_pSkins->GetColorV4(g_Config.m_ClSameClanColor);
TextRender()->TextColor(Color.r, Color.g, Color.b, Color.a);
}
else
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);

tw = TextRender()->TextWidth(nullptr, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1);
TextRender()->SetCursor(&Cursor, ClanOffset + ClanLength / 2 - tw / 2, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = ClanLength;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1);

TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);

// country flag
vec4 Color(1.0f, 1.0f, 1.0f, 0.5f);
m_pClient->m_pCountryFlags->Render(m_pClient->m_aClients[pInfo->m_ClientID].m_Country, &Color,
CountryOffset, y+(Spacing+TeeSizeMod*5.0f)/2.0f, CountryLength, LineHeight-Spacing-TeeSizeMod*5.0f);

// ping
if(g_Config.m_ClEnablePingColor)
{
vec3 rgb = HslToRgb(vec3((300.0f - clamp(pInfo->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f));
TextRender()->TextColor(rgb.r, rgb.g, rgb.b, 1.0f);
}
str_format(aBuf, sizeof(aBuf), "%d", clamp(pInfo->m_Latency, 0, 1000));
tw = TextRender()->TextWidth(0, FontSize, aBuf, -1);
tw = TextRender()->TextWidth(nullptr, FontSize, aBuf, -1);
TextRender()->SetCursor(&Cursor, PingOffset+PingLength-tw, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = PingLength;
TextRender()->TextEx(&Cursor, aBuf, -1);

TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);

y += LineHeight+Spacing;
if (lower32 || upper32) {
if (rendered == 32) break;
Expand Down
41 changes: 41 additions & 0 deletions src/game/client/gameclient.cpp
Expand Up @@ -218,6 +218,7 @@ void CGameClient::OnConsoleInit()
// add the some console commands
Console()->Register("team", "i[team-id]", CFGFLAG_CLIENT, ConTeam, this, "Switch team");
Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself");
Console()->Register("color_from_rgb", "s[color]", CFGFLAG_CLIENT, ConColorFromRgb, this, "Convert HEX RGB color (3 or 6 digits) to TW formats");

// register server dummy commands for tab completion
Console()->Register("tune", "s[tuning] i[value]", CFGFLAG_SERVER, 0, 0, "Tune variable to value");
Expand Down Expand Up @@ -1956,6 +1957,46 @@ void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData)
((CGameClient*)pUserData)->SendKill(-1);
}

void CGameClient::ConColorFromRgb(IConsole::IResult *pResult, void *pUserData)
{
CGameClient *pThis = (CGameClient*)pUserData;
const char *pString = pResult->GetString(0);
const size_t Length = str_length(pString);
vec3 Hsl;
vec3 Rgb;

if(Length == 3)
{
const int Num = str_toint_base(pString, 16);
Rgb.r = (float)(((Num & 0xF00) >> 8) + ((Num & 0xF00) >> 4)) / 255.0f;
Rgb.g = (float)(((Num & 0x0F0) >> 4) + ((Num & 0x0F0) >> 0)) / 255.0f;
Rgb.b = (float)(((Num & 0x00F) >> 0) + ((Num & 0x00F) << 4)) / 255.0f;
}
else if(Length == 6)
{
const int Num = str_toint_base(pString, 16);
Rgb.r = (float)((Num & 0xFF0000) >> 16) / 255.0f;
Rgb.g = (float)((Num & 0x00FF00) >> 8) / 255.0f;
Rgb.b = (float)((Num & 0x0000FF) >> 0) / 255.0f;
}
else
{
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "color", "Unknown color format");
return;
}

char aBuf[32];
Hsl = RgbToHsl(Rgb);
// full lightness range for GUI colors
str_format(aBuf, sizeof(aBuf), "Hue: %d, Sat: %d, Lht: %d", (int)Hsl.h, (int)Hsl.s, (int)Hsl.l);
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "color", aBuf);

// limited lightness range to prevent too dark colors for player colors
Hsl.l = clamp((Hsl.l - 127.0) * 2.0, 0.0, 255.0);
str_format(aBuf, sizeof(aBuf), "%d", ((int)Hsl.h << 16) + ((int)Hsl.s << 8) + (int)Hsl.l);
pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "color", aBuf);
}

void CGameClient::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
Expand Down
1 change: 1 addition & 0 deletions src/game/client/gameclient.h
Expand Up @@ -118,6 +118,7 @@ class CGameClient : public IGameClient

static void ConTeam(IConsole::IResult *pResult, void *pUserData);
static void ConKill(IConsole::IResult *pResult, void *pUserData);
static void ConColorFromRgb(IConsole::IResult *pResult, void *pUserData);

static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainSpecialDummyInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
Expand Down
8 changes: 4 additions & 4 deletions src/game/client/render.cpp
Expand Up @@ -146,7 +146,7 @@ void CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float Size, bo
void CRenderTools::QuadContainerAddSprite(int QuadContainerIndex, float X, float Y, float Width, float Height)
{
IGraphics::CQuadItem QuadItem(X, Y, Width, Height);
Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1);
Graphics()->QuadContainerAddQuads(QuadContainerIndex, &QuadItem, 1);
}

void CRenderTools::DrawRoundRectExt(float x, float y, float w, float h, float r, int Corners)
Expand Down Expand Up @@ -334,7 +334,7 @@ void CRenderTools::RenderTee(CAnimState *pAnim, CTeeRenderInfo *pInfo, int Emote

//Graphics()->TextureSet(data->images[IMAGE_CHAR_DEFAULT].id);
Graphics()->TextureSet(pInfo->m_Texture);

// first pass we draw the outline
// second pass we draw the filling
for(int p = 0; p < 2; p++)
Expand Down Expand Up @@ -453,8 +453,8 @@ void CRenderTools::MapscreenToWorld(float CenterX, float CenterY, float Parallax
{
float Width, Height;
CalcScreenParams(1150*1000, 1500, 1050, Aspect, &Width, &Height);
CenterX *= ParallaxX;
CenterY *= ParallaxY;
CenterX *= ParallaxX/100.0f;
CenterY *= ParallaxY/100.0f;
Width *= Zoom;
Height *= Zoom;
pPoints[0] = OffsetX+CenterX-Width/2;
Expand Down
4 changes: 2 additions & 2 deletions src/game/collision.cpp
Expand Up @@ -1121,8 +1121,8 @@ int CCollision::IsCheckpoint(int Index)
return -1;

int z = m_pTiles[Index].m_Index;
if(z >= 35 && z <= 59)
return z-35;
if(z >= TILE_CHECKPOINT_FIRST && z <= TILE_CHECKPOINT_LAST)
return z - TILE_CHECKPOINT_FIRST;
return -1;
}

Expand Down
71 changes: 60 additions & 11 deletions src/game/editor/editor.cpp
Expand Up @@ -170,8 +170,7 @@ void CLayerGroup::Mapping(float *pPoints)
{
m_pMap->m_pEditor->RenderTools()->MapscreenToWorld(
m_pMap->m_pEditor->m_WorldOffsetX, m_pMap->m_pEditor->m_WorldOffsetY,
m_ParallaxX/100.0f, m_ParallaxY/100.0f,
m_OffsetX, m_OffsetY,
m_ParallaxX, m_ParallaxY, m_OffsetX, m_OffsetY,
m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->m_WorldZoom, pPoints);

pPoints[0] += m_pMap->m_pEditor->m_EditorOffsetX;
Expand Down Expand Up @@ -2548,7 +2547,26 @@ void CEditor::DoMapEditor(CUIRect View)
// brush editing
if(UI()->HotItem() == s_pEditorID)
{
if(m_Brush.IsEmpty())
int Layer = NUM_LAYERS;
if(m_ShowPicker)
{
CLayer *pLayer = GetSelectedLayer(0);
if(pLayer == m_Map.m_pGameLayer)
Layer = LAYER_GAME;
else if(pLayer == m_Map.m_pFrontLayer)
Layer = LAYER_FRONT;
else if(pLayer == m_Map.m_pSwitchLayer)
Layer = LAYER_SWITCH;
else if(pLayer == m_Map.m_pTeleLayer)
Layer = LAYER_TELE;
else if(pLayer == m_Map.m_pSpeedupLayer)
Layer = LAYER_SPEEDUP;
else if(pLayer == m_Map.m_pTuneLayer)
Layer = LAYER_TUNE;
}
if(m_ShowPicker && Layer != NUM_LAYERS)
m_pTooltip = Explain((int)wx / 32 + (int)wy / 32 * 16, Layer);
else if(m_Brush.IsEmpty())
m_pTooltip = "Use left mouse button to drag and create a brush. Hold shift to select multiple quads.";
else
m_pTooltip = "Use left mouse button to paint with the brush. Right button clears the brush.";
Expand Down Expand Up @@ -2874,7 +2892,7 @@ void CEditor::DoMapEditor(CUIRect View)

RenderTools()->MapscreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
1.0f, 1.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);

if(i == 0)
{
Expand Down Expand Up @@ -2916,7 +2934,7 @@ void CEditor::DoMapEditor(CUIRect View)

RenderTools()->MapscreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
1.0f, 1.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);

CUIRect r;
r.x = aPoints[0];
Expand Down Expand Up @@ -4598,14 +4616,23 @@ void CEditor::RenderStatusbar(CUIRect View)

if(m_pTooltip)
{
char aBuf[512];
if(ms_pUiGotContext && ms_pUiGotContext == UI()->HotItem())
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "%s Right click for context menu.", m_pTooltip);
UI()->DoLabel(&View, aBuf, 10.0f, -1, -1);
}
else
UI()->DoLabel(&View, m_pTooltip, 10.0f, -1, -1);
str_copy(aBuf, m_pTooltip, sizeof(aBuf));

float FontSize = 10.0f;

while(TextRender()->TextWidth(0, FontSize, m_pTooltip, -1) > View.w)
{
if(FontSize > 6.0f)
FontSize--;
else
str_format(aBuf, sizeof(aBuf), "%.*s...", str_length(aBuf) - 4, aBuf);
}

UI()->DoLabel(&View, m_pTooltip, FontSize, -1, View.w);
}
}

Expand Down Expand Up @@ -5679,6 +5706,28 @@ void CEditor::Render()
StatusBar.Margin(2.0f, &StatusBar);
}

// show mentions
if(m_GuiActive && m_Mentions)
{
char aBuf[16];
if(m_Mentions == 1)
{
str_copy(aBuf, Localize("1 new mention"), sizeof(aBuf));
}
else if(m_Mentions <= 9)
{
str_format(aBuf, sizeof(aBuf), Localize("%d new mentions"), m_Mentions);
}
else
{
str_copy(aBuf, Localize("9+ new mentions"), sizeof(aBuf));
}

TextRender()->TextColor(1.0f, 0.0f, 0.0f, 1.0f);
TextRender()->Text(0, 5.0f, 27.0f, 10.0f, aBuf, -1);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}

// do the toolbar
if(m_Mode == MODE_LAYERS)
DoToolbar(ToolBar);
Expand Down Expand Up @@ -5870,7 +5919,7 @@ void CEditor::ZoomMouseTarget(float ZoomFactor)
float aPoints[4];
RenderTools()->MapscreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
1.0f, 1.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints);
100.0f, 100.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints);

float WorldWidth = aPoints[2]-aPoints[0];
float WorldHeight = aPoints[3]-aPoints[1];
Expand Down
7 changes: 7 additions & 0 deletions src/game/editor/editor.h
Expand Up @@ -745,11 +745,15 @@ class CEditor : public IEditor
m_PreventUnusedTilesWasWarned = false;
m_AllowPlaceUnusedTiles = 0;
m_BrushDrawDestructive = true;

m_Mentions = 0;
}

virtual void Init();
virtual void UpdateAndRender();
virtual bool HasUnsavedData() { return m_Map.m_Modified; }
virtual void UpdateMentions() { m_Mentions++; }
virtual void ResetMentions() { m_Mentions = 0; }

int64 m_LastUndoUpdateTime;
bool m_UndoRunning;
Expand Down Expand Up @@ -829,6 +833,8 @@ class CEditor : public IEditor
int m_AllowPlaceUnusedTiles;
bool m_BrushDrawDestructive;

int m_Mentions;

enum
{
FILETYPE_MAP,
Expand Down Expand Up @@ -1039,6 +1045,7 @@ class CEditor : public IEditor

void AddFileDialogEntry(int Index, CUIRect *pView);
void SortImages();
const char *Explain(int Tile, int Layer);

int GetLineDistance();
void ZoomMouseTarget(float ZoomFactor);
Expand Down
463 changes: 463 additions & 0 deletions src/game/editor/explanations.cpp

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions src/game/mapitems.cpp
Expand Up @@ -4,8 +4,7 @@ bool IsValidGameTile(int Index)
{
return (
Index == TILE_AIR
|| (Index >= TILE_SOLID && Index <= TILE_NOLASER)
|| Index == TILE_THROUGH
|| (Index >= TILE_SOLID && Index <= TILE_THROUGH)
|| Index == TILE_FREEZE
|| (Index >= TILE_UNFREEZE && Index <= TILE_DUNFREEZE)
|| (Index >= TILE_WALLJUMP && Index <= TILE_SOLO_END)
Expand Down
22 changes: 16 additions & 6 deletions src/game/mapitems.h
Expand Up @@ -56,13 +56,13 @@ enum
ENTITY_POWERUP_NINJA,
ENTITY_WEAPON_RIFLE,
//DDRace - Main Lasers
ENTITY_LASER_FAST_CW,
ENTITY_LASER_NORMAL_CW,
ENTITY_LASER_SLOW_CW,
ENTITY_LASER_STOP,
ENTITY_LASER_SLOW_CCW,
ENTITY_LASER_NORMAL_CCW,
ENTITY_LASER_FAST_CCW,
ENTITY_LASER_NORMAL_CCW,
ENTITY_LASER_SLOW_CCW,
ENTITY_LASER_STOP,
ENTITY_LASER_SLOW_CW,
ENTITY_LASER_NORMAL_CW,
ENTITY_LASER_FAST_CW,
//DDRace - Laser Modifiers
ENTITY_LASER_SHORT,
ENTITY_LASER_MEDIUM,
Expand Down Expand Up @@ -131,6 +131,8 @@ enum
TILE_REFILL_JUMPS = 32,
TILE_BEGIN,
TILE_END,
TILE_CHECKPOINT_FIRST = 35,
TILE_CHECKPOINT_LAST = 59,
TILE_STOP = 60,
TILE_STOPS,
TILE_STOPA,
Expand Down Expand Up @@ -164,6 +166,14 @@ enum
TILE_TELE_GRENADE_DISABLE = 113,
TILE_TELE_LASER_ENABLE = 128,
TILE_TELE_LASER_DISABLE = 129,
TILE_CREDITS_1 = 140,
TILE_CREDITS_2 = 141,
TILE_CREDITS_3 = 142,
TILE_CREDITS_4 = 143,
TILE_CREDITS_5 = 156,
TILE_CREDITS_6 = 157,
TILE_CREDITS_7 = 158,
TILE_CREDITS_8 = 159,
TILE_ENTITIES_OFF_1 = 190,
TILE_ENTITIES_OFF_2,
//End of higher tiles
Expand Down
35 changes: 18 additions & 17 deletions src/game/server/entities/dragger.cpp
Expand Up @@ -9,7 +9,7 @@
#include "dragger.h"

CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
int CatchedTeam, int Layer, int Number) :
int CaughtTeam, int Layer, int Number) :
CEntity(pGameWorld, CGameWorld::ENTTYPE_LASER)
{
m_Layer = Layer;
Expand All @@ -18,7 +18,7 @@ CDragger::CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
m_Strength = Strength;
m_EvalTick = Server()->Tick();
m_NW = NW;
m_CatchedTeam = CatchedTeam;
m_CaughtTeam = CaughtTeam;
GameWorld()->InsertEntity(this);

for (int i = 0; i < MAX_CLIENTS; i++)
Expand All @@ -31,7 +31,7 @@ void CDragger::Move()
{
if (m_Target && (!m_Target->IsAlive() || (m_Target->IsAlive()
&& (m_Target->m_Super || m_Target->IsPaused()
|| (m_Layer == LAYER_SWITCH
|| (m_Layer == LAYER_SWITCH && m_Number
&& !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[m_Target->Team()])))))
m_Target = 0;

Expand All @@ -48,12 +48,12 @@ void CDragger::Move()
for (int i = 0; i < Num; i++)
{
Temp = m_SoloEnts[i];
if (Temp->Team() != m_CatchedTeam)
if (Temp->Team() != m_CaughtTeam)
{
m_SoloEnts[i] = 0;
continue;
}
if (m_Layer == LAYER_SWITCH
if (m_Layer == LAYER_SWITCH && m_Number
&& !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Temp->Team()])
{
m_SoloEnts[i] = 0;
Expand Down Expand Up @@ -259,7 +259,7 @@ void CDragger::Reset()
void CDragger::Tick()
{
if (((CGameControllerDDRace*) GameServer()->m_pController)->m_Teams.GetTeamState(
m_CatchedTeam) == CGameTeams::TEAMSTATE_EMPTY)
m_CaughtTeam) == CGameTeams::TEAMSTATE_EMPTY)
return;
if (Server()->Tick() % int(Server()->TickSpeed() * 0.15f) == 0)
{
Expand All @@ -282,7 +282,7 @@ void CDragger::Tick()
void CDragger::Snap(int SnappingClient)
{
if (((CGameControllerDDRace*) GameServer()->m_pController)->m_Teams.GetTeamState(
m_CatchedTeam) == CGameTeams::TEAMSTATE_EMPTY)
m_CaughtTeam) == CGameTeams::TEAMSTATE_EMPTY)
return;

CCharacter *Target = m_Target;
Expand Down Expand Up @@ -326,19 +326,19 @@ void CDragger::Snap(int SnappingClient)

int Tick = (Server()->Tick() % Server()->TickSpeed()) % 11;
if (Char && Char->IsAlive()
&& (m_Layer == LAYER_SWITCH
&& (m_Layer == LAYER_SWITCH && m_Number
&& !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]
&& (!Tick)))
continue;
if (Char && Char->IsAlive())
{
if (Char->Team() != m_CatchedTeam)
if (Char->Team() != m_CaughtTeam)
continue;
}
else
{
// send to spectators only active draggers and some inactive from team 0
if (!((Target && Target->IsAlive()) || m_CatchedTeam == 0))
if (!((Target && Target->IsAlive()) || m_CaughtTeam == 0))
continue;
}

Expand Down Expand Up @@ -392,13 +392,14 @@ CDraggerTeam::CDraggerTeam(CGameWorld *pGameWorld, vec2 Pos, float Strength,
{
for (int i = 0; i < MAX_CLIENTS; ++i)
{
m_Draggers[i] = new CDragger(pGameWorld, Pos, Strength, NW, i, Layer,
Number);
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];
//}
//}
CDraggerTeam::~CDraggerTeam()
{
for (int i = 0; i < MAX_CLIENTS; ++i)
{
delete m_Draggers[i];
}
}
6 changes: 3 additions & 3 deletions src/game/server/entities/dragger.h
Expand Up @@ -14,14 +14,14 @@ class CDragger: public CEntity
void Drag();
CCharacter * m_Target;
bool m_NW;
int m_CatchedTeam;
int m_CaughtTeam;

CCharacter * m_SoloEnts[MAX_CLIENTS];
int m_SoloIDs[MAX_CLIENTS];
public:

CDragger(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW,
int CatchedTeam, int Layer = 0, int Number = 0);
int CaughtTeam, int Layer = 0, int Number = 0);

virtual void Reset();
virtual void Tick();
Expand All @@ -36,7 +36,7 @@ class CDraggerTeam

CDraggerTeam(CGameWorld *pGameWorld, vec2 Pos, float Strength, bool NW =
false, int Layer = 0, int Number = 0);
//~CDraggerTeam();
~CDraggerTeam();
};

#endif // GAME_SERVER_ENTITIES_DRAGGER_H
2 changes: 1 addition & 1 deletion src/game/server/gamecontroller.cpp
Expand Up @@ -270,7 +270,7 @@ bool IGameController::OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Nu
Type = POWERUP_NINJA;
SubType = WEAPON_NINJA;
}
else if(Index >= ENTITY_LASER_FAST_CW && Index <= ENTITY_LASER_FAST_CCW)
else if(Index >= ENTITY_LASER_FAST_CCW && Index <= ENTITY_LASER_FAST_CW)
{
int sides2[8];
sides2[0]=GameServer()->Collision()->Entity(x, y + 2, Layer);
Expand Down
5 changes: 4 additions & 1 deletion src/game/server/player.cpp
Expand Up @@ -429,7 +429,10 @@ void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput)

void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput)
{
if (AfkTimer(NewInput->m_TargetX, NewInput->m_TargetY))
if(NewInput->m_PlayerFlags)
Server()->SetClientFlags(m_ClientID, NewInput->m_PlayerFlags);

if(AfkTimer(NewInput->m_TargetX, NewInput->m_TargetY))
return; // we must return if kicked, as player struct is already deleted
AfkVoteTimer(NewInput);

Expand Down
34 changes: 22 additions & 12 deletions src/game/server/save.cpp
Expand Up @@ -8,12 +8,10 @@

CSaveTee::CSaveTee()
{
;
}

CSaveTee::~CSaveTee()
{
;
}

void CSaveTee::save(CCharacter *pChr)
Expand Down Expand Up @@ -71,6 +69,10 @@ void CSaveTee::save(CCharacter *pChr)

m_NotEligibleForFinish = pChr->m_pPlayer->m_NotEligibleForFinish;

m_HasTeleGun = pChr->m_HasTeleGun;
m_HasTeleGrenade = pChr->m_HasTeleGrenade;
m_HasTeleLaser = pChr->m_HasTeleLaser;

// Core
m_CorePos = pChr->m_Core.m_Pos;
m_Vel = pChr->m_Core.m_Vel;
Expand All @@ -87,6 +89,8 @@ void CSaveTee::save(CCharacter *pChr)
m_HookTick = pChr->m_Core.m_HookTick;

m_HookState = pChr->m_Core.m_HookState;

FormatUuid(pChr->GameServer()->GameUuid(), aGameUuid, sizeof(aGameUuid));
}

void CSaveTee::load(CCharacter *pChr, int Team)
Expand Down Expand Up @@ -144,6 +148,10 @@ void CSaveTee::load(CCharacter *pChr, int Team)

pChr->m_pPlayer->m_NotEligibleForFinish = pChr->m_pPlayer->m_NotEligibleForFinish || m_NotEligibleForFinish;

pChr->m_HasTeleGun = m_HasTeleGun;
pChr->m_HasTeleLaser = m_HasTeleLaser;
pChr->m_HasTeleGrenade = m_HasTeleGrenade;

// Core
pChr->m_Core.m_Pos = m_CorePos;
pChr->m_Core.m_Vel = m_Vel;
Expand Down Expand Up @@ -174,25 +182,27 @@ void CSaveTee::load(CCharacter *pChr, int Team)

char* CSaveTee::GetString()
{
str_format(m_String, sizeof(m_String), "%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%d", m_name, m_Alive, m_Paused, m_NeededFaketuning, m_TeeFinished, m_IsSolo, m_aWeapons[0].m_AmmoRegenStart, m_aWeapons[0].m_Ammo, m_aWeapons[0].m_Ammocost, m_aWeapons[0].m_Got, m_aWeapons[1].m_AmmoRegenStart, m_aWeapons[1].m_Ammo, m_aWeapons[1].m_Ammocost, m_aWeapons[1].m_Got, m_aWeapons[2].m_AmmoRegenStart, m_aWeapons[2].m_Ammo, m_aWeapons[2].m_Ammocost, m_aWeapons[2].m_Got, m_aWeapons[3].m_AmmoRegenStart, m_aWeapons[3].m_Ammo, m_aWeapons[3].m_Ammocost, m_aWeapons[3].m_Got, m_aWeapons[4].m_AmmoRegenStart, m_aWeapons[4].m_Ammo, m_aWeapons[4].m_Ammocost, m_aWeapons[4].m_Got, m_aWeapons[5].m_AmmoRegenStart, m_aWeapons[5].m_Ammo, m_aWeapons[5].m_Ammocost, m_aWeapons[5].m_Got, m_LastWeapon, m_QueuedWeapon, m_SuperJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeTick, m_DeepFreeze, m_EndlessHook, m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, m_Time, (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y, m_TeleCheckpoint, m_LastPenalty, (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y, m_ActiveWeapon, m_Jumped, m_JumpedTotal, m_Jumps, (int)m_HookPos.x, (int)m_HookPos.y, m_HookDir.x, m_HookDir.y, (int)m_HookTeleBase.x, (int)m_HookTeleBase.y, m_HookTick, m_HookState, m_CpTime, m_CpActive, m_CpLastBroadcast, m_CpCurrent[0], m_CpCurrent[1], m_CpCurrent[2], m_CpCurrent[3], m_CpCurrent[4], m_CpCurrent[5], m_CpCurrent[6], m_CpCurrent[7], m_CpCurrent[8], m_CpCurrent[9], m_CpCurrent[10], m_CpCurrent[11], m_CpCurrent[12], m_CpCurrent[13], m_CpCurrent[14], m_CpCurrent[15], m_CpCurrent[16], m_CpCurrent[17], m_CpCurrent[18], m_CpCurrent[19], m_CpCurrent[20], m_CpCurrent[21], m_CpCurrent[22], m_CpCurrent[23], m_CpCurrent[24], m_NotEligibleForFinish);
str_format(m_String, sizeof(m_String), "%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%d\t%d\t%d\t%d\t%s", m_name, m_Alive, m_Paused, m_NeededFaketuning, m_TeeFinished, m_IsSolo, m_aWeapons[0].m_AmmoRegenStart, m_aWeapons[0].m_Ammo, m_aWeapons[0].m_Ammocost, m_aWeapons[0].m_Got, m_aWeapons[1].m_AmmoRegenStart, m_aWeapons[1].m_Ammo, m_aWeapons[1].m_Ammocost, m_aWeapons[1].m_Got, m_aWeapons[2].m_AmmoRegenStart, m_aWeapons[2].m_Ammo, m_aWeapons[2].m_Ammocost, m_aWeapons[2].m_Got, m_aWeapons[3].m_AmmoRegenStart, m_aWeapons[3].m_Ammo, m_aWeapons[3].m_Ammocost, m_aWeapons[3].m_Got, m_aWeapons[4].m_AmmoRegenStart, m_aWeapons[4].m_Ammo, m_aWeapons[4].m_Ammocost, m_aWeapons[4].m_Got, m_aWeapons[5].m_AmmoRegenStart, m_aWeapons[5].m_Ammo, m_aWeapons[5].m_Ammocost, m_aWeapons[5].m_Got, m_LastWeapon, m_QueuedWeapon, m_SuperJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeTick, m_DeepFreeze, m_EndlessHook, m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, m_Time, (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y, m_TeleCheckpoint, m_LastPenalty, (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y, m_ActiveWeapon, m_Jumped, m_JumpedTotal, m_Jumps, (int)m_HookPos.x, (int)m_HookPos.y, m_HookDir.x, m_HookDir.y, (int)m_HookTeleBase.x, (int)m_HookTeleBase.y, m_HookTick, m_HookState, m_CpTime, m_CpActive, m_CpLastBroadcast, m_CpCurrent[0], m_CpCurrent[1], m_CpCurrent[2], m_CpCurrent[3], m_CpCurrent[4], m_CpCurrent[5], m_CpCurrent[6], m_CpCurrent[7], m_CpCurrent[8], m_CpCurrent[9], m_CpCurrent[10], m_CpCurrent[11], m_CpCurrent[12], m_CpCurrent[13], m_CpCurrent[14], m_CpCurrent[15], m_CpCurrent[16], m_CpCurrent[17], m_CpCurrent[18], m_CpCurrent[19], m_CpCurrent[20], m_CpCurrent[21], m_CpCurrent[22], m_CpCurrent[23], m_CpCurrent[24], m_NotEligibleForFinish, m_HasTeleGun, m_HasTeleLaser, m_HasTeleGrenade, aGameUuid);
return m_String;
}

int CSaveTee::LoadString(char* String)
{
int Num;
Num = sscanf(String, "%[^\t]\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%d\t%d\t%f\t%f\t%f\t%f\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%d", m_name, &m_Alive, &m_Paused, &m_NeededFaketuning, &m_TeeFinished, &m_IsSolo, &m_aWeapons[0].m_AmmoRegenStart, &m_aWeapons[0].m_Ammo, &m_aWeapons[0].m_Ammocost, &m_aWeapons[0].m_Got, &m_aWeapons[1].m_AmmoRegenStart, &m_aWeapons[1].m_Ammo, &m_aWeapons[1].m_Ammocost, &m_aWeapons[1].m_Got, &m_aWeapons[2].m_AmmoRegenStart, &m_aWeapons[2].m_Ammo, &m_aWeapons[2].m_Ammocost, &m_aWeapons[2].m_Got, &m_aWeapons[3].m_AmmoRegenStart, &m_aWeapons[3].m_Ammo, &m_aWeapons[3].m_Ammocost, &m_aWeapons[3].m_Got, &m_aWeapons[4].m_AmmoRegenStart, &m_aWeapons[4].m_Ammo, &m_aWeapons[4].m_Ammocost, &m_aWeapons[4].m_Got, &m_aWeapons[5].m_AmmoRegenStart, &m_aWeapons[5].m_Ammo, &m_aWeapons[5].m_Ammocost, &m_aWeapons[5].m_Got, &m_LastWeapon, &m_QueuedWeapon, &m_SuperJump, &m_Jetpack, &m_NinjaJetpack, &m_FreezeTime, &m_FreezeTick, &m_DeepFreeze, &m_EndlessHook, &m_DDRaceState, &m_Hit, &m_Collision, &m_TuneZone, &m_TuneZoneOld, &m_Hook, &m_Time, &m_Pos.x, &m_Pos.y, &m_PrevPos.x, &m_PrevPos.y, &m_TeleCheckpoint, &m_LastPenalty, &m_CorePos.x, &m_CorePos.y, &m_Vel.x, &m_Vel.y, &m_ActiveWeapon, &m_Jumped, &m_JumpedTotal, &m_Jumps, &m_HookPos.x, &m_HookPos.y, &m_HookDir.x, &m_HookDir.y, &m_HookTeleBase.x, &m_HookTeleBase.y, &m_HookTick, &m_HookState, &m_CpTime, &m_CpActive, &m_CpLastBroadcast, &m_CpCurrent[0], &m_CpCurrent[1], &m_CpCurrent[2], &m_CpCurrent[3], &m_CpCurrent[4], &m_CpCurrent[5], &m_CpCurrent[6], &m_CpCurrent[7], &m_CpCurrent[8], &m_CpCurrent[9], &m_CpCurrent[10], &m_CpCurrent[11], &m_CpCurrent[12], &m_CpCurrent[13], &m_CpCurrent[14], &m_CpCurrent[15], &m_CpCurrent[16], &m_CpCurrent[17], &m_CpCurrent[18], &m_CpCurrent[19], &m_CpCurrent[20], &m_CpCurrent[21], &m_CpCurrent[22], &m_CpCurrent[23], &m_CpCurrent[24], &m_NotEligibleForFinish);
if(Num == 96) // Don't forget to update this when you save / load more / less.
Num = sscanf(String, "%[^\t]\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%d\t%d\t%f\t%f\t%f\t%f\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%d\t%d\t%d\t%d\t%d\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%dt%d\t%d\t%d\t%*s", m_name, &m_Alive, &m_Paused, &m_NeededFaketuning, &m_TeeFinished, &m_IsSolo, &m_aWeapons[0].m_AmmoRegenStart, &m_aWeapons[0].m_Ammo, &m_aWeapons[0].m_Ammocost, &m_aWeapons[0].m_Got, &m_aWeapons[1].m_AmmoRegenStart, &m_aWeapons[1].m_Ammo, &m_aWeapons[1].m_Ammocost, &m_aWeapons[1].m_Got, &m_aWeapons[2].m_AmmoRegenStart, &m_aWeapons[2].m_Ammo, &m_aWeapons[2].m_Ammocost, &m_aWeapons[2].m_Got, &m_aWeapons[3].m_AmmoRegenStart, &m_aWeapons[3].m_Ammo, &m_aWeapons[3].m_Ammocost, &m_aWeapons[3].m_Got, &m_aWeapons[4].m_AmmoRegenStart, &m_aWeapons[4].m_Ammo, &m_aWeapons[4].m_Ammocost, &m_aWeapons[4].m_Got, &m_aWeapons[5].m_AmmoRegenStart, &m_aWeapons[5].m_Ammo, &m_aWeapons[5].m_Ammocost, &m_aWeapons[5].m_Got, &m_LastWeapon, &m_QueuedWeapon, &m_SuperJump, &m_Jetpack, &m_NinjaJetpack, &m_FreezeTime, &m_FreezeTick, &m_DeepFreeze, &m_EndlessHook, &m_DDRaceState, &m_Hit, &m_Collision, &m_TuneZone, &m_TuneZoneOld, &m_Hook, &m_Time, &m_Pos.x, &m_Pos.y, &m_PrevPos.x, &m_PrevPos.y, &m_TeleCheckpoint, &m_LastPenalty, &m_CorePos.x, &m_CorePos.y, &m_Vel.x, &m_Vel.y, &m_ActiveWeapon, &m_Jumped, &m_JumpedTotal, &m_Jumps, &m_HookPos.x, &m_HookPos.y, &m_HookDir.x, &m_HookDir.y, &m_HookTeleBase.x, &m_HookTeleBase.y, &m_HookTick, &m_HookState, &m_CpTime, &m_CpActive, &m_CpLastBroadcast, &m_CpCurrent[0], &m_CpCurrent[1], &m_CpCurrent[2], &m_CpCurrent[3], &m_CpCurrent[4], &m_CpCurrent[5], &m_CpCurrent[6], &m_CpCurrent[7], &m_CpCurrent[8], &m_CpCurrent[9], &m_CpCurrent[10], &m_CpCurrent[11], &m_CpCurrent[12], &m_CpCurrent[13], &m_CpCurrent[14], &m_CpCurrent[15], &m_CpCurrent[16], &m_CpCurrent[17], &m_CpCurrent[18], &m_CpCurrent[19], &m_CpCurrent[20], &m_CpCurrent[21], &m_CpCurrent[22], &m_CpCurrent[23], &m_CpCurrent[24], &m_NotEligibleForFinish, &m_HasTeleGun, &m_HasTeleLaser, &m_HasTeleGrenade);
switch(Num) // Don't forget to update this when you save / load more / less.
{
case 96:
m_NotEligibleForFinish = false;
// fallthrough
case 97:
m_HasTeleGrenade = 0;
m_HasTeleLaser = 0;
m_HasTeleGun = 0;
// fallthrough
case 100:
return 0;
}
else if(Num == 97)
{
return 0;
}
else
{
default:
dbg_msg("load", "failed to load tee-string");
dbg_msg("load", "loaded %d vars", Num);
return Num+1; // never 0 here
Expand Down
6 changes: 6 additions & 0 deletions src/game/server/save.h
Expand Up @@ -68,6 +68,10 @@ class CSaveTee

int m_NotEligibleForFinish;

int m_HasTeleGun;
int m_HasTeleGrenade;
int m_HasTeleLaser;

// Core
vec2 m_CorePos;
vec2 m_Vel;
Expand All @@ -80,6 +84,8 @@ class CSaveTee
vec2 m_HookTeleBase;
int m_HookTick;
int m_HookState;

char aGameUuid[16];
};

class CSaveTeam
Expand Down
10 changes: 7 additions & 3 deletions src/game/server/score.h
Expand Up @@ -4,7 +4,11 @@
#include "entities/character.h"
#include "gamecontext.h"

#define NUM_CHECKPOINTS 25
enum
{
NUM_CHECKPOINTS = 25,
TIMESTAMP_STR_LENGTH = 20, // 2019-04-02 19:38:36
};

class CPlayerData
{
Expand Down Expand Up @@ -47,9 +51,9 @@ class IScore
virtual void MapVote(int ClientID, const char *pMapName) = 0;
virtual void CheckBirthday(int ClientID) = 0;
virtual void LoadScore(int ClientID) = 0;
virtual void SaveScore(int ClientID, float Time, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0;
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0;

virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time) = 0;
virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) = 0;

virtual void ShowTop5(IConsole::IResult *pResult, int ClientID, void *pUserData, int Debut=1) = 0;
virtual void ShowRank(int ClientID, const char *pName, bool Search=false) = 0;
Expand Down
4 changes: 2 additions & 2 deletions src/game/server/score/file_score.cpp
Expand Up @@ -221,12 +221,12 @@ void CFileScore::LoadScore(int ClientID)
}
}

void CFileScore::SaveTeamScore(int* ClientIDs, unsigned int Size, float Time)
void CFileScore::SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp)
{
dbg_msg("filescore", "saveteamscore not implemented for filescore");
}

void CFileScore::SaveScore(int ClientID, float Time,
void CFileScore::SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible)
{
CConsole* pCon = (CConsole*) GameServer()->Console();
Expand Down
4 changes: 2 additions & 2 deletions src/game/server/score/file_score.h
Expand Up @@ -66,9 +66,9 @@ class CFileScore: public IScore
virtual void LoadScore(int ClientID);
virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(int ClientID, const char* MapName);
virtual void SaveScore(int ClientID, float Time,
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time);
virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp);

virtual void ShowTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
Expand Down
27 changes: 10 additions & 17 deletions src/game/server/score/sql_score.cpp
Expand Up @@ -466,7 +466,7 @@ bool CSqlScore::MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData,
return false;
}

void CSqlScore::SaveScore(int ClientID, float Time, float CpTime[NUM_CHECKPOINTS], bool NotEligible)
void CSqlScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible)
{
CConsole* pCon = (CConsole*)GameServer()->Console();
if(pCon->m_Cheated)
Expand All @@ -475,6 +475,7 @@ void CSqlScore::SaveScore(int ClientID, float Time, float CpTime[NUM_CHECKPOINTS
Tmp->m_ClientID = ClientID;
Tmp->m_Name = Server()->ClientName(ClientID);
Tmp->m_Time = Time;
str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp));
Tmp->m_NotEligible = NotEligible;
for(int i = 0; i < NUM_CHECKPOINTS; i++)
Tmp->m_aCpCurrent[i] = CpTime[i];
Expand All @@ -497,11 +498,8 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat
{
dbg_msg("sql", "ERROR: Could not save Score, writing insert to a file now...");

char aTimestamp [20];
sqlstr::GetTimeStamp(aTimestamp, sizeof(aTimestamp));

char aBuf[768];
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s');%s", pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr(), pData->m_NotEligible ? " -- not eligible" : "");
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s');%s", pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr(), pData->m_NotEligible ? " -- not eligible" : "");
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
io_close(File);
Expand Down Expand Up @@ -553,7 +551,7 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat
}

// if no entry found... create a new one
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID) VALUES ('%s', '%s', CURRENT_TIMESTAMP(), '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s');", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr());
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_race(Map, Name, Timestamp, Time, Server, cp1, cp2, cp3, cp4, cp5, cp6, cp7, cp8, cp9, cp10, cp11, cp12, cp13, cp14, cp15, cp16, cp17, cp18, cp19, cp20, cp21, cp22, cp23, cp24, cp25, GameID) VALUES ('%s', '%s', '%s', '%.2f', '%s', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%.2f', '%s');", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_Name.ClrStr(), pData->m_aTimestamp, pData->m_Time, g_Config.m_SvSqlServerName, pData->m_aCpCurrent[0], pData->m_aCpCurrent[1], pData->m_aCpCurrent[2], pData->m_aCpCurrent[3], pData->m_aCpCurrent[4], pData->m_aCpCurrent[5], pData->m_aCpCurrent[6], pData->m_aCpCurrent[7], pData->m_aCpCurrent[8], pData->m_aCpCurrent[9], pData->m_aCpCurrent[10], pData->m_aCpCurrent[11], pData->m_aCpCurrent[12], pData->m_aCpCurrent[13], pData->m_aCpCurrent[14], pData->m_aCpCurrent[15], pData->m_aCpCurrent[16], pData->m_aCpCurrent[17], pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24], pData->m_GameUuid.ClrStr());
dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf);

Expand All @@ -568,7 +566,7 @@ bool CSqlScore::SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameDat
return false;
}

void CSqlScore::SaveTeamScore(int* aClientIDs, unsigned int Size, float Time)
void CSqlScore::SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp)
{
CConsole* pCon = (CConsole*)GameServer()->Console();
if(pCon->m_Cheated)
Expand All @@ -583,6 +581,7 @@ void CSqlScore::SaveTeamScore(int* aClientIDs, unsigned int Size, float Time)
}
Tmp->m_Size = Size;
Tmp->m_Time = Time;
str_copy(Tmp->m_aTimestamp, pTimestamp, sizeof(Tmp->m_aTimestamp));

thread_init_and_detach(ExecSqlFunc, new CSqlExecData(SaveTeamScoreThread, Tmp, false), "save team score");
}
Expand All @@ -606,13 +605,10 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam
io_write(File, pUUID, sizeof(pUUID) - 1);
io_write_newline(File);

char aTimestamp [20];
sqlstr::GetTimeStamp(aTimestamp, sizeof(aTimestamp));

char aBuf[2300];
for(unsigned int i = 0; i < pData->m_Size; i++)
{
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_teamrace(Map, Name, Timestamp, Time, ID, GameID) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s');%s", pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), aTimestamp, pData->m_Time, pData->m_GameUuid.ClrStr(), pData->m_NotEligible ? " -- not eligible" : "");
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_teamrace(Map, Name, Timestamp, Time, ID, GameID) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s');%s", pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid.ClrStr(), pData->m_NotEligible ? " -- not eligible" : "");
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
}
Expand Down Expand Up @@ -699,7 +695,7 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam

if (aUpdateID[0])
{
str_format(aBuf, sizeof(aBuf), "UPDATE %s_teamrace SET Time='%.2f', Timestamp=CURRENT_TIMESTAMP() WHERE ID = '%s';", pSqlServer->GetPrefix(), pData->m_Time, aUpdateID);
str_format(aBuf, sizeof(aBuf), "UPDATE %s_teamrace SET Time='%.2f', Timestamp='%s' WHERE ID = '%s';", pSqlServer->GetPrefix(), pData->m_Time, pData->m_aTimestamp, aUpdateID);
dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf);
}
Expand All @@ -710,7 +706,7 @@ bool CSqlScore::SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGam
for(unsigned int i = 0; i < pData->m_Size; i++)
{
// if no entry found... create a new one
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID) VALUES ('%s', '%s', CURRENT_TIMESTAMP(), '%.2f', @id, '%s');", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_Time, pData->m_GameUuid.ClrStr());
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_teamrace(Map, Name, Timestamp, Time, ID, GameID) VALUES ('%s', '%s', '%s', '%.2f', @id, '%s');", pSqlServer->GetPrefix(), pData->m_Map.ClrStr(), pData->m_aNames[i].ClrStr(), pData->m_aTimestamp, pData->m_Time, pData->m_GameUuid.ClrStr());
dbg_msg("sql", "%s", aBuf);
pSqlServer->executeSql(aBuf);
}
Expand Down Expand Up @@ -1454,11 +1450,8 @@ bool CSqlScore::SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData
{
dbg_msg("sql", "ERROR: Could not save Teamsave, writing insert to a file now...");

char aTimestamp [20];
sqlstr::GetTimeStamp(aTimestamp, sizeof(aTimestamp));

char aBuf[65536];
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server) VALUES ('%s', '%s', '%s', '%s', '%s');", TeamString, pData->m_Map.ClrStr(), pData->m_Code.ClrStr(), aTimestamp, pData->m_Server);
str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %%s_saves(Savegame, Map, Code, Timestamp, Server) VALUES ('%s', '%s', '%s', CURRENT_TIMESTAMP(), '%s');", TeamString, pData->m_Map.ClrStr(), pData->m_Code.ClrStr(), pData->m_Server);
io_write(File, aBuf, str_length(aBuf));
io_write_newline(File);
io_close(File);
Expand Down
13 changes: 7 additions & 6 deletions src/game/server/score/sql_score.h
Expand Up @@ -103,20 +103,21 @@ struct CSqlScoreData : CSqlData

bool m_NotEligible;
float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
float m_aCpCurrent[NUM_CHECKPOINTS];
int m_Num;
bool m_Search;
char m_aRequestingPlayer [MAX_NAME_LENGTH];
char m_aRequestingPlayer[MAX_NAME_LENGTH];
};

struct CSqlTeamScoreData : CSqlData
{
bool m_NotEligible;
float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
unsigned int m_Size;
int m_aClientIDs[MAX_CLIENTS];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames [MAX_CLIENTS];

float m_Time;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames[MAX_CLIENTS];
};

struct CSqlTeamSave : CSqlData
Expand Down Expand Up @@ -179,9 +180,9 @@ class CSqlScore: public IScore
virtual void LoadScore(int ClientID);
virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(int ClientID, const char* MapName);
virtual void SaveScore(int ClientID, float Time,
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time);
virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp);
virtual void ShowRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTimes(int ClientID, const char* pName, int Debut = 1);
Expand Down
159 changes: 81 additions & 78 deletions src/game/server/teams.cpp
Expand Up @@ -27,102 +27,100 @@ void CGameTeams::OnCharacterStart(int ClientID)
{
int Tick = Server()->Tick();
CCharacter* pStartingChar = Character(ClientID);
if (!pStartingChar)
if(!pStartingChar)
return;
if (m_Core.Team(ClientID) != TEAM_FLOCK && pStartingChar->m_DDRaceState == DDRACE_FINISHED)
if(m_Core.Team(ClientID) != TEAM_FLOCK && pStartingChar->m_DDRaceState == DDRACE_FINISHED)
return;
if (m_Core.Team(ClientID) == TEAM_FLOCK
if(m_Core.Team(ClientID) == TEAM_FLOCK
|| m_Core.Team(ClientID) == TEAM_SUPER)
{
pStartingChar->m_DDRaceState = DDRACE_STARTED;
pStartingChar->m_StartTime = Tick;
return;
}
else
bool Waiting = false;
for(int i = 0; i < MAX_CLIENTS; ++i)
{
bool Waiting = false;
for (int i = 0; i < MAX_CLIENTS; ++i)
if(m_Core.Team(ClientID) != m_Core.Team(i))
continue;
CPlayer* pPlayer = GetPlayer(i);
if(!pPlayer || !pPlayer->IsPlaying())
continue;
if(GetDDRaceState(pPlayer) != DDRACE_FINISHED)
continue;

Waiting = true;
pStartingChar->m_DDRaceState = DDRACE_NONE;

if(m_LastChat[ClientID] + Server()->TickSpeed()
+ g_Config.m_SvChatDelay < Tick)
{
if (m_Core.Team(ClientID) == m_Core.Team(i))
{
CPlayer* pPlayer = GetPlayer(i);
if (pPlayer && pPlayer->IsPlaying()
&& GetDDRaceState(pPlayer) == DDRACE_FINISHED)
{
Waiting = true;
pStartingChar->m_DDRaceState = DDRACE_NONE;

if (m_LastChat[ClientID] + Server()->TickSpeed()
+ g_Config.m_SvChatDelay < Tick)
{
char aBuf[128];
str_format(
aBuf,
sizeof(aBuf),
"%s has finished and didn't go through start yet, wait for him or join another team.",
Server()->ClientName(i));
GameServer()->SendChatTarget(ClientID, aBuf);
m_LastChat[ClientID] = Tick;
}
if (m_LastChat[i] + Server()->TickSpeed()
+ g_Config.m_SvChatDelay < Tick)
{
char aBuf[128];
str_format(
aBuf,
sizeof(aBuf),
"%s wants to start a new round, kill or walk to start.",
Server()->ClientName(ClientID));
GameServer()->SendChatTarget(i, aBuf);
m_LastChat[i] = Tick;
}
}
}
char aBuf[128];
str_format(
aBuf,
sizeof(aBuf),
"%s has finished and didn't go through start yet, wait for him or join another team.",
Server()->ClientName(i));
GameServer()->SendChatTarget(ClientID, aBuf);
m_LastChat[ClientID] = Tick;
}

if (m_TeamState[m_Core.Team(ClientID)] < TEAMSTATE_STARTED && !Waiting)
if(m_LastChat[i] + Server()->TickSpeed()
+ g_Config.m_SvChatDelay < Tick)
{
ChangeTeamState(m_Core.Team(ClientID), TEAMSTATE_STARTED);

char aBuf[512];
char aBuf[128];
str_format(
aBuf,
sizeof(aBuf),
"Team %d started with these %d players: ",
m_Core.Team(ClientID),
Count(m_Core.Team(ClientID)));
"%s wants to start a new round, kill or walk to start.",
Server()->ClientName(ClientID));
GameServer()->SendChatTarget(i, aBuf);
m_LastChat[i] = Tick;
}
}

bool First = true;
if(m_TeamState[m_Core.Team(ClientID)] < TEAMSTATE_STARTED && !Waiting)
{
ChangeTeamState(m_Core.Team(ClientID), TEAMSTATE_STARTED);

for (int i = 0; i < MAX_CLIENTS; ++i)
char aBuf[512];
str_format(
aBuf,
sizeof(aBuf),
"Team %d started with these %d players: ",
m_Core.Team(ClientID),
Count(m_Core.Team(ClientID)));

bool First = true;

for(int i = 0; i < MAX_CLIENTS; ++i)
{
if(m_Core.Team(ClientID) == m_Core.Team(i))
{
if (m_Core.Team(ClientID) == m_Core.Team(i))
CPlayer* pPlayer = GetPlayer(i);
// TODO: THE PROBLEM IS THAT THERE IS NO CHARACTER SO START TIME CAN'T BE SET!
if(pPlayer && (pPlayer->IsPlaying() || TeamLocked(m_Core.Team(ClientID))))
{
CPlayer* pPlayer = GetPlayer(i);
// TODO: THE PROBLEM IS THAT THERE IS NO CHARACTER SO START TIME CAN'T BE SET!
if (pPlayer && (pPlayer->IsPlaying() || TeamLocked(m_Core.Team(ClientID))))
{
SetDDRaceState(pPlayer, DDRACE_STARTED);
SetStartTime(pPlayer, Tick);
SetDDRaceState(pPlayer, DDRACE_STARTED);
SetStartTime(pPlayer, Tick);

if (First)
First = false;
else
str_append(aBuf, ", ", sizeof(aBuf));
if(First)
First = false;
else
str_append(aBuf, ", ", sizeof(aBuf));

str_append(aBuf, GameServer()->Server()->ClientName(i), sizeof(aBuf));
}
str_append(aBuf, GameServer()->Server()->ClientName(i), sizeof(aBuf));
}
}
}

if (g_Config.m_SvTeam < 3 && g_Config.m_SvTeamMaxSize != 2 && g_Config.m_SvPauseable)
if(g_Config.m_SvTeam < 3 && g_Config.m_SvTeamMaxSize != 2 && g_Config.m_SvPauseable)
{
for(int i = 0; i < MAX_CLIENTS; ++i)
{
for (int i = 0; i < MAX_CLIENTS; ++i)
CPlayer* pPlayer = GetPlayer(i);
if(m_Core.Team(ClientID) == m_Core.Team(i) && pPlayer && (pPlayer->IsPlaying() || TeamLocked(m_Core.Team(ClientID))))
{
CPlayer* pPlayer = GetPlayer(i);
if (m_Core.Team(ClientID) == m_Core.Team(i) && pPlayer && (pPlayer->IsPlaying() || TeamLocked(m_Core.Team(ClientID))))
{
GameServer()->SendChatTarget(i, aBuf);
}
GameServer()->SendChatTarget(i, aBuf);
}
}
}
Expand All @@ -141,7 +139,10 @@ void CGameTeams::OnCharacterFinish(int ClientID)
/ ((float)Server()->TickSpeed());
if (Time < 0.000001f)
return;
OnFinish(pPlayer, Time);
char aTimestamp[TIMESTAMP_STR_LENGTH];
str_timestamp_format(aTimestamp, sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58

OnFinish(pPlayer, Time, aTimestamp);
}
}
else
Expand Down Expand Up @@ -179,12 +180,14 @@ void CGameTeams::CheckTeamFinished(int Team)
/ ((float)Server()->TickSpeed());
if (Time < 0.000001f)
return;
char aTimestamp[TIMESTAMP_STR_LENGTH];
str_timestamp_format(aTimestamp, sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58

for (unsigned int i = 0; i < PlayersCount; ++i)
OnFinish(TeamPlayers[i], Time);
OnFinish(TeamPlayers[i], Time, aTimestamp);
ChangeTeamState(Team, TEAMSTATE_FINISHED); //TODO: Make it better
//ChangeTeamState(Team, TEAMSTATE_OPEN);
OnTeamFinish(TeamPlayers, PlayersCount, Time);
OnTeamFinish(TeamPlayers, PlayersCount, Time, aTimestamp);
}
}
}
Expand Down Expand Up @@ -455,7 +458,7 @@ float *CGameTeams::GetCpCurrent(CPlayer* Player)
return NULL;
}

void CGameTeams::OnTeamFinish(CPlayer** Players, unsigned int Size, float Time)
void CGameTeams::OnTeamFinish(CPlayer** Players, unsigned int Size, float Time, const char *pTimestamp)
{
bool CallSaveScore = false;

Expand All @@ -480,10 +483,10 @@ void CGameTeams::OnTeamFinish(CPlayer** Players, unsigned int Size, float Time)
}

if (CallSaveScore && Size >= 2)
GameServer()->Score()->SaveTeamScore(PlayerCIDs, Size, Time);
GameServer()->Score()->SaveTeamScore(PlayerCIDs, Size, Time, pTimestamp);
}

void CGameTeams::OnFinish(CPlayer* Player, float Time)
void CGameTeams::OnFinish(CPlayer* Player, float Time, const char *pTimestamp)
{
if (!Player || !Player->IsPlaying())
return;
Expand Down Expand Up @@ -558,7 +561,7 @@ void CGameTeams::OnFinish(CPlayer* Player, float Time)

if (CallSaveScore)
if (g_Config.m_SvNamelessScore || !str_startswith(Server()->ClientName(Player->GetCID()), "nameless tee"))
GameServer()->Score()->SaveScore(Player->GetCID(), Time,
GameServer()->Score()->SaveScore(Player->GetCID(), Time, pTimestamp,
GetCpCurrent(Player), Player->m_NotEligibleForFinish);

bool NeedToSendNewRecord = false;
Expand Down
4 changes: 2 additions & 2 deletions src/game/server/teams.h
Expand Up @@ -18,8 +18,8 @@ class CGameTeams

void CheckTeamFinished(int ClientID);
bool TeamFinished(int Team);
void OnTeamFinish(CPlayer** Players, unsigned int Size, float Time);
void OnFinish(CPlayer* Player, float Time);
void OnTeamFinish(CPlayer** Players, unsigned int Size, float Time, const char *pTimestamp);
void OnFinish(CPlayer* Player, float Time, const char *pTimestamp);

public:
enum
Expand Down
6 changes: 5 additions & 1 deletion src/game/variables.h
Expand Up @@ -20,6 +20,8 @@ MACRO_CONFIG_INT(ClNameplatesClanSize, cl_nameplates_clan_size, 30, 0, 100, CFGF
MACRO_CONFIG_INT(ClNameplatesOwn, cl_nameplates_own, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Show own name plate (useful for demo recording)")
MACRO_CONFIG_INT(ClTextEntities, cl_text_entities, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Render textual entity data")
MACRO_CONFIG_INT(ClAuthedPlayerColor, cl_authed_player_color, 5898183, 0, 0xFFFFFF, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Color of name of authenticated player in scoreboard")
MACRO_CONFIG_INT(ClSameClanColor, cl_same_clan_color, 5898183, 0, 0xFFFFFF, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Clan color of players with the same clan as you in scoreboard.")
MACRO_CONFIG_INT(ClEnablePingColor, cl_enable_ping_color, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Whether ping is colored in scoreboard.")
#if defined(__ANDROID__)
MACRO_CONFIG_INT(ClAutoswitchWeapons, cl_autoswitch_weapons, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Auto switch weapon on pickup")
MACRO_CONFIG_INT(ClAutoswitchWeaponsOutOfAmmo, cl_autoswitch_weapons_out_of_ammo, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Auto switch weapon when out of ammo")
Expand Down Expand Up @@ -53,9 +55,11 @@ MACRO_CONFIG_INT(ClWarningTeambalance, cl_warning_teambalance, 1, 0, 1, CFGFLAG_
MACRO_CONFIG_INT(ClMouseDeadzone, cl_mouse_deadzone, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SAVE, "")
MACRO_CONFIG_INT(ClMouseFollowfactor, cl_mouse_followfactor, 0, 0, 200, CFGFLAG_CLIENT|CFGFLAG_SAVE, "")
MACRO_CONFIG_INT(ClMouseMaxDistance, cl_mouse_max_distance, 400, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SAVE, "")
MACRO_CONFIG_INT(ClMouseMinDistance, cl_mouse_min_distance, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SAVE, "")

MACRO_CONFIG_INT(ClDyncam, cl_dyncam, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Enable dyncam")
MACRO_CONFIG_INT(ClDyncamMaxDistance, cl_dyncam_max_distance, 1000, 0, 2000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Maximal dynamic camera distance")
MACRO_CONFIG_INT(ClDyncamMaxDistance, cl_dyncam_max_distance, 1000, 0, 2000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Maximum dynamic camera distance")
MACRO_CONFIG_INT(ClDyncamMinDistance, cl_dyncam_min_distance, 0, 0, 2000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Minimum dynamic camera distance")
MACRO_CONFIG_INT(ClDyncamMousesens, cl_dyncam_mousesens, 0, 0, 100000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Mouse sens used when dyncam is toggled on")
MACRO_CONFIG_INT(ClDyncamDeadzone, cl_dyncam_deadzone, 300, 1, 1300, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Dynamic camera dead zone")
MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Dynamic camera follow factor")
Expand Down
6 changes: 3 additions & 3 deletions src/game/version.h
Expand Up @@ -2,9 +2,9 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_VERSION_H
#define GAME_VERSION_H
#define GAME_VERSION "0.6.4, 12.0.1"
#define GAME_VERSION "0.6.4, 12.1"
#define GAME_NETVERSION "0.6 626fce9a778df4d4"
#define GAME_RELEASE_VERSION "12.0.1"
#define CLIENT_VERSIONNR 12001
#define GAME_RELEASE_VERSION "12.1"
#define CLIENT_VERSIONNR 12010
extern const char *GIT_SHORTREV_HASH;
#endif
53 changes: 53 additions & 0 deletions src/test/hash.cpp
Expand Up @@ -58,3 +58,56 @@ TEST(Hash, Sha256FromStr)
EXPECT_TRUE(sha256_from_str(&Sha256, "012345678901234567890123456789012345678901234567890123456789012x"));
EXPECT_TRUE(sha256_from_str(&Sha256, "x123456789012345678901234567890123456789012345678901234567890123"));
}

static void Expect2(MD5_DIGEST Actual, const char *pWanted)
{
char aActual[MD5_MAXSTRSIZE];
md5_str(Actual, aActual, sizeof(aActual));
EXPECT_STREQ(aActual, pWanted);
}

TEST(Hash, Md5)
{
// https://en.wikipedia.org/w/index.php?title=MD5&oldid=889664074#MD5_hashes
Expect2(md5("", 0), "d41d8cd98f00b204e9800998ecf8427e");
MD5_CTX ctxt;

md5_init(&ctxt);
Expect2(md5_finish(&ctxt), "d41d8cd98f00b204e9800998ecf8427e");

char QUICK_BROWN_FOX[] = "The quick brown fox jumps over the lazy dog.";
Expect2(md5(QUICK_BROWN_FOX, str_length(QUICK_BROWN_FOX)), "e4d909c290d0fb1ca068ffaddf22cbd0");

md5_init(&ctxt);
md5_update(&ctxt, "The ", 4);
md5_update(&ctxt, "quick ", 6);
md5_update(&ctxt, "brown ", 6);
md5_update(&ctxt, "fox ", 4);
md5_update(&ctxt, "jumps ", 6);
md5_update(&ctxt, "over ", 5);
md5_update(&ctxt, "the ", 4);
md5_update(&ctxt, "lazy ", 5);
md5_update(&ctxt, "dog.", 4);
Expect2(md5_finish(&ctxt), "e4d909c290d0fb1ca068ffaddf22cbd0");
}

TEST(Hash, Md5Eq)
{
EXPECT_EQ(md5("", 0), md5("", 0));
}

TEST(Hash, Md5FromStr)
{
MD5_DIGEST Expected = {{
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89,
0x01, 0x23, 0x45, 0x67, 0x89, 0x01,
}};
MD5_DIGEST Md5;
EXPECT_FALSE(md5_from_str(&Md5, "01234567890123456789012345678901"));
EXPECT_EQ(Md5, Expected);
EXPECT_TRUE(md5_from_str(&Md5, "0123456789012345678901234567890"));
EXPECT_TRUE(md5_from_str(&Md5, "012345678901234567890123456789012"));
EXPECT_TRUE(md5_from_str(&Md5, ""));
EXPECT_TRUE(md5_from_str(&Md5, "0123456789012345678901234567890x"));
EXPECT_TRUE(md5_from_str(&Md5, "x1234567890123456789012345678901"));
}
4 changes: 3 additions & 1 deletion src/test/str.cpp
Expand Up @@ -67,10 +67,12 @@ TEST(Str, Utf8ToLower)
EXPECT_TRUE(str_utf8_comp_nocase("ÖlÜ", "ölüa") < 0); // NULL < a
EXPECT_TRUE(str_utf8_comp_nocase("ölüa", "ÖlÜ") > 0); // a < NULL

const char a[2] = {-128, 0};
#if (CHAR_MIN < 0)
const char a[2] = {CHAR_MIN, 0};
const char b[2] = {0, 0};
EXPECT_TRUE(str_utf8_comp_nocase(a, b) > 0);
EXPECT_TRUE(str_utf8_comp_nocase(b, a) < 0);
#endif

EXPECT_TRUE(str_utf8_comp_nocase_num("ÖlÜ", "ölüa", 5) == 0);
EXPECT_TRUE(str_utf8_comp_nocase_num("ÖlÜ", "ölüa", 6) != 0);
Expand Down