]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/client/Main.qc
CSQC part for the new score system (not really finished yet, needs cleaning)
[divverent/nexuiz.git] / data / qcsrc / client / Main.qc
1 // --------------------------------------------------------------------------\r
2 // BEGIN REQUIRED CSQC FUNCTIONS\r
3 //include "main.qh"\r
4 \r
5 void() menu_show_error =\r
6 {\r
7         drawstring('0 200', "ERROR - MENU IS VISIBLE BUT NO MENU WAS DEFINED!", '8 8 0', '1 0 0', 1, 0);\r
8 };\r
9 \r
10 // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)\r
11 // Useful for precaching things\r
12 \r
13 void() menu_sub_null =\r
14 {\r
15 };\r
16 \r
17 // let's make this a general data buffer...\r
18 float using_gps;\r
19 \r
20 #ifdef USE_FTE\r
21 float __engine_check;\r
22 #endif\r
23 \r
24 string config_get(string key, string defaultvalue)\r
25 {\r
26         string s;\r
27         s = db_get(configdb, strcat("/s/", key));\r
28         if(s == "")\r
29                 return defaultvalue;\r
30         else\r
31                 return db_get(configdb, strcat("/v/", key));\r
32 }\r
33 \r
34 void CSQC_Init(void)\r
35 {\r
36 #ifdef USE_FTE\r
37 #pragma target ID\r
38         __engine_check = checkextension("DP_SV_WRITEPICTURE");\r
39         if(!__engine_check)\r
40         {\r
41                 print("^3Your engine build is outdated\n^3This Server uses a newer QC VM. Please update!\n");\r
42                 localcmd("\ndisconnect\n");\r
43                 return;\r
44         }\r
45 #pragma target FTE\r
46 #endif\r
47         \r
48         float i;\r
49         CSQC_CheckEngine();\r
50 \r
51         configdb = db_create();\r
52 \r
53         drawfont = 0;\r
54         menu_visible = FALSE;\r
55         menu_show = menu_show_error;\r
56         menu_action = menu_sub_null;\r
57         using_gps = false;\r
58         //ctf_temp_1 = "";\r
59         // localcmd("alias order \"cmd order $*\""); enable if ctf-command thingy is used\r
60         //registercmd("ctf_menu");\r
61         registercmd("ons_map");\r
62         //registercmd("menu_action");\r
63         registercmd("sbar_columns_set");\r
64         registercmd("sbar_columns_help");\r
65 \r
66         registercvar("sbar_usecsqc", "1");\r
67         registercvar("sbar_columns", "ping name | caps returns frags deaths", CVAR_SAVE);\r
68 \r
69         gametype = 0;\r
70 \r
71         gps_start = world;\r
72 \r
73         // sbar_fields uses strunzone on the titles!\r
74         for(i = 0; i < MAX_SBAR_FIELDS; ++i)\r
75                 sbar_title[i] = strzone("(null)");\r
76 \r
77         postinit = false;\r
78 \r
79         teams = Sort_Spawn();\r
80         players = Sort_Spawn();\r
81         \r
82         teamspec = AddTeam(COLOR_SPECTATOR); // add specs first\r
83 }\r
84 \r
85 // CSQC_Shutdown : Called every time the CSQC code is shutdown (changing maps, quitting, etc)\r
86 void CSQC_Shutdown(void)\r
87 {\r
88 #ifdef USE_FTE\r
89 #pragma TARGET id\r
90         if(!__engine_check)\r
91                 return 0;\r
92 #pragma TARGET fte\r
93 #endif\r
94 \r
95         remove(teams);\r
96         remove(players);\r
97         db_close(configdb);\r
98         buf_del(databuf);\r
99 }\r
100 \r
101 void PostInit(void)\r
102 {\r
103         float i;\r
104 \r
105         print(strcat("PostInit\n    maxclients = ", ftos(maxclients), "\n"));\r
106         databuf = buf_create();\r
107         for(i = 0; i < maxclients; ++i)\r
108         {\r
109                 bufstr_set(databuf, DATABUF_PING + i, "N/A");\r
110                 bufstr_set(databuf, DATABUF_DEATHS + i, "0");\r
111                 bufstr_set(databuf, DATABUF_CAPTURES + i, "0");\r
112                 bufstr_set(databuf, DATABUF_RETURNS + i, "0");\r
113         }\r
114         \r
115         localcmd(strcat("\nsbar_columns_set ", cvar_string("sbar_columns"), ";\n"));\r
116 \r
117         postinit = true;\r
118 }\r
119 \r
120 // CSQC_ConsoleCommand : Used to parse commands in the console that have been registered with the "registercmd" function\r
121 // Return value should be 1 if CSQC handled the command, otherwise return 0 to have the engine handle it.\r
122 void Cmd_Sbar_SetFields(float);\r
123 void Cmd_Sbar_Help(float);\r
124 float CSQC_ConsoleCommand(string strMessage)\r
125 {\r
126         float argc;\r
127         // Tokenize String\r
128         argc = tokenize(strMessage);\r
129         \r
130         // Acquire Command\r
131         local string strCmd;\r
132         strCmd = argv(0);\r
133 \r
134         /*if(strCmd == "ctf_menu") {\r
135                 ctf_menu_show();\r
136                 nReturn = true;\r
137                 } else*/\r
138         if(strCmd == "ons_map") {\r
139                 Cmd_ons_map();\r
140                 return true;\r
141         } else if(strCmd == "sbar_columns_set") {\r
142                 Cmd_Sbar_SetFields(argc);\r
143                 return true;\r
144         } else if(strCmd == "sbar_columns_help") {\r
145                 Cmd_Sbar_Help(argc);\r
146                 return true;\r
147         } else if(strCmd == "+showscores") {\r
148                 sb_showscores = true;\r
149                 return true;\r
150         } else if(strCmd == "-showscores") {\r
151                 sb_showscores = false;\r
152                 return true;\r
153         }\r
154         \r
155         return false;\r
156 }\r
157 \r
158 float GameCommand(string msg)\r
159 {\r
160         float argc;\r
161         argc = tokenize(msg);\r
162         string cmd;\r
163         cmd = argv(0);\r
164         if(cmd == "mv_download") {\r
165                 Cmd_MapVote_MapDownload(argc);\r
166                 return true;\r
167         }\r
168         \r
169         return false;\r
170 }\r
171 \r
172 // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client.\r
173 // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine.\r
174 // All keys are in ascii.\r
175 // bInputType = 0 is key pressed, 1 is key released, 2 is mouse input.\r
176 // In the case of keyboard input, nPrimary is the ascii code, and nSecondary is 0.\r
177 // In the case of mouse input, nPrimary is xdelta, nSecondary is ydelta.\r
178 float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)\r
179 {\r
180         local float bSkipKey;\r
181         bSkipKey = false;\r
182         \r
183         if(menu_visible)\r
184                 if(menu_action(bInputType, nPrimary, nSecondary))\r
185                         return TRUE;\r
186         return bSkipKey;\r
187 }\r
188 \r
189 // END REQUIRED CSQC FUNCTIONS\r
190 // --------------------------------------------------------------------------\r
191 \r
192 // --------------------------------------------------------------------------\r
193 // BEGIN OPTIONAL CSQC FUNCTIONS\r
194 void Ent_ReadONS()\r
195 {\r
196         entity gps;\r
197         using_gps = true;\r
198 \r
199         self.origin_x = ReadCoord();\r
200         self.origin_y = ReadCoord();\r
201         self.angles_y = ReadCoord();\r
202         self.origin_z = self.angles_x = self.angles_z = 0;\r
203 \r
204         for(gps = gps_start; gps; gps = gps.chain)\r
205         {\r
206                 if(gps == self)\r
207                         break;\r
208         }\r
209         if(!gps)\r
210         {\r
211                 self.chain = gps_start;\r
212                 gps_start = self;\r
213         }\r
214 }\r
215 \r
216 void Ent_RemoveONS()\r
217 {\r
218         if(gps_start == self)\r
219                 gps_start = self.chain;\r
220         else\r
221         {\r
222                 local entity ent;\r
223                 ent = gps_start;\r
224                         \r
225                 while(ent.chain != self && ent.chain != world)\r
226                         ent = ent.chain;\r
227                 if(ent.chain == self)\r
228                         ent.chain = self.chain;\r
229         }\r
230 }\r
231 \r
232 void Ent_ReadScoresInfo()\r
233 {\r
234         float i;\r
235         for(i = 0; i < MAX_SCORE; ++i)\r
236         {\r
237                 scores_label[i] = strzone(ReadString());\r
238                 scores_flags[i] = ReadByte();\r
239         }\r
240         for(i = 0; i < MAX_TEAMSCORE; ++i)\r
241         {\r
242                 teamscores_label[i] = strzone(ReadString());\r
243                 teamscores_flags[i] = ReadByte();\r
244         }\r
245         Sbar_InitScores();\r
246 }\r
247 \r
248 void Ent_ReadPlayerScore(float isNew)\r
249 {\r
250         float i, Team;\r
251         entity tm;\r
252 \r
253         // damnit -.- don't want to go change every single .sv_entnum in sbar.qc AGAIN\r
254         // (no I've never heard of M-x replace-string, sed, or anything like that)\r
255         self.sv_entnum = ReadByte()-1;\r
256         Team = GetPlayerColor(self.sv_entnum);\r
257 \r
258         if(isNew)\r
259                 RegisterPlayer(self);\r
260 \r
261         if(isNew || Team != self.team)\r
262         {\r
263                 if(!isNew)\r
264                 {\r
265                         tm = GetTeam(self.team, false);\r
266                         tm.team_size -= 1;\r
267                 }\r
268                 \r
269                 self.team = Team;\r
270                 tm = GetTeam(Team, true);\r
271                 tm.team_size += 1;\r
272         }\r
273 \r
274         for(i = 0; i < MAX_SCORE; ++i)\r
275                 self.(scores[i]) = ReadShort();\r
276 \r
277         Sbar_UpdatePlayerPos(self);\r
278 }\r
279 \r
280 void Ent_ReadTeamScore(float isNew)\r
281 {\r
282         float i;\r
283         \r
284         self.team = ReadByte();\r
285 \r
286         if(isNew)\r
287                 RegisterTeam(self);\r
288 \r
289         for(i = 0; i < MAX_TEAMSCORE; ++i)\r
290                 self.(teamscores[i]) = ReadShort();\r
291 \r
292         Sbar_UpdateTeamPos(self);\r
293 }\r
294 \r
295 // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured.\r
296 // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.\r
297 void(float bIsNewEntity) CSQC_Ent_Update =\r
298 {\r
299         float msg;\r
300         self.enttype = ReadByte();\r
301         if(self.enttype == ENT_CLIENT_ENTCS)\r
302         {\r
303                 self.sv_entnum = ReadByte()-1;\r
304 \r
305                 for(msg = ReadByte(); msg != ENTCS_MSG_END; msg = ReadByte())\r
306                 {\r
307                         switch(msg)\r
308                         {\r
309                         case ENTCS_MSG_ONS_GPS: Ent_ReadONS(); break;\r
310                         case ENTCS_MSG_ONS_REMOVE: Ent_RemoveONS(); break;\r
311                         default:\r
312                                 error("unknown ENTCS_MSG type\n");\r
313                         }\r
314                 }\r
315         }\r
316         else if(self.enttype == ENT_CLIENT_SCORES_INFO)\r
317                 Ent_ReadScoresInfo();\r
318         else if(self.enttype == ENT_CLIENT_SCORES)\r
319                 Ent_ReadPlayerScore(bIsNewEntity);\r
320         else if(self.enttype == ENT_CLIENT_TEAMSCORES)\r
321                 Ent_ReadTeamScore(bIsNewEntity);\r
322         else\r
323                 error("unknown entity type in CSQC_Ent_Update\n");\r
324         \r
325 };\r
326 // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed.  Essentially call remove(self) as well.\r
327 void CSQC_Ent_Remove()\r
328 {\r
329         if(self.enttype == ENT_CLIENT_ENTCS)\r
330         {\r
331                 if(using_gps) //gametype == GAME_ONSLAUGHT)\r
332                 {\r
333                         if(gps_start == self)\r
334                                 gps_start = self.chain;\r
335                         else\r
336                         {\r
337                                 local entity ent;\r
338                                 ent = gps_start;\r
339                         \r
340                                 while(ent.chain != self && ent.chain != world)\r
341                                         ent = ent.chain;\r
342                                 if(ent.chain == self)\r
343                                         ent.chain = self.chain;\r
344                         }\r
345                 }\r
346         } else if(self.enttype == ENT_CLIENT_SCORES_INFO)\r
347         {\r
348                 // OH NOES!! WE LOST DA SCORES INFO ENTITY\r
349                 print("The world is going to explode.");\r
350                 // kkthxbai\r
351         } else if(self.enttype == ENT_CLIENT_SCORES)\r
352         {\r
353                 entity tm;\r
354                 print("lost a client score\n");\r
355                 tm = GetTeam(self.team, false);\r
356                 tm.team_size -= 1;\r
357                 RemovePlayer(self);\r
358         } else if(self.enttype == ENT_CLIENT_TEAMSCORES)\r
359         {\r
360                 RemoveTeam(self);\r
361         }\r
362         remove(self);\r
363 }\r
364 \r
365 void Gamemode_Init()\r
366 {\r
367         local string mapinfo, infoline;\r
368         local float len;\r
369         local float file;\r
370         local vector mi_min, mi_max;\r
371 \r
372         gametype = cvar("gametype");\r
373         if(gametype == GAME_ONSLAUGHT) {\r
374                 if(!strcasecmp(substring(mapname, 0, 5), "maps/"))\r
375                         minimapname = substring(mapname, 5, 999);\r
376                 else\r
377                         minimapname = mapname;\r
378                 len = strlen(minimapname);\r
379                 if(!strcasecmp(substring(minimapname, len-4, 4), ".bsp"))\r
380                         minimapname = substring(minimapname, 0, len-4);\r
381                 \r
382                 mapinfo = strcat("maps/", minimapname, ".info");\r
383                 minimapname = strzone(strcat("gfx/", minimapname, "_mini.tga"));\r
384 \r
385                 mi_min = world.mins;\r
386                 mi_max = world.maxs;\r
387                 \r
388                 file = fopen(mapinfo, FILE_READ);\r
389                 if(file >= 0) {\r
390                         while((infoline = fgets(file))) {\r
391                                 if(!strncasecmp(infoline, "mins", 4)) {\r
392                                         mi_min = stov(substring(infoline, 5, 999));\r
393                                 } else if(!strncasecmp(infoline, "maxs", 4)) {\r
394                                         mi_max = stov(substring(infoline, 5, 999));\r
395                                 } else if(strncasecmp(infoline, "//", 2)) { // don't print comment-style lines\r
396                                         print(strcat("mapinfo: ", infoline, "\n"));\r
397                                 }\r
398                         }\r
399                 } else {\r
400                         print(strcat("Map has no .info file (", mapinfo, ").\n"));\r
401                 }\r
402                 fclose(file);\r
403 \r
404                 print(strcat("Mins: ", vtos(mi_min), "    Maxs: ", vtos(mi_max), "\n"));\r
405                 \r
406                 mi_center = (mi_min + mi_max) * 0.5;\r
407                 mi_scale = mi_max - mi_min;\r
408                 \r
409                 \r
410                 print(strcat("Using ", minimapname, " as minimap.\n"));\r
411                 precache_pic(minimapname);\r
412                 precache_pic("gfx/ons-cp-neutral.tga");\r
413                 precache_pic("gfx/ons-cp-red.tga");\r
414                 precache_pic("gfx/ons-cp-blue.tga");\r
415                 precache_pic("gfx/ons-frame.tga");\r
416                 precache_pic("gfx/ons-frame-team.tga");\r
417         } else if(gametype == GAME_KEYHUNT) {\r
418                 precache_pic("gfx/sb_key_carrying");\r
419                 precache_pic("gfx/sb_key_carrying_outline");\r
420         }\r
421 }\r
422 // CSQC_Parse_StuffCmd : Provides the stuffcmd string in the first parameter that the server provided.  To execute standard behavior, simply execute localcmd with the string.\r
423 void CSQC_Parse_StuffCmd(string strMessage)\r
424 {\r
425         localcmd(strMessage);\r
426 }\r
427 // CSQC_Parse_Print : Provides the print string in the first parameter that the server provided.  To execute standard behavior, simply execute print with the string.\r
428 void CSQC_Parse_Print(string strMessage)\r
429 {\r
430         print(strMessage);\r
431 }\r
432 // CSQC_Parse_CenterPrint : Provides the centerprint string in the first parameter that the server provided.  To execute standard behavior, simply execute cprint with the string.\r
433 void CSQC_Parse_CenterPrint(string strMessage)\r
434 {\r
435         cprint(strMessage);\r
436 }\r
437 \r
438 void CSQC_CheckRevision();\r
439 \r
440 void Net_ReadInit()\r
441 {\r
442         csqc_revision = ReadShort();\r
443         maxclients = ReadByte();\r
444 \r
445         CSQC_CheckRevision();\r
446 }\r
447 \r
448 void Net_ReadPings()\r
449 {\r
450         float plnum, ping;\r
451         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
452         {\r
453                 ping = ReadShort();\r
454                 bufstr_set(databuf, DATABUF_PING + plnum-1, ftos(ping));\r
455         }\r
456 }\r
457 \r
458 void Net_ReadCaptures()\r
459 {\r
460         float plnum, caps, mode;\r
461         mode = ReadByte();\r
462         caps_team1 = ReadByte();\r
463         caps_team2 = ReadByte();\r
464         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
465         {\r
466                 caps = ReadByte();\r
467                 bufstr_set(databuf, DATABUF_CAPTURES + plnum-1, ftos(caps));\r
468         }\r
469 }\r
470 \r
471 void Net_ReadDatabuf(float ofs)\r
472 {\r
473         float plnum, data;\r
474         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
475         {\r
476                 data = ReadByte();\r
477                 bufstr_set(databuf, ofs + plnum-1, ftos(data));\r
478         }\r
479 }\r
480 \r
481 string Net_ReadPicture()\r
482 {\r
483         string img;\r
484         if(csqc_flags & CSQC_FLAG_READPICTURE)\r
485         {\r
486                 img = ReadPicture();\r
487                 print(strcat("Got Picture: ", img, "\n"));\r
488         } else {\r
489                 img = ReadString();\r
490                 print(strcat("^3Warning: ^7Couldn't download ", img, ". This is probably because your engine build is outdated.\n"));\r
491                 float psize, i;\r
492                 psize = ReadShort();\r
493                 // Can I be sure that ReadShort is 2 bytes and ReadLong is 4 bytes?\r
494                 // Because then this could be optimized to first get all 4-byte-groups,\r
495                 // then the remaining 2, then the remaining 1\r
496                 for(i = 0; i < psize; ++i)\r
497                         ReadByte();\r
498         }\r
499         return img;\r
500 }\r
501 \r
502 void Net_Config()\r
503 {\r
504         string key, value;\r
505         key = ReadString();\r
506         value = ReadString();\r
507         db_put(configdb, strcat("/v/", key), value);\r
508         db_put(configdb, strcat("/s/", key), "1");\r
509 }\r
510 \r
511 // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer.\r
512 // You must ALWAYS first acquire the temporary ID, which is sent as a byte.\r
513 // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event.\r
514 float CSQC_Parse_TempEntity()\r
515 {\r
516         local float bHandled;\r
517                 bHandled  = true;\r
518         // Acquire TE ID\r
519         local float nTEID;\r
520                 nTEID = ReadByte();\r
521 \r
522                 // NOTE: Could just do return instead of break...\r
523         switch(nTEID)\r
524         {\r
525                 case TE_CSQC_INIT:\r
526                         Net_ReadInit();\r
527                         bHandled = true;\r
528                         break;\r
529                 case TE_CSQC_PING:\r
530                         Net_ReadPings();\r
531                         bHandled = true;\r
532                         break;\r
533                 case TE_CSQC_CAPTURES:\r
534                         Net_ReadCaptures();\r
535                         bHandled = true;\r
536                         break;\r
537                 case TE_CSQC_RETURNS:\r
538                         Net_ReadDatabuf(DATABUF_RETURNS);\r
539                         bHandled = true;\r
540                         break;\r
541                 case TE_CSQC_DEATHS:\r
542                         Net_ReadDatabuf(DATABUF_DEATHS);\r
543                         bHandled = true;\r
544                         break;\r
545                 case TE_CSQC_MAPVOTE:\r
546                         Net_Mapvote();\r
547                         bHandled = true;\r
548                         break;\r
549                 case TE_CSQC_CONFIG:\r
550                         Net_Config();\r
551                         bHandled = true;\r
552                         break;\r
553                 default:\r
554                         // No special logic for this temporary entity; return 0 so the engine can handle it\r
555                         bHandled = false;\r
556                         break;\r
557         }\r
558         \r
559         if(!postinit)\r
560                 PostInit();\r
561                 \r
562         return bHandled;\r
563 }\r
564 \r
565 // COMMIT-TODO: Update if necessare, before committing\r
566 float csqc_svn_map[CSQC_REVISION] =\r
567 {\r
568         3812, // 3795,\r
569         3820 // mapvote protocol changed from there\r
570 };\r
571 \r
572 // COMMIT-TODO: Update if necessare, before committing\r
573 void CSQC_CheckRevision()\r
574 {\r
575         if(csqc_revision == CSQC_REVISION)\r
576         {\r
577                 print("^2SVQC and CSQC revisions are compatible.\n");\r
578         } else if(csqc_revision < CSQC_REVISION) {\r
579                 print("^1Your csprogs.dat (CSQC) version is newer than the one on the server.\n");\r
580                 print("^1The last known svn revision for the server's CSQC is: ^7");\r
581                 print(ftos(csqc_svn_map[csqc_revision])); // don't use strcat, fteqcc loves screwing up arrays...\r
582                 print("\n");\r
583         } else if(csqc_revision > CSQC_REVISION) {\r
584                 print("^1Your csprogs.dat (CSQC) is too old for this server.\n");\r
585                 print("^1Please update to a newer version.\n");\r
586         }\r
587 }\r