From: FruitieX Date: Tue, 30 Nov 2010 13:56:46 +0000 (+0200) Subject: Merge remote branch 'origin/master' into fruitiex/gamemode_freezetag X-Git-Tag: xonotic-v0.1.0preview~86^2~2^2~4 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=26c8963b7747f4c53fc541df0dd71b7024d1d496;hp=7620bad4093b6d83eb360cd6c9dd80e5cb5a2a1a Merge remote branch 'origin/master' into fruitiex/gamemode_freezetag --- diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg index bf1d943dae..13e5b7d673 100644 --- a/defaultXonotic.cfg +++ b/defaultXonotic.cfg @@ -182,6 +182,7 @@ seta crosshair_fireball_size 1 "crosshair size when wielding the fireball" // ring around crosshair, used for various purposes (such as indicating bullets left in clip, nex charge) seta crosshair_ring_size 2 "bullet counter ring size for Rifle, velocity ring for Nex" +seta crosshair_ring_alpha 0.2 "ring alpha" seta crosshair_ring_campingrifle_alpha 0.15 @@ -568,6 +569,14 @@ seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo seta g_ctf_win_mode 0 "0: captures only, 1: captures, then points, 2: points only" seta g_ctf_ignore_frags 0 "1: regular frags give no points" +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" +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_time 2.5 "Time it takes to revive a frozen teammate" +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" + // 50% of the spawns shall be far away from any players set g_spawn_furthest 0.5 // respawn delay @@ -607,6 +616,8 @@ set g_cts_respawn_waves 0 set g_cts_respawn_delay 0.25 set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts" set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible" +set g_freezetag_respawn_waves 0 +set g_freezetag_respawn_delay 0.25 // overtime seta timelimit_overtime 2 "duration in minutes of one added overtime, added to the timelimit" @@ -1373,6 +1384,8 @@ seta hud_panel_engineinfo_framecounter_exponentialmovingaverage_instantupdate_ch seta hud_showbinds 1 "the way to show the keys to press in HUD messages: 0 displays commands, 1 bound keys, 2 both" seta hud_showbinds_limit 2 "maximum number of bound keys to show for a command. 0 for unlimited" +seta hud_colorflash_alpha 0.5 "starting alpha of the color flash" + seta hud_damage 1 "an improved version of gl_polyblend, draw an image instead when hurt" seta hud_damage_gentle_alpha_multiplier 0.25 "how much to multiply alpha of flash when using the cl_gentle version, it's much more opaque than the non-gentle version" seta hud_damage_gentle_color "1 0.7 1" "color of flash for cl_gentle version" diff --git a/qcsrc/client/View.qc b/qcsrc/client/View.qc index 714b5d3339..7cf5ee6e57 100644 --- a/qcsrc/client/View.qc +++ b/qcsrc/client/View.qc @@ -764,6 +764,17 @@ void CSQC_UpdateView(float w, float h) CSQC_RAPTOR_HUD(); else { + if(gametype == GAME_FREEZETAG) + { + if(getstati(STAT_FROZEN)) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', cvar_or("hud_colorflash_alpha", 0.5), DRAWFLAG_ADDITIVE); + 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', cvar("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); + } + } + if(cvar("r_letterbox") == 0) if(cvar("viewsize") < 120) CSQC_common_hud(); diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index 7ad148b0cb..74fa29c6eb 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -4549,7 +4549,7 @@ void HUD_ModIcons(void) if(!autocvar_hud_panel_modicons && !autocvar__hud_configure) return; - if (gametype != GAME_KEYHUNT && gametype != GAME_CTF && gametype != GAME_NEXBALL && gametype != GAME_CTS && gametype != GAME_RACE && gametype != GAME_CA && !autocvar__hud_configure) + if (gametype != GAME_KEYHUNT && gametype != GAME_CTF && gametype != GAME_NEXBALL && gametype != GAME_CTS && gametype != GAME_RACE && gametype != GAME_CA && gametype != GAME_FREEZETAG && !autocvar__hud_configure) return; active_panel = HUD_PANEL_MODICONS; @@ -4586,7 +4586,7 @@ void HUD_ModIcons(void) HUD_Mod_NexBall(pos, mySize); else if(gametype == GAME_CTS || gametype == GAME_RACE) HUD_Mod_Race(pos, mySize); - else if(gametype == GAME_CA) + else if(gametype == GAME_CA || gametype == GAME_FREEZETAG) HUD_Mod_CA(pos, mySize); } diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index 8cc24634c3..839f702919 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -39,6 +39,7 @@ const float GAME_RACE = 11; const float GAME_NEXBALL = 12; const float GAME_CTS = 13; const float GAME_CA = 14; +const float GAME_FREEZETAG = 15; const float AS_STRING = 1; const float AS_INT = 2; @@ -342,6 +343,12 @@ const float STAT_VEHICLESTAT_RELOAD2 = 66; // mod stats (1xx) const float STAT_REDALIVE = 100; const float STAT_BLUEALIVE = 101; +const float STAT_YELLOWALIVE = 102; +const float STAT_PINKALIVE = 103; + +// freeze tag +const float STAT_FROZEN = 104; +const float STAT_REVIVE_PROGRESS = 105; //const float STAT_SPIDERBOT_AIM 53 // compressShotOrigin //const float STAT_SPIDERBOT_TARGET 54 // compressShotOrigin diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc index 5192b75ac2..895639e54d 100644 --- a/qcsrc/common/mapinfo.qc +++ b/qcsrc/common/mapinfo.qc @@ -350,6 +350,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp if(spawnpoints >= 8 && diameter > 4096) { MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH; + MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_FREEZETAG; MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CA; } if( diameter < 4096) @@ -406,6 +407,7 @@ string _MapInfo_GetDefault(float t) case MAPINFO_TYPE_ONSLAUGHT: return "20 0"; case MAPINFO_TYPE_NEXBALL: return "5 20 0"; case MAPINFO_TYPE_CTS: return "20 0 0"; + case MAPINFO_TYPE_FREEZETAG: return "10 20 0"; default: return ""; } } @@ -526,6 +528,7 @@ string _MapInfo_GetDefaultEx(float t) case MAPINFO_TYPE_ONSLAUGHT: return "timelimit=20"; case MAPINFO_TYPE_NEXBALL: return "timelimit=20 pointlimit=5 leadlimit=0"; case MAPINFO_TYPE_CTS: return "timelimit=20 skill=-1"; + case MAPINFO_TYPE_FREEZETAG: return "timelimit=20 pointlimit=10 teams=2 leadlimit=0"; default: return ""; } } @@ -650,6 +653,7 @@ float MapInfo_Type_FromString(string t) else if(t == "rc") return MAPINFO_TYPE_RACE; else if(t == "nexball") return MAPINFO_TYPE_NEXBALL; else if(t == "cts") return MAPINFO_TYPE_CTS; + else if(t == "freezetag") return MAPINFO_TYPE_FREEZETAG; else if(t == "all") return MAPINFO_TYPE_ALL; else return 0; } @@ -670,6 +674,7 @@ string MapInfo_Type_ToString(float t) else if(t == MAPINFO_TYPE_RACE) return "rc"; else if(t == MAPINFO_TYPE_NEXBALL) return "nexball"; else if(t == MAPINFO_TYPE_CTS) return "cts"; + else if(t == MAPINFO_TYPE_FREEZETAG) return "freezetag"; else if(t == MAPINFO_TYPE_ALL) return "all"; else return ""; } @@ -1134,6 +1139,8 @@ float MapInfo_CurrentGametype() return MAPINFO_TYPE_NEXBALL; else if(cvar("g_cts")) return MAPINFO_TYPE_CTS; + else if(cvar("g_freezetag")) + return MAPINFO_TYPE_FREEZETAG; else return MAPINFO_TYPE_DEATHMATCH; } @@ -1174,6 +1181,7 @@ string MapInfo_GetGameTypeCvar(float t) case MAPINFO_TYPE_ONSLAUGHT: return "g_onslaught"; case MAPINFO_TYPE_RACE: return "g_race"; case MAPINFO_TYPE_NEXBALL: return "g_nexball"; + case MAPINFO_TYPE_FREEZETAG: return "g_freezetag"; case MAPINFO_TYPE_CTS: return "g_cts"; default: return ""; } @@ -1196,6 +1204,7 @@ void MapInfo_SwitchGameType(float t) cvar_set("g_race", (t == MAPINFO_TYPE_RACE) ? "1" : "0"); cvar_set("g_nexball", (t == MAPINFO_TYPE_NEXBALL) ? "1" : "0"); cvar_set("g_cts", (t == MAPINFO_TYPE_CTS) ? "1" : "0"); + cvar_set("g_freezetag", (t == MAPINFO_TYPE_FREEZETAG) ? "1" : "0"); } void MapInfo_LoadMap(string s) diff --git a/qcsrc/common/mapinfo.qh b/qcsrc/common/mapinfo.qh index 1fcc0dc662..641de1b8a1 100644 --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@ -12,7 +12,8 @@ float MAPINFO_TYPE_KEYHUNT = 1024; float MAPINFO_TYPE_ASSAULT = 2048; float MAPINFO_TYPE_ONSLAUGHT = 4096; float MAPINFO_TYPE_NEXBALL = 8192; -float MAPINFO_TYPE_ALL = 16383; // this has to include all above bits +float MAPINFO_TYPE_FREEZETAG = 16384; +float MAPINFO_TYPE_ALL = 32767; // this has to include all above bits float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps diff --git a/qcsrc/common/util.qc b/qcsrc/common/util.qc index e4db850a9f..76d602dec9 100644 --- a/qcsrc/common/util.qc +++ b/qcsrc/common/util.qc @@ -399,6 +399,7 @@ string GametypeNameFromType(float g) else if (g == GAME_RACE) return "rc"; else if (g == GAME_NEXBALL) return "nexball"; else if (g == GAME_CTS) return "cts"; + else if (g == GAME_FREEZETAG) return "freezetag"; return "dm"; } diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_create.c b/qcsrc/menu/xonotic/dialog_multiplayer_create.c index 4fb8ac0ca5..792be64779 100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer_create.c +++ b/qcsrc/menu/xonotic/dialog_multiplayer_create.c @@ -44,13 +44,15 @@ void XonoticServerCreateTab_fill(entity me) me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_cts", "Race CTS")); if(e.checked) e0 = NULL; me.TR(me); - n = 8; + n = 9; me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_tdm", "TDM")); if(e.checked) e0 = NULL; me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_ctf", "CTF")); if(e.checked) e0 = NULL; me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_ca", "CA")); if(e.checked) e0 = NULL; + me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_freezetag", "Freeze Tag")); + if(e.checked) e0 = NULL; me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_domination", "Domination")); if(e.checked) e0 = NULL; me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_keyhunt", "Key Hunt")); diff --git a/qcsrc/server/arena.qc b/qcsrc/server/arena.qc index 046743151d..12f25fad50 100644 --- a/qcsrc/server/arena.qc +++ b/qcsrc/server/arena.qc @@ -24,6 +24,14 @@ void func_breakable_reset(); void assault_objective_reset(); void target_assault_roundend_reset(); +float next_round; +float stopalivecheck; +float redalive, bluealive, yellowalive, pinkalive; +float totalalive; +.float redalive_stat, bluealive_stat, yellowalive_stat, pinkalive_stat; +float redspawned, bluespawned, yellowspawned, pinkspawned; +float totalspawned; + /** * Resets the state of all clients, items, flags, runes, keys, weapons, waypoints, ... of the map. * Sets the 'warmup' global variable. @@ -39,6 +47,10 @@ void reset_map(float dorespawn) warmup = time + cvar("g_ca_warmup"); allowed_to_spawn = 1; } + else if(g_freezetag) + { + warmup = time + cvar("g_freezetag_warmup"); + } lms_lowest_lives = 999; lms_next_place = player_count; @@ -46,7 +58,7 @@ void reset_map(float dorespawn) race_ReadyRestart(); for(self = world; (self = nextent(self)); ) - if(clienttype(self) == CLIENTTYPE_NOTACLIENT) + if(clienttype(self) == CLIENTTYPE_NOTACLIENT && self.items != IT_STRENGTH && self.items != IT_INVINCIBLE) // don't respawn strength or shield, that will only lead to them spawning very early each match { if(self.reset) { @@ -92,6 +104,11 @@ void reset_map(float dorespawn) self.classname = "player"; PutClientInServer(); } + else if(g_freezetag) + { + if(self.classname == "player") + PutClientInServer(); + } else { /* @@ -192,7 +209,7 @@ void Arena_Warmup() float f; string msg; - if((!g_arena && !g_ca) || (g_arena && !arena_roundbased) || (time < game_starttime)) + if((!g_arena && !g_ca && !g_freezetag) || (g_arena && !arena_roundbased) || (time < game_starttime)) return; f = ceil(warmup - time); @@ -256,14 +273,99 @@ void Arena_Warmup() } } - if(self.classname == "player" && self.health > 0) + if(self.classname == "player" && self.health > 0 && self.movetype == MOVETYPE_NONE) self.movetype = MOVETYPE_WALK; } -float next_round; -float stopalivecheck; -float redalive, bluealive; -.float redalive_stat, bluealive_stat; +void count_spawned_players() +{ + // TODO fix "*spawned" name, it should rather be "*players" or so + // not doing this now to prevent merge hell with Tag + // fix after merging with Tag + + // count amount of players in each team + totalspawned = redspawned = bluespawned = yellowspawned = pinkspawned = 0; + FOR_EACH_PLAYER(self) { + if (self.team == COLOR_TEAM1) + { + redspawned += 1; + totalspawned += 1; + } + else if (self.team == COLOR_TEAM2) + { + bluespawned += 1; + totalspawned += 1; + } + else if (self.team == COLOR_TEAM3) + { + yellowspawned += 1; + totalspawned += 1; + } + else if (self.team == COLOR_TEAM4) + { + pinkspawned += 1; + totalspawned += 1; + } + } +} + +void count_alive_players() +{ + totalalive = redalive = bluealive = yellowalive = pinkalive = 0; + if(g_ca) + { + FOR_EACH_PLAYER(self) { + if (self.team == COLOR_TEAM1 && self.health >= 1) + { + redalive += 1; + totalalive += 1; + } + else if (self.team == COLOR_TEAM2 && self.health >= 1) + { + bluealive += 1; + totalalive += 1; + } + } + FOR_EACH_PLAYER(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 == COLOR_TEAM1 && self.freezetag_frozen == 0 && self.health >= 1) + { + redalive += 1; + totalalive += 1; + } + else if (self.team == COLOR_TEAM2 && self.freezetag_frozen == 0 && self.health >= 1) + { + bluealive += 1; + totalalive += 1; + } + else if (self.team == COLOR_TEAM3 && self.freezetag_frozen == 0 && self.health >= 1) + { + yellowalive += 1; + totalalive += 1; + } + else if (self.team == COLOR_TEAM4 && self.freezetag_frozen == 0 && self.health >= 1) + { + pinkalive += 1; + totalalive += 1; + } + } + FOR_EACH_PLAYER(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 @@ -273,33 +375,15 @@ float redalive, bluealive; */ void Spawnqueue_Check() { - if(g_ca) // we want to perform this before the return block below... + count_spawned_players(); + if(g_ca || g_freezetag) // we want to perform this before the return block below (CA)... { - // this is STUPID to perform again, but has to be done so that we can give instant feedback when a round ends - // and so the code won't start searching for a champion using find() before all players are actually REMOVED - redalive = 0; bluealive = 0; - FOR_EACH_PLAYER(self) { - if (self.team == COLOR_TEAM1 && self.health >= 1) redalive += 1; - else if (self.team == COLOR_TEAM2 && self.health >= 1) bluealive += 1; - } - // as if the above stuff wasn't stupid enough, let's run it a third time! :D - // (so that we can send redalive/bluealive as a stat) - FOR_EACH_PLAYER(self) { - self.redalive_stat = redalive; - self.bluealive_stat = bluealive; - } + count_alive_players(); } - if(time < warmup + 1 || inWarmupStage) + if(time < warmup + 1 || inWarmupStage || intermission_running) return; if(g_ca) { - // check the amount of spawned players in each team - float redspawned, bluespawned; - FOR_EACH_PLAYER(self) { - if (self.team == COLOR_TEAM1) redspawned += 1; - else if (self.team == COLOR_TEAM2) bluespawned += 1; - } - required_ca_players = max(2, fabs(cvar("bot_vs_human") + 1)); if(ca_players < required_ca_players && (redspawned && bluespawned)) { @@ -348,6 +432,12 @@ void Spawnqueue_Check() 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) diff --git a/qcsrc/server/bot/aim.qc b/qcsrc/server/bot/aim.qc index d1c9a5a402..e6950dd3e4 100644 --- a/qcsrc/server/bot/aim.qc +++ b/qcsrc/server/bot/aim.qc @@ -109,6 +109,10 @@ float bot_shouldattack(entity e) return FALSE; } + if(g_freezetag) + if(e.freezetag_frozen) + return FALSE; + if(teams_matter) { if(e.team==0) diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 68f699f371..3c4f8237ae 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -1347,6 +1347,10 @@ void ClientKill (void) { // do nothing } + else if(g_freezetag && self.freezetag_frozen == 1) + { + // do nothing + } else ClientKill_TeamChange(0); } diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc index e9d1932302..4f244a8f5a 100644 --- a/qcsrc/server/cl_physics.qc +++ b/qcsrc/server/cl_physics.qc @@ -46,6 +46,9 @@ When you press the jump key */ void PlayerJump (void) { + if(g_freezetag && self.freezetag_frozen) + return; // no jumping in freezetag when frozen + float mjumpheight; float doublejump; diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index cb20a91a4f..a7296006e2 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -386,6 +386,8 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float } void ClientKill_Now_TeamChange(); +void freezetag_CheckWinner(); +void freezetag_Unfreeze(); void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) { @@ -601,22 +603,37 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht } } - // become fully visible - self.alpha = 1; - // clear selected player display - ClearSelectedPlayer(); - // throw a weapon - SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon); + if(!g_freezetag) + { + // become fully visible + self.alpha = 1; + // clear selected player display + ClearSelectedPlayer(); + // 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(); DropAllRunes(self); + 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; MUTATOR_CALLHOOK(PlayerDies); + if(g_freezetag) + return; + if(self.flagcarried) { if(attacker.classname != "player" && attacker.classname != "gib") diff --git a/qcsrc/server/cl_weapons.qc b/qcsrc/server/cl_weapons.qc index 477b641ba7..5b765396c4 100644 --- a/qcsrc/server/cl_weapons.qc +++ b/qcsrc/server/cl_weapons.qc @@ -310,7 +310,10 @@ void W_WeaponFrame() if (frametime) self.weapon_frametime = frametime; - if(((arena_roundbased || g_ca) && time < warmup) || ((time < game_starttime) && !cvar("sv_ready_restart_after_countdown"))) + if(((arena_roundbased || g_ca || g_freezetag) && time < warmup) || ((time < game_starttime) && !cvar("sv_ready_restart_after_countdown"))) + return; + + if(g_freezetag && self.freezetag_frozen == 1) return; if (!self.weaponentity || self.health < 1) diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh index 6d967e6566..29e0cab024 100644 --- a/qcsrc/server/defs.qh +++ b/qcsrc/server/defs.qh @@ -17,7 +17,7 @@ float require_spawnfunc_prefix; // if this float exists, only functions with spa float ctf_score_value(string parameter); -float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts; +float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts, g_freezetag; float g_cloaked, g_footsteps, g_jump_grunt, g_grappling_hook, g_midair, g_minstagib, g_pinata, g_norecoil, g_minstagib_invis_alpha, g_bloodloss; float g_warmup_limit; float g_warmup_allguns; @@ -654,3 +654,7 @@ float allowed_to_spawn; // boolean variable used by the clan arena code to deter float serverflags; .float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator + +.float freezetag_frozen; +.float freezetag_beginrevive_time; +.float freezetag_revive_progress; diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index cde87fec17..7af3a866ab 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -528,6 +528,15 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float if (attacker.isbot) damage = damage * bound(0.1, (skill + 5) * 0.1, 1); + if(g_freezetag) + { + if(targ.freezetag_frozen == 1) + { + damage = 0; + force = force * cvar("g_freezetag_frozen_force"); + } + } + // nullify damage if teamplay is on if(deathtype != DEATH_TELEFRAG) if(attacker.classname == "player") diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 1b9596909d..f26b2d6e3f 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -809,11 +809,19 @@ void spawnfunc_worldspawn (void) addstat(STAT_NEX_CHARGE, AS_FLOAT, nex_charge); addstat(STAT_NEX_CHARGEPOOL, AS_FLOAT, nex_charge_pool_ammo); - if(g_ca) + 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); diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc new file mode 100644 index 0000000000..6682158507 --- /dev/null +++ b/qcsrc/server/mutators/gamemode_freezetag.qc @@ -0,0 +1,285 @@ +void freezetag_Initialize() +{ + precache_model("models/ice/ice.md3"); + warmup = time + cvar("g_start_delay") + cvar("g_freezetag_warmup"); +} + +void freezetag_CheckWinner() +{ + if(time <= game_starttime) // game didn't even start yet! nobody can win in that case. + return; + + if(next_round || (time > warmup - cvar("g_freezetag_warmup") && time < warmup)) + return; // already waiting for next round to start + + if((redalive >= 1 && bluealive >= 1) // counted in arena.qc + || (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 + + entity e, winner; + string teamname; + + FOR_EACH_PLAYER(e) + { + if(e.freezetag_frozen == 0 && e.classname == "player" && e.health >= 1) // here's one player from the winning team... good + { + winner = e; + break; // break, we found the winner + } + } + + if(winner != world) // just in case a winner wasn't found + { + TeamScore_AddToTeam(winner.team, ST_SCORE, +1); + if(winner.team == COLOR_TEAM1) + teamname = "^1Red Team"; + else if(winner.team == COLOR_TEAM2) + teamname = "^4Blue Team"; + else if(winner.team == COLOR_TEAM3) + teamname = "^3Yellow Team"; + else + teamname = "^6Pink Team"; + FOR_EACH_PLAYER(e) { + centerprint(e, strcat(teamname, "^5 wins the round, all other teams were frozen.\n")); + } + bprint(teamname, "^5 wins the round since all the other teams were frozen.\n"); + } + + next_round = time + 5; +} + +void freezetag_Ice_Think() +{ + setorigin(self, self.owner.origin - '0 0 16'); + self.nextthink = time; +} + +void freezetag_Freeze() +{ + self.freezetag_frozen = 1; + + entity ice; + ice = spawn(); + ice.owner = self; + ice.classname = "freezetag_ice"; + ice.think = freezetag_Ice_Think; + ice.nextthink = time; + ice.frame = floor(random() * 21); // ice model has 20 different looking frames + setmodel(ice, "models/ice/ice.md3"); + + self.movement = '0 0 0'; + + // add waypoint + WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE); + if(self.waypointsprite_attached) + { + WaypointSprite_UpdateTeamRadar(self.waypointsprite_attached, RADARICON_WAYPOINT, '0.25 0.90 1'); + } +} + +void freezetag_Unfreeze() +{ + self.freezetag_frozen = 0; + + // remove the ice block + entity ice; + for(ice = world; (ice = find(ice, classname, "freezetag_ice")); ) if(ice.owner == self) + { + remove(ice); + break; + } + + // remove waypoint + if(self.waypointsprite_attached) + WaypointSprite_Kill(self.waypointsprite_attached); +} + +MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer) +{ + if(self.freezetag_frozen == 0) + { + if(self.team == COLOR_TEAM1) + --redalive; + else if(self.team == COLOR_TEAM2) + --bluealive; + else if(self.team == COLOR_TEAM3) + --yellowalive; + else if(self.team == COLOR_TEAM4) + --pinkalive; + --totalalive; + } + + if(totalspawned > 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(); + + freezetag_Unfreeze(); + + return 1; +} + +MUTATOR_HOOKFUNCTION(freezetag_PlayerDies) +{ + if(self.freezetag_frozen == 0) + { + if(self.team == COLOR_TEAM1) + --redalive; + else if(self.team == COLOR_TEAM2) + --bluealive; + else if(self.team == COLOR_TEAM3) + --yellowalive; + else if(self.team == COLOR_TEAM4) + --pinkalive; + --totalalive; + + freezetag_Freeze(); + } + + if(frag_attacker.classname == STR_PLAYER) + centerprint(frag_attacker, strcat("^2You froze ^7", frag_target.netname, ".\n")); + + if(frag_attacker == frag_target || frag_attacker == world) + { + if(frag_target.classname == STR_PLAYER) + centerprint(frag_target, "^1You froze yourself.\n"); + bprint("^7", frag_target.netname, "^1 froze himself.\n"); + } + else + { + if(frag_target.classname == STR_PLAYER) + centerprint(frag_target, strcat("^1You were frozen by ^7", frag_attacker.netname, ".\n")); + bprint("^7", frag_target.netname, "^1 was frozen by ^7", frag_attacker.netname, ".\n"); + } + + frag_target.health = cvar("g_balance_health_start"); // "respawn" the player :P + + freezetag_CheckWinner(); + + return 1; +} + +MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn) +{ + freezetag_Unfreeze(); // start by making sure that all ice blocks are removed + + if(totalspawned == 1 && time > game_starttime) // only one player active on server, start a new match immediately + if(!next_round && warmup && (time < warmup - cvar("g_freezetag_warmup") || time > warmup)) // not awaiting next round + { + next_round = time; + return 1; + } + if(warmup && time > warmup) // spawn too late, freeze player + { + centerprint(self, "^1You spawned after the round started, you'll spawn as frozen.\n"); + freezetag_Freeze(); + } + + return 1; +} + +MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill) +{ + frag_score = 0; // no frags counted in Freeze Tag + return 1; +} + +MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink) +{ + vector revive_extra_size; + revive_extra_size = '1 1 1' * cvar("g_freezetag_revive_extra_size"); + + float teammate_nearby; + FOR_EACH_PLAYER(other) if(self != other) + { + if(other.freezetag_frozen == 0) + { + if(other.team == self.team) + { + teammate_nearby = boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax); + if(teammate_nearby) + break; + } + } + } + + if(teammate_nearby && self.freezetag_frozen == 1) // OK, there is at least one teammate reviving us + { + if(self.freezetag_beginrevive_time == -9999) // initialize values if this is the first frame of revival + { + self.freezetag_beginrevive_time = time; + self.freezetag_revive_progress = 0; + } + else + { + self.freezetag_revive_progress = (time - self.freezetag_beginrevive_time) / cvar("g_freezetag_revive_time"); + if(time - self.freezetag_beginrevive_time >= cvar("g_freezetag_revive_time")) + { + freezetag_Unfreeze(); + + centerprint(self, strcat("^5You were revived by ^7", other.netname, ".\n")); + centerprint(other, strcat("^5You revived ^7", self.netname, ".\n")); + bprint("^7", other.netname, "^5 revived ^7", self.netname, ".\n"); + + self.freezetag_beginrevive_time = -9999; + self.freezetag_revive_progress = 0; + } + } + // now find EVERY teammate within reviving radius, set their revive_progress values correct + FOR_EACH_PLAYER(other) if(self != other) + { + if(other.freezetag_frozen == 0) + { + if(other.team == self.team) + { + teammate_nearby = boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax); + if(teammate_nearby) + other.freezetag_revive_progress = self.freezetag_revive_progress; + } + } + } + } + else if(!teammate_nearby) // only if no teammate is nearby will we reset + { + self.freezetag_beginrevive_time = -9999; + self.freezetag_revive_progress = 0; + } + + return 1; +} + +MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics) +{ + if(self.freezetag_frozen) + self.movement = '0 0 0'; + return 1; +} + +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(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST); + MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST); + MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, 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."); + g_freezetag = 1; + freezetag_Initialize(); + } + + MUTATOR_ONREMOVE + { + g_freezetag = 0; + error("This is a game type and it cannot be removed at runtime."); + } + + return 0; +} diff --git a/qcsrc/server/mutators/mutators.qh b/qcsrc/server/mutators/mutators.qh index 542014e869..a57db07fd2 100644 --- a/qcsrc/server/mutators/mutators.qh +++ b/qcsrc/server/mutators/mutators.qh @@ -1,4 +1,5 @@ MUTATOR_DECLARATION(gamemode_keyhunt); +MUTATOR_DECLARATION(gamemode_freezetag); MUTATOR_DECLARATION(mutator_nix); MUTATOR_DECLARATION(mutator_dodging); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 58cf663390..3c1e4f521f 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -181,6 +181,7 @@ playerstats.qc mutators/base.qc mutators/gamemode_keyhunt.qc +mutators/gamemode_freezetag.qc mutators/mutator_nix.qc mutators/mutator_dodging.qc mutators/mutator_rocketflying.qc diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index b9064c783b..003ca27427 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -101,6 +101,7 @@ void WriteGameCvars() cvar_set("g_race", ftos(g_race)); cvar_set("g_nexball", ftos(g_nexball)); cvar_set("g_cts", ftos(g_cts)); + cvar_set("g_freezetag", ftos(g_freezetag)); } void ReadGameCvars() @@ -127,6 +128,7 @@ void ReadGameCvars() found += (g_race = (!found && (prev != GAME_RACE) && cvar("g_race"))); found += (g_nexball = (!found && (prev != GAME_NEXBALL) && cvar("g_nexball"))); found += (g_cts = (!found && (prev != GAME_CTS) && cvar("g_cts"))); + found += (g_freezetag = (!found && (prev != GAME_FREEZETAG) && cvar("g_freezetag"))); if(found) break; @@ -317,6 +319,16 @@ void InitGameplayMode() MUTATOR_ADD(gamemode_keyhunt); } + if(g_freezetag) + { + game = GAME_FREEZETAG; + gamemode_name = "Freeze Tag"; + ActivateTeamplay(); + fraglimit_override = cvar("g_freezetag_point_limit"); + leadlimit_override = cvar("g_freezetag_point_leadlimit"); + MUTATOR_ADD(gamemode_freezetag); + } + if(g_assault) { game = GAME_ASSAULT; @@ -816,6 +828,8 @@ float FindSmallestTeam(entity pl, float ignore_pl) error("Too few teams available for ctf\n"); else if(g_keyhunt) error("Too few teams available for key hunt\n"); + else if(g_freezetag) + error("Too few teams available for freeze tag\n"); else error("Too few teams available for team deathmatch\n"); } diff --git a/qcsrc/server/w_electro.qc b/qcsrc/server/w_electro.qc index 11f97f0706..e7716feaf2 100644 --- a/qcsrc/server/w_electro.qc +++ b/qcsrc/server/w_electro.qc @@ -232,7 +232,7 @@ void lgbeam_think() remove(self); return; } - if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK) + if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || (g_freezetag && self.owner.freezetag_frozen)) { if(self == self.owner.lgbeam) self.owner.lgbeam = world;