1  //-----------------------------------------------------------------------------
2 // Cake App
3 //-----------------------------------------------------------------------------
4 
5 #include "app.h"
6 #include "alias.h"
7 #include "commands.h"
8 #include "console.h"
9 #include "definitions.h"
10 #include "framework.h"
11 #include "logfile.h"
12 #include "overlay.h"
13 #include "render.h"
14 #include "timer.h"
15 #include "vars.h"
16 #include "system.h"
17 #include "math.h"
18 #include "mem.h"
19 #include "sound.h"
20 
21 #ifdef WIN32
22   #include <process.h>          // for _getpid
23   #include <direct.h>           // for _getcwd
24   #include <string.h>           // for _strlwr
25   #include <time.h>
26 #else
27   #include <time.h>
28   #include <unistd.h>           // for getcwd
29   #define _getcwd getcwd
30 #endif
31 
32 LogFile*    gLogFile = NULL;  // Log file
33 Console*    gConsole = NULL;  // Console
34 FrameWork*    gFramework = NULL;  // FrameWork
35 Vars*     gVars = NULL;   // Variables
36 Alias*      gAlias = NULL;    // Aliases
37 Commands*   gCommands = NULL; // Commands
38 Render*     gRender = NULL;   // Render
39 Overlay*    gOver = NULL;   // Overlay
40 
41 #define FILENAME_LENGTH 1025    // Filenames length
42 
43 Var r_path("r_path", "base;baseq3", VF_PERSISTENT | VF_LATCH | VF_SYSTEM);
44 Var r_mapsubdir("r_mapsubdir", "maps/", VF_PERSISTENT | VF_SYSTEM);
45 Var cg_drawFPS("cg_drawFPS", 0, VF_PERSISTENT);
46 Var cg_FPSrefreshrate("cg_FPSrefreshrate", 250.f, VF_PERSISTENT);
47 Var cg_drawtime("cg_drawtime", 0, VF_PERSISTENT);
48 Var cg_drawflush("cg_drawflush", 0);
49 Var cg_drawtris("cg_drawtris", 0);
50 Var cg_drawverts("cg_drawverts", 0);
51 Var cg_apicalls("cg_apicalls", 0);
52 Var cg_showpos("cg_showpos", 0);
53 Var cg_showrot("cg_showrot", 0);
54 Var r_debug("r_debug", 0);
55 Var sys_appstate("sys_appstate", INACTIVE, VF_SYSTEM);
56 Var sys_soundsupport("sys_soundsupport", 0, VF_SYSTEM);
57 Var r_targetlocationdist("r_targetlocationdist", 32768.f);
58 
59 float lastfps = 0;          // fps value for last fps refresh interval
60 float timelastfps = 0;        // time of last fps display
61 float totallastfps = 1;       // sum of frames fps during current fps refresh interval
62 unsigned int frameslastfps = 0;   // number of frames during current fps refresh interval
63 char demomsg[64];         // message display when demo is recording/playing
64 int demofilenameindex = 0;      // variable used for demo management
65 
66 /**
67  * Return the length of a string without color codes
68  */
_strlen(const char * s)69 int _strlen(const char *s)
70 {
71   int i = 0, r, l = (int) strlen(s);
72   r = l;
73 
74   while (l--)
75   {
76     if (s[i++] == '^') r-=2;
77   }
78 
79   return r;
80 }
81 
82 //-----------------------------------------------------------------------------
83 // Predefined commands
84 //-----------------------------------------------------------------------------
85 
cmd_bsplist(int argc,char * argv[])86 void cmd_bsplist(int argc, char *argv[])
87 {
88   getPAKContent(NULL, ".bsp", argc>1?argv[1]:NULL);
89 }
90 
cmd_wavlist(int argc,char * argv[])91 void cmd_wavlist(int argc, char *argv[])
92 {
93   getPAKContent(NULL, ".wav", argc>1?argv[1]:NULL);
94 }
95 
cmd_md3list(int argc,char * argv[])96 void cmd_md3list(int argc, char *argv[])
97 {
98   getPAKContent(NULL, ".md3", argc>1?argv[1]:NULL);
99 }
100 
cmd_listfiles(int argc,char * argv[])101 void cmd_listfiles(int argc, char *argv[])
102 {
103   if (argc > 1)
104   {
105     getPAKContent(NULL, argv[1], argc>2?argv[2]:NULL);
106   }
107   else gConsole->Insertln("usage: %s <string> [<string>]", argv[0]);
108 }
109 
cmd_logpreviews(int argc,char * argv[])110 void cmd_logpreviews(int argc, char *argv[])
111 {
112   gConsole->Insertln("Writing previews into logfile...");
113   logPreviews();
114 }
115 
App(void)116 App::App(void)
117 {
118   sys_appstate = INACTIVE;
119 
120   _getcwd(app_path, PATH_LENGTH); // get current path
121   strcat(app_path, "\\");
122 
123   world   = NULL;
124 
125   clients = NULL;
126   demos = NULL;
127   numclients = 0;
128   curr_client = 0;
129 
130   memset(curr_map, '\0', 32*sizeof(char));
131   reloading = false;
132 
133   if (!(gCommands = new Commands))  ThrowException(ALLOCATION_ERROR, "App:App.gCommands");
134   if (!(gVars = new Vars))      ThrowException(ALLOCATION_ERROR, "App:App.gVars");
135   if (!(gAlias = new Alias))      ThrowException(ALLOCATION_ERROR, "App:App.gAlias");
136   if (!(gLogFile = new LogFile))    ThrowException(ALLOCATION_ERROR, "App:App.gLogFile");
137   if (!(gRender = new Render))    ThrowException(ALLOCATION_ERROR, "App:App.gRender");
138   if (!(gConsole = new Console))    ThrowException(ALLOCATION_ERROR, "App:App.gConsole");
139   if (!(gFramework = new FrameWork))  ThrowException(ALLOCATION_ERROR, "App:App.gFramework");
140   if (!(gOver = new Overlay))     ThrowException(ALLOCATION_ERROR, "App:App.gOver");
141   gOver->Init();
142 
143   gConsole->Insertln("<br/><b>^6----- Starting cake engine ------------------------------------</b>");
144 
145   #ifdef _DEBUG
146     gConsole->Insertln("^1%s ^0debug version of %s (%s)", _VERSION_, __DATE__, __TIME__);
147   #else
148     gConsole->Insertln("^2%s ^0release version of %s (%s)", _VERSION_, __DATE__, __TIME__);
149   #endif
150 
151   // Register variables
152   gVars->RegisterVar(r_path);
153   gVars->RegisterVar(r_mapsubdir);
154   gVars->RegisterVar(cg_drawFPS);
155   gVars->RegisterVar(cg_FPSrefreshrate);
156   gVars->RegisterVar(cg_drawtime);
157   gVars->RegisterVar(cg_drawflush);
158   gVars->RegisterVar(cg_drawtris);
159   gVars->RegisterVar(cg_drawverts);
160   gVars->RegisterVar(cg_apicalls);
161   gVars->RegisterVar(cg_showpos);
162   gVars->RegisterVar(cg_showrot);
163   gVars->RegisterVar(r_debug);
164   gVars->RegisterVar(sys_appstate);
165   gVars->RegisterVar(sys_soundsupport);
166   gVars->RegisterVar(r_targetlocationdist);
167 
168   // Add commands
169   gCommands->AddCommand("bsplist", cmd_bsplist, "display the list of bsp map names (if an argument is present, only names containing this argument will be displayed)");
170   gCommands->AddCommand("wavlist", cmd_wavlist, "display the list of wav (music and sounds) files (if an argument is present, only names containing this argument will be displayed)");
171   gCommands->AddCommand("md3list", cmd_md3list, "display the list of md3 files (if an argument is present, only names containing this argument will be displayed)");
172   gCommands->AddCommand("listfiles", cmd_listfiles, "display the list of all files matching with a substring");
173   gCommands->AddCommand("logpreviews", cmd_logpreviews, "write the map list with corresponding levelshot into the logfile");
174 }
175 
Init(void)176 void App::Init(void)
177 {
178   sys_appstate = INITIALIZING;
179 
180   gCommands->AddToHistory = false;
181 
182   // init the file environment
183   FileEnvironment::Init();
184   FileEnvironment::AddPath("./");
185 
186   // Read the config file
187   ConfigFile(CONFIG_FILENAME);
188 
189   Timer::Init();
190 
191   #ifdef WIN32
192   {
193     // OS Info
194     OSVERSIONINFO vinfo;
195     vinfo.dwOSVersionInfoSize = sizeof(vinfo);
196 
197     gLogFile->OpenFile();
198 
199     if (!GetVersionEx (&vinfo))
200       gConsole->Insertln("^5WARNING: Couldn't get OS info");
201 
202     gConsole->Insertln("<b>System infos :</b>");
203     gConsole->Insertln("\tWin32 platform");
204     gConsole->Insertln("\tBuild number : %d", vinfo.dwBuildNumber);
205     gConsole->Insertln("\tMajor version : %d", vinfo.dwMajorVersion);
206     gConsole->Insertln("\tMinor version : %d", vinfo.dwMinorVersion);
207     gConsole->Insertln("\tPlatform ID : %d", vinfo.dwPlatformId);
208     gConsole->Insertln("\tCSD version : %s", vinfo.szCSDVersion);
209     gConsole->Insertln("\tCurrent process ID : %d", _getpid());
210     gConsole->Insertln("\tCPU vendor name : %s", GetVendorString());
211     gConsole->Insertln("\tCPU speed : %d MHz", GetCpuSpeed());
212     gConsole->Insertln("\tCPU cache size : %d", GetCacheInfo());
213     if (GetCPUCaps(HAS_3DNOW)) gConsole->Insertln("\t3DNow! detected");
214     struct tm *newtime; time_t aclock; time(&aclock); newtime = localtime(&aclock);
215     gConsole->Insertln("\tSystem date and time : %s", asctime(newtime));
216 
217     gLogFile->CloseFile();
218   }
219   #else
220   {
221     gConsole->Insertln("\tNot a Win32 Platform");
222   }
223   #endif
224 
225   #if 0
226   { // in this case, last path is more important
227     // for instance, with "../base;../missionpack", the more
228     // important path is "../missionpack"
229     int i, j, l;
230     i = 0;
231     j = 0;
232     l = (int) strlen(r_path.svalue);
233     char buffer[256];
234     while (i < l)
235     {
236       memset(buffer, 0, 256);
237       j = 0;
238       while (i < l && r_path.svalue[i] != ';') buffer[j++] = r_path.svalue[i++];
239       if (buffer[j] != '/') buffer[j] = '/';
240       FileEnvironment::AddPath(buffer);
241       ++i;
242     }
243   }
244   #else
245   { // in this case, first path is more important
246     // for instance, with "../base;../missionpack", the more
247     // important path is "../base"
248     int i, j;
249     i = (int) strlen(r_path.svalue) - 1;
250     j = i;
251     char buffer[256];
252     while (i >= 0)
253     {
254       memset(buffer, 0, 256);
255       while (i >= 0 && r_path.svalue[i] != ';') --i;
256       if (i > 0) strncpy(buffer, &r_path.svalue[i+1], j-i);
257       else strncpy(buffer, r_path.svalue, j-i);
258       if (buffer[j-i] != '/') buffer[j-i] = '/';
259       FileEnvironment::AddPath(buffer);
260       j = --i;
261     }
262   }
263   #endif
264   FileEnvironment::dumpEnvironment();
265 
266   // Initialize sound system
267   if (!initSoundSystem())
268   {
269     gConsole->Insertln("^1Could not initialize sound system!");
270     sys_soundsupport = 0;
271   }
272   else sys_soundsupport = 1;
273 
274   // Loads packages
275   FileEnvironment::LoadPacks();
276 
277   #ifdef _DEBUG
278     getPAKContent(NULL, ".bsp");  // print a list of bsp files in log file
279     cg_drawFPS = 1;
280     cg_drawtime = 1;
281     cg_showpos = 1;
282     cg_showrot = 1;
283     cg_drawflush = 1;
284     cg_drawtris = 1;
285     cg_drawverts = 1;
286     cg_apicalls = 1;
287   #endif
288 
289   // Seed the random-number generator with current time so that
290   // the numbers will be different every time we run.
291   srand((unsigned)time(NULL));
292 
293   InitializeShaders();
294 
295   gCommands->AddToHistory = true;
296 }
297 
~App(void)298 App::~App(void)
299 {
300   if (sys_appstate.ivalue != INACTIVE)
301   {
302     sys_appstate = INACTIVE;
303     gLogFile->Insert("^5WARNING: cake was not shut correctly\n");
304   }
305 
306   // Creating new config file
307 /*  FILE *fp;
308   if (!(fp = fopen(CONFIG_FILENAME, "w")))
309   {
310     gLogFile->Insert("^1ERROR: Unable to create config file for writing\n");
311   }
312   else
313   {
314     fclose(fp);
315     gVars->SaveToFile(CONFIG_FILENAME);
316   }
317 */
318 
319   gVars->UnregisterVar(cg_showrot);
320   gVars->UnregisterVar(cg_showpos);
321   gVars->UnregisterVar(cg_drawtime);
322   gVars->UnregisterVar(cg_drawFPS);
323   gVars->UnregisterVar(cg_FPSrefreshrate);
324   gVars->UnregisterVar(cg_drawflush);
325   gVars->UnregisterVar(cg_drawtris);
326   gVars->UnregisterVar(cg_drawverts);
327   gVars->UnregisterVar(cg_apicalls);
328   gVars->UnregisterVar(r_debug);
329   gVars->UnregisterVar(r_mapsubdir);
330   gVars->UnregisterVar(r_path);
331   gVars->UnregisterVar(sys_appstate);
332   gVars->UnregisterVar(sys_soundsupport);
333   gVars->UnregisterVar(r_targetlocationdist);
334 
335   gCommands->RemoveCommand("bsplist");
336   gCommands->RemoveCommand("wavlist");
337   gCommands->RemoveCommand("md3list");
338   gCommands->RemoveCommand("listfiles");
339   gCommands->RemoveCommand("logpreviews");
340 
341   delete gOver; gOver = NULL;
342   delete gFramework; gFramework = NULL;
343   delete gConsole; gConsole = NULL;
344   delete gRender; gRender = NULL;
345   delete gVars; gVars = NULL;
346   delete gAlias; gAlias = NULL;
347   delete gCommands; gCommands = NULL;
348 
349   #ifdef DEBUG_MEM
350     if (get_references()) log_mem_report();
351     else gLogFile->Insert("No memory leak detected.\n");
352     delete_memregister();
353   #endif
354 
355   delete gLogFile; gLogFile = NULL;
356 }
357 
Run(void)358 void App::Run(void)
359 {
360   sys_appstate = RUNNING;
361 }
362 
Shut(void)363 void App::Shut(void)
364 {
365   sys_appstate = SHUTING;
366 
367   UnloadMap();
368 
369   if (clients) delete [] clients;
370   clients = NULL;
371   numclients = 0;
372 
373   DestroyShaderList();
374 
375   gFramework->Shut();
376   gConsole->Shut();
377   gRender->Shut();
378   gOver->Shut();
379 
380   sys_appstate = INACTIVE;
381 
382   gLogFile->Insert("<br/><b>^6----- cake engine shuting down --------------------------------</b>\n");
383   Timer::Refresh();
384   char time_text[64] =  { '\0' };
385   int minutes = (int)Timer::fTime/60;
386   int seconds = (int)fmod(Timer::fTime,60);
387   sprintf(time_text, "%d:", minutes);
388   if (seconds < 10) strcat(time_text, "0");
389   sprintf(time_text, "%s%d", time_text, seconds);
390   gLogFile->Insert("Execution time: %s\n", time_text);
391 
392   shutdownSoundSystem();
393 }
394 
DrawWorld(void)395 void App::DrawWorld(void)
396 {
397   if (!gRender->GetWidth() || !gRender->GetHeight() ||
398     !world || sys_appstate.ivalue != RUNNING) return;
399 
400   int i;
401 
402   // Updating demo
403   for (i = 0; i < numclients; ++i)
404   {
405     if (demos[i].isPlaying)
406     {
407       float demopercent = demos[i].NewFrame();
408       sprintf(demomsg, "playing demo (%d %%)", (int)(demopercent));
409     }
410     else if (demos[i].isRecording)
411     {
412       demos[i].NewFrame();
413       sprintf(demomsg, "recording");
414     }
415   }
416 
417   world->Render(clients, numclients);
418 
419   for (i = 0; i < numclients; ++i)
420   {
421     if ((demos[i].isRecording && (long)(Timer::fTime*2)%2) || demos[i].isPlaying)
422       gOver->String(demomsg, gFramework->GetFont(NORMAL_CHAR),
423         (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*strlen(demomsg))/2.f,
424         (float) gRender->GetHeight()-SMALL_CHAR_HEIGHT);
425   }
426 
427   // Draws the camera position
428   if (cg_showpos.ivalue && cg_showpos.ivalue <= numclients)
429   {
430     Client *client = &clients[cg_showpos.ivalue-1];
431 
432     char pos_text[128] = { '\0' };
433     float x, y;
434     sprintf(pos_text, "pos (%.3f;%.3f;%.3f)",
435       client->cam.pos[0],
436       client->cam.pos[1],
437       client->cam.pos[2]);
438 
439     x = (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(pos_text));
440     y = (float) gRender->GetHeight()-SMALL_CHAR_HEIGHT;
441 
442     gOver->String(pos_text, gFramework->GetFont(NORMAL_CHAR), x, y);
443   }
444 
445   // Draws the camera rotation
446   if (cg_showrot.ivalue && cg_showrot.ivalue <= numclients)
447   {
448     Client *client = &clients[cg_showrot.ivalue-1];
449 
450     char rot_text[128];
451     float x, y;
452 
453     // rotation
454     memset(rot_text, '\0', 128*sizeof(char));
455     sprintf(rot_text, "rot (%.3f;%.3f;%.3f)",
456       client->cam.rot[PITCH],
457       client->cam.rot[YAW],
458       client->cam.rot[ROLL]);
459 
460     x = (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(rot_text));
461     y = (float) gRender->GetHeight()-SMALL_CHAR_HEIGHT;
462     if (cg_showpos.ivalue) y -= (float) (SMALL_CHAR_HEIGHT);
463 
464     gOver->String(rot_text, gFramework->GetFont(NORMAL_CHAR), x, y);
465 
466     // forward vector
467     memset(rot_text, '\0', 128*sizeof(char));
468     sprintf(rot_text, "forward (%.3f;%.3f;%.3f)",
469       client->forward[0],
470       client->forward[1],
471       client->forward[2]);
472 
473     x = (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(rot_text));
474     y -= (float) (SMALL_CHAR_HEIGHT);
475 
476     gOver->String(rot_text, gFramework->GetFont(NORMAL_CHAR), x, y);
477 
478     // right
479     memset(rot_text, '\0', 128*sizeof(char));
480     sprintf(rot_text, "right (%.3f;%.3f;%.3f)",
481       client->right[0],
482       client->right[1],
483       client->right[2]);
484 
485     x = (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(rot_text));
486     y -= (float) (SMALL_CHAR_HEIGHT);
487 
488     gOver->String(rot_text, gFramework->GetFont(NORMAL_CHAR), x, y);
489 
490     // up
491     memset(rot_text, '\0', 128*sizeof(char));
492     sprintf(rot_text, "up (%.3f;%.3f;%.3f)",
493       client->up[0],
494       client->up[1],
495       client->up[2]);
496 
497     x = (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(rot_text));
498     y -= (float) (SMALL_CHAR_HEIGHT);
499 
500     gOver->String(rot_text, gFramework->GetFont(NORMAL_CHAR), x, y);
501   }
502 
503   gOver->Render(&gFramework->shaders);
504 }
505 
DrawInterface(void)506 void App::DrawInterface(void)
507 {
508   // Draws FPS
509   if (cg_drawFPS.ivalue && totallastfps)
510   {
511     char fps_text[64] = { '\0' };
512     if (1000.f*(Timer::fTime-(float)timelastfps) > cg_FPSrefreshrate.fvalue)
513     {
514       lastfps = (float)frameslastfps/totallastfps;
515       timelastfps = (float) Timer::fTime;
516       totallastfps = 0;
517       frameslastfps = 0;
518     }
519     totallastfps += (float)Timer::frametime;
520     ++frameslastfps;
521     sprintf(fps_text, "^%d%4.2f^0 fps", lastfps<25?1:lastfps>30?0:5, lastfps);
522     gOver->String(fps_text, gFramework->GetFont(NORMAL_CHAR), (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(fps_text)), 0);
523   }
524 
525   // Draw time
526   if (cg_drawtime.ivalue)
527   {
528     char time_text[64] =  { '\0' };
529     float y = 0;
530     int minutes = (int)Timer::fTime/60;
531     int seconds = (int)fmod(Timer::fTime,60);
532     if (cg_drawFPS.ivalue) y += SMALL_CHAR_HEIGHT;
533     sprintf(time_text, "%d:", minutes);
534     if (seconds < 10) strcat(time_text, "0");
535     sprintf(time_text, "%s%d", time_text, seconds);
536     gOver->String(time_text, gFramework->GetFont(NORMAL_CHAR), (float) (gRender->GetWidth()-SMALL_CHAR_WIDTH*_strlen(time_text)), y);
537   }
538 
539   // display rendering infos (bottom left)
540   if (cg_drawflush.ivalue)
541   {
542     char msg[64] =  { '\0' };
543     sprintf(msg, "flush calls: %d", (int) gRender->num_flush);
544     gOver->String(msg, gFramework->GetFont(NORMAL_CHAR), 0, (float)gRender->GetHeight()-SMALL_CHAR_HEIGHT);
545   }
546 
547   if (cg_drawtris.ivalue)
548   {
549     char msg[64] =  { '\0' };
550     float y = (float)gRender->GetHeight()-SMALL_CHAR_HEIGHT;
551     if (cg_drawflush.ivalue) y -= SMALL_CHAR_HEIGHT;
552     sprintf(msg, "rendered tris: %d", (int) gRender->num_tris);
553     gOver->String(msg, gFramework->GetFont(NORMAL_CHAR), 0, y);
554   }
555 
556   if (cg_drawverts.ivalue)
557   {
558     char msg[64] =  { '\0' };
559     float y = (float)gRender->GetHeight()-SMALL_CHAR_HEIGHT;
560     if (cg_drawflush.ivalue) y -= SMALL_CHAR_HEIGHT;
561     if (cg_drawtris.ivalue) y -= SMALL_CHAR_HEIGHT;
562     sprintf(msg, "rendered verts: %d", (int) gRender->num_verts);
563     gOver->String(msg, gFramework->GetFont(NORMAL_CHAR), 0, y);
564   }
565 
566   if (cg_apicalls.ivalue)
567   {
568     char msg[64] =  { '\0' };
569     float y = (float)gRender->GetHeight()-SMALL_CHAR_HEIGHT;
570     if (cg_drawflush.ivalue) y -= SMALL_CHAR_HEIGHT;
571     if (cg_drawtris.ivalue) y -= SMALL_CHAR_HEIGHT;
572     if (cg_drawverts.ivalue) y -= SMALL_CHAR_HEIGHT;
573     sprintf(msg, "api calls: %d", (int) gRender->num_apicalls);
574     gOver->String(msg, gFramework->GetFont(NORMAL_CHAR), 0, y);
575   }
576 
577   // target locations
578   /*
579   if (world)
580   {
581     EntityManager* ent = world->GetBSP()->GetEntities();
582     vec3_t vbuffer;
583     for (int i = 0; i < ent->num_target_location[0]; ++i)
584     {
585       VectorSub(ent->first_target_speaker[i].origin, gRender->current_camera->pos, vbuffer);
586       if (vbuffer[0]*vbuffer[0]+vbuffer[1]*vbuffer[1]+vbuffer[2]+vbuffer[2] < r_targetlocationdist.fvalue)
587       {
588         gOver->String(ent->first_target_location[i].message,
589         gFramework->GetFont(NORMAL_CHAR),
590         (gRender->GetWidth()-(float)(SMALL_CHAR_WIDTH*(int)strlen(ent->first_target_location[i].message))/2),
591         gRender->GetHeight()/2+(float)SMALL_CHAR_WIDTH);
592       }
593     }
594   }
595   */
596 
597   gOver->Render(&gFramework->shaders);
598 
599   gConsole->Update();
600   gConsole->Render();
601 }
602 
GetFPS(void)603 double App::GetFPS(void)
604 {
605   return 1/Timer::frametime;
606 }
607 
GetWorld(void)608 World* App::GetWorld(void)
609 {
610   return world;
611 }
612 
GetCurrentMapName(void)613 const char* App::GetCurrentMapName(void)
614 {
615   return curr_map;
616 }
617 
LoadMap(const char * map_name)618 int App::LoadMap(const char* map_name)
619 {
620   int reload = !stricmp(curr_map, map_name) && reloading;
621   reloading = false;
622 
623   // Get a start position for client
624   if (!reload)
625   {
626     // Create new clients
627     if (!CreateClients(1))
628     {
629       gConsole->Insertln("^1Currently unable to create new clients. Loading aborted.");
630       return 0;
631     }
632   }
633 
634   UnloadMap();
635 
636   strncpy(curr_map, map_name, 32);
637 
638   Timer::Refresh();
639   float startloadingtime = (float) Timer::fTime;
640 
641   gConsole->Insertln("Loading map %s...", map_name);
642 
643   world = new World;
644   if (!world) ThrowException(ALLOCATION_ERROR, "App::LoadMap.world");
645 
646   // load a map, if one was specified:
647   // (objects must be created before shader loading because here we add the shader reference)
648   // TODO: objects should be loaded throug the world, the world should be an object factory
649   if (!world->AddMap(map_name))
650   {
651     delete world;
652     world = NULL;
653     gConsole->Insertln("Map %s not found.", map_name);
654     return 0;
655   }
656 
657   gConsole->SetState(CLOSED);
658   gConsole->addToMessages = false;
659 
660   world->Init();
661 
662   // Get a start position for client
663   if (!reload)
664   {
665     if (world->GetNumStartPos())
666     {
667       // get a random start pos
668       world->SetStartPos(&clients[0], rand()%world->GetNumStartPos());
669     }
670     else gConsole->Insertln("^1ERROR: Couldn't find a spawnpoint.");
671   }
672   else
673   {
674     // If map is reloaded, clients stay at same place as they were before
675     // unloading the map. The internal cluster and leaf camera values are
676     // allways defined but are not valid any more for new map. Following
677     // function call will reinit these values. If they are not reinitialized
678     // screen (and current leaf/cluster) won't be updated until camera
679     // changes leaf/cluster.
680     for (int i = 0; i < numclients; ++i) clients[i].UpdateCam();
681   }
682 
683   Timer::Refresh();
684   gConsole->Insertln("Map loaded successfully in %f sec.", (float)Timer::fTime-startloadingtime);
685   gConsole->addToMessages = true;
686 
687   return 1;
688 }
689 
ReloadMap(void)690 int App::ReloadMap(void)
691 {
692   if (strlen(curr_map))
693   {
694     reloading = true;
695     return LoadMap(curr_map);
696   }
697   else
698   {
699     gConsole->Insertln("^5No map running.");
700     return 0;
701   }
702 }
703 
UnloadMap(void)704 void App::UnloadMap(void)
705 {
706   if (world)
707   {
708     gConsole->Insertln("Unloading map...");
709     world->Shut();
710     delete world;
711     world = NULL;
712 
713     gConsole->Resize(-1, gRender->GetHeight());
714   }
715 }
716 
CreateClients(int num)717 int App::CreateClients(int num)
718 {
719   if (!num) return 0;
720 
721   // Check if already one demo is recording
722   for (int i = 0; i < numclients; ++i)
723   {
724     Demo *demo = &demos[i];
725     if (demos[i].isRecording || demos[i].isPlaying)
726     {
727       gConsole->Insertln("^1Unable to change number of clients when a demo is recording or playing.");
728       return 0;
729     }
730   }
731 
732   if (clients) delete [] clients;
733   clients = new Client[num];
734   if (!clients) ThrowException(ALLOCATION_ERROR, "World::CreateClients.clients");
735   numclients = num;
736   curr_client = 0;
737   InitClients();
738 
739   if (demos) delete [] demos;
740   demos = new Demo[num];
741   if (!demos) ThrowException(ALLOCATION_ERROR, "World::CreateClients.demos");
742 
743   return 1;
744 }
745 
GetClient(int num)746 Client* App::GetClient(int num)
747 {
748   if (!clients) return NULL;
749   if (num < 0 || num >= numclients) return NULL;
750   return &clients[num];
751 }
752 
GetNumClients(void)753 int App::GetNumClients(void)
754 {
755   return numclients;
756 }
757 
SetCurrentClient(int num)758 void App::SetCurrentClient(int num)
759 {
760   if (num < 0 || num >= numclients) return;
761   curr_client = num;
762 
763   for (int i = 0; i < numclients; ++i) clients[i].isCurrent = false;
764   clients[num].isCurrent = true;
765 }
766 
GetCurrentClient(void)767 Client* App::GetCurrentClient(void)
768 {
769   if (clients) return &clients[curr_client];
770   else return NULL;
771 }
772 
GetCurrentClientIndex(void)773 int App::GetCurrentClientIndex(void)
774 {
775   if (!numclients) return -1;
776   return curr_client;
777 }
778 
779 // Initialize the clients
InitClients(void)780 void App::InitClients(void)
781 {
782   if (!numclients) return;
783   int a = numclients, b = 1, c = numclients, i, j;
784 
785   // Get the possible values
786   for (i = 1; i <= c; ++i)
787   {
788     for (j = i; j <= c; ++j)
789     {
790       if (c == i*j)
791       {
792         a = j;
793         b = i;
794       }
795     }
796   }
797 
798   // Create the windows
799   int l, t, w, h;
800   w = gRender->GetWidth()/a;
801   h = gRender->GetHeight()/b;
802   l = 0, t = 0;
803 
804   for (i = 0; i < numclients; ++i)
805   {
806     clients[i].Init(l, t, w, h);
807     l += w;
808     if (!((i+1) % a))
809     {
810       l = 0;
811       t += h;
812     }
813   }
814   curr_client = 0;
815 
816   for (i = 0; i < numclients; ++i)
817     clients[i].isCurrent = (i==curr_client)?true:false;
818 }
819 
RecordDemo(float framesinterval)820 void App::RecordDemo(float framesinterval)
821 {
822   if (!numclients)
823   {
824     gConsole->Insertln("^5WARNING: No available client !");
825     return;
826   }
827 
828   // Check if already one demo is recording
829   for (int i = 0; i < numclients; ++i)
830   {
831     if (demos[i].isRecording)
832     {
833       gConsole->Insertln("^5Already recording a demo.");
834       return;
835     }
836   }
837 
838   Demo* demo = &demos[curr_client];
839 
840   if (demo->StartDemoRecording(&clients[curr_client], curr_map, framesinterval))
841   {
842     gConsole->Close();
843     gConsole->Insertln("Start recording...");
844     gConsole->Insertln("Stop recording by entering \"stopdemo <filename>\" or pressing ESC (default demo filename will be used).");
845   }
846   else ThrowException(DEFAULT_EXCEPTION, "Invalid demo state !");
847 }
848 
StopDemo(const char * destfile)849 int App::StopDemo(const char *destfile)
850 {
851   if (!numclients)
852   {
853     gConsole->Insertln("^5WARNING: No available client !");
854     return 0;
855   }
856 
857   Demo* demo = &demos[curr_client];
858 
859   if (demo->isRecording)
860   {
861     char demofilename[32] = { '\0' };
862 
863     // stop recording
864     if (destfile)
865     {
866       strncpy(demofilename, destfile, 31);
867     }
868     else
869     {
870       // stop recording demo and save to default file
871       int c, d, u;
872       do {  // skip existing files
873         c = demofilenameindex/100;
874         d = (demofilenameindex-c*100)/10;
875         u = (demofilenameindex-c*100-d*10);
876         memset(demofilename, 0, 32);
877         sprintf(demofilename, "cake%d%d%d.demo", c, d, u);
878         ++demofilenameindex;
879       } while(fopen(demofilename, "r"));
880     }
881 
882     if (demo->StopDemoRecording(demofilename))
883       gConsole->Insertln("Demo saved into \"%s\"", demofilename);
884     else
885       gConsole->Insertln("^1Demo saving abordted.");
886 
887     return 1;
888   }
889   else if (demo->isPlaying)
890   {
891     // stop playing
892     demo->StopDemoPlaying();
893     gConsole->Insertln("Demo stopped.");
894     return 2;
895   }
896   else return 0;
897 }
898 
PlayDemo(const char * demofile,bool loop,enum_InterpolationMode mode)899 void App::PlayDemo(const char *demofile, bool loop, enum_InterpolationMode mode)
900 {
901   Demo* curr_demo;
902 
903   if (numclients)
904   {
905     curr_demo = &demos[curr_client];
906 
907     if (curr_demo->isPlaying)
908     {
909       gConsole->Insertln("^1Demo is currently playing");
910       return;
911     }
912 
913     if (curr_demo->isRecording)
914     {
915       gConsole->Insertln("^1Demo is currently recording");
916       return;
917     }
918   }
919 
920   // if no world, we need to create a temporal demo until world is created
921   if (!world || !numclients)
922   {
923     curr_demo = new Demo;
924     if (!curr_demo) ThrowException(ALLOCATION_ERROR, "App::PlayDemo.curr_demo");
925   }
926 
927   // Get the map to be loaded for demo running
928   char mapname[32] = { '\0' };
929   strcpy(mapname, curr_demo->LoadDemo(demofile, mode));
930 
931   if (!strlen(mapname))
932   {
933     // Wrong mapname
934     gConsole->Insertln("Demo loading aborted.");
935     delete curr_demo;
936     return;
937   }
938 
939   // if current map is not same as needed map
940   if (!world || stricmp(curr_map, mapname))
941   {
942     if (!world) delete curr_demo; // demo will be re-created
943     if (! LoadMap(mapname)) return;
944 
945     // if is reloaded, demo is destroyed and re-created, then we
946     // need to reload the demo for default client
947     curr_demo = &demos[curr_client];
948     curr_demo->LoadDemo(demofile, mode);
949   }
950 
951   gConsole->Insertln("Starting demo...");
952   curr_demo->StartDemoPlaying(&clients[curr_client], loop);
953 }
954