]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/client/Main.qc
LMS works!
[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 0', "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", "default", 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         argc = tokenizebyseparator(strMessage, " ");\r
130         \r
131         // Acquire Command\r
132         local string strCmd;\r
133         strCmd = argv(0);\r
134 \r
135         /*if(strCmd == "ctf_menu") {\r
136                 ctf_menu_show();\r
137                 nReturn = true;\r
138                 } else*/\r
139         if(strCmd == "ons_map") {\r
140                 Cmd_ons_map();\r
141                 return true;\r
142         } else if(strCmd == "sbar_columns_set") {\r
143                 Cmd_Sbar_SetFields(argc);\r
144                 return true;\r
145         } else if(strCmd == "sbar_columns_help") {\r
146                 Cmd_Sbar_Help(argc);\r
147                 return true;\r
148         } else if(strCmd == "+showscores") {\r
149                 sb_showscores = true;\r
150                 return true;\r
151         } else if(strCmd == "-showscores") {\r
152                 sb_showscores = false;\r
153                 return true;\r
154         }\r
155         \r
156         return false;\r
157 }\r
158 \r
159 float GameCommand(string msg)\r
160 {\r
161         float argc;\r
162         argc = tokenize(msg);\r
163         string cmd;\r
164         cmd = argv(0);\r
165         if(cmd == "mv_download") {\r
166                 Cmd_MapVote_MapDownload(argc);\r
167                 return true;\r
168         }\r
169         \r
170         return false;\r
171 }\r
172 \r
173 // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client.\r
174 // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine.\r
175 // All keys are in ascii.\r
176 // bInputType = 0 is key pressed, 1 is key released, 2 is mouse input.\r
177 // In the case of keyboard input, nPrimary is the ascii code, and nSecondary is 0.\r
178 // In the case of mouse input, nPrimary is xdelta, nSecondary is ydelta.\r
179 float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)\r
180 {\r
181         local float bSkipKey;\r
182         bSkipKey = false;\r
183         \r
184         if(menu_visible)\r
185                 if(menu_action(bInputType, nPrimary, nSecondary))\r
186                         return TRUE;\r
187         return bSkipKey;\r
188 }\r
189 \r
190 // END REQUIRED CSQC FUNCTIONS\r
191 // --------------------------------------------------------------------------\r
192 \r
193 // --------------------------------------------------------------------------\r
194 // BEGIN OPTIONAL CSQC FUNCTIONS\r
195 void Ent_ReadONS()\r
196 {\r
197         entity gps;\r
198         using_gps = true;\r
199 \r
200         self.origin_x = ReadCoord();\r
201         self.origin_y = ReadCoord();\r
202         self.angles_y = ReadCoord();\r
203         self.origin_z = self.angles_x = self.angles_z = 0;\r
204 \r
205         for(gps = gps_start; gps; gps = gps.chain)\r
206         {\r
207                 if(gps == self)\r
208                         break;\r
209         }\r
210         if(!gps)\r
211         {\r
212                 self.chain = gps_start;\r
213                 gps_start = self;\r
214         }\r
215 }\r
216 \r
217 void Ent_RemoveONS()\r
218 {\r
219         if(gps_start == self)\r
220                 gps_start = self.chain;\r
221         else\r
222         {\r
223                 local entity ent;\r
224                 ent = gps_start;\r
225                         \r
226                 while(ent.chain != self && ent.chain != world)\r
227                         ent = ent.chain;\r
228                 if(ent.chain == self)\r
229                         ent.chain = self.chain;\r
230         }\r
231 }\r
232 \r
233 void Gamemode_Init();\r
234 void Ent_ReadScoresInfo()\r
235 {\r
236         float i;\r
237         gametype = ReadByte();\r
238         for(i = 0; i < MAX_SCORE; ++i)\r
239         {\r
240                 scores_label[i] = strzone(ReadString());\r
241                 scores_flags[i] = ReadByte();\r
242         }\r
243         for(i = 0; i < MAX_TEAMSCORE; ++i)\r
244         {\r
245                 teamscores_label[i] = strzone(ReadString());\r
246                 teamscores_flags[i] = ReadByte();\r
247         }\r
248         Sbar_InitScores();\r
249         Gamemode_Init();\r
250 }\r
251 \r
252 void Ent_ReadPlayerScore(float isNew)\r
253 {\r
254         float i, Team;\r
255         entity tm;\r
256 \r
257         // damnit -.- don't want to go change every single .sv_entnum in sbar.qc AGAIN\r
258         // (no I've never heard of M-x replace-string, sed, or anything like that)\r
259         self.sv_entnum = ReadByte()-1;\r
260         Team = GetPlayerColor(self.sv_entnum);\r
261 \r
262         if(isNew)\r
263                 RegisterPlayer(self);\r
264 \r
265         if(isNew || Team != self.team)\r
266         {\r
267                 if(!isNew)\r
268                 {\r
269                         tm = GetTeam(self.team, false);\r
270                         tm.team_size -= 1;\r
271                 }\r
272                 \r
273                 self.team = Team;\r
274                 tm = GetTeam(Team, true);\r
275                 tm.team_size += 1;\r
276         }\r
277 \r
278         for(i = 0; i < MAX_SCORE; ++i)\r
279                 self.(scores[i]) = ReadShort();\r
280 \r
281         Sbar_UpdatePlayerPos(self);\r
282 }\r
283 \r
284 void Ent_ReadTeamScore(float isNew)\r
285 {\r
286         float i;\r
287         \r
288         self.team = ReadByte();\r
289 \r
290         if(isNew)\r
291                 RegisterTeam(self);\r
292 \r
293         for(i = 0; i < MAX_TEAMSCORE; ++i)\r
294                 self.(teamscores[i]) = ReadShort();\r
295 \r
296         Sbar_UpdateTeamPos(self);\r
297 }\r
298 \r
299 // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured.\r
300 // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.\r
301 void(float bIsNewEntity) CSQC_Ent_Update =\r
302 {\r
303         float msg;\r
304         self.enttype = ReadByte();\r
305         if(self.enttype == ENT_CLIENT_ENTCS)\r
306         {\r
307                 self.sv_entnum = ReadByte()-1;\r
308 \r
309                 for(msg = ReadByte(); msg != ENTCS_MSG_END; msg = ReadByte())\r
310                 {\r
311                         switch(msg)\r
312                         {\r
313                         case ENTCS_MSG_ONS_GPS: Ent_ReadONS(); break;\r
314                         case ENTCS_MSG_ONS_REMOVE: Ent_RemoveONS(); break;\r
315                         default:\r
316                                 error("unknown ENTCS_MSG type\n");\r
317                         }\r
318                 }\r
319         }\r
320         else if(self.enttype == ENT_CLIENT_SCORES_INFO)\r
321                 Ent_ReadScoresInfo();\r
322         else if(self.enttype == ENT_CLIENT_SCORES)\r
323                 Ent_ReadPlayerScore(bIsNewEntity);\r
324         else if(self.enttype == ENT_CLIENT_TEAMSCORES)\r
325                 Ent_ReadTeamScore(bIsNewEntity);\r
326         else\r
327                 error("unknown entity type in CSQC_Ent_Update\n");\r
328         \r
329 };\r
330 // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed.  Essentially call remove(self) as well.\r
331 void CSQC_Ent_Remove()\r
332 {\r
333         if(self.enttype == ENT_CLIENT_ENTCS)\r
334         {\r
335                 if(using_gps) //gametype == GAME_ONSLAUGHT)\r
336                 {\r
337                         if(gps_start == self)\r
338                                 gps_start = self.chain;\r
339                         else\r
340                         {\r
341                                 local entity ent;\r
342                                 ent = gps_start;\r
343                         \r
344                                 while(ent.chain != self && ent.chain != world)\r
345                                         ent = ent.chain;\r
346                                 if(ent.chain == self)\r
347                                         ent.chain = self.chain;\r
348                         }\r
349                 }\r
350         } else if(self.enttype == ENT_CLIENT_SCORES_INFO)\r
351         {\r
352                 // OH NOES!! WE LOST DA SCORES INFO ENTITY\r
353                 print("The world is going to explode.");\r
354                 // kkthxbai\r
355         } else if(self.enttype == ENT_CLIENT_SCORES)\r
356         {\r
357                 entity tm;\r
358                 print("lost a client score\n");\r
359                 tm = GetTeam(self.team, false);\r
360                 tm.team_size -= 1;\r
361                 RemovePlayer(self);\r
362         } else if(self.enttype == ENT_CLIENT_TEAMSCORES)\r
363         {\r
364                 RemoveTeam(self);\r
365         }\r
366         remove(self);\r
367 }\r
368 \r
369 void Gamemode_Init()\r
370 {\r
371         local string mapinfo, infoline;\r
372         local float len;\r
373         local float file;\r
374         local vector mi_min, mi_max;\r
375 \r
376         if(gametype == GAME_ONSLAUGHT) {\r
377                 if(!strcasecmp(substring(mapname, 0, 5), "maps/"))\r
378                         minimapname = substring(mapname, 5, 999);\r
379                 else\r
380                         minimapname = mapname;\r
381                 len = strlen(minimapname);\r
382                 if(!strcasecmp(substring(minimapname, len-4, 4), ".bsp"))\r
383                         minimapname = substring(minimapname, 0, len-4);\r
384                 \r
385                 mapinfo = strcat("maps/", minimapname, ".info");\r
386                 minimapname = strzone(strcat("gfx/", minimapname, "_mini.tga"));\r
387 \r
388                 mi_min = world.mins;\r
389                 mi_max = world.maxs;\r
390                 \r
391                 file = fopen(mapinfo, FILE_READ);\r
392                 if(file >= 0) {\r
393                         while((infoline = fgets(file))) {\r
394                                 if(!strncasecmp(infoline, "mins", 4)) {\r
395                                         mi_min = stov(substring(infoline, 5, 999));\r
396                                 } else if(!strncasecmp(infoline, "maxs", 4)) {\r
397                                         mi_max = stov(substring(infoline, 5, 999));\r
398                                 } else if(strncasecmp(infoline, "//", 2)) { // don't print comment-style lines\r
399                                         print(strcat("mapinfo: ", infoline, "\n"));\r
400                                 }\r
401                         }\r
402                 } else {\r
403                         print(strcat("Map has no .info file (", mapinfo, ").\n"));\r
404                 }\r
405                 fclose(file);\r
406 \r
407                 print(strcat("Mins: ", vtos(mi_min), "    Maxs: ", vtos(mi_max), "\n"));\r
408                 \r
409                 mi_center = (mi_min + mi_max) * 0.5;\r
410                 mi_scale = mi_max - mi_min;\r
411                 \r
412                 \r
413                 print(strcat("Using ", minimapname, " as minimap.\n"));\r
414                 precache_pic(minimapname);\r
415                 precache_pic("gfx/ons-cp-neutral.tga");\r
416                 precache_pic("gfx/ons-cp-red.tga");\r
417                 precache_pic("gfx/ons-cp-blue.tga");\r
418                 precache_pic("gfx/ons-frame.tga");\r
419                 precache_pic("gfx/ons-frame-team.tga");\r
420         } else if(gametype == GAME_KEYHUNT) {\r
421                 precache_pic("gfx/sb_key_carrying");\r
422                 precache_pic("gfx/sb_key_carrying_outline");\r
423         }\r
424 }\r
425 // 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
426 void CSQC_Parse_StuffCmd(string strMessage)\r
427 {\r
428         localcmd(strMessage);\r
429 }\r
430 // 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
431 void CSQC_Parse_Print(string strMessage)\r
432 {\r
433         print(strMessage);\r
434 }\r
435 // 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
436 void CSQC_Parse_CenterPrint(string strMessage)\r
437 {\r
438         cprint(strMessage);\r
439 }\r
440 \r
441 void CSQC_CheckRevision();\r
442 \r
443 void Net_ReadInit()\r
444 {\r
445         csqc_revision = ReadShort();\r
446         maxclients = ReadByte();\r
447 \r
448         CSQC_CheckRevision();\r
449 }\r
450 \r
451 void Net_ReadPings()\r
452 {\r
453         float plnum, ping;\r
454         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
455         {\r
456                 ping = ReadShort();\r
457                 bufstr_set(databuf, DATABUF_PING + plnum-1, ftos(ping));\r
458         }\r
459 }\r
460 \r
461 void Net_ReadCaptures()\r
462 {\r
463         float plnum, caps, mode;\r
464         mode = ReadByte();\r
465         caps_team1 = ReadByte();\r
466         caps_team2 = ReadByte();\r
467         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
468         {\r
469                 caps = ReadByte();\r
470                 bufstr_set(databuf, DATABUF_CAPTURES + plnum-1, ftos(caps));\r
471         }\r
472 }\r
473 \r
474 void Net_ReadDatabuf(float ofs)\r
475 {\r
476         float plnum, data;\r
477         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
478         {\r
479                 data = ReadByte();\r
480                 bufstr_set(databuf, ofs + plnum-1, ftos(data));\r
481         }\r
482 }\r
483 \r
484 string Net_ReadPicture()\r
485 {\r
486         string img;\r
487         if(csqc_flags & CSQC_FLAG_READPICTURE)\r
488         {\r
489                 img = ReadPicture();\r
490                 print(strcat("Got Picture: ", img, "\n"));\r
491         } else {\r
492                 img = ReadString();\r
493                 print(strcat("^3Warning: ^7Couldn't download ", img, ". This is probably because your engine build is outdated.\n"));\r
494                 float psize, i;\r
495                 psize = ReadShort();\r
496                 // Can I be sure that ReadShort is 2 bytes and ReadLong is 4 bytes?\r
497                 // Because then this could be optimized to first get all 4-byte-groups,\r
498                 // then the remaining 2, then the remaining 1\r
499                 for(i = 0; i < psize; ++i)\r
500                         ReadByte();\r
501         }\r
502         return img;\r
503 }\r
504 \r
505 void Net_Config()\r
506 {\r
507         string key, value;\r
508         key = ReadString();\r
509         value = ReadString();\r
510         db_put(configdb, strcat("/v/", key), value);\r
511         db_put(configdb, strcat("/s/", key), "1");\r
512 }\r
513 \r
514 // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer.\r
515 // You must ALWAYS first acquire the temporary ID, which is sent as a byte.\r
516 // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event.\r
517 float CSQC_Parse_TempEntity()\r
518 {\r
519         local float bHandled;\r
520                 bHandled  = true;\r
521         // Acquire TE ID\r
522         local float nTEID;\r
523                 nTEID = ReadByte();\r
524 \r
525                 // NOTE: Could just do return instead of break...\r
526         switch(nTEID)\r
527         {\r
528                 case TE_CSQC_INIT:\r
529                         Net_ReadInit();\r
530                         bHandled = true;\r
531                         break;\r
532                 case TE_CSQC_PING:\r
533                         Net_ReadPings();\r
534                         bHandled = true;\r
535                         break;\r
536                 case TE_CSQC_CAPTURES:\r
537                         Net_ReadCaptures();\r
538                         bHandled = true;\r
539                         break;\r
540                 case TE_CSQC_RETURNS:\r
541                         Net_ReadDatabuf(DATABUF_RETURNS);\r
542                         bHandled = true;\r
543                         break;\r
544                 case TE_CSQC_DEATHS:\r
545                         Net_ReadDatabuf(DATABUF_DEATHS);\r
546                         bHandled = true;\r
547                         break;\r
548                 case TE_CSQC_MAPVOTE:\r
549                         Net_Mapvote();\r
550                         bHandled = true;\r
551                         break;\r
552                 case TE_CSQC_CONFIG:\r
553                         Net_Config();\r
554                         bHandled = true;\r
555                         break;\r
556                 default:\r
557                         // No special logic for this temporary entity; return 0 so the engine can handle it\r
558                         bHandled = false;\r
559                         break;\r
560         }\r
561         \r
562         if(!postinit)\r
563                 PostInit();\r
564                 \r
565         return bHandled;\r
566 }\r
567 \r
568 // COMMIT-TODO: Update if necessare, before committing\r
569 float csqc_svn_map[CSQC_REVISION] =\r
570 {\r
571         3812, // 3795,\r
572         3820 // mapvote protocol changed from there\r
573 };\r
574 \r
575 // COMMIT-TODO: Update if necessare, before committing\r
576 void CSQC_CheckRevision()\r
577 {\r
578         if(csqc_revision == CSQC_REVISION)\r
579         {\r
580                 print("^2SVQC and CSQC revisions are compatible.\n");\r
581         } else if(csqc_revision < CSQC_REVISION) {\r
582                 print("^1Your csprogs.dat (CSQC) version is newer than the one on the server.\n");\r
583                 print("^1The last known svn revision for the server's CSQC is: ^7");\r
584                 print(ftos(csqc_svn_map[csqc_revision])); // don't use strcat, fteqcc loves screwing up arrays...\r
585                 print("\n");\r
586         } else if(csqc_revision > CSQC_REVISION) {\r
587                 print("^1Your csprogs.dat (CSQC) is too old for this server.\n");\r
588                 print("^1Please update to a newer version.\n");\r
589         }\r
590 }\r