From: Mario Date: Thu, 13 Nov 2014 14:08:26 +0000 (+1100) Subject: Merge branch 'Mario/config_updates' X-Git-Tag: xonotic-v0.8.0~167 X-Git-Url: https://git.xonotic.org/?a=commitdiff_plain;h=bbd25803840bd247e33b88250a402b31c196b717;hp=d435aa26b0f66be08b5ed599c7cf410f0f1e4365;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'Mario/config_updates' --- diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..09de22f55 Binary files /dev/null and b/.DS_Store differ diff --git a/_hud_descriptions.cfg b/_hud_descriptions.cfg index c9a086155..270bd2bd9 100644 --- a/_hud_descriptions.cfg +++ b/_hud_descriptions.cfg @@ -297,3 +297,13 @@ seta hud_panel_centerprint_fade_subsequent_passtwo "" "division factor for the s seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "" "minimum factor that the second pass can fade to" seta hud_panel_centerprint_fade_subsequent_minfontsize "" "minimum factor for the font size from the subsequent fading effects" seta hud_panel_centerprint_fade_minfontsize "" "minimum factor for the font size from the fading in/out effects" + +seta hud_panel_buffs "" "enable/disable this panel" +seta hud_panel_buffs_pos "" "position of this panel" +seta hud_panel_buffs_size "" "size of this panel" +seta hud_panel_buffs_bg "" "if set to something else than \"\" = override default background" +seta hud_panel_buffs_bg_color "" "if set to something else than \"\" = override default panel background color" +seta hud_panel_buffs_bg_color_team "" "override panel color with team color in team based games" +seta hud_panel_buffs_bg_alpha "" "if set to something else than \"\" = override default panel background alpha" +seta hud_panel_buffs_bg_border "" "if set to something else than \"\" = override default size of border around the background" +seta hud_panel_buffs_bg_padding "" "if set to something else than \"\" = override default padding of contents from border" diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg index de15b2482..18af3e895 100644 --- a/defaultXonotic.cfg +++ b/defaultXonotic.cfg @@ -813,6 +813,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 dfc240536..2b8874cd2 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/gfx/.DS_Store b/gfx/.DS_Store new file mode 100644 index 000000000..d052ce6c9 Binary files /dev/null and b/gfx/.DS_Store differ diff --git a/gfx/hud/.DS_Store b/gfx/hud/.DS_Store new file mode 100644 index 000000000..127a7c9db Binary files /dev/null and b/gfx/hud/.DS_Store differ diff --git a/hud_luminos.cfg b/hud_luminos.cfg index 2736524c6..f3bc914e6 100644 --- a/hud_luminos.cfg +++ b/hud_luminos.cfg @@ -296,4 +296,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5" seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75" seta hud_panel_centerprint_fade_minfontsize "0" +seta hud_panel_buffs 1 +seta hud_panel_buffs_pos "0.450000 0.855000" +seta hud_panel_buffs_size "0.050000 0.070000" +seta hud_panel_buffs_bg "0" +seta hud_panel_buffs_bg_color "" +seta hud_panel_buffs_bg_color_team "" +seta hud_panel_buffs_bg_alpha "" +seta hud_panel_buffs_bg_border "" +seta hud_panel_buffs_bg_padding "" + menu_sync diff --git a/hud_luminos_minimal.cfg b/hud_luminos_minimal.cfg index fdb57a37e..050689b38 100644 --- a/hud_luminos_minimal.cfg +++ b/hud_luminos_minimal.cfg @@ -296,4 +296,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5" seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75" seta hud_panel_centerprint_fade_minfontsize "0" +seta hud_panel_buffs 1 +seta hud_panel_buffs_pos "0.450000 0.855000" +seta hud_panel_buffs_size "0.050000 0.070000" +seta hud_panel_buffs_bg "0" +seta hud_panel_buffs_bg_color "" +seta hud_panel_buffs_bg_color_team "" +seta hud_panel_buffs_bg_alpha "" +seta hud_panel_buffs_bg_border "" +seta hud_panel_buffs_bg_padding "" + menu_sync diff --git a/hud_luminos_minimal_xhair.cfg b/hud_luminos_minimal_xhair.cfg index e0345f822..8fb6cbe93 100644 --- a/hud_luminos_minimal_xhair.cfg +++ b/hud_luminos_minimal_xhair.cfg @@ -296,4 +296,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5" seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75" seta hud_panel_centerprint_fade_minfontsize "0" +seta hud_panel_buffs 1 +seta hud_panel_buffs_pos "0.450000 0.855000" +seta hud_panel_buffs_size "0.050000 0.070000" +seta hud_panel_buffs_bg "0" +seta hud_panel_buffs_bg_color "" +seta hud_panel_buffs_bg_color_team "" +seta hud_panel_buffs_bg_alpha "" +seta hud_panel_buffs_bg_border "" +seta hud_panel_buffs_bg_padding "" + menu_sync diff --git a/hud_luminos_old.cfg b/hud_luminos_old.cfg index 6a94d2b3e..9d71e2e28 100644 --- a/hud_luminos_old.cfg +++ b/hud_luminos_old.cfg @@ -296,4 +296,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5" seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75" seta hud_panel_centerprint_fade_minfontsize "0" +seta hud_panel_buffs 1 +seta hud_panel_buffs_pos "0.450000 0.855000" +seta hud_panel_buffs_size "0.050000 0.070000" +seta hud_panel_buffs_bg "0" +seta hud_panel_buffs_bg_color "" +seta hud_panel_buffs_bg_color_team "" +seta hud_panel_buffs_bg_alpha "" +seta hud_panel_buffs_bg_border "" +seta hud_panel_buffs_bg_padding "" + menu_sync diff --git a/hud_nexuiz.cfg b/hud_nexuiz.cfg index 5d8ee1fb9..9e4678293 100644 --- a/hud_nexuiz.cfg +++ b/hud_nexuiz.cfg @@ -296,4 +296,14 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5" seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75" seta hud_panel_centerprint_fade_minfontsize "0" +seta hud_panel_buffs 1 +seta hud_panel_buffs_pos "0.450000 0.855000" +seta hud_panel_buffs_size "0.050000 0.070000" +seta hud_panel_buffs_bg "0" +seta hud_panel_buffs_bg_color "" +seta hud_panel_buffs_bg_color_team "" +seta hud_panel_buffs_bg_alpha "" +seta hud_panel_buffs_bg_border "" +seta hud_panel_buffs_bg_padding "" + menu_sync diff --git a/models/.DS_Store b/models/.DS_Store new file mode 100644 index 000000000..62f119c7f Binary files /dev/null and b/models/.DS_Store differ diff --git a/models/player/.DS_Store b/models/player/.DS_Store new file mode 100644 index 000000000..cb95dc878 Binary files /dev/null and b/models/player/.DS_Store differ diff --git a/models/weapons/.DS_Store b/models/weapons/.DS_Store new file mode 100644 index 000000000..966cb13fb Binary files /dev/null and b/models/weapons/.DS_Store differ diff --git a/mutators.cfg b/mutators.cfg index c762ef9de..8ee7751e3 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -162,3 +162,50 @@ set g_campcheck_interval 10 set g_campcheck_damage 100 set g_campcheck_distance 1800 + +// ======= +// buffs +// ======= +set cl_buffs_autoreplace 1 "automatically drop current buff when picking up another" +set g_buffs 0 "enable buffs (requires buff items or powerups)" +set g_buffs_waypoint_distance 1024 "maximum distance at which buff waypoint can be seen from item" +set g_buffs_randomize 1 "randomize buff type when player drops buff" +set g_buffs_random_lifetime 30 "re-spawn the buff again if it hasn't been touched after this time in seconds" +set g_buffs_random_location 0 "randomize buff location on start and when reset" +set g_buffs_random_location_attempts 10 "number of random locations a single buff will attempt to respawn at before giving up" +set g_buffs_spawn_count 5 "how many buffs to spawn on the map if none exist already" +set g_buffs_replace_powerups 1 "replace powerups on the map with random buffs" +set g_buffs_cooldown_activate 5 "cooldown period when buff is first activated" +set g_buffs_cooldown_respawn 3 "cooldown period when buff is reloading" +set g_buffs_ammo 1 "ammo buff: infinite ammunition" +set g_buffs_resistance 1 "resistance buff: greatly reduces damage taken" +set g_buffs_resistance_blockpercent 0.7 "damage reduction multiplier, higher values mean less damage" +set g_buffs_medic 1 "medic buff: increased regeneration speed, extra health, chance to survive a fatal attack" +set g_buffs_medic_survive_chance 0.6 "multiplier chance of player surviving a fatal hit" +set g_buffs_medic_survive_health 5 "amount of health player survives with after taking a fatal hit" +set g_buffs_medic_rot 0.7 "health rot rate multiplier" +set g_buffs_medic_max 1.5 "stable health medic limit multiplier" +set g_buffs_medic_regen 1.7 "health medic rate multiplier" +set g_buffs_vengeance 1 "vengeance buff: attackers also take damage" +set g_buffs_vengeance_damage_multiplier 0.6 "amount of damage dealt the attacker takes when hitting a target with vengeance" +set g_buffs_bash 1 "bash buff: increased knockback force and immunity to knockback" +set g_buffs_bash_force 2 "bash force multiplier" +set g_buffs_bash_force_self 1.2 "bash self force multiplier" +set g_buffs_disability 1 "disability buff: attacks to players and monsters deal slowness (decreased movement/attack speed) for a few seconds" +set g_buffs_disability_time 3 "time in seconds for target disability" +set g_buffs_disability_speed 0.5 "player speed multiplier while disabled" +set g_buffs_disability_rate 1.7 "player weapon rate multiplier while disabled" +set g_buffs_speed 1 "speed buff: increased movement/attack/health regeneration speed, carrier takes slightly more damage" +set g_buffs_speed_speed 1.7 "player speed multiplier while holding speed buff" +set g_buffs_speed_rate 0.8 "player weapon rate multiplier while holding speed buff" +set g_buffs_speed_damage_take 1.2 "damage taken multiplier while holding speed buff" +set g_buffs_speed_regen 1.2 "regeneration speed multiplier while holding speed buff" +set g_buffs_vampire 1 "vampire buff: attacks to players and monsters heal the carrier" +set g_buffs_vampire_damage_steal 0.6 "damage stolen multiplier while holding vampire buff" +set g_buffs_jump 1 "jump buff: greatly increased jump height" +set g_buffs_jump_height 600 "jump height while holding jump buff" +set g_buffs_flight 1 "flight buff: greatly decreased gravity" +set g_buffs_flight_gravity 0.3 "player gravity multiplier while holding flight buff" +set g_buffs_invisible 1 "invisible buff: carrier becomes invisible" +set g_buffs_invisible_alpha 0.4 "player invisibility multiplier while holding invisible buff" + diff --git a/qcsrc/.DS_Store b/qcsrc/.DS_Store new file mode 100644 index 000000000..8a725c542 Binary files /dev/null and b/qcsrc/.DS_Store differ diff --git a/qcsrc/client/.DS_Store b/qcsrc/client/.DS_Store new file mode 100644 index 000000000..a90404938 Binary files /dev/null and b/qcsrc/client/.DS_Store differ diff --git a/qcsrc/client/Main.qc b/qcsrc/client/Main.qc index 4ea750ce5..c948c9a70 100644 --- a/qcsrc/client/Main.qc +++ b/qcsrc/client/Main.qc @@ -109,6 +109,7 @@ void CSQC_Init(void) CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels); + CALL_ACCUMULATED_FUNCTION(RegisterBuffs); WaypointSprite_Load(); diff --git a/qcsrc/client/autocvars.qh b/qcsrc/client/autocvars.qh index 678b5f49f..8516d32f6 100644 --- a/qcsrc/client/autocvars.qh +++ b/qcsrc/client/autocvars.qh @@ -291,6 +291,8 @@ float autocvar_hud_panel_powerups_baralign; float autocvar_hud_panel_powerups_flip; float autocvar_hud_panel_powerups_iconalign; float autocvar_hud_panel_powerups_progressbar; +float autocvar_hud_panel_buffs; +//float autocvar_hud_panel_buffs_iconalign; string autocvar_hud_panel_powerups_progressbar_shield; string autocvar_hud_panel_powerups_progressbar_strength; string autocvar_hud_panel_powerups_progressbar_superweapons; diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index 9faf59445..e1c950258 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -4372,6 +4372,66 @@ void HUD_CenterPrint (void) } } +// Buffs (#18) +// +void HUD_Buffs(void) +{ + float buffs = getstati(STAT_BUFFS, 0, 24); + if(!autocvar__hud_configure) + { + if(!autocvar_hud_panel_buffs) return; + if(spectatee_status == -1) return; + if(getstati(STAT_HEALTH) <= 0) return; + if(!buffs) return; + } + else + { + buffs = Buff_Type_first.items; // force first buff + } + + float b = 0; // counter to tell other functions that we have buffs + entity e; + string s = ""; + for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items) + { + ++b; + string o = strcat(rgb_to_hexcolor(Buff_Color(e.items)), Buff_PrettyName(e.items)); + if(s == "") + s = o; + else + s = strcat(s, " ", o); + } + + HUD_Panel_UpdateCvars(); + + draw_beginBoldFont(); + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + HUD_Panel_DrawBg(bound(0, b, 1)); + if(panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + //float panel_ar = mySize_x/mySize_y; + //float is_vertical = (panel_ar < 1); + //float buff_iconalign = autocvar_hud_panel_buffs_iconalign; + vector buff_offset = '0 0 0'; + + for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items) + { + //DrawNumIcon(pos + buff_offset, mySize, shield, "shield", is_vertical, buff_iconalign, '1 1 1', 1); + drawcolorcodedstring_aspect(pos + buff_offset, s, mySize, panel_fg_alpha * 0.5, DRAWFLAG_NORMAL); + } + + draw_endBoldFont(); +} + + /* ================== Main HUD system diff --git a/qcsrc/client/hud.qh b/qcsrc/client/hud.qh index dcaa81a87..55d4bd0db 100644 --- a/qcsrc/client/hud.qh +++ b/qcsrc/client/hud.qh @@ -114,7 +114,8 @@ float current_player; HUD_PANEL(ENGINEINFO , HUD_EngineInfo , engineinfo) \ HUD_PANEL(INFOMESSAGES , HUD_InfoMessages , infomessages) \ HUD_PANEL(PHYSICS , HUD_Physics , physics) \ - HUD_PANEL(CENTERPRINT , HUD_CenterPrint , centerprint) + HUD_PANEL(CENTERPRINT , HUD_CenterPrint , centerprint) \ + HUD_PANEL(BUFFS , HUD_Buffs , buffs) #define HUD_PANEL(NAME,draw_func,name) \ float HUD_PANEL_##NAME; \ diff --git a/qcsrc/client/mapvoting.qc b/qcsrc/client/mapvoting.qc index 8caeb01d5..a354bacf2 100644 --- a/qcsrc/client/mapvoting.qc +++ b/qcsrc/client/mapvoting.qc @@ -6,17 +6,26 @@ 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 mv_mouse_selection; +float mv_selection_keyboard; + +float gametypevote; +string mapvote_chosenmap; +vector gtv_text_size; +vector gtv_text_size_small; string MapVote_FormatMapItem(float id, string map, float count, float maxwidth, vector fontsize) { @@ -26,7 +35,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 +47,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 +64,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 +167,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 +179,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 +209,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 +219,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; @@ -135,10 +243,10 @@ vector MapVote_GridVec(vector gridspec, float i, float m) float MapVote_Selection(vector topleft, vector cellsize, float rows, float columns) { - float cell; + float c, r; - cell = -1; + mv_mouse_selection = -1; for (r = 0; r < rows; ++r) for (c = 0; c < columns; ++c) @@ -148,18 +256,21 @@ float MapVote_Selection(vector topleft, vector cellsize, float rows, float colum mv_mousepos_y >= topleft_y + cellsize_y * r && mv_mousepos_y <= topleft_y + cellsize_y * (r + 1)) { - cell = r * columns + c; + mv_mouse_selection = r * columns + c; break; } } - if (cell >= mv_num_maps) - cell = -1; + if (mv_mouse_selection >= mv_num_maps) + mv_mouse_selection = -1; - if (mv_abstain && cell < 0) - return mv_num_maps; + if (mv_abstain && mv_mouse_selection < 0) + mv_mouse_selection = mv_num_maps; - return cell; + if ( mv_selection_keyboard ) + return mv_selection; + + return mv_mouse_selection; } void MapVote_Draw() @@ -169,7 +280,7 @@ void MapVote_Draw() vector pos; float isize; float center; - float columns, rows; + float rows; float tsize; vector dist = '0 0 0'; @@ -178,10 +289,14 @@ void MapVote_Draw() if (!autocvar_hud_cursormode) { - mv_mousepos = mv_mousepos + getmousepos(); + vector mpos = mv_mousepos + getmousepos(); + mpos_x = bound(0, mpos_x, vid_conwidth); + mpos_y = bound(0, mpos_y, vid_conheight); + + if ( mpos_x != mv_mousepos_x || mpos_y != mv_mousepos_y ) + mv_selection_keyboard = 0; + mv_mousepos = mpos; - mv_mousepos_x = bound(0, mv_mousepos_x, vid_conwidth); - mv_mousepos_y = bound(0, mv_mousepos_y, vid_conheight); } center = (vid_conwidth - 1)/2; @@ -200,11 +315,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( mapvote_chosenmap != "" ) + { + pos_x = center - stringwidth(mapvote_chosenmap, false, hud_fontsize*1.5/2); + drawstring(pos, mapvote_chosenmap, hud_fontsize*1.5, '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 +340,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 +471,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"); @@ -343,6 +508,7 @@ void MapVote_Init() if(autocvar_hud_cursormode) { setcursormode(1); } else { mv_mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight; } mv_selection = -1; + mv_selection_keyboard = 0; for(n_ssdirs = 0; ; ++n_ssdirs) { @@ -363,39 +529,72 @@ 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) + { + mapvote_chosenmap = strzone(ReadString()); + if ( gametypevote == 2 ) + gametypevote = 0; + + 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)); + string mv_picpath = sprintf("gfx/menu/%s/gametype_%s", autocvar_menu_skin, map); + if(precache_pic(mv_picpath) == "") + mv_picpath = strcat("gfx/menu/default/gametype_", map); + map = strzone(mv_picpath); + 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 +603,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; @@ -415,6 +676,7 @@ float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary) { mv_mousepos_x = nPrimary; mv_mousepos_y = nSecondary; + mv_selection_keyboard = 0; return true; } @@ -440,48 +702,58 @@ 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: + mv_selection_keyboard = 1; + mv_selection = MapVote_MoveRight(mv_selection); + return true; + case K_LEFTARROW: + mv_selection_keyboard = 1; + mv_selection = MapVote_MoveLeft(mv_selection); + return true; + case K_DOWNARROW: + mv_selection_keyboard = 1; + mv_selection = MapVote_MoveDown(mv_selection); + return true; + case K_UPARROW: + mv_selection_keyboard = 1; + mv_selection = MapVote_MoveUp(mv_selection); + return true; + case K_KP_ENTER: + case K_ENTER: + case K_SPACE: + if ( mv_selection_keyboard ) + MapVote_SendChoice(mv_selection); + return true; } if (nPrimary == K_MOUSE1) + { + mv_selection_keyboard = 0; + mv_selection = mv_mouse_selection; if (mv_selection >= 0) { imp = min(mv_selection + 1, mv_num_maps); localcmd(strcat("\nimpulse ", ftos(imp), "\n")); return true; } + } return false; } 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/client/movetypes.qc b/qcsrc/client/movetypes.qc index a4122d261..b536797ce 100644 --- a/qcsrc/client/movetypes.qc +++ b/qcsrc/client/movetypes.qc @@ -1,4 +1,3 @@ -const float STAT_MOVEFLAGS = 225; const float MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE = 4; #define GRAVITY_UNAFFECTED_BY_TICRATE (getstati(STAT_MOVEFLAGS) & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index 1a518c5a6..b7281c562 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -8,6 +8,7 @@ sys-post.qh Defs.qc ../dpdefs/keycodes.qc ../common/constants.qh +../common/stats.qh ../warpzonelib/anglestransform.qh ../warpzonelib/mathlib.qh @@ -16,6 +17,7 @@ Defs.qc ../common/teams.qh ../common/util.qh +../common/buffs.qh ../common/test.qh ../common/counting.qh ../common/items.qh @@ -117,6 +119,8 @@ command/cl_cmd.qc ../common/monsters/monsters.qc +../common/buffs.qc + ../warpzonelib/anglestransform.qc ../warpzonelib/mathlib.qc ../warpzonelib/common.qc diff --git a/qcsrc/client/waypointsprites.qc b/qcsrc/client/waypointsprites.qc index 1367501a8..38d157928 100644 --- a/qcsrc/client/waypointsprites.qc +++ b/qcsrc/client/waypointsprites.qc @@ -241,6 +241,7 @@ vector spritelookupcolor(string s, vector def) } string spritelookuptext(string s) { + if(substring(s, 0, 5) == "buff-") { return Buff_PrettyName(Buff_Type_FromSprite(s)); } switch(s) { case "as-push": return _("Push"); diff --git a/qcsrc/common/.DS_Store b/qcsrc/common/.DS_Store new file mode 100644 index 000000000..ddd2a7a4c Binary files /dev/null and b/qcsrc/common/.DS_Store differ diff --git a/qcsrc/common/buffs.qc b/qcsrc/common/buffs.qc new file mode 100644 index 000000000..2f8e0fc23 --- /dev/null +++ b/qcsrc/common/buffs.qc @@ -0,0 +1,63 @@ +vector Buff_Color(float buff_id) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_id == e.items) + return e.colormod; + return '1 1 1'; +} + +string Buff_PrettyName(float buff_id) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_id == e.items) + return e.message; + return ""; +} + +string Buff_Name(float buff_id) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_id == e.items) + return e.netname; + return ""; +} + +float Buff_Type_FromName(string buff_name) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_name == e.netname) + return e.items; + return 0; +} + +float Buff_Type_FromSprite(string buff_sprite) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_sprite == e.model2) + return e.items; + return 0; +} + + +float Buff_Skin(float buff_id) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_id == e.items) + return e.skin; + return 0; +} + +string Buff_Sprite(float buff_id) +{ + entity e; + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_id == e.items) + return e.model2; + return ""; +} diff --git a/qcsrc/common/buffs.qh b/qcsrc/common/buffs.qh new file mode 100644 index 000000000..c29dad6dd --- /dev/null +++ b/qcsrc/common/buffs.qh @@ -0,0 +1,93 @@ +entity Buff_Type_first; +entity Buff_Type_last; +.entity enemy; // internal next pointer + +var float BUFF_LAST = 1; + +.float items; // buff ID +.string netname; // buff name +.string message; // human readable name +.vector colormod; // buff color +.string model2; // buff sprite +.float skin; // buff skin + +#define REGISTER_BUFF(hname,sname,NAME,bskin,bcolor) \ + var float BUFF_##NAME; \ + var entity Buff_Type##sname; \ + void RegisterBuffs_##sname() \ + { \ + BUFF_##NAME = BUFF_LAST * 2; \ + BUFF_LAST = BUFF_##NAME; \ + Buff_Type##sname = spawn(); \ + Buff_Type##sname.items = BUFF_##NAME; \ + Buff_Type##sname.netname = #sname; \ + Buff_Type##sname.message = hname; \ + Buff_Type##sname.skin = bskin; \ + Buff_Type##sname.colormod = bcolor; \ + Buff_Type##sname.model2 = strzone(strcat("buff-", #sname)); \ + if(!Buff_Type_first) \ + Buff_Type_first = Buff_Type##sname; \ + if(Buff_Type_last) \ + Buff_Type_last.enemy = Buff_Type##sname; \ + Buff_Type_last = Buff_Type##sname; \ + } \ + ACCUMULATE_FUNCTION(RegisterBuffs, RegisterBuffs_##sname) + +REGISTER_BUFF(_("Ammo"),ammo,AMMO,3,'0.2 1 0.2'); +REGISTER_BUFF(_("Resistance"),resistance,RESISTANCE,0,'0.3 0.2 1'); +REGISTER_BUFF(_("Speed"),speed,SPEED,9,'1 1 0.2'); +REGISTER_BUFF(_("Medic"),medic,MEDIC,1,'1 0.3 1'); +REGISTER_BUFF(_("Bash"),bash,BASH,5,'1 0.4 0'); +REGISTER_BUFF(_("Vampire"),vampire,VAMPIRE,2,'1 0.15 0'); +REGISTER_BUFF(_("Disability"),disability,DISABILITY,7,'0.66 0.66 0.73'); +REGISTER_BUFF(_("Vengeance"),vengeance,VENGEANCE,15,'0.55 0.5 1'); +REGISTER_BUFF(_("Jump"),jump,JUMP,10,'0.7 0.2 1'); +REGISTER_BUFF(_("Flight"),flight,FLIGHT,11,'1 0.2 0.5'); +REGISTER_BUFF(_("Invisible"),invisible,INVISIBLE,12,'0.9 0.9 0.9'); +#undef REGISTER_BUFF + +#ifdef SVQC +.float buffs; +void buff_Init(entity ent); +void buff_Init_Compat(entity ent, float replacement); + +#define BUFF_SPAWNFUNC(e,b,t) void spawnfunc_item_buff_##e() { self.buffs = b; self.team = t; buff_Init(self); } +#define BUFF_SPAWNFUNC_Q3TA_COMPAT(o,r) void spawnfunc_item_##o() { buff_Init_Compat(self,r); } +#define BUFF_SPAWNFUNCS(e,b) \ + BUFF_SPAWNFUNC(e, b, 0) \ + BUFF_SPAWNFUNC(e##_team1, b, NUM_TEAM_1) \ + BUFF_SPAWNFUNC(e##_team2, b, NUM_TEAM_2) \ + BUFF_SPAWNFUNC(e##_team3, b, NUM_TEAM_3) \ + BUFF_SPAWNFUNC(e##_team4, b, NUM_TEAM_4) + +BUFF_SPAWNFUNCS(resistance, BUFF_RESISTANCE) +BUFF_SPAWNFUNCS(ammo, BUFF_AMMO) +BUFF_SPAWNFUNCS(speed, BUFF_SPEED) +BUFF_SPAWNFUNCS(medic, BUFF_MEDIC) +BUFF_SPAWNFUNCS(bash, BUFF_BASH) +BUFF_SPAWNFUNCS(vampire, BUFF_VAMPIRE) +BUFF_SPAWNFUNCS(disability, BUFF_DISABILITY) +BUFF_SPAWNFUNCS(vengeance, BUFF_VENGEANCE) +BUFF_SPAWNFUNCS(jump, BUFF_JUMP) +BUFF_SPAWNFUNCS(flight, BUFF_FLIGHT) +BUFF_SPAWNFUNCS(invisible, BUFF_INVISIBLE) +BUFF_SPAWNFUNCS(random, 0) + +BUFF_SPAWNFUNC_Q3TA_COMPAT(doubler, BUFF_MEDIC) +BUFF_SPAWNFUNC_Q3TA_COMPAT(resistance, BUFF_RESISTANCE) +BUFF_SPAWNFUNC_Q3TA_COMPAT(scout, BUFF_SPEED) +BUFF_SPAWNFUNC_Q3TA_COMPAT(ammoregen, BUFF_AMMO) + +// actually Q3 +BUFF_SPAWNFUNC_Q3TA_COMPAT(haste, BUFF_SPEED) +BUFF_SPAWNFUNC_Q3TA_COMPAT(invis, BUFF_INVISIBLE) +BUFF_SPAWNFUNC_Q3TA_COMPAT(medic, BUFF_MEDIC) +#endif + +vector Buff_Color(float buff_id); +string Buff_PrettyName(float buff_id); +string Buff_Name(float buff_id); +float Buff_Type_FromName(string buff_name); +float Buff_Type_FromSprite(string buff_sprite); +float Buff_Skin(float buff_id); +string Buff_Sprite(float buff_id); diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index bf312e3ad..7c8e9b35d 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -139,78 +139,6 @@ const float CVAR_READONLY = 4; /////////////////////////// // csqc communication stuff -const float STAT_KH_KEYS = 32; -const float STAT_CTF_STATE = 33; -const float STAT_WEAPONS = 35; -const float STAT_SWITCHWEAPON = 36; -const float STAT_GAMESTARTTIME = 37; -const float STAT_STRENGTH_FINISHED = 38; -const float STAT_INVINCIBLE_FINISHED = 39; -const float STAT_PRESSED_KEYS = 42; -const float STAT_ALLOW_OLDNEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config -const float STAT_FUEL = 44; -const float STAT_NB_METERSTART = 45; -const float STAT_SHOTORG = 46; // compressShotOrigin -const float STAT_LEADLIMIT = 47; -const float STAT_WEAPON_CLIPLOAD = 48; -const float STAT_WEAPON_CLIPSIZE = 49; -const float STAT_NEX_CHARGE = 50; -const float STAT_LAST_PICKUP = 51; -const float STAT_HUD = 52; -const float STAT_NEX_CHARGEPOOL = 53; -const float STAT_HIT_TIME = 54; -const float STAT_TYPEHIT_TIME = 55; -const float STAT_LAYED_MINES = 56; -const float STAT_HAGAR_LOAD = 57; -const float STAT_SWITCHINGWEAPON = 58; -const float STAT_SUPERWEAPONS_FINISHED = 59; - -const float STAT_VEHICLESTAT_HEALTH = 60; -const float STAT_VEHICLESTAT_SHIELD = 61; -const float STAT_VEHICLESTAT_ENERGY = 62; -const float STAT_VEHICLESTAT_AMMO1 = 63; -const float STAT_VEHICLESTAT_RELOAD1 = 64; -const float STAT_VEHICLESTAT_AMMO2 = 65; -const float STAT_VEHICLESTAT_RELOAD2 = 66; - -const float STAT_SECRETS_TOTAL = 70; -const float STAT_SECRETS_FOUND = 71; - -const float STAT_RESPAWN_TIME = 72; -const float STAT_ROUNDSTARTTIME = 73; - -const float STAT_WEAPONS2 = 74; -const float STAT_WEAPONS3 = 75; - -const float STAT_MONSTERS_TOTAL = 76; -const float STAT_MONSTERS_KILLED = 77; - -// mod stats (1xx) -const float STAT_REDALIVE = 100; -const float STAT_BLUEALIVE = 101; -const float STAT_YELLOWALIVE = 102; -const float STAT_PINKALIVE = 103; - -// freeze tag -const float STAT_FROZEN = 104; -const float STAT_REVIVE_PROGRESS = 105; - -// domination -const float STAT_DOM_TOTAL_PPS = 100; -const float STAT_DOM_PPS_RED = 101; -const float STAT_DOM_PPS_BLUE = 102; -const float STAT_DOM_PPS_PINK = 103; -const float STAT_DOM_PPS_YELLOW = 104; - -//const float STAT_SPIDERBOT_AIM 53 // compressShotOrigin -//const float STAT_SPIDERBOT_TARGET 54 // compressShotOrigin - -// see DP source, quakedef.h -const float STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW = 222; -const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW = 223; -const float STAT_MOVEVARS_MAXSPEED = 244; -const float STAT_MOVEVARS_AIRACCEL_QW = 254; - const float CTF_STATE_ATTACK = 1; const float CTF_STATE_DEFEND = 2; const float CTF_STATE_COMMANDER = 3; @@ -230,7 +158,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) @@ -438,3 +366,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/deathtypes.qh b/qcsrc/common/deathtypes.qh index 1265e7eea..595cad7ee 100644 --- a/qcsrc/common/deathtypes.qh +++ b/qcsrc/common/deathtypes.qh @@ -4,6 +4,7 @@ #define DEATHTYPES \ DEATHTYPE(DEATH_AUTOTEAMCHANGE, DEATH_SELF_AUTOTEAMCHANGE, NO_MSG, DEATH_SPECIAL_START) \ + DEATHTYPE(DEATH_BUFF_VENGEANCE, NO_MSG, DEATH_MURDER_VENGEANCE, NORMAL_POS) \ DEATHTYPE(DEATH_CAMP, DEATH_SELF_CAMP, NO_MSG, NORMAL_POS) \ DEATHTYPE(DEATH_CHEAT, DEATH_SELF_CHEAT, DEATH_MURDER_CHEAT, NORMAL_POS) \ DEATHTYPE(DEATH_CUSTOM, DEATH_SELF_CUSTOM, NO_MSG, NORMAL_POS) \ diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index b8fe58b34..b060b375f 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -696,6 +696,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; @@ -1265,14 +1274,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 3c0afec98..aa1d8fd5e 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=50 teams=0"); +REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=50 teams=0",_("Survive against waves of monsters")); #define g_invasion IS_GAMETYPE(INVASION) const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps @@ -134,13 +136,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/common/notifications.qh b/qcsrc/common/notifications.qh index 117965f7b..9c55e457b 100644 --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@ -378,6 +378,7 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_DEATH, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 got caught in the blast when ^BG%s^K1's Racer exploded%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_GUN, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was bolted down by ^BG%s^K1's Racer%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VH_WAKI_ROCKET, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 couldn't find shelter from ^BG%s^K1's Racer%s%s"), "") \ + MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VENGEANCE, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_death", _("^BG%s%s^K1 was destroyed by the vengeful ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_MURDER_VOID, 3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1", "notify_void", _("^BG%s%s^K1 was thrown into a world of hurt by ^BG%s^K1%s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_AUTOTEAMCHANGE, 2, 1, "s1 s2loc death_team", "", "", _("^BG%s^K1 was moved into the %s%s"), "") \ MSG_INFO_NOTIF(1, INFO_DEATH_SELF_BETRAYAL, 2, 1, "s1 s2loc spree_lost", "s1", "notify_teamkill_red", _("^BG%s^K1 became enemies with the Lord of Teamplay%s%s"), "") \ @@ -439,6 +440,10 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_ROUND_OVER, 0, 0, "", "", "", _("^BGRound over, there's no winner"), "") \ MSG_INFO_NOTIF(1, INFO_FREEZETAG_SELF, 1, 0, "s1", "", "", _("^BG%s^K1 froze themself"), "") \ MSG_INFO_NOTIF(1, INFO_GODMODE_OFF, 0, 1, "f1", "", "", _("^BGGodmode saved you %s units of damage, cheater!"), "") \ + MSG_INFO_NOTIF(1, INFO_ITEM_BUFF, 1, 1, "s1 item_buffname", "", "", _("^BG%s^BG got the %s^BG Buff!"), "") \ + MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_LOST, 1, 1, "s1 item_buffname", "", "", _("^BG%s^BG lost the %s^BG Buff!"), "") \ + MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_DROP, 0, 1, "item_buffname", "", "", _("^BGYou dropped the %s^BG Buff!"), "") \ + MSG_INFO_NOTIF(1, INFO_ITEM_BUFF_GOT, 0, 1, "item_buffname", "", "", _("^BGYou got the %s^BG Buff!"), "") \ MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_DONTHAVE, 0, 1, "item_wepname", "", "", _("^BGYou do not have the ^F1%s"), "") \ MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_DROP, 1, 1, "item_wepname item_wepammo", "", "", _("^BGYou dropped the ^F1%s^BG%s"), "") \ MSG_INFO_NOTIF(0, INFO_ITEM_WEAPON_GOT, 0, 1, "item_wepname", "", "", _("^BGYou got the ^F1%s"), "") \ @@ -627,6 +632,8 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SELF, 0, 0, "", NO_CPID, "0 0", _("^K1You froze yourself"), "") \ MSG_CENTER_NOTIF(1, CENTER_FREEZETAG_SPAWN_LATE, 0, 0, "", NO_CPID, "0 0", _("^K1Round already started, you spawn as frozen"), "") \ MSG_CENTER_NOTIF(1, CENTER_INVASION_SUPERMONSTER, 1, 0, "s1", NO_CPID, "0 0", _("^K1A %s has arrived!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_DROP, 0, 1, "item_buffname", CPID_ITEM, "item_centime 0", _("^BGYou dropped the %s^BG Buff!"), "") \ + MSG_CENTER_NOTIF(1, CENTER_ITEM_BUFF_GOT, 0, 1, "item_buffname", CPID_ITEM, "item_centime 0", _("^BGYou got the %s^BG Buff!"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DONTHAVE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_DROP, 1, 1, "item_wepname item_wepammo", CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "") \ MSG_CENTER_NOTIF(1, CENTER_ITEM_WEAPON_GOT, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "") \ @@ -717,6 +724,7 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_DEATH, NO_MSG, INFO_DEATH_MURDER_VH_WAKI_DEATH, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_GUN, NO_MSG, INFO_DEATH_MURDER_VH_WAKI_GUN, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VH_WAKI_ROCKET, NO_MSG, INFO_DEATH_MURDER_VH_WAKI_ROCKET, NO_MSG) \ + MSG_MULTI_NOTIF(1, DEATH_MURDER_VENGEANCE, NO_MSG, INFO_DEATH_MURDER_VENGEANCE, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_MURDER_VOID, NO_MSG, INFO_DEATH_MURDER_VOID, NO_MSG) \ MSG_MULTI_NOTIF(1, DEATH_SELF_AUTOTEAMCHANGE, NO_MSG, INFO_DEATH_SELF_AUTOTEAMCHANGE, CENTER_DEATH_SELF_AUTOTEAMCHANGE) \ MSG_MULTI_NOTIF(1, DEATH_SELF_BETRAYAL, NO_MSG, INFO_DEATH_SELF_BETRAYAL, CENTER_DEATH_SELF_BETRAYAL) \ @@ -767,6 +775,8 @@ void Send_Notification_WOCOVA( MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_DEATH, NO_MSG, INFO_DEATH_SELF_VH_WAKI_DEATH, CENTER_DEATH_SELF_VH_WAKI_DEATH) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VH_WAKI_ROCKET, NO_MSG, INFO_DEATH_SELF_VH_WAKI_ROCKET, CENTER_DEATH_SELF_VH_WAKI_ROCKET) \ MSG_MULTI_NOTIF(1, DEATH_SELF_VOID, NO_MSG, INFO_DEATH_SELF_VOID, CENTER_DEATH_SELF_VOID) \ + MSG_MULTI_NOTIF(1, ITEM_BUFF_DROP, NO_MSG, INFO_ITEM_BUFF_DROP, CENTER_ITEM_BUFF_DROP) \ + MSG_MULTI_NOTIF(1, ITEM_BUFF_GOT, NO_MSG, INFO_ITEM_BUFF_GOT, CENTER_ITEM_BUFF_GOT) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_DONTHAVE, NO_MSG, INFO_ITEM_WEAPON_DONTHAVE, CENTER_ITEM_WEAPON_DONTHAVE) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_DROP, NO_MSG, INFO_ITEM_WEAPON_DROP, CENTER_ITEM_WEAPON_DROP) \ MSG_MULTI_NOTIF(1, ITEM_WEAPON_GOT, NO_MSG, INFO_ITEM_WEAPON_GOT, CENTER_ITEM_WEAPON_GOT) \ @@ -934,6 +944,7 @@ var float autocvar_notification_show_sprees_center_specialonly = TRUE; item_wepname: return full name of a weapon from weaponid item_wepammo: ammo display for weapon from string item_centime: amount of time to display weapon message in centerprint + item_buffname: return full name of a buff from buffid death_team: show the full name of the team a player is switching from */ @@ -986,6 +997,7 @@ string arg_slot[NOTIF_MAX_ARGS]; ARG_CASE(ARG_CS_SV, "spree_end", (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \ ARG_CASE(ARG_CS_SV, "spree_lost", (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \ ARG_CASE(ARG_CS_SV, "item_wepname", W_Name(f1)) \ + ARG_CASE(ARG_CS_SV, "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f1)), Buff_PrettyName(f1))) \ ARG_CASE(ARG_CS_SV, "item_wepammo", (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \ ARG_CASE(ARG_DC, "item_centime", ftos(autocvar_notification_item_centerprinttime)) \ ARG_CASE(ARG_SV, "death_team", Team_ColoredFullName(f1)) \ diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh new file mode 100644 index 000000000..7965597d1 --- /dev/null +++ b/qcsrc/common/stats.qh @@ -0,0 +1,285 @@ +// Full list of all stat constants, icnluded in a single location for easy reference +// 255 is the current limit (MAX_CL_STATS - 1), engine will need to be modified if you wish to add more stats + +const float MAX_CL_STATS = 256; +const float STAT_HEALTH = 0; +// 1 empty? +const float STAT_WEAPON = 2; +const float STAT_AMMO = 3; +const float STAT_ARMOR = 4; +const float STAT_WEAPONFRAME = 5; +const float STAT_SHELLS = 6; +const float STAT_NAILS = 7; +const float STAT_ROCKETS = 8; +const float STAT_CELLS = 9; +const float STAT_ACTIVEWEAPON = 10; +const float STAT_TOTALSECRETS = 11; +const float STAT_TOTALMONSTERS = 12; +const float STAT_SECRETS = 13; +const float STAT_MONSTERS = 14; +const float STAT_ITEMS = 15; +const float STAT_VIEWHEIGHT = 16; +// 17 empty? +// 18 empty? +// 19 empty? +// 20 empty? +const float STAT_VIEWZOOM = 21; +// 22 empty? +// 23 empty? +// 24 empty? +// 25 empty? +// 26 empty? +// 27 empty? +// 28 empty? +// 29 empty? +// 30 empty? +// 31 empty? +const float STAT_KH_KEYS = 32; +const float STAT_CTF_STATE = 33; +// 34 empty? +const float STAT_WEAPONS = 35; +const float STAT_SWITCHWEAPON = 36; +const float STAT_GAMESTARTTIME = 37; +const float STAT_STRENGTH_FINISHED = 38; +const float STAT_INVINCIBLE_FINISHED = 39; +// 40 empty? +// 41 empty? +const float STAT_PRESSED_KEYS = 42; +const float STAT_ALLOW_OLDNEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config +const float STAT_FUEL = 44; +const float STAT_NB_METERSTART = 45; +const float STAT_SHOTORG = 46; // compressShotOrigin +const float STAT_LEADLIMIT = 47; +const float STAT_WEAPON_CLIPLOAD = 48; +const float STAT_WEAPON_CLIPSIZE = 49; +const float STAT_NEX_CHARGE = 50; +const float STAT_LAST_PICKUP = 51; +const float STAT_HUD = 52; +const float STAT_NEX_CHARGEPOOL = 53; +const float STAT_HIT_TIME = 54; +const float STAT_TYPEHIT_TIME = 55; +const float STAT_LAYED_MINES = 56; +const float STAT_HAGAR_LOAD = 57; +const float STAT_SWITCHINGWEAPON = 58; +const float STAT_SUPERWEAPONS_FINISHED = 59; +const float STAT_VEHICLESTAT_HEALTH = 60; +const float STAT_VEHICLESTAT_SHIELD = 61; +const float STAT_VEHICLESTAT_ENERGY = 62; +const float STAT_VEHICLESTAT_AMMO1 = 63; +const float STAT_VEHICLESTAT_RELOAD1 = 64; +const float STAT_VEHICLESTAT_AMMO2 = 65; +const float STAT_VEHICLESTAT_RELOAD2 = 66; +const float STAT_VEHICLESTAT_W2MODE = 67; +// 68 empty? +// 69 empty? +const float STAT_SECRETS_TOTAL = 70; +const float STAT_SECRETS_FOUND = 71; +const float STAT_RESPAWN_TIME = 72; +const float STAT_ROUNDSTARTTIME = 73; +const float STAT_WEAPONS2 = 74; +const float STAT_WEAPONS3 = 75; +const float STAT_MONSTERS_TOTAL = 76; +const float STAT_MONSTERS_KILLED = 77; +const float STAT_BUFFS = 78; +// 79 empty? +// 80 empty? +// 86 empty? +// 87 empty? +// 88 empty? +// 89 empty? +// 90 empty? +// 91 empty? +// 92 empty? +// 93 empty? +// 94 empty? +// 95 empty? +// 96 empty? +// 97 empty? +// 98 empty? +// 99 empty? + + +/* The following stats change depending on the gamemode, so can share the same ID */ +// IDs 100 to 104 reserved for gamemodes + +// freeze tag, clan arena, jailbreak +const float STAT_REDALIVE = 100; +const float STAT_BLUEALIVE = 101; +const float STAT_YELLOWALIVE = 102; +const float STAT_PINKALIVE = 103; + +// domination +const float STAT_DOM_TOTAL_PPS = 100; +const float STAT_DOM_PPS_RED = 101; +const float STAT_DOM_PPS_BLUE = 102; +const float STAT_DOM_PPS_YELLOW = 103; +const float STAT_DOM_PPS_PINK = 104; + +// vip +const float STAT_VIP = 100; +const float STAT_VIP_RED = 101; +const float STAT_VIP_BLUE = 102; +const float STAT_VIP_YELLOW = 103; +const float STAT_VIP_PINK = 104; + +// key hunt +const float STAT_KH_REDKEY_TEAM = 100; +const float STAT_KH_BLUEKEY_TEAM = 101; +const float STAT_KH_YELLOWKEY_TEAM = 102; +const float STAT_KH_PINKKEY_TEAM = 103; + +/* Gamemode-specific stats end here */ + + +const float STAT_FROZEN = 105; +const float STAT_REVIVE_PROGRESS = 106; +// 107 empty? +// 108 empty? +// 109 empty? +// 110 empty? +// 111 empty? +// 112 empty? +// 113 empty? +// 114 empty? +// 115 empty? +// 116 empty? +// 117 empty? +// 118 empty? +// 119 empty? +// 120 empty? +// 121 empty? +// 122 empty? +// 123 empty? +// 124 empty? +// 125 empty? +// 126 empty? +// 127 empty? +// 128 empty? +// 129 empty? +// 130 empty? +// 131 empty? +// 132 empty? +// 133 empty? +// 134 empty? +// 135 empty? +// 136 empty? +// 137 empty? +// 138 empty? +// 139 empty? +// 140 empty? +// 141 empty? +// 142 empty? +// 143 empty? +// 144 empty? +// 145 empty? +// 146 empty? +// 147 empty? +// 148 empty? +// 149 empty? +// 150 empty? +// 151 empty? +// 152 empty? +// 153 empty? +// 154 empty? +// 155 empty? +// 156 empty? +// 157 empty? +// 158 empty? +// 159 empty? +// 160 empty? +// 161 empty? +// 162 empty? +// 162 empty? +// 163 empty? +// 164 empty? +// 165 empty? +// 166 empty? +// 167 empty? +// 168 empty? +// 169 empty? +// 170 empty? +// 171 empty? +// 172 empty? +// 173 empty? +// 174 empty? +// 175 empty? +// 176 empty? +// 177 empty? +// 178 empty? +// 179 empty? +// 180 empty? +// 181 empty? +// 182 empty? +// 183 empty? +// 184 empty? +// 185 empty? +// 186 empty? +// 187 empty? +// 188 empty? +// 189 empty? +// 190 empty? +// 191 empty? +// 192 empty? +// 193 empty? +// 194 empty? +// 195 empty? +// 196 empty? +// 197 empty? +// 198 empty? +// 199 empty? +// 200 empty? +// 201 empty? +// 202 empty? +// 203 empty? +// 204 empty? +// 205 empty? +// 206 empty? +// 207 empty? +// 208 empty? +// 209 empty? +// 210 empty? +// 211 empty? +// 212 empty? +// 213 empty? +// 214 empty? +// 215 empty? +// 216 empty? +// 217 empty? +// 218 empty? +// 219 empty? +const float STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR = 220; +const float STAT_MOVEVARS_AIRCONTROL_PENALTY = 221; +const float STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW = 222; +const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW = 223; +const float STAT_MOVEVARS_AIRCONTROL_POWER = 224; +const float STAT_MOVEFLAGS = 225; +const float STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL = 226; +const float STAT_MOVEVARS_WARSOWBUNNY_ACCEL = 227; +const float STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED = 228; +const float STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL = 229; +const float STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO = 230; +const float STAT_MOVEVARS_AIRSTOPACCELERATE = 231; +const float STAT_MOVEVARS_AIRSTRAFEACCELERATE = 232; +const float STAT_MOVEVARS_MAXAIRSTRAFESPEED = 233; +const float STAT_MOVEVARS_AIRCONTROL = 234; +const float STAT_FRAGLIMIT = 235; +const float STAT_TIMELIMIT = 236; +const float STAT_MOVEVARS_WALLFRICTION = 237; +const float STAT_MOVEVARS_FRICTION = 238; +const float STAT_MOVEVARS_WATERFRICTION = 239; +const float STAT_MOVEVARS_TICRATE = 240; +const float STAT_MOVEVARS_TIMESCALE = 241; +const float STAT_MOVEVARS_GRAVITY = 242; +const float STAT_MOVEVARS_STOPSPEED = 243; +const float STAT_MOVEVARS_MAXSPEED = 244; +const float STAT_MOVEVARS_SPECTATORMAXSPEED = 245; +const float STAT_MOVEVARS_ACCELERATE = 246; +const float STAT_MOVEVARS_AIRACCELERATE = 247; +const float STAT_MOVEVARS_WATERACCELERATE = 248; +const float STAT_MOVEVARS_ENTGRAVITY = 249; +const float STAT_MOVEVARS_JUMPVELOCITY = 250; +const float STAT_MOVEVARS_EDGEFRICTION = 251; +const float STAT_MOVEVARS_MAXAIRSPEED = 252; +const float STAT_MOVEVARS_STEPHEIGHT = 253; +const float STAT_MOVEVARS_AIRACCEL_QW = 254; +const float STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION = 255; diff --git a/qcsrc/common/weapons/.DS_Store b/qcsrc/common/weapons/.DS_Store new file mode 100644 index 000000000..31f6276c3 Binary files /dev/null and b/qcsrc/common/weapons/.DS_Store differ diff --git a/qcsrc/menu/.DS_Store b/qcsrc/menu/.DS_Store new file mode 100644 index 000000000..98130110a Binary files /dev/null and b/qcsrc/menu/.DS_Store differ diff --git a/qcsrc/menu/classes.c b/qcsrc/menu/classes.c index ff1210c74..ee1ce5d31 100644 --- a/qcsrc/menu/classes.c +++ b/qcsrc/menu/classes.c @@ -111,4 +111,5 @@ #include "xonotic/dialog_hudpanel_weapons.c" #include "xonotic/dialog_hudpanel_physics.c" #include "xonotic/dialog_hudpanel_centerprint.c" +#include "xonotic/dialog_hudpanel_buffs.c" #include "xonotic/slider_picmip.c" diff --git a/qcsrc/menu/xonotic/.DS_Store b/qcsrc/menu/xonotic/.DS_Store new file mode 100644 index 000000000..e3da66e65 Binary files /dev/null and b/qcsrc/menu/xonotic/.DS_Store differ diff --git a/qcsrc/menu/xonotic/dialog_hudpanel_buffs.c b/qcsrc/menu/xonotic/dialog_hudpanel_buffs.c new file mode 100644 index 000000000..ac1033cd5 --- /dev/null +++ b/qcsrc/menu/xonotic/dialog_hudpanel_buffs.c @@ -0,0 +1,22 @@ +#ifdef INTERFACE +CLASS(XonoticHUDBuffsDialog) EXTENDS(XonoticRootDialog) + METHOD(XonoticHUDBuffsDialog, fill, void(entity)) + ATTRIB(XonoticHUDBuffsDialog, title, string, _("Buffs Panel")) + ATTRIB(XonoticHUDBuffsDialog, color, vector, SKINCOLOR_DIALOG_TEAMSELECT) + ATTRIB(XonoticHUDBuffsDialog, intendedWidth, float, 0.4) + ATTRIB(XonoticHUDBuffsDialog, rows, float, 15) + ATTRIB(XonoticHUDBuffsDialog, columns, float, 4) + ATTRIB(XonoticHUDBuffsDialog, name, string, "HUDbuffs") + ATTRIB(XonoticHUDBuffsDialog, requiresConnection, float, TRUE) +ENDCLASS(XonoticHUDBuffsDialog) +#endif + +#ifdef IMPLEMENTATION +void XonoticHUDBuffsDialog_fill(entity me) +{ + entity e; + string panelname = "buffs"; + + DIALOG_HUDPANEL_COMMON(); +} +#endif diff --git a/qcsrc/menu/xonotic/mainwindow.c b/qcsrc/menu/xonotic/mainwindow.c index debe6bd97..5c58025fc 100644 --- a/qcsrc/menu/xonotic/mainwindow.c +++ b/qcsrc/menu/xonotic/mainwindow.c @@ -126,6 +126,10 @@ void MainWindow_configureMainWindow(entity me) i.configureDialog(i); me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); + i = spawnXonoticHUDBuffsDialog(); + i.configureDialog(i); + me.addItemCentered(me, i, i.intendedWidth * eX + i.intendedHeight * eY, SKINALPHAS_MAINMENU_z); + // dialogs used by settings me.userbindEditDialog = i = spawnXonoticUserbindEditDialog(); diff --git a/qcsrc/menu/xonotic/playermodel.c b/qcsrc/menu/xonotic/playermodel.c index e4195617f..a1592de17 100644 --- a/qcsrc/menu/xonotic/playermodel.c +++ b/qcsrc/menu/xonotic/playermodel.c @@ -5,6 +5,7 @@ CLASS(XonoticPlayerModelSelector) EXTENDS(XonoticImage) METHOD(XonoticPlayerModelSelector, saveCvars, void(entity)) METHOD(XonoticPlayerModelSelector, draw, void(entity)) METHOD(XonoticPlayerModelSelector, resizeNotify, void(entity, vector, vector, vector, vector)) + METHOD(XonoticPlayerModelSelector, showNotify, void(entity)) ATTRIB(XonoticPlayerModelSelector, currentModel, string, string_null) ATTRIB(XonoticPlayerModelSelector, currentSkin, float, 0) ATTRIB(XonoticPlayerModelSelector, currentModelImage, string, string_null) @@ -201,4 +202,9 @@ void XonoticPlayerModelSelector_resizeNotify(entity me, vector relOrigin, vector me.realFontSize_y = me.fontSize / absSize_y; me.realFontSize_x = me.fontSize / absSize_x; } + +void XonoticPlayerModelSelector_showNotify(entity me) +{ + me.configureXonoticPlayerModelSelector(me); +} #endif diff --git a/qcsrc/menu/xonotic/skinlist.c b/qcsrc/menu/xonotic/skinlist.c index 3e094e7d7..0387303ea 100644 --- a/qcsrc/menu/xonotic/skinlist.c +++ b/qcsrc/menu/xonotic/skinlist.c @@ -162,8 +162,7 @@ void XonoticSkinList_drawListBoxItem(entity me, float i, vector absSize, float i s = me.skinParameter(me, i, SKINPARM_PREVIEW); draw_Picture(me.columnPreviewOrigin * eX, s, me.columnPreviewSize * eX + eY, '1 1 1', 1); - s = me.skinParameter(me, i, SKINPARM_NAME); - s = sprintf(_("%s: %s"), s, me.skinParameter(me, i, SKINPARM_TITLE)); + s = me.skinParameter(me, i, SKINPARM_TITLE); s = draw_TextShortenToWidth(s, me.columnNameSize, 0, me.realFontSize); draw_Text(me.realUpperMargin1 * eY + (me.columnNameOrigin + 0.00 * (me.columnNameSize - draw_TextWidth(s, 0, me.realFontSize))) * eX, s, me.realFontSize, SKINCOLOR_SKINLIST_TITLE, SKINALPHA_TEXT, 0); diff --git a/qcsrc/server/.DS_Store b/qcsrc/server/.DS_Store new file mode 100644 index 000000000..fe450609a Binary files /dev/null and b/qcsrc/server/.DS_Store differ diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 080d701d8..3df7c1763 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -1156,6 +1156,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; @@ -1283,3 +1288,33 @@ float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health; float autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath; +float autocvar_g_buffs_waypoint_distance; +float autocvar_g_buffs_randomize; +float autocvar_g_buffs_random_lifetime; +float autocvar_g_buffs_random_location; +float autocvar_g_buffs_random_location_attempts; +float autocvar_g_buffs_spawn_count; +float autocvar_g_buffs_replace_powerups; +float autocvar_g_buffs_cooldown_activate; +float autocvar_g_buffs_cooldown_respawn; +float autocvar_g_buffs_resistance_blockpercent; +float autocvar_g_buffs_medic_survive_chance; +float autocvar_g_buffs_medic_survive_health; +float autocvar_g_buffs_medic_rot; +float autocvar_g_buffs_medic_max; +float autocvar_g_buffs_medic_regen; +float autocvar_g_buffs_vengeance_damage_multiplier; +float autocvar_g_buffs_bash_force; +float autocvar_g_buffs_bash_force_self; +float autocvar_g_buffs_disability_time; +float autocvar_g_buffs_disability_speed; +float autocvar_g_buffs_disability_rate; +float autocvar_g_buffs_speed_speed; +float autocvar_g_buffs_speed_rate; +float autocvar_g_buffs_speed_damage_take; +float autocvar_g_buffs_speed_regen; +float autocvar_g_buffs_vampire_damage_steal; +float autocvar_g_buffs_invisible_alpha; +float autocvar_g_buffs_flight_gravity; +float autocvar_g_buffs_jump_height; + diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 498e46453..bb24fd677 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -1588,17 +1588,26 @@ float CalcRotRegen(float current, float regenstable, float regenfactor, float re void player_regen (void) { + float max_mod, regen_mod, rot_mod, limit_mod; + max_mod = regen_mod = rot_mod = limit_mod = 1; + regen_mod_max = max_mod; + regen_mod_regen = regen_mod; + regen_mod_rot = rot_mod; + regen_mod_limit = limit_mod; if(!MUTATOR_CALLHOOK(PlayerRegen)) { - float minh, mina, maxh, maxa, limith, limita, max_mod, regen_mod, rot_mod, limit_mod; + float minh, mina, maxh, maxa, limith, limita; maxh = autocvar_g_balance_health_rotstable; maxa = autocvar_g_balance_armor_rotstable; minh = autocvar_g_balance_health_regenstable; mina = autocvar_g_balance_armor_regenstable; limith = autocvar_g_balance_health_limit; limita = autocvar_g_balance_armor_limit; - - max_mod = regen_mod = rot_mod = limit_mod = 1; + + max_mod = regen_mod_max; + regen_mod = regen_mod_regen; + rot_mod = regen_mod_rot; + limit_mod = regen_mod_limit; maxh = maxh * max_mod; minh = minh * max_mod; diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc index 43c7be517..7563cf520 100644 --- a/qcsrc/server/cl_physics.qc +++ b/qcsrc/server/cl_physics.qc @@ -23,14 +23,15 @@ void PlayerJump (void) return; // no jumping while blocked float doublejump = FALSE; + float mjumpheight = autocvar_sv_jumpvelocity; player_multijump = doublejump; + player_jumpheight = mjumpheight; if(MUTATOR_CALLHOOK(PlayerJump)) return; doublejump = player_multijump; - - float mjumpheight; + mjumpheight = player_jumpheight; if (autocvar_sv_doublejump) { @@ -47,7 +48,6 @@ void PlayerJump (void) } } - mjumpheight = autocvar_sv_jumpvelocity; if (self.waterlevel >= WATERLEVEL_SWIMMING) { self.velocity_z = self.stat_sv_maxspeed * 0.7; diff --git a/qcsrc/server/cl_weaponsystem.qc b/qcsrc/server/cl_weaponsystem.qc index c36033a28..dac383633 100644 --- a/qcsrc/server/cl_weaponsystem.qc +++ b/qcsrc/server/cl_weaponsystem.qc @@ -14,6 +14,10 @@ float W_WeaponRateFactor() float t; t = 1.0 / g_weaponratefactor; + weapon_rate = t; + MUTATOR_CALLHOOK(WeaponRateFactor); + t = weapon_rate; + return t; } diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index f635bff2a..b049972b5 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(); @@ -546,6 +545,7 @@ void spawnfunc___init_dedicated_server(void) CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); + CALL_ACCUMULATED_FUNCTION(RegisterBuffs); MapInfo_Enumerate(); MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0); @@ -595,6 +595,7 @@ void spawnfunc_worldspawn (void) CALL_ACCUMULATED_FUNCTION(RegisterGametypes); CALL_ACCUMULATED_FUNCTION(RegisterNotifications); CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); + CALL_ACCUMULATED_FUNCTION(RegisterBuffs); ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); @@ -896,7 +897,6 @@ string GetGametype() return MapInfo_Type_ToString(MapInfo_LoadedGametype); } -string getmapname_stored; string GetMapname() { return mapname; @@ -1221,13 +1221,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 +1278,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 +2042,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 +2203,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 000000000..3e1a86620 --- /dev/null +++ b/qcsrc/server/mapvoting.qc @@ -0,0 +1,742 @@ +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, 0) ) + return GTV_FORBIDDEN; + if (!(MapInfo_Map_supportedGametypes & 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); + + if ( gametypevote ) + { + // gametype vote + WriteByte(MSG_ENTITY, 1); + WriteString(MSG_ENTITY, autocvar_nextmap); + } + else if ( autocvar_sv_vote_gametype ) + { + // map vote but gametype has been chosen via voting screen + WriteByte(MSG_ENTITY, 2); + WriteString(MSG_ENTITY, MapInfo_Type_ToText(MapInfo_CurrentGametype())); + } + else + WriteByte(MSG_ENTITY, 0); // map vote + + 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 000000000..6875958bf --- /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/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 3cb28bc84..5defa9b1e 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -930,6 +930,7 @@ void readlevelcvars(void) CHECK_MUTATOR_ADD("g_nades", mutator_nades, 1); CHECK_MUTATOR_ADD("g_sandbox", sandbox, 1); CHECK_MUTATOR_ADD("g_campcheck", mutator_campcheck, 1); + CHECK_MUTATOR_ADD("g_buffs", mutator_buffs, 1); #undef CHECK_MUTATOR_ADD diff --git a/qcsrc/server/mutators/base.qh b/qcsrc/server/mutators/base.qh index 78a08c551..f4021eb4c 100644 --- a/qcsrc/server/mutators/base.qh +++ b/qcsrc/server/mutators/base.qh @@ -77,6 +77,7 @@ MUTATOR_HOOKABLE(PlayerJump); // called when a player presses the jump key // INPUT, OUTPUT: float player_multijump; + float player_jumpheight; MUTATOR_HOOKABLE(GiveFragsForKill); // called when someone was fragged by "self", and is expected to change frag_score to adjust scoring for the kill @@ -102,6 +103,11 @@ MUTATOR_HOOKABLE(SpectateCopy); MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon); // returns 1 if throwing the current weapon shall not be allowed +MUTATOR_HOOKABLE(WeaponRateFactor); + // allows changing attack rate + // INPUT, OUTPUT: + float weapon_rate; + MUTATOR_HOOKABLE(SetStartItems); // adjusts {warmup_}start_{items,weapons,ammo_{cells,rockets,nails,shells,fuel}} @@ -222,6 +228,11 @@ MUTATOR_HOOKABLE(PlayerPowerups); MUTATOR_HOOKABLE(PlayerRegen); // called every player think frame // return 1 to disable regen + // INPUT, OUTPUT: + float regen_mod_max; + float regen_mod_regen; + float regen_mod_rot; + float regen_mod_limit; MUTATOR_HOOKABLE(PlayerUseKey); // called when the use key is pressed diff --git a/qcsrc/server/mutators/mutator_buffs.qc b/qcsrc/server/mutators/mutator_buffs.qc new file mode 100644 index 000000000..c71fe60ea --- /dev/null +++ b/qcsrc/server/mutators/mutator_buffs.qc @@ -0,0 +1,787 @@ +float buffs_BuffModel_Customize() +{ + entity player, myowner; + float same_team; + + player = WaypointSprite_getviewentity(other); + myowner = self.owner; + same_team = (SAME_TEAM(player, myowner) || SAME_TEAM(player, myowner)); + + if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0) + return FALSE; + + if(player == myowner || (IS_SPEC(other) && other.enemy == myowner)) + { + // somewhat hide the model, but keep the glow + self.effects = 0; + self.alpha = -1; + } + else + { + self.effects = EF_FULLBRIGHT | EF_LOWPRECISION; + self.alpha = 1; + } + return TRUE; +} + +// buff item +float buff_Waypoint_visible_for_player(entity plr) +{ + if(!self.owner.buff_active && !self.owner.buff_activetime) + return FALSE; + + if(plr.buffs) + { + if(plr.cvar_cl_buffs_autoreplace) + { + if(plr.buffs == self.owner.buffs) + return FALSE; + } + else + return FALSE; + } + + return WaypointSprite_visible_for_player(plr); +} + +void buff_Waypoint_Spawn(entity e) +{ + WaypointSprite_Spawn(Buff_Sprite(e.buffs), 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs_z, world, e.team, e, buff_waypoint, TRUE, RADARICON_POWERUP, e.glowmod); + WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_POWERUP, e.glowmod); + e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player; +} + +void buff_SetCooldown(float cd) +{ + cd = max(0, cd); + + if(!self.buff_waypoint) + buff_Waypoint_Spawn(self); + + WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd); + self.buff_activetime = cd; + self.buff_active = !cd; +} + +void buff_Respawn(entity ent) +{ + if(gameover) { return; } + + vector oldbufforigin = ent.origin; + + if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256)) + { + entity spot = SelectSpawnPoint(TRUE); + setorigin(ent, ((spot.origin + '0 0 200') + (randomvec() * 300))); + ent.angles = spot.angles; + } + + tracebox(ent.origin, ent.mins * 1.5, self.maxs * 1.5, ent.origin, MOVE_NOMONSTERS, ent); + + setorigin(ent, trace_endpos); // attempt to unstick + + ent.movetype = MOVETYPE_TOSS; + + makevectors(ent.angles); + ent.velocity = '0 0 200'; + ent.angles = '0 0 0'; + if(autocvar_g_buffs_random_lifetime > 0) + ent.lifetime = time + autocvar_g_buffs_random_lifetime; + + pointparticles(particleeffectnum("electro_combo"), oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1); + pointparticles(particleeffectnum("electro_combo"), CENTER_OR_VIEWOFS(ent), '0 0 0', 1); + + WaypointSprite_Ping(ent.buff_waypoint); + + sound(ent, CH_TRIGGER, "keepaway/respawn.wav", VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) +} + +void buff_Touch() +{ + if(gameover) { return; } + + if(ITEM_TOUCH_NEEDKILL()) + { + buff_Respawn(self); + return; + } + + if((self.team && DIFF_TEAM(other, self)) + || (other.freezetag_frozen) + || (other.vehicle) + || (!IS_PLAYER(other)) + || (!self.buff_active) + ) + { + // can't touch this + return; + } + + if(other.buffs) + { + if(other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs) + { + //Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_DROP, other.buffs); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ITEM_BUFF_LOST, other.netname, other.buffs); + + other.buffs = 0; + //sound(other, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM); + } + else { return; } // do nothing + } + + self.owner = other; + self.buff_active = FALSE; + self.lifetime = 0; + + Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, self.buffs); + Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, self.buffs); + + pointparticles(particleeffectnum("item_pickup"), CENTER_OR_VIEWOFS(self), '0 0 0', 1); + sound(other, CH_TRIGGER, "misc/shield_respawn.wav", VOL_BASE, ATTN_NORM); + other.buffs |= (self.buffs); +} + +float buff_Available(float buffid) +{ + if(buffid == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only")))) + return FALSE; + + if(buffid == BUFF_VAMPIRE && cvar("g_vampire")) + return FALSE; + + if(!cvar(strcat("g_buffs_", Buff_Name(buffid)))) + return FALSE; + + return TRUE; +} + +void buff_NewType(entity ent, float cb) +{ + entity e; + RandomSelection_Init(); + for(e = Buff_Type_first; e; e = e.enemy) + if(buff_Available(e.items)) + { + RandomSelection_Add(world, e.items, string_null, 1, 1 / e.count); // if it's already been chosen, give it a lower priority + e.count += 1; + } + ent.buffs = RandomSelection_chosen_float; +} + +void buff_Think() +{ + if(self.buffs != self.oldbuffs) + { + self.color = Buff_Color(self.buffs); + self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color); + self.skin = Buff_Skin(self.buffs); + + setmodel(self, "models/relics/relic.md3"); + + if(self.buff_waypoint) + { + //WaypointSprite_Disown(self.buff_waypoint, 1); + WaypointSprite_Kill(self.buff_waypoint); + buff_Waypoint_Spawn(self); + if(self.buff_activetime) + WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime); + } + + self.oldbuffs = self.buffs; + } + + if(!gameover) + if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) + if(!self.buff_activetime_updated) + { + buff_SetCooldown(self.buff_activetime); + self.buff_activetime_updated = TRUE; + } + + if(!self.buff_active && !self.buff_activetime) + if(!self.owner || self.owner.freezetag_frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs)) + { + buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime); + self.owner = world; + if(autocvar_g_buffs_randomize) + buff_NewType(self, self.buffs); + + if(autocvar_g_buffs_random_location || (self.spawnflags & 1)) + buff_Respawn(self); + } + + if(self.buff_activetime) + if(!gameover) + if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) + { + self.buff_activetime = max(0, self.buff_activetime - frametime); + + if(!self.buff_activetime) + { + self.buff_active = TRUE; + sound(self, CH_TRIGGER, "misc/strength_respawn.wav", VOL_BASE, ATTN_NORM); + pointparticles(particleeffectnum("item_respawn"), CENTER_OR_VIEWOFS(self), '0 0 0', 1); + } + } + + if(!self.buff_active) + { + self.alpha = 0.3; + self.effects &= ~(EF_FULLBRIGHT); + self.pflags = 0; + } + else + { + self.alpha = 1; + self.effects |= EF_FULLBRIGHT; + self.light_lev = 220 + 36 * sin(time); + self.pflags = PFLAGS_FULLDYNAMIC; + + if(self.team && !self.buff_waypoint) + buff_Waypoint_Spawn(self); + + if(self.lifetime) + if(time >= self.lifetime) + buff_Respawn(self); + } + + self.nextthink = time; + //self.angles_y = time * 110.1; +} + +void buff_Waypoint_Reset() +{ + WaypointSprite_Kill(self.buff_waypoint); + + if(self.buff_activetime) { buff_Waypoint_Spawn(self); } +} + +void buff_Reset() +{ + if(autocvar_g_buffs_randomize) + buff_NewType(self, self.buffs); + self.owner = world; + buff_SetCooldown(autocvar_g_buffs_cooldown_activate); + buff_Waypoint_Reset(); + self.buff_activetime_updated = FALSE; + + if(autocvar_g_buffs_random_location || (self.spawnflags & 1)) + buff_Respawn(self); +} + +void buff_Init(entity ent) +{ + if(!cvar("g_buffs")) { remove(self); return; } + + if(!teamplay && self.team) { self.team = 0; } + + entity oldself = self; + self = ent; + if(!self.buffs || buff_Available(self.buffs)) + buff_NewType(self, 0); + + self.classname = "item_buff"; + self.solid = SOLID_TRIGGER; + self.flags = FL_ITEM; + self.think = buff_Think; + self.touch = buff_Touch; + self.reset = buff_Reset; + self.nextthink = time + 0.1; + self.gravity = 1; + self.movetype = MOVETYPE_TOSS; + self.scale = 1; + self.skin = Buff_Skin(self.buffs); + self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW; + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; + //self.gravity = 100; + self.color = Buff_Color(self.buffs); + self.glowmod = ((self.team) ? Team_ColorRGB(self.team) + '0.1 0.1 0.1' : self.color); + buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime); + self.buff_active = !self.buff_activetime; + self.pflags = PFLAGS_FULLDYNAMIC; + + if(self.noalign) + self.movetype = MOVETYPE_NONE; // reset by random location + + setmodel(self, "models/relics/relic.md3"); + setsize(self, BUFF_MIN, BUFF_MAX); + + if(cvar("g_buffs_random_location") || (self.spawnflags & 1)) + buff_Respawn(self); + + self = oldself; +} + +void buff_Init_Compat(entity ent, float replacement) +{ + if(ent.spawnflags & 2) + ent.team = NUM_TEAM_1; + else if(ent.spawnflags & 4) + ent.team = NUM_TEAM_2; + + ent.buffs = replacement; + + buff_Init(ent); +} + +void buff_SpawnReplacement(entity ent, entity old) +{ + setorigin(ent, old.origin); + ent.angles = old.angles; + ent.noalign = old.noalign; + + buff_Init(ent); +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_SplitHealthArmor) +{ + if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't + + if(frag_target.buffs & BUFF_RESISTANCE) + { + vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage); + damage_take = v_x; + damage_save = v_y; + } + + return FALSE; +} + +void buff_Vengeance_DelayedDamage() +{ + if(self.enemy) + Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF_VENGEANCE, self.enemy.origin, '0 0 0'); + + remove(self); + return; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerDamage_Calculate) +{ + if(frag_deathtype == DEATH_BUFF_VENGEANCE) { return FALSE; } // oh no you don't + + if(frag_target.buffs & BUFF_SPEED) + if(frag_target != frag_attacker) + frag_damage *= autocvar_g_buffs_speed_damage_take; + + if(frag_target.buffs & BUFF_MEDIC) + if((frag_target.health - frag_damage) <= 0) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(frag_attacker) + if(random() <= autocvar_g_buffs_medic_survive_chance) + if(frag_target.health - autocvar_g_buffs_medic_survive_health > 0) // not if the final result would be less than 0, medic must get health + frag_damage = frag_target.health - autocvar_g_buffs_medic_survive_health; + + if(frag_target.buffs & BUFF_VENGEANCE) + if(frag_attacker) + if(frag_attacker != frag_target) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + { + entity dmgent = spawn(); + + dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; + dmgent.enemy = frag_attacker; + dmgent.owner = frag_target; + dmgent.think = buff_Vengeance_DelayedDamage; + dmgent.nextthink = time + 0.1; + } + + if(frag_target.buffs & BUFF_BASH) + if(frag_attacker != frag_target) + if(vlen(frag_force)) + frag_force = '0 0 0'; + + if(frag_attacker.buffs & BUFF_BASH) + if(vlen(frag_force)) + if(frag_attacker == frag_target) + frag_force *= autocvar_g_buffs_bash_force_self; + else + frag_force *= autocvar_g_buffs_bash_force; + + if(frag_attacker.buffs & BUFF_DISABILITY) + if(frag_target != frag_attacker) + frag_target.buff_disability_time = time + autocvar_g_buffs_disability_time; + + if(frag_attacker.buffs & BUFF_MEDIC) + if(SAME_TEAM(frag_attacker, frag_target)) + if(frag_attacker != frag_target) + { + frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage); + frag_damage = 0; + } + + // this... is ridiculous (TODO: fix!) + if(frag_attacker.buffs & BUFF_VAMPIRE) + if(!frag_target.vehicle) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(frag_target.deadflag == DEAD_NO) + if(IS_PLAYER(frag_target) || (frag_target.flags & FL_MONSTER)) + if(frag_attacker != frag_target) + if(!frag_target.freezetag_frozen) + if(frag_target.takedamage) + if(DIFF_TEAM(frag_attacker, frag_target)) + frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerSpawn) +{ + self.buffs = 0; + // reset timers here to prevent them continuing after re-spawn + self.buff_disability_time = 0; + self.buff_disability_effect_time = 0; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerPhysics) +{ + if(self.buffs & BUFF_SPEED) + { + self.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed; + self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed; + } + + if(time < self.buff_disability_time) + { + self.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed; + self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerJump) +{ + if(self.buffs & BUFF_JUMP) + player_jumpheight = autocvar_g_buffs_jump_height; + self.stat_jumpheight = player_jumpheight; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_MonsterMove) +{ + if(time < self.buff_disability_time) + { + monster_speed_walk *= autocvar_g_buffs_disability_speed; + monster_speed_run *= autocvar_g_buffs_disability_speed; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerDies) +{ + if(self.buffs) + { + Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs); + self.buffs = 0; + + if(self.buff_model) + { + remove(self.buff_model); + self.buff_model = world; + } + } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerUseKey) +{ + if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } + if(self.buffs) + { + Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, self.buffs); + Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs); + + self.buffs = 0; + sound(self, CH_TRIGGER, "relics/relic_effect.wav", VOL_BASE, ATTN_NORM); + return TRUE; + } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_RemovePlayer) +{ + if(self.buff_model) + { + remove(self.buff_model); + self.buff_model = world; + } + + // also reset timers here to prevent them continuing after spectating + self.buff_disability_time = 0; + self.buff_disability_effect_time = 0; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_CustomizeWaypoint) +{ + entity e = WaypointSprite_getviewentity(other); + + // if you have the invisibility powerup, sprites ALWAYS are restricted to your team + // but only apply this to real players, not to spectators + if((self.owner.flags & FL_CLIENT) && (self.owner.buffs & BUFF_INVISIBLE) && (e == other)) + if(DIFF_TEAM(self.owner, e)) + return TRUE; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_OnEntityPreSpawn) +{ + if(autocvar_g_buffs_replace_powerups) + switch(self.classname) + { + case "item_strength": + case "item_invincible": + { + entity e = spawn(); + buff_SpawnReplacement(e, self); + return TRUE; + } + } + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_WeaponRate) +{ + if(self.buffs & BUFF_SPEED) + weapon_rate *= autocvar_g_buffs_speed_rate; + + if(time < self.buff_disability_time) + weapon_rate *= autocvar_g_buffs_disability_rate; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerThink) +{ + if(gameover || self.deadflag != DEAD_NO) { return FALSE; } + + if(time < self.buff_disability_time) + if(time >= self.buff_disability_effect_time) + { + pointparticles(particleeffectnum("smoking"), self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1); + self.buff_disability_effect_time = time + 0.5; + } + + if(self.freezetag_frozen) + { + if(self.buffs) + { + Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, self.buffs); + self.buffs = 0; + } + } + + if((self.buffs & BUFF_INVISIBLE) && (self.oldbuffs & BUFF_INVISIBLE)) + if(self.alpha != autocvar_g_buffs_invisible_alpha) + self.alpha = autocvar_g_buffs_invisible_alpha; + + if(self.buffs != self.oldbuffs) + { + if(self.oldbuffs & BUFF_AMMO) + { + if(self.buff_ammo_prev_infitems) + self.items |= IT_UNLIMITED_WEAPON_AMMO; + else + self.items &= ~IT_UNLIMITED_WEAPON_AMMO; + } + else if(self.buffs & BUFF_AMMO) + { + self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO); + self.items |= IT_UNLIMITED_WEAPON_AMMO; + if(!self.ammo_shells) { self.ammo_shells = 20; } + if(!self.ammo_cells) { self.ammo_cells = 20; } + if(!self.ammo_rockets) { self.ammo_rockets = 20; } + if(!self.ammo_nails) { self.ammo_nails = 20; } + if(!self.ammo_fuel) { self.ammo_fuel = 20; } + } + + if(self.oldbuffs & BUFF_INVISIBLE) + { + if(time < self.strength_finished && g_minstagib) + self.alpha = autocvar_g_minstagib_invis_alpha; + else + self.alpha = self.buff_invisible_prev_alpha; + } + else if(self.buffs & BUFF_INVISIBLE) + { + if(time < self.strength_finished && g_minstagib) + self.buff_invisible_prev_alpha = default_player_alpha; + else + self.buff_invisible_prev_alpha = self.alpha; + self.alpha = autocvar_g_buffs_invisible_alpha; + } + + if(self.oldbuffs & BUFF_FLIGHT) + self.gravity = self.buff_flight_prev_gravity; + else if(self.buffs & BUFF_FLIGHT) + { + self.buff_flight_prev_gravity = self.gravity; + self.gravity = autocvar_g_buffs_flight_gravity; + } + + self.oldbuffs = self.buffs; + if(self.buffs) + { + if(!self.buff_model) + { + self.buff_model = spawn(); + setmodel(self.buff_model, "models/relics/relic.md3"); + setsize(self.buff_model, '0 0 -40', '0 0 40'); + setattachment(self.buff_model, self, ""); + setorigin(self.buff_model, '0 0 1' * (self.buff_model.maxs_z * 1)); + self.buff_model.owner = self; + self.buff_model.scale = 0.7; + self.buff_model.pflags = PFLAGS_FULLDYNAMIC; + self.buff_model.light_lev = 200; + self.buff_model.customizeentityforclient = buffs_BuffModel_Customize; + } + self.buff_model.color = Buff_Color(self.buffs); + self.buff_model.glowmod = ((self.buff_model.team) ? Team_ColorRGB(self.buff_model.team) + '0.1 0.1 0.1' : self.buff_model.color); + self.buff_model.skin = Buff_Skin(self.buffs); + + self.effects |= EF_NOSHADOW; + } + else + { + remove(self.buff_model); + self.buff_model = world; + + self.effects &= ~(EF_NOSHADOW); + } + } + + if(self.buff_model) + { + self.buff_model.effects = self.effects; + self.buff_model.effects |= EF_LOWPRECISION; + self.buff_model.effects = self.buff_model.effects & EFMASK_CHEAP; // eat performance + + self.buff_model.alpha = self.alpha; + } + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_SpectateCopy) +{ + self.buffs = other.buffs; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_VehicleEnter) +{ + vh_vehicle.buffs = vh_player.buffs; + vh_player.buffs = 0; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_VehicleExit) +{ + vh_player.buffs = vh_vehicle.buffs; + vh_vehicle.buffs = 0; + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_PlayerRegen) +{ + if(self.buffs & BUFF_MEDIC) + { + regen_mod_rot = autocvar_g_buffs_medic_rot; + regen_mod_limit = regen_mod_max = autocvar_g_buffs_medic_max; + regen_mod_regen = autocvar_g_buffs_medic_regen; + } + + if(self.buffs & BUFF_SPEED) + regen_mod_regen = autocvar_g_buffs_speed_regen; + + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":Buffs"); + return FALSE; +} + +MUTATOR_HOOKFUNCTION(buffs_BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Buffs"); + return FALSE; +} + +void buffs_DelayedInit() +{ + if(autocvar_g_buffs_spawn_count > 0) + if(find(world, classname, "item_buff") == world) + { + float i; + for(i = 0; i < autocvar_g_buffs_spawn_count; ++i) + { + entity e = spawn(); + e.spawnflags |= 1; // always randomize + e.velocity = randomvec() * 250; // this gets reset anyway if random location works + buff_Init(e); + } + } +} + +void buffs_Initialize() +{ + precache_model("models/relics/relic.md3"); + precache_sound("misc/strength_respawn.wav"); + precache_sound("misc/shield_respawn.wav"); + precache_sound("relics/relic_effect.wav"); + precache_sound("weapons/rocket_impact.wav"); + precache_sound("keepaway/respawn.wav"); + + addstat(STAT_BUFFS, AS_INT, buffs); + addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_jumpheight); + + InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET); +} + +MUTATOR_DEFINITION(mutator_buffs) +{ + MUTATOR_HOOK(PlayerDamage_SplitHealthArmor, buffs_PlayerDamage_SplitHealthArmor, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDamage_Calculate, buffs_PlayerDamage_Calculate, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, buffs_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPhysics, buffs_PlayerPhysics, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerJump, buffs_PlayerJump, CBC_ORDER_ANY); + MUTATOR_HOOK(MonsterMove, buffs_MonsterMove, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, buffs_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(VehicleEnter, buffs_VehicleEnter, CBC_ORDER_ANY); + MUTATOR_HOOK(VehicleExit, buffs_VehicleExit, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerRegen, buffs_PlayerRegen, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, buffs_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerUseKey, buffs_PlayerUseKey, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, buffs_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientDisconnect, buffs_RemovePlayer, CBC_ORDER_ANY); + MUTATOR_HOOK(OnEntityPreSpawn, buffs_OnEntityPreSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(CustomizeWaypoint, buffs_CustomizeWaypoint, CBC_ORDER_ANY); + MUTATOR_HOOK(WeaponRateFactor, buffs_WeaponRate, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerPreThink, buffs_PlayerThink, CBC_ORDER_ANY); + MUTATOR_HOOK(GetCvars, buffs_GetCvars, CBC_ORDER_ANY); + MUTATOR_HOOK(BuildMutatorsString, buffs_BuildMutatorsString, CBC_ORDER_ANY); + MUTATOR_HOOK(BuildMutatorsPrettyString, buffs_BuildMutatorsPrettyString, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + buffs_Initialize(); + } + + return FALSE; +} diff --git a/qcsrc/server/mutators/mutator_buffs.qh b/qcsrc/server/mutators/mutator_buffs.qh new file mode 100644 index 000000000..66540b876 --- /dev/null +++ b/qcsrc/server/mutators/mutator_buffs.qh @@ -0,0 +1,28 @@ +// buff specific variables \\ +// +// ammo +.float buff_ammo_prev_infitems; +// invisible +.float buff_invisible_prev_alpha; +// flight +.float buff_flight_prev_gravity; +// jump +.float stat_jumpheight; +const float STAT_MOVEVARS_JUMPVELOCITY = 250; // engine hack +// disability +.float buff_disability_time; +.float buff_disability_effect_time; + +// buff definitions +.float buff_active; +.float buff_activetime; +.float buff_activetime_updated; +.entity buff_waypoint; +.float oldbuffs; // for updating effects +.entity buff_model; // controls effects (TODO: make csqc) + +#define BUFF_MIN ('-16 -16 -20') +#define BUFF_MAX ('16 16 20') + +// client side options +.float cvar_cl_buffs_autoreplace; diff --git a/qcsrc/server/mutators/mutators.qh b/qcsrc/server/mutators/mutators.qh index ecab77c88..ab519e98f 100644 --- a/qcsrc/server/mutators/mutators.qh +++ b/qcsrc/server/mutators/mutators.qh @@ -29,5 +29,6 @@ MUTATOR_DECLARATION(mutator_multijump); MUTATOR_DECLARATION(mutator_melee_only); MUTATOR_DECLARATION(mutator_nades); MUTATOR_DECLARATION(mutator_campcheck); +MUTATOR_DECLARATION(mutator_buffs); MUTATOR_DECLARATION(sandbox); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 1ae22e202..812a1d390 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -13,8 +13,10 @@ sys-post.qh ../warpzonelib/server.qh ../common/constants.qh +../common/stats.qh ../common/teams.qh ../common/util.qh +../common/buffs.qh ../common/test.qh ../common/counting.qh ../common/items.qh @@ -50,6 +52,7 @@ mutators/gamemode_lms.qh mutators/gamemode_invasion.qh mutators/mutator_dodging.qh mutators/mutator_nades.qh +mutators/mutator_buffs.qh //// tZork Turrets //// tturrets/include/turrets_early.qh @@ -88,6 +91,8 @@ scores.qh spawnpoints.qh +mapvoting.qh + ipban.qh race.qh @@ -131,6 +136,8 @@ pathlib/pathlib.qh g_world.qc g_casings.qc +mapvoting.qc + t_jumppads.qc t_teleporters.qc @@ -214,6 +221,8 @@ target_music.qc ../common/items.qc +../common/buffs.qc + accuracy.qc ../csqcmodellib/sv_model.qc @@ -266,6 +275,7 @@ mutators/mutator_multijump.qc mutators/mutator_melee_only.qc mutators/mutator_nades.qc mutators/mutator_campcheck.qc +mutators/mutator_buffs.qc ../warpzonelib/anglestransform.qc ../warpzonelib/mathlib.qc diff --git a/qcsrc/server/t_quake3.qc b/qcsrc/server/t_quake3.qc index bb1128bd6..86a87c043 100644 --- a/qcsrc/server/t_quake3.qc +++ b/qcsrc/server/t_quake3.qc @@ -114,18 +114,24 @@ void spawnfunc_target_give() InitializeEntity(self, target_give_init, INITPRIO_FINDTARGET); } -//void spawnfunc_item_flight() /* not supported */ -//void spawnfunc_item_haste() /* not supported */ +//void spawnfunc_item_flight() /* handled by buffs mutator or jetpack */ +//void spawnfunc_item_haste() /* handled by buffs mutator */ //void spawnfunc_item_health() /* handled in t_quake.qc */ //void spawnfunc_item_health_large() /* handled in t_items.qc */ //void spawnfunc_item_health_small() /* handled in t_items.qc */ //void spawnfunc_item_health_mega() /* handled in t_items.qc */ -//void spawnfunc_item_invis() /* not supported */ -//void spawnfunc_item_regen() /* not supported */ +//void spawnfunc_item_invis() /* handled by buffs mutator */ +//void spawnfunc_item_regen() /* handled by buffs mutator */ // CTF spawnfuncs handled in mutators/gamemode_ctf.qc now -void spawnfunc_item_flight() { spawnfunc_item_jetpack(); } +void spawnfunc_item_flight() +{ + if(!cvar("g_buffs") || !cvar("g_buffs_flight")) + spawnfunc_item_jetpack(); + else + buff_Init_Compat(self, BUFF_FLIGHT); +} .float notteam; .float notsingle; diff --git a/scripts/.DS_Store b/scripts/.DS_Store new file mode 100644 index 000000000..bcac026e1 Binary files /dev/null and b/scripts/.DS_Store differ diff --git a/textures/.DS_Store b/textures/.DS_Store new file mode 100644 index 000000000..a499c0976 Binary files /dev/null and b/textures/.DS_Store differ diff --git a/xonotic-credits.txt b/xonotic-credits.txt index 9fe937716..2fcdf98ce 100644 --- a/xonotic-credits.txt +++ b/xonotic-credits.txt @@ -113,6 +113,7 @@ Oleh "BlaXpirit" Prypin Przemysław "atheros" Grzywacz Robert "ai" Kuroto The player with the unnecessarily long name +Mattia "Melanosuchus" Basaglia **Translators