From: TimePath Date: Sun, 18 Oct 2015 04:21:03 +0000 (+1100) Subject: Mutators: combine headers X-Git-Tag: xonotic-v0.8.2~1801^2~4 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=a0b626ee78db909c214cc0fc7e829f8a8379704f;p=xonotic%2Fxonotic-data.pk3dir.git Mutators: combine headers --- diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index 90be935df..042536aa7 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -13,7 +13,8 @@ #include "../common/nades/all.qh" #include "../common/stats.qh" #include "../lib/csqcmodel/cl_player.qh" -#include "../server/mutators/gamemode_ctf.qh" +// TODO: remove +#include "../server/mutators/mutator/gamemode_ctf.qc" /* diff --git a/qcsrc/client/progs.inc b/qcsrc/client/progs.inc index 0c3884f67..5c110e2b8 100644 --- a/qcsrc/client/progs.inc +++ b/qcsrc/client/progs.inc @@ -67,7 +67,11 @@ #include "../lib/csqcmodel/cl_player.qc" #include "../lib/csqcmodel/interpolate.qc" -#include "../server/mutators/mutator_multijump.qc" +// TODO: move to common +#include "../server/mutators/mutator/mutator_multijump.qc" +#define IMPLEMENTATION +#include "../server/mutators/mutator/mutator_multijump.qc" +#undef IMPLEMENTATION #include "../lib/warpzone/anglestransform.qc" #include "../lib/warpzone/client.qc" diff --git a/qcsrc/common/monsters/sv_monsters.qc b/qcsrc/common/monsters/sv_monsters.qc index bf74dcea0..ac642fd3c 100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@ -11,7 +11,7 @@ #include "../../server/autocvars.qh" #include "../../server/defs.qh" #include "../deathtypes/all.qh" - #include "../../server/mutators/mutators_include.qh" + #include "../../server/mutators/all.qh" #include "../../server/steerlib.qh" #include "../turrets/sv_turrets.qh" #include "../turrets/util.qh" diff --git a/qcsrc/common/notifications.qc b/qcsrc/common/notifications.qc index 600ddaa1b..811e7007c 100644 --- a/qcsrc/common/notifications.qc +++ b/qcsrc/common/notifications.qc @@ -7,7 +7,7 @@ #include "../server/constants.qh" #include "../server/defs.qh" #include "notifications.qh" - #include "../server/mutators/mutators_include.qh" + #include "../server/mutators/all.qh" #endif // ================================================ diff --git a/qcsrc/common/weapons/all.qc b/qcsrc/common/weapons/all.qc index e603cb16c..ffbc0712d 100644 --- a/qcsrc/common/weapons/all.qc +++ b/qcsrc/common/weapons/all.qc @@ -39,7 +39,7 @@ #include "../../server/defs.qh" #include "../notifications.qh" #include "../deathtypes/all.qh" - #include "../../server/mutators/mutators_include.qh" + #include "../../server/mutators/all.qh" #include "../mapinfo.qh" #include "../../server/command/common.qh" #include "../../lib/csqcmodel/sv_model.qh" diff --git a/qcsrc/lib/csqcmodel/cl_player.qc b/qcsrc/lib/csqcmodel/cl_player.qc index ae4e55ac1..c39ee8797 100644 --- a/qcsrc/lib/csqcmodel/cl_player.qc +++ b/qcsrc/lib/csqcmodel/cl_player.qc @@ -28,6 +28,7 @@ #include "../../client/defs.qh" #include "../../client/main.qh" #include "../../common/constants.qh" +#include "../../common/physics.qh" #include "../../common/stats.qh" #include "../../common/triggers/trigger/viewloc.qh" #include "../../common/util.qh" diff --git a/qcsrc/server/bot/aim.qc b/qcsrc/server/bot/aim.qc index 07ea9091d..50e1d456f 100644 --- a/qcsrc/server/bot/aim.qc +++ b/qcsrc/server/bot/aim.qc @@ -4,7 +4,7 @@ #include "../weapons/weaponsystem.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" // traces multiple trajectories to find one that will impact the target // 'end' vector is the place it aims for, diff --git a/qcsrc/server/bot/bot.qc b/qcsrc/server/bot/bot.qc index 95262bcfa..fb7662328 100644 --- a/qcsrc/server/bot/bot.qc +++ b/qcsrc/server/bot/bot.qc @@ -19,7 +19,7 @@ #include "../race.qh" #include "../t_items.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../weapons/accuracy.qh" diff --git a/qcsrc/server/cheats.qc b/qcsrc/server/cheats.qc index 64c7cec19..8f34a7798 100644 --- a/qcsrc/server/cheats.qc +++ b/qcsrc/server/cheats.qc @@ -4,7 +4,7 @@ #include "race.qh" #include "../common/triggers/teleporters.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "weapons/tracing.qh" diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index dbb9e1a48..e4eee6983 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -12,7 +12,7 @@ #include "../scores.qh" #include "../teamplay.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #ifdef SVQC #include "../../common/vehicles/all.qh" diff --git a/qcsrc/server/command/sv_cmd.qc b/qcsrc/server/command/sv_cmd.qc index 21255e5dd..cde2d6c45 100644 --- a/qcsrc/server/command/sv_cmd.qc +++ b/qcsrc/server/command/sv_cmd.qc @@ -20,7 +20,7 @@ #include "../bot/navigation.qh" #include "../bot/scripting.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../../common/constants.qh" #include "../../common/mapinfo.qh" diff --git a/qcsrc/server/command/vote.qc b/qcsrc/server/command/vote.qc index 0fca5d226..77c0a3560 100644 --- a/qcsrc/server/command/vote.qc +++ b/qcsrc/server/command/vote.qc @@ -9,7 +9,7 @@ #include "../round_handler.qh" #include "../scores.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../../common/constants.qh" #include "../../common/mapinfo.qh" diff --git a/qcsrc/server/ent_cs.qc b/qcsrc/server/ent_cs.qc index 548c60429..cf275c0f8 100644 --- a/qcsrc/server/ent_cs.qc +++ b/qcsrc/server/ent_cs.qc @@ -1,7 +1,5 @@ #include "ent_cs.qh" -#include "mutators/gamemode_ca.qh" - float entcs_customize() { SELFPARAM(); diff --git a/qcsrc/server/g_damage.qc b/qcsrc/server/g_damage.qc index 6a80f7e5e..8354007aa 100644 --- a/qcsrc/server/g_damage.qc +++ b/qcsrc/server/g_damage.qc @@ -2,7 +2,7 @@ #include "bot/bot.qh" #include "g_hook.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "scores.qh" #include "spawnpoints.qh" #include "t_items.qh" diff --git a/qcsrc/server/g_damage.qh b/qcsrc/server/g_damage.qh index 62a74be22..cee65e164 100644 --- a/qcsrc/server/g_damage.qh +++ b/qcsrc/server/g_damage.qh @@ -18,7 +18,7 @@ #include "defs.qh" #include "../common/notifications.qh" #include "../common/deathtypes/all.qh" - #include "mutators/mutators_include.qh" + #include "mutators/all.qh" #include "../common/turrets/sv_turrets.qh" #include "../common/vehicles/all.qh" #include "../lib/csqcmodel/sv_model.qh" diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index e121e6f96..06913e06a 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -13,7 +13,7 @@ #include "g_hook.qh" #include "ipban.qh" #include "mapvoting.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "race.qh" #include "scores.qh" #include "teamplay.qh" diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index c01aca479..95133eb2e 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -4,7 +4,7 @@ #include "constants.qh" #include "g_hook.qh" #include "ipban.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "t_items.qh" #include "weapons/accuracy.qh" #include "weapons/csqcprojectile.qh" diff --git a/qcsrc/server/miscfunctions.qh b/qcsrc/server/miscfunctions.qh index 1c9dfe689..050da2bc0 100644 --- a/qcsrc/server/miscfunctions.qh +++ b/qcsrc/server/miscfunctions.qh @@ -4,7 +4,6 @@ #include "t_items.qh" #include "mutators/events.qh" -#include "mutators/gamemode_race.qh" #include "../common/constants.qh" #include "../common/mapinfo.qh" diff --git a/qcsrc/server/mutators/all.inc b/qcsrc/server/mutators/all.inc new file mode 100644 index 000000000..f67ceaee6 --- /dev/null +++ b/qcsrc/server/mutators/all.inc @@ -0,0 +1,41 @@ +#include "mutator/gamemode_assault.qc" +#include "mutator/gamemode_ca.qc" +#include "mutator/gamemode_ctf.qc" +#include "mutator/gamemode_cts.qc" +#include "mutator/gamemode_deathmatch.qc" +#include "mutator/gamemode_domination.qc" +#include "mutator/gamemode_freezetag.qc" +#include "mutator/gamemode_invasion.qc" +#include "mutator/gamemode_keepaway.qc" +#include "mutator/gamemode_keyhunt.qc" +#include "mutator/gamemode_lms.qc" +#include "mutator/gamemode_onslaught.qc" +#include "mutator/gamemode_race.qc" +#include "mutator/gamemode_tdm.qc" + +#include "mutator/mutator_bloodloss.qc" +#include "mutator/mutator_breakablehook.qc" +#include "mutator/mutator_buffs.qc" +#include "mutator/mutator_campcheck.qc" +#include "mutator/mutator_dodging.qc" +#include "mutator/mutator_hook.qc" +#include "mutator/mutator_invincibleproj.qc" +#include "mutator/mutator_melee_only.qc" +#include "mutator/mutator_midair.qc" +#include "mutator/mutator_multijump.qc" +#include "mutator/mutator_nades.qc" +#include "mutator/mutator_new_toys.qc" +#include "mutator/mutator_nix.qc" +#include "mutator/mutator_overkill.qc" +#include "mutator/mutator_physical_items.qc" +#include "mutator/mutator_pinata.qc" +#include "mutator/mutator_random_gravity.qc" +#include "mutator/mutator_rocketflying.qc" +#include "mutator/mutator_rocketminsta.qc" +#include "mutator/mutator_spawn_near_teammate.qc" +#include "mutator/mutator_superspec.qc" +#include "mutator/mutator_touchexplode.qc" +#include "mutator/mutator_vampirehook.qc" +#include "mutator/mutator_vampire.qc" + +#include "mutator/sandbox.qc" diff --git a/qcsrc/server/mutators/all.qc b/qcsrc/server/mutators/all.qc new file mode 100644 index 000000000..7ad372629 --- /dev/null +++ b/qcsrc/server/mutators/all.qc @@ -0,0 +1,82 @@ +#if defined(CSQC) +#elif defined(MENUQC) +#elif defined(SVQC) + #include "../../lib/warpzone/anglestransform.qh" + #include "../../lib/warpzone/common.qh" + #include "../../lib/warpzone/util_server.qh" + #include "../../lib/warpzone/server.qh" + #include "../../common/constants.qh" + #include "../../common/stats.qh" + #include "../../common/teams.qh" + #include "../../common/util.qh" + #include "../../common/nades/all.qh" + #include "../../common/buffs/all.qh" + #include "../../common/command/markup.qh" + #include "../../common/command/rpn.qh" + #include "../../common/command/generic.qh" + #include "../../common/command/command.qh" + #include "../../common/net_notice.qh" + #include "../../common/animdecide.qh" + #include "../../common/monsters/all.qh" + #include "../../common/monsters/sv_monsters.qh" + #include "../../common/monsters/spawn.qh" + #include "../../common/weapons/config.qh" + #include "../../common/weapons/all.qh" + #include "../weapons/accuracy.qh" + #include "../weapons/common.qh" + #include "../weapons/csqcprojectile.qh" + #include "../weapons/hitplot.qh" + #include "../weapons/selection.qh" + #include "../weapons/spawning.qh" + #include "../weapons/throwing.qh" + #include "../weapons/tracing.qh" + #include "../weapons/weaponstats.qh" + #include "../weapons/weaponsystem.qh" + #include "../t_items.qh" + #include "../autocvars.qh" + #include "../constants.qh" + #include "../defs.qh" + #include "../../common/notifications.qh" + #include "../../common/deathtypes/all.qh" + #include "all.qh" + #include "../../common/turrets/sv_turrets.qh" + #include "../../common/vehicles/all.qh" + #include "../campaign.qh" + #include "../../common/campaign_common.qh" + #include "../../common/mapinfo.qh" + #include "../command/common.qh" + #include "../command/banning.qh" + #include "../command/radarmap.qh" + #include "../command/vote.qh" + #include "../command/getreplies.qh" + #include "../command/cmd.qh" + #include "../command/sv_cmd.qh" + #include "../../common/csqcmodel_settings.qh" + #include "../../lib/csqcmodel/common.qh" + #include "../../lib/csqcmodel/sv_model.qh" + #include "../anticheat.qh" + #include "../cheats.qh" + #include "../../common/playerstats.qh" + #include "../portals.qh" + #include "../g_hook.qh" + #include "../scores.qh" + #include "../spawnpoints.qh" + #include "../mapvoting.qh" + #include "../ipban.qh" + #include "../race.qh" + #include "../antilag.qh" + #include "../playerdemo.qh" + #include "../round_handler.qh" + #include "../item_key.qh" + #include "../pathlib/pathlib.qh" + #include "../../common/vehicles/all.qh" +#endif + +#include "all.qh" + +#include "mutator.qh" +#include "gamemode.qh" + +#define IMPLEMENTATION +#include "all.inc" +#undef IMPLEMENTATION diff --git a/qcsrc/server/mutators/all.qh b/qcsrc/server/mutators/all.qh new file mode 100644 index 000000000..ad4a5b9f5 --- /dev/null +++ b/qcsrc/server/mutators/all.qh @@ -0,0 +1,6 @@ +#ifndef SERVER_MUTATORS_H +#define SERVER_MUTATORS_H + +#include "all.inc" + +#endif diff --git a/qcsrc/server/mutators/gamemode.qh b/qcsrc/server/mutators/gamemode.qh index 5e5675f40..5e2c46357 100644 --- a/qcsrc/server/mutators/gamemode.qh +++ b/qcsrc/server/mutators/gamemode.qh @@ -1,8 +1,6 @@ #ifndef GAMEMODE_H #define GAMEMODE_H -#include "mutator_nades.qh" - #include "../cl_client.qh" #include "../cl_player.qh" #include "../cl_impulse.qh" diff --git a/qcsrc/server/mutators/gamemode_assault.qc b/qcsrc/server/mutators/gamemode_assault.qc deleted file mode 100644 index 8558faaf0..000000000 --- a/qcsrc/server/mutators/gamemode_assault.qc +++ /dev/null @@ -1,638 +0,0 @@ -#include "gamemode_assault.qh" - -#include "gamemode.qh" - -.entity sprite; - -// random functions -void assault_objective_use() -{SELFPARAM(); - // activate objective - self.health = 100; - //print("^2Activated objective ", self.targetname, "=", etos(self), "\n"); - //print("Activator is ", activator.classname, "\n"); - - for (entity e = world; (e = find(e, target, this.targetname)); ) - { - if (e.classname == "target_objective_decrease") - { - WITH(entity, self, e, target_objective_decrease_activate()); - } - } - - setself(this); -} - -vector target_objective_spawn_evalfunc(entity player, entity spot, vector current) -{SELFPARAM(); - if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE) - return '-1 0 0'; - return current; -} - -// reset this objective. Used when spawning an objective -// and when a new round starts -void assault_objective_reset() -{SELFPARAM(); - self.health = ASSAULT_VALUE_INACTIVE; -} - -// decrease the health of targeted objectives -void assault_objective_decrease_use() -{SELFPARAM(); - if(activator.team != assault_attacker_team) - { - // wrong team triggered decrease - return; - } - - if(other.assault_sprite) - { - WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime); - if(other.classname == "func_assault_destructible") - other.sprite = world; - } - else - return; // already activated! cannot activate again! - - if(self.enemy.health < ASSAULT_VALUE_INACTIVE) - { - if(self.enemy.health - self.dmg > 0.5) - { - PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg); - self.enemy.health = self.enemy.health - self.dmg; - } - else - { - PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health); - PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1); - self.enemy.health = -1; - - entity oldactivator, head; - - setself(this.enemy); - if(self.message) - FOR_EACH_PLAYER(head) - centerprint(head, self.message); - - oldactivator = activator; - activator = this; - SUB_UseTargets(); - activator = oldactivator; - setself(this); - } - } -} - -void assault_setenemytoobjective() -{SELFPARAM(); - entity objective; - for(objective = world; (objective = find(objective, targetname, self.target)); ) - { - if(objective.classname == "target_objective") - { - if(self.enemy == world) - self.enemy = objective; - else - objerror("more than one objective as target - fix the map!"); - break; - } - } - - if(self.enemy == world) - objerror("no objective as target - fix the map!"); -} - -float assault_decreaser_sprite_visible(entity e) -{SELFPARAM(); - entity decreaser; - - decreaser = self.assault_decreaser; - - if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE) - return false; - - return true; -} - -void target_objective_decrease_activate() -{SELFPARAM(); - entity ent, spr; - self.owner = world; - for(ent = world; (ent = find(ent, target, self.targetname)); ) - { - if(ent.assault_sprite != world) - { - WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime); - if(ent.classname == "func_assault_destructible") - ent.sprite = world; - } - - spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE); - spr.assault_decreaser = self; - spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible; - spr.classname = "sprite_waypoint"; - WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY); - if(ent.classname == "func_assault_destructible") - { - WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy); - WaypointSprite_UpdateMaxHealth(spr, ent.max_health); - WaypointSprite_UpdateHealth(spr, ent.health); - ent.sprite = spr; - } - else - WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush); - } -} - -void target_objective_decrease_findtarget() -{ - assault_setenemytoobjective(); -} - -void target_assault_roundend_reset() -{SELFPARAM(); - //print("round end reset\n"); - self.cnt = self.cnt + 1; // up round counter - self.winning = 0; // up round -} - -void target_assault_roundend_use() -{SELFPARAM(); - self.winning = 1; // round has been won by attackers -} - -void assault_roundstart_use() -{SELFPARAM(); - activator = self; - SUB_UseTargets(); - - //(Re)spawn all turrets - for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) { - // Swap turret teams - if(ent.team == NUM_TEAM_1) - ent.team = NUM_TEAM_2; - else - ent.team = NUM_TEAM_1; - - // Dubbles as teamchange - WITH(entity, self, ent, turret_respawn()); - } -} - -void assault_wall_think() -{SELFPARAM(); - if(self.enemy.health < 0) - { - self.model = ""; - self.solid = SOLID_NOT; - } - else - { - self.model = self.mdl; - self.solid = SOLID_BSP; - } - - self.nextthink = time + 0.2; -} - -// trigger new round -// reset objectives, toggle spawnpoints, reset triggers, ... -void vehicles_clearreturn(entity veh); -void vehicles_spawn(); -void assault_new_round() -{SELFPARAM(); - //bprint("ASSAULT: new round\n"); - - // Eject players from vehicles - entity e; - FOR_EACH_PLAYER(e) - { - if(e.vehicle) - { - WITH(entity, self, e, vehicles_exit(VHEF_RELEASE)); - } - } - - for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain) - { - setself(e_); - vehicles_clearreturn(self); - vehicles_spawn(); - } - - setself(this); - - // up round counter - self.winning = self.winning + 1; - - // swap attacker/defender roles - if(assault_attacker_team == NUM_TEAM_1) - assault_attacker_team = NUM_TEAM_2; - else - assault_attacker_team = NUM_TEAM_1; - - entity ent; - for(ent = world; (ent = nextent(ent)); ) - { - if(clienttype(ent) == CLIENTTYPE_NOTACLIENT) - { - if(ent.team_saved == NUM_TEAM_1) - ent.team_saved = NUM_TEAM_2; - else if(ent.team_saved == NUM_TEAM_2) - ent.team_saved = NUM_TEAM_1; - } - } - - // reset the level with a countdown - cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60)); - ReadyRestart_force(); // sets game_starttime -} - -// spawnfuncs -spawnfunc(info_player_attacker) -{ - if (!g_assault) { remove(self); return; } - - self.team = NUM_TEAM_1; // red, gets swapped every round - spawnfunc_info_player_deathmatch(this); -} - -spawnfunc(info_player_defender) -{ - if (!g_assault) { remove(self); return; } - - self.team = NUM_TEAM_2; // blue, gets swapped every round - spawnfunc_info_player_deathmatch(this); -} - -spawnfunc(target_objective) -{ - if (!g_assault) { remove(self); return; } - - self.classname = "target_objective"; - self.use = assault_objective_use; - assault_objective_reset(); - self.reset = assault_objective_reset; - self.spawn_evalfunc = target_objective_spawn_evalfunc; -} - -spawnfunc(target_objective_decrease) -{ - if (!g_assault) { remove(self); return; } - - self.classname = "target_objective_decrease"; - - if(!self.dmg) - self.dmg = 101; - - self.use = assault_objective_decrease_use; - self.health = ASSAULT_VALUE_INACTIVE; - self.max_health = ASSAULT_VALUE_INACTIVE; - self.enemy = world; - - InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET); -} - -// destructible walls that can be used to trigger target_objective_decrease -spawnfunc(func_breakable); -spawnfunc(func_assault_destructible) -{ - if (!g_assault) { remove(self); return; } - - self.spawnflags = 3; - self.classname = "func_assault_destructible"; - - if(assault_attacker_team == NUM_TEAM_1) - self.team = NUM_TEAM_2; - else - self.team = NUM_TEAM_1; - - spawnfunc_func_breakable(this); -} - -spawnfunc(func_assault_wall) -{ - if (!g_assault) { remove(self); return; } - - self.classname = "func_assault_wall"; - self.mdl = self.model; - _setmodel(self, self.mdl); - self.solid = SOLID_BSP; - self.think = assault_wall_think; - self.nextthink = time; - InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET); -} - -spawnfunc(target_assault_roundend) -{ - if (!g_assault) { remove(self); return; } - - self.winning = 0; // round not yet won by attackers - self.classname = "target_assault_roundend"; - self.use = target_assault_roundend_use; - self.cnt = 0; // first round - self.reset = target_assault_roundend_reset; -} - -spawnfunc(target_assault_roundstart) -{ - if (!g_assault) { remove(self); return; } - - assault_attacker_team = NUM_TEAM_1; - self.classname = "target_assault_roundstart"; - self.use = assault_roundstart_use; - self.reset2 = assault_roundstart_use; - InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET); -} - -// legacy bot code -void havocbot_goalrating_ast_targets(float ratingscale) -{SELFPARAM(); - entity ad, best, wp, tod; - float radius, found, bestvalue; - vector p; - - ad = findchain(classname, "func_assault_destructible"); - - for (; ad; ad = ad.chain) - { - if (ad.target == "") - continue; - - if (!ad.bot_attack) - continue; - - found = false; - for(tod = world; (tod = find(tod, targetname, ad.target)); ) - { - if(tod.classname == "target_objective_decrease") - { - if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE) - { - // dprint(etos(ad),"\n"); - found = true; - break; - } - } - } - - if(!found) - { - /// dprint("target not found\n"); - continue; - } - /// dprint("target #", etos(ad), " found\n"); - - - p = 0.5 * (ad.absmin + ad.absmax); - // dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n"); - // te_knightspike(p); - // te_lightning2(world, '0 0 0', p); - - // Find and rate waypoints around it - found = false; - best = world; - bestvalue = 99999999999; - for(radius=0; radius<1500 && !found; radius+=500) - { - for(wp=findradius(p, radius); wp; wp=wp.chain) - { - if(!(wp.wpflags & WAYPOINTFLAG_GENERATED)) - if(wp.classname=="waypoint") - if(checkpvs(wp.origin, ad)) - { - found = true; - if(wp.cnt self.havocbot_role_timeout) - { - havocbot_ast_reset_role(self); - return; - } - - if(self.havocbot_attack_time>time) - return; - - if (self.bot_strategytime < time) - { - navigation_goalrating_start(); - havocbot_goalrating_enemyplayers(20000, self.origin, 650); - havocbot_goalrating_ast_targets(20000); - havocbot_goalrating_items(15000, self.origin, 10000); - navigation_goalrating_end(); - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - } -} - -void havocbot_role_ast_defense() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - { - self.havocbot_attack_time = 0; - havocbot_ast_reset_role(self); - return; - } - - // Set the role timeout if necessary - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + 120; - - if (time > self.havocbot_role_timeout) - { - havocbot_ast_reset_role(self); - return; - } - - if(self.havocbot_attack_time>time) - return; - - if (self.bot_strategytime < time) - { - navigation_goalrating_start(); - havocbot_goalrating_enemyplayers(20000, self.origin, 3000); - havocbot_goalrating_ast_targets(20000); - havocbot_goalrating_items(15000, self.origin, 10000); - navigation_goalrating_end(); - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - } -} - -void havocbot_role_ast_setrole(entity bot, float role) -{ - switch(role) - { - case HAVOCBOT_AST_ROLE_DEFENSE: - bot.havocbot_role = havocbot_role_ast_defense; - bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_AST_ROLE_OFFENSE: - bot.havocbot_role = havocbot_role_ast_offense; - bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE; - bot.havocbot_role_timeout = 0; - break; - } -} - -void havocbot_ast_reset_role(entity bot) -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if(bot.team == assault_attacker_team) - havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE); - else - havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE); -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(as, PlayerSpawn) -{SELFPARAM(); - if(self.team == assault_attacker_team) - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING); - else - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING); - - return false; -} - -MUTATOR_HOOKFUNCTION(as, TurretSpawn) -{SELFPARAM(); - if(!self.team || self.team == MAX_SHOT_DISTANCE) - self.team = 5; // this gets reversed when match starts? - - return false; -} - -MUTATOR_HOOKFUNCTION(as, VehicleSpawn) -{SELFPARAM(); - self.nextthink = time + 0.5; - - return false; -} - -MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole) -{SELFPARAM(); - havocbot_ast_reset_role(self); - return true; -} - -MUTATOR_HOOKFUNCTION(as, PlayHitsound) -{ - return (frag_victim.classname == "func_assault_destructible"); -} - -MUTATOR_HOOKFUNCTION(as, GetTeamCount) -{ - // assault always has 2 teams - c1 = c2 = 0; - return true; -} - -MUTATOR_HOOKFUNCTION(as, CheckRules_World) -{ - ret_float = WinningCondition_Assault(); - return true; -} - -MUTATOR_HOOKFUNCTION(as, ReadLevelCvars) -{ - // no assault warmups - warmup_stage = 0; - return false; -} - -MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn) -{ - switch(self.classname) - { - case "info_player_team1": - case "info_player_team2": - case "info_player_team3": - case "info_player_team4": - return true; - } - - return false; -} - -// scoreboard setup -void assault_ScoreRules() -{ - ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true); - ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); - ScoreRules_basics_end(); -} - -REGISTER_MUTATOR(as, g_assault) -{ - ActivateTeamplay(); - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - assault_ScoreRules(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back assault_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_assault.qh b/qcsrc/server/mutators/gamemode_assault.qh deleted file mode 100644 index 1266492ca..000000000 --- a/qcsrc/server/mutators/gamemode_assault.qh +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef GAMEMODE_ASSAULT_H -#define GAMEMODE_ASSAULT_H -// sprites -.entity assault_decreaser; -.entity assault_sprite; - -// legacy bot defs -const int HAVOCBOT_AST_ROLE_NONE = 0; -const int HAVOCBOT_AST_ROLE_DEFENSE = 2; -const int HAVOCBOT_AST_ROLE_OFFENSE = 4; - -.int havocbot_role_flags; -.float havocbot_attack_time; - -.void() havocbot_role; -.void() havocbot_previous_role; - -void() havocbot_role_ast_defense; -void() havocbot_role_ast_offense; -.entity havocbot_ast_target; - -void(entity bot) havocbot_ast_reset_role; - -void(float ratingscale, vector org, float sradius) havocbot_goalrating_items; -void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; - -// scoreboard stuff -const float ST_ASSAULT_OBJECTIVES = 1; -const float SP_ASSAULT_OBJECTIVES = 4; - -// predefined spawnfuncs -void target_objective_decrease_activate(); -#endif diff --git a/qcsrc/server/mutators/gamemode_ca.qc b/qcsrc/server/mutators/gamemode_ca.qc deleted file mode 100644 index 1a9b1daa1..000000000 --- a/qcsrc/server/mutators/gamemode_ca.qc +++ /dev/null @@ -1,524 +0,0 @@ -#include "gamemode_ca.qh" - -#include "gamemode.qh" - -float autocvar_g_ca_damage2score_multiplier; -int autocvar_g_ca_point_leadlimit; -int autocvar_g_ca_point_limit; -float autocvar_g_ca_round_timelimit; -bool autocvar_g_ca_spectate_enemies; -int autocvar_g_ca_teams; -int autocvar_g_ca_teams_override; -bool autocvar_g_ca_team_spawns; -float autocvar_g_ca_warmup; - -float ca_teams; -float allowed_to_spawn; - -const float ST_CA_ROUNDS = 1; -void ca_ScoreRules(float teams) -{ - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); - ScoreInfo_SetLabel_TeamScore(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); - ScoreRules_basics_end(); -} - -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() -{ - 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); - allowed_to_spawn = false; - round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); - FOR_EACH_PLAYER(e) - nades_Clear(e); - 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_CA_ROUNDS, +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); - - FOR_EACH_PLAYER(e) - nades_Clear(e); - - return 1; -} - -void CA_RoundStart() -{ - if(warmup_stage) - allowed_to_spawn = true; - else - allowed_to_spawn = false; -} - -float CA_CheckTeams() -{ - static float prev_missing_teams_mask; - allowed_to_spawn = true; - CA_count_alive_players(); - if(CA_ALIVE_TEAMS_OK()) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return 1; - } - if(total_players == 0) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return 0; - } - float missing_teams_mask = (!redalive) + (!bluealive) * 2; - if(ca_teams >= 3) missing_teams_mask += (!yellowalive) * 4; - if(ca_teams >= 4) missing_teams_mask += (!pinkalive) * 8; - if(prev_missing_teams_mask != missing_teams_mask) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); - prev_missing_teams_mask = missing_teams_mask; - } - return 0; -} - -float ca_isEliminated(entity e) -{ - if(e.caplayer == 1 && (e.deadflag != DEAD_NO || e.frags == FRAGS_LMS_LOSER)) - return true; - if(e.caplayer == 0.5) - return true; - return false; -} - -// Returns next available player to spectate if g_ca_spectate_enemies == 0 -entity CA_SpectateNext(entity player, entity start) -{ - if(SAME_TEAM(start, player)) - return start; - - entity spec_other = start; - // continue from current player - while(spec_other && DIFF_TEAM(spec_other, player)) - spec_other = find(spec_other, classname, "player"); - - if (!spec_other) - { - // restart from begining - spec_other = find(spec_other, classname, "player"); - while(spec_other && DIFF_TEAM(spec_other, player)) - spec_other = find(spec_other, classname, "player"); - } - - return spec_other; -} - -MUTATOR_HOOKFUNCTION(ca, PlayerSpawn) -{SELFPARAM(); - self.caplayer = 1; - if(!warmup_stage) - eliminatedPlayers.SendFlags |= 1; - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, PutClientInServer) -{SELFPARAM(); - if(!allowed_to_spawn) - if(IS_PLAYER(self)) // this is true even when player is trying to join - { - self.classname = "observer"; - if(self.jointime != time) //not when connecting - if(!self.caplayer) - { - self.caplayer = 0.5; - if(IS_REAL_CLIENT(self)) - Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_JOIN_LATE); - } - } - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, reset_map_players) -{SELFPARAM(); - entity e; - FOR_EACH_CLIENT(e) - { - setself(e); - self.killcount = 0; - if(!self.caplayer && IS_BOT_CLIENT(self)) - { - self.team = -1; - self.caplayer = 1; - } - if(self.caplayer) - { - self.classname = "player"; - self.caplayer = 1; - PutClientInServer(); - } - } - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, ClientConnect) -{SELFPARAM(); - self.classname = "observer"; - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, reset_map_global) -{ - allowed_to_spawn = true; - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, GetTeamCount, CBC_ORDER_EXCLUSIVE) -{ - ret_float = ca_teams; - return false; -} - -entity ca_LastPlayerForTeam() -{SELFPARAM(); - entity pl, last_pl = world; - FOR_EACH_PLAYER(pl) - { - if(pl.health >= 1) - if(pl != self) - if(pl.team == self.team) - if(!last_pl) - last_pl = pl; - else - return world; - } - return last_pl; -} - -void ca_LastPlayerForTeam_Notify() -{ - if(round_handler_IsActive()) - if(round_handler_IsRoundStarted()) - { - entity pl = ca_LastPlayerForTeam(); - if(pl) - Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); - } -} - -MUTATOR_HOOKFUNCTION(ca, PlayerDies) -{SELFPARAM(); - ca_LastPlayerForTeam_Notify(); - if(!allowed_to_spawn) - self.respawn_flags = RESPAWN_SILENT; - if(!warmup_stage) - eliminatedPlayers.SendFlags |= 1; - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, ClientDisconnect) -{SELFPARAM(); - if(self.caplayer == 1) - ca_LastPlayerForTeam_Notify(); - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, ForbidPlayerScore_Clear) -{ - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver) -{SELFPARAM(); - if(self.caplayer == 1) - ca_LastPlayerForTeam_Notify(); - if(self.killindicator_teamchange == -2) - self.caplayer = 0; - if(self.caplayer) - self.frags = FRAGS_LMS_LOSER; - if(!warmup_stage) - eliminatedPlayers.SendFlags |= 1; - return true; -} - -MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon) -{ - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST) -{ - frag_score = 0; // score will be given to the winner team when the round ends - return 1; -} - -MUTATOR_HOOKFUNCTION(ca, SetStartItems) -{ - start_items &= ~IT_UNLIMITED_AMMO; - start_health = warmup_start_health = cvar("g_lms_start_health"); - start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); - start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); - start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); - start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); - start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); - start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); - start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); - - return 0; -} - -MUTATOR_HOOKFUNCTION(ca, PlayerDamage_Calculate) -{ - if(IS_PLAYER(frag_target)) - if(frag_target.deadflag == DEAD_NO) - if(frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id) - frag_damage = 0; - - frag_mirrordamage = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(ca, FilterItem) -{SELFPARAM(); - if(autocvar_g_powerups <= 0) - if(self.flags & FL_POWERUP) - return true; - - if(autocvar_g_pickup_items <= 0) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor) -{ - float excess = max(0, frag_damage - damage_take - damage_save); - - if(frag_target != frag_attacker && IS_PLAYER(frag_attacker)) - PlayerTeamScore_Add(frag_attacker, SP_SCORE, ST_SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier); - - return false; -} - -MUTATOR_HOOKFUNCTION(ca, PlayerRegen) -{ - // no regeneration in CA - return true; -} - -MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining) -{ - // announce remaining frags - return true; -} - -MUTATOR_HOOKFUNCTION(ca, SpectateSet) -{ - if(!autocvar_g_ca_spectate_enemies && self.caplayer) - if(DIFF_TEAM(spec_player, self)) - return true; - return false; -} - -MUTATOR_HOOKFUNCTION(ca, SpectateNext) -{SELFPARAM(); - if(!autocvar_g_ca_spectate_enemies && self.caplayer) - { - spec_player = CA_SpectateNext(self, spec_player); - return true; - } - return false; -} - -MUTATOR_HOOKFUNCTION(ca, SpectatePrev) -{SELFPARAM(); - if(!autocvar_g_ca_spectate_enemies && self.caplayer) - { - do { spec_player = spec_player.chain; } - while(spec_player && DIFF_TEAM(spec_player, self)); - - if (!spec_player) - { - spec_player = spec_first; - while(spec_player && DIFF_TEAM(spec_player, self)) - spec_player = spec_player.chain; - if(spec_player == self.enemy) - return MUT_SPECPREV_RETURN; - } - } - - return MUT_SPECPREV_FOUND; -} - -MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE) -{ - entity head; - FOR_EACH_REALCLIENT(head) - { - if(IS_PLAYER(head) || head.caplayer == 1) - ++bot_activerealplayers; - ++bot_realplayers; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate) -{ - if(self.caplayer) - { - // they're going to spec, we can do other checks - if(autocvar_sv_spectate && (IS_SPEC(self) || IS_OBSERVER(self))) - Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE); - return MUT_SPECCMD_FORCE; - } - - return MUT_SPECCMD_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(ca, WantWeapon) -{ - want_allguns = true; - return false; -} - -MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus) -{ - if(set_player.caplayer == 1) - return true; - return false; -} - -MUTATOR_HOOKFUNCTION(ca, SetWeaponArena) -{ - // most weapons arena - if(ret_string == "0" || ret_string == "") - ret_string = "most"; - return false; -} - -void ca_Initialize() -{ - allowed_to_spawn = true; - - 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; - ca_ScoreRules(ca_teams); - - 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); - - EliminatedPlayers_Init(ca_isEliminated); -} - -REGISTER_MUTATOR(ca, g_ca) -{ - ActivateTeamplay(); - SetLimits(autocvar_g_ca_point_limit, autocvar_g_ca_point_leadlimit, -1, -1); - - if(autocvar_g_ca_team_spawns) - have_team_spawns = -1; // request team spawns - - 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 - { - LOG_INFO("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 deleted file mode 100644 index bf6686d73..000000000 --- a/qcsrc/server/mutators/gamemode_ca.qh +++ /dev/null @@ -1,5 +0,0 @@ -#ifndef GAMEMODE_CA_H -#define GAMEMODE_CA_H -// 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 -#endif diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc deleted file mode 100644 index 016f9bd47..000000000 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ /dev/null @@ -1,2606 +0,0 @@ -#include "gamemode_ctf.qh" - -#include "gamemode.qh" - -#ifdef SVQC -#include "../../common/vehicles/all.qh" -#include "../teamplay.qh" -#endif - -#include "../../lib/warpzone/common.qh" - -bool autocvar_g_ctf_allow_vehicle_carry; -bool autocvar_g_ctf_allow_vehicle_touch; -bool autocvar_g_ctf_allow_monster_touch; -bool autocvar_g_ctf_throw; -float autocvar_g_ctf_throw_angle_max; -float autocvar_g_ctf_throw_angle_min; -int autocvar_g_ctf_throw_punish_count; -float autocvar_g_ctf_throw_punish_delay; -float autocvar_g_ctf_throw_punish_time; -float autocvar_g_ctf_throw_strengthmultiplier; -float autocvar_g_ctf_throw_velocity_forward; -float autocvar_g_ctf_throw_velocity_up; -float autocvar_g_ctf_drop_velocity_up; -float autocvar_g_ctf_drop_velocity_side; -bool autocvar_g_ctf_oneflag_reverse; -bool autocvar_g_ctf_portalteleport; -bool autocvar_g_ctf_pass; -float autocvar_g_ctf_pass_arc; -float autocvar_g_ctf_pass_arc_max; -float autocvar_g_ctf_pass_directional_max; -float autocvar_g_ctf_pass_directional_min; -float autocvar_g_ctf_pass_radius; -float autocvar_g_ctf_pass_wait; -bool autocvar_g_ctf_pass_request; -float autocvar_g_ctf_pass_turnrate; -float autocvar_g_ctf_pass_timelimit; -float autocvar_g_ctf_pass_velocity; -bool autocvar_g_ctf_dynamiclights; -float autocvar_g_ctf_flag_collect_delay; -float autocvar_g_ctf_flag_damageforcescale; -bool autocvar_g_ctf_flag_dropped_waypoint; -bool autocvar_g_ctf_flag_dropped_floatinwater; -bool autocvar_g_ctf_flag_glowtrails; -int autocvar_g_ctf_flag_health; -bool autocvar_g_ctf_flag_return; -float autocvar_g_ctf_flag_return_carried_radius; -float autocvar_g_ctf_flag_return_time; -bool autocvar_g_ctf_flag_return_when_unreachable; -float autocvar_g_ctf_flag_return_damage; -float autocvar_g_ctf_flag_return_damage_delay; -float autocvar_g_ctf_flag_return_dropped; -float autocvar_g_ctf_flagcarrier_auto_helpme_damage; -float autocvar_g_ctf_flagcarrier_auto_helpme_time; -float autocvar_g_ctf_flagcarrier_selfdamagefactor; -float autocvar_g_ctf_flagcarrier_selfforcefactor; -float autocvar_g_ctf_flagcarrier_damagefactor; -float autocvar_g_ctf_flagcarrier_forcefactor; -//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting; -bool autocvar_g_ctf_fullbrightflags; -bool autocvar_g_ctf_ignore_frags; -int autocvar_g_ctf_score_capture; -int autocvar_g_ctf_score_capture_assist; -int autocvar_g_ctf_score_kill; -int autocvar_g_ctf_score_penalty_drop; -int autocvar_g_ctf_score_penalty_returned; -int autocvar_g_ctf_score_pickup_base; -int autocvar_g_ctf_score_pickup_dropped_early; -int autocvar_g_ctf_score_pickup_dropped_late; -int autocvar_g_ctf_score_return; -float autocvar_g_ctf_shield_force; -float autocvar_g_ctf_shield_max_ratio; -int autocvar_g_ctf_shield_min_negscore; -bool autocvar_g_ctf_stalemate; -int autocvar_g_ctf_stalemate_endcondition; -float autocvar_g_ctf_stalemate_time; -bool autocvar_g_ctf_reverse; -float autocvar_g_ctf_dropped_capture_delay; -float autocvar_g_ctf_dropped_capture_radius; - -void ctf_FakeTimeLimit(entity e, float t) -{ - msg_entity = e; - WriteByte(MSG_ONE, 3); // svc_updatestat - WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT - if(t < 0) - WriteCoord(MSG_ONE, autocvar_timelimit); - else - WriteCoord(MSG_ONE, (t + 1) / 60); -} - -void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : ""))); - //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void ctf_CaptureRecord(entity flag, entity player) -{ - float cap_record = ctf_captimerecord; - float cap_time = (time - flag.ctf_pickuptime); - string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); - - // notify about shit - if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); } - else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } - else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } - else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } - - // write that shit in the database - if(!ctf_oneflag) // but not in 1-flag mode - if((!ctf_captimerecord) || (cap_time < cap_record)) - { - ctf_captimerecord = cap_time; - db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time)); - db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname); - write_recordmarker(player, (time - cap_time), cap_time); - } -} - -void ctf_FlagcarrierWaypoints(entity player) -{ - WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG); - WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2); - WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); - WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team)); -} - -void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate) -{ - float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis - float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc))); - float current_height = (initial_height * min(1, (current_distance / flag.pass_distance))); - //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); - - vector targpos; - if(current_height) // make sure we can actually do this arcing path - { - targpos = (to + ('0 0 1' * current_height)); - WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); - if(trace_fraction < 1) - { - //print("normal arc line failed, trying to find new pos..."); - WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag); - targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET); - WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); - if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ } - /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */ - } - } - else { targpos = to; } - - //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y)); - - vector desired_direction = normalize(targpos - from); - if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); } - else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); } -} - -bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) -{ - if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min) - { - // directional tracing only - float spreadlimit; - makevectors(passer_angle); - - // find the closest point on the enemy to the center of the attack - float h; // hypotenuse, which is the distance between attacker to head - float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin - - h = vlen(head_center - passer_center); - a = h * (normalize(head_center - passer_center) * v_forward); - - vector nearest_on_line = (passer_center + a * v_forward); - float distance_from_line = vlen(nearest_to_passer - nearest_on_line); - - spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1); - spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit); - - if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90)) - { return true; } - else - { return false; } - } - else { return true; } -} - - -// ======================= -// CaptureShield Functions -// ======================= - -bool ctf_CaptureShield_CheckStatus(entity p) -{ - int s, s2, s3, s4, se, se2, se3, se4, sr, ser; - entity e; - int players_worseeq, players_total; - - if(ctf_captureshield_max_ratio <= 0) - return false; - - s = PlayerScore_Add(p, SP_CTF_CAPS, 0); - s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0); - s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0); - s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0); - - sr = ((s - s2) + (s3 + s4)); - - if(sr >= -ctf_captureshield_min_negscore) - return false; - - players_total = players_worseeq = 0; - FOR_EACH_PLAYER(e) - { - if(DIFF_TEAM(e, p)) - continue; - se = PlayerScore_Add(e, SP_CTF_CAPS, 0); - se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0); - se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0); - se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0); - - ser = ((se - se2) + (se3 + se4)); - - if(ser <= sr) - ++players_worseeq; - ++players_total; - } - - // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse - // use this rule here - - if(players_worseeq >= players_total * ctf_captureshield_max_ratio) - return false; - - return true; -} - -void ctf_CaptureShield_Update(entity player, bool wanted_status) -{ - bool updated_status = ctf_CaptureShield_CheckStatus(player); - if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE)); - player.ctf_captureshielded = updated_status; - } -} - -bool ctf_CaptureShield_Customize() -{SELFPARAM(); - if(!other.ctf_captureshielded) { return false; } - if(CTF_SAMETEAM(self, other)) { return false; } - - return true; -} - -void ctf_CaptureShield_Touch() -{SELFPARAM(); - if(!other.ctf_captureshielded) { return; } - if(CTF_SAMETEAM(self, other)) { return; } - - vector mymid = (self.absmin + self.absmax) * 0.5; - vector othermid = (other.absmin + other.absmax) * 0.5; - - Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force); - if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); } -} - -void ctf_CaptureShield_Spawn(entity flag) -{SELFPARAM(); - entity shield = spawn(); - - shield.enemy = self; - shield.team = self.team; - shield.touch = ctf_CaptureShield_Touch; - shield.customizeentityforclient = ctf_CaptureShield_Customize; - shield.classname = "ctf_captureshield"; - shield.effects = EF_ADDITIVE; - shield.movetype = MOVETYPE_NOCLIP; - shield.solid = SOLID_TRIGGER; - shield.avelocity = '7 0 11'; - shield.scale = 0.5; - - setorigin(shield, self.origin); - setmodel(shield, MDL_CTF_SHIELD); - setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); -} - - -// ==================== -// Drop/Pass/Throw Code -// ==================== - -void ctf_Handle_Drop(entity flag, entity player, int droptype) -{ - // declarations - player = (player ? player : flag.pass_sender); - - // main - flag.movetype = MOVETYPE_TOSS; - flag.takedamage = DAMAGE_YES; - flag.angles = '0 0 0'; - flag.health = flag.max_flag_health; - flag.ctf_droptime = time; - flag.ctf_dropper = player; - flag.ctf_status = FLAG_DROPPED; - - // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname); - _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE); - ctf_EventLog("dropped", player.team, player); - - // scoring - PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop); - PlayerScore_Add(player, SP_CTF_DROPS, 1); - - // waypoints - if(autocvar_g_ctf_flag_dropped_waypoint) { - entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG); - wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team); - } - - if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health)) - { - WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health); - WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); - } - - player.throw_antispam = time + autocvar_g_ctf_pass_wait; - - if(droptype == DROP_PASS) - { - flag.pass_distance = 0; - flag.pass_sender = world; - flag.pass_target = world; - } -} - -void ctf_Handle_Retrieve(entity flag, entity player) -{ - entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players - entity sender = flag.pass_sender; - - // transfer flag to player - flag.owner = player; - flag.owner.flagcarried = flag; - - // reset flag - if(player.vehicle) - { - setattachment(flag, player.vehicle, ""); - setorigin(flag, VEHICLE_FLAG_OFFSET); - flag.scale = VEHICLE_FLAG_SCALE; - } - else - { - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); - } - flag.movetype = MOVETYPE_NONE; - flag.takedamage = DAMAGE_NO; - flag.solid = SOLID_NOT; - flag.angles = '0 0 0'; - flag.ctf_status = FLAG_CARRY; - - // messages and sounds - _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM); - ctf_EventLog("receive", flag.team, player); - - FOR_EACH_REALPLAYER(tmp_player) - { - if(tmp_player == sender) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname); - else if(tmp_player == player) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname); - else if(SAME_TEAM(tmp_player, sender)) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname); - } - - // create new waypoint - ctf_FlagcarrierWaypoints(player); - - sender.throw_antispam = time + autocvar_g_ctf_pass_wait; - player.throw_antispam = sender.throw_antispam; - - flag.pass_distance = 0; - flag.pass_sender = world; - flag.pass_target = world; -} - -void ctf_Handle_Throw(entity player, entity receiver, int droptype) -{ - entity flag = player.flagcarried; - vector targ_origin, flag_velocity; - - if(!flag) { return; } - if((droptype == DROP_PASS) && !receiver) { return; } - - if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } - - // reset the flag - setattachment(flag, world, ""); - setorigin(flag, player.origin + FLAG_DROP_OFFSET); - flag.owner.flagcarried = world; - flag.owner = world; - flag.solid = SOLID_TRIGGER; - flag.ctf_dropper = player; - flag.ctf_droptime = time; - - flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS - - switch(droptype) - { - case DROP_PASS: - { - // warpzone support: - // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver - // findradius has already put wzn ... wz1 into receiver's warpzone parameters! - WarpZone_RefSys_Copy(flag, receiver); - WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver - targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag - - flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis - ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false); - - // main - flag.movetype = MOVETYPE_FLY; - flag.takedamage = DAMAGE_NO; - flag.pass_sender = player; - flag.pass_target = receiver; - flag.ctf_status = FLAG_PASSING; - - // other - _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); - WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin); - ctf_EventLog("pass", flag.team, player); - break; - } - - case DROP_THROW: - { - makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0')); - - flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1))); - flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false); - ctf_Handle_Drop(flag, player, droptype); - break; - } - - case DROP_RESET: - { - flag.velocity = '0 0 0'; // do nothing - break; - } - - default: - case DROP_NORMAL: - { - flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false); - ctf_Handle_Drop(flag, player, droptype); - break; - } - } - - // kill old waypointsprite - WaypointSprite_Ping(player.wps_flagcarrier); - WaypointSprite_Kill(player.wps_flagcarrier); - - if(player.wps_enemyflagcarrier) - WaypointSprite_Kill(player.wps_enemyflagcarrier); - - // captureshield - ctf_CaptureShield_Update(player, 0); // shield player from picking up flag -} - - -// ============== -// Event Handlers -// ============== - -void ctf_Handle_Capture(entity flag, entity toucher, int capturetype) -{ - entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher); - entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper); - entity player_team_flag = world, tmp_entity; - float old_time, new_time; - - if(!player) { return; } // without someone to give the reward to, we can't possibly cap - if(CTF_DIFFTEAM(player, flag)) { return; } - - if(ctf_oneflag) - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - if(SAME_TEAM(tmp_entity, player)) - { - player_team_flag = tmp_entity; - break; - } - - nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); - - player.throw_prevtime = time; - player.throw_count = 0; - - // messages and sounds - Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL)); - ctf_CaptureRecord(enemy_flag, player); - _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE); - - switch(capturetype) - { - case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break; - case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break; - default: break; - } - - // scoring - PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture); - PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1); - - old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0); - new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime); - if(!old_time || new_time < old_time) - PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time); - - // effects - Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1); - //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1); - - // other - if(capturetype == CAPTURE_NORMAL) - { - WaypointSprite_Kill(player.wps_flagcarrier); - if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); } - - if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper)) - { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); } - } - - // reset the flag - player.next_take_time = time + autocvar_g_ctf_flag_collect_delay; - ctf_RespawnFlag(enemy_flag); -} - -void ctf_Handle_Return(entity flag, entity player) -{ - // messages and sounds - if(IS_MONSTER(player)) - { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); - } - else if(flag.team) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_)); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname); - } - _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE); - ctf_EventLog("return", flag.team, player); - - // scoring - if(IS_PLAYER(player)) - { - PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return - PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns - - nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium); - } - - TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it - - if(flag.ctf_dropper) - { - PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag - ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag - flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time - } - - // other - if(player.flagcarried == flag) - WaypointSprite_Kill(player.wps_flagcarrier); - - // reset the flag - ctf_RespawnFlag(flag); -} - -void ctf_Handle_Pickup(entity flag, entity player, int pickuptype) -{ - // declarations - float pickup_dropped_score; // used to calculate dropped pickup score - entity tmp_entity; // temporary entity - - // attach the flag to the player - flag.owner = player; - player.flagcarried = flag; - if(player.vehicle) - { - setattachment(flag, player.vehicle, ""); - setorigin(flag, VEHICLE_FLAG_OFFSET); - flag.scale = VEHICLE_FLAG_SCALE; - } - else - { - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); - } - - // flag setup - flag.movetype = MOVETYPE_NONE; - flag.takedamage = DAMAGE_NO; - flag.solid = SOLID_NOT; - flag.angles = '0 0 0'; - flag.ctf_status = FLAG_CARRY; - - switch(pickuptype) - { - case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs - case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit - default: break; - } - - // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname); - if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); } - if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); } - else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); } - else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); } - - Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname); - - if(!flag.team) - FOR_EACH_PLAYER(tmp_entity) - if(tmp_entity != player) - if(DIFF_TEAM(player, tmp_entity)) - Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); - - if(flag.team) - FOR_EACH_PLAYER(tmp_entity) - if(tmp_entity != player) - if(CTF_SAMETEAM(flag, tmp_entity)) - if(SAME_TEAM(player, tmp_entity)) - Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname); - else - Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname); - - _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE); - - // scoring - PlayerScore_Add(player, SP_CTF_PICKUPS, 1); - nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); - switch(pickuptype) - { - case PICKUP_BASE: - { - PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base); - ctf_EventLog("steal", flag.team, player); - break; - } - - case PICKUP_DROPPED: - { - pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1); - pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5); - LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n"); - PlayerTeamScore_AddScore(player, pickup_dropped_score); - ctf_EventLog("pickup", flag.team, player); - break; - } - - default: break; - } - - // speedrunning - if(pickuptype == PICKUP_BASE) - { - flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record - if((player.speedrunning) && (ctf_captimerecord)) - ctf_FakeTimeLimit(player, time + ctf_captimerecord); - } - - // effects - Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1); - - // waypoints - if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } - ctf_FlagcarrierWaypoints(player); - WaypointSprite_Ping(player.wps_flagcarrier); -} - - -// =================== -// Main Flag Functions -// =================== - -void ctf_CheckFlagReturn(entity flag, int returntype) -{ - if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING)) - { - if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); } - - if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time)) - { - switch(returntype) - { - case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break; - case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break; - case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break; - case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break; - - default: - case RETURN_TIMEOUT: - { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; } - } - _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE); - ctf_EventLog("returned", flag.team, world); - ctf_RespawnFlag(flag); - } - } -} - -bool ctf_Stalemate_Customize() -{SELFPARAM(); - // make spectators see what the player would see - entity e, wp_owner; - e = WaypointSprite_getviewentity(other); - wp_owner = self.owner; - - // team waypoints - if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; } - if(SAME_TEAM(wp_owner, e)) { return false; } - if(!IS_PLAYER(e)) { return false; } - - return true; -} - -void ctf_CheckStalemate(void) -{ - // declarations - int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; - entity tmp_entity; - - entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs - - // build list of stale flags - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - { - if(autocvar_g_ctf_stalemate) - if(tmp_entity.ctf_status != FLAG_BASE) - if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag - { - tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist - ctf_staleflaglist = tmp_entity; - - switch(tmp_entity.team) - { - case NUM_TEAM_1: ++stale_red_flags; break; - case NUM_TEAM_2: ++stale_blue_flags; break; - case NUM_TEAM_3: ++stale_yellow_flags; break; - case NUM_TEAM_4: ++stale_pink_flags; break; - default: ++stale_neutral_flags; break; - } - } - } - - if(ctf_oneflag) - stale_flags = (stale_neutral_flags >= 1); - else - stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); - - if(ctf_oneflag && stale_flags == 1) - ctf_stalemate = true; - else if(stale_flags >= 2) - ctf_stalemate = true; - else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) - { ctf_stalemate = false; wpforenemy_announced = false; } - else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1) - { ctf_stalemate = false; wpforenemy_announced = false; } - - // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary - if(ctf_stalemate) - { - for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext) - { - if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) - { - entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG); - wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team); - tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize; - } - } - - if (!wpforenemy_announced) - { - FOR_EACH_REALPLAYER(tmp_entity) - Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); - - wpforenemy_announced = true; - } - } -} - -void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{SELFPARAM(); - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - if(autocvar_g_ctf_flag_return_damage_delay) - { - self.ctf_flagdamaged = true; - } - else - { - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); - } - return; - } - if(autocvar_g_ctf_flag_return_damage) - { - // reduce health and check if it should be returned - self.health = self.health - damage; - ctf_CheckFlagReturn(self, RETURN_DAMAGE); - return; - } -} - -void ctf_FlagThink() -{SELFPARAM(); - // declarations - entity tmp_entity; - - self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary. - - // captureshield - if(self == ctf_worldflaglist) // only for the first flag - FOR_EACH_CLIENT(tmp_entity) - ctf_CaptureShield_Update(tmp_entity, 1); // release shield only - - // sanity checks - if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished - LOG_TRACE("wtf the flag got squashed?\n"); - tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self); - if(!trace_startsolid || self.noalign) // can we resize it without getting stuck? - setsize(self, FLAG_MIN, FLAG_MAX); } - - switch(self.ctf_status) // reset flag angles in case warpzones adjust it - { - case FLAG_DROPPED: - { - self.angles = '0 0 0'; - break; - } - - default: break; - } - - // main think method - switch(self.ctf_status) - { - case FLAG_BASE: - { - if(autocvar_g_ctf_dropped_capture_radius) - { - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - if(tmp_entity.ctf_status == FLAG_DROPPED) - if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius) - if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay) - ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED); - } - return; - } - - case FLAG_DROPPED: - { - if(autocvar_g_ctf_flag_dropped_floatinwater) - { - vector midpoint = ((self.absmin + self.absmax) * 0.5); - if(pointcontents(midpoint) == CONTENT_WATER) - { - self.velocity = self.velocity * 0.5; - - if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER) - { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; } - else - { self.movetype = MOVETYPE_FLY; } - } - else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; } - } - if(autocvar_g_ctf_flag_return_dropped) - { - if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1)) - { - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_DROPPED); - return; - } - } - if(self.ctf_flagdamaged) - { - self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); - return; - } - else if(autocvar_g_ctf_flag_return_time) - { - self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE); - ctf_CheckFlagReturn(self, RETURN_TIMEOUT); - return; - } - return; - } - - case FLAG_CARRY: - { - if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) - { - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_SPEEDRUN); - - setself(self.owner); - self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set - ImpulseCommands(); - setself(this); - } - if(autocvar_g_ctf_stalemate) - { - if(time >= wpforenemy_nextthink) - { - ctf_CheckStalemate(); - wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) - } - } - if(CTF_SAMETEAM(self, self.owner) && self.team) - { - if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed - ctf_Handle_Throw(self.owner, world, DROP_THROW); - else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius) - ctf_Handle_Return(self, self.owner); - } - return; - } - - case FLAG_PASSING: - { - vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5); - targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us) - WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self); - - if((self.pass_target == world) - || (self.pass_target.deadflag != DEAD_NO) - || (self.pass_target.flagcarried) - || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius) - || ((trace_fraction < 1) && (trace_ent != self.pass_target)) - || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) - { - // give up, pass failed - ctf_Handle_Drop(self, world, DROP_PASS); - } - else - { - // still a viable target, go for it - ctf_CalculatePassVelocity(self, targ_origin, self.origin, true); - } - return; - } - - default: // this should never happen - { - LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n"); - return; - } - } -} - -void ctf_FlagTouch() -{SELFPARAM(); - if(gameover) { return; } - if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } - - entity toucher = other, tmp_entity; - bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0; - - // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces - if(ITEM_TOUCH_NEEDKILL()) - { - if(!autocvar_g_ctf_flag_return_damage_delay) - { - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); - } - if(!self.ctf_flagdamaged) { return; } - } - - FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; } - - // special touch behaviors - if(toucher.frozen) { return; } - else if(IS_VEHICLE(toucher)) - { - if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) - toucher = toucher.owner; // the player is actually the vehicle owner, not other - else - return; // do nothing - } - else if(IS_MONSTER(toucher)) - { - if(!autocvar_g_ctf_allow_monster_touch) - return; // do nothing - } - else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world - { - if(time > self.wait) // if we haven't in a while, play a sound/effect - { - Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1); - _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM); - self.wait = time + FLAG_TOUCHRATE; - } - return; - } - else if(toucher.deadflag != DEAD_NO) { return; } - - switch(self.ctf_status) - { - case FLAG_BASE: - { - if(ctf_oneflag) - { - if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) - ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base - else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) - ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag - } - else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) - ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base - else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) - ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag - break; - } - - case FLAG_DROPPED: - { - if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team - ctf_Handle_Return(self, toucher); // toucher just returned his own flag - else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay))) - ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag - break; - } - - case FLAG_CARRY: - { - LOG_TRACE("Someone touched a flag even though it was being carried?\n"); - break; - } - - case FLAG_PASSING: - { - if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender)) - { - if(DIFF_TEAM(toucher, self.pass_sender)) - ctf_Handle_Return(self, toucher); - else - ctf_Handle_Retrieve(self, toucher); - } - break; - } - } -} - -.float last_respawn; -void ctf_RespawnFlag(entity flag) -{ - // check for flag respawn being called twice in a row - if(flag.last_respawn > time - 0.5) - { backtrace("flag respawn called twice quickly! please notify Samual about this..."); } - - flag.last_respawn = time; - - // reset the player (if there is one) - if((flag.owner) && (flag.owner.flagcarried == flag)) - { - WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); - WaypointSprite_Kill(flag.wps_flagcarrier); - - flag.owner.flagcarried = world; - - if(flag.speedrunning) - ctf_FakeTimeLimit(flag.owner, -1); - } - - if((flag.owner) && (flag.owner.vehicle)) - flag.scale = FLAG_SCALE; - - if(flag.ctf_status == FLAG_DROPPED) - { WaypointSprite_Kill(flag.wps_flagdropped); } - - // reset the flag - setattachment(flag, world, ""); - setorigin(flag, flag.ctf_spawnorigin); - - flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS); - flag.takedamage = DAMAGE_NO; - flag.health = flag.max_flag_health; - flag.solid = SOLID_TRIGGER; - flag.velocity = '0 0 0'; - flag.angles = flag.mangle; - flag.flags = FL_ITEM | FL_NOTARGET; - - flag.ctf_status = FLAG_BASE; - flag.owner = world; - flag.pass_distance = 0; - flag.pass_sender = world; - flag.pass_target = world; - flag.ctf_dropper = world; - flag.ctf_pickuptime = 0; - flag.ctf_droptime = 0; - flag.ctf_flagdamaged = 0; - - ctf_CheckStalemate(); -} - -void ctf_Reset() -{SELFPARAM(); - if(self.owner) - if(IS_PLAYER(self.owner)) - ctf_Handle_Throw(self.owner, world, DROP_RESET); - - ctf_RespawnFlag(self); -} - -void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup() -{SELFPARAM(); - // bot waypoints - waypoint_spawnforitem_force(self, self.origin); - self.nearestwaypointtimeout = 0; // activate waypointing again - self.bot_basewaypoint = self.nearestwaypoint; - - // waypointsprites - entity basename; - switch (self.team) - { - case NUM_TEAM_1: basename = WP_FlagBaseRed; break; - case NUM_TEAM_2: basename = WP_FlagBaseBlue; break; - case NUM_TEAM_3: basename = WP_FlagBaseYellow; break; - case NUM_TEAM_4: basename = WP_FlagBasePink; break; - default: basename = WP_FlagBaseNeutral; break; - } - - entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG); - wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'); - WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1')); - - // captureshield setup - ctf_CaptureShield_Spawn(self); -} - -void set_flag_string(entity flag, .string field, string value, string teamname) -{ - if(flag.(field) == "") - flag.(field) = strzone(sprintf(value,teamname)); -} - -void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc -{SELFPARAM(); - // declarations - setself(flag); // for later usage with droptofloor() - - // main setup - flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist - ctf_worldflaglist = flag; - - setattachment(flag, world, ""); - - flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber))); - flag.team = teamnumber; - flag.classname = "item_flag_team"; - flag.target = "###item###"; // wut? - flag.flags = FL_ITEM | FL_NOTARGET; - flag.solid = SOLID_TRIGGER; - flag.takedamage = DAMAGE_NO; - flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale; - flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100); - flag.health = flag.max_flag_health; - flag.event_damage = ctf_FlagDamage; - flag.pushable = true; - flag.teleportable = TELEPORT_NORMAL; - flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable; - flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable; - flag.velocity = '0 0 0'; - flag.mangle = flag.angles; - flag.reset = ctf_Reset; - flag.touch = ctf_FlagTouch; - flag.think = ctf_FlagThink; - flag.nextthink = time + FLAG_THINKRATE; - flag.ctf_status = FLAG_BASE; - - string teamname = Static_Team_ColorName_Lower(teamnumber); - // appearence - if(!flag.scale) { flag.scale = FLAG_SCALE; } - if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); } - if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); } - set_flag_string(flag, toucheffect, "%sflag_touch", teamname); - set_flag_string(flag, passeffect, "%s_pass", teamname); - set_flag_string(flag, capeffect, "%s_cap", teamname); - - // sounds - flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber)); - flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber)); - flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber)); - flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber)); - if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match. - precache_sound(flag.snd_flag_respawn); - if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound - precache_sound(flag.snd_flag_touch); - if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here - precache_sound(flag.snd_flag_pass); - - // precache - precache_model(flag.model); - - // appearence - _setmodel(flag, flag.model); // precision set below - setsize(flag, FLAG_MIN, FLAG_MAX); - setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET)); - - if(autocvar_g_ctf_flag_glowtrails) - { - switch(teamnumber) - { - case NUM_TEAM_1: flag.glow_color = 251; break; - case NUM_TEAM_2: flag.glow_color = 210; break; - case NUM_TEAM_3: flag.glow_color = 110; break; - case NUM_TEAM_4: flag.glow_color = 145; break; - default: flag.glow_color = 254; break; - } - flag.glow_size = 25; - flag.glow_trail = 1; - } - - flag.effects |= EF_LOWPRECISION; - if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; } - if(autocvar_g_ctf_dynamiclights) - { - switch(teamnumber) - { - case NUM_TEAM_1: flag.effects |= EF_RED; break; - case NUM_TEAM_2: flag.effects |= EF_BLUE; break; - case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; - case NUM_TEAM_4: flag.effects |= EF_RED; break; - default: flag.effects |= EF_DIMLIGHT; break; - } - } - - // flag placement - if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location - { - flag.dropped_origin = flag.origin; - flag.noalign = true; - flag.movetype = MOVETYPE_NONE; - } - else // drop to floor, automatically find a platform and set that as spawn origin - { - flag.noalign = false; - setself(flag); - droptofloor(); - flag.movetype = MOVETYPE_TOSS; - } - - InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION); -} - - -// ================ -// Bot player logic -// ================ - -// NOTE: LEGACY CODE, needs to be re-written! - -void havocbot_calculate_middlepoint() -{ - entity f; - vector s = '0 0 0'; - vector fo = '0 0 0'; - float n = 0; - - f = ctf_worldflaglist; - while (f) - { - fo = f.origin; - s = s + fo; - f = f.ctf_worldflagnext; - } - if(!n) - return; - havocbot_ctf_middlepoint = s * (1.0 / n); - havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint); -} - - -entity havocbot_ctf_find_flag(entity bot) -{ - entity f; - f = ctf_worldflaglist; - while (f) - { - if (CTF_SAMETEAM(bot, f)) - return f; - f = f.ctf_worldflagnext; - } - return world; -} - -entity havocbot_ctf_find_enemy_flag(entity bot) -{ - entity f; - f = ctf_worldflaglist; - while (f) - { - if(ctf_oneflag) - { - if(CTF_DIFFTEAM(bot, f)) - { - if(f.team) - { - if(bot.flagcarried) - return f; - } - else if(!bot.flagcarried) - return f; - } - } - else if (CTF_DIFFTEAM(bot, f)) - return f; - f = f.ctf_worldflagnext; - } - return world; -} - -int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) -{ - if (!teamplay) - return 0; - - int c = 0; - entity head; - - FOR_EACH_PLAYER(head) - { - if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot) - continue; - - if(vlen(head.origin - org) < tc_radius) - ++c; - } - - return c; -} - -void havocbot_goalrating_ctf_ourflag(float ratingscale) -{SELFPARAM(); - entity head; - head = ctf_worldflaglist; - while (head) - { - if (CTF_SAMETEAM(self, head)) - break; - head = head.ctf_worldflagnext; - } - if (head) - navigation_routerating(head, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_ourbase(float ratingscale) -{SELFPARAM(); - entity head; - head = ctf_worldflaglist; - while (head) - { - if (CTF_SAMETEAM(self, head)) - break; - head = head.ctf_worldflagnext; - } - if (!head) - return; - - navigation_routerating(head.bot_basewaypoint, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_enemyflag(float ratingscale) -{SELFPARAM(); - entity head; - head = ctf_worldflaglist; - while (head) - { - if(ctf_oneflag) - { - if(CTF_DIFFTEAM(self, head)) - { - if(head.team) - { - if(self.flagcarried) - break; - } - else if(!self.flagcarried) - break; - } - } - else if(CTF_DIFFTEAM(self, head)) - break; - head = head.ctf_worldflagnext; - } - if (head) - navigation_routerating(head, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_enemybase(float ratingscale) -{SELFPARAM(); - if (!bot_waypoints_for_items) - { - havocbot_goalrating_ctf_enemyflag(ratingscale); - return; - } - - entity head; - - head = havocbot_ctf_find_enemy_flag(self); - - if (!head) - return; - - navigation_routerating(head.bot_basewaypoint, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_ourstolenflag(float ratingscale) -{SELFPARAM(); - entity mf; - - mf = havocbot_ctf_find_flag(self); - - if(mf.ctf_status == FLAG_BASE) - return; - - if(mf.tag_entity) - navigation_routerating(mf.tag_entity, ratingscale, 10000); -} - -void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius) -{ - entity head; - head = ctf_worldflaglist; - while (head) - { - // flag is out in the field - if(head.ctf_status != FLAG_BASE) - if(head.tag_entity==world) // dropped - { - if(df_radius) - { - if(vlen(org-head.origin) 0) - navigation_routerating(head, t * ratingscale, 500); - } - head = head.chain; - } -} - -void havocbot_ctf_reset_role(entity bot) -{ - float cdefense, cmiddle, coffense; - entity mf, ef, head; - float c; - - if(bot.deadflag != DEAD_NO) - return; - - if(vlen(havocbot_ctf_middlepoint)==0) - havocbot_calculate_middlepoint(); - - // Check ctf flags - if (bot.flagcarried) - { - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - mf = havocbot_ctf_find_flag(bot); - ef = havocbot_ctf_find_enemy_flag(bot); - - // Retrieve stolen flag - if(mf.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - - // If enemy flag is taken go to the middle to intercept pursuers - if(ef.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE); - return; - } - - // if there is only me on the team switch to offense - c = 0; - FOR_EACH_PLAYER(head) - if(SAME_TEAM(head, bot)) - ++c; - - if(c==1) - { - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE); - return; - } - - // Evaluate best position to take - // Count mates on middle position - cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5); - - // Count mates on defense position - cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5); - - // Count mates on offense position - coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius); - - if(cdefense<=coffense) - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE); - else if(coffense<=cmiddle) - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE); - else - havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE); -} - -void havocbot_role_ctf_carrier() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.flagcarried == world) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - - navigation_goalrating_start(); - if(ctf_oneflag) - havocbot_goalrating_ctf_enemybase(50000); - else - havocbot_goalrating_ctf_ourbase(50000); - - if(self.health<100) - havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000); - - navigation_goalrating_end(); - - if (self.navigation_hasgoals) - self.havocbot_cantfindflag = time + 10; - else if (time > self.havocbot_cantfindflag) - { - // Can't navigate to my own base, suicide! - // TODO: drop it and wander around - Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0'); - return; - } - } -} - -void havocbot_role_ctf_escort() -{SELFPARAM(); - entity mf, ef; - - if(self.deadflag != DEAD_NO) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.flagcarried) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // If enemy flag is back on the base switch to previous role - ef = havocbot_ctf_find_enemy_flag(self); - if(ef.ctf_status==FLAG_BASE) - { - self.havocbot_role = self.havocbot_previous_role; - self.havocbot_role_timeout = 0; - return; - } - - // If the flag carrier reached the base switch to defense - mf = havocbot_ctf_find_flag(self); - if(mf.ctf_status!=FLAG_BASE) - if(vlen(ef.origin - mf.dropped_origin) < 300) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE); - return; - } - - // Set the role timeout if necessary - if (!self.havocbot_role_timeout) - { - self.havocbot_role_timeout = time + random() * 30 + 60; - } - - // If nothing happened just switch to previous role - if (time > self.havocbot_role_timeout) - { - self.havocbot_role = self.havocbot_previous_role; - self.havocbot_role_timeout = 0; - return; - } - - // Chase the flag carrier - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - havocbot_goalrating_ctf_enemyflag(30000); - havocbot_goalrating_ctf_ourstolenflag(40000); - havocbot_goalrating_items(10000, self.origin, 10000); - navigation_goalrating_end(); - } -} - -void havocbot_role_ctf_offense() -{SELFPARAM(); - entity mf, ef; - vector pos; - - if(self.deadflag != DEAD_NO) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.flagcarried) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // Check flags - mf = havocbot_ctf_find_flag(self); - ef = havocbot_ctf_find_enemy_flag(self); - - // Own flag stolen - if(mf.ctf_status!=FLAG_BASE) - { - if(mf.tag_entity) - pos = mf.tag_entity.origin; - else - pos = mf.origin; - - // Try to get it if closer than the enemy base - if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos)) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - } - - // Escort flag carrier - if(ef.ctf_status!=FLAG_BASE) - { - if(ef.tag_entity) - pos = ef.tag_entity.origin; - else - pos = ef.origin; - - if(vlen(pos-mf.dropped_origin)>700) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT); - return; - } - } - - // About to fail, switch to middlefield - if(self.health<50) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE); - return; - } - - // Set the role timeout if necessary - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + 120; - - if (time > self.havocbot_role_timeout) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - havocbot_goalrating_ctf_ourstolenflag(50000); - havocbot_goalrating_ctf_enemybase(20000); - havocbot_goalrating_items(5000, self.origin, 1000); - havocbot_goalrating_items(1000, self.origin, 10000); - navigation_goalrating_end(); - } -} - -// Retriever (temporary role): -void havocbot_role_ctf_retriever() -{SELFPARAM(); - entity mf; - - if(self.deadflag != DEAD_NO) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.flagcarried) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // If flag is back on the base switch to previous role - mf = havocbot_ctf_find_flag(self); - if(mf.ctf_status==FLAG_BASE) - { - havocbot_ctf_reset_role(self); - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + 20; - - if (time > self.havocbot_role_timeout) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.bot_strategytime < time) - { - float rt_radius; - rt_radius = 10000; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - havocbot_goalrating_ctf_ourstolenflag(50000); - havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius); - havocbot_goalrating_ctf_enemybase(30000); - havocbot_goalrating_items(500, self.origin, rt_radius); - navigation_goalrating_end(); - } -} - -void havocbot_role_ctf_middle() -{SELFPARAM(); - entity mf; - - if(self.deadflag != DEAD_NO) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.flagcarried) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - mf = havocbot_ctf_find_flag(self); - if(mf.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + 10; - - if (time > self.havocbot_role_timeout) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.bot_strategytime < time) - { - vector org; - - org = havocbot_ctf_middlepoint; - org.z = self.origin.z; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - havocbot_goalrating_ctf_ourstolenflag(50000); - havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000); - havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5); - havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5); - havocbot_goalrating_items(2500, self.origin, 10000); - havocbot_goalrating_ctf_enemybase(2500); - navigation_goalrating_end(); - } -} - -void havocbot_role_ctf_defense() -{SELFPARAM(); - entity mf; - - if(self.deadflag != DEAD_NO) - { - havocbot_ctf_reset_role(self); - return; - } - - if (self.flagcarried) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); - return; - } - - // If own flag was captured - mf = havocbot_ctf_find_flag(self); - if(mf.ctf_status!=FLAG_BASE) - { - havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER); - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + 30; - - if (time > self.havocbot_role_timeout) - { - havocbot_ctf_reset_role(self); - return; - } - if (self.bot_strategytime < time) - { - float mp_radius; - vector org; - - org = mf.dropped_origin; - mp_radius = havocbot_ctf_middlepoint_radius; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - // if enemies are closer to our base, go there - entity head, closestplayer = world; - float distance, bestdistance = 10000; - FOR_EACH_PLAYER(head) - { - if(head.deadflag!=DEAD_NO) - continue; - - distance = vlen(org - head.origin); - if(distance1000) - if(checkpvs(self.origin,closestplayer)||random()<0.5) - havocbot_goalrating_ctf_ourbase(30000); - - havocbot_goalrating_ctf_ourstolenflag(20000); - havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius); - havocbot_goalrating_enemyplayers(15000, org, mp_radius); - havocbot_goalrating_items(10000, org, mp_radius); - havocbot_goalrating_items(5000, self.origin, 10000); - navigation_goalrating_end(); - } -} - -void havocbot_role_ctf_setrole(entity bot, int role) -{ - LOG_TRACE(strcat(bot.netname," switched to ")); - switch(role) - { - case HAVOCBOT_CTF_ROLE_CARRIER: - LOG_TRACE("carrier"); - bot.havocbot_role = havocbot_role_ctf_carrier; - bot.havocbot_role_timeout = 0; - bot.havocbot_cantfindflag = time + 10; - bot.bot_strategytime = 0; - break; - case HAVOCBOT_CTF_ROLE_DEFENSE: - LOG_TRACE("defense"); - bot.havocbot_role = havocbot_role_ctf_defense; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_CTF_ROLE_MIDDLE: - LOG_TRACE("middle"); - bot.havocbot_role = havocbot_role_ctf_middle; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_CTF_ROLE_OFFENSE: - LOG_TRACE("offense"); - bot.havocbot_role = havocbot_role_ctf_offense; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_CTF_ROLE_RETRIEVER: - LOG_TRACE("retriever"); - bot.havocbot_previous_role = bot.havocbot_role; - bot.havocbot_role = havocbot_role_ctf_retriever; - bot.havocbot_role_timeout = time + 10; - bot.bot_strategytime = 0; - break; - case HAVOCBOT_CTF_ROLE_ESCORT: - LOG_TRACE("escort"); - bot.havocbot_previous_role = bot.havocbot_role; - bot.havocbot_role = havocbot_role_ctf_escort; - bot.havocbot_role_timeout = time + 30; - bot.bot_strategytime = 0; - break; - } - LOG_TRACE("\n"); -} - - -// ============== -// Hook Functions -// ============== - -MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink) -{SELFPARAM(); - entity flag; - int t = 0, t2 = 0, t3 = 0; - - // initially clear items so they can be set as necessary later. - self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST - | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST - | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST - | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST - | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST - | CTF_FLAG_NEUTRAL | CTF_SHIELDED); - - // scan through all the flags and notify the client about them - for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) - { - if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } - if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } - if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } - if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } - if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; } - - switch(flag.ctf_status) - { - case FLAG_PASSING: - case FLAG_CARRY: - { - if((flag.owner == self) || (flag.pass_sender == self)) - self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag - else - self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag - break; - } - case FLAG_DROPPED: - { - self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map - break; - } - } - } - - // item for stopping players from capturing the flag too often - if(self.ctf_captureshielded) - self.ctf_flagstatus |= CTF_SHIELDED; - - // update the health of the flag carrier waypointsprite - if(self.wps_flagcarrier) - WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc -{ - if(frag_attacker.flagcarried) // if the attacker is a flagcarrier - { - if(frag_target == frag_attacker) // damage done to yourself - { - frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor; - frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor; - } - else // damage done to everyone else - { - frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor; - frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; - } - } - else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier - { - if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id))) - if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time) - { - frag_target.wps_helpme_time = time; - WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); - } - // todo: add notification for when flag carrier needs help? - } - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, PlayerDies) -{ - if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried)) - { - PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill); - PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1); - } - - if(frag_target.flagcarried) - { - entity tmp_entity = frag_target.flagcarried; - ctf_Handle_Throw(frag_target, world, DROP_NORMAL); - tmp_entity.ctf_dropper = world; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill) -{ - frag_score = 0; - return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true -} - -void ctf_RemovePlayer(entity player) -{ - if(player.flagcarried) - { ctf_Handle_Throw(player, world, DROP_NORMAL); } - - for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) - { - if(flag.pass_sender == player) { flag.pass_sender = world; } - if(flag.pass_target == player) { flag.pass_target = world; } - if(flag.ctf_dropper == player) { flag.ctf_dropper = world; } - } -} - -MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver) -{SELFPARAM(); - ctf_RemovePlayer(self); - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect) -{SELFPARAM(); - ctf_RemovePlayer(self); - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, PortalTeleport) -{SELFPARAM(); - if(self.flagcarried) - if(!autocvar_g_ctf_portalteleport) - { ctf_Handle_Throw(self, world, DROP_NORMAL); } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE || gameover) { return false; } - - entity player = self; - - if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch)) - { - // pass the flag to a team mate - if(autocvar_g_ctf_pass) - { - entity head, closest_target = world; - head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true); - - while(head) // find the closest acceptable target to pass to - { - if(IS_PLAYER(head) && head.deadflag == DEAD_NO) - if(head != player && SAME_TEAM(head, player)) - if(!head.speedrunning && !head.vehicle) - { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); - vector passer_center = CENTER_OR_VIEWOFS(player); - - if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest)) - { - if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) - { - if(IS_BOT_CLIENT(head)) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); - ctf_Handle_Throw(head, player, DROP_PASS); - } - else - { - Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname); - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); - } - player.throw_antispam = time + autocvar_g_ctf_pass_wait; - return true; - } - else if(player.flagcarried) - { - if(closest_target) - { - vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); - if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center)) - { closest_target = head; } - } - else { closest_target = head; } - } - } - } - head = head.chain; - } - - if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; } - } - - // throw the flag in front of you - if(autocvar_g_ctf_throw && player.flagcarried) - { - if(player.throw_count == -1) - { - if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - { - player.throw_prevtime = time; - player.throw_count = 1; - ctf_Handle_Throw(player, world, DROP_THROW); - return true; - } - else - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)); - return false; - } - } - else - { - if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; } - else { player.throw_count += 1; } - if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; } - - player.throw_prevtime = time; - ctf_Handle_Throw(player, world, DROP_THROW); - return true; - } - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, HelpMePing) -{SELFPARAM(); - if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification - { - self.wps_helpme_time = time; - WaypointSprite_HelpMePing(self.wps_flagcarrier); - } - else // create a normal help me waypointsprite - { - WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME); - WaypointSprite_Ping(self.wps_helpme); - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ctf, VehicleEnter) -{ - if(vh_player.flagcarried) - { - vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver - - if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch) - { - ctf_Handle_Throw(vh_player, world, DROP_NORMAL); - } - else - { - setattachment(vh_player.flagcarried, vh_vehicle, ""); - setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET); - vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE; - //vh_player.flagcarried.angles = '0 0 0'; - } - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, VehicleExit) -{ - if(vh_player.flagcarried) - { - setattachment(vh_player.flagcarried, vh_player, ""); - setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET); - vh_player.flagcarried.scale = FLAG_SCALE; - vh_player.flagcarried.angles = '0 0 0'; - vh_player.flagcarried.nodrawtoclient = world; - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun) -{SELFPARAM(); - if(self.flagcarried) - { - Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL)); - ctf_RespawnFlag(self.flagcarried); - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, MatchEnd) -{ - entity flag; // temporary entity for the search method - - for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) - { - switch(flag.ctf_status) - { - case FLAG_DROPPED: - case FLAG_PASSING: - { - // lock the flag, game is over - flag.movetype = MOVETYPE_NONE; - flag.takedamage = DAMAGE_NO; - flag.solid = SOLID_NOT; - flag.nextthink = false; // stop thinking - - //dprint("stopping the ", flag.netname, " from moving.\n"); - break; - } - - default: - case FLAG_BASE: - case FLAG_CARRY: - { - // do nothing for these flags - break; - } - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole) -{SELFPARAM(); - havocbot_ctf_reset_role(self); - return true; -} - -MUTATOR_HOOKFUNCTION(ctf, GetTeamCount) -{ - //ret_float = ctf_teams; - ret_string = "ctf_team"; - return true; -} - -MUTATOR_HOOKFUNCTION(ctf, SpectateCopy) -{SELFPARAM(); - self.ctf_flagstatus = other.ctf_flagstatus; - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, GetRecords) -{ - for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) - { - if (MapInfo_Get_ByID(i)) - { - float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time"))); - - if(!r) - continue; - - // TODO: uid2name - string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname")); - ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n"); - } - } - - return false; -} - -bool superspec_Spectate(entity _player); // TODO -void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO -MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand) -{ - if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; } - - if(cmd_name == "followfc") - { - if(!g_ctf) - return true; - - entity _player; - int _team = 0; - bool found = false; - - if(cmd_argc == 2) - { - switch(argv(1)) - { - case "red": _team = NUM_TEAM_1; break; - case "blue": _team = NUM_TEAM_2; break; - case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break; - case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break; - } - } - - FOR_EACH_PLAYER(_player) - { - if(_player.flagcarried && (_player.team == _team || _team == 0)) - { - found = true; - if(_team == 0 && IS_SPEC(self) && self.enemy == _player) - continue; // already spectating a fc, try to find the other fc - return superspec_Spectate(_player); - } - } - - if(!found) - superspec_msg("", "", self, "No active flag carrier\n", 1); - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems) -{ - if(frag_target.flagcarried) - ctf_Handle_Throw(frag_target, world, DROP_THROW); - - return false; -} - - -// ========== -// Spawnfuncs -// ========== - -/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team one (Red). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red and blue as skins 0 and 1... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team1) -{ - if(!g_ctf) { remove(self); return; } - - ctf_FlagSetup(NUM_TEAM_1, self); -} - -/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team two (Blue). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red and blue as skins 0 and 1... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team2) -{ - if(!g_ctf) { remove(self); return; } - - ctf_FlagSetup(NUM_TEAM_2, self); -} - -/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team three (Yellow). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team3) -{ - if(!g_ctf) { remove(self); return; } - - ctf_FlagSetup(NUM_TEAM_3, self); -} - -/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team four (Pink). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_team4) -{ - if(!g_ctf) { remove(self); return; } - - ctf_FlagSetup(NUM_TEAM_4, self); -} - -/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag (Neutral). -Keys: -"angle" Angle the flag will point (minus 90 degrees)... -"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... -"noise" sound played when flag is picked up... -"noise1" sound played when flag is returned by a teammate... -"noise2" sound played when flag is captured... -"noise3" sound played when flag is lost in the field and respawns itself... -"noise4" sound played when flag is dropped by a player... -"noise5" sound played when flag touches the ground... */ -spawnfunc(item_flag_neutral) -{ - if(!g_ctf) { remove(self); return; } - if(!cvar("g_ctf_oneflag")) { remove(self); return; } - - ctf_FlagSetup(0, self); -} - -/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) -Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map. -Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. -Keys: -"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... -"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ -spawnfunc(ctf_team) -{ - if(!g_ctf) { remove(self); return; } - - self.classname = "ctf_team"; - self.team = self.cnt + 1; -} - -// compatibility for quake maps -spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); } -spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); } -spawnfunc(info_player_team1); -spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); } -spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); } -spawnfunc(info_player_team2); -spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); } -spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); } - -void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); } -void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); } - - -// ============== -// Initialization -// ============== - -// scoreboard setup -void ctf_ScoreRules(int teams) -{ - CheckAllowedTeams(world); - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); - ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); - ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0); - ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0); - ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0); - ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER); - ScoreRules_basics_end(); -} - -// code from here on is just to support maps that don't have flag and team entities -void ctf_SpawnTeam (string teamname, int teamcolor) -{ - entity this = new(ctf_team); - this.netname = teamname; - this.cnt = teamcolor; - this.spawnfunc_checked = true; - WITH(entity, self, this, spawnfunc_ctf_team(this)); -} - -void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. -{ - ctf_teams = 2; - - entity tmp_entity; - for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) - { - if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } - if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } - if(tmp_entity.team == 0) { ctf_oneflag = true; } - } - - ctf_teams = bound(2, ctf_teams, 4); - - // if no teams are found, spawn defaults - if(find(world, classname, "ctf_team") == world) - { - LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n"); - ctf_SpawnTeam("Red", NUM_TEAM_1 - 1); - ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1); - if(ctf_teams >= 3) - ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1); - if(ctf_teams >= 4) - ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1); - } - - ctf_ScoreRules(ctf_teams); -} - -void ctf_Initialize() -{ - ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"))); - - ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore; - ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio; - ctf_captureshield_force = autocvar_g_ctf_shield_force; - - addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus); - - InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE); -} - -REGISTER_MUTATOR(ctf, g_ctf) -{ - ActivateTeamplay(); - SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1); - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - ctf_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back ctf_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_ctf.qh b/qcsrc/server/mutators/gamemode_ctf.qh deleted file mode 100644 index 2a8b4818c..000000000 --- a/qcsrc/server/mutators/gamemode_ctf.qh +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef GAMEMODE_CTF_H -#define GAMEMODE_CTF_H -// these are needed since mutators are compiled last - -#ifdef SVQC -// used in cheats.qc -void ctf_RespawnFlag(entity flag); - -// score rule declarations -const int ST_CTF_CAPS = 1; -const int SP_CTF_CAPS = 4; -const int SP_CTF_CAPTIME = 5; -const int SP_CTF_PICKUPS = 6; -const int SP_CTF_DROPS = 7; -const int SP_CTF_FCKILLS = 8; -const int SP_CTF_RETURNS = 9; - -// flag constants // for most of these, there is just one question to be asked: WHYYYYY? -#define FLAG_MIN (PL_MIN_CONST + '0 0 -13') -#define FLAG_MAX (PL_MAX_CONST + '0 0 -13') - -const float FLAG_SCALE = 0.6; - -const float FLAG_THINKRATE = 0.2; -const float FLAG_TOUCHRATE = 0.5; -const float WPFE_THINKRATE = 0.5; - -const vector FLAG_DROP_OFFSET = ('0 0 32'); -const vector FLAG_CARRY_OFFSET = ('-16 0 8'); -#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) -const vector FLAG_WAYPOINT_OFFSET = ('0 0 64'); -const vector FLAG_FLOAT_OFFSET = ('0 0 32'); -const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10'); - -const vector VEHICLE_FLAG_OFFSET = ('0 0 96'); -const float VEHICLE_FLAG_SCALE = 1.0; - -// waypoint colors -#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1') -#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color) -#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1') - -// sounds -#define snd_flag_taken noise -#define snd_flag_returned noise1 -#define snd_flag_capture noise2 -#define snd_flag_respawn noise3 -.string snd_flag_dropped; -.string snd_flag_touch; -.string snd_flag_pass; - -// effects -.string toucheffect; -.string passeffect; -.string capeffect; - -// list of flags on the map -entity ctf_worldflaglist; -.entity ctf_worldflagnext; -.entity ctf_staleflagnext; - -// waypoint sprites -.entity bot_basewaypoint; // flag waypointsprite -.entity wps_helpme; -.entity wps_flagbase; -.entity wps_flagcarrier; -.entity wps_flagdropped; -.entity wps_enemyflagcarrier; -.float wps_helpme_time; -bool wpforenemy_announced; -float wpforenemy_nextthink; - -// statuses -const int FLAG_BASE = 1; -const int FLAG_DROPPED = 2; -const int FLAG_CARRY = 3; -const int FLAG_PASSING = 4; - -const int DROP_NORMAL = 1; -const int DROP_THROW = 2; -const int DROP_PASS = 3; -const int DROP_RESET = 4; - -const int PICKUP_BASE = 1; -const int PICKUP_DROPPED = 2; - -const int CAPTURE_NORMAL = 1; -const int CAPTURE_DROPPED = 2; - -const int RETURN_TIMEOUT = 1; -const int RETURN_DROPPED = 2; -const int RETURN_DAMAGE = 3; -const int RETURN_SPEEDRUN = 4; -const int RETURN_NEEDKILL = 5; - -// flag properties -#define ctf_spawnorigin dropped_origin -bool ctf_stalemate; // indicates that a stalemate is active -float ctf_captimerecord; // record time for capturing the flag -.float ctf_pickuptime; -.float ctf_droptime; -.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally) -.entity ctf_dropper; // don't allow spam of dropping the flag -.int max_flag_health; -.float next_take_time; -.bool ctf_flagdamaged; -int ctf_teams; - -// passing/throwing properties -.float pass_distance; -.entity pass_sender; -.entity pass_target; -.float throw_antispam; -.float throw_prevtime; -.int throw_count; - -// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag. -.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture -float ctf_captureshield_min_negscore; // punish at -20 points -float ctf_captureshield_max_ratio; // punish at most 30% of each team -float ctf_captureshield_force; // push force of the shield - -// 1 flag ctf -bool ctf_oneflag; // indicates whether or not a neutral flag has been found - -// bot player logic -const int HAVOCBOT_CTF_ROLE_NONE = 0; -const int HAVOCBOT_CTF_ROLE_DEFENSE = 2; -const int HAVOCBOT_CTF_ROLE_MIDDLE = 4; -const int HAVOCBOT_CTF_ROLE_OFFENSE = 8; -const int HAVOCBOT_CTF_ROLE_CARRIER = 16; -const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32; -const int HAVOCBOT_CTF_ROLE_ESCORT = 64; - -.bool havocbot_cantfindflag; - -vector havocbot_ctf_middlepoint; -float havocbot_ctf_middlepoint_radius; - -void havocbot_role_ctf_setrole(entity bot, int role); - -// team checking -#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b)) -#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b)) - -// networked flag statuses -.int ctf_flagstatus; -#endif - -const int CTF_RED_FLAG_TAKEN = 1; -const int CTF_RED_FLAG_LOST = 2; -const int CTF_RED_FLAG_CARRYING = 3; -const int CTF_BLUE_FLAG_TAKEN = 4; -const int CTF_BLUE_FLAG_LOST = 8; -const int CTF_BLUE_FLAG_CARRYING = 12; -const int CTF_YELLOW_FLAG_TAKEN = 16; -const int CTF_YELLOW_FLAG_LOST = 32; -const int CTF_YELLOW_FLAG_CARRYING = 48; -const int CTF_PINK_FLAG_TAKEN = 64; -const int CTF_PINK_FLAG_LOST = 128; -const int CTF_PINK_FLAG_CARRYING = 192; -const int CTF_NEUTRAL_FLAG_TAKEN = 256; -const int CTF_NEUTRAL_FLAG_LOST = 512; -const int CTF_NEUTRAL_FLAG_CARRYING = 768; -const int CTF_FLAG_NEUTRAL = 2048; -const int CTF_SHIELDED = 4096; - -#endif diff --git a/qcsrc/server/mutators/gamemode_cts.qc b/qcsrc/server/mutators/gamemode_cts.qc deleted file mode 100644 index 679bbb094..000000000 --- a/qcsrc/server/mutators/gamemode_cts.qc +++ /dev/null @@ -1,436 +0,0 @@ -#include "gamemode_cts.qh" - -#include "gamemode.qh" - -#include "../race.qh" - -float autocvar_g_cts_finish_kill_delay; -bool autocvar_g_cts_selfdamage; - -// legacy bot roles -.float race_checkpoint; -void havocbot_role_cts() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - entity e; - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; ) - { - if(e.cnt == self.race_checkpoint) - { - navigation_routerating(e, 1000000, 5000); - } - else if(self.race_checkpoint == -1) - { - navigation_routerating(e, 1000000, 5000); - } - } - - navigation_goalrating_end(); - } -} - -void cts_ScoreRules() -{ - ScoreRules_basics(0, 0, 0, false); - if(g_race_qualifying) - { - ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); - } - else - { - ScoreInfo_SetLabel_PlayerScore(SP_CTS_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_CTS_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); - ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); - } - ScoreRules_basics_end(); -} - -void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":cts:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -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 -{ - e.killindicator = spawn(); - e.killindicator.owner = e; - e.killindicator.think = KillIndicator_Think; - e.killindicator.nextthink = time + (e.lip) * 0.05; - e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay); - e.killindicator.health = 1; // this is used to indicate that it should be silent - e.lip = 0; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerPhysics) -{SELFPARAM(); - self.race_movetime_frac += PHYS_INPUT_TIMELENGTH; - float f = floor(self.race_movetime_frac); - self.race_movetime_frac -= f; - self.race_movetime_count += f; - self.race_movetime = self.race_movetime_frac + self.race_movetime_count; - -#ifdef SVQC - if(IS_PLAYER(self)) - { - if (self.race_penalty) - if (time > self.race_penalty) - self.race_penalty = 0; - if(self.race_penalty) - { - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; - self.disableclientprediction = 2; - } - } -#endif - - // force kbd movement for fairness - float wishspeed; - vector wishvel; - - // if record times matter - // ensure nothing EVIL is being done (i.e. div0_evade) - // this hinders joystick users though - // but it still gives SOME analog control - wishvel.x = fabs(self.movement.x); - wishvel.y = fabs(self.movement.y); - if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) - { - wishvel.z = 0; - wishspeed = vlen(wishvel); - if(wishvel.x >= 2 * wishvel.y) - { - // pure X motion - if(self.movement.x > 0) - self.movement_x = wishspeed; - else - self.movement_x = -wishspeed; - self.movement_y = 0; - } - else if(wishvel.y >= 2 * wishvel.x) - { - // pure Y motion - self.movement_x = 0; - if(self.movement.y > 0) - self.movement_y = wishspeed; - else - self.movement_y = -wishspeed; - } - else - { - // diagonal - if(self.movement.x > 0) - self.movement_x = M_SQRT1_2 * wishspeed; - else - self.movement_x = -M_SQRT1_2 * wishspeed; - if(self.movement.y > 0) - self.movement_y = M_SQRT1_2 * wishspeed; - else - self.movement_y = -M_SQRT1_2 * wishspeed; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, reset_map_global) -{ - float s; - - Score_NicePrint(world); - - race_ClearRecords(); - PlayerScore_Sort(race_place, 0, 1, 0); - - entity e; - FOR_EACH_CLIENT(e) - { - if(e.race_place) - { - s = PlayerScore_Add(e, SP_RACE_FASTEST, 0); - if(!s) - e.race_place = 0; - } - cts_EventLog(ftos(e.race_place), e); - } - - if(g_race_qualifying == 2) - { - g_race_qualifying = 0; - independent_players = 0; - cvar_set("fraglimit", ftos(race_fraglimit)); - cvar_set("leadlimit", ftos(race_leadlimit)); - cvar_set("timelimit", ftos(race_timelimit)); - cts_ScoreRules(); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerPreThink) -{SELFPARAM(); - if(IS_SPEC(self) || IS_OBSERVER(self)) - if(g_race_qualifying) - if(msg_entity.enemy.race_laptime) - race_SendNextCheckpoint(msg_entity.enemy, 1); - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, ClientConnect) -{SELFPARAM(); - race_PreparePlayer(); - self.race_checkpoint = -1; - - if(IS_REAL_CLIENT(self)) - { - string rr = CTS_RECORD; - - msg_entity = self; - race_send_recordtime(MSG_ONE); - race_send_speedaward(MSG_ONE); - - speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); - speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); - race_send_speedaward_alltimebest(MSG_ONE); - - float i; - for (i = 1; i <= RANKINGS_CNT; ++i) - { - race_SendRankings(i, 0, 0, MSG_ONE); - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver) -{SELFPARAM(); - if(PlayerScore_Add(self, SP_RACE_FASTEST, 0)) - self.frags = FRAGS_LMS_LOSER; - else - self.frags = FRAGS_SPECTATOR; - - race_PreparePlayer(); - self.race_checkpoint = -1; - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerSpawn) -{SELFPARAM(); - if(spawn_spot.target == "") - // Emergency: this wasn't a real spawnpoint. Can this ever happen? - race_PreparePlayer(); - - // if we need to respawn, do it right - self.race_respawn_checkpoint = self.race_checkpoint; - self.race_respawn_spotref = spawn_spot; - - self.race_place = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, PutClientInServer) -{SELFPARAM(); - if(IS_PLAYER(self)) - if(!gameover) - { - if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn - race_PreparePlayer(); - else // respawn - race_RetractPlayer(); - - race_AbandonRaceCheck(self); - } - return false; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerDies) -{SELFPARAM(); - self.respawn_flags |= RESPAWN_FORCE; - race_AbandonRaceCheck(self); - return false; -} - -MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole) -{SELFPARAM(); - self.havocbot_role = havocbot_role_cts; - return true; -} - -MUTATOR_HOOKFUNCTION(cts, GetPressedKeys) -{SELFPARAM(); - if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1) - { - if (!self.stored_netname) - self.stored_netname = strzone(uid2name(self.crypto_idfp)); - if(self.stored_netname != self.netname) - { - db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname); - strunzone(self.stored_netname); - self.stored_netname = strzone(self.netname); - } - } - - if (!IS_OBSERVER(self)) - { - if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed) - { - speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1'); - speedaward_holder = self.netname; - speedaward_uid = self.crypto_idfp; - speedaward_lastupdate = time; - } - if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) - { - string rr = CTS_RECORD; - race_send_speedaward(MSG_ALL); - speedaward_lastsent = speedaward_speed; - if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") - { - speedaward_alltimebest = speedaward_speed; - speedaward_alltimebest_holder = speedaward_holder; - speedaward_alltimebest_uid = speedaward_uid; - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); - race_send_speedaward_alltimebest(MSG_ALL); - } - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon) -{ - // no weapon dropping in CTS - return true; -} - -MUTATOR_HOOKFUNCTION(cts, FilterItem) -{SELFPARAM(); - if(self.classname == "droppedweapon") - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, PlayerDamage_Calculate) -{ - if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id) - if(!autocvar_g_cts_selfdamage) - frag_damage = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear) -{ - return true; // in CTS, you don't lose score by observing -} - -MUTATOR_HOOKFUNCTION(cts, SetModname) -{ - g_cloaked = 1; // always enable cloak in CTS - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, GetRecords) -{ - for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) - { - if(MapInfo_Get_ByID(i)) - { - float r = race_readTime(MapInfo_Map_bspname, 1); - - if(!r) - continue; - - string h = race_readName(MapInfo_Map_bspname, 1); - ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, ClientKill) -{ - ret_float = 0; - - if(self.killindicator && self.killindicator.health == 1) // self.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill - { - remove(self.killindicator); - self.killindicator = world; - - ClientKill_Now(); // allow instant kill in this case - return; - } - -} - -MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint) -{ - if(autocvar_g_cts_finish_kill_delay) - CTS_ClientKill(self); - - return false; -} - -MUTATOR_HOOKFUNCTION(cts, FixClientCvars) -{ - stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n"); - return false; -} - -MUTATOR_HOOKFUNCTION(cts, WantWeapon) -{ - ret_float = (want_weaponinfo.weapon == WEP_SHOTGUN.m_id); - want_mutatorblocked = true; - return true; -} - -void cts_Initialize() -{ - cts_ScoreRules(); -} - -REGISTER_MUTATOR(cts, g_cts) -{ - g_race_qualifying = 1; - independent_players = 1; - SetLimits(0, 0, 0, -1); - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - cts_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back cts_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_cts.qh b/qcsrc/server/mutators/gamemode_cts.qh deleted file mode 100644 index fa27fe4fc..000000000 --- a/qcsrc/server/mutators/gamemode_cts.qh +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef GAMEMODE_CTS_H -#define GAMEMODE_CTS_H -//float g_race_qualifying; - -// scores -const float ST_CTS_LAPS = 1; -const float SP_CTS_LAPS = 4; -const float SP_CTS_TIME = 5; -const float SP_CTS_FASTEST = 6; -#endif diff --git a/qcsrc/server/mutators/gamemode_deathmatch.qc b/qcsrc/server/mutators/gamemode_deathmatch.qc deleted file mode 100644 index d5f9cbdc6..000000000 --- a/qcsrc/server/mutators/gamemode_deathmatch.qc +++ /dev/null @@ -1,31 +0,0 @@ -#include "gamemode.qh" - -MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining) -{ - // announce remaining frags - return true; -} - -REGISTER_MUTATOR(dm, IS_GAMETYPE(DEATHMATCH)) -{ - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back dm_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - 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_domination.qc b/qcsrc/server/mutators/gamemode_domination.qc deleted file mode 100644 index 504f31db0..000000000 --- a/qcsrc/server/mutators/gamemode_domination.qc +++ /dev/null @@ -1,693 +0,0 @@ -#include "gamemode_domination.qh" - -#include "gamemode.qh" - -#include "../teamplay.qh" - -bool g_domination; - -int autocvar_g_domination_default_teams; -bool autocvar_g_domination_disable_frags; -int autocvar_g_domination_point_amt; -bool autocvar_g_domination_point_fullbright; -int autocvar_g_domination_point_leadlimit; -bool autocvar_g_domination_roundbased; -int autocvar_g_domination_roundbased_point_limit; -float autocvar_g_domination_round_timelimit; -float autocvar_g_domination_warmup; -#define autocvar_g_domination_point_limit cvar("g_domination_point_limit") -float autocvar_g_domination_point_rate; -int autocvar_g_domination_teams_override; - -void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void set_dom_state(entity e) -{ - e.dom_total_pps = total_pps; - e.dom_pps_red = pps_red; - e.dom_pps_blue = pps_blue; - if(domination_teams >= 3) - e.dom_pps_yellow = pps_yellow; - if(domination_teams >= 4) - e.dom_pps_pink = pps_pink; -} - -void dompoint_captured () -{SELFPARAM(); - entity head; - float old_delay, old_team, real_team; - - // now that the delay has expired, switch to the latest team to lay claim to this point - head = self.owner; - - real_team = self.cnt; - self.cnt = -1; - - dom_EventLog("taken", self.team, self.dmg_inflictor); - self.dmg_inflictor = world; - - self.goalentity = head; - self.model = head.mdl; - self.modelindex = head.dmg; - self.skin = head.skin; - - float points, wait_time; - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = self.frags; - if (autocvar_g_domination_point_rate) - wait_time = autocvar_g_domination_point_rate; - else - wait_time = self.wait; - - if(domination_roundbased) - bprint(sprintf("^3%s^3%s\n", head.netname, self.message)); - else - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time); - - if(self.enemy.playerid == self.enemy_playerid) - PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1); - else - self.enemy = world; - - if (head.noise != "") - if(self.enemy) - _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); - else - _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); - if (head.noise1 != "") - play2all(head.noise1); - - self.delay = time + wait_time; - - // do trigger work - old_delay = self.delay; - old_team = self.team; - self.team = real_team; - self.delay = 0; - activator = self; - SUB_UseTargets (); - self.delay = old_delay; - self.team = old_team; - - entity msg = WP_DomNeut; - switch(self.team) - { - case NUM_TEAM_1: msg = WP_DomRed; break; - case NUM_TEAM_2: msg = WP_DomBlue; break; - case NUM_TEAM_3: msg = WP_DomYellow; break; - case NUM_TEAM_4: msg = WP_DomPink; break; - } - - WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null); - - total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; - for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; ) - { - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = head.frags; - if (autocvar_g_domination_point_rate) - wait_time = autocvar_g_domination_point_rate; - else - wait_time = head.wait; - switch(head.goalentity.team) - { - case NUM_TEAM_1: - pps_red += points/wait_time; - break; - case NUM_TEAM_2: - pps_blue += points/wait_time; - break; - case NUM_TEAM_3: - pps_yellow += points/wait_time; - break; - case NUM_TEAM_4: - pps_pink += points/wait_time; - break; - } - total_pps += points/wait_time; - } - - WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0)); - WaypointSprite_Ping(self.sprite); - - self.captime = time; - - FOR_EACH_REALCLIENT(head) - set_dom_state(head); -} - -void AnimateDomPoint() -{SELFPARAM(); - if(self.pain_finished > time) - return; - self.pain_finished = time + self.t_width; - if(self.nextthink > self.pain_finished) - self.nextthink = self.pain_finished; - - self.frame = self.frame + 1; - if(self.frame > self.t_length) - self.frame = 0; -} - -void dompointthink() -{SELFPARAM(); - float fragamt; - - self.nextthink = time + 0.1; - - //self.frame = self.frame + 1; - //if(self.frame > 119) - // self.frame = 0; - AnimateDomPoint(); - - // give points - - if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points - return; - - if(autocvar_g_domination_point_rate) - self.delay = time + autocvar_g_domination_point_rate; - else - self.delay = time + self.wait; - - // give credit to the team - // NOTE: this defaults to 0 - if (!domination_roundbased) - if (self.goalentity.netname != "") - { - if(autocvar_g_domination_point_amt) - fragamt = autocvar_g_domination_point_amt; - else - fragamt = self.frags; - TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt); - TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt); - - // give credit to the individual player, if he is still there - if (self.enemy.playerid == self.enemy_playerid) - { - PlayerScore_Add(self.enemy, SP_SCORE, fragamt); - PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt); - } - else - self.enemy = world; - } -} - -void dompointtouch() -{SELFPARAM(); - entity head; - if (!IS_PLAYER(other)) - return; - if (other.health < 1) - return; - - if(round_handler_IsActive() && !round_handler_IsRoundStarted()) - return; - - if(time < self.captime + 0.3) - return; - - // only valid teams can claim it - head = find(world, classname, "dom_team"); - while (head && head.team != other.team) - head = find(head, classname, "dom_team"); - if (!head || head.netname == "" || head == self.goalentity) - return; - - // delay capture - - self.team = self.goalentity.team; // this stores the PREVIOUS team! - - self.cnt = other.team; - self.owner = head; // team to switch to after the delay - self.dmg_inflictor = other; - - // self.state = 1; - // self.delay = time + cvar("g_domination_point_capturetime"); - //self.nextthink = time + cvar("g_domination_point_capturetime"); - //self.think = dompoint_captured; - - // go to neutral team in the mean time - head = find(world, classname, "dom_team"); - while (head && head.netname != "") - head = find(head, classname, "dom_team"); - if(head == world) - return; - - WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null); - WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1'); - WaypointSprite_Ping(self.sprite); - - self.goalentity = head; - self.model = head.mdl; - self.modelindex = head.dmg; - self.skin = head.skin; - - self.enemy = other; // individual player scoring - self.enemy_playerid = other.playerid; - dompoint_captured(); -} - -void dom_controlpoint_setup() -{SELFPARAM(); - entity head; - // find the spawnfunc_dom_team representing unclaimed points - head = find(world, classname, "dom_team"); - while(head && head.netname != "") - head = find(head, classname, "dom_team"); - if (!head) - objerror("no spawnfunc_dom_team with netname \"\" found\n"); - - // copy important properties from spawnfunc_dom_team entity - self.goalentity = head; - _setmodel(self, head.mdl); // precision already set - self.skin = head.skin; - - self.cnt = -1; - - if(self.message == "") - self.message = " has captured a control point"; - - if(self.frags <= 0) - self.frags = 1; - if(self.wait <= 0) - self.wait = 5; - - float points, waittime; - if (autocvar_g_domination_point_amt) - points = autocvar_g_domination_point_amt; - else - points = self.frags; - if (autocvar_g_domination_point_rate) - waittime = autocvar_g_domination_point_rate; - else - waittime = self.wait; - - total_pps += points/waittime; - - if(!self.t_width) - self.t_width = 0.02; // frame animation rate - if(!self.t_length) - self.t_length = 239; // maximum frame - - self.think = dompointthink; - self.nextthink = time; - self.touch = dompointtouch; - self.solid = SOLID_TRIGGER; - self.flags = FL_ITEM; - setsize(self, '-32 -32 -32', '32 32 32'); - setorigin(self, self.origin + '0 0 20'); - droptofloor(); - - waypoint_spawnforitem(self); - WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT); -} - -float total_controlpoints; -void Domination_count_controlpoints() -{ - entity e; - total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0; - for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; ) - { - ++total_controlpoints; - redowned += (e.goalentity.team == NUM_TEAM_1); - blueowned += (e.goalentity.team == NUM_TEAM_2); - yellowowned += (e.goalentity.team == NUM_TEAM_3); - pinkowned += (e.goalentity.team == NUM_TEAM_4); - } -} - -float Domination_GetWinnerTeam() -{ - float winner_team = 0; - if(redowned == total_controlpoints) - winner_team = NUM_TEAM_1; - if(blueowned == total_controlpoints) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_2; - } - if(yellowowned == total_controlpoints) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_3; - } - if(pinkowned == total_controlpoints) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_4; - } - if(winner_team) - return winner_team; - return -1; // no control points left? -} - -#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) -#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints) -float Domination_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); - round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); - return 1; - } - - Domination_count_controlpoints(); - - float winner_team = Domination_GetWinnerTeam(); - - if(winner_team == -1) - return 0; - - 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_DOM_CAPS, +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); - } - - round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); - - return 1; -} - -float Domination_CheckPlayers() -{ - return 1; -} - -void Domination_RoundStart() -{ - entity e; - FOR_EACH_PLAYER(e) - e.player_blocked = 0; -} - -//go to best items, or control points you don't own -void havocbot_role_dom() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - havocbot_goalrating_controlpoints(10000, self.origin, 15000); - havocbot_goalrating_items(8000, self.origin, 8000); - //havocbot_goalrating_enemyplayers(3000, self.origin, 2000); - //havocbot_goalrating_waypoints(1, self.origin, 1000); - navigation_goalrating_end(); - } -} - -MUTATOR_HOOKFUNCTION(dom, GetTeamCount) -{ - // fallback? - ret_float = domination_teams; - ret_string = "dom_team"; - - entity head = find(world, classname, ret_string); - while(head) - { - if(head.netname != "") - { - switch(head.team) - { - case NUM_TEAM_1: c1 = 0; break; - case NUM_TEAM_2: c2 = 0; break; - case NUM_TEAM_3: c3 = 0; break; - case NUM_TEAM_4: c4 = 0; break; - } - } - - head = find(head, classname, ret_string); - } - - ret_string = string_null; - - return true; -} - -MUTATOR_HOOKFUNCTION(dom, reset_map_players) -{SELFPARAM(); - total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; - entity e; - FOR_EACH_PLAYER(e) - { - setself(e); - PutClientInServer(); - self.player_blocked = 1; - if(IS_REAL_CLIENT(self)) - set_dom_state(self); - } - return 1; -} - -MUTATOR_HOOKFUNCTION(dom, PlayerSpawn) -{SELFPARAM(); - if(domination_roundbased) - if(!round_handler_IsRoundStarted()) - self.player_blocked = 1; - else - self.player_blocked = 0; - return false; -} - -MUTATOR_HOOKFUNCTION(dom, ClientConnect) -{SELFPARAM(); - set_dom_state(self); - return false; -} - -MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole) -{SELFPARAM(); - self.havocbot_role = havocbot_role_dom; - return true; -} - -/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32) -Control point for Domination gameplay. -*/ -spawnfunc(dom_controlpoint) -{ - if(!g_domination) - { - remove(self); - return; - } - self.think = dom_controlpoint_setup; - self.nextthink = time + 0.1; - self.reset = dom_controlpoint_setup; - - if(!self.scale) - self.scale = 0.6; - - self.effects = self.effects | EF_LOWPRECISION; - if (autocvar_g_domination_point_fullbright) - self.effects |= EF_FULLBRIGHT; -} - -/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32) -Team declaration for Domination gameplay, this allows you to decide what team -names and control point models are used in your map. - -Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two -can have netname set! The nameless team owns all control points at start. - -Keys: -"netname" - Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc) -"cnt" - Scoreboard color of the team (for example 4 is red and 13 is blue) -"model" - Model to use for control points owned by this team (for example - "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver - keycard) -"skin" - Skin of the model to use (for team skins on a single model) -"noise" - Sound to play when this team captures a point. - (this is a localized sound, like a small alarm or other effect) -"noise1" - Narrator speech to play when this team captures a point. - (this is a global sound, like "Red team has captured a control point") -*/ - -spawnfunc(dom_team) -{ - if(!g_domination || autocvar_g_domination_teams_override >= 2) - { - remove(self); - return; - } - precache_model(self.model); - if (self.noise != "") - precache_sound(self.noise); - if (self.noise1 != "") - precache_sound(self.noise1); - self.classname = "dom_team"; - _setmodel(self, self.model); // precision not needed - self.mdl = self.model; - self.dmg = self.modelindex; - self.model = ""; - self.modelindex = 0; - // this would have to be changed if used in quakeworld - if(self.cnt) - self.team = self.cnt + 1; // WHY are these different anyway? -} - -// scoreboard setup -void ScoreRules_dom(float teams) -{ - if(domination_roundbased) - { - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); - ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); - ScoreRules_basics_end(); - } - else - { - float sp_domticks, sp_score; - sp_score = sp_domticks = 0; - if(autocvar_g_domination_disable_frags) - sp_domticks = SFL_SORT_PRIO_PRIMARY; - else - sp_score = SFL_SORT_PRIO_PRIMARY; - ScoreRules_basics(teams, sp_score, sp_score, true); - ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks); - ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks); - ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); - ScoreRules_basics_end(); - } -} - -// code from here on is just to support maps that don't have control point and team entities -void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage) -{SELFPARAM(); - setself(spawn()); - self.classname = "dom_team"; - self.netname = teamname; - self.cnt = teamcolor; - self.model = pointmodel; - self.skin = pointskin; - self.noise = capsound; - self.noise1 = capnarration; - self.message = capmessage; - - // this code is identical to spawnfunc_dom_team - _setmodel(self, self.model); // precision not needed - self.mdl = self.model; - self.dmg = self.modelindex; - self.model = ""; - self.modelindex = 0; - // this would have to be changed if used in quakeworld - self.team = self.cnt + 1; - - //eprint(self); - setself(this); -} - -void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); } -void dom_spawnpoint(vector org) -{SELFPARAM(); - setself(spawn()); - self.classname = "dom_controlpoint"; - self.think = _spawnfunc_dom_controlpoint; - self.nextthink = time; - setorigin(self, org); - spawnfunc_dom_controlpoint(this); - setself(this); -} - -// spawn some default teams if the map is not set up for domination -void dom_spawnteams(float teams) -{ - dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point"); - dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point"); - if(teams >= 3) - dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point"); - if(teams >= 4) - dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point"); - dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", ""); -} - -void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. -{ - // if no teams are found, spawn defaults - if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2) - { - LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n"); - domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4); - dom_spawnteams(domination_teams); - } - - CheckAllowedTeams(world); - domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2); - - addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps); - addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red); - addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue); - if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow); - if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink); - - domination_roundbased = autocvar_g_domination_roundbased; - - ScoreRules_dom(domination_teams); - - if(domination_roundbased) - { - round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart); - round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); - } -} - -void dom_Initialize() -{ - g_domination = true; - InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE); -} - - -REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION)) -{ - int fraglimit_override = autocvar_g_domination_point_limit; - if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit) - fraglimit_override = autocvar_g_domination_roundbased_point_limit; - - ActivateTeamplay(); - SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1); - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - dom_Initialize(); - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_domination.qh b/qcsrc/server/mutators/gamemode_domination.qh deleted file mode 100644 index d017b62a4..000000000 --- a/qcsrc/server/mutators/gamemode_domination.qh +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef GAMEMODE_DOMINATION_H -#define GAMEMODE_DOMINATION_H -// these are needed since mutators are compiled last - -// score rule declarations -const float ST_DOM_TICKS = 1; -const float SP_DOM_TICKS = 4; -const float SP_DOM_TAKES = 5; -const float ST_DOM_CAPS = 1; -const float SP_DOM_CAPS = 4; - -// pps: points per second -.float dom_total_pps; -.float dom_pps_red; -.float dom_pps_blue; -.float dom_pps_yellow; -.float dom_pps_pink; -float total_pps; -float pps_red; -float pps_blue; -float pps_yellow; -float pps_pink; - -// capture declarations -.float enemy_playerid; -.entity sprite; -.float captime; - -// misc globals -float domination_roundbased; -float domination_teams; -#endif diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc deleted file mode 100644 index d6c8d2941..000000000 --- a/qcsrc/server/mutators/gamemode_freezetag.qc +++ /dev/null @@ -1,643 +0,0 @@ -#include "gamemode_freezetag.qh" - -#include "gamemode.qh" - -float autocvar_g_freezetag_frozen_maxtime; -bool autocvar_g_freezetag_revive_nade; -float autocvar_g_freezetag_revive_nade_health; -int autocvar_g_freezetag_point_leadlimit; -int 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; -int autocvar_g_freezetag_teams; -int autocvar_g_freezetag_teams_override; -bool autocvar_g_freezetag_team_spawns; -float autocvar_g_freezetag_warmup; - -const float SP_FREEZETAG_REVIVALS = 4; -void freezetag_ScoreRules(float teams) -{ - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); // SFL_SORT_PRIO_PRIMARY - ScoreInfo_SetLabel_PlayerScore(SP_FREEZETAG_REVIVALS, "revivals", 0); - ScoreRules_basics_end(); -} - -void freezetag_count_alive_players() -{ - entity e; - total_players = redalive = bluealive = yellowalive = pinkalive = 0; - FOR_EACH_PLAYER(e) - { - switch(e.team) - { - case NUM_TEAM_1: ++total_players; if(e.health >= 1 && e.frozen != 1) ++redalive; break; - case NUM_TEAM_2: ++total_players; if(e.health >= 1 && e.frozen != 1) ++bluealive; break; - case NUM_TEAM_3: ++total_players; if(e.health >= 1 && e.frozen != 1) ++yellowalive; break; - case NUM_TEAM_4: ++total_players; if(e.health >= 1 && e.frozen != 1) ++pinkalive; break; - } - } - FOR_EACH_REALCLIENT(e) - { - e.redalive_stat = redalive; - e.bluealive_stat = bluealive; - e.yellowalive_stat = yellowalive; - e.pinkalive_stat = pinkalive; - } - - eliminatedPlayers.SendFlags |= 1; -} -#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) -#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams) - -float freezetag_CheckTeams() -{ - static float prev_missing_teams_mask; - if(FREEZETAG_ALIVE_TEAMS_OK()) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return 1; - } - if(total_players == 0) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - return 0; - } - float missing_teams_mask = (!redalive) + (!bluealive) * 2; - if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4; - if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8; - if(prev_missing_teams_mask != missing_teams_mask) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); - prev_missing_teams_mask = missing_teams_mask; - } - return 0; -} - -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 -} - -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; - nades_Clear(e); - } - round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); - return 1; - } - - if(FREEZETAG_ALIVE_TEAMS() > 1) - return 0; - - float winner_team; - winner_team = freezetag_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); - } - - FOR_EACH_PLAYER(e) - { - e.freezetag_frozen_timeout = 0; - nades_Clear(e); - } - round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); - return 1; -} - -entity freezetag_LastPlayerForTeam() -{SELFPARAM(); - entity pl, last_pl = world; - FOR_EACH_PLAYER(pl) - { - if(pl.health >= 1) - if(!pl.frozen) - if(pl != self) - if(pl.team == self.team) - if(!last_pl) - last_pl = pl; - else - return world; - } - return last_pl; -} - -void freezetag_LastPlayerForTeam_Notify() -{ - if(round_handler_IsActive()) - if(round_handler_IsRoundStarted()) - { - entity pl = freezetag_LastPlayerForTeam(); - if(pl) - Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); - } -} - -void freezetag_Add_Score(entity attacker) -{SELFPARAM(); - 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) -{SELFPARAM(); - if(self.frozen) - return; - - if(autocvar_g_freezetag_frozen_maxtime > 0) - self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; - - Freeze(self, 0, 1, true); - - freezetag_count_alive_players(); - - freezetag_Add_Score(attacker); -} - -void freezetag_Unfreeze(entity attacker) -{SELFPARAM(); - self.freezetag_frozen_time = 0; - self.freezetag_frozen_timeout = 0; - - Unfreeze(self); -} - -float freezetag_isEliminated(entity e) -{ - if(IS_PLAYER(e) && (e.frozen == 1 || e.deadflag != DEAD_NO)) - return true; - return false; -} - - -// ================ -// Bot player logic -// ================ - -void() havocbot_role_ft_freeing; -void() havocbot_role_ft_offense; - -void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius) -{SELFPARAM(); - entity head; - float distance; - - FOR_EACH_PLAYER(head) - { - if ((head != self) && (head.team == self.team)) - { - if (head.frozen == 1) - { - distance = vlen(head.origin - org); - if (distance > sradius) - continue; - navigation_routerating(head, ratingscale, 2000); - } - else - { - // If teamate is not frozen still seek them out as fight better - // in a group. - navigation_routerating(head, ratingscale/3, 2000); - } - } - } -} - -void havocbot_role_ft_offense() -{SELFPARAM(); - entity head; - float unfrozen; - - if(self.deadflag != DEAD_NO) - return; - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 20; - - // Count how many players on team are unfrozen. - unfrozen = 0; - FOR_EACH_PLAYER(head) - { - if ((head.team == self.team) && (head.frozen != 1)) - unfrozen++; - } - - // If only one left on team or if role has timed out then start trying to free players. - if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout)) - { - LOG_TRACE("changing role to freeing\n"); - self.havocbot_role = havocbot_role_ft_freeing; - self.havocbot_role_timeout = 0; - return; - } - - if (time > self.bot_strategytime) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - - navigation_goalrating_start(); - havocbot_goalrating_items(10000, self.origin, 10000); - havocbot_goalrating_enemyplayers(20000, self.origin, 10000); - havocbot_goalrating_freeplayers(9000, self.origin, 10000); - //havocbot_goalrating_waypoints(1, self.origin, 1000); - navigation_goalrating_end(); - } -} - -void havocbot_role_ft_freeing() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 20; - - if (time > self.havocbot_role_timeout) - { - LOG_TRACE("changing role to offense\n"); - self.havocbot_role = havocbot_role_ft_offense; - self.havocbot_role_timeout = 0; - return; - } - - if (time > self.bot_strategytime) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - - navigation_goalrating_start(); - havocbot_goalrating_items(8000, self.origin, 10000); - havocbot_goalrating_enemyplayers(10000, self.origin, 10000); - havocbot_goalrating_freeplayers(20000, self.origin, 10000); - //havocbot_goalrating_waypoints(1, self.origin, 1000); - navigation_goalrating_end(); - } -} - - -// ============== -// Hook Functions -// ============== - -void ft_RemovePlayer() -{SELFPARAM(); - self.health = 0; // neccessary to update correctly alive stats - if(!self.frozen) - freezetag_LastPlayerForTeam_Notify(); - freezetag_Unfreeze(world); - freezetag_count_alive_players(); -} - -MUTATOR_HOOKFUNCTION(ft, ClientDisconnect) -{SELFPARAM(); - ft_RemovePlayer(); - return 1; -} - -MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver) -{SELFPARAM(); - ft_RemovePlayer(); - return false; -} - -MUTATOR_HOOKFUNCTION(ft, PlayerDies) -{SELFPARAM(); - if(round_handler_IsActive()) - if(round_handler_CountdownRunning()) - { - if(self.frozen) - freezetag_Unfreeze(world); - freezetag_count_alive_players(); - return 1; // let the player die so that he can respawn whenever he wants - } - - // 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.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id) - { - // let the player die, he will be automatically frozen when he respawns - if(self.frozen != 1) - { - freezetag_Add_Score(frag_attacker); - freezetag_count_alive_players(); - freezetag_LastPlayerForTeam_Notify(); - } - else - freezetag_Unfreeze(world); // remove ice - self.health = 0; // Unfreeze resets health - self.freezetag_frozen_timeout = -2; // freeze on respawn - return 1; - } - - if(self.frozen) - return 1; - - freezetag_Freeze(frag_attacker); - freezetag_LastPlayerForTeam_Notify(); - - if(frag_attacker == frag_target || frag_attacker == world) - { - if(IS_PLAYER(frag_target)) - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname); - } - else - { - if(IS_PLAYER(frag_target)) - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname); - if(IS_PLAYER(frag_attacker)) - Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname); - } - - return 1; -} - -MUTATOR_HOOKFUNCTION(ft, PlayerSpawn) -{SELFPARAM(); - if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players - return 1; // do nothing, round is starting right now - - if(self.freezetag_frozen_timeout == -2) // player was dead - { - freezetag_Freeze(world); - return 1; - } - - 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); - } - - return 1; -} - -MUTATOR_HOOKFUNCTION(ft, reset_map_players) -{SELFPARAM(); - entity e; - FOR_EACH_PLAYER(e) - { - e.killcount = 0; - e.freezetag_frozen_timeout = -1; - setself(e); - PutClientInServer(); - e.freezetag_frozen_timeout = 0; - } - freezetag_count_alive_players(); - return 1; -} - -MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST) -{ - frag_score = 0; // no frags counted in Freeze Tag - return 1; -} - -MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) -{SELFPARAM(); - float n; - - if(gameover) - return 1; - - if(self.frozen == 1) - { - // 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; - //if(self.frozen) - //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) - //self.iceblock.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 - { - vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; - n = 0; - FOR_EACH_PLAYER(other) - if(self != other) - if(other.frozen == 0) - if(other.deadflag == DEAD_NO) - if(SAME_TEAM(other, self)) - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - { - if(!o) - o = other; - if(self.frozen == 1) - other.reviving = true; - ++n; - } - } - - if(n && self.frozen == 1) // OK, there is at least one teammate reviving us - { - self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); - - if(self.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(other.reviving) - { - PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); - PlayerScore_Add(other, SP_SCORE, +1); - - nades_GiveBonus(other,autocvar_g_nades_bonus_score_low); - } - } - - 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_REVIVED, self.netname, o.netname); - } - - FOR_EACH_PLAYER(other) - { - if(other.reviving) - { - other.revive_progress = self.revive_progress; - other.reviving = false; - } - } - } - else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset - { - self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1); - self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); - } - else if(!n && !self.frozen) - { - self.revive_progress = 0; // thawing nobody - } - - return 1; -} - -MUTATOR_HOOKFUNCTION(ft, SetStartItems) -{ - start_items &= ~IT_UNLIMITED_AMMO; - //start_health = warmup_start_health = cvar("g_lms_start_health"); - //start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); - start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); - start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); - start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); - start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); - start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); - start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); - - return 0; -} - -MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole) -{SELFPARAM(); - if (!self.deadflag) - { - if (random() < 0.5) - self.havocbot_role = havocbot_role_ft_freeing; - else - self.havocbot_role = havocbot_role_ft_offense; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ft, GetTeamCount, CBC_ORDER_EXCLUSIVE) -{ - ret_float = freezetag_teams; - return false; -} - -MUTATOR_HOOKFUNCTION(ft, SetWeaponArena) -{ - // most weapons arena - if(ret_string == "0" || ret_string == "") - ret_string = "most"; - return false; -} - -void freezetag_Initialize() -{ - freezetag_teams = autocvar_g_freezetag_teams_override; - if(freezetag_teams < 2) - freezetag_teams = autocvar_g_freezetag_teams; - freezetag_teams = bound(2, freezetag_teams, 4); - freezetag_ScoreRules(freezetag_teams); - - 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); - - EliminatedPlayers_Init(freezetag_isEliminated); -} - -REGISTER_MUTATOR(ft, g_freezetag) -{ - ActivateTeamplay(); - SetLimits(autocvar_g_freezetag_point_limit, autocvar_g_freezetag_point_leadlimit, -1, -1); - - if(autocvar_g_freezetag_team_spawns) - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - freezetag_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back freezetag_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_freezetag.qh b/qcsrc/server/mutators/gamemode_freezetag.qh deleted file mode 100644 index 19489fcfb..000000000 --- a/qcsrc/server/mutators/gamemode_freezetag.qh +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef GAMEMODE_FREEZETAG_H -#define GAMEMODE_FREEZETAG_H -.float freezetag_frozen_time; -.float freezetag_frozen_timeout; -const float ICE_MAX_ALPHA = 1; -const float ICE_MIN_ALPHA = 0.1; -float freezetag_teams; - -.float reviving; // temp var - -#endif diff --git a/qcsrc/server/mutators/gamemode_invasion.qc b/qcsrc/server/mutators/gamemode_invasion.qc deleted file mode 100644 index e752da96e..000000000 --- a/qcsrc/server/mutators/gamemode_invasion.qc +++ /dev/null @@ -1,507 +0,0 @@ -#include "gamemode_invasion.qh" - -#include "gamemode.qh" - -#include "../../common/monsters/spawn.qh" -#include "../../common/monsters/sv_monsters.qh" - -#include "../teamplay.qh" - -bool g_invasion; - -float autocvar_g_invasion_round_timelimit; -int autocvar_g_invasion_teams; -bool autocvar_g_invasion_team_spawns; -float autocvar_g_invasion_spawnpoint_spawn_delay; -#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit") -float autocvar_g_invasion_warmup; -int autocvar_g_invasion_monster_count; -bool autocvar_g_invasion_zombies_only; -float autocvar_g_invasion_spawn_delay; - -spawnfunc(invasion_spawnpoint) -{ - if(!g_invasion) { remove(self); return; } - - self.classname = "invasion_spawnpoint"; - - if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already - if(self.monsterid) { - Monster mon = get_monsterinfo(self.monsterid); - mon.mr_precache(mon); - } -} - -float invasion_PickMonster(float supermonster_count) -{ - if(autocvar_g_invasion_zombies_only) - return MON_ZOMBIE.monsterid; - - float i; - entity mon; - - RandomSelection_Init(); - - for(i = MON_FIRST; i <= MON_LAST; ++i) - { - mon = get_monsterinfo(i); - if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1)) - continue; // flying/swimming monsters not yet supported - - RandomSelection_Add(world, i, string_null, 1, 1); - } - - return RandomSelection_chosen_float; -} - -entity invasion_PickSpawn() -{ - entity e; - - RandomSelection_Init(); - - for(e = world;(e = find(e, classname, "invasion_spawnpoint")); ) - { - RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating - e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay; - } - - return RandomSelection_chosen_ent; -} - -void invasion_SpawnChosenMonster(float mon) -{ - entity spawn_point, monster; - - spawn_point = invasion_PickSpawn(); - - if(spawn_point == world) - { - LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n"); - entity e = spawn(); - setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs); - - if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) - monster = spawnmonster("", mon, world, world, e.origin, false, false, 2); - else return; - - e.think = SUB_Remove; - e.nextthink = time + 0.1; - } - else - monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, false, false, 2); - - if(spawn_point) monster.target2 = spawn_point.target2; - monster.spawnshieldtime = time; - if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range; - - if(teamplay) - if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0) - monster.team = spawn_point.team; - else - { - RandomSelection_Init(); - if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1); - if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1); - if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); } - if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); } - - monster.team = RandomSelection_chosen_float; - } - - if(teamplay) - { - monster_setupcolors(monster); - - if(monster.sprite) - { - WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0')); - - monster.sprite.team = 0; - monster.sprite.SendFlags |= 1; - } - } - - monster.monster_attack = false; // it's the player's job to kill all the monsters - - if(inv_roundcnt >= inv_maxrounds) - monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses -} - -void invasion_SpawnMonsters(float supermonster_count) -{ - float chosen_monster = invasion_PickMonster(supermonster_count); - - invasion_SpawnChosenMonster(chosen_monster); -} - -float Invasion_CheckWinner() -{ - entity head; - if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) - { - FOR_EACH_MONSTER(head) - Monster_Remove(head); - - 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_invasion_warmup, autocvar_g_invasion_round_timelimit); - return 1; - } - - float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0; - - FOR_EACH_MONSTER(head) if(head.health > 0) - { - if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) - ++supermonster_count; - ++total_alive_monsters; - - if(teamplay) - switch(head.team) - { - case NUM_TEAM_1: ++red_alive; break; - case NUM_TEAM_2: ++blue_alive; break; - case NUM_TEAM_3: ++yellow_alive; break; - case NUM_TEAM_4: ++pink_alive; break; - } - } - - if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned) - { - if(time >= inv_lastcheck) - { - invasion_SpawnMonsters(supermonster_count); - inv_lastcheck = time + autocvar_g_invasion_spawn_delay; - } - - return 0; - } - - if(inv_numspawned < 1) - return 0; // nothing has spawned yet - - if(teamplay) - { - if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1) - return 0; - } - else if(inv_numkilled < inv_maxspawned) - return 0; - - entity winner = world; - float winning_score = 0, winner_team = 0; - - - if(teamplay) - { - if(red_alive > 0) { winner_team = NUM_TEAM_1; } - if(blue_alive > 0) - if(winner_team) { winner_team = 0; } - else { winner_team = NUM_TEAM_2; } - if(yellow_alive > 0) - if(winner_team) { winner_team = 0; } - else { winner_team = NUM_TEAM_3; } - if(pink_alive > 0) - if(winner_team) { winner_team = 0; } - else { winner_team = NUM_TEAM_4; } - } - else - FOR_EACH_PLAYER(head) - { - float cs = PlayerScore_Add(head, SP_KILLS, 0); - if(cs > winning_score) - { - winning_score = cs; - winner = head; - } - } - - FOR_EACH_MONSTER(head) - Monster_Remove(head); - - if(teamplay) - { - if(winner_team) - { - 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_)); - } - } - else if(winner) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname); - } - - round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); - - return 1; -} - -float Invasion_CheckPlayers() -{ - return true; -} - -void Invasion_RoundStart() -{ - entity e; - float numplayers = 0; - FOR_EACH_PLAYER(e) - { - e.player_blocked = 0; - ++numplayers; - } - - if(inv_roundcnt < inv_maxrounds) - inv_roundcnt += 1; // a limiter to stop crazy counts - - inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3); - - inv_maxcurrent = 0; - inv_numspawned = 0; - inv_numkilled = 0; - - inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5))); - - if(teamplay) - { - DistributeEvenly_Init(inv_maxspawned, invasion_teams); - inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1); - inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1); - if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1); - if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1); - } -} - -MUTATOR_HOOKFUNCTION(inv, MonsterDies) -{SELFPARAM(); - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) - { - inv_numkilled += 1; - inv_maxcurrent -= 1; - if(teamplay) { inv_monsters_perteam[self.team] -= 1; } - - if(IS_PLAYER(frag_attacker)) - if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works - PlayerScore_Add(frag_attacker, SP_KILLS, -1); - else - { - PlayerScore_Add(frag_attacker, SP_KILLS, +1); - if(teamplay) - TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1); - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, MonsterSpawn) -{SELFPARAM(); - if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) - return true; - - if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) - { - inv_numspawned += 1; - inv_maxcurrent += 1; - } - - self.monster_skill = inv_monsterskill; - - if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name); - - self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, OnEntityPreSpawn) -{SELFPARAM(); - if(startsWith(self.classname, "monster_")) - if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, SV_StartFrame) -{ - monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned - monsters_killed = inv_numkilled; - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, PlayerRegen) -{ - // no regeneration in invasion - return true; -} - -MUTATOR_HOOKFUNCTION(inv, PlayerSpawn) -{SELFPARAM(); - self.bot_attack = false; - return false; -} - -MUTATOR_HOOKFUNCTION(inv, PlayerDamage_Calculate) -{ - if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target) - { - frag_damage = 0; - frag_force = '0 0 0'; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, SV_ParseClientCommand) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE) // command was already handled? - return false; - - if(cmd_name == "debuginvasion") - { - sprint(self, strcat("inv_maxspawned = ", ftos(inv_maxspawned), "\n")); - sprint(self, strcat("inv_numspawned = ", ftos(inv_numspawned), "\n")); - sprint(self, strcat("inv_numkilled = ", ftos(inv_numkilled), "\n")); - sprint(self, strcat("inv_roundcnt = ", ftos(inv_roundcnt), "\n")); - sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n")); - sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n")); - sprint(self, strcat("inv_monsterskill = ", ftos(inv_monsterskill), "\n")); - - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, BotShouldAttack) -{ - if(!IS_MONSTER(checkentity)) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(inv, SetStartItems) -{ - start_health = 200; - start_armorvalue = 200; - return false; -} - -MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid) -{ - if(IS_MONSTER(frag_target)) - return MUT_ACCADD_INVALID; - return MUT_ACCADD_INDIFFERENT; -} - -MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning) -{ - // monster spawning disabled during an invasion - return true; -} - -MUTATOR_HOOKFUNCTION(inv, GetTeamCount, CBC_ORDER_EXCLUSIVE) -{ - ret_float = invasion_teams; - return false; -} - -MUTATOR_HOOKFUNCTION(inv, AllowMobButcher) -{ - ret_string = "This command does not work during an invasion!"; - return true; -} - -void invasion_ScoreRules(float inv_teams) -{ - if(inv_teams) { CheckAllowedTeams(world); } - ScoreRules_basics(inv_teams, 0, 0, false); - if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY)); - ScoreRules_basics_end(); -} - -void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. -{ - if(autocvar_g_invasion_teams) - invasion_teams = bound(2, autocvar_g_invasion_teams, 4); - else - invasion_teams = 0; - - independent_players = 1; // to disable extra useless scores - - invasion_ScoreRules(invasion_teams); - - independent_players = 0; - - round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart); - round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); - - inv_roundcnt = 0; - inv_maxrounds = 15; // 15? -} - -void invasion_Initialize() -{ - if(autocvar_g_invasion_zombies_only) { - Monster mon = MON_ZOMBIE; - mon.mr_precache(mon); - } else - { - float i; - entity mon; - for(i = MON_FIRST; i <= MON_LAST; ++i) - { - mon = get_monsterinfo(i); - if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM)) - continue; // flying/swimming monsters not yet supported - - mon.mr_precache(mon); - } - } - - InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE); -} - -REGISTER_MUTATOR(inv, IS_GAMETYPE(INVASION)) -{ - SetLimits(autocvar_g_invasion_point_limit, -1, -1, -1); - if(autocvar_g_invasion_teams >= 2) - { - ActivateTeamplay(); - if(autocvar_g_invasion_team_spawns) - have_team_spawns = -1; // request team spawns - } - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - g_invasion = true; - invasion_Initialize(); - - cvar_settemp("g_monsters", "1"); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back invasion_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_invasion.qh b/qcsrc/server/mutators/gamemode_invasion.qh deleted file mode 100644 index 191aef9ad..000000000 --- a/qcsrc/server/mutators/gamemode_invasion.qh +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GAMEMODE_INVASION_H -#define GAMEMODE_INVASION_H - -float inv_numspawned; -float inv_maxspawned; -float inv_roundcnt; -float inv_maxrounds; -float inv_numkilled; -float inv_lastcheck; -float inv_maxcurrent; - -float invasion_teams; -float inv_monsters_perteam[17]; - -float inv_monsterskill; - -const float ST_INV_KILLS = 1; -#endif diff --git a/qcsrc/server/mutators/gamemode_keepaway.qc b/qcsrc/server/mutators/gamemode_keepaway.qc deleted file mode 100644 index f96aada3e..000000000 --- a/qcsrc/server/mutators/gamemode_keepaway.qc +++ /dev/null @@ -1,485 +0,0 @@ -#include "gamemode_keepaway.qh" - -#include "gamemode.qh" - -int autocvar_g_keepaway_ballcarrier_effects; -float autocvar_g_keepaway_ballcarrier_damage; -float autocvar_g_keepaway_ballcarrier_force; -float autocvar_g_keepaway_ballcarrier_highspeed; -float autocvar_g_keepaway_ballcarrier_selfdamage; -float autocvar_g_keepaway_ballcarrier_selfforce; -float autocvar_g_keepaway_noncarrier_damage; -float autocvar_g_keepaway_noncarrier_force; -float autocvar_g_keepaway_noncarrier_selfdamage; -float autocvar_g_keepaway_noncarrier_selfforce; -bool autocvar_g_keepaway_noncarrier_warn; -int autocvar_g_keepaway_score_bckill; -int autocvar_g_keepaway_score_killac; -int autocvar_g_keepaway_score_timepoints; -float autocvar_g_keepaway_score_timeinterval; -float autocvar_g_keepawayball_damageforcescale; -int autocvar_g_keepawayball_effects; -float autocvar_g_keepawayball_respawntime; -int autocvar_g_keepawayball_trail_color; - -float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame -{ - if(e.ballcarried) - if(IS_SPEC(other)) - return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen - - // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup - - return true; -} - -void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":ka:", mode, ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -void ka_TouchEvent(); -void ka_RespawnBall() // runs whenever the ball needs to be relocated -{SELFPARAM(); - if(gameover) { return; } - vector oldballorigin = self.origin; - - if(!MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) - { - entity spot = SelectSpawnPoint(true); - setorigin(self, spot.origin); - self.angles = spot.angles; - } - - makevectors(self.angles); - self.movetype = MOVETYPE_BOUNCE; - self.velocity = '0 0 200'; - self.angles = '0 0 0'; - self.effects = autocvar_g_keepawayball_effects; - self.touch = ka_TouchEvent; - self.think = ka_RespawnBall; - self.nextthink = time + autocvar_g_keepawayball_respawntime; - - Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, self.origin, '0 0 0', 1); - - WaypointSprite_Spawn(WP_KaBall, 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); - WaypointSprite_Ping(self.waypointsprite_attachedforcarrier); - - sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) -} - -void ka_TimeScoring() -{SELFPARAM(); - if(self.owner.ballcarried) - { // add points for holding the ball after a certain amount of time - if(autocvar_g_keepaway_score_timepoints) - PlayerScore_Add(self.owner, SP_SCORE, autocvar_g_keepaway_score_timepoints); - - PlayerScore_Add(self.owner, SP_KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds" - self.nextthink = time + autocvar_g_keepaway_score_timeinterval; - } -} - -void ka_TouchEvent() // runs any time that the ball comes in contact with something -{SELFPARAM(); - if(gameover) { return; } - if(!self) { return; } - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) - { // The ball fell off the map, respawn it since players can't get to it - ka_RespawnBall(); - return; - } - if(other.deadflag != DEAD_NO) { return; } - if(other.frozen) { return; } - if (!IS_PLAYER(other)) - { // The ball just touched an object, most likely the world - Send_Effect(EFFECT_BALL_SPARKS, self.origin, '0 0 0', 1); - sound(self, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM); - return; - } - else if(self.wait > time) { return; } - - // attach the ball to the player - self.owner = other; - other.ballcarried = self; - setattachment(self, other, ""); - setorigin(self, '0 0 0'); - - // make the ball invisible/unable to do anything/set up time scoring - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; - self.effects |= EF_NODRAW; - self.touch = func_null; - self.think = ka_TimeScoring; - self.nextthink = time + autocvar_g_keepaway_score_timeinterval; - self.takedamage = DAMAGE_NO; - - // apply effects to player - other.glow_color = autocvar_g_keepawayball_trail_color; - other.glow_trail = true; - other.effects |= autocvar_g_keepaway_ballcarrier_effects; - - // messages and sounds - ka_EventLog("pickup", other); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_PICKUP, other.netname); - Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname); - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF); - sound(self.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) - - // scoring - PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1); - - // waypoints - WaypointSprite_AttachCarrier(WP_KaBallCarrier, other, RADARICON_FLAGCARRIER); - other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player; - WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); - WaypointSprite_Ping(other.waypointsprite_attachedforcarrier); - WaypointSprite_Kill(self.waypointsprite_attachedforcarrier); -} - -void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball -{ - entity ball; - ball = plyr.ballcarried; - - if(!ball) { return; } - - // reset the ball - setattachment(ball, world, ""); - ball.movetype = MOVETYPE_BOUNCE; - ball.wait = time + 1; - ball.touch = ka_TouchEvent; - ball.think = ka_RespawnBall; - ball.nextthink = time + autocvar_g_keepawayball_respawntime; - ball.takedamage = DAMAGE_YES; - ball.effects &= ~EF_NODRAW; - setorigin(ball, plyr.origin + '0 0 10'); - ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom(); - ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P - ball.owner = world; - - // reset the player effects - plyr.glow_trail = false; - plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects; - - // messages and sounds - ka_EventLog("dropped", plyr); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname); - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname); - sound(other, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) - - // scoring - // PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless. - - // waypoints - WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); - WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); - WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier); - WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier); -} - -void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal -{SELFPARAM(); - if((self.owner) && (IS_PLAYER(self.owner))) - ka_DropEvent(self.owner); - - if(time < game_starttime) - { - self.think = ka_RespawnBall; - self.touch = func_null; - self.nextthink = game_starttime; - } - else - ka_RespawnBall(); -} - - -// ================ -// Bot player logic -// ================ - -void havocbot_goalrating_ball(float ratingscale, vector org) -{SELFPARAM(); - float t; - entity ball_owner; - ball_owner = ka_ball.owner; - - if (ball_owner == self) - return; - - // If ball is carried by player then hunt them down. - if (ball_owner) - { - t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue); - navigation_routerating(ball_owner, t * ratingscale, 2000); - } - else // Ball has been dropped so collect. - navigation_routerating(ka_ball, ratingscale, 2000); -} - -void havocbot_role_ka_carrier() -{SELFPARAM(); - if (self.deadflag != DEAD_NO) - return; - - if (time > self.bot_strategytime) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - - navigation_goalrating_start(); - havocbot_goalrating_items(10000, self.origin, 10000); - havocbot_goalrating_enemyplayers(20000, self.origin, 10000); - //havocbot_goalrating_waypoints(1, self.origin, 1000); - navigation_goalrating_end(); - } - - if (!self.ballcarried) - { - self.havocbot_role = havocbot_role_ka_collector; - self.bot_strategytime = 0; - } -} - -void havocbot_role_ka_collector() -{SELFPARAM(); - if (self.deadflag != DEAD_NO) - return; - - if (time > self.bot_strategytime) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - - navigation_goalrating_start(); - havocbot_goalrating_items(10000, self.origin, 10000); - havocbot_goalrating_enemyplayers(1000, self.origin, 10000); - havocbot_goalrating_ball(20000, self.origin); - navigation_goalrating_end(); - } - - if (self.ballcarried) - { - self.havocbot_role = havocbot_role_ka_carrier; - self.bot_strategytime = 0; - } -} - - -// ============== -// Hook Functions -// ============== - -MUTATOR_HOOKFUNCTION(ka, PlayerDies) -{SELFPARAM(); - if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker))) - { - if(frag_target.ballcarried) { // add to amount of times killing carrier - PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1); - if(autocvar_g_keepaway_score_bckill) // add bckills to the score - PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_bckill); - } - else if(!frag_attacker.ballcarried) - if(autocvar_g_keepaway_noncarrier_warn) - Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN); - - if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier - PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_killac); - } - - if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it - return 0; -} - -MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill) -{ - frag_score = 0; // no frags counted in keepaway - return 1; // you deceptive little bugger ;3 This needs to be true in order for this function to even count. -} - -MUTATOR_HOOKFUNCTION(ka, PlayerPreThink) -{SELFPARAM(); - // clear the item used for the ball in keepaway - self.items &= ~IT_KEY1; - - // if the player has the ball, make sure they have the item for it (Used for HUD primarily) - if(self.ballcarried) - self.items |= IT_KEY1; - - return 0; -} - -MUTATOR_HOOKFUNCTION(ka, PlayerUseKey) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE == 0) - if(self.ballcarried) - { - ka_DropEvent(self); - return 1; - } - return 0; -} - -MUTATOR_HOOKFUNCTION(ka, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc -{ - if(frag_attacker.ballcarried) // if the attacker is a ballcarrier - { - if(frag_target == frag_attacker) // damage done to yourself - { - frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage; - frag_force *= autocvar_g_keepaway_ballcarrier_selfforce; - } - else // damage done to noncarriers - { - frag_damage *= autocvar_g_keepaway_ballcarrier_damage; - frag_force *= autocvar_g_keepaway_ballcarrier_force; - } - } - else if (!frag_target.ballcarried) // if the target is a noncarrier - { - if(frag_target == frag_attacker) // damage done to yourself - { - frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage; - frag_force *= autocvar_g_keepaway_noncarrier_selfforce; - } - else // damage done to other noncarriers - { - frag_damage *= autocvar_g_keepaway_noncarrier_damage; - frag_force *= autocvar_g_keepaway_noncarrier_force; - } - } - return 0; -} - -MUTATOR_HOOKFUNCTION(ka, ClientDisconnect) -{SELFPARAM(); - if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it - return 0; -} - -MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver) -{SELFPARAM(); - if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it - return 0; -} - -MUTATOR_HOOKFUNCTION(ka, PlayerPowerups) -{SELFPARAM(); - // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup - // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player() - - self.effects &= ~autocvar_g_keepaway_ballcarrier_effects; - - if(self.ballcarried) - self.effects |= autocvar_g_keepaway_ballcarrier_effects; - - return 0; -} - -.float stat_sv_airspeedlimit_nonqw; -.float stat_sv_maxspeed; - -MUTATOR_HOOKFUNCTION(ka, PlayerPhysics) -{SELFPARAM(); - if(self.ballcarried) - { - self.stat_sv_airspeedlimit_nonqw *= autocvar_g_keepaway_ballcarrier_highspeed; - self.stat_sv_maxspeed *= autocvar_g_keepaway_ballcarrier_highspeed; - } - return false; -} - -MUTATOR_HOOKFUNCTION(ka, BotShouldAttack) -{SELFPARAM(); - // if neither player has ball then don't attack unless the ball is on the ground - if(!checkentity.ballcarried && !self.ballcarried && ka_ball.owner) - return true; - return false; -} - -MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole) -{SELFPARAM(); - if (self.ballcarried) - self.havocbot_role = havocbot_role_ka_carrier; - else - self.havocbot_role = havocbot_role_ka_collector; - return true; -} - -MUTATOR_HOOKFUNCTION(ka, DropSpecialItems) -{ - if(frag_target.ballcarried) - ka_DropEvent(frag_target); - - return false; -} - - -// ============== -// Initialization -// ============== - -void ka_SpawnBall() // loads various values for the ball, runs only once at start of match -{ - entity e; - e = spawn(); - e.model = "models/orbs/orbblue.md3"; - precache_model(e.model); - _setmodel(e, e.model); - setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off - e.classname = "keepawayball"; - e.damageforcescale = autocvar_g_keepawayball_damageforcescale; - e.takedamage = DAMAGE_YES; - e.solid = SOLID_TRIGGER; - e.movetype = MOVETYPE_BOUNCE; - e.glow_color = autocvar_g_keepawayball_trail_color; - e.glow_trail = true; - e.flags = FL_ITEM; - e.reset = ka_Reset; - e.touch = ka_TouchEvent; - e.owner = world; - ka_ball = e; - - InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. -} - -void ka_ScoreRules() -{ - ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, true); // SFL_SORT_PRIO_PRIMARY - ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS, "pickups", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY); - ScoreRules_basics_end(); -} - -void ka_Initialize() // run at the start of a match, initiates game mode -{ - ka_ScoreRules(); - ka_SpawnBall(); -} - - -REGISTER_MUTATOR(ka, IS_GAMETYPE(KEEPAWAY)) -{ - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - ka_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back ka_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_keepaway.qh b/qcsrc/server/mutators/gamemode_keepaway.qh deleted file mode 100644 index 250f2fbc9..000000000 --- a/qcsrc/server/mutators/gamemode_keepaway.qh +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef GAMEMODE_KEEPAWAY_H -#define GAMEMODE_KEEPAWAY_H - -// these are needed since mutators are compiled last - -entity ka_ball; - -const float SP_KEEPAWAY_PICKUPS = 4; -const float SP_KEEPAWAY_CARRIERKILLS = 5; -const float SP_KEEPAWAY_BCTIME = 6; - -void() havocbot_role_ka_carrier; -void() havocbot_role_ka_collector; - -void ka_DropEvent(entity plyr); - -#endif diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qc b/qcsrc/server/mutators/gamemode_keyhunt.qc deleted file mode 100644 index 8f680ae64..000000000 --- a/qcsrc/server/mutators/gamemode_keyhunt.qc +++ /dev/null @@ -1,1380 +0,0 @@ -#include "gamemode_keyhunt.qh" - -#include "gamemode.qh" - -float autocvar_g_balance_keyhunt_damageforcescale; -float autocvar_g_balance_keyhunt_delay_collect; -float autocvar_g_balance_keyhunt_delay_return; -float autocvar_g_balance_keyhunt_delay_round; -float autocvar_g_balance_keyhunt_delay_tracking; -float autocvar_g_balance_keyhunt_dropvelocity; -float autocvar_g_balance_keyhunt_maxdist; -float autocvar_g_balance_keyhunt_protecttime; - -int autocvar_g_balance_keyhunt_score_capture; -int autocvar_g_balance_keyhunt_score_carrierfrag; -int autocvar_g_balance_keyhunt_score_collect; -int autocvar_g_balance_keyhunt_score_destroyed; -int autocvar_g_balance_keyhunt_score_destroyed_ownfactor; -int autocvar_g_balance_keyhunt_score_push; -float autocvar_g_balance_keyhunt_throwvelocity; - -int autocvar_g_keyhunt_point_leadlimit; -bool autocvar_g_keyhunt_team_spawns; -#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit") -int autocvar_g_keyhunt_teams; -int autocvar_g_keyhunt_teams_override; - -// #define KH_PLAYER_USE_ATTACHMENT -// #define KH_PLAYER_USE_CARRIEDMODEL - -#ifdef KH_PLAYER_USE_ATTACHMENT -const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0'; -const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0'; -const vector KH_PLAYER_ATTACHMENT = '0 0 0'; -const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0'; -const string KH_PLAYER_ATTACHMENT_BONE = ""; -#else -const float KH_KEY_ZSHIFT = 22; -const float KH_KEY_XYDIST = 24; -const float KH_KEY_XYSPEED = 45; -#endif -const float KH_KEY_WP_ZSHIFT = 20; - -const vector KH_KEY_MIN = '-10 -10 -46'; -const vector KH_KEY_MAX = '10 10 3'; -const float KH_KEY_BRIGHTNESS = 2; - -float kh_no_radar_circles; - -// kh_state -// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self -// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self -.float kh_state; -.float siren_time; // time delay the siren -//.float stuff_time; // time delay to stuffcmd a cvar - -float kh_keystatus[17]; -//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player # -//replace 17 with cvar("maxplayers") or similar !!!!!!!!! -//for(i = 0; i < maxplayers; ++i) -// kh_keystatus[i] = "0"; - -float kh_Team_ByID(float t) -{ - if(t == 0) return NUM_TEAM_1; - if(t == 1) return NUM_TEAM_2; - if(t == 2) return NUM_TEAM_3; - if(t == 3) return NUM_TEAM_4; - return 0; -} - -//entity kh_worldkeylist; -.entity kh_worldkeynext; -entity kh_controller; -//float kh_tracking_enabled; -float kh_teams; -float kh_interferemsg_time, kh_interferemsg_team; -.entity kh_next, kh_prev; // linked list -.float kh_droptime; -.float kh_dropperteam; -.entity kh_previous_owner; -.float kh_previous_owner_playerid; -.float kh_cp_duration; - -float kh_key_dropped, kh_key_carried; - -const float ST_KH_CAPS = 1; -const float SP_KH_CAPS = 4; -const float SP_KH_PUSHES = 5; -const float SP_KH_DESTROYS = 6; -const float SP_KH_PICKUPS = 7; -const float SP_KH_KCKILLS = 8; -const float SP_KH_LOSSES = 9; -void kh_ScoreRules(float teams) -{ - ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); - ScoreInfo_SetLabel_TeamScore( ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES, "pushes", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER); - ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS, "pickups", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS, "kckills", 0); - ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); - ScoreRules_basics_end(); -} - -float kh_KeyCarrier_waypointsprite_visible_for_player(entity e) // runs all the time -{SELFPARAM(); - if(!IS_PLAYER(e) || self.team != e.team) - if(!kh_tracking_enabled) - return false; - - return true; -} - -float kh_Key_waypointsprite_visible_for_player(entity e) // ?? -{SELFPARAM(); - if(!kh_tracking_enabled) - return false; - if(!self.owner) - return true; - if(!self.owner.owner) - return true; - return false; // draw only when key is not owned -} - -void kh_update_state() -{ - entity player; - entity key; - float s; - float f; - - s = 0; - FOR_EACH_KH_KEY(key) - { - if(key.owner) - f = key.team; - else - f = 30; - s |= pow(32, key.count) * f; - } - - FOR_EACH_CLIENT(player) - { - player.kh_state = s; - } - - FOR_EACH_KH_KEY(key) - { - if(key.owner) - key.owner.kh_state |= pow(32, key.count) * 31; - } - //print(ftos((nextent(world)).kh_state), "\n"); -} - - - - -var kh_Think_t kh_Controller_Thinkfunc; -void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly -{ - kh_Controller_Thinkfunc = func; - kh_controller.cnt = ceil(t); - if(t == 0) - kh_controller.nextthink = time; // force -} -void kh_WaitForPlayers(); -void kh_Controller_Think() // called a lot -{SELFPARAM(); - if(intermission_running) - return; - if(self.cnt > 0) - { if(self.think != kh_WaitForPlayers) { self.cnt -= 1; } } - else if(self.cnt == 0) - { - self.cnt -= 1; - kh_Controller_Thinkfunc(); - } - self.nextthink = time + 1; -} - -// frags f: take from cvar * f -// frags 0: no frags -void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured -{ - string s; - if(intermission_running) - return; - - if(frags_player) - UpdateFrags(player, frags_player); - - if(key && key.owner && frags_owner) - UpdateFrags(key.owner, frags_owner); - - if(!autocvar_sv_eventlog) //output extra info to the console or text file - return; - - s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player)); - - if(key && key.owner) - s = strcat(s, ":", ftos(key.owner.playerid)); - else - s = strcat(s, ":0"); - - s = strcat(s, ":", ftos(frags_owner), ":"); - - if(key) - s = strcat(s, key.netname); - - GameLogEcho(s); -} - -vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times. -{ - if(e.tag_entity) - { - makevectors(e.tag_entity.angles); - return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up; - } - else - return e.origin; -} - -void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round -{ -#ifdef KH_PLAYER_USE_ATTACHMENT - entity first; - first = key.owner.kh_next; - if(key == first) - { - setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE); - if(key.kh_next) - { - setattachment(key.kh_next, key, ""); - setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); - setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED); - key.kh_next.angles = '0 0 0'; - } - else - setorigin(key, KH_PLAYER_ATTACHMENT); - key.angles = KH_PLAYER_ATTACHMENT_ANGLES; - } - else - { - setattachment(key, key.kh_prev, ""); - if(key.kh_next) - setattachment(key.kh_next, key, ""); - setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED); - setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); - key.angles = '0 0 0'; - } -#else - setattachment(key, key.owner, ""); - setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think - key.angles_y -= key.owner.angles.y; -#endif - key.flags = 0; - key.solid = SOLID_NOT; - key.movetype = MOVETYPE_NONE; - key.team = key.owner.team; - key.nextthink = time; - key.damageforcescale = 0; - key.takedamage = DAMAGE_NO; - key.modelindex = kh_key_carried; -} - -void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured -{ -#ifdef KH_PLAYER_USE_ATTACHMENT - entity first; - first = key.owner.kh_next; - if(key == first) - { - if(key.kh_next) - { - setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE); - setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); - key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES; - } - } - else - { - if(key.kh_next) - setattachment(key.kh_next, key.kh_prev, ""); - setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); - } - // in any case: - setattachment(key, world, ""); - setorigin(key, key.owner.origin + '0 0 1' * (PL_MIN.z - KH_KEY_MIN_z)); - key.angles = key.owner.angles; -#else - setorigin(key, key.owner.origin + key.origin.z * '0 0 1'); - setattachment(key, world, ""); - key.angles_y += key.owner.angles.y; -#endif - key.flags = FL_ITEM; - key.solid = SOLID_TRIGGER; - key.movetype = MOVETYPE_TOSS; - key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return; - key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale; - key.takedamage = DAMAGE_YES; - // let key.team stay - key.modelindex = kh_key_dropped; - key.kh_previous_owner = key.owner; - key.kh_previous_owner_playerid = key.owner.playerid; -} - -void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach -{ - entity k; - float ownerteam0, ownerteam; - if(key.owner == player) - return; - - ownerteam0 = kh_Key_AllOwnedByWhichTeam(); - - if(key.owner) - { - kh_Key_Detach(key); - - // remove from linked list - if(key.kh_next) - key.kh_next.kh_prev = key.kh_prev; - key.kh_prev.kh_next = key.kh_next; - key.kh_next = world; - key.kh_prev = world; - - if(key.owner.kh_next == world) - { - // No longer a key carrier - if(!kh_no_radar_circles) - WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier); - WaypointSprite_DetachCarrier(key.owner); - } - } - - key.owner = player; - - if(player) - { - // insert into linked list - key.kh_next = player.kh_next; - key.kh_prev = player; - player.kh_next = key; - if(key.kh_next) - key.kh_next.kh_prev = key; - - float i; - i = kh_keystatus[key.owner.playerid]; - if(key.netname == "^1red key") - i += 1; - if(key.netname == "^4blue key") - i += 2; - if(key.netname == "^3yellow key") - i += 4; - if(key.netname == "^6pink key") - i += 8; - kh_keystatus[key.owner.playerid] = i; - - kh_Key_Attach(key); - - if(key.kh_next == world) - { - // player is now a key carrier - entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER); - wp.colormod = colormapPaletteColor(player.team - 1, 0); - player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player; - WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY); - if(player.team == NUM_TEAM_1) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed); - else if(player.team == NUM_TEAM_2) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue); - else if(player.team == NUM_TEAM_3) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow); - else if(player.team == NUM_TEAM_4) - WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink); - if(!kh_no_radar_circles) - WaypointSprite_Ping(player.waypointsprite_attachedforcarrier); - } - } - - // moved that here, also update if there's no player - kh_update_state(); - - key.pusher = world; - - ownerteam = kh_Key_AllOwnedByWhichTeam(); - if(ownerteam != ownerteam0) - { - if(ownerteam != -1) - { - kh_interferemsg_time = time + 0.2; - kh_interferemsg_team = player.team; - - // audit all key carrier sprites, update them to RUN HERE - FOR_EACH_KH_KEY(k) - { - if (!k.owner) continue; - entity first = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break)); - entity third = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break)); - WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third); - } - } - else - { - kh_interferemsg_time = 0; - - // audit all key carrier sprites, update them to RUN HERE - FOR_EACH_KH_KEY(k) - { - if (!k.owner) continue; - entity first = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break)); - entity third = WP_Null; - FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break)); - WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third); - } - } - } -} - -void kh_Key_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{SELFPARAM(); - if(self.owner) - return; - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - // touching lava, or hurt trigger - // what shall we do? - // immediately return is bad - // maybe start a shorter countdown? - } - if(vlen(force) <= 0) - return; - if(time > self.pushltime) - if(IS_PLAYER(attacker)) - self.team = attacker.team; -} - -void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key -{ - sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM); - - if(key.kh_dropperteam != player.team) - { - kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0); - PlayerScore_Add(player, SP_KH_PICKUPS, 1); - } - key.kh_dropperteam = 0; - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_PICKUP_), player.netname); - - kh_Key_AssignTo(key, player); // this also updates .kh_state -} - -void kh_Key_Touch() // runs many, many times when a key has been dropped and can be picked up -{SELFPARAM(); - if(intermission_running) - return; - - if(self.owner) // already carried - return; - - if(ITEM_TOUCH_NEEDKILL()) - { - // touching sky, or nodrop - // what shall we do? - // immediately return is bad - // maybe start a shorter countdown? - } - - if (!IS_PLAYER(other)) - return; - if(other.deadflag != DEAD_NO) - return; - if(other == self.enemy) - if(time < self.kh_droptime + autocvar_g_balance_keyhunt_delay_collect) - return; // you just dropped it! - kh_Key_Collect(self, other); -} - -void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds -{ - entity o; - o = key.owner; - kh_Key_AssignTo(key, world); - if(o) // it was attached - WaypointSprite_Kill(key.waypointsprite_attachedforcarrier); - else // it was dropped - WaypointSprite_DetachCarrier(key); - - // remove key from key list - if (kh_worldkeylist == key) - kh_worldkeylist = kh_worldkeylist.kh_worldkeynext; - else - { - o = kh_worldkeylist; - while (o) - { - if (o.kh_worldkeynext == key) - { - o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext; - break; - } - o = o.kh_worldkeynext; - } - } - - remove(key); - - kh_update_state(); -} - -void kh_FinishRound() // runs when a team captures the keys -{ - // prepare next round - kh_interferemsg_time = 0; - entity key; - - kh_no_radar_circles = true; - FOR_EACH_KH_KEY(key) - kh_Key_Remove(key); - kh_no_radar_circles = false; - - 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); -} - -void kh_WinnerTeam(float teem) // runs when a team wins // Samual: Teem?.... TEEM?!?! what the fuck is wrong with you people -{ - // all key carriers get some points - vector firstorigin, lastorigin, midpoint; - float first; - entity key; - float score; - score = (kh_teams - 1) * autocvar_g_balance_keyhunt_score_capture; - DistributeEvenly_Init(score, kh_teams); - // twice the score for 3 team games, three times the score for 4 team games! - // note: for a win by destroying the key, this should NOT be applied - FOR_EACH_KH_KEY(key) - { - float f; - f = DistributeEvenly_Get(1); - kh_Scores_Event(key.owner, key, "capture", f, 0); - PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1); - nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high); - } - - first = true; - string keyowner = ""; - FOR_EACH_KH_KEY(key) - if(key.owner.kh_next == key) - { - if(!first) - keyowner = strcat(keyowner, ", "); - keyowner = key.owner.netname; - first = false; - } - - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(teem, INFO_KEYHUNT_CAPTURE_), keyowner); - - first = true; - midpoint = '0 0 0'; - firstorigin = '0 0 0'; - lastorigin = '0 0 0'; - FOR_EACH_KH_KEY(key) - { - vector thisorigin; - - thisorigin = kh_AttachedOrigin(key); - //dprint("Key origin: ", vtos(thisorigin), "\n"); - midpoint += thisorigin; - - if(!first) - te_lightning2(world, lastorigin, thisorigin); - lastorigin = thisorigin; - if(first) - firstorigin = thisorigin; - first = false; - } - if(kh_teams > 2) - { - te_lightning2(world, lastorigin, firstorigin); - } - midpoint = midpoint * (1 / kh_teams); - te_customflash(midpoint, 1000, 1, Team_ColorRGB(teem) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component - - play2all(SND(KH_CAPTURE)); - kh_FinishRound(); -} - -void kh_LoserTeam(float teem, entity lostkey) // runs when a player pushes a flag carrier off the map -{ - entity player, key, attacker; - float players; - float keys; - float f; - - attacker = world; - if(lostkey.pusher) - if(lostkey.pusher.team != teem) - if(IS_PLAYER(lostkey.pusher)) - attacker = lostkey.pusher; - - players = keys = 0; - - if(attacker) - { - if(lostkey.kh_previous_owner) - kh_Scores_Event(lostkey.kh_previous_owner, world, "pushed", 0, -autocvar_g_balance_keyhunt_score_push); - // don't actually GIVE him the -nn points, just log - kh_Scores_Event(attacker, world, "push", autocvar_g_balance_keyhunt_score_push, 0); - PlayerScore_Add(attacker, SP_KH_PUSHES, 1); - //centerprint(attacker, "Your push is the best!"); // does this really need to exist? - } - else - { - float of, fragsleft, i, j, thisteam; - of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor; - - FOR_EACH_PLAYER(player) - if(player.team != teem) - ++players; - - FOR_EACH_KH_KEY(key) - if(key.owner && key.team != teem) - ++keys; - - if(lostkey.kh_previous_owner) - kh_Scores_Event(lostkey.kh_previous_owner, world, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed); - // don't actually GIVE him the -nn points, just log - - if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid) - PlayerScore_Add(lostkey.kh_previous_owner, SP_KH_DESTROYS, 1); - - DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players); - - FOR_EACH_KH_KEY(key) - if(key.owner && key.team != teem) - { - f = DistributeEvenly_Get(of); - kh_Scores_Event(key.owner, world, "destroyed_holdingkey", f, 0); - } - - fragsleft = DistributeEvenly_Get(players); - - // Now distribute these among all other teams... - j = kh_teams - 1; - for(i = 0; i < kh_teams; ++i) - { - thisteam = kh_Team_ByID(i); - if(thisteam == teem) // bad boy, no cookie - this WILL happen - continue; - - players = 0; - FOR_EACH_PLAYER(player) - if(player.team == thisteam) - ++players; - - DistributeEvenly_Init(fragsleft, j); - fragsleft = DistributeEvenly_Get(j - 1); - DistributeEvenly_Init(DistributeEvenly_Get(1), players); - - FOR_EACH_PLAYER(player) - if(player.team == thisteam) - { - f = DistributeEvenly_Get(1); - kh_Scores_Event(player, world, "destroyed", f, 0); - } - - --j; - } - } - - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(lostkey, INFO_KEYHUNT_LOST_), lostkey.kh_previous_owner.netname); - - play2all(SND(KH_DESTROY)); - te_tarexplosion(lostkey.origin); - - kh_FinishRound(); -} - -void kh_Key_Think() // runs all the time -{SELFPARAM(); - entity head; - //entity player; // needed by FOR_EACH_PLAYER - - if(intermission_running) - return; - - if(self.owner) - { -#ifndef KH_PLAYER_USE_ATTACHMENT - makevectors('0 1 0' * (self.cnt + (time % 360) * KH_KEY_XYSPEED)); - setorigin(self, v_forward * KH_KEY_XYDIST + '0 0 1' * self.origin.z); -#endif - } - - // if in nodrop or time over, end the round - if(!self.owner) - if(time > self.pain_finished) - kh_LoserTeam(self.team, self); - - if(self.owner) - if(kh_Key_AllOwnedByWhichTeam() != -1) - { - if(self.siren_time < time) - { - sound(self.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm - self.siren_time = time + 2.5; // repeat every 2.5 seconds - } - - entity key; - vector p; - p = self.owner.origin; - FOR_EACH_KH_KEY(key) - if(vlen(key.owner.origin - p) > autocvar_g_balance_keyhunt_maxdist) - goto not_winning; - kh_WinnerTeam(self.team); -:not_winning - } - - if(kh_interferemsg_time && time > kh_interferemsg_time) - { - kh_interferemsg_time = 0; - FOR_EACH_PLAYER(head) - { - if(head.team == kh_interferemsg_team) - if(head.kh_next) - Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_MEET); - else - Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_HELP); - else - Send_Notification(NOTIF_ONE, head, MSG_CENTER, APP_TEAM_NUM_4(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE_)); - } - } - - self.nextthink = time + 0.05; -} - -void key_reset() -{SELFPARAM(); - kh_Key_AssignTo(self, world); - kh_Key_Remove(self); -} - -const string STR_ITEM_KH_KEY = "item_kh_key"; -void kh_Key_Spawn(entity initial_owner, float angle, float i) // runs every time a new flag is created, ie after all the keys have been collected -{ - entity key; - key = spawn(); - key.count = i; - key.classname = STR_ITEM_KH_KEY; - key.touch = kh_Key_Touch; - key.think = kh_Key_Think; - key.nextthink = time; - key.items = IT_KEY1 | IT_KEY2; - key.cnt = angle; - key.angles = '0 360 0' * random(); - key.event_damage = kh_Key_Damage; - key.takedamage = DAMAGE_YES; - key.modelindex = kh_key_dropped; - key.model = "key"; - key.kh_dropperteam = 0; - key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - setsize(key, KH_KEY_MIN, KH_KEY_MAX); - key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS; - key.reset = key_reset; - - switch(initial_owner.team) - { - case NUM_TEAM_1: - key.netname = "^1red key"; - break; - case NUM_TEAM_2: - key.netname = "^4blue key"; - break; - case NUM_TEAM_3: - key.netname = "^3yellow key"; - break; - case NUM_TEAM_4: - key.netname = "^6pink key"; - break; - default: - key.netname = "NETGIER key"; - break; - } - - // link into key list - key.kh_worldkeynext = kh_worldkeylist; - kh_worldkeylist = key; - - Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM_4(initial_owner.team, CENTER_KEYHUNT_START_)); - - WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG); - key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player; - - kh_Key_AssignTo(key, initial_owner); -} - -// -1 when no team completely owns all keys yet -float kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team -{ - entity key; - float teem; - float keys; - - teem = -1; - keys = kh_teams; - FOR_EACH_KH_KEY(key) - { - if(!key.owner) - return -1; - if(teem == -1) - teem = key.team; - else if(teem != key.team) - return -1; - --keys; - } - if(keys != 0) - return -1; - return teem; -} - -void kh_Key_DropOne(entity key) -{ - // prevent collecting this one for some time - entity player; - player = key.owner; - - key.kh_droptime = time; - key.enemy = player; - - kh_Scores_Event(player, key, "dropkey", 0, 0); - PlayerScore_Add(player, SP_KH_LOSSES, 1); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_DROP_), player.netname); - - kh_Key_AssignTo(key, world); - makevectors(player.v_angle); - key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false); - key.pusher = world; - key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; - key.kh_dropperteam = key.team; - - sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); -} - -void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies -{ - entity key; - entity mypusher; - if(player.kh_next) - { - mypusher = world; - if(player.pusher) - if(time < player.pushltime) - mypusher = player.pusher; - while((key = player.kh_next)) - { - kh_Scores_Event(player, key, "losekey", 0, 0); - PlayerScore_Add(player, SP_KH_LOSSES, 1); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_LOST_), player.netname); - kh_Key_AssignTo(key, world); - makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random()); - key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false); - key.pusher = mypusher; - key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; - if(suicide) - key.kh_dropperteam = player.team; - } - sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); - } -} - -float kh_CheckPlayers(float num) -{ - if(num < kh_teams) - { - float t_team = kh_Team_ByID(num); - float players = 0; - entity tmp_player; - FOR_EACH_PLAYER(tmp_player) - if(tmp_player.deadflag == DEAD_NO) - if(!tmp_player.BUTTON_CHAT) - if(tmp_player.team == t_team) - ++players; - - if (!players) { return t_team; } - } - return 0; -} - -#define KH_READY_TEAMS() (!p1 + !p2 + ((kh_teams >= 3) ? !p3 : p3) + ((kh_teams >= 4) ? !p4 : p4)) -#define KH_READY_TEAMS_OK() (KH_READY_TEAMS() == kh_teams) -void kh_WaitForPlayers() // delay start of the round until enough players are present -{ - if(time < game_starttime) - { - kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); - return; - } - - static float prev_missing_teams_mask; - float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); - if(KH_READY_TEAMS_OK()) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - 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 - { - if(player_count == 0) - { - if(prev_missing_teams_mask > 0) - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); - prev_missing_teams_mask = -1; - } - else - { - float missing_teams_mask = (!!p1) + (!!p2) * 2; - if(kh_teams >= 3) missing_teams_mask += (!!p3) * 4; - if(kh_teams >= 4) missing_teams_mask += (!!p4) * 8; - if(prev_missing_teams_mask != missing_teams_mask) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); - prev_missing_teams_mask = missing_teams_mask; - } - } - kh_Controller_SetThink(1, kh_WaitForPlayers); - } -} - -void kh_EnableTrackingDevice() // runs after each round -{ - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT); - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER); - - kh_tracking_enabled = true; -} - -void kh_StartRound() // runs at the start of each round -{ - float i, players, teem; - entity player; - - if(time < game_starttime) - { - kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); - return; - } - - float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); - if(!KH_READY_TEAMS_OK()) - { - kh_Controller_SetThink(1, kh_WaitForPlayers); - return; - } - - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT); - Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER); - - for(i = 0; i < kh_teams; ++i) - { - teem = kh_Team_ByID(i); - players = 0; - entity my_player = world; - FOR_EACH_PLAYER(player) - if(player.deadflag == DEAD_NO) - if(!player.BUTTON_CHAT) - if(player.team == teem) - { - ++players; - if(random() * players <= 1) - my_player = player; - } - kh_Key_Spawn(my_player, 360 * i / kh_teams, i); - } - - kh_tracking_enabled = false; - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking); - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice); -} - -float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score -{ - if(attacker == targ) - return f; - - if(targ.kh_next) - { - if(attacker.team == targ.team) - { - entity k; - float nk; - nk = 0; - for(k = targ.kh_next; k != world; k = k.kh_next) - ++nk; - kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0); - } - else - { - kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0); - PlayerScore_Add(attacker, SP_KH_KCKILLS, 1); - // the frag gets added later - } - } - - return f; -} - -void kh_Initialize() // sets up th KH environment -{ - // setup variables - kh_teams = autocvar_g_keyhunt_teams_override; - if(kh_teams < 2) - kh_teams = autocvar_g_keyhunt_teams; - kh_teams = bound(2, kh_teams, 4); - - // make a KH entity for controlling the game - kh_controller = spawn(); - kh_controller.think = kh_Controller_Think; - kh_Controller_SetThink(0, kh_WaitForPlayers); - - setmodel(kh_controller, MDL_KH_KEY); - kh_key_dropped = kh_controller.modelindex; - /* - dprint(vtos(kh_controller.mins)); - dprint(vtos(kh_controller.maxs)); - dprint("\n"); - */ -#ifdef KH_PLAYER_USE_CARRIEDMODEL - setmodel(kh_controller, MDL_KH_KEY_CARRIED); - kh_key_carried = kh_controller.modelindex; -#else - kh_key_carried = kh_key_dropped; -#endif - - kh_controller.model = ""; - kh_controller.modelindex = 0; - - addstat(STAT_KH_KEYS, AS_INT, kh_state); - - kh_ScoreRules(kh_teams); -} - -void kh_finalize() -{ - // to be called before intermission - kh_FinishRound(); - remove(kh_controller); - kh_controller = world; -} - -// legacy bot role - -void() havocbot_role_kh_carrier; -void() havocbot_role_kh_defense; -void() havocbot_role_kh_offense; -void() havocbot_role_kh_freelancer; - - -void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) -{SELFPARAM(); - entity head; - for (head = kh_worldkeylist; head; head = head.kh_worldkeynext) - { - if(head.owner == self) - continue; - if(!kh_tracking_enabled) - { - // if it's carried by our team we know about it - // otherwise we have to see it to know about it - if(!head.owner || head.team != self.team) - { - traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self); - if (trace_fraction < 1 && trace_ent != head) - continue; // skip what I can't see - } - } - if(!head.owner) - navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000); - else if(head.team == self.team) - navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000); - else - navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000); - } - - havocbot_goalrating_items(1, self.origin, 10000); -} - -void havocbot_role_kh_carrier() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if (!(self.kh_next)) - { - LOG_TRACE("changing role to freelancer\n"); - self.havocbot_role = havocbot_role_kh_freelancer; - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - if(kh_Key_AllOwnedByWhichTeam() == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // bring home - else - havocbot_goalrating_kh(4, 4, 1); // play defensively - - navigation_goalrating_end(); - } -} - -void havocbot_role_kh_defense() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if (self.kh_next) - { - LOG_TRACE("changing role to carrier\n"); - self.havocbot_role = havocbot_role_kh_carrier; - self.havocbot_role_timeout = 0; - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 20; - if (time > self.havocbot_role_timeout) - { - LOG_TRACE("changing role to freelancer\n"); - self.havocbot_role = havocbot_role_kh_freelancer; - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - float key_owner_team; - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers - else if(key_owner_team == -1) - havocbot_goalrating_kh(4, 1, 0.1); // play defensively - else - havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY - - navigation_goalrating_end(); - } -} - -void havocbot_role_kh_offense() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if (self.kh_next) - { - LOG_TRACE("changing role to carrier\n"); - self.havocbot_role = havocbot_role_kh_carrier; - self.havocbot_role_timeout = 0; - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 20; - if (time > self.havocbot_role_timeout) - { - LOG_TRACE("changing role to freelancer\n"); - self.havocbot_role = havocbot_role_kh_freelancer; - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - float key_owner_team; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway - else if(key_owner_team == -1) - havocbot_goalrating_kh(0.1, 1, 4); // play offensively - else - havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY! - - navigation_goalrating_end(); - } -} - -void havocbot_role_kh_freelancer() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - if (self.kh_next) - { - LOG_TRACE("changing role to carrier\n"); - self.havocbot_role = havocbot_role_kh_carrier; - self.havocbot_role_timeout = 0; - return; - } - - if (!self.havocbot_role_timeout) - self.havocbot_role_timeout = time + random() * 10 + 10; - if (time > self.havocbot_role_timeout) - { - if (random() < 0.5) - { - LOG_TRACE("changing role to offense\n"); - self.havocbot_role = havocbot_role_kh_offense; - } - else - { - LOG_TRACE("changing role to defense\n"); - self.havocbot_role = havocbot_role_kh_defense; - } - self.havocbot_role_timeout = 0; - return; - } - - if (self.bot_strategytime < time) - { - float key_owner_team; - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - key_owner_team = kh_Key_AllOwnedByWhichTeam(); - if(key_owner_team == self.team) - havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway - else if(key_owner_team == -1) - havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys - else - havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY - - navigation_goalrating_end(); - } -} - - -// register this as a mutator - -MUTATOR_HOOKFUNCTION(kh, ClientDisconnect) -{SELFPARAM(); - kh_Key_DropAll(self, true); - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver) -{SELFPARAM(); - kh_Key_DropAll(self, true); - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, PlayerDies) -{SELFPARAM(); - if(self == other) - kh_Key_DropAll(self, true); - else if(IS_PLAYER(other)) - kh_Key_DropAll(self, false); - else - kh_Key_DropAll(self, true); - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST) -{ - frag_score = kh_HandleFrags(frag_attacker, frag_target, frag_score); - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, MatchEnd) -{ - kh_finalize(); - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, GetTeamCount, CBC_ORDER_EXCLUSIVE) -{ - ret_float = kh_teams; - return false; -} - -MUTATOR_HOOKFUNCTION(kh, SpectateCopy) -{SELFPARAM(); - self.kh_state = other.kh_state; - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, PlayerUseKey) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE == 0) - { - entity k; - k = self.kh_next; - if(k) - { - kh_Key_DropOne(k); - return 1; - } - } - return 0; -} - -MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole) -{ - if(self.deadflag != DEAD_NO) - return true; - - float r = random() * 3; - if (r < 1) - self.havocbot_role = havocbot_role_kh_offense; - else if (r < 2) - self.havocbot_role = havocbot_role_kh_defense; - else - self.havocbot_role = havocbot_role_kh_freelancer; - - return true; -} - -MUTATOR_HOOKFUNCTION(kh, DropSpecialItems) -{ - kh_Key_DropAll(frag_target, false); - return false; -} - -MUTATOR_HOOKFUNCTION(kh, reset_map_global) -{ - kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round + (game_starttime - time), kh_StartRound); - return false; -} - -REGISTER_MUTATOR(kh, IS_GAMETYPE(KEYHUNT)) -{ - ActivateTeamplay(); - SetLimits(autocvar_g_keyhunt_point_limit, autocvar_g_keyhunt_point_leadlimit, -1, -1); - if(autocvar_g_keyhunt_team_spawns) - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - kh_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back kh_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_keyhunt.qh b/qcsrc/server/mutators/gamemode_keyhunt.qh deleted file mode 100644 index 2d96b2fa2..000000000 --- a/qcsrc/server/mutators/gamemode_keyhunt.qh +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef GAMEMODE_KEYHUNT_H -#define GAMEMODE_KEYHUNT_H - -#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext ) - -// ALL OF THESE should be removed in the future, as other code should not have to care - -// used by bots: -float kh_tracking_enabled; -.entity kh_next; -float kh_Key_AllOwnedByWhichTeam(); - -typedef void(void) kh_Think_t; -void kh_StartRound(); -void kh_Controller_SetThink(float t, kh_Think_t func); - -entity kh_worldkeylist; -.entity kh_worldkeynext; - -#endif diff --git a/qcsrc/server/mutators/gamemode_lms.qc b/qcsrc/server/mutators/gamemode_lms.qc deleted file mode 100644 index 65c85f73a..000000000 --- a/qcsrc/server/mutators/gamemode_lms.qc +++ /dev/null @@ -1,300 +0,0 @@ -#include "gamemode_lms.qh" - -#include "gamemode.qh" - -#include "../campaign.qh" -#include "../command/cmd.qh" - -int autocvar_g_lms_extra_lives; -bool autocvar_g_lms_join_anytime; -int autocvar_g_lms_last_join; -#define autocvar_g_lms_lives_override cvar("g_lms_lives_override") -bool autocvar_g_lms_regenerate; - -// main functions -float LMS_NewPlayerLives() -{ - float fl; - fl = autocvar_fraglimit; - if(fl == 0) - fl = 999; - - // first player has left the game for dying too much? Nobody else can get in. - if(lms_lowest_lives < 1) - return 0; - - if(!autocvar_g_lms_join_anytime) - if(lms_lowest_lives < fl - autocvar_g_lms_last_join) - return 0; - - return bound(1, lms_lowest_lives, fl); -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(lms, reset_map_global) -{ - lms_lowest_lives = 999; - lms_next_place = player_count; - - return false; -} - -MUTATOR_HOOKFUNCTION(lms, reset_map_players) -{SELFPARAM(); - entity e; - if(restart_mapalreadyrestarted || (time < game_starttime)) - FOR_EACH_CLIENT(e) - if(IS_PLAYER(e)) - { - WITH(entity, self, e, PlayerScore_Add(e, SP_LMS_LIVES, LMS_NewPlayerLives())); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(lms, PutClientInServer) -{SELFPARAM(); - // player is dead and becomes observer - // FIXME fix LMS scoring for new system - if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0) - { - self.classname = "observer"; - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_NOLIVES); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(lms, PlayerDies) -{SELFPARAM(); - self.respawn_flags |= RESPAWN_FORCE; - - return false; -} - -void lms_RemovePlayer(entity player) -{ - // Only if the player cannot play at all - if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666) - player.frags = FRAGS_SPECTATOR; - else - player.frags = FRAGS_LMS_LOSER; - - if(player.killcount != -666) - if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2) - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_NOLIVES, player.netname); - else - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_FORFEIT, player.netname); -} - -MUTATOR_HOOKFUNCTION(lms, ClientDisconnect) -{SELFPARAM(); - lms_RemovePlayer(self); - return false; -} - -MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver) -{SELFPARAM(); - lms_RemovePlayer(self); - return false; -} - -MUTATOR_HOOKFUNCTION(lms, ClientConnect) -{SELFPARAM(); - self.classname = "player"; - campaign_bots_may_start = 1; - - if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0) - { - PlayerScore_Add(self, SP_LMS_RANK, 666); - self.frags = FRAGS_SPECTATOR; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(lms, PlayerPreThink) -{SELFPARAM(); - if(self.deadflag == DEAD_DYING) - self.deadflag = DEAD_RESPAWNING; - - return false; -} - -MUTATOR_HOOKFUNCTION(lms, PlayerRegen) -{ - if(autocvar_g_lms_regenerate) - return false; - return true; -} - -MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon) -{ - // forbode! - return true; -} - -MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill) -{ - // remove a life - float tl; - tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1); - if(tl < lms_lowest_lives) - lms_lowest_lives = tl; - if(tl <= 0) - { - if(!lms_next_place) - lms_next_place = player_count; - else - lms_next_place = min(lms_next_place, player_count); - PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again - --lms_next_place; - } - frag_score = 0; - - return true; -} - -MUTATOR_HOOKFUNCTION(lms, SetStartItems) -{ - start_items &= ~IT_UNLIMITED_AMMO; - start_health = warmup_start_health = cvar("g_lms_start_health"); - start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); - start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); - start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); - start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); - start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); - start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); - start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); - - return false; -} - -MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear) -{ - // don't clear player score - return true; -} - -MUTATOR_HOOKFUNCTION(lms, FilterItem) -{SELFPARAM(); - if(autocvar_g_lms_extra_lives) - if(self.itemdef == ITEM_HealthMega) - { - self.max_health = 1; - return false; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(lms, ItemTouch) -{SELFPARAM(); - // give extra lives for mega health - if (self.items & ITEM_HealthMega.m_itemid) - { - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES); - PlayerScore_Add(other, SP_LMS_LIVES, autocvar_g_lms_extra_lives); - } - - return MUT_ITEMTOUCH_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE) -{ - entity head; - FOR_EACH_REALCLIENT(head) - { - ++bot_activerealplayers; - ++bot_realplayers; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate) -{ - if(self.lms_spectate_warning) - { - // for the forfeit message... - self.lms_spectate_warning = 2; - // mark player as spectator - PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0)); - } - else - { - self.lms_spectate_warning = 1; - sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n"); - return MUT_SPECCMD_RETURN; - } - return MUT_SPECCMD_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(lms, CheckRules_World) -{ - ret_float = WinningCondition_LMS(); - return true; -} - -MUTATOR_HOOKFUNCTION(lms, WantWeapon) -{ - want_allguns = true; - return false; -} - -MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(lms, AddPlayerScore) -{ - if(gameover) - if(score_field == SP_LMS_RANK) - return true; // allow writing to this field in intermission as it is needed for newly joining players - return false; -} - -// scoreboard stuff -void lms_ScoreRules() -{ - ScoreRules_basics(0, 0, 0, false); - ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE); - ScoreRules_basics_end(); -} - -void lms_Initialize() -{ - lms_lowest_lives = 9999; - lms_next_place = 0; - - lms_ScoreRules(); -} - -REGISTER_MUTATOR(lms, IS_GAMETYPE(LMS)) -{ - SetLimits(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override), 0, -1, -1); - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - lms_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back lms_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_lms.qh b/qcsrc/server/mutators/gamemode_lms.qh deleted file mode 100644 index 474e3e197..000000000 --- a/qcsrc/server/mutators/gamemode_lms.qh +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef GAMEMODE_LMS_H -#define GAMEMODE_LMS_H - -// scoreboard stuff -const float SP_LMS_LIVES = 4; -const float SP_LMS_RANK = 5; - -// lives related defs -float lms_lowest_lives; -float lms_next_place; -float LMS_NewPlayerLives(); -#endif diff --git a/qcsrc/server/mutators/gamemode_onslaught.qc b/qcsrc/server/mutators/gamemode_onslaught.qc deleted file mode 100644 index 3ebcb7eac..000000000 --- a/qcsrc/server/mutators/gamemode_onslaught.qc +++ /dev/null @@ -1,2217 +0,0 @@ -#include "gamemode.qh" -#include "../controlpoint.qh" -#include "../generator.qh" - -bool g_onslaught; - -float autocvar_g_onslaught_debug; -float autocvar_g_onslaught_teleport_wait; -bool autocvar_g_onslaught_spawn_at_controlpoints; -bool autocvar_g_onslaught_spawn_at_generator; -float autocvar_g_onslaught_cp_proxydecap; -float autocvar_g_onslaught_cp_proxydecap_distance = 512; -float autocvar_g_onslaught_cp_proxydecap_dps = 100; -float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5; -float autocvar_g_onslaught_spawn_at_controlpoints_random; -float autocvar_g_onslaught_spawn_at_generator_chance; -float autocvar_g_onslaught_spawn_at_generator_random; -float autocvar_g_onslaught_cp_buildhealth; -float autocvar_g_onslaught_cp_buildtime; -float autocvar_g_onslaught_cp_health; -float autocvar_g_onslaught_cp_regen; -float autocvar_g_onslaught_gen_health; -float autocvar_g_onslaught_shield_force = 100; -float autocvar_g_onslaught_allow_vehicle_touch; -float autocvar_g_onslaught_round_timelimit; -float autocvar_g_onslaught_point_limit; -float autocvar_g_onslaught_warmup; -float autocvar_g_onslaught_teleport_radius; -float autocvar_g_onslaught_spawn_choose; -float autocvar_g_onslaught_click_radius; - -void FixSize(entity e); - -// ======================= -// CaptureShield Functions -// ======================= - -bool ons_CaptureShield_Customize() -{SELFPARAM(); - entity e = WaypointSprite_getviewentity(other); - - if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; } - if(SAME_TEAM(self, e)) { return false; } - - return true; -} - -void ons_CaptureShield_Touch() -{SELFPARAM(); - if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; } - if(!IS_PLAYER(other)) { return; } - if(SAME_TEAM(other, self)) { return; } - - vector mymid = (self.absmin + self.absmax) * 0.5; - vector othermid = (other.absmin + other.absmax) * 0.5; - - Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ons_captureshield_force); - - if(IS_REAL_CLIENT(other)) - { - play2(other, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); - - if(self.enemy.classname == "onslaught_generator") - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); - else - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); - } -} - -void ons_CaptureShield_Reset() -{SELFPARAM(); - self.colormap = self.enemy.colormap; - self.team = self.enemy.team; -} - -void ons_CaptureShield_Spawn(entity generator, bool is_generator) -{ - entity shield = spawn(); - - shield.enemy = generator; - shield.team = generator.team; - shield.colormap = generator.colormap; - shield.reset = ons_CaptureShield_Reset; - shield.touch = ons_CaptureShield_Touch; - shield.customizeentityforclient = ons_CaptureShield_Customize; - shield.classname = "ons_captureshield"; - shield.effects = EF_ADDITIVE; - shield.movetype = MOVETYPE_NOCLIP; - shield.solid = SOLID_TRIGGER; - shield.avelocity = '7 0 11'; - shield.scale = 1; - shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3"); - - precache_model(shield.model); - setorigin(shield, generator.origin); - _setmodel(shield, shield.model); - setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); -} - - -// ========== -// Junk Pile -// ========== - -void ons_debug(string input) -{ - switch(autocvar_g_onslaught_debug) - { - case 1: LOG_TRACE(input); break; - case 2: LOG_INFO(input); break; - } -} - -void setmodel_fixsize(entity e, Model m) -{ - setmodel(e, m); - FixSize(e); -} - -void onslaught_updatelinks() -{ - entity l; - // first check if the game has ended - ons_debug("--- updatelinks ---\n"); - // mark generators as being shielded and networked - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - if (l.iscaptured) - ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n")); - else - ons_debug(strcat(etos(l), " (generator) is destroyed\n")); - l.islinked = l.iscaptured; - l.isshielded = l.iscaptured; - l.sprite.SendFlags |= 16; - } - // mark points as shielded and not networked - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - l.islinked = false; - l.isshielded = true; - int i; - for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; } - ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n")); - l.sprite.SendFlags |= 16; - } - // flow power outward from the generators through the network - bool stop = false; - while (!stop) - { - stop = true; - for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) - { - // 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.iscaptured && l.goalentity.iscaptured) - if (l.enemy.islinked != l.goalentity.islinked) - if(SAME_TEAM(l.enemy, l.goalentity)) - { - if (!l.goalentity.islinked) - { - stop = false; - l.goalentity.islinked = true; - ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n")); - } - else if (!l.enemy.islinked) - { - stop = false; - l.enemy.islinked = true; - ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n")); - } - } - } - } - // now that we know which points are powered we can mark their neighbors - // as unshielded if team differs - for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) - { - if (l.goalentity.islinked) - { - if(DIFF_TEAM(l.goalentity, l.enemy)) - { - ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n")); - l.enemy.isshielded = false; - } - if(l.goalentity.classname == "onslaught_generator") - l.enemy.isgenneighbor[l.goalentity.team] = true; - else - l.enemy.iscpneighbor[l.goalentity.team] = true; - } - if (l.enemy.islinked) - { - if(DIFF_TEAM(l.goalentity, l.enemy)) - { - ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n")); - l.goalentity.isshielded = false; - } - if(l.enemy.classname == "onslaught_generator") - l.goalentity.isgenneighbor[l.enemy.team] = true; - else - l.goalentity.iscpneighbor[l.enemy.team] = true; - } - } - // now update the generators - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - if (l.isshielded) - { - ons_debug(strcat(etos(l), " (generator) is shielded\n")); - l.takedamage = DAMAGE_NO; - l.bot_attack = false; - } - else - { - ons_debug(strcat(etos(l), " (generator) is not shielded\n")); - l.takedamage = DAMAGE_AIM; - l.bot_attack = true; - } - - ons_Generator_UpdateSprite(l); - } - // now update the takedamage and alpha variables on control point icons - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - if (l.isshielded) - { - ons_debug(strcat(etos(l), " (point) is shielded\n")); - if (l.goalentity) - { - l.goalentity.takedamage = DAMAGE_NO; - l.goalentity.bot_attack = false; - } - } - else - { - ons_debug(strcat(etos(l), " (point) is not shielded\n")); - if (l.goalentity) - { - l.goalentity.takedamage = DAMAGE_AIM; - l.goalentity.bot_attack = true; - } - } - ons_ControlPoint_UpdateSprite(l); - } - l = findchain(classname, "ons_captureshield"); - while(l) - { - l.team = l.enemy.team; - l.colormap = l.enemy.colormap; - l = l.chain; - } -} - - -// =================== -// Main Link Functions -// =================== - -bool ons_Link_Send(entity this, entity to, int sendflags) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK); - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & 1) - { - WriteCoord(MSG_ENTITY, self.goalentity.origin_x); - WriteCoord(MSG_ENTITY, self.goalentity.origin_y); - WriteCoord(MSG_ENTITY, self.goalentity.origin_z); - } - if(sendflags & 2) - { - WriteCoord(MSG_ENTITY, self.enemy.origin_x); - WriteCoord(MSG_ENTITY, self.enemy.origin_y); - WriteCoord(MSG_ENTITY, self.enemy.origin_z); - } - if(sendflags & 4) - { - WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16 - } - return true; -} - -void ons_Link_CheckUpdate() -{SELFPARAM(); - // TODO check if the two sides have moved (currently they won't move anyway) - float cc = 0, cc1 = 0, cc2 = 0; - - if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; } - if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; } - - cc = cc1 + cc2; - - if(cc != self.clientcolors) - { - self.clientcolors = cc; - self.SendFlags |= 4; - } - - self.nextthink = time; -} - -void ons_DelayedLinkSetup() -{SELFPARAM(); - 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"); } - - ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n")); - self.SendFlags |= 3; - self.think = ons_Link_CheckUpdate; - self.nextthink = time; -} - - -// ============================= -// Main Control Point Functions -// ============================= - -int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber) -{ - if(cp.isgenneighbor[teamnumber]) { return 2; } - if(cp.iscpneighbor[teamnumber]) { return 1; } - - return 0; -} - -int ons_ControlPoint_Attackable(entity cp, int teamnumber) - // -2: SAME TEAM, attackable by enemy! - // -1: SAME TEAM! - // 0: off limits - // 1: attack it - // 2: touch it - // 3: attack it (HIGH PRIO) - // 4: touch it (HIGH PRIO) -{ - int a; - - if(cp.isshielded) - { - return 0; - } - else if(cp.goalentity) - { - // if there's already an icon built, nothing happens - if(cp.team == teamnumber) - { - a = ons_ControlPoint_CanBeLinked(cp, teamnumber); - if(a) // attackable by enemy? - return -2; // EMERGENCY! - return -1; - } - // we know it can be linked, so no need to check - // but... - a = ons_ControlPoint_CanBeLinked(cp, teamnumber); - if(a == 2) // near our generator? - return 3; // EMERGENCY! - return 1; - } - else - { - // free point - if(ons_ControlPoint_CanBeLinked(cp, teamnumber)) - { - a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t - if(a == 2) - return 4; // GET THIS ONE NOW! - else - return 2; // TOUCH ME - } - } - return 0; -} - -void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{SELFPARAM(); - if(damage <= 0) { return; } - - if (self.owner.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > self.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); - self.pain_finished = time + 1; - attacker.typehitsound += 1; // play both sounds (shield is way too quiet) - } - - return; - } - - if(IS_PLAYER(attacker)) - if(time - ons_notification_time[self.team] > 10) - { - play2team(self.team, SND(ONS_CONTROLPOINT_UNDERATTACK)); - ons_notification_time[self.team] = time; - } - - self.health = self.health - damage; - if(self.owner.iscaptured) - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - else - WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE)); - self.pain_finished = time + 1; - // particles on every hit - pointparticles(particleeffectnum(EFFECT_SPARKS), hitloc, force*-1, 1); - //sound on every hit - if (random() < 0.5) - sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM); - else - sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM); - - if (self.health < 0) - { - sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), self.origin, '0 0 0', 1); - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname); - - PlayerScore_Add(attacker, SP_ONS_TAKES, 1); - PlayerScore_Add(attacker, SP_SCORE, 10); - - self.owner.goalentity = world; - self.owner.islinked = false; - self.owner.iscaptured = false; - self.owner.team = 0; - self.owner.colormap = 1024; - - WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0); - - onslaught_updatelinks(); - - // Use targets now (somebody make sure this is in the right place..) - setself(self.owner); - activator = self; - SUB_UseTargets (); - setself(this); - - self.owner.waslinked = self.owner.islinked; - if(self.owner.model != "models/onslaught/controlpoint_pad.md3") - setmodel_fixsize(self.owner, MDL_ONS_CP_PAD1); - //setsize(self, '-32 -32 0', '32 32 8'); - - remove(self); - } - - self.SendFlags |= CPSF_STATUS; -} - -void ons_ControlPoint_Icon_Think() -{SELFPARAM(); - self.nextthink = time + ONS_CP_THINKRATE; - - if(autocvar_g_onslaught_cp_proxydecap) - { - int _enemy_count = 0; - int _friendly_count = 0; - float _dist; - entity _player; - - FOR_EACH_PLAYER(_player) - { - if(!_player.deadflag) - { - _dist = vlen(_player.origin - self.origin); - if(_dist < autocvar_g_onslaught_cp_proxydecap_distance) - { - if(SAME_TEAM(_player, self)) - ++_friendly_count; - else - ++_enemy_count; - } - } - } - - _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); - _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); - - self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health); - self.SendFlags |= CPSF_STATUS; - if(self.health <= 0) - { - ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0'); - return; - } - } - - if (time > self.pain_finished + 5) - { - if(self.health < self.max_health) - { - self.health = self.health + self.count; - if (self.health >= self.max_health) - self.health = self.max_health; - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - } - } - - if(self.owner.islinked != self.owner.waslinked) - { - // unteam the spawnpoint if needed - int t = self.owner.team; - if(!self.owner.islinked) - self.owner.team = 0; - - setself(self.owner); - activator = self; - SUB_UseTargets (); - setself(this); - - self.owner.team = t; - - self.owner.waslinked = self.owner.islinked; - } - - // damaged fx - if(random() < 0.6 - self.health / self.max_health) - { - Send_Effect(EFFECT_ELECTRIC_SPARKS, self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); - - if(random() > 0.8) - sound(self, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM); - else if (random() > 0.5) - sound(self, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM); - } -} - -void ons_ControlPoint_Icon_BuildThink() -{SELFPARAM(); - int a; - - self.nextthink = time + ONS_CP_THINKRATE; - - // only do this if there is power - a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team); - if(!a) - return; - - self.health = self.health + self.count; - - self.SendFlags |= CPSF_STATUS; - - if (self.health >= self.max_health) - { - self.health = self.max_health; - self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on - self.think = ons_ControlPoint_Icon_Think; - sound(self, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM); - self.owner.iscaptured = true; - self.solid = SOLID_BBOX; - - Send_Effect(EFFECT_CAP(self.owner.team), self.owner.origin, '0 0 0', 1); - - WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - - if(IS_PLAYER(self.owner.ons_toucher)) - { - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message); - Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message); - Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message); - PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1); - PlayerTeamScore_AddScore(self.owner.ons_toucher, 10); - } - - self.owner.ons_toucher = world; - - onslaught_updatelinks(); - - // Use targets now (somebody make sure this is in the right place..) - setself(self.owner); - activator = self; - SUB_UseTargets (); - setself(this); - - self.SendFlags |= CPSF_SETUP; - } - if(self.owner.model != MDL_ONS_CP_PAD2.model_str()) - setmodel_fixsize(self.owner, MDL_ONS_CP_PAD2); - - if(random() < 0.9 - self.health / self.max_health) - Send_Effect(EFFECT_RAGE, self.origin + 10 * randomvec(), '0 0 -1', 1); -} - -void onslaught_controlpoint_icon_link(entity e, void() spawnproc); - -void ons_ControlPoint_Icon_Spawn(entity cp, entity player) -{ - entity e = spawn(); - - setsize(e, CPICON_MIN, CPICON_MAX); - setorigin(e, cp.origin + CPICON_OFFSET); - - e.classname = "onslaught_controlpoint_icon"; - e.owner = cp; - e.max_health = autocvar_g_onslaught_cp_health; - e.health = autocvar_g_onslaught_cp_buildhealth; - e.solid = SOLID_NOT; - e.takedamage = DAMAGE_AIM; - e.bot_attack = true; - e.event_damage = ons_ControlPoint_Icon_Damage; - e.team = player.team; - e.colormap = 1024 + (e.team - 1) * 17; - e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build - - sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM); - - cp.goalentity = e; - cp.team = e.team; - cp.colormap = e.colormap; - - Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1); - - WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE)); - WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY); - cp.sprite.SendFlags |= 16; - - onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink); -} - -entity ons_ControlPoint_Waypoint(entity e) -{ - if(e.team) - { - int a = ons_ControlPoint_Attackable(e, e.team); - - if(a == -2) { return WP_OnsCPDefend; } // defend now - if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch - if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack - } - else - return WP_OnsCP; - - return WP_Null; -} - -void ons_ControlPoint_UpdateSprite(entity e) -{ - entity s1 = ons_ControlPoint_Waypoint(e); - WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); - - bool sh; - sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4)); - - if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured) - { - if(e.iscaptured) // don't mess up build bars! - { - if(sh) - { - WaypointSprite_UpdateMaxHealth(e.sprite, 0); - } - else - { - WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health); - WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health); - } - } - if(e.lastshielded) - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5'); - } - else - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75'); - } - WaypointSprite_Ping(e.sprite); - - e.lastteam = e.team + 2; - e.lastshielded = sh; - e.lastcaptured = e.iscaptured; - } -} - -void ons_ControlPoint_Touch() -{SELFPARAM(); - entity toucher = other; - int attackable; - - if(IS_VEHICLE(toucher) && toucher.owner) - if(autocvar_g_onslaught_allow_vehicle_touch) - toucher = toucher.owner; - else - return; - - if(!IS_PLAYER(toucher)) { return; } - if(toucher.frozen) { return; } - if(toucher.deadflag != DEAD_NO) { return; } - - if ( SAME_TEAM(self,toucher) ) - if ( self.iscaptured ) - { - if(time <= toucher.teleport_antispam) - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time)); - else - Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); - } - - attackable = ons_ControlPoint_Attackable(self, toucher.team); - if(attackable != 2 && attackable != 4) - 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) - ons_ControlPoint_Icon_Spawn(self, toucher); - - self.ons_toucher = toucher; - - onslaught_updatelinks(); -} - -void ons_ControlPoint_Think() -{SELFPARAM(); - self.nextthink = time + ONS_CP_THINKRATE; - CSQCMODEL_AUTOUPDATE(self); -} - -void ons_ControlPoint_Reset() -{SELFPARAM(); - if(self.goalentity) - remove(self.goalentity); - - self.goalentity = world; - self.team = 0; - self.colormap = 1024; - self.iscaptured = false; - self.islinked = false; - self.isshielded = true; - self.think = ons_ControlPoint_Think; - self.ons_toucher = world; - self.nextthink = time + ONS_CP_THINKRATE; - setmodel_fixsize(self, MDL_ONS_CP_PAD1); - - WaypointSprite_UpdateMaxHealth(self.sprite, 0); - WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); - - onslaught_updatelinks(); - - activator = self; - SUB_UseTargets(); // to reset the structures, playerspawns etc. - - CSQCMODEL_AUTOUPDATE(self); -} - -void ons_DelayedControlPoint_Setup(void) -{SELFPARAM(); - onslaught_updatelinks(); - - // captureshield setup - ons_CaptureShield_Spawn(self, false); - - CSQCMODEL_AUTOINIT(self); -} - -void ons_ControlPoint_Setup(entity cp) -{SELFPARAM(); - // declarations - setself(cp); // for later usage with droptofloor() - - // main setup - cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist - ons_worldcplist = cp; - - cp.netname = "Control point"; - cp.team = 0; - cp.solid = SOLID_BBOX; - cp.movetype = MOVETYPE_NONE; - cp.touch = ons_ControlPoint_Touch; - cp.think = ons_ControlPoint_Think; - cp.nextthink = time + ONS_CP_THINKRATE; - cp.reset = ons_ControlPoint_Reset; - cp.colormap = 1024; - cp.iscaptured = false; - cp.islinked = false; - cp.isshielded = true; - - if(cp.message == "") { cp.message = "a"; } - - // appearence - setmodel_fixsize(cp, MDL_ONS_CP_PAD1); - - // control point placement - if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location - { - cp.noalign = true; - cp.movetype = MOVETYPE_NONE; - } - else // drop to floor, automatically find a platform and set that as spawn origin - { - setorigin(cp, cp.origin + '0 0 20'); - cp.noalign = false; - setself(cp); - droptofloor(); - cp.movetype = MOVETYPE_TOSS; - } - - // waypointsprites - WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE); - WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); - - InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION); -} - - -// ========================= -// Main Generator Functions -// ========================= - -entity ons_Generator_Waypoint(entity e) -{ - if (e.isshielded) - return WP_OnsGenShielded; - return WP_OnsGen; -} - -void ons_Generator_UpdateSprite(entity e) -{ - entity s1 = ons_Generator_Waypoint(e); - WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); - - if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) - { - e.lastteam = e.team + 2; - e.lastshielded = e.isshielded; - if(e.lastshielded) - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5'); - } - else - { - if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false)); - else - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); - } - WaypointSprite_Ping(e.sprite); - } -} - -void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{SELFPARAM(); - if(damage <= 0) { return; } - if(warmup_stage || gameover) { return; } - if(!round_handler_IsRoundStarted()) { return; } - - if (attacker != self) - { - if (self.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > self.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); - attacker.typehitsound += 1; - self.pain_finished = time + 1; - } - return; - } - if (time > self.pain_finished) - { - self.pain_finished = time + 10; - entity head; - FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); } - play2team(self.team, SND(ONS_GENERATOR_UNDERATTACK)); - } - } - self.health = self.health - damage; - WaypointSprite_UpdateHealth(self.sprite, self.health); - // 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) - { - self.lasthealth = self.health; - } - else - { - if (attacker == self) - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_)); - else - { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_)); - PlayerScore_Add(attacker, SP_SCORE, 100); - } - self.iscaptured = false; - self.islinked = false; - self.isshielded = false; - self.takedamage = DAMAGE_NO; // can't be hurt anymore - self.event_damage = func_null; // won't do anything if hurt - self.count = 0; // reset counter - self.think = func_null; - self.nextthink = 0; - //self.think(); // do the first explosion now - - WaypointSprite_UpdateMaxHealth(self.sprite, 0); - WaypointSprite_Ping(self.sprite); - //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor - - onslaught_updatelinks(); - } - - // Throw some flaming gibs on damage, more damage = more chance for gib - if(random() < damage/220) - { - sound(self, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - } - else - { - // particles on every hit - Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1); - - //sound on every hit - if (random() < 0.5) - sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM); - else - sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM); - } - - self.SendFlags |= GSF_STATUS; -} - -void ons_GeneratorThink() -{SELFPARAM(); - entity e; - self.nextthink = time + GEN_THINKRATE; - if (!gameover) - { - if(!self.isshielded && self.wait < time) - { - self.wait = time + 5; - FOR_EACH_REALPLAYER(e) - { - if(SAME_TEAM(e, self)) - { - Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM); - soundto(MSG_ONE, e, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound? - } - else - Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_)); - } - } - } -} - -void ons_GeneratorReset() -{SELFPARAM(); - self.team = self.team_saved; - self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; - self.takedamage = DAMAGE_AIM; - self.bot_attack = true; - self.iscaptured = true; - self.islinked = true; - self.isshielded = true; - self.event_damage = ons_GeneratorDamage; - self.think = ons_GeneratorThink; - self.nextthink = time + GEN_THINKRATE; - - Net_LinkEntity(self, false, 0, generator_send); - - self.SendFlags = GSF_SETUP; // just incase - self.SendFlags |= GSF_STATUS; - - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); - WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); - - onslaught_updatelinks(); -} - -void ons_DelayedGeneratorSetup() -{SELFPARAM(); - // bot waypoints - waypoint_spawnforitem_force(self, self.origin); - self.nearestwaypointtimeout = 0; // activate waypointing again - self.bot_basewaypoint = self.nearestwaypoint; - - // captureshield setup - ons_CaptureShield_Spawn(self, true); - - onslaught_updatelinks(); - - Net_LinkEntity(self, false, 0, generator_send); -} - - -void onslaught_generator_touch() -{SELFPARAM(); - if ( IS_PLAYER(other) ) - if ( SAME_TEAM(self,other) ) - if ( self.iscaptured ) - { - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT); - } -} - -void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc -{SELFPARAM(); - // declarations - int teamnumber = gen.team; - setself(gen); // for later usage with droptofloor() - - // main setup - gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist - ons_worldgeneratorlist = gen; - - gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber)); - gen.classname = "onslaught_generator"; - gen.solid = SOLID_BBOX; - gen.team_saved = teamnumber; - gen.movetype = MOVETYPE_NONE; - gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health; - gen.takedamage = DAMAGE_AIM; - gen.bot_attack = true; - gen.event_damage = ons_GeneratorDamage; - gen.reset = ons_GeneratorReset; - gen.think = ons_GeneratorThink; - gen.nextthink = time + GEN_THINKRATE; - gen.iscaptured = true; - gen.islinked = true; - gen.isshielded = true; - gen.touch = onslaught_generator_touch; - - // appearence - // model handled by CSQC - setsize(gen, GENERATOR_MIN, GENERATOR_MAX); - setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET)); - gen.colormap = 1024 + (teamnumber - 1) * 17; - - // generator placement - setself(gen); - droptofloor(); - - // waypointsprites - WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE); - WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); - - InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION); -} - - -// =============== -// Round Handler -// =============== - -int total_generators; -void Onslaught_count_generators() -{ - entity e; - total_generators = redowned = blueowned = yellowowned = pinkowned = 0; - for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext) - { - ++total_generators; - redowned += (e.team == NUM_TEAM_1 && e.health > 0); - blueowned += (e.team == NUM_TEAM_2 && e.health > 0); - yellowowned += (e.team == NUM_TEAM_3 && e.health > 0); - pinkowned += (e.team == NUM_TEAM_4 && e.health > 0); - } -} - -int Onslaught_GetWinnerTeam() -{ - int winner_team = 0; - if(redowned > 0) - winner_team = NUM_TEAM_1; - if(blueowned > 0) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_2; - } - if(yellowowned > 0) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_3; - } - if(pinkowned > 0) - { - if(winner_team) return 0; - winner_team = NUM_TEAM_4; - } - if(winner_team) - return winner_team; - return -1; // no generators left? -} - -#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) -#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1) -bool Onslaught_CheckWinner() -{ - entity e; - - if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)) - { - ons_stalemate = true; - - if (!wpforenemy_announced) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); - sound(world, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE); - - wpforenemy_announced = true; - } - - entity tmp_entity; // temporary entity - float d; - for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay) - { - // tmp_entity.max_health / 300 gives 5 minutes of overtime. - // control points reduce the overtime duration. - d = 1; - for(e = ons_worldcplist; e; e = e.ons_worldcpnext) - { - if(DIFF_TEAM(e, tmp_entity)) - if(e.islinked) - d = d + 1; - } - - if(autocvar_g_campaign && autocvar__campaign_testrun) - d = d * tmp_entity.max_health; - else - d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath); - - Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0'); - - tmp_entity.sprite.SendFlags |= 16; - - tmp_entity.ons_overtime_damagedelay = time + 1; - } - } - else { wpforenemy_announced = false; ons_stalemate = false; } - - Onslaught_count_generators(); - - if(ONS_OWNED_GENERATORS_OK()) - return 0; - - int winner_team = Onslaught_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_ONS_CAPS, +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); - } - - ons_stalemate = false; - - play2all(SND(CTF_CAPTURE(winner_team))); - - round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); - - FOR_EACH_PLAYER(e) - { - e.ons_roundlost = true; - e.player_blocked = true; - - nades_Clear(e); - } - - return 1; -} - -bool Onslaught_CheckPlayers() -{ - return 1; -} - -void Onslaught_RoundStart() -{ - entity tmp_entity; - FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; } - - for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) - tmp_entity.sprite.SendFlags |= 16; - - for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) - tmp_entity.sprite.SendFlags |= 16; -} - - -// ================ -// Bot player logic -// ================ - -// NOTE: LEGACY CODE, needs to be re-written! - -void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius) -{SELFPARAM(); - entity head; - float t, c; - int i; - bool needarmor = false, needweapons = false; - - // Needs armor/health? - if(self.health<100) - needarmor = true; - - // Needs weapons? - c = 0; - for(i = WEP_FIRST; i <= WEP_LAST ; ++i) - { - // Find weapon - if(self.weapons & WepSet_FromWeapon(i)) - if(++c>=4) - break; - } - - if(c<4) - needweapons = true; - - if(!needweapons && !needarmor) - return; - - ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n")); - ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n")); - - // See what is around - head = findchainfloat(bot_pickup, true); - while (head) - { - // gather health and armor only - if (head.solid) - if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) ) - if (vlen(head.origin - org) < sradius) - { - t = head.bot_pickupevalfunc(self, head); - if (t > 0) - navigation_routerating(head, t * ratingscale, 500); - } - head = head.chain; - } -} - -void havocbot_role_ons_setrole(entity bot, int role) -{ - ons_debug(strcat(bot.netname," switched to ")); - switch(role) - { - case HAVOCBOT_ONS_ROLE_DEFENSE: - ons_debug("defense"); - bot.havocbot_role = havocbot_role_ons_defense; - bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_ONS_ROLE_ASSISTANT: - ons_debug("assistant"); - bot.havocbot_role = havocbot_role_ons_assistant; - bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; - bot.havocbot_role_timeout = 0; - break; - case HAVOCBOT_ONS_ROLE_OFFENSE: - ons_debug("offense"); - bot.havocbot_role = havocbot_role_ons_offense; - bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; - bot.havocbot_role_timeout = 0; - break; - } - ons_debug("\n"); -} - -int havocbot_ons_teamcount(entity bot, int role) -{SELFPARAM(); - int c = 0; - entity head; - - FOR_EACH_PLAYER(head) - if(SAME_TEAM(head, self)) - if(head.havocbot_role_flags & role) - ++c; - - return c; -} - -void havocbot_goalrating_ons_controlpoints_attack(float ratingscale) -{SELFPARAM(); - entity cp, cp1, cp2, best, pl, wp; - float radius, bestvalue; - int c; - bool found; - - // Filter control points - for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext) - { - cp2.wpcost = c = 0; - cp2.wpconsidered = false; - - if(cp2.isshielded) - continue; - - // Ignore owned controlpoints - if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team])) - continue; - - // Count team mates interested in this control point - // (easier and cleaner than keeping counters per cp and teams) - FOR_EACH_PLAYER(pl) - if(SAME_TEAM(pl, self)) - if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) - if(pl.havocbot_ons_target==cp2) - ++c; - - // NOTE: probably decrease the cost of attackable control points - cp2.wpcost = c; - cp2.wpconsidered = true; - } - - // We'll consider only the best case - bestvalue = 99999999999; - cp = world; - for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext) - { - if (!cp1.wpconsidered) - continue; - - if(cp1.wpcost self.havocbot_role_timeout) - { - havocbot_ons_reset_role(self); - return; - } - - if(self.havocbot_attack_time>time) - return; - - if (self.bot_strategytime < time) - { - navigation_goalrating_start(); - havocbot_goalrating_enemyplayers(20000, self.origin, 650); - if(!havocbot_goalrating_ons_generator_attack(20000)) - havocbot_goalrating_ons_controlpoints_attack(20000); - havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000); - navigation_goalrating_end(); - - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - } -} - -void havocbot_role_ons_assistant() -{SELFPARAM(); - havocbot_ons_reset_role(self); -} - -void havocbot_role_ons_defense() -{SELFPARAM(); - havocbot_ons_reset_role(self); -} - -void havocbot_ons_reset_role(entity bot) -{SELFPARAM(); - entity head; - int c = 0; - - if(self.deadflag != DEAD_NO) - return; - - bot.havocbot_ons_target = world; - - // TODO: Defend control points or generator if necessary - - // if there is only me on the team switch to offense - c = 0; - FOR_EACH_PLAYER(head) - if(SAME_TEAM(head, self)) - ++c; - - if(c==1) - { - havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); - return; - } - - havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); -} - - -/* - * Find control point or generator owned by the same team self which is nearest to pos - * if max_dist is positive, only control points within this range will be considered - */ -entity ons_Nearest_ControlPoint(vector pos, float max_dist) -{SELFPARAM(); - entity tmp_entity, closest_target = world; - tmp_entity = findchain(classname, "onslaught_controlpoint"); - while(tmp_entity) - { - if(SAME_TEAM(tmp_entity, self)) - if(tmp_entity.iscaptured) - if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist) - if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) - closest_target = tmp_entity; - tmp_entity = tmp_entity.chain; - } - tmp_entity = findchain(classname, "onslaught_generator"); - while(tmp_entity) - { - if(SAME_TEAM(tmp_entity, self)) - if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist) - if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) - closest_target = tmp_entity; - tmp_entity = tmp_entity.chain; - } - - return closest_target; -} - -/* - * Find control point or generator owned by the same team self which is nearest to pos - * if max_dist is positive, only control points within this range will be considered - * This function only check distances on the XY plane, disregarding Z - */ -entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist) -{SELFPARAM(); - entity tmp_entity, closest_target = world; - vector delta; - float smallest_distance = 0, distance; - - tmp_entity = findchain(classname, "onslaught_controlpoint"); - while(tmp_entity) - { - delta = tmp_entity.origin - pos; - delta_z = 0; - distance = vlen(delta); - - if(SAME_TEAM(tmp_entity, self)) - if(tmp_entity.iscaptured) - if(max_dist <= 0 || distance <= max_dist) - if(closest_target == world || distance <= smallest_distance ) - { - closest_target = tmp_entity; - smallest_distance = distance; - } - - tmp_entity = tmp_entity.chain; - } - tmp_entity = findchain(classname, "onslaught_generator"); - while(tmp_entity) - { - delta = tmp_entity.origin - pos; - delta_z = 0; - distance = vlen(delta); - - if(SAME_TEAM(tmp_entity, self)) - if(max_dist <= 0 || distance <= max_dist) - if(closest_target == world || distance <= smallest_distance ) - { - closest_target = tmp_entity; - smallest_distance = distance; - } - - tmp_entity = tmp_entity.chain; - } - - return closest_target; -} -/** - * find the number of control points and generators in the same team as self - */ -int ons_Count_SelfControlPoints() -{SELFPARAM(); - entity tmp_entity; - tmp_entity = findchain(classname, "onslaught_controlpoint"); - int n = 0; - while(tmp_entity) - { - if(SAME_TEAM(tmp_entity, self)) - if(tmp_entity.iscaptured) - n++; - tmp_entity = tmp_entity.chain; - } - tmp_entity = findchain(classname, "onslaught_generator"); - while(tmp_entity) - { - if(SAME_TEAM(tmp_entity, self)) - n++; - tmp_entity = tmp_entity.chain; - } - return n; -} - -/** - * Teleport player to a random position near tele_target - * if tele_effects is true, teleport sound+particles are created - * return false on failure - */ -bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects) -{ - if ( !tele_target ) - return false; - - int i; - vector loc; - float theta; - for(i = 0; i < 16; ++i) - { - theta = random() * 2 * M_PI; - loc_y = sin(theta); - loc_x = cos(theta); - loc_z = 0; - loc *= random() * range; - - loc += tele_target.origin + '0 0 128'; - - tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player); - if(trace_fraction == 1.0 && !trace_startsolid) - { - traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world - if(trace_fraction == 1.0 && !trace_startsolid) - { - if ( tele_effects ) - { - Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); - sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM); - } - setorigin(player, loc); - player.angles = '0 1 0' * ( theta * RAD2DEG + 180 ); - makevectors(player.angles); - player.fixangle = true; - player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait; - - if ( tele_effects ) - Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1); - return true; - } - } - } - - return false; -} - -// ============== -// Hook Functions -// ============== - -MUTATOR_HOOKFUNCTION(ons, reset_map_global) -{SELFPARAM(); - entity e; - FOR_EACH_PLAYER(e) - { - e.ons_roundlost = false; - e.ons_deathloc = '0 0 0'; - WITH(entity, self, e, PutClientInServer()); - } - return false; -} - -MUTATOR_HOOKFUNCTION(ons, ClientDisconnect) -{SELFPARAM(); - self.ons_deathloc = '0 0 0'; - return false; -} - -MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver) -{SELFPARAM(); - self.ons_deathloc = '0 0 0'; - return false; -} - -MUTATOR_HOOKFUNCTION(ons, PlayerSpawn) -{SELFPARAM(); - if(!round_handler_IsRoundStarted()) - { - self.player_blocked = true; - return false; - } - - entity l; - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - l.sprite.SendFlags |= 16; - } - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - l.sprite.SendFlags |= 16; - } - - if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); } - - if ( autocvar_g_onslaught_spawn_choose ) - if ( self.ons_spawn_by ) - if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) ) - { - self.ons_spawn_by = world; - return false; - } - - if(autocvar_g_onslaught_spawn_at_controlpoints) - if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance) - { - float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random; - entity tmp_entity, closest_target = world; - vector spawn_loc = self.ons_deathloc; - - // new joining player or round reset, don't bother checking - if(spawn_loc == '0 0 0') { return false; } - - if(random_target) { RandomSelection_Init(); } - - for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) - { - if(SAME_TEAM(tmp_entity, self)) - if(random_target) - RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); - else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) - closest_target = tmp_entity; - } - - if(random_target) { closest_target = RandomSelection_chosen_ent; } - - if(closest_target) - { - float i; - vector loc; - for(i = 0; i < 10; ++i) - { - loc = closest_target.origin + '0 0 96'; - loc += ('0 1 0' * random()) * 128; - tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && !trace_startsolid) - { - traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(self, loc); - self.angles = normalize(loc - closest_target.origin) * RAD2DEG; - return false; - } - } - } - } - } - - if(autocvar_g_onslaught_spawn_at_generator) - if(random() <= autocvar_g_onslaught_spawn_at_generator_chance) - { - float random_target = autocvar_g_onslaught_spawn_at_generator_random; - entity tmp_entity, closest_target = world; - vector spawn_loc = self.ons_deathloc; - - // new joining player or round reset, don't bother checking - if(spawn_loc == '0 0 0') { return false; } - - if(random_target) { RandomSelection_Init(); } - - for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) - { - if(random_target) - RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); - else - { - if(SAME_TEAM(tmp_entity, self)) - if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) - closest_target = tmp_entity; - } - } - - if(random_target) { closest_target = RandomSelection_chosen_ent; } - - if(closest_target) - { - float i; - vector loc; - for(i = 0; i < 10; ++i) - { - loc = closest_target.origin + '0 0 128'; - loc += ('0 1 0' * random()) * 256; - tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && !trace_startsolid) - { - traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(self, loc); - self.angles = normalize(loc - closest_target.origin) * RAD2DEG; - return false; - } - } - } - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, PlayerDies) -{SELFPARAM(); - frag_target.ons_deathloc = frag_target.origin; - entity l; - for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) - { - l.sprite.SendFlags |= 16; - } - for(l = ons_worldcplist; l; l = l.ons_worldcpnext) - { - l.sprite.SendFlags |= 16; - } - - if ( autocvar_g_onslaught_spawn_choose ) - if ( ons_Count_SelfControlPoints() > 1 ) - stuffcmd(self, "qc_cmd_cl hud clickradar\n"); - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, MonsterMove) -{SELFPARAM(); - entity e = find(world, targetname, self.target); - if (e != world) - self.team = e.team; - - return false; -} - -void ons_MonsterSpawn_Delayed() -{SELFPARAM(); - entity e, own = self.owner; - - if(!own) { remove(self); return; } - - if(own.targetname) - { - e = find(world, target, own.targetname); - if(e != world) - { - own.team = e.team; - - activator = e; - own.use(); - } - } - - remove(self); -} - -MUTATOR_HOOKFUNCTION(ons, MonsterSpawn) -{SELFPARAM(); - entity e = spawn(); - e.owner = self; - InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET); - - return false; -} - -void ons_TurretSpawn_Delayed() -{SELFPARAM(); - entity e, own = self.owner; - - if(!own) { remove(self); return; } - - if(own.targetname) - { - e = find(world, target, own.targetname); - if(e != world) - { - own.team = e.team; - own.active = ACTIVE_NOT; - - activator = e; - own.use(); - } - } - - remove(self); -} - -MUTATOR_HOOKFUNCTION(ons, TurretSpawn) -{SELFPARAM(); - entity e = spawn(); - e.owner = self; - InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET); - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole) -{SELFPARAM(); - havocbot_ons_reset_role(self); - return true; -} - -MUTATOR_HOOKFUNCTION(ons, GetTeamCount) -{ - // onslaught is special - for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) - { - switch(tmp_entity.team) - { - case NUM_TEAM_1: c1 = 0; break; - case NUM_TEAM_2: c2 = 0; break; - case NUM_TEAM_3: c3 = 0; break; - case NUM_TEAM_4: c4 = 0; break; - } - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ons, SpectateCopy) -{SELFPARAM(); - self.ons_roundlost = other.ons_roundlost; // make spectators see it too - return false; -} - -MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE) // command was already handled? - return false; - - if ( cmd_name == "ons_spawn" ) - { - vector pos = self.origin; - if(cmd_argc > 1) - pos_x = stof(argv(1)); - if(cmd_argc > 2) - pos_y = stof(argv(2)); - if(cmd_argc > 3) - pos_z = stof(argv(3)); - - if ( IS_PLAYER(self) ) - { - if ( !self.frozen ) - { - entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); - - if ( !source_point && self.health > 0 ) - { - sprint(self, "\nYou need to be next to a control point\n"); - return 1; - } - - - entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius); - - if ( closest_target == world ) - { - sprint(self, "\nNo control point found\n"); - return 1; - } - - if ( self.health <= 0 ) - { - self.ons_spawn_by = closest_target; - self.respawn_flags = self.respawn_flags | RESPAWN_FORCE; - } - else - { - if ( source_point == closest_target ) - { - sprint(self, "\nTeleporting to the same point\n"); - return 1; - } - - if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) ) - sprint(self, "\nUnable to teleport there\n"); - } - - return 1; - } - - sprint(self, "\nNo teleportation for you\n"); - } - - return 1; - } - return 0; -} - -MUTATOR_HOOKFUNCTION(ons, PlayerUseKey) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE || gameover) { return false; } - - if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle) - { - entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); - if ( source_point ) - { - stuffcmd(self, "qc_cmd_cl hud clickradar\n"); - return true; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, PlayHitsound) -{ - return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded) - || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded); -} - -MUTATOR_HOOKFUNCTION(ons, SendWaypoint) -{ - if(wp_sendflags & 16) - { - if(self.owner.classname == "onslaught_controlpoint") - { - entity wp_owner = self.owner; - entity e = WaypointSprite_getviewentity(wp_sendto); - if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; } - if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; } - } - if(self.owner.classname == "onslaught_generator") - { - entity wp_owner = self.owner; - if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; } - if(wp_owner.health <= 0) { wp_flag |= 2; } - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget) -{ - if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job! - { - ret_float = -3; - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ons, TurretThink) -{ - // ONS uses somewhat backwards linking. - if(self.target) - { - entity e = find(world, targetname, self.target); - if (e != world) - self.team = e.team; - } - - if(self.team != self.tur_head.team) - turret_respawn(); - - return false; -} - - -// ========== -// Spawnfuncs -// ========== - -/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) - Link between control points. - - This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. - -keys: -"target" - first control point. -"target2" - second control point. - */ -spawnfunc(onslaught_link) -{ - if(!g_onslaught) { remove(self); return; } - - if (self.target == "" || self.target2 == "") - objerror("target and target2 must be set\n"); - - self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist - ons_worldlinklist = self; - - InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET); - Net_LinkEntity(self, false, 0, ons_Link_Send); -} - -/*QUAKED spawnfunc_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 spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. - -keys: -"targetname" - name that spawnfunc_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) - */ - -spawnfunc(onslaught_controlpoint) -{ - if(!g_onslaught) { remove(self); return; } - - ons_ControlPoint_Setup(self); -} - -/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) - Base generator. - - spawnfunc_onslaught_link entities can target this. - -keys: -"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. -"targetname" - name that spawnfunc_onslaught_link entities will use to target this. - */ -spawnfunc(onslaught_generator) -{ - if(!g_onslaught) { remove(self); return; } - if(!self.team) { objerror("team must be set"); } - - ons_GeneratorSetup(self); -} - -// scoreboard setup -void ons_ScoreRules() -{ - CheckAllowedTeams(world); - ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true); - ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); - ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0); - ScoreRules_basics_end(); -} - -void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up -{ - ons_ScoreRules(); - - round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart); - round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); -} - -void ons_Initialize() -{ - g_onslaught = true; - ons_captureshield_force = autocvar_g_onslaught_shield_force; - - addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost); - - InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE); -} - -REGISTER_MUTATOR(ons, IS_GAMETYPE(ONSLAUGHT)) -{ - ActivateTeamplay(); - SetLimits(autocvar_g_onslaught_point_limit, -1, -1, -1); - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - ons_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back ons_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return false; -} diff --git a/qcsrc/server/mutators/gamemode_onslaught.qh b/qcsrc/server/mutators/gamemode_onslaught.qh deleted file mode 100644 index c6c3d1814..000000000 --- a/qcsrc/server/mutators/gamemode_onslaught.qh +++ /dev/null @@ -1,93 +0,0 @@ -// these are needed since mutators are compiled last - -#ifdef SVQC - -.entity ons_toucher; // player who touched the control point - -// control point / generator constants -const float ONS_CP_THINKRATE = 0.2; -const float GEN_THINKRATE = 1; -#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) -const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128'); -const vector CPICON_OFFSET = ('0 0 96'); - -// list of generators on the map -entity ons_worldgeneratorlist; -.entity ons_worldgeneratornext; -.entity ons_stalegeneratornext; - -// list of control points on the map -entity ons_worldcplist; -.entity ons_worldcpnext; -.entity ons_stalecpnext; - -// list of links on the map -entity ons_worldlinklist; -.entity ons_worldlinknext; -.entity ons_stalelinknext; - -// definitions -.entity sprite; -.string target2; -.int iscaptured; -.int islinked; -.int isshielded; -.float lasthealth; -.int lastteam; -.int lastshielded; -.int lastcaptured; - -.bool waslinked; - -bool ons_stalemate; - -.float teleport_antispam; - -.bool ons_roundlost; - -// waypoint sprites -.entity bot_basewaypoint; // generator waypointsprite - -.bool isgenneighbor[17]; -.bool iscpneighbor[17]; -float ons_notification_time[17]; - -.float ons_overtime_damagedelay; - -.vector ons_deathloc; - -.entity ons_spawn_by; - -// declarations for functions used outside gamemode_onslaught.qc -void ons_Generator_UpdateSprite(entity e); -void ons_ControlPoint_UpdateSprite(entity e); -bool ons_ControlPoint_Attackable(entity cp, int teamnumber); - -// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet -float ons_captureshield_force; // push force of the shield - -// bot player logic -const int HAVOCBOT_ONS_ROLE_NONE = 0; -const int HAVOCBOT_ONS_ROLE_DEFENSE = 2; -const int HAVOCBOT_ONS_ROLE_ASSISTANT = 4; -const int HAVOCBOT_ONS_ROLE_OFFENSE = 8; - -.entity havocbot_ons_target; - -.int havocbot_role_flags; -.float havocbot_attack_time; - -void havocbot_role_ons_defense(); -void havocbot_role_ons_offense(); -void havocbot_role_ons_assistant(); - -void havocbot_ons_reset_role(entity bot); -void havocbot_goalrating_items(float ratingscale, vector org, float sradius); -void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius); - -// score rule declarations -const int ST_ONS_CAPS = 1; -const int SP_ONS_CAPS = 4; -const int SP_ONS_TAKES = 6; - -#endif diff --git a/qcsrc/server/mutators/gamemode_race.qc b/qcsrc/server/mutators/gamemode_race.qc deleted file mode 100644 index 7a56d7f9d..000000000 --- a/qcsrc/server/mutators/gamemode_race.qc +++ /dev/null @@ -1,454 +0,0 @@ -#include "gamemode_race.qh" - -#include "gamemode.qh" - -#include "../race.qh" - -#define autocvar_g_race_laps_limit cvar("g_race_laps_limit") -float autocvar_g_race_qualifying_timelimit; -float autocvar_g_race_qualifying_timelimit_override; -int autocvar_g_race_teams; - -// legacy bot roles -.float race_checkpoint; -void havocbot_role_race() -{SELFPARAM(); - if(self.deadflag != DEAD_NO) - return; - - entity e; - if (self.bot_strategytime < time) - { - self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; - navigation_goalrating_start(); - - for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; ) - { - if(e.cnt == self.race_checkpoint) - { - navigation_routerating(e, 1000000, 5000); - } - else if(self.race_checkpoint == -1) - { - navigation_routerating(e, 1000000, 5000); - } - } - - navigation_goalrating_end(); - } -} - -void race_ScoreRules() -{ - ScoreRules_basics(race_teams, 0, 0, false); - if(race_teams) - { - ScoreInfo_SetLabel_TeamScore( ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); - ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); - } - else if(g_race_qualifying) - { - ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); - } - else - { - ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); - ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); - ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); - } - ScoreRules_basics_end(); -} - -void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later -{ - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":race:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); -} - -MUTATOR_HOOKFUNCTION(rc, PlayerPhysics) -{SELFPARAM(); - self.race_movetime_frac += PHYS_INPUT_TIMELENGTH; - float f = floor(self.race_movetime_frac); - self.race_movetime_frac -= f; - self.race_movetime_count += f; - self.race_movetime = self.race_movetime_frac + self.race_movetime_count; - -#ifdef SVQC - if(IS_PLAYER(self)) - { - if (self.race_penalty) - if (time > self.race_penalty) - self.race_penalty = 0; - if(self.race_penalty) - { - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; - self.disableclientprediction = 2; - } - } -#endif - - // force kbd movement for fairness - float wishspeed; - vector wishvel; - - // if record times matter - // ensure nothing EVIL is being done (i.e. div0_evade) - // this hinders joystick users though - // but it still gives SOME analog control - wishvel.x = fabs(self.movement.x); - wishvel.y = fabs(self.movement.y); - if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) - { - wishvel.z = 0; - wishspeed = vlen(wishvel); - if(wishvel.x >= 2 * wishvel.y) - { - // pure X motion - if(self.movement.x > 0) - self.movement_x = wishspeed; - else - self.movement_x = -wishspeed; - self.movement_y = 0; - } - else if(wishvel.y >= 2 * wishvel.x) - { - // pure Y motion - self.movement_x = 0; - if(self.movement.y > 0) - self.movement_y = wishspeed; - else - self.movement_y = -wishspeed; - } - else - { - // diagonal - if(self.movement.x > 0) - self.movement_x = M_SQRT1_2 * wishspeed; - else - self.movement_x = -M_SQRT1_2 * wishspeed; - if(self.movement.y > 0) - self.movement_y = M_SQRT1_2 * wishspeed; - else - self.movement_y = -M_SQRT1_2 * wishspeed; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, reset_map_global) -{ - float s; - - Score_NicePrint(world); - - race_ClearRecords(); - PlayerScore_Sort(race_place, 0, 1, 0); - - entity e; - FOR_EACH_CLIENT(e) - { - if(e.race_place) - { - s = PlayerScore_Add(e, SP_RACE_FASTEST, 0); - if(!s) - e.race_place = 0; - } - race_EventLog(ftos(e.race_place), e); - } - - if(g_race_qualifying == 2) - { - g_race_qualifying = 0; - independent_players = 0; - cvar_set("fraglimit", ftos(race_fraglimit)); - cvar_set("leadlimit", ftos(race_leadlimit)); - cvar_set("timelimit", ftos(race_timelimit)); - race_ScoreRules(); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, PlayerPreThink) -{SELFPARAM(); - if(IS_SPEC(self) || IS_OBSERVER(self)) - if(g_race_qualifying) - if(msg_entity.enemy.race_laptime) - race_SendNextCheckpoint(msg_entity.enemy, 1); - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, ClientConnect) -{SELFPARAM(); - race_PreparePlayer(); - self.race_checkpoint = -1; - - string rr = RACE_RECORD; - - if(IS_REAL_CLIENT(self)) - { - msg_entity = self; - race_send_recordtime(MSG_ONE); - race_send_speedaward(MSG_ONE); - - speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); - speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); - race_send_speedaward_alltimebest(MSG_ONE); - - float i; - for (i = 1; i <= RANKINGS_CNT; ++i) - { - race_SendRankings(i, 0, 0, MSG_ONE); - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver) -{SELFPARAM(); - if(g_race_qualifying) - if(PlayerScore_Add(self, SP_RACE_FASTEST, 0)) - self.frags = FRAGS_LMS_LOSER; - else - self.frags = FRAGS_SPECTATOR; - - race_PreparePlayer(); - self.race_checkpoint = -1; - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, PlayerSpawn) -{SELFPARAM(); - if(spawn_spot.target == "") - // Emergency: this wasn't a real spawnpoint. Can this ever happen? - race_PreparePlayer(); - - // if we need to respawn, do it right - self.race_respawn_checkpoint = self.race_checkpoint; - self.race_respawn_spotref = spawn_spot; - - self.race_place = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, PutClientInServer) -{SELFPARAM(); - if(IS_PLAYER(self)) - if(!gameover) - { - if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn - race_PreparePlayer(); - else // respawn - race_RetractPlayer(); - - race_AbandonRaceCheck(self); - } - return false; -} - -MUTATOR_HOOKFUNCTION(rc, PlayerDies) -{SELFPARAM(); - self.respawn_flags |= RESPAWN_FORCE; - race_AbandonRaceCheck(self); - return false; -} - -MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole) -{SELFPARAM(); - self.havocbot_role = havocbot_role_race; - return true; -} - -MUTATOR_HOOKFUNCTION(rc, GetPressedKeys) -{SELFPARAM(); - if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1) - { - if (!self.stored_netname) - self.stored_netname = strzone(uid2name(self.crypto_idfp)); - if(self.stored_netname != self.netname) - { - db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname); - strunzone(self.stored_netname); - self.stored_netname = strzone(self.netname); - } - } - - if (!IS_OBSERVER(self)) - { - if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed) - { - speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1'); - speedaward_holder = self.netname; - speedaward_uid = self.crypto_idfp; - speedaward_lastupdate = time; - } - if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) - { - string rr = RACE_RECORD; - race_send_speedaward(MSG_ALL); - speedaward_lastsent = speedaward_speed; - if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") - { - speedaward_alltimebest = speedaward_speed; - speedaward_alltimebest_holder = speedaward_holder; - speedaward_alltimebest_uid = speedaward_uid; - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); - db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); - race_send_speedaward_alltimebest(MSG_ALL); - } - } - } - return false; -} - -MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear) -{ - if(g_race_qualifying) - return true; // in qualifying, you don't lose score by observing - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, GetTeamCount, CBC_ORDER_EXCLUSIVE) -{ - ret_float = race_teams; - return false; -} - -MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining) -{ - // announce remaining frags if not in qualifying mode - if(!g_race_qualifying) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, GetRecords) -{ - for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) - { - if(MapInfo_Get_ByID(i)) - { - float r = race_readTime(MapInfo_Map_bspname, 1); - - if(!r) - continue; - - string h = race_readName(MapInfo_Map_bspname, 1); - ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, FixClientCvars) -{ - stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n"); - return false; -} - -MUTATOR_HOOKFUNCTION(rc, CheckRules_World) -{ - if(g_race_qualifying == 2 && checkrules_timelimit >= 0) - { - ret_float = WinningCondition_QualifyingThenRace(checkrules_fraglimit); - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars) -{ - if(g_race_qualifying == 2) - warmup_stage = 0; - return false; -} - -void race_Initialize() -{ - race_ScoreRules(); - if(g_race_qualifying == 2) - warmup_stage = 0; -} - -void rc_SetLimits() -{ - int fraglimit_override, leadlimit_override; - float timelimit_override, qualifying_override; - - if(autocvar_g_race_teams) - { - ActivateTeamplay(); - race_teams = bound(2, autocvar_g_race_teams, 4); - have_team_spawns = -1; // request team spawns - } - else - race_teams = 0; - - qualifying_override = autocvar_g_race_qualifying_timelimit_override; - fraglimit_override = autocvar_g_race_laps_limit; - leadlimit_override = 0; // currently not supported by race - timelimit_override = -1; // use default if we don't set it below - - // we need to find out the correct value for g_race_qualifying - float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0; - - if(autocvar_g_campaign) - { - g_race_qualifying = 1; - independent_players = 1; - } - else if(!autocvar_g_campaign && want_qualifying) - { - g_race_qualifying = 2; - independent_players = 1; - race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit; - race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit; - race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit; - fraglimit_override = 0; - leadlimit_override = 0; - timelimit_override = autocvar_g_race_qualifying_timelimit; - } - else - g_race_qualifying = 0; - - SetLimits(fraglimit_override, leadlimit_override, timelimit_override, qualifying_override); -} - -REGISTER_MUTATOR(rc, g_race) -{ - rc_SetLimits(); - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - race_Initialize(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back race_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/gamemode_race.qh b/qcsrc/server/mutators/gamemode_race.qh deleted file mode 100644 index 0e20b9b57..000000000 --- a/qcsrc/server/mutators/gamemode_race.qh +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef GAMEMODE_RACE_H -#define GAMEMODE_RACE_H - -float g_race_qualifying; -float race_teams; - -// scores -const float ST_RACE_LAPS = 1; -const float SP_RACE_LAPS = 4; -const float SP_RACE_TIME = 5; -const float SP_RACE_FASTEST = 6; -#endif diff --git a/qcsrc/server/mutators/gamemode_tdm.qc b/qcsrc/server/mutators/gamemode_tdm.qc deleted file mode 100644 index 20270b5a2..000000000 --- a/qcsrc/server/mutators/gamemode_tdm.qc +++ /dev/null @@ -1,91 +0,0 @@ -#include "gamemode.qh" - -bool autocvar_g_tdm_team_spawns; -int autocvar_g_tdm_point_limit; -int autocvar_g_tdm_point_leadlimit; -int autocvar_g_tdm_teams; -int autocvar_g_tdm_teams_override; - -/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32) -Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map. -Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. -Keys: -"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... -"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ -spawnfunc(tdm_team) -{ - if(!g_tdm || !self.cnt) { remove(self); return; } - - self.classname = "tdm_team"; - self.team = self.cnt + 1; -} - -// code from here on is just to support maps that don't have team entities -void tdm_SpawnTeam (string teamname, float teamcolor) -{ - entity this = new(tdm_team); - this.netname = teamname; - this.cnt = teamcolor; - this.spawnfunc_checked = true; - WITH(entity, self, this, spawnfunc_tdm_team(this)); -} - -void tdm_DelayedInit() -{ - // if no teams are found, spawn defaults - if(find(world, classname, "tdm_team") == world) - { - LOG_INFO("No ""tdm_team"" entities found on this map, creating them anyway.\n"); - - int numteams = min(4, autocvar_g_tdm_teams_override); - - if(numteams < 2) { numteams = autocvar_g_tdm_teams; } - numteams = bound(2, numteams, 4); - - float i; - for(i = 1; i <= numteams; ++i) - tdm_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1); - } -} - -MUTATOR_HOOKFUNCTION(tdm, GetTeamCount, CBC_ORDER_EXCLUSIVE) -{ - ret_string = "tdm_team"; - return true; -} - -MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining) -{ - // announce remaining frags - return true; -} - -REGISTER_MUTATOR(tdm, g_tdm) -{ - ActivateTeamplay(); - SetLimits(autocvar_g_tdm_point_limit, autocvar_g_tdm_point_leadlimit, -1, -1); - if(autocvar_g_tdm_team_spawns) - have_team_spawns = -1; // request team spawns - - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This is a game type and it cannot be added at runtime."); - InitializeEntity(world, tdm_DelayedInit, INITPRIO_GAMETYPE); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // we actually cannot roll back tdm_Initialize here - // BUT: we don't need to! If this gets called, adding always - // succeeds. - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This is a game type and it cannot be removed at runtime."); - return -1; - } - - return 0; -} diff --git a/qcsrc/server/mutators/mutator.qh b/qcsrc/server/mutators/mutator.qh index 8dde34af3..f883b84b4 100644 --- a/qcsrc/server/mutators/mutator.qh +++ b/qcsrc/server/mutators/mutator.qh @@ -2,7 +2,6 @@ #define MUTATOR_H #include "../../common/mutators/base.qh" -#include "mutator_nades.qh" #include "../cl_client.qh" #include "../cl_player.qh" diff --git a/qcsrc/server/mutators/mutator/gamemode_assault.qc b/qcsrc/server/mutators/mutator/gamemode_assault.qc new file mode 100644 index 000000000..52fb40fea --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_assault.qc @@ -0,0 +1,671 @@ +#ifndef GAMEMODE_ASSAULT_H +#define GAMEMODE_ASSAULT_H + +// sprites +.entity assault_decreaser; +.entity assault_sprite; + +// legacy bot defs +const int HAVOCBOT_AST_ROLE_NONE = 0; +const int HAVOCBOT_AST_ROLE_DEFENSE = 2; +const int HAVOCBOT_AST_ROLE_OFFENSE = 4; + +.int havocbot_role_flags; +.float havocbot_attack_time; + +.void() havocbot_role; +.void() havocbot_previous_role; + +void() havocbot_role_ast_defense; +void() havocbot_role_ast_offense; +.entity havocbot_ast_target; + +void(entity bot) havocbot_ast_reset_role; + +void(float ratingscale, vector org, float sradius) havocbot_goalrating_items; +void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers; + +// scoreboard stuff +const float ST_ASSAULT_OBJECTIVES = 1; +const float SP_ASSAULT_OBJECTIVES = 4; + +// predefined spawnfuncs +void target_objective_decrease_activate(); +#endif + +#ifdef IMPLEMENTATION +.entity sprite; + +// random functions +void assault_objective_use() +{SELFPARAM(); + // activate objective + self.health = 100; + //print("^2Activated objective ", self.targetname, "=", etos(self), "\n"); + //print("Activator is ", activator.classname, "\n"); + + for (entity e = world; (e = find(e, target, this.targetname)); ) + { + if (e.classname == "target_objective_decrease") + { + WITH(entity, self, e, target_objective_decrease_activate()); + } + } + + setself(this); +} + +vector target_objective_spawn_evalfunc(entity player, entity spot, vector current) +{SELFPARAM(); + if(self.health < 0 || self.health >= ASSAULT_VALUE_INACTIVE) + return '-1 0 0'; + return current; +} + +// reset this objective. Used when spawning an objective +// and when a new round starts +void assault_objective_reset() +{SELFPARAM(); + self.health = ASSAULT_VALUE_INACTIVE; +} + +// decrease the health of targeted objectives +void assault_objective_decrease_use() +{SELFPARAM(); + if(activator.team != assault_attacker_team) + { + // wrong team triggered decrease + return; + } + + if(other.assault_sprite) + { + WaypointSprite_Disown(other.assault_sprite, waypointsprite_deadlifetime); + if(other.classname == "func_assault_destructible") + other.sprite = world; + } + else + return; // already activated! cannot activate again! + + if(self.enemy.health < ASSAULT_VALUE_INACTIVE) + { + if(self.enemy.health - self.dmg > 0.5) + { + PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.dmg); + self.enemy.health = self.enemy.health - self.dmg; + } + else + { + PlayerTeamScore_Add(activator, SP_SCORE, ST_SCORE, self.enemy.health); + PlayerTeamScore_Add(activator, SP_ASSAULT_OBJECTIVES, ST_ASSAULT_OBJECTIVES, 1); + self.enemy.health = -1; + + entity oldactivator, head; + + setself(this.enemy); + if(self.message) + FOR_EACH_PLAYER(head) + centerprint(head, self.message); + + oldactivator = activator; + activator = this; + SUB_UseTargets(); + activator = oldactivator; + setself(this); + } + } +} + +void assault_setenemytoobjective() +{SELFPARAM(); + entity objective; + for(objective = world; (objective = find(objective, targetname, self.target)); ) + { + if(objective.classname == "target_objective") + { + if(self.enemy == world) + self.enemy = objective; + else + objerror("more than one objective as target - fix the map!"); + break; + } + } + + if(self.enemy == world) + objerror("no objective as target - fix the map!"); +} + +float assault_decreaser_sprite_visible(entity e) +{SELFPARAM(); + entity decreaser; + + decreaser = self.assault_decreaser; + + if(decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE) + return false; + + return true; +} + +void target_objective_decrease_activate() +{SELFPARAM(); + entity ent, spr; + self.owner = world; + for(ent = world; (ent = find(ent, target, self.targetname)); ) + { + if(ent.assault_sprite != world) + { + WaypointSprite_Disown(ent.assault_sprite, waypointsprite_deadlifetime); + if(ent.classname == "func_assault_destructible") + ent.sprite = world; + } + + spr = WaypointSprite_SpawnFixed(WP_Assault, 0.5 * (ent.absmin + ent.absmax), ent, assault_sprite, RADARICON_OBJECTIVE); + spr.assault_decreaser = self; + spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible; + spr.classname = "sprite_waypoint"; + WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY); + if(ent.classname == "func_assault_destructible") + { + WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy); + WaypointSprite_UpdateMaxHealth(spr, ent.max_health); + WaypointSprite_UpdateHealth(spr, ent.health); + ent.sprite = spr; + } + else + WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush); + } +} + +void target_objective_decrease_findtarget() +{ + assault_setenemytoobjective(); +} + +void target_assault_roundend_reset() +{SELFPARAM(); + //print("round end reset\n"); + self.cnt = self.cnt + 1; // up round counter + self.winning = 0; // up round +} + +void target_assault_roundend_use() +{SELFPARAM(); + self.winning = 1; // round has been won by attackers +} + +void assault_roundstart_use() +{SELFPARAM(); + activator = self; + SUB_UseTargets(); + + //(Re)spawn all turrets + for(entity ent = NULL; (ent = find(ent, classname, "turret_main")); ) { + // Swap turret teams + if(ent.team == NUM_TEAM_1) + ent.team = NUM_TEAM_2; + else + ent.team = NUM_TEAM_1; + + // Dubbles as teamchange + WITH(entity, self, ent, turret_respawn()); + } +} + +void assault_wall_think() +{SELFPARAM(); + if(self.enemy.health < 0) + { + self.model = ""; + self.solid = SOLID_NOT; + } + else + { + self.model = self.mdl; + self.solid = SOLID_BSP; + } + + self.nextthink = time + 0.2; +} + +// trigger new round +// reset objectives, toggle spawnpoints, reset triggers, ... +void vehicles_clearreturn(entity veh); +void vehicles_spawn(); +void assault_new_round() +{SELFPARAM(); + //bprint("ASSAULT: new round\n"); + + // Eject players from vehicles + entity e; + FOR_EACH_PLAYER(e) + { + if(e.vehicle) + { + WITH(entity, self, e, vehicles_exit(VHEF_RELEASE)); + } + } + + for (entity e_ = findchainflags(vehicle_flags, VHF_ISVEHICLE); e_; e_ = e_.chain) + { + setself(e_); + vehicles_clearreturn(self); + vehicles_spawn(); + } + + setself(this); + + // up round counter + self.winning = self.winning + 1; + + // swap attacker/defender roles + if(assault_attacker_team == NUM_TEAM_1) + assault_attacker_team = NUM_TEAM_2; + else + assault_attacker_team = NUM_TEAM_1; + + entity ent; + for(ent = world; (ent = nextent(ent)); ) + { + if(clienttype(ent) == CLIENTTYPE_NOTACLIENT) + { + if(ent.team_saved == NUM_TEAM_1) + ent.team_saved = NUM_TEAM_2; + else if(ent.team_saved == NUM_TEAM_2) + ent.team_saved = NUM_TEAM_1; + } + } + + // reset the level with a countdown + cvar_set("timelimit", ftos(ceil(time - game_starttime) / 60)); + ReadyRestart_force(); // sets game_starttime +} + +// spawnfuncs +spawnfunc(info_player_attacker) +{ + if (!g_assault) { remove(self); return; } + + self.team = NUM_TEAM_1; // red, gets swapped every round + spawnfunc_info_player_deathmatch(this); +} + +spawnfunc(info_player_defender) +{ + if (!g_assault) { remove(self); return; } + + self.team = NUM_TEAM_2; // blue, gets swapped every round + spawnfunc_info_player_deathmatch(this); +} + +spawnfunc(target_objective) +{ + if (!g_assault) { remove(self); return; } + + self.classname = "target_objective"; + self.use = assault_objective_use; + assault_objective_reset(); + self.reset = assault_objective_reset; + self.spawn_evalfunc = target_objective_spawn_evalfunc; +} + +spawnfunc(target_objective_decrease) +{ + if (!g_assault) { remove(self); return; } + + self.classname = "target_objective_decrease"; + + if(!self.dmg) + self.dmg = 101; + + self.use = assault_objective_decrease_use; + self.health = ASSAULT_VALUE_INACTIVE; + self.max_health = ASSAULT_VALUE_INACTIVE; + self.enemy = world; + + InitializeEntity(self, target_objective_decrease_findtarget, INITPRIO_FINDTARGET); +} + +// destructible walls that can be used to trigger target_objective_decrease +spawnfunc(func_breakable); +spawnfunc(func_assault_destructible) +{ + if (!g_assault) { remove(self); return; } + + self.spawnflags = 3; + self.classname = "func_assault_destructible"; + + if(assault_attacker_team == NUM_TEAM_1) + self.team = NUM_TEAM_2; + else + self.team = NUM_TEAM_1; + + spawnfunc_func_breakable(this); +} + +spawnfunc(func_assault_wall) +{ + if (!g_assault) { remove(self); return; } + + self.classname = "func_assault_wall"; + self.mdl = self.model; + _setmodel(self, self.mdl); + self.solid = SOLID_BSP; + self.think = assault_wall_think; + self.nextthink = time; + InitializeEntity(self, assault_setenemytoobjective, INITPRIO_FINDTARGET); +} + +spawnfunc(target_assault_roundend) +{ + if (!g_assault) { remove(self); return; } + + self.winning = 0; // round not yet won by attackers + self.classname = "target_assault_roundend"; + self.use = target_assault_roundend_use; + self.cnt = 0; // first round + self.reset = target_assault_roundend_reset; +} + +spawnfunc(target_assault_roundstart) +{ + if (!g_assault) { remove(self); return; } + + assault_attacker_team = NUM_TEAM_1; + self.classname = "target_assault_roundstart"; + self.use = assault_roundstart_use; + self.reset2 = assault_roundstart_use; + InitializeEntity(self, assault_roundstart_use, INITPRIO_FINDTARGET); +} + +// legacy bot code +void havocbot_goalrating_ast_targets(float ratingscale) +{SELFPARAM(); + entity ad, best, wp, tod; + float radius, found, bestvalue; + vector p; + + ad = findchain(classname, "func_assault_destructible"); + + for (; ad; ad = ad.chain) + { + if (ad.target == "") + continue; + + if (!ad.bot_attack) + continue; + + found = false; + for(tod = world; (tod = find(tod, targetname, ad.target)); ) + { + if(tod.classname == "target_objective_decrease") + { + if(tod.enemy.health > 0 && tod.enemy.health < ASSAULT_VALUE_INACTIVE) + { + // dprint(etos(ad),"\n"); + found = true; + break; + } + } + } + + if(!found) + { + /// dprint("target not found\n"); + continue; + } + /// dprint("target #", etos(ad), " found\n"); + + + p = 0.5 * (ad.absmin + ad.absmax); + // dprint(vtos(ad.origin), " ", vtos(ad.absmin), " ", vtos(ad.absmax),"\n"); + // te_knightspike(p); + // te_lightning2(world, '0 0 0', p); + + // Find and rate waypoints around it + found = false; + best = world; + bestvalue = 99999999999; + for(radius=0; radius<1500 && !found; radius+=500) + { + for(wp=findradius(p, radius); wp; wp=wp.chain) + { + if(!(wp.wpflags & WAYPOINTFLAG_GENERATED)) + if(wp.classname=="waypoint") + if(checkpvs(wp.origin, ad)) + { + found = true; + if(wp.cnt self.havocbot_role_timeout) + { + havocbot_ast_reset_role(self); + return; + } + + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 650); + havocbot_goalrating_ast_targets(20000); + havocbot_goalrating_items(15000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } +} + +void havocbot_role_ast_defense() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + { + self.havocbot_attack_time = 0; + havocbot_ast_reset_role(self); + return; + } + + // Set the role timeout if necessary + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + 120; + + if (time > self.havocbot_role_timeout) + { + havocbot_ast_reset_role(self); + return; + } + + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 3000); + havocbot_goalrating_ast_targets(20000); + havocbot_goalrating_items(15000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } +} + +void havocbot_role_ast_setrole(entity bot, float role) +{ + switch(role) + { + case HAVOCBOT_AST_ROLE_DEFENSE: + bot.havocbot_role = havocbot_role_ast_defense; + bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_AST_ROLE_OFFENSE: + bot.havocbot_role = havocbot_role_ast_offense; + bot.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE; + bot.havocbot_role_timeout = 0; + break; + } +} + +void havocbot_ast_reset_role(entity bot) +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if(bot.team == assault_attacker_team) + havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_OFFENSE); + else + havocbot_role_ast_setrole(bot, HAVOCBOT_AST_ROLE_DEFENSE); +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(as, PlayerSpawn) +{SELFPARAM(); + if(self.team == assault_attacker_team) + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_ATTACKING); + else + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_ASSAULT_DEFENDING); + + return false; +} + +MUTATOR_HOOKFUNCTION(as, TurretSpawn) +{SELFPARAM(); + if(!self.team || self.team == MAX_SHOT_DISTANCE) + self.team = 5; // this gets reversed when match starts? + + return false; +} + +MUTATOR_HOOKFUNCTION(as, VehicleSpawn) +{SELFPARAM(); + self.nextthink = time + 0.5; + + return false; +} + +MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole) +{SELFPARAM(); + havocbot_ast_reset_role(self); + return true; +} + +MUTATOR_HOOKFUNCTION(as, PlayHitsound) +{ + return (frag_victim.classname == "func_assault_destructible"); +} + +MUTATOR_HOOKFUNCTION(as, GetTeamCount) +{ + // assault always has 2 teams + c1 = c2 = 0; + return true; +} + +MUTATOR_HOOKFUNCTION(as, CheckRules_World) +{ + ret_float = WinningCondition_Assault(); + return true; +} + +MUTATOR_HOOKFUNCTION(as, ReadLevelCvars) +{ + // no assault warmups + warmup_stage = 0; + return false; +} + +MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn) +{ + switch(self.classname) + { + case "info_player_team1": + case "info_player_team2": + case "info_player_team3": + case "info_player_team4": + return true; + } + + return false; +} + +// scoreboard setup +void assault_ScoreRules() +{ + ScoreRules_basics(2, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, true); + ScoreInfo_SetLabel_TeamScore( ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics_end(); +} + +REGISTER_MUTATOR(as, g_assault) +{ + ActivateTeamplay(); + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + assault_ScoreRules(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back assault_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_ca.qc b/qcsrc/server/mutators/mutator/gamemode_ca.qc new file mode 100644 index 000000000..20330b757 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_ca.qc @@ -0,0 +1,529 @@ +#ifndef GAMEMODE_CA_H +#define GAMEMODE_CA_H + +// 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 +#endif + +#ifdef IMPLEMENTATION +float autocvar_g_ca_damage2score_multiplier; +int autocvar_g_ca_point_leadlimit; +int autocvar_g_ca_point_limit; +float autocvar_g_ca_round_timelimit; +bool autocvar_g_ca_spectate_enemies; +int autocvar_g_ca_teams; +int autocvar_g_ca_teams_override; +bool autocvar_g_ca_team_spawns; +float autocvar_g_ca_warmup; + +float ca_teams; +float allowed_to_spawn; + +const float ST_CA_ROUNDS = 1; +void ca_ScoreRules(float teams) +{ + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); + ScoreInfo_SetLabel_TeamScore(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY); + ScoreRules_basics_end(); +} + +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() +{ + 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); + allowed_to_spawn = false; + round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit); + FOR_EACH_PLAYER(e) + nades_Clear(e); + 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_CA_ROUNDS, +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); + + FOR_EACH_PLAYER(e) + nades_Clear(e); + + return 1; +} + +void CA_RoundStart() +{ + if(warmup_stage) + allowed_to_spawn = true; + else + allowed_to_spawn = false; +} + +float CA_CheckTeams() +{ + static float prev_missing_teams_mask; + allowed_to_spawn = true; + CA_count_alive_players(); + if(CA_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(ca_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(ca_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + +float ca_isEliminated(entity e) +{ + if(e.caplayer == 1 && (e.deadflag != DEAD_NO || e.frags == FRAGS_LMS_LOSER)) + return true; + if(e.caplayer == 0.5) + return true; + return false; +} + +// Returns next available player to spectate if g_ca_spectate_enemies == 0 +entity CA_SpectateNext(entity player, entity start) +{ + if(SAME_TEAM(start, player)) + return start; + + entity spec_other = start; + // continue from current player + while(spec_other && DIFF_TEAM(spec_other, player)) + spec_other = find(spec_other, classname, "player"); + + if (!spec_other) + { + // restart from begining + spec_other = find(spec_other, classname, "player"); + while(spec_other && DIFF_TEAM(spec_other, player)) + spec_other = find(spec_other, classname, "player"); + } + + return spec_other; +} + +MUTATOR_HOOKFUNCTION(ca, PlayerSpawn) +{SELFPARAM(); + self.caplayer = 1; + if(!warmup_stage) + eliminatedPlayers.SendFlags |= 1; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, PutClientInServer) +{SELFPARAM(); + if(!allowed_to_spawn) + if(IS_PLAYER(self)) // this is true even when player is trying to join + { + self.classname = "observer"; + if(self.jointime != time) //not when connecting + if(!self.caplayer) + { + self.caplayer = 0.5; + if(IS_REAL_CLIENT(self)) + Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_JOIN_LATE); + } + } + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, reset_map_players) +{SELFPARAM(); + entity e; + FOR_EACH_CLIENT(e) + { + setself(e); + self.killcount = 0; + if(!self.caplayer && IS_BOT_CLIENT(self)) + { + self.team = -1; + self.caplayer = 1; + } + if(self.caplayer) + { + self.classname = "player"; + self.caplayer = 1; + PutClientInServer(); + } + } + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, ClientConnect) +{SELFPARAM(); + self.classname = "observer"; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, reset_map_global) +{ + allowed_to_spawn = true; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, GetTeamCount, CBC_ORDER_EXCLUSIVE) +{ + ret_float = ca_teams; + return false; +} + +entity ca_LastPlayerForTeam() +{SELFPARAM(); + entity pl, last_pl = world; + FOR_EACH_PLAYER(pl) + { + if(pl.health >= 1) + if(pl != self) + if(pl.team == self.team) + if(!last_pl) + last_pl = pl; + else + return world; + } + return last_pl; +} + +void ca_LastPlayerForTeam_Notify() +{ + if(round_handler_IsActive()) + if(round_handler_IsRoundStarted()) + { + entity pl = ca_LastPlayerForTeam(); + if(pl) + Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); + } +} + +MUTATOR_HOOKFUNCTION(ca, PlayerDies) +{SELFPARAM(); + ca_LastPlayerForTeam_Notify(); + if(!allowed_to_spawn) + self.respawn_flags = RESPAWN_SILENT; + if(!warmup_stage) + eliminatedPlayers.SendFlags |= 1; + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, ClientDisconnect) +{SELFPARAM(); + if(self.caplayer == 1) + ca_LastPlayerForTeam_Notify(); + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, ForbidPlayerScore_Clear) +{ + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver) +{SELFPARAM(); + if(self.caplayer == 1) + ca_LastPlayerForTeam_Notify(); + if(self.killindicator_teamchange == -2) + self.caplayer = 0; + if(self.caplayer) + self.frags = FRAGS_LMS_LOSER; + if(!warmup_stage) + eliminatedPlayers.SendFlags |= 1; + return true; +} + +MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon) +{ + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST) +{ + frag_score = 0; // score will be given to the winner team when the round ends + return 1; +} + +MUTATOR_HOOKFUNCTION(ca, SetStartItems) +{ + start_items &= ~IT_UNLIMITED_AMMO; + start_health = warmup_start_health = cvar("g_lms_start_health"); + start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); + start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); + start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); + start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); + start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); + start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); + + return 0; +} + +MUTATOR_HOOKFUNCTION(ca, PlayerDamage_Calculate) +{ + if(IS_PLAYER(frag_target)) + if(frag_target.deadflag == DEAD_NO) + if(frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + + frag_mirrordamage = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(ca, FilterItem) +{SELFPARAM(); + if(autocvar_g_powerups <= 0) + if(self.flags & FL_POWERUP) + return true; + + if(autocvar_g_pickup_items <= 0) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor) +{ + float excess = max(0, frag_damage - damage_take - damage_save); + + if(frag_target != frag_attacker && IS_PLAYER(frag_attacker)) + PlayerTeamScore_Add(frag_attacker, SP_SCORE, ST_SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier); + + return false; +} + +MUTATOR_HOOKFUNCTION(ca, PlayerRegen) +{ + // no regeneration in CA + return true; +} + +MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} + +MUTATOR_HOOKFUNCTION(ca, SpectateSet) +{ + if(!autocvar_g_ca_spectate_enemies && self.caplayer) + if(DIFF_TEAM(spec_player, self)) + return true; + return false; +} + +MUTATOR_HOOKFUNCTION(ca, SpectateNext) +{SELFPARAM(); + if(!autocvar_g_ca_spectate_enemies && self.caplayer) + { + spec_player = CA_SpectateNext(self, spec_player); + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(ca, SpectatePrev) +{SELFPARAM(); + if(!autocvar_g_ca_spectate_enemies && self.caplayer) + { + do { spec_player = spec_player.chain; } + while(spec_player && DIFF_TEAM(spec_player, self)); + + if (!spec_player) + { + spec_player = spec_first; + while(spec_player && DIFF_TEAM(spec_player, self)) + spec_player = spec_player.chain; + if(spec_player == self.enemy) + return MUT_SPECPREV_RETURN; + } + } + + return MUT_SPECPREV_FOUND; +} + +MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE) +{ + entity head; + FOR_EACH_REALCLIENT(head) + { + if(IS_PLAYER(head) || head.caplayer == 1) + ++bot_activerealplayers; + ++bot_realplayers; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate) +{ + if(self.caplayer) + { + // they're going to spec, we can do other checks + if(autocvar_sv_spectate && (IS_SPEC(self) || IS_OBSERVER(self))) + Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CA_LEAVE); + return MUT_SPECCMD_FORCE; + } + + return MUT_SPECCMD_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(ca, WantWeapon) +{ + want_allguns = true; + return false; +} + +MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus) +{ + if(set_player.caplayer == 1) + return true; + return false; +} + +MUTATOR_HOOKFUNCTION(ca, SetWeaponArena) +{ + // most weapons arena + if(ret_string == "0" || ret_string == "") + ret_string = "most"; + return false; +} + +void ca_Initialize() +{ + allowed_to_spawn = true; + + 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; + ca_ScoreRules(ca_teams); + + 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); + + EliminatedPlayers_Init(ca_isEliminated); +} + +REGISTER_MUTATOR(ca, g_ca) +{ + ActivateTeamplay(); + SetLimits(autocvar_g_ca_point_limit, autocvar_g_ca_point_leadlimit, -1, -1); + + if(autocvar_g_ca_team_spawns) + have_team_spawns = -1; // request team spawns + + 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 + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_ctf.qc b/qcsrc/server/mutators/mutator/gamemode_ctf.qc new file mode 100644 index 000000000..e2eb898ef --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_ctf.qc @@ -0,0 +1,2772 @@ +#ifndef GAMEMODE_CTF_H +#define GAMEMODE_CTF_H + +#ifdef SVQC +// used in cheats.qc +void ctf_RespawnFlag(entity flag); + +// score rule declarations +const int ST_CTF_CAPS = 1; +const int SP_CTF_CAPS = 4; +const int SP_CTF_CAPTIME = 5; +const int SP_CTF_PICKUPS = 6; +const int SP_CTF_DROPS = 7; +const int SP_CTF_FCKILLS = 8; +const int SP_CTF_RETURNS = 9; + +// flag constants // for most of these, there is just one question to be asked: WHYYYYY? +#define FLAG_MIN (PL_MIN_CONST + '0 0 -13') +#define FLAG_MAX (PL_MAX_CONST + '0 0 -13') + +const float FLAG_SCALE = 0.6; + +const float FLAG_THINKRATE = 0.2; +const float FLAG_TOUCHRATE = 0.5; +const float WPFE_THINKRATE = 0.5; + +const vector FLAG_DROP_OFFSET = ('0 0 32'); +const vector FLAG_CARRY_OFFSET = ('-16 0 8'); +#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) +const vector FLAG_WAYPOINT_OFFSET = ('0 0 64'); +const vector FLAG_FLOAT_OFFSET = ('0 0 32'); +const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10'); + +const vector VEHICLE_FLAG_OFFSET = ('0 0 96'); +const float VEHICLE_FLAG_SCALE = 1.0; + +// waypoint colors +#define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1') +#define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color) +#define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1') + +// sounds +#define snd_flag_taken noise +#define snd_flag_returned noise1 +#define snd_flag_capture noise2 +#define snd_flag_respawn noise3 +.string snd_flag_dropped; +.string snd_flag_touch; +.string snd_flag_pass; + +// effects +.string toucheffect; +.string passeffect; +.string capeffect; + +// list of flags on the map +entity ctf_worldflaglist; +.entity ctf_worldflagnext; +.entity ctf_staleflagnext; + +// waypoint sprites +.entity bot_basewaypoint; // flag waypointsprite +.entity wps_helpme; +.entity wps_flagbase; +.entity wps_flagcarrier; +.entity wps_flagdropped; +.entity wps_enemyflagcarrier; +.float wps_helpme_time; +bool wpforenemy_announced; +float wpforenemy_nextthink; + +// statuses +const int FLAG_BASE = 1; +const int FLAG_DROPPED = 2; +const int FLAG_CARRY = 3; +const int FLAG_PASSING = 4; + +const int DROP_NORMAL = 1; +const int DROP_THROW = 2; +const int DROP_PASS = 3; +const int DROP_RESET = 4; + +const int PICKUP_BASE = 1; +const int PICKUP_DROPPED = 2; + +const int CAPTURE_NORMAL = 1; +const int CAPTURE_DROPPED = 2; + +const int RETURN_TIMEOUT = 1; +const int RETURN_DROPPED = 2; +const int RETURN_DAMAGE = 3; +const int RETURN_SPEEDRUN = 4; +const int RETURN_NEEDKILL = 5; + +// flag properties +#define ctf_spawnorigin dropped_origin +bool ctf_stalemate; // indicates that a stalemate is active +float ctf_captimerecord; // record time for capturing the flag +.float ctf_pickuptime; +.float ctf_droptime; +.int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally) +.entity ctf_dropper; // don't allow spam of dropping the flag +.int max_flag_health; +.float next_take_time; +.bool ctf_flagdamaged; +int ctf_teams; + +// passing/throwing properties +.float pass_distance; +.entity pass_sender; +.entity pass_target; +.float throw_antispam; +.float throw_prevtime; +.int throw_count; + +// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag. +.bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture +float ctf_captureshield_min_negscore; // punish at -20 points +float ctf_captureshield_max_ratio; // punish at most 30% of each team +float ctf_captureshield_force; // push force of the shield + +// 1 flag ctf +bool ctf_oneflag; // indicates whether or not a neutral flag has been found + +// bot player logic +const int HAVOCBOT_CTF_ROLE_NONE = 0; +const int HAVOCBOT_CTF_ROLE_DEFENSE = 2; +const int HAVOCBOT_CTF_ROLE_MIDDLE = 4; +const int HAVOCBOT_CTF_ROLE_OFFENSE = 8; +const int HAVOCBOT_CTF_ROLE_CARRIER = 16; +const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32; +const int HAVOCBOT_CTF_ROLE_ESCORT = 64; + +.bool havocbot_cantfindflag; + +vector havocbot_ctf_middlepoint; +float havocbot_ctf_middlepoint_radius; + +void havocbot_role_ctf_setrole(entity bot, int role); + +// team checking +#define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b)) +#define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b)) + +// networked flag statuses +.int ctf_flagstatus; +#endif + +const int CTF_RED_FLAG_TAKEN = 1; +const int CTF_RED_FLAG_LOST = 2; +const int CTF_RED_FLAG_CARRYING = 3; +const int CTF_BLUE_FLAG_TAKEN = 4; +const int CTF_BLUE_FLAG_LOST = 8; +const int CTF_BLUE_FLAG_CARRYING = 12; +const int CTF_YELLOW_FLAG_TAKEN = 16; +const int CTF_YELLOW_FLAG_LOST = 32; +const int CTF_YELLOW_FLAG_CARRYING = 48; +const int CTF_PINK_FLAG_TAKEN = 64; +const int CTF_PINK_FLAG_LOST = 128; +const int CTF_PINK_FLAG_CARRYING = 192; +const int CTF_NEUTRAL_FLAG_TAKEN = 256; +const int CTF_NEUTRAL_FLAG_LOST = 512; +const int CTF_NEUTRAL_FLAG_CARRYING = 768; +const int CTF_FLAG_NEUTRAL = 2048; +const int CTF_SHIELDED = 4096; +#endif + +#ifdef IMPLEMENTATION + +#ifdef SVQC +#include "../../../common/vehicles/all.qh" +#include "../../teamplay.qh" +#endif + +#include "../../../lib/warpzone/common.qh" + +bool autocvar_g_ctf_allow_vehicle_carry; +bool autocvar_g_ctf_allow_vehicle_touch; +bool autocvar_g_ctf_allow_monster_touch; +bool autocvar_g_ctf_throw; +float autocvar_g_ctf_throw_angle_max; +float autocvar_g_ctf_throw_angle_min; +int autocvar_g_ctf_throw_punish_count; +float autocvar_g_ctf_throw_punish_delay; +float autocvar_g_ctf_throw_punish_time; +float autocvar_g_ctf_throw_strengthmultiplier; +float autocvar_g_ctf_throw_velocity_forward; +float autocvar_g_ctf_throw_velocity_up; +float autocvar_g_ctf_drop_velocity_up; +float autocvar_g_ctf_drop_velocity_side; +bool autocvar_g_ctf_oneflag_reverse; +bool autocvar_g_ctf_portalteleport; +bool autocvar_g_ctf_pass; +float autocvar_g_ctf_pass_arc; +float autocvar_g_ctf_pass_arc_max; +float autocvar_g_ctf_pass_directional_max; +float autocvar_g_ctf_pass_directional_min; +float autocvar_g_ctf_pass_radius; +float autocvar_g_ctf_pass_wait; +bool autocvar_g_ctf_pass_request; +float autocvar_g_ctf_pass_turnrate; +float autocvar_g_ctf_pass_timelimit; +float autocvar_g_ctf_pass_velocity; +bool autocvar_g_ctf_dynamiclights; +float autocvar_g_ctf_flag_collect_delay; +float autocvar_g_ctf_flag_damageforcescale; +bool autocvar_g_ctf_flag_dropped_waypoint; +bool autocvar_g_ctf_flag_dropped_floatinwater; +bool autocvar_g_ctf_flag_glowtrails; +int autocvar_g_ctf_flag_health; +bool autocvar_g_ctf_flag_return; +float autocvar_g_ctf_flag_return_carried_radius; +float autocvar_g_ctf_flag_return_time; +bool autocvar_g_ctf_flag_return_when_unreachable; +float autocvar_g_ctf_flag_return_damage; +float autocvar_g_ctf_flag_return_damage_delay; +float autocvar_g_ctf_flag_return_dropped; +float autocvar_g_ctf_flagcarrier_auto_helpme_damage; +float autocvar_g_ctf_flagcarrier_auto_helpme_time; +float autocvar_g_ctf_flagcarrier_selfdamagefactor; +float autocvar_g_ctf_flagcarrier_selfforcefactor; +float autocvar_g_ctf_flagcarrier_damagefactor; +float autocvar_g_ctf_flagcarrier_forcefactor; +//float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting; +bool autocvar_g_ctf_fullbrightflags; +bool autocvar_g_ctf_ignore_frags; +int autocvar_g_ctf_score_capture; +int autocvar_g_ctf_score_capture_assist; +int autocvar_g_ctf_score_kill; +int autocvar_g_ctf_score_penalty_drop; +int autocvar_g_ctf_score_penalty_returned; +int autocvar_g_ctf_score_pickup_base; +int autocvar_g_ctf_score_pickup_dropped_early; +int autocvar_g_ctf_score_pickup_dropped_late; +int autocvar_g_ctf_score_return; +float autocvar_g_ctf_shield_force; +float autocvar_g_ctf_shield_max_ratio; +int autocvar_g_ctf_shield_min_negscore; +bool autocvar_g_ctf_stalemate; +int autocvar_g_ctf_stalemate_endcondition; +float autocvar_g_ctf_stalemate_time; +bool autocvar_g_ctf_reverse; +float autocvar_g_ctf_dropped_capture_delay; +float autocvar_g_ctf_dropped_capture_radius; + +void ctf_FakeTimeLimit(entity e, float t) +{ + msg_entity = e; + WriteByte(MSG_ONE, 3); // svc_updatestat + WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT + if(t < 0) + WriteCoord(MSG_ONE, autocvar_timelimit); + else + WriteCoord(MSG_ONE, (t + 1) / 60); +} + +void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : ""))); + //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void ctf_CaptureRecord(entity flag, entity player) +{ + float cap_record = ctf_captimerecord; + float cap_time = (time - flag.ctf_pickuptime); + string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); + + // notify about shit + if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); } + else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } + else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } + else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } + + // write that shit in the database + if(!ctf_oneflag) // but not in 1-flag mode + if((!ctf_captimerecord) || (cap_time < cap_record)) + { + ctf_captimerecord = cap_time; + db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time)); + db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname); + write_recordmarker(player, (time - cap_time), cap_time); + } +} + +void ctf_FlagcarrierWaypoints(entity player) +{ + WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG); + WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2); + WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); + WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team)); +} + +void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate) +{ + float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis + float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc))); + float current_height = (initial_height * min(1, (current_distance / flag.pass_distance))); + //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n"); + + vector targpos; + if(current_height) // make sure we can actually do this arcing path + { + targpos = (to + ('0 0 1' * current_height)); + WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); + if(trace_fraction < 1) + { + //print("normal arc line failed, trying to find new pos..."); + WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag); + targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET); + WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag); + if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ } + /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */ + } + } + else { targpos = to; } + + //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y)); + + vector desired_direction = normalize(targpos - from); + if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); } + else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); } +} + +bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer) +{ + if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min) + { + // directional tracing only + float spreadlimit; + makevectors(passer_angle); + + // find the closest point on the enemy to the center of the attack + float h; // hypotenuse, which is the distance between attacker to head + float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin + + h = vlen(head_center - passer_center); + a = h * (normalize(head_center - passer_center) * v_forward); + + vector nearest_on_line = (passer_center + a * v_forward); + float distance_from_line = vlen(nearest_to_passer - nearest_on_line); + + spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1); + spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit); + + if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90)) + { return true; } + else + { return false; } + } + else { return true; } +} + + +// ======================= +// CaptureShield Functions +// ======================= + +bool ctf_CaptureShield_CheckStatus(entity p) +{ + int s, s2, s3, s4, se, se2, se3, se4, sr, ser; + entity e; + int players_worseeq, players_total; + + if(ctf_captureshield_max_ratio <= 0) + return false; + + s = PlayerScore_Add(p, SP_CTF_CAPS, 0); + s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0); + s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0); + s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0); + + sr = ((s - s2) + (s3 + s4)); + + if(sr >= -ctf_captureshield_min_negscore) + return false; + + players_total = players_worseeq = 0; + FOR_EACH_PLAYER(e) + { + if(DIFF_TEAM(e, p)) + continue; + se = PlayerScore_Add(e, SP_CTF_CAPS, 0); + se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0); + se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0); + se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0); + + ser = ((se - se2) + (se3 + se4)); + + if(ser <= sr) + ++players_worseeq; + ++players_total; + } + + // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse + // use this rule here + + if(players_worseeq >= players_total * ctf_captureshield_max_ratio) + return false; + + return true; +} + +void ctf_CaptureShield_Update(entity player, bool wanted_status) +{ + bool updated_status = ctf_CaptureShield_CheckStatus(player); + if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE)); + player.ctf_captureshielded = updated_status; + } +} + +bool ctf_CaptureShield_Customize() +{SELFPARAM(); + if(!other.ctf_captureshielded) { return false; } + if(CTF_SAMETEAM(self, other)) { return false; } + + return true; +} + +void ctf_CaptureShield_Touch() +{SELFPARAM(); + if(!other.ctf_captureshielded) { return; } + if(CTF_SAMETEAM(self, other)) { return; } + + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; + + Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force); + if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); } +} + +void ctf_CaptureShield_Spawn(entity flag) +{SELFPARAM(); + entity shield = spawn(); + + shield.enemy = self; + shield.team = self.team; + shield.touch = ctf_CaptureShield_Touch; + shield.customizeentityforclient = ctf_CaptureShield_Customize; + shield.classname = "ctf_captureshield"; + shield.effects = EF_ADDITIVE; + shield.movetype = MOVETYPE_NOCLIP; + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = 0.5; + + setorigin(shield, self.origin); + setmodel(shield, MDL_CTF_SHIELD); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); +} + + +// ==================== +// Drop/Pass/Throw Code +// ==================== + +void ctf_Handle_Drop(entity flag, entity player, int droptype) +{ + // declarations + player = (player ? player : flag.pass_sender); + + // main + flag.movetype = MOVETYPE_TOSS; + flag.takedamage = DAMAGE_YES; + flag.angles = '0 0 0'; + flag.health = flag.max_flag_health; + flag.ctf_droptime = time; + flag.ctf_dropper = player; + flag.ctf_status = FLAG_DROPPED; + + // messages and sounds + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname); + _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE); + ctf_EventLog("dropped", player.team, player); + + // scoring + PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop); + PlayerScore_Add(player, SP_CTF_DROPS, 1); + + // waypoints + if(autocvar_g_ctf_flag_dropped_waypoint) { + entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG); + wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team); + } + + if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health)) + { + WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health); + WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); + } + + player.throw_antispam = time + autocvar_g_ctf_pass_wait; + + if(droptype == DROP_PASS) + { + flag.pass_distance = 0; + flag.pass_sender = world; + flag.pass_target = world; + } +} + +void ctf_Handle_Retrieve(entity flag, entity player) +{ + entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players + entity sender = flag.pass_sender; + + // transfer flag to player + flag.owner = player; + flag.owner.flagcarried = flag; + + // reset flag + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } + flag.movetype = MOVETYPE_NONE; + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.angles = '0 0 0'; + flag.ctf_status = FLAG_CARRY; + + // messages and sounds + _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM); + ctf_EventLog("receive", flag.team, player); + + FOR_EACH_REALPLAYER(tmp_player) + { + if(tmp_player == sender) + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname); + else if(tmp_player == player) + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname); + else if(SAME_TEAM(tmp_player, sender)) + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname); + } + + // create new waypoint + ctf_FlagcarrierWaypoints(player); + + sender.throw_antispam = time + autocvar_g_ctf_pass_wait; + player.throw_antispam = sender.throw_antispam; + + flag.pass_distance = 0; + flag.pass_sender = world; + flag.pass_target = world; +} + +void ctf_Handle_Throw(entity player, entity receiver, int droptype) +{ + entity flag = player.flagcarried; + vector targ_origin, flag_velocity; + + if(!flag) { return; } + if((droptype == DROP_PASS) && !receiver) { return; } + + if(flag.speedrunning) { ctf_RespawnFlag(flag); return; } + + // reset the flag + setattachment(flag, world, ""); + setorigin(flag, player.origin + FLAG_DROP_OFFSET); + flag.owner.flagcarried = world; + flag.owner = world; + flag.solid = SOLID_TRIGGER; + flag.ctf_dropper = player; + flag.ctf_droptime = time; + + flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS + + switch(droptype) + { + case DROP_PASS: + { + // warpzone support: + // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver + // findradius has already put wzn ... wz1 into receiver's warpzone parameters! + WarpZone_RefSys_Copy(flag, receiver); + WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver + targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag + + flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis + ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false); + + // main + flag.movetype = MOVETYPE_FLY; + flag.takedamage = DAMAGE_NO; + flag.pass_sender = player; + flag.pass_target = receiver; + flag.ctf_status = FLAG_PASSING; + + // other + _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM); + WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin); + ctf_EventLog("pass", flag.team, player); + break; + } + + case DROP_THROW: + { + makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0')); + + flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1))); + flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false); + ctf_Handle_Drop(flag, player, droptype); + break; + } + + case DROP_RESET: + { + flag.velocity = '0 0 0'; // do nothing + break; + } + + default: + case DROP_NORMAL: + { + flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false); + ctf_Handle_Drop(flag, player, droptype); + break; + } + } + + // kill old waypointsprite + WaypointSprite_Ping(player.wps_flagcarrier); + WaypointSprite_Kill(player.wps_flagcarrier); + + if(player.wps_enemyflagcarrier) + WaypointSprite_Kill(player.wps_enemyflagcarrier); + + // captureshield + ctf_CaptureShield_Update(player, 0); // shield player from picking up flag +} + + +// ============== +// Event Handlers +// ============== + +void ctf_Handle_Capture(entity flag, entity toucher, int capturetype) +{ + entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher); + entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper); + entity player_team_flag = world, tmp_entity; + float old_time, new_time; + + if(!player) { return; } // without someone to give the reward to, we can't possibly cap + if(CTF_DIFFTEAM(player, flag)) { return; } + + if(ctf_oneflag) + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(SAME_TEAM(tmp_entity, player)) + { + player_team_flag = tmp_entity; + break; + } + + nades_GiveBonus(player, autocvar_g_nades_bonus_score_high ); + + player.throw_prevtime = time; + player.throw_count = 0; + + // messages and sounds + Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL)); + ctf_CaptureRecord(enemy_flag, player); + _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE); + + switch(capturetype) + { + case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break; + case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break; + default: break; + } + + // scoring + PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture); + PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1); + + old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0); + new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime); + if(!old_time || new_time < old_time) + PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time); + + // effects + Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1); + //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1); + + // other + if(capturetype == CAPTURE_NORMAL) + { + WaypointSprite_Kill(player.wps_flagcarrier); + if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); } + + if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper)) + { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); } + } + + // reset the flag + player.next_take_time = time + autocvar_g_ctf_flag_collect_delay; + ctf_RespawnFlag(enemy_flag); +} + +void ctf_Handle_Return(entity flag, entity player) +{ + // messages and sounds + if(IS_MONSTER(player)) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); + } + else if(flag.team) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname); + } + _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE); + ctf_EventLog("return", flag.team, player); + + // scoring + if(IS_PLAYER(player)) + { + PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return + PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns + + nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium); + } + + TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it + + if(flag.ctf_dropper) + { + PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag + ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag + flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time + } + + // other + if(player.flagcarried == flag) + WaypointSprite_Kill(player.wps_flagcarrier); + + // reset the flag + ctf_RespawnFlag(flag); +} + +void ctf_Handle_Pickup(entity flag, entity player, int pickuptype) +{ + // declarations + float pickup_dropped_score; // used to calculate dropped pickup score + entity tmp_entity; // temporary entity + + // attach the flag to the player + flag.owner = player; + player.flagcarried = flag; + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } + + // flag setup + flag.movetype = MOVETYPE_NONE; + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.angles = '0 0 0'; + flag.ctf_status = FLAG_CARRY; + + switch(pickuptype) + { + case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs + case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit + default: break; + } + + // messages and sounds + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname); + if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); } + if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); } + else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); } + else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); } + + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname); + + if(!flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(DIFF_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); + + if(flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(CTF_SAMETEAM(flag, tmp_entity)) + if(SAME_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname); + else + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname); + + _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE); + + // scoring + PlayerScore_Add(player, SP_CTF_PICKUPS, 1); + nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor); + switch(pickuptype) + { + case PICKUP_BASE: + { + PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base); + ctf_EventLog("steal", flag.team, player); + break; + } + + case PICKUP_DROPPED: + { + pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1); + pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5); + LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n"); + PlayerTeamScore_AddScore(player, pickup_dropped_score); + ctf_EventLog("pickup", flag.team, player); + break; + } + + default: break; + } + + // speedrunning + if(pickuptype == PICKUP_BASE) + { + flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record + if((player.speedrunning) && (ctf_captimerecord)) + ctf_FakeTimeLimit(player, time + ctf_captimerecord); + } + + // effects + Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1); + + // waypoints + if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } + ctf_FlagcarrierWaypoints(player); + WaypointSprite_Ping(player.wps_flagcarrier); +} + + +// =================== +// Main Flag Functions +// =================== + +void ctf_CheckFlagReturn(entity flag, int returntype) +{ + if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING)) + { + if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); } + + if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time)) + { + switch(returntype) + { + case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break; + case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break; + case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break; + case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break; + + default: + case RETURN_TIMEOUT: + { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; } + } + _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE); + ctf_EventLog("returned", flag.team, world); + ctf_RespawnFlag(flag); + } + } +} + +bool ctf_Stalemate_Customize() +{SELFPARAM(); + // make spectators see what the player would see + entity e, wp_owner; + e = WaypointSprite_getviewentity(other); + wp_owner = self.owner; + + // team waypoints + if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; } + if(SAME_TEAM(wp_owner, e)) { return false; } + if(!IS_PLAYER(e)) { return false; } + + return true; +} + +void ctf_CheckStalemate(void) +{ + // declarations + int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; + entity tmp_entity; + + entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs + + // build list of stale flags + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + if(autocvar_g_ctf_stalemate) + if(tmp_entity.ctf_status != FLAG_BASE) + if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag + { + tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist + ctf_staleflaglist = tmp_entity; + + switch(tmp_entity.team) + { + case NUM_TEAM_1: ++stale_red_flags; break; + case NUM_TEAM_2: ++stale_blue_flags; break; + case NUM_TEAM_3: ++stale_yellow_flags; break; + case NUM_TEAM_4: ++stale_pink_flags; break; + default: ++stale_neutral_flags; break; + } + } + } + + if(ctf_oneflag) + stale_flags = (stale_neutral_flags >= 1); + else + stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); + + if(ctf_oneflag && stale_flags == 1) + ctf_stalemate = true; + else if(stale_flags >= 2) + ctf_stalemate = true; + else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) + { ctf_stalemate = false; wpforenemy_announced = false; } + else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1) + { ctf_stalemate = false; wpforenemy_announced = false; } + + // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary + if(ctf_stalemate) + { + for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext) + { + if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) + { + entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG); + wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team); + tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize; + } + } + + if (!wpforenemy_announced) + { + FOR_EACH_REALPLAYER(tmp_entity) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); + + wpforenemy_announced = true; + } + } +} + +void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{SELFPARAM(); + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + if(autocvar_g_ctf_flag_return_damage_delay) + { + self.ctf_flagdamaged = true; + } + else + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } + return; + } + if(autocvar_g_ctf_flag_return_damage) + { + // reduce health and check if it should be returned + self.health = self.health - damage; + ctf_CheckFlagReturn(self, RETURN_DAMAGE); + return; + } +} + +void ctf_FlagThink() +{SELFPARAM(); + // declarations + entity tmp_entity; + + self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary. + + // captureshield + if(self == ctf_worldflaglist) // only for the first flag + FOR_EACH_CLIENT(tmp_entity) + ctf_CaptureShield_Update(tmp_entity, 1); // release shield only + + // sanity checks + if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished + LOG_TRACE("wtf the flag got squashed?\n"); + tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self); + if(!trace_startsolid || self.noalign) // can we resize it without getting stuck? + setsize(self, FLAG_MIN, FLAG_MAX); } + + switch(self.ctf_status) // reset flag angles in case warpzones adjust it + { + case FLAG_DROPPED: + { + self.angles = '0 0 0'; + break; + } + + default: break; + } + + // main think method + switch(self.ctf_status) + { + case FLAG_BASE: + { + if(autocvar_g_ctf_dropped_capture_radius) + { + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(tmp_entity.ctf_status == FLAG_DROPPED) + if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius) + if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay) + ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED); + } + return; + } + + case FLAG_DROPPED: + { + if(autocvar_g_ctf_flag_dropped_floatinwater) + { + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER) + { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; } + else + { self.movetype = MOVETYPE_FLY; } + } + else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; } + } + if(autocvar_g_ctf_flag_return_dropped) + { + if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1)) + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_DROPPED); + return; + } + } + if(self.ctf_flagdamaged) + { + self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + return; + } + else if(autocvar_g_ctf_flag_return_time) + { + self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE); + ctf_CheckFlagReturn(self, RETURN_TIMEOUT); + return; + } + return; + } + + case FLAG_CARRY: + { + if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_SPEEDRUN); + + setself(self.owner); + self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set + ImpulseCommands(); + setself(this); + } + if(autocvar_g_ctf_stalemate) + { + if(time >= wpforenemy_nextthink) + { + ctf_CheckStalemate(); + wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) + } + } + if(CTF_SAMETEAM(self, self.owner) && self.team) + { + if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed + ctf_Handle_Throw(self.owner, world, DROP_THROW); + else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius) + ctf_Handle_Return(self, self.owner); + } + return; + } + + case FLAG_PASSING: + { + vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5); + targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us) + WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self); + + if((self.pass_target == world) + || (self.pass_target.deadflag != DEAD_NO) + || (self.pass_target.flagcarried) + || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius) + || ((trace_fraction < 1) && (trace_ent != self.pass_target)) + || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit)) + { + // give up, pass failed + ctf_Handle_Drop(self, world, DROP_PASS); + } + else + { + // still a viable target, go for it + ctf_CalculatePassVelocity(self, targ_origin, self.origin, true); + } + return; + } + + default: // this should never happen + { + LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n"); + return; + } + } +} + +void ctf_FlagTouch() +{SELFPARAM(); + if(gameover) { return; } + if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; } + + entity toucher = other, tmp_entity; + bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0; + + // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces + if(ITEM_TOUCH_NEEDKILL()) + { + if(!autocvar_g_ctf_flag_return_damage_delay) + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } + if(!self.ctf_flagdamaged) { return; } + } + + FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; } + + // special touch behaviors + if(toucher.frozen) { return; } + else if(IS_VEHICLE(toucher)) + { + if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner) + toucher = toucher.owner; // the player is actually the vehicle owner, not other + else + return; // do nothing + } + else if(IS_MONSTER(toucher)) + { + if(!autocvar_g_ctf_allow_monster_touch) + return; // do nothing + } + else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world + { + if(time > self.wait) // if we haven't in a while, play a sound/effect + { + Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1); + _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM); + self.wait = time + FLAG_TOUCHRATE; + } + return; + } + else if(toucher.deadflag != DEAD_NO) { return; } + + switch(self.ctf_status) + { + case FLAG_BASE: + { + if(ctf_oneflag) + { + if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) + ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base + else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag + } + else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) + ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base + else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag + break; + } + + case FLAG_DROPPED: + { + if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team + ctf_Handle_Return(self, toucher); // toucher just returned his own flag + else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay))) + ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag + break; + } + + case FLAG_CARRY: + { + LOG_TRACE("Someone touched a flag even though it was being carried?\n"); + break; + } + + case FLAG_PASSING: + { + if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender)) + { + if(DIFF_TEAM(toucher, self.pass_sender)) + ctf_Handle_Return(self, toucher); + else + ctf_Handle_Retrieve(self, toucher); + } + break; + } + } +} + +.float last_respawn; +void ctf_RespawnFlag(entity flag) +{ + // check for flag respawn being called twice in a row + if(flag.last_respawn > time - 0.5) + { backtrace("flag respawn called twice quickly! please notify Samual about this..."); } + + flag.last_respawn = time; + + // reset the player (if there is one) + if((flag.owner) && (flag.owner.flagcarried == flag)) + { + WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier); + WaypointSprite_Kill(flag.wps_flagcarrier); + + flag.owner.flagcarried = world; + + if(flag.speedrunning) + ctf_FakeTimeLimit(flag.owner, -1); + } + + if((flag.owner) && (flag.owner.vehicle)) + flag.scale = FLAG_SCALE; + + if(flag.ctf_status == FLAG_DROPPED) + { WaypointSprite_Kill(flag.wps_flagdropped); } + + // reset the flag + setattachment(flag, world, ""); + setorigin(flag, flag.ctf_spawnorigin); + + flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS); + flag.takedamage = DAMAGE_NO; + flag.health = flag.max_flag_health; + flag.solid = SOLID_TRIGGER; + flag.velocity = '0 0 0'; + flag.angles = flag.mangle; + flag.flags = FL_ITEM | FL_NOTARGET; + + flag.ctf_status = FLAG_BASE; + flag.owner = world; + flag.pass_distance = 0; + flag.pass_sender = world; + flag.pass_target = world; + flag.ctf_dropper = world; + flag.ctf_pickuptime = 0; + flag.ctf_droptime = 0; + flag.ctf_flagdamaged = 0; + + ctf_CheckStalemate(); +} + +void ctf_Reset() +{SELFPARAM(); + if(self.owner) + if(IS_PLAYER(self.owner)) + ctf_Handle_Throw(self.owner, world, DROP_RESET); + + ctf_RespawnFlag(self); +} + +void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup() +{SELFPARAM(); + // bot waypoints + waypoint_spawnforitem_force(self, self.origin); + self.nearestwaypointtimeout = 0; // activate waypointing again + self.bot_basewaypoint = self.nearestwaypoint; + + // waypointsprites + entity basename; + switch (self.team) + { + case NUM_TEAM_1: basename = WP_FlagBaseRed; break; + case NUM_TEAM_2: basename = WP_FlagBaseBlue; break; + case NUM_TEAM_3: basename = WP_FlagBaseYellow; break; + case NUM_TEAM_4: basename = WP_FlagBasePink; break; + default: basename = WP_FlagBaseNeutral; break; + } + + entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG); + wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'); + WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1')); + + // captureshield setup + ctf_CaptureShield_Spawn(self); +} + +void set_flag_string(entity flag, .string field, string value, string teamname) +{ + if(flag.(field) == "") + flag.(field) = strzone(sprintf(value,teamname)); +} + +void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc +{SELFPARAM(); + // declarations + setself(flag); // for later usage with droptofloor() + + // main setup + flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist + ctf_worldflaglist = flag; + + setattachment(flag, world, ""); + + flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber))); + flag.team = teamnumber; + flag.classname = "item_flag_team"; + flag.target = "###item###"; // wut? + flag.flags = FL_ITEM | FL_NOTARGET; + flag.solid = SOLID_TRIGGER; + flag.takedamage = DAMAGE_NO; + flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale; + flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100); + flag.health = flag.max_flag_health; + flag.event_damage = ctf_FlagDamage; + flag.pushable = true; + flag.teleportable = TELEPORT_NORMAL; + flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable; + flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable; + flag.velocity = '0 0 0'; + flag.mangle = flag.angles; + flag.reset = ctf_Reset; + flag.touch = ctf_FlagTouch; + flag.think = ctf_FlagThink; + flag.nextthink = time + FLAG_THINKRATE; + flag.ctf_status = FLAG_BASE; + + string teamname = Static_Team_ColorName_Lower(teamnumber); + // appearence + if(!flag.scale) { flag.scale = FLAG_SCALE; } + if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); } + if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); } + set_flag_string(flag, toucheffect, "%sflag_touch", teamname); + set_flag_string(flag, passeffect, "%s_pass", teamname); + set_flag_string(flag, capeffect, "%s_cap", teamname); + + // sounds + flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber)); + flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber)); + flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber)); + flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber)); + if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match. + precache_sound(flag.snd_flag_respawn); + if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound + precache_sound(flag.snd_flag_touch); + if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here + precache_sound(flag.snd_flag_pass); + + // precache + precache_model(flag.model); + + // appearence + _setmodel(flag, flag.model); // precision set below + setsize(flag, FLAG_MIN, FLAG_MAX); + setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET)); + + if(autocvar_g_ctf_flag_glowtrails) + { + switch(teamnumber) + { + case NUM_TEAM_1: flag.glow_color = 251; break; + case NUM_TEAM_2: flag.glow_color = 210; break; + case NUM_TEAM_3: flag.glow_color = 110; break; + case NUM_TEAM_4: flag.glow_color = 145; break; + default: flag.glow_color = 254; break; + } + flag.glow_size = 25; + flag.glow_trail = 1; + } + + flag.effects |= EF_LOWPRECISION; + if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; } + if(autocvar_g_ctf_dynamiclights) + { + switch(teamnumber) + { + case NUM_TEAM_1: flag.effects |= EF_RED; break; + case NUM_TEAM_2: flag.effects |= EF_BLUE; break; + case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; + case NUM_TEAM_4: flag.effects |= EF_RED; break; + default: flag.effects |= EF_DIMLIGHT; break; + } + } + + // flag placement + if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location + { + flag.dropped_origin = flag.origin; + flag.noalign = true; + flag.movetype = MOVETYPE_NONE; + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + flag.noalign = false; + setself(flag); + droptofloor(); + flag.movetype = MOVETYPE_TOSS; + } + + InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION); +} + + +// ================ +// Bot player logic +// ================ + +// NOTE: LEGACY CODE, needs to be re-written! + +void havocbot_calculate_middlepoint() +{ + entity f; + vector s = '0 0 0'; + vector fo = '0 0 0'; + float n = 0; + + f = ctf_worldflaglist; + while (f) + { + fo = f.origin; + s = s + fo; + f = f.ctf_worldflagnext; + } + if(!n) + return; + havocbot_ctf_middlepoint = s * (1.0 / n); + havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint); +} + + +entity havocbot_ctf_find_flag(entity bot) +{ + entity f; + f = ctf_worldflaglist; + while (f) + { + if (CTF_SAMETEAM(bot, f)) + return f; + f = f.ctf_worldflagnext; + } + return world; +} + +entity havocbot_ctf_find_enemy_flag(entity bot) +{ + entity f; + f = ctf_worldflaglist; + while (f) + { + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(bot, f)) + { + if(f.team) + { + if(bot.flagcarried) + return f; + } + else if(!bot.flagcarried) + return f; + } + } + else if (CTF_DIFFTEAM(bot, f)) + return f; + f = f.ctf_worldflagnext; + } + return world; +} + +int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) +{ + if (!teamplay) + return 0; + + int c = 0; + entity head; + + FOR_EACH_PLAYER(head) + { + if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot) + continue; + + if(vlen(head.origin - org) < tc_radius) + ++c; + } + + return c; +} + +void havocbot_goalrating_ctf_ourflag(float ratingscale) +{SELFPARAM(); + entity head; + head = ctf_worldflaglist; + while (head) + { + if (CTF_SAMETEAM(self, head)) + break; + head = head.ctf_worldflagnext; + } + if (head) + navigation_routerating(head, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_ourbase(float ratingscale) +{SELFPARAM(); + entity head; + head = ctf_worldflaglist; + while (head) + { + if (CTF_SAMETEAM(self, head)) + break; + head = head.ctf_worldflagnext; + } + if (!head) + return; + + navigation_routerating(head.bot_basewaypoint, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_enemyflag(float ratingscale) +{SELFPARAM(); + entity head; + head = ctf_worldflaglist; + while (head) + { + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(self, head)) + { + if(head.team) + { + if(self.flagcarried) + break; + } + else if(!self.flagcarried) + break; + } + } + else if(CTF_DIFFTEAM(self, head)) + break; + head = head.ctf_worldflagnext; + } + if (head) + navigation_routerating(head, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_enemybase(float ratingscale) +{SELFPARAM(); + if (!bot_waypoints_for_items) + { + havocbot_goalrating_ctf_enemyflag(ratingscale); + return; + } + + entity head; + + head = havocbot_ctf_find_enemy_flag(self); + + if (!head) + return; + + navigation_routerating(head.bot_basewaypoint, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_ourstolenflag(float ratingscale) +{SELFPARAM(); + entity mf; + + mf = havocbot_ctf_find_flag(self); + + if(mf.ctf_status == FLAG_BASE) + return; + + if(mf.tag_entity) + navigation_routerating(mf.tag_entity, ratingscale, 10000); +} + +void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius) +{ + entity head; + head = ctf_worldflaglist; + while (head) + { + // flag is out in the field + if(head.ctf_status != FLAG_BASE) + if(head.tag_entity==world) // dropped + { + if(df_radius) + { + if(vlen(org-head.origin) 0) + navigation_routerating(head, t * ratingscale, 500); + } + head = head.chain; + } +} + +void havocbot_ctf_reset_role(entity bot) +{ + float cdefense, cmiddle, coffense; + entity mf, ef, head; + float c; + + if(bot.deadflag != DEAD_NO) + return; + + if(vlen(havocbot_ctf_middlepoint)==0) + havocbot_calculate_middlepoint(); + + // Check ctf flags + if (bot.flagcarried) + { + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + mf = havocbot_ctf_find_flag(bot); + ef = havocbot_ctf_find_enemy_flag(bot); + + // Retrieve stolen flag + if(mf.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + + // If enemy flag is taken go to the middle to intercept pursuers + if(ef.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE); + return; + } + + // if there is only me on the team switch to offense + c = 0; + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, bot)) + ++c; + + if(c==1) + { + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE); + return; + } + + // Evaluate best position to take + // Count mates on middle position + cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5); + + // Count mates on defense position + cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5); + + // Count mates on offense position + coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius); + + if(cdefense<=coffense) + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE); + else if(coffense<=cmiddle) + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE); + else + havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE); +} + +void havocbot_role_ctf_carrier() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.flagcarried == world) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + if(ctf_oneflag) + havocbot_goalrating_ctf_enemybase(50000); + else + havocbot_goalrating_ctf_ourbase(50000); + + if(self.health<100) + havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000); + + navigation_goalrating_end(); + + if (self.navigation_hasgoals) + self.havocbot_cantfindflag = time + 10; + else if (time > self.havocbot_cantfindflag) + { + // Can't navigate to my own base, suicide! + // TODO: drop it and wander around + Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0'); + return; + } + } +} + +void havocbot_role_ctf_escort() +{SELFPARAM(); + entity mf, ef; + + if(self.deadflag != DEAD_NO) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.flagcarried) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // If enemy flag is back on the base switch to previous role + ef = havocbot_ctf_find_enemy_flag(self); + if(ef.ctf_status==FLAG_BASE) + { + self.havocbot_role = self.havocbot_previous_role; + self.havocbot_role_timeout = 0; + return; + } + + // If the flag carrier reached the base switch to defense + mf = havocbot_ctf_find_flag(self); + if(mf.ctf_status!=FLAG_BASE) + if(vlen(ef.origin - mf.dropped_origin) < 300) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE); + return; + } + + // Set the role timeout if necessary + if (!self.havocbot_role_timeout) + { + self.havocbot_role_timeout = time + random() * 30 + 60; + } + + // If nothing happened just switch to previous role + if (time > self.havocbot_role_timeout) + { + self.havocbot_role = self.havocbot_previous_role; + self.havocbot_role_timeout = 0; + return; + } + + // Chase the flag carrier + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + havocbot_goalrating_ctf_enemyflag(30000); + havocbot_goalrating_ctf_ourstolenflag(40000); + havocbot_goalrating_items(10000, self.origin, 10000); + navigation_goalrating_end(); + } +} + +void havocbot_role_ctf_offense() +{SELFPARAM(); + entity mf, ef; + vector pos; + + if(self.deadflag != DEAD_NO) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.flagcarried) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // Check flags + mf = havocbot_ctf_find_flag(self); + ef = havocbot_ctf_find_enemy_flag(self); + + // Own flag stolen + if(mf.ctf_status!=FLAG_BASE) + { + if(mf.tag_entity) + pos = mf.tag_entity.origin; + else + pos = mf.origin; + + // Try to get it if closer than the enemy base + if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos)) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + } + + // Escort flag carrier + if(ef.ctf_status!=FLAG_BASE) + { + if(ef.tag_entity) + pos = ef.tag_entity.origin; + else + pos = ef.origin; + + if(vlen(pos-mf.dropped_origin)>700) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT); + return; + } + } + + // About to fail, switch to middlefield + if(self.health<50) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE); + return; + } + + // Set the role timeout if necessary + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + 120; + + if (time > self.havocbot_role_timeout) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + havocbot_goalrating_ctf_ourstolenflag(50000); + havocbot_goalrating_ctf_enemybase(20000); + havocbot_goalrating_items(5000, self.origin, 1000); + havocbot_goalrating_items(1000, self.origin, 10000); + navigation_goalrating_end(); + } +} + +// Retriever (temporary role): +void havocbot_role_ctf_retriever() +{SELFPARAM(); + entity mf; + + if(self.deadflag != DEAD_NO) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.flagcarried) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // If flag is back on the base switch to previous role + mf = havocbot_ctf_find_flag(self); + if(mf.ctf_status==FLAG_BASE) + { + havocbot_ctf_reset_role(self); + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + 20; + + if (time > self.havocbot_role_timeout) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.bot_strategytime < time) + { + float rt_radius; + rt_radius = 10000; + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + havocbot_goalrating_ctf_ourstolenflag(50000); + havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius); + havocbot_goalrating_ctf_enemybase(30000); + havocbot_goalrating_items(500, self.origin, rt_radius); + navigation_goalrating_end(); + } +} + +void havocbot_role_ctf_middle() +{SELFPARAM(); + entity mf; + + if(self.deadflag != DEAD_NO) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.flagcarried) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + mf = havocbot_ctf_find_flag(self); + if(mf.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + 10; + + if (time > self.havocbot_role_timeout) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.bot_strategytime < time) + { + vector org; + + org = havocbot_ctf_middlepoint; + org.z = self.origin.z; + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + havocbot_goalrating_ctf_ourstolenflag(50000); + havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000); + havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5); + havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5); + havocbot_goalrating_items(2500, self.origin, 10000); + havocbot_goalrating_ctf_enemybase(2500); + navigation_goalrating_end(); + } +} + +void havocbot_role_ctf_defense() +{SELFPARAM(); + entity mf; + + if(self.deadflag != DEAD_NO) + { + havocbot_ctf_reset_role(self); + return; + } + + if (self.flagcarried) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER); + return; + } + + // If own flag was captured + mf = havocbot_ctf_find_flag(self); + if(mf.ctf_status!=FLAG_BASE) + { + havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER); + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + 30; + + if (time > self.havocbot_role_timeout) + { + havocbot_ctf_reset_role(self); + return; + } + if (self.bot_strategytime < time) + { + float mp_radius; + vector org; + + org = mf.dropped_origin; + mp_radius = havocbot_ctf_middlepoint_radius; + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + // if enemies are closer to our base, go there + entity head, closestplayer = world; + float distance, bestdistance = 10000; + FOR_EACH_PLAYER(head) + { + if(head.deadflag!=DEAD_NO) + continue; + + distance = vlen(org - head.origin); + if(distance1000) + if(checkpvs(self.origin,closestplayer)||random()<0.5) + havocbot_goalrating_ctf_ourbase(30000); + + havocbot_goalrating_ctf_ourstolenflag(20000); + havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius); + havocbot_goalrating_enemyplayers(15000, org, mp_radius); + havocbot_goalrating_items(10000, org, mp_radius); + havocbot_goalrating_items(5000, self.origin, 10000); + navigation_goalrating_end(); + } +} + +void havocbot_role_ctf_setrole(entity bot, int role) +{ + LOG_TRACE(strcat(bot.netname," switched to ")); + switch(role) + { + case HAVOCBOT_CTF_ROLE_CARRIER: + LOG_TRACE("carrier"); + bot.havocbot_role = havocbot_role_ctf_carrier; + bot.havocbot_role_timeout = 0; + bot.havocbot_cantfindflag = time + 10; + bot.bot_strategytime = 0; + break; + case HAVOCBOT_CTF_ROLE_DEFENSE: + LOG_TRACE("defense"); + bot.havocbot_role = havocbot_role_ctf_defense; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_CTF_ROLE_MIDDLE: + LOG_TRACE("middle"); + bot.havocbot_role = havocbot_role_ctf_middle; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_CTF_ROLE_OFFENSE: + LOG_TRACE("offense"); + bot.havocbot_role = havocbot_role_ctf_offense; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_CTF_ROLE_RETRIEVER: + LOG_TRACE("retriever"); + bot.havocbot_previous_role = bot.havocbot_role; + bot.havocbot_role = havocbot_role_ctf_retriever; + bot.havocbot_role_timeout = time + 10; + bot.bot_strategytime = 0; + break; + case HAVOCBOT_CTF_ROLE_ESCORT: + LOG_TRACE("escort"); + bot.havocbot_previous_role = bot.havocbot_role; + bot.havocbot_role = havocbot_role_ctf_escort; + bot.havocbot_role_timeout = time + 30; + bot.bot_strategytime = 0; + break; + } + LOG_TRACE("\n"); +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink) +{SELFPARAM(); + entity flag; + int t = 0, t2 = 0, t3 = 0; + + // initially clear items so they can be set as necessary later. + self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST + | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST + | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST + | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST + | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST + | CTF_FLAG_NEUTRAL | CTF_SHIELDED); + + // scan through all the flags and notify the client about them + for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } + if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } + if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } + if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } + if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; } + + switch(flag.ctf_status) + { + case FLAG_PASSING: + case FLAG_CARRY: + { + if((flag.owner == self) || (flag.pass_sender == self)) + self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag + else + self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag + break; + } + case FLAG_DROPPED: + { + self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map + break; + } + } + } + + // item for stopping players from capturing the flag too often + if(self.ctf_captureshielded) + self.ctf_flagstatus |= CTF_SHIELDED; + + // update the health of the flag carrier waypointsprite + if(self.wps_flagcarrier) + WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)); + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc +{ + if(frag_attacker.flagcarried) // if the attacker is a flagcarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor; + frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor; + } + else // damage done to everyone else + { + frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor; + frag_force *= autocvar_g_ctf_flagcarrier_forcefactor; + } + } + else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier + { + if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id))) + if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time) + { + frag_target.wps_helpme_time = time; + WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); + } + // todo: add notification for when flag carrier needs help? + } + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, PlayerDies) +{ + if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried)) + { + PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill); + PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1); + } + + if(frag_target.flagcarried) + { + entity tmp_entity = frag_target.flagcarried; + ctf_Handle_Throw(frag_target, world, DROP_NORMAL); + tmp_entity.ctf_dropper = world; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill) +{ + frag_score = 0; + return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true +} + +void ctf_RemovePlayer(entity player) +{ + if(player.flagcarried) + { ctf_Handle_Throw(player, world, DROP_NORMAL); } + + for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + if(flag.pass_sender == player) { flag.pass_sender = world; } + if(flag.pass_target == player) { flag.pass_target = world; } + if(flag.ctf_dropper == player) { flag.ctf_dropper = world; } + } +} + +MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver) +{SELFPARAM(); + ctf_RemovePlayer(self); + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect) +{SELFPARAM(); + ctf_RemovePlayer(self); + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, PortalTeleport) +{SELFPARAM(); + if(self.flagcarried) + if(!autocvar_g_ctf_portalteleport) + { ctf_Handle_Throw(self, world, DROP_NORMAL); } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE || gameover) { return false; } + + entity player = self; + + if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch)) + { + // pass the flag to a team mate + if(autocvar_g_ctf_pass) + { + entity head, closest_target = world; + head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true); + + while(head) // find the closest acceptable target to pass to + { + if(IS_PLAYER(head) && head.deadflag == DEAD_NO) + if(head != player && SAME_TEAM(head, player)) + if(!head.speedrunning && !head.vehicle) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head)); + vector passer_center = CENTER_OR_VIEWOFS(player); + + if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest)) + { + if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) + { + if(IS_BOT_CLIENT(head)) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); + ctf_Handle_Throw(head, player, DROP_PASS); + } + else + { + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname); + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname); + } + player.throw_antispam = time + autocvar_g_ctf_pass_wait; + return true; + } + else if(player.flagcarried) + { + if(closest_target) + { + vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target)); + if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center)) + { closest_target = head; } + } + else { closest_target = head; } + } + } + } + head = head.chain; + } + + if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; } + } + + // throw the flag in front of you + if(autocvar_g_ctf_throw && player.flagcarried) + { + if(player.throw_count == -1) + { + if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) + { + player.throw_prevtime = time; + player.throw_count = 1; + ctf_Handle_Throw(player, world, DROP_THROW); + return true; + } + else + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)); + return false; + } + } + else + { + if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; } + else { player.throw_count += 1; } + if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; } + + player.throw_prevtime = time; + ctf_Handle_Throw(player, world, DROP_THROW); + return true; + } + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, HelpMePing) +{SELFPARAM(); + if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification + { + self.wps_helpme_time = time; + WaypointSprite_HelpMePing(self.wps_flagcarrier); + } + else // create a normal help me waypointsprite + { + WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME); + WaypointSprite_Ping(self.wps_helpme); + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ctf, VehicleEnter) +{ + if(vh_player.flagcarried) + { + vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver + + if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch) + { + ctf_Handle_Throw(vh_player, world, DROP_NORMAL); + } + else + { + setattachment(vh_player.flagcarried, vh_vehicle, ""); + setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET); + vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE; + //vh_player.flagcarried.angles = '0 0 0'; + } + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, VehicleExit) +{ + if(vh_player.flagcarried) + { + setattachment(vh_player.flagcarried, vh_player, ""); + setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET); + vh_player.flagcarried.scale = FLAG_SCALE; + vh_player.flagcarried.angles = '0 0 0'; + vh_player.flagcarried.nodrawtoclient = world; + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun) +{SELFPARAM(); + if(self.flagcarried) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL)); + ctf_RespawnFlag(self.flagcarried); + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, MatchEnd) +{ + entity flag; // temporary entity for the search method + + for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + switch(flag.ctf_status) + { + case FLAG_DROPPED: + case FLAG_PASSING: + { + // lock the flag, game is over + flag.movetype = MOVETYPE_NONE; + flag.takedamage = DAMAGE_NO; + flag.solid = SOLID_NOT; + flag.nextthink = false; // stop thinking + + //dprint("stopping the ", flag.netname, " from moving.\n"); + break; + } + + default: + case FLAG_BASE: + case FLAG_CARRY: + { + // do nothing for these flags + break; + } + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole) +{SELFPARAM(); + havocbot_ctf_reset_role(self); + return true; +} + +MUTATOR_HOOKFUNCTION(ctf, GetTeamCount) +{ + //ret_float = ctf_teams; + ret_string = "ctf_team"; + return true; +} + +MUTATOR_HOOKFUNCTION(ctf, SpectateCopy) +{SELFPARAM(); + self.ctf_flagstatus = other.ctf_flagstatus; + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, GetRecords) +{ + for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) + { + if (MapInfo_Get_ByID(i)) + { + float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time"))); + + if(!r) + continue; + + // TODO: uid2name + string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname")); + ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n"); + } + } + + return false; +} + +bool superspec_Spectate(entity _player); // TODO +void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO +MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand) +{ + if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; } + + if(cmd_name == "followfc") + { + if(!g_ctf) + return true; + + entity _player; + int _team = 0; + bool found = false; + + if(cmd_argc == 2) + { + switch(argv(1)) + { + case "red": _team = NUM_TEAM_1; break; + case "blue": _team = NUM_TEAM_2; break; + case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break; + case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break; + } + } + + FOR_EACH_PLAYER(_player) + { + if(_player.flagcarried && (_player.team == _team || _team == 0)) + { + found = true; + if(_team == 0 && IS_SPEC(self) && self.enemy == _player) + continue; // already spectating a fc, try to find the other fc + return superspec_Spectate(_player); + } + } + + if(!found) + superspec_msg("", "", self, "No active flag carrier\n", 1); + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems) +{ + if(frag_target.flagcarried) + ctf_Handle_Throw(frag_target, world, DROP_THROW); + + return false; +} + + +// ========== +// Spawnfuncs +// ========== + +/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team one (Red). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red and blue as skins 0 and 1... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team1) +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_1, self); +} + +/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team two (Blue). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red and blue as skins 0 and 1... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team2) +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_2, self); +} + +/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team three (Yellow). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team3) +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_3, self); +} + +/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag for team four (Pink). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_team4) +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(NUM_TEAM_4, self); +} + +/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag (Neutral). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"noise" sound played when flag is picked up... +"noise1" sound played when flag is returned by a teammate... +"noise2" sound played when flag is captured... +"noise3" sound played when flag is lost in the field and respawns itself... +"noise4" sound played when flag is dropped by a player... +"noise5" sound played when flag touches the ground... */ +spawnfunc(item_flag_neutral) +{ + if(!g_ctf) { remove(self); return; } + if(!cvar("g_ctf_oneflag")) { remove(self); return; } + + ctf_FlagSetup(0, self); +} + +/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32) +Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map. +Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. +Keys: +"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... +"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ +spawnfunc(ctf_team) +{ + if(!g_ctf) { remove(self); return; } + + self.classname = "ctf_team"; + self.team = self.cnt + 1; +} + +// compatibility for quake maps +spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); } +spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); } +spawnfunc(info_player_team1); +spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); } +spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); } +spawnfunc(info_player_team2); +spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); } +spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); } + +void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); } +void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); } + + +// ============== +// Initialization +// ============== + +// scoreboard setup +void ctf_ScoreRules(int teams) +{ + CheckAllowedTeams(world); + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); + ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME); + ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0); + ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0); + ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0); + ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER); + ScoreRules_basics_end(); +} + +// code from here on is just to support maps that don't have flag and team entities +void ctf_SpawnTeam (string teamname, int teamcolor) +{ + entity this = new(ctf_team); + this.netname = teamname; + this.cnt = teamcolor; + this.spawnfunc_checked = true; + WITH(entity, self, this, spawnfunc_ctf_team(this)); +} + +void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. +{ + ctf_teams = 2; + + entity tmp_entity; + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + { + if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } + if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } + if(tmp_entity.team == 0) { ctf_oneflag = true; } + } + + ctf_teams = bound(2, ctf_teams, 4); + + // if no teams are found, spawn defaults + if(find(world, classname, "ctf_team") == world) + { + LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n"); + ctf_SpawnTeam("Red", NUM_TEAM_1 - 1); + ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1); + if(ctf_teams >= 3) + ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1); + if(ctf_teams >= 4) + ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1); + } + + ctf_ScoreRules(ctf_teams); +} + +void ctf_Initialize() +{ + ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"))); + + ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore; + ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio; + ctf_captureshield_force = autocvar_g_ctf_shield_force; + + addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus); + + InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE); +} + +REGISTER_MUTATOR(ctf, g_ctf) +{ + ActivateTeamplay(); + SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1); + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + ctf_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back ctf_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_cts.qc b/qcsrc/server/mutators/mutator/gamemode_cts.qc new file mode 100644 index 000000000..1414fe065 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_cts.qc @@ -0,0 +1,445 @@ +#ifndef GAMEMODE_CTS_H +#define GAMEMODE_CTS_H + +// scores +const float ST_CTS_LAPS = 1; +const float SP_CTS_LAPS = 4; +const float SP_CTS_TIME = 5; +const float SP_CTS_FASTEST = 6; +#endif + +#ifdef IMPLEMENTATION + +#include "../../race.qh" + +float autocvar_g_cts_finish_kill_delay; +bool autocvar_g_cts_selfdamage; + +// legacy bot roles +.float race_checkpoint; +void havocbot_role_cts() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + entity e; + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; ) + { + if(e.cnt == self.race_checkpoint) + { + navigation_routerating(e, 1000000, 5000); + } + else if(self.race_checkpoint == -1) + { + navigation_routerating(e, 1000000, 5000); + } + } + + navigation_goalrating_end(); + } +} + +void cts_ScoreRules() +{ + ScoreRules_basics(0, 0, 0, false); + if(g_race_qualifying) + { + ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); + } + else + { + ScoreInfo_SetLabel_PlayerScore(SP_CTS_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_CTS_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + ScoreInfo_SetLabel_PlayerScore(SP_CTS_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); + } + ScoreRules_basics_end(); +} + +void cts_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":cts:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +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 +{ + e.killindicator = spawn(); + e.killindicator.owner = e; + e.killindicator.think = KillIndicator_Think; + e.killindicator.nextthink = time + (e.lip) * 0.05; + e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay); + e.killindicator.health = 1; // this is used to indicate that it should be silent + e.lip = 0; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerPhysics) +{SELFPARAM(); + self.race_movetime_frac += PHYS_INPUT_TIMELENGTH; + float f = floor(self.race_movetime_frac); + self.race_movetime_frac -= f; + self.race_movetime_count += f; + self.race_movetime = self.race_movetime_frac + self.race_movetime_count; + +#ifdef SVQC + if(IS_PLAYER(self)) + { + if (self.race_penalty) + if (time > self.race_penalty) + self.race_penalty = 0; + if(self.race_penalty) + { + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.disableclientprediction = 2; + } + } +#endif + + // force kbd movement for fairness + float wishspeed; + vector wishvel; + + // if record times matter + // ensure nothing EVIL is being done (i.e. div0_evade) + // this hinders joystick users though + // but it still gives SOME analog control + wishvel.x = fabs(self.movement.x); + wishvel.y = fabs(self.movement.y); + if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) + { + wishvel.z = 0; + wishspeed = vlen(wishvel); + if(wishvel.x >= 2 * wishvel.y) + { + // pure X motion + if(self.movement.x > 0) + self.movement_x = wishspeed; + else + self.movement_x = -wishspeed; + self.movement_y = 0; + } + else if(wishvel.y >= 2 * wishvel.x) + { + // pure Y motion + self.movement_x = 0; + if(self.movement.y > 0) + self.movement_y = wishspeed; + else + self.movement_y = -wishspeed; + } + else + { + // diagonal + if(self.movement.x > 0) + self.movement_x = M_SQRT1_2 * wishspeed; + else + self.movement_x = -M_SQRT1_2 * wishspeed; + if(self.movement.y > 0) + self.movement_y = M_SQRT1_2 * wishspeed; + else + self.movement_y = -M_SQRT1_2 * wishspeed; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, reset_map_global) +{ + float s; + + Score_NicePrint(world); + + race_ClearRecords(); + PlayerScore_Sort(race_place, 0, 1, 0); + + entity e; + FOR_EACH_CLIENT(e) + { + if(e.race_place) + { + s = PlayerScore_Add(e, SP_RACE_FASTEST, 0); + if(!s) + e.race_place = 0; + } + cts_EventLog(ftos(e.race_place), e); + } + + if(g_race_qualifying == 2) + { + g_race_qualifying = 0; + independent_players = 0; + cvar_set("fraglimit", ftos(race_fraglimit)); + cvar_set("leadlimit", ftos(race_leadlimit)); + cvar_set("timelimit", ftos(race_timelimit)); + cts_ScoreRules(); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerPreThink) +{SELFPARAM(); + if(IS_SPEC(self) || IS_OBSERVER(self)) + if(g_race_qualifying) + if(msg_entity.enemy.race_laptime) + race_SendNextCheckpoint(msg_entity.enemy, 1); + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, ClientConnect) +{SELFPARAM(); + race_PreparePlayer(); + self.race_checkpoint = -1; + + if(IS_REAL_CLIENT(self)) + { + string rr = CTS_RECORD; + + msg_entity = self; + race_send_recordtime(MSG_ONE); + race_send_speedaward(MSG_ONE); + + speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); + speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); + race_send_speedaward_alltimebest(MSG_ONE); + + float i; + for (i = 1; i <= RANKINGS_CNT; ++i) + { + race_SendRankings(i, 0, 0, MSG_ONE); + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver) +{SELFPARAM(); + if(PlayerScore_Add(self, SP_RACE_FASTEST, 0)) + self.frags = FRAGS_LMS_LOSER; + else + self.frags = FRAGS_SPECTATOR; + + race_PreparePlayer(); + self.race_checkpoint = -1; + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerSpawn) +{SELFPARAM(); + if(spawn_spot.target == "") + // Emergency: this wasn't a real spawnpoint. Can this ever happen? + race_PreparePlayer(); + + // if we need to respawn, do it right + self.race_respawn_checkpoint = self.race_checkpoint; + self.race_respawn_spotref = spawn_spot; + + self.race_place = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, PutClientInServer) +{SELFPARAM(); + if(IS_PLAYER(self)) + if(!gameover) + { + if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn + race_PreparePlayer(); + else // respawn + race_RetractPlayer(); + + race_AbandonRaceCheck(self); + } + return false; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerDies) +{SELFPARAM(); + self.respawn_flags |= RESPAWN_FORCE; + race_AbandonRaceCheck(self); + return false; +} + +MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole) +{SELFPARAM(); + self.havocbot_role = havocbot_role_cts; + return true; +} + +MUTATOR_HOOKFUNCTION(cts, GetPressedKeys) +{SELFPARAM(); + if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1) + { + if (!self.stored_netname) + self.stored_netname = strzone(uid2name(self.crypto_idfp)); + if(self.stored_netname != self.netname) + { + db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname); + strunzone(self.stored_netname); + self.stored_netname = strzone(self.netname); + } + } + + if (!IS_OBSERVER(self)) + { + if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed) + { + speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1'); + speedaward_holder = self.netname; + speedaward_uid = self.crypto_idfp; + speedaward_lastupdate = time; + } + if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) + { + string rr = CTS_RECORD; + race_send_speedaward(MSG_ALL); + speedaward_lastsent = speedaward_speed; + if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") + { + speedaward_alltimebest = speedaward_speed; + speedaward_alltimebest_holder = speedaward_holder; + speedaward_alltimebest_uid = speedaward_uid; + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); + race_send_speedaward_alltimebest(MSG_ALL); + } + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon) +{ + // no weapon dropping in CTS + return true; +} + +MUTATOR_HOOKFUNCTION(cts, FilterItem) +{SELFPARAM(); + if(self.classname == "droppedweapon") + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, PlayerDamage_Calculate) +{ + if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id) + if(!autocvar_g_cts_selfdamage) + frag_damage = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear) +{ + return true; // in CTS, you don't lose score by observing +} + +MUTATOR_HOOKFUNCTION(cts, SetModname) +{ + g_cloaked = 1; // always enable cloak in CTS + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, GetRecords) +{ + for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) + { + if(MapInfo_Get_ByID(i)) + { + float r = race_readTime(MapInfo_Map_bspname, 1); + + if(!r) + continue; + + string h = race_readName(MapInfo_Map_bspname, 1); + ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, ClientKill) +{ + ret_float = 0; + + if(self.killindicator && self.killindicator.health == 1) // self.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill + { + remove(self.killindicator); + self.killindicator = world; + + ClientKill_Now(); // allow instant kill in this case + return; + } + +} + +MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint) +{ + if(autocvar_g_cts_finish_kill_delay) + CTS_ClientKill(self); + + return false; +} + +MUTATOR_HOOKFUNCTION(cts, FixClientCvars) +{ + stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n"); + return false; +} + +MUTATOR_HOOKFUNCTION(cts, WantWeapon) +{ + ret_float = (want_weaponinfo.weapon == WEP_SHOTGUN.m_id); + want_mutatorblocked = true; + return true; +} + +void cts_Initialize() +{ + cts_ScoreRules(); +} + +REGISTER_MUTATOR(cts, g_cts) +{ + g_race_qualifying = 1; + independent_players = 1; + SetLimits(0, 0, 0, -1); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + cts_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back cts_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc new file mode 100644 index 000000000..ac7ea21b2 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_deathmatch.qc @@ -0,0 +1,31 @@ +#ifdef IMPLEMENTATION +MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} + +REGISTER_MUTATOR(dm, IS_GAMETYPE(DEATHMATCH)) +{ + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back dm_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + print("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_domination.qc b/qcsrc/server/mutators/mutator/gamemode_domination.qc new file mode 100644 index 000000000..75d0d7dc3 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_domination.qc @@ -0,0 +1,724 @@ +#ifndef GAMEMODE_DOMINATION_H +#define GAMEMODE_DOMINATION_H + +// score rule declarations +const float ST_DOM_TICKS = 1; +const float SP_DOM_TICKS = 4; +const float SP_DOM_TAKES = 5; +const float ST_DOM_CAPS = 1; +const float SP_DOM_CAPS = 4; + +// pps: points per second +.float dom_total_pps; +.float dom_pps_red; +.float dom_pps_blue; +.float dom_pps_yellow; +.float dom_pps_pink; +float total_pps; +float pps_red; +float pps_blue; +float pps_yellow; +float pps_pink; + +// capture declarations +.float enemy_playerid; +.entity sprite; +.float captime; + +// misc globals +float domination_roundbased; +float domination_teams; +#endif + +#ifdef IMPLEMENTATION + +#include "../../teamplay.qh" + +bool g_domination; + +int autocvar_g_domination_default_teams; +bool autocvar_g_domination_disable_frags; +int autocvar_g_domination_point_amt; +bool autocvar_g_domination_point_fullbright; +int autocvar_g_domination_point_leadlimit; +bool autocvar_g_domination_roundbased; +int autocvar_g_domination_roundbased_point_limit; +float autocvar_g_domination_round_timelimit; +float autocvar_g_domination_warmup; +#define autocvar_g_domination_point_limit cvar("g_domination_point_limit") +float autocvar_g_domination_point_rate; +int autocvar_g_domination_teams_override; + +void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":dom:", mode, ":", ftos(team_before), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void set_dom_state(entity e) +{ + e.dom_total_pps = total_pps; + e.dom_pps_red = pps_red; + e.dom_pps_blue = pps_blue; + if(domination_teams >= 3) + e.dom_pps_yellow = pps_yellow; + if(domination_teams >= 4) + e.dom_pps_pink = pps_pink; +} + +void dompoint_captured () +{SELFPARAM(); + entity head; + float old_delay, old_team, real_team; + + // now that the delay has expired, switch to the latest team to lay claim to this point + head = self.owner; + + real_team = self.cnt; + self.cnt = -1; + + dom_EventLog("taken", self.team, self.dmg_inflictor); + self.dmg_inflictor = world; + + self.goalentity = head; + self.model = head.mdl; + self.modelindex = head.dmg; + self.skin = head.skin; + + float points, wait_time; + if (autocvar_g_domination_point_amt) + points = autocvar_g_domination_point_amt; + else + points = self.frags; + if (autocvar_g_domination_point_rate) + wait_time = autocvar_g_domination_point_rate; + else + wait_time = self.wait; + + if(domination_roundbased) + bprint(sprintf("^3%s^3%s\n", head.netname, self.message)); + else + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, self.message, points, wait_time); + + if(self.enemy.playerid == self.enemy_playerid) + PlayerScore_Add(self.enemy, SP_DOM_TAKES, 1); + else + self.enemy = world; + + if (head.noise != "") + if(self.enemy) + _sound(self.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); + else + _sound(self, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM); + if (head.noise1 != "") + play2all(head.noise1); + + self.delay = time + wait_time; + + // do trigger work + old_delay = self.delay; + old_team = self.team; + self.team = real_team; + self.delay = 0; + activator = self; + SUB_UseTargets (); + self.delay = old_delay; + self.team = old_team; + + entity msg = WP_DomNeut; + switch(self.team) + { + case NUM_TEAM_1: msg = WP_DomRed; break; + case NUM_TEAM_2: msg = WP_DomBlue; break; + case NUM_TEAM_3: msg = WP_DomYellow; break; + case NUM_TEAM_4: msg = WP_DomPink; break; + } + + WaypointSprite_UpdateSprites(self.sprite, msg, WP_Null, WP_Null); + + total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; + for(head = world; (head = find(head, classname, "dom_controlpoint")) != world; ) + { + if (autocvar_g_domination_point_amt) + points = autocvar_g_domination_point_amt; + else + points = head.frags; + if (autocvar_g_domination_point_rate) + wait_time = autocvar_g_domination_point_rate; + else + wait_time = head.wait; + switch(head.goalentity.team) + { + case NUM_TEAM_1: + pps_red += points/wait_time; + break; + case NUM_TEAM_2: + pps_blue += points/wait_time; + break; + case NUM_TEAM_3: + pps_yellow += points/wait_time; + break; + case NUM_TEAM_4: + pps_pink += points/wait_time; + break; + } + total_pps += points/wait_time; + } + + WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, colormapPaletteColor(self.goalentity.team - 1, 0)); + WaypointSprite_Ping(self.sprite); + + self.captime = time; + + FOR_EACH_REALCLIENT(head) + set_dom_state(head); +} + +void AnimateDomPoint() +{SELFPARAM(); + if(self.pain_finished > time) + return; + self.pain_finished = time + self.t_width; + if(self.nextthink > self.pain_finished) + self.nextthink = self.pain_finished; + + self.frame = self.frame + 1; + if(self.frame > self.t_length) + self.frame = 0; +} + +void dompointthink() +{SELFPARAM(); + float fragamt; + + self.nextthink = time + 0.1; + + //self.frame = self.frame + 1; + //if(self.frame > 119) + // self.frame = 0; + AnimateDomPoint(); + + // give points + + if (gameover || self.delay > time || time < game_starttime) // game has ended, don't keep giving points + return; + + if(autocvar_g_domination_point_rate) + self.delay = time + autocvar_g_domination_point_rate; + else + self.delay = time + self.wait; + + // give credit to the team + // NOTE: this defaults to 0 + if (!domination_roundbased) + if (self.goalentity.netname != "") + { + if(autocvar_g_domination_point_amt) + fragamt = autocvar_g_domination_point_amt; + else + fragamt = self.frags; + TeamScore_AddToTeam(self.goalentity.team, ST_SCORE, fragamt); + TeamScore_AddToTeam(self.goalentity.team, ST_DOM_TICKS, fragamt); + + // give credit to the individual player, if he is still there + if (self.enemy.playerid == self.enemy_playerid) + { + PlayerScore_Add(self.enemy, SP_SCORE, fragamt); + PlayerScore_Add(self.enemy, SP_DOM_TICKS, fragamt); + } + else + self.enemy = world; + } +} + +void dompointtouch() +{SELFPARAM(); + entity head; + if (!IS_PLAYER(other)) + return; + if (other.health < 1) + return; + + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) + return; + + if(time < self.captime + 0.3) + return; + + // only valid teams can claim it + head = find(world, classname, "dom_team"); + while (head && head.team != other.team) + head = find(head, classname, "dom_team"); + if (!head || head.netname == "" || head == self.goalentity) + return; + + // delay capture + + self.team = self.goalentity.team; // this stores the PREVIOUS team! + + self.cnt = other.team; + self.owner = head; // team to switch to after the delay + self.dmg_inflictor = other; + + // self.state = 1; + // self.delay = time + cvar("g_domination_point_capturetime"); + //self.nextthink = time + cvar("g_domination_point_capturetime"); + //self.think = dompoint_captured; + + // go to neutral team in the mean time + head = find(world, classname, "dom_team"); + while (head && head.netname != "") + head = find(head, classname, "dom_team"); + if(head == world) + return; + + WaypointSprite_UpdateSprites(self.sprite, WP_DomNeut, WP_Null, WP_Null); + WaypointSprite_UpdateTeamRadar(self.sprite, RADARICON_DOMPOINT, '0 1 1'); + WaypointSprite_Ping(self.sprite); + + self.goalentity = head; + self.model = head.mdl; + self.modelindex = head.dmg; + self.skin = head.skin; + + self.enemy = other; // individual player scoring + self.enemy_playerid = other.playerid; + dompoint_captured(); +} + +void dom_controlpoint_setup() +{SELFPARAM(); + entity head; + // find the spawnfunc_dom_team representing unclaimed points + head = find(world, classname, "dom_team"); + while(head && head.netname != "") + head = find(head, classname, "dom_team"); + if (!head) + objerror("no spawnfunc_dom_team with netname \"\" found\n"); + + // copy important properties from spawnfunc_dom_team entity + self.goalentity = head; + _setmodel(self, head.mdl); // precision already set + self.skin = head.skin; + + self.cnt = -1; + + if(self.message == "") + self.message = " has captured a control point"; + + if(self.frags <= 0) + self.frags = 1; + if(self.wait <= 0) + self.wait = 5; + + float points, waittime; + if (autocvar_g_domination_point_amt) + points = autocvar_g_domination_point_amt; + else + points = self.frags; + if (autocvar_g_domination_point_rate) + waittime = autocvar_g_domination_point_rate; + else + waittime = self.wait; + + total_pps += points/waittime; + + if(!self.t_width) + self.t_width = 0.02; // frame animation rate + if(!self.t_length) + self.t_length = 239; // maximum frame + + self.think = dompointthink; + self.nextthink = time; + self.touch = dompointtouch; + self.solid = SOLID_TRIGGER; + self.flags = FL_ITEM; + setsize(self, '-32 -32 -32', '32 32 32'); + setorigin(self, self.origin + '0 0 20'); + droptofloor(); + + waypoint_spawnforitem(self); + WaypointSprite_SpawnFixed(WP_DomNeut, self.origin + '0 0 32', self, sprite, RADARICON_DOMPOINT); +} + +float total_controlpoints; +void Domination_count_controlpoints() +{ + entity e; + total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0; + for(e = world; (e = find(e, classname, "dom_controlpoint")) != world; ) + { + ++total_controlpoints; + redowned += (e.goalentity.team == NUM_TEAM_1); + blueowned += (e.goalentity.team == NUM_TEAM_2); + yellowowned += (e.goalentity.team == NUM_TEAM_3); + pinkowned += (e.goalentity.team == NUM_TEAM_4); + } +} + +float Domination_GetWinnerTeam() +{ + float winner_team = 0; + if(redowned == total_controlpoints) + winner_team = NUM_TEAM_1; + if(blueowned == total_controlpoints) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowowned == total_controlpoints) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkowned == total_controlpoints) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no control points left? +} + +#define DOM_OWNED_CONTROLPOINTS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) +#define DOM_OWNED_CONTROLPOINTS_OK() (DOM_OWNED_CONTROLPOINTS() < total_controlpoints) +float Domination_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); + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + return 1; + } + + Domination_count_controlpoints(); + + float winner_team = Domination_GetWinnerTeam(); + + if(winner_team == -1) + return 0; + + 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_DOM_CAPS, +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); + } + + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + + return 1; +} + +float Domination_CheckPlayers() +{ + return 1; +} + +void Domination_RoundStart() +{ + entity e; + FOR_EACH_PLAYER(e) + e.player_blocked = 0; +} + +//go to best items, or control points you don't own +void havocbot_role_dom() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + havocbot_goalrating_controlpoints(10000, self.origin, 15000); + havocbot_goalrating_items(8000, self.origin, 8000); + //havocbot_goalrating_enemyplayers(3000, self.origin, 2000); + //havocbot_goalrating_waypoints(1, self.origin, 1000); + navigation_goalrating_end(); + } +} + +MUTATOR_HOOKFUNCTION(dom, GetTeamCount) +{ + // fallback? + ret_float = domination_teams; + ret_string = "dom_team"; + + entity head = find(world, classname, ret_string); + while(head) + { + if(head.netname != "") + { + switch(head.team) + { + case NUM_TEAM_1: c1 = 0; break; + case NUM_TEAM_2: c2 = 0; break; + case NUM_TEAM_3: c3 = 0; break; + case NUM_TEAM_4: c4 = 0; break; + } + } + + head = find(head, classname, ret_string); + } + + ret_string = string_null; + + return true; +} + +MUTATOR_HOOKFUNCTION(dom, reset_map_players) +{SELFPARAM(); + total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0; + entity e; + FOR_EACH_PLAYER(e) + { + setself(e); + PutClientInServer(); + self.player_blocked = 1; + if(IS_REAL_CLIENT(self)) + set_dom_state(self); + } + return 1; +} + +MUTATOR_HOOKFUNCTION(dom, PlayerSpawn) +{SELFPARAM(); + if(domination_roundbased) + if(!round_handler_IsRoundStarted()) + self.player_blocked = 1; + else + self.player_blocked = 0; + return false; +} + +MUTATOR_HOOKFUNCTION(dom, ClientConnect) +{SELFPARAM(); + set_dom_state(self); + return false; +} + +MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole) +{SELFPARAM(); + self.havocbot_role = havocbot_role_dom; + return true; +} + +/*QUAKED spawnfunc_dom_controlpoint (0 .5 .8) (-16 -16 -24) (16 16 32) +Control point for Domination gameplay. +*/ +spawnfunc(dom_controlpoint) +{ + if(!g_domination) + { + remove(self); + return; + } + self.think = dom_controlpoint_setup; + self.nextthink = time + 0.1; + self.reset = dom_controlpoint_setup; + + if(!self.scale) + self.scale = 0.6; + + self.effects = self.effects | EF_LOWPRECISION; + if (autocvar_g_domination_point_fullbright) + self.effects |= EF_FULLBRIGHT; +} + +/*QUAKED spawnfunc_dom_team (0 .5 .8) (-32 -32 -24) (32 32 32) +Team declaration for Domination gameplay, this allows you to decide what team +names and control point models are used in your map. + +Note: If you use spawnfunc_dom_team entities you must define at least 3 and only two +can have netname set! The nameless team owns all control points at start. + +Keys: +"netname" + Name of the team (for example Red Team, Blue Team, Green Team, Yellow Team, Life, Death, etc) +"cnt" + Scoreboard color of the team (for example 4 is red and 13 is blue) +"model" + Model to use for control points owned by this team (for example + "progs/b_g_key.mdl" is a gold keycard, and "progs/b_s_key.mdl" is a silver + keycard) +"skin" + Skin of the model to use (for team skins on a single model) +"noise" + Sound to play when this team captures a point. + (this is a localized sound, like a small alarm or other effect) +"noise1" + Narrator speech to play when this team captures a point. + (this is a global sound, like "Red team has captured a control point") +*/ + +spawnfunc(dom_team) +{ + if(!g_domination || autocvar_g_domination_teams_override >= 2) + { + remove(self); + return; + } + precache_model(self.model); + if (self.noise != "") + precache_sound(self.noise); + if (self.noise1 != "") + precache_sound(self.noise1); + self.classname = "dom_team"; + _setmodel(self, self.model); // precision not needed + self.mdl = self.model; + self.dmg = self.modelindex; + self.model = ""; + self.modelindex = 0; + // this would have to be changed if used in quakeworld + if(self.cnt) + self.team = self.cnt + 1; // WHY are these different anyway? +} + +// scoreboard setup +void ScoreRules_dom(float teams) +{ + if(domination_roundbased) + { + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true); + ScoreInfo_SetLabel_TeamScore (ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); + ScoreRules_basics_end(); + } + else + { + float sp_domticks, sp_score; + sp_score = sp_domticks = 0; + if(autocvar_g_domination_disable_frags) + sp_domticks = SFL_SORT_PRIO_PRIMARY; + else + sp_score = SFL_SORT_PRIO_PRIMARY; + ScoreRules_basics(teams, sp_score, sp_score, true); + ScoreInfo_SetLabel_TeamScore (ST_DOM_TICKS, "ticks", sp_domticks); + ScoreInfo_SetLabel_PlayerScore(SP_DOM_TICKS, "ticks", sp_domticks); + ScoreInfo_SetLabel_PlayerScore(SP_DOM_TAKES, "takes", 0); + ScoreRules_basics_end(); + } +} + +// code from here on is just to support maps that don't have control point and team entities +void dom_spawnteam (string teamname, float teamcolor, string pointmodel, float pointskin, string capsound, string capnarration, string capmessage) +{SELFPARAM(); + setself(spawn()); + self.classname = "dom_team"; + self.netname = teamname; + self.cnt = teamcolor; + self.model = pointmodel; + self.skin = pointskin; + self.noise = capsound; + self.noise1 = capnarration; + self.message = capmessage; + + // this code is identical to spawnfunc_dom_team + _setmodel(self, self.model); // precision not needed + self.mdl = self.model; + self.dmg = self.modelindex; + self.model = ""; + self.modelindex = 0; + // this would have to be changed if used in quakeworld + self.team = self.cnt + 1; + + //eprint(self); + setself(this); +} + +void _spawnfunc_dom_controlpoint() { SELFPARAM(); spawnfunc_dom_controlpoint(self); } +void dom_spawnpoint(vector org) +{SELFPARAM(); + setself(spawn()); + self.classname = "dom_controlpoint"; + self.think = _spawnfunc_dom_controlpoint; + self.nextthink = time; + setorigin(self, org); + spawnfunc_dom_controlpoint(this); + setself(this); +} + +// spawn some default teams if the map is not set up for domination +void dom_spawnteams(float teams) +{ + dom_spawnteam("Red", NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND(DOM_CLAIM), "", "Red team has captured a control point"); + dom_spawnteam("Blue", NUM_TEAM_2-1, "models/domination/dom_blue.md3", 0, SND(DOM_CLAIM), "", "Blue team has captured a control point"); + if(teams >= 3) + dom_spawnteam("Yellow", NUM_TEAM_3-1, "models/domination/dom_yellow.md3", 0, SND(DOM_CLAIM), "", "Yellow team has captured a control point"); + if(teams >= 4) + dom_spawnteam("Pink", NUM_TEAM_4-1, "models/domination/dom_pink.md3", 0, SND(DOM_CLAIM), "", "Pink team has captured a control point"); + dom_spawnteam("", 0, "models/domination/dom_unclaimed.md3", 0, "", "", ""); +} + +void dom_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. +{ + // if no teams are found, spawn defaults + if(find(world, classname, "dom_team") == world || autocvar_g_domination_teams_override >= 2) + { + LOG_INFO("No ""dom_team"" entities found on this map, creating them anyway.\n"); + domination_teams = bound(2, ((autocvar_g_domination_teams_override < 2) ? autocvar_g_domination_default_teams : autocvar_g_domination_teams_override), 4); + dom_spawnteams(domination_teams); + } + + CheckAllowedTeams(world); + domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2); + + addstat(STAT_DOM_TOTAL_PPS, AS_FLOAT, dom_total_pps); + addstat(STAT_DOM_PPS_RED, AS_FLOAT, dom_pps_red); + addstat(STAT_DOM_PPS_BLUE, AS_FLOAT, dom_pps_blue); + if(domination_teams >= 3) addstat(STAT_DOM_PPS_YELLOW, AS_FLOAT, dom_pps_yellow); + if(domination_teams >= 4) addstat(STAT_DOM_PPS_PINK, AS_FLOAT, dom_pps_pink); + + domination_roundbased = autocvar_g_domination_roundbased; + + ScoreRules_dom(domination_teams); + + if(domination_roundbased) + { + round_handler_Spawn(Domination_CheckPlayers, Domination_CheckWinner, Domination_RoundStart); + round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit); + } +} + +void dom_Initialize() +{ + g_domination = true; + InitializeEntity(world, dom_DelayedInit, INITPRIO_GAMETYPE); +} + + +REGISTER_MUTATOR(dom, IS_GAMETYPE(DOMINATION)) +{ + int fraglimit_override = autocvar_g_domination_point_limit; + if(autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit) + fraglimit_override = autocvar_g_domination_roundbased_point_limit; + + ActivateTeamplay(); + SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, -1, -1); + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + dom_Initialize(); + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_freezetag.qc b/qcsrc/server/mutators/mutator/gamemode_freezetag.qc new file mode 100644 index 000000000..912df1575 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_freezetag.qc @@ -0,0 +1,654 @@ +#ifndef GAMEMODE_FREEZETAG_H +#define GAMEMODE_FREEZETAG_H + +.float freezetag_frozen_time; +.float freezetag_frozen_timeout; +const float ICE_MAX_ALPHA = 1; +const float ICE_MIN_ALPHA = 0.1; +float freezetag_teams; + +.float reviving; // temp var +#endif + +#ifdef IMPLEMENTATION + +float autocvar_g_freezetag_frozen_maxtime; +bool autocvar_g_freezetag_revive_nade; +float autocvar_g_freezetag_revive_nade_health; +int autocvar_g_freezetag_point_leadlimit; +int 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; +int autocvar_g_freezetag_teams; +int autocvar_g_freezetag_teams_override; +bool autocvar_g_freezetag_team_spawns; +float autocvar_g_freezetag_warmup; + +const float SP_FREEZETAG_REVIVALS = 4; +void freezetag_ScoreRules(float teams) +{ + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); // SFL_SORT_PRIO_PRIMARY + ScoreInfo_SetLabel_PlayerScore(SP_FREEZETAG_REVIVALS, "revivals", 0); + ScoreRules_basics_end(); +} + +void freezetag_count_alive_players() +{ + entity e; + total_players = redalive = bluealive = yellowalive = pinkalive = 0; + FOR_EACH_PLAYER(e) + { + switch(e.team) + { + case NUM_TEAM_1: ++total_players; if(e.health >= 1 && e.frozen != 1) ++redalive; break; + case NUM_TEAM_2: ++total_players; if(e.health >= 1 && e.frozen != 1) ++bluealive; break; + case NUM_TEAM_3: ++total_players; if(e.health >= 1 && e.frozen != 1) ++yellowalive; break; + case NUM_TEAM_4: ++total_players; if(e.health >= 1 && e.frozen != 1) ++pinkalive; break; + } + } + FOR_EACH_REALCLIENT(e) + { + e.redalive_stat = redalive; + e.bluealive_stat = bluealive; + e.yellowalive_stat = yellowalive; + e.pinkalive_stat = pinkalive; + } + + eliminatedPlayers.SendFlags |= 1; +} +#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0)) +#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == freezetag_teams) + +float freezetag_CheckTeams() +{ + static float prev_missing_teams_mask; + if(FREEZETAG_ALIVE_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 1; + } + if(total_players == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + return 0; + } + float missing_teams_mask = (!redalive) + (!bluealive) * 2; + if(freezetag_teams >= 3) missing_teams_mask += (!yellowalive) * 4; + if(freezetag_teams >= 4) missing_teams_mask += (!pinkalive) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + return 0; +} + +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 +} + +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; + nades_Clear(e); + } + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; + } + + if(FREEZETAG_ALIVE_TEAMS() > 1) + return 0; + + float winner_team; + winner_team = freezetag_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); + } + + FOR_EACH_PLAYER(e) + { + e.freezetag_frozen_timeout = 0; + nades_Clear(e); + } + round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit); + return 1; +} + +entity freezetag_LastPlayerForTeam() +{SELFPARAM(); + entity pl, last_pl = world; + FOR_EACH_PLAYER(pl) + { + if(pl.health >= 1) + if(!pl.frozen) + if(pl != self) + if(pl.team == self.team) + if(!last_pl) + last_pl = pl; + else + return world; + } + return last_pl; +} + +void freezetag_LastPlayerForTeam_Notify() +{ + if(round_handler_IsActive()) + if(round_handler_IsRoundStarted()) + { + entity pl = freezetag_LastPlayerForTeam(); + if(pl) + Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE); + } +} + +void freezetag_Add_Score(entity attacker) +{SELFPARAM(); + 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) +{SELFPARAM(); + if(self.frozen) + return; + + if(autocvar_g_freezetag_frozen_maxtime > 0) + self.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime; + + Freeze(self, 0, 1, true); + + freezetag_count_alive_players(); + + freezetag_Add_Score(attacker); +} + +void freezetag_Unfreeze(entity attacker) +{SELFPARAM(); + self.freezetag_frozen_time = 0; + self.freezetag_frozen_timeout = 0; + + Unfreeze(self); +} + +float freezetag_isEliminated(entity e) +{ + if(IS_PLAYER(e) && (e.frozen == 1 || e.deadflag != DEAD_NO)) + return true; + return false; +} + + +// ================ +// Bot player logic +// ================ + +void() havocbot_role_ft_freeing; +void() havocbot_role_ft_offense; + +void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius) +{SELFPARAM(); + entity head; + float distance; + + FOR_EACH_PLAYER(head) + { + if ((head != self) && (head.team == self.team)) + { + if (head.frozen == 1) + { + distance = vlen(head.origin - org); + if (distance > sradius) + continue; + navigation_routerating(head, ratingscale, 2000); + } + else + { + // If teamate is not frozen still seek them out as fight better + // in a group. + navigation_routerating(head, ratingscale/3, 2000); + } + } + } +} + +void havocbot_role_ft_offense() +{SELFPARAM(); + entity head; + float unfrozen; + + if(self.deadflag != DEAD_NO) + return; + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + + // Count how many players on team are unfrozen. + unfrozen = 0; + FOR_EACH_PLAYER(head) + { + if ((head.team == self.team) && (head.frozen != 1)) + unfrozen++; + } + + // If only one left on team or if role has timed out then start trying to free players. + if (((unfrozen == 0) && (!self.frozen)) || (time > self.havocbot_role_timeout)) + { + LOG_TRACE("changing role to freeing\n"); + self.havocbot_role = havocbot_role_ft_freeing; + self.havocbot_role_timeout = 0; + return; + } + + if (time > self.bot_strategytime) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + havocbot_goalrating_items(10000, self.origin, 10000); + havocbot_goalrating_enemyplayers(20000, self.origin, 10000); + havocbot_goalrating_freeplayers(9000, self.origin, 10000); + //havocbot_goalrating_waypoints(1, self.origin, 1000); + navigation_goalrating_end(); + } +} + +void havocbot_role_ft_freeing() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + + if (time > self.havocbot_role_timeout) + { + LOG_TRACE("changing role to offense\n"); + self.havocbot_role = havocbot_role_ft_offense; + self.havocbot_role_timeout = 0; + return; + } + + if (time > self.bot_strategytime) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + havocbot_goalrating_items(8000, self.origin, 10000); + havocbot_goalrating_enemyplayers(10000, self.origin, 10000); + havocbot_goalrating_freeplayers(20000, self.origin, 10000); + //havocbot_goalrating_waypoints(1, self.origin, 1000); + navigation_goalrating_end(); + } +} + + +// ============== +// Hook Functions +// ============== + +void ft_RemovePlayer() +{SELFPARAM(); + self.health = 0; // neccessary to update correctly alive stats + if(!self.frozen) + freezetag_LastPlayerForTeam_Notify(); + freezetag_Unfreeze(world); + freezetag_count_alive_players(); +} + +MUTATOR_HOOKFUNCTION(ft, ClientDisconnect) +{SELFPARAM(); + ft_RemovePlayer(); + return 1; +} + +MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver) +{SELFPARAM(); + ft_RemovePlayer(); + return false; +} + +MUTATOR_HOOKFUNCTION(ft, PlayerDies) +{SELFPARAM(); + if(round_handler_IsActive()) + if(round_handler_CountdownRunning()) + { + if(self.frozen) + freezetag_Unfreeze(world); + freezetag_count_alive_players(); + return 1; // let the player die so that he can respawn whenever he wants + } + + // 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.m_id || frag_deathtype == DEATH_AUTOTEAMCHANGE.m_id) + { + // let the player die, he will be automatically frozen when he respawns + if(self.frozen != 1) + { + freezetag_Add_Score(frag_attacker); + freezetag_count_alive_players(); + freezetag_LastPlayerForTeam_Notify(); + } + else + freezetag_Unfreeze(world); // remove ice + self.health = 0; // Unfreeze resets health + self.freezetag_frozen_timeout = -2; // freeze on respawn + return 1; + } + + if(self.frozen) + return 1; + + freezetag_Freeze(frag_attacker); + freezetag_LastPlayerForTeam_Notify(); + + if(frag_attacker == frag_target || frag_attacker == world) + { + if(IS_PLAYER(frag_target)) + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname); + } + else + { + if(IS_PLAYER(frag_target)) + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_FROZEN, frag_attacker.netname); + if(IS_PLAYER(frag_attacker)) + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_FREEZETAG_FREEZE, frag_target.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname); + } + + return 1; +} + +MUTATOR_HOOKFUNCTION(ft, PlayerSpawn) +{SELFPARAM(); + if(self.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players + return 1; // do nothing, round is starting right now + + if(self.freezetag_frozen_timeout == -2) // player was dead + { + freezetag_Freeze(world); + return 1; + } + + 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); + } + + return 1; +} + +MUTATOR_HOOKFUNCTION(ft, reset_map_players) +{SELFPARAM(); + entity e; + FOR_EACH_PLAYER(e) + { + e.killcount = 0; + e.freezetag_frozen_timeout = -1; + setself(e); + PutClientInServer(); + e.freezetag_frozen_timeout = 0; + } + freezetag_count_alive_players(); + return 1; +} + +MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST) +{ + frag_score = 0; // no frags counted in Freeze Tag + return 1; +} + +MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST) +{SELFPARAM(); + float n; + + if(gameover) + return 1; + + if(self.frozen == 1) + { + // 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; + //if(self.frozen) + //if(self.freezetag_frozen_timeout > 0 && time < self.freezetag_frozen_timeout) + //self.iceblock.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 + { + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOR_EACH_PLAYER(other) + if(self != other) + if(other.frozen == 0) + if(other.deadflag == DEAD_NO) + if(SAME_TEAM(other, self)) + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + { + if(!o) + o = other; + if(self.frozen == 1) + other.reviving = true; + ++n; + } + } + + if(n && self.frozen == 1) // OK, there is at least one teammate reviving us + { + self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); + + if(self.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(other.reviving) + { + PlayerScore_Add(other, SP_FREEZETAG_REVIVALS, +1); + PlayerScore_Add(other, SP_SCORE, +1); + + nades_GiveBonus(other,autocvar_g_nades_bonus_score_low); + } + } + + 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_REVIVED, self.netname, o.netname); + } + + FOR_EACH_PLAYER(other) + { + if(other.reviving) + { + other.revive_progress = self.revive_progress; + other.reviving = false; + } + } + } + else if(!n && self.frozen == 1) // only if no teammate is nearby will we reset + { + self.revive_progress = bound(0, self.revive_progress - frametime * autocvar_g_freezetag_revive_clearspeed, 1); + self.health = max(1, self.revive_progress * ((warmup_stage) ? warmup_start_health : start_health)); + } + else if(!n && !self.frozen) + { + self.revive_progress = 0; // thawing nobody + } + + return 1; +} + +MUTATOR_HOOKFUNCTION(ft, SetStartItems) +{ + start_items &= ~IT_UNLIMITED_AMMO; + //start_health = warmup_start_health = cvar("g_lms_start_health"); + //start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); + start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); + start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); + start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); + start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); + start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); + + return 0; +} + +MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole) +{SELFPARAM(); + if (!self.deadflag) + { + if (random() < 0.5) + self.havocbot_role = havocbot_role_ft_freeing; + else + self.havocbot_role = havocbot_role_ft_offense; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ft, GetTeamCount, CBC_ORDER_EXCLUSIVE) +{ + ret_float = freezetag_teams; + return false; +} + +MUTATOR_HOOKFUNCTION(ft, SetWeaponArena) +{ + // most weapons arena + if(ret_string == "0" || ret_string == "") + ret_string = "most"; + return false; +} + +void freezetag_Initialize() +{ + freezetag_teams = autocvar_g_freezetag_teams_override; + if(freezetag_teams < 2) + freezetag_teams = autocvar_g_freezetag_teams; + freezetag_teams = bound(2, freezetag_teams, 4); + freezetag_ScoreRules(freezetag_teams); + + 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); + + EliminatedPlayers_Init(freezetag_isEliminated); +} + +REGISTER_MUTATOR(ft, g_freezetag) +{ + ActivateTeamplay(); + SetLimits(autocvar_g_freezetag_point_limit, autocvar_g_freezetag_point_leadlimit, -1, -1); + + if(autocvar_g_freezetag_team_spawns) + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + freezetag_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back freezetag_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_invasion.qc b/qcsrc/server/mutators/mutator/gamemode_invasion.qc new file mode 100644 index 000000000..4dc5ed63a --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_invasion.qc @@ -0,0 +1,525 @@ +#ifndef GAMEMODE_INVASION_H +#define GAMEMODE_INVASION_H + +float inv_numspawned; +float inv_maxspawned; +float inv_roundcnt; +float inv_maxrounds; +float inv_numkilled; +float inv_lastcheck; +float inv_maxcurrent; + +float invasion_teams; +float inv_monsters_perteam[17]; + +float inv_monsterskill; + +const float ST_INV_KILLS = 1; +#endif + +#ifdef IMPLEMENTATION + +#include "../../../common/monsters/spawn.qh" +#include "../../../common/monsters/sv_monsters.qh" + +#include "../../teamplay.qh" + +bool g_invasion; + +float autocvar_g_invasion_round_timelimit; +int autocvar_g_invasion_teams; +bool autocvar_g_invasion_team_spawns; +float autocvar_g_invasion_spawnpoint_spawn_delay; +#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit") +float autocvar_g_invasion_warmup; +int autocvar_g_invasion_monster_count; +bool autocvar_g_invasion_zombies_only; +float autocvar_g_invasion_spawn_delay; + +spawnfunc(invasion_spawnpoint) +{ + if(!g_invasion) { remove(self); return; } + + self.classname = "invasion_spawnpoint"; + + if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already + if(self.monsterid) { + Monster mon = get_monsterinfo(self.monsterid); + mon.mr_precache(mon); + } +} + +float invasion_PickMonster(float supermonster_count) +{ + if(autocvar_g_invasion_zombies_only) + return MON_ZOMBIE.monsterid; + + float i; + entity mon; + + RandomSelection_Init(); + + for(i = MON_FIRST; i <= MON_LAST; ++i) + { + mon = get_monsterinfo(i); + if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1)) + continue; // flying/swimming monsters not yet supported + + RandomSelection_Add(world, i, string_null, 1, 1); + } + + return RandomSelection_chosen_float; +} + +entity invasion_PickSpawn() +{ + entity e; + + RandomSelection_Init(); + + for(e = world;(e = find(e, classname, "invasion_spawnpoint")); ) + { + RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating + e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay; + } + + return RandomSelection_chosen_ent; +} + +void invasion_SpawnChosenMonster(float mon) +{ + entity spawn_point, monster; + + spawn_point = invasion_PickSpawn(); + + if(spawn_point == world) + { + LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n"); + entity e = spawn(); + setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs); + + if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) + monster = spawnmonster("", mon, world, world, e.origin, false, false, 2); + else return; + + e.think = SUB_Remove; + e.nextthink = time + 0.1; + } + else + monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, false, false, 2); + + if(spawn_point) monster.target2 = spawn_point.target2; + monster.spawnshieldtime = time; + if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range; + + if(teamplay) + if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0) + monster.team = spawn_point.team; + else + { + RandomSelection_Init(); + if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1); + if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1); + if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); } + if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); } + + monster.team = RandomSelection_chosen_float; + } + + if(teamplay) + { + monster_setupcolors(monster); + + if(monster.sprite) + { + WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0')); + + monster.sprite.team = 0; + monster.sprite.SendFlags |= 1; + } + } + + monster.monster_attack = false; // it's the player's job to kill all the monsters + + if(inv_roundcnt >= inv_maxrounds) + monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses +} + +void invasion_SpawnMonsters(float supermonster_count) +{ + float chosen_monster = invasion_PickMonster(supermonster_count); + + invasion_SpawnChosenMonster(chosen_monster); +} + +float Invasion_CheckWinner() +{ + entity head; + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + FOR_EACH_MONSTER(head) + Monster_Remove(head); + + 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_invasion_warmup, autocvar_g_invasion_round_timelimit); + return 1; + } + + float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0; + + FOR_EACH_MONSTER(head) if(head.health > 0) + { + if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) + ++supermonster_count; + ++total_alive_monsters; + + if(teamplay) + switch(head.team) + { + case NUM_TEAM_1: ++red_alive; break; + case NUM_TEAM_2: ++blue_alive; break; + case NUM_TEAM_3: ++yellow_alive; break; + case NUM_TEAM_4: ++pink_alive; break; + } + } + + if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned) + { + if(time >= inv_lastcheck) + { + invasion_SpawnMonsters(supermonster_count); + inv_lastcheck = time + autocvar_g_invasion_spawn_delay; + } + + return 0; + } + + if(inv_numspawned < 1) + return 0; // nothing has spawned yet + + if(teamplay) + { + if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1) + return 0; + } + else if(inv_numkilled < inv_maxspawned) + return 0; + + entity winner = world; + float winning_score = 0, winner_team = 0; + + + if(teamplay) + { + if(red_alive > 0) { winner_team = NUM_TEAM_1; } + if(blue_alive > 0) + if(winner_team) { winner_team = 0; } + else { winner_team = NUM_TEAM_2; } + if(yellow_alive > 0) + if(winner_team) { winner_team = 0; } + else { winner_team = NUM_TEAM_3; } + if(pink_alive > 0) + if(winner_team) { winner_team = 0; } + else { winner_team = NUM_TEAM_4; } + } + else + FOR_EACH_PLAYER(head) + { + float cs = PlayerScore_Add(head, SP_KILLS, 0); + if(cs > winning_score) + { + winning_score = cs; + winner = head; + } + } + + FOR_EACH_MONSTER(head) + Monster_Remove(head); + + if(teamplay) + { + if(winner_team) + { + 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_)); + } + } + else if(winner) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname); + } + + round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + + return 1; +} + +float Invasion_CheckPlayers() +{ + return true; +} + +void Invasion_RoundStart() +{ + entity e; + float numplayers = 0; + FOR_EACH_PLAYER(e) + { + e.player_blocked = 0; + ++numplayers; + } + + if(inv_roundcnt < inv_maxrounds) + inv_roundcnt += 1; // a limiter to stop crazy counts + + inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3); + + inv_maxcurrent = 0; + inv_numspawned = 0; + inv_numkilled = 0; + + inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5))); + + if(teamplay) + { + DistributeEvenly_Init(inv_maxspawned, invasion_teams); + inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1); + inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1); + if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1); + if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1); + } +} + +MUTATOR_HOOKFUNCTION(inv, MonsterDies) +{SELFPARAM(); + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) + { + inv_numkilled += 1; + inv_maxcurrent -= 1; + if(teamplay) { inv_monsters_perteam[self.team] -= 1; } + + if(IS_PLAYER(frag_attacker)) + if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works + PlayerScore_Add(frag_attacker, SP_KILLS, -1); + else + { + PlayerScore_Add(frag_attacker, SP_KILLS, +1); + if(teamplay) + TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1); + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, MonsterSpawn) +{SELFPARAM(); + if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) + return true; + + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) + { + inv_numspawned += 1; + inv_maxcurrent += 1; + } + + self.monster_skill = inv_monsterskill; + + if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER) + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, self.monster_name); + + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, OnEntityPreSpawn) +{SELFPARAM(); + if(startsWith(self.classname, "monster_")) + if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, SV_StartFrame) +{ + monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned + monsters_killed = inv_numkilled; + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, PlayerRegen) +{ + // no regeneration in invasion + return true; +} + +MUTATOR_HOOKFUNCTION(inv, PlayerSpawn) +{SELFPARAM(); + self.bot_attack = false; + return false; +} + +MUTATOR_HOOKFUNCTION(inv, PlayerDamage_Calculate) +{ + if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target) + { + frag_damage = 0; + frag_force = '0 0 0'; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, SV_ParseClientCommand) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE) // command was already handled? + return false; + + if(cmd_name == "debuginvasion") + { + sprint(self, strcat("inv_maxspawned = ", ftos(inv_maxspawned), "\n")); + sprint(self, strcat("inv_numspawned = ", ftos(inv_numspawned), "\n")); + sprint(self, strcat("inv_numkilled = ", ftos(inv_numkilled), "\n")); + sprint(self, strcat("inv_roundcnt = ", ftos(inv_roundcnt), "\n")); + sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n")); + sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n")); + sprint(self, strcat("inv_monsterskill = ", ftos(inv_monsterskill), "\n")); + + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, BotShouldAttack) +{ + if(!IS_MONSTER(checkentity)) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(inv, SetStartItems) +{ + start_health = 200; + start_armorvalue = 200; + return false; +} + +MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid) +{ + if(IS_MONSTER(frag_target)) + return MUT_ACCADD_INVALID; + return MUT_ACCADD_INDIFFERENT; +} + +MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning) +{ + // monster spawning disabled during an invasion + return true; +} + +MUTATOR_HOOKFUNCTION(inv, GetTeamCount, CBC_ORDER_EXCLUSIVE) +{ + ret_float = invasion_teams; + return false; +} + +MUTATOR_HOOKFUNCTION(inv, AllowMobButcher) +{ + ret_string = "This command does not work during an invasion!"; + return true; +} + +void invasion_ScoreRules(float inv_teams) +{ + if(inv_teams) { CheckAllowedTeams(world); } + ScoreRules_basics(inv_teams, 0, 0, false); + if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY)); + ScoreRules_basics_end(); +} + +void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up. +{ + if(autocvar_g_invasion_teams) + invasion_teams = bound(2, autocvar_g_invasion_teams, 4); + else + invasion_teams = 0; + + independent_players = 1; // to disable extra useless scores + + invasion_ScoreRules(invasion_teams); + + independent_players = 0; + + round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart); + round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit); + + inv_roundcnt = 0; + inv_maxrounds = 15; // 15? +} + +void invasion_Initialize() +{ + if(autocvar_g_invasion_zombies_only) { + Monster mon = MON_ZOMBIE; + mon.mr_precache(mon); + } else + { + float i; + entity mon; + for(i = MON_FIRST; i <= MON_LAST; ++i) + { + mon = get_monsterinfo(i); + if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM)) + continue; // flying/swimming monsters not yet supported + + mon.mr_precache(mon); + } + } + + InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE); +} + +REGISTER_MUTATOR(inv, IS_GAMETYPE(INVASION)) +{ + SetLimits(autocvar_g_invasion_point_limit, -1, -1, -1); + if(autocvar_g_invasion_teams >= 2) + { + ActivateTeamplay(); + if(autocvar_g_invasion_team_spawns) + have_team_spawns = -1; // request team spawns + } + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + g_invasion = true; + invasion_Initialize(); + + cvar_settemp("g_monsters", "1"); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back invasion_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_keepaway.qc b/qcsrc/server/mutators/mutator/gamemode_keepaway.qc new file mode 100644 index 000000000..855b766a9 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_keepaway.qc @@ -0,0 +1,500 @@ +#ifndef GAMEMODE_KEEPAWAY_H +#define GAMEMODE_KEEPAWAY_H + + +entity ka_ball; + +const float SP_KEEPAWAY_PICKUPS = 4; +const float SP_KEEPAWAY_CARRIERKILLS = 5; +const float SP_KEEPAWAY_BCTIME = 6; + +void() havocbot_role_ka_carrier; +void() havocbot_role_ka_collector; + +void ka_DropEvent(entity plyr); +#endif + +#ifdef IMPLEMENTATION + +int autocvar_g_keepaway_ballcarrier_effects; +float autocvar_g_keepaway_ballcarrier_damage; +float autocvar_g_keepaway_ballcarrier_force; +float autocvar_g_keepaway_ballcarrier_highspeed; +float autocvar_g_keepaway_ballcarrier_selfdamage; +float autocvar_g_keepaway_ballcarrier_selfforce; +float autocvar_g_keepaway_noncarrier_damage; +float autocvar_g_keepaway_noncarrier_force; +float autocvar_g_keepaway_noncarrier_selfdamage; +float autocvar_g_keepaway_noncarrier_selfforce; +bool autocvar_g_keepaway_noncarrier_warn; +int autocvar_g_keepaway_score_bckill; +int autocvar_g_keepaway_score_killac; +int autocvar_g_keepaway_score_timepoints; +float autocvar_g_keepaway_score_timeinterval; +float autocvar_g_keepawayball_damageforcescale; +int autocvar_g_keepawayball_effects; +float autocvar_g_keepawayball_respawntime; +int autocvar_g_keepawayball_trail_color; + +float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame +{ + if(e.ballcarried) + if(IS_SPEC(other)) + return false; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen + + // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup + + return true; +} + +void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":ka:", mode, ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +void ka_TouchEvent(); +void ka_RespawnBall() // runs whenever the ball needs to be relocated +{SELFPARAM(); + if(gameover) { return; } + vector oldballorigin = self.origin; + + if(!MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) + { + entity spot = SelectSpawnPoint(true); + setorigin(self, spot.origin); + self.angles = spot.angles; + } + + makevectors(self.angles); + self.movetype = MOVETYPE_BOUNCE; + self.velocity = '0 0 200'; + self.angles = '0 0 0'; + self.effects = autocvar_g_keepawayball_effects; + self.touch = ka_TouchEvent; + self.think = ka_RespawnBall; + self.nextthink = time + autocvar_g_keepawayball_respawntime; + + Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, self.origin, '0 0 0', 1); + + WaypointSprite_Spawn(WP_KaBall, 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); + WaypointSprite_Ping(self.waypointsprite_attachedforcarrier); + + sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) +} + +void ka_TimeScoring() +{SELFPARAM(); + if(self.owner.ballcarried) + { // add points for holding the ball after a certain amount of time + if(autocvar_g_keepaway_score_timepoints) + PlayerScore_Add(self.owner, SP_SCORE, autocvar_g_keepaway_score_timepoints); + + PlayerScore_Add(self.owner, SP_KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds" + self.nextthink = time + autocvar_g_keepaway_score_timeinterval; + } +} + +void ka_TouchEvent() // runs any time that the ball comes in contact with something +{SELFPARAM(); + if(gameover) { return; } + if(!self) { return; } + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + { // The ball fell off the map, respawn it since players can't get to it + ka_RespawnBall(); + return; + } + if(other.deadflag != DEAD_NO) { return; } + if(other.frozen) { return; } + if (!IS_PLAYER(other)) + { // The ball just touched an object, most likely the world + Send_Effect(EFFECT_BALL_SPARKS, self.origin, '0 0 0', 1); + sound(self, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM); + return; + } + else if(self.wait > time) { return; } + + // attach the ball to the player + self.owner = other; + other.ballcarried = self; + setattachment(self, other, ""); + setorigin(self, '0 0 0'); + + // make the ball invisible/unable to do anything/set up time scoring + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.effects |= EF_NODRAW; + self.touch = func_null; + self.think = ka_TimeScoring; + self.nextthink = time + autocvar_g_keepaway_score_timeinterval; + self.takedamage = DAMAGE_NO; + + // apply effects to player + other.glow_color = autocvar_g_keepawayball_trail_color; + other.glow_trail = true; + other.effects |= autocvar_g_keepaway_ballcarrier_effects; + + // messages and sounds + ka_EventLog("pickup", other); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_PICKUP, other.netname); + Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, other.netname); + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF); + sound(self.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) + + // scoring + PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1); + + // waypoints + WaypointSprite_AttachCarrier(WP_KaBallCarrier, other, RADARICON_FLAGCARRIER); + other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player; + WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); + WaypointSprite_Ping(other.waypointsprite_attachedforcarrier); + WaypointSprite_Kill(self.waypointsprite_attachedforcarrier); +} + +void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball +{ + entity ball; + ball = plyr.ballcarried; + + if(!ball) { return; } + + // reset the ball + setattachment(ball, world, ""); + ball.movetype = MOVETYPE_BOUNCE; + ball.wait = time + 1; + ball.touch = ka_TouchEvent; + ball.think = ka_RespawnBall; + ball.nextthink = time + autocvar_g_keepawayball_respawntime; + ball.takedamage = DAMAGE_YES; + ball.effects &= ~EF_NODRAW; + setorigin(ball, plyr.origin + '0 0 10'); + ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom(); + ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P + ball.owner = world; + + // reset the player effects + plyr.glow_trail = false; + plyr.effects &= ~autocvar_g_keepaway_ballcarrier_effects; + + // messages and sounds + ka_EventLog("dropped", plyr); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname); + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname); + sound(other, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) + + // scoring + // PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1); Not anymore, this is 100% the same as pickups and is useless. + + // waypoints + WaypointSprite_Spawn(WP_KaBall, 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER); + WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT); + WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier); + WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier); +} + +void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal +{SELFPARAM(); + if((self.owner) && (IS_PLAYER(self.owner))) + ka_DropEvent(self.owner); + + if(time < game_starttime) + { + self.think = ka_RespawnBall; + self.touch = func_null; + self.nextthink = game_starttime; + } + else + ka_RespawnBall(); +} + + +// ================ +// Bot player logic +// ================ + +void havocbot_goalrating_ball(float ratingscale, vector org) +{SELFPARAM(); + float t; + entity ball_owner; + ball_owner = ka_ball.owner; + + if (ball_owner == self) + return; + + // If ball is carried by player then hunt them down. + if (ball_owner) + { + t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue); + navigation_routerating(ball_owner, t * ratingscale, 2000); + } + else // Ball has been dropped so collect. + navigation_routerating(ka_ball, ratingscale, 2000); +} + +void havocbot_role_ka_carrier() +{SELFPARAM(); + if (self.deadflag != DEAD_NO) + return; + + if (time > self.bot_strategytime) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + havocbot_goalrating_items(10000, self.origin, 10000); + havocbot_goalrating_enemyplayers(20000, self.origin, 10000); + //havocbot_goalrating_waypoints(1, self.origin, 1000); + navigation_goalrating_end(); + } + + if (!self.ballcarried) + { + self.havocbot_role = havocbot_role_ka_collector; + self.bot_strategytime = 0; + } +} + +void havocbot_role_ka_collector() +{SELFPARAM(); + if (self.deadflag != DEAD_NO) + return; + + if (time > self.bot_strategytime) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + + navigation_goalrating_start(); + havocbot_goalrating_items(10000, self.origin, 10000); + havocbot_goalrating_enemyplayers(1000, self.origin, 10000); + havocbot_goalrating_ball(20000, self.origin); + navigation_goalrating_end(); + } + + if (self.ballcarried) + { + self.havocbot_role = havocbot_role_ka_carrier; + self.bot_strategytime = 0; + } +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ka, PlayerDies) +{SELFPARAM(); + if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker))) + { + if(frag_target.ballcarried) { // add to amount of times killing carrier + PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1); + if(autocvar_g_keepaway_score_bckill) // add bckills to the score + PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_bckill); + } + else if(!frag_attacker.ballcarried) + if(autocvar_g_keepaway_noncarrier_warn) + Send_Notification(NOTIF_ONE_ONLY, frag_attacker, MSG_CENTER, CENTER_KEEPAWAY_WARN); + + if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier + PlayerScore_Add(frag_attacker, SP_SCORE, autocvar_g_keepaway_score_killac); + } + + if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it + return 0; +} + +MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill) +{ + frag_score = 0; // no frags counted in keepaway + return 1; // you deceptive little bugger ;3 This needs to be true in order for this function to even count. +} + +MUTATOR_HOOKFUNCTION(ka, PlayerPreThink) +{SELFPARAM(); + // clear the item used for the ball in keepaway + self.items &= ~IT_KEY1; + + // if the player has the ball, make sure they have the item for it (Used for HUD primarily) + if(self.ballcarried) + self.items |= IT_KEY1; + + return 0; +} + +MUTATOR_HOOKFUNCTION(ka, PlayerUseKey) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE == 0) + if(self.ballcarried) + { + ka_DropEvent(self); + return 1; + } + return 0; +} + +MUTATOR_HOOKFUNCTION(ka, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc +{ + if(frag_attacker.ballcarried) // if the attacker is a ballcarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage; + frag_force *= autocvar_g_keepaway_ballcarrier_selfforce; + } + else // damage done to noncarriers + { + frag_damage *= autocvar_g_keepaway_ballcarrier_damage; + frag_force *= autocvar_g_keepaway_ballcarrier_force; + } + } + else if (!frag_target.ballcarried) // if the target is a noncarrier + { + if(frag_target == frag_attacker) // damage done to yourself + { + frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage; + frag_force *= autocvar_g_keepaway_noncarrier_selfforce; + } + else // damage done to other noncarriers + { + frag_damage *= autocvar_g_keepaway_noncarrier_damage; + frag_force *= autocvar_g_keepaway_noncarrier_force; + } + } + return 0; +} + +MUTATOR_HOOKFUNCTION(ka, ClientDisconnect) +{SELFPARAM(); + if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it + return 0; +} + +MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver) +{SELFPARAM(); + if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it + return 0; +} + +MUTATOR_HOOKFUNCTION(ka, PlayerPowerups) +{SELFPARAM(); + // In the future this hook is supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup + // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player() + + self.effects &= ~autocvar_g_keepaway_ballcarrier_effects; + + if(self.ballcarried) + self.effects |= autocvar_g_keepaway_ballcarrier_effects; + + return 0; +} + +.float stat_sv_airspeedlimit_nonqw; +.float stat_sv_maxspeed; + +MUTATOR_HOOKFUNCTION(ka, PlayerPhysics) +{SELFPARAM(); + if(self.ballcarried) + { + self.stat_sv_airspeedlimit_nonqw *= autocvar_g_keepaway_ballcarrier_highspeed; + self.stat_sv_maxspeed *= autocvar_g_keepaway_ballcarrier_highspeed; + } + return false; +} + +MUTATOR_HOOKFUNCTION(ka, BotShouldAttack) +{SELFPARAM(); + // if neither player has ball then don't attack unless the ball is on the ground + if(!checkentity.ballcarried && !self.ballcarried && ka_ball.owner) + return true; + return false; +} + +MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole) +{SELFPARAM(); + if (self.ballcarried) + self.havocbot_role = havocbot_role_ka_carrier; + else + self.havocbot_role = havocbot_role_ka_collector; + return true; +} + +MUTATOR_HOOKFUNCTION(ka, DropSpecialItems) +{ + if(frag_target.ballcarried) + ka_DropEvent(frag_target); + + return false; +} + + +// ============== +// Initialization +// ============== + +void ka_SpawnBall() // loads various values for the ball, runs only once at start of match +{ + entity e; + e = spawn(); + e.model = "models/orbs/orbblue.md3"; + precache_model(e.model); + _setmodel(e, e.model); + setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off + e.classname = "keepawayball"; + e.damageforcescale = autocvar_g_keepawayball_damageforcescale; + e.takedamage = DAMAGE_YES; + e.solid = SOLID_TRIGGER; + e.movetype = MOVETYPE_BOUNCE; + e.glow_color = autocvar_g_keepawayball_trail_color; + e.glow_trail = true; + e.flags = FL_ITEM; + e.reset = ka_Reset; + e.touch = ka_TouchEvent; + e.owner = world; + ka_ball = e; + + InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. +} + +void ka_ScoreRules() +{ + ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, true); // SFL_SORT_PRIO_PRIMARY + ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS, "pickups", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY); + ScoreRules_basics_end(); +} + +void ka_Initialize() // run at the start of a match, initiates game mode +{ + ka_ScoreRules(); + ka_SpawnBall(); +} + + +REGISTER_MUTATOR(ka, IS_GAMETYPE(KEEPAWAY)) +{ + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + ka_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back ka_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc new file mode 100644 index 000000000..3b86b6641 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_keyhunt.qc @@ -0,0 +1,1399 @@ +#ifndef GAMEMODE_KEYHUNT_H +#define GAMEMODE_KEYHUNT_H + +#define FOR_EACH_KH_KEY(v) for(v = kh_worldkeylist; v; v = v.kh_worldkeynext ) + +// ALL OF THESE should be removed in the future, as other code should not have to care + +// used by bots: +float kh_tracking_enabled; +.entity kh_next; +float kh_Key_AllOwnedByWhichTeam(); + +typedef void(void) kh_Think_t; +void kh_StartRound(); +void kh_Controller_SetThink(float t, kh_Think_t func); + +entity kh_worldkeylist; +.entity kh_worldkeynext; +#endif + +#ifdef IMPLEMENTATION + +float autocvar_g_balance_keyhunt_damageforcescale; +float autocvar_g_balance_keyhunt_delay_collect; +float autocvar_g_balance_keyhunt_delay_return; +float autocvar_g_balance_keyhunt_delay_round; +float autocvar_g_balance_keyhunt_delay_tracking; +float autocvar_g_balance_keyhunt_dropvelocity; +float autocvar_g_balance_keyhunt_maxdist; +float autocvar_g_balance_keyhunt_protecttime; + +int autocvar_g_balance_keyhunt_score_capture; +int autocvar_g_balance_keyhunt_score_carrierfrag; +int autocvar_g_balance_keyhunt_score_collect; +int autocvar_g_balance_keyhunt_score_destroyed; +int autocvar_g_balance_keyhunt_score_destroyed_ownfactor; +int autocvar_g_balance_keyhunt_score_push; +float autocvar_g_balance_keyhunt_throwvelocity; + +int autocvar_g_keyhunt_point_leadlimit; +bool autocvar_g_keyhunt_team_spawns; +#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit") +int autocvar_g_keyhunt_teams; +int autocvar_g_keyhunt_teams_override; + +// #define KH_PLAYER_USE_ATTACHMENT +// #define KH_PLAYER_USE_CARRIEDMODEL + +#ifdef KH_PLAYER_USE_ATTACHMENT +const vector KH_PLAYER_ATTACHMENT_DIST_ROTATED = '0 -4 0'; +const vector KH_PLAYER_ATTACHMENT_DIST = '4 0 0'; +const vector KH_PLAYER_ATTACHMENT = '0 0 0'; +const vector KH_PLAYER_ATTACHMENT_ANGLES = '0 0 0'; +const string KH_PLAYER_ATTACHMENT_BONE = ""; +#else +const float KH_KEY_ZSHIFT = 22; +const float KH_KEY_XYDIST = 24; +const float KH_KEY_XYSPEED = 45; +#endif +const float KH_KEY_WP_ZSHIFT = 20; + +const vector KH_KEY_MIN = '-10 -10 -46'; +const vector KH_KEY_MAX = '10 10 3'; +const float KH_KEY_BRIGHTNESS = 2; + +float kh_no_radar_circles; + +// kh_state +// bits 0- 4: team of key 1, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 5- 9: team of key 2, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 10-14: team of key 3, or 0 for no such key, or 30 for dropped, or 31 for self +// bits 15-19: team of key 4, or 0 for no such key, or 30 for dropped, or 31 for self +.float kh_state; +.float siren_time; // time delay the siren +//.float stuff_time; // time delay to stuffcmd a cvar + +float kh_keystatus[17]; +//kh_keystatus[0] = status of dropped keys, kh_keystatus[1 - 16] = player # +//replace 17 with cvar("maxplayers") or similar !!!!!!!!! +//for(i = 0; i < maxplayers; ++i) +// kh_keystatus[i] = "0"; + +float kh_Team_ByID(float t) +{ + if(t == 0) return NUM_TEAM_1; + if(t == 1) return NUM_TEAM_2; + if(t == 2) return NUM_TEAM_3; + if(t == 3) return NUM_TEAM_4; + return 0; +} + +//entity kh_worldkeylist; +.entity kh_worldkeynext; +entity kh_controller; +//float kh_tracking_enabled; +float kh_teams; +float kh_interferemsg_time, kh_interferemsg_team; +.entity kh_next, kh_prev; // linked list +.float kh_droptime; +.float kh_dropperteam; +.entity kh_previous_owner; +.float kh_previous_owner_playerid; +.float kh_cp_duration; + +float kh_key_dropped, kh_key_carried; + +const float ST_KH_CAPS = 1; +const float SP_KH_CAPS = 4; +const float SP_KH_PUSHES = 5; +const float SP_KH_DESTROYS = 6; +const float SP_KH_PICKUPS = 7; +const float SP_KH_KCKILLS = 8; +const float SP_KH_LOSSES = 9; +void kh_ScoreRules(float teams) +{ + ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, true); + ScoreInfo_SetLabel_TeamScore( ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_KH_PUSHES, "pushes", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER); + ScoreInfo_SetLabel_PlayerScore(SP_KH_PICKUPS, "pickups", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KH_KCKILLS, "kckills", 0); + ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); + ScoreRules_basics_end(); +} + +float kh_KeyCarrier_waypointsprite_visible_for_player(entity e) // runs all the time +{SELFPARAM(); + if(!IS_PLAYER(e) || self.team != e.team) + if(!kh_tracking_enabled) + return false; + + return true; +} + +float kh_Key_waypointsprite_visible_for_player(entity e) // ?? +{SELFPARAM(); + if(!kh_tracking_enabled) + return false; + if(!self.owner) + return true; + if(!self.owner.owner) + return true; + return false; // draw only when key is not owned +} + +void kh_update_state() +{ + entity player; + entity key; + float s; + float f; + + s = 0; + FOR_EACH_KH_KEY(key) + { + if(key.owner) + f = key.team; + else + f = 30; + s |= pow(32, key.count) * f; + } + + FOR_EACH_CLIENT(player) + { + player.kh_state = s; + } + + FOR_EACH_KH_KEY(key) + { + if(key.owner) + key.owner.kh_state |= pow(32, key.count) * 31; + } + //print(ftos((nextent(world)).kh_state), "\n"); +} + + + + +var kh_Think_t kh_Controller_Thinkfunc; +void kh_Controller_SetThink(float t, kh_Think_t func) // runs occasionaly +{ + kh_Controller_Thinkfunc = func; + kh_controller.cnt = ceil(t); + if(t == 0) + kh_controller.nextthink = time; // force +} +void kh_WaitForPlayers(); +void kh_Controller_Think() // called a lot +{SELFPARAM(); + if(intermission_running) + return; + if(self.cnt > 0) + { if(self.think != kh_WaitForPlayers) { self.cnt -= 1; } } + else if(self.cnt == 0) + { + self.cnt -= 1; + kh_Controller_Thinkfunc(); + } + self.nextthink = time + 1; +} + +// frags f: take from cvar * f +// frags 0: no frags +void kh_Scores_Event(entity player, entity key, string what, float frags_player, float frags_owner) // update the score when a key is captured +{ + string s; + if(intermission_running) + return; + + if(frags_player) + UpdateFrags(player, frags_player); + + if(key && key.owner && frags_owner) + UpdateFrags(key.owner, frags_owner); + + if(!autocvar_sv_eventlog) //output extra info to the console or text file + return; + + s = strcat(":keyhunt:", what, ":", ftos(player.playerid), ":", ftos(frags_player)); + + if(key && key.owner) + s = strcat(s, ":", ftos(key.owner.playerid)); + else + s = strcat(s, ":0"); + + s = strcat(s, ":", ftos(frags_owner), ":"); + + if(key) + s = strcat(s, key.netname); + + GameLogEcho(s); +} + +vector kh_AttachedOrigin(entity e) // runs when a team captures the flag, it can run 2 or 3 times. +{ + if(e.tag_entity) + { + makevectors(e.tag_entity.angles); + return e.tag_entity.origin + e.origin.x * v_forward - e.origin.y * v_right + e.origin.z * v_up; + } + else + return e.origin; +} + +void kh_Key_Attach(entity key) // runs when a player picks up a key and several times when a key is assigned to a player at the start of a round +{ +#ifdef KH_PLAYER_USE_ATTACHMENT + entity first; + first = key.owner.kh_next; + if(key == first) + { + setattachment(key, key.owner, KH_PLAYER_ATTACHMENT_BONE); + if(key.kh_next) + { + setattachment(key.kh_next, key, ""); + setorigin(key, key.kh_next.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); + setorigin(key.kh_next, KH_PLAYER_ATTACHMENT_DIST_ROTATED); + key.kh_next.angles = '0 0 0'; + } + else + setorigin(key, KH_PLAYER_ATTACHMENT); + key.angles = KH_PLAYER_ATTACHMENT_ANGLES; + } + else + { + setattachment(key, key.kh_prev, ""); + if(key.kh_next) + setattachment(key.kh_next, key, ""); + setorigin(key, KH_PLAYER_ATTACHMENT_DIST_ROTATED); + setorigin(first, first.origin - 0.5 * KH_PLAYER_ATTACHMENT_DIST); + key.angles = '0 0 0'; + } +#else + setattachment(key, key.owner, ""); + setorigin(key, '0 0 1' * KH_KEY_ZSHIFT); // fixing x, y in think + key.angles_y -= key.owner.angles.y; +#endif + key.flags = 0; + key.solid = SOLID_NOT; + key.movetype = MOVETYPE_NONE; + key.team = key.owner.team; + key.nextthink = time; + key.damageforcescale = 0; + key.takedamage = DAMAGE_NO; + key.modelindex = kh_key_carried; +} + +void kh_Key_Detach(entity key) // runs every time a key is dropped or lost. Runs several times times when all the keys are captured +{ +#ifdef KH_PLAYER_USE_ATTACHMENT + entity first; + first = key.owner.kh_next; + if(key == first) + { + if(key.kh_next) + { + setattachment(key.kh_next, key.owner, KH_PLAYER_ATTACHMENT_BONE); + setorigin(key.kh_next, key.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); + key.kh_next.angles = KH_PLAYER_ATTACHMENT_ANGLES; + } + } + else + { + if(key.kh_next) + setattachment(key.kh_next, key.kh_prev, ""); + setorigin(first, first.origin + 0.5 * KH_PLAYER_ATTACHMENT_DIST); + } + // in any case: + setattachment(key, world, ""); + setorigin(key, key.owner.origin + '0 0 1' * (PL_MIN.z - KH_KEY_MIN_z)); + key.angles = key.owner.angles; +#else + setorigin(key, key.owner.origin + key.origin.z * '0 0 1'); + setattachment(key, world, ""); + key.angles_y += key.owner.angles.y; +#endif + key.flags = FL_ITEM; + key.solid = SOLID_TRIGGER; + key.movetype = MOVETYPE_TOSS; + key.pain_finished = time + autocvar_g_balance_keyhunt_delay_return; + key.damageforcescale = autocvar_g_balance_keyhunt_damageforcescale; + key.takedamage = DAMAGE_YES; + // let key.team stay + key.modelindex = kh_key_dropped; + key.kh_previous_owner = key.owner; + key.kh_previous_owner_playerid = key.owner.playerid; +} + +void kh_Key_AssignTo(entity key, entity player) // runs every time a key is picked up or assigned. Runs prior to kh_key_attach +{ + entity k; + float ownerteam0, ownerteam; + if(key.owner == player) + return; + + ownerteam0 = kh_Key_AllOwnedByWhichTeam(); + + if(key.owner) + { + kh_Key_Detach(key); + + // remove from linked list + if(key.kh_next) + key.kh_next.kh_prev = key.kh_prev; + key.kh_prev.kh_next = key.kh_next; + key.kh_next = world; + key.kh_prev = world; + + if(key.owner.kh_next == world) + { + // No longer a key carrier + if(!kh_no_radar_circles) + WaypointSprite_Ping(key.owner.waypointsprite_attachedforcarrier); + WaypointSprite_DetachCarrier(key.owner); + } + } + + key.owner = player; + + if(player) + { + // insert into linked list + key.kh_next = player.kh_next; + key.kh_prev = player; + player.kh_next = key; + if(key.kh_next) + key.kh_next.kh_prev = key; + + float i; + i = kh_keystatus[key.owner.playerid]; + if(key.netname == "^1red key") + i += 1; + if(key.netname == "^4blue key") + i += 2; + if(key.netname == "^3yellow key") + i += 4; + if(key.netname == "^6pink key") + i += 8; + kh_keystatus[key.owner.playerid] = i; + + kh_Key_Attach(key); + + if(key.kh_next == world) + { + // player is now a key carrier + entity wp = WaypointSprite_AttachCarrier(WP_Null, player, RADARICON_FLAGCARRIER); + wp.colormod = colormapPaletteColor(player.team - 1, 0); + player.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_KeyCarrier_waypointsprite_visible_for_player; + WaypointSprite_UpdateRule(player.waypointsprite_attachedforcarrier, player.team, SPRITERULE_TEAMPLAY); + if(player.team == NUM_TEAM_1) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierRed, WP_KeyCarrierFriend, WP_KeyCarrierRed); + else if(player.team == NUM_TEAM_2) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierBlue, WP_KeyCarrierFriend, WP_KeyCarrierBlue); + else if(player.team == NUM_TEAM_3) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierYellow, WP_KeyCarrierFriend, WP_KeyCarrierYellow); + else if(player.team == NUM_TEAM_4) + WaypointSprite_UpdateSprites(player.waypointsprite_attachedforcarrier, WP_KeyCarrierPink, WP_KeyCarrierFriend, WP_KeyCarrierPink); + if(!kh_no_radar_circles) + WaypointSprite_Ping(player.waypointsprite_attachedforcarrier); + } + } + + // moved that here, also update if there's no player + kh_update_state(); + + key.pusher = world; + + ownerteam = kh_Key_AllOwnedByWhichTeam(); + if(ownerteam != ownerteam0) + { + if(ownerteam != -1) + { + kh_interferemsg_time = time + 0.2; + kh_interferemsg_team = player.team; + + // audit all key carrier sprites, update them to RUN HERE + FOR_EACH_KH_KEY(k) + { + if (!k.owner) continue; + entity first = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break)); + entity third = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break)); + WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFinish, third); + } + } + else + { + kh_interferemsg_time = 0; + + // audit all key carrier sprites, update them to RUN HERE + FOR_EACH_KH_KEY(k) + { + if (!k.owner) continue; + entity first = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, LAMBDA(first = it; break)); + entity third = WP_Null; + FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, LAMBDA(third = it; break)); + WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third); + } + } + } +} + +void kh_Key_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{SELFPARAM(); + if(self.owner) + return; + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + // touching lava, or hurt trigger + // what shall we do? + // immediately return is bad + // maybe start a shorter countdown? + } + if(vlen(force) <= 0) + return; + if(time > self.pushltime) + if(IS_PLAYER(attacker)) + self.team = attacker.team; +} + +void kh_Key_Collect(entity key, entity player) //a player picks up a dropped key +{ + sound(player, CH_TRIGGER, SND_KH_COLLECT, VOL_BASE, ATTEN_NORM); + + if(key.kh_dropperteam != player.team) + { + kh_Scores_Event(player, key, "collect", autocvar_g_balance_keyhunt_score_collect, 0); + PlayerScore_Add(player, SP_KH_PICKUPS, 1); + } + key.kh_dropperteam = 0; + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_PICKUP_), player.netname); + + kh_Key_AssignTo(key, player); // this also updates .kh_state +} + +void kh_Key_Touch() // runs many, many times when a key has been dropped and can be picked up +{SELFPARAM(); + if(intermission_running) + return; + + if(self.owner) // already carried + return; + + if(ITEM_TOUCH_NEEDKILL()) + { + // touching sky, or nodrop + // what shall we do? + // immediately return is bad + // maybe start a shorter countdown? + } + + if (!IS_PLAYER(other)) + return; + if(other.deadflag != DEAD_NO) + return; + if(other == self.enemy) + if(time < self.kh_droptime + autocvar_g_balance_keyhunt_delay_collect) + return; // you just dropped it! + kh_Key_Collect(self, other); +} + +void kh_Key_Remove(entity key) // runs after when all the keys have been collected or when a key has been dropped for more than X seconds +{ + entity o; + o = key.owner; + kh_Key_AssignTo(key, world); + if(o) // it was attached + WaypointSprite_Kill(key.waypointsprite_attachedforcarrier); + else // it was dropped + WaypointSprite_DetachCarrier(key); + + // remove key from key list + if (kh_worldkeylist == key) + kh_worldkeylist = kh_worldkeylist.kh_worldkeynext; + else + { + o = kh_worldkeylist; + while (o) + { + if (o.kh_worldkeynext == key) + { + o.kh_worldkeynext = o.kh_worldkeynext.kh_worldkeynext; + break; + } + o = o.kh_worldkeynext; + } + } + + remove(key); + + kh_update_state(); +} + +void kh_FinishRound() // runs when a team captures the keys +{ + // prepare next round + kh_interferemsg_time = 0; + entity key; + + kh_no_radar_circles = true; + FOR_EACH_KH_KEY(key) + kh_Key_Remove(key); + kh_no_radar_circles = false; + + 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); +} + +void kh_WinnerTeam(float teem) // runs when a team wins // Samual: Teem?.... TEEM?!?! what the fuck is wrong with you people +{ + // all key carriers get some points + vector firstorigin, lastorigin, midpoint; + float first; + entity key; + float score; + score = (kh_teams - 1) * autocvar_g_balance_keyhunt_score_capture; + DistributeEvenly_Init(score, kh_teams); + // twice the score for 3 team games, three times the score for 4 team games! + // note: for a win by destroying the key, this should NOT be applied + FOR_EACH_KH_KEY(key) + { + float f; + f = DistributeEvenly_Get(1); + kh_Scores_Event(key.owner, key, "capture", f, 0); + PlayerTeamScore_Add(key.owner, SP_KH_CAPS, ST_KH_CAPS, 1); + nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high); + } + + first = true; + string keyowner = ""; + FOR_EACH_KH_KEY(key) + if(key.owner.kh_next == key) + { + if(!first) + keyowner = strcat(keyowner, ", "); + keyowner = key.owner.netname; + first = false; + } + + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(teem, INFO_KEYHUNT_CAPTURE_), keyowner); + + first = true; + midpoint = '0 0 0'; + firstorigin = '0 0 0'; + lastorigin = '0 0 0'; + FOR_EACH_KH_KEY(key) + { + vector thisorigin; + + thisorigin = kh_AttachedOrigin(key); + //dprint("Key origin: ", vtos(thisorigin), "\n"); + midpoint += thisorigin; + + if(!first) + te_lightning2(world, lastorigin, thisorigin); + lastorigin = thisorigin; + if(first) + firstorigin = thisorigin; + first = false; + } + if(kh_teams > 2) + { + te_lightning2(world, lastorigin, firstorigin); + } + midpoint = midpoint * (1 / kh_teams); + te_customflash(midpoint, 1000, 1, Team_ColorRGB(teem) * 0.5 + '0.5 0.5 0.5'); // make the color >=0.5 in each component + + play2all(SND(KH_CAPTURE)); + kh_FinishRound(); +} + +void kh_LoserTeam(float teem, entity lostkey) // runs when a player pushes a flag carrier off the map +{ + entity player, key, attacker; + float players; + float keys; + float f; + + attacker = world; + if(lostkey.pusher) + if(lostkey.pusher.team != teem) + if(IS_PLAYER(lostkey.pusher)) + attacker = lostkey.pusher; + + players = keys = 0; + + if(attacker) + { + if(lostkey.kh_previous_owner) + kh_Scores_Event(lostkey.kh_previous_owner, world, "pushed", 0, -autocvar_g_balance_keyhunt_score_push); + // don't actually GIVE him the -nn points, just log + kh_Scores_Event(attacker, world, "push", autocvar_g_balance_keyhunt_score_push, 0); + PlayerScore_Add(attacker, SP_KH_PUSHES, 1); + //centerprint(attacker, "Your push is the best!"); // does this really need to exist? + } + else + { + float of, fragsleft, i, j, thisteam; + of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor; + + FOR_EACH_PLAYER(player) + if(player.team != teem) + ++players; + + FOR_EACH_KH_KEY(key) + if(key.owner && key.team != teem) + ++keys; + + if(lostkey.kh_previous_owner) + kh_Scores_Event(lostkey.kh_previous_owner, world, "destroyed", 0, -autocvar_g_balance_keyhunt_score_destroyed); + // don't actually GIVE him the -nn points, just log + + if(lostkey.kh_previous_owner.playerid == lostkey.kh_previous_owner_playerid) + PlayerScore_Add(lostkey.kh_previous_owner, SP_KH_DESTROYS, 1); + + DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players); + + FOR_EACH_KH_KEY(key) + if(key.owner && key.team != teem) + { + f = DistributeEvenly_Get(of); + kh_Scores_Event(key.owner, world, "destroyed_holdingkey", f, 0); + } + + fragsleft = DistributeEvenly_Get(players); + + // Now distribute these among all other teams... + j = kh_teams - 1; + for(i = 0; i < kh_teams; ++i) + { + thisteam = kh_Team_ByID(i); + if(thisteam == teem) // bad boy, no cookie - this WILL happen + continue; + + players = 0; + FOR_EACH_PLAYER(player) + if(player.team == thisteam) + ++players; + + DistributeEvenly_Init(fragsleft, j); + fragsleft = DistributeEvenly_Get(j - 1); + DistributeEvenly_Init(DistributeEvenly_Get(1), players); + + FOR_EACH_PLAYER(player) + if(player.team == thisteam) + { + f = DistributeEvenly_Get(1); + kh_Scores_Event(player, world, "destroyed", f, 0); + } + + --j; + } + } + + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(lostkey, INFO_KEYHUNT_LOST_), lostkey.kh_previous_owner.netname); + + play2all(SND(KH_DESTROY)); + te_tarexplosion(lostkey.origin); + + kh_FinishRound(); +} + +void kh_Key_Think() // runs all the time +{SELFPARAM(); + entity head; + //entity player; // needed by FOR_EACH_PLAYER + + if(intermission_running) + return; + + if(self.owner) + { +#ifndef KH_PLAYER_USE_ATTACHMENT + makevectors('0 1 0' * (self.cnt + (time % 360) * KH_KEY_XYSPEED)); + setorigin(self, v_forward * KH_KEY_XYDIST + '0 0 1' * self.origin.z); +#endif + } + + // if in nodrop or time over, end the round + if(!self.owner) + if(time > self.pain_finished) + kh_LoserTeam(self.team, self); + + if(self.owner) + if(kh_Key_AllOwnedByWhichTeam() != -1) + { + if(self.siren_time < time) + { + sound(self.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm + self.siren_time = time + 2.5; // repeat every 2.5 seconds + } + + entity key; + vector p; + p = self.owner.origin; + FOR_EACH_KH_KEY(key) + if(vlen(key.owner.origin - p) > autocvar_g_balance_keyhunt_maxdist) + goto not_winning; + kh_WinnerTeam(self.team); +:not_winning + } + + if(kh_interferemsg_time && time > kh_interferemsg_time) + { + kh_interferemsg_time = 0; + FOR_EACH_PLAYER(head) + { + if(head.team == kh_interferemsg_team) + if(head.kh_next) + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_MEET); + else + Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_KEYHUNT_HELP); + else + Send_Notification(NOTIF_ONE, head, MSG_CENTER, APP_TEAM_NUM_4(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE_)); + } + } + + self.nextthink = time + 0.05; +} + +void key_reset() +{SELFPARAM(); + kh_Key_AssignTo(self, world); + kh_Key_Remove(self); +} + +const string STR_ITEM_KH_KEY = "item_kh_key"; +void kh_Key_Spawn(entity initial_owner, float angle, float i) // runs every time a new flag is created, ie after all the keys have been collected +{ + entity key; + key = spawn(); + key.count = i; + key.classname = STR_ITEM_KH_KEY; + key.touch = kh_Key_Touch; + key.think = kh_Key_Think; + key.nextthink = time; + key.items = IT_KEY1 | IT_KEY2; + key.cnt = angle; + key.angles = '0 360 0' * random(); + key.event_damage = kh_Key_Damage; + key.takedamage = DAMAGE_YES; + key.modelindex = kh_key_dropped; + key.model = "key"; + key.kh_dropperteam = 0; + key.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + setsize(key, KH_KEY_MIN, KH_KEY_MAX); + key.colormod = Team_ColorRGB(initial_owner.team) * KH_KEY_BRIGHTNESS; + key.reset = key_reset; + + switch(initial_owner.team) + { + case NUM_TEAM_1: + key.netname = "^1red key"; + break; + case NUM_TEAM_2: + key.netname = "^4blue key"; + break; + case NUM_TEAM_3: + key.netname = "^3yellow key"; + break; + case NUM_TEAM_4: + key.netname = "^6pink key"; + break; + default: + key.netname = "NETGIER key"; + break; + } + + // link into key list + key.kh_worldkeynext = kh_worldkeylist; + kh_worldkeylist = key; + + Send_Notification(NOTIF_ONE, initial_owner, MSG_CENTER, APP_TEAM_NUM_4(initial_owner.team, CENTER_KEYHUNT_START_)); + + WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, world, key.team, key, waypointsprite_attachedforcarrier, false, RADARICON_FLAG); + key.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = kh_Key_waypointsprite_visible_for_player; + + kh_Key_AssignTo(key, initial_owner); +} + +// -1 when no team completely owns all keys yet +float kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team +{ + entity key; + float teem; + float keys; + + teem = -1; + keys = kh_teams; + FOR_EACH_KH_KEY(key) + { + if(!key.owner) + return -1; + if(teem == -1) + teem = key.team; + else if(teem != key.team) + return -1; + --keys; + } + if(keys != 0) + return -1; + return teem; +} + +void kh_Key_DropOne(entity key) +{ + // prevent collecting this one for some time + entity player; + player = key.owner; + + key.kh_droptime = time; + key.enemy = player; + + kh_Scores_Event(player, key, "dropkey", 0, 0); + PlayerScore_Add(player, SP_KH_LOSSES, 1); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_DROP_), player.netname); + + kh_Key_AssignTo(key, world); + makevectors(player.v_angle); + key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false); + key.pusher = world; + key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; + key.kh_dropperteam = key.team; + + sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); +} + +void kh_Key_DropAll(entity player, float suicide) // runs whenever a player dies +{ + entity key; + entity mypusher; + if(player.kh_next) + { + mypusher = world; + if(player.pusher) + if(time < player.pushltime) + mypusher = player.pusher; + while((key = player.kh_next)) + { + kh_Scores_Event(player, key, "losekey", 0, 0); + PlayerScore_Add(player, SP_KH_LOSSES, 1); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(key, INFO_KEYHUNT_LOST_), player.netname); + kh_Key_AssignTo(key, world); + makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random()); + key.velocity = W_CalculateProjectileVelocity(player.velocity, autocvar_g_balance_keyhunt_dropvelocity * v_forward, false); + key.pusher = mypusher; + key.pushltime = time + autocvar_g_balance_keyhunt_protecttime; + if(suicide) + key.kh_dropperteam = player.team; + } + sound(player, CH_TRIGGER, SND_KH_DROP, VOL_BASE, ATTEN_NORM); + } +} + +float kh_CheckPlayers(float num) +{ + if(num < kh_teams) + { + float t_team = kh_Team_ByID(num); + float players = 0; + entity tmp_player; + FOR_EACH_PLAYER(tmp_player) + if(tmp_player.deadflag == DEAD_NO) + if(!tmp_player.BUTTON_CHAT) + if(tmp_player.team == t_team) + ++players; + + if (!players) { return t_team; } + } + return 0; +} + +#define KH_READY_TEAMS() (!p1 + !p2 + ((kh_teams >= 3) ? !p3 : p3) + ((kh_teams >= 4) ? !p4 : p4)) +#define KH_READY_TEAMS_OK() (KH_READY_TEAMS() == kh_teams) +void kh_WaitForPlayers() // delay start of the round until enough players are present +{ + if(time < game_starttime) + { + kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); + return; + } + + static float prev_missing_teams_mask; + float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); + if(KH_READY_TEAMS_OK()) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + 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 + { + if(player_count == 0) + { + if(prev_missing_teams_mask > 0) + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_MISSING_TEAMS); + prev_missing_teams_mask = -1; + } + else + { + float missing_teams_mask = (!!p1) + (!!p2) * 2; + if(kh_teams >= 3) missing_teams_mask += (!!p3) * 4; + if(kh_teams >= 4) missing_teams_mask += (!!p4) * 8; + if(prev_missing_teams_mask != missing_teams_mask) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask); + prev_missing_teams_mask = missing_teams_mask; + } + } + kh_Controller_SetThink(1, kh_WaitForPlayers); + } +} + +void kh_EnableTrackingDevice() // runs after each round +{ + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT); + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER); + + kh_tracking_enabled = true; +} + +void kh_StartRound() // runs at the start of each round +{ + float i, players, teem; + entity player; + + if(time < game_starttime) + { + kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers); + return; + } + + float p1 = kh_CheckPlayers(0), p2 = kh_CheckPlayers(1), p3 = kh_CheckPlayers(2), p4 = kh_CheckPlayers(3); + if(!KH_READY_TEAMS_OK()) + { + kh_Controller_SetThink(1, kh_WaitForPlayers); + return; + } + + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT); + Kill_Notification(NOTIF_ALL, world, MSG_CENTER_CPID, CPID_KEYHUNT_OTHER); + + for(i = 0; i < kh_teams; ++i) + { + teem = kh_Team_ByID(i); + players = 0; + entity my_player = world; + FOR_EACH_PLAYER(player) + if(player.deadflag == DEAD_NO) + if(!player.BUTTON_CHAT) + if(player.team == teem) + { + ++players; + if(random() * players <= 1) + my_player = player; + } + kh_Key_Spawn(my_player, 360 * i / kh_teams, i); + } + + kh_tracking_enabled = false; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_KEYHUNT_SCAN, autocvar_g_balance_keyhunt_delay_tracking); + kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_tracking, kh_EnableTrackingDevice); +} + +float kh_HandleFrags(entity attacker, entity targ, float f) // adds to the player score +{ + if(attacker == targ) + return f; + + if(targ.kh_next) + { + if(attacker.team == targ.team) + { + entity k; + float nk; + nk = 0; + for(k = targ.kh_next; k != world; k = k.kh_next) + ++nk; + kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", -nk * autocvar_g_balance_keyhunt_score_collect, 0); + } + else + { + kh_Scores_Event(attacker, targ.kh_next, "carrierfrag", autocvar_g_balance_keyhunt_score_carrierfrag-1, 0); + PlayerScore_Add(attacker, SP_KH_KCKILLS, 1); + // the frag gets added later + } + } + + return f; +} + +void kh_Initialize() // sets up th KH environment +{ + // setup variables + kh_teams = autocvar_g_keyhunt_teams_override; + if(kh_teams < 2) + kh_teams = autocvar_g_keyhunt_teams; + kh_teams = bound(2, kh_teams, 4); + + // make a KH entity for controlling the game + kh_controller = spawn(); + kh_controller.think = kh_Controller_Think; + kh_Controller_SetThink(0, kh_WaitForPlayers); + + setmodel(kh_controller, MDL_KH_KEY); + kh_key_dropped = kh_controller.modelindex; + /* + dprint(vtos(kh_controller.mins)); + dprint(vtos(kh_controller.maxs)); + dprint("\n"); + */ +#ifdef KH_PLAYER_USE_CARRIEDMODEL + setmodel(kh_controller, MDL_KH_KEY_CARRIED); + kh_key_carried = kh_controller.modelindex; +#else + kh_key_carried = kh_key_dropped; +#endif + + kh_controller.model = ""; + kh_controller.modelindex = 0; + + addstat(STAT_KH_KEYS, AS_INT, kh_state); + + kh_ScoreRules(kh_teams); +} + +void kh_finalize() +{ + // to be called before intermission + kh_FinishRound(); + remove(kh_controller); + kh_controller = world; +} + +// legacy bot role + +void() havocbot_role_kh_carrier; +void() havocbot_role_kh_defense; +void() havocbot_role_kh_offense; +void() havocbot_role_kh_freelancer; + + +void havocbot_goalrating_kh(float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy) +{SELFPARAM(); + entity head; + for (head = kh_worldkeylist; head; head = head.kh_worldkeynext) + { + if(head.owner == self) + continue; + if(!kh_tracking_enabled) + { + // if it's carried by our team we know about it + // otherwise we have to see it to know about it + if(!head.owner || head.team != self.team) + { + traceline(self.origin + self.view_ofs, head.origin, MOVE_NOMONSTERS, self); + if (trace_fraction < 1 && trace_ent != head) + continue; // skip what I can't see + } + } + if(!head.owner) + navigation_routerating(head, ratingscale_dropped * BOT_PICKUP_RATING_HIGH, 100000); + else if(head.team == self.team) + navigation_routerating(head.owner, ratingscale_team * BOT_PICKUP_RATING_HIGH, 100000); + else + navigation_routerating(head.owner, ratingscale_enemy * BOT_PICKUP_RATING_HIGH, 100000); + } + + havocbot_goalrating_items(1, self.origin, 10000); +} + +void havocbot_role_kh_carrier() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if (!(self.kh_next)) + { + LOG_TRACE("changing role to freelancer\n"); + self.havocbot_role = havocbot_role_kh_freelancer; + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + if(kh_Key_AllOwnedByWhichTeam() == self.team) + havocbot_goalrating_kh(10, 0.1, 0.1); // bring home + else + havocbot_goalrating_kh(4, 4, 1); // play defensively + + navigation_goalrating_end(); + } +} + +void havocbot_role_kh_defense() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if (self.kh_next) + { + LOG_TRACE("changing role to carrier\n"); + self.havocbot_role = havocbot_role_kh_carrier; + self.havocbot_role_timeout = 0; + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + if (time > self.havocbot_role_timeout) + { + LOG_TRACE("changing role to freelancer\n"); + self.havocbot_role = havocbot_role_kh_freelancer; + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + float key_owner_team; + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + key_owner_team = kh_Key_AllOwnedByWhichTeam(); + if(key_owner_team == self.team) + havocbot_goalrating_kh(10, 0.1, 0.1); // defend key carriers + else if(key_owner_team == -1) + havocbot_goalrating_kh(4, 1, 0.1); // play defensively + else + havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(); + } +} + +void havocbot_role_kh_offense() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if (self.kh_next) + { + LOG_TRACE("changing role to carrier\n"); + self.havocbot_role = havocbot_role_kh_carrier; + self.havocbot_role_timeout = 0; + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 20; + if (time > self.havocbot_role_timeout) + { + LOG_TRACE("changing role to freelancer\n"); + self.havocbot_role = havocbot_role_kh_freelancer; + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + float key_owner_team; + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + key_owner_team = kh_Key_AllOwnedByWhichTeam(); + if(key_owner_team == self.team) + havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway + else if(key_owner_team == -1) + havocbot_goalrating_kh(0.1, 1, 4); // play offensively + else + havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK! EMERGENCY! + + navigation_goalrating_end(); + } +} + +void havocbot_role_kh_freelancer() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + if (self.kh_next) + { + LOG_TRACE("changing role to carrier\n"); + self.havocbot_role = havocbot_role_kh_carrier; + self.havocbot_role_timeout = 0; + return; + } + + if (!self.havocbot_role_timeout) + self.havocbot_role_timeout = time + random() * 10 + 10; + if (time > self.havocbot_role_timeout) + { + if (random() < 0.5) + { + LOG_TRACE("changing role to offense\n"); + self.havocbot_role = havocbot_role_kh_offense; + } + else + { + LOG_TRACE("changing role to defense\n"); + self.havocbot_role = havocbot_role_kh_defense; + } + self.havocbot_role_timeout = 0; + return; + } + + if (self.bot_strategytime < time) + { + float key_owner_team; + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + key_owner_team = kh_Key_AllOwnedByWhichTeam(); + if(key_owner_team == self.team) + havocbot_goalrating_kh(10, 0.1, 0.1); // defend anyway + else if(key_owner_team == -1) + havocbot_goalrating_kh(1, 10, 4); // prefer dropped keys + else + havocbot_goalrating_kh(0.1, 0.1, 10); // ATTACK ANYWAY + + navigation_goalrating_end(); + } +} + + +// register this as a mutator + +MUTATOR_HOOKFUNCTION(kh, ClientDisconnect) +{SELFPARAM(); + kh_Key_DropAll(self, true); + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver) +{SELFPARAM(); + kh_Key_DropAll(self, true); + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, PlayerDies) +{SELFPARAM(); + if(self == other) + kh_Key_DropAll(self, true); + else if(IS_PLAYER(other)) + kh_Key_DropAll(self, false); + else + kh_Key_DropAll(self, true); + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST) +{ + frag_score = kh_HandleFrags(frag_attacker, frag_target, frag_score); + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, MatchEnd) +{ + kh_finalize(); + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, GetTeamCount, CBC_ORDER_EXCLUSIVE) +{ + ret_float = kh_teams; + return false; +} + +MUTATOR_HOOKFUNCTION(kh, SpectateCopy) +{SELFPARAM(); + self.kh_state = other.kh_state; + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, PlayerUseKey) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE == 0) + { + entity k; + k = self.kh_next; + if(k) + { + kh_Key_DropOne(k); + return 1; + } + } + return 0; +} + +MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole) +{ + if(self.deadflag != DEAD_NO) + return true; + + float r = random() * 3; + if (r < 1) + self.havocbot_role = havocbot_role_kh_offense; + else if (r < 2) + self.havocbot_role = havocbot_role_kh_defense; + else + self.havocbot_role = havocbot_role_kh_freelancer; + + return true; +} + +MUTATOR_HOOKFUNCTION(kh, DropSpecialItems) +{ + kh_Key_DropAll(frag_target, false); + return false; +} + +MUTATOR_HOOKFUNCTION(kh, reset_map_global) +{ + kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round + (game_starttime - time), kh_StartRound); + return false; +} + +REGISTER_MUTATOR(kh, IS_GAMETYPE(KEYHUNT)) +{ + ActivateTeamplay(); + SetLimits(autocvar_g_keyhunt_point_limit, autocvar_g_keyhunt_point_leadlimit, -1, -1); + if(autocvar_g_keyhunt_team_spawns) + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + kh_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back kh_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_lms.qc b/qcsrc/server/mutators/mutator/gamemode_lms.qc new file mode 100644 index 000000000..30789a520 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_lms.qc @@ -0,0 +1,313 @@ +#ifndef GAMEMODE_LMS_H +#define GAMEMODE_LMS_H + +// scoreboard stuff +const float SP_LMS_LIVES = 4; +const float SP_LMS_RANK = 5; + +// lives related defs +float lms_lowest_lives; +float lms_next_place; +float LMS_NewPlayerLives(); + +#endif + +#ifdef IMPLEMENTATION + +#include "../../campaign.qh" +#include "../../command/cmd.qh" + +int autocvar_g_lms_extra_lives; +bool autocvar_g_lms_join_anytime; +int autocvar_g_lms_last_join; +#define autocvar_g_lms_lives_override cvar("g_lms_lives_override") +bool autocvar_g_lms_regenerate; + +// main functions +float LMS_NewPlayerLives() +{ + float fl; + fl = autocvar_fraglimit; + if(fl == 0) + fl = 999; + + // first player has left the game for dying too much? Nobody else can get in. + if(lms_lowest_lives < 1) + return 0; + + if(!autocvar_g_lms_join_anytime) + if(lms_lowest_lives < fl - autocvar_g_lms_last_join) + return 0; + + return bound(1, lms_lowest_lives, fl); +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(lms, reset_map_global) +{ + lms_lowest_lives = 999; + lms_next_place = player_count; + + return false; +} + +MUTATOR_HOOKFUNCTION(lms, reset_map_players) +{SELFPARAM(); + entity e; + if(restart_mapalreadyrestarted || (time < game_starttime)) + FOR_EACH_CLIENT(e) + if(IS_PLAYER(e)) + { + WITH(entity, self, e, PlayerScore_Add(e, SP_LMS_LIVES, LMS_NewPlayerLives())); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(lms, PutClientInServer) +{SELFPARAM(); + // player is dead and becomes observer + // FIXME fix LMS scoring for new system + if(PlayerScore_Add(self, SP_LMS_RANK, 0) > 0) + { + self.classname = "observer"; + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_LMS_NOLIVES); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(lms, PlayerDies) +{SELFPARAM(); + self.respawn_flags |= RESPAWN_FORCE; + + return false; +} + +void lms_RemovePlayer(entity player) +{ + // Only if the player cannot play at all + if(PlayerScore_Add(player, SP_LMS_RANK, 0) == 666) + player.frags = FRAGS_SPECTATOR; + else + player.frags = FRAGS_LMS_LOSER; + + if(player.killcount != -666) + if(PlayerScore_Add(player, SP_LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2) + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_NOLIVES, player.netname); + else + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_LMS_FORFEIT, player.netname); +} + +MUTATOR_HOOKFUNCTION(lms, ClientDisconnect) +{SELFPARAM(); + lms_RemovePlayer(self); + return false; +} + +MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver) +{SELFPARAM(); + lms_RemovePlayer(self); + return false; +} + +MUTATOR_HOOKFUNCTION(lms, ClientConnect) +{SELFPARAM(); + self.classname = "player"; + campaign_bots_may_start = 1; + + if(PlayerScore_Add(self, SP_LMS_LIVES, LMS_NewPlayerLives()) <= 0) + { + PlayerScore_Add(self, SP_LMS_RANK, 666); + self.frags = FRAGS_SPECTATOR; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(lms, PlayerPreThink) +{SELFPARAM(); + if(self.deadflag == DEAD_DYING) + self.deadflag = DEAD_RESPAWNING; + + return false; +} + +MUTATOR_HOOKFUNCTION(lms, PlayerRegen) +{ + if(autocvar_g_lms_regenerate) + return false; + return true; +} + +MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon) +{ + // forbode! + return true; +} + +MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill) +{ + // remove a life + float tl; + tl = PlayerScore_Add(frag_target, SP_LMS_LIVES, -1); + if(tl < lms_lowest_lives) + lms_lowest_lives = tl; + if(tl <= 0) + { + if(!lms_next_place) + lms_next_place = player_count; + else + lms_next_place = min(lms_next_place, player_count); + PlayerScore_Add(frag_target, SP_LMS_RANK, lms_next_place); // won't ever spawn again + --lms_next_place; + } + frag_score = 0; + + return true; +} + +MUTATOR_HOOKFUNCTION(lms, SetStartItems) +{ + start_items &= ~IT_UNLIMITED_AMMO; + start_health = warmup_start_health = cvar("g_lms_start_health"); + start_armorvalue = warmup_start_armorvalue = cvar("g_lms_start_armor"); + start_ammo_shells = warmup_start_ammo_shells = cvar("g_lms_start_ammo_shells"); + start_ammo_nails = warmup_start_ammo_nails = cvar("g_lms_start_ammo_nails"); + start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_lms_start_ammo_rockets"); + start_ammo_cells = warmup_start_ammo_cells = cvar("g_lms_start_ammo_cells"); + start_ammo_plasma = warmup_start_ammo_plasma = cvar("g_lms_start_ammo_plasma"); + start_ammo_fuel = warmup_start_ammo_fuel = cvar("g_lms_start_ammo_fuel"); + + return false; +} + +MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear) +{ + // don't clear player score + return true; +} + +MUTATOR_HOOKFUNCTION(lms, FilterItem) +{SELFPARAM(); + if(autocvar_g_lms_extra_lives) + if(self.itemdef == ITEM_HealthMega) + { + self.max_health = 1; + return false; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(lms, ItemTouch) +{SELFPARAM(); + // give extra lives for mega health + if (self.items & ITEM_HealthMega.m_itemid) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_EXTRALIVES); + PlayerScore_Add(other, SP_LMS_LIVES, autocvar_g_lms_extra_lives); + } + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE) +{ + entity head; + FOR_EACH_REALCLIENT(head) + { + ++bot_activerealplayers; + ++bot_realplayers; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate) +{ + if(self.lms_spectate_warning) + { + // for the forfeit message... + self.lms_spectate_warning = 2; + // mark player as spectator + PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0)); + } + else + { + self.lms_spectate_warning = 1; + sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n"); + return MUT_SPECCMD_RETURN; + } + return MUT_SPECCMD_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(lms, CheckRules_World) +{ + ret_float = WinningCondition_LMS(); + return true; +} + +MUTATOR_HOOKFUNCTION(lms, WantWeapon) +{ + want_allguns = true; + return false; +} + +MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(lms, AddPlayerScore) +{ + if(gameover) + if(score_field == SP_LMS_RANK) + return true; // allow writing to this field in intermission as it is needed for newly joining players + return false; +} + +// scoreboard stuff +void lms_ScoreRules() +{ + ScoreRules_basics(0, 0, 0, false); + ScoreInfo_SetLabel_PlayerScore(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE); + ScoreRules_basics_end(); +} + +void lms_Initialize() +{ + lms_lowest_lives = 9999; + lms_next_place = 0; + + lms_ScoreRules(); +} + +REGISTER_MUTATOR(lms, IS_GAMETYPE(LMS)) +{ + SetLimits(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override), 0, -1, -1); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + lms_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back lms_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_onslaught.qc b/qcsrc/server/mutators/mutator/gamemode_onslaught.qc new file mode 100644 index 000000000..d01b48ca4 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_onslaught.qc @@ -0,0 +1,2315 @@ +#ifndef GAMEMODE_ONSLAUGHT_H +#define GAMEMODE_ONSLAUGHT_H + +#ifdef SVQC + +.entity ons_toucher; // player who touched the control point + +// control point / generator constants +const float ONS_CP_THINKRATE = 0.2; +const float GEN_THINKRATE = 1; +#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13)) +const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128'); +const vector CPICON_OFFSET = ('0 0 96'); + +// list of generators on the map +entity ons_worldgeneratorlist; +.entity ons_worldgeneratornext; +.entity ons_stalegeneratornext; + +// list of control points on the map +entity ons_worldcplist; +.entity ons_worldcpnext; +.entity ons_stalecpnext; + +// list of links on the map +entity ons_worldlinklist; +.entity ons_worldlinknext; +.entity ons_stalelinknext; + +// definitions +.entity sprite; +.string target2; +.int iscaptured; +.int islinked; +.int isshielded; +.float lasthealth; +.int lastteam; +.int lastshielded; +.int lastcaptured; + +.bool waslinked; + +bool ons_stalemate; + +.float teleport_antispam; + +.bool ons_roundlost; + +// waypoint sprites +.entity bot_basewaypoint; // generator waypointsprite + +.bool isgenneighbor[17]; +.bool iscpneighbor[17]; +float ons_notification_time[17]; + +.float ons_overtime_damagedelay; + +.vector ons_deathloc; + +.entity ons_spawn_by; + +// declarations for functions used outside gamemode_onslaught.qc +void ons_Generator_UpdateSprite(entity e); +void ons_ControlPoint_UpdateSprite(entity e); +bool ons_ControlPoint_Attackable(entity cp, int teamnumber); + +// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet +float ons_captureshield_force; // push force of the shield + +// bot player logic +const int HAVOCBOT_ONS_ROLE_NONE = 0; +const int HAVOCBOT_ONS_ROLE_DEFENSE = 2; +const int HAVOCBOT_ONS_ROLE_ASSISTANT = 4; +const int HAVOCBOT_ONS_ROLE_OFFENSE = 8; + +.entity havocbot_ons_target; + +.int havocbot_role_flags; +.float havocbot_attack_time; + +void havocbot_role_ons_defense(); +void havocbot_role_ons_offense(); +void havocbot_role_ons_assistant(); + +void havocbot_ons_reset_role(entity bot); +void havocbot_goalrating_items(float ratingscale, vector org, float sradius); +void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius); + +// score rule declarations +const int ST_ONS_CAPS = 1; +const int SP_ONS_CAPS = 4; +const int SP_ONS_TAKES = 6; + +#endif +#endif + +#ifdef IMPLEMENTATION + +#include "../../controlpoint.qh" +#include "../../generator.qh" + +bool g_onslaught; + +float autocvar_g_onslaught_debug; +float autocvar_g_onslaught_teleport_wait; +bool autocvar_g_onslaught_spawn_at_controlpoints; +bool autocvar_g_onslaught_spawn_at_generator; +float autocvar_g_onslaught_cp_proxydecap; +float autocvar_g_onslaught_cp_proxydecap_distance = 512; +float autocvar_g_onslaught_cp_proxydecap_dps = 100; +float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5; +float autocvar_g_onslaught_spawn_at_controlpoints_random; +float autocvar_g_onslaught_spawn_at_generator_chance; +float autocvar_g_onslaught_spawn_at_generator_random; +float autocvar_g_onslaught_cp_buildhealth; +float autocvar_g_onslaught_cp_buildtime; +float autocvar_g_onslaught_cp_health; +float autocvar_g_onslaught_cp_regen; +float autocvar_g_onslaught_gen_health; +float autocvar_g_onslaught_shield_force = 100; +float autocvar_g_onslaught_allow_vehicle_touch; +float autocvar_g_onslaught_round_timelimit; +float autocvar_g_onslaught_point_limit; +float autocvar_g_onslaught_warmup; +float autocvar_g_onslaught_teleport_radius; +float autocvar_g_onslaught_spawn_choose; +float autocvar_g_onslaught_click_radius; + +void FixSize(entity e); + +// ======================= +// CaptureShield Functions +// ======================= + +bool ons_CaptureShield_Customize() +{SELFPARAM(); + entity e = WaypointSprite_getviewentity(other); + + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; } + if(SAME_TEAM(self, e)) { return false; } + + return true; +} + +void ons_CaptureShield_Touch() +{SELFPARAM(); + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; } + if(!IS_PLAYER(other)) { return; } + if(SAME_TEAM(other, self)) { return; } + + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; + + Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ons_captureshield_force); + + if(IS_REAL_CLIENT(other)) + { + play2(other, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); + + if(self.enemy.classname == "onslaught_generator") + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); + else + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); + } +} + +void ons_CaptureShield_Reset() +{SELFPARAM(); + self.colormap = self.enemy.colormap; + self.team = self.enemy.team; +} + +void ons_CaptureShield_Spawn(entity generator, bool is_generator) +{ + entity shield = spawn(); + + shield.enemy = generator; + shield.team = generator.team; + shield.colormap = generator.colormap; + shield.reset = ons_CaptureShield_Reset; + shield.touch = ons_CaptureShield_Touch; + shield.customizeentityforclient = ons_CaptureShield_Customize; + shield.classname = "ons_captureshield"; + shield.effects = EF_ADDITIVE; + shield.movetype = MOVETYPE_NOCLIP; + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = 1; + shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3"); + + precache_model(shield.model); + setorigin(shield, generator.origin); + _setmodel(shield, shield.model); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); +} + + +// ========== +// Junk Pile +// ========== + +void ons_debug(string input) +{ + switch(autocvar_g_onslaught_debug) + { + case 1: LOG_TRACE(input); break; + case 2: LOG_INFO(input); break; + } +} + +void setmodel_fixsize(entity e, Model m) +{ + setmodel(e, m); + FixSize(e); +} + +void onslaught_updatelinks() +{ + entity l; + // first check if the game has ended + ons_debug("--- updatelinks ---\n"); + // mark generators as being shielded and networked + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + if (l.iscaptured) + ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n")); + else + ons_debug(strcat(etos(l), " (generator) is destroyed\n")); + l.islinked = l.iscaptured; + l.isshielded = l.iscaptured; + l.sprite.SendFlags |= 16; + } + // mark points as shielded and not networked + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.islinked = false; + l.isshielded = true; + int i; + for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; } + ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n")); + l.sprite.SendFlags |= 16; + } + // flow power outward from the generators through the network + bool stop = false; + while (!stop) + { + stop = true; + for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) + { + // 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.iscaptured && l.goalentity.iscaptured) + if (l.enemy.islinked != l.goalentity.islinked) + if(SAME_TEAM(l.enemy, l.goalentity)) + { + if (!l.goalentity.islinked) + { + stop = false; + l.goalentity.islinked = true; + ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n")); + } + else if (!l.enemy.islinked) + { + stop = false; + l.enemy.islinked = true; + ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n")); + } + } + } + } + // now that we know which points are powered we can mark their neighbors + // as unshielded if team differs + for(l = ons_worldlinklist; l; l = l.ons_worldlinknext) + { + if (l.goalentity.islinked) + { + if(DIFF_TEAM(l.goalentity, l.enemy)) + { + ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n")); + l.enemy.isshielded = false; + } + if(l.goalentity.classname == "onslaught_generator") + l.enemy.isgenneighbor[l.goalentity.team] = true; + else + l.enemy.iscpneighbor[l.goalentity.team] = true; + } + if (l.enemy.islinked) + { + if(DIFF_TEAM(l.goalentity, l.enemy)) + { + ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n")); + l.goalentity.isshielded = false; + } + if(l.enemy.classname == "onslaught_generator") + l.goalentity.isgenneighbor[l.enemy.team] = true; + else + l.goalentity.iscpneighbor[l.enemy.team] = true; + } + } + // now update the generators + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + if (l.isshielded) + { + ons_debug(strcat(etos(l), " (generator) is shielded\n")); + l.takedamage = DAMAGE_NO; + l.bot_attack = false; + } + else + { + ons_debug(strcat(etos(l), " (generator) is not shielded\n")); + l.takedamage = DAMAGE_AIM; + l.bot_attack = true; + } + + ons_Generator_UpdateSprite(l); + } + // now update the takedamage and alpha variables on control point icons + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + if (l.isshielded) + { + ons_debug(strcat(etos(l), " (point) is shielded\n")); + if (l.goalentity) + { + l.goalentity.takedamage = DAMAGE_NO; + l.goalentity.bot_attack = false; + } + } + else + { + ons_debug(strcat(etos(l), " (point) is not shielded\n")); + if (l.goalentity) + { + l.goalentity.takedamage = DAMAGE_AIM; + l.goalentity.bot_attack = true; + } + } + ons_ControlPoint_UpdateSprite(l); + } + l = findchain(classname, "ons_captureshield"); + while(l) + { + l.team = l.enemy.team; + l.colormap = l.enemy.colormap; + l = l.chain; + } +} + + +// =================== +// Main Link Functions +// =================== + +bool ons_Link_Send(entity this, entity to, int sendflags) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK); + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & 1) + { + WriteCoord(MSG_ENTITY, self.goalentity.origin_x); + WriteCoord(MSG_ENTITY, self.goalentity.origin_y); + WriteCoord(MSG_ENTITY, self.goalentity.origin_z); + } + if(sendflags & 2) + { + WriteCoord(MSG_ENTITY, self.enemy.origin_x); + WriteCoord(MSG_ENTITY, self.enemy.origin_y); + WriteCoord(MSG_ENTITY, self.enemy.origin_z); + } + if(sendflags & 4) + { + WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16 + } + return true; +} + +void ons_Link_CheckUpdate() +{SELFPARAM(); + // TODO check if the two sides have moved (currently they won't move anyway) + float cc = 0, cc1 = 0, cc2 = 0; + + if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; } + if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; } + + cc = cc1 + cc2; + + if(cc != self.clientcolors) + { + self.clientcolors = cc; + self.SendFlags |= 4; + } + + self.nextthink = time; +} + +void ons_DelayedLinkSetup() +{SELFPARAM(); + 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"); } + + ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n")); + self.SendFlags |= 3; + self.think = ons_Link_CheckUpdate; + self.nextthink = time; +} + + +// ============================= +// Main Control Point Functions +// ============================= + +int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber) +{ + if(cp.isgenneighbor[teamnumber]) { return 2; } + if(cp.iscpneighbor[teamnumber]) { return 1; } + + return 0; +} + +int ons_ControlPoint_Attackable(entity cp, int teamnumber) + // -2: SAME TEAM, attackable by enemy! + // -1: SAME TEAM! + // 0: off limits + // 1: attack it + // 2: touch it + // 3: attack it (HIGH PRIO) + // 4: touch it (HIGH PRIO) +{ + int a; + + if(cp.isshielded) + { + return 0; + } + else if(cp.goalentity) + { + // if there's already an icon built, nothing happens + if(cp.team == teamnumber) + { + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); + if(a) // attackable by enemy? + return -2; // EMERGENCY! + return -1; + } + // we know it can be linked, so no need to check + // but... + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); + if(a == 2) // near our generator? + return 3; // EMERGENCY! + return 1; + } + else + { + // free point + if(ons_ControlPoint_CanBeLinked(cp, teamnumber)) + { + a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t + if(a == 2) + return 4; // GET THIS ONE NOW! + else + return 2; // TOUCH ME + } + } + return 0; +} + +void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{SELFPARAM(); + if(damage <= 0) { return; } + + if (self.owner.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (IS_PLAYER(attacker)) + { + play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); + self.pain_finished = time + 1; + attacker.typehitsound += 1; // play both sounds (shield is way too quiet) + } + + return; + } + + if(IS_PLAYER(attacker)) + if(time - ons_notification_time[self.team] > 10) + { + play2team(self.team, SND(ONS_CONTROLPOINT_UNDERATTACK)); + ons_notification_time[self.team] = time; + } + + self.health = self.health - damage; + if(self.owner.iscaptured) + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + else + WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE)); + self.pain_finished = time + 1; + // particles on every hit + pointparticles(particleeffectnum(EFFECT_SPARKS), hitloc, force*-1, 1); + //sound on every hit + if (random() < 0.5) + sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM); + else + sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM); + + if (self.health < 0) + { + sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), self.origin, '0 0 0', 1); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname); + + PlayerScore_Add(attacker, SP_ONS_TAKES, 1); + PlayerScore_Add(attacker, SP_SCORE, 10); + + self.owner.goalentity = world; + self.owner.islinked = false; + self.owner.iscaptured = false; + self.owner.team = 0; + self.owner.colormap = 1024; + + WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0); + + onslaught_updatelinks(); + + // Use targets now (somebody make sure this is in the right place..) + setself(self.owner); + activator = self; + SUB_UseTargets (); + setself(this); + + self.owner.waslinked = self.owner.islinked; + if(self.owner.model != "models/onslaught/controlpoint_pad.md3") + setmodel_fixsize(self.owner, MDL_ONS_CP_PAD1); + //setsize(self, '-32 -32 0', '32 32 8'); + + remove(self); + } + + self.SendFlags |= CPSF_STATUS; +} + +void ons_ControlPoint_Icon_Think() +{SELFPARAM(); + self.nextthink = time + ONS_CP_THINKRATE; + + if(autocvar_g_onslaught_cp_proxydecap) + { + int _enemy_count = 0; + int _friendly_count = 0; + float _dist; + entity _player; + + FOR_EACH_PLAYER(_player) + { + if(!_player.deadflag) + { + _dist = vlen(_player.origin - self.origin); + if(_dist < autocvar_g_onslaught_cp_proxydecap_distance) + { + if(SAME_TEAM(_player, self)) + ++_friendly_count; + else + ++_enemy_count; + } + } + } + + _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + + self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health); + self.SendFlags |= CPSF_STATUS; + if(self.health <= 0) + { + ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0'); + return; + } + } + + if (time > self.pain_finished + 5) + { + if(self.health < self.max_health) + { + self.health = self.health + self.count; + if (self.health >= self.max_health) + self.health = self.max_health; + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + } + } + + if(self.owner.islinked != self.owner.waslinked) + { + // unteam the spawnpoint if needed + int t = self.owner.team; + if(!self.owner.islinked) + self.owner.team = 0; + + setself(self.owner); + activator = self; + SUB_UseTargets (); + setself(this); + + self.owner.team = t; + + self.owner.waslinked = self.owner.islinked; + } + + // damaged fx + if(random() < 0.6 - self.health / self.max_health) + { + Send_Effect(EFFECT_ELECTRIC_SPARKS, self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); + + if(random() > 0.8) + sound(self, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM); + else if (random() > 0.5) + sound(self, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM); + } +} + +void ons_ControlPoint_Icon_BuildThink() +{SELFPARAM(); + int a; + + self.nextthink = time + ONS_CP_THINKRATE; + + // only do this if there is power + a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team); + if(!a) + return; + + self.health = self.health + self.count; + + self.SendFlags |= CPSF_STATUS; + + if (self.health >= self.max_health) + { + self.health = self.max_health; + self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on + self.think = ons_ControlPoint_Icon_Think; + sound(self, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM); + self.owner.iscaptured = true; + self.solid = SOLID_BBOX; + + Send_Effect(EFFECT_CAP(self.owner.team), self.owner.origin, '0 0 0', 1); + + WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + + if(IS_PLAYER(self.owner.ons_toucher)) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message); + Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message); + Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message); + PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1); + PlayerTeamScore_AddScore(self.owner.ons_toucher, 10); + } + + self.owner.ons_toucher = world; + + onslaught_updatelinks(); + + // Use targets now (somebody make sure this is in the right place..) + setself(self.owner); + activator = self; + SUB_UseTargets (); + setself(this); + + self.SendFlags |= CPSF_SETUP; + } + if(self.owner.model != MDL_ONS_CP_PAD2.model_str()) + setmodel_fixsize(self.owner, MDL_ONS_CP_PAD2); + + if(random() < 0.9 - self.health / self.max_health) + Send_Effect(EFFECT_RAGE, self.origin + 10 * randomvec(), '0 0 -1', 1); +} + +void onslaught_controlpoint_icon_link(entity e, void() spawnproc); + +void ons_ControlPoint_Icon_Spawn(entity cp, entity player) +{ + entity e = spawn(); + + setsize(e, CPICON_MIN, CPICON_MAX); + setorigin(e, cp.origin + CPICON_OFFSET); + + e.classname = "onslaught_controlpoint_icon"; + e.owner = cp; + e.max_health = autocvar_g_onslaught_cp_health; + e.health = autocvar_g_onslaught_cp_buildhealth; + e.solid = SOLID_NOT; + e.takedamage = DAMAGE_AIM; + e.bot_attack = true; + e.event_damage = ons_ControlPoint_Icon_Damage; + e.team = player.team; + e.colormap = 1024 + (e.team - 1) * 17; + e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build + + sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM); + + cp.goalentity = e; + cp.team = e.team; + cp.colormap = e.colormap; + + Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1); + + WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE)); + WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY); + cp.sprite.SendFlags |= 16; + + onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink); +} + +entity ons_ControlPoint_Waypoint(entity e) +{ + if(e.team) + { + int a = ons_ControlPoint_Attackable(e, e.team); + + if(a == -2) { return WP_OnsCPDefend; } // defend now + if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch + if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack + } + else + return WP_OnsCP; + + return WP_Null; +} + +void ons_ControlPoint_UpdateSprite(entity e) +{ + entity s1 = ons_ControlPoint_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); + + bool sh; + sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4)); + + if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured) + { + if(e.iscaptured) // don't mess up build bars! + { + if(sh) + { + WaypointSprite_UpdateMaxHealth(e.sprite, 0); + } + else + { + WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health); + WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health); + } + } + if(e.lastshielded) + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5'); + } + else + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75'); + } + WaypointSprite_Ping(e.sprite); + + e.lastteam = e.team + 2; + e.lastshielded = sh; + e.lastcaptured = e.iscaptured; + } +} + +void ons_ControlPoint_Touch() +{SELFPARAM(); + entity toucher = other; + int attackable; + + if(IS_VEHICLE(toucher) && toucher.owner) + if(autocvar_g_onslaught_allow_vehicle_touch) + toucher = toucher.owner; + else + return; + + if(!IS_PLAYER(toucher)) { return; } + if(toucher.frozen) { return; } + if(toucher.deadflag != DEAD_NO) { return; } + + if ( SAME_TEAM(self,toucher) ) + if ( self.iscaptured ) + { + if(time <= toucher.teleport_antispam) + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time)); + else + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); + } + + attackable = ons_ControlPoint_Attackable(self, toucher.team); + if(attackable != 2 && attackable != 4) + 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) + ons_ControlPoint_Icon_Spawn(self, toucher); + + self.ons_toucher = toucher; + + onslaught_updatelinks(); +} + +void ons_ControlPoint_Think() +{SELFPARAM(); + self.nextthink = time + ONS_CP_THINKRATE; + CSQCMODEL_AUTOUPDATE(self); +} + +void ons_ControlPoint_Reset() +{SELFPARAM(); + if(self.goalentity) + remove(self.goalentity); + + self.goalentity = world; + self.team = 0; + self.colormap = 1024; + self.iscaptured = false; + self.islinked = false; + self.isshielded = true; + self.think = ons_ControlPoint_Think; + self.ons_toucher = world; + self.nextthink = time + ONS_CP_THINKRATE; + setmodel_fixsize(self, MDL_ONS_CP_PAD1); + + WaypointSprite_UpdateMaxHealth(self.sprite, 0); + WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); + + onslaught_updatelinks(); + + activator = self; + SUB_UseTargets(); // to reset the structures, playerspawns etc. + + CSQCMODEL_AUTOUPDATE(self); +} + +void ons_DelayedControlPoint_Setup(void) +{SELFPARAM(); + onslaught_updatelinks(); + + // captureshield setup + ons_CaptureShield_Spawn(self, false); + + CSQCMODEL_AUTOINIT(self); +} + +void ons_ControlPoint_Setup(entity cp) +{SELFPARAM(); + // declarations + setself(cp); // for later usage with droptofloor() + + // main setup + cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist + ons_worldcplist = cp; + + cp.netname = "Control point"; + cp.team = 0; + cp.solid = SOLID_BBOX; + cp.movetype = MOVETYPE_NONE; + cp.touch = ons_ControlPoint_Touch; + cp.think = ons_ControlPoint_Think; + cp.nextthink = time + ONS_CP_THINKRATE; + cp.reset = ons_ControlPoint_Reset; + cp.colormap = 1024; + cp.iscaptured = false; + cp.islinked = false; + cp.isshielded = true; + + if(cp.message == "") { cp.message = "a"; } + + // appearence + setmodel_fixsize(cp, MDL_ONS_CP_PAD1); + + // control point placement + if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location + { + cp.noalign = true; + cp.movetype = MOVETYPE_NONE; + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + setorigin(cp, cp.origin + '0 0 20'); + cp.noalign = false; + setself(cp); + droptofloor(); + cp.movetype = MOVETYPE_TOSS; + } + + // waypointsprites + WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE); + WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); + + InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION); +} + + +// ========================= +// Main Generator Functions +// ========================= + +entity ons_Generator_Waypoint(entity e) +{ + if (e.isshielded) + return WP_OnsGenShielded; + return WP_OnsGen; +} + +void ons_Generator_UpdateSprite(entity e) +{ + entity s1 = ons_Generator_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); + + if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) + { + e.lastteam = e.team + 2; + e.lastshielded = e.isshielded; + if(e.lastshielded) + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5'); + } + else + { + if(e.team) + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); + } + WaypointSprite_Ping(e.sprite); + } +} + +void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{SELFPARAM(); + if(damage <= 0) { return; } + if(warmup_stage || gameover) { return; } + if(!round_handler_IsRoundStarted()) { return; } + + if (attacker != self) + { + if (self.isshielded) + { + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (IS_PLAYER(attacker)) + { + play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD)); + attacker.typehitsound += 1; + self.pain_finished = time + 1; + } + return; + } + if (time > self.pain_finished) + { + self.pain_finished = time + 10; + entity head; + FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); } + play2team(self.team, SND(ONS_GENERATOR_UNDERATTACK)); + } + } + self.health = self.health - damage; + WaypointSprite_UpdateHealth(self.sprite, self.health); + // 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) + { + self.lasthealth = self.health; + } + else + { + if (attacker == self) + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_)); + else + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_)); + PlayerScore_Add(attacker, SP_SCORE, 100); + } + self.iscaptured = false; + self.islinked = false; + self.isshielded = false; + self.takedamage = DAMAGE_NO; // can't be hurt anymore + self.event_damage = func_null; // won't do anything if hurt + self.count = 0; // reset counter + self.think = func_null; + self.nextthink = 0; + //self.think(); // do the first explosion now + + WaypointSprite_UpdateMaxHealth(self.sprite, 0); + WaypointSprite_Ping(self.sprite); + //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor + + onslaught_updatelinks(); + } + + // Throw some flaming gibs on damage, more damage = more chance for gib + if(random() < damage/220) + { + sound(self, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + } + else + { + // particles on every hit + Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1); + + //sound on every hit + if (random() < 0.5) + sound(self, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM); + else + sound(self, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM); + } + + self.SendFlags |= GSF_STATUS; +} + +void ons_GeneratorThink() +{SELFPARAM(); + entity e; + self.nextthink = time + GEN_THINKRATE; + if (!gameover) + { + if(!self.isshielded && self.wait < time) + { + self.wait = time + 5; + FOR_EACH_REALPLAYER(e) + { + if(SAME_TEAM(e, self)) + { + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM); + soundto(MSG_ONE, e, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound? + } + else + Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_)); + } + } + } +} + +void ons_GeneratorReset() +{SELFPARAM(); + self.team = self.team_saved; + self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; + self.takedamage = DAMAGE_AIM; + self.bot_attack = true; + self.iscaptured = true; + self.islinked = true; + self.isshielded = true; + self.event_damage = ons_GeneratorDamage; + self.think = ons_GeneratorThink; + self.nextthink = time + GEN_THINKRATE; + + Net_LinkEntity(self, false, 0, generator_send); + + self.SendFlags = GSF_SETUP; // just incase + self.SendFlags |= GSF_STATUS; + + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); + + onslaught_updatelinks(); +} + +void ons_DelayedGeneratorSetup() +{SELFPARAM(); + // bot waypoints + waypoint_spawnforitem_force(self, self.origin); + self.nearestwaypointtimeout = 0; // activate waypointing again + self.bot_basewaypoint = self.nearestwaypoint; + + // captureshield setup + ons_CaptureShield_Spawn(self, true); + + onslaught_updatelinks(); + + Net_LinkEntity(self, false, 0, generator_send); +} + + +void onslaught_generator_touch() +{SELFPARAM(); + if ( IS_PLAYER(other) ) + if ( SAME_TEAM(self,other) ) + if ( self.iscaptured ) + { + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT); + } +} + +void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc +{SELFPARAM(); + // declarations + int teamnumber = gen.team; + setself(gen); // for later usage with droptofloor() + + // main setup + gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist + ons_worldgeneratorlist = gen; + + gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber)); + gen.classname = "onslaught_generator"; + gen.solid = SOLID_BBOX; + gen.team_saved = teamnumber; + gen.movetype = MOVETYPE_NONE; + gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health; + gen.takedamage = DAMAGE_AIM; + gen.bot_attack = true; + gen.event_damage = ons_GeneratorDamage; + gen.reset = ons_GeneratorReset; + gen.think = ons_GeneratorThink; + gen.nextthink = time + GEN_THINKRATE; + gen.iscaptured = true; + gen.islinked = true; + gen.isshielded = true; + gen.touch = onslaught_generator_touch; + + // appearence + // model handled by CSQC + setsize(gen, GENERATOR_MIN, GENERATOR_MAX); + setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET)); + gen.colormap = 1024 + (teamnumber - 1) * 17; + + // generator placement + setself(gen); + droptofloor(); + + // waypointsprites + WaypointSprite_SpawnFixed(WP_Null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE); + WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + + InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION); +} + + +// =============== +// Round Handler +// =============== + +int total_generators; +void Onslaught_count_generators() +{ + entity e; + total_generators = redowned = blueowned = yellowowned = pinkowned = 0; + for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext) + { + ++total_generators; + redowned += (e.team == NUM_TEAM_1 && e.health > 0); + blueowned += (e.team == NUM_TEAM_2 && e.health > 0); + yellowowned += (e.team == NUM_TEAM_3 && e.health > 0); + pinkowned += (e.team == NUM_TEAM_4 && e.health > 0); + } +} + +int Onslaught_GetWinnerTeam() +{ + int winner_team = 0; + if(redowned > 0) + winner_team = NUM_TEAM_1; + if(blueowned > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_2; + } + if(yellowowned > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_3; + } + if(pinkowned > 0) + { + if(winner_team) return 0; + winner_team = NUM_TEAM_4; + } + if(winner_team) + return winner_team; + return -1; // no generators left? +} + +#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) +#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1) +bool Onslaught_CheckWinner() +{ + entity e; + + if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)) + { + ons_stalemate = true; + + if (!wpforenemy_announced) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); + sound(world, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE); + + wpforenemy_announced = true; + } + + entity tmp_entity; // temporary entity + float d; + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay) + { + // tmp_entity.max_health / 300 gives 5 minutes of overtime. + // control points reduce the overtime duration. + d = 1; + for(e = ons_worldcplist; e; e = e.ons_worldcpnext) + { + if(DIFF_TEAM(e, tmp_entity)) + if(e.islinked) + d = d + 1; + } + + if(autocvar_g_campaign && autocvar__campaign_testrun) + d = d * tmp_entity.max_health; + else + d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath); + + Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0'); + + tmp_entity.sprite.SendFlags |= 16; + + tmp_entity.ons_overtime_damagedelay = time + 1; + } + } + else { wpforenemy_announced = false; ons_stalemate = false; } + + Onslaught_count_generators(); + + if(ONS_OWNED_GENERATORS_OK()) + return 0; + + int winner_team = Onslaught_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_ONS_CAPS, +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); + } + + ons_stalemate = false; + + play2all(SND(CTF_CAPTURE(winner_team))); + + round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); + + FOR_EACH_PLAYER(e) + { + e.ons_roundlost = true; + e.player_blocked = true; + + nades_Clear(e); + } + + return 1; +} + +bool Onslaught_CheckPlayers() +{ + return 1; +} + +void Onslaught_RoundStart() +{ + entity tmp_entity; + FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; } + + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + tmp_entity.sprite.SendFlags |= 16; + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + tmp_entity.sprite.SendFlags |= 16; +} + + +// ================ +// Bot player logic +// ================ + +// NOTE: LEGACY CODE, needs to be re-written! + +void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius) +{SELFPARAM(); + entity head; + float t, c; + int i; + bool needarmor = false, needweapons = false; + + // Needs armor/health? + if(self.health<100) + needarmor = true; + + // Needs weapons? + c = 0; + for(i = WEP_FIRST; i <= WEP_LAST ; ++i) + { + // Find weapon + if(self.weapons & WepSet_FromWeapon(i)) + if(++c>=4) + break; + } + + if(c<4) + needweapons = true; + + if(!needweapons && !needarmor) + return; + + ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n")); + ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n")); + + // See what is around + head = findchainfloat(bot_pickup, true); + while (head) + { + // gather health and armor only + if (head.solid) + if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) ) + if (vlen(head.origin - org) < sradius) + { + t = head.bot_pickupevalfunc(self, head); + if (t > 0) + navigation_routerating(head, t * ratingscale, 500); + } + head = head.chain; + } +} + +void havocbot_role_ons_setrole(entity bot, int role) +{ + ons_debug(strcat(bot.netname," switched to ")); + switch(role) + { + case HAVOCBOT_ONS_ROLE_DEFENSE: + ons_debug("defense"); + bot.havocbot_role = havocbot_role_ons_defense; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_ASSISTANT: + ons_debug("assistant"); + bot.havocbot_role = havocbot_role_ons_assistant; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_OFFENSE: + ons_debug("offense"); + bot.havocbot_role = havocbot_role_ons_offense; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; + bot.havocbot_role_timeout = 0; + break; + } + ons_debug("\n"); +} + +int havocbot_ons_teamcount(entity bot, int role) +{SELFPARAM(); + int c = 0; + entity head; + + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, self)) + if(head.havocbot_role_flags & role) + ++c; + + return c; +} + +void havocbot_goalrating_ons_controlpoints_attack(float ratingscale) +{SELFPARAM(); + entity cp, cp1, cp2, best, pl, wp; + float radius, bestvalue; + int c; + bool found; + + // Filter control points + for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext) + { + cp2.wpcost = c = 0; + cp2.wpconsidered = false; + + if(cp2.isshielded) + continue; + + // Ignore owned controlpoints + if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team])) + continue; + + // Count team mates interested in this control point + // (easier and cleaner than keeping counters per cp and teams) + FOR_EACH_PLAYER(pl) + if(SAME_TEAM(pl, self)) + if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) + if(pl.havocbot_ons_target==cp2) + ++c; + + // NOTE: probably decrease the cost of attackable control points + cp2.wpcost = c; + cp2.wpconsidered = true; + } + + // We'll consider only the best case + bestvalue = 99999999999; + cp = world; + for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext) + { + if (!cp1.wpconsidered) + continue; + + if(cp1.wpcost self.havocbot_role_timeout) + { + havocbot_ons_reset_role(self); + return; + } + + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 650); + if(!havocbot_goalrating_ons_generator_attack(20000)) + havocbot_goalrating_ons_controlpoints_attack(20000); + havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } +} + +void havocbot_role_ons_assistant() +{SELFPARAM(); + havocbot_ons_reset_role(self); +} + +void havocbot_role_ons_defense() +{SELFPARAM(); + havocbot_ons_reset_role(self); +} + +void havocbot_ons_reset_role(entity bot) +{SELFPARAM(); + entity head; + int c = 0; + + if(self.deadflag != DEAD_NO) + return; + + bot.havocbot_ons_target = world; + + // TODO: Defend control points or generator if necessary + + // if there is only me on the team switch to offense + c = 0; + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, self)) + ++c; + + if(c==1) + { + havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); + return; + } + + havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); +} + + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + */ +entity ons_Nearest_ControlPoint(vector pos, float max_dist) +{SELFPARAM(); + entity tmp_entity, closest_target = world; + tmp_entity = findchain(classname, "onslaught_controlpoint"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + tmp_entity = tmp_entity.chain; + } + + return closest_target; +} + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + * This function only check distances on the XY plane, disregarding Z + */ +entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist) +{SELFPARAM(); + entity tmp_entity, closest_target = world; + vector delta; + float smallest_distance = 0, distance; + + tmp_entity = findchain(classname, "onslaught_controlpoint"); + while(tmp_entity) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + + tmp_entity = tmp_entity.chain; + } + + return closest_target; +} +/** + * find the number of control points and generators in the same team as self + */ +int ons_Count_SelfControlPoints() +{SELFPARAM(); + entity tmp_entity; + tmp_entity = findchain(classname, "onslaught_controlpoint"); + int n = 0; + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + n++; + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + n++; + tmp_entity = tmp_entity.chain; + } + return n; +} + +/** + * Teleport player to a random position near tele_target + * if tele_effects is true, teleport sound+particles are created + * return false on failure + */ +bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects) +{ + if ( !tele_target ) + return false; + + int i; + vector loc; + float theta; + for(i = 0; i < 16; ++i) + { + theta = random() * 2 * M_PI; + loc_y = sin(theta); + loc_x = cos(theta); + loc_z = 0; + loc *= random() * range; + + loc += tele_target.origin + '0 0 128'; + + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + if ( tele_effects ) + { + Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1); + sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM); + } + setorigin(player, loc); + player.angles = '0 1 0' * ( theta * RAD2DEG + 180 ); + makevectors(player.angles); + player.fixangle = true; + player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait; + + if ( tele_effects ) + Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1); + return true; + } + } + } + + return false; +} + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ons, reset_map_global) +{SELFPARAM(); + entity e; + FOR_EACH_PLAYER(e) + { + e.ons_roundlost = false; + e.ons_deathloc = '0 0 0'; + WITH(entity, self, e, PutClientInServer()); + } + return false; +} + +MUTATOR_HOOKFUNCTION(ons, ClientDisconnect) +{SELFPARAM(); + self.ons_deathloc = '0 0 0'; + return false; +} + +MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver) +{SELFPARAM(); + self.ons_deathloc = '0 0 0'; + return false; +} + +MUTATOR_HOOKFUNCTION(ons, PlayerSpawn) +{SELFPARAM(); + if(!round_handler_IsRoundStarted()) + { + self.player_blocked = true; + return false; + } + + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + l.sprite.SendFlags |= 16; + } + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.sprite.SendFlags |= 16; + } + + if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); } + + if ( autocvar_g_onslaught_spawn_choose ) + if ( self.ons_spawn_by ) + if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) ) + { + self.ons_spawn_by = world; + return false; + } + + if(autocvar_g_onslaught_spawn_at_controlpoints) + if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance) + { + float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random; + entity tmp_entity, closest_target = world; + vector spawn_loc = self.ons_deathloc; + + // new joining player or round reset, don't bother checking + if(spawn_loc == '0 0 0') { return false; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self)) + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 96'; + loc += ('0 1 0' * random()) * 128; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin) * RAD2DEG; + return false; + } + } + } + } + } + + if(autocvar_g_onslaught_spawn_at_generator) + if(random() <= autocvar_g_onslaught_spawn_at_generator_chance) + { + float random_target = autocvar_g_onslaught_spawn_at_generator_random; + entity tmp_entity, closest_target = world; + vector spawn_loc = self.ons_deathloc; + + // new joining player or round reset, don't bother checking + if(spawn_loc == '0 0 0') { return false; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else + { + if(SAME_TEAM(tmp_entity, self)) + if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 128'; + loc += ('0 1 0' * random()) * 256; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin) * RAD2DEG; + return false; + } + } + } + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, PlayerDies) +{SELFPARAM(); + frag_target.ons_deathloc = frag_target.origin; + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + l.sprite.SendFlags |= 16; + } + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.sprite.SendFlags |= 16; + } + + if ( autocvar_g_onslaught_spawn_choose ) + if ( ons_Count_SelfControlPoints() > 1 ) + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, MonsterMove) +{SELFPARAM(); + entity e = find(world, targetname, self.target); + if (e != world) + self.team = e.team; + + return false; +} + +void ons_MonsterSpawn_Delayed() +{SELFPARAM(); + entity e, own = self.owner; + + if(!own) { remove(self); return; } + + if(own.targetname) + { + e = find(world, target, own.targetname); + if(e != world) + { + own.team = e.team; + + activator = e; + own.use(); + } + } + + remove(self); +} + +MUTATOR_HOOKFUNCTION(ons, MonsterSpawn) +{SELFPARAM(); + entity e = spawn(); + e.owner = self; + InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET); + + return false; +} + +void ons_TurretSpawn_Delayed() +{SELFPARAM(); + entity e, own = self.owner; + + if(!own) { remove(self); return; } + + if(own.targetname) + { + e = find(world, target, own.targetname); + if(e != world) + { + own.team = e.team; + own.active = ACTIVE_NOT; + + activator = e; + own.use(); + } + } + + remove(self); +} + +MUTATOR_HOOKFUNCTION(ons, TurretSpawn) +{SELFPARAM(); + entity e = spawn(); + e.owner = self; + InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET); + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole) +{SELFPARAM(); + havocbot_ons_reset_role(self); + return true; +} + +MUTATOR_HOOKFUNCTION(ons, GetTeamCount) +{ + // onslaught is special + for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + switch(tmp_entity.team) + { + case NUM_TEAM_1: c1 = 0; break; + case NUM_TEAM_2: c2 = 0; break; + case NUM_TEAM_3: c3 = 0; break; + case NUM_TEAM_4: c4 = 0; break; + } + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ons, SpectateCopy) +{SELFPARAM(); + self.ons_roundlost = other.ons_roundlost; // make spectators see it too + return false; +} + +MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE) // command was already handled? + return false; + + if ( cmd_name == "ons_spawn" ) + { + vector pos = self.origin; + if(cmd_argc > 1) + pos_x = stof(argv(1)); + if(cmd_argc > 2) + pos_y = stof(argv(2)); + if(cmd_argc > 3) + pos_z = stof(argv(3)); + + if ( IS_PLAYER(self) ) + { + if ( !self.frozen ) + { + entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); + + if ( !source_point && self.health > 0 ) + { + sprint(self, "\nYou need to be next to a control point\n"); + return 1; + } + + + entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius); + + if ( closest_target == world ) + { + sprint(self, "\nNo control point found\n"); + return 1; + } + + if ( self.health <= 0 ) + { + self.ons_spawn_by = closest_target; + self.respawn_flags = self.respawn_flags | RESPAWN_FORCE; + } + else + { + if ( source_point == closest_target ) + { + sprint(self, "\nTeleporting to the same point\n"); + return 1; + } + + if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) ) + sprint(self, "\nUnable to teleport there\n"); + } + + return 1; + } + + sprint(self, "\nNo teleportation for you\n"); + } + + return 1; + } + return 0; +} + +MUTATOR_HOOKFUNCTION(ons, PlayerUseKey) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE || gameover) { return false; } + + if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle) + { + entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); + if ( source_point ) + { + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + return true; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, PlayHitsound) +{ + return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded) + || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded); +} + +MUTATOR_HOOKFUNCTION(ons, SendWaypoint) +{ + if(wp_sendflags & 16) + { + if(self.owner.classname == "onslaught_controlpoint") + { + entity wp_owner = self.owner; + entity e = WaypointSprite_getviewentity(wp_sendto); + if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; } + if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; } + } + if(self.owner.classname == "onslaught_generator") + { + entity wp_owner = self.owner; + if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; } + if(wp_owner.health <= 0) { wp_flag |= 2; } + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget) +{ + if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job! + { + ret_float = -3; + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ons, TurretThink) +{ + // ONS uses somewhat backwards linking. + if(self.target) + { + entity e = find(world, targetname, self.target); + if (e != world) + self.team = e.team; + } + + if(self.team != self.tur_head.team) + turret_respawn(); + + return false; +} + + +// ========== +// Spawnfuncs +// ========== + +/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) + Link between control points. + + This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams. + +keys: +"target" - first control point. +"target2" - second control point. + */ +spawnfunc(onslaught_link) +{ + if(!g_onslaught) { remove(self); return; } + + if (self.target == "" || self.target2 == "") + objerror("target and target2 must be set\n"); + + self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist + ons_worldlinklist = self; + + InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET); + Net_LinkEntity(self, false, 0, ons_Link_Send); +} + +/*QUAKED spawnfunc_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 spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. + +keys: +"targetname" - name that spawnfunc_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) + */ + +spawnfunc(onslaught_controlpoint) +{ + if(!g_onslaught) { remove(self); return; } + + ons_ControlPoint_Setup(self); +} + +/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) + Base generator. + + spawnfunc_onslaught_link entities can target this. + +keys: +"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET. +"targetname" - name that spawnfunc_onslaught_link entities will use to target this. + */ +spawnfunc(onslaught_generator) +{ + if(!g_onslaught) { remove(self); return; } + if(!self.team) { objerror("team must be set"); } + + ons_GeneratorSetup(self); +} + +// scoreboard setup +void ons_ScoreRules() +{ + CheckAllowedTeams(world); + ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true); + ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0); + ScoreRules_basics_end(); +} + +void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up +{ + ons_ScoreRules(); + + round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart); + round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); +} + +void ons_Initialize() +{ + g_onslaught = true; + ons_captureshield_force = autocvar_g_onslaught_shield_force; + + addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost); + + InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE); +} + +REGISTER_MUTATOR(ons, IS_GAMETYPE(ONSLAUGHT)) +{ + ActivateTeamplay(); + SetLimits(autocvar_g_onslaught_point_limit, -1, -1, -1); + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + ons_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back ons_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_race.qc b/qcsrc/server/mutators/mutator/gamemode_race.qc new file mode 100644 index 000000000..4b9201d45 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_race.qc @@ -0,0 +1,466 @@ +#ifndef GAMEMODE_RACE_H +#define GAMEMODE_RACE_H + +float g_race_qualifying; +float race_teams; + +// scores +const float ST_RACE_LAPS = 1; +const float SP_RACE_LAPS = 4; +const float SP_RACE_TIME = 5; +const float SP_RACE_FASTEST = 6; +#endif + +#ifdef IMPLEMENTATION + +#include "../../race.qh" + +#define autocvar_g_race_laps_limit cvar("g_race_laps_limit") +float autocvar_g_race_qualifying_timelimit; +float autocvar_g_race_qualifying_timelimit_override; +int autocvar_g_race_teams; + +// legacy bot roles +.float race_checkpoint; +void havocbot_role_race() +{SELFPARAM(); + if(self.deadflag != DEAD_NO) + return; + + entity e; + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + navigation_goalrating_start(); + + for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; ) + { + if(e.cnt == self.race_checkpoint) + { + navigation_routerating(e, 1000000, 5000); + } + else if(self.race_checkpoint == -1) + { + navigation_routerating(e, 1000000, 5000); + } + } + + navigation_goalrating_end(); + } +} + +void race_ScoreRules() +{ + ScoreRules_basics(race_teams, 0, 0, false); + if(race_teams) + { + ScoreInfo_SetLabel_TeamScore( ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); + } + else if(g_race_qualifying) + { + ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME); + } + else + { + ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME); + } + ScoreRules_basics_end(); +} + +void race_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later +{ + if(autocvar_sv_eventlog) + GameLogEcho(strcat(":race:", mode, ":", ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); +} + +MUTATOR_HOOKFUNCTION(rc, PlayerPhysics) +{SELFPARAM(); + self.race_movetime_frac += PHYS_INPUT_TIMELENGTH; + float f = floor(self.race_movetime_frac); + self.race_movetime_frac -= f; + self.race_movetime_count += f; + self.race_movetime = self.race_movetime_frac + self.race_movetime_count; + +#ifdef SVQC + if(IS_PLAYER(self)) + { + if (self.race_penalty) + if (time > self.race_penalty) + self.race_penalty = 0; + if(self.race_penalty) + { + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.disableclientprediction = 2; + } + } +#endif + + // force kbd movement for fairness + float wishspeed; + vector wishvel; + + // if record times matter + // ensure nothing EVIL is being done (i.e. div0_evade) + // this hinders joystick users though + // but it still gives SOME analog control + wishvel.x = fabs(self.movement.x); + wishvel.y = fabs(self.movement.y); + if(wishvel.x != 0 && wishvel.y != 0 && wishvel.x != wishvel.y) + { + wishvel.z = 0; + wishspeed = vlen(wishvel); + if(wishvel.x >= 2 * wishvel.y) + { + // pure X motion + if(self.movement.x > 0) + self.movement_x = wishspeed; + else + self.movement_x = -wishspeed; + self.movement_y = 0; + } + else if(wishvel.y >= 2 * wishvel.x) + { + // pure Y motion + self.movement_x = 0; + if(self.movement.y > 0) + self.movement_y = wishspeed; + else + self.movement_y = -wishspeed; + } + else + { + // diagonal + if(self.movement.x > 0) + self.movement_x = M_SQRT1_2 * wishspeed; + else + self.movement_x = -M_SQRT1_2 * wishspeed; + if(self.movement.y > 0) + self.movement_y = M_SQRT1_2 * wishspeed; + else + self.movement_y = -M_SQRT1_2 * wishspeed; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, reset_map_global) +{ + float s; + + Score_NicePrint(world); + + race_ClearRecords(); + PlayerScore_Sort(race_place, 0, 1, 0); + + entity e; + FOR_EACH_CLIENT(e) + { + if(e.race_place) + { + s = PlayerScore_Add(e, SP_RACE_FASTEST, 0); + if(!s) + e.race_place = 0; + } + race_EventLog(ftos(e.race_place), e); + } + + if(g_race_qualifying == 2) + { + g_race_qualifying = 0; + independent_players = 0; + cvar_set("fraglimit", ftos(race_fraglimit)); + cvar_set("leadlimit", ftos(race_leadlimit)); + cvar_set("timelimit", ftos(race_timelimit)); + race_ScoreRules(); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, PlayerPreThink) +{SELFPARAM(); + if(IS_SPEC(self) || IS_OBSERVER(self)) + if(g_race_qualifying) + if(msg_entity.enemy.race_laptime) + race_SendNextCheckpoint(msg_entity.enemy, 1); + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, ClientConnect) +{SELFPARAM(); + race_PreparePlayer(); + self.race_checkpoint = -1; + + string rr = RACE_RECORD; + + if(IS_REAL_CLIENT(self)) + { + msg_entity = self; + race_send_recordtime(MSG_ONE); + race_send_speedaward(MSG_ONE); + + speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"))); + speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"))); + race_send_speedaward_alltimebest(MSG_ONE); + + float i; + for (i = 1; i <= RANKINGS_CNT; ++i) + { + race_SendRankings(i, 0, 0, MSG_ONE); + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver) +{SELFPARAM(); + if(g_race_qualifying) + if(PlayerScore_Add(self, SP_RACE_FASTEST, 0)) + self.frags = FRAGS_LMS_LOSER; + else + self.frags = FRAGS_SPECTATOR; + + race_PreparePlayer(); + self.race_checkpoint = -1; + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, PlayerSpawn) +{SELFPARAM(); + if(spawn_spot.target == "") + // Emergency: this wasn't a real spawnpoint. Can this ever happen? + race_PreparePlayer(); + + // if we need to respawn, do it right + self.race_respawn_checkpoint = self.race_checkpoint; + self.race_respawn_spotref = spawn_spot; + + self.race_place = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, PutClientInServer) +{SELFPARAM(); + if(IS_PLAYER(self)) + if(!gameover) + { + if(self.killcount == -666 /* initial spawn */ || g_race_qualifying) // spawn + race_PreparePlayer(); + else // respawn + race_RetractPlayer(); + + race_AbandonRaceCheck(self); + } + return false; +} + +MUTATOR_HOOKFUNCTION(rc, PlayerDies) +{SELFPARAM(); + self.respawn_flags |= RESPAWN_FORCE; + race_AbandonRaceCheck(self); + return false; +} + +MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole) +{SELFPARAM(); + self.havocbot_role = havocbot_role_race; + return true; +} + +MUTATOR_HOOKFUNCTION(rc, GetPressedKeys) +{SELFPARAM(); + if(self.cvar_cl_allow_uidtracking == 1 && self.cvar_cl_allow_uid2name == 1) + { + if (!self.stored_netname) + self.stored_netname = strzone(uid2name(self.crypto_idfp)); + if(self.stored_netname != self.netname) + { + db_put(ServerProgsDB, strcat("/uid2name/", self.crypto_idfp), self.netname); + strunzone(self.stored_netname); + self.stored_netname = strzone(self.netname); + } + } + + if (!IS_OBSERVER(self)) + { + if (vlen(self.velocity - self.velocity_z * '0 0 1') > speedaward_speed) + { + speedaward_speed = vlen(self.velocity - self.velocity_z * '0 0 1'); + speedaward_holder = self.netname; + speedaward_uid = self.crypto_idfp; + speedaward_lastupdate = time; + } + if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1) + { + string rr = RACE_RECORD; + race_send_speedaward(MSG_ALL); + speedaward_lastsent = speedaward_speed; + if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "") + { + speedaward_alltimebest = speedaward_speed; + speedaward_alltimebest_holder = speedaward_holder; + speedaward_alltimebest_uid = speedaward_uid; + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest)); + db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid); + race_send_speedaward_alltimebest(MSG_ALL); + } + } + } + return false; +} + +MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear) +{ + if(g_race_qualifying) + return true; // in qualifying, you don't lose score by observing + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, GetTeamCount, CBC_ORDER_EXCLUSIVE) +{ + ret_float = race_teams; + return false; +} + +MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining) +{ + // announce remaining frags if not in qualifying mode + if(!g_race_qualifying) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, GetRecords) +{ + for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i) + { + if(MapInfo_Get_ByID(i)) + { + float r = race_readTime(MapInfo_Map_bspname, 1); + + if(!r) + continue; + + string h = race_readName(MapInfo_Map_bspname, 1); + ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-8, TIME_ENCODED_TOSTRING(r)), " ", h, "\n"); + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, FixClientCvars) +{ + stuffcmd(fix_client, "cl_cmd settemp cl_movecliptokeyboard 2\n"); + return false; +} + +MUTATOR_HOOKFUNCTION(rc, CheckRules_World) +{ + if(g_race_qualifying == 2 && checkrules_timelimit >= 0) + { + ret_float = WinningCondition_QualifyingThenRace(checkrules_fraglimit); + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars) +{ + if(g_race_qualifying == 2) + warmup_stage = 0; + return false; +} + +void race_Initialize() +{ + race_ScoreRules(); + if(g_race_qualifying == 2) + warmup_stage = 0; +} + +void rc_SetLimits() +{ + int fraglimit_override, leadlimit_override; + float timelimit_override, qualifying_override; + + if(autocvar_g_race_teams) + { + ActivateTeamplay(); + race_teams = bound(2, autocvar_g_race_teams, 4); + have_team_spawns = -1; // request team spawns + } + else + race_teams = 0; + + qualifying_override = autocvar_g_race_qualifying_timelimit_override; + fraglimit_override = autocvar_g_race_laps_limit; + leadlimit_override = 0; // currently not supported by race + timelimit_override = -1; // use default if we don't set it below + + // we need to find out the correct value for g_race_qualifying + float want_qualifying = ((qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit) > 0; + + if(autocvar_g_campaign) + { + g_race_qualifying = 1; + independent_players = 1; + } + else if(!autocvar_g_campaign && want_qualifying) + { + g_race_qualifying = 2; + independent_players = 1; + race_fraglimit = (race_fraglimit >= 0) ? fraglimit_override : autocvar_fraglimit; + race_leadlimit = (race_leadlimit >= 0) ? leadlimit_override : autocvar_leadlimit; + race_timelimit = (race_timelimit >= 0) ? timelimit_override : autocvar_timelimit; + fraglimit_override = 0; + leadlimit_override = 0; + timelimit_override = autocvar_g_race_qualifying_timelimit; + } + else + g_race_qualifying = 0; + + SetLimits(fraglimit_override, leadlimit_override, timelimit_override, qualifying_override); +} + +REGISTER_MUTATOR(rc, g_race) +{ + rc_SetLimits(); + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + race_Initialize(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back race_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/gamemode_tdm.qc b/qcsrc/server/mutators/mutator/gamemode_tdm.qc new file mode 100644 index 000000000..aaa3d5163 --- /dev/null +++ b/qcsrc/server/mutators/mutator/gamemode_tdm.qc @@ -0,0 +1,91 @@ +#ifdef IMPLEMENTATION +bool autocvar_g_tdm_team_spawns; +int autocvar_g_tdm_point_limit; +int autocvar_g_tdm_point_leadlimit; +int autocvar_g_tdm_teams; +int autocvar_g_tdm_teams_override; + +/*QUAKED spawnfunc_tdm_team (0 .5 .8) (-16 -16 -24) (16 16 32) +Team declaration for TDM gameplay, this allows you to decide what team names and control point models are used in your map. +Note: If you use spawnfunc_tdm_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too. +Keys: +"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)... +"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */ +spawnfunc(tdm_team) +{ + if(!g_tdm || !self.cnt) { remove(self); return; } + + self.classname = "tdm_team"; + self.team = self.cnt + 1; +} + +// code from here on is just to support maps that don't have team entities +void tdm_SpawnTeam (string teamname, float teamcolor) +{ + entity this = new(tdm_team); + this.netname = teamname; + this.cnt = teamcolor; + this.spawnfunc_checked = true; + WITH(entity, self, this, spawnfunc_tdm_team(this)); +} + +void tdm_DelayedInit() +{ + // if no teams are found, spawn defaults + if(find(world, classname, "tdm_team") == world) + { + LOG_INFO("No ""tdm_team"" entities found on this map, creating them anyway.\n"); + + int numteams = min(4, autocvar_g_tdm_teams_override); + + if(numteams < 2) { numteams = autocvar_g_tdm_teams; } + numteams = bound(2, numteams, 4); + + float i; + for(i = 1; i <= numteams; ++i) + tdm_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1); + } +} + +MUTATOR_HOOKFUNCTION(tdm, GetTeamCount, CBC_ORDER_EXCLUSIVE) +{ + ret_string = "tdm_team"; + return true; +} + +MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} + +REGISTER_MUTATOR(tdm, g_tdm) +{ + ActivateTeamplay(); + SetLimits(autocvar_g_tdm_point_limit, autocvar_g_tdm_point_leadlimit, -1, -1); + if(autocvar_g_tdm_team_spawns) + have_team_spawns = -1; // request team spawns + + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This is a game type and it cannot be added at runtime."); + InitializeEntity(world, tdm_DelayedInit, INITPRIO_GAMETYPE); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // we actually cannot roll back tdm_Initialize here + // BUT: we don't need to! If this gets called, adding always + // succeeds. + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This is a game type and it cannot be removed at runtime."); + return -1; + } + + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_bloodloss.qc b/qcsrc/server/mutators/mutator/mutator_bloodloss.qc new file mode 100644 index 000000000..ca3716669 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_bloodloss.qc @@ -0,0 +1,45 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss")); + +.float bloodloss_timer; + +MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink) +{SELFPARAM(); + if(IS_PLAYER(self)) + if(self.health <= autocvar_g_bloodloss && self.deadflag == DEAD_NO) + { + self.BUTTON_CROUCH = true; + + if(time >= self.bloodloss_timer) + { + if(self.vehicle) + vehicles_exit(VHEF_RELEASE); + if(self.event_damage) + self.event_damage(self, self, 1, DEATH_ROT.m_id, self.origin, '0 0 0'); + self.bloodloss_timer = time + 0.5 + random() * 0.5; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump) +{SELFPARAM(); + if(self.health <= autocvar_g_bloodloss) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":bloodloss"); + return false; +} + +MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Blood loss"); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_breakablehook.qc b/qcsrc/server/mutators/mutator/mutator_breakablehook.qc new file mode 100644 index 000000000..cb9463b86 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_breakablehook.qc @@ -0,0 +1,29 @@ +#ifdef IMPLEMENTATION +#include "../../../common/deathtypes/all.qh" +#include "../../g_hook.qh" + +REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook")); + +bool autocvar_g_breakablehook; // allow toggling mid match? +bool autocvar_g_breakablehook_owner; + +MUTATOR_HOOKFUNCTION(breakablehook, PlayerDamage_Calculate) +{ + if(frag_target.classname == "grapplinghook") + { + if((!autocvar_g_breakablehook) + || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner) + ) { frag_damage = 0; } + + // hurt the owner of the hook + if(DIFF_TEAM(frag_attacker, frag_target.realowner)) + { + Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0'); + RemoveGrapplingHook(frag_target.realowner); + return false; // dead + } + } + + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_buffs.qc b/qcsrc/server/mutators/mutator/mutator_buffs.qc new file mode 100644 index 000000000..4f0758fa3 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_buffs.qc @@ -0,0 +1,1000 @@ +#ifndef MUTATOR_BUFFS_H +#define MUTATOR_BUFFS_H + +// ammo +.float buff_ammo_prev_infitems; +.int buff_ammo_prev_clipload; +// invisible +.float buff_invisible_prev_alpha; +// flight +.float buff_flight_prev_gravity; +// disability +.float buff_disability_time; +.float buff_disability_effect_time; +// common buff variables +.float buff_effect_delay; + +// buff definitions +.float buff_active; +.float buff_activetime; +.float buff_activetime_updated; +.entity buff_waypoint; +.int oldbuffs; // for updating effects +.entity buff_model; // controls effects (TODO: make csqc) + +const vector BUFF_MIN = ('-16 -16 -20'); +const vector BUFF_MAX = ('16 16 20'); + +// client side options +.float cvar_cl_buffs_autoreplace; +#endif + +#ifdef IMPLEMENTATION + +#include "../../../common/triggers/target/music.qh" +#include "../../../common/gamemodes/all.qh" +#include "../../../common/buffs/all.qh" + +.float buff_time; +void buffs_DelayedInit(); + +REGISTER_MUTATOR(buffs, cvar("g_buffs")) +{ + MUTATOR_ONADD + { + addstat(STAT_BUFFS, AS_INT, buffs); + addstat(STAT_BUFF_TIME, AS_FLOAT, buff_time); + + InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET); + } +} + +entity buff_FirstFromFlags(int _buffs) +{ + if (flags) + { + FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it)); + } + return BUFF_Null; +} + +bool buffs_BuffModel_Customize() +{SELFPARAM(); + entity player, myowner; + bool same_team; + + player = WaypointSprite_getviewentity(other); + myowner = self.owner; + same_team = (SAME_TEAM(player, myowner) || SAME_TEAM(player, myowner)); + + if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0) + return false; + + if(MUTATOR_CALLHOOK(BuffModel_Customize, self, player)) + return false; + + if(player == myowner || (IS_SPEC(other) && other.enemy == myowner)) + { + // somewhat hide the model, but keep the glow + self.effects = 0; + self.alpha = -1; + } + else + { + self.effects = EF_FULLBRIGHT | EF_LOWPRECISION; + self.alpha = 1; + } + return true; +} + +void buffs_BuffModel_Spawn(entity player) +{ + player.buff_model = spawn(); + setmodel(player.buff_model, MDL_BUFF); + setsize(player.buff_model, '0 0 -40', '0 0 40'); + setattachment(player.buff_model, player, ""); + setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1)); + player.buff_model.owner = player; + player.buff_model.scale = 0.7; + player.buff_model.pflags = PFLAGS_FULLDYNAMIC; + player.buff_model.light_lev = 200; + player.buff_model.customizeentityforclient = buffs_BuffModel_Customize; +} + +vector buff_GlowColor(entity buff) +{ + //if(buff.team) { return Team_ColorRGB(buff.team); } + return buff.m_color; +} + +void buff_Effect(entity player, string eff) +{SELFPARAM(); + if(!autocvar_g_buffs_effects) { return; } + + if(time >= self.buff_effect_delay) + { + Send_Effect_(eff, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1); + self.buff_effect_delay = time + 0.05; // prevent spam + } +} + +// buff item +float buff_Waypoint_visible_for_player(entity plr) +{SELFPARAM(); + if(!self.owner.buff_active && !self.owner.buff_activetime) + return false; + + if (plr.buffs) + { + return plr.cvar_cl_buffs_autoreplace == false || plr.buffs != self.owner.buffs; + } + + return WaypointSprite_visible_for_player(plr); +} + +void buff_Waypoint_Spawn(entity e) +{ + entity buff = buff_FirstFromFlags(e.buffs); + entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, world, e.team, e, buff_waypoint, true, RADARICON_Buff); + wp.wp_extra = buff.m_id; + WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod); + e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player; +} + +void buff_SetCooldown(float cd) +{SELFPARAM(); + cd = max(0, cd); + + if(!self.buff_waypoint) + buff_Waypoint_Spawn(self); + + WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd); + self.buff_activetime = cd; + self.buff_active = !cd; +} + +void buff_Respawn(entity ent) +{SELFPARAM(); + if(gameover) { return; } + + vector oldbufforigin = ent.origin; + + if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256)) + { + entity spot = SelectSpawnPoint(true); + setorigin(ent, ((spot.origin + '0 0 200') + (randomvec() * 300))); + ent.angles = spot.angles; + } + + tracebox(ent.origin, ent.mins * 1.5, self.maxs * 1.5, ent.origin, MOVE_NOMONSTERS, ent); + + setorigin(ent, trace_endpos); // attempt to unstick + + ent.movetype = MOVETYPE_TOSS; + + makevectors(ent.angles); + ent.velocity = '0 0 200'; + ent.angles = '0 0 0'; + if(autocvar_g_buffs_random_lifetime > 0) + ent.lifetime = time + autocvar_g_buffs_random_lifetime; + + Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(ent), '0 0 0', 1); + + WaypointSprite_Ping(ent.buff_waypoint); + + sound(ent, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) +} + +void buff_Touch() +{SELFPARAM(); + if(gameover) { return; } + + if(ITEM_TOUCH_NEEDKILL()) + { + buff_Respawn(self); + return; + } + + if((self.team && DIFF_TEAM(other, self)) + || (other.frozen) + || (other.vehicle) + || (!self.buff_active) + ) + { + // can't touch this + return; + } + + if(MUTATOR_CALLHOOK(BuffTouch, self, other)) + return; + + if(!IS_PLAYER(other)) + return; // incase mutator changed other + + if (other.buffs) + { + if (other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs) + { + int buffid = buff_FirstFromFlags(other.buffs).m_id; + //Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_DROP, other.buffs); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ITEM_BUFF_LOST, other.netname, buffid); + + other.buffs = 0; + //sound(other, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); + } + else { return; } // do nothing + } + + self.owner = other; + self.buff_active = false; + self.lifetime = 0; + int buffid = buff_FirstFromFlags(self.buffs).m_id; + Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, buffid); + Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, buffid); + + Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(self), '0 0 0', 1); + sound(other, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM); + other.buffs |= (self.buffs); +} + +float buff_Available(entity buff) +{ + if (buff == BUFF_Null) + return false; + if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only")))) + return false; + if (buff == BUFF_VAMPIRE && cvar("g_vampire")) + return false; + return cvar(strcat("g_buffs_", buff.m_name)); +} + +.int buff_seencount; + +void buff_NewType(entity ent, float cb) +{ + RandomSelection_Init(); + FOREACH(Buffs, buff_Available(it), LAMBDA( + it.buff_seencount += 1; + // if it's already been chosen, give it a lower priority + RandomSelection_Add(world, it.m_itemid, string_null, 1, max(0.2, 1 / it.buff_seencount)); + )); + ent.buffs = RandomSelection_chosen_float; +} + +void buff_Think() +{SELFPARAM(); + if(self.buffs != self.oldbuffs) + { + entity buff = buff_FirstFromFlags(self.buffs); + self.color = buff.m_color; + self.glowmod = buff_GlowColor(buff); + self.skin = buff.m_skin; + + setmodel(self, MDL_BUFF); + + if(self.buff_waypoint) + { + //WaypointSprite_Disown(self.buff_waypoint, 1); + WaypointSprite_Kill(self.buff_waypoint); + buff_Waypoint_Spawn(self); + if(self.buff_activetime) + WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime); + } + + self.oldbuffs = self.buffs; + } + + if(!gameover) + if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) + if(!self.buff_activetime_updated) + { + buff_SetCooldown(self.buff_activetime); + self.buff_activetime_updated = true; + } + + if(!self.buff_active && !self.buff_activetime) + if(!self.owner || self.owner.frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs)) + { + buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime); + self.owner = world; + if(autocvar_g_buffs_randomize) + buff_NewType(self, self.buffs); + + if(autocvar_g_buffs_random_location || (self.spawnflags & 64)) + buff_Respawn(self); + } + + if(self.buff_activetime) + if(!gameover) + if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) + { + self.buff_activetime = max(0, self.buff_activetime - frametime); + + if(!self.buff_activetime) + { + self.buff_active = true; + sound(self, CH_TRIGGER, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM); + Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(self), '0 0 0', 1); + } + } + + if(self.buff_active) + { + if(self.team && !self.buff_waypoint) + buff_Waypoint_Spawn(self); + + if(self.lifetime) + if(time >= self.lifetime) + buff_Respawn(self); + } + + self.nextthink = time; + //self.angles_y = time * 110.1; +} + +void buff_Waypoint_Reset() +{SELFPARAM(); + WaypointSprite_Kill(self.buff_waypoint); + + if(self.buff_activetime) { buff_Waypoint_Spawn(self); } +} + +void buff_Reset() +{SELFPARAM(); + if(autocvar_g_buffs_randomize) + buff_NewType(self, self.buffs); + self.owner = world; + buff_SetCooldown(autocvar_g_buffs_cooldown_activate); + buff_Waypoint_Reset(); + self.buff_activetime_updated = false; + + if(autocvar_g_buffs_random_location || (self.spawnflags & 64)) + buff_Respawn(self); +} + +float buff_Customize() +{SELFPARAM(); + entity player = WaypointSprite_getviewentity(other); + if(!self.buff_active || (self.team && DIFF_TEAM(player, self))) + { + self.alpha = 0.3; + if(self.effects & EF_FULLBRIGHT) { self.effects &= ~(EF_FULLBRIGHT); } + self.pflags = 0; + } + else + { + self.alpha = 1; + if(!(self.effects & EF_FULLBRIGHT)) { self.effects |= EF_FULLBRIGHT; } + self.light_lev = 220 + 36 * sin(time); + self.pflags = PFLAGS_FULLDYNAMIC; + } + return true; +} + +void buff_Init(entity ent) +{SELFPARAM(); + if(!cvar("g_buffs")) { remove(ent); return; } + + if(!teamplay && ent.team) { ent.team = 0; } + + entity buff = buff_FirstFromFlags(self.buffs); + + setself(ent); + if(!self.buffs || buff_Available(buff)) + buff_NewType(self, 0); + + self.classname = "item_buff"; + self.solid = SOLID_TRIGGER; + self.flags = FL_ITEM; + self.think = buff_Think; + self.touch = buff_Touch; + self.reset = buff_Reset; + self.nextthink = time + 0.1; + self.gravity = 1; + self.movetype = MOVETYPE_TOSS; + self.scale = 1; + self.skin = buff.m_skin; + self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW; + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; + self.customizeentityforclient = buff_Customize; + //self.gravity = 100; + self.color = buff.m_color; + self.glowmod = buff_GlowColor(self); + buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime); + self.buff_active = !self.buff_activetime; + self.pflags = PFLAGS_FULLDYNAMIC; + + if(self.spawnflags & 1) + self.noalign = true; + + if(self.noalign) + self.movetype = MOVETYPE_NONE; // reset by random location + + setmodel(self, MDL_BUFF); + setsize(self, BUFF_MIN, BUFF_MAX); + + if(cvar("g_buffs_random_location") || (self.spawnflags & 64)) + buff_Respawn(self); + + setself(this); +} + +void buff_Init_Compat(entity ent, entity replacement) +{ + if (ent.spawnflags & 2) + ent.team = NUM_TEAM_1; + else if (ent.spawnflags & 4) + ent.team = NUM_TEAM_2; + + ent.buffs = replacement.m_itemid; + + buff_Init(ent); +} + +void buff_SpawnReplacement(entity ent, entity old) +{ + setorigin(ent, old.origin); + ent.angles = old.angles; + ent.noalign = (old.noalign || (old.spawnflags & 1)); + + buff_Init(ent); +} + +void buff_Vengeance_DelayedDamage() +{SELFPARAM(); + if(self.enemy) + Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF.m_id, self.enemy.origin, '0 0 0'); + + remove(self); + return; +} + +float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base) +{ + return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base); +} + +// mutator hooks +MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) +{ + if(frag_deathtype == DEATH_BUFF.m_id) { return false; } + + if(frag_target.buffs & BUFF_RESISTANCE.m_itemid) + { + vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage); + damage_take = v.x; + damage_save = v.y; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate) +{ + if(frag_deathtype == DEATH_BUFF.m_id) { return false; } + + if(frag_target.buffs & BUFF_SPEED.m_itemid) + if(frag_target != frag_attacker) + frag_damage *= autocvar_g_buffs_speed_damage_take; + + if(frag_target.buffs & BUFF_MEDIC.m_itemid) + if((frag_target.health - frag_damage) <= 0) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(frag_attacker) + if(random() <= autocvar_g_buffs_medic_survive_chance) + frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health); + + if(frag_target.buffs & BUFF_JUMP.m_itemid) + if(frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + + if(frag_target.buffs & BUFF_VENGEANCE.m_itemid) + if(frag_attacker) + if(frag_attacker != frag_target) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + { + entity dmgent = spawn(); + + dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; + dmgent.enemy = frag_attacker; + dmgent.owner = frag_target; + dmgent.think = buff_Vengeance_DelayedDamage; + dmgent.nextthink = time + 0.1; + } + + if(frag_target.buffs & BUFF_BASH.m_itemid) + if(frag_attacker != frag_target) + if(vlen(frag_force)) + frag_force = '0 0 0'; + + if(frag_attacker.buffs & BUFF_BASH.m_itemid) + if(vlen(frag_force)) + if(frag_attacker == frag_target) + frag_force *= autocvar_g_buffs_bash_force_self; + else + frag_force *= autocvar_g_buffs_bash_force; + + if(frag_attacker.buffs & BUFF_DISABILITY.m_itemid) + if(frag_target != frag_attacker) + frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime; + + if(frag_attacker.buffs & BUFF_MEDIC.m_itemid) + if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC) + if(SAME_TEAM(frag_attacker, frag_target)) + if(frag_attacker != frag_target) + { + frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage); + frag_damage = 0; + } + + if(frag_attacker.buffs & BUFF_INFERNO.m_itemid) + if(frag_target != frag_attacker) { + float time = buff_Inferno_CalculateTime( + frag_damage, + 0, + autocvar_g_buffs_inferno_burntime_min_time, + autocvar_g_buffs_inferno_burntime_target_damage, + autocvar_g_buffs_inferno_burntime_target_time, + autocvar_g_buffs_inferno_burntime_factor + ); + Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier) * time, time, DEATH_BUFF.m_id); + } + + // this... is ridiculous (TODO: fix!) + if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid) + if(!frag_target.vehicle) + if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC) + if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(frag_target.deadflag == DEAD_NO) + if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target)) + if(frag_attacker != frag_target) + if(!frag_target.frozen) + if(frag_target.takedamage) + if(DIFF_TEAM(frag_attacker, frag_target)) + { + frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); + if(frag_target.armorvalue) + frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs,PlayerSpawn) +{SELFPARAM(); + self.buffs = 0; + // reset timers here to prevent them continuing after re-spawn + self.buff_disability_time = 0; + self.buff_disability_effect_time = 0; + return false; +} + +.float stat_sv_maxspeed; +.float stat_sv_airspeedlimit_nonqw; +.float stat_sv_jumpvelocity; + +MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) +{SELFPARAM(); + if(self.buffs & BUFF_SPEED.m_itemid) + { + self.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed; + self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed; + } + + if(time < self.buff_disability_time) + { + self.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed; + self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed; + } + + if(self.buffs & BUFF_JUMP.m_itemid) + { + // automatically reset, no need to worry + self.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerJump) +{SELFPARAM(); + if(self.buffs & BUFF_JUMP.m_itemid) + player_jumpheight = autocvar_g_buffs_jump_height; + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, MonsterMove) +{SELFPARAM(); + if(time < self.buff_disability_time) + { + monster_speed_walk *= autocvar_g_buffs_disability_speed; + monster_speed_run *= autocvar_g_buffs_disability_speed; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerDies) +{SELFPARAM(); + if(self.buffs) + { + int buffid = buff_FirstFromFlags(self.buffs).m_id; + Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid); + self.buffs = 0; + + if(self.buff_model) + { + remove(self.buff_model); + self.buff_model = world; + } + } + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE || gameover) { return false; } + if(self.buffs) + { + int buffid = buff_FirstFromFlags(self.buffs).m_id; + Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid); + Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid); + + self.buffs = 0; + sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE || gameover) { return false; } + + if(self.buffs & BUFF_SWAPPER.m_itemid) + { + float best_distance = autocvar_g_buffs_swapper_range; + entity closest = world; + entity player; + FOR_EACH_PLAYER(player) + if(DIFF_TEAM(self, player)) + if(player.deadflag == DEAD_NO && !player.frozen && !player.vehicle) + if(vlen(self.origin - player.origin) <= best_distance) + { + best_distance = vlen(self.origin - player.origin); + closest = player; + } + + if(closest) + { + vector my_org, my_vel, my_ang, their_org, their_vel, their_ang; + + my_org = self.origin; + my_vel = self.velocity; + my_ang = self.angles; + their_org = closest.origin; + their_vel = closest.velocity; + their_ang = closest.angles; + + Drop_Special_Items(closest); + + MUTATOR_CALLHOOK(PortalTeleport, self); // initiate flag dropper + + setorigin(self, their_org); + setorigin(closest, my_org); + + closest.velocity = my_vel; + closest.angles = my_ang; + closest.fixangle = true; + closest.oldorigin = my_org; + closest.oldvelocity = my_vel; + self.velocity = their_vel; + self.angles = their_ang; + self.fixangle = true; + self.oldorigin = their_org; + self.oldvelocity = their_vel; + + // set pusher so self gets the kill if they fall into void + closest.pusher = self; + closest.pushltime = time + autocvar_g_maxpushtime; + closest.istypefrag = closest.BUTTON_CHAT; + + Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1); + Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1); + + sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); + sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); + + // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam + self.buffs = 0; + return true; + } + } + return false; +} + +bool buffs_RemovePlayer(entity player) +{ + if(player.buff_model) + { + remove(player.buff_model); + player.buff_model = world; + } + + // also reset timers here to prevent them continuing after spectating + player.buff_disability_time = 0; + player.buff_disability_effect_time = 0; + + return false; +} +MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { return buffs_RemovePlayer(self); } +MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { return buffs_RemovePlayer(self); } + +MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint) +{SELFPARAM(); + entity e = WaypointSprite_getviewentity(other); + + // if you have the invisibility powerup, sprites ALWAYS are restricted to your team + // but only apply this to real players, not to spectators + if((self.owner.flags & FL_CLIENT) && (self.owner.buffs & BUFF_INVISIBLE.m_itemid) && (e == other)) + if(DIFF_TEAM(self.owner, e)) + return true; + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST) +{SELFPARAM(); + if(autocvar_g_buffs_replace_powerups) + switch(self.classname) + { + case "item_strength": + case "item_invincible": + { + entity e = spawn(); + buff_SpawnReplacement(e, self); + return true; + } + } + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) +{SELFPARAM(); + if(self.buffs & BUFF_SPEED.m_itemid) + weapon_rate *= autocvar_g_buffs_speed_rate; + + if(time < self.buff_disability_time) + weapon_rate *= autocvar_g_buffs_disability_rate; + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) +{SELFPARAM(); + if(self.buffs & BUFF_SPEED.m_itemid) + ret_float *= autocvar_g_buffs_speed_weaponspeed; + + if(time < self.buff_disability_time) + ret_float *= autocvar_g_buffs_disability_weaponspeed; + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) +{SELFPARAM(); + if(gameover || self.deadflag != DEAD_NO) { return false; } + + if(time < self.buff_disability_time) + if(time >= self.buff_disability_effect_time) + { + Send_Effect(EFFECT_SMOKING, self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1); + self.buff_disability_effect_time = time + 0.5; + } + + // handle buff lost status + // 1: notify everyone else + // 2: notify carrier as well + int buff_lost = 0; + + if(self.buff_time) + if(time >= self.buff_time) + buff_lost = 2; + + if(self.frozen) { buff_lost = 1; } + + if(buff_lost) + { + if(self.buffs) + { + int buffid = buff_FirstFromFlags(self.buffs).m_id; + Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid); + if(buff_lost >= 2) + { + Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message? + sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); + } + self.buffs = 0; + } + } + + if(self.buffs & BUFF_MAGNET.m_itemid) + { + vector pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item; + for(other = world; (other = findflags(other, flags, FL_ITEM)); ) + if(boxesoverlap(self.absmin - pickup_size, self.absmax + pickup_size, other.absmin, other.absmax)) + { + setself(other); + other = this; + if(self.touch) + self.touch(); + other = self; + setself(this); + } + } + + if(self.buffs & BUFF_AMMO.m_itemid) + if(self.clip_size) + self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size; + + if((self.buffs & BUFF_INVISIBLE.m_itemid) && (self.oldbuffs & BUFF_INVISIBLE.m_itemid)) + if(self.alpha != autocvar_g_buffs_invisible_alpha) + self.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO) + +#define BUFF_ONADD(b) if ( (self.buffs & (b).m_itemid) && !(self.oldbuffs & (b).m_itemid)) +#define BUFF_ONREM(b) if (!(self.buffs & (b).m_itemid) && (self.oldbuffs & (b).m_itemid)) + + if(self.buffs != self.oldbuffs) + { + entity buff = buff_FirstFromFlags(self.buffs); + float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0; + self.buff_time = (bufftime) ? time + bufftime : 0; + + BUFF_ONADD(BUFF_AMMO) + { + self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO); + self.items |= IT_UNLIMITED_WEAPON_AMMO; + + if(self.clip_load) + self.buff_ammo_prev_clipload = self.clip_load; + self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size; + } + + BUFF_ONREM(BUFF_AMMO) + { + if(self.buff_ammo_prev_infitems) + self.items |= IT_UNLIMITED_WEAPON_AMMO; + else + self.items &= ~IT_UNLIMITED_WEAPON_AMMO; + + if(self.buff_ammo_prev_clipload) + self.clip_load = self.buff_ammo_prev_clipload; + } + + BUFF_ONADD(BUFF_INVISIBLE) + { + if(time < self.strength_finished && g_instagib) + self.alpha = autocvar_g_instagib_invis_alpha; + else + self.alpha = self.buff_invisible_prev_alpha; + self.alpha = autocvar_g_buffs_invisible_alpha; + } + + BUFF_ONREM(BUFF_INVISIBLE) + self.alpha = self.buff_invisible_prev_alpha; + + BUFF_ONADD(BUFF_FLIGHT) + { + self.buff_flight_prev_gravity = self.gravity; + self.gravity = autocvar_g_buffs_flight_gravity; + } + + BUFF_ONREM(BUFF_FLIGHT) + self.gravity = self.buff_flight_prev_gravity; + + self.oldbuffs = self.buffs; + if(self.buffs) + { + if(!self.buff_model) + buffs_BuffModel_Spawn(self); + + self.buff_model.color = buff.m_color; + self.buff_model.glowmod = buff_GlowColor(self.buff_model); + self.buff_model.skin = buff.m_skin; + + self.effects |= EF_NOSHADOW; + } + else + { + remove(self.buff_model); + self.buff_model = world; + + self.effects &= ~(EF_NOSHADOW); + } + } + + if(self.buff_model) + { + self.buff_model.effects = self.effects; + self.buff_model.effects |= EF_LOWPRECISION; + self.buff_model.effects = self.buff_model.effects & EFMASK_CHEAP; // eat performance + + self.buff_model.alpha = self.alpha; + } + +#undef BUFF_ONADD +#undef BUFF_ONREM + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, SpectateCopy) +{SELFPARAM(); + self.buffs = other.buffs; + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, VehicleEnter) +{ + vh_vehicle.buffs = vh_player.buffs; + vh_player.buffs = 0; + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, VehicleExit) +{ + vh_player.buffs = vh_vehicle.buffs; + vh_vehicle.buffs = 0; + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, PlayerRegen) +{SELFPARAM(); + if(self.buffs & BUFF_MEDIC.m_itemid) + { + regen_mod_rot = autocvar_g_buffs_medic_rot; + regen_mod_limit = regen_mod_max = autocvar_g_buffs_medic_max; + regen_mod_regen = autocvar_g_buffs_medic_regen; + } + + if(self.buffs & BUFF_SPEED.m_itemid) + regen_mod_regen = autocvar_g_buffs_speed_regen; + + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace"); + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":Buffs"); + return false; +} + +MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Buffs"); + return false; +} + +void buffs_DelayedInit() +{ + if(autocvar_g_buffs_spawn_count > 0) + if(find(world, classname, "item_buff") == world) + { + float i; + for(i = 0; i < autocvar_g_buffs_spawn_count; ++i) + { + entity e = spawn(); + e.spawnflags |= 64; // always randomize + e.velocity = randomvec() * 250; // this gets reset anyway if random location works + buff_Init(e); + } + } +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_campcheck.qc b/qcsrc/server/mutators/mutator/mutator_campcheck.qc new file mode 100644 index 000000000..be5bfceb8 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_campcheck.qc @@ -0,0 +1,86 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(campcheck, cvar("g_campcheck")); + +.float campcheck_nextcheck; +.float campcheck_traveled_distance; + +MUTATOR_HOOKFUNCTION(campcheck, PlayerDies) +{SELFPARAM(); + Kill_Notification(NOTIF_ONE, self, MSG_CENTER_CPID, CPID_CAMPCHECK); + + return false; +} + +MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate) +{ + if(IS_PLAYER(frag_target)) + if(IS_PLAYER(frag_attacker)) + if(frag_attacker != frag_target) + { + frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance; + frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink) +{SELFPARAM(); + if(!gameover) + if(!warmup_stage) // don't consider it camping during warmup? + if(time >= game_starttime) + if(IS_PLAYER(self)) + if(IS_REAL_CLIENT(self)) // bots may camp, but that's no reason to constantly kill them + if(self.deadflag == DEAD_NO) + if(!self.frozen) + if(!self.BUTTON_CHAT) + if(autocvar_g_campcheck_interval) + { + vector dist; + + // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement) + dist = self.prevorigin - self.origin; + dist.z = 0; + self.campcheck_traveled_distance += fabs(vlen(dist)); + + if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted())) + { + self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; + self.campcheck_traveled_distance = 0; + } + + if(time > self.campcheck_nextcheck) + { + if(self.campcheck_traveled_distance < autocvar_g_campcheck_distance) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_CAMPCHECK); + if(self.vehicle) + Damage(self.vehicle, self, self, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, self.vehicle.origin, '0 0 0'); + else + Damage(self, self, self, bound(0, autocvar_g_campcheck_damage, self.health + self.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, self.origin, '0 0 0'); + } + self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; + self.campcheck_traveled_distance = 0; + } + + return false; + } + + self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date + return false; +} + +MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn) +{SELFPARAM(); + self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; + self.campcheck_traveled_distance = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":CampCheck"); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_dodging.qc b/qcsrc/server/mutators/mutator/mutator_dodging.qc new file mode 100644 index 000000000..85b9fea61 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_dodging.qc @@ -0,0 +1,323 @@ +#ifdef IMPLEMENTATION + +#ifdef CSQC + #define PHYS_DODGING_FRAMETIME (1 / (frametime <= 0 ? 60 : frametime)) + #define PHYS_DODGING getstati(STAT_DODGING) + #define PHYS_DODGING_DELAY getstatf(STAT_DODGING_DELAY) + #define PHYS_DODGING_TIMEOUT(s) getstatf(STAT_DODGING_TIMEOUT) + #define PHYS_DODGING_HORIZ_SPEED_FROZEN getstatf(STAT_DODGING_HORIZ_SPEED_FROZEN) + #define PHYS_DODGING_FROZEN_NODOUBLETAP getstati(STAT_DODGING_FROZEN_NO_DOUBLETAP) + #define PHYS_DODGING_HORIZ_SPEED getstatf(STAT_DODGING_HORIZ_SPEED) + #define PHYS_DODGING_PRESSED_KEYS(s) s.pressedkeys + #define PHYS_DODGING_HEIGHT_THRESHOLD getstatf(STAT_DODGING_HEIGHT_THRESHOLD) + #define PHYS_DODGING_DISTANCE_THRESHOLD getstatf(STAT_DODGING_DISTANCE_THRESHOLD) + #define PHYS_DODGING_RAMP_TIME getstatf(STAT_DODGING_RAMP_TIME) + #define PHYS_DODGING_UP_SPEED getstatf(STAT_DODGING_UP_SPEED) + #define PHYS_DODGING_WALL getstatf(STAT_DODGING_WALL) +#elif defined(SVQC) + #define PHYS_DODGING_FRAMETIME sys_frametime + #define PHYS_DODGING g_dodging + #define PHYS_DODGING_DELAY autocvar_sv_dodging_delay + #define PHYS_DODGING_TIMEOUT(s) s.cvar_cl_dodging_timeout + #define PHYS_DODGING_HORIZ_SPEED_FROZEN autocvar_sv_dodging_horiz_speed_frozen + #define PHYS_DODGING_FROZEN_NODOUBLETAP autocvar_sv_dodging_frozen_doubletap + #define PHYS_DODGING_HORIZ_SPEED autocvar_sv_dodging_horiz_speed + #define PHYS_DODGING_PRESSED_KEYS(s) s.pressedkeys + #define PHYS_DODGING_HEIGHT_THRESHOLD autocvar_sv_dodging_height_threshold + #define PHYS_DODGING_DISTANCE_THRESHOLD autocvar_sv_dodging_wall_distance_threshold + #define PHYS_DODGING_RAMP_TIME autocvar_sv_dodging_ramp_time + #define PHYS_DODGING_UP_SPEED autocvar_sv_dodging_up_speed + #define PHYS_DODGING_WALL autocvar_sv_dodging_wall_dodging +#endif + +#ifdef SVQC + +float g_dodging; + +// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done.. +.float dodging_action; + +// the jump part of the dodge cannot be ramped +.float dodging_single_action; + +#include "../../../common/animdecide.qh" +#include "../../../common/physics.qh" + +.float cvar_cl_dodging_timeout; + +.float stat_dodging; +.float stat_dodging_delay; +.float stat_dodging_horiz_speed_frozen; +.float stat_dodging_frozen_nodoubletap; +.float stat_dodging_frozen; +.float stat_dodging_horiz_speed; +.float stat_dodging_height_threshold; +.float stat_dodging_distance_threshold; +.float stat_dodging_ramp_time; +.float stat_dodging_up_speed; +.float stat_dodging_wall; + +REGISTER_MUTATOR(dodging, cvar("g_dodging")) +{ + // this just turns on the cvar. + MUTATOR_ONADD + { + g_dodging = cvar("g_dodging"); + addstat(STAT_DODGING, AS_INT, stat_dodging); + addstat(STAT_DODGING_DELAY, AS_FLOAT, stat_dodging_delay); + addstat(STAT_DODGING_TIMEOUT, AS_FLOAT, cvar_cl_dodging_timeout); // we stat this, so it is updated on the client when updated on server (otherwise, chaos) + addstat(STAT_DODGING_FROZEN_NO_DOUBLETAP, AS_INT, stat_dodging_frozen_nodoubletap); + addstat(STAT_DODGING_HORIZ_SPEED_FROZEN, AS_FLOAT, stat_dodging_horiz_speed_frozen); + addstat(STAT_DODGING_FROZEN, AS_INT, stat_dodging_frozen); + addstat(STAT_DODGING_HORIZ_SPEED, AS_FLOAT, stat_dodging_horiz_speed); + addstat(STAT_DODGING_HEIGHT_THRESHOLD, AS_FLOAT, stat_dodging_height_threshold); + addstat(STAT_DODGING_DISTANCE_THRESHOLD, AS_FLOAT, stat_dodging_distance_threshold); + addstat(STAT_DODGING_RAMP_TIME, AS_FLOAT, stat_dodging_ramp_time); + addstat(STAT_DODGING_UP_SPEED, AS_FLOAT, stat_dodging_up_speed); + addstat(STAT_DODGING_WALL, AS_FLOAT, stat_dodging_wall); + } + + // this just turns off the cvar. + MUTATOR_ONROLLBACK_OR_REMOVE + { + g_dodging = 0; + } + + return false; +} + +#endif + +// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done.. +.float dodging_action; + +// the jump part of the dodge cannot be ramped +.float dodging_single_action; + + +// these are used to store the last key press time for each of the keys.. +.float last_FORWARD_KEY_time; +.float last_BACKWARD_KEY_time; +.float last_LEFT_KEY_time; +.float last_RIGHT_KEY_time; + +// these store the movement direction at the time of the dodge action happening. +.vector dodging_direction; + +// this indicates the last time a dodge was executed. used to check if another one is allowed +// and to ramp up the dodge acceleration in the physics hook. +.float last_dodging_time; + +// This is the velocity gain to be added over the ramp time. +// It will decrease from frame to frame during dodging_action = 1 +// until it's 0. +.float dodging_velocity_gain; + +#ifdef CSQC +.int pressedkeys; + +#elif defined(SVQC) + +void dodging_UpdateStats() +{SELFPARAM(); + self.stat_dodging = PHYS_DODGING; + self.stat_dodging_delay = PHYS_DODGING_DELAY; + self.stat_dodging_horiz_speed_frozen = PHYS_DODGING_HORIZ_SPEED_FROZEN; + self.stat_dodging_frozen = PHYS_DODGING_FROZEN; + self.stat_dodging_frozen_nodoubletap = PHYS_DODGING_FROZEN_NODOUBLETAP; + self.stat_dodging_height_threshold = PHYS_DODGING_HEIGHT_THRESHOLD; + self.stat_dodging_distance_threshold = PHYS_DODGING_DISTANCE_THRESHOLD; + self.stat_dodging_ramp_time = PHYS_DODGING_RAMP_TIME; + self.stat_dodging_up_speed = PHYS_DODGING_UP_SPEED; + self.stat_dodging_wall = PHYS_DODGING_WALL; +} + +#endif + +// returns 1 if the player is close to a wall +bool check_close_to_wall(float threshold) +{SELFPARAM(); + if (PHYS_DODGING_WALL == 0) { return false; } + + #define X(OFFSET) \ + tracebox(self.origin, self.mins, self.maxs, self.origin + OFFSET, true, self); \ + if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold) \ + return true; + X(1000*v_right); + X(-1000*v_right); + X(1000*v_forward); + X(-1000*v_forward); + #undef X + + return false; +} + +bool check_close_to_ground(float threshold) +{SELFPARAM(); + return IS_ONGROUND(self) ? true : false; +} + +float PM_dodging_checkpressedkeys() +{SELFPARAM(); + if(!PHYS_DODGING) + return false; + + float frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN); + float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP); + + // first check if the last dodge is far enough back in time so we can dodge again + if ((time - self.last_dodging_time) < PHYS_DODGING_DELAY) + return false; + + makevectors(self.angles); + + if (check_close_to_ground(PHYS_DODGING_HEIGHT_THRESHOLD) != 1 + && check_close_to_wall(PHYS_DODGING_DISTANCE_THRESHOLD) != 1) + return true; + + float tap_direction_x = 0; + float tap_direction_y = 0; + float dodge_detected = 0; + + #define X(COND,BTN,RESULT) \ + if (self.movement_##COND) \ + /* is this a state change? */ \ + if(!(PHYS_DODGING_PRESSED_KEYS(self) & KEY_##BTN) || frozen_no_doubletap) { \ + tap_direction_##RESULT; \ + if ((time - self.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(self)) \ + dodge_detected = 1; \ + self.last_##BTN##_KEY_time = time; \ + } + X(x < 0, BACKWARD, x--); + X(x > 0, FORWARD, x++); + X(y < 0, LEFT, y--); + X(y > 0, RIGHT, y++); + #undef X + + if (dodge_detected == 1) + { + self.last_dodging_time = time; + + self.dodging_action = 1; + self.dodging_single_action = 1; + + self.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED; + + self.dodging_direction_x = tap_direction_x; + self.dodging_direction_y = tap_direction_y; + + // normalize the dodging_direction vector.. (unlike UT99) XD + float length = self.dodging_direction_x * self.dodging_direction_x + + self.dodging_direction_y * self.dodging_direction_y; + length = sqrt(length); + + self.dodging_direction_x = self.dodging_direction_x * 1.0 / length; + self.dodging_direction_y = self.dodging_direction_y * 1.0 / length; + return true; + } + return false; +} + +void PM_dodging() +{SELFPARAM(); + if (!PHYS_DODGING) + return; + +#ifdef SVQC + dodging_UpdateStats(); +#endif + + if (PHYS_DEAD(self)) + return; + + // when swimming, no dodging allowed.. + if (self.waterlevel >= WATERLEVEL_SWIMMING) + { + self.dodging_action = 0; + self.dodging_direction_x = 0; + self.dodging_direction_y = 0; + return; + } + + // make sure v_up, v_right and v_forward are sane + makevectors(self.angles); + + // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code + // will be called ramp_time/frametime times = 2 times. so, we need to + // add 0.5 * the total speed each frame until the dodge action is done.. + float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME; + + // if ramp time is smaller than frametime we get problems ;D + common_factor = min(common_factor, 1); + + float horiz_speed = PHYS_FROZEN(self) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED; + float new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed); + new_velocity_gain = max(0, new_velocity_gain); + + float velocity_difference = self.dodging_velocity_gain - new_velocity_gain; + + // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D + if (self.dodging_action == 1) + { + //disable jump key during dodge accel phase + if(self.movement_z > 0) { self.movement_z = 0; } + + self.velocity += ((self.dodging_direction_y * velocity_difference) * v_right) + + ((self.dodging_direction_x * velocity_difference) * v_forward); + + self.dodging_velocity_gain = self.dodging_velocity_gain - velocity_difference; + } + + // the up part of the dodge is a single shot action + if (self.dodging_single_action == 1) + { + UNSET_ONGROUND(self); + + self.velocity += PHYS_DODGING_UP_SPEED * v_up; + +#ifdef SVQC + if (autocvar_sv_dodging_sound) + PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND); + + animdecide_setaction(self, ANIMACTION_JUMP, true); +#endif + + self.dodging_single_action = 0; + } + + // are we done with the dodging ramp yet? + if((self.dodging_action == 1) && ((time - self.last_dodging_time) > PHYS_DODGING_RAMP_TIME)) + { + // reset state so next dodge can be done correctly + self.dodging_action = 0; + self.dodging_direction_x = 0; + self.dodging_direction_y = 0; + } +} + +#ifdef SVQC + +MUTATOR_HOOKFUNCTION(dodging, GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout"); + return false; +} + +MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics) +{ + // print("dodging_PlayerPhysics\n"); + PM_dodging(); + + return false; +} + +MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys) +{ + PM_dodging_checkpressedkeys(); + + return false; +} + +#endif + +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_hook.qc b/qcsrc/server/mutators/mutator/mutator_hook.qc new file mode 100644 index 000000000..b298e7b2e --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_hook.qc @@ -0,0 +1,42 @@ +#ifdef IMPLEMENTATION +AUTOCVAR(g_grappling_hook, bool, false, _("let players spawn with the grappling hook which allows them to pull themselves up")); +#ifdef SVQC +REGISTER_MUTATOR(hook, autocvar_g_grappling_hook) { + MUTATOR_ONADD { + g_grappling_hook = true; + WEP_HOOK.ammo_factor = 0; + } + MUTATOR_ONROLLBACK_OR_REMOVE { + g_grappling_hook = false; + WEP_HOOK.ammo_factor = 1; + } +} + +MUTATOR_HOOKFUNCTION(hook, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":grappling_hook"); +} + +MUTATOR_HOOKFUNCTION(hook, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Hook"); +} + +MUTATOR_HOOKFUNCTION(hook, BuildGameplayTipsString) +{ + ret_string = strcat(ret_string, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n"); +} + +MUTATOR_HOOKFUNCTION(hook, PlayerSpawn) +{ + SELFPARAM(); + self.offhand = OFFHAND_HOOK; +} + +MUTATOR_HOOKFUNCTION(hook, FilterItem) +{ + return self.weapon == WEP_HOOK.m_id; +} + +#endif +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_invincibleproj.qc b/qcsrc/server/mutators/mutator/mutator_invincibleproj.qc new file mode 100644 index 000000000..5a781a881 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_invincibleproj.qc @@ -0,0 +1,25 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles")); + +MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile) +{ + if(other.health) + { + // disable health which in effect disables damage calculations + other.health = 0; + } + return 0; +} + +MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":InvincibleProjectiles"); + return 0; +} + +MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Invincible Projectiles"); + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_melee_only.qc b/qcsrc/server/mutators/mutator/mutator_melee_only.qc new file mode 100644 index 000000000..5b03f46ec --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_melee_only.qc @@ -0,0 +1,40 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !g_nexball); + +MUTATOR_HOOKFUNCTION(melee_only, SetStartItems) +{ + start_ammo_shells = warmup_start_ammo_shells = 0; + start_weapons = warmup_start_weapons = WEPSET(SHOTGUN); + + return false; +} + +MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(melee_only, FilterItem) +{SELFPARAM(); + switch (self.items) + { + case ITEM_HealthSmall.m_itemid: + case ITEM_ArmorSmall.m_itemid: + return false; + } + + return true; +} + +MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":MeleeOnly"); + return false; +} + +MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Melee Only Arena"); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_midair.qc b/qcsrc/server/mutators/mutator/mutator_midair.qc new file mode 100644 index 000000000..bf3428334 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_midair.qc @@ -0,0 +1,47 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(midair, cvar("g_midair")); + +.float midair_shieldtime; + +MUTATOR_HOOKFUNCTION(midair, PlayerDamage_Calculate) +{SELFPARAM(); + if(IS_PLAYER(frag_attacker)) + if(IS_PLAYER(frag_target)) + if(time < self.midair_shieldtime) + frag_damage = false; + + return false; +} + +MUTATOR_HOOKFUNCTION(midair, PlayerPowerups) +{SELFPARAM(); + if(time >= game_starttime) + if(self.flags & FL_ONGROUND) + { + self.effects |= (EF_ADDITIVE | EF_FULLBRIGHT); + self.midair_shieldtime = max(self.midair_shieldtime, time + autocvar_g_midair_shieldtime); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(midair, PlayerSpawn) +{SELFPARAM(); + if(IS_BOT_CLIENT(self)) + self.bot_moveskill = 0; // disable bunnyhopping + + return false; +} + +MUTATOR_HOOKFUNCTION(midair, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":midair"); + return false; +} + +MUTATOR_HOOKFUNCTION(midair, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Midair"); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_multijump.qc b/qcsrc/server/mutators/mutator/mutator_multijump.qc new file mode 100644 index 000000000..f01a801aa --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_multijump.qc @@ -0,0 +1,182 @@ +#ifdef IMPLEMENTATION +#ifdef SVQC + #include "../../antilag.qh" +#endif +#include "../../../common/physics.qh" + +.int multijump_count; +.bool multijump_ready; +.bool cvar_cl_multijump; + +#ifdef CSQC + +#define PHYS_MULTIJUMP getstati(STAT_MULTIJUMP) +#define PHYS_MULTIJUMP_SPEED getstatf(STAT_MULTIJUMP_SPEED) +#define PHYS_MULTIJUMP_ADD getstati(STAT_MULTIJUMP_ADD) +#define PHYS_MULTIJUMP_MAXSPEED getstatf(STAT_MULTIJUMP_MAXSPEED) +#define PHYS_MULTIJUMP_DODGING getstati(STAT_MULTIJUMP_DODGING) + +#elif defined(SVQC) + +#define PHYS_MULTIJUMP autocvar_g_multijump +#define PHYS_MULTIJUMP_SPEED autocvar_g_multijump_speed +#define PHYS_MULTIJUMP_ADD autocvar_g_multijump_add +#define PHYS_MULTIJUMP_MAXSPEED autocvar_g_multijump_maxspeed +#define PHYS_MULTIJUMP_DODGING autocvar_g_multijump_dodging + + +.float stat_multijump; +.float stat_multijump_speed; +.float stat_multijump_add; +.float stat_multijump_maxspeed; +.float stat_multijump_dodging; + +void multijump_UpdateStats() +{SELFPARAM(); + self.stat_multijump = PHYS_MULTIJUMP; + self.stat_multijump_speed = PHYS_MULTIJUMP_SPEED; + self.stat_multijump_add = PHYS_MULTIJUMP_ADD; + self.stat_multijump_maxspeed = PHYS_MULTIJUMP_MAXSPEED; + self.stat_multijump_dodging = PHYS_MULTIJUMP_DODGING; +} + +void multijump_AddStats() +{ + addstat(STAT_MULTIJUMP, AS_INT, stat_multijump); + addstat(STAT_MULTIJUMP_SPEED, AS_FLOAT, stat_multijump_speed); + addstat(STAT_MULTIJUMP_ADD, AS_INT, stat_multijump_add); + addstat(STAT_MULTIJUMP_MAXSPEED, AS_FLOAT, stat_multijump_maxspeed); + addstat(STAT_MULTIJUMP_DODGING, AS_INT, stat_multijump_dodging); +} + +#endif + +void PM_multijump() +{SELFPARAM(); + if(!PHYS_MULTIJUMP) { return; } + + if(IS_ONGROUND(self)) + { + self.multijump_count = 0; + } +} + +bool PM_multijump_checkjump() +{SELFPARAM(); + if(!PHYS_MULTIJUMP) { return false; } + +#ifdef SVQC + bool client_multijump = self.cvar_cl_multijump; +#elif defined(CSQC) + bool client_multijump = cvar("cl_multijump"); + + if(cvar("cl_multijump") > 1) + return false; // nope +#endif + + if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self) && client_multijump) // jump button pressed this frame and we are in midair + self.multijump_ready = true; // this is necessary to check that we released the jump button and pressed it again + else + self.multijump_ready = false; + + int phys_multijump = PHYS_MULTIJUMP; + +#ifdef CSQC + phys_multijump = (PHYS_MULTIJUMP) ? -1 : 0; +#endif + + if(!player_multijump && self.multijump_ready && (self.multijump_count < phys_multijump || phys_multijump == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED && (!PHYS_MULTIJUMP_MAXSPEED || vlen(self.velocity) <= PHYS_MULTIJUMP_MAXSPEED)) + { + if (PHYS_MULTIJUMP) + { + if (!PHYS_MULTIJUMP_ADD) // in this case we make the z velocity == jumpvelocity + { + if (self.velocity_z < PHYS_JUMPVELOCITY) + { + player_multijump = true; + self.velocity_z = 0; + } + } + else + player_multijump = true; + + if(player_multijump) + { + if(PHYS_MULTIJUMP_DODGING) + if(self.movement_x != 0 || self.movement_y != 0) // don't remove all speed if player isnt pressing any movement keys + { + float curspeed; + vector wishvel, wishdir; + +/*#ifdef SVQC + curspeed = max( + vlen(vec2(self.velocity)), // current xy speed + vlen(vec2(antilag_takebackavgvelocity(self, max(self.lastteleporttime + sys_frametime, time - 0.25), time))) // average xy topspeed over the last 0.25 secs + ); +#elif defined(CSQC)*/ + curspeed = vlen(vec2(self.velocity)); +//#endif + + makevectors(self.v_angle_y * '0 1 0'); + wishvel = v_forward * self.movement_x + v_right * self.movement_y; + wishdir = normalize(wishvel); + + self.velocity_x = wishdir_x * curspeed; // allow "dodging" at a multijump + self.velocity_y = wishdir_y * curspeed; + // keep velocity_z unchanged! + } + if (PHYS_MULTIJUMP > 0) + { + self.multijump_count += 1; + } + } + } + self.multijump_ready = false; // require releasing and pressing the jump button again for the next jump + } + + return false; +} + +#ifdef SVQC +REGISTER_MUTATOR(multijump, cvar("g_multijump")) +{ + MUTATOR_ONADD + { + multijump_AddStats(); + } + return false; +} + +MUTATOR_HOOKFUNCTION(multijump, PlayerPhysics) +{ + multijump_UpdateStats(); + PM_multijump(); + + return false; +} + +MUTATOR_HOOKFUNCTION(multijump, PlayerJump) +{ + return PM_multijump_checkjump(); +} + +MUTATOR_HOOKFUNCTION(multijump, GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_multijump, "cl_multijump"); + return false; +} + +MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":multijump"); + return false; +} + +MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Multi jump"); + return false; +} + +#endif +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_nades.qc b/qcsrc/server/mutators/mutator/mutator_nades.qc new file mode 100644 index 000000000..d9fd1c8b6 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_nades.qc @@ -0,0 +1,1233 @@ +#ifndef MUTATOR_NADES_H +#define MUTATOR_NADES_H + +.entity nade; +.entity fake_nade; +.float nade_timer; +.float nade_refire; +.float bonus_nades; +.float nade_special_time; +.float bonus_nade_score; +.float nade_type; +.string pokenade_type; +.entity nade_damage_target; +.float cvar_cl_nade_type; +.string cvar_cl_pokenade_type; +.float toss_time; +.float stat_healing_orb; +.float stat_healing_orb_alpha; +.float nade_show_particles; + +// Remove nades that are being thrown +void(entity player) nades_Clear; + +// Give a bonus grenade to a player +void(entity player, float score) nades_GiveBonus; +// Remove all bonus nades from a player +void(entity player) nades_RemoveBonus; + +#endif +#ifdef IMPLEMENTATION + +#include "../../../common/nades/all.qh" +#include "../../../common/gamemodes/all.qh" +#include "../../../common/monsters/spawn.qh" +#include "../../../common/monsters/sv_monsters.qh" +#include "../../g_subs.qh" + +REGISTER_MUTATOR(nades, cvar("g_nades")) +{ + MUTATOR_ONADD + { + addstat(STAT_NADE_TIMER, AS_FLOAT, nade_timer); + addstat(STAT_NADE_BONUS, AS_FLOAT, bonus_nades); + addstat(STAT_NADE_BONUS_TYPE, AS_INT, nade_type); + addstat(STAT_NADE_BONUS_SCORE, AS_FLOAT, bonus_nade_score); + addstat(STAT_HEALING_ORB, AS_FLOAT, stat_healing_orb); + addstat(STAT_HEALING_ORB_ALPHA, AS_FLOAT, stat_healing_orb_alpha); + } + + return false; +} + +.float nade_time_primed; + +.entity nade_spawnloc; + +void nade_timer_think() +{SELFPARAM(); + self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10); + self.nextthink = time; + if(!self.owner || wasfreed(self.owner)) + remove(self); +} + +void nade_burn_spawn(entity _nade) +{ + CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[true], true); +} + +void nade_spawn(entity _nade) +{ + entity timer = spawn(); + setmodel(timer, MDL_NADE_TIMER); + setattachment(timer, _nade, ""); + timer.classname = "nade_timer"; + timer.colormap = _nade.colormap; + timer.glowmod = _nade.glowmod; + timer.think = nade_timer_think; + timer.nextthink = time; + timer.wait = _nade.wait; + timer.owner = _nade; + timer.skin = 10; + + _nade.effects |= EF_LOWPRECISION; + + CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[false], true); +} + +void napalm_damage(float dist, float damage, float edgedamage, float burntime) +{SELFPARAM(); + entity e; + float d; + vector p; + + if ( damage < 0 ) + return; + + RandomSelection_Init(); + for(e = WarpZone_FindRadius(self.origin, dist, true); e; e = e.chain) + if(e.takedamage == DAMAGE_AIM) + if(self.realowner != e || autocvar_g_nades_napalm_selfdamage) + if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self)) + if(!e.frozen) + { + p = e.origin; + p.x += e.mins.x + random() * (e.maxs.x - e.mins.x); + p.y += e.mins.y + random() * (e.maxs.y - e.mins.y); + p.z += e.mins.z + random() * (e.maxs.z - e.mins.z); + d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p); + if(d < dist) + { + e.fireball_impactvec = p; + RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e)); + } + } + if(RandomSelection_chosen_ent) + { + d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec); + d = damage + (edgedamage - damage) * (d / dist); + Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE); + //trailparticles(self, particleeffectnum(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec); + Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); + } +} + + +void napalm_ball_think() +{SELFPARAM(); + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time > self.pushltime) + { + remove(self); + return; + } + + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { self.velocity_z = 200; } + } + + self.angles = vectoangles(self.velocity); + + napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage, + autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime); + + self.nextthink = time + 0.1; +} + + +void nade_napalm_ball() +{SELFPARAM(); + entity proj; + vector kick; + + spamsound(self, CH_SHOTS, SND(FIREBALL_FIRE), VOL_BASE, ATTEN_NORM); + + proj = spawn (); + proj.owner = self.owner; + proj.realowner = self.realowner; + proj.team = self.owner.team; + proj.classname = "grenade"; + proj.bot_dodge = true; + proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage; + proj.movetype = MOVETYPE_BOUNCE; + proj.projectiledeathtype = DEATH_NADE_NAPALM.m_id; + PROJECTILE_MAKETRIGGER(proj); + setmodel(proj, MDL_Null); + proj.scale = 1;//0.5; + setsize(proj, '-4 -4 -4', '4 4 4'); + setorigin(proj, self.origin); + proj.think = napalm_ball_think; + proj.nextthink = time; + proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale; + proj.effects = EF_LOWPRECISION | EF_FLAME; + + kick.x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick.y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; + kick.z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread; + proj.velocity = kick; + + proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime; + + proj.angles = vectoangles(proj.velocity); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; + + //CSQCProjectile(proj, true, PROJECTILE_NAPALM_FIRE, true); +} + + +void napalm_fountain_think() +{SELFPARAM(); + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time >= self.ltime) + { + remove(self); + return; + } + + vector midpoint = ((self.absmin + self.absmax) * 0.5); + if(pointcontents(midpoint) == CONTENT_WATER) + { + self.velocity = self.velocity * 0.5; + + if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) + { self.velocity_z = 200; } + + UpdateCSQCProjectile(self); + } + + napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage, + autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime); + + self.nextthink = time + 0.1; + if(time >= self.nade_special_time) + { + self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay; + nade_napalm_ball(); + } +} + +void nade_napalm_boom() +{SELFPARAM(); + entity fountain; + int c; + for (c = 0; c < autocvar_g_nades_napalm_ball_count; c++) + nade_napalm_ball(); + + + fountain = spawn(); + fountain.owner = self.owner; + fountain.realowner = self.realowner; + fountain.origin = self.origin; + setorigin(fountain, fountain.origin); + fountain.think = napalm_fountain_think; + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime; + fountain.pushltime = fountain.ltime; + fountain.team = self.team; + fountain.movetype = MOVETYPE_TOSS; + fountain.projectiledeathtype = DEATH_NADE_NAPALM.m_id; + fountain.bot_dodge = true; + fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage; + fountain.nade_special_time = time; + setsize(fountain, '-16 -16 -16', '16 16 16'); + CSQCProjectile(fountain, true, PROJECTILE_NAPALM_FOUNTAIN, true); +} + +void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time) +{ + frost_target.frozen_by = freezefield.realowner; + Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1); + Freeze(frost_target, 1/freeze_time, 3, false); + + Drop_Special_Items(frost_target); +} + +void nade_ice_think() +{SELFPARAM(); + + if(round_handler_IsActive()) + if(!round_handler_IsRoundStarted()) + { + remove(self); + return; + } + + if(time >= self.ltime) + { + if ( autocvar_g_nades_ice_explode ) + { + entity expef = EFFECT_NADE_EXPLODE(self.realowner.team); + Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1); + sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + + RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + } + remove(self); + return; + } + + + self.nextthink = time+0.1; + + // gaussian + float randomr; + randomr = random(); + randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius; + float randomw; + randomw = random()*M_PI*2; + vector randomp; + randomp.x = randomr*cos(randomw); + randomp.y = randomr*sin(randomw); + randomp.z = 1; + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, self.origin + randomp, '0 0 0', 1); + + if(time >= self.nade_special_time) + { + self.nade_special_time = time+0.7; + + Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1); + Send_Effect(EFFECT_ICEFIELD, self.origin, '0 0 0', 1); + } + + + float current_freeze_time = self.ltime - time - 0.1; + + entity e; + for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain) + if(e != self) + if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner)) + if(e.takedamage && e.deadflag == DEAD_NO) + if(e.health > 0) + if(!e.revival_time || ((time - e.revival_time) >= 1.5)) + if(!e.frozen) + if(current_freeze_time > 0) + nade_ice_freeze(self, e, current_freeze_time); +} + +void nade_ice_boom() +{SELFPARAM(); + entity fountain; + fountain = spawn(); + fountain.owner = self.owner; + fountain.realowner = self.realowner; + fountain.origin = self.origin; + setorigin(fountain, fountain.origin); + fountain.think = nade_ice_think; + fountain.nextthink = time; + fountain.ltime = time + autocvar_g_nades_ice_freeze_time; + fountain.pushltime = fountain.wait = fountain.ltime; + fountain.team = self.team; + fountain.movetype = MOVETYPE_TOSS; + fountain.projectiledeathtype = DEATH_NADE_ICE.m_id; + fountain.bot_dodge = false; + setsize(fountain, '-16 -16 -16', '16 16 16'); + fountain.nade_special_time = time+0.3; + fountain.angles = self.angles; + + if ( autocvar_g_nades_ice_explode ) + { + setmodel(fountain, MDL_PROJECTILE_GRENADE); + entity timer = spawn(); + setmodel(timer, MDL_NADE_TIMER); + setattachment(timer, fountain, ""); + timer.classname = "nade_timer"; + timer.colormap = self.colormap; + timer.glowmod = self.glowmod; + timer.think = nade_timer_think; + timer.nextthink = time; + timer.wait = fountain.ltime; + timer.owner = fountain; + timer.skin = 10; + } + else + setmodel(fountain, MDL_Null); +} + +void nade_translocate_boom() +{SELFPARAM(); + if(self.realowner.vehicle) + return; + + vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins.z - 24); + tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner); + locout = trace_endpos; + + makevectors(self.realowner.angles); + + MUTATOR_CALLHOOK(PortalTeleport, self.realowner); + + TeleportPlayer(self, self.realowner, locout, self.realowner.angles, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); +} + +void nade_spawn_boom() +{SELFPARAM(); + entity spawnloc = spawn(); + setorigin(spawnloc, self.origin); + setsize(spawnloc, self.realowner.mins, self.realowner.maxs); + spawnloc.movetype = MOVETYPE_NONE; + spawnloc.solid = SOLID_NOT; + spawnloc.drawonlytoclient = self.realowner; + spawnloc.effects = EF_STARDUST; + spawnloc.cnt = autocvar_g_nades_spawn_count; + + if(self.realowner.nade_spawnloc) + { + remove(self.realowner.nade_spawnloc); + self.realowner.nade_spawnloc = world; + } + + self.realowner.nade_spawnloc = spawnloc; +} + +void nade_heal_think() +{SELFPARAM(); + if(time >= self.ltime) + { + remove(self); + return; + } + + self.nextthink = time; + + if(time >= self.nade_special_time) + { + self.nade_special_time = time+0.25; + self.nade_show_particles = 1; + } + else + self.nade_show_particles = 0; +} + +void nade_heal_touch() +{SELFPARAM(); + float maxhealth; + float health_factor; + if(IS_PLAYER(other) || IS_MONSTER(other)) + if(other.deadflag == DEAD_NO) + if(!other.frozen) + { + health_factor = autocvar_g_nades_heal_rate*frametime/2; + if ( other != self.realowner ) + { + if ( SAME_TEAM(other,self) ) + health_factor *= autocvar_g_nades_heal_friend; + else + health_factor *= autocvar_g_nades_heal_foe; + } + if ( health_factor > 0 ) + { + maxhealth = (IS_MONSTER(other)) ? other.max_health : g_pickup_healthmega_max; + if ( other.health < maxhealth ) + { + if ( self.nade_show_particles ) + Send_Effect(EFFECT_HEALING, other.origin, '0 0 0', 1); + other.health = min(other.health+health_factor, maxhealth); + } + other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); + } + else if ( health_factor < 0 ) + { + Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL.m_id,other.origin,'0 0 0'); + } + + } + + if ( IS_REAL_CLIENT(other) || IS_VEHICLE(other) ) + { + entity show_red = (IS_VEHICLE(other)) ? other.owner : other; + show_red.stat_healing_orb = time+0.1; + show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime; + } +} + +void nade_heal_boom() +{SELFPARAM(); + entity healer; + healer = spawn(); + healer.owner = self.owner; + healer.realowner = self.realowner; + setorigin(healer, self.origin); + healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar + healer.ltime = time + healer.healer_lifetime; + healer.team = self.realowner.team; + healer.bot_dodge = false; + healer.solid = SOLID_TRIGGER; + healer.touch = nade_heal_touch; + + setmodel(healer, MDL_NADE_HEAL); + healer.healer_radius = autocvar_g_nades_nade_radius; + vector size = '1 1 1' * healer.healer_radius / 2; + setsize(healer,-size,size); + + Net_LinkEntity(healer, true, 0, healer_send); + + healer.think = nade_heal_think; + healer.nextthink = time; + healer.SendFlags |= 1; +} + +void nade_monster_boom() +{SELFPARAM(); + entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, false, false, 1); + + if(autocvar_g_nades_pokenade_monster_lifetime > 0) + e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime; + e.monster_skill = MONSTER_SKILL_INSANE; +} + +void nade_boom() +{SELFPARAM(); + entity expef = NULL; + bool nade_blast = true; + + switch ( Nades[self.nade_type] ) + { + case NADE_TYPE_NAPALM: + nade_blast = autocvar_g_nades_napalm_blast; + expef = EFFECT_EXPLOSION_MEDIUM; + break; + case NADE_TYPE_ICE: + nade_blast = false; + expef = EFFECT_ELECTRO_COMBO; // hookbomb_explode electro_combo bigplasma_impact + break; + case NADE_TYPE_TRANSLOCATE: + nade_blast = false; + break; + case NADE_TYPE_MONSTER: + case NADE_TYPE_SPAWN: + nade_blast = false; + switch(self.realowner.team) + { + case NUM_TEAM_1: expef = EFFECT_SPAWN_RED; break; + case NUM_TEAM_2: expef = EFFECT_SPAWN_BLUE; break; + case NUM_TEAM_3: expef = EFFECT_SPAWN_YELLOW; break; + case NUM_TEAM_4: expef = EFFECT_SPAWN_PINK; break; + default: expef = EFFECT_SPAWN_NEUTRAL; break; + } + break; + case NADE_TYPE_HEAL: + nade_blast = false; + expef = EFFECT_SPAWN_RED; + break; + + default: + case NADE_TYPE_NORMAL: + expef = EFFECT_NADE_EXPLODE(self.realowner.team); + break; + } + + if(expef) + Send_Effect(expef, findbetterlocation(self.origin, 8), '0 0 0', 1); + + sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); + sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); + + self.event_damage = func_null; // prevent somehow calling damage in the next call + + if(nade_blast) + { + RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, + autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); + Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); + } + + if(self.takedamage) + switch ( Nades[self.nade_type] ) + { + case NADE_TYPE_NAPALM: nade_napalm_boom(); break; + case NADE_TYPE_ICE: nade_ice_boom(); break; + case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break; + case NADE_TYPE_SPAWN: nade_spawn_boom(); break; + case NADE_TYPE_HEAL: nade_heal_boom(); break; + case NADE_TYPE_MONSTER: nade_monster_boom(); break; + } + + entity head; + for(head = world; (head = find(head, classname, "grapplinghook")); ) + if(head.aiment == self) + RemoveGrapplingHook(head.realowner); + + remove(self); +} + +void nade_touch() +{SELFPARAM(); + /*float is_weapclip = 0; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW) + if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)) + if (!(trace_dphitcontents & DPCONTENTS_OPAQUE)) + is_weapclip = 1;*/ + if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip) + { + entity head; + for(head = world; (head = find(head, classname, "grapplinghook")); ) + if(head.aiment == self) + RemoveGrapplingHook(head.realowner); + remove(self); + return; + } + + PROJECTILE_TOUCH; + + //setsize(self, '-2 -2 -2', '2 2 2'); + //UpdateCSQCProjectile(self); + if(self.health == self.max_health) + { + spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTEN_NORM); + return; + } + + self.enemy = other; + nade_boom(); +} + +void nade_beep() +{SELFPARAM(); + sound(self, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); + self.think = nade_boom; + self.nextthink = max(self.wait, time); +} + +void nade_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{SELFPARAM(); + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + self.takedamage = DAMAGE_NO; + nade_boom(); + return; + } + + if(self.nade_type == NADE_TYPE_TRANSLOCATE.m_id || self.nade_type == NADE_TYPE_SPAWN.m_id) + return; + + if(DEATH_ISWEAPON(deathtype, WEP_BLASTER)) + { + force *= 1.5; + damage = 0; + } + + if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) && (deathtype & HITTYPE_SECONDARY)) + { + force *= 0.5; // too much + frag_damage = 0; + } + + if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER)) + { + force *= 6; + damage = self.max_health * 0.55; + } + + if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_HMG)) + damage = self.max_health * 0.1; + + if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) // WEAPONTODO + if(deathtype & HITTYPE_SECONDARY) + { + damage = self.max_health * 0.1; + force *= 10; + } + else + damage = self.max_health * 1.15; + + self.velocity += force; + UpdateCSQCProjectile(self); + + if(damage <= 0 || ((self.flags & FL_ONGROUND) && IS_PLAYER(attacker))) + return; + + if(self.health == self.max_health) + { + sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); + self.nextthink = max(time + autocvar_g_nades_nade_lifetime, time); + self.think = nade_beep; + } + + self.health -= damage; + + if ( self.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) ) + self.realowner = attacker; + + if(self.health <= 0) + W_PrepareExplosionByDamage(attacker, nade_boom); + else + nade_burn_spawn(self); +} + +void toss_nade(entity e, vector _velocity, float _time) +{SELFPARAM(); + if(e.nade == world) + return; + + entity _nade = e.nade; + e.nade = world; + + remove(e.fake_nade); + e.fake_nade = world; + + makevectors(e.v_angle); + + W_SetupShot(e, false, false, "", CH_WEAPON_A, 0); + + Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES); + + vector offset = (v_forward * autocvar_g_nades_throw_offset.x) + + (v_right * autocvar_g_nades_throw_offset.y) + + (v_up * autocvar_g_nades_throw_offset.z); + if(autocvar_g_nades_throw_offset == '0 0 0') + offset = '0 0 0'; + + setorigin(_nade, w_shotorg + offset + (v_right * 25) * -1); + //setmodel(_nade, MDL_PROJECTILE_NADE); + //setattachment(_nade, world, ""); + PROJECTILE_MAKETRIGGER(_nade); + setsize(_nade, '-16 -16 -16', '16 16 16'); + _nade.movetype = MOVETYPE_BOUNCE; + + tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, false, _nade); + if (trace_startsolid) + setorigin(_nade, e.origin); + + if(self.v_angle.x >= 70 && self.v_angle.x <= 110 && self.BUTTON_CROUCH) + _nade.velocity = '0 0 100'; + else if(autocvar_g_nades_nade_newton_style == 1) + _nade.velocity = e.velocity + _velocity; + else if(autocvar_g_nades_nade_newton_style == 2) + _nade.velocity = _velocity; + else + _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, true); + + _nade.touch = nade_touch; + _nade.health = autocvar_g_nades_nade_health; + _nade.max_health = _nade.health; + _nade.takedamage = DAMAGE_AIM; + _nade.event_damage = nade_damage; + _nade.customizeentityforclient = func_null; + _nade.exteriormodeltoclient = world; + _nade.traileffectnum = 0; + _nade.teleportable = true; + _nade.pushable = true; + _nade.gravity = 1; + _nade.missile_flags = MIF_SPLASH | MIF_ARC; + _nade.damagedbycontents = true; + _nade.angles = vectoangles(_nade.velocity); + _nade.flags = FL_PROJECTILE; + _nade.projectiledeathtype = DEATH_NADE.m_id; + _nade.toss_time = time; + _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX); + + if(_nade.nade_type == NADE_TYPE_TRANSLOCATE.m_id || _nade.nade_type == NADE_TYPE_SPAWN.m_id) + _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; + else + _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; + + nade_spawn(_nade); + + if(_time) + { + _nade.think = nade_boom; + _nade.nextthink = _time; + } + + e.nade_refire = time + autocvar_g_nades_nade_refire; + e.nade_timer = 0; +} + +void nades_GiveBonus(entity player, float score) +{ + if (autocvar_g_nades) + if (autocvar_g_nades_bonus) + if (IS_REAL_CLIENT(player)) + if (IS_PLAYER(player) && player.bonus_nades < autocvar_g_nades_bonus_max) + if (player.frozen == 0) + if (player.deadflag == DEAD_NO) + { + if ( player.bonus_nade_score < 1 ) + player.bonus_nade_score += score/autocvar_g_nades_bonus_score_max; + + if ( player.bonus_nade_score >= 1 ) + { + Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS); + play2(player, SND(KH_ALARM)); + player.bonus_nades++; + player.bonus_nade_score -= 1; + } + } +} + +void nades_RemoveBonus(entity player) +{ + player.bonus_nades = player.bonus_nade_score = 0; +} + +float nade_customize() +{SELFPARAM(); + //if(IS_SPEC(other)) { return false; } + if(other == self.realowner || (IS_SPEC(other) && other.enemy == self.realowner)) + { + // somewhat hide the model, but keep the glow + //self.effects = 0; + if(self.traileffectnum) + self.traileffectnum = 0; + self.alpha = -1; + } + else + { + //self.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + if(!self.traileffectnum) + self.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades[self.nade_type].m_projectile[false], self.team).eent_eff_name); + self.alpha = 1; + } + + return true; +} + +void nade_prime() +{SELFPARAM(); + if(autocvar_g_nades_bonus_only) + if(!self.bonus_nades) + return; // only allow bonus nades + + if(self.nade) + remove(self.nade); + + if(self.fake_nade) + remove(self.fake_nade); + + entity n = spawn(), fn = spawn(); + + n.classname = "nade"; + fn.classname = "fake_nade"; + + if(self.items & ITEM_Strength.m_itemid && autocvar_g_nades_bonus_onstrength) + n.nade_type = self.nade_type; + else if (self.bonus_nades >= 1) + { + n.nade_type = self.nade_type; + n.pokenade_type = self.pokenade_type; + self.bonus_nades -= 1; + } + else + { + n.nade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_nade_type : autocvar_g_nades_nade_type); + n.pokenade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type); + } + + n.nade_type = bound(1, n.nade_type, Nades_COUNT); + + setmodel(n, MDL_PROJECTILE_NADE); + //setattachment(n, self, "bip01 l hand"); + n.exteriormodeltoclient = self; + n.customizeentityforclient = nade_customize; + n.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades[n.nade_type].m_projectile[false], self.team).eent_eff_name); + n.colormod = Nades[n.nade_type].m_color; + n.realowner = self; + n.colormap = self.colormap; + n.glowmod = self.glowmod; + n.wait = time + autocvar_g_nades_nade_lifetime; + n.nade_time_primed = time; + n.think = nade_beep; + n.nextthink = max(n.wait - 3, time); + n.projectiledeathtype = DEATH_NADE.m_id; + + setmodel(fn, MDL_NADE_VIEW); + setattachment(fn, self.weaponentity, ""); + fn.realowner = fn.owner = self; + fn.colormod = Nades[n.nade_type].m_color; + fn.colormap = self.colormap; + fn.glowmod = self.glowmod; + fn.think = SUB_Remove; + fn.nextthink = n.wait; + + self.nade = n; + self.fake_nade = fn; +} + +float CanThrowNade() +{SELFPARAM(); + if(self.vehicle) + return false; + + if(gameover) + return false; + + if(self.deadflag != DEAD_NO) + return false; + + if (!autocvar_g_nades) + return false; // allow turning them off mid match + + if(forbidWeaponUse(self)) + return false; + + if (!IS_PLAYER(self)) + return false; + + return true; +} + +.bool nade_altbutton; + +void nades_CheckThrow() +{SELFPARAM(); + if(!CanThrowNade()) + return; + + entity held_nade = self.nade; + if (!held_nade) + { + self.nade_altbutton = true; + if(time > self.nade_refire) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW); + nade_prime(); + self.nade_refire = time + autocvar_g_nades_nade_refire; + } + } + else + { + self.nade_altbutton = false; + if (time >= held_nade.nade_time_primed + 1) { + makevectors(self.v_angle); + float _force = time - held_nade.nade_time_primed; + _force /= autocvar_g_nades_nade_lifetime; + _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); + toss_nade(self, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0); + } + } +} + +void nades_Clear(entity player) +{ + if(player.nade) + remove(player.nade); + if(player.fake_nade) + remove(player.fake_nade); + + player.nade = player.fake_nade = world; + player.nade_timer = 0; +} + +MUTATOR_HOOKFUNCTION(nades, VehicleEnter) +{ + if(vh_player.nade) + toss_nade(vh_player, '0 0 100', max(vh_player.nade.wait, time + 0.05)); + + return false; +} + +CLASS(NadeOffhand, OffhandWeapon) + METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed)) + { + entity held_nade = player.nade; + if (held_nade) + { + player.nade_timer = bound(0, (time - held_nade.nade_time_primed) / autocvar_g_nades_nade_lifetime, 1); + // LOG_TRACEF("%d %d\n", player.nade_timer, time - held_nade.nade_time_primed); + makevectors(player.angles); + held_nade.velocity = player.velocity; + setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0); + held_nade.angles_y = player.angles.y; + + if (time + 0.1 >= held_nade.wait) + toss_nade(player, '0 0 0', time + 0.05); + } + + if (!CanThrowNade()) return; + if (!(time > player.nade_refire)) return; + if (key_pressed) { + if (!held_nade) { + nade_prime(); + held_nade = player.nade; + } + } else if (time >= held_nade.nade_time_primed + 1) { + if (held_nade) { + makevectors(player.v_angle); + float _force = time - held_nade.nade_time_primed; + _force /= autocvar_g_nades_nade_lifetime; + _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); + toss_nade(player, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0); + } + } + } +ENDCLASS(NadeOffhand) +NadeOffhand OFFHAND_NADE; STATIC_INIT(OFFHAND_NADE) { OFFHAND_NADE = NEW(NadeOffhand); } + +MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST) +{ + if (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) { + nades_CheckThrow(); + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) +{SELFPARAM(); + if (!IS_PLAYER(self)) { return false; } + + if (self.nade && (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)))) OFFHAND_NADE.offhand_think(OFFHAND_NADE, self, self.nade_altbutton); + + if(IS_PLAYER(self)) + { + if ( autocvar_g_nades_bonus && autocvar_g_nades ) + { + entity key; + float key_count = 0; + FOR_EACH_KH_KEY(key) if(key.owner == self) { ++key_count; } + + float time_score; + if(self.flagcarried || self.ballcarried) // this player is important + time_score = autocvar_g_nades_bonus_score_time_flagcarrier; + else + time_score = autocvar_g_nades_bonus_score_time; + + if(key_count) + time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding + + if(autocvar_g_nades_bonus_client_select) + { + self.nade_type = self.cvar_cl_nade_type; + self.pokenade_type = self.cvar_cl_pokenade_type; + } + else + { + self.nade_type = autocvar_g_nades_bonus_type; + self.pokenade_type = autocvar_g_nades_pokenade_monster_type; + } + + self.nade_type = bound(1, self.nade_type, Nades_COUNT); + + if(self.bonus_nade_score >= 0 && autocvar_g_nades_bonus_score_max) + nades_GiveBonus(self, time_score / autocvar_g_nades_bonus_score_max); + } + else + { + self.bonus_nades = self.bonus_nade_score = 0; + } + } + + float n = 0; + entity o = world; + if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) + n = -1; + else + { + vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; + n = 0; + FOR_EACH_PLAYER(other) if(self != other) + { + if(other.deadflag == DEAD_NO) + if(other.frozen == 0) + if(SAME_TEAM(other, self)) + if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) + { + if(!o) + o = other; + if(self.frozen == 1) + other.reviving = true; + ++n; + } + } + } + + if(n && self.frozen == 3) // OK, there is at least one teammate reviving us + { + self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); + self.health = max(1, self.revive_progress * start_health); + + if(self.revive_progress >= 1) + { + Unfreeze(self); + + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); + Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname); + } + + FOR_EACH_PLAYER(other) if(other.reviving) + { + other.revive_progress = self.revive_progress; + other.reviving = false; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(nades, PlayerSpawn) +{SELFPARAM(); + if(autocvar_g_nades_spawn) + self.nade_refire = time + autocvar_g_spawnshieldtime; + else + self.nade_refire = time + autocvar_g_nades_nade_refire; + + if(autocvar_g_nades_bonus_client_select) + self.nade_type = self.cvar_cl_nade_type; + + self.nade_timer = 0; + + if (!self.offhand) self.offhand = OFFHAND_NADE; + + if(self.nade_spawnloc) + { + setorigin(self, self.nade_spawnloc.origin); + self.nade_spawnloc.cnt -= 1; + + if(self.nade_spawnloc.cnt <= 0) + { + remove(self.nade_spawnloc); + self.nade_spawnloc = world; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(nades, PlayerDies, CBC_ORDER_LAST) +{ + if(frag_target.nade) + if(!frag_target.frozen || !autocvar_g_freezetag_revive_nade) + toss_nade(frag_target, '0 0 100', max(frag_target.nade.wait, time + 0.05)); + + float killcount_bonus = ((frag_attacker.killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * frag_attacker.killcount, autocvar_g_nades_bonus_score_medium) : autocvar_g_nades_bonus_score_minor); + + if(IS_PLAYER(frag_attacker)) + { + if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target) + nades_RemoveBonus(frag_attacker); + else if(frag_target.flagcarried) + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium); + else if(autocvar_g_nades_bonus_score_spree && frag_attacker.killcount > 1) + { + #define SPREE_ITEM(counta,countb,center,normal,gentle) \ + case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; } + switch(frag_attacker.killcount) + { + KILL_SPREE_LIST + default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break; + } + #undef SPREE_ITEM + } + else + nades_GiveBonus(frag_attacker, killcount_bonus); + } + + nades_RemoveBonus(frag_target); + + return false; +} + +MUTATOR_HOOKFUNCTION(nades, PlayerDamage_Calculate) +{ + if(frag_target.frozen) + if(autocvar_g_freezetag_revive_nade) + if(frag_attacker == frag_target) + if(frag_deathtype == DEATH_NADE.m_id) + if(time - frag_inflictor.toss_time <= 0.1) + { + Unfreeze(frag_target); + frag_target.health = autocvar_g_freezetag_revive_nade_health; + Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); + frag_damage = 0; + frag_force = '0 0 0'; + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname); + Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(nades, MonsterDies) +{SELFPARAM(); + if(IS_PLAYER(frag_attacker)) + if(DIFF_TEAM(frag_attacker, self)) + if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) + nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); + + return false; +} + +MUTATOR_HOOKFUNCTION(nades, DropSpecialItems) +{ + if(frag_target.nade) + toss_nade(frag_target, '0 0 0', time + 0.05); + + return false; +} + +bool nades_RemovePlayer() +{SELFPARAM(); + nades_Clear(self); + nades_RemoveBonus(self); + return false; +} + +MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { nades_RemovePlayer(); } +MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { nades_RemovePlayer(); } +MUTATOR_HOOKFUNCTION(nades, reset_map_global) { nades_RemovePlayer(); } + +MUTATOR_HOOKFUNCTION(nades, SpectateCopy) +{SELFPARAM(); + self.nade_timer = other.nade_timer; + self.nade_type = other.nade_type; + self.pokenade_type = other.pokenade_type; + self.bonus_nades = other.bonus_nades; + self.bonus_nade_score = other.bonus_nade_score; + self.stat_healing_orb = other.stat_healing_orb; + self.stat_healing_orb_alpha = other.stat_healing_orb_alpha; + return false; +} + +MUTATOR_HOOKFUNCTION(nades, GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_nade_type, "cl_nade_type"); + GetCvars_handleString(get_cvars_s, get_cvars_f, cvar_cl_pokenade_type, "cl_pokenade_type"); + + return false; +} + +MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":Nades"); + return false; +} + +MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Nades"); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_new_toys.qc b/qcsrc/server/mutators/mutator/mutator_new_toys.qc new file mode 100644 index 000000000..78904ffae --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_new_toys.qc @@ -0,0 +1,227 @@ +#ifdef IMPLEMENTATION +/* + +CORE laser vortex lg rl cry gl elec hagar fireb hook + vaporizer porto + tuba + +NEW rifle hlac minel seeker +IDEAS OPEN flak OPEN FUN FUN FUN FUN + + + +How this mutator works: + ======================= + +When a gun tries to spawn, this mutator is called. It will provide alternate +weaponreplace lists. + +Entity: + +{ +"classname" "weapon_vortex" +"new_toys" "rifle" +} +-> This will spawn as Rifle in this mutator ONLY, and as Vortex otherwise. + +{ +"classname" "weapon_vortext" +"new_toys" "vortex rifle" +} +-> This will spawn as either Vortex or Rifle in this mutator ONLY, and as Vortex otherwise. + +{ +"classname" "weapon_vortex" +"new_toys" "vortex" +} +-> This is always a Vortex. + +If the map specifies no "new_toys" argument + +There will be two default replacements selectable: "replace all" and "replace random". +In "replace all" mode, e.g. Vortex will have the default replacement "rifle". +In "replace random" mode, Vortex will have the default replacement "vortex rifle". + +This mutator's replacements run BEFORE regular weaponreplace! + +The New Toys guns do NOT get a spawn function, so they can only ever be spawned +when this mutator is active. + +Likewise, warmup, give all, give ALL and impulse 99 will not give them unless +this mutator is active. + +Outside this mutator, they still can be spawned by: +- setting their start weapon cvar to 1 +- give weaponname +- weaponreplace +- weaponarena (but all and most weapons arena again won't include them) + +This mutator performs the default replacements on the DEFAULTS of the +start weapon selection. + +These weapons appear in the menu's priority list, BUT get a suffix +"(Mutator weapon)". + +Picking up a "new toys" weapon will not play standard weapon pickup sound, but +roflsound "New toys, new toys!" sound. + +*/ + +bool nt_IsNewToy(int w); + +REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill")) +{ + MUTATOR_ONADD + { + if(time > 1) // game loads at time 1 + error("This cannot be added at runtime\n"); + + // mark the guns as ok to use by e.g. impulse 99 + for(int i = WEP_FIRST; i <= WEP_LAST; ++i) + if(nt_IsNewToy(i)) + get_weaponinfo(i).spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + for(int i = WEP_FIRST; i <= WEP_LAST; ++i) + if(nt_IsNewToy(i)) + get_weaponinfo(i).spawnflags |= WEP_FLAG_MUTATORBLOCKED; + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This cannot be removed at runtime\n"); + return -1; + } + + return 0; +} + +.string new_toys; + +float autocvar_g_new_toys_autoreplace; +bool autocvar_g_new_toys_use_pickupsound = true; +const float NT_AUTOREPLACE_NEVER = 0; +const float NT_AUTOREPLACE_ALWAYS = 1; +const float NT_AUTOREPLACE_RANDOM = 2; + +MUTATOR_HOOKFUNCTION(nt, SetModname) +{ + modname = "NewToys"; + return 0; +} + +bool nt_IsNewToy(int w) +{ + switch(w) + { + case WEP_SEEKER.m_id: + case WEP_MINE_LAYER.m_id: + case WEP_HLAC.m_id: + case WEP_RIFLE.m_id: + case WEP_SHOCKWAVE.m_id: + return true; + default: + return false; + } +} + +string nt_GetFullReplacement(string w) +{ + switch(w) + { + case "hagar": return "seeker"; + case "devastator": return "minelayer"; + case "machinegun": return "hlac"; + case "vortex": return "rifle"; + //case "shotgun": return "shockwave"; + default: return string_null; + } +} + +string nt_GetReplacement(string w, float m) +{ + if(m == NT_AUTOREPLACE_NEVER) + return w; + string s = nt_GetFullReplacement(w); + if (!s) + return w; + if(m == NT_AUTOREPLACE_RANDOM) + s = strcat(w, " ", s); + return s; +} + +MUTATOR_HOOKFUNCTION(nt, SetStartItems) +{ + // rearrange start_weapon_default + // apply those bits that are set by start_weapon_defaultmask + // same for warmup + + float i, j, k, n; + + WepSet newdefault; + WepSet warmup_newdefault; + + newdefault = '0 0 0'; + warmup_newdefault = '0 0 0'; + + for(i = WEP_FIRST; i <= WEP_LAST; ++i) + { + entity e = get_weaponinfo(i); + if(!e.weapon) + continue; + + n = tokenize_console(nt_GetReplacement(e.netname, autocvar_g_new_toys_autoreplace)); + + for(j = 0; j < n; ++j) + for(k = WEP_FIRST; k <= WEP_LAST; ++k) + if(get_weaponinfo(k).netname == argv(j)) + { + if(start_weapons & WepSet_FromWeapon(i)) + newdefault |= WepSet_FromWeapon(k); + if(warmup_start_weapons & WepSet_FromWeapon(i)) + warmup_newdefault |= WepSet_FromWeapon(k); + } + } + + newdefault &= start_weapons_defaultmask; + start_weapons &= ~start_weapons_defaultmask; + start_weapons |= newdefault; + + warmup_newdefault &= warmup_start_weapons_defaultmask; + warmup_start_weapons &= ~warmup_start_weapons_defaultmask; + warmup_start_weapons |= warmup_newdefault; + + return 0; +} + +MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace) +{SELFPARAM(); + // otherwise, we do replace + if(self.new_toys) + { + // map defined replacement: + ret_string = self.new_toys; + } + else + { + // auto replacement: + ret_string = nt_GetReplacement(other.netname, autocvar_g_new_toys_autoreplace); + } + + // apply regular weaponreplace + ret_string = W_Apply_Weaponreplace(ret_string); + + return 0; +} + +MUTATOR_HOOKFUNCTION(nt, FilterItem) +{SELFPARAM(); + if(nt_IsNewToy(self.weapon) && autocvar_g_new_toys_use_pickupsound) { + self.item_pickupsound = string_null; + self.item_pickupsound_ent = SND_WEAPONPICKUP_NEW_TOYS; + } + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_nix.qc b/qcsrc/server/mutators/mutator/mutator_nix.qc new file mode 100644 index 000000000..259547a05 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_nix.qc @@ -0,0 +1,267 @@ +#ifdef IMPLEMENTATION +float g_nix_with_blaster; +// WEAPONTODO +int nix_weapon; +float nix_nextchange; +float nix_nextweapon; +.float nix_lastchange_id; +.float nix_lastinfotime; +.float nix_nextincr; + +bool NIX_CanChooseWeapon(int wpn); + +REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill")) +{ + MUTATOR_ONADD + { + g_nix_with_blaster = autocvar_g_nix_with_blaster; + + nix_nextchange = 0; + nix_nextweapon = 0; + + for (int i = WEP_FIRST; i <= WEP_LAST; ++i) + if (NIX_CanChooseWeapon(i)) { + Weapon w = get_weaponinfo(i); + w.wr_init(w); + } + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // nothing to roll back + } + + MUTATOR_ONREMOVE + { + // as the PlayerSpawn hook will no longer run, NIX is turned off by this! + entity e; + FOR_EACH_PLAYER(e) if(e.deadflag == DEAD_NO) + { + e.ammo_cells = start_ammo_cells; + e.ammo_plasma = start_ammo_plasma; + e.ammo_shells = start_ammo_shells; + e.ammo_nails = start_ammo_nails; + e.ammo_rockets = start_ammo_rockets; + e.ammo_fuel = start_ammo_fuel; + e.weapons = start_weapons; + if(!client_hasweapon(e, e.weapon, true, false)) + e.switchweapon = w_getbestweapon(self); + } + } + + return 0; +} + +bool NIX_CanChooseWeapon(int wpn) +{ + entity e = get_weaponinfo(wpn); + if(!e.weapon) // skip dummies + return false; + if(g_weaponarena) + { + if(!(g_weaponarena_weapons & WepSet_FromWeapon(wpn))) + return false; + } + else + { + if(wpn == WEP_BLASTER.m_id && g_nix_with_blaster) + return false; + if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED) + return false; + if (!(e.spawnflags & WEP_FLAG_NORMAL)) + return false; + } + return true; +} +void NIX_ChooseNextWeapon() +{ + float j; + RandomSelection_Init(); + for(j = WEP_FIRST; j <= WEP_LAST; ++j) + if(NIX_CanChooseWeapon(j)) + RandomSelection_Add(world, j, string_null, 1, (j != nix_weapon)); + nix_nextweapon = RandomSelection_chosen_float; +} + +void NIX_GiveCurrentWeapon() +{SELFPARAM(); + float dt; + + if(!nix_nextweapon) + NIX_ChooseNextWeapon(); + + dt = ceil(nix_nextchange - time); + + if(dt <= 0) + { + nix_weapon = nix_nextweapon; + nix_nextweapon = 0; + if (!nix_nextchange) // no round played yet? + nix_nextchange = time; // start the first round now! + else + nix_nextchange = time + autocvar_g_balance_nix_roundtime; + // Weapon w = get_weaponinfo(nix_weapon); + // w.wr_init(w); // forget it, too slow + } + + // get weapon info + entity e = get_weaponinfo(nix_weapon); + + if(nix_nextchange != self.nix_lastchange_id) // this shall only be called once per round! + { + self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = self.ammo_plasma = self.ammo_fuel = 0; + + if(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + switch(e.ammo_field) + { + case ammo_shells: self.ammo_shells = autocvar_g_pickup_shells_max; break; + case ammo_nails: self.ammo_nails = autocvar_g_pickup_nails_max; break; + case ammo_rockets: self.ammo_rockets = autocvar_g_pickup_rockets_max; break; + case ammo_cells: self.ammo_cells = autocvar_g_pickup_cells_max; break; + case ammo_plasma: self.ammo_plasma = autocvar_g_pickup_plasma_max; break; + case ammo_fuel: self.ammo_fuel = autocvar_g_pickup_fuel_max; break; + } + } + else + { + switch(e.ammo_field) + { + case ammo_shells: self.ammo_shells = autocvar_g_balance_nix_ammo_shells; break; + case ammo_nails: self.ammo_nails = autocvar_g_balance_nix_ammo_nails; break; + case ammo_rockets: self.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break; + case ammo_cells: self.ammo_cells = autocvar_g_balance_nix_ammo_cells; break; + case ammo_plasma: self.ammo_plasma = autocvar_g_balance_nix_ammo_plasma; break; + case ammo_fuel: self.ammo_fuel = autocvar_g_balance_nix_ammo_fuel; break; + } + } + + self.nix_nextincr = time + autocvar_g_balance_nix_incrtime; + if(dt >= 1 && dt <= 5) + self.nix_lastinfotime = -42; + else + Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_NEWWEAPON, nix_weapon); + + Weapon w = get_weaponinfo(nix_weapon); + w.wr_resetplayer(w); + + // all weapons must be fully loaded when we spawn + if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars + self.(weapon_load[nix_weapon]) = e.reloading_ammo; + + // vortex too + if(WEP_CVAR(vortex, charge)) + { + if(WEP_CVAR_SEC(vortex, chargepool)) + self.vortex_chargepool_ammo = 1; + self.vortex_charge = WEP_CVAR(vortex, charge_start); + } + + // set last change info + self.nix_lastchange_id = nix_nextchange; + } + if(self.nix_lastinfotime != dt) + { + self.nix_lastinfotime = dt; // initial value 0 should count as "not seen" + if(dt >= 1 && dt <= 5) + Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_COUNTDOWN, nix_nextweapon, dt); + } + + if(!(self.items & IT_UNLIMITED_WEAPON_AMMO) && time > self.nix_nextincr) + { + switch(e.ammo_field) + { + case ammo_shells: self.ammo_shells += autocvar_g_balance_nix_ammoincr_shells; break; + case ammo_nails: self.ammo_nails += autocvar_g_balance_nix_ammoincr_nails; break; + case ammo_rockets: self.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break; + case ammo_cells: self.ammo_cells += autocvar_g_balance_nix_ammoincr_cells; break; + case ammo_plasma: self.ammo_plasma += autocvar_g_balance_nix_ammoincr_plasma; break; + case ammo_fuel: self.ammo_fuel += autocvar_g_balance_nix_ammoincr_fuel; break; + } + + self.nix_nextincr = time + autocvar_g_balance_nix_incrtime; + } + + self.weapons = '0 0 0'; + if(g_nix_with_blaster) + self.weapons |= WEPSET(BLASTER); + self.weapons |= WepSet_FromWeapon(nix_weapon); + + if(self.switchweapon != nix_weapon) + if(!client_hasweapon(self, self.switchweapon, true, false)) + if(client_hasweapon(self, nix_weapon, true, false)) + W_SwitchWeapon(nix_weapon); +} + +MUTATOR_HOOKFUNCTION(nix, ForbidThrowCurrentWeapon) +{ + return 1; // no throwing in NIX +} + +MUTATOR_HOOKFUNCTION(nix, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":NIX"); + return 0; +} + +MUTATOR_HOOKFUNCTION(nix, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", NIX"); + return 0; +} + +MUTATOR_HOOKFUNCTION(nix, FilterItem) +{SELFPARAM(); + switch (self.items) + { + case ITEM_HealthSmall.m_itemid: + case ITEM_HealthMedium.m_itemid: + case ITEM_HealthLarge.m_itemid: + case ITEM_HealthMega.m_itemid: + case ITEM_ArmorSmall.m_itemid: + case ITEM_ArmorMedium.m_itemid: + case ITEM_ArmorLarge.m_itemid: + case ITEM_ArmorMega.m_itemid: + if (autocvar_g_nix_with_healtharmor) + return 0; + break; + case ITEM_Strength.m_itemid: + case ITEM_Shield.m_itemid: + if (autocvar_g_nix_with_powerups) + return 0; + break; + } + + return 1; // delete all other items +} + +MUTATOR_HOOKFUNCTION(nix, OnEntityPreSpawn) +{SELFPARAM(); + if(self.classname == "target_items") // items triggers cannot work in nix (as they change weapons/ammo) + return 1; + return 0; +} + +MUTATOR_HOOKFUNCTION(nix, PlayerPreThink) +{SELFPARAM(); + if(!intermission_running) + if(self.deadflag == DEAD_NO) + if(IS_PLAYER(self)) + NIX_GiveCurrentWeapon(); + return 0; +} + +MUTATOR_HOOKFUNCTION(nix, PlayerSpawn) +{SELFPARAM(); + self.nix_lastchange_id = -1; + NIX_GiveCurrentWeapon(); // overrides the weapons you got when spawning + self.items |= IT_UNLIMITED_SUPERWEAPONS; + return 0; +} + +MUTATOR_HOOKFUNCTION(nix, SetModname, CBC_ORDER_LAST) +{ + modname = "NIX"; + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_overkill.qc b/qcsrc/server/mutators/mutator/mutator_overkill.qc new file mode 100644 index 000000000..ea5adbf5a --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_overkill.qc @@ -0,0 +1,363 @@ +#ifdef IMPLEMENTATION +.vector ok_deathloc; +.float ok_spawnsys_timer; +.float ok_lastwep; +.float ok_item; + +.float ok_notice_time; +.float ammo_charge[Weapons_MAX]; +.float ok_use_ammocharge; +.float ok_ammo_charge; + +.float ok_pauseregen_finished; + +void(entity ent, float wep) ok_DecreaseCharge; + +void ok_Initialize(); + +REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill") +{ + MUTATOR_ONADD + { + ok_Initialize(); + } + + MUTATOR_ONREMOVE + { + WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED; + } + + return false; +} + +void W_Blaster_Attack(entity, float, float, float, float, float, float, float, float, float, float); +spawnfunc(weapon_hmg); +spawnfunc(weapon_rpc); + +void ok_DecreaseCharge(entity ent, int wep) +{ + if(!ent.ok_use_ammocharge) return; + + entity wepent = get_weaponinfo(wep); + + if(wepent.weapon == 0) + return; // dummy + + ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); +} + +void ok_IncreaseCharge(entity ent, int wep) +{ + entity wepent = get_weaponinfo(wep); + + if(wepent.weapon == 0) + return; // dummy + + if(ent.ok_use_ammocharge) + if(!ent.BUTTON_ATCK) // not while attacking? + ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME); +} + +float ok_CheckWeaponCharge(entity ent, int wep) +{ + if(!ent.ok_use_ammocharge) return true; + + entity wepent = get_weaponinfo(wep); + + if(wepent.weapon == 0) + return 0; // dummy + + return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); +} + +MUTATOR_HOOKFUNCTION(ok, PlayerDamage_Calculate, CBC_ORDER_LAST) +{ + if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target)) + if(DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) + { + frag_damage = 0; + + if(frag_attacker != frag_target) + if(frag_target.health > 0) + if(frag_target.frozen == 0) + if(frag_target.deadflag == DEAD_NO) + { + Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); + frag_force = '0 0 0'; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ok, PlayerDamage_SplitHealthArmor) +{SELFPARAM(); + if(damage_take) + self.ok_pauseregen_finished = max(self.ok_pauseregen_finished, time + 2); + return false; +} + +MUTATOR_HOOKFUNCTION(ok, PlayerDies) +{SELFPARAM(); + entity targ = ((frag_attacker) ? frag_attacker : frag_target); + + if(IS_MONSTER(self)) + { + remove(other); // remove default item + other = world; + } + + setself(spawn()); + self.ok_item = true; + self.noalign = true; + self.pickup_anyway = true; + spawnfunc_item_armor_small(this); + self.movetype = MOVETYPE_TOSS; + self.gravity = 1; + self.reset = SUB_Remove; + setorigin(self, frag_target.origin + '0 0 32'); + self.velocity = '0 0 200' + normalize(targ.origin - self.origin) * 500; + self.classname = "droppedweapon"; // hax + SUB_SetFade(self, time + 5, 1); + setself(this); + + self.ok_lastwep = self.switchweapon; + + return false; +} +MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) { ok_PlayerDies(); } + +MUTATOR_HOOKFUNCTION(ok, PlayerRegen) +{SELFPARAM(); + // overkill's values are different, so use custom regen + if(!self.frozen) + { + self.armorvalue = CalcRotRegen(self.armorvalue, autocvar_g_balance_armor_regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 1 * frametime * (time > self.ok_pauseregen_finished), 0, 0, 1, 1 * frametime * (time > self.pauserotarmor_finished), autocvar_g_balance_armor_limit); + self.health = CalcRotRegen(self.health, autocvar_g_balance_health_regenstable, 0, 100, 1 * frametime * (time > self.ok_pauseregen_finished), 200, 0, autocvar_g_balance_health_rotlinear, 1 * frametime * (time > self.pauserothealth_finished), autocvar_g_balance_health_limit); + + float minf, maxf, limitf; + + maxf = autocvar_g_balance_fuel_rotstable; + minf = autocvar_g_balance_fuel_regenstable; + limitf = autocvar_g_balance_fuel_limit; + + self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > self.pauseregen_finished) * ((self.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > self.pauserotfuel_finished), limitf); + } + return true; // return true anyway, as frozen uses no regen +} + +MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(ok, PlayerPreThink) +{SELFPARAM(); + if(intermission_running || gameover) + return false; + + if(self.deadflag != DEAD_NO || !IS_PLAYER(self) || self.frozen) + return false; + + if(self.ok_lastwep) + { + self.switchweapon = self.ok_lastwep; + self.ok_lastwep = 0; + } + + ok_IncreaseCharge(self, self.weapon); + + if(self.BUTTON_ATCK2) + if(!forbidWeaponUse(self) || self.weapon_blocked) // allow if weapon is blocked + if(time >= self.jump_interval) + { + self.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(); + makevectors(self.v_angle); + + int oldwep = self.weapon; + self.weapon = WEP_BLASTER.m_id; + W_Blaster_Attack( + self, + WEP_BLASTER.m_id | HITTYPE_SECONDARY, + WEP_CVAR_SEC(vaporizer, shotangle), + WEP_CVAR_SEC(vaporizer, damage), + WEP_CVAR_SEC(vaporizer, edgedamage), + WEP_CVAR_SEC(vaporizer, radius), + WEP_CVAR_SEC(vaporizer, force), + WEP_CVAR_SEC(vaporizer, speed), + WEP_CVAR_SEC(vaporizer, spread), + WEP_CVAR_SEC(vaporizer, delay), + WEP_CVAR_SEC(vaporizer, lifetime) + ); + self.weapon = oldwep; + } + + self.weapon_blocked = false; + + self.ok_ammo_charge = self.ammo_charge[self.weapon]; + + if(self.ok_use_ammocharge) + if(!ok_CheckWeaponCharge(self, self.weapon)) + { + if(autocvar_g_overkill_ammo_charge_notice && time > self.ok_notice_time && self.BUTTON_ATCK && IS_REAL_CLIENT(self) && self.weapon == self.switchweapon) + { + //Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERKILL_CHARGE); + self.ok_notice_time = time + 2; + play2(self, SND(DRYFIRE)); + } + Weapon wpn = get_weaponinfo(self.weapon); + if(self.weaponentity.state != WS_CLEAR) + w_ready(wpn, self, self.BUTTON_ATCK, self.BUTTON_ATCK2); + + self.weapon_blocked = true; + } + + self.BUTTON_ATCK2 = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(ok, PlayerSpawn) +{SELFPARAM(); + if(autocvar_g_overkill_ammo_charge) + { + for(int i = WEP_FIRST; i <= WEP_LAST; ++i) + self.ammo_charge[i] = autocvar_g_overkill_ammo_charge_limit; + + self.ok_use_ammocharge = 1; + self.ok_notice_time = time; + } + else + self.ok_use_ammocharge = 0; + + self.ok_pauseregen_finished = time + 2; + + return false; +} + +void _spawnfunc_weapon_hmg() { SELFPARAM(); spawnfunc_weapon_hmg(this); } +void _spawnfunc_weapon_rpc() { SELFPARAM(); spawnfunc_weapon_rpc(this); } + +MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn) +{SELFPARAM(); + if(autocvar_g_powerups) + if(autocvar_g_overkill_powerups_replace) + { + if(self.classname == "item_strength") + { + entity wep = spawn(); + setorigin(wep, self.origin); + setmodel(wep, MDL_OK_HMG); + wep.classname = "weapon_hmg"; + wep.ok_item = true; + wep.noalign = self.noalign; + wep.cnt = self.cnt; + wep.team = self.team; + wep.respawntime = autocvar_g_overkill_superguns_respawn_time; + wep.pickup_anyway = true; + wep.think = _spawnfunc_weapon_hmg; + wep.nextthink = time + 0.1; + return true; + } + + if(self.classname == "item_invincible") + { + entity wep = spawn(); + setorigin(wep, self.origin); + setmodel(wep, MDL_OK_RPC); + wep.classname = "weapon_rpc"; + wep.ok_item = true; + wep.noalign = self.noalign; + wep.cnt = self.cnt; + wep.team = self.team; + wep.respawntime = autocvar_g_overkill_superguns_respawn_time; + wep.pickup_anyway = true; + wep.think = _spawnfunc_weapon_rpc; + wep.nextthink = time + 0.1; + return true; + } + } + + return false; +} + +MUTATOR_HOOKFUNCTION(ok, FilterItem) +{SELFPARAM(); + if(self.ok_item) + return false; + + switch(self.items) + { + case ITEM_HealthMega.m_itemid: return !(autocvar_g_overkill_100h_anyway); + case ITEM_ArmorMega.m_itemid: return !(autocvar_g_overkill_100a_anyway); + } + + return true; +} + +MUTATOR_HOOKFUNCTION(ok, SpectateCopy) +{SELFPARAM(); + self.ammo_charge[self.weapon] = other.ammo_charge[other.weapon]; + self.ok_use_ammocharge = other.ok_use_ammocharge; + + return false; +} + +MUTATOR_HOOKFUNCTION(ok, SetStartItems) +{ + WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN)); + + if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); } + if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); } + + start_items |= IT_UNLIMITED_WEAPON_AMMO; + start_weapons = warmup_start_weapons = ok_start_items; + + return false; +} + +MUTATOR_HOOKFUNCTION(ok, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":OK"); + return false; +} + +MUTATOR_HOOKFUNCTION(ok, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Overkill"); + return false; +} + +MUTATOR_HOOKFUNCTION(ok, SetModname) +{ + modname = "Overkill"; + return true; +} + +void ok_SetCvars() +{ + // hack to force overkill playermodels + cvar_settemp("sv_defaultcharacter", "1"); + cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); + cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm"); + cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); +} + +void ok_Initialize() +{ + ok_SetCvars(); + + precache_all_playermodels("models/ok_player/*.dpm"); + + addstat(STAT_OK_AMMO_CHARGE, AS_FLOAT, ok_use_ammocharge); + addstat(STAT_OK_AMMO_CHARGEPOOL, AS_FLOAT, ok_ammo_charge); + + WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; + + WEP_SHOTGUN.mdl = "ok_shotgun"; + WEP_MACHINEGUN.mdl = "ok_mg"; + WEP_VORTEX.mdl = "ok_sniper"; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_physical_items.qc b/qcsrc/server/mutators/mutator/mutator_physical_items.qc new file mode 100644 index 000000000..58a01ca2e --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_physical_items.qc @@ -0,0 +1,139 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(physical_items, cvar("g_physical_items")) +{ + // check if we have a physics engine + MUTATOR_ONADD + { + if (!(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE"))) + { + LOG_TRACE("Warning: Physical items are enabled but no physics engine can be used. Reverting to old items.\n"); + return -1; + } + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // nothing to roll back + } + + MUTATOR_ONREMOVE + { + LOG_INFO("This cannot be removed at runtime\n"); + return -1; + } + + return 0; +} + +.vector spawn_origin, spawn_angles; + +void physical_item_think() +{SELFPARAM(); + self.nextthink = time; + + self.alpha = self.owner.alpha; // apply fading and ghosting + + if(!self.cnt) // map item, not dropped + { + // copy ghost item properties + self.colormap = self.owner.colormap; + self.colormod = self.owner.colormod; + self.glowmod = self.owner.glowmod; + + // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there + if(autocvar_g_physical_items_reset) + { + if(self.owner.wait > time) // awaiting respawn + { + setorigin(self, self.spawn_origin); + self.angles = self.spawn_angles; + self.solid = SOLID_NOT; + self.alpha = -1; + self.movetype = MOVETYPE_NONE; + } + else + { + self.alpha = 1; + self.solid = SOLID_CORPSE; + self.movetype = MOVETYPE_PHYSICS; + } + } + } + + if(!self.owner.modelindex) + remove(self); // the real item is gone, remove this +} + +void physical_item_touch() +{SELFPARAM(); + if(!self.cnt) // not for dropped items + if (ITEM_TOUCH_NEEDKILL()) + { + setorigin(self, self.spawn_origin); + self.angles = self.spawn_angles; + } +} + +void physical_item_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) +{SELFPARAM(); + if(!self.cnt) // not for dropped items + if(ITEM_DAMAGE_NEEDKILL(deathtype)) + { + setorigin(self, self.spawn_origin); + self.angles = self.spawn_angles; + } +} + +MUTATOR_HOOKFUNCTION(physical_items, Item_Spawn) +{SELFPARAM(); + if(self.owner == world && autocvar_g_physical_items <= 1) + return false; + if (self.spawnflags & 1) // floating item + return false; + + // The actual item can't be physical and trigger at the same time, so make it invisible and use a second entity for physics. + // Ugly hack, but unless SOLID_TRIGGER is gotten to work with MOVETYPE_PHYSICS in the engine it can't be fixed. + entity wep; + wep = spawn(); + _setmodel(wep, self.model); + setsize(wep, self.mins, self.maxs); + setorigin(wep, self.origin); + wep.angles = self.angles; + wep.velocity = self.velocity; + + wep.owner = self; + wep.solid = SOLID_CORPSE; + wep.movetype = MOVETYPE_PHYSICS; + wep.takedamage = DAMAGE_AIM; + wep.effects |= EF_NOMODELFLAGS; // disable the spinning + wep.colormap = self.owner.colormap; + wep.glowmod = self.owner.glowmod; + wep.damageforcescale = autocvar_g_physical_items_damageforcescale; + wep.dphitcontentsmask = self.dphitcontentsmask; + wep.cnt = (self.owner != world); + + wep.think = physical_item_think; + wep.nextthink = time; + wep.touch = physical_item_touch; + wep.event_damage = physical_item_damage; + + if(!wep.cnt) + { + // fix the spawn origin + setorigin(wep, wep.origin + '0 0 1'); + entity oldself; + oldself = self; + WITH(entity, self, wep, builtin_droptofloor()); + } + + wep.spawn_origin = wep.origin; + wep.spawn_angles = self.angles; + + self.effects |= EF_NODRAW; // hide the original weapon + self.movetype = MOVETYPE_FOLLOW; + self.aiment = wep; // attach the original weapon + self.SendEntity = func_null; + + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_pinata.qc b/qcsrc/server/mutators/mutator/mutator_pinata.qc new file mode 100644 index 000000000..a806b2958 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_pinata.qc @@ -0,0 +1,27 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill")); + +MUTATOR_HOOKFUNCTION(pinata, PlayerDies) +{SELFPARAM(); + for(int j = WEP_FIRST; j <= WEP_LAST; ++j) + if(self.weapons & WepSet_FromWeapon(j)) + if(self.switchweapon != j) + if(W_IsWeaponThrowable(j)) + W_ThrowNewWeapon(self, j, false, self.origin + (self.mins + self.maxs) * 0.5, randomvec() * 175 + '0 0 325'); + + return true; +} + +MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":Pinata"); + return false; +} + +MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Piñata"); + return false; +} + +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_random_gravity.qc b/qcsrc/server/mutators/mutator/mutator_random_gravity.qc new file mode 100644 index 000000000..1b17c9f69 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_random_gravity.qc @@ -0,0 +1,49 @@ +#ifdef IMPLEMENTATION +// Random Gravity +// +// Mutator by Mario +// Inspired by Player 2 + +REGISTER_MUTATOR(random_gravity, cvar("g_random_gravity")) +{ + MUTATOR_ONADD + { + cvar_settemp("sv_gravity", cvar_string("sv_gravity")); // settemp current gravity so it's restored on match end + } + + return false; +} + +float gravity_delay; + +MUTATOR_HOOKFUNCTION(random_gravity, SV_StartFrame) +{ + if(gameover || !cvar("g_random_gravity")) return false; + if(time < gravity_delay) return false; + if(time < game_starttime) return false; + if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return false; + + if(random() >= autocvar_g_random_gravity_negative_chance) + cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() - random() * -autocvar_g_random_gravity_negative, autocvar_g_random_gravity_max))); + else + cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() * autocvar_g_random_gravity_positive, autocvar_g_random_gravity_max))); + + gravity_delay = time + autocvar_g_random_gravity_delay; + + LOG_TRACE("Gravity is now: ", ftos(autocvar_sv_gravity), "\n"); + + return false; +} + +MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":RandomGravity"); + return 0; +} + +MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Random gravity"); + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_rocketflying.qc b/qcsrc/server/mutators/mutator/mutator_rocketflying.qc new file mode 100644 index 000000000..f23d9918b --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_rocketflying.qc @@ -0,0 +1,25 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying")); + +MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile) +{ + if(other.classname == "rocket" || other.classname == "mine") + { + // kill detonate delay of rockets + other.spawnshieldtime = time; + } + return 0; +} + +MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":RocketFlying"); + return 0; +} + +MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Rocket Flying"); + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_rocketminsta.qc b/qcsrc/server/mutators/mutator/mutator_rocketminsta.qc new file mode 100644 index 000000000..f8a1709da --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_rocketminsta.qc @@ -0,0 +1,35 @@ +#ifdef IMPLEMENTATION +#include "../../../common/deathtypes/all.qh" +#include "../../round_handler.qh" + +REGISTER_MUTATOR(rm, cvar("g_instagib")); + +MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate) +{ + // we do it this way, so rm can be toggled during the match + if(!autocvar_g_rm) { return false; } + + if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR)) + if(frag_attacker == frag_target || frag_target.classname == "nade") + frag_damage = 0; + + if(autocvar_g_rm_laser) + if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) + if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted())) + frag_damage = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(rm, PlayerDies) +{ + // we do it this way, so rm can be toggled during the match + if(!autocvar_g_rm) { return false; } + + if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR) || DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) + frag_damage = 1000; // always gib if it was a vaporizer death + + return false; +} + +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator/mutator_spawn_near_teammate.qc new file mode 100644 index 000000000..24147b279 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_spawn_near_teammate.qc @@ -0,0 +1,167 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate") && teamplay); + +.entity msnt_lookat; + +.float msnt_timer; +.vector msnt_deathloc; + +.float cvar_cl_spawn_near_teammate; + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) +{SELFPARAM(); + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate)) + return 0; + + entity p; + + spawn_spot.msnt_lookat = world; + + if(!teamplay) + return 0; + + RandomSelection_Init(); + FOR_EACH_PLAYER(p) if(p != self) if(p.team == self.team) if(!p.deadflag) + { + float l = vlen(spawn_spot.origin - p.origin); + if(l > autocvar_g_spawn_near_teammate_distance) + continue; + if(l < 48) + continue; + if(!checkpvs(spawn_spot.origin, p)) + continue; + RandomSelection_Add(p, 0, string_null, 1, 1); + } + + if(RandomSelection_chosen_ent) + { + spawn_spot.msnt_lookat = RandomSelection_chosen_ent; + spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND; + } + else if(self.team == spawn_spot.team) + spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate + + return 0; +} + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) +{SELFPARAM(); + // Note: when entering this, fixangle is already set. + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate)) + { + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death) + self.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; + + entity team_mate, best_mate = world; + vector best_spot = '0 0 0'; + float pc = 0, best_dist = 0, dist = 0; + FOR_EACH_PLAYER(team_mate) + { + if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && team_mate.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0) + if(team_mate.deadflag == DEAD_NO) + if(team_mate.msnt_timer < time) + if(SAME_TEAM(self, team_mate)) + if(time > team_mate.spawnshieldtime) // spawn shielding + if(team_mate.frozen == 0) + if(team_mate != self) + { + tracebox(team_mate.origin, PL_MIN, PL_MAX, team_mate.origin - '0 0 100', MOVE_WORLDONLY, team_mate); + if(trace_fraction != 1.0) + if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) + { + pc = pointcontents(trace_endpos + '0 0 1'); + if(pc == CONTENT_EMPTY) + { + if(vlen(team_mate.velocity) > 5) + fixedmakevectors(vectoangles(team_mate.velocity)); + else + fixedmakevectors(team_mate.angles); + + for(pc = 0; pc != 5; ++pc) // test 5 diffrent spots close to mate + { + switch(pc) + { + case 0: + tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 128, MOVE_NORMAL, team_mate); + break; + case 1: + tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 128 , MOVE_NORMAL, team_mate); + break; + case 2: + tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate); + break; + case 3: + tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate); + break; + case 4: + tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_forward * 128, MOVE_NORMAL, team_mate); + break; + } + + if(trace_fraction == 1.0) + { + traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NORMAL, team_mate); + if(trace_fraction != 1.0) + { + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) + { + dist = vlen(trace_endpos - self.msnt_deathloc); + if(dist < best_dist || best_dist == 0) + { + best_dist = dist; + best_spot = trace_endpos; + best_mate = team_mate; + } + } + else + { + setorigin(self, trace_endpos); + self.angles = team_mate.angles; + self.angles_z = 0; // never spawn tilted even if the spot says to + team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; + return 0; + } + } + } + } + } + } + } + } + + if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) + if(best_dist) + { + setorigin(self, best_spot); + self.angles = best_mate.angles; + self.angles_z = 0; // never spawn tilted even if the spot says to + best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; + } + } + else if(spawn_spot.msnt_lookat) + { + self.angles = vectoangles(spawn_spot.msnt_lookat.origin - self.origin); + self.angles_x = -self.angles.x; + self.angles_z = 0; // never spawn tilted even if the spot says to + /* + sprint(self, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n"); + sprint(self, "distance: ", vtos(spawn_spot.msnt_lookat.origin - self.origin), "\n"); + sprint(self, "angles: ", vtos(self.angles), "\n"); + */ + } + + return 0; +} + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies) +{SELFPARAM(); + self.msnt_deathloc = self.origin; + return 0; +} + +MUTATOR_HOOKFUNCTION(spawn_near_teammate, GetCvars) +{ + GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_spawn_near_teammate, "cl_spawn_near_teammate"); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_superspec.qc b/qcsrc/server/mutators/mutator/mutator_superspec.qc new file mode 100644 index 000000000..f24b32335 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_superspec.qc @@ -0,0 +1,480 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(superspec, cvar("g_superspectate")); + +#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1" +#define _ISLOCAL ((edict_num(1) == self) ? true : false) + +const float ASF_STRENGTH = 1; +const float ASF_SHIELD = 2; +const float ASF_MEGA_AR = 4; +const float ASF_MEGA_HP = 8; +const float ASF_FLAG_GRAB = 16; +const float ASF_OBSERVER_ONLY = 32; +const float ASF_SHOWWHAT = 64; +const float ASF_SSIM = 128; +const float ASF_FOLLOWKILLER = 256; +const float ASF_ALL = 0xFFFFFF; +.float autospec_flags; + +const float SSF_SILENT = 1; +const float SSF_VERBOSE = 2; +const float SSF_ITEMMSG = 4; +.float superspec_flags; + +.string superspec_itemfilter; //"classname1 classname2 ..." + +bool superspec_Spectate(entity _player) +{SELFPARAM(); + if(Spectate(_player) == 1) + self.classname = "spectator"; + + return true; +} + +void superspec_save_client_conf() +{SELFPARAM(); + string fn = "superspec-local.options"; + float fh; + + if (!_ISLOCAL) + { + if(self.crypto_idfp == "") + return; + + fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp)); + } + + fh = fopen(fn, FILE_WRITE); + if(fh < 0) + { + LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for writing.\n"); + } + else + { + fputs(fh, _SSMAGIX); + fputs(fh, "\n"); + fputs(fh, ftos(self.autospec_flags)); + fputs(fh, "\n"); + fputs(fh, ftos(self.superspec_flags)); + fputs(fh, "\n"); + fputs(fh, self.superspec_itemfilter); + fputs(fh, "\n"); + fclose(fh); + } +} + +void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel) +{ + sprint(_to, strcat(_con_title, _msg)); + + if(_to.superspec_flags & SSF_SILENT) + return; + + if(_spamlevel > 1) + if (!(_to.superspec_flags & SSF_VERBOSE)) + return; + + centerprint(_to, strcat(_center_title, _msg)); +} + +float superspec_filteritem(entity _for, entity _item) +{ + float i; + + if(_for.superspec_itemfilter == "") + return true; + + if(_for.superspec_itemfilter == "") + return true; + + float l = tokenize_console(_for.superspec_itemfilter); + for(i = 0; i < l; ++i) + { + if(argv(i) == _item.classname) + return true; + } + + return false; +} + +MUTATOR_HOOKFUNCTION(superspec, ItemTouch) +{SELFPARAM(); + entity _item = self; + + entity e; + FOR_EACH_SPEC(e) + { + setself(e); + if(self.superspec_flags & SSF_ITEMMSG) + if(superspec_filteritem(self, _item)) + { + if(self.superspec_flags & SSF_VERBOSE) + superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n", other.netname, _item.netname), 1); + else + superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n^8(%s^8)\n", other.netname, _item.netname, _item.classname), 1); + if((self.autospec_flags & ASF_SSIM) && self.enemy != other) + { + superspec_Spectate(other); + + setself(this); + return MUT_ITEMTOUCH_CONTINUE; + } + } + + if((self.autospec_flags & ASF_SHIELD && _item.invincible_finished) || + (self.autospec_flags & ASF_STRENGTH && _item.strength_finished) || + (self.autospec_flags & ASF_MEGA_AR && _item.itemdef == ITEM_ArmorMega) || + (self.autospec_flags & ASF_MEGA_HP && _item.itemdef == ITEM_HealthMega) || + (self.autospec_flags & ASF_FLAG_GRAB && _item.classname == "item_flag_team")) + { + + if((self.enemy != other) || IS_OBSERVER(self)) + { + if(self.autospec_flags & ASF_OBSERVER_ONLY && !IS_OBSERVER(self)) + { + if(self.superspec_flags & SSF_VERBOSE) + superspec_msg("", "", self, sprintf("^8Ignored that ^7%s^8 grabbed %s^8 since the observer_only option is ON\n", other.netname, _item.netname), 2); + } + else + { + if(self.autospec_flags & ASF_SHOWWHAT) + superspec_msg("", "", self, sprintf("^7Following %s^7 due to picking up %s\n", other.netname, _item.netname), 2); + + superspec_Spectate(other); + } + } + } + } + + setself(this); + + return MUT_ITEMTOUCH_CONTINUE; +} + +MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) +{SELFPARAM(); +#define OPTIONINFO(flag,var,test,text,long,short) \ + var = strcat(var, ((flag & test) ? "^2[ON] ^7" : "^1[OFF] ^7")); \ + var = strcat(var, text," ^7(^3 ", long, "^7 | ^3", short, " ^7)\n") + + if(MUTATOR_RETURNVALUE) // command was already handled? + return false; + + if(IS_PLAYER(self)) + return false; + + if(cmd_name == "superspec_itemfilter") + { + if(argv(1) == "help") + { + string _aspeco; + _aspeco = "^7 superspec_itemfilter ^3\"item_classname1 item_classname2\"^7 only show thise items when ^2superspec ^3item_message^7 is on\n"; + _aspeco = strcat(_aspeco, "^3 clear^7 Remove the filter (show all pickups)\n"); + _aspeco = strcat(_aspeco, "^3 show ^7 Display current filter\n"); + superspec_msg("^3superspec_itemfilter help:\n\n\n", "\n^3superspec_itemfilter help:\n", self, _aspeco, 1); + } + else if(argv(1) == "clear") + { + if(self.superspec_itemfilter != "") + strunzone(self.superspec_itemfilter); + + self.superspec_itemfilter = ""; + } + else if(argv(1) == "show" || argv(1) == "") + { + if(self.superspec_itemfilter == "") + { + superspec_msg("^3superspec_itemfilter^7 is ^1not^7 set", "\n^3superspec_itemfilter^7 is ^1not^7 set\n", self, "", 1); + return true; + } + float i; + float l = tokenize_console(self.superspec_itemfilter); + string _msg = ""; + for(i = 0; i < l; ++i) + _msg = strcat(_msg, "^3#", ftos(i), " ^7", argv(i), "\n"); + //_msg = sprintf("^3#%d^7 %s\n%s", i, _msg, argv(i)); + + _msg = strcat(_msg,"\n"); + + superspec_msg("^3superspec_itemfilter is:\n\n\n", "\n^3superspec_itemfilter is:\n", self, _msg, 1); + } + else + { + if(self.superspec_itemfilter != "") + strunzone(self.superspec_itemfilter); + + self.superspec_itemfilter = strzone(argv(1)); + } + + return true; + } + + if(cmd_name == "superspec") + { + string _aspeco; + + if(cmd_argc > 1) + { + float i, _bits = 0, _start = 1; + if(argv(1) == "help") + { + _aspeco = "use cmd superspec [option] [on|off] to set options\n\n"; + _aspeco = strcat(_aspeco, "^3 silent ^7(short^5 si^7) supresses ALL messages from superspectate.\n"); + _aspeco = strcat(_aspeco, "^3 verbose ^7(short^5 ve^7) makes superspectate print some additional information.\n"); + _aspeco = strcat(_aspeco, "^3 item_message ^7(short^5 im^7) makes superspectate print items that were picked up.\n"); + _aspeco = strcat(_aspeco, "^7 Use cmd superspec_itemfilter \"item_class1 item_class2\" to set up a filter of what to show with ^3item_message.\n"); + superspec_msg("^2Available Super Spectate ^3options:\n\n\n", "\n^2Available Super Spectate ^3options:\n", self, _aspeco, 1); + return true; + } + + if(argv(1) == "clear") + { + self.superspec_flags = 0; + _start = 2; + } + + for(i = _start; i < cmd_argc; ++i) + { + if(argv(i) == "on" || argv(i) == "1") + { + self.superspec_flags |= _bits; + _bits = 0; + } + else if(argv(i) == "off" || argv(i) == "0") + { + if(_start == 1) + self.superspec_flags &= ~_bits; + + _bits = 0; + } + else + { + if((argv(i) == "silent") || (argv(i) == "si")) _bits |= SSF_SILENT ; + if((argv(i) == "verbose") || (argv(i) == "ve")) _bits |= SSF_VERBOSE; + if((argv(i) == "item_message") || (argv(i) == "im")) _bits |= SSF_ITEMMSG; + } + } + } + + _aspeco = ""; + OPTIONINFO(self.superspec_flags, _aspeco, SSF_SILENT, "Silent", "silent", "si"); + OPTIONINFO(self.superspec_flags, _aspeco, SSF_VERBOSE, "Verbose", "verbose", "ve"); + OPTIONINFO(self.superspec_flags, _aspeco, SSF_ITEMMSG, "Item pickup messages", "item_message", "im"); + + superspec_msg("^3Current Super Spectate options are:\n\n\n\n\n", "\n^3Current Super Spectate options are:\n", self, _aspeco, 1); + + return true; + } + +///////////////////// + + if(cmd_name == "autospec") + { + string _aspeco; + if(cmd_argc > 1) + { + if(argv(1) == "help") + { + _aspeco = "use cmd autospec [option] [on|off] to set options\n\n"; + _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n"); + _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n"); + _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n"); + _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n"); + _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n"); + _aspeco = strcat(_aspeco, "^3 observer_only ^7(short^5 oo^7) for automatic spectate only if in observer mode\n"); + _aspeco = strcat(_aspeco, "^3 show_what ^7(short^5 sw^7) to display what event triggered autospectate\n"); + _aspeco = strcat(_aspeco, "^3 item_msg ^7(short^5 im^7) to autospec when item_message in superspectate is triggered\n"); + _aspeco = strcat(_aspeco, "^3 followkiller ^7(short ^5fk^7) to autospec the killer/off\n"); + _aspeco = strcat(_aspeco, "^3 all ^7(short ^5aa^7) to turn everything on/off\n"); + superspec_msg("^2Available Auto Spectate ^3options:\n\n\n", "\n^2Available Auto Spectate ^3options:\n", self, _aspeco, 1); + return true; + } + + float i, _bits = 0, _start = 1; + if(argv(1) == "clear") + { + self.autospec_flags = 0; + _start = 2; + } + + for(i = _start; i < cmd_argc; ++i) + { + if(argv(i) == "on" || argv(i) == "1") + { + self.autospec_flags |= _bits; + _bits = 0; + } + else if(argv(i) == "off" || argv(i) == "0") + { + if(_start == 1) + self.autospec_flags &= ~_bits; + + _bits = 0; + } + else + { + if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH; + if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD; + if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP; + if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR; + if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB; + if((argv(i) == "observer_only") || (argv(i) == "oo")) _bits |= ASF_OBSERVER_ONLY; + if((argv(i) == "show_what") || (argv(i) == "sw")) _bits |= ASF_SHOWWHAT; + if((argv(i) == "item_msg") || (argv(i) == "im")) _bits |= ASF_SSIM; + if((argv(i) == "followkiller") || (argv(i) == "fk")) _bits |= ASF_FOLLOWKILLER; + if((argv(i) == "all") || (argv(i) == "aa")) _bits |= ASF_ALL; + } + } + } + + _aspeco = ""; + OPTIONINFO(self.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_OBSERVER_ONLY, "Only switch if observer", "observer_only", "oo"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHOWWHAT, "Show what item triggered spectate", "show_what", "sw"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_SSIM, "Switch on superspec item message", "item_msg", "im"); + OPTIONINFO(self.autospec_flags, _aspeco, ASF_FOLLOWKILLER, "Followkiller", "followkiller", "fk"); + + superspec_msg("^3Current auto spectate options are:\n\n\n\n\n", "\n^3Current auto spectate options are:\n", self, _aspeco, 1); + return true; + } + + if(cmd_name == "followpowerup") + { + entity _player; + FOR_EACH_PLAYER(_player) + { + if(_player.strength_finished > time || _player.invincible_finished > time) + return superspec_Spectate(_player); + } + + superspec_msg("", "", self, "No active powerup\n", 1); + return true; + } + + if(cmd_name == "followstrength") + { + entity _player; + FOR_EACH_PLAYER(_player) + { + if(_player.strength_finished > time) + return superspec_Spectate(_player); + } + + superspec_msg("", "", self, "No active Strength\n", 1); + return true; + } + + if(cmd_name == "followshield") + { + entity _player; + FOR_EACH_PLAYER(_player) + { + if(_player.invincible_finished > time) + return superspec_Spectate(_player); + } + + superspec_msg("", "", self, "No active Shield\n", 1); + return true; + } + + return false; +#undef OPTIONINFO +} + +MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":SS"); + return 0; +} + +MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Super Spectators"); + return 0; +} + +void superspec_hello() +{SELFPARAM(); + if(self.enemy.crypto_idfp == "") + Send_Notification(NOTIF_ONE_ONLY, self.enemy, MSG_INFO, INFO_SUPERSPEC_MISSING_UID); + + remove(self); +} + +MUTATOR_HOOKFUNCTION(superspec, ClientConnect) +{SELFPARAM(); + if(!IS_REAL_CLIENT(self)) + return false; + + string fn = "superspec-local.options"; + float fh; + + self.superspec_flags = SSF_VERBOSE; + self.superspec_itemfilter = ""; + + entity _hello = spawn(); + _hello.enemy = self; + _hello.think = superspec_hello; + _hello.nextthink = time + 5; + + if (!_ISLOCAL) + { + if(self.crypto_idfp == "") + return false; + + fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp)); + } + + fh = fopen(fn, FILE_READ); + if(fh < 0) + { + LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for reading.\n"); + } + else + { + string _magic = fgets(fh); + if(_magic != _SSMAGIX) + { + LOG_TRACE("^1ERROR^7 While reading superspec options file: unknown magic\n"); + } + else + { + self.autospec_flags = stof(fgets(fh)); + self.superspec_flags = stof(fgets(fh)); + self.superspec_itemfilter = strzone(fgets(fh)); + } + fclose(fh); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(superspec, PlayerDies) +{SELFPARAM(); + entity e; + FOR_EACH_SPEC(e) + { + setself(e); + if(self.autospec_flags & ASF_FOLLOWKILLER && IS_PLAYER(frag_attacker) && self.enemy == this) + { + if(self.autospec_flags & ASF_SHOWWHAT) + superspec_msg("", "", self, sprintf("^7Following %s^7 due to followkiller\n", frag_attacker.netname), 2); + + superspec_Spectate(frag_attacker); + } + } + + setself(this); + return false; +} + +MUTATOR_HOOKFUNCTION(superspec, ClientDisconnect) +{ + superspec_save_client_conf(); + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_touchexplode.qc b/qcsrc/server/mutators/mutator/mutator_touchexplode.qc new file mode 100644 index 000000000..29d9a2c60 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_touchexplode.qc @@ -0,0 +1,43 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode")); + +.float touchexplode_time; + +void PlayerTouchExplode(entity p1, entity p2) +{SELFPARAM(); + vector org = (p1.origin + p2.origin) * 0.5; + org.z += (p1.mins.z + p2.mins.z) * 0.5; + + sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); + Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1); + + entity e = spawn(); + setorigin(e, org); + RadiusDamage(e, world, autocvar_g_touchexplode_damage, autocvar_g_touchexplode_edgedamage, autocvar_g_touchexplode_radius, world, world, autocvar_g_touchexplode_force, DEATH_TOUCHEXPLODE.m_id, world); + remove(e); +} + +MUTATOR_HOOKFUNCTION(touchexplode, PlayerPreThink) +{SELFPARAM(); + if(time > self.touchexplode_time) + if(!gameover) + if(!self.frozen) + if(IS_PLAYER(self)) + if(self.deadflag == DEAD_NO) + if (!IS_INDEPENDENT_PLAYER(self)) + FOR_EACH_PLAYER(other) if(self != other) + { + if(time > other.touchexplode_time) + if(!other.frozen) + if(other.deadflag == DEAD_NO) + if (!IS_INDEPENDENT_PLAYER(other)) + if(boxesoverlap(self.absmin, self.absmax, other.absmin, other.absmax)) + { + PlayerTouchExplode(self, other); + self.touchexplode_time = other.touchexplode_time = time + 0.2; + } + } + + return false; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_vampire.qc b/qcsrc/server/mutators/mutator/mutator_vampire.qc new file mode 100644 index 000000000..315da7dc6 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_vampire.qc @@ -0,0 +1,28 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib")); + +MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor) +{ + if(time >= frag_target.spawnshieldtime) + if(frag_target != frag_attacker) + if(frag_target.deadflag == DEAD_NO) + { + frag_attacker.health += bound(0, damage_take, frag_target.health); + frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit); + } + + return false; +} + +MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsString) +{ + ret_string = strcat(ret_string, ":Vampire"); + return 0; +} + +MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsPrettyString) +{ + ret_string = strcat(ret_string, ", Vampire"); + return 0; +} +#endif diff --git a/qcsrc/server/mutators/mutator/mutator_vampirehook.qc b/qcsrc/server/mutators/mutator/mutator_vampirehook.qc new file mode 100644 index 000000000..f669f6a96 --- /dev/null +++ b/qcsrc/server/mutators/mutator/mutator_vampirehook.qc @@ -0,0 +1,38 @@ +#ifdef IMPLEMENTATION +REGISTER_MUTATOR(vh, cvar("g_vampirehook")); + +bool autocvar_g_vampirehook_teamheal; +float autocvar_g_vampirehook_damage; +float autocvar_g_vampirehook_damagerate; +float autocvar_g_vampirehook_health_steal; + +.float last_dmg; + +MUTATOR_HOOKFUNCTION(vh, GrappleHookThink) +{SELFPARAM(); + entity dmgent = ((SAME_TEAM(self.owner, self.aiment) && autocvar_g_vampirehook_teamheal) ? self.owner : self.aiment); + + if(IS_PLAYER(self.aiment)) + if(self.last_dmg < time) + if(!self.aiment.frozen) + if(time >= game_starttime) + if(DIFF_TEAM(self.owner, self.aiment) || autocvar_g_vampirehook_teamheal) + if(self.aiment.health > 0) + if(autocvar_g_vampirehook_damage) + { + self.last_dmg = time + autocvar_g_vampirehook_damagerate; + self.owner.damage_dealt += autocvar_g_vampirehook_damage; + Damage(dmgent, self, self.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, self.origin, '0 0 0'); + if(SAME_TEAM(self.owner, self.aiment)) + self.aiment.health = min(self.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); + else + self.owner.health = min(self.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); + + if(dmgent == self.owner) + dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?! + } + + return false; +} + +#endif diff --git a/qcsrc/server/mutators/mutator/sandbox.qc b/qcsrc/server/mutators/mutator/sandbox.qc new file mode 100644 index 000000000..caa87a0bb --- /dev/null +++ b/qcsrc/server/mutators/mutator/sandbox.qc @@ -0,0 +1,834 @@ +#ifdef IMPLEMENTATION +float autosave_time; +void sandbox_Database_Load(); + +REGISTER_MUTATOR(sandbox, cvar("g_sandbox")) +{ + MUTATOR_ONADD + { + autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame + if(autocvar_g_sandbox_storage_autoload) + sandbox_Database_Load(); + } + + MUTATOR_ONROLLBACK_OR_REMOVE + { + // nothing to roll back + } + + MUTATOR_ONREMOVE + { + // nothing to remove + } + + return false; +} + +const float MAX_STORAGE_ATTACHMENTS = 16; +float object_count; +.float object_flood; +.entity object_attach; +.string material; + +.float touch_timer; +void sandbox_ObjectFunction_Touch() +{SELFPARAM(); + // apply material impact effects + + if(!self.material) + return; + if(self.touch_timer > time) + return; // don't execute each frame + self.touch_timer = time + 0.1; + + // make particle count and sound volume depend on impact speed + float intensity; + intensity = vlen(self.velocity) + vlen(other.velocity); + if(intensity) // avoid divisions by 0 + intensity /= 2; // average the two velocities + if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min)) + return; // impact not strong enough to do anything + // now offset intensity and apply it to the effects + intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity + intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1); + + _sound(self, CH_TRIGGER, strcat("object/impact_", self.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM); + Send_Effect_(strcat("impact_", self.material), self.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10 +} + +void sandbox_ObjectFunction_Think() +{SELFPARAM(); + entity e; + + // decide if and how this object can be grabbed + if(autocvar_g_sandbox_readonly) + self.grab = 0; // no grabbing + else if(autocvar_g_sandbox_editor_free < 2 && self.crypto_idfp) + self.grab = 1; // owner only + else + self.grab = 3; // anyone + + // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server). + // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this, + // since if the owning player disconnects, the object's owner should also be reset. + FOR_EACH_REALPLAYER(e) // bots can't have objects + { + if(self.crypto_idfp == e.crypto_idfp) + { + self.realowner = e; + break; + } + self.realowner = world; + } + + self.nextthink = time; + + CSQCMODEL_AUTOUPDATE(self); +} + +.float old_solid, old_movetype; +entity sandbox_ObjectEdit_Get(float permissions) +{SELFPARAM(); + // Returns the traced entity if the player can edit it, and world if not. + // If permissions if false, the object is returned regardless of editing rights. + // Attached objects are SOLID_NOT and do not get traced. + + crosshair_trace_plusvisibletriggers(self); + if(vlen(self.origin - trace_ent.origin) > autocvar_g_sandbox_editor_distance_edit) + return world; // out of trace range + if(trace_ent.classname != "object") + return world; // entity is not an object + if(!permissions) + return trace_ent; // don't check permissions, anyone can edit this object + if(trace_ent.crypto_idfp == "") + return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it + if (!(trace_ent.realowner != self && autocvar_g_sandbox_editor_free < 2)) + return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server + return world; +} + +void sandbox_ObjectEdit_Scale(entity e, float f) +{ + e.scale = f; + if(e.scale) + { + e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max); + _setmodel(e, e.model); // reset mins and maxs based on mesh + setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size + } +} + +void sandbox_ObjectAttach_Remove(entity e); +void sandbox_ObjectAttach_Set(entity e, entity parent, string s) +{ + // attaches e to parent on string s + + // we can't attach to an attachment, for obvious reasons + sandbox_ObjectAttach_Remove(e); + + e.old_solid = e.solid; // persist solidity + e.old_movetype = e.movetype; // persist physics + e.movetype = MOVETYPE_FOLLOW; + e.solid = SOLID_NOT; + e.takedamage = DAMAGE_NO; + + setattachment(e, parent, s); + e.owner = parent; +} + +void sandbox_ObjectAttach_Remove(entity e) +{ + // detaches any object attached to e + + entity head; + for(head = world; (head = find(head, classname, "object")); ) + { + if(head.owner == e) + { + vector org; + org = gettaginfo(head, 0); + setattachment(head, world, ""); + head.owner = world; + + // objects change origin and angles when detached, so apply previous position + setorigin(head, org); + head.angles = e.angles; // don't allow detached objects to spin or roll + + head.solid = head.old_solid; // restore persisted solidity + head.movetype = head.old_movetype; // restore persisted physics + head.takedamage = DAMAGE_AIM; + } + } +} + +entity sandbox_ObjectSpawn(float database) +{SELFPARAM(); + // spawn a new object with default properties + + entity e = spawn(); + e.classname = "object"; + e.takedamage = DAMAGE_AIM; + e.damageforcescale = 1; + e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly + e.movetype = MOVETYPE_TOSS; + e.frame = 0; + e.skin = 0; + e.material = string_null; + e.touch = sandbox_ObjectFunction_Touch; + e.think = sandbox_ObjectFunction_Think; + e.nextthink = time; + //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects? + + if(!database) + { + // set the object's owner via player UID + // if the player does not have an UID, the owner cannot be stored and his objects may be edited by anyone + if(self.crypto_idfp != "") + e.crypto_idfp = strzone(self.crypto_idfp); + else + print_to(self, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!"); + + // set public object information + e.netname = strzone(self.netname); // name of the owner + e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time + e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time + + // set origin and direction based on player position and view angle + makevectors(self.v_angle); + WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, self); + setorigin(e, trace_endpos); + e.angles_y = self.v_angle.y; + } + + WITH(entity, self, e, CSQCMODEL_AUTOINIT(e)); + + object_count += 1; + return e; +} + +void sandbox_ObjectRemove(entity e) +{ + sandbox_ObjectAttach_Remove(e); // detach child objects + + // if the object being removed has been selected for attachment by a player, unset it + entity head; + FOR_EACH_REALPLAYER(head) // bots can't have objects + { + if(head.object_attach == e) + head.object_attach = world; + } + + if(e.material) { strunzone(e.material); e.material = string_null; } + if(e.crypto_idfp) { strunzone(e.crypto_idfp); e.crypto_idfp = string_null; } + if(e.netname) { strunzone(e.netname); e.netname = string_null; } + if(e.message) { strunzone(e.message); e.message = string_null; } + if(e.message2) { strunzone(e.message2); e.message2 = string_null; } + remove(e); + e = world; + + object_count -= 1; +} + +string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global + +string sandbox_ObjectPort_Save(entity e, float database) +{ + // save object properties, and return them as a string + float i = 0; + string s; + entity head; + + for(head = world; (head = find(head, classname, "object")); ) + { + // the main object needs to be first in the array [0] with attached objects following + float slot, physics, solidity; + if(head == e) // this is the main object, place it first + { + slot = 0; + solidity = head.solid; // applied solidity is normal solidity for children + physics = head.movetype; // applied physics are normal physics for parents + } + else if(head.owner == e) // child object, list them in order + { + i += 1; // children start from 1 + slot = i; + solidity = head.old_solid; // persisted solidity is normal solidity for children + physics = head.old_movetype; // persisted physics are normal physics for children + gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below + } + else + continue; + + // ---------------- OBJECT PROPERTY STORAGE: SAVE ---------------- + if(slot) + { + // properties stored only for child objects + if(gettaginfo_name) port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none + } + else + { + // properties stored only for parent objects + if(database) + { + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.origin), " "); + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.angles), " "); + } + } + // properties stored for all objects + port_string[slot] = strcat(port_string[slot], "\"", head.model, "\" "); + port_string[slot] = strcat(port_string[slot], ftos(head.skin), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.alpha), " "); + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.colormod), " "); + port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.frame), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.scale), " "); + port_string[slot] = strcat(port_string[slot], ftos(solidity), " "); + port_string[slot] = strcat(port_string[slot], ftos(physics), " "); + port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " "); + if(head.material) port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none + if(database) + { + // properties stored only for the database + if(head.crypto_idfp) port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none + port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" "); + port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" "); + port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" "); + } + } + + // now apply the array to a simple string, with the ; symbol separating objects + s = ""; + for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) + { + if(port_string[i]) + s = strcat(s, port_string[i], "; "); + port_string[i] = string_null; // fully clear the string + } + + return s; +} + +entity sandbox_ObjectPort_Load(string s, float database) +{ + // load object properties, and spawn a new object with them + float n, i; + entity e = world, parent = world; + + // separate objects between the ; symbols + n = tokenizebyseparator(s, "; "); + for(i = 0; i < n; ++i) + port_string[i] = argv(i); + + // now separate and apply the properties of each object + for(i = 0; i < n; ++i) + { + float argv_num; + string tagname = string_null; + argv_num = 0; + tokenize_console(port_string[i]); + e = sandbox_ObjectSpawn(database); + + // ---------------- OBJECT PROPERTY STORAGE: LOAD ---------------- + if(i) + { + // properties stored only for child objects + if(argv(argv_num) != "") tagname = argv(argv_num); else tagname = string_null; ++argv_num; + } + else + { + // properties stored only for parent objects + if(database) + { + setorigin(e, stov(argv(argv_num))); ++argv_num; + e.angles = stov(argv(argv_num)); ++argv_num; + } + parent = e; // mark parent objects as such + } + // properties stored for all objects + _setmodel(e, argv(argv_num)); ++argv_num; + e.skin = stof(argv(argv_num)); ++argv_num; + e.alpha = stof(argv(argv_num)); ++argv_num; + e.colormod = stov(argv(argv_num)); ++argv_num; + e.glowmod = stov(argv(argv_num)); ++argv_num; + e.frame = stof(argv(argv_num)); ++argv_num; + sandbox_ObjectEdit_Scale(e, stof(argv(argv_num))); ++argv_num; + e.solid = e.old_solid = stof(argv(argv_num)); ++argv_num; + e.movetype = e.old_movetype = stof(argv(argv_num)); ++argv_num; + e.damageforcescale = stof(argv(argv_num)); ++argv_num; + if(e.material) strunzone(e.material); if(argv(argv_num) != "") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num; + if(database) + { + // properties stored only for the database + if(e.crypto_idfp) strunzone(e.crypto_idfp); if(argv(argv_num) != "") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num; + if(e.netname) strunzone(e.netname); e.netname = strzone(argv(argv_num)); ++argv_num; + if(e.message) strunzone(e.message); e.message = strzone(argv(argv_num)); ++argv_num; + if(e.message2) strunzone(e.message2); e.message2 = strzone(argv(argv_num)); ++argv_num; + } + + // attach last + if(i) + sandbox_ObjectAttach_Set(e, parent, tagname); + } + + for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) + port_string[i] = string_null; // fully clear the string + + return e; +} + +void sandbox_Database_Save() +{ + // saves all objects to the database file + entity head; + string file_name; + float file_get; + + file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_WRITE); + fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S"))); + fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n")); + + for(head = world; (head = find(head, classname, "object")); ) + { + // attached objects are persisted separately, ignore them here + if(head.owner != world) + continue; + + // use a line of text for each object, listing all properties + fputs(file_get, strcat(sandbox_ObjectPort_Save(head, true), "\n")); + } + fclose(file_get); +} + +void sandbox_Database_Load() +{ + // loads all objects from the database file + string file_read, file_name; + float file_get, i; + + file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); + file_get = fopen(file_name, FILE_READ); + if(file_get < 0) + { + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects were loaded\n")); + } + else + { + for (;;) + { + file_read = fgets(file_get); + if(file_read == "") + break; + if(substring(file_read, 0, 2) == "//") + continue; + if(substring(file_read, 0, 1) == "#") + continue; + + entity e; + e = sandbox_ObjectPort_Load(file_read, true); + + if(e.material) + { + // since objects are being loaded for the first time, precache material sounds for each + for (i = 1; i <= 5; i++) // 5 sounds in total + precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav")); + } + } + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n")); + } + fclose(file_get); +} + +MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand) +{SELFPARAM(); + if(MUTATOR_RETURNVALUE) // command was already handled? + return false; + if(cmd_name == "g_sandbox") + { + if(autocvar_g_sandbox_readonly) + { + print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used"); + return true; + } + if(cmd_argc < 2) + { + print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'"); + return true; + } + + switch(argv(1)) + { + entity e; + float i; + string s; + + // ---------------- COMMAND: HELP ---------------- + case "help": + print_to(self, "You can use the following sandbox commands:"); + print_to(self, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model"); + print_to(self, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects"); + print_to(self, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original"); + print_to(self, "^3copy value ^7- copies the properties of the object to the specified client cvar"); + print_to(self, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\""); + print_to(self, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects"); + print_to(self, "^3get ^7- selects the object you are facing as the object to be attached"); + print_to(self, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone"); + print_to(self, "^3remove ^7- detaches all objects from the object you are facing"); + print_to(self, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects"); + print_to(self, "^3skin value ^7- changes the skin of the object"); + print_to(self, "^3alpha value ^7- sets object transparency"); + print_to(self, "^3colormod \"value_x value_y value_z\" ^7- main object color"); + print_to(self, "^3glowmod \"value_x value_y value_z\" ^7- glow object color"); + print_to(self, "^3frame value ^7- object animation frame, for self-animated models"); + print_to(self, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size"); + print_to(self, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid"); + print_to(self, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical"); + print_to(self, "^3force value ^7- amount of force applied to objects that are shot"); + print_to(self, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh"); + print_to(self, "^7\"^2object_claim^7\" sets the player as the owner of the object, if he has the right to edit it"); + print_to(self, "^7\"^2object_info ^3value^7\" shows public information about the object"); + print_to(self, "^3object ^7- prints general information about the object, such as owner and creation / editing date"); + print_to(self, "^3mesh ^7- prints information about the object's mesh, including skeletal bones"); + print_to(self, "^3attachments ^7- prints information about the object's attachments"); + print_to(self, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects"); + return true; + + // ---------------- COMMAND: OBJECT, SPAWN ---------------- + case "object_spawn": + if(time < self.object_flood) + { + print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object")); + return true; + } + self.object_flood = time + autocvar_g_sandbox_editor_flood; + if(object_count >= autocvar_g_sandbox_editor_maxobjects) + { + print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); + return true; + } + if(cmd_argc < 3) + { + print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command"); + return true; + } + if (!(fexists(argv(2)))) + { + print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct"); + return true; + } + + e = sandbox_ObjectSpawn(false); + _setmodel(e, argv(2)); + + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " spawned an object at origin ^3", vtos(e.origin), "\n")); + return true; + + // ---------------- COMMAND: OBJECT, REMOVE ---------------- + case "object_remove": + e = sandbox_ObjectEdit_Get(true); + if(e != world) + { + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " removed an object at origin ^3", vtos(e.origin), "\n")); + sandbox_ObjectRemove(e); + return true; + } + + print_to(self, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over"); + return true; + + // ---------------- COMMAND: OBJECT, DUPLICATE ---------------- + case "object_duplicate": + switch(argv(2)) + { + case "copy": + // copies customizable properties of the selected object to the clipboard cvar + e = sandbox_ObjectEdit_Get(autocvar_g_sandbox_editor_free); // can we copy objects we can't edit? + if(e != world) + { + s = sandbox_ObjectPort_Save(e, false); + s = strreplace("\"", "\\\"", s); + stuffcmd(self, strcat("set ", argv(3), " \"", s, "\"")); + + print_to(self, "^2SANDBOX - INFO: ^7Object copied to clipboard"); + return true; + } + print_to(self, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over"); + return true; + + case "paste": + // spawns a new object using the properties in the player's clipboard cvar + if(time < self.object_flood) + { + print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object")); + return true; + } + self.object_flood = time + autocvar_g_sandbox_editor_flood; + if(argv(3) == "") // no object in clipboard + { + print_to(self, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it"); + return true; + } + if(object_count >= autocvar_g_sandbox_editor_maxobjects) + { + print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); + return true; + } + e = sandbox_ObjectPort_Load(argv(3), false); + + print_to(self, "^2SANDBOX - INFO: ^7Object pasted successfully"); + if(autocvar_g_sandbox_info > 0) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " pasted an object at origin ^3", vtos(e.origin), "\n")); + return true; + } + return true; + + // ---------------- COMMAND: OBJECT, ATTACH ---------------- + case "object_attach": + switch(argv(2)) + { + case "get": + // select e as the object as meant to be attached + e = sandbox_ObjectEdit_Get(true); + if(e != world) + { + self.object_attach = e; + print_to(self, "^2SANDBOX - INFO: ^7Object selected for attachment"); + return true; + } + print_to(self, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over"); + return true; + case "set": + if(self.object_attach == world) + { + print_to(self, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first."); + return true; + } + + // attaches the previously selected object to e + e = sandbox_ObjectEdit_Get(true); + if(e != world) + { + sandbox_ObjectAttach_Set(self.object_attach, e, argv(3)); + self.object_attach = world; // object was attached, no longer keep it scheduled for attachment + print_to(self, "^2SANDBOX - INFO: ^7Object attached successfully"); + if(autocvar_g_sandbox_info > 1) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " attached objects at origin ^3", vtos(e.origin), "\n")); + return true; + } + print_to(self, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over"); + return true; + case "remove": + // removes e if it was attached + e = sandbox_ObjectEdit_Get(true); + if(e != world) + { + sandbox_ObjectAttach_Remove(e); + print_to(self, "^2SANDBOX - INFO: ^7Child objects detached successfully"); + if(autocvar_g_sandbox_info > 1) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " detached objects at origin ^3", vtos(e.origin), "\n")); + return true; + } + print_to(self, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over"); + return true; + } + return true; + + // ---------------- COMMAND: OBJECT, EDIT ---------------- + case "object_edit": + if(argv(2) == "") + { + print_to(self, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit"); + return true; + } + + e = sandbox_ObjectEdit_Get(true); + if(e != world) + { + switch(argv(2)) + { + case "skin": + e.skin = stof(argv(3)); + break; + case "alpha": + e.alpha = stof(argv(3)); + break; + case "color_main": + e.colormod = stov(argv(3)); + break; + case "color_glow": + e.glowmod = stov(argv(3)); + break; + case "frame": + e.frame = stof(argv(3)); + break; + case "scale": + sandbox_ObjectEdit_Scale(e, stof(argv(3))); + break; + case "solidity": + switch(argv(3)) + { + case "0": // non-solid + e.solid = SOLID_TRIGGER; + break; + case "1": // solid + e.solid = SOLID_BBOX; + break; + default: + break; + } + case "physics": + switch(argv(3)) + { + case "0": // static + e.movetype = MOVETYPE_NONE; + break; + case "1": // movable + e.movetype = MOVETYPE_TOSS; + break; + case "2": // physical + e.movetype = MOVETYPE_PHYSICS; + break; + default: + break; + } + break; + case "force": + e.damageforcescale = stof(argv(3)); + break; + case "material": + if(e.material) strunzone(e.material); + if(argv(3)) + { + for (i = 1; i <= 5; i++) // precache material sounds, 5 in total + precache_sound(strcat("object/impact_", argv(3), "_", ftos(i), ".wav")); + e.material = strzone(argv(3)); + } + else + e.material = string_null; // no material + break; + default: + print_to(self, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'"); + return true; + } + + // update last editing time + if(e.message2) strunzone(e.message2); + e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); + + if(autocvar_g_sandbox_info > 1) + LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin), "\n")); + return true; + } + + print_to(self, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over"); + return true; + + // ---------------- COMMAND: OBJECT, CLAIM ---------------- + case "object_claim": + // if the player can edit an object but is not its owner, this can be used to claim that object + if(self.crypto_idfp == "") + { + print_to(self, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects"); + return true; + } + e = sandbox_ObjectEdit_Get(true); + if(e != world) + { + // update the owner's name + // Do this before checking if you're already the owner and skipping if such, so we + // also update the player's nickname if he changed it (but has the same player UID) + if(e.netname != self.netname) + { + if(e.netname) strunzone(e.netname); + e.netname = strzone(self.netname); + print_to(self, "^2SANDBOX - INFO: ^7Object owner name updated"); + } + + if(e.crypto_idfp == self.crypto_idfp) + { + print_to(self, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim"); + return true; + } + + if(e.crypto_idfp) strunzone(e.crypto_idfp); + e.crypto_idfp = strzone(self.crypto_idfp); + + print_to(self, "^2SANDBOX - INFO: ^7Object claimed successfully"); + } + print_to(self, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over"); + return true; + + // ---------------- COMMAND: OBJECT, INFO ---------------- + case "object_info": + // prints public information about the object to the player + e = sandbox_ObjectEdit_Get(false); + if(e != world) + { + switch(argv(2)) + { + case "object": + print_to(self, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\"")); + return true; + case "mesh": + s = ""; + FOR_EACH_TAG(e) + s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", "); + print_to(self, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s)); + return true; + case "attachments": + // this should show the same info as 'mesh' but for attachments + s = ""; + entity head; + i = 0; + for(head = world; (head = find(head, classname, "object")); ) + { + if(head.owner == e) + { + ++i; // start from 1 + gettaginfo(e, head.tag_index); + s = strcat(s, "^1attachment ", ftos(i), "^7 has mesh \"^3", head.model, "^7\" at animation frame ^3", ftos(head.frame)); + s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", "); + } + } + if(i) // object contains attachments + print_to(self, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(i), "^7 attachment(s): ", s)); + else + print_to(self, "^2SANDBOX - INFO: ^7Object contains no attachments"); + return true; + } + } + print_to(self, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object"); + return true; + + // ---------------- COMMAND: DEFAULT ---------------- + default: + print_to(self, "Invalid command. For usage information, type 'sandbox help'"); + return true; + } + } + return false; +} + +MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame) +{ + if(!autocvar_g_sandbox_storage_autosave) + return false; + if(time < autosave_time) + return false; + autosave_time = time + autocvar_g_sandbox_storage_autosave; + + sandbox_Database_Save(); + + return true; +} + +MUTATOR_HOOKFUNCTION(sandbox, SetModname) +{ + modname = "Sandbox"; + return true; +} +#endif diff --git a/qcsrc/server/mutators/mutator_bloodloss.qc b/qcsrc/server/mutators/mutator_bloodloss.qc deleted file mode 100644 index 5db1f98a0..000000000 --- a/qcsrc/server/mutators/mutator_bloodloss.qc +++ /dev/null @@ -1,46 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(bloodloss, cvar("g_bloodloss")); - -.float bloodloss_timer; - -MUTATOR_HOOKFUNCTION(bloodloss, PlayerPreThink) -{SELFPARAM(); - if(IS_PLAYER(self)) - if(self.health <= autocvar_g_bloodloss && self.deadflag == DEAD_NO) - { - self.BUTTON_CROUCH = true; - - if(time >= self.bloodloss_timer) - { - if(self.vehicle) - vehicles_exit(VHEF_RELEASE); - if(self.event_damage) - self.event_damage(self, self, 1, DEATH_ROT.m_id, self.origin, '0 0 0'); - self.bloodloss_timer = time + 0.5 + random() * 0.5; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(bloodloss, PlayerJump) -{SELFPARAM(); - if(self.health <= autocvar_g_bloodloss) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":bloodloss"); - return false; -} - -MUTATOR_HOOKFUNCTION(bloodloss, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Blood loss"); - return false; -} diff --git a/qcsrc/server/mutators/mutator_breakablehook.qc b/qcsrc/server/mutators/mutator_breakablehook.qc deleted file mode 100644 index 257937efe..000000000 --- a/qcsrc/server/mutators/mutator_breakablehook.qc +++ /dev/null @@ -1,27 +0,0 @@ -#include "../../common/deathtypes/all.qh" -#include "../g_hook.qh" - -REGISTER_MUTATOR(breakablehook, cvar("g_breakablehook")); - -bool autocvar_g_breakablehook; // allow toggling mid match? -bool autocvar_g_breakablehook_owner; - -MUTATOR_HOOKFUNCTION(breakablehook, PlayerDamage_Calculate) -{ - if(frag_target.classname == "grapplinghook") - { - if((!autocvar_g_breakablehook) - || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner) - ) { frag_damage = 0; } - - // hurt the owner of the hook - if(DIFF_TEAM(frag_attacker, frag_target.realowner)) - { - Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0'); - RemoveGrapplingHook(frag_target.realowner); - return false; // dead - } - } - - return false; -} diff --git a/qcsrc/server/mutators/mutator_buffs.qc b/qcsrc/server/mutators/mutator_buffs.qc deleted file mode 100644 index 80de63c95..000000000 --- a/qcsrc/server/mutators/mutator_buffs.qc +++ /dev/null @@ -1,970 +0,0 @@ -#include "../../common/triggers/target/music.qh" -#include "mutator_buffs.qh" - -#include "mutator.qh" - -#include "../../common/gamemodes/all.qh" -#include "../../common/buffs/all.qh" - -.float buff_time; -void buffs_DelayedInit(); - -REGISTER_MUTATOR(buffs, cvar("g_buffs")) -{ - MUTATOR_ONADD - { - addstat(STAT_BUFFS, AS_INT, buffs); - addstat(STAT_BUFF_TIME, AS_FLOAT, buff_time); - - InitializeEntity(world, buffs_DelayedInit, INITPRIO_FINDTARGET); - } -} - -entity buff_FirstFromFlags(int _buffs) -{ - if (flags) - { - FOREACH(Buffs, it.m_itemid & _buffs, LAMBDA(return it)); - } - return BUFF_Null; -} - -bool buffs_BuffModel_Customize() -{SELFPARAM(); - entity player, myowner; - bool same_team; - - player = WaypointSprite_getviewentity(other); - myowner = self.owner; - same_team = (SAME_TEAM(player, myowner) || SAME_TEAM(player, myowner)); - - if(myowner.alpha <= 0.5 && !same_team && myowner.alpha != 0) - return false; - - if(MUTATOR_CALLHOOK(BuffModel_Customize, self, player)) - return false; - - if(player == myowner || (IS_SPEC(other) && other.enemy == myowner)) - { - // somewhat hide the model, but keep the glow - self.effects = 0; - self.alpha = -1; - } - else - { - self.effects = EF_FULLBRIGHT | EF_LOWPRECISION; - self.alpha = 1; - } - return true; -} - -void buffs_BuffModel_Spawn(entity player) -{ - player.buff_model = spawn(); - setmodel(player.buff_model, MDL_BUFF); - setsize(player.buff_model, '0 0 -40', '0 0 40'); - setattachment(player.buff_model, player, ""); - setorigin(player.buff_model, '0 0 1' * (player.buff_model.maxs.z * 1)); - player.buff_model.owner = player; - player.buff_model.scale = 0.7; - player.buff_model.pflags = PFLAGS_FULLDYNAMIC; - player.buff_model.light_lev = 200; - player.buff_model.customizeentityforclient = buffs_BuffModel_Customize; -} - -vector buff_GlowColor(entity buff) -{ - //if(buff.team) { return Team_ColorRGB(buff.team); } - return buff.m_color; -} - -void buff_Effect(entity player, string eff) -{SELFPARAM(); - if(!autocvar_g_buffs_effects) { return; } - - if(time >= self.buff_effect_delay) - { - Send_Effect_(eff, player.origin + ((player.mins + player.maxs) * 0.5), '0 0 0', 1); - self.buff_effect_delay = time + 0.05; // prevent spam - } -} - -// buff item -float buff_Waypoint_visible_for_player(entity plr) -{SELFPARAM(); - if(!self.owner.buff_active && !self.owner.buff_activetime) - return false; - - if (plr.buffs) - { - return plr.cvar_cl_buffs_autoreplace == false || plr.buffs != self.owner.buffs; - } - - return WaypointSprite_visible_for_player(plr); -} - -void buff_Waypoint_Spawn(entity e) -{ - entity buff = buff_FirstFromFlags(e.buffs); - entity wp = WaypointSprite_Spawn(WP_Buff, 0, autocvar_g_buffs_waypoint_distance, e, '0 0 1' * e.maxs.z, world, e.team, e, buff_waypoint, true, RADARICON_Buff); - wp.wp_extra = buff.m_id; - WaypointSprite_UpdateTeamRadar(e.buff_waypoint, RADARICON_Buff, e.glowmod); - e.buff_waypoint.waypointsprite_visible_for_player = buff_Waypoint_visible_for_player; -} - -void buff_SetCooldown(float cd) -{SELFPARAM(); - cd = max(0, cd); - - if(!self.buff_waypoint) - buff_Waypoint_Spawn(self); - - WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + cd); - self.buff_activetime = cd; - self.buff_active = !cd; -} - -void buff_Respawn(entity ent) -{SELFPARAM(); - if(gameover) { return; } - - vector oldbufforigin = ent.origin; - - if(!MoveToRandomMapLocation(ent, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, ((autocvar_g_buffs_random_location_attempts > 0) ? autocvar_g_buffs_random_location_attempts : 10), 1024, 256)) - { - entity spot = SelectSpawnPoint(true); - setorigin(ent, ((spot.origin + '0 0 200') + (randomvec() * 300))); - ent.angles = spot.angles; - } - - tracebox(ent.origin, ent.mins * 1.5, self.maxs * 1.5, ent.origin, MOVE_NOMONSTERS, ent); - - setorigin(ent, trace_endpos); // attempt to unstick - - ent.movetype = MOVETYPE_TOSS; - - makevectors(ent.angles); - ent.velocity = '0 0 200'; - ent.angles = '0 0 0'; - if(autocvar_g_buffs_random_lifetime > 0) - ent.lifetime = time + autocvar_g_buffs_random_lifetime; - - Send_Effect(EFFECT_ELECTRO_COMBO, oldbufforigin + ((ent.mins + ent.maxs) * 0.5), '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, CENTER_OR_VIEWOFS(ent), '0 0 0', 1); - - WaypointSprite_Ping(ent.buff_waypoint); - - sound(ent, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere) -} - -void buff_Touch() -{SELFPARAM(); - if(gameover) { return; } - - if(ITEM_TOUCH_NEEDKILL()) - { - buff_Respawn(self); - return; - } - - if((self.team && DIFF_TEAM(other, self)) - || (other.frozen) - || (other.vehicle) - || (!self.buff_active) - ) - { - // can't touch this - return; - } - - if(MUTATOR_CALLHOOK(BuffTouch, self, other)) - return; - - if(!IS_PLAYER(other)) - return; // incase mutator changed other - - if (other.buffs) - { - if (other.cvar_cl_buffs_autoreplace && other.buffs != self.buffs) - { - int buffid = buff_FirstFromFlags(other.buffs).m_id; - //Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_DROP, other.buffs); - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ITEM_BUFF_LOST, other.netname, buffid); - - other.buffs = 0; - //sound(other, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); - } - else { return; } // do nothing - } - - self.owner = other; - self.buff_active = false; - self.lifetime = 0; - int buffid = buff_FirstFromFlags(self.buffs).m_id; - Send_Notification(NOTIF_ONE, other, MSG_MULTI, ITEM_BUFF_GOT, buffid); - Send_Notification(NOTIF_ALL_EXCEPT, other, MSG_INFO, INFO_ITEM_BUFF, other.netname, buffid); - - Send_Effect(EFFECT_ITEM_PICKUP, CENTER_OR_VIEWOFS(self), '0 0 0', 1); - sound(other, CH_TRIGGER, SND_SHIELD_RESPAWN, VOL_BASE, ATTN_NORM); - other.buffs |= (self.buffs); -} - -float buff_Available(entity buff) -{ - if (buff == BUFF_Null) - return false; - if (buff == BUFF_AMMO && ((start_items & IT_UNLIMITED_WEAPON_AMMO) || (start_items & IT_UNLIMITED_AMMO) || (cvar("g_melee_only")))) - return false; - if (buff == BUFF_VAMPIRE && cvar("g_vampire")) - return false; - return cvar(strcat("g_buffs_", buff.m_name)); -} - -.int buff_seencount; - -void buff_NewType(entity ent, float cb) -{ - RandomSelection_Init(); - FOREACH(Buffs, buff_Available(it), LAMBDA( - it.buff_seencount += 1; - // if it's already been chosen, give it a lower priority - RandomSelection_Add(world, it.m_itemid, string_null, 1, max(0.2, 1 / it.buff_seencount)); - )); - ent.buffs = RandomSelection_chosen_float; -} - -void buff_Think() -{SELFPARAM(); - if(self.buffs != self.oldbuffs) - { - entity buff = buff_FirstFromFlags(self.buffs); - self.color = buff.m_color; - self.glowmod = buff_GlowColor(buff); - self.skin = buff.m_skin; - - setmodel(self, MDL_BUFF); - - if(self.buff_waypoint) - { - //WaypointSprite_Disown(self.buff_waypoint, 1); - WaypointSprite_Kill(self.buff_waypoint); - buff_Waypoint_Spawn(self); - if(self.buff_activetime) - WaypointSprite_UpdateBuildFinished(self.buff_waypoint, time + self.buff_activetime - frametime); - } - - self.oldbuffs = self.buffs; - } - - if(!gameover) - if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) - if(!self.buff_activetime_updated) - { - buff_SetCooldown(self.buff_activetime); - self.buff_activetime_updated = true; - } - - if(!self.buff_active && !self.buff_activetime) - if(!self.owner || self.owner.frozen || self.owner.deadflag != DEAD_NO || !self.owner.iscreature || !(self.owner.buffs & self.buffs)) - { - buff_SetCooldown(autocvar_g_buffs_cooldown_respawn + frametime); - self.owner = world; - if(autocvar_g_buffs_randomize) - buff_NewType(self, self.buffs); - - if(autocvar_g_buffs_random_location || (self.spawnflags & 64)) - buff_Respawn(self); - } - - if(self.buff_activetime) - if(!gameover) - if((round_handler_IsActive() && !round_handler_IsRoundStarted()) || time >= game_starttime) - { - self.buff_activetime = max(0, self.buff_activetime - frametime); - - if(!self.buff_activetime) - { - self.buff_active = true; - sound(self, CH_TRIGGER, SND_STRENGTH_RESPAWN, VOL_BASE, ATTN_NORM); - Send_Effect(EFFECT_ITEM_RESPAWN, CENTER_OR_VIEWOFS(self), '0 0 0', 1); - } - } - - if(self.buff_active) - { - if(self.team && !self.buff_waypoint) - buff_Waypoint_Spawn(self); - - if(self.lifetime) - if(time >= self.lifetime) - buff_Respawn(self); - } - - self.nextthink = time; - //self.angles_y = time * 110.1; -} - -void buff_Waypoint_Reset() -{SELFPARAM(); - WaypointSprite_Kill(self.buff_waypoint); - - if(self.buff_activetime) { buff_Waypoint_Spawn(self); } -} - -void buff_Reset() -{SELFPARAM(); - if(autocvar_g_buffs_randomize) - buff_NewType(self, self.buffs); - self.owner = world; - buff_SetCooldown(autocvar_g_buffs_cooldown_activate); - buff_Waypoint_Reset(); - self.buff_activetime_updated = false; - - if(autocvar_g_buffs_random_location || (self.spawnflags & 64)) - buff_Respawn(self); -} - -float buff_Customize() -{SELFPARAM(); - entity player = WaypointSprite_getviewentity(other); - if(!self.buff_active || (self.team && DIFF_TEAM(player, self))) - { - self.alpha = 0.3; - if(self.effects & EF_FULLBRIGHT) { self.effects &= ~(EF_FULLBRIGHT); } - self.pflags = 0; - } - else - { - self.alpha = 1; - if(!(self.effects & EF_FULLBRIGHT)) { self.effects |= EF_FULLBRIGHT; } - self.light_lev = 220 + 36 * sin(time); - self.pflags = PFLAGS_FULLDYNAMIC; - } - return true; -} - -void buff_Init(entity ent) -{SELFPARAM(); - if(!cvar("g_buffs")) { remove(ent); return; } - - if(!teamplay && ent.team) { ent.team = 0; } - - entity buff = buff_FirstFromFlags(self.buffs); - - setself(ent); - if(!self.buffs || buff_Available(buff)) - buff_NewType(self, 0); - - self.classname = "item_buff"; - self.solid = SOLID_TRIGGER; - self.flags = FL_ITEM; - self.think = buff_Think; - self.touch = buff_Touch; - self.reset = buff_Reset; - self.nextthink = time + 0.1; - self.gravity = 1; - self.movetype = MOVETYPE_TOSS; - self.scale = 1; - self.skin = buff.m_skin; - self.effects = EF_FULLBRIGHT | EF_STARDUST | EF_NOSHADOW; - self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; - self.customizeentityforclient = buff_Customize; - //self.gravity = 100; - self.color = buff.m_color; - self.glowmod = buff_GlowColor(self); - buff_SetCooldown(autocvar_g_buffs_cooldown_activate + game_starttime); - self.buff_active = !self.buff_activetime; - self.pflags = PFLAGS_FULLDYNAMIC; - - if(self.spawnflags & 1) - self.noalign = true; - - if(self.noalign) - self.movetype = MOVETYPE_NONE; // reset by random location - - setmodel(self, MDL_BUFF); - setsize(self, BUFF_MIN, BUFF_MAX); - - if(cvar("g_buffs_random_location") || (self.spawnflags & 64)) - buff_Respawn(self); - - setself(this); -} - -void buff_Init_Compat(entity ent, entity replacement) -{ - if (ent.spawnflags & 2) - ent.team = NUM_TEAM_1; - else if (ent.spawnflags & 4) - ent.team = NUM_TEAM_2; - - ent.buffs = replacement.m_itemid; - - buff_Init(ent); -} - -void buff_SpawnReplacement(entity ent, entity old) -{ - setorigin(ent, old.origin); - ent.angles = old.angles; - ent.noalign = (old.noalign || (old.spawnflags & 1)); - - buff_Init(ent); -} - -void buff_Vengeance_DelayedDamage() -{SELFPARAM(); - if(self.enemy) - Damage(self.enemy, self.owner, self.owner, self.dmg, DEATH_BUFF.m_id, self.enemy.origin, '0 0 0'); - - remove(self); - return; -} - -float buff_Inferno_CalculateTime(float x, float offset_x, float offset_y, float intersect_x, float intersect_y, float base) -{ - return offset_y + (intersect_y - offset_y) * logn(((x - offset_x) * ((base - 1) / intersect_x)) + 1, base); -} - -// mutator hooks -MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_SplitHealthArmor) -{ - if(frag_deathtype == DEATH_BUFF.m_id) { return false; } - - if(frag_target.buffs & BUFF_RESISTANCE.m_itemid) - { - vector v = healtharmor_applydamage(50, autocvar_g_buffs_resistance_blockpercent, frag_deathtype, frag_damage); - damage_take = v.x; - damage_save = v.y; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerDamage_Calculate) -{ - if(frag_deathtype == DEATH_BUFF.m_id) { return false; } - - if(frag_target.buffs & BUFF_SPEED.m_itemid) - if(frag_target != frag_attacker) - frag_damage *= autocvar_g_buffs_speed_damage_take; - - if(frag_target.buffs & BUFF_MEDIC.m_itemid) - if((frag_target.health - frag_damage) <= 0) - if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - if(frag_attacker) - if(random() <= autocvar_g_buffs_medic_survive_chance) - frag_damage = max(5, frag_target.health - autocvar_g_buffs_medic_survive_health); - - if(frag_target.buffs & BUFF_JUMP.m_itemid) - if(frag_deathtype == DEATH_FALL.m_id) - frag_damage = 0; - - if(frag_target.buffs & BUFF_VENGEANCE.m_itemid) - if(frag_attacker) - if(frag_attacker != frag_target) - if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - { - entity dmgent = spawn(); - - dmgent.dmg = frag_damage * autocvar_g_buffs_vengeance_damage_multiplier; - dmgent.enemy = frag_attacker; - dmgent.owner = frag_target; - dmgent.think = buff_Vengeance_DelayedDamage; - dmgent.nextthink = time + 0.1; - } - - if(frag_target.buffs & BUFF_BASH.m_itemid) - if(frag_attacker != frag_target) - if(vlen(frag_force)) - frag_force = '0 0 0'; - - if(frag_attacker.buffs & BUFF_BASH.m_itemid) - if(vlen(frag_force)) - if(frag_attacker == frag_target) - frag_force *= autocvar_g_buffs_bash_force_self; - else - frag_force *= autocvar_g_buffs_bash_force; - - if(frag_attacker.buffs & BUFF_DISABILITY.m_itemid) - if(frag_target != frag_attacker) - frag_target.buff_disability_time = time + autocvar_g_buffs_disability_slowtime; - - if(frag_attacker.buffs & BUFF_MEDIC.m_itemid) - if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC) - if(SAME_TEAM(frag_attacker, frag_target)) - if(frag_attacker != frag_target) - { - frag_target.health = min(g_pickup_healthmega_max, frag_target.health + frag_damage); - frag_damage = 0; - } - - if(frag_attacker.buffs & BUFF_INFERNO.m_itemid) - if(frag_target != frag_attacker) { - float time = buff_Inferno_CalculateTime( - frag_damage, - 0, - autocvar_g_buffs_inferno_burntime_min_time, - autocvar_g_buffs_inferno_burntime_target_damage, - autocvar_g_buffs_inferno_burntime_target_time, - autocvar_g_buffs_inferno_burntime_factor - ); - Fire_AddDamage(frag_target, frag_attacker, (frag_damage * autocvar_g_buffs_inferno_damagemultiplier) * time, time, DEATH_BUFF.m_id); - } - - // this... is ridiculous (TODO: fix!) - if(frag_attacker.buffs & BUFF_VAMPIRE.m_itemid) - if(!frag_target.vehicle) - if(DEATH_WEAPONOF(frag_deathtype) != WEP_ARC) - if(!ITEM_DAMAGE_NEEDKILL(frag_deathtype)) - if(frag_target.deadflag == DEAD_NO) - if(IS_PLAYER(frag_target) || IS_MONSTER(frag_target)) - if(frag_attacker != frag_target) - if(!frag_target.frozen) - if(frag_target.takedamage) - if(DIFF_TEAM(frag_attacker, frag_target)) - { - frag_attacker.health = bound(0, frag_attacker.health + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.health), g_pickup_healthsmall_max); - if(frag_target.armorvalue) - frag_attacker.armorvalue = bound(0, frag_attacker.armorvalue + bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal, frag_target.armorvalue), g_pickup_armorsmall_max); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs,PlayerSpawn) -{SELFPARAM(); - self.buffs = 0; - // reset timers here to prevent them continuing after re-spawn - self.buff_disability_time = 0; - self.buff_disability_effect_time = 0; - return false; -} - -.float stat_sv_maxspeed; -.float stat_sv_airspeedlimit_nonqw; -.float stat_sv_jumpvelocity; - -MUTATOR_HOOKFUNCTION(buffs, PlayerPhysics) -{SELFPARAM(); - if(self.buffs & BUFF_SPEED.m_itemid) - { - self.stat_sv_maxspeed *= autocvar_g_buffs_speed_speed; - self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_speed_speed; - } - - if(time < self.buff_disability_time) - { - self.stat_sv_maxspeed *= autocvar_g_buffs_disability_speed; - self.stat_sv_airspeedlimit_nonqw *= autocvar_g_buffs_disability_speed; - } - - if(self.buffs & BUFF_JUMP.m_itemid) - { - // automatically reset, no need to worry - self.stat_sv_jumpvelocity = autocvar_g_buffs_jump_height; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerJump) -{SELFPARAM(); - if(self.buffs & BUFF_JUMP.m_itemid) - player_jumpheight = autocvar_g_buffs_jump_height; - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, MonsterMove) -{SELFPARAM(); - if(time < self.buff_disability_time) - { - monster_speed_walk *= autocvar_g_buffs_disability_speed; - monster_speed_run *= autocvar_g_buffs_disability_speed; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerDies) -{SELFPARAM(); - if(self.buffs) - { - int buffid = buff_FirstFromFlags(self.buffs).m_id; - Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid); - self.buffs = 0; - - if(self.buff_model) - { - remove(self.buff_model); - self.buff_model = world; - } - } - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerUseKey, CBC_ORDER_FIRST) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE || gameover) { return false; } - if(self.buffs) - { - int buffid = buff_FirstFromFlags(self.buffs).m_id; - Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid); - Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid); - - self.buffs = 0; - sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); - return true; - } - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, ForbidThrowCurrentWeapon) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE || gameover) { return false; } - - if(self.buffs & BUFF_SWAPPER.m_itemid) - { - float best_distance = autocvar_g_buffs_swapper_range; - entity closest = world; - entity player; - FOR_EACH_PLAYER(player) - if(DIFF_TEAM(self, player)) - if(player.deadflag == DEAD_NO && !player.frozen && !player.vehicle) - if(vlen(self.origin - player.origin) <= best_distance) - { - best_distance = vlen(self.origin - player.origin); - closest = player; - } - - if(closest) - { - vector my_org, my_vel, my_ang, their_org, their_vel, their_ang; - - my_org = self.origin; - my_vel = self.velocity; - my_ang = self.angles; - their_org = closest.origin; - their_vel = closest.velocity; - their_ang = closest.angles; - - Drop_Special_Items(closest); - - MUTATOR_CALLHOOK(PortalTeleport, self); // initiate flag dropper - - setorigin(self, their_org); - setorigin(closest, my_org); - - closest.velocity = my_vel; - closest.angles = my_ang; - closest.fixangle = true; - closest.oldorigin = my_org; - closest.oldvelocity = my_vel; - self.velocity = their_vel; - self.angles = their_ang; - self.fixangle = true; - self.oldorigin = their_org; - self.oldvelocity = their_vel; - - // set pusher so self gets the kill if they fall into void - closest.pusher = self; - closest.pushltime = time + autocvar_g_maxpushtime; - closest.istypefrag = closest.BUTTON_CHAT; - - Send_Effect(EFFECT_ELECTRO_COMBO, their_org, '0 0 0', 1); - Send_Effect(EFFECT_ELECTRO_COMBO, my_org, '0 0 0', 1); - - sound(self, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); - sound(closest, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NORM); - - // TODO: add a counter to handle how many times one can teleport, and a delay to prevent spam - self.buffs = 0; - return true; - } - } - return false; -} - -bool buffs_RemovePlayer(entity player) -{ - if(player.buff_model) - { - remove(player.buff_model); - player.buff_model = world; - } - - // also reset timers here to prevent them continuing after spectating - player.buff_disability_time = 0; - player.buff_disability_effect_time = 0; - - return false; -} -MUTATOR_HOOKFUNCTION(buffs, MakePlayerObserver) { return buffs_RemovePlayer(self); } -MUTATOR_HOOKFUNCTION(buffs, ClientDisconnect) { return buffs_RemovePlayer(self); } - -MUTATOR_HOOKFUNCTION(buffs, CustomizeWaypoint) -{SELFPARAM(); - entity e = WaypointSprite_getviewentity(other); - - // if you have the invisibility powerup, sprites ALWAYS are restricted to your team - // but only apply this to real players, not to spectators - if((self.owner.flags & FL_CLIENT) && (self.owner.buffs & BUFF_INVISIBLE.m_itemid) && (e == other)) - if(DIFF_TEAM(self.owner, e)) - return true; - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, OnEntityPreSpawn, CBC_ORDER_LAST) -{SELFPARAM(); - if(autocvar_g_buffs_replace_powerups) - switch(self.classname) - { - case "item_strength": - case "item_invincible": - { - entity e = spawn(); - buff_SpawnReplacement(e, self); - return true; - } - } - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, WeaponRateFactor) -{SELFPARAM(); - if(self.buffs & BUFF_SPEED.m_itemid) - weapon_rate *= autocvar_g_buffs_speed_rate; - - if(time < self.buff_disability_time) - weapon_rate *= autocvar_g_buffs_disability_rate; - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, WeaponSpeedFactor) -{SELFPARAM(); - if(self.buffs & BUFF_SPEED.m_itemid) - ret_float *= autocvar_g_buffs_speed_weaponspeed; - - if(time < self.buff_disability_time) - ret_float *= autocvar_g_buffs_disability_weaponspeed; - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerPreThink) -{SELFPARAM(); - if(gameover || self.deadflag != DEAD_NO) { return false; } - - if(time < self.buff_disability_time) - if(time >= self.buff_disability_effect_time) - { - Send_Effect(EFFECT_SMOKING, self.origin + ((self.mins + self.maxs) * 0.5), '0 0 0', 1); - self.buff_disability_effect_time = time + 0.5; - } - - // handle buff lost status - // 1: notify everyone else - // 2: notify carrier as well - int buff_lost = 0; - - if(self.buff_time) - if(time >= self.buff_time) - buff_lost = 2; - - if(self.frozen) { buff_lost = 1; } - - if(buff_lost) - { - if(self.buffs) - { - int buffid = buff_FirstFromFlags(self.buffs).m_id; - Send_Notification(NOTIF_ALL_EXCEPT, self, MSG_INFO, INFO_ITEM_BUFF_LOST, self.netname, buffid); - if(buff_lost >= 2) - { - Send_Notification(NOTIF_ONE, self, MSG_MULTI, ITEM_BUFF_DROP, buffid); // TODO: special timeout message? - sound(self, CH_TRIGGER, SND_BUFF_LOST, VOL_BASE, ATTN_NORM); - } - self.buffs = 0; - } - } - - if(self.buffs & BUFF_MAGNET.m_itemid) - { - vector pickup_size = '1 1 1' * autocvar_g_buffs_magnet_range_item; - for(other = world; (other = findflags(other, flags, FL_ITEM)); ) - if(boxesoverlap(self.absmin - pickup_size, self.absmax + pickup_size, other.absmin, other.absmax)) - { - setself(other); - other = this; - if(self.touch) - self.touch(); - other = self; - setself(this); - } - } - - if(self.buffs & BUFF_AMMO.m_itemid) - if(self.clip_size) - self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size; - - if((self.buffs & BUFF_INVISIBLE.m_itemid) && (self.oldbuffs & BUFF_INVISIBLE.m_itemid)) - if(self.alpha != autocvar_g_buffs_invisible_alpha) - self.alpha = autocvar_g_buffs_invisible_alpha; // powerups reset alpha, so we must enforce this (TODO) - -#define BUFF_ONADD(b) if ( (self.buffs & (b).m_itemid) && !(self.oldbuffs & (b).m_itemid)) -#define BUFF_ONREM(b) if (!(self.buffs & (b).m_itemid) && (self.oldbuffs & (b).m_itemid)) - - if(self.buffs != self.oldbuffs) - { - entity buff = buff_FirstFromFlags(self.buffs); - float bufftime = buff != BUFF_Null ? buff.m_time(buff) : 0; - self.buff_time = (bufftime) ? time + bufftime : 0; - - BUFF_ONADD(BUFF_AMMO) - { - self.buff_ammo_prev_infitems = (self.items & IT_UNLIMITED_WEAPON_AMMO); - self.items |= IT_UNLIMITED_WEAPON_AMMO; - - if(self.clip_load) - self.buff_ammo_prev_clipload = self.clip_load; - self.clip_load = self.(weapon_load[self.switchweapon]) = self.clip_size; - } - - BUFF_ONREM(BUFF_AMMO) - { - if(self.buff_ammo_prev_infitems) - self.items |= IT_UNLIMITED_WEAPON_AMMO; - else - self.items &= ~IT_UNLIMITED_WEAPON_AMMO; - - if(self.buff_ammo_prev_clipload) - self.clip_load = self.buff_ammo_prev_clipload; - } - - BUFF_ONADD(BUFF_INVISIBLE) - { - if(time < self.strength_finished && g_instagib) - self.alpha = autocvar_g_instagib_invis_alpha; - else - self.alpha = self.buff_invisible_prev_alpha; - self.alpha = autocvar_g_buffs_invisible_alpha; - } - - BUFF_ONREM(BUFF_INVISIBLE) - self.alpha = self.buff_invisible_prev_alpha; - - BUFF_ONADD(BUFF_FLIGHT) - { - self.buff_flight_prev_gravity = self.gravity; - self.gravity = autocvar_g_buffs_flight_gravity; - } - - BUFF_ONREM(BUFF_FLIGHT) - self.gravity = self.buff_flight_prev_gravity; - - self.oldbuffs = self.buffs; - if(self.buffs) - { - if(!self.buff_model) - buffs_BuffModel_Spawn(self); - - self.buff_model.color = buff.m_color; - self.buff_model.glowmod = buff_GlowColor(self.buff_model); - self.buff_model.skin = buff.m_skin; - - self.effects |= EF_NOSHADOW; - } - else - { - remove(self.buff_model); - self.buff_model = world; - - self.effects &= ~(EF_NOSHADOW); - } - } - - if(self.buff_model) - { - self.buff_model.effects = self.effects; - self.buff_model.effects |= EF_LOWPRECISION; - self.buff_model.effects = self.buff_model.effects & EFMASK_CHEAP; // eat performance - - self.buff_model.alpha = self.alpha; - } - -#undef BUFF_ONADD -#undef BUFF_ONREM - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, SpectateCopy) -{SELFPARAM(); - self.buffs = other.buffs; - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, VehicleEnter) -{ - vh_vehicle.buffs = vh_player.buffs; - vh_player.buffs = 0; - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, VehicleExit) -{ - vh_player.buffs = vh_vehicle.buffs; - vh_vehicle.buffs = 0; - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, PlayerRegen) -{SELFPARAM(); - if(self.buffs & BUFF_MEDIC.m_itemid) - { - regen_mod_rot = autocvar_g_buffs_medic_rot; - regen_mod_limit = regen_mod_max = autocvar_g_buffs_medic_max; - regen_mod_regen = autocvar_g_buffs_medic_regen; - } - - if(self.buffs & BUFF_SPEED.m_itemid) - regen_mod_regen = autocvar_g_buffs_speed_regen; - - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, GetCvars) -{ - GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_buffs_autoreplace, "cl_buffs_autoreplace"); - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":Buffs"); - return false; -} - -MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Buffs"); - return false; -} - -void buffs_DelayedInit() -{ - if(autocvar_g_buffs_spawn_count > 0) - if(find(world, classname, "item_buff") == world) - { - float i; - for(i = 0; i < autocvar_g_buffs_spawn_count; ++i) - { - entity e = spawn(); - e.spawnflags |= 64; // always randomize - e.velocity = randomvec() * 250; // this gets reset anyway if random location works - buff_Init(e); - } - } -} diff --git a/qcsrc/server/mutators/mutator_buffs.qh b/qcsrc/server/mutators/mutator_buffs.qh deleted file mode 100644 index 10d84ef4e..000000000 --- a/qcsrc/server/mutators/mutator_buffs.qh +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef MUTATOR_BUFFS_H -#define MUTATOR_BUFFS_H - -// buff specific variables \\ -// -// ammo -.float buff_ammo_prev_infitems; -.int buff_ammo_prev_clipload; -// invisible -.float buff_invisible_prev_alpha; -// flight -.float buff_flight_prev_gravity; -// disability -.float buff_disability_time; -.float buff_disability_effect_time; -// common buff variables -.float buff_effect_delay; - -// buff definitions -.float buff_active; -.float buff_activetime; -.float buff_activetime_updated; -.entity buff_waypoint; -.int oldbuffs; // for updating effects -.entity buff_model; // controls effects (TODO: make csqc) - -const vector BUFF_MIN = ('-16 -16 -20'); -const vector BUFF_MAX = ('16 16 20'); - -// client side options -.float cvar_cl_buffs_autoreplace; -#endif diff --git a/qcsrc/server/mutators/mutator_campcheck.qc b/qcsrc/server/mutators/mutator_campcheck.qc deleted file mode 100644 index 04598edc2..000000000 --- a/qcsrc/server/mutators/mutator_campcheck.qc +++ /dev/null @@ -1,89 +0,0 @@ - -#include "mutator.qh" - -#include "../campaign.qh" - -REGISTER_MUTATOR(campcheck, cvar("g_campcheck")); - -.float campcheck_nextcheck; -.float campcheck_traveled_distance; - -MUTATOR_HOOKFUNCTION(campcheck, PlayerDies) -{SELFPARAM(); - Kill_Notification(NOTIF_ONE, self, MSG_CENTER_CPID, CPID_CAMPCHECK); - - return false; -} - -MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate) -{ - if(IS_PLAYER(frag_target)) - if(IS_PLAYER(frag_attacker)) - if(frag_attacker != frag_target) - { - frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance; - frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink) -{SELFPARAM(); - if(!gameover) - if(!warmup_stage) // don't consider it camping during warmup? - if(time >= game_starttime) - if(IS_PLAYER(self)) - if(IS_REAL_CLIENT(self)) // bots may camp, but that's no reason to constantly kill them - if(self.deadflag == DEAD_NO) - if(!self.frozen) - if(!self.BUTTON_CHAT) - if(autocvar_g_campcheck_interval) - { - vector dist; - - // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement) - dist = self.prevorigin - self.origin; - dist.z = 0; - self.campcheck_traveled_distance += fabs(vlen(dist)); - - if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted())) - { - self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; - self.campcheck_traveled_distance = 0; - } - - if(time > self.campcheck_nextcheck) - { - if(self.campcheck_traveled_distance < autocvar_g_campcheck_distance) - { - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_CAMPCHECK); - if(self.vehicle) - Damage(self.vehicle, self, self, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, self.vehicle.origin, '0 0 0'); - else - Damage(self, self, self, bound(0, autocvar_g_campcheck_damage, self.health + self.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, self.origin, '0 0 0'); - } - self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; - self.campcheck_traveled_distance = 0; - } - - return false; - } - - self.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date - return false; -} - -MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn) -{SELFPARAM(); - self.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2; - self.campcheck_traveled_distance = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":CampCheck"); - return false; -} diff --git a/qcsrc/server/mutators/mutator_dodging.qc b/qcsrc/server/mutators/mutator_dodging.qc deleted file mode 100644 index e41456ad5..000000000 --- a/qcsrc/server/mutators/mutator_dodging.qc +++ /dev/null @@ -1,314 +0,0 @@ -#ifdef CSQC - #define PHYS_DODGING_FRAMETIME (1 / (frametime <= 0 ? 60 : frametime)) - #define PHYS_DODGING getstati(STAT_DODGING) - #define PHYS_DODGING_DELAY getstatf(STAT_DODGING_DELAY) - #define PHYS_DODGING_TIMEOUT(s) getstatf(STAT_DODGING_TIMEOUT) - #define PHYS_DODGING_HORIZ_SPEED_FROZEN getstatf(STAT_DODGING_HORIZ_SPEED_FROZEN) - #define PHYS_DODGING_FROZEN_NODOUBLETAP getstati(STAT_DODGING_FROZEN_NO_DOUBLETAP) - #define PHYS_DODGING_HORIZ_SPEED getstatf(STAT_DODGING_HORIZ_SPEED) - #define PHYS_DODGING_PRESSED_KEYS(s) s.pressedkeys - #define PHYS_DODGING_HEIGHT_THRESHOLD getstatf(STAT_DODGING_HEIGHT_THRESHOLD) - #define PHYS_DODGING_DISTANCE_THRESHOLD getstatf(STAT_DODGING_DISTANCE_THRESHOLD) - #define PHYS_DODGING_RAMP_TIME getstatf(STAT_DODGING_RAMP_TIME) - #define PHYS_DODGING_UP_SPEED getstatf(STAT_DODGING_UP_SPEED) - #define PHYS_DODGING_WALL getstatf(STAT_DODGING_WALL) -#elif defined(SVQC) - #define PHYS_DODGING_FRAMETIME sys_frametime - #define PHYS_DODGING g_dodging - #define PHYS_DODGING_DELAY autocvar_sv_dodging_delay - #define PHYS_DODGING_TIMEOUT(s) s.cvar_cl_dodging_timeout - #define PHYS_DODGING_HORIZ_SPEED_FROZEN autocvar_sv_dodging_horiz_speed_frozen - #define PHYS_DODGING_FROZEN_NODOUBLETAP autocvar_sv_dodging_frozen_doubletap - #define PHYS_DODGING_HORIZ_SPEED autocvar_sv_dodging_horiz_speed - #define PHYS_DODGING_PRESSED_KEYS(s) s.pressedkeys - #define PHYS_DODGING_HEIGHT_THRESHOLD autocvar_sv_dodging_height_threshold - #define PHYS_DODGING_DISTANCE_THRESHOLD autocvar_sv_dodging_wall_distance_threshold - #define PHYS_DODGING_RAMP_TIME autocvar_sv_dodging_ramp_time - #define PHYS_DODGING_UP_SPEED autocvar_sv_dodging_up_speed - #define PHYS_DODGING_WALL autocvar_sv_dodging_wall_dodging -#endif - -#ifdef SVQC -#include "mutator_dodging.qh" - -#include "mutator.qh" - -#include "../../common/animdecide.qh" -#include "../../common/physics.qh" - -.float cvar_cl_dodging_timeout; - -.float stat_dodging; -.float stat_dodging_delay; -.float stat_dodging_horiz_speed_frozen; -.float stat_dodging_frozen_nodoubletap; -.float stat_dodging_frozen; -.float stat_dodging_horiz_speed; -.float stat_dodging_height_threshold; -.float stat_dodging_distance_threshold; -.float stat_dodging_ramp_time; -.float stat_dodging_up_speed; -.float stat_dodging_wall; - -REGISTER_MUTATOR(dodging, cvar("g_dodging")) -{ - // this just turns on the cvar. - MUTATOR_ONADD - { - g_dodging = cvar("g_dodging"); - addstat(STAT_DODGING, AS_INT, stat_dodging); - addstat(STAT_DODGING_DELAY, AS_FLOAT, stat_dodging_delay); - addstat(STAT_DODGING_TIMEOUT, AS_FLOAT, cvar_cl_dodging_timeout); // we stat this, so it is updated on the client when updated on server (otherwise, chaos) - addstat(STAT_DODGING_FROZEN_NO_DOUBLETAP, AS_INT, stat_dodging_frozen_nodoubletap); - addstat(STAT_DODGING_HORIZ_SPEED_FROZEN, AS_FLOAT, stat_dodging_horiz_speed_frozen); - addstat(STAT_DODGING_FROZEN, AS_INT, stat_dodging_frozen); - addstat(STAT_DODGING_HORIZ_SPEED, AS_FLOAT, stat_dodging_horiz_speed); - addstat(STAT_DODGING_HEIGHT_THRESHOLD, AS_FLOAT, stat_dodging_height_threshold); - addstat(STAT_DODGING_DISTANCE_THRESHOLD, AS_FLOAT, stat_dodging_distance_threshold); - addstat(STAT_DODGING_RAMP_TIME, AS_FLOAT, stat_dodging_ramp_time); - addstat(STAT_DODGING_UP_SPEED, AS_FLOAT, stat_dodging_up_speed); - addstat(STAT_DODGING_WALL, AS_FLOAT, stat_dodging_wall); - } - - // this just turns off the cvar. - MUTATOR_ONROLLBACK_OR_REMOVE - { - g_dodging = 0; - } - - return false; -} - -#endif - -// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done.. -.float dodging_action; - -// the jump part of the dodge cannot be ramped -.float dodging_single_action; - - -// these are used to store the last key press time for each of the keys.. -.float last_FORWARD_KEY_time; -.float last_BACKWARD_KEY_time; -.float last_LEFT_KEY_time; -.float last_RIGHT_KEY_time; - -// these store the movement direction at the time of the dodge action happening. -.vector dodging_direction; - -// this indicates the last time a dodge was executed. used to check if another one is allowed -// and to ramp up the dodge acceleration in the physics hook. -.float last_dodging_time; - -// This is the velocity gain to be added over the ramp time. -// It will decrease from frame to frame during dodging_action = 1 -// until it's 0. -.float dodging_velocity_gain; - -#ifdef CSQC -.int pressedkeys; - -#elif defined(SVQC) - -void dodging_UpdateStats() -{SELFPARAM(); - self.stat_dodging = PHYS_DODGING; - self.stat_dodging_delay = PHYS_DODGING_DELAY; - self.stat_dodging_horiz_speed_frozen = PHYS_DODGING_HORIZ_SPEED_FROZEN; - self.stat_dodging_frozen = PHYS_DODGING_FROZEN; - self.stat_dodging_frozen_nodoubletap = PHYS_DODGING_FROZEN_NODOUBLETAP; - self.stat_dodging_height_threshold = PHYS_DODGING_HEIGHT_THRESHOLD; - self.stat_dodging_distance_threshold = PHYS_DODGING_DISTANCE_THRESHOLD; - self.stat_dodging_ramp_time = PHYS_DODGING_RAMP_TIME; - self.stat_dodging_up_speed = PHYS_DODGING_UP_SPEED; - self.stat_dodging_wall = PHYS_DODGING_WALL; -} - -#endif - -// returns 1 if the player is close to a wall -bool check_close_to_wall(float threshold) -{SELFPARAM(); - if (PHYS_DODGING_WALL == 0) { return false; } - - #define X(OFFSET) \ - tracebox(self.origin, self.mins, self.maxs, self.origin + OFFSET, true, self); \ - if (trace_fraction < 1 && vlen (self.origin - trace_endpos) < threshold) \ - return true; - X(1000*v_right); - X(-1000*v_right); - X(1000*v_forward); - X(-1000*v_forward); - #undef X - - return false; -} - -bool check_close_to_ground(float threshold) -{SELFPARAM(); - return IS_ONGROUND(self) ? true : false; -} - -float PM_dodging_checkpressedkeys() -{SELFPARAM(); - if(!PHYS_DODGING) - return false; - - float frozen_dodging = (PHYS_FROZEN(self) && PHYS_DODGING_FROZEN); - float frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP); - - // first check if the last dodge is far enough back in time so we can dodge again - if ((time - self.last_dodging_time) < PHYS_DODGING_DELAY) - return false; - - makevectors(self.angles); - - if (check_close_to_ground(PHYS_DODGING_HEIGHT_THRESHOLD) != 1 - && check_close_to_wall(PHYS_DODGING_DISTANCE_THRESHOLD) != 1) - return true; - - float tap_direction_x = 0; - float tap_direction_y = 0; - float dodge_detected = 0; - - #define X(COND,BTN,RESULT) \ - if (self.movement_##COND) \ - /* is this a state change? */ \ - if(!(PHYS_DODGING_PRESSED_KEYS(self) & KEY_##BTN) || frozen_no_doubletap) { \ - tap_direction_##RESULT; \ - if ((time - self.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(self)) \ - dodge_detected = 1; \ - self.last_##BTN##_KEY_time = time; \ - } - X(x < 0, BACKWARD, x--); - X(x > 0, FORWARD, x++); - X(y < 0, LEFT, y--); - X(y > 0, RIGHT, y++); - #undef X - - if (dodge_detected == 1) - { - self.last_dodging_time = time; - - self.dodging_action = 1; - self.dodging_single_action = 1; - - self.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED; - - self.dodging_direction_x = tap_direction_x; - self.dodging_direction_y = tap_direction_y; - - // normalize the dodging_direction vector.. (unlike UT99) XD - float length = self.dodging_direction_x * self.dodging_direction_x - + self.dodging_direction_y * self.dodging_direction_y; - length = sqrt(length); - - self.dodging_direction_x = self.dodging_direction_x * 1.0 / length; - self.dodging_direction_y = self.dodging_direction_y * 1.0 / length; - return true; - } - return false; -} - -void PM_dodging() -{SELFPARAM(); - if (!PHYS_DODGING) - return; - -#ifdef SVQC - dodging_UpdateStats(); -#endif - - if (PHYS_DEAD(self)) - return; - - // when swimming, no dodging allowed.. - if (self.waterlevel >= WATERLEVEL_SWIMMING) - { - self.dodging_action = 0; - self.dodging_direction_x = 0; - self.dodging_direction_y = 0; - return; - } - - // make sure v_up, v_right and v_forward are sane - makevectors(self.angles); - - // if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code - // will be called ramp_time/frametime times = 2 times. so, we need to - // add 0.5 * the total speed each frame until the dodge action is done.. - float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME; - - // if ramp time is smaller than frametime we get problems ;D - common_factor = min(common_factor, 1); - - float horiz_speed = PHYS_FROZEN(self) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED; - float new_velocity_gain = self.dodging_velocity_gain - (common_factor * horiz_speed); - new_velocity_gain = max(0, new_velocity_gain); - - float velocity_difference = self.dodging_velocity_gain - new_velocity_gain; - - // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D - if (self.dodging_action == 1) - { - //disable jump key during dodge accel phase - if(self.movement_z > 0) { self.movement_z = 0; } - - self.velocity += ((self.dodging_direction_y * velocity_difference) * v_right) - + ((self.dodging_direction_x * velocity_difference) * v_forward); - - self.dodging_velocity_gain = self.dodging_velocity_gain - velocity_difference; - } - - // the up part of the dodge is a single shot action - if (self.dodging_single_action == 1) - { - UNSET_ONGROUND(self); - - self.velocity += PHYS_DODGING_UP_SPEED * v_up; - -#ifdef SVQC - if (autocvar_sv_dodging_sound) - PlayerSound(playersound_jump, CH_PLAYER, VOICETYPE_PLAYERSOUND); - - animdecide_setaction(self, ANIMACTION_JUMP, true); -#endif - - self.dodging_single_action = 0; - } - - // are we done with the dodging ramp yet? - if((self.dodging_action == 1) && ((time - self.last_dodging_time) > PHYS_DODGING_RAMP_TIME)) - { - // reset state so next dodge can be done correctly - self.dodging_action = 0; - self.dodging_direction_x = 0; - self.dodging_direction_y = 0; - } -} - -#ifdef SVQC - -MUTATOR_HOOKFUNCTION(dodging, GetCvars) -{ - GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_dodging_timeout, "cl_dodging_timeout"); - return false; -} - -MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics) -{ - // print("dodging_PlayerPhysics\n"); - PM_dodging(); - - return false; -} - -MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys) -{ - PM_dodging_checkpressedkeys(); - - return false; -} - -#endif diff --git a/qcsrc/server/mutators/mutator_dodging.qh b/qcsrc/server/mutators/mutator_dodging.qh deleted file mode 100644 index a8fd66563..000000000 --- a/qcsrc/server/mutators/mutator_dodging.qh +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef MUTATOR_DODGING_H -#define MUTATOR_DODGING_H - -float g_dodging; - -// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done.. -.float dodging_action; - -// the jump part of the dodge cannot be ramped -.float dodging_single_action; -#endif diff --git a/qcsrc/server/mutators/mutator_hook.qc b/qcsrc/server/mutators/mutator_hook.qc deleted file mode 100644 index e43848b30..000000000 --- a/qcsrc/server/mutators/mutator_hook.qc +++ /dev/null @@ -1,40 +0,0 @@ -AUTOCVAR(g_grappling_hook, bool, false, _("let players spawn with the grappling hook which allows them to pull themselves up")); -#ifdef SVQC -REGISTER_MUTATOR(hook, autocvar_g_grappling_hook) { - MUTATOR_ONADD { - g_grappling_hook = true; - WEP_HOOK.ammo_factor = 0; - } - MUTATOR_ONROLLBACK_OR_REMOVE { - g_grappling_hook = false; - WEP_HOOK.ammo_factor = 1; - } -} - -MUTATOR_HOOKFUNCTION(hook, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":grappling_hook"); -} - -MUTATOR_HOOKFUNCTION(hook, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Hook"); -} - -MUTATOR_HOOKFUNCTION(hook, BuildGameplayTipsString) -{ - ret_string = strcat(ret_string, "\n\n^3grappling hook^8 is enabled, press 'e' to use it\n"); -} - -MUTATOR_HOOKFUNCTION(hook, PlayerSpawn) -{ - SELFPARAM(); - self.offhand = OFFHAND_HOOK; -} - -MUTATOR_HOOKFUNCTION(hook, FilterItem) -{ - return self.weapon == WEP_HOOK.m_id; -} - -#endif diff --git a/qcsrc/server/mutators/mutator_invincibleproj.qc b/qcsrc/server/mutators/mutator_invincibleproj.qc deleted file mode 100644 index 3f76bf727..000000000 --- a/qcsrc/server/mutators/mutator_invincibleproj.qc +++ /dev/null @@ -1,26 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(invincibleprojectiles, cvar("g_invincible_projectiles")); - -MUTATOR_HOOKFUNCTION(invincibleprojectiles, EditProjectile) -{ - if(other.health) - { - // disable health which in effect disables damage calculations - other.health = 0; - } - return 0; -} - -MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":InvincibleProjectiles"); - return 0; -} - -MUTATOR_HOOKFUNCTION(invincibleprojectiles, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Invincible Projectiles"); - return 0; -} diff --git a/qcsrc/server/mutators/mutator_melee_only.qc b/qcsrc/server/mutators/mutator_melee_only.qc deleted file mode 100644 index f85260d86..000000000 --- a/qcsrc/server/mutators/mutator_melee_only.qc +++ /dev/null @@ -1,41 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(melee_only, cvar("g_melee_only") && !cvar("g_instagib") && !g_nexball); - -MUTATOR_HOOKFUNCTION(melee_only, SetStartItems) -{ - start_ammo_shells = warmup_start_ammo_shells = 0; - start_weapons = warmup_start_weapons = WEPSET(SHOTGUN); - - return false; -} - -MUTATOR_HOOKFUNCTION(melee_only, ForbidThrowCurrentWeapon) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(melee_only, FilterItem) -{SELFPARAM(); - switch (self.items) - { - case ITEM_HealthSmall.m_itemid: - case ITEM_ArmorSmall.m_itemid: - return false; - } - - return true; -} - -MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":MeleeOnly"); - return false; -} - -MUTATOR_HOOKFUNCTION(melee_only, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Melee Only Arena"); - return false; -} diff --git a/qcsrc/server/mutators/mutator_midair.qc b/qcsrc/server/mutators/mutator_midair.qc deleted file mode 100644 index 4dfb7d5fb..000000000 --- a/qcsrc/server/mutators/mutator_midair.qc +++ /dev/null @@ -1,48 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(midair, cvar("g_midair")); - -.float midair_shieldtime; - -MUTATOR_HOOKFUNCTION(midair, PlayerDamage_Calculate) -{SELFPARAM(); - if(IS_PLAYER(frag_attacker)) - if(IS_PLAYER(frag_target)) - if(time < self.midair_shieldtime) - frag_damage = false; - - return false; -} - -MUTATOR_HOOKFUNCTION(midair, PlayerPowerups) -{SELFPARAM(); - if(time >= game_starttime) - if(self.flags & FL_ONGROUND) - { - self.effects |= (EF_ADDITIVE | EF_FULLBRIGHT); - self.midair_shieldtime = max(self.midair_shieldtime, time + autocvar_g_midair_shieldtime); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(midair, PlayerSpawn) -{SELFPARAM(); - if(IS_BOT_CLIENT(self)) - self.bot_moveskill = 0; // disable bunnyhopping - - return false; -} - -MUTATOR_HOOKFUNCTION(midair, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":midair"); - return false; -} - -MUTATOR_HOOKFUNCTION(midair, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Midair"); - return false; -} diff --git a/qcsrc/server/mutators/mutator_multijump.qc b/qcsrc/server/mutators/mutator_multijump.qc deleted file mode 100644 index d36f281a1..000000000 --- a/qcsrc/server/mutators/mutator_multijump.qc +++ /dev/null @@ -1,181 +0,0 @@ -#ifdef SVQC - #include "mutator.qh" - #include "../antilag.qh" -#endif -#include "../../common/physics.qh" - -.int multijump_count; -.bool multijump_ready; -.bool cvar_cl_multijump; - -#ifdef CSQC - -#define PHYS_MULTIJUMP getstati(STAT_MULTIJUMP) -#define PHYS_MULTIJUMP_SPEED getstatf(STAT_MULTIJUMP_SPEED) -#define PHYS_MULTIJUMP_ADD getstati(STAT_MULTIJUMP_ADD) -#define PHYS_MULTIJUMP_MAXSPEED getstatf(STAT_MULTIJUMP_MAXSPEED) -#define PHYS_MULTIJUMP_DODGING getstati(STAT_MULTIJUMP_DODGING) - -#elif defined(SVQC) - -#define PHYS_MULTIJUMP autocvar_g_multijump -#define PHYS_MULTIJUMP_SPEED autocvar_g_multijump_speed -#define PHYS_MULTIJUMP_ADD autocvar_g_multijump_add -#define PHYS_MULTIJUMP_MAXSPEED autocvar_g_multijump_maxspeed -#define PHYS_MULTIJUMP_DODGING autocvar_g_multijump_dodging - - -.float stat_multijump; -.float stat_multijump_speed; -.float stat_multijump_add; -.float stat_multijump_maxspeed; -.float stat_multijump_dodging; - -void multijump_UpdateStats() -{SELFPARAM(); - self.stat_multijump = PHYS_MULTIJUMP; - self.stat_multijump_speed = PHYS_MULTIJUMP_SPEED; - self.stat_multijump_add = PHYS_MULTIJUMP_ADD; - self.stat_multijump_maxspeed = PHYS_MULTIJUMP_MAXSPEED; - self.stat_multijump_dodging = PHYS_MULTIJUMP_DODGING; -} - -void multijump_AddStats() -{ - addstat(STAT_MULTIJUMP, AS_INT, stat_multijump); - addstat(STAT_MULTIJUMP_SPEED, AS_FLOAT, stat_multijump_speed); - addstat(STAT_MULTIJUMP_ADD, AS_INT, stat_multijump_add); - addstat(STAT_MULTIJUMP_MAXSPEED, AS_FLOAT, stat_multijump_maxspeed); - addstat(STAT_MULTIJUMP_DODGING, AS_INT, stat_multijump_dodging); -} - -#endif - -void PM_multijump() -{SELFPARAM(); - if(!PHYS_MULTIJUMP) { return; } - - if(IS_ONGROUND(self)) - { - self.multijump_count = 0; - } -} - -bool PM_multijump_checkjump() -{SELFPARAM(); - if(!PHYS_MULTIJUMP) { return false; } - -#ifdef SVQC - bool client_multijump = self.cvar_cl_multijump; -#elif defined(CSQC) - bool client_multijump = cvar("cl_multijump"); - - if(cvar("cl_multijump") > 1) - return false; // nope -#endif - - if (!IS_JUMP_HELD(self) && !IS_ONGROUND(self) && client_multijump) // jump button pressed this frame and we are in midair - self.multijump_ready = true; // this is necessary to check that we released the jump button and pressed it again - else - self.multijump_ready = false; - - int phys_multijump = PHYS_MULTIJUMP; - -#ifdef CSQC - phys_multijump = (PHYS_MULTIJUMP) ? -1 : 0; -#endif - - if(!player_multijump && self.multijump_ready && (self.multijump_count < phys_multijump || phys_multijump == -1) && self.velocity_z > PHYS_MULTIJUMP_SPEED && (!PHYS_MULTIJUMP_MAXSPEED || vlen(self.velocity) <= PHYS_MULTIJUMP_MAXSPEED)) - { - if (PHYS_MULTIJUMP) - { - if (!PHYS_MULTIJUMP_ADD) // in this case we make the z velocity == jumpvelocity - { - if (self.velocity_z < PHYS_JUMPVELOCITY) - { - player_multijump = true; - self.velocity_z = 0; - } - } - else - player_multijump = true; - - if(player_multijump) - { - if(PHYS_MULTIJUMP_DODGING) - if(self.movement_x != 0 || self.movement_y != 0) // don't remove all speed if player isnt pressing any movement keys - { - float curspeed; - vector wishvel, wishdir; - -/*#ifdef SVQC - curspeed = max( - vlen(vec2(self.velocity)), // current xy speed - vlen(vec2(antilag_takebackavgvelocity(self, max(self.lastteleporttime + sys_frametime, time - 0.25), time))) // average xy topspeed over the last 0.25 secs - ); -#elif defined(CSQC)*/ - curspeed = vlen(vec2(self.velocity)); -//#endif - - makevectors(self.v_angle_y * '0 1 0'); - wishvel = v_forward * self.movement_x + v_right * self.movement_y; - wishdir = normalize(wishvel); - - self.velocity_x = wishdir_x * curspeed; // allow "dodging" at a multijump - self.velocity_y = wishdir_y * curspeed; - // keep velocity_z unchanged! - } - if (PHYS_MULTIJUMP > 0) - { - self.multijump_count += 1; - } - } - } - self.multijump_ready = false; // require releasing and pressing the jump button again for the next jump - } - - return false; -} - -#ifdef SVQC -REGISTER_MUTATOR(multijump, cvar("g_multijump")) -{ - MUTATOR_ONADD - { - multijump_AddStats(); - } - return false; -} - -MUTATOR_HOOKFUNCTION(multijump, PlayerPhysics) -{ - multijump_UpdateStats(); - PM_multijump(); - - return false; -} - -MUTATOR_HOOKFUNCTION(multijump, PlayerJump) -{ - return PM_multijump_checkjump(); -} - -MUTATOR_HOOKFUNCTION(multijump, GetCvars) -{ - GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_multijump, "cl_multijump"); - return false; -} - -MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":multijump"); - return false; -} - -MUTATOR_HOOKFUNCTION(multijump, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Multi jump"); - return false; -} - -#endif diff --git a/qcsrc/server/mutators/mutator_nades.qc b/qcsrc/server/mutators/mutator_nades.qc deleted file mode 100644 index 45cc4f9c4..000000000 --- a/qcsrc/server/mutators/mutator_nades.qc +++ /dev/null @@ -1,1207 +0,0 @@ -#include "mutator_nades.qh" - -#include "mutator.qh" - -#include "gamemode_keyhunt.qh" -#include "gamemode_freezetag.qh" -#include "../../common/nades/all.qh" -#include "../../common/gamemodes/all.qh" -#include "../../common/monsters/spawn.qh" -#include "../../common/monsters/sv_monsters.qh" -#include "../g_subs.qh" - -REGISTER_MUTATOR(nades, cvar("g_nades")) -{ - MUTATOR_ONADD - { - addstat(STAT_NADE_TIMER, AS_FLOAT, nade_timer); - addstat(STAT_NADE_BONUS, AS_FLOAT, bonus_nades); - addstat(STAT_NADE_BONUS_TYPE, AS_INT, nade_type); - addstat(STAT_NADE_BONUS_SCORE, AS_FLOAT, bonus_nade_score); - addstat(STAT_HEALING_ORB, AS_FLOAT, stat_healing_orb); - addstat(STAT_HEALING_ORB_ALPHA, AS_FLOAT, stat_healing_orb_alpha); - } - - return false; -} - -.float nade_time_primed; - -.entity nade_spawnloc; - -void nade_timer_think() -{SELFPARAM(); - self.skin = 8 - (self.owner.wait - time) / (autocvar_g_nades_nade_lifetime / 10); - self.nextthink = time; - if(!self.owner || wasfreed(self.owner)) - remove(self); -} - -void nade_burn_spawn(entity _nade) -{ - CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[true], true); -} - -void nade_spawn(entity _nade) -{ - entity timer = spawn(); - setmodel(timer, MDL_NADE_TIMER); - setattachment(timer, _nade, ""); - timer.classname = "nade_timer"; - timer.colormap = _nade.colormap; - timer.glowmod = _nade.glowmod; - timer.think = nade_timer_think; - timer.nextthink = time; - timer.wait = _nade.wait; - timer.owner = _nade; - timer.skin = 10; - - _nade.effects |= EF_LOWPRECISION; - - CSQCProjectile(_nade, true, Nades[_nade.nade_type].m_projectile[false], true); -} - -void napalm_damage(float dist, float damage, float edgedamage, float burntime) -{SELFPARAM(); - entity e; - float d; - vector p; - - if ( damage < 0 ) - return; - - RandomSelection_Init(); - for(e = WarpZone_FindRadius(self.origin, dist, true); e; e = e.chain) - if(e.takedamage == DAMAGE_AIM) - if(self.realowner != e || autocvar_g_nades_napalm_selfdamage) - if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self)) - if(!e.frozen) - { - p = e.origin; - p.x += e.mins.x + random() * (e.maxs.x - e.mins.x); - p.y += e.mins.y + random() * (e.maxs.y - e.mins.y); - p.z += e.mins.z + random() * (e.maxs.z - e.mins.z); - d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p); - if(d < dist) - { - e.fireball_impactvec = p; - RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e)); - } - } - if(RandomSelection_chosen_ent) - { - d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec); - d = damage + (edgedamage - damage) * (d / dist); - Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE); - //trailparticles(self, particleeffectnum(EFFECT_FIREBALL_LASER), self.origin, RandomSelection_chosen_ent.fireball_impactvec); - Send_Effect(EFFECT_FIREBALL_LASER, self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1); - } -} - - -void napalm_ball_think() -{SELFPARAM(); - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - remove(self); - return; - } - - if(time > self.pushltime) - { - remove(self); - return; - } - - vector midpoint = ((self.absmin + self.absmax) * 0.5); - if(pointcontents(midpoint) == CONTENT_WATER) - { - self.velocity = self.velocity * 0.5; - - if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) - { self.velocity_z = 200; } - } - - self.angles = vectoangles(self.velocity); - - napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage, - autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime); - - self.nextthink = time + 0.1; -} - - -void nade_napalm_ball() -{SELFPARAM(); - entity proj; - vector kick; - - spamsound(self, CH_SHOTS, SND(FIREBALL_FIRE), VOL_BASE, ATTEN_NORM); - - proj = spawn (); - proj.owner = self.owner; - proj.realowner = self.realowner; - proj.team = self.owner.team; - proj.classname = "grenade"; - proj.bot_dodge = true; - proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage; - proj.movetype = MOVETYPE_BOUNCE; - proj.projectiledeathtype = DEATH_NADE_NAPALM.m_id; - PROJECTILE_MAKETRIGGER(proj); - setmodel(proj, MDL_Null); - proj.scale = 1;//0.5; - setsize(proj, '-4 -4 -4', '4 4 4'); - setorigin(proj, self.origin); - proj.think = napalm_ball_think; - proj.nextthink = time; - proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale; - proj.effects = EF_LOWPRECISION | EF_FLAME; - - kick.x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; - kick.y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread; - kick.z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread; - proj.velocity = kick; - - proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime; - - proj.angles = vectoangles(proj.velocity); - proj.flags = FL_PROJECTILE; - proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC; - - //CSQCProjectile(proj, true, PROJECTILE_NAPALM_FIRE, true); -} - - -void napalm_fountain_think() -{SELFPARAM(); - - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - remove(self); - return; - } - - if(time >= self.ltime) - { - remove(self); - return; - } - - vector midpoint = ((self.absmin + self.absmax) * 0.5); - if(pointcontents(midpoint) == CONTENT_WATER) - { - self.velocity = self.velocity * 0.5; - - if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER) - { self.velocity_z = 200; } - - UpdateCSQCProjectile(self); - } - - napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage, - autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime); - - self.nextthink = time + 0.1; - if(time >= self.nade_special_time) - { - self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay; - nade_napalm_ball(); - } -} - -void nade_napalm_boom() -{SELFPARAM(); - entity fountain; - int c; - for (c = 0; c < autocvar_g_nades_napalm_ball_count; c++) - nade_napalm_ball(); - - - fountain = spawn(); - fountain.owner = self.owner; - fountain.realowner = self.realowner; - fountain.origin = self.origin; - setorigin(fountain, fountain.origin); - fountain.think = napalm_fountain_think; - fountain.nextthink = time; - fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime; - fountain.pushltime = fountain.ltime; - fountain.team = self.team; - fountain.movetype = MOVETYPE_TOSS; - fountain.projectiledeathtype = DEATH_NADE_NAPALM.m_id; - fountain.bot_dodge = true; - fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage; - fountain.nade_special_time = time; - setsize(fountain, '-16 -16 -16', '16 16 16'); - CSQCProjectile(fountain, true, PROJECTILE_NAPALM_FOUNTAIN, true); -} - -void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time) -{ - frost_target.frozen_by = freezefield.realowner; - Send_Effect(EFFECT_ELECTRO_IMPACT, frost_target.origin, '0 0 0', 1); - Freeze(frost_target, 1/freeze_time, 3, false); - - Drop_Special_Items(frost_target); -} - -void nade_ice_think() -{SELFPARAM(); - - if(round_handler_IsActive()) - if(!round_handler_IsRoundStarted()) - { - remove(self); - return; - } - - if(time >= self.ltime) - { - if ( autocvar_g_nades_ice_explode ) - { - entity expef = EFFECT_NADE_EXPLODE(self.realowner.team); - Send_Effect(expef, self.origin + '0 0 1', '0 0 0', 1); - sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - - RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); - Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); - } - remove(self); - return; - } - - - self.nextthink = time+0.1; - - // gaussian - float randomr; - randomr = random(); - randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius; - float randomw; - randomw = random()*M_PI*2; - vector randomp; - randomp.x = randomr*cos(randomw); - randomp.y = randomr*sin(randomw); - randomp.z = 1; - Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, self.origin + randomp, '0 0 0', 1); - - if(time >= self.nade_special_time) - { - self.nade_special_time = time+0.7; - - Send_Effect(EFFECT_ELECTRO_IMPACT, self.origin, '0 0 0', 1); - Send_Effect(EFFECT_ICEFIELD, self.origin, '0 0 0', 1); - } - - - float current_freeze_time = self.ltime - time - 0.1; - - entity e; - for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain) - if(e != self) - if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner)) - if(e.takedamage && e.deadflag == DEAD_NO) - if(e.health > 0) - if(!e.revival_time || ((time - e.revival_time) >= 1.5)) - if(!e.frozen) - if(current_freeze_time > 0) - nade_ice_freeze(self, e, current_freeze_time); -} - -void nade_ice_boom() -{SELFPARAM(); - entity fountain; - fountain = spawn(); - fountain.owner = self.owner; - fountain.realowner = self.realowner; - fountain.origin = self.origin; - setorigin(fountain, fountain.origin); - fountain.think = nade_ice_think; - fountain.nextthink = time; - fountain.ltime = time + autocvar_g_nades_ice_freeze_time; - fountain.pushltime = fountain.wait = fountain.ltime; - fountain.team = self.team; - fountain.movetype = MOVETYPE_TOSS; - fountain.projectiledeathtype = DEATH_NADE_ICE.m_id; - fountain.bot_dodge = false; - setsize(fountain, '-16 -16 -16', '16 16 16'); - fountain.nade_special_time = time+0.3; - fountain.angles = self.angles; - - if ( autocvar_g_nades_ice_explode ) - { - setmodel(fountain, MDL_PROJECTILE_GRENADE); - entity timer = spawn(); - setmodel(timer, MDL_NADE_TIMER); - setattachment(timer, fountain, ""); - timer.classname = "nade_timer"; - timer.colormap = self.colormap; - timer.glowmod = self.glowmod; - timer.think = nade_timer_think; - timer.nextthink = time; - timer.wait = fountain.ltime; - timer.owner = fountain; - timer.skin = 10; - } - else - setmodel(fountain, MDL_Null); -} - -void nade_translocate_boom() -{SELFPARAM(); - if(self.realowner.vehicle) - return; - - vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins.z - 24); - tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner); - locout = trace_endpos; - - makevectors(self.realowner.angles); - - MUTATOR_CALLHOOK(PortalTeleport, self.realowner); - - TeleportPlayer(self, self.realowner, locout, self.realowner.angles, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER); -} - -void nade_spawn_boom() -{SELFPARAM(); - entity spawnloc = spawn(); - setorigin(spawnloc, self.origin); - setsize(spawnloc, self.realowner.mins, self.realowner.maxs); - spawnloc.movetype = MOVETYPE_NONE; - spawnloc.solid = SOLID_NOT; - spawnloc.drawonlytoclient = self.realowner; - spawnloc.effects = EF_STARDUST; - spawnloc.cnt = autocvar_g_nades_spawn_count; - - if(self.realowner.nade_spawnloc) - { - remove(self.realowner.nade_spawnloc); - self.realowner.nade_spawnloc = world; - } - - self.realowner.nade_spawnloc = spawnloc; -} - -void nade_heal_think() -{SELFPARAM(); - if(time >= self.ltime) - { - remove(self); - return; - } - - self.nextthink = time; - - if(time >= self.nade_special_time) - { - self.nade_special_time = time+0.25; - self.nade_show_particles = 1; - } - else - self.nade_show_particles = 0; -} - -void nade_heal_touch() -{SELFPARAM(); - float maxhealth; - float health_factor; - if(IS_PLAYER(other) || IS_MONSTER(other)) - if(other.deadflag == DEAD_NO) - if(!other.frozen) - { - health_factor = autocvar_g_nades_heal_rate*frametime/2; - if ( other != self.realowner ) - { - if ( SAME_TEAM(other,self) ) - health_factor *= autocvar_g_nades_heal_friend; - else - health_factor *= autocvar_g_nades_heal_foe; - } - if ( health_factor > 0 ) - { - maxhealth = (IS_MONSTER(other)) ? other.max_health : g_pickup_healthmega_max; - if ( other.health < maxhealth ) - { - if ( self.nade_show_particles ) - Send_Effect(EFFECT_HEALING, other.origin, '0 0 0', 1); - other.health = min(other.health+health_factor, maxhealth); - } - other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot); - } - else if ( health_factor < 0 ) - { - Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL.m_id,other.origin,'0 0 0'); - } - - } - - if ( IS_REAL_CLIENT(other) || IS_VEHICLE(other) ) - { - entity show_red = (IS_VEHICLE(other)) ? other.owner : other; - show_red.stat_healing_orb = time+0.1; - show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime; - } -} - -void nade_heal_boom() -{SELFPARAM(); - entity healer; - healer = spawn(); - healer.owner = self.owner; - healer.realowner = self.realowner; - setorigin(healer, self.origin); - healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar - healer.ltime = time + healer.healer_lifetime; - healer.team = self.realowner.team; - healer.bot_dodge = false; - healer.solid = SOLID_TRIGGER; - healer.touch = nade_heal_touch; - - setmodel(healer, MDL_NADE_HEAL); - healer.healer_radius = autocvar_g_nades_nade_radius; - vector size = '1 1 1' * healer.healer_radius / 2; - setsize(healer,-size,size); - - Net_LinkEntity(healer, true, 0, healer_send); - - healer.think = nade_heal_think; - healer.nextthink = time; - healer.SendFlags |= 1; -} - -void nade_monster_boom() -{SELFPARAM(); - entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, false, false, 1); - - if(autocvar_g_nades_pokenade_monster_lifetime > 0) - e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime; - e.monster_skill = MONSTER_SKILL_INSANE; -} - -void nade_boom() -{SELFPARAM(); - entity expef = NULL; - bool nade_blast = true; - - switch ( Nades[self.nade_type] ) - { - case NADE_TYPE_NAPALM: - nade_blast = autocvar_g_nades_napalm_blast; - expef = EFFECT_EXPLOSION_MEDIUM; - break; - case NADE_TYPE_ICE: - nade_blast = false; - expef = EFFECT_ELECTRO_COMBO; // hookbomb_explode electro_combo bigplasma_impact - break; - case NADE_TYPE_TRANSLOCATE: - nade_blast = false; - break; - case NADE_TYPE_MONSTER: - case NADE_TYPE_SPAWN: - nade_blast = false; - switch(self.realowner.team) - { - case NUM_TEAM_1: expef = EFFECT_SPAWN_RED; break; - case NUM_TEAM_2: expef = EFFECT_SPAWN_BLUE; break; - case NUM_TEAM_3: expef = EFFECT_SPAWN_YELLOW; break; - case NUM_TEAM_4: expef = EFFECT_SPAWN_PINK; break; - default: expef = EFFECT_SPAWN_NEUTRAL; break; - } - break; - case NADE_TYPE_HEAL: - nade_blast = false; - expef = EFFECT_SPAWN_RED; - break; - - default: - case NADE_TYPE_NORMAL: - expef = EFFECT_NADE_EXPLODE(self.realowner.team); - break; - } - - if(expef) - Send_Effect(expef, findbetterlocation(self.origin, 8), '0 0 0', 1); - - sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); - sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM); - - self.event_damage = func_null; // prevent somehow calling damage in the next call - - if(nade_blast) - { - RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, - autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy); - Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self); - } - - if(self.takedamage) - switch ( Nades[self.nade_type] ) - { - case NADE_TYPE_NAPALM: nade_napalm_boom(); break; - case NADE_TYPE_ICE: nade_ice_boom(); break; - case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break; - case NADE_TYPE_SPAWN: nade_spawn_boom(); break; - case NADE_TYPE_HEAL: nade_heal_boom(); break; - case NADE_TYPE_MONSTER: nade_monster_boom(); break; - } - - entity head; - for(head = world; (head = find(head, classname, "grapplinghook")); ) - if(head.aiment == self) - RemoveGrapplingHook(head.realowner); - - remove(self); -} - -void nade_touch() -{SELFPARAM(); - /*float is_weapclip = 0; - if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NODRAW) - if (!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NONSOLID)) - if (!(trace_dphitcontents & DPCONTENTS_OPAQUE)) - is_weapclip = 1;*/ - if(ITEM_TOUCH_NEEDKILL()) // || is_weapclip) - { - entity head; - for(head = world; (head = find(head, classname, "grapplinghook")); ) - if(head.aiment == self) - RemoveGrapplingHook(head.realowner); - remove(self); - return; - } - - PROJECTILE_TOUCH; - - //setsize(self, '-2 -2 -2', '2 2 2'); - //UpdateCSQCProjectile(self); - if(self.health == self.max_health) - { - spamsound(self, CH_SHOTS, SND(GRENADE_BOUNCE_RANDOM()), VOL_BASE, ATTEN_NORM); - return; - } - - self.enemy = other; - nade_boom(); -} - -void nade_beep() -{SELFPARAM(); - sound(self, CH_SHOTS_SINGLE, SND_NADE_BEEP, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); - self.think = nade_boom; - self.nextthink = max(self.wait, time); -} - -void nade_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{SELFPARAM(); - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - self.takedamage = DAMAGE_NO; - nade_boom(); - return; - } - - if(self.nade_type == NADE_TYPE_TRANSLOCATE.m_id || self.nade_type == NADE_TYPE_SPAWN.m_id) - return; - - if(DEATH_ISWEAPON(deathtype, WEP_BLASTER)) - { - force *= 1.5; - damage = 0; - } - - if(DEATH_ISWEAPON(deathtype, WEP_VAPORIZER) && (deathtype & HITTYPE_SECONDARY)) - { - force *= 0.5; // too much - frag_damage = 0; - } - - if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER)) - { - force *= 6; - damage = self.max_health * 0.55; - } - - if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN) || DEATH_ISWEAPON(deathtype, WEP_HMG)) - damage = self.max_health * 0.1; - - if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) // WEAPONTODO - if(deathtype & HITTYPE_SECONDARY) - { - damage = self.max_health * 0.1; - force *= 10; - } - else - damage = self.max_health * 1.15; - - self.velocity += force; - UpdateCSQCProjectile(self); - - if(damage <= 0 || ((self.flags & FL_ONGROUND) && IS_PLAYER(attacker))) - return; - - if(self.health == self.max_health) - { - sound(self, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, 0.5 *(ATTEN_LARGE + ATTEN_MAX)); - self.nextthink = max(time + autocvar_g_nades_nade_lifetime, time); - self.think = nade_beep; - } - - self.health -= damage; - - if ( self.nade_type != NADE_TYPE_HEAL.m_id || IS_PLAYER(attacker) ) - self.realowner = attacker; - - if(self.health <= 0) - W_PrepareExplosionByDamage(attacker, nade_boom); - else - nade_burn_spawn(self); -} - -void toss_nade(entity e, vector _velocity, float _time) -{SELFPARAM(); - if(e.nade == world) - return; - - entity _nade = e.nade; - e.nade = world; - - remove(e.fake_nade); - e.fake_nade = world; - - makevectors(e.v_angle); - - W_SetupShot(e, false, false, "", CH_WEAPON_A, 0); - - Kill_Notification(NOTIF_ONE_ONLY, e, MSG_CENTER_CPID, CPID_NADES); - - vector offset = (v_forward * autocvar_g_nades_throw_offset.x) - + (v_right * autocvar_g_nades_throw_offset.y) - + (v_up * autocvar_g_nades_throw_offset.z); - if(autocvar_g_nades_throw_offset == '0 0 0') - offset = '0 0 0'; - - setorigin(_nade, w_shotorg + offset + (v_right * 25) * -1); - //setmodel(_nade, MDL_PROJECTILE_NADE); - //setattachment(_nade, world, ""); - PROJECTILE_MAKETRIGGER(_nade); - setsize(_nade, '-16 -16 -16', '16 16 16'); - _nade.movetype = MOVETYPE_BOUNCE; - - tracebox(_nade.origin, _nade.mins, _nade.maxs, _nade.origin, false, _nade); - if (trace_startsolid) - setorigin(_nade, e.origin); - - if(self.v_angle.x >= 70 && self.v_angle.x <= 110 && self.BUTTON_CROUCH) - _nade.velocity = '0 0 100'; - else if(autocvar_g_nades_nade_newton_style == 1) - _nade.velocity = e.velocity + _velocity; - else if(autocvar_g_nades_nade_newton_style == 2) - _nade.velocity = _velocity; - else - _nade.velocity = W_CalculateProjectileVelocity(e.velocity, _velocity, true); - - _nade.touch = nade_touch; - _nade.health = autocvar_g_nades_nade_health; - _nade.max_health = _nade.health; - _nade.takedamage = DAMAGE_AIM; - _nade.event_damage = nade_damage; - _nade.customizeentityforclient = func_null; - _nade.exteriormodeltoclient = world; - _nade.traileffectnum = 0; - _nade.teleportable = true; - _nade.pushable = true; - _nade.gravity = 1; - _nade.missile_flags = MIF_SPLASH | MIF_ARC; - _nade.damagedbycontents = true; - _nade.angles = vectoangles(_nade.velocity); - _nade.flags = FL_PROJECTILE; - _nade.projectiledeathtype = DEATH_NADE.m_id; - _nade.toss_time = time; - _nade.solid = SOLID_CORPSE; //((_nade.nade_type == NADE_TYPE_TRANSLOCATE) ? SOLID_CORPSE : SOLID_BBOX); - - if(_nade.nade_type == NADE_TYPE_TRANSLOCATE.m_id || _nade.nade_type == NADE_TYPE_SPAWN.m_id) - _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP; - else - _nade.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY; - - nade_spawn(_nade); - - if(_time) - { - _nade.think = nade_boom; - _nade.nextthink = _time; - } - - e.nade_refire = time + autocvar_g_nades_nade_refire; - e.nade_timer = 0; -} - -void nades_GiveBonus(entity player, float score) -{ - if (autocvar_g_nades) - if (autocvar_g_nades_bonus) - if (IS_REAL_CLIENT(player)) - if (IS_PLAYER(player) && player.bonus_nades < autocvar_g_nades_bonus_max) - if (player.frozen == 0) - if (player.deadflag == DEAD_NO) - { - if ( player.bonus_nade_score < 1 ) - player.bonus_nade_score += score/autocvar_g_nades_bonus_score_max; - - if ( player.bonus_nade_score >= 1 ) - { - Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_NADE_BONUS); - play2(player, SND(KH_ALARM)); - player.bonus_nades++; - player.bonus_nade_score -= 1; - } - } -} - -void nades_RemoveBonus(entity player) -{ - player.bonus_nades = player.bonus_nade_score = 0; -} - -float nade_customize() -{SELFPARAM(); - //if(IS_SPEC(other)) { return false; } - if(other == self.realowner || (IS_SPEC(other) && other.enemy == self.realowner)) - { - // somewhat hide the model, but keep the glow - //self.effects = 0; - if(self.traileffectnum) - self.traileffectnum = 0; - self.alpha = -1; - } - else - { - //self.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - if(!self.traileffectnum) - self.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades[self.nade_type].m_projectile[false], self.team).eent_eff_name); - self.alpha = 1; - } - - return true; -} - -void nade_prime() -{SELFPARAM(); - if(autocvar_g_nades_bonus_only) - if(!self.bonus_nades) - return; // only allow bonus nades - - if(self.nade) - remove(self.nade); - - if(self.fake_nade) - remove(self.fake_nade); - - entity n = spawn(), fn = spawn(); - - n.classname = "nade"; - fn.classname = "fake_nade"; - - if(self.items & ITEM_Strength.m_itemid && autocvar_g_nades_bonus_onstrength) - n.nade_type = self.nade_type; - else if (self.bonus_nades >= 1) - { - n.nade_type = self.nade_type; - n.pokenade_type = self.pokenade_type; - self.bonus_nades -= 1; - } - else - { - n.nade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_nade_type : autocvar_g_nades_nade_type); - n.pokenade_type = ((autocvar_g_nades_client_select) ? self.cvar_cl_pokenade_type : autocvar_g_nades_pokenade_monster_type); - } - - n.nade_type = bound(1, n.nade_type, Nades_COUNT); - - setmodel(n, MDL_PROJECTILE_NADE); - //setattachment(n, self, "bip01 l hand"); - n.exteriormodeltoclient = self; - n.customizeentityforclient = nade_customize; - n.traileffectnum = _particleeffectnum(Nade_TrailEffect(Nades[n.nade_type].m_projectile[false], self.team).eent_eff_name); - n.colormod = Nades[n.nade_type].m_color; - n.realowner = self; - n.colormap = self.colormap; - n.glowmod = self.glowmod; - n.wait = time + autocvar_g_nades_nade_lifetime; - n.nade_time_primed = time; - n.think = nade_beep; - n.nextthink = max(n.wait - 3, time); - n.projectiledeathtype = DEATH_NADE.m_id; - - setmodel(fn, MDL_NADE_VIEW); - setattachment(fn, self.weaponentity, ""); - fn.realowner = fn.owner = self; - fn.colormod = Nades[n.nade_type].m_color; - fn.colormap = self.colormap; - fn.glowmod = self.glowmod; - fn.think = SUB_Remove; - fn.nextthink = n.wait; - - self.nade = n; - self.fake_nade = fn; -} - -float CanThrowNade() -{SELFPARAM(); - if(self.vehicle) - return false; - - if(gameover) - return false; - - if(self.deadflag != DEAD_NO) - return false; - - if (!autocvar_g_nades) - return false; // allow turning them off mid match - - if(forbidWeaponUse(self)) - return false; - - if (!IS_PLAYER(self)) - return false; - - return true; -} - -.bool nade_altbutton; - -void nades_CheckThrow() -{SELFPARAM(); - if(!CanThrowNade()) - return; - - entity held_nade = self.nade; - if (!held_nade) - { - self.nade_altbutton = true; - if(time > self.nade_refire) - { - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_NADE_THROW); - nade_prime(); - self.nade_refire = time + autocvar_g_nades_nade_refire; - } - } - else - { - self.nade_altbutton = false; - if (time >= held_nade.nade_time_primed + 1) { - makevectors(self.v_angle); - float _force = time - held_nade.nade_time_primed; - _force /= autocvar_g_nades_nade_lifetime; - _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); - toss_nade(self, (v_forward * 0.75 + v_up * 0.2 + v_right * 0.05) * _force, 0); - } - } -} - -void nades_Clear(entity player) -{ - if(player.nade) - remove(player.nade); - if(player.fake_nade) - remove(player.fake_nade); - - player.nade = player.fake_nade = world; - player.nade_timer = 0; -} - -MUTATOR_HOOKFUNCTION(nades, VehicleEnter) -{ - if(vh_player.nade) - toss_nade(vh_player, '0 0 100', max(vh_player.nade.wait, time + 0.05)); - - return false; -} - -CLASS(NadeOffhand, OffhandWeapon) - METHOD(NadeOffhand, offhand_think, void(NadeOffhand this, entity player, bool key_pressed)) - { - entity held_nade = player.nade; - if (held_nade) - { - player.nade_timer = bound(0, (time - held_nade.nade_time_primed) / autocvar_g_nades_nade_lifetime, 1); - // LOG_TRACEF("%d %d\n", player.nade_timer, time - held_nade.nade_time_primed); - makevectors(player.angles); - held_nade.velocity = player.velocity; - setorigin(held_nade, player.origin + player.view_ofs + v_forward * 8 + v_right * -8 + v_up * 0); - held_nade.angles_y = player.angles.y; - - if (time + 0.1 >= held_nade.wait) - toss_nade(player, '0 0 0', time + 0.05); - } - - if (!CanThrowNade()) return; - if (!(time > player.nade_refire)) return; - if (key_pressed) { - if (!held_nade) { - nade_prime(); - held_nade = player.nade; - } - } else if (time >= held_nade.nade_time_primed + 1) { - if (held_nade) { - makevectors(player.v_angle); - float _force = time - held_nade.nade_time_primed; - _force /= autocvar_g_nades_nade_lifetime; - _force = autocvar_g_nades_nade_minforce + (_force * (autocvar_g_nades_nade_maxforce - autocvar_g_nades_nade_minforce)); - toss_nade(player, (v_forward * 0.7 + v_up * 0.2 + v_right * 0.1) * _force, 0); - } - } - } -ENDCLASS(NadeOffhand) -NadeOffhand OFFHAND_NADE; STATIC_INIT(OFFHAND_NADE) { OFFHAND_NADE = NEW(NadeOffhand); } - -MUTATOR_HOOKFUNCTION(nades, ForbidThrowCurrentWeapon, CBC_ORDER_LAST) -{ - if (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)) || autocvar_g_nades_override_dropweapon) { - nades_CheckThrow(); - return true; - } - return false; -} - -MUTATOR_HOOKFUNCTION(nades, PlayerPreThink) -{SELFPARAM(); - if (!IS_PLAYER(self)) { return false; } - - if (self.nade && (self.offhand != OFFHAND_NADE || (self.weapons & WEPSET(HOOK)))) OFFHAND_NADE.offhand_think(OFFHAND_NADE, self, self.nade_altbutton); - - if(IS_PLAYER(self)) - { - if ( autocvar_g_nades_bonus && autocvar_g_nades ) - { - entity key; - float key_count = 0; - FOR_EACH_KH_KEY(key) if(key.owner == self) { ++key_count; } - - float time_score; - if(self.flagcarried || self.ballcarried) // this player is important - time_score = autocvar_g_nades_bonus_score_time_flagcarrier; - else - time_score = autocvar_g_nades_bonus_score_time; - - if(key_count) - time_score = autocvar_g_nades_bonus_score_time_flagcarrier * key_count; // multiply by the number of keys the player is holding - - if(autocvar_g_nades_bonus_client_select) - { - self.nade_type = self.cvar_cl_nade_type; - self.pokenade_type = self.cvar_cl_pokenade_type; - } - else - { - self.nade_type = autocvar_g_nades_bonus_type; - self.pokenade_type = autocvar_g_nades_pokenade_monster_type; - } - - self.nade_type = bound(1, self.nade_type, Nades_COUNT); - - if(self.bonus_nade_score >= 0 && autocvar_g_nades_bonus_score_max) - nades_GiveBonus(self, time_score / autocvar_g_nades_bonus_score_max); - } - else - { - self.bonus_nades = self.bonus_nade_score = 0; - } - } - - float n = 0; - entity o = world; - if(self.freezetag_frozen_timeout > 0 && time >= self.freezetag_frozen_timeout) - n = -1; - else - { - vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size; - n = 0; - FOR_EACH_PLAYER(other) if(self != other) - { - if(other.deadflag == DEAD_NO) - if(other.frozen == 0) - if(SAME_TEAM(other, self)) - if(boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax)) - { - if(!o) - o = other; - if(self.frozen == 1) - other.reviving = true; - ++n; - } - } - } - - if(n && self.frozen == 3) // OK, there is at least one teammate reviving us - { - self.revive_progress = bound(0, self.revive_progress + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1); - self.health = max(1, self.revive_progress * start_health); - - if(self.revive_progress >= 1) - { - Unfreeze(self); - - Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname); - Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, self.netname); - } - - FOR_EACH_PLAYER(other) if(other.reviving) - { - other.revive_progress = self.revive_progress; - other.reviving = false; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(nades, PlayerSpawn) -{SELFPARAM(); - if(autocvar_g_nades_spawn) - self.nade_refire = time + autocvar_g_spawnshieldtime; - else - self.nade_refire = time + autocvar_g_nades_nade_refire; - - if(autocvar_g_nades_bonus_client_select) - self.nade_type = self.cvar_cl_nade_type; - - self.nade_timer = 0; - - if (!self.offhand) self.offhand = OFFHAND_NADE; - - if(self.nade_spawnloc) - { - setorigin(self, self.nade_spawnloc.origin); - self.nade_spawnloc.cnt -= 1; - - if(self.nade_spawnloc.cnt <= 0) - { - remove(self.nade_spawnloc); - self.nade_spawnloc = world; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(nades, PlayerDies, CBC_ORDER_LAST) -{ - if(frag_target.nade) - if(!frag_target.frozen || !autocvar_g_freezetag_revive_nade) - toss_nade(frag_target, '0 0 100', max(frag_target.nade.wait, time + 0.05)); - - float killcount_bonus = ((frag_attacker.killcount >= 1) ? bound(0, autocvar_g_nades_bonus_score_minor * frag_attacker.killcount, autocvar_g_nades_bonus_score_medium) : autocvar_g_nades_bonus_score_minor); - - if(IS_PLAYER(frag_attacker)) - { - if (SAME_TEAM(frag_attacker, frag_target) || frag_attacker == frag_target) - nades_RemoveBonus(frag_attacker); - else if(frag_target.flagcarried) - nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_medium); - else if(autocvar_g_nades_bonus_score_spree && frag_attacker.killcount > 1) - { - #define SPREE_ITEM(counta,countb,center,normal,gentle) \ - case counta: { nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_spree); break; } - switch(frag_attacker.killcount) - { - KILL_SPREE_LIST - default: nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); break; - } - #undef SPREE_ITEM - } - else - nades_GiveBonus(frag_attacker, killcount_bonus); - } - - nades_RemoveBonus(frag_target); - - return false; -} - -MUTATOR_HOOKFUNCTION(nades, PlayerDamage_Calculate) -{ - if(frag_target.frozen) - if(autocvar_g_freezetag_revive_nade) - if(frag_attacker == frag_target) - if(frag_deathtype == DEATH_NADE.m_id) - if(time - frag_inflictor.toss_time <= 0.1) - { - Unfreeze(frag_target); - frag_target.health = autocvar_g_freezetag_revive_nade_health; - Send_Effect(EFFECT_ICEORGLASS, frag_target.origin, '0 0 0', 3); - frag_damage = 0; - frag_force = '0 0 0'; - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_FREEZETAG_REVIVED_NADE, frag_target.netname); - Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_REVIVE_SELF); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(nades, MonsterDies) -{SELFPARAM(); - if(IS_PLAYER(frag_attacker)) - if(DIFF_TEAM(frag_attacker, self)) - if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) - nades_GiveBonus(frag_attacker, autocvar_g_nades_bonus_score_minor); - - return false; -} - -MUTATOR_HOOKFUNCTION(nades, DropSpecialItems) -{ - if(frag_target.nade) - toss_nade(frag_target, '0 0 0', time + 0.05); - - return false; -} - -bool nades_RemovePlayer() -{SELFPARAM(); - nades_Clear(self); - nades_RemoveBonus(self); - return false; -} - -MUTATOR_HOOKFUNCTION(nades, MakePlayerObserver) { nades_RemovePlayer(); } -MUTATOR_HOOKFUNCTION(nades, ClientDisconnect) { nades_RemovePlayer(); } -MUTATOR_HOOKFUNCTION(nades, reset_map_global) { nades_RemovePlayer(); } - -MUTATOR_HOOKFUNCTION(nades, SpectateCopy) -{SELFPARAM(); - self.nade_timer = other.nade_timer; - self.nade_type = other.nade_type; - self.pokenade_type = other.pokenade_type; - self.bonus_nades = other.bonus_nades; - self.bonus_nade_score = other.bonus_nade_score; - self.stat_healing_orb = other.stat_healing_orb; - self.stat_healing_orb_alpha = other.stat_healing_orb_alpha; - return false; -} - -MUTATOR_HOOKFUNCTION(nades, GetCvars) -{ - GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_nade_type, "cl_nade_type"); - GetCvars_handleString(get_cvars_s, get_cvars_f, cvar_cl_pokenade_type, "cl_pokenade_type"); - - return false; -} - -MUTATOR_HOOKFUNCTION(nades, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":Nades"); - return false; -} - -MUTATOR_HOOKFUNCTION(nades, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Nades"); - return false; -} diff --git a/qcsrc/server/mutators/mutator_nades.qh b/qcsrc/server/mutators/mutator_nades.qh deleted file mode 100644 index 8f571b828..000000000 --- a/qcsrc/server/mutators/mutator_nades.qh +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef MUTATOR_NADES_H -#define MUTATOR_NADES_H - -.entity nade; -.entity fake_nade; -.float nade_timer; -.float nade_refire; -.float bonus_nades; -.float nade_special_time; -.float bonus_nade_score; -.float nade_type; -.string pokenade_type; -.entity nade_damage_target; -.float cvar_cl_nade_type; -.string cvar_cl_pokenade_type; -.float toss_time; -.float stat_healing_orb; -.float stat_healing_orb_alpha; -.float nade_show_particles; - -// Remove nades that are being thrown -void(entity player) nades_Clear; - -// Give a bonus grenade to a player -void(entity player, float score) nades_GiveBonus; -// Remove all bonus nades from a player -void(entity player) nades_RemoveBonus; -#endif diff --git a/qcsrc/server/mutators/mutator_new_toys.qc b/qcsrc/server/mutators/mutator_new_toys.qc deleted file mode 100644 index d7fc5257e..000000000 --- a/qcsrc/server/mutators/mutator_new_toys.qc +++ /dev/null @@ -1,228 +0,0 @@ - -#include "mutator.qh" - -/* - -CORE laser vortex lg rl cry gl elec hagar fireb hook - vaporizer porto - tuba - -NEW rifle hlac minel seeker -IDEAS OPEN flak OPEN FUN FUN FUN FUN - - - -How this mutator works: - ======================= - -When a gun tries to spawn, this mutator is called. It will provide alternate -weaponreplace lists. - -Entity: - -{ -"classname" "weapon_vortex" -"new_toys" "rifle" -} --> This will spawn as Rifle in this mutator ONLY, and as Vortex otherwise. - -{ -"classname" "weapon_vortext" -"new_toys" "vortex rifle" -} --> This will spawn as either Vortex or Rifle in this mutator ONLY, and as Vortex otherwise. - -{ -"classname" "weapon_vortex" -"new_toys" "vortex" -} --> This is always a Vortex. - -If the map specifies no "new_toys" argument - -There will be two default replacements selectable: "replace all" and "replace random". -In "replace all" mode, e.g. Vortex will have the default replacement "rifle". -In "replace random" mode, Vortex will have the default replacement "vortex rifle". - -This mutator's replacements run BEFORE regular weaponreplace! - -The New Toys guns do NOT get a spawn function, so they can only ever be spawned -when this mutator is active. - -Likewise, warmup, give all, give ALL and impulse 99 will not give them unless -this mutator is active. - -Outside this mutator, they still can be spawned by: -- setting their start weapon cvar to 1 -- give weaponname -- weaponreplace -- weaponarena (but all and most weapons arena again won't include them) - -This mutator performs the default replacements on the DEFAULTS of the -start weapon selection. - -These weapons appear in the menu's priority list, BUT get a suffix -"(Mutator weapon)". - -Picking up a "new toys" weapon will not play standard weapon pickup sound, but -roflsound "New toys, new toys!" sound. - -*/ - -bool nt_IsNewToy(int w); - -REGISTER_MUTATOR(nt, cvar("g_new_toys") && !cvar("g_instagib") && !cvar("g_overkill")) -{ - MUTATOR_ONADD - { - if(time > 1) // game loads at time 1 - error("This cannot be added at runtime\n"); - - // mark the guns as ok to use by e.g. impulse 99 - for(int i = WEP_FIRST; i <= WEP_LAST; ++i) - if(nt_IsNewToy(i)) - get_weaponinfo(i).spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - for(int i = WEP_FIRST; i <= WEP_LAST; ++i) - if(nt_IsNewToy(i)) - get_weaponinfo(i).spawnflags |= WEP_FLAG_MUTATORBLOCKED; - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This cannot be removed at runtime\n"); - return -1; - } - - return 0; -} - -.string new_toys; - -float autocvar_g_new_toys_autoreplace; -bool autocvar_g_new_toys_use_pickupsound = true; -const float NT_AUTOREPLACE_NEVER = 0; -const float NT_AUTOREPLACE_ALWAYS = 1; -const float NT_AUTOREPLACE_RANDOM = 2; - -MUTATOR_HOOKFUNCTION(nt, SetModname) -{ - modname = "NewToys"; - return 0; -} - -bool nt_IsNewToy(int w) -{ - switch(w) - { - case WEP_SEEKER.m_id: - case WEP_MINE_LAYER.m_id: - case WEP_HLAC.m_id: - case WEP_RIFLE.m_id: - case WEP_SHOCKWAVE.m_id: - return true; - default: - return false; - } -} - -string nt_GetFullReplacement(string w) -{ - switch(w) - { - case "hagar": return "seeker"; - case "devastator": return "minelayer"; - case "machinegun": return "hlac"; - case "vortex": return "rifle"; - //case "shotgun": return "shockwave"; - default: return string_null; - } -} - -string nt_GetReplacement(string w, float m) -{ - if(m == NT_AUTOREPLACE_NEVER) - return w; - string s = nt_GetFullReplacement(w); - if (!s) - return w; - if(m == NT_AUTOREPLACE_RANDOM) - s = strcat(w, " ", s); - return s; -} - -MUTATOR_HOOKFUNCTION(nt, SetStartItems) -{ - // rearrange start_weapon_default - // apply those bits that are set by start_weapon_defaultmask - // same for warmup - - float i, j, k, n; - - WepSet newdefault; - WepSet warmup_newdefault; - - newdefault = '0 0 0'; - warmup_newdefault = '0 0 0'; - - for(i = WEP_FIRST; i <= WEP_LAST; ++i) - { - entity e = get_weaponinfo(i); - if(!e.weapon) - continue; - - n = tokenize_console(nt_GetReplacement(e.netname, autocvar_g_new_toys_autoreplace)); - - for(j = 0; j < n; ++j) - for(k = WEP_FIRST; k <= WEP_LAST; ++k) - if(get_weaponinfo(k).netname == argv(j)) - { - if(start_weapons & WepSet_FromWeapon(i)) - newdefault |= WepSet_FromWeapon(k); - if(warmup_start_weapons & WepSet_FromWeapon(i)) - warmup_newdefault |= WepSet_FromWeapon(k); - } - } - - newdefault &= start_weapons_defaultmask; - start_weapons &= ~start_weapons_defaultmask; - start_weapons |= newdefault; - - warmup_newdefault &= warmup_start_weapons_defaultmask; - warmup_start_weapons &= ~warmup_start_weapons_defaultmask; - warmup_start_weapons |= warmup_newdefault; - - return 0; -} - -MUTATOR_HOOKFUNCTION(nt, SetWeaponreplace) -{SELFPARAM(); - // otherwise, we do replace - if(self.new_toys) - { - // map defined replacement: - ret_string = self.new_toys; - } - else - { - // auto replacement: - ret_string = nt_GetReplacement(other.netname, autocvar_g_new_toys_autoreplace); - } - - // apply regular weaponreplace - ret_string = W_Apply_Weaponreplace(ret_string); - - return 0; -} - -MUTATOR_HOOKFUNCTION(nt, FilterItem) -{SELFPARAM(); - if(nt_IsNewToy(self.weapon) && autocvar_g_new_toys_use_pickupsound) { - self.item_pickupsound = string_null; - self.item_pickupsound_ent = SND_WEAPONPICKUP_NEW_TOYS; - } - return 0; -} diff --git a/qcsrc/server/mutators/mutator_nix.qc b/qcsrc/server/mutators/mutator_nix.qc deleted file mode 100644 index 96e8ca97b..000000000 --- a/qcsrc/server/mutators/mutator_nix.qc +++ /dev/null @@ -1,268 +0,0 @@ - -#include "mutator.qh" - -float g_nix_with_blaster; -// WEAPONTODO -int nix_weapon; -float nix_nextchange; -float nix_nextweapon; -.float nix_lastchange_id; -.float nix_lastinfotime; -.float nix_nextincr; - -bool NIX_CanChooseWeapon(int wpn); - -REGISTER_MUTATOR(nix, cvar("g_nix") && !cvar("g_instagib") && !cvar("g_overkill")) -{ - MUTATOR_ONADD - { - g_nix_with_blaster = autocvar_g_nix_with_blaster; - - nix_nextchange = 0; - nix_nextweapon = 0; - - for (int i = WEP_FIRST; i <= WEP_LAST; ++i) - if (NIX_CanChooseWeapon(i)) { - Weapon w = get_weaponinfo(i); - w.wr_init(w); - } - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // nothing to roll back - } - - MUTATOR_ONREMOVE - { - // as the PlayerSpawn hook will no longer run, NIX is turned off by this! - entity e; - FOR_EACH_PLAYER(e) if(e.deadflag == DEAD_NO) - { - e.ammo_cells = start_ammo_cells; - e.ammo_plasma = start_ammo_plasma; - e.ammo_shells = start_ammo_shells; - e.ammo_nails = start_ammo_nails; - e.ammo_rockets = start_ammo_rockets; - e.ammo_fuel = start_ammo_fuel; - e.weapons = start_weapons; - if(!client_hasweapon(e, e.weapon, true, false)) - e.switchweapon = w_getbestweapon(self); - } - } - - return 0; -} - -bool NIX_CanChooseWeapon(int wpn) -{ - entity e = get_weaponinfo(wpn); - if(!e.weapon) // skip dummies - return false; - if(g_weaponarena) - { - if(!(g_weaponarena_weapons & WepSet_FromWeapon(wpn))) - return false; - } - else - { - if(wpn == WEP_BLASTER.m_id && g_nix_with_blaster) - return false; - if(e.spawnflags & WEP_FLAG_MUTATORBLOCKED) - return false; - if (!(e.spawnflags & WEP_FLAG_NORMAL)) - return false; - } - return true; -} -void NIX_ChooseNextWeapon() -{ - float j; - RandomSelection_Init(); - for(j = WEP_FIRST; j <= WEP_LAST; ++j) - if(NIX_CanChooseWeapon(j)) - RandomSelection_Add(world, j, string_null, 1, (j != nix_weapon)); - nix_nextweapon = RandomSelection_chosen_float; -} - -void NIX_GiveCurrentWeapon() -{SELFPARAM(); - float dt; - - if(!nix_nextweapon) - NIX_ChooseNextWeapon(); - - dt = ceil(nix_nextchange - time); - - if(dt <= 0) - { - nix_weapon = nix_nextweapon; - nix_nextweapon = 0; - if (!nix_nextchange) // no round played yet? - nix_nextchange = time; // start the first round now! - else - nix_nextchange = time + autocvar_g_balance_nix_roundtime; - // Weapon w = get_weaponinfo(nix_weapon); - // w.wr_init(w); // forget it, too slow - } - - // get weapon info - entity e = get_weaponinfo(nix_weapon); - - if(nix_nextchange != self.nix_lastchange_id) // this shall only be called once per round! - { - self.ammo_shells = self.ammo_nails = self.ammo_rockets = self.ammo_cells = self.ammo_plasma = self.ammo_fuel = 0; - - if(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - switch(e.ammo_field) - { - case ammo_shells: self.ammo_shells = autocvar_g_pickup_shells_max; break; - case ammo_nails: self.ammo_nails = autocvar_g_pickup_nails_max; break; - case ammo_rockets: self.ammo_rockets = autocvar_g_pickup_rockets_max; break; - case ammo_cells: self.ammo_cells = autocvar_g_pickup_cells_max; break; - case ammo_plasma: self.ammo_plasma = autocvar_g_pickup_plasma_max; break; - case ammo_fuel: self.ammo_fuel = autocvar_g_pickup_fuel_max; break; - } - } - else - { - switch(e.ammo_field) - { - case ammo_shells: self.ammo_shells = autocvar_g_balance_nix_ammo_shells; break; - case ammo_nails: self.ammo_nails = autocvar_g_balance_nix_ammo_nails; break; - case ammo_rockets: self.ammo_rockets = autocvar_g_balance_nix_ammo_rockets; break; - case ammo_cells: self.ammo_cells = autocvar_g_balance_nix_ammo_cells; break; - case ammo_plasma: self.ammo_plasma = autocvar_g_balance_nix_ammo_plasma; break; - case ammo_fuel: self.ammo_fuel = autocvar_g_balance_nix_ammo_fuel; break; - } - } - - self.nix_nextincr = time + autocvar_g_balance_nix_incrtime; - if(dt >= 1 && dt <= 5) - self.nix_lastinfotime = -42; - else - Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_NEWWEAPON, nix_weapon); - - Weapon w = get_weaponinfo(nix_weapon); - w.wr_resetplayer(w); - - // all weapons must be fully loaded when we spawn - if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars - self.(weapon_load[nix_weapon]) = e.reloading_ammo; - - // vortex too - if(WEP_CVAR(vortex, charge)) - { - if(WEP_CVAR_SEC(vortex, chargepool)) - self.vortex_chargepool_ammo = 1; - self.vortex_charge = WEP_CVAR(vortex, charge_start); - } - - // set last change info - self.nix_lastchange_id = nix_nextchange; - } - if(self.nix_lastinfotime != dt) - { - self.nix_lastinfotime = dt; // initial value 0 should count as "not seen" - if(dt >= 1 && dt <= 5) - Send_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER, CENTER_NIX_COUNTDOWN, nix_nextweapon, dt); - } - - if(!(self.items & IT_UNLIMITED_WEAPON_AMMO) && time > self.nix_nextincr) - { - switch(e.ammo_field) - { - case ammo_shells: self.ammo_shells += autocvar_g_balance_nix_ammoincr_shells; break; - case ammo_nails: self.ammo_nails += autocvar_g_balance_nix_ammoincr_nails; break; - case ammo_rockets: self.ammo_rockets += autocvar_g_balance_nix_ammoincr_rockets; break; - case ammo_cells: self.ammo_cells += autocvar_g_balance_nix_ammoincr_cells; break; - case ammo_plasma: self.ammo_plasma += autocvar_g_balance_nix_ammoincr_plasma; break; - case ammo_fuel: self.ammo_fuel += autocvar_g_balance_nix_ammoincr_fuel; break; - } - - self.nix_nextincr = time + autocvar_g_balance_nix_incrtime; - } - - self.weapons = '0 0 0'; - if(g_nix_with_blaster) - self.weapons |= WEPSET(BLASTER); - self.weapons |= WepSet_FromWeapon(nix_weapon); - - if(self.switchweapon != nix_weapon) - if(!client_hasweapon(self, self.switchweapon, true, false)) - if(client_hasweapon(self, nix_weapon, true, false)) - W_SwitchWeapon(nix_weapon); -} - -MUTATOR_HOOKFUNCTION(nix, ForbidThrowCurrentWeapon) -{ - return 1; // no throwing in NIX -} - -MUTATOR_HOOKFUNCTION(nix, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":NIX"); - return 0; -} - -MUTATOR_HOOKFUNCTION(nix, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", NIX"); - return 0; -} - -MUTATOR_HOOKFUNCTION(nix, FilterItem) -{SELFPARAM(); - switch (self.items) - { - case ITEM_HealthSmall.m_itemid: - case ITEM_HealthMedium.m_itemid: - case ITEM_HealthLarge.m_itemid: - case ITEM_HealthMega.m_itemid: - case ITEM_ArmorSmall.m_itemid: - case ITEM_ArmorMedium.m_itemid: - case ITEM_ArmorLarge.m_itemid: - case ITEM_ArmorMega.m_itemid: - if (autocvar_g_nix_with_healtharmor) - return 0; - break; - case ITEM_Strength.m_itemid: - case ITEM_Shield.m_itemid: - if (autocvar_g_nix_with_powerups) - return 0; - break; - } - - return 1; // delete all other items -} - -MUTATOR_HOOKFUNCTION(nix, OnEntityPreSpawn) -{SELFPARAM(); - if(self.classname == "target_items") // items triggers cannot work in nix (as they change weapons/ammo) - return 1; - return 0; -} - -MUTATOR_HOOKFUNCTION(nix, PlayerPreThink) -{SELFPARAM(); - if(!intermission_running) - if(self.deadflag == DEAD_NO) - if(IS_PLAYER(self)) - NIX_GiveCurrentWeapon(); - return 0; -} - -MUTATOR_HOOKFUNCTION(nix, PlayerSpawn) -{SELFPARAM(); - self.nix_lastchange_id = -1; - NIX_GiveCurrentWeapon(); // overrides the weapons you got when spawning - self.items |= IT_UNLIMITED_SUPERWEAPONS; - return 0; -} - -MUTATOR_HOOKFUNCTION(nix, SetModname, CBC_ORDER_LAST) -{ - modname = "NIX"; - return 0; -} diff --git a/qcsrc/server/mutators/mutator_overkill.qc b/qcsrc/server/mutators/mutator_overkill.qc deleted file mode 100644 index 79607a1aa..000000000 --- a/qcsrc/server/mutators/mutator_overkill.qc +++ /dev/null @@ -1,351 +0,0 @@ -#include "mutator_overkill.qh" - -#include "mutator.qh" - -void ok_Initialize(); - -REGISTER_MUTATOR(ok, cvar("g_overkill") && !cvar("g_instagib") && !g_nexball && cvar_string("g_mod_balance") == "Overkill") -{ - MUTATOR_ONADD - { - ok_Initialize(); - } - - MUTATOR_ONREMOVE - { - WEP_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - WEP_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED; - } - - return false; -} - -void W_Blaster_Attack(entity, float, float, float, float, float, float, float, float, float, float); -spawnfunc(weapon_hmg); -spawnfunc(weapon_rpc); - -void ok_DecreaseCharge(entity ent, int wep) -{ - if(!ent.ok_use_ammocharge) return; - - entity wepent = get_weaponinfo(wep); - - if(wepent.weapon == 0) - return; // dummy - - ent.ammo_charge[wep] -= max(0, cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); -} - -void ok_IncreaseCharge(entity ent, int wep) -{ - entity wepent = get_weaponinfo(wep); - - if(wepent.weapon == 0) - return; // dummy - - if(ent.ok_use_ammocharge) - if(!ent.BUTTON_ATCK) // not while attacking? - ent.ammo_charge[wep] = min(autocvar_g_overkill_ammo_charge_limit, ent.ammo_charge[wep] + cvar(sprintf("g_overkill_ammo_charge_rate_%s", wepent.netname)) * frametime / W_TICSPERFRAME); -} - -float ok_CheckWeaponCharge(entity ent, int wep) -{ - if(!ent.ok_use_ammocharge) return true; - - entity wepent = get_weaponinfo(wep); - - if(wepent.weapon == 0) - return 0; // dummy - - return (ent.ammo_charge[wep] >= cvar(sprintf("g_overkill_ammo_decharge_%s", wepent.netname))); -} - -MUTATOR_HOOKFUNCTION(ok, PlayerDamage_Calculate, CBC_ORDER_LAST) -{ - if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target)) - if(DEATH_ISWEAPON(frag_deathtype, WEP_BLASTER)) - { - frag_damage = 0; - - if(frag_attacker != frag_target) - if(frag_target.health > 0) - if(frag_target.frozen == 0) - if(frag_target.deadflag == DEAD_NO) - { - Send_Notification(NOTIF_ONE, frag_attacker, MSG_CENTER, CENTER_SECONDARY_NODAMAGE); - frag_force = '0 0 0'; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ok, PlayerDamage_SplitHealthArmor) -{SELFPARAM(); - if(damage_take) - self.ok_pauseregen_finished = max(self.ok_pauseregen_finished, time + 2); - return false; -} - -MUTATOR_HOOKFUNCTION(ok, PlayerDies) -{SELFPARAM(); - entity targ = ((frag_attacker) ? frag_attacker : frag_target); - - if(IS_MONSTER(self)) - { - remove(other); // remove default item - other = world; - } - - setself(spawn()); - self.ok_item = true; - self.noalign = true; - self.pickup_anyway = true; - spawnfunc_item_armor_small(this); - self.movetype = MOVETYPE_TOSS; - self.gravity = 1; - self.reset = SUB_Remove; - setorigin(self, frag_target.origin + '0 0 32'); - self.velocity = '0 0 200' + normalize(targ.origin - self.origin) * 500; - self.classname = "droppedweapon"; // hax - SUB_SetFade(self, time + 5, 1); - setself(this); - - self.ok_lastwep = self.switchweapon; - - return false; -} -MUTATOR_HOOKFUNCTION(ok, MonsterDropItem) { ok_PlayerDies(); } - -MUTATOR_HOOKFUNCTION(ok, PlayerRegen) -{SELFPARAM(); - // overkill's values are different, so use custom regen - if(!self.frozen) - { - self.armorvalue = CalcRotRegen(self.armorvalue, autocvar_g_balance_armor_regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, 1 * frametime * (time > self.ok_pauseregen_finished), 0, 0, 1, 1 * frametime * (time > self.pauserotarmor_finished), autocvar_g_balance_armor_limit); - self.health = CalcRotRegen(self.health, autocvar_g_balance_health_regenstable, 0, 100, 1 * frametime * (time > self.ok_pauseregen_finished), 200, 0, autocvar_g_balance_health_rotlinear, 1 * frametime * (time > self.pauserothealth_finished), autocvar_g_balance_health_limit); - - float minf, maxf, limitf; - - maxf = autocvar_g_balance_fuel_rotstable; - minf = autocvar_g_balance_fuel_regenstable; - limitf = autocvar_g_balance_fuel_limit; - - self.ammo_fuel = CalcRotRegen(self.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > self.pauseregen_finished) * ((self.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > self.pauserotfuel_finished), limitf); - } - return true; // return true anyway, as frozen uses no regen -} - -MUTATOR_HOOKFUNCTION(ok, ForbidThrowCurrentWeapon) -{ - return true; -} - -MUTATOR_HOOKFUNCTION(ok, PlayerPreThink) -{SELFPARAM(); - if(intermission_running || gameover) - return false; - - if(self.deadflag != DEAD_NO || !IS_PLAYER(self) || self.frozen) - return false; - - if(self.ok_lastwep) - { - self.switchweapon = self.ok_lastwep; - self.ok_lastwep = 0; - } - - ok_IncreaseCharge(self, self.weapon); - - if(self.BUTTON_ATCK2) - if(!forbidWeaponUse(self) || self.weapon_blocked) // allow if weapon is blocked - if(time >= self.jump_interval) - { - self.jump_interval = time + WEP_CVAR_PRI(blaster, refire) * W_WeaponRateFactor(); - makevectors(self.v_angle); - - int oldwep = self.weapon; - self.weapon = WEP_BLASTER.m_id; - W_Blaster_Attack( - self, - WEP_BLASTER.m_id | HITTYPE_SECONDARY, - WEP_CVAR_SEC(vaporizer, shotangle), - WEP_CVAR_SEC(vaporizer, damage), - WEP_CVAR_SEC(vaporizer, edgedamage), - WEP_CVAR_SEC(vaporizer, radius), - WEP_CVAR_SEC(vaporizer, force), - WEP_CVAR_SEC(vaporizer, speed), - WEP_CVAR_SEC(vaporizer, spread), - WEP_CVAR_SEC(vaporizer, delay), - WEP_CVAR_SEC(vaporizer, lifetime) - ); - self.weapon = oldwep; - } - - self.weapon_blocked = false; - - self.ok_ammo_charge = self.ammo_charge[self.weapon]; - - if(self.ok_use_ammocharge) - if(!ok_CheckWeaponCharge(self, self.weapon)) - { - if(autocvar_g_overkill_ammo_charge_notice && time > self.ok_notice_time && self.BUTTON_ATCK && IS_REAL_CLIENT(self) && self.weapon == self.switchweapon) - { - //Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERKILL_CHARGE); - self.ok_notice_time = time + 2; - play2(self, SND(DRYFIRE)); - } - Weapon wpn = get_weaponinfo(self.weapon); - if(self.weaponentity.state != WS_CLEAR) - w_ready(wpn, self, self.BUTTON_ATCK, self.BUTTON_ATCK2); - - self.weapon_blocked = true; - } - - self.BUTTON_ATCK2 = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(ok, PlayerSpawn) -{SELFPARAM(); - if(autocvar_g_overkill_ammo_charge) - { - for(int i = WEP_FIRST; i <= WEP_LAST; ++i) - self.ammo_charge[i] = autocvar_g_overkill_ammo_charge_limit; - - self.ok_use_ammocharge = 1; - self.ok_notice_time = time; - } - else - self.ok_use_ammocharge = 0; - - self.ok_pauseregen_finished = time + 2; - - return false; -} - -void _spawnfunc_weapon_hmg() { SELFPARAM(); spawnfunc_weapon_hmg(this); } -void _spawnfunc_weapon_rpc() { SELFPARAM(); spawnfunc_weapon_rpc(this); } - -MUTATOR_HOOKFUNCTION(ok, OnEntityPreSpawn) -{SELFPARAM(); - if(autocvar_g_powerups) - if(autocvar_g_overkill_powerups_replace) - { - if(self.classname == "item_strength") - { - entity wep = spawn(); - setorigin(wep, self.origin); - setmodel(wep, MDL_OK_HMG); - wep.classname = "weapon_hmg"; - wep.ok_item = true; - wep.noalign = self.noalign; - wep.cnt = self.cnt; - wep.team = self.team; - wep.respawntime = autocvar_g_overkill_superguns_respawn_time; - wep.pickup_anyway = true; - wep.think = _spawnfunc_weapon_hmg; - wep.nextthink = time + 0.1; - return true; - } - - if(self.classname == "item_invincible") - { - entity wep = spawn(); - setorigin(wep, self.origin); - setmodel(wep, MDL_OK_RPC); - wep.classname = "weapon_rpc"; - wep.ok_item = true; - wep.noalign = self.noalign; - wep.cnt = self.cnt; - wep.team = self.team; - wep.respawntime = autocvar_g_overkill_superguns_respawn_time; - wep.pickup_anyway = true; - wep.think = _spawnfunc_weapon_rpc; - wep.nextthink = time + 0.1; - return true; - } - } - - return false; -} - -MUTATOR_HOOKFUNCTION(ok, FilterItem) -{SELFPARAM(); - if(self.ok_item) - return false; - - switch(self.items) - { - case ITEM_HealthMega.m_itemid: return !(autocvar_g_overkill_100h_anyway); - case ITEM_ArmorMega.m_itemid: return !(autocvar_g_overkill_100a_anyway); - } - - return true; -} - -MUTATOR_HOOKFUNCTION(ok, SpectateCopy) -{SELFPARAM(); - self.ammo_charge[self.weapon] = other.ammo_charge[other.weapon]; - self.ok_use_ammocharge = other.ok_use_ammocharge; - - return false; -} - -MUTATOR_HOOKFUNCTION(ok, SetStartItems) -{ - WepSet ok_start_items = (WEPSET(MACHINEGUN) | WEPSET(VORTEX) | WEPSET(SHOTGUN)); - - if(WEP_RPC.weaponstart > 0) { ok_start_items |= WEPSET(RPC); } - if(WEP_HMG.weaponstart > 0) { ok_start_items |= WEPSET(HMG); } - - start_items |= IT_UNLIMITED_WEAPON_AMMO; - start_weapons = warmup_start_weapons = ok_start_items; - - return false; -} - -MUTATOR_HOOKFUNCTION(ok, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":OK"); - return false; -} - -MUTATOR_HOOKFUNCTION(ok, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Overkill"); - return false; -} - -MUTATOR_HOOKFUNCTION(ok, SetModname) -{ - modname = "Overkill"; - return true; -} - -void ok_SetCvars() -{ - // hack to force overkill playermodels - cvar_settemp("sv_defaultcharacter", "1"); - cvar_settemp("sv_defaultplayermodel", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); - cvar_settemp("sv_defaultplayermodel_red", "models/ok_player/okrobot1.dpm models/ok_player/okrobot2.dpm models/ok_player/okrobot3.dpm models/ok_player/okrobot4.dpm"); - cvar_settemp("sv_defaultplayermodel_blue", "models/ok_player/okmale1.dpm models/ok_player/okmale2.dpm models/ok_player/okmale3.dpm models/ok_player/okmale4.dpm"); -} - -void ok_Initialize() -{ - ok_SetCvars(); - - precache_all_playermodels("models/ok_player/*.dpm"); - - addstat(STAT_OK_AMMO_CHARGE, AS_FLOAT, ok_use_ammocharge); - addstat(STAT_OK_AMMO_CHARGEPOOL, AS_FLOAT, ok_ammo_charge); - - WEP_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - WEP_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED; - - WEP_SHOTGUN.mdl = "ok_shotgun"; - WEP_MACHINEGUN.mdl = "ok_mg"; - WEP_VORTEX.mdl = "ok_sniper"; -} diff --git a/qcsrc/server/mutators/mutator_overkill.qh b/qcsrc/server/mutators/mutator_overkill.qh deleted file mode 100644 index 10f89c08e..000000000 --- a/qcsrc/server/mutators/mutator_overkill.qh +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MUTATOR_OVERKILL_H -#define MUTATOR_OVERKILL_H - -.vector ok_deathloc; -.float ok_spawnsys_timer; -.float ok_lastwep; -.float ok_item; - -.float ok_notice_time; -.float ammo_charge[Weapons_MAX]; -.float ok_use_ammocharge; -.float ok_ammo_charge; - -.float ok_pauseregen_finished; - -void(entity ent, float wep) ok_DecreaseCharge; - -#endif diff --git a/qcsrc/server/mutators/mutator_physical_items.qc b/qcsrc/server/mutators/mutator_physical_items.qc deleted file mode 100644 index 4bcf495f7..000000000 --- a/qcsrc/server/mutators/mutator_physical_items.qc +++ /dev/null @@ -1,140 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(physical_items, cvar("g_physical_items")) -{ - // check if we have a physics engine - MUTATOR_ONADD - { - if (!(autocvar_physics_ode && checkextension("DP_PHYSICS_ODE"))) - { - LOG_TRACE("Warning: Physical items are enabled but no physics engine can be used. Reverting to old items.\n"); - return -1; - } - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // nothing to roll back - } - - MUTATOR_ONREMOVE - { - LOG_INFO("This cannot be removed at runtime\n"); - return -1; - } - - return 0; -} - -.vector spawn_origin, spawn_angles; - -void physical_item_think() -{SELFPARAM(); - self.nextthink = time; - - self.alpha = self.owner.alpha; // apply fading and ghosting - - if(!self.cnt) // map item, not dropped - { - // copy ghost item properties - self.colormap = self.owner.colormap; - self.colormod = self.owner.colormod; - self.glowmod = self.owner.glowmod; - - // if the item is not spawned, make sure the invisible / ghost item returns to its origin and stays there - if(autocvar_g_physical_items_reset) - { - if(self.owner.wait > time) // awaiting respawn - { - setorigin(self, self.spawn_origin); - self.angles = self.spawn_angles; - self.solid = SOLID_NOT; - self.alpha = -1; - self.movetype = MOVETYPE_NONE; - } - else - { - self.alpha = 1; - self.solid = SOLID_CORPSE; - self.movetype = MOVETYPE_PHYSICS; - } - } - } - - if(!self.owner.modelindex) - remove(self); // the real item is gone, remove this -} - -void physical_item_touch() -{SELFPARAM(); - if(!self.cnt) // not for dropped items - if (ITEM_TOUCH_NEEDKILL()) - { - setorigin(self, self.spawn_origin); - self.angles = self.spawn_angles; - } -} - -void physical_item_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{SELFPARAM(); - if(!self.cnt) // not for dropped items - if(ITEM_DAMAGE_NEEDKILL(deathtype)) - { - setorigin(self, self.spawn_origin); - self.angles = self.spawn_angles; - } -} - -MUTATOR_HOOKFUNCTION(physical_items, Item_Spawn) -{SELFPARAM(); - if(self.owner == world && autocvar_g_physical_items <= 1) - return false; - if (self.spawnflags & 1) // floating item - return false; - - // The actual item can't be physical and trigger at the same time, so make it invisible and use a second entity for physics. - // Ugly hack, but unless SOLID_TRIGGER is gotten to work with MOVETYPE_PHYSICS in the engine it can't be fixed. - entity wep; - wep = spawn(); - _setmodel(wep, self.model); - setsize(wep, self.mins, self.maxs); - setorigin(wep, self.origin); - wep.angles = self.angles; - wep.velocity = self.velocity; - - wep.owner = self; - wep.solid = SOLID_CORPSE; - wep.movetype = MOVETYPE_PHYSICS; - wep.takedamage = DAMAGE_AIM; - wep.effects |= EF_NOMODELFLAGS; // disable the spinning - wep.colormap = self.owner.colormap; - wep.glowmod = self.owner.glowmod; - wep.damageforcescale = autocvar_g_physical_items_damageforcescale; - wep.dphitcontentsmask = self.dphitcontentsmask; - wep.cnt = (self.owner != world); - - wep.think = physical_item_think; - wep.nextthink = time; - wep.touch = physical_item_touch; - wep.event_damage = physical_item_damage; - - if(!wep.cnt) - { - // fix the spawn origin - setorigin(wep, wep.origin + '0 0 1'); - entity oldself; - oldself = self; - WITH(entity, self, wep, builtin_droptofloor()); - } - - wep.spawn_origin = wep.origin; - wep.spawn_angles = self.angles; - - self.effects |= EF_NODRAW; // hide the original weapon - self.movetype = MOVETYPE_FOLLOW; - self.aiment = wep; // attach the original weapon - self.SendEntity = func_null; - - return false; -} diff --git a/qcsrc/server/mutators/mutator_pinata.qc b/qcsrc/server/mutators/mutator_pinata.qc deleted file mode 100644 index a531d6bec..000000000 --- a/qcsrc/server/mutators/mutator_pinata.qc +++ /dev/null @@ -1,28 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(pinata, cvar("g_pinata") && !cvar("g_instagib") && !cvar("g_overkill")); - -MUTATOR_HOOKFUNCTION(pinata, PlayerDies) -{SELFPARAM(); - for(int j = WEP_FIRST; j <= WEP_LAST; ++j) - if(self.weapons & WepSet_FromWeapon(j)) - if(self.switchweapon != j) - if(W_IsWeaponThrowable(j)) - W_ThrowNewWeapon(self, j, false, self.origin + (self.mins + self.maxs) * 0.5, randomvec() * 175 + '0 0 325'); - - return true; -} - -MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":Pinata"); - return false; -} - -MUTATOR_HOOKFUNCTION(pinata, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Piñata"); - return false; -} - diff --git a/qcsrc/server/mutators/mutator_random_gravity.qc b/qcsrc/server/mutators/mutator_random_gravity.qc deleted file mode 100644 index 1a927648d..000000000 --- a/qcsrc/server/mutators/mutator_random_gravity.qc +++ /dev/null @@ -1,50 +0,0 @@ - -#include "mutator.qh" - -// Random Gravity -// -// Mutator by Mario -// Inspired by Player 2 - -REGISTER_MUTATOR(random_gravity, cvar("g_random_gravity")) -{ - MUTATOR_ONADD - { - cvar_settemp("sv_gravity", cvar_string("sv_gravity")); // settemp current gravity so it's restored on match end - } - - return false; -} - -float gravity_delay; - -MUTATOR_HOOKFUNCTION(random_gravity, SV_StartFrame) -{ - if(gameover || !cvar("g_random_gravity")) return false; - if(time < gravity_delay) return false; - if(time < game_starttime) return false; - if(round_handler_IsActive() && !round_handler_IsRoundStarted()) return false; - - if(random() >= autocvar_g_random_gravity_negative_chance) - cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() - random() * -autocvar_g_random_gravity_negative, autocvar_g_random_gravity_max))); - else - cvar_set("sv_gravity", ftos(bound(autocvar_g_random_gravity_min, random() * autocvar_g_random_gravity_positive, autocvar_g_random_gravity_max))); - - gravity_delay = time + autocvar_g_random_gravity_delay; - - LOG_TRACE("Gravity is now: ", ftos(autocvar_sv_gravity), "\n"); - - return false; -} - -MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":RandomGravity"); - return 0; -} - -MUTATOR_HOOKFUNCTION(random_gravity, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Random gravity"); - return 0; -} diff --git a/qcsrc/server/mutators/mutator_rocketflying.qc b/qcsrc/server/mutators/mutator_rocketflying.qc deleted file mode 100644 index 44ceeaa9e..000000000 --- a/qcsrc/server/mutators/mutator_rocketflying.qc +++ /dev/null @@ -1,26 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(rocketflying, cvar("g_rocket_flying")); - -MUTATOR_HOOKFUNCTION(rocketflying, EditProjectile) -{ - if(other.classname == "rocket" || other.classname == "mine") - { - // kill detonate delay of rockets - other.spawnshieldtime = time; - } - return 0; -} - -MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":RocketFlying"); - return 0; -} - -MUTATOR_HOOKFUNCTION(rocketflying, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Rocket Flying"); - return 0; -} diff --git a/qcsrc/server/mutators/mutator_rocketminsta.qc b/qcsrc/server/mutators/mutator_rocketminsta.qc deleted file mode 100644 index b7319cf3e..000000000 --- a/qcsrc/server/mutators/mutator_rocketminsta.qc +++ /dev/null @@ -1,33 +0,0 @@ -#include "../../common/deathtypes/all.qh" -#include "../round_handler.qh" - -REGISTER_MUTATOR(rm, cvar("g_instagib")); - -MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate) -{ - // we do it this way, so rm can be toggled during the match - if(!autocvar_g_rm) { return false; } - - if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR)) - if(frag_attacker == frag_target || frag_target.classname == "nade") - frag_damage = 0; - - if(autocvar_g_rm_laser) - if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) - if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted())) - frag_damage = 0; - - return false; -} - -MUTATOR_HOOKFUNCTION(rm, PlayerDies) -{ - // we do it this way, so rm can be toggled during the match - if(!autocvar_g_rm) { return false; } - - if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR) || DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO)) - frag_damage = 1000; // always gib if it was a vaporizer death - - return false; -} - diff --git a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc b/qcsrc/server/mutators/mutator_spawn_near_teammate.qc deleted file mode 100644 index 3bc1f7b3d..000000000 --- a/qcsrc/server/mutators/mutator_spawn_near_teammate.qc +++ /dev/null @@ -1,168 +0,0 @@ - -#include "mutator.qh" - -REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate") && teamplay); - -.entity msnt_lookat; - -.float msnt_timer; -.vector msnt_deathloc; - -.float cvar_cl_spawn_near_teammate; - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score) -{SELFPARAM(); - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate)) - return 0; - - entity p; - - spawn_spot.msnt_lookat = world; - - if(!teamplay) - return 0; - - RandomSelection_Init(); - FOR_EACH_PLAYER(p) if(p != self) if(p.team == self.team) if(!p.deadflag) - { - float l = vlen(spawn_spot.origin - p.origin); - if(l > autocvar_g_spawn_near_teammate_distance) - continue; - if(l < 48) - continue; - if(!checkpvs(spawn_spot.origin, p)) - continue; - RandomSelection_Add(p, 0, string_null, 1, 1); - } - - if(RandomSelection_chosen_ent) - { - spawn_spot.msnt_lookat = RandomSelection_chosen_ent; - spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND; - } - else if(self.team == spawn_spot.team) - spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate - - return 0; -} - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn) -{SELFPARAM(); - // Note: when entering this, fixangle is already set. - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && self.cvar_cl_spawn_near_teammate)) - { - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death) - self.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death; - - entity team_mate, best_mate = world; - vector best_spot = '0 0 0'; - float pc = 0, best_dist = 0, dist = 0; - FOR_EACH_PLAYER(team_mate) - { - if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && team_mate.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0) - if(team_mate.deadflag == DEAD_NO) - if(team_mate.msnt_timer < time) - if(SAME_TEAM(self, team_mate)) - if(time > team_mate.spawnshieldtime) // spawn shielding - if(team_mate.frozen == 0) - if(team_mate != self) - { - tracebox(team_mate.origin, PL_MIN, PL_MAX, team_mate.origin - '0 0 100', MOVE_WORLDONLY, team_mate); - if(trace_fraction != 1.0) - if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) - { - pc = pointcontents(trace_endpos + '0 0 1'); - if(pc == CONTENT_EMPTY) - { - if(vlen(team_mate.velocity) > 5) - fixedmakevectors(vectoangles(team_mate.velocity)); - else - fixedmakevectors(team_mate.angles); - - for(pc = 0; pc != 5; ++pc) // test 5 diffrent spots close to mate - { - switch(pc) - { - case 0: - tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 128, MOVE_NORMAL, team_mate); - break; - case 1: - tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 128 , MOVE_NORMAL, team_mate); - break; - case 2: - tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin + v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate); - break; - case 3: - tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_right * 64 - v_forward * 64, MOVE_NORMAL, team_mate); - break; - case 4: - tracebox(team_mate.origin , PL_MIN, PL_MAX, team_mate.origin - v_forward * 128, MOVE_NORMAL, team_mate); - break; - } - - if(trace_fraction == 1.0) - { - traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NORMAL, team_mate); - if(trace_fraction != 1.0) - { - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) - { - dist = vlen(trace_endpos - self.msnt_deathloc); - if(dist < best_dist || best_dist == 0) - { - best_dist = dist; - best_spot = trace_endpos; - best_mate = team_mate; - } - } - else - { - setorigin(self, trace_endpos); - self.angles = team_mate.angles; - self.angles_z = 0; // never spawn tilted even if the spot says to - team_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; - return 0; - } - } - } - } - } - } - } - } - - if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath) - if(best_dist) - { - setorigin(self, best_spot); - self.angles = best_mate.angles; - self.angles_z = 0; // never spawn tilted even if the spot says to - best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay; - } - } - else if(spawn_spot.msnt_lookat) - { - self.angles = vectoangles(spawn_spot.msnt_lookat.origin - self.origin); - self.angles_x = -self.angles.x; - self.angles_z = 0; // never spawn tilted even if the spot says to - /* - sprint(self, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n"); - sprint(self, "distance: ", vtos(spawn_spot.msnt_lookat.origin - self.origin), "\n"); - sprint(self, "angles: ", vtos(self.angles), "\n"); - */ - } - - return 0; -} - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies) -{SELFPARAM(); - self.msnt_deathloc = self.origin; - return 0; -} - -MUTATOR_HOOKFUNCTION(spawn_near_teammate, GetCvars) -{ - GetCvars_handleFloat(get_cvars_s, get_cvars_f, cvar_cl_spawn_near_teammate, "cl_spawn_near_teammate"); - return false; -} diff --git a/qcsrc/server/mutators/mutator_superspec.qc b/qcsrc/server/mutators/mutator_superspec.qc deleted file mode 100644 index 8f1060371..000000000 --- a/qcsrc/server/mutators/mutator_superspec.qc +++ /dev/null @@ -1,480 +0,0 @@ -#include "mutator.qh" - -REGISTER_MUTATOR(superspec, cvar("g_superspectate")); - -#define _SSMAGIX "SUPERSPEC_OPTIONSFILE_V1" -#define _ISLOCAL ((edict_num(1) == self) ? true : false) - -const float ASF_STRENGTH = 1; -const float ASF_SHIELD = 2; -const float ASF_MEGA_AR = 4; -const float ASF_MEGA_HP = 8; -const float ASF_FLAG_GRAB = 16; -const float ASF_OBSERVER_ONLY = 32; -const float ASF_SHOWWHAT = 64; -const float ASF_SSIM = 128; -const float ASF_FOLLOWKILLER = 256; -const float ASF_ALL = 0xFFFFFF; -.float autospec_flags; - -const float SSF_SILENT = 1; -const float SSF_VERBOSE = 2; -const float SSF_ITEMMSG = 4; -.float superspec_flags; - -.string superspec_itemfilter; //"classname1 classname2 ..." - -bool superspec_Spectate(entity _player) -{SELFPARAM(); - if(Spectate(_player) == 1) - self.classname = "spectator"; - - return true; -} - -void superspec_save_client_conf() -{SELFPARAM(); - string fn = "superspec-local.options"; - float fh; - - if (!_ISLOCAL) - { - if(self.crypto_idfp == "") - return; - - fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp)); - } - - fh = fopen(fn, FILE_WRITE); - if(fh < 0) - { - LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for writing.\n"); - } - else - { - fputs(fh, _SSMAGIX); - fputs(fh, "\n"); - fputs(fh, ftos(self.autospec_flags)); - fputs(fh, "\n"); - fputs(fh, ftos(self.superspec_flags)); - fputs(fh, "\n"); - fputs(fh, self.superspec_itemfilter); - fputs(fh, "\n"); - fclose(fh); - } -} - -void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel) -{ - sprint(_to, strcat(_con_title, _msg)); - - if(_to.superspec_flags & SSF_SILENT) - return; - - if(_spamlevel > 1) - if (!(_to.superspec_flags & SSF_VERBOSE)) - return; - - centerprint(_to, strcat(_center_title, _msg)); -} - -float superspec_filteritem(entity _for, entity _item) -{ - float i; - - if(_for.superspec_itemfilter == "") - return true; - - if(_for.superspec_itemfilter == "") - return true; - - float l = tokenize_console(_for.superspec_itemfilter); - for(i = 0; i < l; ++i) - { - if(argv(i) == _item.classname) - return true; - } - - return false; -} - -MUTATOR_HOOKFUNCTION(superspec, ItemTouch) -{SELFPARAM(); - entity _item = self; - - entity e; - FOR_EACH_SPEC(e) - { - setself(e); - if(self.superspec_flags & SSF_ITEMMSG) - if(superspec_filteritem(self, _item)) - { - if(self.superspec_flags & SSF_VERBOSE) - superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n", other.netname, _item.netname), 1); - else - superspec_msg("", "", self, sprintf("Player %s^7 just picked up ^3%s\n^8(%s^8)\n", other.netname, _item.netname, _item.classname), 1); - if((self.autospec_flags & ASF_SSIM) && self.enemy != other) - { - superspec_Spectate(other); - - setself(this); - return MUT_ITEMTOUCH_CONTINUE; - } - } - - if((self.autospec_flags & ASF_SHIELD && _item.invincible_finished) || - (self.autospec_flags & ASF_STRENGTH && _item.strength_finished) || - (self.autospec_flags & ASF_MEGA_AR && _item.itemdef == ITEM_ArmorMega) || - (self.autospec_flags & ASF_MEGA_HP && _item.itemdef == ITEM_HealthMega) || - (self.autospec_flags & ASF_FLAG_GRAB && _item.classname == "item_flag_team")) - { - - if((self.enemy != other) || IS_OBSERVER(self)) - { - if(self.autospec_flags & ASF_OBSERVER_ONLY && !IS_OBSERVER(self)) - { - if(self.superspec_flags & SSF_VERBOSE) - superspec_msg("", "", self, sprintf("^8Ignored that ^7%s^8 grabbed %s^8 since the observer_only option is ON\n", other.netname, _item.netname), 2); - } - else - { - if(self.autospec_flags & ASF_SHOWWHAT) - superspec_msg("", "", self, sprintf("^7Following %s^7 due to picking up %s\n", other.netname, _item.netname), 2); - - superspec_Spectate(other); - } - } - } - } - - setself(this); - - return MUT_ITEMTOUCH_CONTINUE; -} - -MUTATOR_HOOKFUNCTION(superspec, SV_ParseClientCommand) -{SELFPARAM(); -#define OPTIONINFO(flag,var,test,text,long,short) \ - var = strcat(var, ((flag & test) ? "^2[ON] ^7" : "^1[OFF] ^7")); \ - var = strcat(var, text," ^7(^3 ", long, "^7 | ^3", short, " ^7)\n") - - if(MUTATOR_RETURNVALUE) // command was already handled? - return false; - - if(IS_PLAYER(self)) - return false; - - if(cmd_name == "superspec_itemfilter") - { - if(argv(1) == "help") - { - string _aspeco; - _aspeco = "^7 superspec_itemfilter ^3\"item_classname1 item_classname2\"^7 only show thise items when ^2superspec ^3item_message^7 is on\n"; - _aspeco = strcat(_aspeco, "^3 clear^7 Remove the filter (show all pickups)\n"); - _aspeco = strcat(_aspeco, "^3 show ^7 Display current filter\n"); - superspec_msg("^3superspec_itemfilter help:\n\n\n", "\n^3superspec_itemfilter help:\n", self, _aspeco, 1); - } - else if(argv(1) == "clear") - { - if(self.superspec_itemfilter != "") - strunzone(self.superspec_itemfilter); - - self.superspec_itemfilter = ""; - } - else if(argv(1) == "show" || argv(1) == "") - { - if(self.superspec_itemfilter == "") - { - superspec_msg("^3superspec_itemfilter^7 is ^1not^7 set", "\n^3superspec_itemfilter^7 is ^1not^7 set\n", self, "", 1); - return true; - } - float i; - float l = tokenize_console(self.superspec_itemfilter); - string _msg = ""; - for(i = 0; i < l; ++i) - _msg = strcat(_msg, "^3#", ftos(i), " ^7", argv(i), "\n"); - //_msg = sprintf("^3#%d^7 %s\n%s", i, _msg, argv(i)); - - _msg = strcat(_msg,"\n"); - - superspec_msg("^3superspec_itemfilter is:\n\n\n", "\n^3superspec_itemfilter is:\n", self, _msg, 1); - } - else - { - if(self.superspec_itemfilter != "") - strunzone(self.superspec_itemfilter); - - self.superspec_itemfilter = strzone(argv(1)); - } - - return true; - } - - if(cmd_name == "superspec") - { - string _aspeco; - - if(cmd_argc > 1) - { - float i, _bits = 0, _start = 1; - if(argv(1) == "help") - { - _aspeco = "use cmd superspec [option] [on|off] to set options\n\n"; - _aspeco = strcat(_aspeco, "^3 silent ^7(short^5 si^7) supresses ALL messages from superspectate.\n"); - _aspeco = strcat(_aspeco, "^3 verbose ^7(short^5 ve^7) makes superspectate print some additional information.\n"); - _aspeco = strcat(_aspeco, "^3 item_message ^7(short^5 im^7) makes superspectate print items that were picked up.\n"); - _aspeco = strcat(_aspeco, "^7 Use cmd superspec_itemfilter \"item_class1 item_class2\" to set up a filter of what to show with ^3item_message.\n"); - superspec_msg("^2Available Super Spectate ^3options:\n\n\n", "\n^2Available Super Spectate ^3options:\n", self, _aspeco, 1); - return true; - } - - if(argv(1) == "clear") - { - self.superspec_flags = 0; - _start = 2; - } - - for(i = _start; i < cmd_argc; ++i) - { - if(argv(i) == "on" || argv(i) == "1") - { - self.superspec_flags |= _bits; - _bits = 0; - } - else if(argv(i) == "off" || argv(i) == "0") - { - if(_start == 1) - self.superspec_flags &= ~_bits; - - _bits = 0; - } - else - { - if((argv(i) == "silent") || (argv(i) == "si")) _bits |= SSF_SILENT ; - if((argv(i) == "verbose") || (argv(i) == "ve")) _bits |= SSF_VERBOSE; - if((argv(i) == "item_message") || (argv(i) == "im")) _bits |= SSF_ITEMMSG; - } - } - } - - _aspeco = ""; - OPTIONINFO(self.superspec_flags, _aspeco, SSF_SILENT, "Silent", "silent", "si"); - OPTIONINFO(self.superspec_flags, _aspeco, SSF_VERBOSE, "Verbose", "verbose", "ve"); - OPTIONINFO(self.superspec_flags, _aspeco, SSF_ITEMMSG, "Item pickup messages", "item_message", "im"); - - superspec_msg("^3Current Super Spectate options are:\n\n\n\n\n", "\n^3Current Super Spectate options are:\n", self, _aspeco, 1); - - return true; - } - -///////////////////// - - if(cmd_name == "autospec") - { - string _aspeco; - if(cmd_argc > 1) - { - if(argv(1) == "help") - { - _aspeco = "use cmd autospec [option] [on|off] to set options\n\n"; - _aspeco = strcat(_aspeco, "^3 strength ^7(short^5 st^7) for automatic spectate on strength powerup\n"); - _aspeco = strcat(_aspeco, "^3 shield ^7(short^5 sh^7) for automatic spectate on shield powerup\n"); - _aspeco = strcat(_aspeco, "^3 mega_health ^7(short^5 mh^7) for automatic spectate on mega health\n"); - _aspeco = strcat(_aspeco, "^3 mega_armor ^7(short^5 ma^7) for automatic spectate on mega armor\n"); - _aspeco = strcat(_aspeco, "^3 flag_grab ^7(short^5 fg^7) for automatic spectate on CTF flag grab\n"); - _aspeco = strcat(_aspeco, "^3 observer_only ^7(short^5 oo^7) for automatic spectate only if in observer mode\n"); - _aspeco = strcat(_aspeco, "^3 show_what ^7(short^5 sw^7) to display what event triggered autospectate\n"); - _aspeco = strcat(_aspeco, "^3 item_msg ^7(short^5 im^7) to autospec when item_message in superspectate is triggered\n"); - _aspeco = strcat(_aspeco, "^3 followkiller ^7(short ^5fk^7) to autospec the killer/off\n"); - _aspeco = strcat(_aspeco, "^3 all ^7(short ^5aa^7) to turn everything on/off\n"); - superspec_msg("^2Available Auto Spectate ^3options:\n\n\n", "\n^2Available Auto Spectate ^3options:\n", self, _aspeco, 1); - return true; - } - - float i, _bits = 0, _start = 1; - if(argv(1) == "clear") - { - self.autospec_flags = 0; - _start = 2; - } - - for(i = _start; i < cmd_argc; ++i) - { - if(argv(i) == "on" || argv(i) == "1") - { - self.autospec_flags |= _bits; - _bits = 0; - } - else if(argv(i) == "off" || argv(i) == "0") - { - if(_start == 1) - self.autospec_flags &= ~_bits; - - _bits = 0; - } - else - { - if((argv(i) == "strength") || (argv(i) == "st")) _bits |= ASF_STRENGTH; - if((argv(i) == "shield") || (argv(i) == "sh")) _bits |= ASF_SHIELD; - if((argv(i) == "mega_health") || (argv(i) == "mh")) _bits |= ASF_MEGA_HP; - if((argv(i) == "mega_armor") || (argv(i) == "ma")) _bits |= ASF_MEGA_AR; - if((argv(i) == "flag_grab") || (argv(i) == "fg")) _bits |= ASF_FLAG_GRAB; - if((argv(i) == "observer_only") || (argv(i) == "oo")) _bits |= ASF_OBSERVER_ONLY; - if((argv(i) == "show_what") || (argv(i) == "sw")) _bits |= ASF_SHOWWHAT; - if((argv(i) == "item_msg") || (argv(i) == "im")) _bits |= ASF_SSIM; - if((argv(i) == "followkiller") || (argv(i) == "fk")) _bits |= ASF_FOLLOWKILLER; - if((argv(i) == "all") || (argv(i) == "aa")) _bits |= ASF_ALL; - } - } - } - - _aspeco = ""; - OPTIONINFO(self.autospec_flags, _aspeco, ASF_STRENGTH, "Strength", "strength", "st"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHIELD, "Shield", "shield", "sh"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_HP, "Mega Health", "mega_health", "mh"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_MEGA_AR, "Mega Armor", "mega_armor", "ma"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_FLAG_GRAB, "Flag grab", "flag_grab","fg"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_OBSERVER_ONLY, "Only switch if observer", "observer_only", "oo"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_SHOWWHAT, "Show what item triggered spectate", "show_what", "sw"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_SSIM, "Switch on superspec item message", "item_msg", "im"); - OPTIONINFO(self.autospec_flags, _aspeco, ASF_FOLLOWKILLER, "Followkiller", "followkiller", "fk"); - - superspec_msg("^3Current auto spectate options are:\n\n\n\n\n", "\n^3Current auto spectate options are:\n", self, _aspeco, 1); - return true; - } - - if(cmd_name == "followpowerup") - { - entity _player; - FOR_EACH_PLAYER(_player) - { - if(_player.strength_finished > time || _player.invincible_finished > time) - return superspec_Spectate(_player); - } - - superspec_msg("", "", self, "No active powerup\n", 1); - return true; - } - - if(cmd_name == "followstrength") - { - entity _player; - FOR_EACH_PLAYER(_player) - { - if(_player.strength_finished > time) - return superspec_Spectate(_player); - } - - superspec_msg("", "", self, "No active Strength\n", 1); - return true; - } - - if(cmd_name == "followshield") - { - entity _player; - FOR_EACH_PLAYER(_player) - { - if(_player.invincible_finished > time) - return superspec_Spectate(_player); - } - - superspec_msg("", "", self, "No active Shield\n", 1); - return true; - } - - return false; -#undef OPTIONINFO -} - -MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":SS"); - return 0; -} - -MUTATOR_HOOKFUNCTION(superspec, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Super Spectators"); - return 0; -} - -void superspec_hello() -{SELFPARAM(); - if(self.enemy.crypto_idfp == "") - Send_Notification(NOTIF_ONE_ONLY, self.enemy, MSG_INFO, INFO_SUPERSPEC_MISSING_UID); - - remove(self); -} - -MUTATOR_HOOKFUNCTION(superspec, ClientConnect) -{SELFPARAM(); - if(!IS_REAL_CLIENT(self)) - return false; - - string fn = "superspec-local.options"; - float fh; - - self.superspec_flags = SSF_VERBOSE; - self.superspec_itemfilter = ""; - - entity _hello = spawn(); - _hello.enemy = self; - _hello.think = superspec_hello; - _hello.nextthink = time + 5; - - if (!_ISLOCAL) - { - if(self.crypto_idfp == "") - return false; - - fn = sprintf("superspec-%s.options", uri_escape(self.crypto_idfp)); - } - - fh = fopen(fn, FILE_READ); - if(fh < 0) - { - LOG_TRACE("^1ERROR: ^7 superspec can not open ", fn, " for reading.\n"); - } - else - { - string _magic = fgets(fh); - if(_magic != _SSMAGIX) - { - LOG_TRACE("^1ERROR^7 While reading superspec options file: unknown magic\n"); - } - else - { - self.autospec_flags = stof(fgets(fh)); - self.superspec_flags = stof(fgets(fh)); - self.superspec_itemfilter = strzone(fgets(fh)); - } - fclose(fh); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(superspec, PlayerDies) -{SELFPARAM(); - entity e; - FOR_EACH_SPEC(e) - { - setself(e); - if(self.autospec_flags & ASF_FOLLOWKILLER && IS_PLAYER(frag_attacker) && self.enemy == this) - { - if(self.autospec_flags & ASF_SHOWWHAT) - superspec_msg("", "", self, sprintf("^7Following %s^7 due to followkiller\n", frag_attacker.netname), 2); - - superspec_Spectate(frag_attacker); - } - } - - setself(this); - return false; -} - -MUTATOR_HOOKFUNCTION(superspec, ClientDisconnect) -{ - superspec_save_client_conf(); - return false; -} diff --git a/qcsrc/server/mutators/mutator_touchexplode.qc b/qcsrc/server/mutators/mutator_touchexplode.qc deleted file mode 100644 index ba90bedbb..000000000 --- a/qcsrc/server/mutators/mutator_touchexplode.qc +++ /dev/null @@ -1,43 +0,0 @@ -#include "mutator.qh" - -REGISTER_MUTATOR(touchexplode, cvar("g_touchexplode")); - -.float touchexplode_time; - -void PlayerTouchExplode(entity p1, entity p2) -{SELFPARAM(); - vector org = (p1.origin + p2.origin) * 0.5; - org.z += (p1.mins.z + p2.mins.z) * 0.5; - - sound(self, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM); - Send_Effect(EFFECT_EXPLOSION_SMALL, org, '0 0 0', 1); - - entity e = spawn(); - setorigin(e, org); - RadiusDamage(e, world, autocvar_g_touchexplode_damage, autocvar_g_touchexplode_edgedamage, autocvar_g_touchexplode_radius, world, world, autocvar_g_touchexplode_force, DEATH_TOUCHEXPLODE.m_id, world); - remove(e); -} - -MUTATOR_HOOKFUNCTION(touchexplode, PlayerPreThink) -{SELFPARAM(); - if(time > self.touchexplode_time) - if(!gameover) - if(!self.frozen) - if(IS_PLAYER(self)) - if(self.deadflag == DEAD_NO) - if (!IS_INDEPENDENT_PLAYER(self)) - FOR_EACH_PLAYER(other) if(self != other) - { - if(time > other.touchexplode_time) - if(!other.frozen) - if(other.deadflag == DEAD_NO) - if (!IS_INDEPENDENT_PLAYER(other)) - if(boxesoverlap(self.absmin, self.absmax, other.absmin, other.absmax)) - { - PlayerTouchExplode(self, other); - self.touchexplode_time = other.touchexplode_time = time + 0.2; - } - } - - return false; -} diff --git a/qcsrc/server/mutators/mutator_vampire.qc b/qcsrc/server/mutators/mutator_vampire.qc deleted file mode 100644 index d06b0eed5..000000000 --- a/qcsrc/server/mutators/mutator_vampire.qc +++ /dev/null @@ -1,28 +0,0 @@ -#include "mutator.qh" - -REGISTER_MUTATOR(vampire, cvar("g_vampire") && !cvar("g_instagib")); - -MUTATOR_HOOKFUNCTION(vampire, PlayerDamage_SplitHealthArmor) -{ - if(time >= frag_target.spawnshieldtime) - if(frag_target != frag_attacker) - if(frag_target.deadflag == DEAD_NO) - { - frag_attacker.health += bound(0, damage_take, frag_target.health); - frag_attacker.health = bound(0, frag_attacker.health, autocvar_g_balance_health_limit); - } - - return false; -} - -MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsString) -{ - ret_string = strcat(ret_string, ":Vampire"); - return 0; -} - -MUTATOR_HOOKFUNCTION(vampire, BuildMutatorsPrettyString) -{ - ret_string = strcat(ret_string, ", Vampire"); - return 0; -} diff --git a/qcsrc/server/mutators/mutator_vampirehook.qc b/qcsrc/server/mutators/mutator_vampirehook.qc deleted file mode 100644 index 3d88a9015..000000000 --- a/qcsrc/server/mutators/mutator_vampirehook.qc +++ /dev/null @@ -1,36 +0,0 @@ -REGISTER_MUTATOR(vh, cvar("g_vampirehook")); - -bool autocvar_g_vampirehook_teamheal; -float autocvar_g_vampirehook_damage; -float autocvar_g_vampirehook_damagerate; -float autocvar_g_vampirehook_health_steal; - -.float last_dmg; - -MUTATOR_HOOKFUNCTION(vh, GrappleHookThink) -{SELFPARAM(); - entity dmgent = ((SAME_TEAM(self.owner, self.aiment) && autocvar_g_vampirehook_teamheal) ? self.owner : self.aiment); - - if(IS_PLAYER(self.aiment)) - if(self.last_dmg < time) - if(!self.aiment.frozen) - if(time >= game_starttime) - if(DIFF_TEAM(self.owner, self.aiment) || autocvar_g_vampirehook_teamheal) - if(self.aiment.health > 0) - if(autocvar_g_vampirehook_damage) - { - self.last_dmg = time + autocvar_g_vampirehook_damagerate; - self.owner.damage_dealt += autocvar_g_vampirehook_damage; - Damage(dmgent, self, self.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, self.origin, '0 0 0'); - if(SAME_TEAM(self.owner, self.aiment)) - self.aiment.health = min(self.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); - else - self.owner.health = min(self.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); - - if(dmgent == self.owner) - dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?! - } - - return false; -} - diff --git a/qcsrc/server/mutators/mutators_include.qc b/qcsrc/server/mutators/mutators_include.qc deleted file mode 100644 index d2ea4688f..000000000 --- a/qcsrc/server/mutators/mutators_include.qc +++ /dev/null @@ -1,117 +0,0 @@ -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include "../../lib/warpzone/anglestransform.qh" - #include "../../lib/warpzone/common.qh" - #include "../../lib/warpzone/util_server.qh" - #include "../../lib/warpzone/server.qh" - #include "../../common/constants.qh" - #include "../../common/stats.qh" - #include "../../common/teams.qh" - #include "../../common/util.qh" - #include "../../common/nades/all.qh" - #include "../../common/buffs/all.qh" - #include "../../common/command/markup.qh" - #include "../../common/command/rpn.qh" - #include "../../common/command/generic.qh" - #include "../../common/command/command.qh" - #include "../../common/net_notice.qh" - #include "../../common/animdecide.qh" - #include "../../common/monsters/all.qh" - #include "../../common/monsters/sv_monsters.qh" - #include "../../common/monsters/spawn.qh" - #include "../../common/weapons/config.qh" - #include "../../common/weapons/all.qh" - #include "../weapons/accuracy.qh" - #include "../weapons/common.qh" - #include "../weapons/csqcprojectile.qh" - #include "../weapons/hitplot.qh" - #include "../weapons/selection.qh" - #include "../weapons/spawning.qh" - #include "../weapons/throwing.qh" - #include "../weapons/tracing.qh" - #include "../weapons/weaponstats.qh" - #include "../weapons/weaponsystem.qh" - #include "../t_items.qh" - #include "../autocvars.qh" - #include "../constants.qh" - #include "../defs.qh" - #include "../../common/notifications.qh" - #include "../../common/deathtypes/all.qh" - #include "mutators_include.qh" - #include "../../common/turrets/sv_turrets.qh" - #include "../../common/vehicles/all.qh" - #include "../campaign.qh" - #include "../../common/campaign_common.qh" - #include "../../common/mapinfo.qh" - #include "../command/common.qh" - #include "../command/banning.qh" - #include "../command/radarmap.qh" - #include "../command/vote.qh" - #include "../command/getreplies.qh" - #include "../command/cmd.qh" - #include "../command/sv_cmd.qh" - #include "../../common/csqcmodel_settings.qh" - #include "../../lib/csqcmodel/common.qh" - #include "../../lib/csqcmodel/sv_model.qh" - #include "../anticheat.qh" - #include "../cheats.qh" - #include "../../common/playerstats.qh" - #include "../portals.qh" - #include "../g_hook.qh" - #include "../scores.qh" - #include "../spawnpoints.qh" - #include "../mapvoting.qh" - #include "../ipban.qh" - #include "../race.qh" - #include "../antilag.qh" - #include "../playerdemo.qh" - #include "../round_handler.qh" - #include "../item_key.qh" - #include "../pathlib/pathlib.qh" - #include "../../common/vehicles/all.qh" -#endif - -#include "../../common/mutators/base.qh" - -#include "gamemode_assault.qc" -#include "gamemode_ca.qc" -#include "gamemode_ctf.qc" -#include "gamemode_cts.qc" -#include "gamemode_deathmatch.qc" -#include "gamemode_domination.qc" -#include "gamemode_freezetag.qc" -#include "gamemode_invasion.qc" -#include "gamemode_keepaway.qc" -#include "gamemode_keyhunt.qc" -#include "gamemode_lms.qc" -#include "gamemode_onslaught.qc" -#include "gamemode_race.qc" -#include "gamemode_tdm.qc" - -#include "mutator_bloodloss.qc" -#include "mutator_breakablehook.qc" -#include "mutator_buffs.qc" -#include "mutator_campcheck.qc" -#include "mutator_dodging.qc" -#include "mutator_hook.qc" -#include "mutator_invincibleproj.qc" -#include "mutator_melee_only.qc" -#include "mutator_midair.qc" -#include "mutator_multijump.qc" -#include "mutator_nades.qc" -#include "mutator_new_toys.qc" -#include "mutator_nix.qc" -#include "mutator_overkill.qc" -#include "mutator_physical_items.qc" -#include "mutator_pinata.qc" -#include "mutator_random_gravity.qc" -#include "mutator_rocketflying.qc" -#include "mutator_rocketminsta.qc" -#include "mutator_spawn_near_teammate.qc" -#include "mutator_superspec.qc" -#include "mutator_touchexplode.qc" -#include "mutator_vampirehook.qc" -#include "mutator_vampire.qc" - -#include "sandbox.qc" diff --git a/qcsrc/server/mutators/mutators_include.qh b/qcsrc/server/mutators/mutators_include.qh deleted file mode 100644 index fa91a83f3..000000000 --- a/qcsrc/server/mutators/mutators_include.qh +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef MUTATORS_INCLUDE_H -#define MUTATORS_INCLUDE_H - -#include "../../common/mutators/base.qh" - -#include "gamemode_assault.qh" -#include "gamemode_ca.qh" -#include "gamemode_ctf.qh" -#include "gamemode_cts.qh" -#include "gamemode_domination.qh" -#include "gamemode_invasion.qh" -#include "gamemode_keepaway.qh" -#include "gamemode_keyhunt.qh" -#include "gamemode_lms.qh" -#include "gamemode_onslaught.qh" -#include "gamemode_race.qh" - -#include "mutator_buffs.qh" -#include "mutator_dodging.qh" -#include "mutator_nades.qh" -#include "mutator_overkill.qh" -#endif diff --git a/qcsrc/server/mutators/sandbox.qc b/qcsrc/server/mutators/sandbox.qc deleted file mode 100644 index c61199510..000000000 --- a/qcsrc/server/mutators/sandbox.qc +++ /dev/null @@ -1,834 +0,0 @@ -#include "mutator.qh" - -float autosave_time; -void sandbox_Database_Load(); - -REGISTER_MUTATOR(sandbox, cvar("g_sandbox")) -{ - MUTATOR_ONADD - { - autosave_time = time + autocvar_g_sandbox_storage_autosave; // don't save the first server frame - if(autocvar_g_sandbox_storage_autoload) - sandbox_Database_Load(); - } - - MUTATOR_ONROLLBACK_OR_REMOVE - { - // nothing to roll back - } - - MUTATOR_ONREMOVE - { - // nothing to remove - } - - return false; -} - -const float MAX_STORAGE_ATTACHMENTS = 16; -float object_count; -.float object_flood; -.entity object_attach; -.string material; - -.float touch_timer; -void sandbox_ObjectFunction_Touch() -{SELFPARAM(); - // apply material impact effects - - if(!self.material) - return; - if(self.touch_timer > time) - return; // don't execute each frame - self.touch_timer = time + 0.1; - - // make particle count and sound volume depend on impact speed - float intensity; - intensity = vlen(self.velocity) + vlen(other.velocity); - if(intensity) // avoid divisions by 0 - intensity /= 2; // average the two velocities - if (!(intensity >= autocvar_g_sandbox_object_material_velocity_min)) - return; // impact not strong enough to do anything - // now offset intensity and apply it to the effects - intensity -= autocvar_g_sandbox_object_material_velocity_min; // start from minimum velocity, not actual velocity - intensity = bound(0, intensity * autocvar_g_sandbox_object_material_velocity_factor, 1); - - _sound(self, CH_TRIGGER, strcat("object/impact_", self.material, "_", ftos(ceil(random() * 5)) , ".wav"), VOL_BASE * intensity, ATTEN_NORM); - Send_Effect_(strcat("impact_", self.material), self.origin, '0 0 0', ceil(intensity * 10)); // allow a count from 1 to 10 -} - -void sandbox_ObjectFunction_Think() -{SELFPARAM(); - entity e; - - // decide if and how this object can be grabbed - if(autocvar_g_sandbox_readonly) - self.grab = 0; // no grabbing - else if(autocvar_g_sandbox_editor_free < 2 && self.crypto_idfp) - self.grab = 1; // owner only - else - self.grab = 3; // anyone - - // Object owner is stored via player UID, but we also need the owner as an entity (if the player is available on the server). - // Therefore, scan for all players, and update the owner as long as the player is present. We must always do this, - // since if the owning player disconnects, the object's owner should also be reset. - FOR_EACH_REALPLAYER(e) // bots can't have objects - { - if(self.crypto_idfp == e.crypto_idfp) - { - self.realowner = e; - break; - } - self.realowner = world; - } - - self.nextthink = time; - - CSQCMODEL_AUTOUPDATE(self); -} - -.float old_solid, old_movetype; -entity sandbox_ObjectEdit_Get(float permissions) -{SELFPARAM(); - // Returns the traced entity if the player can edit it, and world if not. - // If permissions if false, the object is returned regardless of editing rights. - // Attached objects are SOLID_NOT and do not get traced. - - crosshair_trace_plusvisibletriggers(self); - if(vlen(self.origin - trace_ent.origin) > autocvar_g_sandbox_editor_distance_edit) - return world; // out of trace range - if(trace_ent.classname != "object") - return world; // entity is not an object - if(!permissions) - return trace_ent; // don't check permissions, anyone can edit this object - if(trace_ent.crypto_idfp == "") - return trace_ent; // the player who spawned this object did not have an UID, so anyone can edit it - if (!(trace_ent.realowner != self && autocvar_g_sandbox_editor_free < 2)) - return trace_ent; // object does not belong to the player, and players can only edit their own objects on this server - return world; -} - -void sandbox_ObjectEdit_Scale(entity e, float f) -{ - e.scale = f; - if(e.scale) - { - e.scale = bound(autocvar_g_sandbox_object_scale_min, e.scale, autocvar_g_sandbox_object_scale_max); - _setmodel(e, e.model); // reset mins and maxs based on mesh - setsize(e, e.mins * e.scale, e.maxs * e.scale); // adapt bounding box size to model size - } -} - -void sandbox_ObjectAttach_Remove(entity e); -void sandbox_ObjectAttach_Set(entity e, entity parent, string s) -{ - // attaches e to parent on string s - - // we can't attach to an attachment, for obvious reasons - sandbox_ObjectAttach_Remove(e); - - e.old_solid = e.solid; // persist solidity - e.old_movetype = e.movetype; // persist physics - e.movetype = MOVETYPE_FOLLOW; - e.solid = SOLID_NOT; - e.takedamage = DAMAGE_NO; - - setattachment(e, parent, s); - e.owner = parent; -} - -void sandbox_ObjectAttach_Remove(entity e) -{ - // detaches any object attached to e - - entity head; - for(head = world; (head = find(head, classname, "object")); ) - { - if(head.owner == e) - { - vector org; - org = gettaginfo(head, 0); - setattachment(head, world, ""); - head.owner = world; - - // objects change origin and angles when detached, so apply previous position - setorigin(head, org); - head.angles = e.angles; // don't allow detached objects to spin or roll - - head.solid = head.old_solid; // restore persisted solidity - head.movetype = head.old_movetype; // restore persisted physics - head.takedamage = DAMAGE_AIM; - } - } -} - -entity sandbox_ObjectSpawn(float database) -{SELFPARAM(); - // spawn a new object with default properties - - entity e = spawn(); - e.classname = "object"; - e.takedamage = DAMAGE_AIM; - e.damageforcescale = 1; - e.solid = SOLID_BBOX; // SOLID_BSP would be best, but can lag the server badly - e.movetype = MOVETYPE_TOSS; - e.frame = 0; - e.skin = 0; - e.material = string_null; - e.touch = sandbox_ObjectFunction_Touch; - e.think = sandbox_ObjectFunction_Think; - e.nextthink = time; - //e.effects |= EF_SELECTABLE; // don't do this all the time, maybe just when editing objects? - - if(!database) - { - // set the object's owner via player UID - // if the player does not have an UID, the owner cannot be stored and his objects may be edited by anyone - if(self.crypto_idfp != "") - e.crypto_idfp = strzone(self.crypto_idfp); - else - print_to(self, "^1SANDBOX - WARNING: ^7You spawned an object, but lack a player UID. ^1Your objects are not secured and can be edited by any player!"); - - // set public object information - e.netname = strzone(self.netname); // name of the owner - e.message = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // creation time - e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); // last editing time - - // set origin and direction based on player position and view angle - makevectors(self.v_angle); - WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * autocvar_g_sandbox_editor_distance_spawn, MOVE_NORMAL, self); - setorigin(e, trace_endpos); - e.angles_y = self.v_angle.y; - } - - WITH(entity, self, e, CSQCMODEL_AUTOINIT(e)); - - object_count += 1; - return e; -} - -void sandbox_ObjectRemove(entity e) -{ - sandbox_ObjectAttach_Remove(e); // detach child objects - - // if the object being removed has been selected for attachment by a player, unset it - entity head; - FOR_EACH_REALPLAYER(head) // bots can't have objects - { - if(head.object_attach == e) - head.object_attach = world; - } - - if(e.material) { strunzone(e.material); e.material = string_null; } - if(e.crypto_idfp) { strunzone(e.crypto_idfp); e.crypto_idfp = string_null; } - if(e.netname) { strunzone(e.netname); e.netname = string_null; } - if(e.message) { strunzone(e.message); e.message = string_null; } - if(e.message2) { strunzone(e.message2); e.message2 = string_null; } - remove(e); - e = world; - - object_count -= 1; -} - -string port_string[MAX_STORAGE_ATTACHMENTS]; // fteqcc crashes if this isn't defined as a global - -string sandbox_ObjectPort_Save(entity e, float database) -{ - // save object properties, and return them as a string - float i = 0; - string s; - entity head; - - for(head = world; (head = find(head, classname, "object")); ) - { - // the main object needs to be first in the array [0] with attached objects following - float slot, physics, solidity; - if(head == e) // this is the main object, place it first - { - slot = 0; - solidity = head.solid; // applied solidity is normal solidity for children - physics = head.movetype; // applied physics are normal physics for parents - } - else if(head.owner == e) // child object, list them in order - { - i += 1; // children start from 1 - slot = i; - solidity = head.old_solid; // persisted solidity is normal solidity for children - physics = head.old_movetype; // persisted physics are normal physics for children - gettaginfo(head.owner, head.tag_index); // get the name of the tag our object is attached to, used further below - } - else - continue; - - // ---------------- OBJECT PROPERTY STORAGE: SAVE ---------------- - if(slot) - { - // properties stored only for child objects - if(gettaginfo_name) port_string[slot] = strcat(port_string[slot], "\"", gettaginfo_name, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none - } - else - { - // properties stored only for parent objects - if(database) - { - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.origin), " "); - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.angles), " "); - } - } - // properties stored for all objects - port_string[slot] = strcat(port_string[slot], "\"", head.model, "\" "); - port_string[slot] = strcat(port_string[slot], ftos(head.skin), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.alpha), " "); - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.colormod), " "); - port_string[slot] = strcat(port_string[slot], sprintf("\"%.9v\"", head.glowmod), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.frame), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.scale), " "); - port_string[slot] = strcat(port_string[slot], ftos(solidity), " "); - port_string[slot] = strcat(port_string[slot], ftos(physics), " "); - port_string[slot] = strcat(port_string[slot], ftos(head.damageforcescale), " "); - if(head.material) port_string[slot] = strcat(port_string[slot], "\"", head.material, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none - if(database) - { - // properties stored only for the database - if(head.crypto_idfp) port_string[slot] = strcat(port_string[slot], "\"", head.crypto_idfp, "\" "); else port_string[slot] = strcat(port_string[slot], "\"\" "); // none - port_string[slot] = strcat(port_string[slot], "\"", e.netname, "\" "); - port_string[slot] = strcat(port_string[slot], "\"", e.message, "\" "); - port_string[slot] = strcat(port_string[slot], "\"", e.message2, "\" "); - } - } - - // now apply the array to a simple string, with the ; symbol separating objects - s = ""; - for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) - { - if(port_string[i]) - s = strcat(s, port_string[i], "; "); - port_string[i] = string_null; // fully clear the string - } - - return s; -} - -entity sandbox_ObjectPort_Load(string s, float database) -{ - // load object properties, and spawn a new object with them - float n, i; - entity e = world, parent = world; - - // separate objects between the ; symbols - n = tokenizebyseparator(s, "; "); - for(i = 0; i < n; ++i) - port_string[i] = argv(i); - - // now separate and apply the properties of each object - for(i = 0; i < n; ++i) - { - float argv_num; - string tagname = string_null; - argv_num = 0; - tokenize_console(port_string[i]); - e = sandbox_ObjectSpawn(database); - - // ---------------- OBJECT PROPERTY STORAGE: LOAD ---------------- - if(i) - { - // properties stored only for child objects - if(argv(argv_num) != "") tagname = argv(argv_num); else tagname = string_null; ++argv_num; - } - else - { - // properties stored only for parent objects - if(database) - { - setorigin(e, stov(argv(argv_num))); ++argv_num; - e.angles = stov(argv(argv_num)); ++argv_num; - } - parent = e; // mark parent objects as such - } - // properties stored for all objects - _setmodel(e, argv(argv_num)); ++argv_num; - e.skin = stof(argv(argv_num)); ++argv_num; - e.alpha = stof(argv(argv_num)); ++argv_num; - e.colormod = stov(argv(argv_num)); ++argv_num; - e.glowmod = stov(argv(argv_num)); ++argv_num; - e.frame = stof(argv(argv_num)); ++argv_num; - sandbox_ObjectEdit_Scale(e, stof(argv(argv_num))); ++argv_num; - e.solid = e.old_solid = stof(argv(argv_num)); ++argv_num; - e.movetype = e.old_movetype = stof(argv(argv_num)); ++argv_num; - e.damageforcescale = stof(argv(argv_num)); ++argv_num; - if(e.material) strunzone(e.material); if(argv(argv_num) != "") e.material = strzone(argv(argv_num)); else e.material = string_null; ++argv_num; - if(database) - { - // properties stored only for the database - if(e.crypto_idfp) strunzone(e.crypto_idfp); if(argv(argv_num) != "") e.crypto_idfp = strzone(argv(argv_num)); else e.crypto_idfp = string_null; ++argv_num; - if(e.netname) strunzone(e.netname); e.netname = strzone(argv(argv_num)); ++argv_num; - if(e.message) strunzone(e.message); e.message = strzone(argv(argv_num)); ++argv_num; - if(e.message2) strunzone(e.message2); e.message2 = strzone(argv(argv_num)); ++argv_num; - } - - // attach last - if(i) - sandbox_ObjectAttach_Set(e, parent, tagname); - } - - for(i = 0; i <= MAX_STORAGE_ATTACHMENTS; ++i) - port_string[i] = string_null; // fully clear the string - - return e; -} - -void sandbox_Database_Save() -{ - // saves all objects to the database file - entity head; - string file_name; - float file_get; - - file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); - file_get = fopen(file_name, FILE_WRITE); - fputs(file_get, strcat("// sandbox storage \"", autocvar_g_sandbox_storage_name, "\" for map \"", GetMapname(), "\" last updated ", strftime(true, "%d-%m-%Y %H:%M:%S"))); - fputs(file_get, strcat(" containing ", ftos(object_count), " objects\n")); - - for(head = world; (head = find(head, classname, "object")); ) - { - // attached objects are persisted separately, ignore them here - if(head.owner != world) - continue; - - // use a line of text for each object, listing all properties - fputs(file_get, strcat(sandbox_ObjectPort_Save(head, true), "\n")); - } - fclose(file_get); -} - -void sandbox_Database_Load() -{ - // loads all objects from the database file - string file_read, file_name; - float file_get, i; - - file_name = strcat("sandbox/storage_", autocvar_g_sandbox_storage_name, "_", GetMapname(), ".txt"); - file_get = fopen(file_name, FILE_READ); - if(file_get < 0) - { - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7could not find storage file ^3", file_name, "^7, no objects were loaded\n")); - } - else - { - for (;;) - { - file_read = fgets(file_get); - if(file_read == "") - break; - if(substring(file_read, 0, 2) == "//") - continue; - if(substring(file_read, 0, 1) == "#") - continue; - - entity e; - e = sandbox_ObjectPort_Load(file_read, true); - - if(e.material) - { - // since objects are being loaded for the first time, precache material sounds for each - for (i = 1; i <= 5; i++) // 5 sounds in total - precache_sound(strcat("object/impact_", e.material, "_", ftos(i), ".wav")); - } - } - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7successfully loaded storage file ^3", file_name, "\n")); - } - fclose(file_get); -} - -MUTATOR_HOOKFUNCTION(sandbox, SV_ParseClientCommand) -{SELFPARAM(); - if(MUTATOR_RETURNVALUE) // command was already handled? - return false; - if(cmd_name == "g_sandbox") - { - if(autocvar_g_sandbox_readonly) - { - print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active, but in read-only mode. Sandbox commands cannot be used"); - return true; - } - if(cmd_argc < 2) - { - print_to(self, "^2SANDBOX - INFO: ^7Sandbox mode is active. For usage information, type 'sandbox help'"); - return true; - } - - switch(argv(1)) - { - entity e; - float i; - string s; - - // ---------------- COMMAND: HELP ---------------- - case "help": - print_to(self, "You can use the following sandbox commands:"); - print_to(self, "^7\"^2object_spawn ^3models/foo/bar.md3^7\" spawns a new object in front of the player, and gives it the specified model"); - print_to(self, "^7\"^2object_remove^7\" removes the object the player is looking at. Players can only remove their own objects"); - print_to(self, "^7\"^2object_duplicate ^3value^7\" duplicates the object, if the player has copying rights over the original"); - print_to(self, "^3copy value ^7- copies the properties of the object to the specified client cvar"); - print_to(self, "^3paste value ^7- spawns an object with the given properties. Properties or cvars must be specified as follows; eg1: \"0 1 2 ...\", eg2: \"$cl_cvar\""); - print_to(self, "^7\"^2object_attach ^3property value^7\" attaches one object to another. Players can only attach their own objects"); - print_to(self, "^3get ^7- selects the object you are facing as the object to be attached"); - print_to(self, "^3set value ^7- attaches the previously selected object to the object you are facing, on the specified bone"); - print_to(self, "^3remove ^7- detaches all objects from the object you are facing"); - print_to(self, "^7\"^2object_edit ^3property value^7\" edits the given property of the object. Players can only edit their own objects"); - print_to(self, "^3skin value ^7- changes the skin of the object"); - print_to(self, "^3alpha value ^7- sets object transparency"); - print_to(self, "^3colormod \"value_x value_y value_z\" ^7- main object color"); - print_to(self, "^3glowmod \"value_x value_y value_z\" ^7- glow object color"); - print_to(self, "^3frame value ^7- object animation frame, for self-animated models"); - print_to(self, "^3scale value ^7- changes object scale. 0.5 is half size and 2 is double size"); - print_to(self, "^3solidity value ^7- object collisions, 0 = non-solid, 1 = solid"); - print_to(self, "^3physics value ^7- object physics, 0 = static, 1 = movable, 2 = physical"); - print_to(self, "^3force value ^7- amount of force applied to objects that are shot"); - print_to(self, "^3material value ^7- sets the material of the object. Default materials are: metal, stone, wood, flesh"); - print_to(self, "^7\"^2object_claim^7\" sets the player as the owner of the object, if he has the right to edit it"); - print_to(self, "^7\"^2object_info ^3value^7\" shows public information about the object"); - print_to(self, "^3object ^7- prints general information about the object, such as owner and creation / editing date"); - print_to(self, "^3mesh ^7- prints information about the object's mesh, including skeletal bones"); - print_to(self, "^3attachments ^7- prints information about the object's attachments"); - print_to(self, "^7The ^1drag object ^7key can be used to grab and carry objects. Players can only grab their own objects"); - return true; - - // ---------------- COMMAND: OBJECT, SPAWN ---------------- - case "object_spawn": - if(time < self.object_flood) - { - print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object")); - return true; - } - self.object_flood = time + autocvar_g_sandbox_editor_flood; - if(object_count >= autocvar_g_sandbox_editor_maxobjects) - { - print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); - return true; - } - if(cmd_argc < 3) - { - print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object without specifying a model. Please specify the path to your model file after the 'object_spawn' command"); - return true; - } - if (!(fexists(argv(2)))) - { - print_to(self, "^1SANDBOX - WARNING: ^7Attempted to spawn an object with a non-existent model. Make sure the path to your model file is correct"); - return true; - } - - e = sandbox_ObjectSpawn(false); - _setmodel(e, argv(2)); - - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " spawned an object at origin ^3", vtos(e.origin), "\n")); - return true; - - // ---------------- COMMAND: OBJECT, REMOVE ---------------- - case "object_remove": - e = sandbox_ObjectEdit_Get(true); - if(e != world) - { - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " removed an object at origin ^3", vtos(e.origin), "\n")); - sandbox_ObjectRemove(e); - return true; - } - - print_to(self, "^1SANDBOX - WARNING: ^7Object could not be removed. Make sure you are facing an object that you have edit rights over"); - return true; - - // ---------------- COMMAND: OBJECT, DUPLICATE ---------------- - case "object_duplicate": - switch(argv(2)) - { - case "copy": - // copies customizable properties of the selected object to the clipboard cvar - e = sandbox_ObjectEdit_Get(autocvar_g_sandbox_editor_free); // can we copy objects we can't edit? - if(e != world) - { - s = sandbox_ObjectPort_Save(e, false); - s = strreplace("\"", "\\\"", s); - stuffcmd(self, strcat("set ", argv(3), " \"", s, "\"")); - - print_to(self, "^2SANDBOX - INFO: ^7Object copied to clipboard"); - return true; - } - print_to(self, "^1SANDBOX - WARNING: ^7Object could not be copied. Make sure you are facing an object that you have copy rights over"); - return true; - - case "paste": - // spawns a new object using the properties in the player's clipboard cvar - if(time < self.object_flood) - { - print_to(self, strcat("^1SANDBOX - WARNING: ^7Flood protection active. Please wait ^3", ftos(self.object_flood - time), " ^7seconds beofore spawning another object")); - return true; - } - self.object_flood = time + autocvar_g_sandbox_editor_flood; - if(argv(3) == "") // no object in clipboard - { - print_to(self, "^1SANDBOX - WARNING: ^7No object in clipboard. You must copy an object before you can paste it"); - return true; - } - if(object_count >= autocvar_g_sandbox_editor_maxobjects) - { - print_to(self, strcat("^1SANDBOX - WARNING: ^7Cannot spawn any more objects. Up to ^3", ftos(autocvar_g_sandbox_editor_maxobjects), " ^7objects may exist at a time")); - return true; - } - e = sandbox_ObjectPort_Load(argv(3), false); - - print_to(self, "^2SANDBOX - INFO: ^7Object pasted successfully"); - if(autocvar_g_sandbox_info > 0) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " pasted an object at origin ^3", vtos(e.origin), "\n")); - return true; - } - return true; - - // ---------------- COMMAND: OBJECT, ATTACH ---------------- - case "object_attach": - switch(argv(2)) - { - case "get": - // select e as the object as meant to be attached - e = sandbox_ObjectEdit_Get(true); - if(e != world) - { - self.object_attach = e; - print_to(self, "^2SANDBOX - INFO: ^7Object selected for attachment"); - return true; - } - print_to(self, "^1SANDBOX - WARNING: ^7Object could not be selected for attachment. Make sure you are facing an object that you have edit rights over"); - return true; - case "set": - if(self.object_attach == world) - { - print_to(self, "^1SANDBOX - WARNING: ^7No object selected for attachment. Please select an object to be attached first."); - return true; - } - - // attaches the previously selected object to e - e = sandbox_ObjectEdit_Get(true); - if(e != world) - { - sandbox_ObjectAttach_Set(self.object_attach, e, argv(3)); - self.object_attach = world; // object was attached, no longer keep it scheduled for attachment - print_to(self, "^2SANDBOX - INFO: ^7Object attached successfully"); - if(autocvar_g_sandbox_info > 1) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " attached objects at origin ^3", vtos(e.origin), "\n")); - return true; - } - print_to(self, "^1SANDBOX - WARNING: ^7Object could not be attached to the parent. Make sure you are facing an object that you have edit rights over"); - return true; - case "remove": - // removes e if it was attached - e = sandbox_ObjectEdit_Get(true); - if(e != world) - { - sandbox_ObjectAttach_Remove(e); - print_to(self, "^2SANDBOX - INFO: ^7Child objects detached successfully"); - if(autocvar_g_sandbox_info > 1) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " detached objects at origin ^3", vtos(e.origin), "\n")); - return true; - } - print_to(self, "^1SANDBOX - WARNING: ^7Child objects could not be detached. Make sure you are facing an object that you have edit rights over"); - return true; - } - return true; - - // ---------------- COMMAND: OBJECT, EDIT ---------------- - case "object_edit": - if(argv(2) == "") - { - print_to(self, "^1SANDBOX - WARNING: ^7Too few parameters. You must specify a property to edit"); - return true; - } - - e = sandbox_ObjectEdit_Get(true); - if(e != world) - { - switch(argv(2)) - { - case "skin": - e.skin = stof(argv(3)); - break; - case "alpha": - e.alpha = stof(argv(3)); - break; - case "color_main": - e.colormod = stov(argv(3)); - break; - case "color_glow": - e.glowmod = stov(argv(3)); - break; - case "frame": - e.frame = stof(argv(3)); - break; - case "scale": - sandbox_ObjectEdit_Scale(e, stof(argv(3))); - break; - case "solidity": - switch(argv(3)) - { - case "0": // non-solid - e.solid = SOLID_TRIGGER; - break; - case "1": // solid - e.solid = SOLID_BBOX; - break; - default: - break; - } - case "physics": - switch(argv(3)) - { - case "0": // static - e.movetype = MOVETYPE_NONE; - break; - case "1": // movable - e.movetype = MOVETYPE_TOSS; - break; - case "2": // physical - e.movetype = MOVETYPE_PHYSICS; - break; - default: - break; - } - break; - case "force": - e.damageforcescale = stof(argv(3)); - break; - case "material": - if(e.material) strunzone(e.material); - if(argv(3)) - { - for (i = 1; i <= 5; i++) // precache material sounds, 5 in total - precache_sound(strcat("object/impact_", argv(3), "_", ftos(i), ".wav")); - e.material = strzone(argv(3)); - } - else - e.material = string_null; // no material - break; - default: - print_to(self, "^1SANDBOX - WARNING: ^7Invalid object property. For usage information, type 'sandbox help'"); - return true; - } - - // update last editing time - if(e.message2) strunzone(e.message2); - e.message2 = strzone(strftime(true, "%d-%m-%Y %H:%M:%S")); - - if(autocvar_g_sandbox_info > 1) - LOG_INFO(strcat("^3SANDBOX - SERVER: ^7", self.netname, " edited property ^3", argv(2), " ^7of an object at origin ^3", vtos(e.origin), "\n")); - return true; - } - - print_to(self, "^1SANDBOX - WARNING: ^7Object could not be edited. Make sure you are facing an object that you have edit rights over"); - return true; - - // ---------------- COMMAND: OBJECT, CLAIM ---------------- - case "object_claim": - // if the player can edit an object but is not its owner, this can be used to claim that object - if(self.crypto_idfp == "") - { - print_to(self, "^1SANDBOX - WARNING: ^7You do not have a player UID, and cannot claim objects"); - return true; - } - e = sandbox_ObjectEdit_Get(true); - if(e != world) - { - // update the owner's name - // Do this before checking if you're already the owner and skipping if such, so we - // also update the player's nickname if he changed it (but has the same player UID) - if(e.netname != self.netname) - { - if(e.netname) strunzone(e.netname); - e.netname = strzone(self.netname); - print_to(self, "^2SANDBOX - INFO: ^7Object owner name updated"); - } - - if(e.crypto_idfp == self.crypto_idfp) - { - print_to(self, "^2SANDBOX - INFO: ^7Object is already yours, nothing to claim"); - return true; - } - - if(e.crypto_idfp) strunzone(e.crypto_idfp); - e.crypto_idfp = strzone(self.crypto_idfp); - - print_to(self, "^2SANDBOX - INFO: ^7Object claimed successfully"); - } - print_to(self, "^1SANDBOX - WARNING: ^7Object could not be claimed. Make sure you are facing an object that you have edit rights over"); - return true; - - // ---------------- COMMAND: OBJECT, INFO ---------------- - case "object_info": - // prints public information about the object to the player - e = sandbox_ObjectEdit_Get(false); - if(e != world) - { - switch(argv(2)) - { - case "object": - print_to(self, strcat("^2SANDBOX - INFO: ^7Object is owned by \"^7", e.netname, "^7\", created \"^3", e.message, "^7\", last edited \"^3", e.message2, "^7\"")); - return true; - case "mesh": - s = ""; - FOR_EACH_TAG(e) - s = strcat(s, "^7\"^5", gettaginfo_name, "^7\", "); - print_to(self, strcat("^2SANDBOX - INFO: ^7Object mesh is \"^3", e.model, "^7\" at animation frame ^3", ftos(e.frame), " ^7containing the following tags: ", s)); - return true; - case "attachments": - // this should show the same info as 'mesh' but for attachments - s = ""; - entity head; - i = 0; - for(head = world; (head = find(head, classname, "object")); ) - { - if(head.owner == e) - { - ++i; // start from 1 - gettaginfo(e, head.tag_index); - s = strcat(s, "^1attachment ", ftos(i), "^7 has mesh \"^3", head.model, "^7\" at animation frame ^3", ftos(head.frame)); - s = strcat(s, "^7 and is attached to bone \"^5", gettaginfo_name, "^7\", "); - } - } - if(i) // object contains attachments - print_to(self, strcat("^2SANDBOX - INFO: ^7Object contains the following ^1", ftos(i), "^7 attachment(s): ", s)); - else - print_to(self, "^2SANDBOX - INFO: ^7Object contains no attachments"); - return true; - } - } - print_to(self, "^1SANDBOX - WARNING: ^7No information could be found. Make sure you are facing an object"); - return true; - - // ---------------- COMMAND: DEFAULT ---------------- - default: - print_to(self, "Invalid command. For usage information, type 'sandbox help'"); - return true; - } - } - return false; -} - -MUTATOR_HOOKFUNCTION(sandbox, SV_StartFrame) -{ - if(!autocvar_g_sandbox_storage_autosave) - return false; - if(time < autosave_time) - return false; - autosave_time = time + autocvar_g_sandbox_storage_autosave; - - sandbox_Database_Save(); - - return true; -} - -MUTATOR_HOOKFUNCTION(sandbox, SetModname) -{ - modname = "Sandbox"; - return true; -} diff --git a/qcsrc/server/portals.qc b/qcsrc/server/portals.qc index ca6442f97..9b1d7194f 100644 --- a/qcsrc/server/portals.qc +++ b/qcsrc/server/portals.qc @@ -1,7 +1,7 @@ #include "portals.qh" #include "g_hook.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "../common/constants.qh" #include "../common/deathtypes/all.qh" #include "../common/notifications.qh" diff --git a/qcsrc/server/progs.inc b/qcsrc/server/progs.inc index 37946e38c..ab94c42bd 100644 --- a/qcsrc/server/progs.inc +++ b/qcsrc/server/progs.inc @@ -43,7 +43,7 @@ #include "command/all.qc" -#include "mutators/mutators_include.qc" +#include "mutators/all.qc" #include "pathlib/_all.inc" diff --git a/qcsrc/server/scores.qc b/qcsrc/server/scores.qc index 2279e649d..678baec95 100644 --- a/qcsrc/server/scores.qc +++ b/qcsrc/server/scores.qc @@ -1,7 +1,7 @@ #include "scores.qh" #include "command/common.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "../common/playerstats.qh" #include "../common/teams.qh" diff --git a/qcsrc/server/spawnpoints.qc b/qcsrc/server/spawnpoints.qc index 5ad02707b..a1abfa58f 100644 --- a/qcsrc/server/spawnpoints.qc +++ b/qcsrc/server/spawnpoints.qc @@ -1,6 +1,6 @@ #include "spawnpoints.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "g_world.qh" #include "race.qh" #include "../common/constants.qh" diff --git a/qcsrc/server/sv_main.qc b/qcsrc/server/sv_main.qc index 0a9d8f210..5fc3f771a 100644 --- a/qcsrc/server/sv_main.qc +++ b/qcsrc/server/sv_main.qc @@ -8,7 +8,7 @@ #include "command/common.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "weapons/csqcprojectile.qh" #include "../common/constants.qh" diff --git a/qcsrc/server/t_items.qc b/qcsrc/server/t_items.qc index a640cce10..8cde49cbd 100644 --- a/qcsrc/server/t_items.qc +++ b/qcsrc/server/t_items.qc @@ -7,7 +7,7 @@ #include "bot/bot.qh" #include "bot/waypoints.qh" - #include "mutators/mutators_include.qh" + #include "mutators/all.qh" #include "weapons/common.qh" #include "weapons/selection.qh" diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc index 253697905..2f2bf5454 100644 --- a/qcsrc/server/teamplay.qc +++ b/qcsrc/server/teamplay.qc @@ -9,7 +9,7 @@ #include "command/vote.qh" -#include "mutators/mutators_include.qh" +#include "mutators/all.qh" #include "../common/deathtypes/all.qh" #include "../common/gamemodes/all.qh" diff --git a/qcsrc/server/weapons/accuracy.qc b/qcsrc/server/weapons/accuracy.qc index baa95c649..7d1633f7f 100644 --- a/qcsrc/server/weapons/accuracy.qc +++ b/qcsrc/server/weapons/accuracy.qc @@ -1,6 +1,6 @@ #include "accuracy.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../../common/constants.qh" #include "../../common/teams.qh" #include "../../common/util.qh" diff --git a/qcsrc/server/weapons/spawning.qc b/qcsrc/server/weapons/spawning.qc index be2ca6b50..995154509 100644 --- a/qcsrc/server/weapons/spawning.qc +++ b/qcsrc/server/weapons/spawning.qc @@ -1,7 +1,7 @@ #include "spawning.qh" #include "weaponsystem.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../t_items.qh" #include "../../common/weapons/all.qh" diff --git a/qcsrc/server/weapons/throwing.qc b/qcsrc/server/weapons/throwing.qc index 4c6b99a50..9e1b3e4dc 100644 --- a/qcsrc/server/weapons/throwing.qc +++ b/qcsrc/server/weapons/throwing.qc @@ -1,7 +1,7 @@ #include "throwing.qh" #include "weaponsystem.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../t_items.qh" #include "../g_damage.qh" #include "../../common/items/item.qh" diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index dd7e2b599..8b0997bec 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -3,7 +3,7 @@ #include "selection.qh" #include "../command/common.qh" -#include "../mutators/mutators_include.qh" +#include "../mutators/all.qh" #include "../round_handler.qh" #include "../t_items.qh" #include "../../common/animdecide.qh"