]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/intermission.qc
mapinfo: fix and rename noautomaplist flag to donotwant
[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 float Map_Count, Map_Current;
24 string Map_Current_Name;
25
26 // NOTE: this now expects the map list to be already tokenized and the count in Map_Count
27 int GetMaplistPosition()
28 {
29         string map = GetMapname();
30         int idx = autocvar_g_maplist_index;
31
32         if(idx >= 0)
33         {
34                 if(idx < Map_Count)
35                 {
36                         if(map == argv(idx))
37                         {
38                                 return idx;
39                         }
40                 }
41         }
42
43         for(int pos = 0; pos < Map_Count; ++pos)
44         {
45                 if(map == argv(pos))
46                         return pos;
47         }
48
49         // resume normal maplist rotation if current map is not in g_maplist
50         return idx;
51 }
52
53 bool MapHasRightSize(string map)
54 {
55         int minplayers = max(0, floor(autocvar_minplayers));
56         if (teamplay)
57                 minplayers = max(0, floor(autocvar_minplayers_per_team) * AVAILABLE_TEAMS);
58         if (autocvar_g_maplist_check_waypoints
59                 && (currentbots || autocvar_bot_number || player_count < minplayers))
60         {
61                 string checkwp_msg = strcat("checkwp ", map);
62                 if(!fexists(strcat("maps/", map, ".waypoints")))
63                 {
64                         LOG_TRACE(checkwp_msg, ": no waypoints");
65                         return false;
66                 }
67                 LOG_TRACE(checkwp_msg, ": has waypoints");
68         }
69
70         if(autocvar_g_maplist_ignore_sizes)
71                 return true;
72
73         // open map size restriction file
74         if(!MapReadSizes(map))
75                 return true; // map has no size restrictions
76
77         string checksize_msg = strcat("MapHasRightSize ", map);
78         int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
79         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
80
81         if(!autocvar_g_maplist_sizes_count_bots)
82                 pcount -= currentbots;
83         pcount -= rint(cvar("g_maplist_sizes_specparty") * pcount);
84
85         // ensure small maps can be selected when pcount is low
86         if(map_minplayers <= (_MapInfo_GetTeamPlayBool(MapInfo_CurrentGametype()) ? 4 : 2))
87                 map_minplayers = 0;
88
89         if(pcount < map_minplayers)
90         {
91                 LOG_TRACE(checksize_msg, ": not enough");
92                 return false;
93         }
94         if(map_maxplayers && pcount > map_maxplayers)
95         {
96                 LOG_TRACE(checksize_msg, ": too many");
97                 return false;
98         }
99         LOG_TRACE(checksize_msg, ": right size");
100         return true;
101 }
102
103 string Map_Filename(int position)
104 {
105         return strcat("maps/", argv(position), ".bsp");
106 }
107
108 void Map_MarkAsRecent(string m)
109 {
110         cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count)));
111 }
112
113 bool Map_IsRecent(string m)
114 {
115         return strhasword(autocvar_g_maplist_mostrecent, m);
116 }
117
118 bool Map_Check(int position, float pass)
119 {
120         string filename;
121         string map_next;
122         map_next = argv(position);
123         if(pass <= 1)
124         {
125                 if(Map_IsRecent(map_next))
126                         return false;
127         }
128         filename = Map_Filename(position);
129         if(MapInfo_CheckMap(map_next))
130         {
131                 if(pass == 2)
132                         return true;
133                 // MapInfo_Map_flags was set by MapInfo_CheckMap()
134                 if (MapInfo_Map_flags & MAPINFO_FLAG_DONOTWANT)
135                         return false;
136                 if(MapHasRightSize(map_next))
137                         return true;
138                 return false;
139         }
140         else
141                 LOG_DEBUG( "Couldn't select '", filename, "'..." );
142
143         return false;
144 }
145
146 void Map_Goto_SetStr(string nextmapname)
147 {
148         if(getmapname_stored != "")
149                 strunzone(getmapname_stored);
150         if(nextmapname == "")
151                 getmapname_stored = "";
152         else
153                 getmapname_stored = strzone(nextmapname);
154 }
155
156 void Map_Goto_SetFloat(float position)
157 {
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 float MaplistMethod_Iterate() // usual method
171 {
172         float 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                         float 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 float MaplistMethod_Repeat() // 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 float MaplistMethod_Random() // random map selection
199 {
200         float i, imax;
201
202         LOG_TRACE("Trying MaplistMethod_Random");
203
204         imax = 42;
205
206         for(i = 0; i <= imax; ++i)
207         {
208                 float 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 float 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                 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
259                 if(Map_Check(Map_Current, 1))
260                         return Map_Current;
261         }
262         return -1;
263 }
264
265 void Maplist_Init()
266 {
267         float i = 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                 {
273                         if (Map_Check(i, 2))
274                                 break;
275                 }
276         }
277
278         if (i == Map_Count)
279         {
280                 bprint( "Maplist contains no usable maps!  Resetting it to default map list.\n" );
281                 cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
282                 if(autocvar_g_maplist_shuffle)
283                         ShuffleMaplist();
284                 if(!server_is_dedicated)
285                         localcmd("\nmenu_cmd sync\n");
286                 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
287         }
288         if(Map_Count == 0)
289                 error("empty maplist, cannot select a new map");
290         Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
291
292         strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
293         // this may or may not be correct, but who cares, in the worst case a map
294         // isn't chosen in the first pass that should have been
295 }
296
297 string GetNextMap()
298 {
299         Maplist_Init();
300         float nextMap = -1;
301
302         if(nextMap == -1)
303                 if(autocvar_g_maplist_shuffle > 0)
304                         nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
305
306         if(nextMap == -1)
307                 if(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_SetFloat(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         string nextMap = GetNextMap();
396         if(nextMap == "")
397                 error("Everything is broken - cannot find a next map. Please report this to the developers.");
398         Map_Goto(reinit);
399 }
400
401 void ShuffleMaplist()
402 {
403         cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
404 }
405
406 string GotoMap(string m)
407 {
408         m = GameTypeVote_MapInfo_FixName(m);
409         if (!m)
410                 return "The map you suggested is not available on this server.";
411         if (!autocvar_sv_vote_gametype)
412         if(!MapInfo_CheckMap(m))
413                 return "The map you suggested does not support the current game mode.";
414         cvar_set("nextmap", m);
415         if (!intermission_running)
416                 cvar_set("_endmatch", "1");
417         if(mapvote_initialized || alreadychangedlevel)
418         {
419                 if(DoNextMapOverride(0))
420                         return "Map switch initiated.";
421                 else
422                         return "Hm... no. For some reason I like THIS map more.";
423         }
424         else
425                 return "Map switch will happen after scoreboard.";
426 }
427
428
429 /*
430 ============
431 IntermissionThink
432
433 When the player presses attack or jump, change to the next level
434 ============
435 */
436 .float autoscreenshot;
437 void IntermissionThink(entity this)
438 {
439         FixIntermissionClient(this);
440
441         float server_screenshot = (autocvar_sv_autoscreenshot && CS_CVAR(this).cvar_cl_autoscreenshot);
442         float client_screenshot = (CS_CVAR(this).cvar_cl_autoscreenshot == 2);
443
444         if( (server_screenshot || client_screenshot)
445                 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
446         {
447                 this.autoscreenshot = -1;
448                 if(IS_REAL_CLIENT(this))
449                 {
450                         string num = strftime_s(); // strftime(false, "%s") isn't reliable, see strftime_s description
451                         stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; "
452                                 "echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), num));
453                 }
454                 return;
455         }
456
457         if (time < intermission_exittime)
458                 return;
459
460         if(!mapvote_initialized)
461                 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)))
462                         return;
463
464         MapVote_Start();
465 }
466
467 void FixIntermissionClient(entity e)
468 {
469         if(!e.autoscreenshot) // initial call
470         {
471                 e.autoscreenshot = time + 0.8;  // used for autoscreenshot
472                 SetResourceExplicit(e, RES_HEALTH, -2342); // health in the first intermission phase
473                 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
474                 {
475                         .entity weaponentity = weaponentities[slot];
476                         if(e.(weaponentity))
477                         {
478                                 e.(weaponentity).effects = EF_NODRAW;
479                                 if (e.(weaponentity).weaponchild)
480                                         e.(weaponentity).weaponchild.effects = EF_NODRAW;
481                         }
482                 }
483                 if(IS_REAL_CLIENT(e))
484                 {
485                         stuffcmd(e, "\nscr_printspeed 1000000\n");
486                         RandomSelection_Init();
487                         FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
488                                 RandomSelection_AddString(it, 1, 1);
489                         });
490                         if (RandomSelection_chosen_string != "")
491                         {
492                                 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
493                         }
494                         msg_entity = e;
495                         WriteByte(MSG_ONE, SVC_INTERMISSION);
496                 }
497         }
498 }