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) * AvailableTeams());
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 string opensize_msg = strcat("opensize ", map);
75 float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
76 int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
77 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
78 if(!autocvar_g_maplist_sizes_count_bots)
79 pcount -= currentbots;
82 opensize_msg = strcat(opensize_msg, ": ok, ");
83 int mapmin = stoi(fgets(fh));
84 int mapmax = stoi(fgets(fh));
88 LOG_TRACE(opensize_msg, "not enough");
91 if(mapmax && pcount > mapmax)
93 LOG_TRACE(opensize_msg, "too many");
96 LOG_TRACE(opensize_msg, "right size");
99 LOG_TRACE(opensize_msg, ": not found");
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 if(MapHasRightSize(map_next))
138 LOG_DEBUG( "Couldn't select '", filename, "'..." );
143 void Map_Goto_SetStr(string nextmapname)
145 if(getmapname_stored != "")
146 strunzone(getmapname_stored);
147 if(nextmapname == "")
148 getmapname_stored = "";
150 getmapname_stored = strzone(nextmapname);
153 void Map_Goto_SetFloat(float position)
155 cvar_set("g_maplist_index", ftos(position));
156 Map_Goto_SetStr(argv(position));
159 void Map_Goto(float reinit)
161 MapInfo_LoadMap(getmapname_stored, reinit);
164 // return codes of map selectors:
165 // -1 = temporary failure (that is, try some method that is guaranteed to succeed)
166 // -2 = permanent failure
167 float MaplistMethod_Iterate() // usual method
171 LOG_TRACE("Trying MaplistMethod_Iterate");
173 for(pass = 1; pass <= 2; ++pass)
175 for(i = 1; i < Map_Count; ++i)
178 mapindex = (i + Map_Current) % Map_Count;
179 if(Map_Check(mapindex, pass))
186 float MaplistMethod_Repeat() // fallback method
188 LOG_TRACE("Trying MaplistMethod_Repeat");
190 if(Map_Check(Map_Current, 2))
195 float MaplistMethod_Random() // random map selection
199 LOG_TRACE("Trying MaplistMethod_Random");
203 for(i = 0; i <= imax; ++i)
206 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
207 if(Map_Check(mapindex, 1))
213 float MaplistMethod_Shuffle(float exponent) // more clever shuffling
214 // the exponent sets a bias on the map selection:
215 // the higher the exponent, the less likely "shortly repeated" same maps are
217 float i, j, imax, insertpos;
219 LOG_TRACE("Trying MaplistMethod_Shuffle");
223 for(i = 0; i <= imax; ++i)
227 // now reinsert this at another position
228 insertpos = (random() ** (1 / exponent)); // ]0, 1]
229 insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1]
230 insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count}
231 LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
233 // insert the current map there
235 for(j = 1; j < insertpos; ++j) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
236 newlist = strcat(newlist, " ", argv(j));
237 newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map
238 for(j = insertpos; j < Map_Count; ++j) // i == Map_Count: no loop, has just been inserted as last
239 newlist = strcat(newlist, " ", argv(j));
240 newlist = substring(newlist, 1, strlen(newlist) - 1);
241 cvar_set("g_maplist", newlist);
242 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
244 // NOTE: the selected map has just been inserted at (insertpos-1)th position
245 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
246 if(Map_Check(Map_Current, 1))
254 float i = Map_Count = 0;
255 if(autocvar_g_maplist != "")
257 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
258 for (i = 0; i < Map_Count; ++i)
267 bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" );
268 cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST));
269 if(autocvar_g_maplist_shuffle)
271 if(!server_is_dedicated)
272 localcmd("\nmenu_cmd sync\n");
273 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
276 error("empty maplist, cannot select a new map");
277 Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
279 strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
280 // this may or may not be correct, but who cares, in the worst case a map
281 // isn't chosen in the first pass that should have been
290 if(autocvar_g_maplist_shuffle > 0)
291 nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
294 if(autocvar_g_maplist_selectrandom)
295 nextMap = MaplistMethod_Random();
298 nextMap = MaplistMethod_Iterate();
301 nextMap = MaplistMethod_Repeat();
305 Map_Goto_SetFloat(nextMap);
306 return getmapname_stored;
312 float DoNextMapOverride(float reinit)
314 if(autocvar_g_campaign)
316 CampaignPostIntermission();
317 alreadychangedlevel = true;
320 if(autocvar_quit_when_empty)
322 if(player_count <= currentbots)
325 alreadychangedlevel = true;
329 if(autocvar_quit_and_redirect != "")
331 redirection_target = strzone(autocvar_quit_and_redirect);
332 alreadychangedlevel = true;
335 if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
337 localcmd("restart\n");
338 alreadychangedlevel = true;
341 if(autocvar_nextmap != "")
344 m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
345 cvar_set("nextmap",m);
347 if(!m || gametypevote)
349 if(autocvar_sv_vote_gametype)
355 if(MapInfo_CheckMap(m))
359 alreadychangedlevel = true;
363 if(!reinit && autocvar_lastlevel)
365 cvar_settemp_restore();
366 localcmd("set lastlevel 0\ntogglemenu 1\n");
367 alreadychangedlevel = true;
373 void GotoNextMap(float reinit)
378 if (alreadychangedlevel)
380 alreadychangedlevel = true;
382 string nextMap = GetNextMap();
384 error("Everything is broken - cannot find a next map. Please report this to the developers.");
388 void ShuffleMaplist()
390 cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
393 string GotoMap(string m)
395 m = GameTypeVote_MapInfo_FixName(m);
397 return "The map you suggested is not available on this server.";
398 if (!autocvar_sv_vote_gametype)
399 if(!MapInfo_CheckMap(m))
400 return "The map you suggested does not support the current game mode.";
401 cvar_set("nextmap", m);
402 cvar_set("timelimit", "-1");
403 if(mapvote_initialized || alreadychangedlevel)
405 if(DoNextMapOverride(0))
406 return "Map switch initiated.";
408 return "Hm... no. For some reason I like THIS map more.";
411 return "Map switch will happen after scoreboard.";
419 When the player presses attack or jump, change to the next level
422 .float autoscreenshot;
423 void IntermissionThink(entity this)
425 FixIntermissionClient(this);
427 float server_screenshot = (autocvar_sv_autoscreenshot && CS_CVAR(this).cvar_cl_autoscreenshot);
428 float client_screenshot = (CS_CVAR(this).cvar_cl_autoscreenshot == 2);
430 if( (server_screenshot || client_screenshot)
431 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
433 this.autoscreenshot = -1;
434 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"))); }
438 if (time < intermission_exittime)
441 if(!mapvote_initialized)
442 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)))
448 void FixIntermissionClient(entity e)
450 if(!e.autoscreenshot) // initial call
452 e.autoscreenshot = time + 0.8; // used for autoscreenshot
453 SetResourceExplicit(e, RES_HEALTH, -2342); // health in the first intermission phase
454 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
456 .entity weaponentity = weaponentities[slot];
459 e.(weaponentity).effects = EF_NODRAW;
460 if (e.(weaponentity).weaponchild)
461 e.(weaponentity).weaponchild.effects = EF_NODRAW;
464 if(IS_REAL_CLIENT(e))
466 stuffcmd(e, "\nscr_printspeed 1000000\n");
467 RandomSelection_Init();
468 FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
469 RandomSelection_AddString(it, 1, 1);
471 if (RandomSelection_chosen_string != "")
473 stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
476 WriteByte(MSG_ONE, SVC_INTERMISSION);