From 84d3168e45d3923201fe690255afdb656c708615 Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 20 Mar 2014 10:48:21 +1100 Subject: [PATCH] Gametype vote screen by Melanosuchus --- defaultXonotic.cfg | 6 + gamemodes.cfg | 22 ++ qcsrc/client/mapvoting.qc | 374 ++++++++++++++++---- qcsrc/common/constants.qh | 7 +- qcsrc/common/mapinfo.qc | 16 +- qcsrc/common/mapinfo.qh | 37 +- qcsrc/server/autocvars.qh | 5 + qcsrc/server/g_world.qc | 543 +--------------------------- qcsrc/server/mapvoting.qc | 728 ++++++++++++++++++++++++++++++++++++++ qcsrc/server/mapvoting.qh | 39 ++ qcsrc/server/progs.src | 4 + 11 files changed, 1170 insertions(+), 611 deletions(-) create mode 100644 qcsrc/server/mapvoting.qc create mode 100644 qcsrc/server/mapvoting.qh diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg index 747e0c4192..796b1f9987 100644 --- a/defaultXonotic.cfg +++ b/defaultXonotic.cfg @@ -812,6 +812,12 @@ seta g_maplist_votable_nodetail 1 "nodetail only shows total count instead of al seta g_maplist_votable_abstain 0 "when 1, you can abstain from your vote" seta g_maplist_votable_screenshot_dir "maps levelshots" "where to look for map screenshots" +set sv_vote_gametype 0 "show a vote screen for gametypes before map vote screen" +set sv_vote_gametype_keeptwotime 10 "show only 2 options for this amount of time during gametype vote screen" +set sv_vote_gametype_options "dm ctf ca lms tdm ft" +set sv_vote_gametype_timeout 20 +set sv_vote_gametype_default_current 1 "Keep the current gametype if no one votes" + set g_chat_flood_spl 3 "normal chat: seconds between lines to not count as flooding" set g_chat_flood_lmax 2 "normal chat: maximum number of lines per chat message at once" set g_chat_flood_burst 2 "normal chat: allow bursts of so many chat lines" diff --git a/gamemodes.cfg b/gamemodes.cfg index 30352bba7b..edf68ac6e7 100644 --- a/gamemodes.cfg +++ b/gamemodes.cfg @@ -62,6 +62,28 @@ alias sv_hook_gamerestart alias sv_hook_gameend +// ===================== +// gametype vote hooks +// ===================== +// these are called when the mode is switched via gametype vote screen, earlier than gamestart hooks (useful for enabling per-gamemode mutators) +alias sv_vote_gametype_hook_all +alias sv_vote_gametype_hook_as +alias sv_vote_gametype_hook_ca +alias sv_vote_gametype_hook_ctf +alias sv_vote_gametype_hook_cts +alias sv_vote_gametype_hook_dm +alias sv_vote_gametype_hook_dom +alias sv_vote_gametype_hook_ft +alias sv_vote_gametype_hook_inv +alias sv_vote_gametype_hook_ka +alias sv_vote_gametype_hook_kh +alias sv_vote_gametype_hook_lms +alias sv_vote_gametype_hook_nb +alias sv_vote_gametype_hook_ons +alias sv_vote_gametype_hook_rc +alias sv_vote_gametype_hook_tdm + + // =========== // leadlimit // =========== diff --git a/qcsrc/client/mapvoting.qc b/qcsrc/client/mapvoting.qc index 8caeb01d52..a3f5d45e9e 100644 --- a/qcsrc/client/mapvoting.qc +++ b/qcsrc/client/mapvoting.qc @@ -6,17 +6,24 @@ string mv_pics[MAPVOTE_COUNT]; string mv_pk3[MAPVOTE_COUNT]; float mv_preview[MAPVOTE_COUNT]; float mv_votes[MAPVOTE_COUNT]; +float mv_avail[MAPVOTE_COUNT]; +float mv_avail_start[MAPVOTE_COUNT]; entity mv_pk3list; float mv_abstain; float mv_ownvote; float mv_detail; float mv_timeout; -float mv_maps_mask; float mv_top2_time; float mv_top2_alpha; vector mv_mousepos; float mv_selection; +float mv_columns; + +float gametypevote; +string mapvote_choosenmap; +vector gtv_text_size; +vector gtv_text_size_small; string MapVote_FormatMapItem(float id, string map, float count, float maxwidth, vector fontsize) { @@ -26,7 +33,7 @@ string MapVote_FormatMapItem(float id, string map, float count, float maxwidth, { if(count == 1) post = _(" (1 vote)"); - else if(count >= 0) + else if(count >= 0 && mv_avail[id] == GTV_AVAILABLE) post = sprintf(_(" (%d votes)"), count); else post = ""; @@ -38,9 +45,14 @@ string MapVote_FormatMapItem(float id, string map, float count, float maxwidth, return strcat(pre, map, post); } -vector MapVote_RGB(float id, float count) +string GameTypeVote_DescriptionByID(float id) +{ + return MapInfo_Type_Description(MapInfo_Type_FromString(mv_maps[id])); +} + +vector MapVote_RGB(float id) { - if(count < 0) + if(mv_avail[id] != GTV_AVAILABLE) return '1 1 1'; if(id == mv_ownvote) return '0 1 0'; @@ -50,6 +62,100 @@ vector MapVote_RGB(float id, float count) return '1 1 1'; } +void GameTypeVote_DrawGameTypeItem(vector pos, float maxh, float tsize, string gtype, string pic, float count, float id) +{ + float alpha; + float desc_padding = gtv_text_size_x * 3; + float rect_margin = hud_fontsize_y / 2; + vector rect_pos = pos - '0.5 0.5 0' * rect_margin; + vector rect_size = '1 1 0'; + rect_size_x = tsize + rect_margin; + rect_size_y = maxh + rect_margin; + vector rgb = MapVote_RGB(id); + vector offset = pos; + float nlines = 0; + + if(mv_avail_start[id] != GTV_AVAILABLE) + alpha = 0.2; + else if ( mv_avail[id] != GTV_AVAILABLE && mv_top2_alpha) + alpha = mv_top2_alpha; + else + alpha = 1; + + if(id == mv_selection && mv_avail[id] == GTV_AVAILABLE) + { + drawfill(rect_pos, rect_size, '1 1 1', 0.1, DRAWFLAG_NORMAL); + } + if(id == mv_ownvote) + { + drawfill(rect_pos, rect_size, rgb, 0.1*alpha, DRAWFLAG_NORMAL); + drawborderlines(autocvar_scoreboard_border_thickness, rect_pos, rect_size, rgb, alpha, DRAWFLAG_NORMAL); + } + + entity title; + title = spawn(); + title.message = MapVote_FormatMapItem(id, MapInfo_Type_ToText(MapInfo_Type_FromString(gtype)), + count, tsize, gtv_text_size); + title.origin = pos-offset; + + pos_y += gtv_text_size_small_y; + pos_y += gtv_text_size_y/2; + + maxh -= gtv_text_size_y; + + entity picent = spawn(); + picent.origin = pos-offset; + picent.maxs = '1 1 0 ' * min(maxh, desc_padding) * 0.8; + + pos_x += desc_padding; + tsize -= desc_padding; + + string thelabel = GameTypeVote_DescriptionByID(id), ts; + entity last = title; + entity next = world; + if( thelabel != "") + { + float i,n = tokenizebyseparator(thelabel, "\n"); + for(i = 0; i < n && maxh > (nlines+1)*gtv_text_size_small_y; ++i) + { + getWrappedLine_remaining = argv(i); + while(getWrappedLine_remaining && maxh > (nlines+1)*gtv_text_size_small_y) + { + ts = getWrappedLine(tsize, gtv_text_size_small, stringwidth_colors); + if (ts != "") + { + next = spawn(); + next.message = ts; + next.origin = pos-offset; + last.chain = next; + last = next; + pos_y += gtv_text_size_small_y; + nlines++; + } + } + } + } + + maxh -= max(nlines*gtv_text_size_small_y,picent.maxs_y); + if ( maxh > 0 ) + offset_y += maxh/2; + drawstring(title.origin+offset, title.message, gtv_text_size, rgb, alpha, DRAWFLAG_NORMAL); + + if(pic != "") + drawpic(picent.origin+offset, pic, picent.maxs, '1 1 1', alpha, DRAWFLAG_NORMAL); + + for ( last = title.chain; last ; ) + { + drawstring(last.origin+offset, last.message, gtv_text_size_small, '1 1 1', alpha, DRAWFLAG_NORMAL); + next = last; + last = last.chain; + remove(next); + } + + remove(picent); + remove(title); +} + void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, string pic, float count, float id) { vector img_size = '0 0 0'; @@ -59,7 +165,7 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin isize -= hud_fontsize_y; // respect the text when calculating the image size - rgb = MapVote_RGB(id, count); + rgb = MapVote_RGB(id); img_size_y = isize; img_size_x = isize / 0.75; // 4:3 x can be stretched easily, height is defined in isize @@ -71,7 +177,7 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin text_size = stringwidth(label, false, hud_fontsize); float theAlpha; - if (count < 0 && mv_top2_alpha) + if (mv_avail[id] != GTV_AVAILABLE && mv_top2_alpha) theAlpha = mv_top2_alpha; else theAlpha = 1; @@ -101,7 +207,7 @@ void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, strin else drawborderlines(autocvar_scoreboard_border_thickness, pos, img_size, '0 0 0', theAlpha, DRAWFLAG_NORMAL); - if(id == mv_selection && count >= 0) + if(id == mv_selection && mv_avail[id] == GTV_AVAILABLE) drawfill(pos, img_size, '1 1 1', 0.1, DRAWFLAG_NORMAL); } @@ -111,7 +217,7 @@ void MapVote_DrawAbstain(vector pos, float isize, float tsize, float count, floa float text_size; string label; - rgb = MapVote_RGB(id, count); + rgb = MapVote_RGB(id); pos_y = pos_y + hud_fontsize_y; @@ -169,7 +275,7 @@ void MapVote_Draw() vector pos; float isize; float center; - float columns, rows; + float rows; float tsize; vector dist = '0 0 0'; @@ -200,11 +306,18 @@ void MapVote_Draw() pos_z = 0; draw_beginBoldFont(); - map = _("Vote for a map"); + map = ((gametypevote) ? _("Decide the gametype") : _("Vote for a map")); pos_x = center - stringwidth(map, false, '12 0 0'); drawstring(pos, map, '24 24 0', '1 1 1', 1, DRAWFLAG_NORMAL); pos_y += 26; + if(gametypevote && mapvote_choosenmap != "" ) + { + pos_x = center - stringwidth(mapvote_choosenmap, false, hud_fontsize); + drawstring(pos, mapvote_choosenmap, hud_fontsize*2, '1 1 1', 1, DRAWFLAG_NORMAL); + pos_y += hud_fontsize_y*2; + } + i = ceil(max(0, mv_timeout - time)); map = sprintf(_("%d seconds left"), i); pos_x = center - stringwidth(map, false, '8 0 0'); @@ -218,36 +331,56 @@ void MapVote_Draw() if(mv_abstain) mv_num_maps -= 1; - if(mv_num_maps > 3) - { - columns = 3; - } else { - columns = mv_num_maps; - } - rows = ceil(mv_num_maps / columns); + rows = ceil(mv_num_maps / mv_columns); - dist_x = (xmax - xmin) / columns; + dist_x = (xmax - xmin) / mv_columns; dist_y = (ymax - pos_y) / rows; - tsize = dist_x - 10; - isize = min(dist_y - 10, 0.75 * tsize); - mv_selection = MapVote_Selection(pos, dist, rows, columns); + if ( gametypevote ) + { + tsize = dist_x - hud_fontsize_y; + isize = dist_y; + float maxheight = (ymax - pos_y) / 3; + if ( isize > maxheight ) + { + pos_x += (isize - maxheight)/2; + isize = maxheight; + } + else + dist_y += hud_fontsize_y; + pos_x = ( vid_conwidth - dist_x * mv_columns ) / 2; + } + else + { + tsize = dist_x - 10; + isize = min(dist_y - 10, 0.75 * tsize); + } + + mv_selection = MapVote_Selection(pos, dist, rows, mv_columns); - pos_x += (xmax - xmin) / (2 * columns); + if ( !gametypevote ) + pos_x += dist_x / 2; pos_y += (dist_y - isize) / 2; ymax -= isize; if (mv_top2_time) mv_top2_alpha = max(0.2, 1 - (time - mv_top2_time)*(time - mv_top2_time)); + void (vector, float, float, string, string, float, float) DrawItem; + + if(gametypevote) + DrawItem = GameTypeVote_DrawGameTypeItem; + else + DrawItem = MapVote_DrawMapItem; + for(i = 0; i < mv_num_maps; ++i) { tmp = mv_votes[i]; // FTEQCC bug: too many array accesses in the function call screw it up map = mv_maps[i]; if(mv_preview[i]) - MapVote_DrawMapItem(pos + MapVote_GridVec(dist, i, columns), isize, tsize, map, mv_pics[i], tmp, i); + DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), isize, tsize, map, mv_pics[i], tmp, i); else - MapVote_DrawMapItem(pos + MapVote_GridVec(dist, i, columns), isize, tsize, map, "", tmp, i); + DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), isize, tsize, map, "", tmp, i); } if(mv_abstain) @@ -329,12 +462,35 @@ void MapVote_CheckPic(string pic, string pk3, float id) MapVote_CheckPK3(pic, pk3, id); } +void MapVote_ReadMask() +{ + float i; + if ( mv_num_maps < 24 ) + { + float mask, power; + if(mv_num_maps < 8) + mask = ReadByte(); + else if(mv_num_maps < 16) + mask = ReadShort(); + else + mask = ReadLong(); + + for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2) + mv_avail[i] = (mask & power) ? GTV_AVAILABLE : GTV_FORBIDDEN; + } + else + { + for(i = 0; i < mv_num_maps; ++i ) + mv_avail[i] = ReadByte(); + } +} + #define NUM_SSDIRS 4 string ssdirs[NUM_SSDIRS]; float n_ssdirs; void MapVote_Init() { - float i, j, power; + float i, j; string map, pk3, s; precache_sound ("misc/invshot.wav"); @@ -363,39 +519,67 @@ void MapVote_Init() mv_ownvote = -1; mv_timeout = ReadCoord(); - if(mv_num_maps <= 8) - mv_maps_mask = ReadByte(); - else - mv_maps_mask = ReadShort(); + gametypevote = ReadByte(); + + float mv_real_num_maps = mv_num_maps - mv_abstain; + + if(gametypevote) + { + // read map name in case we have nextmap set + mapvote_choosenmap = strzone(ReadString()); + + gtv_text_size = hud_fontsize*1.4; + gtv_text_size_small = hud_fontsize*1.1; + + if (mv_real_num_maps > 8 ) + mv_columns = 3; + else + mv_columns = min(2, mv_real_num_maps); + } + else + { + if (mv_real_num_maps > 16) + mv_columns = 5; + else if (mv_real_num_maps > 9) + mv_columns = 4; + else if(mv_real_num_maps > 3) + mv_columns = 3; + else + mv_columns = mv_real_num_maps; + } + + MapVote_ReadMask(); + for(i = 0; i < mv_num_maps; ++i ) + mv_avail_start[i] = mv_avail[i]; // Assume mv_pk3list is world, there should only be 1 mapvote per round mv_pk3list = world; // I'm still paranoid! - for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2) + for(i = 0; i < mv_num_maps; ++i) { mv_votes[i] = 0; - if(mv_maps_mask & power) - { - map = strzone(ReadString()); - pk3 = strzone(ReadString()); - j = bound(0, ReadByte(), n_ssdirs - 1); - - mv_maps[i] = map; - mv_pk3[i] = pk3; - map = strzone(strcat(ssdirs[j], "/", map)); - mv_pics[i] = map; + map = strzone(ReadString()); + pk3 = strzone(ReadString()); + j = bound(0, ReadByte(), n_ssdirs - 1); - mv_preview[i] = false; + mv_maps[i] = map; + mv_pk3[i] = pk3; + mv_avail[i] = ReadByte(); - MapVote_CheckPic(map, pk3, i); + if(gametypevote) + { + //map = strzone(strcat("gfx/menu/default/gametype_", map)); + map = strzone(sprintf("gfx/menu/%s/gametype_%s", autocvar_menu_skin, map)); + mv_pics[i] = map; + mv_preview[i] = PreviewExists(map); } else { - mv_maps[i] = strzone("if-you-see-this-the-code-is-broken"); - mv_pk3[i] = strzone("if-you-see-this-the-code-is-broken"); - mv_pics[i] = strzone("if-you-see-this-the-code-is-broken"); + map = strzone(strcat(ssdirs[j], "/", map)); + mv_pics[i] = map; mv_preview[i] = false; + MapVote_CheckPic(map, pk3, i); } } @@ -404,6 +588,68 @@ void MapVote_Init() n_ssdirs = 0; } +void MapVote_SendChoice(float index) +{ + localcmd(strcat("\nimpulse ", ftos(index+1), "\n")); +} + +float MapVote_MoveLeft(float pos) +{ + float imp; + if ( pos < 0 ) + imp = mv_num_maps - 1; + else + imp = pos < 1 ? mv_num_maps - 1 : pos - 1; + if ( mv_avail[imp] != GTV_AVAILABLE && imp != mv_ownvote ) + imp = MapVote_MoveLeft(imp); + return imp; +} +float MapVote_MoveRight(float pos) +{ + float imp; + if ( pos < 0 ) + imp = 0; + else + imp = pos >= mv_num_maps - 1 ? 0 : pos + 1; + if ( mv_avail[imp] != GTV_AVAILABLE && imp != mv_ownvote ) + imp = MapVote_MoveRight(imp); + return imp; +} +float MapVote_MoveUp(float pos) +{ + float imp; + if ( pos < 0 ) + imp = mv_num_maps - 1; + else + { + imp = pos - mv_columns; + if ( imp < 0 ) + { + imp = floor(mv_num_maps/mv_columns)*mv_columns + pos % mv_columns; + if ( imp >= mv_num_maps ) + imp -= mv_columns; + } + } + if ( mv_avail[imp] != GTV_AVAILABLE && imp != mv_ownvote ) + imp = MapVote_MoveUp(imp); + return imp; +} +float MapVote_MoveDown(float pos) +{ + float imp; + if ( pos < 0 ) + imp = 0; + else + { + imp = pos + mv_columns; + if ( imp >= mv_num_maps ) + imp = imp % mv_columns; + } + if ( mv_avail[imp] != GTV_AVAILABLE && imp != mv_ownvote ) + imp = MapVote_MoveDown(imp); + return imp; +} + float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary) { float imp; @@ -440,6 +686,19 @@ float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary) case K_KP_8: localcmd("\nimpulse 8\n"); return true; case K_KP_9: localcmd("\nimpulse 9\n"); return true; case K_KP_0: localcmd("\nimpulse 10\n"); return true; + + case K_RIGHTARROW: + MapVote_SendChoice(MapVote_MoveRight(mv_ownvote)); + return true; + case K_LEFTARROW: + MapVote_SendChoice(MapVote_MoveLeft(mv_ownvote)); + return true; + case K_DOWNARROW: + MapVote_SendChoice(MapVote_MoveDown(mv_ownvote)); + return true; + case K_UPARROW: + MapVote_SendChoice(MapVote_MoveUp(mv_ownvote)); + return true; } if (nPrimary == K_MOUSE1) @@ -455,33 +714,16 @@ float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary) void MapVote_UpdateMask() { - float i, power; - float oldmask; - - oldmask = mv_maps_mask; - if(mv_num_maps <= 8) - mv_maps_mask = ReadByte(); - else - mv_maps_mask = ReadShort(); - - if((oldmask & mv_maps_mask) != oldmask) - if((oldmask & mv_maps_mask) == mv_maps_mask) - sound(world, CH_INFO, "misc_invshot.wav", VOL_BASE, ATTEN_NONE); - - // remove votes that no longer apply - for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2) - if (!(mv_maps_mask & power)) - mv_votes[i] = -1; - + MapVote_ReadMask(); mv_top2_time = time; } void MapVote_UpdateVotes() { - float i, power; - for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2) + float i; + for(i = 0; i < mv_num_maps; ++i) { - if(mv_maps_mask & power) + if(mv_avail[i] == GTV_AVAILABLE) { if(mv_detail) mv_votes[i] = ReadByte(); diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index e02fad45f0..00f8358c7a 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -230,7 +230,7 @@ const vector eZ = '0 0 1'; // moved that here so the client knows the max. // # of maps, I'll use arrays for them :P -#define MAPVOTE_COUNT 10 +#define MAPVOTE_COUNT 30 /** * Lower scores are better (e.g. suicides) @@ -455,3 +455,8 @@ noref var vector autocvar_sv_player_headsize = '24 24 12'; #define URI_GET_UPDATENOTIFICATION 33 #define URI_GET_URLLIB 128 #define URI_GET_URLLIB_END 191 + +// gametype votes +#define GTV_AVAILABLE 0 +// for later use in per-map gametype filtering +#define GTV_FORBIDDEN 2 diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index feb0e03642..a09689b089 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -693,6 +693,15 @@ float MapInfo_Type_FromString(string t) return 0; } +string MapInfo_Type_Description(float t) +{ + entity e; + for(e = MapInfo_Type_first; e; e = e.enemy) + if(t == e.items) + return e.gametype_description; + return ""; +} + string MapInfo_Type_ToString(float t) { entity e; @@ -1132,7 +1141,8 @@ float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, float pGametype { if(!(MapInfo_Map_supportedGametypes & pGametypeToSet)) { - error("Can't select the requested game type. This should never happen as the caller should prevent it!\n"); + //error("Can't select the requested game type. This should never happen as the caller should prevent it!\n"); + return 0; //_MapInfo_Map_ApplyGametypeEx("", pGametypeToSet, MAPINFO_TYPE_DEATHMATCH); //return; } @@ -1261,14 +1271,14 @@ void MapInfo_LoadMap(string s, float reinit) localcmd(strcat("\nchangelevel ", s, "\n")); } -string MapInfo_ListAllowedMaps(float pRequiredFlags, float pForbiddenFlags) +string MapInfo_ListAllowedMaps(float type, float pRequiredFlags, float pForbiddenFlags) { string out; float i; // to make absolutely sure: MapInfo_Enumerate(); - MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0); + MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), pRequiredFlags, pForbiddenFlags, 0); out = ""; for(i = 0; i < MapInfo_count; ++i) diff --git a/qcsrc/common/mapinfo.qh b/qcsrc/common/mapinfo.qh index 7746dfe3eb..14f5a3d1b1 100644 --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@ -8,8 +8,9 @@ entity MapInfo_Type_last; .string mdl; // game type short name .string message; // human readable name .string model2; // game type defaults +.string gametype_description; // game type description -#define REGISTER_GAMETYPE(hname,sname,g_name,NAME,defaults) \ +#define REGISTER_GAMETYPE(hname,sname,g_name,NAME,defaults,gdescription) \ var float MAPINFO_TYPE_##NAME; \ var entity MapInfo_Type##g_name; \ void RegisterGametypes_##g_name() \ @@ -22,6 +23,7 @@ entity MapInfo_Type_last; MapInfo_Type##g_name.mdl = #sname; \ MapInfo_Type##g_name.message = hname; \ MapInfo_Type##g_name.model2 = defaults; \ + MapInfo_Type##g_name.gametype_description = gdescription; \ if(!MapInfo_Type_first) \ MapInfo_Type_first = MapInfo_Type##g_name; \ if(MapInfo_Type_last) \ @@ -33,49 +35,49 @@ entity MapInfo_Type_last; #define IS_GAMETYPE(NAME) \ (MapInfo_LoadedGametype == MAPINFO_TYPE_##NAME) -REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,"timelimit=20 pointlimit=30 leadlimit=0"); +REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,"timelimit=20 pointlimit=30 leadlimit=0",_("Kill all enemies")); #define g_dm IS_GAMETYPE(DEATHMATCH) -REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,"timelimit=20 lives=9 leadlimit=0"); +REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,"timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left")); #define g_lms IS_GAMETYPE(LMS) -REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,"timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0"); +REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,"timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line")); #define g_race IS_GAMETYPE(RACE) -REGISTER_GAMETYPE(_("Race CTS"),cts,g_cts,CTS,"timelimit=20 skill=-1"); +REGISTER_GAMETYPE(_("Race CTS"),cts,g_cts,CTS,"timelimit=20 skill=-1",_("Race for fastest time")); #define g_cts IS_GAMETYPE(CTS) -REGISTER_GAMETYPE(_("Team Deathmatch"),tdm,g_tdm,TEAM_DEATHMATCH,"timelimit=20 pointlimit=50 teams=2 leadlimit=0"); +REGISTER_GAMETYPE(_("Team Deathmatch"),tdm,g_tdm,TEAM_DEATHMATCH,"timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Kill all enemy teammates")); #define g_tdm IS_GAMETYPE(TEAM_DEATHMATCH) -REGISTER_GAMETYPE(_("Capture the Flag"),ctf,g_ctf,CTF,"timelimit=20 caplimit=10 leadlimit=0"); +REGISTER_GAMETYPE(_("Capture the Flag"),ctf,g_ctf,CTF,"timelimit=20 caplimit=10 leadlimit=0",_("Find and bring the enemy flag to your base to capture it")); #define g_ctf IS_GAMETYPE(CTF) -REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,"timelimit=20 pointlimit=10 leadlimit=0"); +REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,"timelimit=20 pointlimit=10 leadlimit=0",_("Kill all enemy teammates to win the round")); #define g_ca IS_GAMETYPE(CA) -REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,"timelimit=20 pointlimit=200 teams=2 leadlimit=0"); +REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,"timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture all the control points to win")); #define g_domination IS_GAMETYPE(DOMINATION) -REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,"timelimit=20 pointlimit=1000 teams=3 leadlimit=0"); +REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,"timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round")); #define g_keyhunt IS_GAMETYPE(KEYHUNT) -REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,"timelimit=20"); +REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,"timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out")); #define g_assault IS_GAMETYPE(ASSAULT) -REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,"timelimit=20"); +REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,"timelimit=20",_("Capture control points to reach and destroy the enemy generator")); #define g_onslaught IS_GAMETYPE(ONSLAUGHT) -REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,"timelimit=20 pointlimit=5 leadlimit=0"); +REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,"timelimit=20 pointlimit=5 leadlimit=0",_("XonSports")); #define g_nexball IS_GAMETYPE(NEXBALL) -REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,"timelimit=20 pointlimit=10 teams=2 leadlimit=0"); +REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to teammates to revive them")); #define g_freezetag IS_GAMETYPE(FREEZETAG) -REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30"); +REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30",_("Hold the ball to get points for kills")); #define g_keepaway IS_GAMETYPE(KEEPAWAY) -REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=5"); +REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=5",_("Survive against waves of monsters")); #define g_invasion IS_GAMETYPE(INVASION) const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps @@ -133,13 +135,14 @@ float MapInfo_CheckMap(string s); // returns 0 if the map can't be played with t void MapInfo_LoadMap(string s, float reinit); // list all maps for the current game type -string MapInfo_ListAllowedMaps(float pFlagsRequired, float pFlagsForbidden); +string MapInfo_ListAllowedMaps(float type, float pFlagsRequired, float pFlagsForbidden); // list all allowed maps (for any game type) string MapInfo_ListAllAllowedMaps(float pFlagsRequired, float pFlagsForbidden); // gets a gametype from a string string _MapInfo_GetDefaultEx(float t); float MapInfo_Type_FromString(string t); +string MapInfo_Type_Description(float t); string MapInfo_Type_ToString(float t); string MapInfo_Type_ToText(float t); void MapInfo_SwitchGameType(float t); diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 674c95b14e..b467acc4d2 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -1155,6 +1155,11 @@ float autocvar_sv_timeout_resumetime; float autocvar_sv_vote_call; float autocvar_sv_vote_change; string autocvar_sv_vote_commands; +float autocvar_sv_vote_gametype; +float autocvar_sv_vote_gametype_timeout; +string autocvar_sv_vote_gametype_options; +float autocvar_sv_vote_gametype_keeptwotime; +float autocvar_sv_vote_gametype_default_current; float autocvar_sv_vote_limit; float autocvar_sv_vote_majority_factor; float autocvar_sv_vote_majority_factor_of_voted; diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 875a0c3e6a..6cf19fa4fe 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -55,7 +55,6 @@ const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1; string redirection_target; float world_initialized; -string GetMapname(); string GetGametype(); void GotoNextMap(float reinit); void ShuffleMaplist(); @@ -896,7 +895,6 @@ string GetGametype() return MapInfo_Type_ToString(MapInfo_LoadedGametype); } -string getmapname_stored; string GetMapname() { return mapname; @@ -1221,13 +1219,27 @@ float DoNextMapOverride(float reinit) return TRUE; } if(autocvar_nextmap != "") - if(MapInfo_CheckMap(autocvar_nextmap)) + { + string m; + m = GameTypeVote_MapInfo_FixName(autocvar_nextmap); + cvar_set("nextmap",m); + + if(!m || gametypevote) + return FALSE; + if(autocvar_sv_vote_gametype) { - Map_Goto_SetStr(autocvar_nextmap); + Map_Goto_SetStr(m); + return FALSE; + } + + if(MapInfo_CheckMap(m)) + { + Map_Goto_SetStr(m); Map_Goto(reinit); alreadychangedlevel = TRUE; return TRUE; } + } if(!reinit && autocvar_lastlevel) { cvar_settemp_restore(); @@ -1264,9 +1276,6 @@ When the player presses attack or jump, change to the next level ============ */ .float autoscreenshot; -void() MapVote_Start; -void() MapVote_Think; -float mapvote_initialized; void IntermissionThink() { FixIntermissionClient(self); @@ -2031,17 +2040,6 @@ void CheckRules_World() SetDefaultAlpha(); - /* - MapVote_Think should now do that part - if (intermission_running) - if (time >= intermission_exittime + 60) - { - if(!DoNextMapOverride()) - GotoNextMap(); - return; - } - */ - if (gameover) // someone else quit the game already { if(player_count == 0) // Nobody there? Then let's go to the next map @@ -2203,517 +2201,14 @@ void CheckRules_World() } } -float mapvote_nextthink; -float mapvote_initialized; -float mapvote_keeptwotime; -float mapvote_timeout; -string mapvote_message; -#define MAPVOTE_SCREENSHOT_DIRS_COUNT 4 -string mapvote_screenshot_dirs[MAPVOTE_SCREENSHOT_DIRS_COUNT]; -float mapvote_screenshot_dirs_count; - -float mapvote_count; -float mapvote_count_real; -string mapvote_maps[MAPVOTE_COUNT]; -float mapvote_maps_screenshot_dir[MAPVOTE_COUNT]; -string mapvote_maps_pakfile[MAPVOTE_COUNT]; -float mapvote_maps_suggested[MAPVOTE_COUNT]; -string mapvote_suggestions[MAPVOTE_COUNT]; -float mapvote_suggestion_ptr; -float mapvote_voters; -float mapvote_selections[MAPVOTE_COUNT]; -float mapvote_run; -float mapvote_detail; -float mapvote_abstain; -.float mapvote; - -void MapVote_ClearAllVotes() -{ - FOR_EACH_CLIENT(other) - other.mapvote = 0; -} - -string MapVote_Suggest(string m) +string GotoMap(string m) { - float i; - if(m == "") - return "That's not how to use this command."; - if(!autocvar_g_maplist_votable_suggestions) - return "Suggestions are not accepted on this server."; - if(mapvote_initialized) - return "Can't suggest - voting is already in progress!"; - m = MapInfo_FixName(m); + m = GameTypeVote_MapInfo_FixName(m); if (!m) return "The map you suggested is not available on this server."; - if(!autocvar_g_maplist_votable_suggestions_override_mostrecent) - if(Map_IsRecent(m)) - return "This server does not allow for recent maps to be played again. Please be patient for some rounds."; - + if (!autocvar_sv_vote_gametype) if(!MapInfo_CheckMap(m)) return "The map you suggested does not support the current game mode."; - for(i = 0; i < mapvote_suggestion_ptr; ++i) - if(mapvote_suggestions[i] == m) - return "This map was already suggested."; - if(mapvote_suggestion_ptr >= MAPVOTE_COUNT) - { - i = floor(random() * mapvote_suggestion_ptr); - } - else - { - i = mapvote_suggestion_ptr; - mapvote_suggestion_ptr += 1; - } - if(mapvote_suggestions[i] != "") - strunzone(mapvote_suggestions[i]); - mapvote_suggestions[i] = strzone(m); - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":vote:suggested:", m, ":", ftos(self.playerid))); - return strcat("Suggestion of ", m, " accepted."); -} - -void MapVote_AddVotable(string nextMap, float isSuggestion) -{ - float j, i, o; - string pakfile, mapfile; - - if(nextMap == "") - return; - for(j = 0; j < mapvote_count; ++j) - if(mapvote_maps[j] == nextMap) - return; - // suggestions might be no longer valid/allowed after gametype switch! - if(isSuggestion) - if(!MapInfo_CheckMap(nextMap)) - return; - mapvote_maps[mapvote_count] = strzone(nextMap); - mapvote_maps_suggested[mapvote_count] = isSuggestion; - - pakfile = string_null; - for(i = 0; i < mapvote_screenshot_dirs_count; ++i) - { - mapfile = strcat(mapvote_screenshot_dirs[i], "/", mapvote_maps[i]); - pakfile = whichpack(strcat(mapfile, ".tga")); - if(pakfile == "") - pakfile = whichpack(strcat(mapfile, ".jpg")); - if(pakfile == "") - pakfile = whichpack(strcat(mapfile, ".png")); - if(pakfile != "") - break; - } - if(i >= mapvote_screenshot_dirs_count) - i = 0; // FIXME maybe network this error case, as that means there is no mapshot on the server? - for(o = strstr(pakfile, "/", 0)+1; o > 0; o = strstr(pakfile, "/", 0)+1) - pakfile = substring(pakfile, o, -1); - - mapvote_maps_screenshot_dir[mapvote_count] = i; - mapvote_maps_pakfile[mapvote_count] = strzone(pakfile); - - mapvote_count += 1; -} - -void MapVote_Spawn(); -void MapVote_Init() -{ - float i; - float nmax, smax; - - MapVote_ClearAllVotes(); - - mapvote_count = 0; - mapvote_detail = !autocvar_g_maplist_votable_nodetail; - mapvote_abstain = autocvar_g_maplist_votable_abstain; - - if(mapvote_abstain) - nmax = min(MAPVOTE_COUNT - 1, autocvar_g_maplist_votable); - else - nmax = min(MAPVOTE_COUNT, autocvar_g_maplist_votable); - smax = min3(nmax, autocvar_g_maplist_votable_suggestions, mapvote_suggestion_ptr); - - // we need this for AddVotable, as that cycles through the screenshot dirs - mapvote_screenshot_dirs_count = tokenize_console(autocvar_g_maplist_votable_screenshot_dir); - if(mapvote_screenshot_dirs_count == 0) - mapvote_screenshot_dirs_count = tokenize_console("maps levelshots"); - mapvote_screenshot_dirs_count = min(mapvote_screenshot_dirs_count, MAPVOTE_SCREENSHOT_DIRS_COUNT); - for(i = 0; i < mapvote_screenshot_dirs_count; ++i) - mapvote_screenshot_dirs[i] = strzone(argv(i)); - - if(mapvote_suggestion_ptr) - for(i = 0; i < 100 && mapvote_count < smax; ++i) - MapVote_AddVotable(mapvote_suggestions[floor(random() * mapvote_suggestion_ptr)], TRUE); - - for(i = 0; i < 100 && mapvote_count < nmax; ++i) - MapVote_AddVotable(GetNextMap(), FALSE); - - if(mapvote_count == 0) - { - bprint( "Maplist contains no single playable map! Resetting it to default map list.\n" ); - cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags())); - if(autocvar_g_maplist_shuffle) - ShuffleMaplist(); - localcmd("\nmenu_cmd sync\n"); - for(i = 0; i < 100 && mapvote_count < nmax; ++i) - MapVote_AddVotable(GetNextMap(), FALSE); - } - - mapvote_count_real = mapvote_count; - if(mapvote_abstain) - MapVote_AddVotable("don't care", 0); - - //dprint("mapvote count is ", ftos(mapvote_count), "\n"); - - mapvote_keeptwotime = time + autocvar_g_maplist_votable_keeptwotime; - mapvote_timeout = time + autocvar_g_maplist_votable_timeout; - if(mapvote_count_real < 3 || mapvote_keeptwotime <= time) - mapvote_keeptwotime = 0; - mapvote_message = "Choose a map and press its key!"; - - MapVote_Spawn(); -} - -void MapVote_SendPicture(float id) -{ - msg_entity = self; - WriteByte(MSG_ONE, SVC_TEMPENTITY); - WriteByte(MSG_ONE, TE_CSQC_PICTURE); - WriteByte(MSG_ONE, id); - WritePicture(MSG_ONE, strcat(mapvote_screenshot_dirs[mapvote_maps_screenshot_dir[id]], "/", mapvote_maps[id]), 3072); -} - -float MapVote_GetMapMask() -{ - float mask, i, power; - mask = 0; - for(i = 0, power = 1; i < mapvote_count; ++i, power *= 2) - if(mapvote_maps[i] != "") - mask |= power; - return mask; -} - -entity mapvote_ent; -float MapVote_SendEntity(entity to, float sf) -{ - float i; - - if(sf & 1) - sf &= ~2; // if we send 1, we don't need to also send 2 - - WriteByte(MSG_ENTITY, ENT_CLIENT_MAPVOTE); - WriteByte(MSG_ENTITY, sf); - - if(sf & 1) - { - // flag 1 == initialization - for(i = 0; i < mapvote_screenshot_dirs_count; ++i) - WriteString(MSG_ENTITY, mapvote_screenshot_dirs[i]); - WriteString(MSG_ENTITY, ""); - WriteByte(MSG_ENTITY, mapvote_count); - WriteByte(MSG_ENTITY, mapvote_abstain); - WriteByte(MSG_ENTITY, mapvote_detail); - WriteCoord(MSG_ENTITY, mapvote_timeout); - if(mapvote_count <= 8) - WriteByte(MSG_ENTITY, MapVote_GetMapMask()); - else - WriteShort(MSG_ENTITY, MapVote_GetMapMask()); - for(i = 0; i < mapvote_count; ++i) - if(mapvote_maps[i] != "") - { - if(mapvote_abstain && i == mapvote_count - 1) - { - WriteString(MSG_ENTITY, ""); // abstain needs no text - WriteString(MSG_ENTITY, ""); // abstain needs no pack - WriteByte(MSG_ENTITY, 0); // abstain needs no screenshot dir - } - else - { - WriteString(MSG_ENTITY, mapvote_maps[i]); - WriteString(MSG_ENTITY, mapvote_maps_pakfile[i]); - WriteByte(MSG_ENTITY, mapvote_maps_screenshot_dir[i]); - } - } - } - - if(sf & 2) - { - // flag 2 == update of mask - if(mapvote_count <= 8) - WriteByte(MSG_ENTITY, MapVote_GetMapMask()); - else - WriteShort(MSG_ENTITY, MapVote_GetMapMask()); - } - - if(sf & 4) - { - if(mapvote_detail) - for(i = 0; i < mapvote_count; ++i) - if(mapvote_maps[i] != "") - WriteByte(MSG_ENTITY, mapvote_selections[i]); - - WriteByte(MSG_ENTITY, to.mapvote); - } - - return TRUE; -} - -void MapVote_Spawn() -{ - Net_LinkEntity(mapvote_ent = spawn(), FALSE, 0, MapVote_SendEntity); -} - -void MapVote_TouchMask() -{ - mapvote_ent.SendFlags |= 2; -} - -void MapVote_TouchVotes(entity voter) -{ - mapvote_ent.SendFlags |= 4; -} - -float MapVote_Finished(float mappos) -{ - string result; - float i; - float didntvote; - - if(autocvar_sv_eventlog) - { - result = strcat(":vote:finished:", mapvote_maps[mappos]); - result = strcat(result, ":", ftos(mapvote_selections[mappos]), "::"); - didntvote = mapvote_voters; - for(i = 0; i < mapvote_count; ++i) - if(mapvote_maps[i] != "") - { - didntvote -= mapvote_selections[i]; - if(i != mappos) - { - result = strcat(result, ":", mapvote_maps[i]); - result = strcat(result, ":", ftos(mapvote_selections[i])); - } - } - result = strcat(result, ":didn't vote:", ftos(didntvote)); - - GameLogEcho(result); - if(mapvote_maps_suggested[mappos]) - GameLogEcho(strcat(":vote:suggestion_accepted:", mapvote_maps[mappos])); - } - - FOR_EACH_REALCLIENT(other) - FixClientCvars(other); - - Map_Goto_SetStr(mapvote_maps[mappos]); - Map_Goto(0); - alreadychangedlevel = TRUE; - return TRUE; -} -void MapVote_CheckRules_1() -{ - float i; - - for(i = 0; i < mapvote_count; ++i) if(mapvote_maps[i] != "") - { - //dprint("Map ", ftos(i), ": "); dprint(mapvote_maps[i], "\n"); - mapvote_selections[i] = 0; - } - - mapvote_voters = 0; - FOR_EACH_REALCLIENT(other) - { - ++mapvote_voters; - if(other.mapvote) - { - i = other.mapvote - 1; - //dprint("Player ", other.netname, " vote = ", ftos(other.mapvote - 1), "\n"); - mapvote_selections[i] = mapvote_selections[i] + 1; - } - } -} - -float MapVote_CheckRules_2() -{ - float i; - float firstPlace, secondPlace; - float firstPlaceVotes, secondPlaceVotes; - float mapvote_voters_real; - string result; - - if(mapvote_count_real == 1) - return MapVote_Finished(0); - - mapvote_voters_real = mapvote_voters; - if(mapvote_abstain) - mapvote_voters_real -= mapvote_selections[mapvote_count - 1]; - - RandomSelection_Init(); - for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "") - RandomSelection_Add(world, i, string_null, 1, mapvote_selections[i]); - firstPlace = RandomSelection_chosen_float; - firstPlaceVotes = RandomSelection_best_priority; - //dprint("First place: ", ftos(firstPlace), "\n"); - //dprint("First place votes: ", ftos(firstPlaceVotes), "\n"); - - RandomSelection_Init(); - for(i = 0; i < mapvote_count_real; ++i) if(mapvote_maps[i] != "") - if(i != firstPlace) - RandomSelection_Add(world, i, string_null, 1, mapvote_selections[i]); - secondPlace = RandomSelection_chosen_float; - secondPlaceVotes = RandomSelection_best_priority; - //dprint("Second place: ", ftos(secondPlace), "\n"); - //dprint("Second place votes: ", ftos(secondPlaceVotes), "\n"); - - if(firstPlace == -1) - error("No first place in map vote... WTF?"); - - if(secondPlace == -1 || time > mapvote_timeout || (mapvote_voters_real - firstPlaceVotes) < firstPlaceVotes) - return MapVote_Finished(firstPlace); - - if(mapvote_keeptwotime) - if(time > mapvote_keeptwotime || (mapvote_voters_real - firstPlaceVotes - secondPlaceVotes) < secondPlaceVotes) - { - float didntvote; - MapVote_TouchMask(); - mapvote_message = "Now decide between the TOP TWO!"; - mapvote_keeptwotime = 0; - result = strcat(":vote:keeptwo:", mapvote_maps[firstPlace]); - result = strcat(result, ":", ftos(firstPlaceVotes)); - result = strcat(result, ":", mapvote_maps[secondPlace]); - result = strcat(result, ":", ftos(secondPlaceVotes), "::"); - didntvote = mapvote_voters; - for(i = 0; i < mapvote_count; ++i) - if(mapvote_maps[i] != "") - { - didntvote -= mapvote_selections[i]; - if(i != firstPlace) - if(i != secondPlace) - { - result = strcat(result, ":", mapvote_maps[i]); - result = strcat(result, ":", ftos(mapvote_selections[i])); - if(i < mapvote_count_real) - { - strunzone(mapvote_maps[i]); - mapvote_maps[i] = ""; - strunzone(mapvote_maps_pakfile[i]); - mapvote_maps_pakfile[i] = ""; - } - } - } - result = strcat(result, ":didn't vote:", ftos(didntvote)); - if(autocvar_sv_eventlog) - GameLogEcho(result); - } - - return FALSE; -} -void MapVote_Tick() -{ - float keeptwo; - float totalvotes; - - keeptwo = mapvote_keeptwotime; - MapVote_CheckRules_1(); // count - if(MapVote_CheckRules_2()) // decide - return; - - totalvotes = 0; - FOR_EACH_REALCLIENT(other) - { - // hide scoreboard again - if(other.health != 2342) - { - other.health = 2342; - other.impulse = 0; - if(IS_REAL_CLIENT(other)) - { - msg_entity = other; - WriteByte(MSG_ONE, SVC_FINALE); - WriteString(MSG_ONE, ""); - } - } - - // clear possibly invalid votes - if(mapvote_maps[other.mapvote - 1] == "") - other.mapvote = 0; - // use impulses as new vote - if(other.impulse >= 1 && other.impulse <= mapvote_count) - if(mapvote_maps[other.impulse - 1] != "") - { - other.mapvote = other.impulse; - MapVote_TouchVotes(other); - } - other.impulse = 0; - - if(other.mapvote) - ++totalvotes; - } - - MapVote_CheckRules_1(); // just count -} -void MapVote_Start() -{ - if(mapvote_run) - return; - - // wait for stats to be sent first - if(!playerstats_waitforme) - return; - - MapInfo_Enumerate(); - if(MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1)) - mapvote_run = TRUE; -} -void MapVote_Think() -{ - if(!mapvote_run) - return; - - if(alreadychangedlevel) - return; - - if(time < mapvote_nextthink) - return; - //dprint("tick\n"); - - mapvote_nextthink = time + 0.5; - - if(!mapvote_initialized) - { - if(autocvar_rescan_pending == 1) - { - cvar_set("rescan_pending", "2"); - localcmd("fs_rescan\nrescan_pending 3\n"); - return; - } - else if(autocvar_rescan_pending == 2) - { - return; - } - else if(autocvar_rescan_pending == 3) - { - // now build missing mapinfo files - if(!MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1)) - return; - - // we're done, start the timer - cvar_set("rescan_pending", "0"); - } - - mapvote_initialized = TRUE; - if(DoNextMapOverride(0)) - return; - if(!autocvar_g_maplist_votable || player_count <= 0) - { - GotoNextMap(0); - return; - } - MapVote_Init(); - } - - MapVote_Tick(); -} - -string GotoMap(string m) -{ - if(!MapInfo_CheckMap(m)) - return "The map you chose is not available on this server."; cvar_set("nextmap", m); cvar_set("timelimit", "-1"); if(mapvote_initialized || alreadychangedlevel) diff --git a/qcsrc/server/mapvoting.qc b/qcsrc/server/mapvoting.qc new file mode 100644 index 0000000000..7a016fd4b3 --- /dev/null +++ b/qcsrc/server/mapvoting.qc @@ -0,0 +1,728 @@ +float GameTypeVote_AvailabilityStatus(string gtname) +{ + float type = MapInfo_Type_FromString(gtname); + if( type == 0 ) + return GTV_FORBIDDEN; + + if ( autocvar_nextmap != "" ) + if ( !MapInfo_Get_ByName(autocvar_nextmap, FALSE, type) ) + return GTV_FORBIDDEN; + + return GTV_AVAILABLE; +} + +float GameTypeVote_GetMask() +{ + float n, j, gametype_mask; + n = tokenizebyseparator(autocvar_sv_vote_gametype_options, " "); + n = min(MAPVOTE_COUNT, n); + gametype_mask = 0; + for(j = 0; j < n; ++j) + gametype_mask |= MapInfo_Type_FromString(argv(j)); + return gametype_mask; +} + +string GameTypeVote_MapInfo_FixName(string m) +{ + if ( autocvar_sv_vote_gametype ) + { + MapInfo_Enumerate(); + MapInfo_FilterGametype(GameTypeVote_GetMask(), 0, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); + } + return MapInfo_FixName(m); +} + +void MapVote_ClearAllVotes() +{ + FOR_EACH_CLIENT(other) + other.mapvote = 0; +} + +void MapVote_UnzoneStrings() +{ + float j; + for(j = 0; j < mapvote_count; ++j) + { + if ( mapvote_maps[j] ) + { + strunzone(mapvote_maps[j]); + mapvote_maps[j] = string_null; + } + if ( mapvote_maps_pakfile[j] ) + { + strunzone(mapvote_maps_pakfile[j]); + mapvote_maps_pakfile[j] = string_null; + } + } +} + +string MapVote_Suggest(string m) +{ + float i; + if(m == "") + return "That's not how to use this command."; + if(!autocvar_g_maplist_votable_suggestions) + return "Suggestions are not accepted on this server."; + if(mapvote_initialized) + if(!gametypevote) + return "Can't suggest - voting is already in progress!"; + m = GameTypeVote_MapInfo_FixName(m); + if (!m) + return "The map you suggested is not available on this server."; + if(!autocvar_g_maplist_votable_suggestions_override_mostrecent) + if(Map_IsRecent(m)) + return "This server does not allow for recent maps to be played again. Please be patient for some rounds."; + + if (!autocvar_sv_vote_gametype) + if(!MapInfo_CheckMap(m)) + return "The map you suggested does not support the current game mode."; + for(i = 0; i < mapvote_suggestion_ptr; ++i) + if(mapvote_suggestions[i] == m) + return "This map was already suggested."; + if(mapvote_suggestion_ptr >= MAPVOTE_COUNT) + { + i = floor(random() * mapvote_suggestion_ptr); + } + else + { + i = mapvote_suggestion_ptr; + mapvote_suggestion_ptr += 1; + } + if(mapvote_suggestions[i] != "") + strunzone(mapvote_suggestions[i]); + mapvote_suggestions[i] = strzone(m); + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":vote:suggested:", m, ":", ftos(self.playerid))); + return strcat("Suggestion of ", m, " accepted."); +} + +void MapVote_AddVotable(string nextMap, float isSuggestion) +{ + float j, i, o; + string pakfile, mapfile; + + if(nextMap == "") + return; + for(j = 0; j < mapvote_count; ++j) + if(mapvote_maps[j] == nextMap) + return; + // suggestions might be no longer valid/allowed after gametype switch! + if(isSuggestion) + if(!MapInfo_CheckMap(nextMap)) + return; + mapvote_maps[mapvote_count] = strzone(nextMap); + mapvote_maps_suggested[mapvote_count] = isSuggestion; + + pakfile = string_null; + for(i = 0; i < mapvote_screenshot_dirs_count; ++i) + { + mapfile = strcat(mapvote_screenshot_dirs[i], "/", mapvote_maps[i]); + pakfile = whichpack(strcat(mapfile, ".tga")); + if(pakfile == "") + pakfile = whichpack(strcat(mapfile, ".jpg")); + if(pakfile == "") + pakfile = whichpack(strcat(mapfile, ".png")); + if(pakfile != "") + break; + } + if(i >= mapvote_screenshot_dirs_count) + i = 0; // FIXME maybe network this error case, as that means there is no mapshot on the server? + for(o = strstr(pakfile, "/", 0)+1; o > 0; o = strstr(pakfile, "/", 0)+1) + pakfile = substring(pakfile, o, -1); + + mapvote_maps_screenshot_dir[mapvote_count] = i; + mapvote_maps_pakfile[mapvote_count] = strzone(pakfile); + mapvote_maps_availability[mapvote_count] = GTV_AVAILABLE; + + mapvote_count += 1; +} + +void MapVote_Init() +{ + float i; + float nmax, smax; + + MapVote_ClearAllVotes(); + MapVote_UnzoneStrings(); + + mapvote_count = 0; + mapvote_detail = !autocvar_g_maplist_votable_nodetail; + mapvote_abstain = autocvar_g_maplist_votable_abstain; + + if(mapvote_abstain) + nmax = min(MAPVOTE_COUNT - 1, autocvar_g_maplist_votable); + else + nmax = min(MAPVOTE_COUNT, autocvar_g_maplist_votable); + smax = min3(nmax, autocvar_g_maplist_votable_suggestions, mapvote_suggestion_ptr); + + // we need this for AddVotable, as that cycles through the screenshot dirs + mapvote_screenshot_dirs_count = tokenize_console(autocvar_g_maplist_votable_screenshot_dir); + if(mapvote_screenshot_dirs_count == 0) + mapvote_screenshot_dirs_count = tokenize_console("maps levelshots"); + mapvote_screenshot_dirs_count = min(mapvote_screenshot_dirs_count, MAPVOTE_SCREENSHOT_DIRS_COUNT); + for(i = 0; i < mapvote_screenshot_dirs_count; ++i) + mapvote_screenshot_dirs[i] = strzone(argv(i)); + + if(mapvote_suggestion_ptr) + for(i = 0; i < 100 && mapvote_count < smax; ++i) + MapVote_AddVotable(mapvote_suggestions[floor(random() * mapvote_suggestion_ptr)], TRUE); + + for(i = 0; i < 100 && mapvote_count < nmax; ++i) + MapVote_AddVotable(GetNextMap(), FALSE); + + if(mapvote_count == 0) + { + bprint( "Maplist contains no single playable map! Resetting it to default map list.\n" ); + cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags())); + if(autocvar_g_maplist_shuffle) + ShuffleMaplist(); + localcmd("\nmenu_cmd sync\n"); + for(i = 0; i < 100 && mapvote_count < nmax; ++i) + MapVote_AddVotable(GetNextMap(), FALSE); + } + + mapvote_count_real = mapvote_count; + if(mapvote_abstain) + MapVote_AddVotable("don't care", 0); + + //dprint("mapvote count is ", ftos(mapvote_count), "\n"); + + mapvote_keeptwotime = time + autocvar_g_maplist_votable_keeptwotime; + mapvote_timeout = time + autocvar_g_maplist_votable_timeout; + if(mapvote_count_real < 3 || mapvote_keeptwotime <= time) + mapvote_keeptwotime = 0; + mapvote_message = "Choose a map and press its key!"; + + MapVote_Spawn(); +} + +void MapVote_SendPicture(float id) +{ + msg_entity = self; + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_PICTURE); + WriteByte(MSG_ONE, id); + WritePicture(MSG_ONE, strcat(mapvote_screenshot_dirs[mapvote_maps_screenshot_dir[id]], "/", mapvote_maps[id]), 3072); +} + + +void MapVote_WriteMask() +{ + float i; + if ( mapvote_count < 24 ) + { + float mask,power; + mask = 0; + for(i = 0, power = 1; i < mapvote_count; ++i, power *= 2) + if(mapvote_maps_availability[i] == GTV_AVAILABLE ) + mask |= power; + + if(mapvote_count < 8) + WriteByte(MSG_ENTITY, mask); + else if (mapvote_count < 16) + WriteShort(MSG_ENTITY,mask); + else + WriteLong(MSG_ENTITY, mask); + } + else + { + for ( i = 0; i < mapvote_count; ++i ) + WriteByte(MSG_ENTITY, mapvote_maps_availability[i]); + } +} + +float MapVote_SendEntity(entity to, float sf) +{ + float i; + + if(sf & 1) + sf &= ~2; // if we send 1, we don't need to also send 2 + + WriteByte(MSG_ENTITY, ENT_CLIENT_MAPVOTE); + WriteByte(MSG_ENTITY, sf); + + if(sf & 1) + { + // flag 1 == initialization + for(i = 0; i < mapvote_screenshot_dirs_count; ++i) + WriteString(MSG_ENTITY, mapvote_screenshot_dirs[i]); + WriteString(MSG_ENTITY, ""); + WriteByte(MSG_ENTITY, mapvote_count); + WriteByte(MSG_ENTITY, mapvote_abstain); + WriteByte(MSG_ENTITY, mapvote_detail); + WriteCoord(MSG_ENTITY, mapvote_timeout); + + WriteByte(MSG_ENTITY, gametypevote); + + if(gametypevote) + WriteString(MSG_ENTITY, autocvar_nextmap); + + MapVote_WriteMask(); + + for(i = 0; i < mapvote_count; ++i) + { + if(mapvote_abstain && i == mapvote_count - 1) + { + WriteString(MSG_ENTITY, ""); // abstain needs no text + WriteString(MSG_ENTITY, ""); // abstain needs no pack + WriteByte(MSG_ENTITY, 0); // abstain needs no screenshot dir + WriteByte(MSG_ENTITY, GTV_AVAILABLE); + } + else + { + WriteString(MSG_ENTITY, mapvote_maps[i]); + WriteString(MSG_ENTITY, mapvote_maps_pakfile[i]); + WriteByte(MSG_ENTITY, mapvote_maps_screenshot_dir[i]); + WriteByte(MSG_ENTITY, mapvote_maps_availability[i]); + } + } + } + + if(sf & 2) + { + // flag 2 == update of mask + MapVote_WriteMask(); + } + + if(sf & 4) + { + if(mapvote_detail) + for(i = 0; i < mapvote_count; ++i) + if ( mapvote_maps_availability[i] == GTV_AVAILABLE ) + WriteByte(MSG_ENTITY, mapvote_selections[i]); + + WriteByte(MSG_ENTITY, to.mapvote); + } + + return TRUE; +} + +void MapVote_Spawn() +{ + Net_LinkEntity(mapvote_ent = spawn(), FALSE, 0, MapVote_SendEntity); +} + +void MapVote_TouchMask() +{ + mapvote_ent.SendFlags |= 2; +} + +void MapVote_TouchVotes(entity voter) +{ + mapvote_ent.SendFlags |= 4; +} + +float MapVote_Finished(float mappos) +{ + if(alreadychangedlevel) + return FALSE; + + string result; + float i; + float didntvote; + + if(autocvar_sv_eventlog) + { + result = strcat(":vote:finished:", mapvote_maps[mappos]); + result = strcat(result, ":", ftos(mapvote_selections[mappos]), "::"); + didntvote = mapvote_voters; + for(i = 0; i < mapvote_count; ++i) + if(mapvote_maps_availability[i] == GTV_AVAILABLE ) + { + didntvote -= mapvote_selections[i]; + if(i != mappos) + { + result = strcat(result, ":", mapvote_maps[i]); + result = strcat(result, ":", ftos(mapvote_selections[i])); + } + } + result = strcat(result, ":didn't vote:", ftos(didntvote)); + + GameLogEcho(result); + if(mapvote_maps_suggested[mappos]) + GameLogEcho(strcat(":vote:suggestion_accepted:", mapvote_maps[mappos])); + } + + FOR_EACH_REALCLIENT(other) + FixClientCvars(other); + + if(gametypevote) + { + if ( GameTypeVote_Finished(mappos) ) + { + gametypevote = FALSE; + if(autocvar_nextmap != "") + { + Map_Goto_SetStr(autocvar_nextmap); + Map_Goto(0); + alreadychangedlevel = TRUE; + return TRUE; + } + else + MapVote_Init(); + } + return FALSE; + } + + Map_Goto_SetStr(mapvote_maps[mappos]); + Map_Goto(0); + alreadychangedlevel = TRUE; + + return TRUE; +} + +void MapVote_CheckRules_1() +{ + float i; + + for(i = 0; i < mapvote_count; ++i) + if( mapvote_maps_availability[i] == GTV_AVAILABLE ) + { + //dprint("Map ", ftos(i), ": "); dprint(mapvote_maps[i], "\n"); + mapvote_selections[i] = 0; + } + + mapvote_voters = 0; + FOR_EACH_REALCLIENT(other) + { + ++mapvote_voters; + if(other.mapvote) + { + i = other.mapvote - 1; + //dprint("Player ", other.netname, " vote = ", ftos(other.mapvote - 1), "\n"); + mapvote_selections[i] = mapvote_selections[i] + 1; + } + } +} + +float MapVote_CheckRules_2() +{ + float i; + float firstPlace, secondPlace, currentPlace; + float firstPlaceVotes, secondPlaceVotes, currentVotes; + float mapvote_voters_real; + string result; + + if(mapvote_count_real == 1) + return MapVote_Finished(0); + + mapvote_voters_real = mapvote_voters; + if(mapvote_abstain) + mapvote_voters_real -= mapvote_selections[mapvote_count - 1]; + + RandomSelection_Init(); + currentPlace = 0; + currentVotes = -1; + for(i = 0; i < mapvote_count_real; ++i) + if ( mapvote_maps_availability[i] == GTV_AVAILABLE ) + { + RandomSelection_Add(world, i, string_null, 1, mapvote_selections[i]); + if ( gametypevote && mapvote_maps[i] == MapInfo_Type_ToString(MapInfo_CurrentGametype()) ) + { + currentVotes = mapvote_selections[i]; + currentPlace = i; + } + } + firstPlaceVotes = RandomSelection_best_priority; + if ( autocvar_sv_vote_gametype_default_current && currentVotes == firstPlaceVotes ) + firstPlace = currentPlace; + else + firstPlace = RandomSelection_chosen_float; + + //dprint("First place: ", ftos(firstPlace), "\n"); + //dprint("First place votes: ", ftos(firstPlaceVotes), "\n"); + + RandomSelection_Init(); + for(i = 0; i < mapvote_count_real; ++i) + if(i != firstPlace) + if ( mapvote_maps_availability[i] == GTV_AVAILABLE ) + RandomSelection_Add(world, i, string_null, 1, mapvote_selections[i]); + secondPlace = RandomSelection_chosen_float; + secondPlaceVotes = RandomSelection_best_priority; + //dprint("Second place: ", ftos(secondPlace), "\n"); + //dprint("Second place votes: ", ftos(secondPlaceVotes), "\n"); + + if(firstPlace == -1) + error("No first place in map vote... WTF?"); + + if(secondPlace == -1 || time > mapvote_timeout || (mapvote_voters_real - firstPlaceVotes) < firstPlaceVotes) + return MapVote_Finished(firstPlace); + + if(mapvote_keeptwotime) + if(time > mapvote_keeptwotime || (mapvote_voters_real - firstPlaceVotes - secondPlaceVotes) < secondPlaceVotes) + { + float didntvote; + MapVote_TouchMask(); + mapvote_message = "Now decide between the TOP TWO!"; + mapvote_keeptwotime = 0; + result = strcat(":vote:keeptwo:", mapvote_maps[firstPlace]); + result = strcat(result, ":", ftos(firstPlaceVotes)); + result = strcat(result, ":", mapvote_maps[secondPlace]); + result = strcat(result, ":", ftos(secondPlaceVotes), "::"); + didntvote = mapvote_voters; + for(i = 0; i < mapvote_count; ++i) + { + didntvote -= mapvote_selections[i]; + if(i != firstPlace) + if(i != secondPlace) + { + result = strcat(result, ":", mapvote_maps[i]); + result = strcat(result, ":", ftos(mapvote_selections[i])); + if(i < mapvote_count_real) + { + mapvote_maps_availability[i] = GTV_FORBIDDEN; + } + } + } + result = strcat(result, ":didn't vote:", ftos(didntvote)); + if(autocvar_sv_eventlog) + GameLogEcho(result); + } + + return FALSE; +} + +void MapVote_Tick() +{ + float keeptwo; + float totalvotes; + + keeptwo = mapvote_keeptwotime; + MapVote_CheckRules_1(); // count + if(MapVote_CheckRules_2()) // decide + return; + + totalvotes = 0; + FOR_EACH_REALCLIENT(other) + { + // hide scoreboard again + if(other.health != 2342) + { + other.health = 2342; + other.impulse = 0; + if(IS_REAL_CLIENT(other)) + { + msg_entity = other; + WriteByte(MSG_ONE, SVC_FINALE); + WriteString(MSG_ONE, ""); + } + } + + // clear possibly invalid votes + if ( mapvote_maps_availability[other.mapvote-1] != GTV_AVAILABLE ) + other.mapvote = 0; + // use impulses as new vote + if(other.impulse >= 1 && other.impulse <= mapvote_count) + if( mapvote_maps_availability[other.impulse - 1] == GTV_AVAILABLE ) + { + other.mapvote = other.impulse; + MapVote_TouchVotes(other); + } + other.impulse = 0; + + if(other.mapvote) + ++totalvotes; + } + + MapVote_CheckRules_1(); // just count +} + +void MapVote_Start() +{ + if(mapvote_run) + return; + + // wait for stats to be sent first + if(!playerstats_waitforme) + return; + + MapInfo_Enumerate(); + if(MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1)) + mapvote_run = TRUE; +} + +void MapVote_Think() +{ + if(!mapvote_run) + return; + + if(alreadychangedlevel) + return; + + if(time < mapvote_nextthink) + return; + //dprint("tick\n"); + + mapvote_nextthink = time + 0.5; + + if(!mapvote_initialized) + { + if(autocvar_rescan_pending == 1) + { + cvar_set("rescan_pending", "2"); + localcmd("fs_rescan\nrescan_pending 3\n"); + return; + } + else if(autocvar_rescan_pending == 2) + { + return; + } + else if(autocvar_rescan_pending == 3) + { + // now build missing mapinfo files + if(!MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1)) + return; + + // we're done, start the timer + cvar_set("rescan_pending", "0"); + } + + mapvote_initialized = TRUE; + if(DoNextMapOverride(0)) + return; + if(!autocvar_g_maplist_votable || player_count <= 0) + { + GotoNextMap(0); + return; + } + + if(autocvar_sv_vote_gametype) { GameTypeVote_Start(); } + else if(autocvar_nextmap == "") { MapVote_Init(); } + } + + MapVote_Tick(); +} + +float GameTypeVote_SetGametype(float type) +{ + if (MapInfo_CurrentGametype() == type) + return TRUE; + + float tsave = MapInfo_CurrentGametype(); + + MapInfo_SwitchGameType(type); + + MapInfo_Enumerate(); + MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); + if(MapInfo_count > 0) + { + // update lsmaps in case the gametype changed, this way people can easily list maps for it + if(lsmaps_reply != "") { strunzone(lsmaps_reply); } + lsmaps_reply = strzone(getlsmaps()); + bprint("Game type successfully switched to ", MapInfo_Type_ToString(type), "\n"); + } + else + { + bprint("Cannot use this game type: no map for it found\n"); + MapInfo_SwitchGameType(tsave); + MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); + return FALSE; + } + + //localcmd("gametype ", MapInfo_Type_ToString(type), "\n"); + + cvar_set("g_maplist", MapInfo_ListAllowedMaps(type, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()) ); + if(autocvar_g_maplist_shuffle) + ShuffleMaplist(); + + return TRUE; +} + +float gametypevote_finished; +float GameTypeVote_Finished(float pos) +{ + if(!gametypevote || gametypevote_finished) + return FALSE; + + if ( !GameTypeVote_SetGametype(MapInfo_Type_FromString(mapvote_maps[pos])) ) + { + dprint("Selected gametype is not supported by any map"); + } + + localcmd("sv_vote_gametype_hook_all\n"); + localcmd("sv_vote_gametype_hook_", mapvote_maps[pos], "\n"); + + gametypevote_finished = TRUE; + + return TRUE; +} + +float GameTypeVote_AddVotable(string nextMode) +{ + float j; + if ( nextMode == "" || MapInfo_Type_FromString(nextMode) == 0 ) + return FALSE; + for(j = 0; j < mapvote_count; ++j) + if(mapvote_maps[j] == nextMode) + return FALSE; + + mapvote_maps[mapvote_count] = strzone(nextMode); + mapvote_maps_suggested[mapvote_count] = FALSE; + + mapvote_maps_screenshot_dir[mapvote_count] = 0; + mapvote_maps_pakfile[mapvote_count] = strzone(""); + mapvote_maps_availability[mapvote_count] = GameTypeVote_AvailabilityStatus(nextMode); + + mapvote_count += 1; + + return TRUE; + +} + +float GameTypeVote_Start() +{ + float j; + MapVote_ClearAllVotes(); + MapVote_UnzoneStrings(); + + mapvote_count = 0; + mapvote_timeout = time + autocvar_sv_vote_gametype_timeout; + mapvote_abstain = 0; + mapvote_detail = !autocvar_g_maplist_votable_nodetail; + + float n = tokenizebyseparator(autocvar_sv_vote_gametype_options, " "); + n = min(MAPVOTE_COUNT, n); + + float really_available, which_available; + really_available = 0; + which_available = -1; + for(j = 0; j < n; ++j) + { + if ( GameTypeVote_AddVotable(argv(j)) ) + if ( mapvote_maps_availability[j] == GTV_AVAILABLE ) + { + really_available++; + which_available = j; + } + } + + mapvote_count_real = mapvote_count; + + gametypevote = 1; + + if ( really_available == 0 ) + { + if ( mapvote_count > 0 ) + strunzone(mapvote_maps[0]); + mapvote_maps[0] = strzone(MapInfo_Type_ToString(MapInfo_CurrentGametype())); + //GameTypeVote_Finished(0); + MapVote_Finished(0); + return FALSE; + } + if ( really_available == 1 ) + { + //GameTypeVote_Finished(which_available); + MapVote_Finished(which_available); + return FALSE; + } + + mapvote_count_real = mapvote_count; + + mapvote_keeptwotime = time + autocvar_sv_vote_gametype_keeptwotime; + if(mapvote_count_real < 3 || mapvote_keeptwotime <= time) + mapvote_keeptwotime = 0; + + MapVote_Spawn(); + + return TRUE; +} diff --git a/qcsrc/server/mapvoting.qh b/qcsrc/server/mapvoting.qh new file mode 100644 index 0000000000..6875958bfe --- /dev/null +++ b/qcsrc/server/mapvoting.qh @@ -0,0 +1,39 @@ +// definitions for functions used outside mapvoting.qc +void MapVote_Start(); +void MapVote_Spawn(); +void MapVote_Think(); +float GameTypeVote_Start(); +float GameTypeVote_Finished(float pos); +string GameTypeVote_MapInfo_FixName(string m); + +// definitions +float gametypevote; +string getmapname_stored; +float mapvote_initialized; + +float mapvote_nextthink; +float mapvote_initialized; +float mapvote_keeptwotime; +float mapvote_timeout; +string mapvote_message; +#define MAPVOTE_SCREENSHOT_DIRS_COUNT 4 +string mapvote_screenshot_dirs[MAPVOTE_SCREENSHOT_DIRS_COUNT]; +float mapvote_screenshot_dirs_count; + +float mapvote_count; +float mapvote_count_real; +string mapvote_maps[MAPVOTE_COUNT]; +float mapvote_maps_screenshot_dir[MAPVOTE_COUNT]; +string mapvote_maps_pakfile[MAPVOTE_COUNT]; +float mapvote_maps_suggested[MAPVOTE_COUNT]; +string mapvote_suggestions[MAPVOTE_COUNT]; +float mapvote_suggestion_ptr; +float mapvote_voters; +float mapvote_selections[MAPVOTE_COUNT]; +float mapvote_maps_availability[MAPVOTE_COUNT]; +float mapvote_run; +float mapvote_detail; +float mapvote_abstain; +.float mapvote; + +entity mapvote_ent; diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 1ae22e2029..9f03af24e4 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -88,6 +88,8 @@ scores.qh spawnpoints.qh +mapvoting.qh + ipban.qh race.qh @@ -131,6 +133,8 @@ pathlib/pathlib.qh g_world.qc g_casings.qc +mapvoting.qc + t_jumppads.qc t_teleporters.qc -- 2.39.2