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 int Map_Count, Map_Current;
25 // NOTE: this now expects the map list to be already tokenized and the count in Map_Count
26 int GetMaplistPosition()
28 string map = GetMapname();
29 int idx = autocvar_g_maplist_index;
42 for(int pos = 0; pos < Map_Count; ++pos)
48 // resume normal maplist rotation if current map is not in g_maplist
52 bool MapHasRightSize(string map)
54 int minplayers = max(0, floor(autocvar_minplayers));
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))
60 string checkwp_msg = strcat("checkwp ", map);
61 if(!fexists(strcat("maps/", map, ".waypoints")))
63 LOG_TRACE(checkwp_msg, ": no waypoints");
66 LOG_TRACE(checkwp_msg, ": has waypoints");
69 if(autocvar_g_maplist_ignore_sizes)
72 // open map size restriction file
73 if(!MapReadSizes(map))
74 return true; // map has no size restrictions
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
80 if(!autocvar_g_maplist_sizes_count_bots)
81 pcount -= currentbots;
82 pcount -= rint(cvar("g_maplist_sizes_specparty") * pcount);
84 // ensure small maps can be selected when pcount is low
85 if(map_minplayers <= (_MapInfo_GetTeamPlayBool(MapInfo_CurrentGametype()) ? 4 : 2))
88 if(pcount < map_minplayers)
90 LOG_TRACE(checksize_msg, ": not enough");
93 if(map_maxplayers && pcount > map_maxplayers)
95 LOG_TRACE(checksize_msg, ": too many");
98 LOG_TRACE(checksize_msg, ": right size");
102 string Map_Filename(int position)
104 return strcat("maps/", argv(position), ".bsp");
107 void Map_MarkAsRecent(string m)
109 cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count)));
112 bool Map_IsRecent(string m)
114 return strhasword(autocvar_g_maplist_mostrecent, m);
117 bool Map_Check(int position, float pass)
121 map_next = argv(position);
124 if(Map_IsRecent(map_next))
127 filename = Map_Filename(position);
128 if(MapInfo_CheckMap(map_next))
132 // MapInfo_Map_flags was set by MapInfo_CheckMap()
133 if (MapInfo_Map_flags & MAPINFO_FLAG_DONOTWANT)
135 if(MapHasRightSize(map_next))
140 LOG_DEBUG( "Couldn't select '", filename, "'..." );
145 void Map_Goto_SetStr(string nextmapname)
147 if(getmapname_stored != "")
148 strunzone(getmapname_stored);
149 if(nextmapname == "")
150 getmapname_stored = "";
152 getmapname_stored = strzone(nextmapname);
155 void Map_Goto_SetIndex(int position)
157 cvar_set("g_maplist_index", ftos(position));
158 Map_Goto_SetStr(argv(position));
161 void Map_Goto(float reinit)
163 MapInfo_LoadMap(getmapname_stored, reinit);
166 // return codes of map selectors:
167 // -1 = temporary failure (that is, try some method that is guaranteed to succeed)
168 // -2 = permanent failure
169 int MaplistMethod_Iterate(void) // usual method
173 LOG_TRACE("Trying MaplistMethod_Iterate");
175 for(pass = 1; pass <= 2; ++pass)
177 for(i = 1; i < Map_Count; ++i)
180 mapindex = (i + Map_Current) % Map_Count;
181 if(Map_Check(mapindex, pass))
188 int MaplistMethod_Repeat(void) // fallback method
190 LOG_TRACE("Trying MaplistMethod_Repeat");
192 if(Map_Check(Map_Current, 2))
197 int MaplistMethod_Random(void) // random map selection
201 LOG_TRACE("Trying MaplistMethod_Random");
205 for(i = 0; i <= imax; ++i)
208 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
209 if(Map_Check(mapindex, 1))
215 // the exponent sets a bias on the map selection:
216 // the higher the exponent, the less likely "shortly repeated" same maps are
217 int MaplistMethod_Shuffle(float exponent) // more clever shuffling
219 float i, j, imax, insertpos;
221 LOG_TRACE("Trying MaplistMethod_Shuffle");
225 for(i = 0; i <= imax; ++i)
229 // now reinsert this at another position
230 insertpos = (random() ** (1 / exponent)); // ]0, 1]
231 insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1]
232 insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count}
233 LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
235 // insert the current map there
237 for(j = 1; j < insertpos; ) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
239 if (j + 2 < insertpos)
240 newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++));
242 newlist = strcat(newlist, " ", argv(j++));
244 newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map
245 for(j = insertpos; j < Map_Count; ) // i == Map_Count: no loop, has just been inserted as last
247 if (j + 2 < Map_Count)
248 newlist = strcat(newlist, " ", argv(j++), " ", argv(j++), " ", argv(j++));
250 newlist = strcat(newlist, " ", argv(j++));
252 newlist = substring(newlist, 1, strlen(newlist) - 1);
253 cvar_set("g_maplist", newlist);
254 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
256 // NOTE: the selected map has just been inserted at (insertpos-1)th position
257 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
258 if(Map_Check(Map_Current, 1))
264 int Maplist_Init(void)
266 int i, available_maps = 0;
268 if(autocvar_g_maplist != "")
270 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
271 for (i = 0; i < Map_Count; ++i)
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)
289 error("empty maplist, cannot select a new map");
291 Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
293 if(autocvar_g_maplist_shuffle)
294 cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
296 return available_maps;
299 // NOTE: call Maplist_Init() before making GetNextMap() call(s)
300 string GetNextMap(void)
304 if(nextMap == -1 && autocvar_g_maplist_shuffle > 0)
305 nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
307 if(nextMap == -1 && autocvar_g_maplist_selectrandom)
308 nextMap = MaplistMethod_Random();
311 nextMap = MaplistMethod_Iterate();
314 nextMap = MaplistMethod_Repeat();
318 Map_Goto_SetIndex(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;
396 string nextMap = GetNextMap();
398 error("Everything is broken - cannot find a next map. Please report this to the developers.");
402 string GotoMap(string m)
404 m = GameTypeVote_MapInfo_FixName(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)
415 if(DoNextMapOverride(0))
416 return "Map switch initiated.";
418 return "Hm... no. For some reason I like THIS map more.";
421 return "Map switch will happen after scoreboard.";
429 When the player presses attack or jump, change to the next level
432 .float autoscreenshot;
433 void IntermissionThink(entity this)
435 FixIntermissionClient(this);
437 float server_screenshot = (autocvar_sv_autoscreenshot && CS_CVAR(this).cvar_cl_autoscreenshot);
438 float client_screenshot = (CS_CVAR(this).cvar_cl_autoscreenshot == 2);
440 if( (server_screenshot || client_screenshot)
441 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
443 this.autoscreenshot = -1;
444 if(IS_REAL_CLIENT(this))
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));
453 if (time < intermission_exittime)
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)))
463 void FixIntermissionClient(entity e)
465 if(!e.autoscreenshot) // initial call
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)
471 .entity weaponentity = weaponentities[slot];
474 e.(weaponentity).effects = EF_NODRAW;
475 if (e.(weaponentity).weaponchild)
476 e.(weaponentity).weaponchild.effects = EF_NODRAW;
479 if(IS_REAL_CLIENT(e))
481 stuffcmd(e, "\nscr_printspeed 1000000\n");
482 RandomSelection_Init();
483 FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
484 RandomSelection_AddString(it, 1, 1);
486 if (RandomSelection_chosen_string != "")
488 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
491 WriteByte(MSG_ONE, SVC_INTERMISSION);