1 #include "mapvoting.qh"
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>
15 return MapInfo_Type_ToString(MapInfo_LoadedGametype);
23 float Map_Count, Map_Current;
24 string Map_Current_Name;
26 // NOTE: this now expects the map list to be already tokenized and the count in Map_Count
27 int GetMaplistPosition()
29 string map = GetMapname();
30 int idx = autocvar_g_maplist_index;
43 for(int pos = 0; pos < Map_Count; ++pos)
49 // resume normal maplist rotation if current map is not in g_maplist
53 bool MapHasRightSize(string map)
55 int minplayers = max(0, floor(autocvar_minplayers));
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))
61 string checkwp_msg = strcat("checkwp ", map);
62 if(!fexists(strcat("maps/", map, ".waypoints")))
64 LOG_TRACE(checkwp_msg, ": no waypoints");
67 LOG_TRACE(checkwp_msg, ": has waypoints");
70 if(autocvar_g_maplist_ignore_sizes)
73 // open map size restriction file
74 if(!MapReadSizes(map))
75 return true; // map has no size restrictions
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
81 if(!autocvar_g_maplist_sizes_count_bots)
82 pcount -= currentbots;
83 pcount -= rint(cvar("g_maplist_sizes_specparty") * pcount);
85 // ensure small maps can be selected when pcount is low
86 if(map_minplayers <= (_MapInfo_GetTeamPlayBool(MapInfo_CurrentGametype()) ? 4 : 2))
89 if(pcount < map_minplayers)
91 LOG_TRACE(checksize_msg, ": not enough");
94 if(map_maxplayers && pcount > map_maxplayers)
96 LOG_TRACE(checksize_msg, ": too many");
99 LOG_TRACE(checksize_msg, ": right size");
103 string Map_Filename(int position)
105 return strcat("maps/", argv(position), ".bsp");
108 void Map_MarkAsRecent(string m)
110 cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count)));
113 bool Map_IsRecent(string m)
115 return strhasword(autocvar_g_maplist_mostrecent, m);
118 bool Map_Check(int position, float pass)
122 map_next = argv(position);
125 if(Map_IsRecent(map_next))
128 filename = Map_Filename(position);
129 if(MapInfo_CheckMap(map_next))
133 // MapInfo_Map_flags was set by MapInfo_CheckMap()
134 if (MapInfo_Map_flags & MAPINFO_FLAG_DONOTWANT)
136 if(MapHasRightSize(map_next))
141 LOG_DEBUG( "Couldn't select '", filename, "'..." );
146 void Map_Goto_SetStr(string nextmapname)
148 if(getmapname_stored != "")
149 strunzone(getmapname_stored);
150 if(nextmapname == "")
151 getmapname_stored = "";
153 getmapname_stored = strzone(nextmapname);
156 void Map_Goto_SetFloat(float position)
158 cvar_set("g_maplist_index", ftos(position));
159 Map_Goto_SetStr(argv(position));
162 void Map_Goto(float reinit)
164 MapInfo_LoadMap(getmapname_stored, reinit);
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
174 LOG_TRACE("Trying MaplistMethod_Iterate");
176 for(pass = 1; pass <= 2; ++pass)
178 for(i = 1; i < Map_Count; ++i)
181 mapindex = (i + Map_Current) % Map_Count;
182 if(Map_Check(mapindex, pass))
189 float MaplistMethod_Repeat() // fallback method
191 LOG_TRACE("Trying MaplistMethod_Repeat");
193 if(Map_Check(Map_Current, 2))
198 float MaplistMethod_Random() // random map selection
202 LOG_TRACE("Trying MaplistMethod_Random");
206 for(i = 0; i <= imax; ++i)
209 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
210 if(Map_Check(mapindex, 1))
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
220 float i, j, imax, insertpos;
222 LOG_TRACE("Trying MaplistMethod_Shuffle");
226 for(i = 0; i <= imax; ++i)
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));
236 // insert the current map there
238 for(j = 1; j < insertpos; ) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
240 if (j + 2 < insertpos)
241 newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++));
243 newlist = strcat(newlist, " ", argv(j++));
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
248 if (j + 2 < Map_Count)
249 newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++));
251 newlist = strcat(newlist, " ", argv(j++));
253 newlist = substring(newlist, 1, strlen(newlist) - 1);
254 cvar_set("g_maplist", newlist);
255 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
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))
267 float i = Map_Count = 0;
268 if(autocvar_g_maplist != "")
270 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
271 for (i = 0; i < Map_Count; ++i)
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)
284 if(!server_is_dedicated)
285 localcmd("\nmenu_cmd sync\n");
286 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
289 error("empty maplist, cannot select a new map");
290 Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
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
303 if(autocvar_g_maplist_shuffle > 0)
304 nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
307 if(autocvar_g_maplist_selectrandom)
308 nextMap = MaplistMethod_Random();
311 nextMap = MaplistMethod_Iterate();
314 nextMap = MaplistMethod_Repeat();
318 Map_Goto_SetFloat(nextMap);
319 return getmapname_stored;
325 float DoNextMapOverride(float reinit)
327 if(autocvar_g_campaign)
329 CampaignPostIntermission();
330 alreadychangedlevel = true;
333 if(autocvar_quit_when_empty)
335 if(player_count <= currentbots)
338 alreadychangedlevel = true;
342 if(autocvar_quit_and_redirect != "")
344 redirection_target = strzone(autocvar_quit_and_redirect);
345 alreadychangedlevel = true;
348 if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
350 localcmd("restart\n");
351 alreadychangedlevel = true;
354 if(autocvar_nextmap != "")
357 m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
358 cvar_set("nextmap",m);
360 if(!m || gametypevote)
362 if(autocvar_sv_vote_gametype)
368 if(MapInfo_CheckMap(m))
372 alreadychangedlevel = true;
376 if(!reinit && autocvar_lastlevel)
378 cvar_settemp_restore();
379 localcmd("set lastlevel 0\ntogglemenu 1\n");
380 alreadychangedlevel = true;
386 void GotoNextMap(float reinit)
391 if (alreadychangedlevel)
393 alreadychangedlevel = true;
395 string nextMap = GetNextMap();
397 error("Everything is broken - cannot find a next map. Please report this to the developers.");
401 void ShuffleMaplist()
403 cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
406 string GotoMap(string m)
408 m = GameTypeVote_MapInfo_FixName(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)
419 if(DoNextMapOverride(0))
420 return "Map switch initiated.";
422 return "Hm... no. For some reason I like THIS map more.";
425 return "Map switch will happen after scoreboard.";
433 When the player presses attack or jump, change to the next level
436 .float autoscreenshot;
437 void IntermissionThink(entity this)
439 FixIntermissionClient(this);
441 float server_screenshot = (autocvar_sv_autoscreenshot && CS_CVAR(this).cvar_cl_autoscreenshot);
442 float client_screenshot = (CS_CVAR(this).cvar_cl_autoscreenshot == 2);
444 if( (server_screenshot || client_screenshot)
445 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
447 this.autoscreenshot = -1;
448 if(IS_REAL_CLIENT(this))
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));
457 if (time < intermission_exittime)
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)))
467 void FixIntermissionClient(entity e)
469 if(!e.autoscreenshot) // initial call
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)
475 .entity weaponentity = weaponentities[slot];
478 e.(weaponentity).effects = EF_NODRAW;
479 if (e.(weaponentity).weaponchild)
480 e.(weaponentity).weaponchild.effects = EF_NODRAW;
483 if(IS_REAL_CLIENT(e))
485 stuffcmd(e, "\nscr_printspeed 1000000\n");
486 RandomSelection_Init();
487 FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
488 RandomSelection_AddString(it, 1, 1);
490 if (RandomSelection_chosen_string != "")
492 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
495 WriteByte(MSG_ONE, SVC_INTERMISSION);