1 #include "mapvoting.qh"
3 #include <common/mapinfo.qh>
4 #include <common/util.qh>
5 #include <server/bot/api.qh>
6 #include <server/campaign.qh>
7 #include <server/client.qh>
8 #include <server/mapvoting.qh>
9 #include <server/scores_rules.qh>
10 #include <server/world.qh>
14 return MapInfo_Type_ToString(MapInfo_LoadedGametype);
22 float Map_Count, Map_Current;
23 string Map_Current_Name;
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) * AvailableTeams());
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 string opensize_msg = strcat("opensize ", map);
74 float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
75 int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
76 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
77 if(!autocvar_g_maplist_sizes_count_bots)
78 pcount -= currentbots;
81 opensize_msg = strcat(opensize_msg, ": ok, ");
82 int mapmin = stoi(fgets(fh));
83 int mapmax = stoi(fgets(fh));
87 LOG_TRACE(opensize_msg, "not enough");
90 if(mapmax && pcount > mapmax)
92 LOG_TRACE(opensize_msg, "too many");
95 LOG_TRACE(opensize_msg, "right size");
98 LOG_TRACE(opensize_msg, ": not found");
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 if(MapHasRightSize(map_next))
137 LOG_DEBUG( "Couldn't select '", filename, "'..." );
142 void Map_Goto_SetStr(string nextmapname)
144 if(getmapname_stored != "")
145 strunzone(getmapname_stored);
146 if(nextmapname == "")
147 getmapname_stored = "";
149 getmapname_stored = strzone(nextmapname);
152 void Map_Goto_SetFloat(float position)
154 cvar_set("g_maplist_index", ftos(position));
155 Map_Goto_SetStr(argv(position));
158 void Map_Goto(float reinit)
160 MapInfo_LoadMap(getmapname_stored, reinit);
163 // return codes of map selectors:
164 // -1 = temporary failure (that is, try some method that is guaranteed to succeed)
165 // -2 = permanent failure
166 float MaplistMethod_Iterate() // usual method
170 LOG_TRACE("Trying MaplistMethod_Iterate");
172 for(pass = 1; pass <= 2; ++pass)
174 for(i = 1; i < Map_Count; ++i)
177 mapindex = (i + Map_Current) % Map_Count;
178 if(Map_Check(mapindex, pass))
185 float MaplistMethod_Repeat() // fallback method
187 LOG_TRACE("Trying MaplistMethod_Repeat");
189 if(Map_Check(Map_Current, 2))
194 float MaplistMethod_Random() // random map selection
198 LOG_TRACE("Trying MaplistMethod_Random");
202 for(i = 0; i <= imax; ++i)
205 mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
206 if(Map_Check(mapindex, 1))
212 float MaplistMethod_Shuffle(float exponent) // more clever shuffling
213 // the exponent sets a bias on the map selection:
214 // the higher the exponent, the less likely "shortly repeated" same maps are
216 float i, j, imax, insertpos;
218 LOG_TRACE("Trying MaplistMethod_Shuffle");
222 for(i = 0; i <= imax; ++i)
226 // now reinsert this at another position
227 insertpos = (random() ** (1 / exponent)); // ]0, 1]
228 insertpos = insertpos * (Map_Count - 1); // ]0, Map_Count - 1]
229 insertpos = ceil(insertpos) + 1; // {2, 3, 4, ..., Map_Count}
230 LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
232 // insert the current map there
234 for(j = 1; j < insertpos; ++j) // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
235 newlist = strcat(newlist, " ", argv(j));
236 newlist = strcat(newlist, " ", argv(0)); // now insert the just selected map
237 for(j = insertpos; j < Map_Count; ++j) // i == Map_Count: no loop, has just been inserted as last
238 newlist = strcat(newlist, " ", argv(j));
239 newlist = substring(newlist, 1, strlen(newlist) - 1);
240 cvar_set("g_maplist", newlist);
241 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
243 // NOTE: the selected map has just been inserted at (insertpos-1)th position
244 Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
245 if(Map_Check(Map_Current, 1))
253 float i = Map_Count = 0;
254 if(autocvar_g_maplist != "")
256 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
257 for (i = 0; i < Map_Count; ++i)
266 bprint( "Maplist contains no usable maps! Resetting it to default map list.\n" );
267 cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST));
268 if(autocvar_g_maplist_shuffle)
270 if(!server_is_dedicated)
271 localcmd("\nmenu_cmd sync\n");
272 Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
275 error("empty maplist, cannot select a new map");
276 Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
278 strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
279 // this may or may not be correct, but who cares, in the worst case a map
280 // isn't chosen in the first pass that should have been
289 if(autocvar_g_maplist_shuffle > 0)
290 nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
293 if(autocvar_g_maplist_selectrandom)
294 nextMap = MaplistMethod_Random();
297 nextMap = MaplistMethod_Iterate();
300 nextMap = MaplistMethod_Repeat();
304 Map_Goto_SetFloat(nextMap);
305 return getmapname_stored;
311 float DoNextMapOverride(float reinit)
313 if(autocvar_g_campaign)
315 CampaignPostIntermission();
316 alreadychangedlevel = true;
319 if(autocvar_quit_when_empty)
321 if(player_count <= currentbots)
324 alreadychangedlevel = true;
328 if(autocvar_quit_and_redirect != "")
330 redirection_target = strzone(autocvar_quit_and_redirect);
331 alreadychangedlevel = true;
334 if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
336 localcmd("restart\n");
337 alreadychangedlevel = true;
340 if(autocvar_nextmap != "")
343 m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
344 cvar_set("nextmap",m);
346 if(!m || gametypevote)
348 if(autocvar_sv_vote_gametype)
354 if(MapInfo_CheckMap(m))
358 alreadychangedlevel = true;
362 if(!reinit && autocvar_lastlevel)
364 cvar_settemp_restore();
365 localcmd("set lastlevel 0\ntogglemenu 1\n");
366 alreadychangedlevel = true;
372 void GotoNextMap(float reinit)
377 if (alreadychangedlevel)
379 alreadychangedlevel = true;
381 string nextMap = GetNextMap();
383 error("Everything is broken - cannot find a next map. Please report this to the developers.");
387 void ShuffleMaplist()
389 cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
392 string GotoMap(string m)
394 m = GameTypeVote_MapInfo_FixName(m);
396 return "The map you suggested is not available on this server.";
397 if (!autocvar_sv_vote_gametype)
398 if(!MapInfo_CheckMap(m))
399 return "The map you suggested does not support the current game mode.";
400 cvar_set("nextmap", m);
401 cvar_set("timelimit", "-1");
402 if(mapvote_initialized || alreadychangedlevel)
404 if(DoNextMapOverride(0))
405 return "Map switch initiated.";
407 return "Hm... no. For some reason I like THIS map more.";
410 return "Map switch will happen after scoreboard.";
418 When the player presses attack or jump, change to the next level
421 .float autoscreenshot;
422 void IntermissionThink(entity this)
424 FixIntermissionClient(this);
426 float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot);
427 float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2);
429 if( (server_screenshot || client_screenshot)
430 && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
432 this.autoscreenshot = -1;
433 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"))); }
437 if (time < intermission_exittime)
440 if(!mapvote_initialized)
441 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)))
447 void FixIntermissionClient(entity e)
449 if(!e.autoscreenshot) // initial call
451 e.autoscreenshot = time + 0.8; // used for autoscreenshot
452 SetResourceExplicit(e, RES_HEALTH, -2342);
453 // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
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);