Add server control (start/stop/restart, live logs, broadcast, kick) to webadmin
Docker SDK drives login/char/worldserver containers via docker.sock (mounted read-only into webadmin); live logs stream over SSE with nginx buffering disabled so lines arrive in real time. Broadcast/kick go through a new admin_queue DB table that the world server polls every ~2s in its existing WorldProcess tick loop, avoiding a second IPC channel to the game server.
This commit is contained in:
1 parent
3554c85f2f
commit
15e562cb04
11 files changed
+331
-2
No files matched your search
@@ -403,8 +403,16 @@ PVOID VisibilityProcess(PVOID TS)
|
||||
// World Process
|
||||
PVOID WorldProcess( PVOID TS )
|
||||
{
|
||||
int adminQueueTicks = 0;
|
||||
while( GServer->ServerOnline )
|
||||
{
|
||||
// Poll roughly every 2 seconds regardless of WorldDelay, instead of
|
||||
// hitting the DB on every tick.
|
||||
if( ++adminQueueTicks * GServer->Config.WorldDelay >= 2000 )
|
||||
{
|
||||
adminQueueTicks = 0;
|
||||
GServer->PollAdminQueue( );
|
||||
}
|
||||
pthread_mutex_lock( &GServer->MapMutex );
|
||||
for(UINT i=0;i<GServer->MapList.Map.size();i++)
|
||||
{
|
||||
|
||||
@@ -66,6 +66,48 @@ bool CWorldServer::NPCAnnounce( const char msg[200], const char npc[50])
|
||||
return true;
|
||||
}
|
||||
|
||||
// Poll the admin_queue table (written by the webadmin panel) for pending
|
||||
// broadcast/kick actions and execute them. There's no direct socket between
|
||||
// webadmin and the world server, so this DB-polling approach reuses the
|
||||
// existing MYSQL connection instead of adding a second IPC channel.
|
||||
void CWorldServer::PollAdminQueue( )
|
||||
{
|
||||
// QStore holds SQLMutex locked until QFree() -- copy the rows out and
|
||||
// free the result set first, since NPCAnnounce/pakGMKick/QExecute below
|
||||
// issue their own queries and would deadlock on the same (non-recursive)
|
||||
// mutex if called while this result set is still open.
|
||||
struct QueueItem { int id; char type[20]; char payload[255]; };
|
||||
QueueItem items[20];
|
||||
int count = 0;
|
||||
|
||||
MYSQL_RES* result = DB->QStore("SELECT id, type, payload FROM admin_queue WHERE executed=0 ORDER BY id LIMIT 20");
|
||||
if(result != NULL)
|
||||
{
|
||||
MYSQL_ROW row;
|
||||
while(count < 20 && (row = mysql_fetch_row(result)) != NULL)
|
||||
{
|
||||
items[count].id = atoi(row[0]);
|
||||
strncpy_s(items[count].type, row[1], sizeof(items[count].type)-1);
|
||||
strncpy_s(items[count].payload, row[2], sizeof(items[count].payload)-1);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
DB->QFree( );
|
||||
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
if(strcmp(items[i].type, "broadcast") == 0)
|
||||
{
|
||||
NPCAnnounce(items[i].payload, "[Admin]");
|
||||
}
|
||||
else if(strcmp(items[i].type, "kick") == 0)
|
||||
{
|
||||
pakGMKick(NULL, items[i].payload);
|
||||
}
|
||||
DB->QExecute("UPDATE admin_queue SET executed=1 WHERE id=%i", items[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
// NPC Whisper to a character
|
||||
bool CWorldServer::NPCWhisper( CPlayer* thisclient, CMonster* thismon, const char msg[200], const char npc[50] )
|
||||
{
|
||||
|
||||
@@ -342,6 +342,7 @@ class CWorldServer : public CServerSocket
|
||||
bool NPCMessage( CMonster* thismon, const char msg[200], const char npc[50] );
|
||||
bool NPCWhisper( CPlayer* thisclient, CMonster* thismon, const char msg[200], const char npc[50] );
|
||||
bool NPCAnnounce( const char msg[200], const char npc[50] );
|
||||
void PollAdminQueue( );
|
||||
UINT GetMaxPartyExp( UINT partylevel );
|
||||
bool DoSkillScript( CCharacter* character, CSkills* thisskill );
|
||||
|
||||
|
||||
Reference in new issue
Block a user