add csqc_flags cvar for client compatibility
[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 databuf;\r
19 float using_gps;\r
20 \r
21 void CSQC_Init(void)\r
22 {\r
23         float i;\r
24         CSQC_CheckEngine();\r
25         drawfont = 0;\r
26         menu_visible = FALSE;\r
27         menu_show = menu_show_error;\r
28         menu_action = menu_sub_null;\r
29         using_gps = false;\r
30         //ctf_temp_1 = "";\r
31         localcmd("alias order \"cmd order $*\"");\r
32         //registercmd("ctf_menu");\r
33         registercmd("ons_map");\r
34         //registercmd("menu_action");\r
35         registercmd("sbar_columns_set");\r
36         registercmd("sbar_columns_help");\r
37 \r
38         registercvar("sbar_usecsqc", "1");\r
39         registercvar("sbar_columns", "ping name | caps returns frags deaths", CVAR_SAVE);\r
40 \r
41         gametype = 0;\r
42 \r
43         gps_start = world;\r
44 \r
45         // sbar_fields uses strunzone on the titles!\r
46         for(i = 0; i < MAX_SBAR_FIELDS; ++i)\r
47                 sbar_title[i] = strzone("(null)");\r
48 \r
49         postinit = false;\r
50 }\r
51 \r
52 void PostInit(void)\r
53 {\r
54         float i;\r
55 \r
56         print(strcat("PostInit\n    maxclients = ", ftos(maxclients), "\n"));\r
57         databuf = buf_create();\r
58         for(i = 0; i < maxclients; ++i)\r
59         {\r
60                 bufstr_set(databuf, DATABUF_PING + i, "N/A");\r
61                 bufstr_set(databuf, DATABUF_DEATHS + i, "0");\r
62                 bufstr_set(databuf, DATABUF_CAPTURES + i, "0");\r
63                 bufstr_set(databuf, DATABUF_RETURNS + i, "0");\r
64         }\r
65         \r
66         localcmd(strcat("\nsbar_columns_set ", cvar_string("sbar_columns"), ";\n"));\r
67 \r
68         postinit = true;\r
69 }\r
70 // CSQC_Shutdown : Called every time the CSQC code is shutdown (changing maps, quitting, etc)\r
71 void CSQC_Shutdown(void)\r
72 {\r
73         buf_del(databuf);\r
74 }\r
75 \r
76 // CSQC_ConsoleCommand : Used to parse commands in the console that have been registered with the "registercmd" function\r
77 // Return value should be 1 if CSQC handled the command, otherwise return 0 to have the engine handle it.\r
78 void Cmd_Sbar_SetFields(float);\r
79 void Cmd_Sbar_Help(float);\r
80 float CSQC_ConsoleCommand(string strMessage)\r
81 {\r
82         float argc;\r
83         // Tokenize String\r
84         argc = tokenize(strMessage);\r
85         \r
86         // Acquire Command\r
87         local string strCmd;\r
88         strCmd = argv(0);\r
89 \r
90         /*if(strCmd == "ctf_menu") {\r
91                 ctf_menu_show();\r
92                 nReturn = true;\r
93                 } else*/\r
94         if(strCmd == "ons_map") {\r
95                 Cmd_ons_map();\r
96                 return true;\r
97         } else if(strCmd == "sbar_columns_set") {\r
98                 Cmd_Sbar_SetFields(argc);\r
99                 return true;\r
100         } else if(strCmd == "sbar_columns_help") {\r
101                 Cmd_Sbar_Help(argc);\r
102                 return true;\r
103         }\r
104         \r
105         return false;\r
106 }\r
107 // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client.\r
108 // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine.\r
109 // All keys are in ascii.\r
110 // bInputType = 0 is key pressed, 1 is key released, 2 is mouse input.\r
111 // In the case of keyboard input, nPrimary is the ascii code, and nSecondary is 0.\r
112 // In the case of mouse input, nPrimary is xdelta, nSecondary is ydelta.\r
113 float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary)\r
114 {\r
115         local float bSkipKey;\r
116         bSkipKey = false;\r
117         if(menu_visible)\r
118                 if(menu_action(bInputType, nPrimary, nSecondary))\r
119                         return TRUE;\r
120         return bSkipKey;\r
121 }\r
122 \r
123 // END REQUIRED CSQC FUNCTIONS\r
124 // --------------------------------------------------------------------------\r
125 \r
126 // --------------------------------------------------------------------------\r
127 // BEGIN OPTIONAL CSQC FUNCTIONS\r
128 void ReadONS()\r
129 {\r
130         entity gps;\r
131         using_gps = true;\r
132 \r
133         self.origin_x = ReadCoord();\r
134         self.origin_y = ReadCoord();\r
135         self.angles_y = ReadCoord();\r
136         self.origin_z = self.angles_x = self.angles_z = 0;\r
137 \r
138         for(gps = gps_start; gps; gps = gps.chain)\r
139         {\r
140                 if(gps == self)\r
141                         break;\r
142         }\r
143         if(!gps)\r
144         {\r
145                 self.chain = gps_start;\r
146                 gps_start = self;\r
147         }\r
148 }\r
149 \r
150 void RemoveONS()\r
151 {\r
152         if(gps_start == self)\r
153                 gps_start = self.chain;\r
154         else\r
155         {\r
156                 local entity ent;\r
157                 ent = gps_start;\r
158                         \r
159                 while(ent.chain != self && ent.chain != world)\r
160                         ent = ent.chain;\r
161                 if(ent.chain == self)\r
162                         ent.chain = self.chain;\r
163         }\r
164 }\r
165 \r
166 // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured.\r
167 // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS.\r
168 void(float bIsNewEntity) CSQC_Ent_Update =\r
169 {\r
170         float msg;\r
171         self.enttype = ReadByte();\r
172         if(self.enttype == ENT_CLIENT_ENTCS)\r
173         {\r
174                 self.sv_entnum = ReadByte()-1;\r
175 \r
176                 for(msg = ReadByte(); msg != ENTCS_MSG_END; msg = ReadByte())\r
177                 {\r
178                         switch(msg)\r
179                         {\r
180                         case ENTCS_MSG_ONS_GPS: ReadONS(); break;\r
181                         case ENTCS_MSG_ONS_REMOVE: RemoveONS(); break;\r
182                         default:\r
183                                 error("unknown ENTCS_MSG type\n");\r
184                         }\r
185                 }\r
186         }\r
187         else\r
188                 error("unknown entity type in CSQC_Ent_Update\n");\r
189         \r
190 };\r
191 // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed.  Essentially call remove(self) as well.\r
192 void CSQC_Ent_Remove()\r
193 {\r
194         if(self.enttype == ENT_CLIENT_ENTCS)\r
195         {\r
196                 if(using_gps) //gametype == GAME_ONSLAUGHT)\r
197                 {\r
198                         if(gps_start == self)\r
199                                 gps_start = self.chain;\r
200                         else\r
201                         {\r
202                                 local entity ent;\r
203                                 ent = gps_start;\r
204                         \r
205                                 while(ent.chain != self && ent.chain != world)\r
206                                         ent = ent.chain;\r
207                                 if(ent.chain == self)\r
208                                         ent.chain = self.chain;\r
209                         }\r
210                 }\r
211         }\r
212         remove(self);\r
213 }\r
214 \r
215 void Gamemode_Init()\r
216 {\r
217         local string mapinfo, infoline;\r
218         local float len;\r
219         local float file;\r
220         local vector mi_min, mi_max;\r
221 \r
222         draw_enginesbar = true;\r
223         gametype = cvar("gametype");\r
224         if(gametype == GAME_ONSLAUGHT) {\r
225                 if(!strcasecmp(substring(mapname, 0, 5), "maps/"))\r
226                         minimapname = substring(mapname, 5, 999);\r
227                 else\r
228                         minimapname = mapname;\r
229                 len = strlen(minimapname);\r
230                 if(!strcasecmp(substring(minimapname, len-4, 4), ".bsp"))\r
231                         minimapname = substring(minimapname, 0, len-4);\r
232                 \r
233                 mapinfo = strcat("maps/", minimapname, ".info");\r
234                 minimapname = strzone(strcat("gfx/", minimapname, "_mini.tga"));\r
235 \r
236                 mi_min = world.mins;\r
237                 mi_max = world.maxs;\r
238                 \r
239                 file = fopen(mapinfo, FILE_READ);\r
240                 if(file >= 0) {\r
241                         while((infoline = fgets(file))) {\r
242                                 if(!strncasecmp(infoline, "mins", 4)) {\r
243                                         mi_min = stov(substring(infoline, 5, 999));\r
244                                 } else if(!strncasecmp(infoline, "maxs", 4)) {\r
245                                         mi_max = stov(substring(infoline, 5, 999));\r
246                                 } else if(strncasecmp(infoline, "//", 2)) { // don't print comment-style lines\r
247                                         print(strcat("mapinfo: ", infoline, "\n"));\r
248                                 }\r
249                         }\r
250                 } else {\r
251                         print(strcat("^1Error: ^7Mapinfo file '", mapinfo, "' missing! Minimap will be screwed.\n"));\r
252                 }\r
253                 fclose(file);\r
254 \r
255                 print(strcat("Mins: ", vtos(mi_min), "    Maxs: ", vtos(mi_max), "\n"));\r
256                 \r
257                 mi_center = (mi_min + mi_max) * 0.5;\r
258                 mi_scale = mi_max - mi_min;\r
259                 \r
260                 \r
261                 print(strcat("Using ", minimapname, " as minimap.\n"));\r
262                 precache_pic(minimapname);\r
263                 precache_pic("gfx/ons-cp-neutral.tga");\r
264                 precache_pic("gfx/ons-cp-red.tga");\r
265                 precache_pic("gfx/ons-cp-blue.tga");\r
266                 precache_pic("gfx/ons-frame.tga");\r
267                 precache_pic("gfx/ons-frame-team.tga");\r
268         } else if(gametype == GAME_KEYHUNT) {\r
269                 precache_pic("gfx/sb_key_carrying");\r
270                 precache_pic("gfx/sb_key_carrying_outline");\r
271                 draw_enginesbar = false;\r
272         }\r
273 }\r
274 // 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
275 void CSQC_Parse_StuffCmd(string strMessage)\r
276 {\r
277         localcmd(strMessage);\r
278         // watch for gametype changes!\r
279         if(gametype != cvar("gametype"))\r
280         {\r
281                 Gamemode_Init();\r
282         }\r
283 }\r
284 // 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
285 void CSQC_Parse_Print(string strMessage)\r
286 {\r
287         print(strMessage);\r
288 }\r
289 // 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
290 void CSQC_Parse_CenterPrint(string strMessage)\r
291 {\r
292         cprint(strMessage);\r
293 }\r
294 \r
295 void CSQC_CheckRevision();\r
296 void ReadInit()\r
297 {\r
298         csqc_revision = ReadShort();\r
299         maxclients = ReadByte();\r
300 \r
301         CSQC_CheckRevision();\r
302 }\r
303 \r
304 void ReadPings()\r
305 {\r
306         float plnum, ping;\r
307         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
308         {\r
309                 ping = ReadShort();\r
310                 bufstr_set(databuf, DATABUF_PING + plnum-1, ftos(ping));\r
311         }\r
312 }\r
313 \r
314 void ReadCaptures()\r
315 {\r
316         float plnum, caps, mode;\r
317         mode = ReadByte();\r
318         caps_team1 = ReadByte();\r
319         caps_team2 = ReadByte();\r
320         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
321         {\r
322                 caps = ReadByte();\r
323                 bufstr_set(databuf, DATABUF_CAPTURES + plnum-1, ftos(caps));\r
324         }\r
325 }\r
326 \r
327 void ReadDatabuf(float ofs)\r
328 {\r
329         float plnum, data;\r
330         for(plnum = ReadByte(); plnum != 0; plnum = ReadByte())\r
331         {\r
332                 data = ReadByte();\r
333                 bufstr_set(databuf, ofs + plnum-1, ftos(data));\r
334         }\r
335 }\r
336 \r
337 // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer.\r
338 // You must ALWAYS first acquire the temporary ID, which is sent as a byte.\r
339 // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event.\r
340 float CSQC_Parse_TempEntity()\r
341 {\r
342         local float bHandled;\r
343                 bHandled  = true;\r
344         // Acquire TE ID\r
345         local float nTEID;\r
346                 nTEID = ReadByte();\r
347                 \r
348         switch(nTEID)\r
349         {\r
350                 case TE_CSQC_INIT:\r
351                         ReadInit();\r
352                         bHandled = true;\r
353                         break;\r
354                 case TE_CSQC_PING:\r
355                         ReadPings();\r
356                         bHandled = true;\r
357                         break;\r
358                 case TE_CSQC_CAPTURES:\r
359                         ReadCaptures();\r
360                         bHandled = true;\r
361                         break;\r
362                 case TE_CSQC_RETURNS:\r
363                         ReadDatabuf(DATABUF_RETURNS);\r
364                         bHandled = true;\r
365                         break;\r
366                 case TE_CSQC_DEATHS:\r
367                         ReadDatabuf(DATABUF_DEATHS);\r
368                         bHandled = true;\r
369                         break;\r
370                 default:\r
371                         // No special logic for this temporary entity; return 0 so the engine can handle it\r
372                         bHandled = false;\r
373                         break;\r
374         }\r
375         \r
376         if(!postinit)\r
377                 PostInit();\r
378                 \r
379         return bHandled;\r
380 }\r
381 \r
382 // COMMIT-TODO: Update if necessare, before committing\r
383 float csqc_svn_map[CSQC_REVISION] =\r
384 {\r
385         3795,\r
386 };\r
387 \r
388 // COMMIT-TODO: Update if necessare, before committing\r
389 void CSQC_CheckRevision()\r
390 {\r
391         if(csqc_revision == CSQC_REVISION)\r
392         {\r
393                 print("^2SVQC and CSQC revisions are compatible.\n");\r
394         } else if(csqc_revision < CSQC_REVISION) {\r
395                 print("^1Your csprogs.dat (CSQC) version is newer than the one on the server.\n");\r
396                 print("^1The last known svn revision for the server's CSQC is: ^7");\r
397                 print(ftos(csqc_svn_map[csqc_revision])); // don't use strcat, fteqcc loves screwing up arrays...\r
398                 print("\n");\r
399         } else if(csqc_revision > CSQC_REVISION) {\r
400                 print("^1Your csprogs.dat (CSQC) is too old for this server.\n");\r
401                 print("^1Please update to a newer version.\n");\r
402         }\r
403 }\r