From dad82c7f9f54119c1011b94dadfd0ab05ca35804 Mon Sep 17 00:00:00 2001 From: lordhavoc Date: Wed, 4 Jul 2007 22:29:22 +0000 Subject: [PATCH] added onslaught mode git-svn-id: svn://svn.icculus.org/nexuiz/trunk@2780 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- data/default.cfg | 3 + data/game_reset.cfg | 2 + data/qcsrc/server/g_world.qc | 42 +++ data/qcsrc/server/mode_onslaught.qc | 477 ++++++++++++++++++++++++++++ data/qcsrc/server/progs.src | 1 + data/qcsrc/server/teamplay.qc | 31 +- pro/default.cfg | 3 + 7 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 data/qcsrc/server/mode_onslaught.qc diff --git a/data/default.cfg b/data/default.cfg index f84ee9792..493fb7f6f 100644 --- a/data/default.cfg +++ b/data/default.cfg @@ -393,6 +393,9 @@ set g_arena_roundbased 1 // if disabled, the next player will spawn as soon as s set g_arena_warmup 5 // time, newly spawned players have to prepare themselves in round based matches set g_arena_powerups 0 // enables powerups (superhealth, strength and shield), which are removed by default +// onslaught +set g_onslaught 0 + // server game balance settings set g_balance_armor_regen 0 set g_balance_armor_rot 0.1 diff --git a/data/game_reset.cfg b/data/game_reset.cfg index 534e973d5..071fac76b 100644 --- a/data/game_reset.cfg +++ b/data/game_reset.cfg @@ -15,6 +15,8 @@ set g_lms 0 set g_arena 0 set g_campaign 0 set g_keyhunt 0 +set g_assault 0 +set g_onslaught 0 set teamplay 0 set gamecfg 0 diff --git a/data/qcsrc/server/g_world.qc b/data/qcsrc/server/g_world.qc index 7ce20714a..74b221184 100644 --- a/data/qcsrc/server/g_world.qc +++ b/data/qcsrc/server/g_world.qc @@ -384,6 +384,8 @@ string GetGametype() return "lms"; else if (game == GAME_KEYHUNT) return "kh"; + else if (game == GAME_ONSLAUGHT) + return "ons"; return "dm"; } @@ -1073,6 +1075,42 @@ void(void) ClearWinners = head.winning = 0; } +// Onslaught winning condition: +// game terminates if only one team has a working generator (or none) +float() WinningCondition_Onslaught = +{ + entity head; + local float t1, t2, t3, t4; + // first check if the game has ended + t1 = t2 = t3 = t4 = 0; + head = find(world, classname, "onslaught_generator"); + while (head) + { + if (head.health > 0) + { + if (head.team == COLOR_TEAM1) t1 = 1; + if (head.team == COLOR_TEAM2) t2 = 1; + if (head.team == COLOR_TEAM3) t3 = 1; + if (head.team == COLOR_TEAM4) t4 = 1; + } + head = find(head, classname, "onslaught_generator"); + } + if (t1 + t2 + t3 + t4 < 2) + { + // game over, only one team remains (or none) + ClearWinners(); + if (t1) SetWinners(team, COLOR_TEAM1); + if (t2) SetWinners(team, COLOR_TEAM2); + if (t3) SetWinners(team, COLOR_TEAM3); + if (t4) SetWinners(team, COLOR_TEAM4); + dprint("Have a winner, ending game.\n"); + return WINNING_YES; + } + + // Two or more teams remain + return WINNING_NO; +} + float() LMS_NewPlayerLives = { float fl; @@ -1499,6 +1537,10 @@ void() CheckRules_World = { status = WinningCondition_LMS(); } + else if (cvar("g_onslaught")) + { + status = WinningCondition_Onslaught(); + } else { if(teams_matter) diff --git a/data/qcsrc/server/mode_onslaught.qc b/data/qcsrc/server/mode_onslaught.qc new file mode 100644 index 000000000..6bd8c92bd --- /dev/null +++ b/data/qcsrc/server/mode_onslaught.qc @@ -0,0 +1,477 @@ + +.string target2; +.float iscaptured; +.float islinked; +.float isshielded; +void() onslaught_updatelinks = +{ + local entity l, links; + local float stop, t1, t2, t3, t4; + // first check if the game has ended + t1 = t2 = t3 = t4 = 0; + l = findchain(classname, "onslaught_generator"); + while (l) + { + if (l.iscaptured) + { + if (l.team == COLOR_TEAM1) t1 = 1; + if (l.team == COLOR_TEAM2) t2 = 1; + if (l.team == COLOR_TEAM3) t3 = 1; + if (l.team == COLOR_TEAM4) t4 = 1; + } + l = l.chain; + } + if (t1 + t2 + t3 + t4 < 2) + { + // game over, only one team remains (or none) + return; + } + // mark generators as being shielded and networked + l = findchain(classname, "onslaught_generator"); + while (l) + { + l.islinked = l.iscaptured; + l.isshielded = l.iscaptured; + l = l.chain; + } + // mark points as shielded and not networked + l = findchain(classname, "onslaught_controlpoint"); + while (l) + { + l.islinked = FALSE; + l.isshielded = TRUE; + l = l.chain; + } + // flow power outward from the generators through the network + links = findchain(classname, "onslaught_link"); + stop = TRUE; + while (stop) + { + stop = FALSE; + l = links; + while (l) + { + // if both points are captured by the same team, and only one of + // them is powered, mark the other one as powered as well + if (l.enemy.team == l.goalentity.team) + { + if (l.goalentity.islinked) + { + if (l.enemy.iscaptured) + if (!l.enemy.islinked) + { + stop = FALSE; + l.enemy.islinked = TRUE; + } + } + else + { + if (l.goalentity.iscaptured) + if (!l.goalentity.islinked) + { + stop = FALSE; + l.goalentity.islinked = TRUE; + } + } + } + l = l.chain; + } + } + // now that we know which points are powered we can mark their neighbors + // as unshielded if team differs + l = links; + while (l) + { + if (l.goalentity.team != l.enemy.team) + { + if (l.goalentity.islinked) + l.enemy.isshielded = FALSE; + if (l.enemy.islinked) + l.goalentity.isshielded = FALSE; + } + l = l.chain; + } + // now update the takedamage and alpha variables on generator shields + l = findchain(classname, "onslaught_generator"); + while (l) + { + if (l.isshielded) + { + l.enemy.alpha = -1; + l.takedamage = DAMAGE_AIM; + } + else + { + l.enemy.alpha = -1; + l.takedamage = DAMAGE_AIM; + } + l = l.chain; + } + // now update the takedamage and alpha variables on control point icons + l = findchain(classname, "onslaught_controlpoint"); + while (l) + { + if (l.isshielded) + { + l.enemy.alpha = -1; + if (l.goalentity) + l.goalentity.takedamage = DAMAGE_AIM; + } + else + { + l.enemy.alpha = -1; + if (l.goalentity) + l.goalentity.takedamage = DAMAGE_AIM; + } + l = l.chain; + } +}; + +void() onslaught_generator_think = +{ + self.nextthink = ceil(time + 1); + if (cvar("timelimit")) + if (time > cvar("timelimit") * 60 - 60) + { + // self.max_health / 120 gives 2 minutes of overtime + sound(self, CHAN_AUTO, "sound/onslaught/generator_decay.wav", 1, ATTN_NORM); + Damage(self, self, self, self.max_health / 60, DEATH_HURTTRIGGER, self.origin, '0 0 0'); + } +}; + +void() onslaught_generator_deaththink = +{ + local vector org; + if (self.count > 0) + { + self.nextthink = time + 0.1; + self.count = self.count - 1; + org = randompos(self.origin + self.mins + '8 8 8', self.origin + self.maxs + '-8 -8 -8'); + pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1); + sound(self, CHAN_AUTO, "sound/weapons/grenade_impact.wav", 1, ATTN_NORM); + } + else + { + org = self.origin; + pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1); + sound(self, CHAN_AUTO, "sound/weapons/rocket_impact.wav", 1, ATTN_NORM); + } +}; + +void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) onslaught_generator_damage = +{ + if (damage <= 0) + return; + if (attacker != self) + { + if (self.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (attacker.classname == "player") + { + play2(attacker, "sound/onslaught/damageblockedbyshield.wav"); + self.pain_finished = time + 1; + } + return; + } + if (time > self.pain_finished) + { + self.pain_finished = time + 5; + bprint(ColoredTeamName(self.team), " generator under attack!\n"); + play2team(self.team, "sound/onslaught/generator_underattack.wav"); + } + } + self.health = self.health - damage; + // choose an animation frame based on health + self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1); + // see if the generator is still functional, or dying + if (self.health > 0) + bprint(ColoredTeamName(self.team), " generator has ", ftos(floor(self.health)), " health remaining\n"); + else + { + if (attacker == self) + bprint(ColoredTeamName(self.team), " generator spontaneously exploded due to overtime!\n"); + else + bprint(ColoredTeamName(self.team), " generator destroyed by ", ColoredTeamName(attacker.team), "!\n"); + self.iscaptured = FALSE; + self.islinked = FALSE; + self.isshielded = FALSE; + self.takedamage = DAMAGE_NO; // can't be hurt anymore + self.event_damage = SUB_Null; // won't do anything if hurt + self.count = 30; // 30 explosions + self.think = onslaught_generator_deaththink; // explosion sequence + self.nextthink = time; // start exploding immediately + self.think(); // do the first explosion now + onslaught_updatelinks(); + } +}; + +// update links after a delay +void() onslaught_generator_delayed = +{ + onslaught_updatelinks(); + // now begin normal thinking + self.think = onslaught_generator_think; + self.nextthink = time; +}; + +/*QUAKED onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) +Base generator. + +onslaught_link entities can target this. + +keys: +"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. +"targetname" - name that onslaught_link entities will use to target this. +*/ +void() onslaught_generator = +{ + if (!cvar("g_onslaught")) + { + remove(self); + return; + } + local entity e; + precache_model("models/onslaught/generator.md3"); + precache_model("models/onslaught/generator_shield.md3"); + precache_sound("sound/onslaught/generator_decay.wav"); + precache_sound("sound/weapons/grenade_impact.wav"); + precache_sound("sound/weapons/rocket_impact.wav"); + precache_sound("sound/onslaught/generator_underattack.wav"); + if (!self.team) + objerror("team must be set"); + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_NONE; + self.max_health = self.health = 1000; + setmodel(self, "models/onslaught/generator.md3"); + //setsize(self, '-32 -32 -24', '32 32 64'); + setorigin(self, self.origin); + self.takedamage = DAMAGE_AIM; + self.event_damage = onslaught_generator_damage; + self.iscaptured = TRUE; + self.islinked = TRUE; + self.isshielded = TRUE; + // spawn shield model which indicates whether this can be damaged + self.enemy = e = spawn(); + e.solid = SOLID_NOT; + e.movetype = MOVETYPE_NONE; + e.effects = EF_ADDITIVE; + setmodel(e, "models/onslaught/generator_shield.md3"); + //setsize(e, '-32 -32 0', '32 32 128'); + setorigin(e, self.origin); + e.colormap = self.colormap; + self.think = onslaught_generator_delayed; + self.nextthink = time + 0.2; +}; + +void(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) onslaught_controlpoint_icon_damage = +{ + if (damage <= 0) + return; + if (self.owner.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (attacker.classname == "player") + { + play2(attacker, "sound/onslaught/damageblockedbyshield.wav"); + self.pain_finished = time + 1; + } + return; + } + if (time > self.pain_finished) + if (attacker.classname == "player") + { + play2team(self.team, "sound/onslaught/controlpoint_underattack.wav"); + self.pain_finished = time + 5; + } + self.health = self.health - damage; + self.alpha = self.health / self.max_health; + self.pain_finished = time + 1; + // colormod flash when shot + self.colormod = '2 2 2'; + if (self.health < 0) + { + sound(self, CHAN_AUTO, "sound/weapons/grenade_impact.wav", 1, ATTN_NORM); + pointparticles(particleeffectnum("onslaught_controlpoint_explosion"), self.origin, '0 0 0', 1); + bprint(ColoredTeamName(self.team), " ", self.message, " control point destroyed by ", ColoredTeamName(attacker.team), "\n"); + self.owner.goalentity = world; + self.owner.islinked = FALSE; + self.owner.iscaptured = FALSE; + self.owner.team = 0; + self.owner.colormap = 1024; + onslaught_updatelinks(); + remove(self); + } +}; + +void() onslaught_controlpoint_icon_think = +{ + self.nextthink = time + 0.1; + if (time > self.pain_finished + 1) + { + self.health = self.health + self.count; + if (self.health >= self.max_health) + self.health = self.max_health; + } + self.alpha = self.health / self.max_health; + // colormod flash when shot + self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); +}; + +void() onslaught_controlpoint_icon_buildthink = +{ + self.nextthink = time + 0.1; + self.health = self.health + self.count; + if (self.health >= self.max_health) + { + self.health = self.max_health; + self.count = self.count * 0.2; // slow repair rate from now on + self.think = onslaught_controlpoint_icon_think; + sound(self, CHAN_BODY, "sound/onslaught/controlpoint_built.wav", 1, ATTN_NORM); + bprint(ColoredTeamName(self.team), " captured ", self.owner.message, " control point\n"); + self.owner.iscaptured = TRUE; + onslaught_updatelinks(); + } + self.alpha = self.health / self.max_health; + // colormod flash when shot + self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); +}; + +void() onslaught_controlpoint_touch = +{ + local entity e; + if (other.classname != "player") + return; + // if there's already an icon built, nothing happens + if (self.goalentity) + return; + // shielded points are definitely off-limits + if (self.isshielded) + return; + // check to see if this player has a legitimate claim to capture this + // control point - more specifically that there is a captured path of + // points leading back to the team generator + e = findchain(classname, "onslaught_link"); + while (e) + { + if (e.goalentity == self) + { + if (e.enemy.islinked) + if (e.enemy.team == other.team) + break; + } + else if (e.enemy == self) + { + if (e.goalentity.islinked) + if (e.goalentity.team == other.team) + break; + } + e = e.chain; + } + if (!e) + return; + // we've verified that this player has a legitimate claim to this point, + // so start building the captured point icon (which only captures this + // point if it successfully builds without being destroyed first) + self.goalentity = e = spawn(); + e.owner = self; + e.max_health = 300; + e.health = e.max_health * 0.1; + e.alpha = e.health / e.max_health; + e.solid = SOLID_BBOX; + e.movetype = MOVETYPE_NONE; + setmodel(e, "models/onslaught/controlpoint_icon.md3"); + setsize(e, '-32 -32 -32', '32 32 32'); + setorigin(e, self.origin + '0 0 96'); + e.takedamage = DAMAGE_AIM; + e.event_damage = onslaught_controlpoint_icon_damage; + e.team = other.team; + e.think = onslaught_controlpoint_icon_buildthink; + e.nextthink = time + 0.1; + e.count = e.max_health / 50; // how long it takes to build + sound(e, CHAN_BODY, "sound/onslaught/controlpoint_build.wav", 1, ATTN_NORM); + self.team = e.team; + self.colormap = 1024 + (self.team - 1) * 17; +}; + +/*QUAKED onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128) +Control point. Be sure to give this enough clearance so that the shootable part has room to exist + +This should link to an onslaught_controlpoint entity or onslaught_generator entity. + +keys: +"targetname" - name that onslaught_link entities will use to target this. +"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities. +"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc) +*/ +void() onslaught_controlpoint = +{ + local entity e; + if (!cvar("g_onslaught")) + { + remove(self); + return; + } + precache_model("models/onslaught/controlpoint_pad.md3"); + precache_model("models/onslaught/controlpoint_shield.md3"); + precache_model("models/onslaught/controlpoint_icon.md3"); + precache_sound("sound/onslaught/controlpoint_build.wav"); + precache_sound("sound/onslaught/controlpoint_built.wav"); + precache_sound("sound/weapons/grenade_impact.wav"); + precache_sound("sound/onslaught/damageblockedbyshield.wav"); + precache_sound("sound/onslaught/controlpoint_underattack.wav"); + self.solid = SOLID_BSP; + self.movetype = MOVETYPE_NONE; + setmodel(self, "models/onslaught/controlpoint_pad.md3"); + //setsize(self, '-32 -32 0', '32 32 8'); + setorigin(self, self.origin); + self.touch = onslaught_controlpoint_touch; + self.colormap = 1024; + self.iscaptured = FALSE; + self.islinked = FALSE; + self.isshielded = TRUE; + // spawn shield model which indicates whether this can be damaged + self.enemy = e = spawn(); + e.solid = SOLID_NOT; + e.movetype = MOVETYPE_NONE; + e.effects = EF_ADDITIVE; + setmodel(e, "models/onslaught/controlpoint_shield.md3"); + //setsize(e, '-32 -32 0', '32 32 128'); + setorigin(e, self.origin); + e.colormap = self.colormap; + onslaught_updatelinks(); +}; + +void() onslaught_link_delayed = +{ + self.goalentity = find(world, targetname, self.target); + self.enemy = find(world, targetname, self.target2); + if (!self.goalentity) + objerror("can not find target\n"); + if (!self.enemy) + objerror("can not find target2\n"); +} + +/*QUAKED onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) +Link between control points. + +This entity targets two different onslaught_controlpoint or onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. + +keys: +"target" - first control point. +"target2" - second control point. +*/ +void() onslaught_link = +{ + if (!cvar("g_onslaught")) + { + remove(self); + return; + } + if (self.target == "" || self.target2 == "") + objerror("target and target2 must be set\n"); + self.think = onslaught_link_delayed; + self.nextthink = time + 0.1; +}; diff --git a/data/qcsrc/server/progs.src b/data/qcsrc/server/progs.src index 76106028a..aad7fb316 100644 --- a/data/qcsrc/server/progs.src +++ b/data/qcsrc/server/progs.src @@ -76,6 +76,7 @@ t_plats.qc ctf.qc domination.qc +mode_onslaught.qc g_hook.qc t_swamp.qc diff --git a/data/qcsrc/server/teamplay.qc b/data/qcsrc/server/teamplay.qc index 943da63c2..d0e57ddd2 100644 --- a/data/qcsrc/server/teamplay.qc +++ b/data/qcsrc/server/teamplay.qc @@ -7,13 +7,14 @@ float GAME_LMS = 6; float GAME_ARENA = 7; float GAME_KEYHUNT = 8; float GAME_ASSAULT = 9; +float GAME_ONSLAUGHT = 10; // client counts for each team float c1, c2, c3, c4; // # of bots on those teams float cb1, cb2, cb3, cb4; -float g_domination, g_ctf, g_tdm, g_keyhunt; +float g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught; float audit_teams_time; @@ -105,6 +106,7 @@ void ResetGameCvars() cvar_set("g_arena", "0"); cvar_set("g_keyhunt", "0"); cvar_set("g_assault", "0"); + cvar_set("g_onslaught", "0"); cvar_set("teamplay", "0"); } @@ -247,6 +249,17 @@ void InitGameplayMode() gamemode_name = "Key Hunt"; teams_matter = 1; } + else if(game == GAME_ONSLAUGHT || cvar("g_onslaught")) + { + ResetGameCvars(); + game = GAME_ONSLAUGHT; + cvar_set("g_onslaught", "1"); + + ActivateTeamplay(); + + gamemode_name = "Onslaught"; + teams_matter = 1; + } else { // we can only assume... @@ -312,6 +325,7 @@ void InitGameplayMode() g_ctf = cvar("g_ctf"); g_tdm = cvar("g_tdm"); g_keyhunt = cvar("g_keyhunt"); + g_onslaught = cvar("g_onslaught"); } string GetClientVersionMessage(float v) { @@ -520,6 +534,21 @@ void CheckAllowedTeams () c1 = c2 = c3 = c4 = -1; cb1 = cb2 = cb3 = cb4 = 0; + // onslaught is special + if(g_onslaught) + { + head = findchain(classname, "onslaught_generator"); + while (head) + { + if (head.team == COLOR_TEAM1) c1 = 0; + if (head.team == COLOR_TEAM2) c2 = 0; + if (head.team == COLOR_TEAM3) c3 = 0; + if (head.team == COLOR_TEAM4) c4 = 0; + head = head.chain; + } + return; + } + if(g_domination) teament_name = "dom_team"; else if(g_ctf) diff --git a/pro/default.cfg b/pro/default.cfg index 1c2ddfe86..8e5b6f1a8 100644 --- a/pro/default.cfg +++ b/pro/default.cfg @@ -393,6 +393,9 @@ set g_arena_roundbased 1 // if disabled, the next player will spawn as soon as s set g_arena_warmup 5 // time, newly spawned players have to prepare themselves in round based matches set g_arena_powerups 0 // enables powerups (superhealth, strength and shield), which are removed by default +// onslaught +set g_onslaught 0 + // server game balance settings set g_balance_armor_regen 0 set g_balance_armor_rot 0 -- 2.39.2