1 /*
2 * DedicatedControl.cpp
3 * OpenLieroX
4 *
5 * Created by Albert Zeyer on 11.01.08.
6 * code under LGPL
7 *
8 */
9
10 // define HAVE_BOOST if you want to compile dedicated server for Win32 and have Boost headers installed.
11
12 // A workaround for a bug in boost - doesn't compile with DEBUG_NEW in MSVC 2005
13 #if defined(_MSC_VER) && _MSC_VER == 1400
14 #undef new
15 #endif
16
17 #include <string>
18 #include <sstream>
19 #include <stdexcept>
20 #include "ThreadPool.h"
21 #include <fcntl.h>
22 #include <iostream>
23
24 #include "Debug.h"
25 #include "LieroX.h"
26 #include "IpToCountryDB.h"
27 #include "DedicatedControl.h"
28 #include "FindFile.h"
29 #include "StringUtils.h"
30 #include "CMap.h"
31 #include "ProfileSystem.h"
32 #include "CClient.h"
33 #include "CServer.h"
34 #include "CWorm.h"
35 #include "CGameScript.h"
36 #include "Unicode.h"
37 #include "Protocol.h"
38 #include "CScriptableVars.h"
39 #include "CClientNetEngine.h"
40 #include "CChannel.h"
41 #include "CServerConnection.h"
42 #include "CServerNetEngine.h"
43 #include "Command.h"
44 #include "Process.h"
45 #include "Event.h"
46 #include "CGameMode.h"
47 #include "Cache.h"
48 #include "AuxLib.h"
49 #include "DeprecatedGUI/Menu.h"
50
51 #ifdef _MSC_VER
52 #pragma warning(disable: 4996)
53 #endif
54
55
56 using std::endl;
57
58
59
60 static DedicatedControl* dedicatedControlInstance = NULL;
61
Get()62 DedicatedControl* DedicatedControl::Get()
63 {
64 return dedicatedControlInstance;
65 }
66
Init()67 bool DedicatedControl::Init() {
68 dedicatedControlInstance = new DedicatedControl();
69 return dedicatedControlInstance->Init_priv();
70 }
71
Uninit()72 void DedicatedControl::Uninit() {
73 delete dedicatedControlInstance;
74 dedicatedControlInstance = NULL;
75 }
76
77
78
79 struct ScriptCmdLineIntf : CmdLineIntf {
80 Process pipe;
81 ThreadPoolItem* thread;
82
83
ScriptCmdLineIntfScriptCmdLineIntf84 ScriptCmdLineIntf() : thread(NULL) {
85 }
86
~ScriptCmdLineIntfScriptCmdLineIntf87 ~ScriptCmdLineIntf() {
88 breakCurrentScript();
89 }
90
91
pushReturnArgScriptCmdLineIntf92 void pushReturnArg(const std::string& str) {
93 pipeOut() << ":" << str << endl;
94 }
95
finalizeReturnScriptCmdLineIntf96 void finalizeReturn() {
97 pipeOut() << "." << endl;
98 }
99
writeMsgScriptCmdLineIntf100 void writeMsg(const std::string& str, CmdLineMsgType type) {
101 // TODO: handle type
102 hints << "Script Dedicated: " << str << endl;
103 }
104
105
pipeOutScriptCmdLineIntf106 std::ostream& pipeOut() {
107 return pipe.in();
108 }
109
closePipeScriptCmdLineIntf110 void closePipe() {
111 pipe.close();
112 }
113
havePipeScriptCmdLineIntf114 bool havePipe() {
115 return thread != NULL;
116 }
117
118
119
breakCurrentScriptScriptCmdLineIntf120 bool breakCurrentScript() {
121 if(thread) {
122 notes << "waiting for pipeThread ..." << endl;
123 pipe.close();
124 threadPool->wait(thread, NULL);
125 }
126 thread = NULL;
127
128 return true;
129 }
130
loadScriptScriptCmdLineIntf131 bool loadScript(const std::string& script, const std::string& scriptArgs)
132 {
133 breakCurrentScript();
134 return loadScript_Pipe(script, scriptArgs);
135 }
136
137
loadScript_PipeScriptCmdLineIntf138 bool loadScript_Pipe(const std::string& script, const std::string& scriptArgs) {
139 breakCurrentScript();
140
141 std::string scriptfn = GetAbsolutePath(GetFullFileName(script));
142 if(script != "/dev/null") {
143 if(!IsFileAvailable(scriptfn, true)) {
144 errors << "Dedicated: " << scriptfn << " not found" << endl;
145 return false;
146 }
147
148 notes << "Dedicated server: running script \"" << scriptfn << "\" args \"" << scriptArgs << "\"" << endl;
149 // HINT: If a script need this change in his own directory, it is a bug in the script.
150 // If we change into script directory, ded function "getfullfilename" won't work anymore
151 std::vector<std::string> args = explode(script + " " + scriptArgs, " ");
152 if(!pipe.open(scriptfn, args, ExtractDirectory(scriptfn) )) {
153 errors << "cannot start dedicated server - cannot run script " << scriptfn << endl;
154 return false;
155 }
156
157 thread = threadPool->start(&ScriptCmdLineIntf::pipeThreadFunc, this, "Ded pipe watcher");
158 }
159 else
160 notes << "Dedicated server: not running any script" << endl;
161
162 return true;
163 }
164
ScriptSignalHandlerScriptCmdLineIntf165 void ScriptSignalHandler() {} // stub
166
167
168
169 // Pipe functions
170
171 // reading lines from pipe-out and put them to pipeOutput
pipeThreadFuncScriptCmdLineIntf172 static int pipeThreadFunc(void* o) {
173 ScriptCmdLineIntf* owner = (ScriptCmdLineIntf*)o;
174
175 while(!owner->pipe.out().eof()) {
176 std::string buf;
177 getline(owner->pipe.out(), buf);
178
179 Execute( owner, buf );
180 }
181 return 0;
182 }
183
184 };
185
186 struct StdinCmdLineIntf : CmdLineIntf {
187 ThreadPoolItem* thread;
188
StdinCmdLineIntfStdinCmdLineIntf189 StdinCmdLineIntf() {
190 thread = threadPool->start(&StdinCmdLineIntf::stdinThreadFunc, this, "Ded stdin watcher");
191 }
192
~StdinCmdLineIntfStdinCmdLineIntf193 ~StdinCmdLineIntf() {
194 notes << "waiting for stdinThread ..." << endl;
195 threadPool->wait(thread, NULL);
196 thread = NULL;
197 }
198
pushReturnArgStdinCmdLineIntf199 void pushReturnArg(const std::string& str) {
200 notes << "Dedicated return: " << str << endl;
201 }
202
finalizeReturnStdinCmdLineIntf203 void finalizeReturn() {
204 notes << "Dedicated return." << endl;
205 }
206
writeMsgStdinCmdLineIntf207 void writeMsg(const std::string& str, CmdLineMsgType type) {
208 // TODO: handle type
209 hints << "STDIN Dedicated: " << str << endl;
210 }
211
212
213 // reading lines from stdin and put them to pipeOutput
stdinThreadFuncStdinCmdLineIntf214 static int stdinThreadFunc(void* o) {
215 StdinCmdLineIntf* owner = (StdinCmdLineIntf*)o;
216
217 #ifndef WIN32
218 // TODO: there's no fcntl for Windows!
219 if(fcntl(0, F_SETFL, O_NONBLOCK) == -1)
220 #endif
221 warnings << "ERROR setting standard input into non-blocking mode" << endl;
222
223 while(true) {
224 std::string buf;
225 while(true) {
226 SDL_Delay(10); // TODO: select() here
227 if(tLX->bQuitGame) return 0;
228
229 char c;
230
231 if(read(0, &c, 1) >= 0) {
232 if(c == '\n') break;
233 // TODO: why is this needed? is that WIN32 only?
234 if(c == -52) return 0; // CTRL-C
235 buf += c;
236 }
237 }
238
239 Execute( owner, buf );
240 }
241 return 0;
242 }
243 };
244
245
246
247
248 struct DedIntern {
249
GetDedIntern250 static DedIntern* Get() { return (DedIntern*)dedicatedControlInstance->internData; }
251
252 ScriptCmdLineIntf* scriptInterface;
253 StdinCmdLineIntf* stdinInterface;
254
255 bool quitSignal;
256 SDL_mutex* pendingSignalsMutex;
257 bool waitingForNextSignal;
258 struct Signal {
259 std::string name;
260 std::list<std::string> args;
SignalDedIntern::Signal261 Signal(const std::string& n, const std::list<std::string>& a) : name(n), args(a) {}
262 };
263 std::list<Signal> pendingSignals;
264
265
266
DedInternDedIntern267 DedIntern() :
268 scriptInterface(NULL), stdinInterface(NULL),
269 quitSignal(false),
270 pendingSignalsMutex(NULL), waitingForNextSignal(false)
271 {
272 dedicatedControlInstance->internData = this;
273 pendingSignalsMutex = SDL_CreateMutex();
274
275 scriptInterface = new ScriptCmdLineIntf();
276 stdinInterface = new StdinCmdLineIntf();
277 }
278
~DedInternDedIntern279 ~DedIntern() {
280 Sig_Quit();
281 quitSignal = true;
282
283 delete stdinInterface; stdinInterface = NULL;
284 delete scriptInterface; scriptInterface = NULL;
285
286 SDL_DestroyMutex(pendingSignalsMutex);
287
288 notes << "DedicatedControl destroyed" << endl;
289 dedicatedControlInstance->internData = NULL;
290 }
291
292
pushSignalDedIntern293 void pushSignal(const std::string& name, const std::list<std::string>& args = std::list<std::string>()) {
294 if(!scriptInterface->havePipe()) return;
295 ScopedLock lock(pendingSignalsMutex);
296 if(waitingForNextSignal) {
297 if(pendingSignals.size() > 0)
298 errors << "Dedicated pending signals queue should be empty when the script is waiting for next signal" << endl;
299 scriptInterface->pushReturnArg(name);
300 for(std::list<std::string>::const_iterator i = args.begin(); i != args.end(); ++i)
301 scriptInterface->pushReturnArg(*i);
302 scriptInterface->finalizeReturn();
303 waitingForNextSignal = false;
304 }
305 else {
306 pendingSignals.push_back(Signal(name, args));
307 if(pendingSignals.size() > 1000) {
308 warnings << "Dedicated pending signals queue got too full!" << endl;
309 pendingSignals.pop_front();
310 }
311 }
312 }
313
pushSignalDedIntern314 void pushSignal(const std::string& name, const std::string& arg1) {
315 std::list<std::string> args; args.push_back(arg1);
316 pushSignal(name, args);
317 }
318
pushSignalDedIntern319 void pushSignal(const std::string& name, const std::string& arg1, const std::string& arg2) {
320 std::list<std::string> args; args.push_back(arg1); args.push_back(arg2);
321 pushSignal(name, args);
322 }
323
pushSignalDedIntern324 void pushSignal(const std::string& name, const std::string& arg1, const std::string& arg2, const std::string& arg3) {
325 std::list<std::string> args; args.push_back(arg1); args.push_back(arg2); args.push_back(arg3);
326 pushSignal(name, args);
327 }
328
329
330 // ----------------------------------
331 // ----------- signals --------------
332
Sig_LobbyStartedDedIntern333 void Sig_LobbyStarted() { pushSignal("lobbystarted"); }
Sig_GameLoopStartDedIntern334 void Sig_GameLoopStart() { pushSignal("gameloopstart"); }
Sig_GameLoopEndDedIntern335 void Sig_GameLoopEnd() {
336 pushSignal("gameloopend");
337 if(currentGameState() != S_SVRLOBBY && currentGameState() != S_CLILOBBY)
338 // This is because of the current game logic: It will end the game
339 // loop and then return to the lobby but only in the case if we got a
340 // BackToLobby-signal before; if we didn't get such a signal and
341 // the gameloop was ended, that means that the game was stopped
342 // completely.
343 notes << "gameloopend without backtolobby -> stop" << endl;
344 }
Sig_WeaponSelectionsDedIntern345 void Sig_WeaponSelections() { pushSignal("weaponselections"); }
Sig_GameStartedDedIntern346 void Sig_GameStarted() { pushSignal("gamestarted"); }
Sig_BackToLobbyDedIntern347 void Sig_BackToLobby() { pushSignal("backtolobby"); }
Sig_QuitDedIntern348 void Sig_Quit() { pushSignal("quit"); scriptInterface->closePipe(); }
349
Sig_ConnectingDedIntern350 void Sig_Connecting(const std::string& addr) { pushSignal("connecting", addr); }
Sig_ConnectErrorDedIntern351 void Sig_ConnectError(const std::string& err) { pushSignal("connecterror", err); }
Sig_ConnectedDedIntern352 void Sig_Connected() { pushSignal("connected"); }
Sig_ClientErrorDedIntern353 void Sig_ClientError() { pushSignal("clienterror"); }
Sig_ClientConnectionErrorDedIntern354 void Sig_ClientConnectionError(const std::string& err) { pushSignal("connectionerror", err); }
Sig_ClientGameStartedDedIntern355 void Sig_ClientGameStarted() { pushSignal("clientgamestarted"); }
Sig_ClientGotoLobbyDedIntern356 void Sig_ClientGotoLobby() { pushSignal("clientbacktolobby"); }
357
Sig_NewWormDedIntern358 void Sig_NewWorm(CWorm* w) { pushSignal("newworm", itoa(w->getID()), w->getName()); }
Sig_WormLeftDedIntern359 void Sig_WormLeft(CWorm* w) { pushSignal("wormleft", itoa(w->getID()), w->getName()); }
Sig_ChatMessageDedIntern360 void Sig_ChatMessage(CWorm* w, const std::string& message) { pushSignal("chatmessage", itoa(w->getID()), message); }
Sig_PrivateMessageDedIntern361 void Sig_PrivateMessage(CWorm* w, CWorm* to, const std::string& message) { pushSignal("privatemessage", itoa(w->getID()), itoa(to->getID()), message); }
Sig_WormDiedDedIntern362 void Sig_WormDied(int died, int killer) { pushSignal("wormdied", itoa(died), itoa(killer)); }
Sig_WormSpawnedDedIntern363 void Sig_WormSpawned(CWorm* worm) { pushSignal("wormspawned", itoa(worm->getID())); }
Sig_WormGotAdminDedIntern364 void Sig_WormGotAdmin(CWorm* w) { pushSignal("wormgotadmin", itoa(w->getID())); }
Sig_WormAuthorizedDedIntern365 void Sig_WormAuthorized(CWorm* w) { pushSignal("wormauthorized", itoa(w->getID())); }
366
Sig_TimerDedIntern367 void Sig_Timer(const std::string& name) { pushSignal("timer", name); }
368
369
370 // ----------------------------------
371 // ---------- frame handlers --------
372
Frame_ServerLobbyDedIntern373 void Frame_ServerLobby() {
374 // Process the server & client frames
375 cServer->Frame();
376 cClient->Frame();
377 }
378
Frame_PlayingDedIntern379 void Frame_Playing() {
380 // we don't have to process server/client frames here as it is done already by the main loop
381 }
382
Frame_ClientConnectingDedIntern383 void Frame_ClientConnecting() {
384 cClient->Frame();
385
386 // are we connected?
387 if(cClient->getStatus() == NET_CONNECTED) {
388 Sig_Connected();
389 return;
390 }
391
392 // error?
393 if(cClient->getBadConnection()) {
394 warnings << "Bad connection: " << cClient->getBadConnectionMsg() << endl;
395 Sig_ConnectError(cClient->getBadConnectionMsg());
396 cClient->Shutdown();
397 return;
398 }
399 }
400
Frame_ClientLobbyDedIntern401 void Frame_ClientLobby() {
402 // Process the client
403 cClient->Frame();
404
405 // If there is a client error, leave
406 if(cClient->getClientError()) {
407 Sig_ClientError();
408 return;
409 }
410
411 // If we have started, leave the frontend
412 if(cClient->getGameReady()) {
413 // Leave the frontend
414 *DeprecatedGUI::bGame = true;
415 DeprecatedGUI::tMenu->bMenuRunning = false;
416 tLX->iGameType = GME_JOIN;
417 Sig_ClientGameStarted();
418 return;
419 }
420
421
422 // Check if the communication link between us & server is still ok
423 if(cClient->getServerError()) {
424 warnings << "Client connection error: " << cClient->getServerErrorMsg() << endl;
425 Sig_ClientConnectionError(cClient->getServerErrorMsg());
426 cClient->Disconnect();
427 cClient->Shutdown();
428 SetQuitEngineFlag("Frame_ClientLobby: connection error");
429 return;
430 }
431 }
432
Frame_BasicDedIntern433 void Frame_Basic() {
434
435 // TODO: make this clean!
436 // TODO: no static var here
437 // TODO: not a single timer, the script should register as much as it want
438 // TODO: each timer should be configurable
439 static AbsTime lastTimeHandlerCalled = tLX->currentTime;
440 if( tLX->currentTime > lastTimeHandlerCalled + 1.0f ) // Call once per second, when no signals pending, TODO: configurable from ded script
441 {
442 Sig_Timer("second-ticker"); // TODO ...
443 lastTimeHandlerCalled = tLX->currentTime;
444 }
445
446
447 // Some debugging stuff
448 // TODO: This really much spams my logs. There should be a better way to inform about this.
449 #if 0
450 //#if DEBUG
451 int fps = GetFPS();
452 static AbsTime lastFpsPrint = tLX->currentTime;
453 if (tLX->currentTime - lastFpsPrint >= 20.0f) {
454 notes << "Current FPS: " << fps << endl;
455 lastFpsPrint = tLX->currentTime;
456 }
457
458 static AbsTime lastBandwidthPrint = tLX->currentTime;
459 if (tLX->currentTime - lastBandwidthPrint >= 20.0f) {
460 // Upload and download rates
461 float up = 0;
462 float down = 0;
463
464 // Get the rates
465 if( tLX->iGameType == GME_JOIN ) {
466 if(cClient->getChannel()) {
467 down = cClient->getChannel()->getIncomingRate() / 1024.0f;
468 up = cClient->getChannel()->getOutgoingRate() / 1024.0f;
469 }
470 }
471 else if( tLX->iGameType == GME_HOST ) {
472 down = cServer->GetDownload() / 1024.0f;
473 up = cServer->GetUpload() / 1024.0f;
474 }
475
476 notes << "Current upload rate: " << up << " kB/s" << endl;
477 notes << "Current download rate: " << down << " kB/s" << endl;
478 lastBandwidthPrint = tLX->currentTime;
479 }
480 #endif
481
482
483 switch(currentGameState()) {
484 case S_INACTIVE: break;
485 case S_SVRLOBBY: Frame_ServerLobby(); break;
486 case S_SVRWEAPONS: Frame_Playing(); break;
487 case S_SVRPLAYING: Frame_Playing(); break;
488 case S_CLICONNECTING: Frame_ClientConnecting(); break;
489 case S_CLILOBBY: Frame_ClientLobby(); break;
490 case S_CLIPLAYING: Frame_Playing(); break;
491 case S_CLIWEAPONS: Frame_Playing(); break;
492 }
493 }
494 };
495
496
DedicatedControl()497 DedicatedControl::DedicatedControl() : internData(NULL) {}
~DedicatedControl()498 DedicatedControl::~DedicatedControl() { if(internData) delete internData; internData = NULL; }
499
500 // gets called from static DedicatedControl::Init()
Init_priv()501 bool DedicatedControl::Init_priv() {
502 DedIntern* dedIntern = new DedIntern; // constr will assign this->internData to this obj
503 if(tLXOptions->sDedicatedScript != "" && tLXOptions->sDedicatedScript != "/dev/null") {
504 if(IsAbsolutePath(tLXOptions->sDedicatedScript)) {
505 dedIntern->scriptInterface->loadScript(tLXOptions->sDedicatedScript, tLXOptions->sDedicatedScriptArgs);
506 return true;
507 }
508
509 if(strStartsWith(tLXOptions->sDedicatedScript, "scripts/")) { // old clients will use it like that
510 return dedIntern->scriptInterface->loadScript(tLXOptions->sDedicatedScript, tLXOptions->sDedicatedScriptArgs);
511 return true;
512 }
513
514 dedIntern->scriptInterface->loadScript("scripts/" + tLXOptions->sDedicatedScript, tLXOptions->sDedicatedScriptArgs);
515 }
516 else
517 dedIntern->scriptInterface->loadScript("/dev/null", "");
518
519 return true;
520 }
521
522
523 // This is the main game loop, the one that do all the simulation etc.
GameLoopStart_Signal()524 void DedicatedControl::GameLoopStart_Signal() { internData->Sig_GameLoopStart(); }
GameLoopEnd_Signal()525 void DedicatedControl::GameLoopEnd_Signal() { internData->Sig_GameLoopEnd(); }
LobbyStarted_Signal()526 void DedicatedControl::LobbyStarted_Signal() { internData->Sig_LobbyStarted(); }
BackToServerLobby_Signal()527 void DedicatedControl::BackToServerLobby_Signal() { internData->Sig_BackToLobby(); }
BackToClientLobby_Signal()528 void DedicatedControl::BackToClientLobby_Signal() { internData->Sig_ClientGotoLobby(); }
WeaponSelections_Signal()529 void DedicatedControl::WeaponSelections_Signal() { internData->Sig_WeaponSelections(); }
GameStarted_Signal()530 void DedicatedControl::GameStarted_Signal() { internData->Sig_GameStarted(); }
Connecting_Signal(const std::string & addr)531 void DedicatedControl::Connecting_Signal(const std::string& addr) { internData->Sig_Connecting(addr); }
NewWorm_Signal(CWorm * w)532 void DedicatedControl::NewWorm_Signal(CWorm* w) { internData->Sig_NewWorm(w); }
WormLeft_Signal(CWorm * w)533 void DedicatedControl::WormLeft_Signal(CWorm* w) { internData->Sig_WormLeft(w); }
ChatMessage_Signal(CWorm * w,const std::string & message)534 void DedicatedControl::ChatMessage_Signal(CWorm* w, const std::string& message) { internData->Sig_ChatMessage(w,message); }
PrivateMessage_Signal(CWorm * w,CWorm * to,const std::string & message)535 void DedicatedControl::PrivateMessage_Signal(CWorm* w, CWorm* to, const std::string& message) { internData->Sig_PrivateMessage(w,to,message); }
WormDied_Signal(CWorm * worm,CWorm * killer)536 void DedicatedControl::WormDied_Signal(CWorm* worm, CWorm* killer) { internData->Sig_WormDied(worm->getID(), killer ? killer->getID() : -1); }
WormSpawned_Signal(CWorm * worm)537 void DedicatedControl::WormSpawned_Signal(CWorm* worm){ internData->Sig_WormSpawned(worm); };
WormGotAdmin_Signal(CWorm * worm)538 void DedicatedControl::WormGotAdmin_Signal(CWorm* worm){ internData->Sig_WormGotAdmin(worm); };
WormAuthorized_Signal(CWorm * worm)539 void DedicatedControl::WormAuthorized_Signal(CWorm* worm){ internData->Sig_WormAuthorized(worm); };
Custom_Signal(const std::list<std::string> & args)540 void DedicatedControl::Custom_Signal(const std::list<std::string>& args) { internData->pushSignal("custom", args); }
541
Menu_Frame()542 void DedicatedControl::Menu_Frame() { internData->Frame_Basic(); }
GameLoop_Frame()543 void DedicatedControl::GameLoop_Frame() { internData->Frame_Basic(); }
544
ChangeScript(const std::string & filename,const std::string & args)545 void DedicatedControl::ChangeScript(const std::string& filename, const std::string& args) {
546 {
547 ScopedLock lock(internData->pendingSignalsMutex);
548 internData->waitingForNextSignal = false;
549 internData->pendingSignals.clear();
550 }
551
552 if(filename == "" || filename == "/dev/null")
553 internData->scriptInterface->loadScript("/dev/null", "");
554 else
555 internData->scriptInterface->loadScript("scripts/" + filename, args);
556 }
557
GetNextSignal(CmdLineIntf * sender)558 bool DedicatedControl::GetNextSignal(CmdLineIntf* sender) {
559 if(sender != internData->scriptInterface) {
560 sender->writeMsg("nextsignal can only be called from the dedicated script");
561 return true; // true means that finalizeReturn() should be called from caller of this func
562 }
563
564 ScopedLock lock(internData->pendingSignalsMutex);
565 if(internData->pendingSignals.size() > 0) {
566 DedIntern::Signal sig = internData->pendingSignals.front();
567 internData->pendingSignals.pop_front();
568 sender->pushReturnArg(sig.name);
569 for(std::list<std::string>::iterator i = sig.args.begin(); i != sig.args.end(); ++i)
570 sender->pushReturnArg(*i);
571 return true;
572 }
573 else {
574 // do nothing, we will send the next signal when it arrives
575 internData->waitingForNextSignal = true;
576 return false;
577 }
578 }
579