From: Samual Lenks Date: Tue, 7 May 2013 05:14:10 +0000 (-0400) Subject: Merge remote-tracking branch 'origin/terencehill/ca_arena_mutators' X-Git-Tag: xonotic-v0.7.0~61 X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=58b8eafbf5e2ff9147477e115292246458c4c5eb;hp=55f87426a3f58e4161eae8a7e8523f165cb9da00 Merge remote-tracking branch 'origin/terencehill/ca_arena_mutators' --- diff --git a/_hud_descriptions.cfg b/_hud_descriptions.cfg index ea7552a2a..9c7201d0c 100644 --- a/_hud_descriptions.cfg +++ b/_hud_descriptions.cfg @@ -199,7 +199,9 @@ seta hud_panel_modicons_bg_color_team "" "override panel color with team color i seta hud_panel_modicons_bg_alpha "" "if set to something else than \"\" = override default panel background alpha" seta hud_panel_modicons_bg_border "" "if set to something else than \"\" = override default size of border around the background" seta hud_panel_modicons_bg_padding "" "if set to something else than \"\" = override default padding of contents from border" +seta hud_panel_modicons_ca_layout "" "2 possible layouts: 0) number of alive players; 1) icons and number of alive players" seta hud_panel_modicons_dom_layout "" "3 possible layouts: 0) only icons; 1) icons and percentage of average pps (points per second); 2) icons and average pps" +seta hud_panel_modicons_freezetag_layout "" "2 possible layouts: 0) number of alive players; 1) icons and number of alive players" seta hud_panel_pressedkeys "" "enable/disable this panel, 1 = show only when spectating other players, 2 = show always" seta hud_panel_pressedkeys_pos "" "position of this base of the panel" diff --git a/gamemodes.cfg b/gamemodes.cfg index af8d3e83d..6913ac666 100644 --- a/gamemodes.cfg +++ b/gamemodes.cfg @@ -142,6 +142,7 @@ set g_ft_weapon_stay 0 set g_arena 0 "Arena: many one-on-one rounds are played to find the winner" set g_arena_maxspawned 2 "maximum number of players to spawn at once (the rest is spectating, waiting for their turn)" set g_arena_roundbased 1 "if disabled, the next player will spawn as soon as someone dies" +set g_arena_round_timelimit 180 set g_arena_warmup 5 "time, newly spawned players have to prepare themselves in round based matches" @@ -161,6 +162,9 @@ set g_ca_spectate_enemies 0 "Allow spectating enemy player by dead player during set g_ca_warmup 10 "how long the players will have time to run around the map before the round starts" set g_ca_damage2score_multiplier 0.01 set g_ca_round_timelimit 180 +seta g_ca_teams_override 0 +set g_ca_teams 0 + // ================== @@ -268,13 +272,17 @@ set g_domination_point_glow 0 "domination point glow (warning, slow)" // freezetag // =========== set g_freezetag 0 "Freeze Tag: Freeze the opposing team(s) to win, unfreeze teammates by standing next to them" -seta g_freezetag_warmup 5 "Time players get to run around before the round starts" +set g_freezetag_warmup 5 "Time players get to run around before the round starts" seta g_freezetag_point_limit -1 "Freeze Tag point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" seta g_freezetag_point_leadlimit -1 "Freeze Tag point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" -seta g_freezetag_revive_speed 0.4 "Speed for reviving a frozen teammate" -seta g_freezetag_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range" -seta g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him" -seta g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with" +set g_freezetag_revive_speed 0.4 "Speed for reviving a frozen teammate" +set g_freezetag_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range" +set g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him" +set g_freezetag_round_timelimit 180 +set g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with" +set g_freezetag_frozen_maxtime 60 "frozen players will be automatically unfrozen after this time in seconds" +seta g_freezetag_teams_override 0 +set g_freezetag_teams 0 // ========== diff --git a/gfx/hud/default/player_pink.tga b/gfx/hud/default/player_pink.tga new file mode 100644 index 000000000..89a765921 Binary files /dev/null and b/gfx/hud/default/player_pink.tga differ diff --git a/gfx/hud/default/player_yellow.tga b/gfx/hud/default/player_yellow.tga new file mode 100644 index 000000000..8717d8f76 Binary files /dev/null and b/gfx/hud/default/player_yellow.tga differ diff --git a/hud_luminos.cfg b/hud_luminos.cfg index bb0c97e09..f99494a1c 100644 --- a/hud_luminos.cfg +++ b/hud_luminos.cfg @@ -197,7 +197,9 @@ seta hud_panel_modicons_bg_color_team "" seta hud_panel_modicons_bg_alpha "" seta hud_panel_modicons_bg_border "" seta hud_panel_modicons_bg_padding "0" +seta hud_panel_modicons_ca_layout "1" seta hud_panel_modicons_dom_layout "1" +seta hud_panel_modicons_freezetag_layout "1" seta hud_panel_pressedkeys 1 seta hud_panel_pressedkeys_pos "0.450000 0.720000" diff --git a/hud_luminos_minimal.cfg b/hud_luminos_minimal.cfg index 775ddaf8f..bfa0c0b3e 100644 --- a/hud_luminos_minimal.cfg +++ b/hud_luminos_minimal.cfg @@ -197,7 +197,9 @@ seta hud_panel_modicons_bg_color_team "" seta hud_panel_modicons_bg_alpha "" seta hud_panel_modicons_bg_border "" seta hud_panel_modicons_bg_padding "" +seta hud_panel_modicons_ca_layout "1" seta hud_panel_modicons_dom_layout "1" +seta hud_panel_modicons_freezetag_layout "1" seta hud_panel_pressedkeys 1 seta hud_panel_pressedkeys_pos "0.450000 0.650000" diff --git a/hud_luminos_minimal_xhair.cfg b/hud_luminos_minimal_xhair.cfg index b7a208dbd..27ca9ab80 100644 --- a/hud_luminos_minimal_xhair.cfg +++ b/hud_luminos_minimal_xhair.cfg @@ -197,7 +197,9 @@ seta hud_panel_modicons_bg_color_team "" seta hud_panel_modicons_bg_alpha "" seta hud_panel_modicons_bg_border "" seta hud_panel_modicons_bg_padding "" +seta hud_panel_modicons_ca_layout "1" seta hud_panel_modicons_dom_layout "1" +seta hud_panel_modicons_freezetag_layout "1" seta hud_panel_pressedkeys 1 seta hud_panel_pressedkeys_pos "0.450000 0.690000" diff --git a/hud_luminos_old.cfg b/hud_luminos_old.cfg index 2e718b288..3a489a04e 100644 --- a/hud_luminos_old.cfg +++ b/hud_luminos_old.cfg @@ -197,7 +197,9 @@ seta hud_panel_modicons_bg_color_team "" seta hud_panel_modicons_bg_alpha "" seta hud_panel_modicons_bg_border "" seta hud_panel_modicons_bg_padding "" +seta hud_panel_modicons_ca_layout "1" seta hud_panel_modicons_dom_layout "1" +seta hud_panel_modicons_freezetag_layout "1" seta hud_panel_pressedkeys 1 seta hud_panel_pressedkeys_pos "0.410000 0.710000" diff --git a/hud_nexuiz.cfg b/hud_nexuiz.cfg index d4e71d876..57053411d 100644 --- a/hud_nexuiz.cfg +++ b/hud_nexuiz.cfg @@ -197,7 +197,9 @@ seta hud_panel_modicons_bg_color_team "" seta hud_panel_modicons_bg_alpha "" seta hud_panel_modicons_bg_border "" seta hud_panel_modicons_bg_padding "" +seta hud_panel_modicons_ca_layout "1" seta hud_panel_modicons_dom_layout "1" +seta hud_panel_modicons_freezetag_layout "1" seta hud_panel_pressedkeys 1 seta hud_panel_pressedkeys_pos "0.440000 0.760000" diff --git a/qcsrc/client/View.qc b/qcsrc/client/View.qc index 547f90268..d27fd4d98 100644 --- a/qcsrc/client/View.qc +++ b/qcsrc/client/View.qc @@ -895,8 +895,8 @@ void CSQC_UpdateView(float w, float h) } } } - - if(autocvar_hud_damage) + + if(autocvar_hud_damage && !getstati(STAT_FROZEN)) { splash_size_x = max(vid_conwidth, vid_conheight); splash_size_y = max(vid_conwidth, vid_conheight); @@ -1080,7 +1080,7 @@ void CSQC_UpdateView(float w, float h) if(getstatf(STAT_REVIVE_PROGRESS)) { DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); - drawstring_aspect(eY * 0.64 * vid_conheight, "Revival progress", eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); + drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); } } diff --git a/qcsrc/client/announcer.qc b/qcsrc/client/announcer.qc index 0409aea8b..7a4ed9223 100644 --- a/qcsrc/client/announcer.qc +++ b/qcsrc/client/announcer.qc @@ -16,19 +16,34 @@ float announcer_5min; void Announcer_Countdown() { float starttime = getstatf(STAT_GAMESTARTTIME); + float roundstarttime = getstatf(STAT_ROUNDSTARTTIME); + if(roundstarttime == -1) + { + Local_Notification(MSG_CENTER, CENTER_COUNTDOWN_ROUNDSTOP); + remove(self); + return; + } + if(roundstarttime >= starttime) + starttime = roundstarttime; + if(starttime <= time && roundstarttime != starttime) // game start time has passed + announcer_5min = announcer_1min = FALSE; // reset maptime announcers now as well + float countdown = (starttime - time); float countdown_rounded = floor(0.5 + countdown); - + if(countdown <= 0) // countdown has finished, starttime is now { + Local_Notification(MSG_CENTER, CENTER_COUNTDOWN_BEGIN); Local_Notification(MSG_MULTI, MULTI_COUNTDOWN_BEGIN); - announcer_5min = announcer_1min = FALSE; // reset maptime announcers now as well remove(self); return; } else // countdown is still going { - Local_Notification(MSG_CENTER, CENTER_COUNTDOWN_GAMESTART, countdown_rounded); + if(roundstarttime == starttime) + Local_Notification(MSG_CENTER, CENTER_COUNTDOWN_ROUNDSTART, countdown_rounded); + else + Local_Notification(MSG_CENTER, CENTER_COUNTDOWN_GAMESTART, countdown_rounded); switch(countdown_rounded) { @@ -52,17 +67,24 @@ void Announcer_Countdown() void Announcer_Gamestart() { float startTime = getstatf(STAT_GAMESTARTTIME); - + float roundstarttime = getstatf(STAT_ROUNDSTARTTIME); + if(roundstarttime > startTime) + startTime = roundstarttime; + if(previous_game_starttime != startTime) { if((time + 5.0) < startTime) // if connecting to server while restart was active don't always play prepareforbattle Local_Notification(MSG_ANNCE, ANNCE_PREPARE); - + if(time < startTime) { - entity e; - e = spawn(); - e.think = Announcer_Countdown; + entity e = find(world, classname, "announcer_countdown"); + if not(e) + { + e = spawn(); + e.classname = "announcer_countdown"; + e.think = Announcer_Countdown; + } e.nextthink = startTime - floor(startTime - time); //synchronize nextthink to startTime } } diff --git a/qcsrc/client/autocvars.qh b/qcsrc/client/autocvars.qh index 6243a00ec..c13e2bf6e 100644 --- a/qcsrc/client/autocvars.qh +++ b/qcsrc/client/autocvars.qh @@ -253,7 +253,9 @@ float autocvar_hud_panel_healtharmor_text; float autocvar_hud_panel_infomessages; float autocvar_hud_panel_infomessages_flip; float autocvar_hud_panel_modicons; +float autocvar_hud_panel_modicons_ca_layout; float autocvar_hud_panel_modicons_dom_layout; +float autocvar_hud_panel_modicons_freezetag_layout; float autocvar_hud_panel_notify; float autocvar_hud_panel_notify_fadetime; float autocvar_hud_panel_notify_flip; diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index e49ac603a..ee44db91a 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -2633,32 +2633,100 @@ void HUD_Vote(void) float mod_active; // is there any active mod icon? -// Clan Arena HUD modicons -void HUD_Mod_CA(vector pos, vector mySize) +void DrawCAItem(vector myPos, vector mySize, float aspect_ratio, float layout, float i) { - mod_active = 1; // CA should never hide the mod icons panel - float redalive, bluealive; - redalive = getstati(STAT_REDALIVE); - bluealive = getstati(STAT_BLUEALIVE); + float stat; + string pic; + vector color; +#ifdef GMQCC + stat = -1; + pic = ""; + color = '0 0 0'; +#endif + switch(i) + { + case 0: + stat = getstati(STAT_REDALIVE); + pic = "player_red.tga"; + color = '1 0 0'; + break; + case 1: + stat = getstati(STAT_BLUEALIVE); + pic = "player_blue.tga"; + color = '0 0 1'; + break; + case 2: + stat = getstati(STAT_YELLOWALIVE); + pic = "player_yellow.tga"; + color = '1 1 0'; + break; + default: + case 3: + stat = getstati(STAT_PINKALIVE); + pic = "player_pink.tga"; + color = '1 0 1'; + break; + } - vector redpos, bluepos; - if(mySize_x > mySize_y) + if(mySize_x/mySize_y > aspect_ratio) { - redpos = pos; - bluepos = pos + eY * 0.5 * mySize_y; - drawpic_aspect_skin(redpos, "player_red.tga", 0.5 * mySize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - drawstring_aspect(redpos + eX * 0.5 * mySize_x, ftos(redalive), 0.5 * mySize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - drawpic_aspect_skin(bluepos, "player_blue.tga", 0.5 * mySize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - drawstring_aspect(bluepos + eX * 0.5 * mySize_x, ftos(bluealive), 0.5 * mySize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); + i = aspect_ratio * mySize_y; + myPos_x = myPos_x + (mySize_x - i) / 2; + mySize_x = i; } else { - redpos = pos; - bluepos = pos + eY * 0.5 * mySize_y; - drawpic_aspect_skin(redpos, "player_red.tga", eX * mySize_x + eY * 0.3 * mySize_y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - drawstring_aspect(redpos + eY * 0.3 * mySize_y, ftos(redalive), eX * mySize_x + eY * 0.2 * mySize_y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - drawpic_aspect_skin(bluepos, "player_blue.tga", eX * mySize_x + eY * 0.3 * mySize_y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); - drawstring_aspect(bluepos + eY * 0.3 * mySize_y, ftos(bluealive), eX * mySize_x + eY * 0.2 * mySize_y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); + i = 1/aspect_ratio * mySize_x; + myPos_y = myPos_y + (mySize_y - i) / 2; + mySize_y = i; + } + + if(layout) + { + drawpic_aspect_skin(myPos, pic, eX * 0.7 * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); + drawstring_aspect(myPos + eX * 0.7 * mySize_x, ftos(stat), eX * 0.3 * mySize_x + eY * mySize_y, color, panel_fg_alpha, DRAWFLAG_NORMAL); + } + else + drawstring_aspect(myPos, ftos(stat), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL); +} + +// Clan Arena and Freeze Tag HUD modicons +void HUD_Mod_CA(vector myPos, vector mySize) +{ + mod_active = 1; // required in each mod function that always shows something + entity tm; + float teams_count = 0; + for(tm = teams.sort_next; tm; tm = tm.sort_next) + if(tm.team != NUM_SPECTATOR) + ++teams_count; + + float layout; + if(gametype == MAPINFO_TYPE_CA) + layout = autocvar_hud_panel_modicons_ca_layout; + else //if(gametype == MAPINFO_TYPE_FREEZETAG) + layout = autocvar_hud_panel_modicons_freezetag_layout; + float rows, columns, aspect_ratio; + rows = mySize_y/mySize_x; + aspect_ratio = (layout) ? 2 : 1; + rows = bound(1, floor((sqrt((4 * aspect_ratio * teams_count + rows) * rows) + rows + 0.5) / 2), teams_count); + columns = ceil(teams_count/rows); + + int i; + float row = 0, column = 0; + vector pos, itemSize; + itemSize = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows); + for(i=0; i= rows) + { + row = 0; + ++column; + } } } @@ -3272,11 +3340,11 @@ void HUD_Mod_Dom(vector myPos, vector mySize) int i; float row = 0, column = 0; + vector pos, itemSize; + itemSize = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows); for(i=0; i f3) ? sprintf(CCR("^1[+%s]"), mmssss(f2 - f3)) : sprintf(CCR("^2[-%s]"), mmssss(f3 - f2)))) \ - ARG_CASE(ARG_CS, "kh_teams", notif_arg_kh_teams(f1, f2, f3, f4)) \ + ARG_CASE(ARG_CS, "missing_teams", notif_arg_missing_teams(f1, f2, f3, f4)) \ ARG_CASE(ARG_CS, "pass_key", ((((tmp_s = getcommandkey("pass", "+use")) != "pass") && !(strstrofs(tmp_s, "not bound", 0) >= 0)) ? sprintf(CCR(_(" ^F1(Press %s)")), tmp_s) : "")) \ ARG_CASE(ARG_CS, "frag_ping", notif_arg_frag_ping(TRUE, f2)) \ ARG_CASE(ARG_CS, "frag_stats", notif_arg_frag_stats(f2, f3, f4)) \ @@ -835,7 +844,7 @@ string notif_arg_frag_stats(float fhealth, float farmor, float fping) return sprintf(CCR(_("\n(^F4Dead^BG)%s")), notif_arg_frag_ping(FALSE, fping)); } -string notif_arg_kh_teams(float f1, float f2, float f3, float f4) +string notif_arg_missing_teams(float f1, float f2, float f3, float f4) { return sprintf("%s%s%s%s", (f1 ? diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_create_advanced.c b/qcsrc/menu/xonotic/dialog_multiplayer_create_advanced.c index 3fcabaaab..e6fffe093 100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer_create_advanced.c +++ b/qcsrc/menu/xonotic/dialog_multiplayer_create_advanced.c @@ -55,7 +55,7 @@ void XonoticAdvancedDialog_fill(entity me) me.TR(me); me.TDempty(me, 0.2); me.TD(me, 1, 1.2, makeXonoticTextLabel(0, _("Teams:"))); - me.TD(me, 1, 1.6, e = makeXonoticTextSlider("g_tdm_teams_override g_domination_teams_override g_keyhunt_teams_override")); + me.TD(me, 1, 1.6, e = makeXonoticTextSlider("g_tdm_teams_override g_domination_teams_override g_ca_teams_override g_freezetag_teams_override g_keyhunt_teams_override")); e.addValue(e, "Default", "0"); e.addValue(e, "2 teams", "2"); e.addValue(e, "3 teams", "3"); diff --git a/qcsrc/server/arena.qc b/qcsrc/server/arena.qc deleted file mode 100644 index 34b81bb62..000000000 --- a/qcsrc/server/arena.qc +++ /dev/null @@ -1,462 +0,0 @@ -float maxspawned; -float numspawned; -float arena_roundbased; -.float spawned; -.entity spawnqueue_next; -.entity spawnqueue_prev; -.float spawnqueue_in; -entity spawnqueue_first; -entity spawnqueue_last; -entity champion; -float warmup; -.float caplayer; - -void PutObserverInServer(); -void PutClientInServer(); - -float next_round; -float redalive, bluealive, yellowalive, pinkalive; -float totalalive; -.float redalive_stat, bluealive_stat, yellowalive_stat, pinkalive_stat; -float red_players, blue_players, yellow_players, pink_players; -float total_players; - -/** - * Resets the state of all clients, items, flags, keys, weapons, waypoints, ... of the map. - * Sets the 'warmup' global variable. - */ -void reset_map(float dorespawn) -{ - entity oldself; - oldself = self; - - if(g_arena && autocvar_g_arena_warmup) - warmup = time + autocvar_g_arena_warmup; - else if(g_ca) { - warmup = time + autocvar_g_ca_warmup; - allowed_to_spawn = 1; - } - else if(g_freezetag) - { - warmup = time + autocvar_g_freezetag_warmup; - } - - lms_lowest_lives = 999; - lms_next_place = player_count; - - race_ReadyRestart(); - - for(self = world; (self = nextent(self)); ) - if(clienttype(self) == CLIENTTYPE_NOTACLIENT) - { - if(self.reset) - { - self.reset(); - continue; - } - - if(self.team_saved) - self.team = self.team_saved; - - if(self.flags & FL_PROJECTILE) // remove any projectiles left - remove(self); - } - - // Waypoints and assault start come LAST - for(self = world; (self = nextent(self)); ) - if(clienttype(self) == CLIENTTYPE_NOTACLIENT) - { - if(self.reset2) - { - self.reset2(); - continue; - } - } - - // Moving the player reset code here since the player-reset depends - // on spawnpoint entities which have to be reset first --blub - if(dorespawn) - FOR_EACH_CLIENT(self) { - if(self.flags & FL_CLIENT) // reset all players - { - if(g_arena) - { - if(self.spawned) - PutClientInServer(); - else - PutObserverInServer(); - } - else if(g_ca && self.caplayer) { - self.classname = "player"; - PutClientInServer(); - } - else if(g_freezetag) - { - if(self.classname == "player") - PutClientInServer(); - } - else - { - /* - only reset players if a restart countdown is active - this can either be due to cvar sv_ready_restart_after_countdown having set - restart_mapalreadyrestarted to 1 after the countdown ended or when - sv_ready_restart_after_countdown is not used and countdown is still running - */ - if (restart_mapalreadyrestarted || (time < game_starttime)) - { - //NEW: changed behaviour so that it prevents that previous spectators/observers suddenly spawn as players - if (self.classname == "player") { - //PlayerScore_Clear(self); - if(g_lms) - PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()); - self.killcount = 0; - //stop the player from moving so that he stands still once he gets respawned - self.velocity = '0 0 0'; - self.avelocity = '0 0 0'; - self.movement = '0 0 0'; - PutClientInServer(); - } - } - } - } - } - - if(g_keyhunt) - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round+(game_starttime - time), kh_StartRound); - - if(g_arena) - if(champion && champion.classname == "player" && player_count > 1) - UpdateFrags(champion, +1); - - self = oldself; -} - -void Spawnqueue_Insert(entity e) -{ - if(e.spawnqueue_in) - return; - dprint(strcat("Into queue: ", e.netname, "\n")); - e.spawnqueue_in = TRUE; - e.spawnqueue_prev = spawnqueue_last; - e.spawnqueue_next = world; - if(spawnqueue_last) - spawnqueue_last.spawnqueue_next = e; - spawnqueue_last = e; - if(!spawnqueue_first) - spawnqueue_first = e; -} - -void Spawnqueue_Remove(entity e) -{ - if(!e.spawnqueue_in) - return; - dprint(strcat("Out of queue: ", e.netname, "\n")); - e.spawnqueue_in = FALSE; - if(e == spawnqueue_first) - spawnqueue_first = e.spawnqueue_next; - if(e == spawnqueue_last) - spawnqueue_last = e.spawnqueue_prev; - if(e.spawnqueue_prev) - e.spawnqueue_prev.spawnqueue_next = e.spawnqueue_next; - if(e.spawnqueue_next) - e.spawnqueue_next.spawnqueue_prev = e.spawnqueue_prev; - e.spawnqueue_next = world; - e.spawnqueue_prev = world; -} - -void Spawnqueue_Unmark(entity e) -{ - if(!e.spawned) - return; - e.spawned = FALSE; - numspawned = numspawned - 1; -} - -void Spawnqueue_Mark(entity e) -{ - if(e.spawned) - return; - e.spawned = TRUE; - numspawned = numspawned + 1; -} - -/** - * If roundbased arena game mode is active, it centerprints the texts for the - * player when player is waiting for the countdown to finish. - * Blocks the players movement while countdown is active. - * Unblocks the player once the countdown is over. - * - * Called in StartFrame() - */ -float roundStartTime_prev; // prevent networkspam -void Arena_Warmup() -{ - float f; - entity e; - - if(gameover) - { - if(warmup && time < warmup) - { - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_ARENA); - warmup = 0; - } - if(champion && g_arena) - { - FOR_EACH_REALCLIENT(e) - centerprint(e, strcat("The Champion is ", champion.netname)); - champion = world; - } - return; - } - if((!g_arena && !g_ca && !g_freezetag) || (g_arena && !arena_roundbased) || (time < game_starttime)) - return; - - f = ceil(warmup - time); - - if(inWarmupStage) - allowed_to_spawn = 1; - else if(!g_ca) - allowed_to_spawn = 0; - - if(time < warmup && !inWarmupStage) - { - if (g_ca) - allowed_to_spawn = 1; - if(champion && g_arena) - { - FOR_EACH_REALCLIENT(e) - centerprint(e, strcat("The Champion is ", champion.netname)); - } - - if(f != roundStartTime_prev) { - roundStartTime_prev = f; - if(g_ca && !(red_players && blue_players)) { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ARENA_NEEDPLAYER); - warmup = time + autocvar_g_ca_warmup; - } else { - if(f == 5) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_PREPARE); - else if(f == 3) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_3); - else if(f == 2) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_2); - else if(f == 1) - Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_NUM_1); - - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ARENA_ROUNDSTART, f); - } - } - - if (g_arena) { - FOR_EACH_CLIENT(e) - { - if(e.spawned && e.classname == "player") - e.player_blocked = 1; - } - } - } - else if(f > -1 && f != roundStartTime_prev) - { - roundStartTime_prev = f; - if(g_ca) { - if(red_players && blue_players) - allowed_to_spawn = 0; - else - reset_map(TRUE); - } else { - Send_Notification(NOTIF_ALL, world, MSG_MULTI, MULTI_ARENA_BEGIN); - } - - if(g_arena) { - FOR_EACH_CLIENT(e) - { - if(e.player_blocked) - e.player_blocked = 0; - } - } - } - - // clear champion to avoid centerprinting again the champion msg - if (champion) - champion = world; -} - -void count_players() -{ - // count amount of players in each team - total_players = red_players = blue_players = yellow_players = pink_players = 0; - FOR_EACH_PLAYER(self) { - if (self.team == NUM_TEAM_1) - { - red_players += 1; - total_players += 1; - } - else if (self.team == NUM_TEAM_2) - { - blue_players += 1; - total_players += 1; - } - else if (self.team == NUM_TEAM_3) - { - yellow_players += 1; - total_players += 1; - } - else if (self.team == NUM_TEAM_4) - { - pink_players += 1; - total_players += 1; - } - } -} - -void count_alive_players() -{ - totalalive = redalive = bluealive = yellowalive = pinkalive = 0; - if(g_ca) - { - FOR_EACH_PLAYER(self) { - if (self.team == NUM_TEAM_1 && self.health >= 1) - { - redalive += 1; - totalalive += 1; - } - else if (self.team == NUM_TEAM_2 && self.health >= 1) - { - bluealive += 1; - totalalive += 1; - } - } - FOR_EACH_REALCLIENT(self) { - self.redalive_stat = redalive; - self.bluealive_stat = bluealive; - } - } - else if(g_freezetag) - { - // count amount of alive players in each team - FOR_EACH_PLAYER(self) { - if (self.team == NUM_TEAM_1 && self.freezetag_frozen == 0 && self.health >= 1) - { - redalive += 1; - totalalive += 1; - } - else if (self.team == NUM_TEAM_2 && self.freezetag_frozen == 0 && self.health >= 1) - { - bluealive += 1; - totalalive += 1; - } - else if (self.team == NUM_TEAM_3 && self.freezetag_frozen == 0 && self.health >= 1) - { - yellowalive += 1; - totalalive += 1; - } - else if (self.team == NUM_TEAM_4 && self.freezetag_frozen == 0 && self.health >= 1) - { - pinkalive += 1; - totalalive += 1; - } - } - FOR_EACH_REALCLIENT(self) { - self.redalive_stat = redalive; - self.bluealive_stat = bluealive; - self.yellowalive_stat = yellowalive; - self.pinkalive_stat = pinkalive; - } - } - -} - -/** - * This function finds out whether an arena round is over 1 player is left. - * It determines the last player who's still alive and saves it's entity reference - * in the global variable 'champion'. Then the new enemy/enemies are put into the server. - * - * Gets called in StartFrame() - */ -void Spawnqueue_Check() -{ - if(warmup == 0 && g_ca && !inWarmupStage) - { - if(red_players || blue_players) - reset_map(TRUE); - return; - } - if(time < warmup + 1 || inWarmupStage || intermission_running) - return; - - if(g_ca) { - if(allowed_to_spawn) // round is not started yet - return; - if(!next_round) { - if(!(redalive && bluealive)) { - // every player of (at least) one team is dead, round ends here - if(redalive) { - play2all("ctf/red_capture.wav"); - FOR_EACH_CLIENT(self) centerprint(self, "^1RED ^7team wins the round"); - TeamScore_AddToTeam(NUM_TEAM_1, ST_SCORE, +1); - } - else if(bluealive) { - play2all("ctf/blue_capture.wav"); - FOR_EACH_CLIENT(self) centerprint(self, "^4BLUE ^7team wins the round"); - TeamScore_AddToTeam(NUM_TEAM_2, ST_SCORE, +1); - } - else - FOR_EACH_CLIENT(self) centerprint(self, "^7Round tied"); - next_round = -1; - } - else if(time - warmup > autocvar_g_ca_round_timelimit) { - FOR_EACH_CLIENT(self) centerprint(self, "^7Round tied"); - next_round = time + 5; - } - } - else if(next_round == -1) { - // wait for killed players to be put as spectators - if(!(red_players && blue_players)) - next_round = time + 5; - } - else if((next_round > 0 && next_round < time)) - { - next_round = 0; - reset_map(TRUE); - } - } else if(g_freezetag) { - if((next_round && next_round < time)) - { - next_round = 0; - reset_map(TRUE); - } - } else { // arena - //extend next_round if it isn't set yet and only 1 player is spawned - if(!next_round) - if(numspawned < 2) - next_round = time + 3; - - if(!arena_roundbased || (next_round && next_round < time && player_count > 1)) - { - next_round = 0; - - if(arena_roundbased) - { - champion = find(world, classname, "player"); - while(champion && champion.deadflag) - champion = find(champion, classname, "player"); - reset_map(TRUE); - } - - while(numspawned < maxspawned && spawnqueue_first) - { - self = spawnqueue_first; - - bprint ("^4", self.netname, "^4 is the next challenger\n"); - - Spawnqueue_Remove(self); - Spawnqueue_Mark(self); - - self.classname = "player"; - PutClientInServer(); - } - } - } -} diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 15ad76c87..ad8157a9d 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -75,6 +75,7 @@ float autocvar_g_arena_maxspawned; float autocvar_g_arena_point_leadlimit; float autocvar_g_arena_point_limit; float autocvar_g_arena_roundbased; +float autocvar_g_arena_round_timelimit; float autocvar_g_arena_warmup; float autocvar_g_assault; float autocvar_g_balance_armor_blockpercent; @@ -709,6 +710,8 @@ float autocvar_g_ca_point_leadlimit; float autocvar_g_ca_point_limit; float autocvar_g_ca_round_timelimit; float autocvar_g_ca_spectate_enemies; +float autocvar_g_ca_teams; +float autocvar_g_ca_teams_override; float autocvar_g_ca_warmup; float autocvar_g_campaign; #define autocvar_g_campaign_forceteam cvar("g_campaign_forceteam") @@ -814,11 +817,15 @@ string autocvar_g_forced_team_pink; string autocvar_g_forced_team_red; string autocvar_g_forced_team_yellow; float autocvar_g_freezetag_frozen_force; +float autocvar_g_freezetag_frozen_maxtime; float autocvar_g_freezetag_point_leadlimit; float autocvar_g_freezetag_point_limit; float autocvar_g_freezetag_revive_extra_size; float autocvar_g_freezetag_revive_speed; float autocvar_g_freezetag_revive_clearspeed; +float autocvar_g_freezetag_round_timelimit; +float autocvar_g_freezetag_teams; +float autocvar_g_freezetag_teams_override; float autocvar_g_freezetag_warmup; #define autocvar_g_friendlyfire cvar("g_friendlyfire") #define autocvar_g_friendlyfire_virtual cvar("g_friendlyfire_virtual") @@ -967,7 +974,6 @@ float autocvar_g_spawn_useallspawns; float autocvar_g_spawnpoints_auto_move_out_of_solid; #define autocvar_g_spawnshieldtime cvar("g_spawnshieldtime") float autocvar_g_spawnsound; -float autocvar_g_start_delay; #define autocvar_g_start_weapon_laser cvar("g_start_weapon_laser") float autocvar_g_tdm_team_spawns; float autocvar_g_tdm_teams; diff --git a/qcsrc/server/bot/aim.qc b/qcsrc/server/bot/aim.qc index 3bff21ecf..cb42aa5c2 100644 --- a/qcsrc/server/bot/aim.qc +++ b/qcsrc/server/bot/aim.qc @@ -111,9 +111,8 @@ float bot_shouldattack(entity e) return FALSE; } - if(g_freezetag) - if(e.freezetag_frozen) - return FALSE; + if(e.freezetag_frozen) + return FALSE; // If neither player has ball then don't attack unless the ball is on the // ground. diff --git a/qcsrc/server/bot/bot.qc b/qcsrc/server/bot/bot.qc index d87c72695..f6e7f6f1b 100644 --- a/qcsrc/server/bot/bot.qc +++ b/qcsrc/server/bot/bot.qc @@ -551,7 +551,7 @@ float bot_fixcount() FOR_EACH_REALCLIENT(head) { - if(head.classname == "player" || g_lms || g_arena || g_ca) + if(head.classname == "player" || g_lms || g_arena || head.caplayer == 1) ++activerealplayers; ++realplayers; } diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 7f16dfd74..5d29bf7e1 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -277,7 +277,7 @@ entity SelectSpawnPoint (float anypoint) else { float mindist; - if (arena_roundbased && !g_ca) + if (g_arena && arena_roundbased) mindist = 800; else mindist = 100; @@ -385,6 +385,24 @@ void PutObserverInServer (void) WriteEntity(MSG_ONE, self); } + if(g_lms) + { + // Only if the player cannot play at all + if(PlayerScore_Add(self, SP_LMS_RANK, 0) == 666) + self.frags = FRAGS_SPECTATOR; + else + self.frags = FRAGS_LMS_LOSER; + } + else if((g_race && g_race_qualifying) || g_cts) + { + if(PlayerScore_Add(self, SP_RACE_FASTEST, 0)) + self.frags = FRAGS_LMS_LOSER; + else + self.frags = FRAGS_SPECTATOR; + } + else + self.frags = FRAGS_SPECTATOR; + MUTATOR_CALLHOOK(MakePlayerObserver); minstagib_stop_countdown(self); @@ -443,7 +461,9 @@ void PutObserverInServer (void) self.pauseregen_finished = 0; self.damageforcescale = 0; self.death_time = 0; + self.respawn_flags = 0; self.respawn_time = 0; + self.stat_respawn_time = 0; self.alpha = 0; self.scale = 0; self.fade_time = 0; @@ -488,45 +508,6 @@ void PutObserverInServer (void) self.punchvector = '0 0 0'; self.oldvelocity = self.velocity; self.fire_endtime = -1; - - if(g_arena) - { - if(self.version_mismatch) - { - self.frags = FRAGS_SPECTATOR; - Spawnqueue_Unmark(self); - Spawnqueue_Remove(self); - } - else - { - self.frags = FRAGS_LMS_LOSER; - Spawnqueue_Insert(self); - } - } - else if(g_lms) - { - // Only if the player cannot play at all - if(PlayerScore_Add(self, SP_LMS_RANK, 0) == 666) - self.frags = FRAGS_SPECTATOR; - else - self.frags = FRAGS_LMS_LOSER; - } - else if(g_ca) - { - if(self.caplayer) - self.frags = FRAGS_LMS_LOSER; - else - self.frags = FRAGS_SPECTATOR; - } - else if((g_race && g_race_qualifying) || g_cts) - { - if(PlayerScore_Add(self, SP_RACE_FASTEST, 0)) - self.frags = FRAGS_LMS_LOSER; - else - self.frags = FRAGS_SPECTATOR; - } - else - self.frags = FRAGS_SPECTATOR; } .float model_randomizer; @@ -622,11 +603,7 @@ Called when a client spawns in the server void PutClientInServer (void) { if(clienttype(self) == CLIENTTYPE_BOT) - { self.classname = "player"; - if(g_ca) - self.caplayer = 1; - } else if(clienttype(self) == CLIENTTYPE_REAL) { msg_entity = self; @@ -645,13 +622,12 @@ void PutClientInServer (void) self.classname = "observer"; } - if((g_arena && !self.spawned) || (g_ca && !allowed_to_spawn)) - self.classname = "observer"; + MUTATOR_CALLHOOK(PutClientInServer); if(gameover) self.classname = "observer"; - if(self.classname == "player" && (!g_ca || (g_ca && allowed_to_spawn))) { + if(self.classname == "player") { entity spot, oldself; float j; @@ -757,7 +733,9 @@ void PutClientInServer (void) } self.damageforcescale = 2; self.death_time = 0; + self.respawn_flags = 0; self.respawn_time = 0; + self.stat_respawn_time = 0; self.scale = 0; self.fade_time = 0; self.pain_frame = 0; @@ -809,14 +787,6 @@ void PutClientInServer (void) self.lastteleporttime = time; // prevent insane speeds due to changing origin self.hud = HUD_NORMAL; - if(g_arena) - { - Spawnqueue_Remove(self); - Spawnqueue_Mark(self); - } - else if(g_ca) - self.caplayer = 1; - self.event_damage = PlayerDamage; self.bot_attack = TRUE; @@ -1047,14 +1017,13 @@ void ClientKill_Now_TeamChange() } else if(self.killindicator_teamchange == -2) { - if(g_ca) - self.caplayer = 0; if(blockSpectators) Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime); PutObserverInServer(); } else SV_ChangeTeam(self.killindicator_teamchange - 1); + self.killindicator_teamchange = 0; } void ClientKill_Now() @@ -1230,19 +1199,11 @@ void ClientKill_TeamChange (float targetteam) // 0 = don't change, -1 = auto, -2 void ClientKill (void) { - if (gameover) - return; + if(gameover) return; + if(self.player_blocked) return; + if(self.freezetag_frozen) return; - if((g_arena || g_ca) && ((champion && champion.classname == "player" && player_count > 1) || player_count == 1)) // don't allow a kill in this case either - { - // do nothing - } - else if(self.freezetag_frozen) - { - // do nothing - } - else - ClientKill_TeamChange(0); + ClientKill_TeamChange(0); } void CTS_ClientKill (entity e) // silent version of ClientKill, used when player finishes a CTS run. Useful to prevent cheating by running back to the start line and starting out with more speed @@ -1472,13 +1433,6 @@ void ClientConnect (void) else stuffcmd(self, "set _teams_available 0\n"); - if(g_arena || g_ca) - { - self.classname = "observer"; - if(g_arena) - Spawnqueue_Insert(self); - } - attach_entcs(); bot_relinkplayerlist(); @@ -1552,13 +1506,11 @@ void ClientConnect (void) CSQCMODEL_AUTOINIT(); self.model_randomizer = random(); - - if(clienttype(self) != CLIENTTYPE_REAL) - return; - - sv_notice_join(); - - MUTATOR_CALLHOOK(ClientConnect); + + if(clienttype(self) == CLIENTTYPE_REAL) + sv_notice_join(); + + MUTATOR_CALLHOOK(ClientConnect); } /* ============= @@ -1625,12 +1577,6 @@ void ClientDisconnect (void) bot_relinkplayerlist(); - if(g_arena) - { - Spawnqueue_Unmark(self); - Spawnqueue_Remove(self); - } - accuracy_free(self); ClientData_Detach(); PlayerScore_Detach(self); @@ -1988,7 +1934,7 @@ void player_regen (void) limita = limita * limit_mod; //limitf = limitf * limit_mod; - if(g_lms && g_ca) + if(g_ca) rot_mod = 0; if (!g_minstagib && !g_ca && (!g_lms || autocvar_g_lms_regenerate)) @@ -2113,7 +2059,6 @@ void SpectateCopy(entity spectatee) { self.dmg_inflictor = spectatee.dmg_inflictor; self.v_angle = spectatee.v_angle; self.angles = spectatee.v_angle; - self.stat_respawn_time = spectatee.stat_respawn_time; if(!self.BUTTON_USE) self.fixangle = TRUE; setorigin(self, spectatee.origin); @@ -2264,6 +2209,8 @@ void ShowRespawnCountdown() void LeaveSpectatorMode() { + if(self.caplayer) + return; if(nJoinAllowed(self)) { if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (self.wasplayer && autocvar_g_changeteam_banned) || self.team_forced > 0) @@ -2282,7 +2229,8 @@ void LeaveSpectatorMode() if(IS_PLAYER(self)) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_PLAY, self.netname); } } - else if not(g_ca && self.caplayer) { stuffcmd(self, "menu_showteamselect\n"); } + else + stuffcmd(self, "menu_showteamselect\n"); } else { @@ -2322,8 +2270,9 @@ float nJoinAllowed(entity ignore) { return maxclients - totalClients; float currentlyPlaying = 0; - FOR_EACH_REALPLAYER(e) - currentlyPlaying += 1; + FOR_EACH_REALCLIENT(e) + if(e.classname == "player" || e.caplayer == 1) + currentlyPlaying += 1; if(currentlyPlaying < autocvar_g_maxplayers) return min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying); @@ -2490,14 +2439,10 @@ void PlayerPreThink (void) WarpZone_PlayerPhysics_FixVAngle(); self.stat_game_starttime = game_starttime; + self.stat_round_starttime = round_starttime; self.stat_allow_oldnexbeam = autocvar_g_allow_oldnexbeam; self.stat_leadlimit = autocvar_leadlimit; - if(g_arena || (g_ca && !allowed_to_spawn)) - self.stat_respawn_time = 0; - else - self.stat_respawn_time = self.respawn_time; - if(frametime) { // physics frames: update anticheat stuff @@ -2622,25 +2567,26 @@ void PlayerPreThink (void) if (self.deadflag != DEAD_NO) { - float button_pressed, force_respawn; if(self.personal && g_race_qualifying) { if(time > self.respawn_time) { self.respawn_time = time + 1; // only retry once a second + self.stat_respawn_time = self.respawn_time; respawn(); self.impulse = 141; } } else { + float button_pressed; if(frametime) player_anim(); button_pressed = (self.BUTTON_ATCK || self.BUTTON_JUMP || self.BUTTON_ATCK2 || self.BUTTON_HOOK || self.BUTTON_USE); - force_respawn = (g_lms || g_ca || g_cts || autocvar_g_forced_respawn); + if (self.deadflag == DEAD_DYING) { - if(force_respawn) + if(self.respawn_flags & RESPAWN_FORCE) self.deadflag = DEAD_RESPAWNING; else if(!button_pressed) self.deadflag = DEAD_DEAD; @@ -2663,7 +2609,13 @@ void PlayerPreThink (void) respawn(); } } + ShowRespawnCountdown(); + + if(self.respawn_flags & RESPAWN_SILENT) + self.stat_respawn_time = 0; + else + self.stat_respawn_time = self.respawn_time; } // if respawning, invert stat_respawn_time to indicate this, the client translates it diff --git a/qcsrc/server/cl_impulse.qc b/qcsrc/server/cl_impulse.qc index 529567a3b..d09c0704f 100644 --- a/qcsrc/server/cl_impulse.qc +++ b/qcsrc/server/cl_impulse.qc @@ -46,6 +46,10 @@ void ImpulseCommands (void) return; self.impulse = 0; + // forbid impulses when not in round time + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return; + if (timeout_status == TIMEOUT_ACTIVE) //don't allow any impulses while the game is paused return; diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index 944db7e5b..95aeced31 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -338,7 +338,6 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float } void ClientKill_Now_TeamChange(); -void freezetag_CheckWinner(); void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { @@ -347,9 +346,6 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht float valid_damage_for_weaponstats; float excess; - if((g_arena && numspawned < 2) || (g_ca && allowed_to_spawn) && !inWarmupStage) - return; - dh = max(self.health, 0); da = max(self.armorvalue, 0); @@ -579,14 +575,6 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht } } - if(!g_freezetag) - { - // become fully visible - self.alpha = default_player_alpha; - // throw a weapon - SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon); - } - // print an obituary message Obituary (attacker, inflictor, self, deathtype); race_PreDie(); @@ -598,18 +586,12 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht if(accuracy_isgooddamage(attacker, self)) attacker.accuracy.(accuracy_frags[w-1]) += 1; - if(deathtype == DEATH_HURTTRIGGER && g_freezetag) - { - PutClientInServer(); - count_alive_players(); // re-count players - freezetag_CheckWinner(); - return; - } - frag_attacker = attacker; frag_inflictor = inflictor; frag_target = self; + frag_deathtype = deathtype; MUTATOR_CALLHOOK(PlayerDies); + weapon_action(self.weapon, WR_PLAYERDEATH); RemoveGrapplingHook(self); @@ -626,19 +608,23 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht //WriteAngle (MSG_ONE, 80); } - if(defer_ClientKill_Now_TeamChange) // TODO does this work with FreezeTag? - ClientKill_Now_TeamChange(); - - if(g_arena) - Spawnqueue_Unmark(self); + if(defer_ClientKill_Now_TeamChange) + ClientKill_Now_TeamChange(); // can turn player into spectator - if(g_freezetag) + // player could have been miraculously resuscitated ;) + // e.g. players in freezetag get frozen, they don't really die + if(self.health >= 1 || self.classname != "player") return; // when we get here, player actually dies - // clear waypoints (do this AFTER FreezeTag) + + // clear waypoints WaypointSprite_PlayerDead(); + // throw a weapon + SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon); + // become fully visible + self.alpha = default_player_alpha; // make the corpse upright (not tilted) self.angles_x = 0; self.angles_z = 0; @@ -677,6 +663,10 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht self.respawn_countdown = 10; // first number to count down from is 10 else self.respawn_countdown = -1; // do not count down + + if(g_lms || g_cts || autocvar_g_forced_respawn) + self.respawn_flags = self.respawn_flags | RESPAWN_FORCE; + self.death_time = time; if (random() < 0.5) animdecide_setstate(self, self.anim_state | ANIMSTATE_DEAD1, TRUE); diff --git a/qcsrc/server/cl_weapons.qc b/qcsrc/server/cl_weapons.qc index 2a1297dc2..57b1cf4f4 100644 --- a/qcsrc/server/cl_weapons.qc +++ b/qcsrc/server/cl_weapons.qc @@ -303,8 +303,6 @@ float W_IsWeaponThrowable(float w) return 0; if (g_lms) return 0; - if (g_ca) - return 0; if (g_cts) return 0; if (g_nexball && w == WEP_GRENADE_LAUNCHER) @@ -354,7 +352,19 @@ void W_ThrowWeapon(vector velo, vector delta, float doreduce) Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_WEAPON_DROP, a, w); } -// Bringed back weapon frame +float forbidWeaponUse() +{ + if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown) + return 1; + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return 1; + if(self.player_blocked) + return 1; + if(self.freezetag_frozen) + return 1; + return 0; +} + void W_WeaponFrame() { vector fo, ri, up; @@ -362,15 +372,16 @@ void W_WeaponFrame() if (frametime) self.weapon_frametime = frametime; - if(((arena_roundbased || g_ca || g_freezetag) && time < warmup) || ((time < game_starttime) && !autocvar_sv_ready_restart_after_countdown)) - return; - - if(self.freezetag_frozen == 1) - return; - if (!self.weaponentity || self.health < 1) return; // Dead player can't use weapons and injure impulse commands + if(forbidWeaponUse()) + if(self.weaponentity.state != WS_CLEAR) + { + w_ready(); + return; + } + if(!self.switchweapon) { self.weapon = 0; diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index d363de527..e22399f4a 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -154,9 +154,8 @@ void ClientCommand_join(float request) { if(nJoinAllowed(self)) { - if(g_ca) { self.caplayer = 1; } if(autocvar_g_campaign) { campaign_bots_may_start = 1; } - + self.classname = "player"; PlayerScore_Clear(self); Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_PREVENT_JOIN); @@ -195,6 +194,8 @@ void ClientCommand_ready(float request) // todo: anti-spam for toggling readynes { if(!readyrestart_happened || autocvar_sv_ready_restart_repeatable) { + if(time < game_starttime) // game is already restarting + return; if (self.ready) // toggle { self.ready = FALSE; @@ -413,10 +414,10 @@ void ClientCommand_spectate(float request) if(self.classname == "player" && autocvar_sv_spectate == 1) ClientKill_TeamChange(-2); // observe - + // in CA, allow a dead player to move to spectators (without that, caplayer!=0 will be moved back to the player list) // note: if arena game mode is ever done properly, this needs to be removed. - if(g_ca && self.caplayer && (self.classname == "spectator" || self.classname == "observer")) + if(self.caplayer && (self.classname == "spectator" || self.classname == "observer")) { sprint(self, "WARNING: you will spectate in the next round.\n"); self.caplayer = 0; diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 5e7810a4a..3e564e39a 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -319,6 +319,84 @@ void VoteThink() // Game logic for warmup // ======================= +// Resets the state of all clients, items, weapons, waypoints, ... of the map. +void reset_map(float dorespawn) +{ + entity oldself; + oldself = self; + + if(time <= game_starttime && round_handler_IsActive()) + round_handler_Reset(game_starttime); + + if(g_race || g_cts) + race_ReadyRestart(); + else MUTATOR_CALLHOOK(reset_map_global); + + lms_lowest_lives = 999; + lms_next_place = player_count; + + for(self = world; (self = nextent(self)); ) + if(clienttype(self) == CLIENTTYPE_NOTACLIENT) + { + if(self.reset) + { + self.reset(); + continue; + } + + if(self.team_saved) + self.team = self.team_saved; + + if(self.flags & FL_PROJECTILE) // remove any projectiles left + remove(self); + } + + // Waypoints and assault start come LAST + for(self = world; (self = nextent(self)); ) + if(clienttype(self) == CLIENTTYPE_NOTACLIENT) + { + if(self.reset2) + { + self.reset2(); + continue; + } + } + + // Moving the player reset code here since the player-reset depends + // on spawnpoint entities which have to be reset first --blub + if(dorespawn) + if(!MUTATOR_CALLHOOK(reset_map_players)) + FOR_EACH_CLIENT(self) // reset all players + { + /* + only reset players if a restart countdown is active + this can either be due to cvar sv_ready_restart_after_countdown having set + restart_mapalreadyrestarted to 1 after the countdown ended or when + sv_ready_restart_after_countdown is not used and countdown is still running + */ + if (restart_mapalreadyrestarted || (time < game_starttime)) + { + //NEW: changed behaviour so that it prevents that previous spectators/observers suddenly spawn as players + if (IS_PLAYER(self)) { + //PlayerScore_Clear(self); + if(g_lms) + PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()); + self.killcount = 0; + //stop the player from moving so that he stands still once he gets respawned + self.velocity = '0 0 0'; + self.avelocity = '0 0 0'; + self.movement = '0 0 0'; + PutClientInServer(); + } + } + } + + if(g_keyhunt) + kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round + (game_starttime - time), kh_StartRound); + + self = oldself; +} + // Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set) void ReadyRestart_think() { @@ -345,8 +423,7 @@ void ReadyRestart_force() checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0; readyrestart_happened = 1; - game_starttime = time; - if(!g_ca && !g_arena) { game_starttime += RESTART_COUNTDOWN; } + game_starttime = time + RESTART_COUNTDOWN; // clear player attributes FOR_EACH_CLIENT(tmp_player) @@ -362,19 +439,19 @@ void ReadyRestart_force() inWarmupStage = 0; // once the game is restarted the game is in match stage // reset the .ready status of all players (also spectators) - FOR_EACH_CLIENTSLOT(tmp_player) { tmp_player.ready = 0; } + FOR_EACH_REALCLIENT(tmp_player) { tmp_player.ready = 0; } readycount = 0; Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client // lock teams with lockonrestart - if(autocvar_teamplay_lockonrestart && teamplay) + if(autocvar_teamplay_lockonrestart && teamplay) { lockteams = 1; bprint("^1The teams are now locked.\n"); } //initiate the restart-countdown-announcer entity - if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena) + if(autocvar_sv_ready_restart_after_countdown) { restart_timer = spawn(); restart_timer.think = ReadyRestart_think; @@ -414,10 +491,13 @@ void ReadyCount() float ready_needed_factor, ready_needed_count; float t_ready = 0, t_players = 0; - FOR_EACH_REALPLAYER(tmp_player) + FOR_EACH_REALCLIENT(tmp_player) { - ++t_players; - if(tmp_player.ready) { ++t_ready; } + if(IS_PLAYER(tmp_player) || tmp_player.caplayer == 1) + { + ++t_players; + if(tmp_player.ready) { ++t_ready; } + } } readycount = t_ready; diff --git a/qcsrc/server/command/vote.qh b/qcsrc/server/command/vote.qh index 748b7ce6d..1225b6be5 100644 --- a/qcsrc/server/command/vote.qh +++ b/qcsrc/server/command/vote.qh @@ -47,4 +47,5 @@ float readycount; // amount of players who are ready float readyrestart_happened; // keeps track of whether a restart has already happened float restart_mapalreadyrestarted; // bool, indicates whether reset_map() was already executed .float ready; // flag for if a player is ready +void reset_map(float dorespawn); void ReadyCount(); \ No newline at end of file diff --git a/qcsrc/server/constants.qh b/qcsrc/server/constants.qh index 522bb2ffc..cb3c34fdc 100644 --- a/qcsrc/server/constants.qh +++ b/qcsrc/server/constants.qh @@ -47,6 +47,9 @@ float DEAD_DEAD = 2; float DEAD_RESPAWNABLE = 3; float DEAD_RESPAWNING = 4; +float RESPAWN_FORCE = 1; +float RESPAWN_SILENT = 2; + float DAMAGE_NO = 0; float DAMAGE_YES = 1; float DAMAGE_AIM = 2; diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index a7cd80136..c5720daf3 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -104,6 +104,7 @@ float server_is_dedicated; //.float cnt2; .float play_time; +.float respawn_flags; .float respawn_time; .float death_time; .float fade_time; @@ -446,8 +447,10 @@ string cvar_changes; string cvar_purechanges; float cvar_purechanges_count; -float game_starttime; //point in time when the countdown is over +float game_starttime; //point in time when the countdown to game start is over +float round_starttime; //point in time when the countdown to round start is over .float stat_game_starttime; +.float stat_round_starttime; .float stat_sv_airaccel_qw; .float stat_sv_airstrafeaccel_qw; @@ -579,8 +582,6 @@ string deathmessage; .void (float act_state) setactive; .entity realowner; -float allowed_to_spawn; // boolean variable used by the clan arena code to determine if a player can spawn (after the round has ended) - float serverflags; .float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator @@ -588,7 +589,6 @@ float serverflags; .float player_blocked; .float freezetag_frozen; -.float freezetag_revive_progress; .entity muzzle_flash; .float misc_bulletcounter; // replaces uzi & hlac bullet counter. diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index 9648bccf9..16a6a04af 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -120,10 +120,6 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype) PlayerScore_Add(targ, SP_DEATHS, 1); - if(g_arena || g_ca) - if(autocvar_g_arena_roundbased) - return; - if(targ != attacker) // not for suicides if(g_weaponarena_random) { diff --git a/qcsrc/server/g_hook.qc b/qcsrc/server/g_hook.qc index f7af47d92..dec6e167d 100644 --- a/qcsrc/server/g_hook.qc +++ b/qcsrc/server/g_hook.qc @@ -121,7 +121,7 @@ void GrapplingHookThink() error("Owner lost the hook!\n"); return; } - if(LostMovetypeFollow(self) || intermission_running) + if(LostMovetypeFollow(self) || intermission_running || (round_handler_IsActive() && !round_handler_IsRoundStarted())) { RemoveGrapplingHook(self.realowner); return; @@ -299,14 +299,8 @@ void FireGrapplingHook (void) float s; vector vs; - if((arena_roundbased && time < warmup) || (time < game_starttime)) - return; - - if(self.freezetag_frozen) - return; - - if(self.vehicle) - return; + if(forbidWeaponUse()) return; + if(self.vehicle) return; makevectors(self.v_angle); diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index ed194ef0d..d8ce88cf3 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -256,16 +256,17 @@ void cvar_changes_init() BADCVAR("g_arena"); BADCVAR("g_assault"); BADCVAR("g_ca"); + BADCVAR("g_ca_teams"); BADCVAR("g_ctf"); BADCVAR("g_cts"); BADCVAR("g_dm"); BADCVAR("g_domination"); BADCVAR("g_domination_default_teams"); BADCVAR("g_freezetag"); + BADCVAR("g_freezetag_teams"); BADCVAR("g_keepaway"); BADCVAR("g_keyhunt"); BADCVAR("g_keyhunt_teams"); - BADCVAR("g_keyhunt_teams"); BADCVAR("g_lms"); BADCVAR("g_nexball"); BADCVAR("g_onslaught"); @@ -359,8 +360,10 @@ void cvar_changes_init() BADCVAR("g_balance_teams_scorefactor"); BADCVAR("g_ban_sync_trusted_servers"); BADCVAR("g_ban_sync_uri"); + BADCVAR("g_ca_teams_override"); BADCVAR("g_ctf_ignore_frags"); BADCVAR("g_domination_point_limit"); + BADCVAR("g_freezetag_teams_override"); BADCVAR("g_friendlyfire"); BADCVAR("g_fullbrightitems"); BADCVAR("g_fullbrightplayers"); @@ -581,8 +584,6 @@ void spawnfunc_worldspawn (void) compressShortVector_init(); - allowed_to_spawn = TRUE; - entity head; head = nextent(world); maxclients = 0; @@ -783,6 +784,7 @@ void spawnfunc_worldspawn (void) addstat(STAT_SWITCHWEAPON, AS_INT, switchweapon); addstat(STAT_SWITCHINGWEAPON, AS_INT, switchingweapon); addstat(STAT_GAMESTARTTIME, AS_FLOAT, stat_game_starttime); + addstat(STAT_ROUNDSTARTTIME, AS_FLOAT, stat_round_starttime); addstat(STAT_ALLOW_OLDNEXBEAM, AS_INT, stat_allow_oldnexbeam); Nagger_Init(); @@ -805,19 +807,6 @@ void spawnfunc_worldspawn (void) addstat(STAT_HAGAR_LOAD, AS_INT, hagar_load); - if(g_ca || g_freezetag) - { - addstat(STAT_REDALIVE, AS_INT, redalive_stat); - addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); - addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); - addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); - } - if(g_freezetag) - { - addstat(STAT_FROZEN, AS_INT, freezetag_frozen); - addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress); - } - // g_movementspeed hack addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw); addstat(STAT_MOVEVARS_MAXSPEED, AS_FLOAT, stat_sv_maxspeed); @@ -1478,7 +1467,7 @@ void DumpStats(float final) { s = strcat(":player:see-labels:", GetPlayerScoreString(other, 0), ":"); s = strcat(s, ftos(rint(time - other.jointime)), ":"); - if(other.classname == "player" || g_arena || g_ca || g_lms) + if(other.classname == "player" || g_arena || other.caplayer == 1 || g_lms) s = strcat(s, ftos(other.team), ":"); else s = strcat(s, "spectator:"); diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 545aa31b2..fa8be67fe 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -1140,8 +1140,8 @@ void readlevelcvars(void) if(!g_weapon_stay) g_weapon_stay = cvar("g_weapon_stay"); - if not(inWarmupStage && !g_ca) - game_starttime = cvar("g_start_delay"); + if not(inWarmupStage) + game_starttime = time + cvar("g_start_delay"); readplayerstartcvars(); } diff --git a/qcsrc/server/mutators/base.qh b/qcsrc/server/mutators/base.qh index 35b2e6523..d705f5d5e 100644 --- a/qcsrc/server/mutators/base.qh +++ b/qcsrc/server/mutators/base.qh @@ -46,10 +46,22 @@ void Mutator_Remove(mutatorfunc_t func, string name); // calls error() on fail MUTATOR_HOOKABLE(MakePlayerObserver); // called when a player becomes observer, after shared setup +MUTATOR_HOOKABLE(PutClientInServer); + entity self; // client wanting to spawn + MUTATOR_HOOKABLE(PlayerSpawn); entity spawn_spot; // spot that was used, or world // called when a player spawns as player, after shared setup, before his weapon is chosen (so items may be changed in here) +MUTATOR_HOOKABLE(reset_map_global); + // called in reset_map + +MUTATOR_HOOKABLE(reset_map_players); + // called in reset_map + +MUTATOR_HOOKABLE(ForbidPlayerScore_Clear); + // returns 1 if clearing player score shall not be allowed + MUTATOR_HOOKABLE(ClientDisconnect); // called when a player disconnects @@ -59,6 +71,7 @@ MUTATOR_HOOKABLE(PlayerDies); entity frag_inflictor; entity frag_attacker; entity frag_target; // same as self + float frag_deathtype; MUTATOR_HOOKABLE(GiveFragsForKill); // called when someone was fragged by "self", and is expected to change frag_score to adjust scoring for the kill diff --git a/qcsrc/server/mutators/gamemode_arena.qc b/qcsrc/server/mutators/gamemode_arena.qc new file mode 100644 index 000000000..46b8faccc --- /dev/null +++ b/qcsrc/server/mutators/gamemode_arena.qc @@ -0,0 +1,278 @@ +.float spawned; +float maxspawned; +float numspawned; +.entity spawnqueue_next; +.entity spawnqueue_prev; +.float spawnqueue_in; +entity spawnqueue_first; +entity spawnqueue_last; + +void Spawnqueue_Insert(entity e) +{ + if(e.spawnqueue_in) + return; + dprint(strcat("Into queue: ", e.netname, "\n")); + e.spawnqueue_in = TRUE; + e.spawnqueue_prev = spawnqueue_last; + e.spawnqueue_next = world; + if(spawnqueue_last) + spawnqueue_last.spawnqueue_next = e; + spawnqueue_last = e; + if(!spawnqueue_first) + spawnqueue_first = e; +} + +void Spawnqueue_Remove(entity e) +{ + if(!e.spawnqueue_in) + return; + dprint(strcat("Out of queue: ", e.netname, "\n")); + e.spawnqueue_in = FALSE; + if(e == spawnqueue_first) + spawnqueue_first = e.spawnqueue_next; + if(e == spawnqueue_last) + spawnqueue_last = e.spawnqueue_prev; + if(e.spawnqueue_prev) + e.spawnqueue_prev.spawnqueue_next = e.spawnqueue_next; + if(e.spawnqueue_next) + e.spawnqueue_next.spawnqueue_prev = e.spawnqueue_prev; + e.spawnqueue_next = world; + e.spawnqueue_prev = world; +} + +void Spawnqueue_Unmark(entity e) +{ + if(!e.spawned) + return; + e.spawned = FALSE; + numspawned = numspawned - 1; +} + +void Spawnqueue_Mark(entity e) +{ + if(e.spawned) + return; + e.spawned = TRUE; + numspawned = numspawned + 1; +} + +float Arena_CheckWinner() +{ + entity e; + + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + round_handler_Init(5, autocvar_g_arena_warmup, autocvar_g_arena_round_timelimit); + return 1; + } + + if(numspawned > 1) + return 0; + + entity champion; + champion = world; + FOR_EACH_CLIENT(e) + { + if(e.spawned && IS_PLAYER(e)) + champion = e; + } + + if(champion) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, champion.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, champion.netname); + UpdateFrags(champion, +1); + } + else + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + } + round_handler_Init(5, autocvar_g_arena_warmup, autocvar_g_arena_round_timelimit); + return 1; +} + +void Arena_AddChallengers() +{ + entity e; + if(time < 2) // don't force players to spawn so early + return; + e = self; + while(numspawned < maxspawned && spawnqueue_first) + { + self = spawnqueue_first; + + bprint ("^4", self.netname, "^4 is the next challenger\n"); + + Spawnqueue_Remove(self); + Spawnqueue_Mark(self); + + self.classname = "player"; + PutClientInServer(); + } + self = e; +} + +float prev_numspawned; +float Arena_CheckPlayers() +{ + Arena_AddChallengers(); + + if(numspawned >= 2) + { + if(prev_numspawned > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_PLAYERS); + prev_numspawned = -1; + return 1; + } + + if(prev_numspawned != numspawned && numspawned == 1) + { + if(maxspawned - numspawned > 0) + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_PLAYERS, maxspawned - numspawned); + prev_numspawned = numspawned; + } + + return 0; +} + +void Arena_RoundStart() +{ + entity e; + FOR_EACH_PLAYER(e) + e.player_blocked = 0; +} + +MUTATOR_HOOKFUNCTION(arena_ClientDisconnect) +{ + Spawnqueue_Unmark(self); + Spawnqueue_Remove(self); + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_reset_map_players) +{ + FOR_EACH_CLIENT(self) + { + if(self.spawned) + { + PutClientInServer(); + self.player_blocked = 1; + } + else + PutObserverInServer(); + } + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_MakePlayerObserver) +{ + if(self.version_mismatch) + { + self.frags = FRAGS_SPECTATOR; + Spawnqueue_Unmark(self); + Spawnqueue_Remove(self); + } + else + { + self.frags = FRAGS_LMS_LOSER; + Spawnqueue_Insert(self); + } + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_PutClientInServer) +{ + if(!self.spawned) + self.classname = "observer"; + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_ClientConnect) +{ + self.classname = "observer"; + Spawnqueue_Insert(self); + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_PlayerSpawn) +{ + Spawnqueue_Remove(self); + Spawnqueue_Mark(self); + if(arena_roundbased) + self.player_blocked = 1; + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_ForbidPlayerScore_Clear) +{ + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_GiveFragsForKill) +{ + if(arena_roundbased) + frag_score = 0; // score will be given to the champion when the round ends + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_PlayerDies) +{ + // put dead players in the spawn queue + if(arena_roundbased) + self.respawn_flags = (RESPAWN_FORCE | RESPAWN_SILENT); + else + self.respawn_flags = RESPAWN_SILENT; + Spawnqueue_Unmark(self); + return 1; +} + +MUTATOR_HOOKFUNCTION(arena_SV_StartFrame) +{ + if(gameover) return 1; + if(time <= game_starttime || !arena_roundbased) + Arena_AddChallengers(); + return 1; +} + +void arena_Initialize() +{ + maxspawned = max(2, autocvar_g_arena_maxspawned); + arena_roundbased = autocvar_g_arena_roundbased; + if(arena_roundbased) + { + round_handler_Spawn(Arena_CheckPlayers, Arena_CheckWinner, Arena_RoundStart); + round_handler_Init(5, autocvar_g_arena_warmup, autocvar_g_arena_round_timelimit); + } +} + +MUTATOR_DEFINITION(gamemode_arena) +{ + MUTATOR_HOOK(ClientDisconnect, arena_ClientDisconnect, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, arena_reset_map_players, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, arena_MakePlayerObserver, CBC_ORDER_ANY); + MUTATOR_HOOK(PutClientInServer, arena_PutClientInServer, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientConnect, arena_ClientConnect, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerSpawn, arena_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidPlayerScore_Clear, arena_ForbidPlayerScore_Clear, CBC_ORDER_ANY); + MUTATOR_HOOK(GiveFragsForKill, arena_GiveFragsForKill, CBC_ORDER_ANY); + MUTATOR_HOOK(PlayerDies, arena_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(SV_StartFrame, arena_SV_StartFrame, CBC_ORDER_ANY); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + arena_Initialize(); + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} diff --git a/qcsrc/server/mutators/gamemode_arena.qh b/qcsrc/server/mutators/gamemode_arena.qh new file mode 100644 index 000000000..a2f623a00 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_arena.qh @@ -0,0 +1,2 @@ +// should be removed in the future, as other code should not have to care +float arena_roundbased; diff --git a/qcsrc/server/mutators/gamemode_ca.qc b/qcsrc/server/mutators/gamemode_ca.qc new file mode 100644 index 000000000..11330fbd7 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_ca.qc @@ -0,0 +1,266 @@ +float total_players; +float redalive, bluealive, yellowalive, pinkalive; +.float redalive_stat, bluealive_stat, yellowalive_stat, pinkalive_stat; +float ca_teams; +float allowed_to_spawn; + +void CA_count_alive_players() +{ + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) { + if(e.team == NUM_TEAM_1) + { + ++total_players; + if (e.health >= 1) ++redalive; + } + else if(e.team == NUM_TEAM_2) + { + ++total_players; + if (e.health >= 1) ++bluealive; + } + else if(e.team == NUM_TEAM_3) + { + ++total_players; + if (e.health >= 1) ++yellowalive; + } + else if(e.team == NUM_TEAM_4) + { + ++total_players; + if (e.health >= 1) ++pinkalive; + } + } + FOR_EACH_REALCLIENT(e) { + e.redalive_stat = redalive; + e.bluealive_stat = bluealive; + e.yellowalive_stat = yellowalive; + e.pinkalive_stat = pinkalive; + } +} + +float CA_GetWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} + +#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == ca_teams) +float CA_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + allowed_to_spawn = FALSE; + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + return 1; + } + + CA_count_alive_players(); + if(CA_ALIVE_TEAMS() > 1) + return 0; + + float winner_team = CA_GetWinnerTeam(); + if(winner_team > 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_SCORE, +1); + } + else if(winner_team == -1) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); + } + + allowed_to_spawn = FALSE; + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + return 1; +} + +void CA_RoundStart() +{ + if(inWarmupStage) + allowed_to_spawn = TRUE; + else + allowed_to_spawn = FALSE; +} + +float prev_total_players; +float CA_CheckTeams() +{ + allowed_to_spawn = TRUE; + CA_count_alive_players(); + if(CA_ALIVE_TEAMS_OK()) + { + if(prev_total_players > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_total_players = -1; + return 1; + } + if(prev_total_players != total_players) + { + float p1 = 0, p2 = 0, p3 = 0, p4 = 0; + if(!redalive) p1 = NUM_TEAM_1; + if(!bluealive) p2 = NUM_TEAM_2; + if(ca_teams >= 3) + if(!yellowalive) p3 = NUM_TEAM_3; + if(ca_teams >= 4) + if(!pinkalive) p4 = NUM_TEAM_4; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, p1, p2, p3, p4); + prev_total_players = total_players; + } + return 0; +} + +MUTATOR_HOOKFUNCTION(ca_PlayerSpawn) +{ + self.caplayer = 1; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_PutClientInServer) +{ + if(!allowed_to_spawn) + { + self.classname = "observer"; + if(!self.caplayer) + { + self.caplayer = 0.5; + if(clienttype(self) == CLIENTTYPE_REAL) + sprint(self, "You will join the game in the next round.\n"); + } + } + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_reset_map_players) +{ + FOR_EACH_CLIENT(self) + { + if(self.caplayer) + { + self.classname = "player"; + self.caplayer = 1; + PutClientInServer(); + } + } + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_ClientConnect) +{ + self.classname = "observer"; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_reset_map_global) +{ + allowed_to_spawn = TRUE; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_GetTeamCount) +{ + ca_teams = autocvar_g_ca_teams_override; + if(ca_teams < 2) + ca_teams = autocvar_g_ca_teams; + ca_teams = bound(2, ca_teams, 4); + ret_float = ca_teams; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_PlayerDies) +{ + if(!allowed_to_spawn) + self.respawn_flags = RESPAWN_SILENT; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_ForbidPlayerScore_Clear) +{ + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_MakePlayerObserver) +{ + if(self.killindicator_teamchange == -2) + self.caplayer = 0; + if(self.caplayer) + self.frags = FRAGS_LMS_LOSER; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_ForbidThrowCurrentWeapon) +{ + return 1; +} + +MUTATOR_HOOKFUNCTION(ca_GiveFragsForKill) +{ + frag_score = 0; // score will be given to the winner team when the round ends + return 1; +} + +void ca_Initialize() +{ + allowed_to_spawn = TRUE; + + round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart); + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + + addstat(STAT_REDALIVE, AS_INT, redalive_stat); + addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); + addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); + addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); +} + +MUTATOR_DEFINITION(gamemode_ca) +{ + MUTATOR_HOOK(PlayerSpawn, ca_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(PutClientInServer, ca_PutClientInServer, CBC_ORDER_ANY); + MUTATOR_HOOK(MakePlayerObserver, ca_MakePlayerObserver, CBC_ORDER_ANY); + MUTATOR_HOOK(ClientConnect, ca_ClientConnect, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_global, ca_reset_map_global, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, ca_reset_map_players, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, ca_GetTeamCount, CBC_ORDER_EXCLUSIVE); + MUTATOR_HOOK(PlayerDies, ca_PlayerDies, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidPlayerScore_Clear, ca_ForbidPlayerScore_Clear, CBC_ORDER_ANY); + MUTATOR_HOOK(ForbidThrowCurrentWeapon, ca_ForbidThrowCurrentWeapon, CBC_ORDER_ANY); + MUTATOR_HOOK(GiveFragsForKill, ca_GiveFragsForKill, CBC_ORDER_FIRST); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + ca_Initialize(); + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} diff --git a/qcsrc/server/mutators/gamemode_ca.qh b/qcsrc/server/mutators/gamemode_ca.qh new file mode 100644 index 000000000..ab0a9d195 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_ca.qh @@ -0,0 +1,3 @@ +// should be removed in the future, as other code should not have to care +.float caplayer; // 0.5 if scheduled to join the next round + diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc index c43a39875..980a9b20d 100644 --- a/qcsrc/server/mutators/gamemode_freezetag.qc +++ b/qcsrc/server/mutators/gamemode_freezetag.qc @@ -1,46 +1,131 @@ -void freezetag_Initialize() +.float freezetag_frozen_time; +.float freezetag_frozen_timeout; +.float freezetag_revive_progress; +.entity freezetag_ice; +#define ICE_MAX_ALPHA 1 +#define ICE_MIN_ALPHA 0.1 +float freezetag_teams; + +void freezetag_count_alive_players() { - precache_model("models/ice/ice.md3"); - warmup = time + autocvar_g_start_delay + autocvar_g_freezetag_warmup; - ScoreRules_freezetag(); + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) { + if(e.team == NUM_TEAM_1 && e.health >= 1) + { + ++total_players; + if (!e.freezetag_frozen) ++redalive; + } + else if(e.team == NUM_TEAM_2 && e.health >= 1) + { + ++total_players; + if (!e.freezetag_frozen) ++bluealive; + } + else if(e.team == NUM_TEAM_3 && e.health >= 1) + { + ++total_players; + if (!e.freezetag_frozen) ++yellowalive; + } + else if(e.team == NUM_TEAM_4 && e.health >= 1) + { + ++total_players; + if (!e.freezetag_frozen) ++pinkalive; + } + } + FOR_EACH_REALCLIENT(e) { + e.redalive_stat = redalive; + e.bluealive_stat = bluealive; + e.yellowalive_stat = yellowalive; + e.pinkalive_stat = pinkalive; + } } +#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams) -void freezetag_CheckWinner() +float prev_total_players; +float freezetag_CheckTeams() { - if(time <= game_starttime) // game didn't even start yet! nobody can win in that case. - return; + if(FREEZETAG_ALIVE_TEAMS_OK()) + { + if(prev_total_players > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_total_players = -1; + return 1; + } + if(prev_total_players != total_players) + { + float p1 = 0, p2 = 0, p3 = 0, p4 = 0; + if(!redalive) p1 = NUM_TEAM_1; + if(!bluealive) p2 = NUM_TEAM_2; + if(freezetag_teams >= 3) + if(!yellowalive) p3 = NUM_TEAM_3; + if(freezetag_teams >= 4) + if(!pinkalive) p4 = NUM_TEAM_4; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, p1, p2, p3, p4); + prev_total_players = total_players; + } + return 0; +} - if(next_round || (time > warmup - autocvar_g_freezetag_warmup && time < warmup)) - return; // already waiting for next round to start +float freezetag_getWinnerTeam() +{ + float winner_team = 0; + if(redalive >= 1) + winner_team = NUM_TEAM_1; + if(bluealive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkalive >= 1) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no player left +} - if((redalive >= 1 && bluealive >= 1) - || (redalive >= 1 && yellowalive >= 1) - || (redalive >= 1 && pinkalive >= 1) - || (bluealive >= 1 && yellowalive >= 1) - || (bluealive >= 1 && pinkalive >= 1) - || (yellowalive >= 1 && pinkalive >= 1)) - return; // we still have active players on two or more teams, nobody won yet +float freezetag_CheckWinner() +{ + entity e; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER); + FOR_EACH_PLAYER(e) + e.freezetag_frozen_timeout = 0; + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; + } - entity e, winner; - winner = world; + if(FREEZETAG_ALIVE_TEAMS() > 1) + return 0; - FOR_EACH_PLAYER(e) + float winner_team; + winner_team = freezetag_getWinnerTeam(); + if(winner_team > 0) { - if(e.freezetag_frozen == 0 && e.health >= 1) // here's one player from the winning team... good - { - winner = e; - break; // break, we found the winner - } + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_SCORE, +1); } - - if(winner != world) // just in case a winner wasn't found + else if(winner_team == -1) { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner.team, CENTER_FREEZETAG_ROUND_WIN_)); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner.team, INFO_FREEZETAG_ROUND_WIN_)); - TeamScore_AddToTeam(winner.team, ST_SCORE, +1); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } - next_round = time + 5; + FOR_EACH_PLAYER(e) + e.freezetag_frozen_timeout = 0; + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; } // this is needed to allow the player to turn his view around (fixangle can't @@ -52,13 +137,36 @@ void freezetag_Ice_Think() self.nextthink = time; } +void freezetag_Add_Score(entity attacker) +{ + if(attacker == self) + { + // you froze your own dumb self + // counted as "suicide" already + PlayerScore_Add(self, SP_SCORE, -1); + } + else if(IS_PLAYER(attacker)) + { + // got frozen by an enemy + // counted as "kill" and "death" already + PlayerScore_Add(self, SP_SCORE, -1); + PlayerScore_Add(attacker, SP_SCORE, +1); + } + // else nothing - got frozen by the game type rules themselves +} + void freezetag_Freeze(entity attacker) { if(self.freezetag_frozen) return; self.freezetag_frozen = 1; + self.freezetag_frozen_time = time; self.freezetag_revive_progress = 0; self.health = 1; + if(autocvar_g_freezetag_frozen_maxtime > 0) + self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; + + freezetag_count_alive_players(); entity ice; ice = spawn(); @@ -67,53 +175,31 @@ void freezetag_Freeze(entity attacker) ice.think = freezetag_Ice_Think; ice.nextthink = time; ice.frame = floor(random() * 21); // ice model has 20 different looking frames + ice.alpha = ICE_MAX_ALPHA; + ice.colormod = Team_ColorRGB(self.team); + ice.glowmod = ice.colormod; setmodel(ice, "models/ice/ice.md3"); - entity oldself; - oldself = self; - self = ice; - freezetag_Ice_Think(); - self = oldself; + self.freezetag_ice = ice; RemoveGrapplingHook(self); // add waypoint WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE, RADARICON_WAYPOINT, '0.25 0.90 1'); - if(attacker == self) - { - // you froze your own dumb self - // counted as "suicide" already - PlayerScore_Add(self, SP_SCORE, -1); - } - else if(attacker.classname == "player") - { - // got frozen by an enemy - // counted as "kill" and "death" already - PlayerScore_Add(self, SP_SCORE, -1); - PlayerScore_Add(attacker, SP_SCORE, +1); - } - else - { - // nothing - got frozen by the game type rules themselves - } + freezetag_Add_Score(attacker); } void freezetag_Unfreeze(entity attacker) { self.freezetag_frozen = 0; + self.freezetag_frozen_time = 0; + self.freezetag_frozen_timeout = 0; self.freezetag_revive_progress = 0; - self.health = autocvar_g_balance_health_start; - // remove the ice block - entity ice; - for(ice = world; (ice = find(ice, classname, "freezetag_ice")); ) if(ice.owner == self) - { - remove(ice); - break; - } + remove(self.freezetag_ice); + self.freezetag_ice = world; - // remove waypoint if(self.waypointsprite_attached) WaypointSprite_Kill(self.waypointsprite_attached); } @@ -229,44 +315,45 @@ void havocbot_role_ft_freeing() MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer) { - if(self.freezetag_frozen == 0 && self.health >= 1) - { - if(self.team == NUM_TEAM_1) - --redalive; - else if(self.team == NUM_TEAM_2) - --bluealive; - else if(self.team == NUM_TEAM_3) - --yellowalive; - else if(self.team == NUM_TEAM_4) - --pinkalive; - --totalalive; - } - - if(total_players > 2) // only check for winners if we had more than two players (one of them left, don't let the other player win just because of that) - freezetag_CheckWinner(); - + self.health = 0; // neccessary to update correctly alive stats freezetag_Unfreeze(world); - + freezetag_count_alive_players(); return 1; } MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) { - if(self.freezetag_frozen == 0) + if(round_handler_IsActive()) + if(round_handler_CountdownRunning()) { - if(self.team == NUM_TEAM_1) - --redalive; - else if(self.team == NUM_TEAM_2) - --bluealive; - else if(self.team == NUM_TEAM_3) - --yellowalive; - else if(self.team == NUM_TEAM_4) - --pinkalive; - --totalalive; + if(self.freezetag_frozen) + freezetag_Unfreeze(world); + freezetag_count_alive_players(); + return 1; // let the player die so that he can respawn whenever he wants + } - freezetag_Freeze(frag_attacker); + // Cases DEATH_TEAMCHANGE and DEATH_AUTOTEAMCHANGE are needed to fix a bug whe + // you succeed changing team through the menu: you both really die (gibbing) and get frozen + if(ITEM_DAMAGE_NEEDKILL(frag_deathtype) + || frag_deathtype == DEATH_TEAMCHANGE || frag_deathtype == DEATH_AUTOTEAMCHANGE) + { + // let the player die, he will be automatically frozen when he respawns + if(!self.freezetag_frozen) + { + freezetag_Add_Score(frag_attacker); + freezetag_count_alive_players(); + } + else + freezetag_Unfreeze(world); // remove ice + self.freezetag_frozen_timeout = -2; // freeze on respawn + return 1; } + if(self.freezetag_frozen) + return 1; + + freezetag_Freeze(frag_attacker); + if(frag_attacker == frag_target || frag_attacker == world) { if(frag_target.classname == STR_PLAYER) @@ -284,22 +371,24 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) frag_target.health = 1; // "respawn" the player :P - freezetag_CheckWinner(); - return 1; } MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn) { - freezetag_Unfreeze(world); // start by making sure that all ice blocks are removed + if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players + return 1; // do nothing, round is starting right now - if(total_players == 1 && time > game_starttime) // only one player active on server, start a new match immediately - if(!next_round && warmup && (time < warmup - autocvar_g_freezetag_warmup || time > warmup)) // not awaiting next round + if(self.freezetag_frozen_timeout == -2) // player was dead { - next_round = time; + freezetag_Freeze(world); return 1; } - if(warmup && time > warmup) // spawn too late, freeze player + + freezetag_count_alive_players(); + + if(round_handler_IsActive()) + if(round_handler_IsRoundStarted()) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE); freezetag_Freeze(world); @@ -308,33 +397,69 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn) return 1; } +MUTATOR_HOOKFUNCTION(freezetag_reset_map_players) +{ + FOR_EACH_PLAYER(self) + { + if (self.freezetag_frozen) + freezetag_Unfreeze(world); + self.freezetag_frozen_timeout = -1; + PutClientInServer(); + self.freezetag_frozen_timeout = 0; + } + freezetag_count_alive_players(); + return 1; +} + MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill) { frag_score = 0; // no frags counted in Freeze Tag return 1; } +.float reviving; // temp var MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) { float n; - vector revive_extra_size; - revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + if(gameover) + return 1; + + if(self.freezetag_frozen) + { + // keep health = 1 + self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen; + } + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + return 1; entity o; o = world; - n = 0; - FOR_EACH_PLAYER(other) if(self != other) + if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) + self.freezetag_ice.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (self.freezetag_frozen_timeout - time) / (self.freezetag_frozen_timeout - self.freezetag_frozen_time); + + if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) + n = -1; + else { - if(other.freezetag_frozen == 0) + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOR_EACH_PLAYER(other) if(self != other) { - if(other.team == self.team) + if(other.freezetag_frozen == 0) { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + if(other.team == self.team) { - if(!o) - o = other; - ++n; + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + { + if(!o) + o = other; + if(self.freezetag_frozen) + other.reviving = TRUE; + ++n; + } } } } @@ -342,44 +467,42 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) if(n && self.freezetag_frozen) // OK, there is at least one teammate reviving us { - self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * autocvar_g_freezetag_revive_speed, 1); + self.freezetag_revive_progress = bound(0, self.freezetag_revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); self.health = max(1, self.freezetag_revive_progress * autocvar_g_balance_health_start); if(self.freezetag_revive_progress >= 1) { freezetag_Unfreeze(self); + freezetag_count_alive_players(); + + if(n == -1) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, self.netname, autocvar_g_freezetag_frozen_maxtime); + return 1; + } // EVERY team mate nearby gets a point (even if multiple!) - FOR_EACH_PLAYER(other) if(self != other) + FOR_EACH_PLAYER(other) { - if(other.freezetag_frozen == 0) + if(other.reviving) { - if(other.team == self.team) - { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - { - PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); - PlayerScore_Add(other, SP_SCORE, +1); - } - } + PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); + PlayerScore_Add(other, SP_SCORE, +1); } } Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVE, self.netname, o.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED, self.netname, o.netname); } - // now find EVERY teammate within reviving radius, set their revive_progress values correct - FOR_EACH_PLAYER(other) if(self != other) + FOR_EACH_PLAYER(other) { - if(other.freezetag_frozen == 0) + if(other.reviving) { - if(other.team == self.team) - { - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - other.freezetag_revive_progress = self.freezetag_revive_progress; - } + other.freezetag_revive_progress = self.freezetag_revive_progress; + other.reviving = FALSE; } } } @@ -408,15 +531,12 @@ MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics) MUTATOR_HOOKFUNCTION(freezetag_PlayerDamage_Calculate) { - if(g_freezetag) - { - if(frag_target.freezetag_frozen == 1 && frag_deathtype != DEATH_HURTTRIGGER) - { - frag_damage = 0; - frag_force = frag_force * autocvar_g_freezetag_frozen_force; - } - } - return 1; + if(frag_target.freezetag_frozen && frag_deathtype != DEATH_HURTTRIGGER) + { + frag_damage = 0; + frag_force = frag_force * autocvar_g_freezetag_frozen_force; + } + return 1; } MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon) @@ -426,6 +546,13 @@ MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon) return 0; } +MUTATOR_HOOKFUNCTION(freezetag_ItemTouch) +{ + if (other.freezetag_frozen) + return 1; + return 0; +} + MUTATOR_HOOKFUNCTION(freezetag_BotRoles) { if not(self.deadflag) @@ -435,22 +562,60 @@ MUTATOR_HOOKFUNCTION(freezetag_BotRoles) else self.havocbot_role = havocbot_role_ft_offense; } - + return TRUE; } +MUTATOR_HOOKFUNCTION(freezetag_SpectateCopy) +{ + self.freezetag_frozen = other.freezetag_frozen; + self.freezetag_revive_progress = other.freezetag_revive_progress; + return 0; +} + +MUTATOR_HOOKFUNCTION(freezetag_GetTeamCount) +{ + freezetag_teams = autocvar_g_freezetag_teams_override; + if(freezetag_teams < 2) + freezetag_teams = autocvar_g_freezetag_teams; + freezetag_teams = bound(2, freezetag_teams, 4); + ret_float = freezetag_teams; + return 0; +} + +void freezetag_Initialize() +{ + precache_model("models/ice/ice.md3"); + ScoreRules_freezetag(); + + round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null); + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + + addstat(STAT_REDALIVE, AS_INT, redalive_stat); + addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat); + addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat); + addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat); + + addstat(STAT_FROZEN, AS_INT, freezetag_frozen); + addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress); +} + MUTATOR_DEFINITION(gamemode_freezetag) { MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY); MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY); + MUTATOR_HOOK(reset_map_players, freezetag_reset_map_players, CBC_ORDER_ANY); MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST); MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST); MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST); MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY); - MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_FIRST); //first, last or any? dunno. + MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_ANY); + MUTATOR_HOOK(ItemTouch, freezetag_ItemTouch, CBC_ORDER_ANY); MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, freezetag_SpectateCopy, CBC_ORDER_ANY); + MUTATOR_HOOK(GetTeamCount, freezetag_GetTeamCount, CBC_ORDER_EXCLUSIVE); MUTATOR_ONADD { diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qc b/qcsrc/server/mutators/gamemode_keyhunt.qc index 97d2dcd47..13a06c367 100644 --- a/qcsrc/server/mutators/gamemode_keyhunt.qc +++ b/qcsrc/server/mutators/gamemode_keyhunt.qc @@ -479,7 +479,7 @@ void kh_FinishRound() // runs when a team captures the keys kh_Key_Remove(key); kh_no_radar_circles = FALSE; - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ARENA_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); } @@ -857,7 +857,7 @@ void kh_WaitForPlayers() // delay start of the round until enough players are p float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); if not(p1 || p2 || p3 || p4) { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ARENA_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round); kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound); } else diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qh b/qcsrc/server/mutators/gamemode_keyhunt.qh index bda701f96..611f7f065 100644 --- a/qcsrc/server/mutators/gamemode_keyhunt.qh +++ b/qcsrc/server/mutators/gamemode_keyhunt.qh @@ -6,7 +6,6 @@ float kh_tracking_enabled; .entity kh_next; float kh_Key_AllOwnedByWhichTeam(); -// used by arena.qc ready-restart: typedef void(void) kh_Think_t; void kh_StartRound(); void kh_Controller_SetThink(float t, kh_Think_t func); diff --git a/qcsrc/server/mutators/mutator_superspec.qc b/qcsrc/server/mutators/mutator_superspec.qc index a351f937c..5a050356d 100644 --- a/qcsrc/server/mutators/mutator_superspec.qc +++ b/qcsrc/server/mutators/mutator_superspec.qc @@ -443,6 +443,9 @@ void superspec_hello() MUTATOR_HOOKFUNCTION(superspec_ClientConnect) { + if(clienttype(self) != CLIENTTYPE_REAL) + return FALSE; + string fn = "superspec-local.options"; float fh; diff --git a/qcsrc/server/mutators/mutators.qh b/qcsrc/server/mutators/mutators.qh index cf90b957e..7665cc873 100644 --- a/qcsrc/server/mutators/mutators.qh +++ b/qcsrc/server/mutators/mutators.qh @@ -1,3 +1,5 @@ +MUTATOR_DECLARATION(gamemode_arena); +MUTATOR_DECLARATION(gamemode_ca); MUTATOR_DECLARATION(gamemode_keyhunt); MUTATOR_DECLARATION(gamemode_freezetag); MUTATOR_DECLARATION(gamemode_keepaway); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 157aa38a4..edef590d0 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -35,6 +35,8 @@ defs.qh // Should rename this, it has fields and globals mutators/base.qh mutators/mutators.qh +mutators/gamemode_arena.qh +mutators/gamemode_ca.qh mutators/gamemode_ctf.qh mutators/gamemode_domination.qh mutators/gamemode_keyhunt.qh // TODO fix this @@ -85,6 +87,8 @@ antilag.qh playerdemo.qh +round_handler.qh + // singleplayer stuff item_key.qh secret.qh @@ -102,7 +106,6 @@ g_subs.qc g_tetris.qc //runematch.qc -arena.qc g_violence.qc g_damage.qc @@ -211,9 +214,13 @@ anticheat.qc cheats.qc playerstats.qc +round_handler.qc + ../common/explosion_equation.qc mutators/base.qc +mutators/gamemode_arena.qc +mutators/gamemode_ca.qc mutators/gamemode_ctf.qc mutators/gamemode_domination.qc mutators/gamemode_freezetag.qc diff --git a/qcsrc/server/round_handler.qc b/qcsrc/server/round_handler.qc new file mode 100644 index 000000000..af69e8e9e --- /dev/null +++ b/qcsrc/server/round_handler.qc @@ -0,0 +1,116 @@ +void round_handler_Think() +{ + float f; + + if(time < game_starttime) + { + round_handler_Reset(game_starttime); + return; + } + + if(gameover) + { + round_handler_Reset(0); + round_handler_Remove(); + return; + } + + if(self.wait) + { + self.wait = FALSE; + self.cnt = self.count + 1; // init countdown + round_starttime = time + self.count; + reset_map(TRUE); + } + + if(self.cnt > 0) // countdown running + { + if(self.canRoundStart()) + { + if(self.cnt == self.count + 1) + round_starttime = time + self.count; + f = self.cnt - 1; + if(f == 0) + { + self.cnt = 0; + self.round_endtime = (self.round_timelimit) ? time + self.round_timelimit : 0; + self.nextthink = time; + if(self.roundStart) + self.roundStart(); + return; + } + self.cnt = self.cnt - 1; + } + else + { + round_handler_Reset(0); + } + self.nextthink = time + 1; // canRoundStart every second + } + else + { + if(self.canRoundEnd()) + { + // schedule a new round + self.wait = TRUE; + self.nextthink = time + self.delay; + } + else + { + self.nextthink = time; // canRoundEnd every frame + } + } +} + +void round_handler_Init(float the_delay, float the_count, float the_round_timelimit) +{ + round_handler.delay = (the_delay > 0) ? the_delay : 0; + round_handler.count = fabs(floor(the_count)); + round_handler.cnt = round_handler.count + 1; + round_handler.round_timelimit = (the_round_timelimit > 0) ? the_round_timelimit : 0; +} + +// NOTE: this is only needed because if round_handler spawns at time 1 +// gamestarttime isn't initialized yet +void round_handler_FirstThink() +{ + round_starttime = max(time, game_starttime) + round_handler.count; + round_handler.think = round_handler_Think; + round_handler.nextthink = max(time, game_starttime); +} + +void round_handler_Spawn(float() canRoundStart_func, float() canRoundEnd_func, void() roundStart_func) +{ + if(round_handler) + { + backtrace("Can't spawn round_handler again!"); + return; + } + round_handler = spawn(); + round_handler.classname = "round_handler"; + + round_handler.think = round_handler_FirstThink; + round_handler.canRoundStart = canRoundStart_func; + round_handler.canRoundEnd = canRoundEnd_func; + round_handler.roundStart = roundStart_func; + round_handler.wait = FALSE; + round_handler_Init(5, 5, 180); + round_handler.nextthink = time; +} + +void round_handler_Reset(float next_think) +{ + round_handler.wait = FALSE; + if(round_handler.count) + if(round_handler.cnt < round_handler.count + 1) + round_handler.cnt = round_handler.count + 1; + round_handler.nextthink = next_think; + round_starttime = (next_think) ? (next_think + round_handler.count) : -1; +} + +void round_handler_Remove() +{ + remove(round_handler); + round_handler = world; +} + diff --git a/qcsrc/server/round_handler.qh b/qcsrc/server/round_handler.qh new file mode 100644 index 000000000..22a91dc2c --- /dev/null +++ b/qcsrc/server/round_handler.qh @@ -0,0 +1,23 @@ +entity round_handler; +.float delay; // stores delay from round end to countdown start +.float count; // stores initial number of the countdown +.float wait; // it's set to TRUE when round ends, to FALSE when countdown starts +.float cnt; // its initial value is .count + 1, then decreased while counting down + // reaches 0 when the round starts +.float round_timelimit; +.float round_endtime; +.float() canRoundStart; +.float() canRoundEnd; +.void() roundStart; + +void round_handler_Init(float the_delay, float the_count, float the_round_timelimit); +void round_handler_Spawn(float() canRoundStart_func, float() canRoundEnd_func, void() roundStart_func); +void round_handler_Reset(float next_think); +void round_handler_Remove(); + +#define round_handler_IsActive() (round_handler != world) +#define round_handler_AwaitingNextRound() (round_handler.wait) +#define round_handler_CountdownRunning() (!round_handler.wait && round_handler.cnt) +#define round_handler_IsRoundStarted() (!round_handler.wait && !round_handler.cnt) +#define round_handler_GetEndTime() (round_handler.round_endtime) + diff --git a/qcsrc/server/scores.qc b/qcsrc/server/scores.qc index e8d559023..4c75c9850 100644 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@ -256,8 +256,8 @@ float PlayerScore_Clear(entity player) if(teamscores_entities_count) return 0; + if(MUTATOR_CALLHOOK(ForbidPlayerScore_Clear)) return 0; if(g_lms) return 0; - if(g_arena || g_ca) return 0; if(g_cts) return 0; // in CTS, you don't lose score by observing if(g_race && g_race_qualifying) return 0; // in qualifying, you don't lose score by observing @@ -527,12 +527,12 @@ void WinningConditionHelper() s = strcat(s, ":human"); else s = strcat(s, ":bot"); - if(p.classname != "player" && !g_arena && !g_ca && !g_lms) + if(p.classname != "player" && !g_arena && p.caplayer != 1 && !g_lms) s = strcat(s, ":spectator"); } else { - if(p.classname == "player" || g_arena || g_ca || g_lms) + if(p.classname == "player" || g_arena || p.caplayer == 1 || g_lms) s = GetPlayerScoreString(p, 2); else s = "-666"; diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index d80b777a6..8d81e0380 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -209,19 +209,13 @@ void StartFrame (void) skill = autocvar_skill; - count_players(); - if(g_ca || g_freezetag) - count_alive_players(); - Arena_Warmup(); - Spawnqueue_Check(); - // detect when the pre-game countdown (if any) has ended and the game has started game_delay = (time < game_starttime) ? TRUE : FALSE; if(game_delay_last == TRUE) if(game_delay == FALSE) if(autocvar_sv_eventlog) - GameLogEcho(":startdelay_ended"); + GameLogEcho(":startdelay_ended"); game_delay_last = game_delay; diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 6608fc9a3..e30748990 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -121,10 +121,7 @@ void InitGameplayMode() { fraglimit_override = autocvar_g_arena_point_limit; leadlimit_override = autocvar_g_arena_point_leadlimit; - maxspawned = autocvar_g_arena_maxspawned; - if(maxspawned < 2) - maxspawned = 2; - arena_roundbased = autocvar_g_arena_roundbased; + MUTATOR_ADD(gamemode_arena); } if(g_ca) @@ -132,9 +129,9 @@ void InitGameplayMode() ActivateTeamplay(); fraglimit_override = autocvar_g_ca_point_limit; leadlimit_override = autocvar_g_ca_point_leadlimit; - precache_sound("ctf/red_capture.wav"); - precache_sound("ctf/blue_capture.wav"); + MUTATOR_ADD(gamemode_ca); } + if(g_keyhunt) { ActivateTeamplay(); diff --git a/qcsrc/server/w_minstanex.qc b/qcsrc/server/w_minstanex.qc index b34d66bf6..611319a16 100644 --- a/qcsrc/server/w_minstanex.qc +++ b/qcsrc/server/w_minstanex.qc @@ -97,8 +97,9 @@ void minstagib_ammocheck(void) minstagib_stop_countdown(self); else if (self.ammo_cells > 0 || (self.items & IT_UNLIMITED_WEAPON_AMMO)) { + if (self.minstagib_needammo) + self.health = 100; minstagib_stop_countdown(self); - self.health = 100; } else {