3879: Use SDL_FlashWindow to request user's attention r=heinrich5991 a=Jupeyy

We should soon update to SDL 2.0.16 for our bundled libs, but don't merge before this happened.

This drops the remaining X11 dependency.

We should then also use GLEW with EGL for official builds for easy native wayland support(which i discussed before already)

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


4079: Make teehistorian easier to reproduce r=heinrich5991 a=Zwelf

I've tested it ingame, but still have to make sure, that the ordering of the teehistorian messages are right. I would be really happy if we could land this before changing team joining logic (#4006 or /practice logic, but there isn't a pr for this yet), but I would like to make sure that the change is correct.

## Checklist

- [x] Tested the change ingame
- [ ] Tested in combination with possibly related configuration options
- [x] Written a unit test if it works standalone, system.c especially
- [x] Considered possible null pointers and out of bounds array indexing
- [x] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
Co-authored-by: Zwelf <zwelf@strct.cc>
This commit is contained in:
bors[bot] 2021-10-06 14:52:39 +00:00 committed by GitHub
commit 121c6d16f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 171 additions and 64 deletions

View file

@ -599,9 +599,8 @@ elseif(TARGET_OS STREQUAL "android")
else()
find_package(Notify)
find_package(OpenGL)
find_package(X11)
set(PLATFORM_CLIENT_LIBS ${OPENGL_gl_LIBRARY} ${X11_X11_LIB} ${NOTIFY_LIBRARIES})
set(PLATFORM_CLIENT_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR} ${X11_X11_INCLUDE_PATH} ${NOTIFY_INCLUDE_DIRS})
set(PLATFORM_CLIENT_LIBS ${OPENGL_gl_LIBRARY} ${NOTIFY_LIBRARIES})
set(PLATFORM_CLIENT_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR} ${NOTIFY_INCLUDE_DIRS})
set(PLATFORM_CLIENT)
if(TARGET_OS STREQUAL "linux")
set(PLATFORM_LIBS rt) # clock_gettime for glibc < 2.17

View file

@ -24,11 +24,6 @@
#include "SDL_pixels.h"
#include "SDL_video.h"
#if defined(SDL_VIDEO_DRIVER_X11)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif
#include <engine/shared/config.h>
#include <base/tl/threading.h>
@ -1213,41 +1208,12 @@ void CGraphicsBackend_SDL_OpenGL::GetViewportSize(int &w, int &h)
void CGraphicsBackend_SDL_OpenGL::NotifyWindow()
{
// get window handle
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
if(!SDL_GetWindowWMInfo(m_pWindow, &info))
#if SDL_MAJOR_VERSION > 2 || (SDL_MAJOR_VERSION == 2 && SDL_PATCHLEVEL >= 16)
if(SDL_FlashWindow(m_pWindow, SDL_FlashOperation::SDL_FLASH_BRIEFLY) != 0)
{
dbg_msg("gfx", "unable to obtain window handle");
// fails if SDL hasn't implemented it
return;
}
#if defined(CONF_FAMILY_WINDOWS)
FLASHWINFO desc;
desc.cbSize = sizeof(desc);
desc.hwnd = info.info.win.window;
desc.dwFlags = FLASHW_TRAY;
desc.uCount = 3; // flash 3 times
desc.dwTimeout = 0;
FlashWindowEx(&desc);
#elif defined(SDL_VIDEO_DRIVER_X11) && !defined(CONF_PLATFORM_MACOS)
Display *pX11Dpy = info.info.x11.display;
Window X11Win = info.info.x11.window;
static Atom s_DemandsAttention = XInternAtom(pX11Dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", true);
static Atom s_WmState = XInternAtom(pX11Dpy, "_NET_WM_STATE", true);
XEvent SndNtfyEvent = {ClientMessage};
SndNtfyEvent.xclient.window = X11Win;
SndNtfyEvent.xclient.message_type = s_WmState;
SndNtfyEvent.xclient.format = 32;
SndNtfyEvent.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
SndNtfyEvent.xclient.data.l[1] = s_DemandsAttention;
SndNtfyEvent.xclient.data.l[2] = 0;
SndNtfyEvent.xclient.data.l[3] = 1; // normal application
SndNtfyEvent.xclient.data.l[4] = 0;
XSendEvent(pX11Dpy, XDefaultRootWindow(pX11Dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &SndNtfyEvent);
#endif
}

View file

@ -301,6 +301,8 @@ public:
// DDRace
virtual void OnPreTickTeehistorian() = 0;
virtual void OnSetAuthed(int ClientID, int Level) = 0;
virtual bool PlayerExists(int ClientID) const = 0;

View file

@ -2596,6 +2596,8 @@ int CServer::Run()
while(t > TickStartTime(m_CurrentGameTick + 1))
{
GameServer()->OnPreTickTeehistorian();
for(int c = 0; c < MAX_CLIENTS; c++)
if(m_aClients[c].m_State == CClient::STATE_INGAME)
for(auto &Input : m_aClients[c].m_aInputs)

View file

@ -185,7 +185,7 @@ MACRO_CONFIG_STR(DbgStressServer, dbg_stress_server, 32, "localhost", CFGFLAG_CL
// DDRace
MACRO_CONFIG_STR(SvWelcome, sv_welcome, 64, "", CFGFLAG_SERVER, "Message that will be displayed to players who join the server")
MACRO_CONFIG_INT(SvReservedSlots, sv_reserved_slots, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "The number of slots that are reserved for special players")
MACRO_CONFIG_STR(SvReservedSlotsPass, sv_reserved_slots_pass, 32, "", CFGFLAG_SERVER, "The password that is required to use a reserved slot")
MACRO_CONFIG_STR(SvReservedSlotsPass, sv_reserved_slots_pass, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "The password that is required to use a reserved slot")
MACRO_CONFIG_INT(SvHit, sv_hit, 1, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Whether players can hammer/grenade/laser each other or not")
MACRO_CONFIG_INT(SvEndlessDrag, sv_endless_drag, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Turns endless hooking on/off")
MACRO_CONFIG_INT(SvTestingCommands, sv_test_cmds, 0, 0, 1, CFGFLAG_SERVER, "Turns testing commands aka cheats on/off (setting only works in initial config)")

View file

@ -12,3 +12,5 @@ UUID(TEEHISTORIAN_SAVE_SUCCESS, "teehistorian-save-success@ddnet.tw")
UUID(TEEHISTORIAN_SAVE_FAILURE, "teehistorian-save-failure@ddnet.tw")
UUID(TEEHISTORIAN_LOAD_SUCCESS, "teehistorian-load-success@ddnet.tw")
UUID(TEEHISTORIAN_LOAD_FAILURE, "teehistorian-load-failure@ddnet.tw")
UUID(TEEHISTORIAN_PLAYER_TEAM, "teehistorian-player-team@ddnet.tw")
UUID(TEEHISTORIAN_TEAM_PRACTICE, "teehistorian-team-practice@ddnet.tw")

View file

@ -1,7 +1,5 @@
/* (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 "SDL.h" // SDL_VIDEO_DRIVER_X11
#include <base/tl/string.h>
#include <base/math.h>

View file

@ -763,6 +763,22 @@ void CGameContext::SendTuningParams(int ClientID, int Zone)
Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID);
}
void CGameContext::OnPreTickTeehistorian()
{
auto *pController = ((CGameControllerDDRace *)m_pController);
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i] != nullptr)
m_TeeHistorian.RecordPlayerTeam(i, pController->m_Teams.m_Core.Team(i));
else
m_TeeHistorian.RecordPlayerTeam(i, 0);
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
m_TeeHistorian.RecordTeamPractice(i, pController->m_Teams.IsPractice(i));
}
}
void CGameContext::OnTick()
{
// check tuning
@ -793,25 +809,6 @@ void CGameContext::OnTick()
//if(world.paused) // make sure that the game object always updates
m_pController->Tick();
if(m_TeeHistorianActive)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i] && m_apPlayers[i]->GetCharacter())
{
CNetObj_CharacterCore Char;
m_apPlayers[i]->GetCharacter()->GetCore().Write(&Char);
m_TeeHistorian.RecordPlayer(i, &Char);
}
else
{
m_TeeHistorian.RecordDeadPlayer(i);
}
}
m_TeeHistorian.EndPlayers();
m_TeeHistorian.BeginInputs();
}
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i])
@ -1072,6 +1069,27 @@ void CGameContext::OnTick()
}
}
#endif
// Record player position at the end of the tick
if(m_TeeHistorianActive)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_apPlayers[i] && m_apPlayers[i]->GetCharacter())
{
CNetObj_CharacterCore Char;
m_apPlayers[i]->GetCharacter()->GetCore().Write(&Char);
m_TeeHistorian.RecordPlayer(i, &Char);
}
else
{
m_TeeHistorian.RecordDeadPlayer(i);
}
}
m_TeeHistorian.EndPlayers();
m_TeeHistorian.BeginInputs();
}
// Warning: do not put code in this function directly above or below this comment
}
// Server hooks

View file

@ -273,6 +273,7 @@ public:
virtual const char *NetVersion() const;
// DDRace
void OnPreTickTeehistorian();
bool OnClientDDNetVersionKnown(int ClientID);
virtual void FillAntibot(CAntibotRoundData *pData);
int ProcessSpamProtection(int ClientID, bool RespectChatInitialDelay = true);

View file

@ -8,6 +8,7 @@
static const char TEEHISTORIAN_NAME[] = "teehistorian@ddnet.tw";
static const CUuid TEEHISTORIAN_UUID = CalculateUuid(TEEHISTORIAN_NAME);
static const char TEEHISTORIAN_VERSION[] = "2";
static const char TEEHISTORIAN_VERSION_MINOR[] = "2";
#define UUID(id, name) static const CUuid UUID_##id = CalculateUuid(name);
#include <engine/shared/teehistorian_ex_chunks.h>
@ -47,11 +48,17 @@ void CTeeHistorian::Reset(const CGameInfo *pGameInfo, WRITE_CALLBACK pfnWriteCal
// Tick 0 is implicit at the start, game starts as tick 1.
m_TickWritten = true;
m_MaxClientID = MAX_CLIENTS;
// `m_PrevMaxClientID` is initialized in `BeginPlayers`
for(auto &PrevPlayer : m_aPrevPlayers)
{
PrevPlayer.m_Alive = false;
PrevPlayer.m_InputExists = false;
PrevPlayer.m_Team = 0;
}
for(auto &PrevTeam : m_aPrevTeams)
{
PrevTeam.m_Practice = false;
}
m_pfnWriteCallback = pfnWriteCallback;
m_pWriteCallbackUserdata = pUser;
@ -86,9 +93,26 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo)
#define E(buf, str) EscapeJson(buf, sizeof(buf), str)
str_format(aJson, sizeof(aJson), "{\"comment\":\"%s\",\"version\":\"%s\",\"game_uuid\":\"%s\",\"server_version\":\"%s\",\"start_time\":\"%s\",\"server_name\":\"%s\",\"server_port\":\"%d\",\"game_type\":\"%s\",\"map_name\":\"%s\",\"map_size\":\"%d\",\"map_sha256\":\"%s\",\"map_crc\":\"%08x\",\"prng_description\":\"%s\",\"config\":{",
str_format(aJson, sizeof(aJson),
"{"
"\"comment\":\"%s\","
"\"version\":\"%s\","
"\"version_minor\":\"%s\","
"\"game_uuid\":\"%s\","
"\"server_version\":\"%s\","
"\"start_time\":\"%s\","
"\"server_name\":\"%s\","
"\"server_port\":\"%d\","
"\"game_type\":\"%s\","
"\"map_name\":\"%s\","
"\"map_size\":\"%d\","
"\"map_sha256\":\"%s\","
"\"map_crc\":\"%08x\","
"\"prng_description\":\"%s\","
"\"config\":{",
E(aCommentBuffer, TEEHISTORIAN_NAME),
TEEHISTORIAN_VERSION,
TEEHISTORIAN_VERSION_MINOR,
aGameUuid,
E(aServerVersionBuffer, pGameInfo->m_pServerVersion),
E(aStartTimeBuffer, aStartTime),
@ -293,6 +317,50 @@ void CTeeHistorian::RecordDeadPlayer(int ClientID)
pPrev->m_Alive = false;
}
void CTeeHistorian::RecordPlayerTeam(int ClientID, int Team)
{
if(m_aPrevPlayers[ClientID].m_Team != Team)
{
m_aPrevPlayers[ClientID].m_Team = Team;
EnsureTickWritten();
CPacker Buffer;
Buffer.Reset();
Buffer.AddInt(ClientID);
Buffer.AddInt(Team);
if(m_Debug)
{
dbg_msg("teehistorian", "team_change cid=%d team=%d", ClientID, Team);
}
WriteExtra(UUID_TEEHISTORIAN_PLAYER_TEAM, Buffer.Data(), Buffer.Size());
}
}
void CTeeHistorian::RecordTeamPractice(int Team, bool Practice)
{
if(m_aPrevTeams[Team].m_Practice != Practice)
{
m_aPrevTeams[Team].m_Practice = Practice;
EnsureTickWritten();
CPacker Buffer;
Buffer.Reset();
Buffer.AddInt(Team);
Buffer.AddInt(Practice);
if(m_Debug)
{
dbg_msg("teehistorian", "team_rescue team=%d practice=%d", Team, Practice);
}
WriteExtra(UUID_TEEHISTORIAN_TEAM_PRACTICE, Buffer.Data(), Buffer.Size());
}
}
void CTeeHistorian::Write(const void *pData, int DataSize)
{
m_pfnWriteCallback(pData, DataSize, m_pWriteCallbackUserdata);

View file

@ -57,6 +57,8 @@ public:
void BeginPlayers();
void RecordPlayer(int ClientID, const CNetObj_CharacterCore *pChar);
void RecordDeadPlayer(int ClientID);
void RecordPlayerTeam(int ClientID, int Team);
void RecordTeamPractice(int Team, bool Practice);
void EndPlayers();
void BeginInputs();
@ -111,6 +113,14 @@ private:
CNetObj_PlayerInput m_Input;
bool m_InputExists;
// DDNet team
int m_Team;
};
struct CTeam
{
bool m_Practice;
};
WRITE_CALLBACK m_pfnWriteCallback;
@ -124,6 +134,7 @@ private:
int m_PrevMaxClientID;
int m_MaxClientID;
CPlayer m_aPrevPlayers[MAX_CLIENTS];
CTeam m_aPrevTeams[MAX_CLIENTS];
};
#endif // GAME_SERVER_TEEHISTORIAN_H

View file

@ -89,7 +89,7 @@ protected:
void Expect(const unsigned char *pOutput, int OutputSize)
{
static CUuid TEEHISTORIAN_UUID = CalculateUuid("teehistorian@ddnet.tw");
static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\"";
static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"version_minor\":\"2\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\"";
static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_sha256\":\"0123456789012345678901234567890123456789012345678901234567890123\",\"map_crc\":\"eceaf25c\",\"prng_description\":\"test-prng:02468ace\",\"config\":{},\"tuning\":{},\"uuids\":[";
static const char PREFIX3[] = "]}";
@ -532,3 +532,43 @@ TEST_F(TeeHistorian, LoadFailed)
Finish();
Expect(EXPECTED, sizeof(EXPECTED));
}
TEST_F(TeeHistorian, PlayerTeam)
{
const unsigned char EXPECTED[] = {
// EX uuid=a111c04e-1ea8-38e0-90b1-d7f993ca0da9 datalen=2
0x4a,
0xa1, 0x11, 0xc0, 0x4e, 0x1e, 0xa8, 0x38, 0xe0,
0x90, 0xb1, 0xd7, 0xf9, 0x93, 0xca, 0x0d, 0xa9,
0x02,
// player_id=33
0x21,
// team=54
0x36,
// FINISH
0x40};
m_TH.RecordPlayerTeam(33, 54);
Finish();
Expect(EXPECTED, sizeof(EXPECTED));
}
TEST_F(TeeHistorian, TeamPractice)
{
const unsigned char EXPECTED[] = {
// EX uuid=5792834e-81d1-34c9-a29b-b5ff25dac3bc datalen=2
0x4a,
0x57, 0x92, 0x83, 0x4e, 0x81, 0xd1, 0x34, 0xc9,
0xa2, 0x9b, 0xb5, 0xff, 0x25, 0xda, 0xc3, 0xbc,
0x02,
// team=23
0x17,
// practice=1
0x01,
// FINISH
0x40};
m_TH.RecordTeamPractice(23, 1);
Finish();
Expect(EXPECTED, sizeof(EXPECTED));
}