]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/intermission.qc
Fix current custom gametype not being kept if gametype vote ends without votes
[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/bot/default/cvars.qh>
7 #include <server/campaign.qh>
8 #include <server/client.qh>
9 #include <server/mapvoting.qh>
10 #include <server/scores_rules.qh>
11 #include <server/world.qh>
12
13 string GetGametype()
14 {
15         return MapInfo_Type_ToString(MapInfo_LoadedGametype);
16 }
17
18 string GetMapname()
19 {
20         return mapname;
21 }
22
23 int Map_Count, Map_Current;
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) * AVAILABLE_TEAMS);
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         if(!MapReadSizes(map))
74                 return true; // map has no size restrictions
75
76         string checksize_msg = strcat("MapHasRightSize ", map);
77         int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
78         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
79
80         if(!autocvar_g_maplist_sizes_count_bots)
81                 pcount -= currentbots;
82         pcount -= rint(cvar("g_maplist_sizes_specparty") * pcount);
83
84         // ensure small maps can be selected when pcount is low
85         if(map_minplayers <= (_MapInfo_GetTeamPlayBool(MapInfo_CurrentGametype()) ? 4 : 2))
86                 map_minplayers = 0;
87
88         if(pcount < map_minplayers)
89         {
90                 LOG_TRACE(checksize_msg, ": not enough");
91                 return false;
92         }
93         if(map_maxplayers && pcount > map_maxplayers)
94         {
95                 LOG_TRACE(checksize_msg, ": too many");
96                 return false;
97         }
98         LOG_TRACE(checksize_msg, ": right size");
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                 // MapInfo_Map_flags was set by MapInfo_CheckMap()
133                 if (MapInfo_Map_flags & MAPINFO_FLAG_DONOTWANT)
134                         return false;
135                 if(MapHasRightSize(map_next))
136                         return true;
137                 return false;
138         }
139         else
140                 LOG_DEBUG( "Couldn't select '", filename, "'..." );
141
142         return false;
143 }
144
145 void Map_Goto_SetStr(string nextmapname)
146 {
147         if(getmapname_stored != "")
148                 strunzone(getmapname_stored);
149         if(nextmapname == "")
150                 getmapname_stored = "";
151         else
152                 getmapname_stored = strzone(nextmapname);
153 }
154
155 void Map_Goto_SetIndex(int position)
156 {
157         Map_Current = position;
158         cvar_set("g_maplist_index", ftos(position));
159         Map_Goto_SetStr(argv(position));
160 }
161
162 void Map_Goto(float reinit)
163 {
164         MapInfo_LoadMap(getmapname_stored, reinit);
165 }
166
167 // return codes of map selectors:
168 //   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
169 //   -2 = permanent failure
170 int MaplistMethod_Iterate(void) // usual method
171 {
172         int pass, i;
173
174         LOG_TRACE("Trying MaplistMethod_Iterate");
175
176         for(pass = 1; pass <= 2; ++pass)
177         {
178                 for(i = 1; i < Map_Count; ++i)
179                 {
180                         int mapindex;
181                         mapindex = (i + Map_Current) % Map_Count;
182                         if(Map_Check(mapindex, pass))
183                                 return mapindex;
184                 }
185         }
186         return -1;
187 }
188
189 int MaplistMethod_Repeat(void) // fallback method
190 {
191         LOG_TRACE("Trying MaplistMethod_Repeat");
192
193         if(Map_Check(Map_Current, 2))
194                 return Map_Current;
195         return -2;
196 }
197
198 int MaplistMethod_Random(void) // random map selection
199 {
200         int i, imax;
201
202         LOG_TRACE("Trying MaplistMethod_Random");
203
204         imax = 42;
205
206         for(i = 0; i <= imax; ++i)
207         {
208                 int mapindex;
209                 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
210                 if(Map_Check(mapindex, 1))
211                         return mapindex;
212         }
213         return -1;
214 }
215
216 // the exponent sets a bias on the map selection:
217 // the higher the exponent, the less likely "shortly repeated" same maps are
218 int MaplistMethod_Shuffle(float exponent) // more clever shuffling
219 {
220         float i, j, imax, insertpos;
221
222         LOG_TRACE("Trying MaplistMethod_Shuffle");
223
224         imax = 42;
225
226         for(i = 0; i <= imax; ++i)
227         {
228                 string newlist;
229
230                 // now reinsert this at another position
231                 insertpos = (random() ** (1 / exponent));       // ]0, 1]
232                 insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
233                 insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
234                 LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
235
236                 // insert the current map there
237                 newlist = "";
238                 for(j = 1; j < insertpos; ) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
239                 {
240                         if (j + 2 < insertpos)
241                                 newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++));
242                         else
243                                 newlist = strcat(newlist, " ", argv(j++));
244                 }
245                 newlist = strcat(newlist, " ", argv(0));       // now insert the just selected map
246                 for(j = insertpos; j < Map_Count; ) // i == Map_Count: no loop, has just been inserted as last
247                 {
248                         if (j + 2 < Map_Count)
249                                 newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++));
250                         else
251                                 newlist = strcat(newlist, " ", argv(j++));
252                 }
253                 newlist = substring(newlist, 1, strlen(newlist) - 1);
254                 cvar_set("g_maplist", newlist);
255                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
256
257                 // NOTE: the selected map has just been inserted at (insertpos-1)th position
258                 if (Map_Check(insertpos - 1, 1))
259                         return insertpos - 1;
260         }
261         return -1;
262 }
263
264 int Maplist_Init(void)
265 {
266         int i, available_maps = 0;
267         Map_Count = 0;
268         if(autocvar_g_maplist != "")
269         {
270                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
271                 for (i = 0; i < Map_Count; ++i)
272                         if (Map_Check(i, 2))
273                                 ++available_maps;
274         }
275
276         if (!available_maps)
277         {
278                 bprint( "Maplist contains no usable maps!  Resetting it to default map list.\n" );
279                 cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
280                 if(!server_is_dedicated)
281                         localcmd("\nmenu_cmd sync\n");
282                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
283                 for (i = 0; i < Map_Count; ++i)
284                         if (Map_Check(i, 2))
285                                 ++available_maps;
286         }
287
288         if(Map_Count == 0)
289                 error("empty maplist, cannot select a new map");
290
291         Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
292
293         if(autocvar_g_maplist_shuffle)
294                 cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
295
296         return available_maps;
297 }
298
299 // NOTE: call Maplist_Init() before making GetNextMap() call(s)
300 string GetNextMap(void)
301 {
302         int nextMap = -1;
303
304         if(nextMap == -1 && autocvar_g_maplist_shuffle > 0)
305                 nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
306
307         if(nextMap == -1 && autocvar_g_maplist_selectrandom)
308                 nextMap = MaplistMethod_Random();
309
310         if(nextMap == -1)
311                 nextMap = MaplistMethod_Iterate();
312
313         if(nextMap == -1)
314                 nextMap = MaplistMethod_Repeat();
315
316         if(nextMap >= 0)
317         {
318                 Map_Goto_SetIndex(nextMap);
319                 return getmapname_stored;
320         }
321
322         return "";
323 }
324
325 float DoNextMapOverride(float reinit)
326 {
327         if(autocvar_g_campaign)
328         {
329                 CampaignPostIntermission();
330                 alreadychangedlevel = true;
331                 return true;
332         }
333         if(autocvar_quit_when_empty)
334         {
335                 if(player_count <= currentbots)
336                 {
337                         localcmd("quit\n");
338                         alreadychangedlevel = true;
339                         return true;
340                 }
341         }
342         if(autocvar_quit_and_redirect != "")
343         {
344                 redirection_target = strzone(autocvar_quit_and_redirect);
345                 alreadychangedlevel = true;
346                 return true;
347         }
348         if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
349         {
350                 localcmd("restart\n");
351                 alreadychangedlevel = true;
352                 return true;
353         }
354         if(autocvar_nextmap != "")
355         {
356                 string m;
357                 m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
358                 cvar_set("nextmap",m);
359
360                 if(!m || gametypevote)
361                         return false;
362                 if(autocvar_sv_vote_gametype)
363                 {
364                         Map_Goto_SetStr(m);
365                         return false;
366                 }
367
368                 if(MapInfo_CheckMap(m))
369                 {
370                         Map_Goto_SetStr(m);
371                         Map_Goto(reinit);
372                         alreadychangedlevel = true;
373                         return true;
374                 }
375         }
376         if(!reinit && autocvar_lastlevel)
377         {
378                 cvar_settemp_restore();
379                 localcmd("set lastlevel 0\ntogglemenu 1\n");
380                 alreadychangedlevel = true;
381                 return true;
382         }
383         return false;
384 }
385
386 void GotoNextMap(float reinit)
387 {
388         //string nextmap;
389         //float n, nummaps;
390         //string s;
391         if (alreadychangedlevel)
392                 return;
393         alreadychangedlevel = true;
394
395         Maplist_Init();
396         string nextMap = GetNextMap();
397         if(nextMap == "")
398                 error("Everything is broken - cannot find a next map. Please report this to the developers.");
399         Map_Goto(reinit);
400 }
401
402 string GotoMap(string m)
403 {
404         m = GameTypeVote_MapInfo_FixName(m);
405         if (!m)
406                 return "The map you suggested is not available on this server.";
407         if (!autocvar_sv_vote_gametype)
408         if(!MapInfo_CheckMap(m))
409                 return "The map you suggested does not support the current game mode.";
410         cvar_set("nextmap", m);
411         if (!intermission_running)
412                 cvar_set("_endmatch", "1");
413         if(mapvote_initialized || alreadychangedlevel)
414         {
415                 if(DoNextMapOverride(0))
416                         return "Map switch initiated.";
417                 else
418                         return "Hm... no. For some reason I like THIS map more.";
419         }
420         else
421                 return "Map switch will happen after scoreboard.";
422 }
423
424
425 /*
426 ============
427 IntermissionThink
428
429 When the player presses attack or jump, change to the next level
430 ============
431 */
432 .float autoscreenshot;
433 void IntermissionThink(entity this)
434 {
435         FixIntermissionClient(this);
436
437         float server_screenshot = (autocvar_sv_autoscreenshot && CS_CVAR(this).cvar_cl_autoscreenshot);
438         float client_screenshot = (CS_CVAR(this).cvar_cl_autoscreenshot == 2);
439
440         if( (server_screenshot || client_screenshot)
441                 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
442         {
443                 this.autoscreenshot = -1;
444                 if(IS_REAL_CLIENT(this))
445                 {
446                         string num = strftime_s(); // strftime(false, "%s") isn't reliable, see strftime_s description
447                         stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; "
448                                 "echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), num));
449                 }
450                 return;
451         }
452
453         if (time < intermission_exittime)
454                 return;
455
456         if(!mapvote_initialized)
457                 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)))
458                         return;
459
460         MapVote_Start();
461 }
462
463 void FixIntermissionClient(entity e)
464 {
465         if(!e.autoscreenshot) // initial call
466         {
467                 e.autoscreenshot = time + 0.8;  // used for autoscreenshot
468                 SetResourceExplicit(e, RES_HEALTH, -2342); // health in the first intermission phase
469                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
470                 {
471                         .entity weaponentity = weaponentities[slot];
472                         if(e.(weaponentity))
473                         {
474                                 e.(weaponentity).effects = EF_NODRAW;
475                                 if (e.(weaponentity).weaponchild)
476                                         e.(weaponentity).weaponchild.effects = EF_NODRAW;
477                         }
478                 }
479                 if(IS_REAL_CLIENT(e))
480                 {
481                         stuffcmd(e, "\nscr_printspeed 1000000\n");
482                         RandomSelection_Init();
483                         FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
484                                 RandomSelection_AddString(it, 1, 1);
485                         });
486                         if (RandomSelection_chosen_string != "")
487                         {
488                                 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
489                         }
490                         msg_entity = e;
491                         WriteByte(MSG_ONE, SVC_INTERMISSION);
492                 }
493         }
494 }