]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/intermission.qc
Minor cleanup of world.qc, move most intermission and map handling code into intermis...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / intermission.qc
1 #include "mapvoting.qh"
2
3 #include <common/mapinfo.qh>
4 #include <common/util.qh>
5 #include <server/bot/api.qh>
6 #include <server/campaign.qh>
7 #include <server/client.qh>
8 #include <server/mapvoting.qh>
9 #include <server/scores_rules.qh>
10 #include <server/world.qh>
11
12 string GetGametype()
13 {
14         return MapInfo_Type_ToString(MapInfo_LoadedGametype);
15 }
16
17 string GetMapname()
18 {
19         return mapname;
20 }
21
22 float Map_Count, Map_Current;
23 string Map_Current_Name;
24
25 // NOTE: this now expects the map list to be already tokenized and the count in Map_Count
26 int GetMaplistPosition()
27 {
28         string map = GetMapname();
29         int idx = autocvar_g_maplist_index;
30
31         if(idx >= 0)
32         {
33                 if(idx < Map_Count)
34                 {
35                         if(map == argv(idx))
36                         {
37                                 return idx;
38                         }
39                 }
40         }
41
42         for(int pos = 0; pos < Map_Count; ++pos)
43         {
44                 if(map == argv(pos))
45                         return pos;
46         }
47
48         // resume normal maplist rotation if current map is not in g_maplist
49         return idx;
50 }
51
52 bool MapHasRightSize(string map)
53 {
54         int minplayers = max(0, floor(autocvar_minplayers));
55         if (teamplay)
56                 minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams());
57         if (autocvar_g_maplist_check_waypoints
58                 && (currentbots || autocvar_bot_number || player_count < minplayers))
59         {
60                 string checkwp_msg = strcat("checkwp ", map);
61                 if(!fexists(strcat("maps/", map, ".waypoints")))
62                 {
63                         LOG_TRACE(checkwp_msg, ": no waypoints");
64                         return false;
65                 }
66                 LOG_TRACE(checkwp_msg, ": has waypoints");
67         }
68
69         if(autocvar_g_maplist_ignore_sizes)
70                 return true;
71
72         // open map size restriction file
73         string opensize_msg = strcat("opensize ", map);
74         float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
75         int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
76         int pcount = ((player_limit > 0) ? min(player_count, player_limit) : player_count); // bind it to the player limit so that forced spectators don't influence the limits
77         if(!autocvar_g_maplist_sizes_count_bots)
78                 pcount -= currentbots;
79         if(fh >= 0)
80         {
81                 opensize_msg = strcat(opensize_msg, ": ok, ");
82                 int mapmin = stoi(fgets(fh));
83                 int mapmax = stoi(fgets(fh));
84                 fclose(fh);
85                 if(pcount < mapmin)
86                 {
87                         LOG_TRACE(opensize_msg, "not enough");
88                         return false;
89                 }
90                 if(mapmax && pcount > mapmax)
91                 {
92                         LOG_TRACE(opensize_msg, "too many");
93                         return false;
94                 }
95                 LOG_TRACE(opensize_msg, "right size");
96                 return true;
97         }
98         LOG_TRACE(opensize_msg, ": not found");
99         return true;
100 }
101
102 string Map_Filename(int position)
103 {
104         return strcat("maps/", argv(position), ".bsp");
105 }
106
107 void Map_MarkAsRecent(string m)
108 {
109         cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count)));
110 }
111
112 bool Map_IsRecent(string m)
113 {
114         return strhasword(autocvar_g_maplist_mostrecent, m);
115 }
116
117 bool Map_Check(int position, float pass)
118 {
119         string filename;
120         string map_next;
121         map_next = argv(position);
122         if(pass <= 1)
123         {
124                 if(Map_IsRecent(map_next))
125                         return false;
126         }
127         filename = Map_Filename(position);
128         if(MapInfo_CheckMap(map_next))
129         {
130                 if(pass == 2)
131                         return true;
132                 if(MapHasRightSize(map_next))
133                         return true;
134                 return false;
135         }
136         else
137                 LOG_DEBUG( "Couldn't select '", filename, "'..." );
138
139         return false;
140 }
141
142 void Map_Goto_SetStr(string nextmapname)
143 {
144         if(getmapname_stored != "")
145                 strunzone(getmapname_stored);
146         if(nextmapname == "")
147                 getmapname_stored = "";
148         else
149                 getmapname_stored = strzone(nextmapname);
150 }
151
152 void Map_Goto_SetFloat(float position)
153 {
154         cvar_set("g_maplist_index", ftos(position));
155         Map_Goto_SetStr(argv(position));
156 }
157
158 void Map_Goto(float reinit)
159 {
160         MapInfo_LoadMap(getmapname_stored, reinit);
161 }
162
163 // return codes of map selectors:
164 //   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
165 //   -2 = permanent failure
166 float MaplistMethod_Iterate() // usual method
167 {
168         float pass, i;
169
170         LOG_TRACE("Trying MaplistMethod_Iterate");
171
172         for(pass = 1; pass <= 2; ++pass)
173         {
174                 for(i = 1; i < Map_Count; ++i)
175                 {
176                         float mapindex;
177                         mapindex = (i + Map_Current) % Map_Count;
178                         if(Map_Check(mapindex, pass))
179                                 return mapindex;
180                 }
181         }
182         return -1;
183 }
184
185 float MaplistMethod_Repeat() // fallback method
186 {
187         LOG_TRACE("Trying MaplistMethod_Repeat");
188
189         if(Map_Check(Map_Current, 2))
190                 return Map_Current;
191         return -2;
192 }
193
194 float MaplistMethod_Random() // random map selection
195 {
196         float i, imax;
197
198         LOG_TRACE("Trying MaplistMethod_Random");
199
200         imax = 42;
201
202         for(i = 0; i <= imax; ++i)
203         {
204                 float mapindex;
205                 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
206                 if(Map_Check(mapindex, 1))
207                         return mapindex;
208         }
209         return -1;
210 }
211
212 float MaplistMethod_Shuffle(float exponent) // more clever shuffling
213 // the exponent sets a bias on the map selection:
214 // the higher the exponent, the less likely "shortly repeated" same maps are
215 {
216         float i, j, imax, insertpos;
217
218         LOG_TRACE("Trying MaplistMethod_Shuffle");
219
220         imax = 42;
221
222         for(i = 0; i <= imax; ++i)
223         {
224                 string newlist;
225
226                 // now reinsert this at another position
227                 insertpos = (random() ** (1 / exponent));       // ]0, 1]
228                 insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
229                 insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
230                 LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
231
232                 // insert the current map there
233                 newlist = "";
234                 for(j = 1; j < insertpos; ++j)                 // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
235                         newlist = strcat(newlist, " ", argv(j));
236                 newlist = strcat(newlist, " ", argv(0));       // now insert the just selected map
237                 for(j = insertpos; j < Map_Count; ++j)         // i == Map_Count: no loop, has just been inserted as last
238                         newlist = strcat(newlist, " ", argv(j));
239                 newlist = substring(newlist, 1, strlen(newlist) - 1);
240                 cvar_set("g_maplist", newlist);
241                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
242
243                 // NOTE: the selected map has just been inserted at (insertpos-1)th position
244                 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
245                 if(Map_Check(Map_Current, 1))
246                         return Map_Current;
247         }
248         return -1;
249 }
250
251 void Maplist_Init()
252 {
253         float i = Map_Count = 0;
254         if(autocvar_g_maplist != "")
255         {
256                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
257                 for (i = 0; i < Map_Count; ++i)
258                 {
259                         if (Map_Check(i, 2))
260                                 break;
261                 }
262         }
263
264         if (i == Map_Count)
265         {
266                 bprint( "Maplist contains no usable maps!  Resetting it to default map list.\n" );
267                 cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST));
268                 if(autocvar_g_maplist_shuffle)
269                         ShuffleMaplist();
270                 if(!server_is_dedicated)
271                         localcmd("\nmenu_cmd sync\n");
272                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
273         }
274         if(Map_Count == 0)
275                 error("empty maplist, cannot select a new map");
276         Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
277
278         strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
279         // this may or may not be correct, but who cares, in the worst case a map
280         // isn't chosen in the first pass that should have been
281 }
282
283 string GetNextMap()
284 {
285         Maplist_Init();
286         float nextMap = -1;
287
288         if(nextMap == -1)
289                 if(autocvar_g_maplist_shuffle > 0)
290                         nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
291
292         if(nextMap == -1)
293                 if(autocvar_g_maplist_selectrandom)
294                         nextMap = MaplistMethod_Random();
295
296         if(nextMap == -1)
297                 nextMap = MaplistMethod_Iterate();
298
299         if(nextMap == -1)
300                 nextMap = MaplistMethod_Repeat();
301
302         if(nextMap >= 0)
303         {
304                 Map_Goto_SetFloat(nextMap);
305                 return getmapname_stored;
306         }
307
308         return "";
309 }
310
311 float DoNextMapOverride(float reinit)
312 {
313         if(autocvar_g_campaign)
314         {
315                 CampaignPostIntermission();
316                 alreadychangedlevel = true;
317                 return true;
318         }
319         if(autocvar_quit_when_empty)
320         {
321                 if(player_count <= currentbots)
322                 {
323                         localcmd("quit\n");
324                         alreadychangedlevel = true;
325                         return true;
326                 }
327         }
328         if(autocvar_quit_and_redirect != "")
329         {
330                 redirection_target = strzone(autocvar_quit_and_redirect);
331                 alreadychangedlevel = true;
332                 return true;
333         }
334         if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
335         {
336                 localcmd("restart\n");
337                 alreadychangedlevel = true;
338                 return true;
339         }
340         if(autocvar_nextmap != "")
341         {
342                 string m;
343                 m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
344                 cvar_set("nextmap",m);
345
346                 if(!m || gametypevote)
347                         return false;
348                 if(autocvar_sv_vote_gametype)
349                 {
350                         Map_Goto_SetStr(m);
351                         return false;
352                 }
353
354                 if(MapInfo_CheckMap(m))
355                 {
356                         Map_Goto_SetStr(m);
357                         Map_Goto(reinit);
358                         alreadychangedlevel = true;
359                         return true;
360                 }
361         }
362         if(!reinit && autocvar_lastlevel)
363         {
364                 cvar_settemp_restore();
365                 localcmd("set lastlevel 0\ntogglemenu 1\n");
366                 alreadychangedlevel = true;
367                 return true;
368         }
369         return false;
370 }
371
372 void GotoNextMap(float reinit)
373 {
374         //string nextmap;
375         //float n, nummaps;
376         //string s;
377         if (alreadychangedlevel)
378                 return;
379         alreadychangedlevel = true;
380
381         string nextMap = GetNextMap();
382         if(nextMap == "")
383                 error("Everything is broken - cannot find a next map. Please report this to the developers.");
384         Map_Goto(reinit);
385 }
386
387 void ShuffleMaplist()
388 {
389         cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
390 }
391
392 string GotoMap(string m)
393 {
394         m = GameTypeVote_MapInfo_FixName(m);
395         if (!m)
396                 return "The map you suggested is not available on this server.";
397         if (!autocvar_sv_vote_gametype)
398         if(!MapInfo_CheckMap(m))
399                 return "The map you suggested does not support the current game mode.";
400         cvar_set("nextmap", m);
401         cvar_set("timelimit", "-1");
402         if(mapvote_initialized || alreadychangedlevel)
403         {
404                 if(DoNextMapOverride(0))
405                         return "Map switch initiated.";
406                 else
407                         return "Hm... no. For some reason I like THIS map more.";
408         }
409         else
410                 return "Map switch will happen after scoreboard.";
411 }
412
413
414 /*
415 ============
416 IntermissionThink
417
418 When the player presses attack or jump, change to the next level
419 ============
420 */
421 .float autoscreenshot;
422 void IntermissionThink(entity this)
423 {
424         FixIntermissionClient(this);
425
426         float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot);
427         float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2);
428
429         if( (server_screenshot || client_screenshot)
430                 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
431         {
432                 this.autoscreenshot = -1;
433                 if(IS_REAL_CLIENT(this)) { stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); }
434                 return;
435         }
436
437         if (time < intermission_exittime)
438                 return;
439
440         if(!mapvote_initialized)
441                 if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this)))
442                         return;
443
444         MapVote_Start();
445 }
446
447 void FixIntermissionClient(entity e)
448 {
449         if(!e.autoscreenshot) // initial call
450         {
451                 e.autoscreenshot = time + 0.8;  // used for autoscreenshot
452                 SetResourceExplicit(e, RES_HEALTH, -2342);
453                 // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
454                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
455                 {
456                     .entity weaponentity = weaponentities[slot];
457                         if(e.(weaponentity))
458                         {
459                                 e.(weaponentity).effects = EF_NODRAW;
460                                 if (e.(weaponentity).weaponchild)
461                                         e.(weaponentity).weaponchild.effects = EF_NODRAW;
462                         }
463                 }
464                 if(IS_REAL_CLIENT(e))
465                 {
466                         stuffcmd(e, "\nscr_printspeed 1000000\n");
467                         RandomSelection_Init();
468                         FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
469                                 RandomSelection_AddString(it, 1, 1);
470                         });
471                         if (RandomSelection_chosen_string != "")
472                         {
473                                 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
474                         }
475                         msg_entity = e;
476                         WriteByte(MSG_ONE, SVC_INTERMISSION);
477                 }
478         }
479 }