- wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
- wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
- make
- - EXPECT=033546d32426e6409458fb39d0130f56
+ - EXPECT=de6a7d95ce65fb6c66558a93a9fb994f
- HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
| tee /dev/stderr
| grep '^:'
// {{{ #1: Blaster
set g_balance_blaster_primary_animtime 0.2
-set g_balance_blaster_primary_damage 25
+set g_balance_blaster_primary_damage 20
set g_balance_blaster_primary_delay 0
set g_balance_blaster_primary_edgedamage 12.5
set g_balance_blaster_primary_force 300
set g_balance_shotgun_secondary_alt_refire 1.2
set g_balance_shotgun_switchdelay_drop 0.2
set g_balance_shotgun_switchdelay_raise 0.2
-set g_balance_shotgun_weaponreplace ""
-set g_balance_shotgun_weaponstart 1
+set g_balance_shotgun_weaponreplace "shockwave"
+set g_balance_shotgun_weaponstart 0
set g_balance_shotgun_weaponstartoverride -1
set g_balance_shotgun_weaponthrowable 1
// }}}
set g_balance_machinegun_first 1
set g_balance_machinegun_first_ammo 1
set g_balance_machinegun_first_damage 14
-set g_balance_machinegun_first_force 5
+set g_balance_machinegun_first_force 3
set g_balance_machinegun_first_refire 0.125
set g_balance_machinegun_first_spread 0.03
set g_balance_machinegun_mode 1
set g_balance_machinegun_spread_min 0.02
set g_balance_machinegun_sustained_ammo 1
set g_balance_machinegun_sustained_damage 10
-set g_balance_machinegun_sustained_force 5
+set g_balance_machinegun_sustained_force 3
set g_balance_machinegun_sustained_refire 0.1
set g_balance_machinegun_sustained_spread 0.03
set g_balance_machinegun_switchdelay_drop 0.2
set g_balance_machinegun_switchdelay_raise 0.2
-set g_balance_machinegun_weaponreplace ""
+set g_balance_machinegun_weaponreplace "arc"
set g_balance_machinegun_weaponstart 0
set g_balance_machinegun_weaponstartoverride -1
set g_balance_machinegun_weaponthrowable 1
set g_balance_electro_secondary_speed_up 200
set g_balance_electro_secondary_speed_z 0
set g_balance_electro_secondary_spread 0
-set g_balance_electro_secondary_stick 1
+set g_balance_electro_secondary_stick 0
set g_balance_electro_secondary_touchexplode 1
set g_balance_electro_switchdelay_drop 0.2
set g_balance_electro_switchdelay_raise 0.2
set g_balance_crylink_primary_radius 80
set g_balance_crylink_primary_refire 0.7
set g_balance_crylink_primary_shots 6
-set g_balance_crylink_primary_speed 2000
+set g_balance_crylink_primary_speed 4000
set g_balance_crylink_primary_spread 0.08
set g_balance_crylink_reload_ammo 0
set g_balance_crylink_reload_time 2
set g_balance_crylink_secondary 1
-set g_balance_crylink_secondary_ammo 2
+set g_balance_crylink_secondary_ammo 3
set g_balance_crylink_secondary_animtime 0.2
set g_balance_crylink_secondary_bouncedamagefactor 0.5
set g_balance_crylink_secondary_bounces 0
-set g_balance_crylink_secondary_damage 10
-set g_balance_crylink_secondary_edgedamage 5
-set g_balance_crylink_secondary_force -250
+set g_balance_crylink_secondary_damage 50
+set g_balance_crylink_secondary_edgedamage 15
+set g_balance_crylink_secondary_force -400
set g_balance_crylink_secondary_joindelay 0
set g_balance_crylink_secondary_joinexplode 0
set g_balance_crylink_secondary_joinexplode_damage 0
set g_balance_crylink_secondary_middle_lifetime 5
set g_balance_crylink_secondary_other_fadetime 5
set g_balance_crylink_secondary_other_lifetime 5
-set g_balance_crylink_secondary_radius 100
-set g_balance_crylink_secondary_refire 0.7
-set g_balance_crylink_secondary_shots 5
+set g_balance_crylink_secondary_radius 70
+set g_balance_crylink_secondary_refire 0.8
+set g_balance_crylink_secondary_shots 1
set g_balance_crylink_secondary_speed 3000
-set g_balance_crylink_secondary_spread 0.01
+set g_balance_crylink_secondary_spread 0
set g_balance_crylink_secondary_spreadtype 1
set g_balance_crylink_switchdelay_drop 0.2
set g_balance_crylink_switchdelay_raise 0.2
set g_balance_vortex_primary_ammo 6
set g_balance_vortex_primary_animtime 0.4
set g_balance_vortex_primary_armorpierce 0
-set g_balance_vortex_primary_damage 70
+set g_balance_vortex_primary_damage 65
set g_balance_vortex_primary_damagefalloff_forcehalflife 0
set g_balance_vortex_primary_damagefalloff_halflife 0
set g_balance_vortex_primary_damagefalloff_maxdist 0
set g_balance_hagar_secondary_spread 0
set g_balance_hagar_switchdelay_drop 0.2
set g_balance_hagar_switchdelay_raise 0.2
-set g_balance_hagar_weaponreplace ""
+set g_balance_hagar_weaponreplace "0"
set g_balance_hagar_weaponstart 0
set g_balance_hagar_weaponstartoverride -1
set g_balance_hagar_weaponthrowable 1
set g_balance_vaporizer_weaponreplace ""
set g_balance_vaporizer_weaponstart 0
set g_balance_vaporizer_weaponstartoverride -1
-set g_balance_vaporizer_weaponthrowable 0
+set g_balance_vaporizer_weaponthrowable 1
// }}}
// {{{ #13: Grappling Hook
set g_balance_hook_primary_ammo 5
set g_balance_shockwave_switchdelay_drop 0.2
set g_balance_shockwave_switchdelay_raise 0.2
set g_balance_shockwave_weaponreplace ""
-set g_balance_shockwave_weaponstart 0
+set g_balance_shockwave_weaponstart 1
set g_balance_shockwave_weaponstartoverride -1
set g_balance_shockwave_weaponthrowable 0
// }}}
set g_balance_arc_burst_heat 5
set g_balance_arc_beam_maxangle 10
set g_balance_arc_beam_nonplayerdamage 80
-set g_balance_arc_beam_range 1000
+set g_balance_arc_beam_range 1250
set g_balance_arc_beam_refire 0.25
set g_balance_arc_beam_returnspeed 8
set g_balance_arc_beam_tightness 0.5
set g_balance_okrpc_weaponstartoverride 0
set g_balance_okrpc_weaponthrowable 0
// }}}
+// {{{ Overkill Shotgun
+set g_balance_okshotgun_primary_ammo 3
+set g_balance_okshotgun_primary_animtime 0.65
+set g_balance_okshotgun_primary_bot_range 512
+set g_balance_okshotgun_primary_bullets 10
+set g_balance_okshotgun_primary_damage 17
+set g_balance_okshotgun_primary_force 80
+set g_balance_okshotgun_primary_refire 0.75
+set g_balance_okshotgun_primary_solidpenetration 3.8
+set g_balance_okshotgun_primary_spread 0.07
+set g_balance_okshotgun_reload_ammo 24
+set g_balance_okshotgun_reload_time 2
+set g_balance_okshotgun_secondary_animtime 0.2
+set g_balance_okshotgun_secondary_damage 25
+set g_balance_okshotgun_secondary_delay 0
+set g_balance_okshotgun_secondary_edgedamage 12.5
+set g_balance_okshotgun_secondary_force 300
+set g_balance_okshotgun_secondary_lifetime 5
+set g_balance_okshotgun_secondary_radius 70
+set g_balance_okshotgun_secondary_refire 0.7
+set g_balance_okshotgun_secondary_refire_type 1
+set g_balance_okshotgun_secondary_shotangle 0
+set g_balance_okshotgun_secondary_speed 6000
+set g_balance_okshotgun_secondary_spread 0
+set g_balance_okshotgun_switchdelay_drop 0.2
+set g_balance_okshotgun_switchdelay_raise 0.2
+set g_balance_okshotgun_weaponreplace ""
+set g_balance_okshotgun_weaponstart 0
+set g_balance_okshotgun_weaponstartoverride -1
+set g_balance_okshotgun_weaponthrowable 1
+// }}}
+// {{{ Overkill Machine Gun
+set g_balance_okmachinegun_primary_ammo 1
+set g_balance_okmachinegun_primary_damage 25
+set g_balance_okmachinegun_primary_force 5
+set g_balance_okmachinegun_primary_refire 0.1
+set g_balance_okmachinegun_primary_solidpenetration 13.1
+set g_balance_okmachinegun_primary_spread_add 0.012
+set g_balance_okmachinegun_primary_spread_max 0.05
+set g_balance_okmachinegun_primary_spread_min 0
+set g_balance_okmachinegun_reload_ammo 30
+set g_balance_okmachinegun_reload_time 1.5
+set g_balance_okmachinegun_secondary_animtime 0.2
+set g_balance_okmachinegun_secondary_damage 25
+set g_balance_okmachinegun_secondary_delay 0
+set g_balance_okmachinegun_secondary_edgedamage 12.5
+set g_balance_okmachinegun_secondary_force 300
+set g_balance_okmachinegun_secondary_lifetime 5
+set g_balance_okmachinegun_secondary_radius 70
+set g_balance_okmachinegun_secondary_refire 0.7
+set g_balance_okmachinegun_secondary_refire_type 1
+set g_balance_okmachinegun_secondary_shotangle 0
+set g_balance_okmachinegun_secondary_speed 6000
+set g_balance_okmachinegun_secondary_spread 0
+set g_balance_okmachinegun_switchdelay_drop 0.2
+set g_balance_okmachinegun_switchdelay_raise 0.2
+set g_balance_okmachinegun_weaponreplace ""
+set g_balance_okmachinegun_weaponstart 0
+set g_balance_okmachinegun_weaponstartoverride -1
+set g_balance_okmachinegun_weaponthrowable 1
+// }}}
+// {{{ Overkill Nex
+set g_balance_oknex_charge 0
+set g_balance_oknex_charge_animlimit 0.5
+set g_balance_oknex_charge_limit 1
+set g_balance_oknex_charge_maxspeed 800
+set g_balance_oknex_charge_mindmg 40
+set g_balance_oknex_charge_minspeed 400
+set g_balance_oknex_charge_rate 0.6
+set g_balance_oknex_charge_rot_pause 0
+set g_balance_oknex_charge_rot_rate 0
+set g_balance_oknex_charge_shot_multiplier 0
+set g_balance_oknex_charge_start 0.5
+set g_balance_oknex_charge_velocity_rate 0
+set g_balance_oknex_primary_ammo 10
+set g_balance_oknex_primary_animtime 0.65
+set g_balance_oknex_primary_damage 100
+set g_balance_oknex_primary_damagefalloff_forcehalflife 0
+set g_balance_oknex_primary_damagefalloff_halflife 0
+set g_balance_oknex_primary_damagefalloff_maxdist 0
+set g_balance_oknex_primary_damagefalloff_mindist 0
+set g_balance_oknex_primary_force 500
+set g_balance_oknex_primary_refire 1
+set g_balance_oknex_reload_ammo 50
+set g_balance_oknex_reload_time 2
+set g_balance_oknex_secondary 2
+set g_balance_oknex_secondary_ammo 0
+set g_balance_oknex_secondary_animtime 0.2
+set g_balance_oknex_secondary_chargepool 0
+set g_balance_oknex_secondary_chargepool_pause_regen 1
+set g_balance_oknex_secondary_chargepool_regen 0.15
+set g_balance_oknex_secondary_damage 25
+set g_balance_oknex_secondary_damagefalloff_forcehalflife 0
+set g_balance_oknex_secondary_damagefalloff_halflife 0
+set g_balance_oknex_secondary_damagefalloff_maxdist 0
+set g_balance_oknex_secondary_damagefalloff_mindist 0
+set g_balance_oknex_secondary_force 300
+set g_balance_oknex_secondary_refire 0.7
+set g_balance_oknex_secondary_refire_type 1
+set g_balance_oknex_secondary_delay 0
+set g_balance_oknex_secondary_edgedamage 12.5
+set g_balance_oknex_secondary_lifetime 5
+set g_balance_oknex_secondary_radius 70
+set g_balance_oknex_secondary_shotangle 0
+set g_balance_oknex_secondary_speed 6000
+set g_balance_oknex_secondary_spread 0
+set g_balance_oknex_switchdelay_drop 0.2
+set g_balance_oknex_switchdelay_raise 0.2
+set g_balance_oknex_weaponreplace ""
+set g_balance_oknex_weaponstart 0
+set g_balance_oknex_weaponstartoverride -1
+set g_balance_oknex_weaponthrowable 1
+// }}}
set g_pickup_respawntime_weapon 10
set g_pickup_respawntime_superweapon 120
set g_pickup_respawntime_ammo 10
-set g_pickup_respawntime_initial_random 2
+set g_pickup_respawntime_initial_random 1
set g_pickup_respawntimejitter_short 0
set g_pickup_respawntimejitter_medium 0
set g_pickup_respawntimejitter_long 0
set g_balance_armor_regenstable 100
set g_balance_armor_rotstable 100
set g_balance_armor_limit 200
-set g_balance_armor_blockpercent 0.7
+set g_balance_armor_blockpercent 0.55
set g_balance_fuel_regen 0.1 "fuel regeneration (only applies if the player owns IT_FUEL_REGEN)"
set g_balance_fuel_regenlinear 0
set g_balance_pause_fuel_regen 2 // other than this, fuel uses the health regen counter
// {{{ powerups
set g_balance_powerup_invincible_takedamage 0.33 // only 1/3rd damage is taken
-set g_balance_powerup_invincible_takeforce 1
+set g_balance_powerup_invincible_takeforce 0.33
set g_balance_powerup_invincible_time 30
set g_balance_powerup_strength_damage 3
set g_balance_powerup_strength_force 3
set sv_vote_master_playerlimit 2 "Minimum number of players needed for a player to be allowed to vote for master"
set sv_vote_no_stops_vote 1 "Allow the vote caller to stop his own vote simply by voting no"
set sv_vote_singlecount 0 "set to 1 to count votes once after timeout or to 0 to count with every vote"
-set sv_vote_timeout 30 "a vote will timeout after this many seconds"
+set sv_vote_timeout 24 "a vote will timeout after this many seconds"
set sv_vote_wait 120 "a player can not call a vote again for this many seconds when his vote was not accepted"
set sv_vote_stop 15 "a player can not call a vote again for this many seconds when he stopped this vote (e.g. to correct it)"
set sv_vote_majority_factor 0.5 "What percentage of the PLAYERS constitute a majority? (Must be at least 0.5, recommended: 0.5)"
set g_onslaught 0 "Onslaught: take control points towards the enemy generator and then destroy it"
set g_onslaught_point_limit 1 "Onslaught point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
set g_onslaught_warmup 5
-set g_onslaught_round_timelimit 280
+set g_onslaught_round_timelimit 500
set g_onslaught_teleport_radius 200 "Allows teleporting from a control point to another"
set g_onslaught_teleport_wait 5 "Time before player can teleport again"
set g_onslaught_spawn_choose 1 "Allow players to choose the control point to be spawned at"
seta hud_panel_weapons_bg_border ""
seta hud_panel_weapons_bg_padding "0"
seta hud_panel_weapons_accuracy "0"
-seta hud_panel_weapons_label "1"
+seta hud_panel_weapons_label "2"
seta hud_panel_weapons_label_scale "0.3"
seta hud_panel_weapons_complainbubble "1"
seta hud_panel_weapons_complainbubble_padding "0"
// overkill
// ==========
set g_overkill 0 "internal cvar, to enable overkill, use `exec ruleset-overkill.cfg`"
+set g_overkill_weapons 0 "Whether to enable overkill weapons outside of overkill ruleset."
set g_overkill_powerups_replace 1
set g_overkill_itemwaypoints 1
// ==========
set g_new_toys 0 "Mutator 'New Toys': enable extra fun guns"
set g_new_toys_autoreplace 2 "0: never replace, 1: always auto replace guns by available new toys, 2: randomly auto replace guns by available new toys"
-set g_new_toys_use_pickupsound 1 "play the 'new toys, new toys!' roflsound when picking up a new toys weapon"
+set g_new_toys_use_pickupsound 0 "play the 'new toys, new toys!' roflsound when picking up a new toys weapon"
// =======
#include <client/mapvoting.qc>
#include <client/miscfunctions.qc>
#include <client/player_skeleton.qc>
+#include <client/resources.qc>
#include <client/shownames.qc>
#include <client/teamradar.qc>
#include <client/view.qc>
-#include <client/wall.qc>
#include <client/commands/_mod.inc>
#include <client/hud/_mod.inc>
#include <client/mapvoting.qh>
#include <client/miscfunctions.qh>
#include <client/player_skeleton.qh>
+#include <client/resources.qh>
#include <client/shownames.qh>
#include <client/teamradar.qh>
#include <client/view.qh>
-#include <client/wall.qh>
#include <client/commands/_mod.qh>
#include <client/hud/_mod.qh>
#include "announcer.qh"
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
#include <common/notifications/all.qh>
#include <common/stats.qh>
void Announcer_Time()
{
+ if(intermission)
+ return;
+
float timeleft;
if(warmup_stage)
{
#include "../autocvars.qh"
#include "../defs.qh"
#include <client/hud/_mod.qh>
+#include <client/hud/panel/quickmenu.qh>
+#include <client/hud/panel/radar.qh>
#include "../main.qh"
#include "../mapvoting.qh"
#include "../miscfunctions.qh"
-#include "../mutators/events.qh"
+#include <client/mutators/_mod.qh>
+
+#include <common/minigames/cl_minigames_hud.qh>
#include <common/mapinfo.qh>
}
}
-bool QuickMenu_IsOpened();
-void QuickMenu_Close();
-bool QuickMenu_Open(string mode, string submenu, string file);
-
-bool HUD_MinigameMenu_IsOpened();
-void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger);
-void HUD_MinigameMenu_Open();
-
-void HUD_Radar_Show_Maximized(bool doshow, bool clickable);
-
void LocalCommand_hud(int request, int argc)
{
TC(int, request); TC(int, argc);
void Cmd_Scoreboard_SetFields(int);
void Cmd_Scoreboard_Help();
+void ConsoleCommand_macro_init();
// used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file
void LocalCommand_macro_write_aliases(int fh);
#include "autocvars.qh"
#include "csqcmodel_hooks.qh"
#include "miscfunctions.qh"
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
#include "player_skeleton.qh"
#include "weapons/projectile.qh"
#include <common/animdecide.qh>
.float death_time;
.int modelflags;
-void CSQCModel_Hook_PreDraw(entity this, bool isplayer);
-
.bool isplayermodel;
// FEATURE: LOD
.int csqcmodel_traileffect;
void CSQCModel_Effects_Apply(entity this);
+
+void CSQCModel_Hook_PreDraw(entity this, bool isplayer);
#include <client/defs.qh>
#include <client/miscfunctions.qh>
+#include <client/view.qh>
#include "panel/scoreboard.qh"
#include "hud_config.qh"
#include "../mapvoting.qh"
#include <common/items/_mod.qh>
#include <common/mapinfo.qh>
#include <common/vehicles/all.qh>
+#include <common/vehicles/vehicle/bumblebee.qh>
#include <common/mutators/mutator/waypoints/all.qh>
#include <common/stats.qh>
#include <lib/csqcmodel/cl_player.qh>
-#include <server/mutators/mutator/gamemode_ctf.qh> // TODO: remove
+#include <lib/csqcmodel/cl_model.qh>
+#include <common/gamemodes/_mod.qh>
/*
==================
*/
-void CSQC_BUMBLE_GUN_HUD();
-
void HUD_Vehicle()
{
if(autocvar__hud_configure) return;
return true;
}
-entity CSQCModel_server2csqc(int i);
-void calc_followmodel_ofs(entity view);
void Hud_Dynamic_Frame()
{
vector ofs = '0 0 0';
vector hud_shift_current = '0 0 0';
vector hud_scale_center;
-float stringwidth_colors(string s, vector theSize);
-float stringwidth_nocolors(string s, vector theSize);
void HUD_Panel_DrawProgressBar(vector theOrigin, vector theSize, string pic, float length_ratio, bool vertical, float baralign, vector theColor, float theAlpha, int drawflag);
.int panel_showflags;
}
}
-void HUD_Panel_EnableMenu();
entity tab_panels[hud_panels_MAX];
entity tab_panel;
vector tab_panel_pos;
float tab_backward;
-void HUD_Panel_FirstInDrawQ(float id);
void reset_tab_panels()
{
for (int i = 0; i < hud_panels_COUNT; ++i)
void HUD_Configure_PostDraw();
float HUD_Panel_InputEvent(float bInputType, float nPrimary, float nSecondary);
+
+void HUD_Panel_EnableMenu();
+
+void HUD_Panel_FirstInDrawQ(float id);
#include "hud.qh"
#include "hud_config.qh"
-#include <client/mutators/events.qh>
+#include <client/mutators/_mod.qh>
#include <client/view.qh>
#include <common/t_items.qh>
#include <common/wepent.qh>
+#include <common/mutators/mutator/nades/nades.qh>
// Ammo (#1)
autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
}
-void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator
-
void DrawAmmoItem(vector myPos, vector mySize, int ammoType, bool isCurrent, bool isInfinite)
{
TC(bool, isCurrent); TC(bool, isInfinite);
return img_cur_msg[group_id];
}
-float stringwidth_colors(string s, vector theSize);
vector InfoMessages_drawstring(string s, vector pos, vector sz, float a, vector fontsize)
{
getWrappedLine_remaining = s;
#include <common/mapinfo.qh>
#include <common/ent_cs.qh>
#include <common/scores.qh>
-#include <server/mutators/mutator/gamemode_ctf.qh> // TODO: remove
+#include <common/gamemodes/_mod.qh>
// Mod icons (#10)
QuickMenu_Page_Command_Type[i] = 0;
}
-float QuickMenu_Page_Load(string target_submenu, float new_page);
-void QuickMenu_Default(string submenu);
bool QuickMenu_Open(string mode, string submenu, string file)
{
int fh = -1;
return (QuickMenu_Page_Entries > 0);
}
-void HUD_Quickmenu_PlayerListEntries(string cmd, int teamplayers, bool without_me);
bool HUD_Quickmenu_PlayerListEntries_Create(string cmd, int teamplayers, bool without_me)
{
TC(int, teamplayers); TC(bool, without_me);
bool QuickMenu_InputEvent(float bInputType, float nPrimary, float nSecondary);
bool QuickMenu_IsOpened();
void QuickMenu_Mouse();
+float QuickMenu_Page_Load(string target_submenu, float new_page);
+void QuickMenu_Default(string submenu);
+void HUD_Quickmenu_PlayerListEntries(string cmd, int teamplayers, bool without_me);
+void QuickMenu_Close();
+bool QuickMenu_Open(string mode, string submenu, string file);
#include <common/ent_cs.qh>
#include <common/mapinfo.qh>
#include <client/mapvoting.qh>
+#include <client/resources.qh>
#include <client/teamradar.qh>
#include <common/mutators/mutator/waypoints/all.qh>
IL_EACH(g_radaricons, it.teamradar_icon, {
if ( hud_panel_radar_mouse )
- if ( it.health >= 0 )
+ if ( GetResourceAmount(it, RESOURCE_HEALTH) >= 0 )
if ( it.team == myteam + 1 || gametype == MAPINFO_TYPE_RACE || !teamplay )
{
vector coord = teamradar_texcoord_to_2dcoord(teamradar_3dcoord_to_texcoord(it.origin));
#pragma once
#include "../panel.qh"
+
+void HUD_Radar_Show_Maximized(bool doshow, bool clickable);
#include <client/autocvars.qh>
#include <client/defs.qh>
+#include <client/main.qh>
#include <client/miscfunctions.qh>
#include "quickmenu.qh"
#include <common/ent_cs.qh>
bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
float autocvar_hud_panel_scoreboard_minwidth = 0.4;
-
-void drawstringright(vector, string, vector, vector, float, float);
-void drawstringcenter(vector, string, vector, vector, float, float);
-
// wrapper to put all possible scores titles through gettext
string TranslateScoresLabel(string l)
{
Cmd_Scoreboard_SetFields(0);
}
-float SetTeam(entity pl, float Team);
//float lastpnum;
void Scoreboard_UpdatePlayerTeams()
{
#include <common/effects/all.qh>
#include <common/effects/all.inc>
#include "hud/_mod.qh"
+#include "commands/cl_cmd.qh"
#include "mapvoting.qh"
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
#include "hud/panel/scoreboard.qh"
#include "hud/panel/quickmenu.qh"
#include "shownames.qh"
+#include "view.qh"
#include <common/t_items.qh>
-#include "wall.qh"
#include "weapons/projectile.qh"
#include <common/deathtypes/all.qh>
#include <common/items/_mod.qh>
// CSQC_Init : Called every time the CSQC code is initialized (essentially at map load)
// Useful for precaching things
-void ConsoleCommand_macro_init();
void CSQC_Init()
{
prvm_language = strzone(cvar_string("prvm_language"));
this.nextthink = time + 0.2;
}
-void TrueAim_Init();
void PostInit()
{
entity playerchecker = new_pure(playerchecker);
// --------------------------------------------------------------------------
// BEGIN OPTIONAL CSQC FUNCTIONS
-void Ent_Remove(entity this);
-
void Ent_RemovePlayerScore(entity this)
{
if(this.owner) {
localcmd(sprintf("\nfog %s\nr_fog_exp2 0\nr_drawfog 1\n", forcefog));
}
-void Gamemode_Init();
NET_HANDLE(ENT_CLIENT_SCORES_INFO, bool isnew)
{
make_pure(this);
void draw_cursor_normal(vector pos, vector col, float a);
void LoadMenuSkinValues();
+void PostInit();
+
+void Ent_Remove(entity this);
+
+void Gamemode_Init();
+
+float SetTeam(entity pl, float Team);
+
vector hud_fontsize;
float RANKINGS_RECEIVED_CNT;
return mv_mouse_selection;
}
-vector HUD_GetTableSize_BestItemAR(int item_count, vector psize, float item_aspect);
void MapVote_Draw()
{
string map;
vector Rotate(vector v, float a);
-#define IS_DEAD(s) (((s).classname == "csqcmodel") ? (s).csqcmodel_isdead : ((s).health <= 0))
+#define IS_DEAD(s) (((s).classname == "csqcmodel") ? (s).csqcmodel_isdead : (GetResourceAmount((s), RESOURCE_HEALTH) <= 0))
// decolorizes and team colors the player name when needed
#include <common/physics/movetypes/movetypes.qh>
#include <common/physics/player.qh>
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
#include "../lib/csqcmodel/cl_player.qh"
#include "../lib/warpzone/anglestransform.qh"
--- /dev/null
+#include "resources.qh"
+
+/// \file
+/// \brief Source file that contains implementation of the resource system.
+/// \copyright GNU GPLv2 or any later version.
+
+float GetResourceAmount(entity e, int resource_type)
+{
+ .float resource_field = GetResourceField(resource_type);
+ return e.(resource_field);
+}
+
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount)
+{
+ .float resource_field = GetResourceField(resource_type);
+ if (e.(resource_field) != amount)
+ {
+ e.(resource_field) = amount;
+ return true;
+ }
+ return false;
+}
+
+void SetResourceAmount(entity e, int resource_type, float amount)
+{
+ SetResourceAmountExplicit(e, resource_type, amount);
+}
+
+void TakeResource(entity receiver, int resource_type, float amount)
+{
+ if (amount == 0)
+ {
+ return;
+ }
+ SetResourceAmount(receiver, resource_type,
+ GetResourceAmount(receiver, resource_type) - amount);
+}
+
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+ float limit)
+{
+ if (amount == 0)
+ {
+ return;
+ }
+ float current_amount = GetResourceAmount(receiver, resource_type);
+ if (current_amount - amount < limit)
+ {
+ amount = limit + current_amount;
+ }
+ TakeResource(receiver, resource_type, amount);
+}
+
+int GetResourceType(.float resource_field)
+{
+ switch (resource_field)
+ {
+ case health: { return RESOURCE_HEALTH; }
+ case armorvalue: { return RESOURCE_ARMOR; }
+ case ammo_shells: { return RESOURCE_SHELLS; }
+ case ammo_nails: { return RESOURCE_BULLETS; }
+ case ammo_rockets: { return RESOURCE_ROCKETS; }
+ case ammo_cells: { return RESOURCE_CELLS; }
+ case ammo_plasma: { return RESOURCE_PLASMA; }
+ case ammo_fuel: { return RESOURCE_FUEL; }
+ }
+ error("GetResourceType: Invalid field.");
+ return 0;
+}
+
+.float GetResourceField(int resource_type)
+{
+ switch (resource_type)
+ {
+ case RESOURCE_HEALTH: { return health; }
+ case RESOURCE_ARMOR: { return armorvalue; }
+ case RESOURCE_SHELLS: { return ammo_shells; }
+ case RESOURCE_BULLETS: { return ammo_nails; }
+ case RESOURCE_ROCKETS: { return ammo_rockets; }
+ case RESOURCE_CELLS: { return ammo_cells; }
+ case RESOURCE_PLASMA: { return ammo_plasma; }
+ case RESOURCE_FUEL: { return ammo_fuel; }
+ }
+ error("GetResourceField: Invalid resource type.");
+ return health;
+}
--- /dev/null
+#pragma once
+
+/// \file
+/// \brief Header file that describes the resource system.
+/// \copyright GNU GPLv2 or any later version.
+
+#include <common/resources.qh>
+
+// ============================ Public API ====================================
+
+/// \brief Returns the current amount of resource the given entity has.
+/// \param[in] e Entity to check.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \return Current amount of resource the given entity has.
+float GetResourceAmount(entity e, int resource_type);
+
+/// \brief Sets the resource amount of an entity without calling any hooks.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return Boolean for whether the ammo amount was changed
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount);
+
+/// \brief Sets the current amount of resource the given entity will have.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return No return.
+void SetResourceAmount(entity e, int resource_type, float amount);
+
+/// \brief Takes an entity some resource.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \return No return.
+void TakeResource(entity receiver, int resource_type, float amount);
+
+/// \brief Takes an entity some resource but not less than a limit.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \param[in] limit Limit of resources to take.
+/// \return No return.
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+ float limit);
+
+// ===================== Legacy and/or internal API ===========================
+
+/// \brief Converts an entity field to resource type.
+/// \param[in] resource_field Entity field to convert.
+/// \return Resource type (a RESOURCE_* constant).
+int GetResourceType(.float resource_field);
+
+/// \brief Converts resource type (a RESOURCE_* constant) to entity field.
+/// \param[in] resource_type Type of the resource.
+/// \return Entity field for that resource.
+.float GetResourceField(int resource_type);
this.healthvalue / autocvar_hud_panel_healtharmor_maxhealth, false, 1, '1 0 0', a,
DRAWFLAG_NORMAL);
}
- if (this.armorvalue > 0)
+ if (GetResourceAmount(this, RESOURCE_ARMOR) > 0)
{
HUD_Panel_DrawProgressBar(pos + eX * 0.5 * mySize.x, sz, "nametag_statusbar",
- this.armorvalue / autocvar_hud_panel_healtharmor_maxarmor, false, 0, '0 1 0', a,
+ GetResourceAmount(this, RESOURCE_ARMOR) / autocvar_hud_panel_healtharmor_maxarmor, false, 0, '0 1 0', a,
DRAWFLAG_NORMAL);
}
}
if (entcs.m_entcs_private)
{
it.healthvalue = entcs.healthvalue;
- it.armorvalue = entcs.armorvalue;
+ SetResourceAmountExplicit(it, RESOURCE_ARMOR, GetResourceAmount(entcs, RESOURCE_ARMOR));
it.sameteam = true;
}
else
{
it.healthvalue = 0;
- it.armorvalue = 0;
+ SetResourceAmountExplicit(it, RESOURCE_ARMOR, 0);
it.sameteam = false;
}
bool dead = entcs_IsDead(i) || entcs_IsSpectating(i);
#include "hud/panel/scoreboard.qh"
#include "hud/panel/quickmenu.qh"
-#include "mutators/events.qh"
+#include <client/mutators/_mod.qh>
#include <common/animdecide.qh>
#include <common/deathtypes/all.qh>
#include <common/anim.qh>
#include <common/constants.qh>
#include <common/net_linked.qh>
+#include <common/net_notice.qh>
#include <common/debug.qh>
#include <common/mapinfo.qh>
#include <common/gamemodes/_mod.qh>
#include <common/vehicles/all.qh>
#include <common/weapons/_all.qh>
#include <common/mutators/mutator/overkill/oknex.qh>
+#include <common/mutators/mutator/waypoints/all.qh>
#include <common/viewloc.qh>
#include <common/mapobjects/trigger/viewloc.qh>
#include <common/minigames/cl_minigames.qh>
showfps_prevfps_time = currentTime; // we must initialize it to avoid an instant low frame sending
}
-void Porto_Draw(entity this);
STATIC_INIT(Porto)
{
entity e = new_pure(porto);
return SHOTTYPE_HITWORLD;
}
-void PostInit();
-void CSQC_Demo_Camera();
float camera_mode;
const float CAMERA_FREE = 1;
const float CAMERA_CHASE = 2;
float oldr_useportalculling;
float oldr_useinfinitefarclip;
-void cl_notice_run();
-
float prev_myteam;
int lasthud;
float vh_notice_time;
-void WaypointSprite_Load();
void CSQC_UpdateView(entity this, float w, float h)
{
TC(int, w); TC(int, h);
IL_EACH(g_drawables, it.draw, it.draw(it));
- addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS);
+ addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS); // TODO: .health is used in cl_deathfade (a feature we have turned off currently)
renderscene();
// now switch to 2D drawing mode by calling a 2D drawing function
vector crosshair_getcolor(entity this, float health_stat);
+void calc_followmodel_ofs(entity view);
+
+void Porto_Draw(entity this);
+
+void CSQC_Demo_Camera();
+
+void TrueAim_Init();
+
entity viewmodels[MAX_WEAPONSLOTS];
vector viewloc_mousepos;
+++ /dev/null
-#include "wall.qh"
-
-#include "autocvars.qh"
-#include "main.qh"
-#include "bgmscript.qh"
-
-
-#include "../lib/csqcmodel/interpolate.qh"
-
-.float alpha;
-.float scale;
-.vector movedir;
-
-void Ent_Wall_PreDraw(entity this)
-{
- if (this.inactive)
- {
- this.alpha = 0;
- }
- else
- {
- vector org = getpropertyvec(VF_ORIGIN);
- if(!checkpvs(org, this))
- this.alpha = 0;
- else if(this.fade_start || this.fade_end) {
- vector offset = '0 0 0';
- offset_z = this.fade_vertical_offset;
- float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset);
- if (this.fade_end == this.fade_start)
- {
- if (player_dist >= this.fade_start)
- this.alpha = 0;
- else
- this.alpha = 1;
- }
- else
- {
- this.alpha = (this.alpha_min + this.alpha_max * bound(0,
- (this.fade_end - player_dist)
- / (this.fade_end - this.fade_start), 1)) / 100.0;
- }
- }
- else
- {
- this.alpha = 1;
- }
- }
- if(this.alpha <= 0)
- this.drawmask = 0;
- else
- this.drawmask = MASK_NORMAL;
-}
-
-void Ent_Wall_Draw(entity this)
-{
- float f;
- var .vector fld;
-
- if(this.bgmscriptangular)
- fld = angles;
- else
- fld = origin;
- this.(fld) = this.saved;
-
- if(this.lodmodelindex1)
- {
- if(autocvar_cl_modeldetailreduction <= 0)
- {
- if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2)
- this.modelindex = this.lodmodelindex2;
- else if(autocvar_cl_modeldetailreduction <= -1)
- this.modelindex = this.lodmodelindex1;
- else
- this.modelindex = this.lodmodelindex0;
- }
- else
- {
- float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin);
- f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction;
- f *= 1.0 / bound(0.01, view_quality, 1);
- if(this.lodmodelindex2 && f > this.loddistance2)
- this.modelindex = this.lodmodelindex2;
- else if(f > this.loddistance1)
- this.modelindex = this.lodmodelindex1;
- else
- this.modelindex = this.lodmodelindex0;
- }
- }
-
- InterpolateOrigin_Do(this);
-
- this.saved = this.(fld);
-
- f = doBGMScript(this);
- if(f >= 0)
- {
- if(this.lip < 0) // < 0: alpha goes from 1 to 1-|lip| when toggled (toggling subtracts lip)
- this.alpha = 1 + this.lip * f;
- else // > 0: alpha goes from 1-|lip| to 1 when toggled (toggling adds lip)
- this.alpha = 1 - this.lip * (1 - f);
- this.(fld) = this.(fld) + this.movedir * f;
- }
- else
- this.alpha = 1;
-
- if(this.alpha >= ALPHA_MIN_VISIBLE)
- this.drawmask = MASK_NORMAL;
- else
- this.drawmask = 0;
-}
-
-void Ent_Wall_Remove(entity this)
-{
- strfree(this.bgmscript);
-}
-
-NET_HANDLE(ENT_CLIENT_WALL, bool isnew)
-{
- int f;
- var .vector fld;
-
- InterpolateOrigin_Undo(this);
- this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
-
- if(this.bgmscriptangular)
- fld = angles;
- else
- fld = origin;
- this.(fld) = this.saved;
-
- f = ReadByte();
-
- if(f & 1)
- {
- if(f & 0x40)
- this.colormap = ReadShort();
- else
- this.colormap = 0;
- this.skin = ReadByte();
- }
-
- if(f & 2)
- {
- this.origin = ReadVector();
- setorigin(this, this.origin);
- }
-
- if(f & 4)
- {
- if(f & 0x10)
- {
- this.angles_x = ReadAngle();
- this.angles_y = ReadAngle();
- this.angles_z = ReadAngle();
- }
- else
- this.angles = '0 0 0';
- }
-
- if(f & 8)
- {
- if(f & 0x80)
- {
- this.lodmodelindex0 = ReadShort();
- this.loddistance1 = ReadShort();
- this.lodmodelindex1 = ReadShort();
- this.loddistance2 = ReadShort();
- this.lodmodelindex2 = ReadShort();
- }
- else
- {
- this.modelindex = ReadShort();
- this.loddistance1 = 0;
- this.loddistance2 = 0;
- }
- this.solid = ReadByte();
- this.scale = ReadShort() / 256.0;
- if(f & 0x20)
- {
- this.mins = ReadVector();
- this.maxs = ReadVector();
- }
- else
- this.mins = this.maxs = '0 0 0';
- setsize(this, this.mins, this.maxs);
-
- string s = ReadString();
- if(substring(s, 0, 1) == "<")
- {
- strcpy(this.bgmscript, substring(s, 1, -1));
- this.bgmscriptangular = 1;
- }
- else
- {
- strcpy(this.bgmscript, s);
- this.bgmscriptangular = 0;
- }
- if(this.bgmscript != "")
- {
- this.bgmscriptattack = ReadByte() / 64.0;
- this.bgmscriptdecay = ReadByte() / 64.0;
- this.bgmscriptsustain = ReadByte() / 255.0;
- this.bgmscriptrelease = ReadByte() / 64.0;
- this.movedir = ReadVector();
- this.lip = ReadByte() / 255.0;
- }
- this.fade_start = ReadByte();
- this.fade_end = ReadByte();
- this.alpha_max = ReadByte();
- this.alpha_min = ReadByte();
- this.inactive = ReadByte();
- this.fade_vertical_offset = ReadShort();
- BGMScript_InitEntity(this);
- }
-
- return = true;
-
- InterpolateOrigin_Note(this);
-
- this.saved = this.(fld);
-
- this.entremove = Ent_Wall_Remove;
- this.draw = Ent_Wall_Draw;
- if (isnew) IL_PUSH(g_drawables, this);
- setpredraw(this, Ent_Wall_PreDraw);
-}
+++ /dev/null
-#pragma once
-
-entityclass(Wall);
-classfield(Wall) .float lip;
-classfield(Wall) .float bgmscriptangular;
-classfield(Wall) .int lodmodelindex0, lodmodelindex1, lodmodelindex2;
-classfield(Wall) .float loddistance1, loddistance2;
-classfield(Wall) .vector saved;
-
-// Needed for interactive clientwalls
-.float inactive; // Clientwall disappears when inactive
-.float alpha_max, alpha_min;
-// If fade_start > fade_end, fadeout will be inverted
-// fade_vertical_offset is a vertival offset for player position
-.float fade_start, fade_end, fade_vertical_offset;
-.float default_solid;
-
-void Ent_Wall_Draw(entity this);
-
-void Ent_Wall_Remove(entity this);
#include "../autocvars.qh"
#include "../defs.qh"
#include "../main.qh"
-#include "../mutators/events.qh"
+#include <client/mutators/_mod.qh>
#include <common/constants.qh>
#include <common/effects/effect.qh>
#include <common/net_linked.qh>
#include <common/physics/movetypes/movetypes.qh>
+#include <common/mutators/mutator/nades/nades.qh>
+
#include <lib/csqcmodel/interpolate.qh>
#include <lib/warpzone/anglestransform.qh>
}
}
-bool Projectile_isnade(int proj); // TODO: remove
-
void Projectile_Draw(entity this)
{
vector rot;
CONSTRUCT(DebugText3d);
this.origin = pos;
this.message = strzone(msg);
- this.health = align;
+ SetResourceAmount(this, RESOURCE_HEALTH, align);
this.hit_time = time;
this.fade_rate = fade_rate_;
this.velocity = vel;
int size = 8;
vector screen_pos = project_3d_to_2d(this.origin) + since_created * this.velocity;
- float align = this.health;
+ float align = GetResourceAmount(this, RESOURCE_HEALTH);
if (align > 0)
screen_pos.x -= stringwidth(this.message, true, size * '1 1 0') * min(1, align);
if (screen_pos.z < 0) return; // behind camera
#ifdef CSQC
#include <common/deathtypes/all.qh>
#include <common/physics/movetypes/movetypes.qh>
-#include <client/mutators/events.qh>
+#include <client/mutators/_mod.qh>
#include <common/vehicles/all.qh>
#include <common/weapons/_all.qh>
#endif
#include "ent_cs.qh"
+#include <common/gamemodes/_mod.qh>
+#include <common/resources.qh>
+#ifdef SVQC
+#include <server/resources.qh>
+#endif
REGISTRY(EntCSProps, BITS(16) - 1)
#define EntCSProps_from(i) _EntCSProps_from(i, NULL)
}
#endif
+#ifdef SVQC
+#define ENTCS_PROP_RESOURCE(id, ispublic, checkprop, setprop, svsend, clreceive) \
+ bool id##_check(entity ent, entity player) { return (GetResourceAmount(ent, checkprop) != GetResourceAmount(player, checkprop)); } \
+ void id##_set(entity ent, entity player) { SetResourceAmountExplicit(ent, checkprop, GetResourceAmount(player, checkprop)); } \
+ void id##_send(int chan, entity ent) { LAMBDA(svsend); } \
+ REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
+ this.m_public = ispublic; \
+ this.m_check = id##_check; \
+ this.m_set = id##_set; \
+ this.m_send = id##_send; \
+ }
+#elif defined(CSQC)
+#define ENTCS_PROP_RESOURCE(id, ispublic, checkprop, setprop, svsend, clreceive) \
+ void id##_receive(entity ent) { LAMBDA(clreceive); } \
+ REGISTER(EntCSProps, ENTCS_PROP, id, m_id, new_pure(entcs_prop)) { \
+ this.m_public = ispublic; \
+ this.m_receive = id##_receive; \
+ }
+#endif
+
#define ENTCS_SET_NORMAL(var, x) MACRO_BEGIN \
var = x; \
MACRO_END
{ WriteByte(chan, ent.angles.y / 360 * 256); },
{ vector v = '0 0 0'; v.y = ReadByte() / 256 * 360; ent.angles = v; })
-ENTCS_PROP(HEALTH, false, health, ENTCS_SET_NORMAL,
- { WriteByte(chan, bound(0, ent.health / 10, 255)); /* FIXME: use a better scale? */ },
+ENTCS_PROP_RESOURCE(HEALTH, false, RESOURCE_HEALTH, ENTCS_SET_NORMAL,
+ { WriteByte(chan, bound(0, GetResourceAmount(ent, RESOURCE_HEALTH) / 10, 255)); /* FIXME: use a better scale? */ },
{ ent.healthvalue = ReadByte() * 10; })
-ENTCS_PROP(ARMOR, false, armorvalue, ENTCS_SET_NORMAL,
- { WriteByte(chan, bound(0, ent.armorvalue / 10, 255)); /* FIXME: use a better scale? */ },
- { ent.armorvalue = ReadByte() * 10; })
+ENTCS_PROP_RESOURCE(ARMOR, false, RESOURCE_ARMOR, ENTCS_SET_NORMAL,
+ { WriteByte(chan, bound(0, GetResourceAmount(ent, RESOURCE_ARMOR) / 10, 255)); /* FIXME: use a better scale? */ },
+ { SetResourceAmountExplicit(ent, RESOURCE_ARMOR, ReadByte() * 10); })
ENTCS_PROP(NAME, true, netname, ENTCS_SET_MUTABLE_STRING,
{ WriteString(chan, ent.netname); },
// generated file; do not modify
+#include <common/gamemodes/gamemode/assault/_mod.inc>
+#include <common/gamemodes/gamemode/clanarena/_mod.inc>
+#include <common/gamemodes/gamemode/ctf/_mod.inc>
+#include <common/gamemodes/gamemode/cts/_mod.inc>
+#include <common/gamemodes/gamemode/deathmatch/_mod.inc>
+#include <common/gamemodes/gamemode/domination/_mod.inc>
+#include <common/gamemodes/gamemode/freezetag/_mod.inc>
+#include <common/gamemodes/gamemode/invasion/_mod.inc>
+#include <common/gamemodes/gamemode/keepaway/_mod.inc>
+#include <common/gamemodes/gamemode/keyhunt/_mod.inc>
+#include <common/gamemodes/gamemode/lms/_mod.inc>
#include <common/gamemodes/gamemode/nexball/_mod.inc>
#include <common/gamemodes/gamemode/onslaught/_mod.inc>
+#include <common/gamemodes/gamemode/race/_mod.inc>
+#include <common/gamemodes/gamemode/tdm/_mod.inc>
// generated file; do not modify
+#include <common/gamemodes/gamemode/assault/_mod.qh>
+#include <common/gamemodes/gamemode/clanarena/_mod.qh>
+#include <common/gamemodes/gamemode/ctf/_mod.qh>
+#include <common/gamemodes/gamemode/cts/_mod.qh>
+#include <common/gamemodes/gamemode/deathmatch/_mod.qh>
+#include <common/gamemodes/gamemode/domination/_mod.qh>
+#include <common/gamemodes/gamemode/freezetag/_mod.qh>
+#include <common/gamemodes/gamemode/invasion/_mod.qh>
+#include <common/gamemodes/gamemode/keepaway/_mod.qh>
+#include <common/gamemodes/gamemode/keyhunt/_mod.qh>
+#include <common/gamemodes/gamemode/lms/_mod.qh>
#include <common/gamemodes/gamemode/nexball/_mod.qh>
#include <common/gamemodes/gamemode/onslaught/_mod.qh>
+#include <common/gamemodes/gamemode/race/_mod.qh>
+#include <common/gamemodes/gamemode/tdm/_mod.qh>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/assault/assault.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/assault/assault.qh>
--- /dev/null
+#include "assault.qh"
+
+// TODO: split into sv_assault
+#ifdef SVQC
+.entity sprite;
+#define AS_ROUND_DELAY 5
+
+IntrusiveList g_assault_destructibles;
+IntrusiveList g_assault_objectivedecreasers;
+IntrusiveList g_assault_objectives;
+STATIC_INIT(g_assault)
+{
+ g_assault_destructibles = IL_NEW();
+ g_assault_objectivedecreasers = IL_NEW();
+ g_assault_objectives = IL_NEW();
+}
+
+// random functions
+void assault_objective_use(entity this, entity actor, entity trigger)
+{
+ // activate objective
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
+ //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
+ //print("Activator is ", actor.classname, "\n");
+
+ IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
+ {
+ target_objective_decrease_activate(it);
+ });
+}
+
+vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
+{
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 0 || GetResourceAmount(this, RESOURCE_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(entity this)
+{
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+}
+
+// decrease the health of targeted objectives
+void assault_objective_decrease_use(entity this, entity actor, entity trigger)
+{
+ if(actor.team != assault_attacker_team)
+ {
+ // wrong team triggered decrease
+ return;
+ }
+
+ if(trigger.assault_sprite)
+ {
+ WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
+ if(trigger.classname == "func_assault_destructible")
+ trigger.sprite = NULL; // TODO: just unsetting it?!
+ }
+ else
+ return; // already activated! cannot activate again!
+
+ if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < ASSAULT_VALUE_INACTIVE)
+ {
+ if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) - this.dmg > 0.5)
+ {
+ GameRules_scoring_add_team(actor, SCORE, this.dmg);
+ TakeResource(this.enemy, RESOURCE_HEALTH, this.dmg);
+ }
+ else
+ {
+ GameRules_scoring_add_team(actor, SCORE, GetResourceAmount(this.enemy, RESOURCE_HEALTH));
+ GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
+ SetResourceAmountExplicit(this.enemy, RESOURCE_HEALTH, -1);
+
+ if(this.enemy.message)
+ FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
+
+ SUB_UseTargets(this.enemy, this, trigger);
+ }
+ }
+}
+
+void assault_setenemytoobjective(entity this)
+{
+ IL_EACH(g_assault_objectives, it.targetname == this.target,
+ {
+ if(this.enemy == NULL)
+ this.enemy = it;
+ else
+ objerror(this, "more than one objective as target - fix the map!");
+ break;
+ });
+
+ if(this.enemy == NULL)
+ objerror(this, "no objective as target - fix the map!");
+}
+
+bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
+{
+ if(GetResourceAmount(this.assault_decreaser.enemy, RESOURCE_HEALTH) >= ASSAULT_VALUE_INACTIVE)
+ return false;
+
+ return true;
+}
+
+void target_objective_decrease_activate(entity this)
+{
+ entity spr;
+ this.owner = NULL;
+ FOREACH_ENTITY_STRING(target, this.targetname,
+ {
+ if(it.assault_sprite != NULL)
+ {
+ WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
+ if(it.classname == "func_assault_destructible")
+ it.sprite = NULL; // TODO: just unsetting it?!
+ }
+
+ spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
+ spr.assault_decreaser = this;
+ spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
+ spr.classname = "sprite_waypoint";
+ WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
+ if(it.classname == "func_assault_destructible")
+ {
+ WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
+ WaypointSprite_UpdateMaxHealth(spr, it.max_health);
+ WaypointSprite_UpdateHealth(spr, GetResourceAmount(it, RESOURCE_HEALTH));
+ it.sprite = spr;
+ }
+ else
+ WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
+ });
+}
+
+void target_objective_decrease_findtarget(entity this)
+{
+ assault_setenemytoobjective(this);
+}
+
+void target_assault_roundend_reset(entity this)
+{
+ //print("round end reset\n");
+ ++this.cnt; // up round counter
+ this.winning = false; // up round
+}
+
+void target_assault_roundend_use(entity this, entity actor, entity trigger)
+{
+ this.winning = 1; // round has been won by attackers
+}
+
+void assault_roundstart_use(entity this, entity actor, entity trigger)
+{
+ SUB_UseTargets(this, this, trigger);
+
+ //(Re)spawn all turrets
+ IL_EACH(g_turrets, true,
+ {
+ // Swap turret teams
+ if(it.team == NUM_TEAM_1)
+ it.team = NUM_TEAM_2;
+ else
+ it.team = NUM_TEAM_1;
+
+ // Doubles as teamchange
+ turret_respawn(it);
+ });
+}
+void assault_roundstart_use_this(entity this)
+{
+ assault_roundstart_use(this, NULL, NULL);
+}
+
+void assault_wall_think(entity this)
+{
+ if(GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 0)
+ {
+ this.model = "";
+ this.solid = SOLID_NOT;
+ }
+ else
+ {
+ this.model = this.mdl;
+ this.solid = SOLID_BSP;
+ }
+
+ this.nextthink = time + 0.2;
+}
+
+// trigger new round
+// reset objectives, toggle spawnpoints, reset triggers, ...
+void assault_new_round(entity this)
+{
+ //bprint("ASSAULT: new round\n");
+
+ // up round counter
+ this.winning = this.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;
+
+ IL_EACH(g_saved_team, !IS_CLIENT(it),
+ {
+ if(it.team_saved == NUM_TEAM_1)
+ it.team_saved = NUM_TEAM_2;
+ else if(it.team_saved == NUM_TEAM_2)
+ it.team_saved = NUM_TEAM_1;
+ });
+
+ // reset the level with a countdown
+ cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
+ ReadyRestart_force(); // sets game_starttime
+}
+
+entity as_round;
+.entity ent_winning;
+void as_round_think()
+{
+ game_stopped = false;
+ assault_new_round(as_round.ent_winning);
+ delete(as_round);
+ as_round = NULL;
+}
+
+// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win. Otherwise the defending team wins once the timelimit passes.
+int WinningCondition_Assault()
+{
+ if(as_round)
+ return WINNING_NO;
+
+ WinningConditionHelper(NULL); // set worldstatus
+
+ int status = WINNING_NO;
+ // as the timelimit has not yet passed just assume the defending team will win
+ if(assault_attacker_team == NUM_TEAM_1)
+ {
+ SetWinners(team, NUM_TEAM_2);
+ }
+ else
+ {
+ SetWinners(team, NUM_TEAM_1);
+ }
+
+ entity ent;
+ ent = find(NULL, classname, "target_assault_roundend");
+ if(ent)
+ {
+ if(ent.winning) // round end has been triggered by attacking team
+ {
+ bprint("Assault: round completed.\n");
+ SetWinners(team, assault_attacker_team);
+
+ TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
+
+ if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
+ {
+ status = WINNING_YES;
+ }
+ else
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
+ as_round = new(as_round);
+ as_round.think = as_round_think;
+ as_round.ent_winning = ent;
+ as_round.nextthink = time + AS_ROUND_DELAY;
+ game_stopped = true;
+
+ // make sure timelimit isn't hit while the game is blocked
+ if(autocvar_timelimit > 0)
+ if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
+ cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
+ }
+ }
+ }
+
+ return status;
+}
+
+// spawnfuncs
+spawnfunc(info_player_attacker)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.team = NUM_TEAM_1; // red, gets swapped every round
+ spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(info_player_defender)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.team = NUM_TEAM_2; // blue, gets swapped every round
+ spawnfunc_info_player_deathmatch(this);
+}
+
+spawnfunc(target_objective)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.classname = "target_objective";
+ IL_PUSH(g_assault_objectives, this);
+ this.use = assault_objective_use;
+ this.reset = assault_objective_reset;
+ this.reset(this);
+ this.spawn_evalfunc = target_objective_spawn_evalfunc;
+}
+
+spawnfunc(target_objective_decrease)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.classname = "target_objective_decrease";
+ IL_PUSH(g_assault_objectivedecreasers, this);
+
+ if(!this.dmg)
+ this.dmg = 101;
+
+ this.use = assault_objective_decrease_use;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, ASSAULT_VALUE_INACTIVE);
+ this.max_health = ASSAULT_VALUE_INACTIVE;
+ this.enemy = NULL;
+
+ InitializeEntity(this, target_objective_decrease_findtarget, INITPRIO_FINDTARGET);
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+bool destructible_heal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ if(targ.sprite)
+ {
+ WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+ }
+ func_breakable_colormod(targ);
+ return true;
+}
+
+spawnfunc(func_breakable);
+spawnfunc(func_assault_destructible)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.spawnflags = 3;
+ this.classname = "func_assault_destructible";
+ this.event_heal = destructible_heal;
+ IL_PUSH(g_assault_destructibles, this);
+
+ if(assault_attacker_team == NUM_TEAM_1)
+ this.team = NUM_TEAM_2;
+ else
+ this.team = NUM_TEAM_1;
+
+ spawnfunc_func_breakable(this);
+}
+
+spawnfunc(func_assault_wall)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.classname = "func_assault_wall";
+ this.mdl = this.model;
+ _setmodel(this, this.mdl);
+ this.solid = SOLID_BSP;
+ setthink(this, assault_wall_think);
+ this.nextthink = time;
+ InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
+}
+
+spawnfunc(target_assault_roundend)
+{
+ if (!g_assault) { delete(this); return; }
+
+ this.winning = 0; // round not yet won by attackers
+ this.classname = "target_assault_roundend";
+ this.use = target_assault_roundend_use;
+ this.cnt = 0; // first round
+ this.reset = target_assault_roundend_reset;
+}
+
+spawnfunc(target_assault_roundstart)
+{
+ if (!g_assault) { delete(this); return; }
+
+ assault_attacker_team = NUM_TEAM_1;
+ this.classname = "target_assault_roundstart";
+ this.use = assault_roundstart_use;
+ this.reset2 = assault_roundstart_use_this;
+ InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
+}
+
+// legacy bot code
+void havocbot_goalrating_ast_targets(entity this, float ratingscale)
+{
+ IL_EACH(g_assault_destructibles, it.bot_attack,
+ {
+ if (it.target == "")
+ continue;
+
+ bool found = false;
+ entity destr = it;
+ IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
+ {
+ if(GetResourceAmount(it.enemy, RESOURCE_HEALTH) > 0 && GetResourceAmount(it.enemy, RESOURCE_HEALTH) < ASSAULT_VALUE_INACTIVE)
+ {
+ found = true;
+ break;
+ }
+ });
+
+ if(!found)
+ continue;
+
+ vector p = 0.5 * (it.absmin + it.absmax);
+
+ // Find and rate waypoints around it
+ found = false;
+ entity best = NULL;
+ float bestvalue = 99999999999;
+ entity des = it;
+ for(float radius = 0; radius < 1500 && !found; radius += 500)
+ {
+ FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
+ {
+ if(checkpvs(it.origin, des))
+ {
+ found = true;
+ if(it.cnt < bestvalue)
+ {
+ best = it;
+ bestvalue = it.cnt;
+ }
+ }
+ });
+ }
+
+ if(best)
+ {
+ /// dprint("waypoints around target were found\n");
+ // te_lightning2(NULL, '0 0 0', best.origin);
+ // te_knightspike(best.origin);
+
+ navigation_routerating(this, best, ratingscale, 4000);
+ best.cnt += 1;
+
+ this.havocbot_attack_time = 0;
+
+ if(checkpvs(this.origin + this.view_ofs, it))
+ if(checkpvs(this.origin + this.view_ofs, best))
+ {
+ // dprint("increasing attack time for this target\n");
+ this.havocbot_attack_time = time + 2;
+ }
+ }
+ });
+}
+
+void havocbot_role_ast_offense(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ this.havocbot_attack_time = 0;
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ if(this.havocbot_attack_time>time)
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
+ havocbot_goalrating_ast_targets(this, 20000);
+ havocbot_goalrating_items(this, 15000, this.origin, 10000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ast_defense(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ this.havocbot_attack_time = 0;
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ast_reset_role(this);
+ return;
+ }
+
+ if(this.havocbot_attack_time>time)
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
+ havocbot_goalrating_ast_targets(this, 20000);
+ havocbot_goalrating_items(this, 15000, this.origin, 10000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ast_setrole(entity this, float role)
+{
+ switch(role)
+ {
+ case HAVOCBOT_AST_ROLE_DEFENSE:
+ this.havocbot_role = havocbot_role_ast_defense;
+ this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
+ this.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_AST_ROLE_OFFENSE:
+ this.havocbot_role = havocbot_role_ast_offense;
+ this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
+ this.havocbot_role_timeout = 0;
+ break;
+ }
+}
+
+void havocbot_ast_reset_role(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if(this.team == assault_attacker_team)
+ havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
+ else
+ havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.team == assault_attacker_team)
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
+ else
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
+}
+
+MUTATOR_HOOKFUNCTION(as, TurretSpawn)
+{
+ entity turret = M_ARGV(0, entity);
+
+ if(!turret.team || turret.team == FLOAT_MAX)
+ turret.team = 5; // this gets reversed when match starts?
+}
+
+MUTATOR_HOOKFUNCTION(as, VehicleInit)
+{
+ entity veh = M_ARGV(0, entity);
+
+ veh.nextthink = time + 0.5;
+}
+
+MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ havocbot_ast_reset_role(bot);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, PlayHitsound)
+{
+ entity frag_victim = M_ARGV(0, entity);
+
+ return (frag_victim.classname == "func_assault_destructible");
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
+{
+ // assault always has 2 teams
+ c1 = c2 = 0;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, CheckRules_World)
+{
+ M_ARGV(0, float) = WinningCondition_Assault();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
+{
+ // incompatible
+ warmup_stage = 0;
+ sv_ready_restart_after_countdown = 0;
+}
+
+MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
+{
+ entity ent = M_ARGV(0, entity);
+
+ switch(ent.classname)
+ {
+ case "info_player_team1":
+ case "info_player_team2":
+ case "info_player_team3":
+ case "info_player_team4":
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
+{
+ // readyrestart not supported (yet)
+ return true;
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+
+const int ASSAULT_VALUE_INACTIVE = 1000;
+
+const int ST_ASSAULT_OBJECTIVES = 1;
+
+REGISTER_MUTATOR(as, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ int teams = BITS(2); // always red vs blue
+ GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
+ field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+ field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
+ });
+ }
+ return 0;
+}
+
+// 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(entity this) havocbot_role_ast_defense;
+void(entity this) havocbot_role_ast_offense;
+
+void(entity bot) havocbot_ast_reset_role;
+
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+// predefined spawnfuncs
+void target_objective_decrease_activate(entity this);
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/clanarena/clanarena.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/clanarena/clanarena.qh>
--- /dev/null
+#include "clanarena.qh"
+
+// TODO: split into sv_clanarena
+#ifdef SVQC
+float autocvar_g_ca_damage2score_multiplier;
+bool autocvar_g_ca_spectate_enemies;
+
+void CA_count_alive_players()
+{
+ total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ switch(it.team)
+ {
+ case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
+ case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
+ case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
+ case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
+ }
+ });
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ STAT(REDALIVE, it) = redalive;
+ STAT(BLUEALIVE, it) = bluealive;
+ STAT(YELLOWALIVE, it) = yellowalive;
+ STAT(PINKALIVE, it) = 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
+}
+
+void nades_Clear(entity player);
+
+#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
+float CA_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+ FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+ allowed_to_spawn = false;
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+ return 1;
+ }
+
+ CA_count_alive_players();
+ if(CA_ALIVE_TEAMS() > 1)
+ return 0;
+
+ int winner_team = CA_GetWinnerTeam();
+ if(winner_team > 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+ }
+ else if(winner_team == -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ }
+
+ allowed_to_spawn = false;
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+
+ FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+ return 1;
+}
+
+void CA_RoundStart()
+{
+ allowed_to_spawn = boolean(warmup_stage);
+}
+
+bool CA_CheckTeams()
+{
+ static int 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return true;
+ }
+ if(total_players == 0)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return false;
+ }
+ int missing_teams_mask = 0;
+ if(ca_teams & BIT(0))
+ missing_teams_mask += (!redalive) * 1;
+ if(ca_teams & BIT(1))
+ missing_teams_mask += (!bluealive) * 2;
+ if(ca_teams & BIT(2))
+ missing_teams_mask += (!yellowalive) * 4;
+ if(ca_teams & BIT(3))
+ missing_teams_mask += (!pinkalive) * 8;
+ if(prev_missing_teams_mask != missing_teams_mask)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+ prev_missing_teams_mask = missing_teams_mask;
+ }
+ return false;
+}
+
+bool ca_isEliminated(entity e)
+{
+ if(e.caplayer == 1 && (IS_DEAD(e) || 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;
+ // continue from current player
+ for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
+ {
+ if (SAME_TEAM(player, e)) return e;
+ }
+ // restart from begining
+ for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
+ {
+ if (SAME_TEAM(player, e)) return e;
+ }
+ return start;
+}
+
+
+MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.caplayer = 1;
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ // spectators / observers that weren't playing can join; they are
+ // immediately forced to observe in the PutClientInServer hook
+ // this way they are put in a team and can play in the next round
+ if (!allowed_to_spawn && player.caplayer)
+ return true;
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
+ {
+ TRANSMUTE(Observer, player);
+ if (CS(player).jointime != time && !player.caplayer) // not when connecting
+ {
+ player.caplayer = 0.5;
+ Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_players)
+{
+ FOREACH_CLIENT(true, {
+ CS(it).killcount = 0;
+ if (!it.caplayer && IS_BOT_CLIENT(it))
+ {
+ it.team = -1;
+ it.caplayer = 1;
+ }
+ if (it.caplayer)
+ {
+ TRANSMUTE(Player, it);
+ it.caplayer = 1;
+ PutClientInServer(it);
+ }
+ });
+ bot_relinkplayerlist();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ TRANSMUTE(Observer, player);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, reset_map_global)
+{
+ allowed_to_spawn = true;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = ca_teams;
+}
+
+entity ca_LastPlayerForTeam(entity this)
+{
+ entity last_pl = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+ if (!IS_DEAD(it))
+ if (SAME_TEAM(this, it))
+ if (!last_pl)
+ last_pl = it;
+ else
+ return NULL;
+ });
+ return last_pl;
+}
+
+void ca_LastPlayerForTeam_Notify(entity this)
+{
+ if (round_handler_IsActive())
+ if (round_handler_IsRoundStarted())
+ {
+ entity pl = ca_LastPlayerForTeam(this);
+ if (pl)
+ Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ ca_LastPlayerForTeam_Notify(frag_target);
+ if (!allowed_to_spawn)
+ {
+ frag_target.respawn_flags = RESPAWN_SILENT;
+ // prevent unwanted sudden rejoin as spectator and movement of spectator camera
+ frag_target.respawn_time = time + 2;
+ }
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
+ if(IS_BOT_CLIENT(frag_target))
+ bot_clear(frag_target);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (player.caplayer == 1)
+ ca_LastPlayerForTeam_Notify(player);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (!IS_DEAD(player))
+ ca_LastPlayerForTeam_Notify(player);
+ if (player.killindicator_teamchange == -2) // player wants to spectate
+ player.caplayer = 0;
+ if (player.caplayer)
+ player.frags = FRAGS_LMS_LOSER;
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
+ if (!player.caplayer)
+ return false; // allow team reset
+ return true; // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
+{
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
+ return true;
+}
+
+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");
+}
+
+MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_deathtype = M_ARGV(3, float);
+ float frag_damage = M_ARGV(4, float);
+ float frag_mirrordamage = M_ARGV(5, float);
+
+ if (IS_PLAYER(frag_target))
+ if (!IS_DEAD(frag_target))
+ if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
+ frag_damage = 0;
+
+ frag_mirrordamage = 0;
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(5, float) = frag_mirrordamage;
+}
+
+MUTATOR_HOOKFUNCTION(ca, FilterItem)
+{
+ entity item = M_ARGV(0, entity);
+
+ if (autocvar_g_powerups <= 0)
+ if (item.flags & FL_POWERUP)
+ return true;
+
+ if (autocvar_g_pickup_items <= 0)
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(7, float);
+ float damage_take = M_ARGV(4, float);
+ float damage_save = M_ARGV(5, float);
+
+ float excess = max(0, frag_damage - damage_take - damage_save);
+
+ if (frag_target != frag_attacker && IS_PLAYER(frag_attacker))
+ GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
+}
+
+MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
+{
+ // no respawn calculations needed, player is forced to spectate anyway
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
+{
+ // no regeneration in CA
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateSet)
+{
+ entity client = M_ARGV(0, entity);
+ entity targ = M_ARGV(1, entity);
+
+ if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ if (DIFF_TEAM(targ, client))
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectateNext)
+{
+ entity client = M_ARGV(0, entity);
+
+ if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ {
+ entity targ = M_ARGV(1, entity);
+ M_ARGV(1, entity) = CA_SpectateNext(client, targ);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
+{
+ entity client = M_ARGV(0, entity);
+ entity targ = M_ARGV(1, entity);
+ entity first = M_ARGV(2, entity);
+
+ if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ {
+ do { targ = targ.chain; }
+ while(targ && DIFF_TEAM(targ, client));
+
+ if (!targ)
+ {
+ for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
+
+ if (targ == client.enemy)
+ return MUT_SPECPREV_RETURN;
+ }
+ }
+
+ M_ARGV(1, entity) = targ;
+
+ return MUT_SPECPREV_FOUND;
+}
+
+MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ if (IS_PLAYER(it) || it.caplayer == 1)
+ ++M_ARGV(0, int);
+ ++M_ARGV(1, int);
+ });
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (player.caplayer)
+ {
+ // they're going to spec, we can do other checks
+ if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
+ Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
+ return MUT_SPECCMD_FORCE;
+ }
+
+ return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(ca, WantWeapon)
+{
+ M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
+{
+ return true; // doesn't work well with the whole spectator as player thing
+}
+
+MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
+{
+ entity player = M_ARGV(0, entity);
+
+ return player.caplayer == 1;
+}
+
+MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
+{
+ // most weapons arena
+ if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <server/round_handler.qh>
+#include <server/miscfunctions.qh>
+
+int autocvar_g_ca_point_limit;
+int autocvar_g_ca_point_leadlimit;
+float autocvar_g_ca_round_timelimit;
+bool autocvar_g_ca_team_spawns;
+//int autocvar_g_ca_teams;
+int autocvar_g_ca_teams_override;
+float autocvar_g_ca_warmup;
+
+
+int ca_teams;
+bool allowed_to_spawn;
+
+const int ST_CA_ROUNDS = 1;
+
+bool CA_CheckTeams();
+bool CA_CheckWinner();
+void CA_RoundStart();
+bool ca_isEliminated(entity e);
+
+REGISTER_MUTATOR(ca, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_ca_team_spawns);
+ GameRules_limit_score(autocvar_g_ca_point_limit);
+ GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
+
+ ca_teams = autocvar_g_ca_teams_override;
+ if (ca_teams < 2)
+ ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+ ca_teams = BITS(bound(2, ca_teams, 4));
+ GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
+ field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
+ });
+
+ allowed_to_spawn = true;
+ round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
+ round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
+ EliminatedPlayers_Init(ca_isEliminated);
+ }
+ return 0;
+}
+
+// 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
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/ctf/ctf.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/ctf/ctf.qh>
--- /dev/null
+#include "ctf.qh"
+
+// TODO: split into sv_ctf
+#ifdef SVQC
+#include <common/effects/all.qh>
+#include <common/vehicles/all.qh>
+#include <server/teamplay.qh>
+
+#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;
+bool autocvar_g_ctf_flag_return_carrying;
+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;
+bool autocvar_g_ctf_score_ignore_fields;
+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 != NULL) ? ftos(actor.playerid) : "")));
+ //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (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, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
+ else if(!ctf_captimerecord)
+ Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
+ else if(cap_time < cap_record)
+ Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
+
+ // 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, flag.ctf_pickuptime, cap_time);
+ }
+
+ if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
+ race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
+}
+
+bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
+{
+ int num_perteam = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
+
+ // automatically return if there's only 1 player on the team
+ return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
+ && flag.team);
+}
+
+bool ctf_Return_Customize(entity this, entity client)
+{
+ // only to the carrier
+ return boolean(client == this.owner);
+}
+
+void ctf_FlagcarrierWaypoints(entity player)
+{
+ WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 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(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+ WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+
+ if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
+ {
+ if(!player.wps_enemyflagcarrier)
+ {
+ entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
+ wp.colormod = WPCOLOR_ENEMYFC(player.team);
+ setcefc(wp, ctf_Stalemate_Customize);
+
+ if(IS_REAL_CLIENT(player) && !ctf_stalemate)
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
+ }
+
+ if(!player.wps_flagreturn)
+ {
+ entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
+ owp.colormod = '0 0.8 0.8';
+ //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
+ setcefc(owp, ctf_Return_Customize);
+ }
+ }
+}
+
+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;
+ int players_worseeq, players_total;
+
+ if(ctf_captureshield_max_ratio <= 0)
+ return false;
+
+ s = GameRules_scoring_add(p, CTF_CAPS, 0);
+ s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
+ s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
+ s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
+
+ sr = ((s - s2) + (s3 + s4));
+
+ if(sr >= -ctf_captureshield_min_negscore)
+ return false;
+
+ players_total = players_worseeq = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(DIFF_TEAM(it, p))
+ continue;
+ se = GameRules_scoring_add(it, CTF_CAPS, 0);
+ se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
+ se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
+ se4 = GameRules_scoring_add(it, 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(entity this, entity client)
+{
+ if(!client.ctf_captureshielded) { return false; }
+ if(CTF_SAMETEAM(this, client)) { return false; }
+
+ return true;
+}
+
+void ctf_CaptureShield_Touch(entity this, entity toucher)
+{
+ if(!toucher.ctf_captureshielded) { return; }
+ if(CTF_SAMETEAM(this, toucher)) { return; }
+
+ vector mymid = (this.absmin + this.absmax) * 0.5;
+ vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
+
+ Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
+ if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
+}
+
+void ctf_CaptureShield_Spawn(entity flag)
+{
+ entity shield = new(ctf_captureshield);
+
+ shield.enemy = flag;
+ shield.team = flag.team;
+ settouch(shield, ctf_CaptureShield_Touch);
+ setcefc(shield, ctf_CaptureShield_Customize);
+ shield.effects = EF_ADDITIVE;
+ set_movetype(shield, MOVETYPE_NOCLIP);
+ shield.solid = SOLID_TRIGGER;
+ shield.avelocity = '7 0 11';
+ shield.scale = 0.5;
+
+ setorigin(shield, flag.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
+ set_movetype(flag, MOVETYPE_TOSS);
+ flag.takedamage = DAMAGE_YES;
+ flag.angles = '0 0 0';
+ SetResourceAmountExplicit(flag, RESOURCE_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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
+ _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
+ ctf_EventLog("dropped", player.team, player);
+
+ // scoring
+ GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
+ GameRules_scoring_add(player, CTF_DROPS, 1);
+
+ // waypoints
+ if(autocvar_g_ctf_flag_dropped_waypoint) {
+ entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((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, GetResourceAmount(flag, RESOURCE_HEALTH));
+ }
+
+ player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+
+ if(droptype == DROP_PASS)
+ {
+ flag.pass_distance = 0;
+ flag.pass_sender = NULL;
+ flag.pass_target = NULL;
+ }
+}
+
+void ctf_Handle_Retrieve(entity flag, entity player)
+{
+ entity sender = flag.pass_sender;
+
+ // transfer flag to player
+ flag.owner = player;
+ flag.owner.flagcarried = flag;
+ GameRules_scoring_vip(player, true);
+
+ // 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);
+ }
+ set_movetype(flag, 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);
+
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+ if(it == sender)
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
+ else if(it == player)
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
+ else if(SAME_TEAM(it, sender))
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), 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 = NULL;
+ flag.pass_target = NULL;
+}
+
+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, NULL, "");
+ setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+ flag.owner.flagcarried = NULL;
+ GameRules_scoring_vip(flag.owner, false);
+ flag.owner = NULL;
+ flag.solid = SOLID_TRIGGER;
+ flag.ctf_dropper = player;
+ flag.ctf_droptime = time;
+ navigation_dynamicgoal_set(flag);
+
+ 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
+ set_movetype(flag, 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(NULL, _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, 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, 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);
+
+ if(player.wps_flagreturn)
+ WaypointSprite_Kill(player.wps_flagreturn);
+
+ // captureshield
+ ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
+}
+
+void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
+{
+ return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
+}
+
+// ==============
+// Event Handlers
+// ==============
+
+void nades_GiveBonus(entity player, float score);
+
+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 = NULL, 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((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
+
+ if (toucher.goalentity == flag.bot_basewaypoint)
+ toucher.goalentity_lock_timeout = 0;
+
+ 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, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
+ 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
+ float pscore = 0;
+ if(enemy_flag.score_capture || flag.score_capture)
+ pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
+ GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
+ float capscore = 0;
+ if(enemy_flag.score_team_capture || flag.score_team_capture)
+ capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
+ GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
+
+ old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
+ new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
+ if(!old_time || new_time < old_time)
+ GameRules_scoring_add(player, 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))
+ { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
+ }
+
+ flag.enemy = toucher;
+
+ // 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, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
+ }
+ else if(flag.team)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, 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))
+ {
+ GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
+ GameRules_scoring_add(player, 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)
+ {
+ GameRules_scoring_add(flag.ctf_dropper, 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);
+
+ flag.enemy = player;
+
+ // 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
+
+ // attach the flag to the player
+ flag.owner = player;
+ player.flagcarried = flag;
+ GameRules_scoring_vip(player, true);
+ 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
+ set_movetype(flag, 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: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
+ default: break;
+ }
+
+ // messages and sounds
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), 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_NUM(flag.team, CENTER_CTF_PICKUP));
+ else
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
+
+ Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+
+ if(!flag.team)
+ FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
+
+ if(flag.team)
+ FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
+ if(CTF_SAMETEAM(flag, it))
+ if(SAME_TEAM(player, it))
+ Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+ else
+ Send_Notification(NOTIF_ONE, it, 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
+ GameRules_scoring_add(player, CTF_PICKUPS, 1);
+ nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
+ switch(pickuptype)
+ {
+ case PICKUP_BASE:
+ {
+ GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : 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));
+ GameRules_scoring_add_team(player, SCORE, 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, GetResourceAmount(flag, RESOURCE_HEALTH)); }
+
+ if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+ {
+ switch(returntype)
+ {
+ case RETURN_DROPPED:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
+ case RETURN_DAMAGE:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
+ case RETURN_SPEEDRUN:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
+ case RETURN_NEEDKILL:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
+ default:
+ case RETURN_TIMEOUT:
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
+ }
+ _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
+ ctf_EventLog("returned", flag.team, NULL);
+ flag.enemy = NULL;
+ ctf_RespawnFlag(flag);
+ }
+ }
+}
+
+bool ctf_Stalemate_Customize(entity this, entity client)
+{
+ // make spectators see what the player would see
+ entity e = WaypointSprite_getviewentity(client);
+ entity wp_owner = this.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()
+{
+ // 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 = NULL; // 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, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
+ wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
+ setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
+ }
+ }
+
+ if (!wpforenemy_announced)
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
+
+ wpforenemy_announced = true;
+ }
+ }
+}
+
+void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+ if(ITEM_DAMAGE_NEEDKILL(deathtype))
+ {
+ if(autocvar_g_ctf_flag_return_damage_delay)
+ this.ctf_flagdamaged_byworld = true;
+ else
+ {
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+ }
+ return;
+ }
+ if(autocvar_g_ctf_flag_return_damage)
+ {
+ // reduce health and check if it should be returned
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ ctf_CheckFlagReturn(this, RETURN_DAMAGE);
+ return;
+ }
+}
+
+void ctf_FlagThink(entity this)
+{
+ // declarations
+ entity tmp_entity;
+
+ this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
+ // captureshield
+ if(this == ctf_worldflaglist) // only for the first flag
+ FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
+
+ // sanity checks
+ if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
+ LOG_TRACE("wtf the flag got squashed?");
+ tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
+ if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
+ setsize(this, this.m_mins, this.m_maxs);
+ }
+
+ // main think method
+ switch(this.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(vdist(this.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(this, tmp_entity, CAPTURE_DROPPED);
+ }
+ return;
+ }
+
+ case FLAG_DROPPED:
+ {
+ this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
+
+ if(autocvar_g_ctf_flag_dropped_floatinwater)
+ {
+ vector midpoint = ((this.absmin + this.absmax) * 0.5);
+ if(pointcontents(midpoint) == CONTENT_WATER)
+ {
+ this.velocity = this.velocity * 0.5;
+
+ if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
+ { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+ else
+ { set_movetype(this, MOVETYPE_FLY); }
+ }
+ else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
+ }
+ if(autocvar_g_ctf_flag_return_dropped)
+ {
+ if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
+ {
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(this, RETURN_DROPPED);
+ return;
+ }
+ }
+ if(this.ctf_flagdamaged_byworld)
+ {
+ TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
+ ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
+ return;
+ }
+ else if(autocvar_g_ctf_flag_return_time)
+ {
+ TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
+ ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
+ return;
+ }
+ return;
+ }
+
+ case FLAG_CARRY:
+ {
+ if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
+ {
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
+
+ CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
+ ImpulseCommands(this.owner);
+ }
+ 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(this, this.owner) && this.team)
+ {
+ if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+ ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
+ else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
+ ctf_Handle_Return(this, this.owner);
+ }
+ return;
+ }
+
+ case FLAG_PASSING:
+ {
+ vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
+ targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
+ WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
+
+ if((this.pass_target == NULL)
+ || (IS_DEAD(this.pass_target))
+ || (this.pass_target.flagcarried)
+ || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
+ || ((trace_fraction < 1) && (trace_ent != this.pass_target))
+ || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
+ {
+ // give up, pass failed
+ ctf_Handle_Drop(this, NULL, DROP_PASS);
+ }
+ else
+ {
+ // still a viable target, go for it
+ ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
+ }
+ return;
+ }
+
+ default: // this should never happen
+ {
+ LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
+ return;
+ }
+ }
+}
+
+METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
+{
+ return = false;
+ if(game_stopped) return;
+ if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
+
+ bool is_not_monster = (!IS_MONSTER(toucher));
+
+ // 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)
+ {
+ SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+ ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
+ }
+ if(!flag.ctf_flagdamaged_byworld) { return; }
+ }
+
+ // special touch behaviors
+ if(STAT(FROZEN, toucher)) { 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 > flag.wait) // if we haven't in a while, play a sound/effect
+ {
+ Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
+ _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+ flag.wait = time + FLAG_TOUCHRATE;
+ }
+ return;
+ }
+ else if(IS_DEAD(toucher)) { return; }
+
+ switch(flag.ctf_status)
+ {
+ case FLAG_BASE:
+ {
+ if(ctf_oneflag)
+ {
+ if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+ ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+ else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+ ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+ }
+ else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
+ ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
+ else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
+ {
+ ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
+ ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
+ }
+ else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+ ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
+ break;
+ }
+
+ case FLAG_DROPPED:
+ {
+ if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
+ ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
+ else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+ ctf_Handle_Pickup(flag, 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?");
+ break;
+ }
+
+ case FLAG_PASSING:
+ {
+ if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
+ {
+ if(DIFF_TEAM(toucher, flag.pass_sender))
+ {
+ if(ctf_Immediate_Return_Allowed(flag, toucher))
+ ctf_Handle_Return(flag, toucher);
+ else if(is_not_monster && (!toucher.flagcarried))
+ ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
+ }
+ else if(!toucher.flagcarried)
+ ctf_Handle_Retrieve(flag, 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.owner.wps_flagreturn);
+ WaypointSprite_Kill(flag.wps_flagcarrier);
+
+ flag.owner.flagcarried = NULL;
+ GameRules_scoring_vip(flag.owner, false);
+
+ 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, NULL, "");
+ setorigin(flag, flag.ctf_spawnorigin);
+
+ set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
+ flag.takedamage = DAMAGE_NO;
+ SetResourceAmountExplicit(flag, RESOURCE_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 = NULL;
+ flag.pass_distance = 0;
+ flag.pass_sender = NULL;
+ flag.pass_target = NULL;
+ flag.ctf_dropper = NULL;
+ flag.ctf_pickuptime = 0;
+ flag.ctf_droptime = 0;
+ flag.ctf_flagdamaged_byworld = false;
+ navigation_dynamicgoal_unset(flag);
+
+ ctf_CheckStalemate();
+}
+
+void ctf_Reset(entity this)
+{
+ if(this.owner && IS_PLAYER(this.owner))
+ ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
+
+ this.enemy = NULL;
+ ctf_RespawnFlag(this);
+}
+
+bool ctf_FlagBase_Customize(entity this, entity client)
+{
+ entity e = WaypointSprite_getviewentity(client);
+ entity wp_owner = this.owner;
+ entity flag = e.flagcarried;
+ if(flag && CTF_SAMETEAM(e, flag))
+ return false;
+ if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
+ return false;
+ return true;
+}
+
+void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
+{
+ // bot waypoints
+ waypoint_spawnforitem_force(this, this.origin);
+ navigation_dynamicgoal_init(this, true);
+
+ // waypointsprites
+ entity basename;
+ switch (this.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, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
+ wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
+ WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+ setcefc(wp, ctf_FlagBase_Customize);
+
+ // captureshield setup
+ ctf_CaptureShield_Spawn(this);
+}
+
+.bool pushable;
+
+void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+{
+ // main setup
+ flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
+ ctf_worldflaglist = flag;
+
+ setattachment(flag, NULL, "");
+
+ 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;
+ IL_PUSH(g_items, flag);
+ 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);
+ SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+ flag.event_damage = ctf_FlagDamage;
+ flag.pushable = true;
+ flag.teleportable = TELEPORT_NORMAL;
+ flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
+ flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
+ flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
+ if(flag.damagedbycontents)
+ IL_PUSH(g_damagedbycontents, flag);
+ flag.velocity = '0 0 0';
+ flag.mangle = flag.angles;
+ flag.reset = ctf_Reset;
+ settouch(flag, ctf_FlagTouch);
+ setthink(flag, ctf_FlagThink);
+ flag.nextthink = time + FLAG_THINKRATE;
+ flag.ctf_status = FLAG_BASE;
+
+ // crudely force them all to 0
+ if(autocvar_g_ctf_score_ignore_fields)
+ flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
+
+ 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)); }
+ if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
+ if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
+ if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
+
+ // sounds
+#define X(s,b) \
+ if(flag.s == "") flag.s = b; \
+ precache_sound(flag.s);
+
+ X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
+ X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
+ X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
+ X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
+ X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
+ X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
+ X(snd_flag_pass, strzone(SND(CTF_PASS)))
+#undef X
+
+ // precache
+ precache_model(flag.model);
+
+ // appearence
+ _setmodel(flag, flag.model); // precision set below
+ setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
+ flag.m_mins = flag.mins; // store these for squash checks
+ flag.m_maxs = flag.maxs;
+ 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;
+ set_movetype(flag, MOVETYPE_NONE);
+ }
+ else // drop to floor, automatically find a platform and set that as spawn origin
+ {
+ flag.noalign = false;
+ droptofloor(flag);
+ set_movetype(flag, MOVETYPE_NONE);
+ }
+
+ InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_ctf_calculate_middlepoint()
+{
+ entity f;
+ vector s = '0 0 0';
+ vector fo = '0 0 0';
+ int n = 0;
+
+ f = ctf_worldflaglist;
+ while (f)
+ {
+ fo = f.origin;
+ s = s + fo;
+ f = f.ctf_worldflagnext;
+ n++;
+ }
+ if(!n)
+ return;
+
+ havocbot_middlepoint = s / n;
+ havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
+
+ havocbot_symmetry_axis_m = 0;
+ havocbot_symmetry_axis_q = 0;
+ if(n == 2)
+ {
+ // for symmetrical editing of waypoints
+ entity f1 = ctf_worldflaglist;
+ entity f2 = f1.ctf_worldflagnext;
+ float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+ float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
+ havocbot_symmetry_axis_m = m;
+ havocbot_symmetry_axis_q = q;
+ }
+ havocbot_symmetry_origin_order = n;
+}
+
+
+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 NULL;
+}
+
+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 NULL;
+}
+
+int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+{
+ if (!teamplay)
+ return 0;
+
+ int c = 0;
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
+ continue;
+
+ if(vdist(it.origin - org, <, tc_radius))
+ ++c;
+ });
+
+ return c;
+}
+
+// unused
+#if 0
+void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (CTF_SAMETEAM(this, head))
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if (head)
+ navigation_routerating(this, head, ratingscale, 10000);
+}
+#endif
+
+void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if (CTF_SAMETEAM(this, head))
+ {
+ if (this.flagcarried)
+ if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
+ {
+ head = head.ctf_worldflagnext; // skip base if it has a different group
+ continue;
+ }
+ break;
+ }
+ head = head.ctf_worldflagnext;
+ }
+ if (!head)
+ return;
+
+ navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
+{
+ entity head;
+ head = ctf_worldflaglist;
+ while (head)
+ {
+ if(ctf_oneflag)
+ {
+ if(CTF_DIFFTEAM(this, head))
+ {
+ if(head.team)
+ {
+ if(this.flagcarried)
+ break;
+ }
+ else if(!this.flagcarried)
+ break;
+ }
+ }
+ else if(CTF_DIFFTEAM(this, head))
+ break;
+ head = head.ctf_worldflagnext;
+ }
+ if (head)
+ navigation_routerating(this, head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
+{
+ if (!bot_waypoints_for_items)
+ {
+ havocbot_goalrating_ctf_enemyflag(this, ratingscale);
+ return;
+ }
+
+ entity head;
+
+ head = havocbot_ctf_find_enemy_flag(this);
+
+ if (!head)
+ return;
+
+ navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
+{
+ entity mf;
+
+ mf = havocbot_ctf_find_flag(this);
+
+ if(mf.ctf_status == FLAG_BASE)
+ return;
+
+ if(mf.tag_entity)
+ navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(entity this, 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==NULL) // dropped
+ {
+ if(df_radius)
+ {
+ if(vdist(org - head.origin, <, df_radius))
+ navigation_routerating(this, head, ratingscale, 10000);
+ }
+ else
+ navigation_routerating(this, head, ratingscale, 10000);
+ }
+
+ head = head.ctf_worldflagnext;
+ }
+}
+
+void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
+{
+ IL_EACH(g_items, it.bot_pickup,
+ {
+ // gather health and armor only
+ if (it.solid)
+ if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
+ if (vdist(it.origin - org, <, sradius))
+ {
+ // get the value of the item
+ float t = it.bot_pickupevalfunc(this, it) * 0.0001;
+ if (t > 0)
+ navigation_routerating(this, it, t * ratingscale, 500);
+ }
+ });
+}
+
+void havocbot_ctf_reset_role(entity this)
+{
+ float cdefense, cmiddle, coffense;
+ entity mf, ef;
+ float c;
+
+ if(IS_DEAD(this))
+ return;
+
+ // Check ctf flags
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ mf = havocbot_ctf_find_flag(this);
+ ef = havocbot_ctf_find_enemy_flag(this);
+
+ // Retrieve stolen flag
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, 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(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+
+ // if there is only me on the team switch to offense
+ c = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
+
+ if(c==1)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+ return;
+ }
+
+ // Evaluate best position to take
+ // Count mates on middle position
+ cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
+
+ // Count mates on defense position
+ cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
+
+ // Count mates on offense position
+ coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
+
+ if(cdefense<=coffense)
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+ else if(coffense<=cmiddle)
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
+ else
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+}
+
+void havocbot_role_ctf_carrier(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried == NULL)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ if(ctf_oneflag)
+ havocbot_goalrating_ctf_enemybase(this, 50000);
+ else
+ havocbot_goalrating_ctf_ourbase(this, 50000);
+
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
+ havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+
+ entity head = ctf_worldflaglist;
+ while (head)
+ {
+ if (this.goalentity == head.bot_basewaypoint)
+ {
+ this.goalentity_lock_timeout = time + 5;
+ break;
+ }
+ head = head.ctf_worldflagnext;
+ }
+
+ if (this.goalentity)
+ this.havocbot_cantfindflag = time + 10;
+ else if (time > this.havocbot_cantfindflag)
+ {
+ // Can't navigate to my own base, suicide!
+ // TODO: drop it and wander around
+ Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
+ return;
+ }
+ }
+}
+
+void havocbot_role_ctf_escort(entity this)
+{
+ entity mf, ef;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If enemy flag is back on the base switch to previous role
+ ef = havocbot_ctf_find_enemy_flag(this);
+ if(ef.ctf_status==FLAG_BASE)
+ {
+ this.havocbot_role = this.havocbot_previous_role;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ // If the flag carrier reached the base switch to defense
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status!=FLAG_BASE)
+ if(vdist(ef.origin - mf.dropped_origin, <, 300))
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ {
+ this.havocbot_role_timeout = time + random() * 30 + 60;
+ }
+
+ // If nothing happened just switch to previous role
+ if (time > this.havocbot_role_timeout)
+ {
+ this.havocbot_role = this.havocbot_previous_role;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ // Chase the flag carrier
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ havocbot_goalrating_ctf_enemyflag(this, 30000);
+ havocbot_goalrating_ctf_ourstolenflag(this, 40000);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_offense(entity this)
+{
+ entity mf, ef;
+ vector pos;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // Check flags
+ mf = havocbot_ctf_find_flag(this);
+ ef = havocbot_ctf_find_enemy_flag(this);
+
+ // 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(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
+ {
+ havocbot_role_ctf_setrole(this, 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(vdist(pos - mf.dropped_origin, >, 700))
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
+ return;
+ }
+ }
+
+ // About to fail, switch to middlefield
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+ return;
+ }
+
+ // Set the role timeout if necessary
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 120;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ havocbot_goalrating_ctf_ourstolenflag(this, 50000);
+ havocbot_goalrating_ctf_enemybase(this, 20000);
+ havocbot_goalrating_items(this, 5000, this.origin, 1000);
+ havocbot_goalrating_items(this, 1000, this.origin, 10000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever(entity this)
+{
+ entity mf;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If flag is back on the base switch to previous role
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status==FLAG_BASE)
+ {
+ if (mf.enemy == this) // did this bot return the flag?
+ navigation_goalrating_timeout_force(this);
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 20;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ float rt_radius;
+ rt_radius = 10000;
+
+ navigation_goalrating_start(this);
+
+ havocbot_goalrating_ctf_ourstolenflag(this, 50000);
+ havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
+ havocbot_goalrating_ctf_enemybase(this, 30000);
+ havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_middle(entity this)
+{
+ entity mf;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 10;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ vector org;
+
+ org = havocbot_middlepoint;
+ org.z = this.origin.z;
+
+ navigation_goalrating_start(this);
+
+ havocbot_goalrating_ctf_ourstolenflag(this, 50000);
+ havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
+ havocbot_goalrating_items(this, 2500, this.origin, 10000);
+ havocbot_goalrating_ctf_enemybase(this, 2500);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_defense(entity this)
+{
+ entity mf;
+
+ if(IS_DEAD(this))
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+
+ if (this.flagcarried)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
+ return;
+ }
+
+ // If own flag was captured
+ mf = havocbot_ctf_find_flag(this);
+ if(mf.ctf_status!=FLAG_BASE)
+ {
+ havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + 30;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ havocbot_ctf_reset_role(this);
+ return;
+ }
+ if (navigation_goalrating_timeout(this))
+ {
+ vector org = mf.dropped_origin;
+
+ navigation_goalrating_start(this);
+
+ // if enemies are closer to our base, go there
+ entity closestplayer = NULL;
+ float distance, bestdistance = 10000;
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
+ distance = vlen(org - it.origin);
+ if(distance<bestdistance)
+ {
+ closestplayer = it;
+ bestdistance = distance;
+ }
+ });
+
+ if(closestplayer)
+ if(DIFF_TEAM(closestplayer, this))
+ if(vdist(org - this.origin, >, 1000))
+ if(checkpvs(this.origin,closestplayer)||random()<0.5)
+ havocbot_goalrating_ctf_ourbase(this, 30000);
+
+ havocbot_goalrating_ctf_ourstolenflag(this, 20000);
+ havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
+ havocbot_goalrating_items(this, 5000, this.origin, 10000);
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ctf_setrole(entity bot, int role)
+{
+ string s = "(null)";
+ switch(role)
+ {
+ case HAVOCBOT_CTF_ROLE_CARRIER:
+ s = "carrier";
+ bot.havocbot_role = havocbot_role_ctf_carrier;
+ bot.havocbot_role_timeout = 0;
+ bot.havocbot_cantfindflag = time + 10;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_force(bot);
+ break;
+ case HAVOCBOT_CTF_ROLE_DEFENSE:
+ s = "defense";
+ bot.havocbot_role = havocbot_role_ctf_defense;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_MIDDLE:
+ s = "middle";
+ bot.havocbot_role = havocbot_role_ctf_middle;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_OFFENSE:
+ s = "offense";
+ bot.havocbot_role = havocbot_role_ctf_offense;
+ bot.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_CTF_ROLE_RETRIEVER:
+ s = "retriever";
+ bot.havocbot_previous_role = bot.havocbot_role;
+ bot.havocbot_role = havocbot_role_ctf_retriever;
+ bot.havocbot_role_timeout = time + 10;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_expire(bot, 2);
+ break;
+ case HAVOCBOT_CTF_ROLE_ESCORT:
+ s = "escort";
+ bot.havocbot_previous_role = bot.havocbot_role;
+ bot.havocbot_role = havocbot_role_ctf_escort;
+ bot.havocbot_role_timeout = time + 30;
+ if (bot.havocbot_previous_role != bot.havocbot_role)
+ navigation_goalrating_timeout_expire(bot, 2);
+ break;
+ }
+ LOG_TRACE(bot.netname, " switched to ", s);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ int t = 0, t2 = 0, t3 = 0;
+ bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
+
+ // initially clear items so they can be set as necessary later.
+ STAT(CTF_FLAGSTATUS, player) &= ~(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 | CTF_STALEMATE);
+
+ // scan through all the flags and notify the client about them
+ for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
+ if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
+ if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
+ if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
+ if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
+
+ switch(flag.ctf_status)
+ {
+ case FLAG_PASSING:
+ case FLAG_CARRY:
+ {
+ if((flag.owner == player) || (flag.pass_sender == player))
+ STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
+ else
+ STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
+ break;
+ }
+ case FLAG_DROPPED:
+ {
+ STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
+ break;
+ }
+ }
+ }
+
+ // item for stopping players from capturing the flag too often
+ if(player.ctf_captureshielded)
+ STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
+
+ if(ctf_stalemate)
+ STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
+
+ // update the health of the flag carrier waypointsprite
+ if(player.wps_flagcarrier)
+ WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+}
+
+MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(4, float);
+ vector frag_force = M_ARGV(6, vector);
+
+ 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;
+ }
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(6, vector) = frag_force;
+ }
+ else if(frag_target.flagcarried && !IS_DEAD(frag_target) && 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(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), 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?
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
+ {
+ GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
+ GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
+ }
+
+ if(frag_target.flagcarried)
+ {
+ entity tmp_entity = frag_target.flagcarried;
+ ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
+ tmp_entity.ctf_dropper = NULL;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
+{
+ M_ARGV(2, float) = 0; // frag score
+ 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, NULL, DROP_NORMAL); }
+
+ for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ if(flag.pass_sender == player) { flag.pass_sender = NULL; }
+ if(flag.pass_target == player) { flag.pass_target = NULL; }
+ if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ ctf_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
+{
+ if(!autocvar_g_ctf_leaderboard)
+ return;
+
+ entity player = M_ARGV(0, entity);
+
+ if(IS_REAL_CLIENT(player))
+ {
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (int i = 1; i <= m; ++i)
+ {
+ race_SendRankings(i, 0, 0, MSG_ONE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
+{
+ if(!autocvar_g_ctf_leaderboard)
+ return;
+
+ entity player = M_ARGV(0, entity);
+
+ if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.flagcarried)
+ if(!autocvar_g_ctf_portalteleport)
+ { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
+{
+ if(MUTATOR_RETURNVALUE || game_stopped) return;
+
+ entity player = M_ARGV(0, entity);
+
+ if((time > player.throw_antispam) && !IS_DEAD(player) && !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 = NULL;
+ 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) && !IS_DEAD(head))
+ 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 && !head.flagcarried)
+ {
+ if(closest_target)
+ {
+ vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
+ if(vlen2(passer_center - head_center) < vlen2(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, NULL, 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, NULL, DROP_THROW);
+ return true;
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
+ {
+ player.wps_helpme_time = time;
+ WaypointSprite_HelpMePing(player.wps_flagcarrier);
+ }
+ else // create a normal help me waypointsprite
+ {
+ WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
+ WaypointSprite_Ping(player.wps_helpme);
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
+{
+ entity player = M_ARGV(0, entity);
+ entity veh = M_ARGV(1, entity);
+
+ if(player.flagcarried)
+ {
+ if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
+ {
+ ctf_Handle_Throw(player, NULL, DROP_NORMAL);
+ }
+ else
+ {
+ player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
+ setattachment(player.flagcarried, veh, "");
+ setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
+ player.flagcarried.scale = VEHICLE_FLAG_SCALE;
+ //player.flagcarried.angles = '0 0 0';
+ }
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.flagcarried)
+ {
+ setattachment(player.flagcarried, player, "");
+ setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
+ player.flagcarried.scale = FLAG_SCALE;
+ player.flagcarried.angles = '0 0 0';
+ player.flagcarried.nodrawtoclient = NULL;
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.flagcarried)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
+ ctf_RespawnFlag(player.flagcarried);
+ return true;
+ }
+}
+
+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
+ set_movetype(flag, 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;
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ havocbot_ctf_reset_role(bot);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
+{
+ //M_ARGV(0, float) = ctf_teams;
+ M_ARGV(1, string) = "ctf_team";
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
+{
+ entity spectatee = M_ARGV(0, entity);
+ entity client = M_ARGV(1, entity);
+
+ STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(ctf, GetRecords)
+{
+ int record_page = M_ARGV(0, int);
+ string ret_string = M_ARGV(1, string);
+
+ 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");
+ }
+ }
+
+ M_ARGV(1, string) = ret_string;
+}
+
+bool superspec_Spectate(entity this, entity targ); // TODO
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
+MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
+{
+ entity player = M_ARGV(0, entity);
+ string cmd_name = M_ARGV(1, string);
+ int cmd_argc = M_ARGV(2, int);
+
+ if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
+
+ if(cmd_name == "followfc")
+ {
+ if(!g_ctf)
+ return true;
+
+ int _team = 0;
+ bool found = false;
+
+ if(cmd_argc == 2)
+ {
+ switch(argv(1))
+ {
+ case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
+ case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
+ case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
+ case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
+ }
+ }
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(it.flagcarried && (it.team == _team || _team == 0))
+ {
+ found = true;
+ if(_team == 0 && IS_SPEC(player) && player.enemy == it)
+ continue; // already spectating this fc, try another
+ return superspec_Spectate(player, it);
+ }
+ });
+
+ if(!found)
+ superspec_msg("", "", player, "No active flag carrier\n", 1);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ if(frag_target.flagcarried)
+ ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
+}
+
+
+// ==========
+// 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) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_1, this);
+}
+
+/*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) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_2, this);
+}
+
+/*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) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_3, this);
+}
+
+/*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) { delete(this); return; }
+
+ ctf_FlagSetup(NUM_TEAM_4, this);
+}
+
+/*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) { delete(this); return; }
+ if(!cvar("g_ctf_oneflag")) { delete(this); return; }
+
+ ctf_FlagSetup(0, this);
+}
+
+/*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) { delete(this); return; }
+
+ this.classname = "ctf_team";
+ this.team = this.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); }
+
+spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
+spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
+
+// compatibility for wop maps
+spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
+spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
+spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
+spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
+spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
+spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
+
+
+// ==============
+// Initialization
+// ==============
+
+// scoreboard setup
+void ctf_ScoreRules(int teams)
+{
+ CheckAllowedTeams(NULL);
+ GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+ field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_CTF_PICKUPS, "pickups", 0);
+ field(SP_CTF_FCKILLS, "fckills", 0);
+ field(SP_CTF_RETURNS, "returns", 0);
+ field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
+ });
+}
+
+// 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_pure(ctf_team);
+ this.netname = teamname;
+ this.cnt = teamcolor - 1;
+ this.spawnfunc_checked = true;
+ this.team = teamcolor;
+}
+
+void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+ ctf_teams = 0;
+
+ 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); }
+
+ switch(tmp_entity.team)
+ {
+ case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
+ case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
+ case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
+ case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
+ }
+ if(tmp_entity.team == 0) { ctf_oneflag = true; }
+ }
+
+ havocbot_ctf_calculate_middlepoint();
+
+ if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
+ {
+ ctf_teams = 0; // so set the default red and blue teams
+ BITSET_ASSIGN(ctf_teams, BIT(0));
+ BITSET_ASSIGN(ctf_teams, BIT(1));
+ }
+
+ //ctf_teams = bound(2, ctf_teams, 4);
+
+ // if no teams are found, spawn defaults
+ if(find(NULL, classname, "ctf_team") == NULL)
+ {
+ LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
+ if(ctf_teams & BIT(0))
+ ctf_SpawnTeam("Red", NUM_TEAM_1);
+ if(ctf_teams & BIT(1))
+ ctf_SpawnTeam("Blue", NUM_TEAM_2);
+ if(ctf_teams & BIT(2))
+ ctf_SpawnTeam("Yellow", NUM_TEAM_3);
+ if(ctf_teams & BIT(3))
+ ctf_SpawnTeam("Pink", NUM_TEAM_4);
+ }
+
+ 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;
+
+ InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+
+void ctf_Initialize();
+
+REGISTER_MUTATOR(ctf, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_limit_score(autocvar_capturelimit_override);
+ GameRules_limit_lead(autocvar_captureleadlimit_override);
+
+ ctf_Initialize();
+ }
+ return 0;
+}
+
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag);
+
+// score rule declarations
+const int ST_CTF_CAPS = 1;
+
+CLASS(Flag, Pickup)
+ ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
+ ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
+ENDCLASS(Flag)
+Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
+void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
+
+// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
+
+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) ((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;
+
+// score fields
+.float score_assist;
+.float score_capture;
+.float score_drop; // note: negated
+.float score_pickup;
+.float score_return;
+.float score_team_capture; // shouldn't be too high
+
+// 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 wps_helpme;
+.entity wps_flagbase;
+.entity wps_flagcarrier;
+.entity wps_flagdropped;
+.entity wps_flagreturn;
+.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;
+
+bool ctf_Stalemate_Customize(entity this, entity client);
+
+void ctf_Handle_Throw(entity player, entity receiver, float droptype);
+
+// 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_byworld;
+int ctf_teams;
+.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
+
+// 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;
+
+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))
+#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;
+const int CTF_STALEMATE = 8192;
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/cts/cts.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/cts/cts.qh>
--- /dev/null
+#include "cts.qh"
+
+// TODO: split into sv_cts
+#ifdef SVQC
+#include <server/race.qh>
+#include <server/items.qh>
+
+float autocvar_g_cts_finish_kill_delay;
+bool autocvar_g_cts_selfdamage;
+
+// legacy bot roles
+.float race_checkpoint;
+void havocbot_role_cts(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ bool raw_touch_check = true;
+ int cp = this.race_checkpoint;
+
+ LABEL(search_racecheckpoints)
+ IL_EACH(g_racecheckpoints, true,
+ {
+ if(it.cnt == cp || cp == -1)
+ {
+ // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+ // e.g. checkpoint in front of Stormkeep's warpzone
+ // the same workaround is applied in Race game mode
+ if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+ {
+ cp = race_NextCheckpoint(cp);
+ raw_touch_check = false;
+ goto search_racecheckpoints;
+ }
+ navigation_routerating(this, it, 1000000, 5000);
+ }
+ });
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void cts_ScoreRules()
+{
+ GameRules_score_enabled(false);
+ GameRules_scoring(0, 0, 0, {
+ if (g_race_qualifying) {
+ field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ } else {
+ field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+ }
+ });
+}
+
+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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void KillIndicator_Think(entity this);
+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;
+ setthink(e.killindicator, KillIndicator_Think);
+ e.killindicator.nextthink = time + (e.lip) * 0.05;
+ e.killindicator.cnt = ceil(autocvar_g_cts_finish_kill_delay);
+ e.killindicator.count = 1; // this is used to indicate that it should be silent
+ e.lip = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerPhysics)
+{
+ entity player = M_ARGV(0, entity);
+ float dt = M_ARGV(1, float);
+
+ player.race_movetime_frac += dt;
+ float f = floor(player.race_movetime_frac);
+ player.race_movetime_frac -= f;
+ player.race_movetime_count += f;
+ player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+#ifdef SVQC
+ if(IS_PLAYER(player))
+ {
+ if (player.race_penalty)
+ if (time > player.race_penalty)
+ player.race_penalty = 0;
+ if(player.race_penalty)
+ {
+ player.velocity = '0 0 0';
+ set_movetype(player, MOVETYPE_NONE);
+ player.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(CS(player).movement.x);
+ wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
+ CS(player).movement_x = wishspeed;
+ else
+ CS(player).movement_x = -wishspeed;
+ CS(player).movement_y = 0;
+ }
+ else if(wishvel.y >= 2 * wishvel.x)
+ {
+ // pure Y motion
+ CS(player).movement_x = 0;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = wishspeed;
+ else
+ CS(player).movement_y = -wishspeed;
+ }
+ else
+ {
+ // diagonal
+ if(CS(player).movement.x > 0)
+ CS(player).movement_x = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, reset_map_global)
+{
+ float s;
+
+ Score_NicePrint(NULL);
+
+ race_ClearRecords();
+ PlayerScore_Sort(race_place, 0, 1, 0);
+
+ FOREACH_CLIENT(true, {
+ if(it.race_place)
+ {
+ s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+ if(!s)
+ it.race_place = 0;
+ }
+ cts_EventLog(ftos(it.race_place), it);
+ });
+
+ 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();
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+
+ if(IS_REAL_CLIENT(player))
+ {
+ string rr = CTS_RECORD;
+
+ msg_entity = player;
+ 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;
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (i = 1; i <= m; ++i)
+ {
+ race_SendRankings(i, 0, 0, MSG_ONE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(autocvar_g_allow_checkpoints)
+ race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+ player.frags = FRAGS_LMS_LOSER;
+ else
+ player.frags = FRAGS_SPECTATOR;
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+ entity spawn_spot = M_ARGV(1, entity);
+
+ if(spawn_spot.target == "")
+ // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+ race_PreparePlayer(player);
+
+ // if we need to respawn, do it right
+ player.race_respawn_checkpoint = player.race_checkpoint;
+ player.race_respawn_spotref = spawn_spot;
+
+ player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(IS_PLAYER(player))
+ if(!game_stopped)
+ {
+ if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+ race_PreparePlayer(player);
+ else // respawn
+ race_RetractPlayer(player);
+
+ race_AbandonRaceCheck(player);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+ race_AbandonRaceCheck(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ bot.havocbot_role = havocbot_role_cts;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
+
+ if (!IS_OBSERVER(player))
+ {
+ if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+ {
+ speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+ speedaward_holder = player.netname;
+ speedaward_uid = player.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);
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
+{
+ // no weapon dropping in CTS
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, FilterItem)
+{
+ entity item = M_ARGV(0, entity);
+
+ if (Item_IsLoot(item))
+ {
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_deathtype = M_ARGV(3, float);
+ float frag_damage = M_ARGV(4, float);
+
+ if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
+ if(!autocvar_g_cts_selfdamage)
+ {
+ frag_damage = 0;
+ M_ARGV(4, float) = frag_damage;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
+{
+ return true; // in CTS, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(cts, GetRecords)
+{
+ int record_page = M_ARGV(0, int);
+ string ret_string = M_ARGV(1, string);
+
+ 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");
+ }
+ }
+
+ M_ARGV(1, string) = ret_string;
+}
+
+void ClientKill_Now(entity this);
+MUTATOR_HOOKFUNCTION(cts, ClientKill)
+{
+ entity player = M_ARGV(0, entity);
+
+ M_ARGV(1, float) = 0; // kill delay
+
+ if(player.killindicator && player.killindicator.count == 1) // player.killindicator.count == 1 means that the kill indicator was spawned by CTS_ClientKill
+ {
+ delete(player.killindicator);
+ player.killindicator = NULL;
+
+ ClientKill_Now(player); // allow instant kill in this case
+ return;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(autocvar_g_cts_finish_kill_delay)
+ CTS_ClientKill(player);
+}
+
+MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
+{
+ return true; // doesn't work so well (but isn't cts a teamless mode?)
+}
+
+MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
+{
+ entity player = M_ARGV(0, entity);
+
+ stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(cts, WantWeapon)
+{
+ M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
+ M_ARGV(3, bool) = true; // want mutator blocked
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
+{
+ return true;
+}
+
+void cts_Initialize()
+{
+ cts_ScoreRules();
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <server/race.qh>
+
+void cts_Initialize();
+
+REGISTER_MUTATOR(cts, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ g_race_qualifying = true;
+ independent_players = 1;
+ GameRules_limit_score(0);
+ GameRules_limit_lead(0);
+
+ cts_Initialize();
+ }
+ return 0;
+}
+
+// scores
+const float ST_CTS_LAPS = 1;
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/deathmatch/deathmatch.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/deathmatch/deathmatch.qh>
--- /dev/null
+#include "deathmatch.qh"
+
+// TODO: sv_deathmatch?
+#ifdef SVQC
+MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+REGISTER_MUTATOR(dm, false)
+{
+ MUTATOR_STATIC();
+ return 0;
+}
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/domination/domination.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/domination/domination.qh>
--- /dev/null
+#include "domination.qh"
+
+// TODO: sv_domination
+#ifdef SVQC
+#include <server/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;
+float autocvar_g_domination_round_timelimit;
+float autocvar_g_domination_warmup;
+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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void set_dom_state(entity e)
+{
+ STAT(DOM_TOTAL_PPS, e) = total_pps;
+ STAT(DOM_PPS_RED, e) = pps_red;
+ STAT(DOM_PPS_BLUE, e) = pps_blue;
+ if(domination_teams >= 3)
+ STAT(DOM_PPS_YELLOW, e) = pps_yellow;
+ if(domination_teams >= 4)
+ STAT(DOM_PPS_PINK, e) = pps_pink;
+}
+
+void dompoint_captured(entity this)
+{
+ float old_delay, old_team, real_team;
+
+ // now that the delay has expired, switch to the latest team to lay claim to this point
+ entity head = this.owner;
+
+ real_team = this.cnt;
+ this.cnt = -1;
+
+ dom_EventLog("taken", this.team, this.dmg_inflictor);
+ this.dmg_inflictor = NULL;
+
+ this.goalentity = head;
+ this.model = head.mdl;
+ this.modelindex = head.dmg;
+ this.skin = head.skin;
+
+ float points, wait_time;
+ if (autocvar_g_domination_point_amt)
+ points = autocvar_g_domination_point_amt;
+ else
+ points = this.frags;
+ if (autocvar_g_domination_point_rate)
+ wait_time = autocvar_g_domination_point_rate;
+ else
+ wait_time = this.wait;
+
+ if(domination_roundbased)
+ bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
+
+ if(this.enemy.playerid == this.enemy_playerid)
+ GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
+ else
+ this.enemy = NULL;
+
+ if (head.noise != "")
+ if(this.enemy)
+ _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+ else
+ _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
+ if (head.noise1 != "")
+ play2all(head.noise1);
+
+ this.delay = time + wait_time;
+
+ // do trigger work
+ old_delay = this.delay;
+ old_team = this.team;
+ this.team = real_team;
+ this.delay = 0;
+ SUB_UseTargets (this, this, NULL);
+ this.delay = old_delay;
+ this.team = old_team;
+
+ entity msg = WP_DomNeut;
+ switch(real_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(this.sprite, msg, WP_Null, WP_Null);
+
+ total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+ IL_EACH(g_dompoints, true,
+ {
+ if (autocvar_g_domination_point_amt)
+ points = autocvar_g_domination_point_amt;
+ else
+ points = it.frags;
+ if (autocvar_g_domination_point_rate)
+ wait_time = autocvar_g_domination_point_rate;
+ else
+ wait_time = it.wait;
+ switch(it.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(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
+ WaypointSprite_Ping(this.sprite);
+
+ this.captime = time;
+
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
+}
+
+void AnimateDomPoint(entity this)
+{
+ if(this.pain_finished > time)
+ return;
+ this.pain_finished = time + this.t_width;
+ if(this.nextthink > this.pain_finished)
+ this.nextthink = this.pain_finished;
+
+ this.frame = this.frame + 1;
+ if(this.frame > this.t_length)
+ this.frame = 0;
+}
+
+void dompointthink(entity this)
+{
+ float fragamt;
+
+ this.nextthink = time + 0.1;
+
+ //this.frame = this.frame + 1;
+ //if(this.frame > 119)
+ // this.frame = 0;
+ AnimateDomPoint(this);
+
+ // give points
+
+ if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
+ return;
+
+ if(autocvar_g_domination_point_rate)
+ this.delay = time + autocvar_g_domination_point_rate;
+ else
+ this.delay = time + this.wait;
+
+ // give credit to the team
+ // NOTE: this defaults to 0
+ if (!domination_roundbased)
+ if (this.goalentity.netname != "")
+ {
+ if(autocvar_g_domination_point_amt)
+ fragamt = autocvar_g_domination_point_amt;
+ else
+ fragamt = this.frags;
+ TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
+ TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
+
+ // give credit to the individual player, if he is still there
+ if (this.enemy.playerid == this.enemy_playerid)
+ {
+ GameRules_scoring_add(this.enemy, SCORE, fragamt);
+ GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
+ }
+ else
+ this.enemy = NULL;
+ }
+}
+
+void dompointtouch(entity this, entity toucher)
+{
+ if(!IS_PLAYER(toucher))
+ return;
+ if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
+ return;
+
+ if(round_handler_IsActive() && !round_handler_IsRoundStarted())
+ return;
+
+ if(time < this.captime + 0.3)
+ return;
+
+ // only valid teams can claim it
+ entity head = find(NULL, classname, "dom_team");
+ while (head && head.team != toucher.team)
+ head = find(head, classname, "dom_team");
+ if (!head || head.netname == "" || head == this.goalentity)
+ return;
+
+ // delay capture
+
+ this.team = this.goalentity.team; // this stores the PREVIOUS team!
+
+ this.cnt = toucher.team;
+ this.owner = head; // team to switch to after the delay
+ this.dmg_inflictor = toucher;
+
+ // this.state = 1;
+ // this.delay = time + cvar("g_domination_point_capturetime");
+ //this.nextthink = time + cvar("g_domination_point_capturetime");
+ //this.think = dompoint_captured;
+
+ // go to neutral team in the mean time
+ head = find(NULL, classname, "dom_team");
+ while (head && head.netname != "")
+ head = find(head, classname, "dom_team");
+ if(head == NULL)
+ return;
+
+ WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
+ WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
+ WaypointSprite_Ping(this.sprite);
+
+ this.goalentity = head;
+ this.model = head.mdl;
+ this.modelindex = head.dmg;
+ this.skin = head.skin;
+
+ this.enemy = toucher; // individual player scoring
+ this.enemy_playerid = toucher.playerid;
+ dompoint_captured(this);
+}
+
+void dom_controlpoint_setup(entity this)
+{
+ entity head;
+ // find the spawnfunc_dom_team representing unclaimed points
+ head = find(NULL, classname, "dom_team");
+ while(head && head.netname != "")
+ head = find(head, classname, "dom_team");
+ if (!head)
+ objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
+
+ // copy important properties from spawnfunc_dom_team entity
+ this.goalentity = head;
+ _setmodel(this, head.mdl); // precision already set
+ this.skin = head.skin;
+
+ this.cnt = -1;
+
+ if(this.message == "")
+ this.message = " has captured a control point";
+
+ if(this.frags <= 0)
+ this.frags = 1;
+ if(this.wait <= 0)
+ this.wait = 5;
+
+ float points, waittime;
+ if (autocvar_g_domination_point_amt)
+ points = autocvar_g_domination_point_amt;
+ else
+ points = this.frags;
+ if (autocvar_g_domination_point_rate)
+ waittime = autocvar_g_domination_point_rate;
+ else
+ waittime = this.wait;
+
+ total_pps += points/waittime;
+
+ if(!this.t_width)
+ this.t_width = 0.02; // frame animation rate
+ if(!this.t_length)
+ this.t_length = 239; // maximum frame
+
+ setthink(this, dompointthink);
+ this.nextthink = time;
+ settouch(this, dompointtouch);
+ this.solid = SOLID_TRIGGER;
+ if(!this.flags & FL_ITEM)
+ IL_PUSH(g_items, this);
+ this.flags = FL_ITEM;
+ setsize(this, '-32 -32 -32', '32 32 32');
+ setorigin(this, this.origin + '0 0 20');
+ droptofloor(this);
+
+ waypoint_spawnforitem(this);
+ WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
+}
+
+float total_controlpoints;
+void Domination_count_controlpoints()
+{
+ total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
+ IL_EACH(g_dompoints, true,
+ {
+ ++total_controlpoints;
+ redowned += (it.goalentity.team == NUM_TEAM_1);
+ blueowned += (it.goalentity.team == NUM_TEAM_2);
+ yellowowned += (it.goalentity.team == NUM_TEAM_3);
+ pinkowned += (it.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, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+
+ game_stopped = true;
+ 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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
+ }
+ else if(winner_team == -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ }
+
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
+
+ return 1;
+}
+
+float Domination_CheckPlayers()
+{
+ return 1;
+}
+
+void Domination_RoundStart()
+{
+ FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
+}
+
+//go to best items, or control points you don't own
+void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
+{
+ IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
+ {
+ if(it.cnt > -1) // this is just being fought
+ navigation_routerating(this, it, ratingscale, 5000);
+ else if(it.goalentity.cnt == 0) // unclaimed
+ navigation_routerating(this, it, ratingscale * 0.5, 5000);
+ else if(it.goalentity.team != this.team) // other team's point
+ navigation_routerating(this, it, ratingscale * 0.2, 5000);
+ });
+}
+
+void havocbot_role_dom(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
+ havocbot_goalrating_items(this, 8000, this.origin, 8000);
+ //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
+{
+ // fallback?
+ M_ARGV(0, float) = domination_teams;
+ string ret_string = "dom_team";
+
+ entity head = find(NULL, 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);
+ }
+
+ M_ARGV(1, string) = string_null;
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, reset_map_players)
+{
+ total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ PutClientInServer(it);
+ if(domination_roundbased)
+ it.player_blocked = 1;
+ if(IS_REAL_CLIENT(it))
+ set_dom_state(it);
+ });
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(domination_roundbased)
+ if(!round_handler_IsRoundStarted())
+ player.player_blocked = 1;
+ else
+ player.player_blocked = 0;
+}
+
+MUTATOR_HOOKFUNCTION(dom, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ set_dom_state(player);
+}
+
+MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ bot.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)
+ {
+ delete(this);
+ return;
+ }
+ setthink(this, dom_controlpoint_setup);
+ this.nextthink = time + 0.1;
+ this.reset = dom_controlpoint_setup;
+
+ if(!this.scale)
+ this.scale = 0.6;
+
+ this.effects = this.effects | EF_LOWPRECISION;
+ if (autocvar_g_domination_point_fullbright)
+ this.effects |= EF_FULLBRIGHT;
+
+ IL_PUSH(g_dompoints, this);
+}
+
+/*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)
+ {
+ delete(this);
+ return;
+ }
+ precache_model(this.model);
+ if (this.noise != "")
+ precache_sound(this.noise);
+ if (this.noise1 != "")
+ precache_sound(this.noise1);
+ this.classname = "dom_team";
+ _setmodel(this, this.model); // precision not needed
+ this.mdl = this.model;
+ this.dmg = this.modelindex;
+ this.model = "";
+ this.modelindex = 0;
+ // this would have to be changed if used in quakeworld
+ if(this.cnt)
+ this.team = this.cnt + 1; // WHY are these different anyway?
+}
+
+// scoreboard setup
+void ScoreRules_dom(int teams)
+{
+ if(domination_roundbased)
+ {
+ GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
+ field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_DOM_TAKES, "takes", 0);
+ });
+ }
+ 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;
+ GameRules_scoring(teams, sp_score, sp_score, {
+ field_team(ST_DOM_TICKS, "ticks", sp_domticks);
+ field(SP_DOM_TICKS, "ticks", sp_domticks);
+ field(SP_DOM_TAKES, "takes", 0);
+ });
+ }
+}
+
+// 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, Sound capsound, string capnarration, string capmessage)
+{
+ TC(Sound, capsound);
+ entity e = new_pure(dom_team);
+ e.netname = strzone(teamname);
+ e.cnt = teamcolor;
+ e.model = pointmodel;
+ e.skin = pointskin;
+ e.noise = strzone(Sound_fixpath(capsound));
+ e.noise1 = strzone(capnarration);
+ e.message = strzone(capmessage);
+
+ // this code is identical to spawnfunc_dom_team
+ _setmodel(e, e.model); // precision not needed
+ e.mdl = e.model;
+ e.dmg = e.modelindex;
+ e.model = "";
+ e.modelindex = 0;
+ // this would have to be changed if used in quakeworld
+ e.team = e.cnt + 1;
+
+ //eprint(e);
+}
+
+void dom_spawnpoint(vector org)
+{
+ entity e = spawn();
+ e.classname = "dom_controlpoint";
+ setthink(e, spawnfunc_dom_controlpoint);
+ e.nextthink = time;
+ setorigin(e, org);
+ spawnfunc_dom_controlpoint(e);
+}
+
+// spawn some default teams if the map is not set up for domination
+void dom_spawnteams(int teams)
+{
+ TC(int, teams);
+ dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
+ dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), 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(Team_ColoredFullName(NUM_TEAM_3), 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(Team_ColoredFullName(NUM_TEAM_4), 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, SND_Null, "", "");
+}
+
+void dom_DelayedInit(entity this) // 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(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
+ {
+ LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
+ domination_teams = autocvar_g_domination_teams_override;
+ if (domination_teams < 2)
+ domination_teams = autocvar_g_domination_default_teams;
+ domination_teams = bound(2, domination_teams, 4);
+ dom_spawnteams(domination_teams);
+ }
+
+ CheckAllowedTeams(NULL);
+ //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
+
+ int teams = 0;
+ if(c1 >= 0) teams |= BIT(0);
+ if(c2 >= 0) teams |= BIT(1);
+ if(c3 >= 0) teams |= BIT(2);
+ if(c4 >= 0) teams |= BIT(3);
+ domination_teams = teams;
+
+ 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(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
+bool autocvar_g_domination_roundbased;
+int autocvar_g_domination_roundbased_point_limit;
+int autocvar_g_domination_point_leadlimit;
+
+void dom_Initialize();
+
+REGISTER_MUTATOR(dom, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ 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;
+
+ GameRules_teams(true);
+ GameRules_limit_score(fraglimit_override);
+ GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
+
+ dom_Initialize();
+ }
+ return 0;
+}
+
+// score rule declarations
+const float ST_DOM_TICKS = 1;
+const float ST_DOM_CAPS = 1;
+
+// pps: points per second
+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;
+
+void AnimateDomPoint(entity this);
+
+IntrusiveList g_dompoints;
+STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/freezetag/freezetag.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/freezetag/freezetag.qh>
--- /dev/null
+#include "freezetag.qh"
+
+// TODO: sv_freezetag
+#ifdef SVQC
+#include <server/resources.qh>
+
+float autocvar_g_freezetag_frozen_maxtime;
+float autocvar_g_freezetag_revive_clearspeed;
+float autocvar_g_freezetag_round_timelimit;
+//int autocvar_g_freezetag_teams;
+int autocvar_g_freezetag_teams_override;
+float autocvar_g_freezetag_warmup;
+
+void freezetag_count_alive_players()
+{
+ total_players = redalive = bluealive = yellowalive = pinkalive = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ switch(it.team)
+ {
+ case NUM_TEAM_1: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++redalive; break;
+ case NUM_TEAM_2: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break;
+ case NUM_TEAM_3: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break;
+ case NUM_TEAM_4: ++total_players; if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break;
+ }
+ });
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ STAT(REDALIVE, it) = redalive;
+ STAT(BLUEALIVE, it) = bluealive;
+ STAT(YELLOWALIVE, it) = yellowalive;
+ STAT(PINKALIVE, it) = pinkalive;
+ });
+
+ eliminatedPlayers.SendFlags |= 1;
+}
+#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
+#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return 1;
+ }
+ if(total_players == 0)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ return 0;
+ }
+ int missing_teams_mask = 0;
+ if(freezetag_teams & BIT(0))
+ missing_teams_mask += (!redalive) * 1;
+ if(freezetag_teams & BIT(1))
+ missing_teams_mask += (!bluealive) * 2;
+ if(freezetag_teams & BIT(2))
+ missing_teams_mask += (!yellowalive) * 4;
+ if(freezetag_teams & BIT(3))
+ missing_teams_mask += (!pinkalive) * 8;
+ if(prev_missing_teams_mask != missing_teams_mask)
+ {
+ Send_Notification(NOTIF_ALL, NULL, 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
+}
+
+void nades_Clear(entity);
+void nades_GiveBonus(entity player, float score);
+
+float freezetag_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ it.freezetag_frozen_timeout = 0;
+ nades_Clear(it);
+ });
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+ return 1;
+ }
+
+ if(FREEZETAG_ALIVE_TEAMS() > 1)
+ return 0;
+
+ int winner_team = freezetag_getWinnerTeam();
+ if(winner_team > 0)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
+ }
+ else if(winner_team == -1)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ }
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ it.freezetag_frozen_timeout = 0;
+ nades_Clear(it);
+ });
+
+ game_stopped = true;
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+ return 1;
+}
+
+entity freezetag_LastPlayerForTeam(entity this)
+{
+ entity last_pl = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+ if(GetResourceAmount(it, RESOURCE_HEALTH) >= 1)
+ if(!STAT(FROZEN, it))
+ if(SAME_TEAM(it, this))
+ if(!last_pl)
+ last_pl = it;
+ else
+ return NULL;
+ });
+ return last_pl;
+}
+
+void freezetag_LastPlayerForTeam_Notify(entity this)
+{
+ if(round_handler_IsActive())
+ if(round_handler_IsRoundStarted())
+ {
+ entity pl = freezetag_LastPlayerForTeam(this);
+ if(pl)
+ Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+ }
+}
+
+void freezetag_Add_Score(entity targ, entity attacker)
+{
+ if(attacker == targ)
+ {
+ // you froze your own dumb targ
+ // counted as "suicide" already
+ GameRules_scoring_add(targ, SCORE, -1);
+ }
+ else if(IS_PLAYER(attacker))
+ {
+ // got frozen by an enemy
+ // counted as "kill" and "death" already
+ GameRules_scoring_add(targ, SCORE, -1);
+ GameRules_scoring_add(attacker, SCORE, +1);
+ }
+ // else nothing - got frozen by the game type rules themselves
+}
+
+void freezetag_Freeze(entity targ, entity attacker)
+{
+ if(STAT(FROZEN, targ))
+ return;
+
+ if(autocvar_g_freezetag_frozen_maxtime > 0)
+ targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
+
+ Freeze(targ, 0, 1, true);
+
+ freezetag_count_alive_players();
+
+ freezetag_Add_Score(targ, attacker);
+}
+
+void freezetag_Unfreeze(entity this)
+{
+ this.freezetag_frozen_time = 0;
+ this.freezetag_frozen_timeout = 0;
+
+ Unfreeze(this);
+}
+
+float freezetag_isEliminated(entity e)
+{
+ if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
+ return true;
+ return false;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void(entity this) havocbot_role_ft_freeing;
+void(entity this) havocbot_role_ft_offense;
+
+void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
+{
+ float t;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
+ if (STAT(FROZEN, it) == 1)
+ {
+ if(vdist(it.origin - org, >, sradius))
+ continue;
+ navigation_routerating(this, it, ratingscale, 2000);
+ }
+ else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
+ {
+ // If teamate is not frozen still seek them out as fight better
+ // in a group.
+ t = 0.2 * 150 / (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR));
+ navigation_routerating(this, it, t * ratingscale, 2000);
+ }
+ });
+}
+
+void havocbot_role_ft_offense(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+
+ // Count how many players on team are unfrozen.
+ int unfrozen = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
+
+ // If only one left on team or if role has timed out then start trying to free players.
+ if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
+ {
+ LOG_TRACE("changing role to freeing");
+ this.havocbot_role = havocbot_role_ft_freeing;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_ft_freeing(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+
+ if (time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to offense");
+ this.havocbot_role = havocbot_role_ft_offense;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 8000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
+ havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+void ft_RemovePlayer(entity this)
+{
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // neccessary to update correctly alive stats
+ if(!STAT(FROZEN, this))
+ freezetag_LastPlayerForTeam_Notify(this);
+ freezetag_Unfreeze(this);
+ freezetag_count_alive_players();
+}
+
+MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ ft_RemovePlayer(player);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ ft_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_deathtype = M_ARGV(3, float);
+
+ if(round_handler_IsActive())
+ if(round_handler_CountdownRunning())
+ {
+ if(STAT(FROZEN, frag_target))
+ freezetag_Unfreeze(frag_target);
+ freezetag_count_alive_players();
+ return true; // 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(STAT(FROZEN, frag_target) != 1)
+ {
+ freezetag_Add_Score(frag_target, frag_attacker);
+ freezetag_count_alive_players();
+ freezetag_LastPlayerForTeam_Notify(frag_target);
+ }
+ else
+ freezetag_Unfreeze(frag_target); // remove ice
+ SetResourceAmountExplicit(frag_target, RESOURCE_HEALTH, 0); // Unfreeze resets health
+ frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
+ return true;
+ }
+
+ if(STAT(FROZEN, frag_target))
+ return true;
+
+ freezetag_Freeze(frag_target, frag_attacker);
+ freezetag_LastPlayerForTeam_Notify(frag_target);
+
+ if(frag_attacker == frag_target || frag_attacker == NULL)
+ {
+ if(IS_PLAYER(frag_target))
+ Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
+ }
+ else
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
+ return true; // do nothing, round is starting right now
+
+ if(player.freezetag_frozen_timeout == -2) // player was dead
+ {
+ freezetag_Freeze(player, NULL);
+ return true;
+ }
+
+ freezetag_count_alive_players();
+
+ if(round_handler_IsActive())
+ if(round_handler_IsRoundStarted())
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
+ freezetag_Freeze(player, NULL);
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, reset_map_players)
+{
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ CS(it).killcount = 0;
+ it.freezetag_frozen_timeout = -1;
+ PutClientInServer(it);
+ it.freezetag_frozen_timeout = 0;
+ });
+ freezetag_count_alive_players();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
+{
+ if(game_stopped)
+ return true;
+
+ if(round_handler_IsActive())
+ if(!round_handler_IsRoundStarted())
+ return true;
+
+ int n;
+ entity o = NULL;
+ entity player = M_ARGV(0, entity);
+ //if(STAT(FROZEN, player))
+ //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
+ //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
+
+ if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
+ n = -1;
+ else
+ {
+ vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
+ n = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
+ if(STAT(FROZEN, it) == 0)
+ if(!IS_DEAD(it))
+ if(SAME_TEAM(it, player))
+ if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
+ {
+ if(!o)
+ o = it;
+ if(STAT(FROZEN, player) == 1)
+ it.reviving = true;
+ ++n;
+ }
+ });
+
+ }
+
+ if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
+ {
+ STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
+ SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+
+ if(STAT(REVIVE_PROGRESS, player) >= 1)
+ {
+ freezetag_Unfreeze(player);
+ freezetag_count_alive_players();
+
+ if(n == -1)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
+ return true;
+ }
+
+ // EVERY team mate nearby gets a point (even if multiple!)
+ FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
+ GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
+ GameRules_scoring_add(it, SCORE, +1);
+ nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
+ });
+
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
+ Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
+ }
+
+ FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
+ STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
+ it.reviving = false;
+ });
+ }
+ else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
+ {
+ STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
+ SetResourceAmountExplicit(player, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health)));
+ }
+ else if(!n && !STAT(FROZEN, player))
+ {
+ STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
+ }
+
+ return true;
+}
+
+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");
+}
+
+MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ if (!IS_DEAD(bot))
+ {
+ if (random() < 0.5)
+ bot.havocbot_role = havocbot_role_ft_freeing;
+ else
+ bot.havocbot_role = havocbot_role_ft_offense;
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = freezetag_teams;
+}
+
+MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
+{
+ // most weapons arena
+ if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
+ M_ARGV(0, string) = "most";
+}
+
+MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
+{
+ entity frag_attacker = M_ARGV(0, entity);
+ entity frag_target = M_ARGV(1, entity);
+ //float frag_deathtype = M_ARGV(2, float);
+ int kill_count_to_attacker = M_ARGV(3, int);
+ int kill_count_to_target = M_ARGV(4, int);
+
+ if(STAT(FROZEN, frag_target))
+ return; // target was already frozen, so this is just pushing them off the cliff
+
+ Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
+ Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target,
+ GetResourceAmount(frag_attacker, RESOURCE_HEALTH), GetResourceAmount(frag_attacker, RESOURCE_ARMOR), (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
+
+ return true;
+}
+
+void freezetag_Initialize()
+{
+ freezetag_teams = autocvar_g_freezetag_teams_override;
+ if(freezetag_teams < 2)
+ freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
+
+ freezetag_teams = BITS(bound(2, freezetag_teams, 4));
+ GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+ field(SP_FREEZETAG_REVIVALS, "revivals", 0);
+ });
+
+ round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
+ round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
+
+ EliminatedPlayers_Init(freezetag_isEliminated);
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+int autocvar_g_freezetag_point_limit;
+int autocvar_g_freezetag_point_leadlimit;
+bool autocvar_g_freezetag_team_spawns;
+void freezetag_Initialize();
+
+REGISTER_MUTATOR(ft, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
+ GameRules_limit_score(autocvar_g_freezetag_point_limit);
+ GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
+
+ freezetag_Initialize();
+ }
+ return 0;
+}
+
+.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
+
+float autocvar_g_freezetag_revive_extra_size;
+float autocvar_g_freezetag_revive_speed;
+bool autocvar_g_freezetag_revive_nade;
+float autocvar_g_freezetag_revive_nade_health;
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/invasion/invasion.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/invasion/invasion.qh>
--- /dev/null
+#include "invasion.qh"
+
+// TODO: sv_invasion
+#ifdef SVQC
+#include <common/monsters/sv_spawn.qh>
+#include <common/monsters/sv_spawner.qh>
+#include <common/monsters/sv_monsters.qh>
+
+#include <server/teamplay.qh>
+
+IntrusiveList g_invasion_roundends;
+IntrusiveList g_invasion_waves;
+IntrusiveList g_invasion_spawns;
+STATIC_INIT(g_invasion)
+{
+ g_invasion_roundends = IL_NEW();
+ g_invasion_waves = IL_NEW();
+ g_invasion_spawns = IL_NEW();
+}
+
+float autocvar_g_invasion_round_timelimit;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
+float autocvar_g_invasion_warmup;
+int autocvar_g_invasion_monster_count;
+bool autocvar_g_invasion_zombies_only;
+float autocvar_g_invasion_spawn_delay;
+
+bool victent_present;
+.bool inv_endreached;
+
+bool inv_warning_shown; // spammy
+
+void target_invasion_roundend_use(entity this, entity actor, entity trigger)
+{
+ if(!IS_PLAYER(actor)) { return; }
+
+ actor.inv_endreached = true;
+
+ int plnum = 0;
+ int realplnum = 0;
+ // let's not count bots
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
+ ++realplnum;
+ if(it.inv_endreached)
+ ++plnum;
+ });
+ if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
+ return;
+
+ this.winning = true;
+}
+
+spawnfunc(target_invasion_roundend)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
+
+ if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
+
+ this.use = target_invasion_roundend_use;
+
+ IL_PUSH(g_invasion_roundends, this);
+}
+
+spawnfunc(invasion_wave)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ IL_PUSH(g_invasion_waves, this);
+}
+
+spawnfunc(invasion_spawnpoint)
+{
+ if(!g_invasion) { delete(this); return; }
+
+ this.classname = "invasion_spawnpoint";
+ IL_PUSH(g_invasion_spawns, this);
+}
+
+void ClearWinners();
+
+// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
+// they win.
+int WinningCondition_Invasion()
+{
+ WinningConditionHelper(NULL); // set worldstatus
+
+ int status = WINNING_NO;
+
+ if(autocvar_g_invasion_type == INV_TYPE_STAGE)
+ {
+ SetWinners(inv_endreached, true);
+
+ int found = 0;
+ IL_EACH(g_invasion_roundends, true,
+ {
+ ++found;
+ if(it.winning)
+ {
+ bprint("Invasion: round completed.\n");
+ // winners already set (TODO: teamplay support)
+
+ status = WINNING_YES;
+ break;
+ }
+ });
+
+ if(!found)
+ status = WINNING_YES; // just end it? TODO: should warn mapper!
+ }
+ else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+ {
+ ClearWinners();
+
+ int found = 0; // NOTE: this ends the round if no monsters are placed
+ IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
+ {
+ ++found;
+ });
+
+ if(found <= 0)
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+ {
+ it.winning = true;
+ });
+ status = WINNING_YES;
+ }
+ }
+
+ return status;
+}
+
+Monster invasion_PickMonster(int supermonster_count)
+{
+ RandomSelection_Init();
+
+ FOREACH(Monsters, it != MON_Null,
+ {
+ if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
+ (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
+ continue;
+ if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
+ continue;
+ RandomSelection_AddEnt(it, 1, 1);
+ });
+
+ return RandomSelection_chosen_ent;
+}
+
+entity invasion_PickSpawn()
+{
+ RandomSelection_Init();
+
+ IL_EACH(g_invasion_spawns, true,
+ {
+ RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+ it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
+ });
+
+ return RandomSelection_chosen_ent;
+}
+
+entity invasion_GetWaveEntity(int wavenum)
+{
+ IL_EACH(g_invasion_waves, it.cnt == wavenum,
+ {
+ return it; // found one
+ });
+
+ // if no specific one is found, find the last existing wave ent
+ entity best = NULL;
+ IL_EACH(g_invasion_waves, it.cnt <= wavenum,
+ {
+ if(!best || it.cnt > best.cnt)
+ best = it;
+ });
+
+ return best;
+}
+
+void invasion_SpawnChosenMonster(Monster mon)
+{
+ entity monster;
+ entity spawn_point = invasion_PickSpawn();
+ entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
+
+ string tospawn = "";
+ if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
+ {
+ RandomSelection_Init();
+ FOREACH_WORD(wave_ent.spawnmob, true,
+ {
+ RandomSelection_AddString(it, 1, 1);
+ });
+
+ tospawn = RandomSelection_chosen_string;
+ }
+
+ if(spawn_point == NULL)
+ {
+ if(!inv_warning_shown)
+ {
+ inv_warning_shown = true;
+ LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
+ }
+ entity e = spawn();
+ setsize(e, mon.m_mins, mon.m_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(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
+ else
+ {
+ delete(e);
+ return;
+ }
+ }
+ else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
+ monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
+
+ if(!monster)
+ return;
+
+ monster.spawnshieldtime = time;
+
+ if(spawn_point)
+ {
+ if(spawn_point.target_range)
+ monster.target_range = spawn_point.target_range;
+ monster.target2 = spawn_point.target2;
+ }
+
+ 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_AddFloat(NUM_TEAM_1, 1, 1);
+ if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
+ if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
+ if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
+
+ monster.team = RandomSelection_chosen_float;
+ }
+
+ 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;
+ }
+ }
+
+ if(monster.monster_attack)
+ IL_REMOVE(g_monster_targets, monster);
+ 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(int supermonster_count)
+{
+ Monster chosen_monster = invasion_PickMonster(supermonster_count);
+
+ invasion_SpawnChosenMonster(chosen_monster);
+}
+
+bool Invasion_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ IL_EACH(g_monsters, true,
+ {
+ Monster_Remove(it);
+ });
+ IL_CLEAR(g_monsters);
+
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, 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;
+
+ IL_EACH(g_monsters, GetResourceAmount(it, RESOURCE_HEALTH) > 0,
+ {
+ if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+ ++supermonster_count;
+ ++total_alive_monsters;
+
+ if(teamplay)
+ switch(it.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 = NULL;
+ 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
+ {
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ float cs = GameRules_scoring_add(it, KILLS, 0);
+ if(cs > winning_score)
+ {
+ winning_score = cs;
+ winner = it;
+ }
+ });
+ }
+
+ IL_EACH(g_monsters, true,
+ {
+ Monster_Remove(it);
+ });
+ IL_CLEAR(g_monsters);
+
+ if(teamplay)
+ {
+ if(winner_team)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ }
+ }
+ else if(winner)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
+ }
+
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+ return 1;
+}
+
+bool Invasion_CheckPlayers()
+{
+ return true;
+}
+
+void Invasion_RoundStart()
+{
+ int numplayers = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ it.player_blocked = false;
+ ++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)
+{
+ entity frag_target = M_ARGV(0, entity);
+ entity frag_attacker = M_ARGV(1, entity);
+
+ if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
+ {
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ inv_numkilled += 1;
+ inv_maxcurrent -= 1;
+ }
+ if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
+
+ if(IS_PLAYER(frag_attacker))
+ if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
+ GameRules_scoring_add(frag_attacker, KILLS, -1);
+ else
+ {
+ GameRules_scoring_add(frag_attacker, KILLS, +1);
+ if(teamplay)
+ TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
+{
+ entity mon = M_ARGV(0, entity);
+ mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+ if(autocvar_g_invasion_type == INV_TYPE_HUNT)
+ return false; // allowed
+
+ if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
+ return true;
+
+ if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
+ {
+ inv_numspawned += 1;
+ inv_maxcurrent += 1;
+ }
+
+ mon.monster_skill = inv_monsterskill;
+
+ if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
+}
+
+MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
+{
+ if(autocvar_g_invasion_type != INV_TYPE_ROUND)
+ return; // uses map spawned monsters
+
+ monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+ monsters_killed = inv_numkilled;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
+{
+ // no regeneration in invasion, regardless of the game type
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.bot_attack)
+ IL_REMOVE(g_bot_targets, player);
+ player.bot_attack = false;
+}
+
+MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(4, float);
+ vector frag_force = M_ARGV(6, vector);
+
+ if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
+ {
+ frag_damage = 0;
+ frag_force = '0 0 0';
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(6, vector) = frag_force;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
+{
+ entity targ = M_ARGV(1, entity);
+
+ if(!IS_MONSTER(targ))
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, SetStartItems)
+{
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ start_health = 200;
+ start_armorvalue = 200;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
+{
+ entity frag_target = M_ARGV(1, entity);
+
+ if(IS_MONSTER(frag_target))
+ return MUT_ACCADD_INVALID;
+ return MUT_ACCADD_INDIFFERENT;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
+{
+ // monster spawning disabled during an invasion
+ M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
+{
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ return false;
+
+ M_ARGV(0, float) = WinningCondition_Invasion();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = invasion_teams;
+}
+
+MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
+{
+ M_ARGV(0, string) = "This command does not work during an invasion!";
+ return true;
+}
+
+void invasion_ScoreRules(int inv_teams)
+{
+ if(inv_teams) { CheckAllowedTeams(NULL); }
+ GameRules_score_enabled(false);
+ GameRules_scoring(inv_teams, 0, 0, {
+ if (inv_teams) {
+ field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+ }
+ field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
+ });
+}
+
+void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
+{
+ if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
+ cvar_set("fraglimit", "0");
+
+ if(autocvar_g_invasion_teams)
+ {
+ invasion_teams = BITS(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;
+
+ if(autocvar_g_invasion_type == INV_TYPE_ROUND)
+ {
+ 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()
+{
+ InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
+int autocvar_g_invasion_teams;
+int autocvar_g_invasion_type;
+bool autocvar_g_invasion_team_spawns;
+bool g_invasion;
+void invasion_Initialize();
+
+REGISTER_MUTATOR(inv, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ if (autocvar_g_invasion_teams >= 2) {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
+ }
+ GameRules_limit_score(autocvar_g_invasion_point_limit);
+
+ g_invasion = true;
+ cvar_settemp("g_monsters", "1");
+ invasion_Initialize();
+ }
+ return 0;
+}
+
+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;
+
+const int INV_TYPE_ROUND = 0; // round-based waves of enemies
+const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
+const int INV_TYPE_STAGE = 2; // reach the end of the level
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keepaway/keepaway.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keepaway/keepaway.qh>
--- /dev/null
+#include "keepaway.qh"
+
+// TODO: keepaway
+#ifdef SVQC
+#include <common/effects/all.qh>
+
+.entity ballcarried;
+
+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;
+
+bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
+{
+ if(view.ballcarried)
+ if(IS_SPEC(player))
+ 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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+void ka_TouchEvent(entity this, entity toucher);
+void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
+{
+ if(game_stopped) return;
+ vector oldballorigin = this.origin;
+
+ if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+ {
+ entity spot = SelectSpawnPoint(this, true);
+ setorigin(this, spot.origin);
+ this.angles = spot.angles;
+ }
+
+ makevectors(this.angles);
+ set_movetype(this, MOVETYPE_BOUNCE);
+ this.velocity = '0 0 200';
+ this.angles = '0 0 0';
+ this.effects = autocvar_g_keepawayball_effects;
+ settouch(this, ka_TouchEvent);
+ setthink(this, ka_RespawnBall);
+ this.nextthink = time + autocvar_g_keepawayball_respawntime;
+ navigation_dynamicgoal_set(this);
+
+ Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
+ Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
+
+ WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
+ WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
+
+ sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+}
+
+void ka_TimeScoring(entity this)
+{
+ if(this.owner.ballcarried)
+ { // add points for holding the ball after a certain amount of time
+ if(autocvar_g_keepaway_score_timepoints)
+ GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
+
+ GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
+ this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+ }
+}
+
+void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
+{
+ if(game_stopped) return;
+ if(!this) return;
+ if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+ { // The ball fell off the map, respawn it since players can't get to it
+ ka_RespawnBall(this);
+ return;
+ }
+ if(IS_DEAD(toucher)) { return; }
+ if(STAT(FROZEN, toucher)) { return; }
+ if (!IS_PLAYER(toucher))
+ { // The ball just touched an object, most likely the world
+ Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
+ sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
+ return;
+ }
+ else if(this.wait > time) { return; }
+
+ // attach the ball to the player
+ this.owner = toucher;
+ toucher.ballcarried = this;
+ GameRules_scoring_vip(toucher, true);
+ setattachment(this, toucher, "");
+ setorigin(this, '0 0 0');
+
+ // make the ball invisible/unable to do anything/set up time scoring
+ this.velocity = '0 0 0';
+ set_movetype(this, MOVETYPE_NONE);
+ this.effects |= EF_NODRAW;
+ settouch(this, func_null);
+ setthink(this, ka_TimeScoring);
+ this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+ this.takedamage = DAMAGE_NO;
+ navigation_dynamicgoal_unset(this);
+
+ // apply effects to player
+ toucher.glow_color = autocvar_g_keepawayball_trail_color;
+ toucher.glow_trail = true;
+ toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
+
+ // messages and sounds
+ ka_EventLog("pickup", toucher);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
+ Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
+ sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+ // scoring
+ GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
+
+ // waypoints
+ WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
+ toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+ WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+ WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
+ WaypointSprite_Kill(this.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, NULL, "");
+ set_movetype(ball, MOVETYPE_BOUNCE);
+ ball.wait = time + 1;
+ settouch(ball, ka_TouchEvent);
+ setthink(ball, 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();
+ entity e = ball.owner; ball.owner = NULL;
+ e.ballcarried = NULL;
+ GameRules_scoring_vip(e, false);
+ navigation_dynamicgoal_set(ball);
+
+ // 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, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
+ sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
+
+ // scoring
+ // GameRules_scoring_add(plyr, 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', NULL, 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);
+}
+
+/** used to clear the ballcarrier whenever the match switches from warmup to normal */
+void ka_Reset(entity this)
+{
+ if((this.owner) && (IS_PLAYER(this.owner)))
+ ka_DropEvent(this.owner);
+
+ if(time < game_starttime)
+ {
+ setthink(this, ka_RespawnBall);
+ settouch(this, func_null);
+ this.nextthink = game_starttime;
+ }
+ else
+ ka_RespawnBall(this);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
+{
+ float t;
+ entity ball_owner;
+ ball_owner = ka_ball.owner;
+
+ if (ball_owner == this)
+ return;
+
+ // If ball is carried by player then hunt them down.
+ if (ball_owner)
+ {
+ t = (GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) / (GetResourceAmount(ball_owner, RESOURCE_HEALTH) + GetResourceAmount(ball_owner, RESOURCE_ARMOR));
+ navigation_routerating(this, ball_owner, t * ratingscale, 2000);
+ }
+ else // Ball has been dropped so collect.
+ navigation_routerating(this, ka_ball, ratingscale, 2000);
+}
+
+void havocbot_role_ka_carrier(entity this)
+{
+ if (IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
+ havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+
+ if (!this.ballcarried)
+ {
+ this.havocbot_role = havocbot_role_ka_collector;
+ navigation_goalrating_timeout_expire(this, 2);
+ }
+}
+
+void havocbot_role_ka_collector(entity this)
+{
+ if (IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_items(this, 10000, this.origin, 10000);
+ havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
+ havocbot_goalrating_ball(this, 20000, this.origin);
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+
+ if (this.ballcarried)
+ {
+ this.havocbot_role = havocbot_role_ka_carrier;
+ navigation_goalrating_timeout_expire(this, 2);
+ }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ka, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
+ {
+ if(frag_target.ballcarried) { // add to amount of times killing carrier
+ GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
+ if(autocvar_g_keepaway_score_bckill) // add bckills to the score
+ GameRules_scoring_add(frag_attacker, 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
+ GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
+ }
+
+ if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
+{
+ M_ARGV(2, float) = 0; // no frags counted in keepaway
+ return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ // clear the item used for the ball in keepaway
+ player.items &= ~IT_KEY1;
+
+ // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+ if(player.ballcarried)
+ player.items |= IT_KEY1;
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(MUTATOR_RETURNVALUE == 0)
+ if(player.ballcarried)
+ {
+ ka_DropEvent(player);
+ return true;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+ float frag_damage = M_ARGV(4, float);
+ vector frag_force = M_ARGV(6, vector);
+
+ 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;
+ }
+ }
+
+ M_ARGV(4, float) = frag_damage;
+ M_ARGV(6, vector) = frag_force;
+}
+
+MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
+}
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
+{
+ entity player = M_ARGV(0, entity);
+
+ // 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()
+
+ player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
+
+ if(player.ballcarried)
+ player.effects |= autocvar_g_keepaway_ballcarrier_effects;
+}
+
+
+MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
+{
+ entity player = M_ARGV(0, entity);
+ // these automatically reset, no need to worry
+
+ if(player.ballcarried)
+ STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
+}
+
+MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
+{
+ entity bot = M_ARGV(0, entity);
+ entity targ = M_ARGV(1, entity);
+
+ // if neither player has ball then don't attack unless the ball is on the ground
+ if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ if (bot.ballcarried)
+ bot.havocbot_role = havocbot_role_ka_carrier;
+ else
+ bot.havocbot_role = havocbot_role_ka_collector;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ if(frag_target.ballcarried)
+ ka_DropEvent(frag_target);
+}
+
+.bool pushable;
+
+// ==============
+// Initialization
+// ==============
+
+MODEL(KA_BALL, "models/orbs/orbblue.md3");
+
+void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+{
+ entity e = new(keepawayball);
+ setmodel(e, MDL_KA_BALL);
+ 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.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+ e.takedamage = DAMAGE_YES;
+ e.solid = SOLID_TRIGGER;
+ set_movetype(e, MOVETYPE_BOUNCE);
+ e.glow_color = autocvar_g_keepawayball_trail_color;
+ e.glow_trail = true;
+ e.flags = FL_ITEM;
+ IL_PUSH(g_items, e);
+ e.pushable = true;
+ e.reset = ka_Reset;
+ settouch(e, ka_TouchEvent);
+ e.owner = NULL;
+ ka_ball = e;
+ navigation_dynamicgoal_init(ka_ball, false);
+
+ InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
+}
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+ ka_SpawnBall();
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+void ka_Initialize();
+
+REGISTER_MUTATOR(ka, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+ field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
+ field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
+ field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
+ });
+
+ ka_Initialize();
+ }
+ return false;
+}
+
+
+entity ka_ball;
+
+void(entity this) havocbot_role_ka_carrier;
+void(entity this) havocbot_role_ka_collector;
+
+void ka_DropEvent(entity plyr);
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keyhunt/keyhunt.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/keyhunt/keyhunt.qh>
--- /dev/null
+#include "keyhunt.qh"
+
+// TODO: sv_keyhunt
+#ifdef SVQC
+float autocvar_g_balance_keyhunt_damageforcescale;
+float autocvar_g_balance_keyhunt_delay_collect;
+float autocvar_g_balance_keyhunt_delay_damage_return;
+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_return_when_unreachable;
+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_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;
+
+bool 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 siren_time; // time delay the siren
+//.float stuff_time; // time delay to stuffcmd a cvar
+
+int 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";
+
+int kh_Team_ByID(int 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;
+//bool kh_tracking_enabled;
+int kh_teams;
+int kh_interferemsg_team;
+float kh_interferemsg_time;
+.entity kh_next, kh_prev; // linked list
+.float kh_droptime;
+.int kh_dropperteam;
+.entity kh_previous_owner;
+.int kh_previous_owner_playerid;
+
+int kh_key_dropped, kh_key_carried;
+
+int kh_Key_AllOwnedByWhichTeam();
+
+const int ST_KH_CAPS = 1;
+void kh_ScoreRules(int teams)
+{
+ GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
+ field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ field(SP_KH_PUSHES, "pushes", 0);
+ field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
+ field(SP_KH_PICKUPS, "pickups", 0);
+ field(SP_KH_KCKILLS, "kckills", 0);
+ field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
+ });
+}
+
+bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time
+{
+ if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
+ if(!kh_tracking_enabled)
+ return false;
+
+ return true;
+}
+
+bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
+{
+ if(!kh_tracking_enabled)
+ return false;
+ if(!this.owner)
+ return true;
+ if(!this.owner.owner)
+ return true;
+ return false; // draw only when key is not owned
+}
+
+void kh_update_state()
+{
+ entity key;
+ int f;
+ int s = 0;
+ FOR_EACH_KH_KEY(key)
+ {
+ if(key.owner)
+ f = key.team;
+ else
+ f = 30;
+ s |= (32 ** key.count) * f;
+ }
+
+ FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
+
+ FOR_EACH_KH_KEY(key)
+ {
+ if(key.owner)
+ STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
+ }
+ //print(ftos((nextent(NULL)).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(entity this) // called a lot
+{
+ if(game_stopped)
+ return;
+ if(this.cnt > 0)
+ {
+ if(getthink(this) != kh_WaitForPlayers)
+ this.cnt -= 1;
+ }
+ else if(this.cnt == 0)
+ {
+ this.cnt -= 1;
+ kh_Controller_Thinkfunc();
+ }
+ this.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(game_stopped)
+ 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 = 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;
+ if(IL_CONTAINS(g_items, key))
+ IL_REMOVE(g_items, key);
+ key.solid = SOLID_NOT;
+ set_movetype(key, MOVETYPE_NONE);
+ key.team = key.owner.team;
+ key.nextthink = time;
+ key.damageforcescale = 0;
+ key.takedamage = DAMAGE_NO;
+ key.modelindex = kh_key_carried;
+ navigation_dynamicgoal_unset(key);
+}
+
+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 = 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, NULL, "");
+ setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
+ key.angles = key.owner.angles;
+#else
+ setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
+ setattachment(key, NULL, "");
+ key.angles_y += key.owner.angles.y;
+#endif
+ key.flags = FL_ITEM;
+ if(!IL_CONTAINS(g_items, key))
+ IL_PUSH(g_items, key);
+ key.solid = SOLID_TRIGGER;
+ set_movetype(key, 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;
+ navigation_dynamicgoal_set(key);
+ 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
+{
+ if(key.owner == player)
+ return;
+
+ int 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 = NULL;
+ key.kh_prev = NULL;
+
+ if(key.owner.kh_next == NULL)
+ {
+ // 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 == NULL)
+ {
+ // 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 = NULL;
+
+ int ownerteam = kh_Key_AllOwnedByWhichTeam();
+ if(ownerteam != ownerteam0)
+ {
+ entity k;
+ 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, { first = it; break; });
+ entity third = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { 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 "Key Carrier"
+ FOR_EACH_KH_KEY(k)
+ {
+ if (!k.owner) continue;
+ entity first = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
+ entity third = WP_Null;
+ FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
+ WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
+ }
+ }
+ }
+}
+
+void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
+{
+ if(this.owner)
+ return;
+ if(ITEM_DAMAGE_NEEDKILL(deathtype))
+ {
+ this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+ return;
+ }
+ if(force == '0 0 0')
+ return;
+ if(time > this.pushltime)
+ if(IS_PLAYER(attacker))
+ this.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);
+ GameRules_scoring_add(player, KH_PICKUPS, 1);
+ }
+ key.kh_dropperteam = 0;
+ int realteam = kh_Team_ByID(key.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
+
+ kh_Key_AssignTo(key, player); // this also updates .kh_state
+}
+
+void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up
+{
+ if(game_stopped)
+ return;
+
+ if(this.owner) // already carried
+ return;
+
+ if(ITEM_TOUCH_NEEDKILL())
+ {
+ this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
+ return;
+ }
+
+ if (!IS_PLAYER(toucher))
+ return;
+ if(IS_DEAD(toucher))
+ return;
+ if(toucher == this.enemy)
+ if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
+ return; // you just dropped it!
+ kh_Key_Collect(this, toucher);
+}
+
+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 = key.owner;
+ kh_Key_AssignTo(key, NULL);
+ 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;
+ }
+ }
+
+ delete(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, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
+ kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
+}
+
+void nades_GiveBonus(entity player, float score);
+
+void kh_WinnerTeam(int winner_team) // runs when a team wins
+{
+ // all key carriers get some points
+ entity key;
+ float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
+ DistributeEvenly_Init(score, NumTeams(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 = DistributeEvenly_Get(1);
+ kh_Scores_Event(key.owner, key, "capture", f, 0);
+ GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
+ nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
+ }
+
+ bool 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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
+
+ first = true;
+ vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
+ FOR_EACH_KH_KEY(key)
+ {
+ vector thisorigin = kh_AttachedOrigin(key);
+ //dprint("Key origin: ", vtos(thisorigin), "\n");
+ midpoint += thisorigin;
+
+ if(!first)
+ te_lightning2(NULL, lastorigin, thisorigin);
+ lastorigin = thisorigin;
+ if(first)
+ firstorigin = thisorigin;
+ first = false;
+ }
+ if(NumTeams(kh_teams) > 2)
+ {
+ te_lightning2(NULL, lastorigin, firstorigin);
+ }
+ midpoint = midpoint * (1 / NumTeams(kh_teams));
+ te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 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(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map
+{
+ float f;
+ entity attacker = NULL;
+ if(lostkey.pusher)
+ if(lostkey.pusher.team != loser_team)
+ if(IS_PLAYER(lostkey.pusher))
+ attacker = lostkey.pusher;
+
+ if(attacker)
+ {
+ if(lostkey.kh_previous_owner)
+ kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
+ // don't actually GIVE him the -nn points, just log
+ kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
+ GameRules_scoring_add(attacker, KH_PUSHES, 1);
+ //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
+ }
+ else
+ {
+ int players = 0;
+ float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
+
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
+
+ entity key;
+ int keys = 0;
+ FOR_EACH_KH_KEY(key)
+ if(key.owner && key.team != loser_team)
+ ++keys;
+
+ if(lostkey.kh_previous_owner)
+ kh_Scores_Event(lostkey.kh_previous_owner, NULL, "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)
+ GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
+
+ DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
+
+ FOR_EACH_KH_KEY(key)
+ if(key.owner && key.team != loser_team)
+ {
+ f = DistributeEvenly_Get(of);
+ kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
+ }
+
+ int fragsleft = DistributeEvenly_Get(players);
+
+ // Now distribute these among all other teams...
+ int j = NumTeams(kh_teams) - 1;
+ for(int i = 0; i < NumTeams(kh_teams); ++i)
+ {
+ int thisteam = kh_Team_ByID(i);
+ if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
+ continue;
+
+ players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
+
+ DistributeEvenly_Init(fragsleft, j);
+ fragsleft = DistributeEvenly_Get(j - 1);
+ DistributeEvenly_Init(DistributeEvenly_Get(1), players);
+
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
+ f = DistributeEvenly_Get(1);
+ kh_Scores_Event(it, NULL, "destroyed", f, 0);
+ });
+
+ --j;
+ }
+ }
+
+ int realteam = kh_Team_ByID(lostkey.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
+ if(attacker)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
+
+ play2all(SND(KH_DESTROY));
+ te_tarexplosion(lostkey.origin);
+
+ kh_FinishRound();
+}
+
+void kh_Key_Think(entity this) // runs all the time
+{
+ if(game_stopped)
+ return;
+
+ if(this.owner)
+ {
+#ifndef KH_PLAYER_USE_ATTACHMENT
+ makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
+ setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
+#endif
+ }
+
+ // if in nodrop or time over, end the round
+ if(!this.owner)
+ if(time > this.pain_finished)
+ kh_LoserTeam(this.team, this);
+
+ if(this.owner)
+ if(kh_Key_AllOwnedByWhichTeam() != -1)
+ {
+ if(this.siren_time < time)
+ {
+ sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm
+ this.siren_time = time + 2.5; // repeat every 2.5 seconds
+ }
+
+ entity key;
+ vector p = this.owner.origin;
+ FOR_EACH_KH_KEY(key)
+ if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
+ goto not_winning;
+ kh_WinnerTeam(this.team);
+LABEL(not_winning)
+ }
+
+ if(kh_interferemsg_time && time > kh_interferemsg_time)
+ {
+ kh_interferemsg_time = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(it.team == kh_interferemsg_team)
+ if(it.kh_next)
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
+ else
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
+ else
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
+ });
+ }
+
+ this.nextthink = time + 0.05;
+}
+
+void key_reset(entity this)
+{
+ kh_Key_AssignTo(this, NULL);
+ kh_Key_Remove(this);
+}
+
+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 = spawn();
+ key.count = i;
+ key.classname = STR_ITEM_KH_KEY;
+ settouch(key, kh_Key_Touch);
+ setthink(key, 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.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
+ key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
+ 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;
+ navigation_dynamicgoal_init(key, false);
+
+ 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(initial_owner.team, CENTER_KEYHUNT_START));
+
+ WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, 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
+int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team
+{
+ entity key;
+ int teem = -1;
+ int keys = NumTeams(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 = key.owner;
+
+ key.kh_droptime = time;
+ key.enemy = player;
+
+ kh_Scores_Event(player, key, "dropkey", 0, 0);
+ GameRules_scoring_add(player, KH_LOSSES, 1);
+ int realteam = kh_Team_ByID(key.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
+
+ kh_Key_AssignTo(key, NULL);
+ makevectors(player.v_angle);
+ key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
+ key.pusher = NULL;
+ 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
+{
+ if(player.kh_next)
+ {
+ entity mypusher = NULL;
+ if(player.pusher)
+ if(time < player.pushltime)
+ mypusher = player.pusher;
+
+ entity key;
+ while((key = player.kh_next))
+ {
+ kh_Scores_Event(player, key, "losekey", 0, 0);
+ GameRules_scoring_add(player, KH_LOSSES, 1);
+ int realteam = kh_Team_ByID(key.count);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
+ kh_Key_AssignTo(key, NULL);
+ makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
+ key.velocity = W_CalculateProjectileVelocity(player, 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);
+ }
+}
+
+int kh_GetMissingTeams()
+{
+ int missing_teams = 0;
+ for(int i = 0; i < NumTeams(kh_teams); ++i)
+ {
+ int teem = kh_Team_ByID(i);
+ int players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+ ++players;
+ });
+ if (!players)
+ missing_teams |= (2 ** i);
+ }
+ return missing_teams;
+}
+
+void kh_WaitForPlayers() // delay start of the round until enough players are present
+{
+ static int prev_missing_teams_mask;
+ if(time < game_starttime)
+ {
+ if (prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+ return;
+ }
+
+ int missing_teams_mask = kh_GetMissingTeams();
+ if(!missing_teams_mask)
+ {
+ if(prev_missing_teams_mask > 0)
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+ prev_missing_teams_mask = -1;
+ }
+ else
+ {
+ if(prev_missing_teams_mask != missing_teams_mask)
+ {
+ Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_KEYHUNT);
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+ kh_tracking_enabled = true;
+}
+
+void kh_StartRound() // runs at the start of each round
+{
+ if(time < game_starttime)
+ {
+ kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
+ return;
+ }
+
+ if(kh_GetMissingTeams())
+ {
+ kh_Controller_SetThink(1, kh_WaitForPlayers);
+ return;
+ }
+
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
+ Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
+
+ for(int i = 0; i < NumTeams(kh_teams); ++i)
+ {
+ int teem = kh_Team_ByID(i);
+ int players = 0;
+ entity my_player = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
+ {
+ ++players;
+ if(random() * players <= 1)
+ my_player = it;
+ }
+ });
+ kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
+ }
+
+ kh_tracking_enabled = false;
+ Send_Notification(NOTIF_ALL, NULL, 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)
+ {
+ int nk = 0;
+ for(entity k = targ.kh_next; k != NULL; 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);
+ GameRules_scoring_add(attacker, 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 = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
+ kh_teams = BITS(bound(2, kh_teams, 4));
+
+ // make a KH entity for controlling the game
+ kh_controller = spawn();
+ setthink(kh_controller, 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;
+
+ kh_ScoreRules(kh_teams);
+}
+
+void kh_finalize()
+{
+ // to be called before intermission
+ kh_FinishRound();
+ delete(kh_controller);
+ kh_controller = NULL;
+}
+
+// legacy bot role
+
+void(entity this) havocbot_role_kh_carrier;
+void(entity this) havocbot_role_kh_defense;
+void(entity this) havocbot_role_kh_offense;
+void(entity this) havocbot_role_kh_freelancer;
+
+
+void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
+{
+ entity head;
+ for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
+ {
+ if(head.owner == this)
+ 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 != this.team)
+ {
+ traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
+ if (trace_fraction < 1 && trace_ent != head)
+ continue; // skip what I can't see
+ }
+ }
+ if(!head.owner)
+ navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
+ else if(head.team == this.team)
+ navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
+ else
+ navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
+ }
+
+ havocbot_goalrating_items(this, 1, this.origin, 10000);
+}
+
+void havocbot_role_kh_carrier(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (!(this.kh_next))
+ {
+ LOG_TRACE("changing role to freelancer");
+ this.havocbot_role = havocbot_role_kh_freelancer;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ if(kh_Key_AllOwnedByWhichTeam() == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
+ else
+ havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_kh_defense(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (this.kh_next)
+ {
+ LOG_TRACE("changing role to carrier");
+ this.havocbot_role = havocbot_role_kh_carrier;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+ if (time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freelancer");
+ this.havocbot_role = havocbot_role_kh_freelancer;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ float key_owner_team;
+ navigation_goalrating_start(this);
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
+ else
+ havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_kh_offense(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (this.kh_next)
+ {
+ LOG_TRACE("changing role to carrier");
+ this.havocbot_role = havocbot_role_kh_carrier;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 20;
+ if (time > this.havocbot_role_timeout)
+ {
+ LOG_TRACE("changing role to freelancer");
+ this.havocbot_role = havocbot_role_kh_freelancer;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ float key_owner_team;
+
+ navigation_goalrating_start(this);
+
+ key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
+ else
+ havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void havocbot_role_kh_freelancer(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (this.kh_next)
+ {
+ LOG_TRACE("changing role to carrier");
+ this.havocbot_role = havocbot_role_kh_carrier;
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (!this.havocbot_role_timeout)
+ this.havocbot_role_timeout = time + random() * 10 + 10;
+ if (time > this.havocbot_role_timeout)
+ {
+ if (random() < 0.5)
+ {
+ LOG_TRACE("changing role to offense");
+ this.havocbot_role = havocbot_role_kh_offense;
+ }
+ else
+ {
+ LOG_TRACE("changing role to defense");
+ this.havocbot_role = havocbot_role_kh_defense;
+ }
+ this.havocbot_role_timeout = 0;
+ return;
+ }
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ int key_owner_team = kh_Key_AllOwnedByWhichTeam();
+ if(key_owner_team == this.team)
+ havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
+ else if(key_owner_team == -1)
+ havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
+ else
+ havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+
+// register this as a mutator
+
+MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ kh_Key_DropAll(player, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerDies)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if(frag_target == frag_attacker)
+ kh_Key_DropAll(frag_target, true);
+ else if(IS_PLAYER(frag_attacker))
+ kh_Key_DropAll(frag_target, false);
+ else
+ kh_Key_DropAll(frag_target, true);
+}
+
+MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+ entity frag_attacker = M_ARGV(0, entity);
+ entity frag_target = M_ARGV(1, entity);
+ float frag_score = M_ARGV(2, float);
+ M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
+}
+
+MUTATOR_HOOKFUNCTION(kh, MatchEnd)
+{
+ kh_finalize();
+}
+
+MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = kh_teams;
+}
+
+MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
+{
+ entity spectatee = M_ARGV(0, entity);
+ entity client = M_ARGV(1, entity);
+
+ STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
+}
+
+MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(MUTATOR_RETURNVALUE == 0)
+ {
+ entity k = player.kh_next;
+ if(k)
+ {
+ kh_Key_DropOne(k);
+ return true;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ if(IS_DEAD(bot))
+ return true;
+
+ float r = random() * 3;
+ if (r < 1)
+ bot.havocbot_role = havocbot_role_kh_offense;
+ else if (r < 2)
+ bot.havocbot_role = havocbot_role_kh_defense;
+ else
+ bot.havocbot_role = havocbot_role_kh_freelancer;
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ kh_Key_DropAll(frag_target, false);
+}
+
+MUTATOR_HOOKFUNCTION(kh, reset_map_global)
+{
+ kh_WaitForPlayers(); // takes care of killing the "missing teams" message
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
+int autocvar_g_keyhunt_point_leadlimit;
+bool autocvar_g_keyhunt_team_spawns;
+void kh_Initialize();
+
+REGISTER_MUTATOR(kh, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
+ GameRules_limit_score(autocvar_g_keyhunt_point_limit);
+ GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
+
+ kh_Initialize();
+ }
+ return 0;
+}
+
+#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:
+bool kh_tracking_enabled;
+.entity kh_next;
+
+USING(kh_Think_t, void());
+void kh_StartRound();
+void kh_Controller_SetThink(float t, kh_Think_t func);
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/lms/lms.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/lms/lms.qh>
--- /dev/null
+#include "lms.qh"
+
+#ifdef SVQC
+#include <common/mutators/mutator/instagib/items.qh>
+#include <server/campaign.qh>
+#include <server/command/_mod.qh>
+
+int autocvar_g_lms_extra_lives;
+bool autocvar_g_lms_join_anytime;
+int autocvar_g_lms_last_join;
+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);
+}
+
+void ClearWinners();
+
+// LMS winning condition: game terminates if and only if there's at most one
+// one player who's living lives. Top two scores being equal cancels the time
+// limit.
+int WinningCondition_LMS()
+{
+ entity first_player = NULL;
+ int total_players = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if (!total_players)
+ first_player = it;
+ ++total_players;
+ });
+
+ if (total_players)
+ {
+ if (total_players > 1)
+ {
+ // two or more active players - continue with the game
+
+ if (autocvar_g_campaign)
+ {
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
+ if (!pl_lives)
+ return WINNING_YES; // human player lost, game over
+ break;
+ });
+ }
+ }
+ else
+ {
+ // exactly one player?
+
+ ClearWinners();
+ SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
+
+ if (LMS_NewPlayerLives())
+ {
+ // game still running (that is, nobody got removed from the game by a frag yet)? then continue
+ return WINNING_NO;
+ }
+ else
+ {
+ // a winner!
+ // and assign him his first place
+ GameRules_scoring_add(first_player, LMS_RANK, 1);
+ if(warmup_stage)
+ return WINNING_NO;
+ else
+ return WINNING_YES;
+ }
+ }
+ }
+ else
+ {
+ // nobody is playing at all...
+ if (LMS_NewPlayerLives())
+ {
+ // wait for players...
+ }
+ else
+ {
+ // SNAFU (maybe a draw game?)
+ ClearWinners();
+ LOG_TRACE("No players, ending game.");
+ return WINNING_YES;
+ }
+ }
+
+ // When we get here, we have at least two players who are actually LIVING,
+ // now check if the top two players have equal score.
+ WinningConditionHelper(NULL);
+
+ ClearWinners();
+ if(WinningConditionHelper_winner)
+ WinningConditionHelper_winner.winning = true;
+ if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
+ return WINNING_NEVER;
+
+ // Top two have different scores? Way to go for our beloved TIMELIMIT!
+ return WINNING_NO;
+}
+
+// mutator hooks
+MUTATOR_HOOKFUNCTION(lms, reset_map_global)
+{
+ lms_lowest_lives = 999;
+}
+
+MUTATOR_HOOKFUNCTION(lms, reset_map_players)
+{
+ FOREACH_CLIENT(true, {
+ TRANSMUTE(Player, it);
+ it.frags = FRAGS_PLAYER;
+ GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
+ PutClientInServer(it);
+ });
+}
+
+MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.frags == FRAGS_SPECTATOR)
+ TRANSMUTE(Observer, player);
+ else
+ {
+ float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
+ if(tl < lms_lowest_lives)
+ lms_lowest_lives = tl;
+ if(tl <= 0)
+ TRANSMUTE(Observer, player);
+ if(warmup_stage)
+ GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
+ }
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(warmup_stage)
+ return false;
+ if(player.frags == FRAGS_SPECTATOR)
+ return true;
+ if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
+ return true;
+ }
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+}
+
+void lms_RemovePlayer(entity player)
+{
+ static int quitters = 0;
+ float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
+ if (!player_rank)
+ {
+ int pl_cnt = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+ if (player.lms_spectate_warning != 2)
+ {
+ if(IS_BOT_CLIENT(player))
+ bot_clear(player);
+ player.frags = FRAGS_LMS_LOSER;
+ GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
+ }
+ else
+ {
+ lms_lowest_lives = 999;
+ FOREACH_CLIENT(true, {
+ if (it.frags == FRAGS_LMS_LOSER)
+ {
+ float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
+ if (it_rank > player_rank && it_rank <= 256)
+ GameRules_scoring_add(it, LMS_RANK, -1);
+ lms_lowest_lives = 0;
+ }
+ else if (it.frags != FRAGS_SPECTATOR)
+ {
+ float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
+ if(tl < lms_lowest_lives)
+ lms_lowest_lives = tl;
+ }
+ });
+ GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
+ if(!warmup_stage)
+ {
+ GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
+ ++quitters;
+ }
+ player.frags = FRAGS_LMS_LOSER;
+ TRANSMUTE(Observer, player);
+ }
+ if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
+ lms_lowest_lives = 0; // end the game now!
+ }
+
+ if(CS(player).killcount != FRAGS_SPECTATOR)
+ if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
+ else
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ lms_RemovePlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if (!IS_PLAYER(player))
+ return true;
+
+ lms_RemovePlayer(player);
+ return true; // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
+ {
+ GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
+ player.frags = FRAGS_SPECTATOR;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(lms, AutoJoinOnConnection)
+{
+ if(autocvar_g_campaign)
+ return false;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(player.deadflag == DEAD_DYING)
+ player.deadflag = DEAD_RESPAWNING;
+}
+
+MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
+{
+ if(autocvar_g_lms_regenerate)
+ return false;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
+{
+ // forbode!
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
+{
+ entity frag_target = M_ARGV(1, entity);
+
+ if (!warmup_stage)
+ {
+ // remove a life
+ int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
+ if(tl < lms_lowest_lives)
+ lms_lowest_lives = tl;
+ if(tl <= 0)
+ {
+ int pl_cnt = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
+ if(IS_BOT_CLIENT(frag_target))
+ bot_clear(frag_target);
+ frag_target.frags = FRAGS_LMS_LOSER;
+ GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
+ }
+ }
+ M_ARGV(2, float) = 0; // frag score
+
+ 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");
+}
+
+MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
+{
+ // don't clear player score
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
+{
+ entity definition = M_ARGV(0, entity);
+
+ if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
+ {
+ return false;
+ }
+ return true;
+}
+
+void lms_extralife(entity this)
+{
+ StartItem(this, ITEM_ExtraLife);
+}
+
+MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
+{
+ if (!autocvar_g_powerups) return false;
+ if (!autocvar_g_lms_extra_lives) return false;
+
+ entity ent = M_ARGV(0, entity);
+
+ // Can't use .itemdef here
+ if (ent.classname != "item_health_mega") return false;
+
+ entity e = spawn();
+ setthink(e, lms_extralife);
+
+ e.nextthink = time + 0.1;
+ e.spawnflags = ent.spawnflags;
+ e.noalign = ent.noalign;
+ setorigin(e, ent.origin);
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ItemTouch)
+{
+ entity item = M_ARGV(0, entity);
+ entity toucher = M_ARGV(1, entity);
+
+ if(item.itemdef == ITEM_ExtraLife)
+ {
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
+ GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
+ return MUT_ITEMTOUCH_PICKUP;
+ }
+
+ return MUT_ITEMTOUCH_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+ FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+ ++M_ARGV(0, int); // activerealplayers
+ ++M_ARGV(1, int); // realplayers
+ });
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(warmup_stage || player.lms_spectate_warning)
+ {
+ // for the forfeit message...
+ player.lms_spectate_warning = 2;
+ }
+ else
+ {
+ if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
+ {
+ player.lms_spectate_warning = 1;
+ sprint(player, "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)
+{
+ M_ARGV(0, float) = WinningCondition_LMS();
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, WantWeapon)
+{
+ M_ARGV(2, bool) = true; // all weapons
+}
+
+MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
+{
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
+{
+ if(game_stopped)
+ if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
+ return true; // allow writing to this field in intermission as it is needed for newly joining players
+}
+
+void lms_Initialize()
+{
+ lms_lowest_lives = 9999;
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+.float lms_spectate_warning;
+#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
+void lms_Initialize();
+
+REGISTER_MUTATOR(lms, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
+ GameRules_limit_lead(0);
+ GameRules_score_enabled(false);
+ GameRules_scoring(0, 0, 0, {
+ field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
+ field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
+ });
+
+ lms_Initialize();
+ }
+ return 0;
+}
+
+// lives related defs
+float lms_lowest_lives;
+float LMS_NewPlayerLives();
+#endif
}
if (!IS_PLAYER(toucher))
return;
- if(toucher.health < 1)
+ if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
return;
if(!this.cnt)
this.nextthink = time + autocvar_g_nexball_delay_idle;
}
if(!this.cnt && IS_PLAYER(toucher) && !STAT(FROZEN, toucher) && !IS_DEAD(toucher) && (toucher != this.nb_dropper || time > this.nb_droptime + autocvar_g_nexball_delay_collect))
{
- if(toucher.health <= 0)
+ if(GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
return;
LogNB("caught", toucher);
GiveBall(toucher, this);
this.cp_bob_spd = this.cp_bob_spd + 1.875 * frametime;
this.colormod = '1 1 1' * (2 - bound(0, (this.pain_finished - time) / 10, 1));
- if(!this.iscaptured) this.alpha = this.health / this.max_health;
+ if(!this.iscaptured) this.alpha = GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health;
if(this.iscaptured)
{
this.origin = ReadVector();
setorigin(this, this.origin);
- this.health = ReadByte();
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, ReadByte());
this.max_health = ReadByte();
this.count = ReadByte();
this.team = ReadByte();
this.iscaptured = ReadByte();
if(!this.count)
- this.count = (this.health - this.max_health) * frametime;
+ this.count = (GetResourceAmount(this, RESOURCE_HEALTH) - this.max_health) * frametime;
cpicon_changeteam(this);
cpicon_construct(this, isnew);
_tmp = ReadByte();
- if(_tmp != this.health)
+ if(_tmp != GetResourceAmount(this, RESOURCE_HEALTH))
cpicon_damage(this, _tmp);
- this.health = _tmp;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, _tmp);
}
}
if(time < this.move_time)
return;
- if(this.health > 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) > 0)
{
// damaged fx (less probable the more damaged is the generator)
- if(random() < 0.9 - this.health / this.max_health)
+ if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
if(random() < 0.01)
{
pointparticles(EFFECT_ELECTRO_BALLEXPLODE, this.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1);
this.origin = ReadVector();
setorigin(this, this.origin);
- this.health = ReadByte();
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, ReadByte());
this.max_health = ReadByte();
this.count = ReadByte();
this.team = ReadByte();
_tmp = ReadByte();
- if(_tmp != this.health)
+ if(_tmp != GetResourceAmount(this, RESOURCE_HEALTH))
generator_damage(this, _tmp);
- this.health = _tmp;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, _tmp);
}
}
entity gen = NULL;
if(ons_roundlost)
{
- IL_EACH(g_onsgenerators, it.health <= 0,
+ IL_EACH(g_onsgenerators, GetResourceAmount(it, RESOURCE_HEALTH) <= 0,
{
gen = it;
break;
{
WriteVector(MSG_ENTITY, this.origin);
- WriteByte(MSG_ENTITY, this.health);
+ WriteByte(MSG_ENTITY, GetResourceAmount(this, RESOURCE_HEALTH));
WriteByte(MSG_ENTITY, this.max_health);
WriteByte(MSG_ENTITY, this.count);
WriteByte(MSG_ENTITY, this.team);
{
WriteByte(MSG_ENTITY, this.team);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
WriteByte(MSG_ENTITY, 0);
else
- WriteByte(MSG_ENTITY, ceil((this.health / this.max_health) * 255));
+ WriteByte(MSG_ENTITY, ceil((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 255));
}
return true;
{
WriteVector(MSG_ENTITY, this.origin);
- WriteByte(MSG_ENTITY, this.health);
+ WriteByte(MSG_ENTITY, GetResourceAmount(this, RESOURCE_HEALTH));
WriteByte(MSG_ENTITY, this.max_health);
WriteByte(MSG_ENTITY, this.count);
WriteByte(MSG_ENTITY, this.team);
{
WriteByte(MSG_ENTITY, this.team);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
WriteByte(MSG_ENTITY, 0);
else
- WriteByte(MSG_ENTITY, ceil((this.health / this.max_health) * 255));
+ WriteByte(MSG_ENTITY, ceil((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 255));
}
return true;
ons_notification_time[this.team] = time;
}
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
if(this.owner.iscaptured)
- WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
else
- WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - this.health) / (this.count / ONS_CP_THINKRATE));
+ WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - GetResourceAmount(this, RESOURCE_HEALTH)) / (this.count / ONS_CP_THINKRATE));
this.pain_finished = time + 1;
// particles on every hit
pointparticles(EFFECT_SPARKS, hitloc, force*-1, 1);
else
sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
- if (this.health < 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) < 0)
{
sound(this, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
pointparticles(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
this.SendFlags |= CPSF_STATUS;
}
+bool ons_ControlPoint_Icon_Heal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ if(targ.owner.iscaptured)
+ WaypointSprite_UpdateHealth(targ.owner.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+ else
+ WaypointSprite_UpdateBuildFinished(targ.owner.sprite, time + (targ.max_health - GetResourceAmount(targ, RESOURCE_HEALTH)) / (targ.count / ONS_CP_THINKRATE));
+ targ.SendFlags |= CPSF_STATUS;
+ return true;
+}
+
void ons_ControlPoint_Icon_Think(entity this)
{
this.nextthink = time + ONS_CP_THINKRATE;
_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);
- this.health = bound(0, this.health + (_friendly_count - _enemy_count), this.max_health);
+ GiveResourceWithLimit(this, RESOURCE_HEALTH, (_friendly_count - _enemy_count), this.max_health);
this.SendFlags |= CPSF_STATUS;
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
ons_ControlPoint_Icon_Damage(this, this, this, 1, 0, DMG_NOWEP, this.origin, '0 0 0');
return;
if (time > this.pain_finished + 5)
{
- if(this.health < this.max_health)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < this.max_health)
{
- this.health = this.health + this.count;
- if (this.health >= this.max_health)
- this.health = this.max_health;
- WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+ GiveResourceWithLimit(this, RESOURCE_HEALTH, this.count, this.max_health);
+ WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
}
}
}
// damaged fx
- if(random() < 0.6 - this.health / this.max_health)
+ if(random() < 0.6 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
{
Send_Effect(EFFECT_ELECTRIC_SPARKS, this.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
if(!a)
return;
- this.health = this.health + this.count;
+ GiveResource(this, RESOURCE_HEALTH, this.count);
this.SendFlags |= CPSF_STATUS;
- if (this.health >= this.max_health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) >= this.max_health)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on
setthink(this, ons_ControlPoint_Icon_Think);
sound(this, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILT, VOL_BASE, ATTEN_NORM);
Send_Effect(EFFECT_CAP(this.owner.team), this.owner.origin, '0 0 0', 1);
WaypointSprite_UpdateMaxHealth(this.owner.sprite, this.max_health);
- WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.owner.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
if(IS_PLAYER(this.owner.ons_toucher))
{
if(this.owner.model != MDL_ONS_CP_PAD2.model_str())
setmodel_fixsize(this.owner, MDL_ONS_CP_PAD2);
- if(random() < 0.9 - this.health / this.max_health)
+ if(random() < 0.9 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health)
Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1);
}
e.owner = cp;
e.max_health = autocvar_g_onslaught_cp_health;
- e.health = autocvar_g_onslaught_cp_buildhealth;
+ SetResourceAmountExplicit(e, RESOURCE_HEALTH, autocvar_g_onslaught_cp_buildhealth);
e.solid = SOLID_NOT;
e.takedamage = DAMAGE_AIM;
e.bot_attack = true;
IL_PUSH(g_bot_targets, e);
e.event_damage = ons_ControlPoint_Icon_Damage;
+ e.event_heal = ons_ControlPoint_Icon_Heal;
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
+ e.count = (e.max_health - GetResourceAmount(e, RESOURCE_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);
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_UpdateBuildFinished(cp.sprite, time + (e.max_health - GetResourceAmount(e, RESOURCE_HEALTH)) / (e.count / ONS_CP_THINKRATE));
WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
cp.sprite.SendFlags |= 16;
else
{
WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
- WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
+ WaypointSprite_UpdateHealth(e.sprite, GetResourceAmount(e.goalentity, RESOURCE_HEALTH));
}
}
if(e.lastshielded)
play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK));
}
}
- this.health = this.health - damage;
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
// choose an animation frame based on health
- this.frame = 10 * bound(0, (1 - this.health / this.max_health), 1);
+ this.frame = 10 * bound(0, (1 - GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health), 1);
// see if the generator is still functional, or dying
- if (this.health > 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) > 0)
{
- this.lasthealth = this.health;
+ this.lasthealth = GetResourceAmount(this, RESOURCE_HEALTH);
}
else
{
this.isshielded = false;
this.takedamage = DAMAGE_NO; // can't be hurt anymore
this.event_damage = func_null; // won't do anything if hurt
+ this.event_heal = func_null;
this.count = 0; // reset counter
setthink(this, func_null);
this.nextthink = 0;
this.SendFlags |= GSF_STATUS;
}
+bool ons_GeneratorHeal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+ targ.frame = 10 * bound(0, (1 - GetResourceAmount(targ, RESOURCE_HEALTH) / targ.max_health), 1);
+ targ.lasthealth = GetResourceAmount(targ, RESOURCE_HEALTH);
+ targ.SendFlags |= GSF_STATUS;
+ return true;
+}
+
void ons_GeneratorThink(entity this)
{
this.nextthink = time + GEN_THINKRATE;
- if (!game_stopped)
+
+ if (game_stopped || this.isshielded || time < this.wait)
+ return;
+
+ this.wait = time + 5;
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it),
{
- if(!this.isshielded && this.wait < time)
+ if (SAME_TEAM(it, this))
{
- this.wait = time + 5;
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
- if(SAME_TEAM(it, this))
- {
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
- soundto(MSG_ONE, it, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound?
- }
- else
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED));
- });
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM);
+ msg_entity = it;
+ soundto(MSG_ONE, this, CHAN_AUTO, SND(KH_ALARM), VOL_BASE, ATTEN_NONE); // FIXME: unique sound?
}
- }
+ else
+ Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_ONS_NOTSHIELDED));
+ });
}
void ons_GeneratorReset(entity this)
{
this.team = this.team_saved;
- this.lasthealth = this.max_health = this.health = autocvar_g_onslaught_gen_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, autocvar_g_onslaught_gen_health);
+ this.lasthealth = this.max_health = autocvar_g_onslaught_gen_health;
this.takedamage = DAMAGE_AIM;
this.bot_attack = true;
if(!IL_CONTAINS(g_bot_targets, this))
this.islinked = true;
this.isshielded = true;
this.event_damage = ons_GeneratorDamage;
+ this.event_heal = ons_GeneratorHeal;
setthink(this, ons_GeneratorThink);
this.nextthink = time + GEN_THINKRATE;
this.SendFlags |= GSF_STATUS;
WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
onslaught_updatelinks();
gen.team_saved = teamnumber;
IL_PUSH(g_saved_team, gen);
set_movetype(gen, MOVETYPE_NONE);
- gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
+ gen.lasthealth = gen.max_health = autocvar_g_onslaught_gen_health;
+ SetResourceAmountExplicit(gen, RESOURCE_HEALTH, autocvar_g_onslaught_gen_health);
gen.takedamage = DAMAGE_AIM;
gen.bot_attack = true;
IL_PUSH(g_bot_targets, gen);
gen.event_damage = ons_GeneratorDamage;
+ gen.event_heal = ons_GeneratorHeal;
gen.reset = ons_GeneratorReset;
setthink(gen, ons_GeneratorThink);
gen.nextthink = time + GEN_THINKRATE;
WaypointSprite_SpawnFixed(WP_Null, gen.origin + CPGEN_WAYPOINT_OFFSET, gen, sprite, RADARICON_NONE);
WaypointSprite_UpdateRule(gen.sprite, gen.team, SPRITERULE_TEAMPLAY);
WaypointSprite_UpdateMaxHealth(gen.sprite, gen.max_health);
- WaypointSprite_UpdateHealth(gen.sprite, gen.health);
+ WaypointSprite_UpdateHealth(gen.sprite, GetResourceAmount(gen, RESOURCE_HEALTH));
InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
}
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);
+ redowned += (e.team == NUM_TEAM_1 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+ blueowned += (e.team == NUM_TEAM_2 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+ yellowowned += (e.team == NUM_TEAM_3 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
+ pinkowned += (e.team == NUM_TEAM_4 && GetResourceAmount(e, RESOURCE_HEALTH) > 0);
}
}
bool needarmor = false, needweapons = false;
// Needs armor/health?
- if(this.health<100)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
needarmor = true;
// Needs weapons?
{
// gather health and armor only
if (it.solid)
- if ( ((it.health || it.armorvalue) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
+ if ( ((GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR)) && needarmor) || (STAT(WEAPONS, it) && needweapons ) )
if (vdist(it.origin - org, <, sradius))
{
int t = it.bot_pickupevalfunc(this, it);
{
entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
- if ( !source_point && player.health > 0 )
+ if ( !source_point && GetResourceAmount(player, RESOURCE_HEALTH) > 0 )
{
sprint(player, "\nYou need to be next to a control point\n");
return true;
return true;
}
- if ( player.health <= 0 )
+ if ( GetResourceAmount(player, RESOURCE_HEALTH) <= 0 )
{
player.ons_spawn_by = closest_target;
player.respawn_flags = player.respawn_flags | RESPAWN_FORCE;
{
entity wp_owner = wp.owner;
entity e = WaypointSprite_getviewentity(to);
- if(SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
+ if(SAME_TEAM(e, wp_owner) && GetResourceAmount(wp_owner.goalentity, RESOURCE_HEALTH) >= wp_owner.goalentity.max_health) { wp_flag |= 2; }
if(!ons_ControlPoint_Attackable(wp_owner, e.team)) { wp_flag |= 2; }
}
if(wp.owner.classname == "onslaught_generator")
{
entity wp_owner = wp.owner;
- if(wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { wp_flag |= 2; }
- if(wp_owner.health <= 0) { wp_flag |= 2; }
+ if(wp_owner.isshielded && GetResourceAmount(wp_owner, RESOURCE_HEALTH) >= wp_owner.max_health) { wp_flag |= 2; }
+ if(GetResourceAmount(wp_owner, RESOURCE_HEALTH) <= 0) { wp_flag |= 2; }
}
}
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/race/race.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/race/race.qh>
--- /dev/null
+#include "race.qh"
+
+// TODO: sv_race
+#ifdef SVQC
+#include <server/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(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ if (navigation_goalrating_timeout(this))
+ {
+ navigation_goalrating_start(this);
+
+ bool raw_touch_check = true;
+ int cp = this.race_checkpoint;
+
+ LABEL(search_racecheckpoints)
+ IL_EACH(g_racecheckpoints, true,
+ {
+ if(it.cnt == cp || cp == -1)
+ {
+ // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
+ // e.g. checkpoint in front of Stormkeep's warpzone
+ // the same workaround is applied in CTS game mode
+ if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
+ {
+ cp = race_NextCheckpoint(cp);
+ raw_touch_check = false;
+ goto search_racecheckpoints;
+ }
+ navigation_routerating(this, it, 1000000, 5000);
+ }
+ });
+
+ navigation_goalrating_end(this);
+
+ navigation_goalrating_timeout_set(this);
+ }
+}
+
+void race_ScoreRules()
+{
+ GameRules_score_enabled(false);
+ GameRules_scoring(race_teams, 0, 0, {
+ if (race_teams) {
+ field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+ } else if (g_race_qualifying) {
+ field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ } else {
+ field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
+ field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
+ field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
+ }
+ });
+}
+
+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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+float WinningCondition_Race(float fraglimit)
+{
+ float wc;
+ float n, c;
+
+ n = 0;
+ c = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ ++n;
+ if(CS(it).race_completed)
+ ++c;
+ });
+ if(n && (n == c))
+ return WINNING_YES;
+ wc = WinningCondition_Scores(fraglimit, 0);
+
+ // ALWAYS initiate overtime, unless EVERYONE has finished the race!
+ if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+ // do NOT support equality when the laps are all raced!
+ return WINNING_STARTSUDDENDEATHOVERTIME;
+ else
+ return WINNING_NEVER;
+}
+
+float WinningCondition_QualifyingThenRace(float limit)
+{
+ float wc;
+ wc = WinningCondition_Scores(limit, 0);
+
+ // NEVER initiate overtime
+ if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
+ {
+ return WINNING_YES;
+ }
+
+ return wc;
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientKill)
+{
+ if(g_race_qualifying)
+ M_ARGV(1, float) = 0; // killtime
+}
+
+MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(autocvar_g_allow_checkpoints)
+ race_PreparePlayer(player); // nice try
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
+{
+ entity player = M_ARGV(0, entity);
+ float dt = M_ARGV(1, float);
+
+ player.race_movetime_frac += dt;
+ float f = floor(player.race_movetime_frac);
+ player.race_movetime_frac -= f;
+ player.race_movetime_count += f;
+ player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
+
+#ifdef SVQC
+ if(IS_PLAYER(player))
+ {
+ if (player.race_penalty)
+ if (time > player.race_penalty)
+ player.race_penalty = 0;
+ if(player.race_penalty)
+ {
+ player.velocity = '0 0 0';
+ set_movetype(player, MOVETYPE_NONE);
+ player.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(CS(player).movement.x);
+ wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
+ CS(player).movement_x = wishspeed;
+ else
+ CS(player).movement_x = -wishspeed;
+ CS(player).movement_y = 0;
+ }
+ else if(wishvel.y >= 2 * wishvel.x)
+ {
+ // pure Y motion
+ CS(player).movement_x = 0;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = wishspeed;
+ else
+ CS(player).movement_y = -wishspeed;
+ }
+ else
+ {
+ // diagonal
+ if(CS(player).movement.x > 0)
+ CS(player).movement_x = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_x = -M_SQRT1_2 * wishspeed;
+ if(CS(player).movement.y > 0)
+ CS(player).movement_y = M_SQRT1_2 * wishspeed;
+ else
+ CS(player).movement_y = -M_SQRT1_2 * wishspeed;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, reset_map_global)
+{
+ float s;
+
+ Score_NicePrint(NULL);
+
+ race_ClearRecords();
+ PlayerScore_Sort(race_place, 0, 1, 0);
+
+ FOREACH_CLIENT(true, {
+ if(it.race_place)
+ {
+ s = GameRules_scoring_add(it, RACE_FASTEST, 0);
+ if(!s)
+ it.race_place = 0;
+ }
+ race_EventLog(ftos(it.race_place), it);
+ });
+
+ 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();
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ClientConnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+
+ string rr = RACE_RECORD;
+
+ if(IS_REAL_CLIENT(player))
+ {
+ msg_entity = player;
+ 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;
+ int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
+ race_send_rankings_cnt(MSG_ONE);
+ for (i = 1; i <= m; ++i)
+ {
+ race_SendRankings(i, 0, 0, MSG_ONE);
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(g_race_qualifying)
+ if(GameRules_scoring_add(player, RACE_FASTEST, 0))
+ player.frags = FRAGS_LMS_LOSER;
+ else
+ player.frags = FRAGS_SPECTATOR;
+
+ race_PreparePlayer(player);
+ player.race_checkpoint = -1;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+ entity spawn_spot = M_ARGV(1, entity);
+
+ if(spawn_spot.target == "")
+ // Emergency: this wasn't a real spawnpoint. Can this ever happen?
+ race_PreparePlayer(player);
+
+ // if we need to respawn, do it right
+ player.race_respawn_checkpoint = player.race_checkpoint;
+ player.race_respawn_spotref = spawn_spot;
+
+ player.race_place = 0;
+}
+
+MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(IS_PLAYER(player))
+ if(!game_stopped)
+ {
+ if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
+ race_PreparePlayer(player);
+ else // respawn
+ race_RetractPlayer(player);
+
+ race_AbandonRaceCheck(player);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.respawn_flags |= RESPAWN_FORCE;
+ race_AbandonRaceCheck(frag_target);
+}
+
+MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ bot.havocbot_role = havocbot_role_race;
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
+ {
+ if (!player.stored_netname)
+ player.stored_netname = strzone(uid2name(player.crypto_idfp));
+ if(player.stored_netname != player.netname)
+ {
+ db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
+ strcpy(player.stored_netname, player.netname);
+ }
+ }
+
+ if (!IS_OBSERVER(player))
+ {
+ if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
+ {
+ speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
+ speedaward_holder = player.netname;
+ speedaward_uid = player.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);
+ }
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
+{
+ if(g_race_qualifying)
+ return true; // in qualifying, you don't lose score by observing
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(0, float) = race_teams;
+}
+
+MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
+{
+ // announce remaining frags if not in qualifying mode
+ if(!g_race_qualifying)
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(rc, GetRecords)
+{
+ int record_page = M_ARGV(0, int);
+ string ret_string = M_ARGV(1, string);
+
+ 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");
+ }
+ }
+
+ M_ARGV(1, string) = ret_string;
+}
+
+MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
+{
+ return true; // doesn't work so well
+}
+
+MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
+{
+ entity player = M_ARGV(0, entity);
+
+ stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
+}
+
+MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
+{
+ float checkrules_timelimit = M_ARGV(1, float);
+ float checkrules_fraglimit = M_ARGV(2, float);
+
+ if(checkrules_timelimit >= 0)
+ {
+ if(!g_race_qualifying)
+ {
+ M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
+ return true;
+ }
+ else if(g_race_qualifying == 2)
+ {
+ M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
+ return true;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
+{
+ if(g_race_qualifying == 2)
+ warmup_stage = 0;
+}
+
+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)
+ {
+ GameRules_teams(true);
+ race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
+ }
+ 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 = autocvar_timelimit_override;
+
+ 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(want_qualifying)
+ {
+ g_race_qualifying = 2;
+ independent_players = 1;
+ race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
+ race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
+ race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
+ qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
+ fraglimit_override = 0;
+ leadlimit_override = 0;
+ timelimit_override = qualifying_override;
+ }
+ else
+ g_race_qualifying = 0;
+ GameRules_limit_score(fraglimit_override);
+ GameRules_limit_lead(leadlimit_override);
+ GameRules_limit_time(timelimit_override);
+ GameRules_limit_time_qualifying(qualifying_override);
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+void rc_SetLimits();
+void race_Initialize();
+
+REGISTER_MUTATOR(rc, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ rc_SetLimits();
+
+ race_Initialize();
+ }
+ return 0;
+}
+#endif
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/tdm/tdm.qc>
--- /dev/null
+// generated file; do not modify
+#include <common/gamemodes/gamemode/tdm/tdm.qh>
--- /dev/null
+#include "tdm.qh"
+
+// TODO: sv_tdm
+// TODO? rename to teamdeathmatch
+#ifdef SVQC
+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 || !this.cnt) { delete(this); return; }
+
+ this.classname = "tdm_team";
+ this.team = this.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have team entities
+void tdm_SpawnTeam (string teamname, int teamcolor)
+{
+ entity this = new_pure(tdm_team);
+ this.netname = teamname;
+ this.cnt = teamcolor - 1;
+ this.team = teamcolor;
+ this.spawnfunc_checked = true;
+ //spawnfunc_tdm_team(this);
+}
+
+void tdm_DelayedInit(entity this)
+{
+ // if no teams are found, spawn defaults
+ if(find(NULL, classname, "tdm_team") == NULL)
+ {
+ LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
+
+ int numteams = autocvar_g_tdm_teams_override;
+ if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
+
+ int teams = BITS(bound(2, numteams, 4));
+ if(teams & BIT(0))
+ tdm_SpawnTeam("Red", NUM_TEAM_1);
+ if(teams & BIT(1))
+ tdm_SpawnTeam("Blue", NUM_TEAM_2);
+ if(teams & BIT(2))
+ tdm_SpawnTeam("Yellow", NUM_TEAM_3);
+ if(teams & BIT(3))
+ tdm_SpawnTeam("Pink", NUM_TEAM_4);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
+{
+ M_ARGV(1, string) = "tdm_team";
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
+#endif
--- /dev/null
+#pragma once
+
+#ifdef SVQC
+#include <common/mutators/base.qh>
+int autocvar_g_tdm_point_limit;
+int autocvar_g_tdm_point_leadlimit;
+bool autocvar_g_tdm_team_spawns;
+void tdm_DelayedInit(entity this);
+
+REGISTER_MUTATOR(tdm, false)
+{
+ MUTATOR_STATIC();
+ MUTATOR_ONADD
+ {
+ GameRules_teams(true);
+ GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
+ GameRules_limit_score(autocvar_g_tdm_point_limit);
+ GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
+
+ InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
+ }
+ return 0;
+}
+#endif
#pragma once
+// TODO: find a better location for these?
+float total_players;
+float redalive, bluealive, yellowalive, pinkalive;
+
// todo: accept the number of teams as a parameter
void GameRules_teams(bool value);
#define G_MINOR(id) ((id) % Inventory_groups_minor)
#ifdef CSQC
+Inventory g_inventory;
NET_HANDLE(ENT_CLIENT_INVENTORY, bool isnew)
{
make_pure(this);
+ g_inventory = this;
const int majorBits = ReadShort();
for (int i = 0; i < Inventory_groups_major; ++i) {
if (!(majorBits & BIT(i))) {
enum
{
ITEM_FLAG_NORMAL = BIT(0), ///< Item is usable during normal gameplay.
- ITEM_FLAG_INSTAGIB = BIT(1), ///< Item is usable in instagib.
- ITEM_FLAG_OVERKILL = BIT(2), ///< Item is usable in overkill.
- ITEM_FLAG_MUTATORBLOCKED = BIT(3)
+ ITEM_FLAG_MUTATORBLOCKED = BIT(1),
+ ITEM_FLAG_RESOURCE = BIT(2) ///< Item is is a resource, not a held item.
};
#define ITEM_HANDLE(signal, ...) __Item_Send_##signal(__VA_ARGS__)
#include "pickup.qh"
#ifdef SVQC
#include <common/t_items.qh>
+ #include <server/resources.qh>
#endif
+#if 1
.int ammo_none;
.int ammo_shells;
.int ammo_nails;
.int ammo_plasma;
.int ammo_fuel;
#endif
+#endif
#ifdef SVQC
PROPERTY(float, g_pickup_ammo_anyway);
#ifdef SVQC
PROPERTY(int, g_pickup_nails);
-void ammo_bullets_init(entity item)
+void ammo_bullets_init(Pickup this, entity item)
{
- if(!item.ammo_nails)
- item.ammo_nails = g_pickup_nails;
+ if(!GetResourceAmount(item, RESOURCE_BULLETS))
+ SetResourceAmountExplicit(item, RESOURCE_BULLETS, g_pickup_nails);
}
#endif
REGISTER_ITEM(Bullets, Bullets) {
this.m_canonical_spawnfunc = "item_bullets";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_Bullets_ITEM;
#endif
this.netname = "bullets";
#ifdef SVQC
PROPERTY(int, g_pickup_cells);
-void ammo_cells_init(entity item)
+void ammo_cells_init(Pickup this, entity item)
{
- if(!item.ammo_cells)
- item.ammo_cells = g_pickup_cells;
+ if(!GetResourceAmount(item, RESOURCE_CELLS))
+ SetResourceAmountExplicit(item, RESOURCE_CELLS, g_pickup_cells);
}
#endif
REGISTER_ITEM(Cells, Ammo) {
this.m_canonical_spawnfunc = "item_cells";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_Cells_ITEM;
#endif
this.netname = "cells";
#ifdef SVQC
PROPERTY(int, g_pickup_plasma);
-void ammo_plasma_init(entity item)
+void ammo_plasma_init(Pickup this, entity item)
{
- if(!item.ammo_plasma)
- item.ammo_plasma = g_pickup_plasma;
+ if(!GetResourceAmount(item, RESOURCE_PLASMA))
+ SetResourceAmountExplicit(item, RESOURCE_PLASMA, g_pickup_plasma);
}
#endif
REGISTER_ITEM(Plasma, Ammo) {
this.m_canonical_spawnfunc = "item_plasma";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_Plasma_ITEM;
#endif
this.netname = "plasma";
#ifdef SVQC
PROPERTY(int, g_pickup_rockets);
-void ammo_rockets_init(entity item)
+void ammo_rockets_init(Pickup this, entity item)
{
- if(!item.ammo_rockets)
- item.ammo_rockets = g_pickup_rockets;
+ if(!GetResourceAmount(item, RESOURCE_ROCKETS))
+ SetResourceAmountExplicit(item, RESOURCE_ROCKETS, g_pickup_rockets);
}
#endif
REGISTER_ITEM(Rockets, Ammo) {
this.m_canonical_spawnfunc = "item_rockets";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_Rockets_ITEM;
#endif
this.netname = "rockets";
#ifdef SVQC
PROPERTY(int, g_pickup_shells);
-void ammo_shells_init(entity item)
+void ammo_shells_init(Pickup this, entity item)
{
- if(!item.ammo_shells)
- item.ammo_shells = g_pickup_shells;
+ if(!GetResourceAmount(item, RESOURCE_SHELLS))
+ SetResourceAmountExplicit(item, RESOURCE_SHELLS, g_pickup_shells);
}
#endif
REGISTER_ITEM(Shells, Shells) {
this.m_canonical_spawnfunc = "item_shells";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_Shells_ITEM;
#endif
this.netname = "shells";
PROPERTY(float, g_pickup_armorsmall_anyway);
PROPERTY(int, g_pickup_armorsmall);
PROPERTY(int, g_pickup_armorsmall_max);
-void item_armorsmall_init(entity item)
+void item_armorsmall_init(Pickup this, entity item)
{
if(!item.max_armorvalue)
item.max_armorvalue = g_pickup_armorsmall_max;
- if(!item.armorvalue)
- item.armorvalue = g_pickup_armorsmall;
+ if(!GetResourceAmount(item, RESOURCE_ARMOR))
+ SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armorsmall);
}
#endif
REGISTER_ITEM(ArmorSmall, Armor) {
this.m_canonical_spawnfunc = "item_armor_small";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_ArmorSmall_ITEM;
this.m_sound = SND_ArmorSmall;
#endif
PROPERTY(float, g_pickup_armormedium_anyway);
PROPERTY(int, g_pickup_armormedium);
PROPERTY(int, g_pickup_armormedium_max);
-void item_armormedium_init(entity item)
+void item_armormedium_init(Pickup this, entity item)
{
if(!item.max_armorvalue)
item.max_armorvalue = g_pickup_armormedium_max;
- if(!item.armorvalue)
- item.armorvalue = g_pickup_armormedium;
+ if(!GetResourceAmount(item, RESOURCE_ARMOR))
+ SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armormedium);
}
#endif
REGISTER_ITEM(ArmorMedium, Armor) {
this.m_canonical_spawnfunc = "item_armor_medium";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_ArmorMedium_ITEM;
this.m_sound = SND_ArmorMedium;
#endif
PROPERTY(float, g_pickup_armorbig_anyway);
PROPERTY(int, g_pickup_armorbig);
PROPERTY(int, g_pickup_armorbig_max);
-void item_armorbig_init(entity item)
+void item_armorbig_init(Pickup this, entity item)
{
if(!item.max_armorvalue)
item.max_armorvalue = g_pickup_armorbig_max;
- if(!item.armorvalue)
- item.armorvalue = g_pickup_armorbig;
+ if(!GetResourceAmount(item, RESOURCE_ARMOR))
+ SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armorbig);
}
#endif
REGISTER_ITEM(ArmorBig, Armor) {
this.m_canonical_spawnfunc = "item_armor_big";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_ArmorBig_ITEM;
this.m_sound = SND_ArmorBig;
#endif
PROPERTY(float, g_pickup_armormega_anyway);
PROPERTY(int, g_pickup_armormega);
PROPERTY(int, g_pickup_armormega_max);
-void item_armormega_init(entity item)
+void item_armormega_init(Pickup this, entity item)
{
if(!item.max_armorvalue)
item.max_armorvalue = g_pickup_armormega_max;
- if(!item.armorvalue)
- item.armorvalue = g_pickup_armormega;
+ if(!GetResourceAmount(item, RESOURCE_ARMOR))
+ SetResourceAmountExplicit(item, RESOURCE_ARMOR, g_pickup_armormega);
}
#endif
REGISTER_ITEM(ArmorMega, Armor) {
this.m_canonical_spawnfunc = "item_armor_mega";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_ArmorMega_ITEM;
this.m_sound = SND_ArmorMega;
#endif
PROPERTY(float, g_pickup_healthsmall_anyway);
PROPERTY(int, g_pickup_healthsmall);
PROPERTY(int, g_pickup_healthsmall_max);
-void item_healthsmall_init(entity item)
+void item_healthsmall_init(Pickup this, entity item)
{
if(!item.max_health)
item.max_health = g_pickup_healthsmall_max;
- if(!item.health)
- item.health = g_pickup_healthsmall;
+ if(!GetResourceAmount(item, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthsmall);
}
#endif
REGISTER_ITEM(HealthSmall, Health) {
this.m_canonical_spawnfunc = "item_health_small";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_HealthSmall_ITEM;
this.m_sound = SND_HealthSmall;
#endif
PROPERTY(float, g_pickup_healthmedium_anyway);
PROPERTY(int, g_pickup_healthmedium);
PROPERTY(int, g_pickup_healthmedium_max);
-void item_healthmedium_init(entity item)
+void item_healthmedium_init(Pickup this, entity item)
{
if(!item.max_health)
item.max_health = g_pickup_healthmedium_max;
- if(!item.health)
- item.health = g_pickup_healthmedium;
+ if(!GetResourceAmount(item, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthmedium);
}
#endif
REGISTER_ITEM(HealthMedium, Health) {
this.m_canonical_spawnfunc = "item_health_medium";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_HealthMedium_ITEM;
this.m_sound = SND_HealthMedium;
#endif
PROPERTY(float, g_pickup_healthbig_anyway);
PROPERTY(int, g_pickup_healthbig);
PROPERTY(int, g_pickup_healthbig_max);
-void item_healthbig_init(entity item)
+void item_healthbig_init(Pickup this, entity item)
{
if(!item.max_health)
item.max_health = g_pickup_healthbig_max;
- if(!item.health)
- item.health = g_pickup_healthbig;
+ if(!GetResourceAmount(item, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthbig);
}
#endif
REGISTER_ITEM(HealthBig, Health) {
this.m_canonical_spawnfunc = "item_health_big";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_HealthBig_ITEM;
this.m_sound = SND_HealthBig;
#endif
PROPERTY(float, g_pickup_healthmega_anyway);
PROPERTY(int, g_pickup_healthmega);
PROPERTY(int, g_pickup_healthmega_max);
-void item_healthmega_init(entity item)
+void item_healthmega_init(Pickup this, entity item)
{
if(!item.max_health)
item.max_health = g_pickup_healthmega_max;
- if(!item.health)
- item.health = g_pickup_healthmega;
+ if(!GetResourceAmount(item, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(item, RESOURCE_HEALTH, g_pickup_healthmega);
}
#endif
REGISTER_ITEM(HealthMega, Health) {
this.m_canonical_spawnfunc = "item_health_mega";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_OVERKILL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_HealthMega_ITEM;
this.m_sound = SND_HealthMega;
#endif
#ifdef SVQC
PROPERTY(int, g_pickup_fuel_jetpack);
-void powerup_jetpack_init(entity item)
+void powerup_jetpack_init(Pickup this, entity item)
{
- if(!item.ammo_fuel)
- item.ammo_fuel = g_pickup_fuel_jetpack;
+ if(!GetResourceAmount(item, RESOURCE_FUEL))
+ SetResourceAmountExplicit(item, RESOURCE_FUEL, g_pickup_fuel_jetpack);
}
#endif
this.m_itemid = IT_JETPACK;
#endif
this.netname = "jetpack";
- this.m_name = "Jet pack";
+ this.m_name = "Jetpack";
this.m_icon = "jetpack";
this.m_color = '0.5 0.5 0.5';
- this.m_waypoint = _("Jet Pack");
+ this.m_waypoint = _("Jetpack");
this.m_waypointblink = 2;
#ifdef SVQC
this.m_botvalue = 3000;
#ifdef SVQC
PROPERTY(int, g_pickup_fuel);
-void ammo_fuel_init(entity item)
+void ammo_fuel_init(Pickup this, entity item)
{
- if(!item.ammo_fuel)
- item.ammo_fuel = g_pickup_fuel;
+ if(!GetResourceAmount(item, RESOURCE_FUEL))
+ SetResourceAmountExplicit(item, RESOURCE_FUEL, g_pickup_fuel);
}
#endif
REGISTER_ITEM(JetpackFuel, Ammo) {
this.m_canonical_spawnfunc = "item_fuel";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_NORMAL;
+ this.spawnflags = ITEM_FLAG_NORMAL | ITEM_FLAG_RESOURCE;
this.m_model = MDL_JetpackFuel_ITEM;
#endif
this.netname = "fuel";
ATTRIB(Pickup, m_respawntime, float());
ATTRIB(Pickup, m_respawntimejitter, float());
ATTRIB(Pickup, m_pickupanyway, float());
- ATTRIB(Pickup, m_iteminit, void(entity item));
+ ATTRIB(Pickup, m_iteminit, void(Pickup this, entity item));
float Item_GiveTo(entity item, entity player);
METHOD(Pickup, giveTo, bool(Pickup this, entity item, entity player));
bool ITEM_HANDLE(Pickup, Pickup this, entity item, entity player);
#ifdef SVQC
float autocvar_g_balance_powerup_strength_time;
-void powerup_strength_init(entity item)
+void powerup_strength_init(Pickup this, entity item)
{
if(!item.strength_finished)
item.strength_finished = autocvar_g_balance_powerup_strength_time;
#ifdef SVQC
float autocvar_g_balance_powerup_invincible_time;
-void powerup_shield_init(entity item)
+void powerup_shield_init(Pickup this, entity item)
{
if(!item.invincible_finished)
item.invincible_finished = autocvar_g_balance_powerup_invincible_time;
CLASS(Deathmatch, Gametype)
INIT(Deathmatch)
{
- this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,"","timelimit=20 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
+ this.gametype_init(this, _("Deathmatch"),"dm","g_dm",false,"","timelimit=15 pointlimit=30 leadlimit=0",_("Score as many frags as you can"));
}
METHOD(Deathmatch, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
{
CLASS(TeamDeathmatch, Gametype)
INIT(TeamDeathmatch)
{
- this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,"","timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
+ this.gametype_init(this, _("Team Deathmatch"),"tdm","g_tdm",true,"","timelimit=15 pointlimit=50 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team"));
}
METHOD(TeamDeathmatch, m_parse_mapinfo, bool(string k, string v))
{
#include "breakable.qh"
#ifdef SVQC
-#include <server/g_subs.qh>
#include <server/g_damage.qh>
#include <server/bot/api.qh>
#include <common/csqcmodel_settings.qh>
float h;
if (!(this.spawnflags & BREAKABLE_INDICATE_DAMAGE))
return;
- h = this.health / this.max_health;
+ h = GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health;
if(h < 0.25)
this.colormod = '1 0 0';
else if(h <= 0.75)
void func_breakable_behave_destroyed(entity this)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.takedamage = DAMAGE_NO;
if(this.bot_attack)
IL_REMOVE(g_bot_targets, this);
func_breakable_colormod(this);
if (this.noise1)
stopsound (this, CH_TRIGGER_SINGLE);
+
+ IL_EACH(g_projectiles, it.classname == "grapplinghook" && it.aiment == this,
+ {
+ RemoveHook(it);
+ });
}
void func_breakable_think(entity this)
void func_breakable_destroy(entity this, entity actor, entity trigger);
void func_breakable_behave_restore(entity this)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
if(this.sprite)
{
WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
}
if(!(this.spawnflags & BREAKABLE_NODAMAGE))
{
void func_breakable_restore_self(entity this)
{
+ // TODO: use a clipgroup for all func_breakables so they don't collide with eachother
+ float oldhit = this.dphitcontentsmask;
+ this.dphitcontentsmask = DPCONTENTS_BODY; // we really only care about when players are standing inside, obey the mapper in other cases!
+ tracebox(this.origin, this.mins, this.maxs, this.origin, MOVE_NORMAL, this);
+ this.dphitcontentsmask = oldhit;
+ if(trace_startsolid || trace_fraction < 1)
+ {
+ this.nextthink = time + 5; // retry every 5 seconds until the area becomes clear
+ return;
+ }
func_breakable_restore(this, NULL, NULL);
}
if(attacker.team == this.team)
return;
this.pain_finished = time;
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
if(this.sprite)
{
WaypointSprite_Ping(this.sprite);
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
}
func_breakable_colormod(this);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
debrisforce = force;
spawnfunc(func_breakable)
{
float n, i;
- if(!this.health)
- this.health = 100;
- this.max_health = this.health;
+ if(!GetResourceAmount(this, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100);
+ this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
// yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
if(!this.debrismovetype) this.debrismovetype = MOVETYPE_BOUNCE;
this.state = STATE_DOWN;
SUB_CalcMove (this, this.pos1, TSPEED_LINEAR, this.speed, button_done);
this.frame = 0; // use normal textures
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
this.takedamage = DAMAGE_YES; // can be shot again
}
void button_fire(entity this)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.takedamage = DAMAGE_NO; // will be reset upon return
if (this.state == STATE_UP || this.state == STATE_TOP)
void button_reset(entity this)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
setorigin(this, this.pos1);
this.frame = 0; // use normal textures
this.state = STATE_BOTTOM;
this.velocity = '0 0 0';
setthink(this, func_null);
this.nextthink = 0;
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
this.takedamage = DAMAGE_YES; // can be shot again
}
return;
if (this.spawnflags & BUTTON_DONTACCUMULATEDMG)
{
- if (this.health <= damage)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= damage)
{
this.enemy = attacker;
button_fire(this);
}
else
{
- this.health = this.health - damage;
- if (this.health <= 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
this.enemy = attacker;
button_fire(this);
// if (this.health == 0) // all buttons are now shootable
// this.health = 10;
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
{
- this.max_health = this.health;
+ this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
this.event_damage = button_damage;
this.takedamage = DAMAGE_YES;
}
if (this.max_health)
{
this.takedamage = DAMAGE_YES;
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
}
this.state = STATE_DOWN;
if(this.spawnflags & NOSPLASH)
if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
return;
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
if (this.itemkeys)
{
return;
}
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
- this.owner.health = this.owner.max_health;
- this.owner.takedamage = DAMAGE_NO; // wil be reset upon return
+ SetResourceAmountExplicit(this.owner, RESOURCE_HEALTH, this.owner.max_health);
+ this.owner.takedamage = DAMAGE_NO; // will be reset upon return
door_use(this.owner, NULL, NULL);
}
}
void door_trigger_touch(entity this, entity toucher)
{
- if (toucher.health < 1)
+ if (GetResourceAmount(toucher, RESOURCE_HEALTH) < 1)
#ifdef SVQC
if (!((toucher.iscreature || (toucher.flags & FL_PROJECTILE)) && !IS_DEAD(toucher)))
#elif defined(CSQC)
{
this.owner = this.enemy = this;
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
return;
IFTARGETED
return;
cmaxs = this.absmax;
for(t = this; ; t = t.enemy)
{
- if(t.health && !this.health)
- this.health = t.health;
+ if(GetResourceAmount(t, RESOURCE_HEALTH) && !GetResourceAmount(this, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(t, RESOURCE_HEALTH));
if((t.targetname != "") && (this.targetname == ""))
this.targetname = t.targetname;
if((t.message != "") && (this.message == ""))
// distribute health, targetname, message
for(t = this; t; t = t.enemy)
{
- t.health = this.health;
+ SetResourceAmountExplicit(t, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
t.targetname = this.targetname;
t.message = this.message;
if(t.enemy == this)
// shootable, or triggered doors just needed the owner/enemy links,
// they don't spawn a field
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
return;
IFTARGETED
return;
// common code for func_door and func_door_rotating spawnfuncs
void door_init_shared(entity this)
{
- this.max_health = this.health;
+ this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
// unlock sound
if(this.noise == "")
this.state = STATE_BOTTOM;
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
{
//this.canteamdamage = true; // TODO
this.takedamage = DAMAGE_YES;
if (this.max_health)
{
this.takedamage = DAMAGE_YES;
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
}
this.state = STATE_DOWN;
float temp;
string message_save;
- this.health = 10000;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
if(!this.bot_attack)
IL_PUSH(g_bot_targets, this);
this.bot_attack = true;
{
if (this.spawnflags&DOOR_SECRET_YES_SHOOT)
{
- this.health = 10000;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
this.takedamage = DAMAGE_YES;
//this.th_pain = fd_secret_use;
}
{
if (this.spawnflags & DOOR_SECRET_YES_SHOOT)
{
- this.health = 10000;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
this.takedamage = DAMAGE_YES;
}
setorigin(this, this.oldorigin);
if (this.spawnflags & DOOR_SECRET_YES_SHOOT)
{
//this.canteamdamage = true; // TODO
- this.health = 10000;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10000);
this.takedamage = DAMAGE_YES;
this.event_damage = fd_secret_damage;
}
#ifdef SVQC
#include <server/defs.qh>
#include <server/miscfunctions.qh>
-#include <server/g_subs.qh>
#include <common/net_linked.qh>
#include "subs.qh"
#include "triggers.qh"
// solid brush entities
spawnfunc(func_wall) { G_MODEL_INIT (this, SOLID_BSP) } // Q1 name
spawnfunc(func_clientwall) { G_CLIENTMODEL_INIT(this, SOLID_BSP) } // brush entity (WARNING: MISPREDICTED)
+#elif defined(CSQC)
+.float alpha;
+.float scale;
+.vector movedir;
+
+void Ent_Wall_PreDraw(entity this)
+{
+ if (this.inactive)
+ {
+ this.alpha = 0;
+ }
+ else
+ {
+ vector org = getpropertyvec(VF_ORIGIN);
+ if(!checkpvs(org, this))
+ this.alpha = 0;
+ else if(this.fade_start || this.fade_end) {
+ vector offset = '0 0 0';
+ offset_z = this.fade_vertical_offset;
+ float player_dist = vlen(org - this.origin - 0.5 * (this.mins + this.maxs) + offset);
+ if (this.fade_end == this.fade_start)
+ {
+ if (player_dist >= this.fade_start)
+ this.alpha = 0;
+ else
+ this.alpha = 1;
+ }
+ else
+ {
+ this.alpha = (this.alpha_min + this.alpha_max * bound(0,
+ (this.fade_end - player_dist)
+ / (this.fade_end - this.fade_start), 1)) / 100.0;
+ }
+ }
+ else
+ {
+ this.alpha = 1;
+ }
+ }
+ if(this.alpha <= 0)
+ this.drawmask = 0;
+ else
+ this.drawmask = MASK_NORMAL;
+}
+
+void Ent_Wall_Draw(entity this)
+{
+ float f;
+ var .vector fld;
+
+ if(this.bgmscriptangular)
+ fld = angles;
+ else
+ fld = origin;
+ this.(fld) = this.saved;
+
+ if(this.lodmodelindex1)
+ {
+ if(autocvar_cl_modeldetailreduction <= 0)
+ {
+ if(this.lodmodelindex2 && autocvar_cl_modeldetailreduction <= -2)
+ this.modelindex = this.lodmodelindex2;
+ else if(autocvar_cl_modeldetailreduction <= -1)
+ this.modelindex = this.lodmodelindex1;
+ else
+ this.modelindex = this.lodmodelindex0;
+ }
+ else
+ {
+ float distance = vlen(NearestPointOnBox(this, view_origin) - view_origin);
+ f = (distance * current_viewzoom + 100.0) * autocvar_cl_modeldetailreduction;
+ f *= 1.0 / bound(0.01, view_quality, 1);
+ if(this.lodmodelindex2 && f > this.loddistance2)
+ this.modelindex = this.lodmodelindex2;
+ else if(f > this.loddistance1)
+ this.modelindex = this.lodmodelindex1;
+ else
+ this.modelindex = this.lodmodelindex0;
+ }
+ }
+
+ InterpolateOrigin_Do(this);
+
+ this.saved = this.(fld);
+
+ f = doBGMScript(this);
+ if(f >= 0)
+ {
+ if(this.lip < 0) // < 0: alpha goes from 1 to 1-|lip| when toggled (toggling subtracts lip)
+ this.alpha = 1 + this.lip * f;
+ else // > 0: alpha goes from 1-|lip| to 1 when toggled (toggling adds lip)
+ this.alpha = 1 - this.lip * (1 - f);
+ this.(fld) = this.(fld) + this.movedir * f;
+ }
+ else
+ this.alpha = 1;
+
+ if(this.alpha >= ALPHA_MIN_VISIBLE)
+ this.drawmask = MASK_NORMAL;
+ else
+ this.drawmask = 0;
+}
+
+void Ent_Wall_Remove(entity this)
+{
+ strfree(this.bgmscript);
+}
+
+NET_HANDLE(ENT_CLIENT_WALL, bool isnew)
+{
+ int f;
+ var .vector fld;
+
+ InterpolateOrigin_Undo(this);
+ this.iflags = IFLAG_ANGLES | IFLAG_ORIGIN;
+
+ if(this.bgmscriptangular)
+ fld = angles;
+ else
+ fld = origin;
+ this.(fld) = this.saved;
+
+ f = ReadByte();
+
+ if(f & 1)
+ {
+ if(f & 0x40)
+ this.colormap = ReadShort();
+ else
+ this.colormap = 0;
+ this.skin = ReadByte();
+ }
+
+ if(f & 2)
+ {
+ this.origin = ReadVector();
+ setorigin(this, this.origin);
+ }
+
+ if(f & 4)
+ {
+ if(f & 0x10)
+ {
+ this.angles_x = ReadAngle();
+ this.angles_y = ReadAngle();
+ this.angles_z = ReadAngle();
+ }
+ else
+ this.angles = '0 0 0';
+ }
+
+ if(f & 8)
+ {
+ if(f & 0x80)
+ {
+ this.lodmodelindex0 = ReadShort();
+ this.loddistance1 = ReadShort();
+ this.lodmodelindex1 = ReadShort();
+ this.loddistance2 = ReadShort();
+ this.lodmodelindex2 = ReadShort();
+ }
+ else
+ {
+ this.modelindex = ReadShort();
+ this.loddistance1 = 0;
+ this.loddistance2 = 0;
+ }
+ this.solid = ReadByte();
+ this.scale = ReadShort() / 256.0;
+ if(f & 0x20)
+ {
+ this.mins = ReadVector();
+ this.maxs = ReadVector();
+ }
+ else
+ this.mins = this.maxs = '0 0 0';
+ setsize(this, this.mins, this.maxs);
+
+ string s = ReadString();
+ if(substring(s, 0, 1) == "<")
+ {
+ strcpy(this.bgmscript, substring(s, 1, -1));
+ this.bgmscriptangular = 1;
+ }
+ else
+ {
+ strcpy(this.bgmscript, s);
+ this.bgmscriptangular = 0;
+ }
+ if(this.bgmscript != "")
+ {
+ this.bgmscriptattack = ReadByte() / 64.0;
+ this.bgmscriptdecay = ReadByte() / 64.0;
+ this.bgmscriptsustain = ReadByte() / 255.0;
+ this.bgmscriptrelease = ReadByte() / 64.0;
+ this.movedir = ReadVector();
+ this.lip = ReadByte() / 255.0;
+ }
+ this.fade_start = ReadByte();
+ this.fade_end = ReadByte();
+ this.alpha_max = ReadByte();
+ this.alpha_min = ReadByte();
+ this.inactive = ReadByte();
+ this.fade_vertical_offset = ReadShort();
+ BGMScript_InitEntity(this);
+ }
+
+ return = true;
+
+ InterpolateOrigin_Note(this);
+
+ this.saved = this.(fld);
+
+ this.entremove = Ent_Wall_Remove;
+ this.draw = Ent_Wall_Draw;
+ if (isnew) IL_PUSH(g_drawables, this);
+ setpredraw(this, Ent_Wall_PreDraw);
+}
#endif
#pragma once
+
+#ifdef CSQC
+entityclass(Wall);
+classfield(Wall) .float lip;
+classfield(Wall) .float bgmscriptangular;
+classfield(Wall) .int lodmodelindex0, lodmodelindex1, lodmodelindex2;
+classfield(Wall) .float loddistance1, loddistance2;
+classfield(Wall) .vector saved;
+
+// Needed for interactive clientwalls
+.float inactive; // Clientwall disappears when inactive
+.float alpha_max, alpha_min;
+// If fade_start > fade_end, fadeout will be inverted
+// fade_vertical_offset is a vertival offset for player position
+.float fade_start, fade_end, fade_vertical_offset;
+.float default_solid;
+
+void Ent_Wall_Draw(entity this);
+
+void Ent_Wall_Remove(entity this);
+#endif
if (!toucher.iscreature)
return;
- if (toucher.health <= 0)
+ if (GetResourceAmount(toucher, RESOURCE_HEALTH) <= 0)
return;
#elif defined(CSQC)
if (!IS_PLAYER(toucher))
if (!toucher.iscreature)
return;
- if (toucher.health <= 0)
+ if (GetResourceAmount(toucher, RESOURCE_HEALTH) <= 0)
return;
#elif defined(CSQC)
if (!IS_PLAYER(toucher))
ApplyMinMaxScaleAngles(this);
}
+bool LOD_customize(entity this, entity client)
+{
+ if(autocvar_loddebug)
+ {
+ int d = autocvar_loddebug;
+ if(d == 1)
+ this.modelindex = this.lodmodelindex0;
+ else if(d == 2 || !this.lodmodelindex2)
+ this.modelindex = this.lodmodelindex1;
+ else // if(d == 3)
+ this.modelindex = this.lodmodelindex2;
+ return true;
+ }
+
+ // TODO csqc network this so it only gets sent once
+ vector near_point = NearestPointOnBox(this, client.origin);
+ if(vdist(near_point - client.origin, <, this.loddistance1))
+ this.modelindex = this.lodmodelindex0;
+ else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2))
+ this.modelindex = this.lodmodelindex1;
+ else
+ this.modelindex = this.lodmodelindex2;
+
+ return true;
+}
+
+void LOD_uncustomize(entity this)
+{
+ this.modelindex = this.lodmodelindex0;
+}
+
+void LODmodel_attach(entity this)
+{
+ entity e;
+
+ if(!this.loddistance1)
+ this.loddistance1 = 1000;
+ if(!this.loddistance2)
+ this.loddistance2 = 2000;
+ this.lodmodelindex0 = this.modelindex;
+
+ if(this.lodtarget1 != "")
+ {
+ e = find(NULL, targetname, this.lodtarget1);
+ if(e)
+ {
+ this.lodmodel1 = e.model;
+ delete(e);
+ }
+ }
+ if(this.lodtarget2 != "")
+ {
+ e = find(NULL, targetname, this.lodtarget2);
+ if(e)
+ {
+ this.lodmodel2 = e.model;
+ delete(e);
+ }
+ }
+
+ if(autocvar_loddebug < 0)
+ {
+ this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
+ }
+
+ if(this.lodmodel1 != "")
+ {
+ vector mi, ma;
+ mi = this.mins;
+ ma = this.maxs;
+
+ precache_model(this.lodmodel1);
+ _setmodel(this, this.lodmodel1);
+ this.lodmodelindex1 = this.modelindex;
+
+ if(this.lodmodel2 != "")
+ {
+ precache_model(this.lodmodel2);
+ _setmodel(this, this.lodmodel2);
+ this.lodmodelindex2 = this.modelindex;
+ }
+
+ this.modelindex = this.lodmodelindex0;
+ setsize(this, mi, ma);
+ }
+
+ if(this.lodmodelindex1)
+ if (!getSendEntity(this))
+ SetCustomizer(this, LOD_customize, LOD_uncustomize);
+}
+
/*
================
InitTrigger
#pragma once
#include "defs.qh"
-void SUB_SetFade (entity ent, float when, float fading_time);
-void SUB_VanishOrRemove (entity ent);
+.float friction;
+void SUB_Friction(entity this);
+
+void SUB_NullThink(entity this);
+
+/*
+==================
+SUB_VanishOrRemove
+
+Makes client invisible or removes non-client
+==================
+*/
+void SUB_VanishOrRemove(entity ent);
+
+void SUB_SetFade_Think(entity this);
+
+/*
+==================
+SUB_SetFade
+
+Fade 'ent' out when time >= 'when'
+==================
+*/
+void SUB_SetFade(entity ent, float when, float fading_time);
.vector finaldest, finalangle; //plat.qc stuff
.void(entity this) think1;
void SetBrushEntityModel(entity this);
void SetBrushEntityModelNoLOD(entity this);
+
+int autocvar_loddebug;
+.string lodtarget1;
+.string lodtarget2;
+.string lodmodel1;
+.string lodmodel2;
+.float lodmodelindex0;
+.float lodmodelindex1;
+.float lodmodelindex2;
+.float loddistance1;
+.float loddistance2;
+
+bool LOD_customize(entity this, entity client);
+
+void LOD_uncustomize(entity this);
+
+void LODmodel_attach(entity this);
#endif
/*
#elif defined(SVQC)
void target_music_kill();
+
+void TargetMusic_RestoreGame();
#endif
{
TDEATHLOOP(player.origin)
{
- if (IS_PLAYER(player) && player.health >= 1)
+ if (IS_PLAYER(player) && GetResourceAmount(player, RESOURCE_HEALTH) >= 1)
{
if (!(teamplay && autocvar_g_telefrags_teamplay && head.team == player.team))
{
if(IS_PLAYER(head))
- if(head.health >= 1)
+ if(GetResourceAmount(head, RESOURCE_HEALTH) >= 1)
++tdeath_hit;
Damage (head, teleporter, telefragger, 10000, DEATH_TELEFRAG.m_id, DMG_NOWEP, head.origin, '0 0 0');
}
toucher.triggerhealtime = time + this.delay;
bool playthesound = (this.spawnflags & HEAL_SOUND_ALWAYS);
- if (toucher.health < this.max_health)
- {
- playthesound = true;
- toucher.health = min(toucher.health + this.health, this.max_health);
- toucher.pauserothealth_finished = max(toucher.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
- }
+ bool healed = Heal(toucher, this, GetResourceAmount(this, RESOURCE_HEALTH), this.max_health);
- if(playthesound)
+ if(playthesound || healed)
_sound (toucher, CH_TRIGGER, this.noise, VOL_BASE, ATTEN_NORM);
}
}
this.active = ACTIVE_ACTIVE;
if(!this.delay)
this.delay = 1;
- if(!this.health)
- this.health = 10;
+ if(!GetResourceAmount(this, RESOURCE_HEALTH))
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 10); // TODO: use a special field for this, it doesn't have actual health!
if(!this.max_health)
this.max_health = 200; // max health topoff for field
if(this.noise == "")
{
if (this.max_health)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.takedamage = DAMAGE_YES;
this.solid = SOLID_BBOX;
}
if(this.team)
if(((this.spawnflags & INVERT_TEAMS) == 0) == (this.team != attacker.team))
return;
- this.health = this.health - damage;
- if (this.health <= 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
this.enemy = attacker;
this.goalentity = inflictor;
settouch(this, multi_touch);
if (this.max_health)
{
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.takedamage = DAMAGE_YES;
this.solid = SOLID_BBOX;
}
this.team_saved = this.team;
IL_PUSH(g_saved_team, this);
- if (this.health)
+ if (GetResourceAmount(this, RESOURCE_HEALTH))
{
if (this.spawnflags & SPAWNFLAG_NOTOUCH)
objerror (this, "health and notouch don't make sense\n");
this.canteamdamage = true;
- this.max_health = this.health;
+ this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
this.event_damage = multi_eventdamage;
this.takedamage = DAMAGE_YES;
this.solid = SOLID_BBOX;
this.targetname = "";
// you can't just shoot a room to find it, can you?
- this.health = 0;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
// a secret can not be delayed
this.delay = 0;
.float swamp_interval; //Hurt players in swamp with this interval
.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
+.float swamp_lifetime; // holds the points remaining until slug dies (not quite health!)
.entity swampslug;
#ifdef SVQC
void swampslug_think(entity this)
{
//Slowly kill the slug
- this.health = this.health - 1;
+ this.swamp_lifetime -= 1;
//Slug dead? then remove curses.
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
this.owner.in_swamp = 0;
delete(this);
// If not attach one.
//centerprint(toucher,"Entering swamp!\n");
toucher.swampslug = spawn();
- toucher.swampslug.health = 2;
+ toucher.swampslug.swamp_lifetime = 2;
setthink(toucher.swampslug, swampslug_think);
toucher.swampslug.nextthink = time;
toucher.swampslug.owner = toucher;
//toucher.in_swamp = 1;
//Revitalize players swampslug
- toucher.swampslug.health = 2;
+ toucher.swampslug.swamp_lifetime = 2;
}
REGISTER_NET_LINKED(ENT_CLIENT_SWAMP)
#pragma once
+#include "cl_minigames_hud.qh"
+
// Get a square in the center of the avaliable area
// \note macro to pass by reference pos and mySize
#define minigame_hud_fitsqare(pos, mySize) \
// (ie: it's their turn and they should get back to the minigame)
void minigame_prompt();
-float HUD_MinigameMenu_IsOpened();
-void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger);
-
-// Adds a game-specific entry to the menu
-void HUD_MinigameMenu_CustomEntry(entity parent, string message, string event_arg);
-
#define FOREACH_MINIGAME_ENTITY(entityvar) \
entityvar=NULL; \
float HUD_Minigame_InputEvent(float bInputType, float nPrimary, float nSecondary);
void HUD_Minigame_Mouse();
+
+float HUD_MinigameMenu_IsOpened();
+void HUD_MinigameMenu_Close(entity this, entity actor, entity trigger);
+
+// Adds a game-specific entry to the menu
+void HUD_MinigameMenu_CustomEntry(entity parent, string message, string event_arg);
{
if(targ == NULL)
return false;
- if(targ.health <= 0)
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0)
return false;
if(DIFF_TEAM(targ, this) && targ != this.monster_follow)
return false;
if(STAT(FROZEN, targ))
return false;
if(!IS_PLAYER(targ))
- return (IS_MONSTER(targ) && targ.health < targ.max_health);
+ return (IS_MONSTER(targ) && GetResourceAmount(targ, RESOURCE_HEALTH) < targ.max_health);
if(targ.items & ITEM_Shield.m_itemid)
return false;
switch(this.skin)
{
- case 0: return (targ.health < autocvar_g_balance_health_regenstable);
- case 1: return ((targ.ammo_cells && targ.ammo_cells < g_pickup_cells_max) || (targ.ammo_plasma && targ.ammo_plasma < g_pickup_plasma_max) || (targ.ammo_rockets && targ.ammo_rockets < g_pickup_rockets_max) || (targ.ammo_nails && targ.ammo_nails < g_pickup_nails_max) || (targ.ammo_shells && targ.ammo_shells < g_pickup_shells_max));
- case 2: return (targ.armorvalue < autocvar_g_balance_armor_regenstable);
- case 3: return (targ.health > 0);
+ case 0: return (GetResourceAmount(targ, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable);
+ case 1:
+ {
+ return ((GetResourceAmount(targ, RESOURCE_CELLS) && GetResourceAmount(targ, RESOURCE_CELLS) < g_pickup_cells_max)
+ || (GetResourceAmount(targ, RESOURCE_PLASMA) && GetResourceAmount(targ, RESOURCE_PLASMA) < g_pickup_plasma_max)
+ || (GetResourceAmount(targ, RESOURCE_ROCKETS) && GetResourceAmount(targ, RESOURCE_ROCKETS) < g_pickup_rockets_max)
+ || (GetResourceAmount(targ, RESOURCE_BULLETS) && GetResourceAmount(targ, RESOURCE_BULLETS) < g_pickup_nails_max)
+ || (GetResourceAmount(targ, RESOURCE_SHELLS) && GetResourceAmount(targ, RESOURCE_SHELLS) < g_pickup_shells_max)
+ );
+ }
+ case 2: return (GetResourceAmount(targ, RESOURCE_ARMOR) < autocvar_g_balance_armor_regenstable);
+ case 3: return (GetResourceAmount(targ, RESOURCE_HEALTH) > 0);
}
return false;
// copied from W_Seeker_Think
void M_Mage_Attack_Spike_Think(entity this)
{
- if (time > this.ltime || (this.enemy && this.enemy.health <= 0) || this.owner.health <= 0) {
+ if (time > this.ltime || (this.enemy && GetResourceAmount(this.enemy, RESOURCE_HEALTH) <= 0) || GetResourceAmount(this.owner, RESOURCE_HEALTH) <= 0) {
this.projectiledeathtype |= HITTYPE_SPLASH;
M_Mage_Attack_Spike_Explode(this, NULL);
}
switch(this.skin)
{
case 0:
- if(it.health < autocvar_g_balance_health_regenstable) it.health = bound(0, it.health + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_health_regenstable);
+ {
+ Heal(it, this, autocvar_g_monster_mage_heal_allies, autocvar_g_balance_health_regenstable);
fx = EFFECT_HEALING;
break;
+ }
case 1:
- if(it.ammo_cells) it.ammo_cells = bound(it.ammo_cells, it.ammo_cells + 1, g_pickup_cells_max);
- if(it.ammo_plasma) it.ammo_plasma = bound(it.ammo_plasma, it.ammo_plasma + 1, g_pickup_plasma_max);
- if(it.ammo_rockets) it.ammo_rockets = bound(it.ammo_rockets, it.ammo_rockets + 1, g_pickup_rockets_max);
- if(it.ammo_shells) it.ammo_shells = bound(it.ammo_shells, it.ammo_shells + 2, g_pickup_shells_max);
- if(it.ammo_nails) it.ammo_nails = bound(it.ammo_nails, it.ammo_nails + 5, g_pickup_nails_max);
+ {
+ if(GetResourceAmount(this, RESOURCE_CELLS)) GiveResourceWithLimit(it, RESOURCE_CELLS, 1, g_pickup_cells_max);
+ if(GetResourceAmount(this, RESOURCE_PLASMA)) GiveResourceWithLimit(it, RESOURCE_PLASMA, 1, g_pickup_plasma_max);
+ if(GetResourceAmount(this, RESOURCE_ROCKETS)) GiveResourceWithLimit(it, RESOURCE_ROCKETS, 1, g_pickup_rockets_max);
+ if(GetResourceAmount(this, RESOURCE_SHELLS)) GiveResourceWithLimit(it, RESOURCE_SHELLS, 2, g_pickup_shells_max);
+ if(GetResourceAmount(this, RESOURCE_BULLETS)) GiveResourceWithLimit(it, RESOURCE_BULLETS, 5, g_pickup_nails_max);
+ // TODO: fuel?
fx = EFFECT_AMMO_REGEN;
break;
+ }
case 2:
- if(it.armorvalue < autocvar_g_balance_armor_regenstable)
+ if(GetResourceAmount(it, RESOURCE_ARMOR) < autocvar_g_balance_armor_regenstable)
{
- it.armorvalue = bound(0, it.armorvalue + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_armor_regenstable);
+ GiveResourceWithLimit(it, RESOURCE_ARMOR, autocvar_g_monster_mage_heal_allies, autocvar_g_balance_armor_regenstable);
fx = EFFECT_ARMOR_REPAIR;
}
break;
case 3:
- it.health = bound(0, it.health - ((it == this) ? (autocvar_g_monster_mage_heal_self) : (autocvar_g_monster_mage_heal_allies)), autocvar_g_balance_health_regenstable);
+ float hp = ((it == this) ? autocvar_g_monster_mage_heal_self : autocvar_g_monster_mage_heal_allies);
+ TakeResource(it, RESOURCE_HEALTH, hp); // TODO: use regular damage functions? needs a way to bypass friendly fire checks
fx = EFFECT_RAGE;
break;
}
else
{
Send_Effect(EFFECT_HEALING, it.origin, '0 0 0', 1);
- it.health = bound(0, it.health + (autocvar_g_monster_mage_heal_allies), it.max_health);
+ Heal(it, this, autocvar_g_monster_mage_heal_allies, RESOURCE_LIMIT_NONE);
if(!(it.spawnflags & MONSTERFLAG_INVINCIBLE) && it.sprite)
- WaypointSprite_UpdateHealth(it.sprite, it.health);
+ WaypointSprite_UpdateHealth(it.sprite, GetResourceAmount(it, RESOURCE_HEALTH));
}
});
void M_Mage_Defend_Shield_Remove(entity this)
{
this.effects &= ~(EF_ADDITIVE | EF_BLUE);
- this.armorvalue = autocvar_g_monsters_armor_blockpercent;
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
}
void M_Mage_Defend_Shield(entity this)
{
this.effects |= (EF_ADDITIVE | EF_BLUE);
this.mage_shield_delay = time + (autocvar_g_monster_mage_shield_delay);
- this.armorvalue = (autocvar_g_monster_mage_shield_blockpercent);
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monster_mage_shield_blockpercent);
this.mage_shield_time = time + (autocvar_g_monster_mage_shield_time);
setanim(this, this.anim_shoot, true, true, true);
this.attack_finished_single[0] = time + 1;
});
}
- if(actor.health < (autocvar_g_monster_mage_heal_minhealth) || need_help)
+ if(GetResourceAmount(actor, RESOURCE_HEALTH) < (autocvar_g_monster_mage_heal_minhealth) || need_help)
if(time >= actor.attack_finished_single[0])
if(random() < 0.5)
M_Mage_Defend_Heal(actor);
- if(time >= actor.mage_shield_time && actor.armorvalue)
+ if(time >= actor.mage_shield_time && GetResourceAmount(actor, RESOURCE_ARMOR))
M_Mage_Defend_Shield_Remove(actor);
if(actor.enemy)
- if(actor.health < actor.max_health)
+ if(GetResourceAmount(actor, RESOURCE_HEALTH) < actor.max_health)
if(time >= actor.mage_shield_delay)
if(random() < 0.5)
M_Mage_Defend_Shield(actor);
METHOD(Mage, mr_setup, bool(Mage this, entity actor))
{
TC(Mage, this);
- if(!actor.health) actor.health = (autocvar_g_monster_mage_health);
+ if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_mage_health);
if(!actor.speed) { actor.speed = (autocvar_g_monster_mage_speed_walk); }
if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_mage_speed_run); }
if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_mage_speed_stop); }
void M_Shambler_Attack_Lightning_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, adaptor_think2use);
}
settouch(gren, M_Shambler_Attack_Lightning_Touch);
gren.takedamage = DAMAGE_YES;
- gren.health = 50;
+ SetResourceAmountExplicit(gren, RESOURCE_HEALTH, 50);
gren.damageforcescale = 0;
gren.event_damage = M_Shambler_Attack_Lightning_Damage;
gren.damagedbycontents = true;
METHOD(Shambler, mr_setup, bool(Shambler this, entity actor))
{
TC(Shambler, this);
- if(!actor.health) actor.health = (autocvar_g_monster_shambler_health);
+ if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_shambler_health);
if(!actor.attack_range) actor.attack_range = 150;
if(!actor.speed) { actor.speed = (autocvar_g_monster_shambler_speed_walk); }
if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_shambler_speed_run); }
Send_Effect(EFFECT_ELECTRO_IMPACT, this.origin, '0 0 0', 1);
RadiusDamage(this, this.realowner, 0, 0, 25, NULL, NULL, 25, this.projectiledeathtype, DMG_NOWEP, NULL);
- FOREACH_ENTITY_RADIUS(this.origin, 25, it != this && it.takedamage && !IS_DEAD(it) && it.health > 0 && it.monsterid != MON_SPIDER.monsterid,
+ FOREACH_ENTITY_RADIUS(this.origin, 25, it != this && it.takedamage && !IS_DEAD(it) && GetResourceAmount(it, RESOURCE_HEALTH) > 0 && it.monsterid != MON_SPIDER.monsterid,
{
it.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
});
setsize(proj, '-4 -4 -4', '4 4 4');
proj.takedamage = DAMAGE_NO;
proj.damageforcescale = 0;
- proj.health = 500;
+ SetResourceAmountExplicit(proj, RESOURCE_HEALTH, 500);
proj.event_damage = func_null;
proj.flags = FL_PROJECTILE;
IL_PUSH(g_projectiles, proj);
METHOD(Spider, mr_setup, bool(Spider this, entity actor))
{
TC(Spider, this);
- if(!actor.health) actor.health = (autocvar_g_monster_spider_health);
+ if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_spider_health);
if(!actor.speed) { actor.speed = (autocvar_g_monster_spider_speed_walk); }
if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_spider_speed_run); }
if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_spider_speed_stop); }
METHOD(Wyvern, mr_setup, bool(Wyvern this, entity actor))
{
TC(Wyvern, this);
- if(!actor.health) actor.health = (autocvar_g_monster_wyvern_health);
+ if(!GetResourceAmount(this, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_wyvern_health);
if(!actor.speed) { actor.speed = (autocvar_g_monster_wyvern_speed_walk); }
if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_wyvern_speed_run); }
if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_wyvern_speed_stop); }
void M_Zombie_Attack_Leap_Touch(entity this, entity toucher)
{
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
vector angles_face;
void M_Zombie_Defend_Block_End(entity this)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
setanim(this, this.anim_blockend, false, true, true);
- this.armorvalue = autocvar_g_monsters_armor_blockpercent;
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
}
bool M_Zombie_Defend_Block(entity this)
{
- this.armorvalue = 0.9;
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, 0.9);
this.state = MONSTER_ATTACK_MELEE; // freeze monster
this.attack_finished_single[0] = time + 2.1;
this.anim_finished = this.attack_finished_single[0];
{
case MONSTER_ATTACK_MELEE:
{
- if(random() < 0.3 && actor.health < 75 && actor.enemy.health > 10)
+ if(random() < 0.3 && GetResourceAmount(actor, RESOURCE_HEALTH) < 75 && GetResourceAmount(actor.enemy, RESOURCE_HEALTH) > 10)
return M_Zombie_Defend_Block(actor);
float anim_chance = random();
METHOD(Zombie, mr_death, bool(Zombie this, entity actor))
{
TC(Zombie, this);
- actor.armorvalue = autocvar_g_monsters_armor_blockpercent;
+ SetResourceAmountExplicit(actor, RESOURCE_ARMOR, autocvar_g_monsters_armor_blockpercent);
setanim(actor, ((random() > 0.5) ? actor.anim_die1 : actor.anim_die2), false, true, true);
return true;
METHOD(Zombie, mr_setup, bool(Zombie this, entity actor))
{
TC(Zombie, this);
- if(!actor.health) actor.health = (autocvar_g_monster_zombie_health);
+ if(!GetResourceAmount(actor, RESOURCE_HEALTH)) SetResourceAmountExplicit(actor, RESOURCE_HEALTH, autocvar_g_monster_zombie_health);
if(!actor.speed) { actor.speed = (autocvar_g_monster_zombie_speed_walk); }
if(!actor.speed2) { actor.speed2 = (autocvar_g_monster_zombie_speed_run); }
if(!actor.stopspeed) { actor.stopspeed = (autocvar_g_monster_zombie_speed_stop); }
#include "sv_monsters.qh"
-#include <server/g_subs.qh>
#include <lib/warpzone/common.qh>
#include "../constants.qh"
#include "../teams.qh"
|| (game_stopped)
|| (targ.items & IT_INVISIBILITY)
|| (IS_SPEC(targ) || IS_OBSERVER(targ)) // don't attack spectators
- || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || targ.health <= 0 || this.health <= 0))
+ || (!IS_VEHICLE(targ) && (IS_DEAD(targ) || IS_DEAD(this) || GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(this, RESOURCE_HEALTH) <= 0))
|| (this.monster_follow == targ || targ.monster_follow == this)
|| (!IS_VEHICLE(targ) && (targ.flags & FL_NOTARGET))
|| (!autocvar_g_monsters_typefrag && PHYS_INPUT_BUTTON_CHAT(targ))
return false; // already attacking
if(!IS_ONGROUND(this))
return false; // not on the ground
- if(this.health <= 0 || IS_DEAD(this))
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0 || IS_DEAD(this))
return false; // called when dead?
if(time < this.attack_finished_single[0])
return false; // still attacking
// g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
if ((this.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
{
- this.health += autocvar_g_monsters_miniboss_healthboost;
+ GiveResource(this, RESOURCE_HEALTH, autocvar_g_monsters_miniboss_healthboost);
this.effects |= EF_RED;
if(!this.weapon)
this.weapon = WEP_VORTEX.m_id;
this.pos2 = this.angles;
}
this.event_damage = func_null;
+ this.event_heal = func_null;
this.takedamage = DAMAGE_NO;
setorigin(this, this.pos1);
this.angles = this.pos2;
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
setmodel(this, MDL_Null);
}
else
// cases where the enemy may have changed their state (don't need to check everything here)
if((!this.enemy)
- || (IS_DEAD(this.enemy) || this.enemy.health < 1)
+ || (IS_DEAD(this.enemy) || GetResourceAmount(this.enemy, RESOURCE_HEALTH) < 1)
|| (STAT(FROZEN, this.enemy))
|| (this.enemy.flags & FL_NOTARGET)
|| (this.enemy.alpha < 0.5 && this.enemy.alpha != 0)
Unfreeze(this); // remove any icy remains
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.velocity = '0 0 0';
this.enemy = NULL;
this.goalentity = NULL;
void Monster_Dead_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- this.health -= damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
- if(this.health <= -50) // 100 health until gone?
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= -50) // 100 health until gone?
{
Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
if(STAT(FROZEN, this))
{
Unfreeze(this); // remove any icy remains
- this.health = 0; // reset by Unfreeze
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // reset by Unfreeze (TODO)
}
monster_dropitem(this, attacker);
_setmodel(this, this.mdl_dead);
this.event_damage = ((gibbed) ? func_null : Monster_Dead_Damage);
+ this.event_heal = func_null;
this.solid = SOLID_CORPSE;
this.takedamage = DAMAGE_AIM;
this.deadflag = DEAD_DEAD;
if(deathtype == DEATH_FALL.m_id && this.draggedby != NULL)
return;
- vector v = healtharmor_applydamage(100, this.armorvalue / 100, deathtype, damage);
+ vector v = healtharmor_applydamage(100, GetResourceAmount(this, RESOURCE_ARMOR) / 100, deathtype, damage);
float take = v.x;
//float save = v.y;
if(take)
{
- this.health -= take;
+ TakeResource(this, RESOURCE_HEALTH, take);
Monster_Sound(this, monstersound_pain, 1.2, true, CH_PAIN);
}
if(this.sprite)
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
this.dmg_time = time;
Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
}
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
if(deathtype == DEATH_KILL.m_id)
this.candrop = false; // killed by mobkill command
SUB_UseTargets(this, attacker, this.enemy);
this.target2 = this.oldtarget2; // reset to original target on death, incase we respawn
- Monster_Dead(this, attacker, (this.health <= -100 || deathtype == DEATH_KILL.m_id));
+ Monster_Dead(this, attacker, (GetResourceAmount(this, RESOURCE_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id));
WaypointSprite_Kill(this.sprite);
MUTATOR_CALLHOOK(MonsterDies, this, attacker, deathtype);
- if(this.health <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= -100 || deathtype == DEATH_KILL.m_id) // check if we're already gibbed
{
Violence_GibSplash(this, 1, 0.5, attacker);
}
}
+bool Monster_Heal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ if(targ.sprite)
+ WaypointSprite_UpdateHealth(targ.sprite, GetResourceAmount(targ, RESOURCE_HEALTH));
+ return true;
+}
+
// don't check for enemies, just keep walking in a straight line
void Monster_Move_2D(entity this, float mspeed, bool allow_jumpoff)
{
if(STAT(FROZEN, this) == 2)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + this.ticrate * this.revive_speed, 1);
- this.health = max(1, STAT(REVIVE_PROGRESS, this) * this.max_health);
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * this.max_health));
this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
if(STAT(REVIVE_PROGRESS, this) >= 1)
Unfreeze(this);
else if(STAT(FROZEN, this) == 3)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - this.ticrate * this.revive_speed, 1);
- this.health = max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (this.max_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE) && this.sprite)
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
- if(this.health < 1)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
{
Unfreeze(this);
- this.health = 0;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
if(this.event_damage)
this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
}
if(this.monster_lifetime && time >= this.monster_lifetime)
{
- Damage(this, this, this, this.health + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
+ Damage(this, this, this, GetResourceAmount(this, RESOURCE_HEALTH) + this.max_health, DEATH_KILL.m_id, DMG_NOWEP, this.origin, this.origin);
return;
}
mon.mr_setup(mon, this);
// ensure some basic needs are met
- if(!this.health) { this.health = 100; }
- if(!this.armorvalue) { this.armorvalue = bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9); }
+ if(!GetResourceAmount(this, RESOURCE_HEALTH)) { SetResourceAmountExplicit(this, RESOURCE_HEALTH, 100); }
+ if(!GetResourceAmount(this, RESOURCE_ARMOR)) { SetResourceAmountExplicit(this, RESOURCE_ARMOR, bound(0.2, 0.5 * MONSTER_SKILLMOD(this), 0.9)); }
if(!this.target_range) { this.target_range = autocvar_g_monsters_target_range; }
if(!this.respawntime) { this.respawntime = autocvar_g_monsters_respawn_delay; }
if(!this.monster_moveflags) { this.monster_moveflags = MONSTER_MOVE_WANDER; }
if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
{
Monster_Miniboss_Check(this);
- this.health *= MONSTER_SKILLMOD(this);
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH) * MONSTER_SKILLMOD(this));
if(!this.skin)
this.skin = rint(random() * 4);
}
- this.max_health = this.health;
+ this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
this.pain_finished = this.nextthink;
if(IS_PLAYER(this.monster_follow))
if(!(this.spawnflags & MONSTERFLAG_INVINCIBLE))
{
WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
- WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateHealth(this.sprite, GetResourceAmount(this, RESOURCE_HEALTH));
}
}
this.damagedbycontents = true;
this.monsterid = mon_id;
this.event_damage = Monster_Damage;
+ this.event_heal = Monster_Heal;
settouch(this, Monster_Touch);
this.use = Monster_Use;
this.solid = SOLID_BBOX;
#pragma once
+
+.string spawnmob;
#endif
string Buff_UndeprecateName(string buffname);
+entity buff_FirstFromFlags(int _buffs);
REGISTER_BUFF(Null);
BUFF_SPAWNFUNCS(random, BUFF_Null)
float amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal,
GetResourceAmount(frag_target, RESOURCE_HEALTH));
GiveResourceWithLimit(frag_attacker, RESOURCE_HEALTH, amount, g_pickup_healthsmall_max);
- if (frag_target.armorvalue)
+ if (GetResourceAmount(frag_target, RESOURCE_ARMOR))
{
amount = bound(0, frag_damage * autocvar_g_buffs_vampire_damage_steal,
GetResourceAmount(frag_target, RESOURCE_ARMOR));
#ifdef SVQC
int autocvar_g_instagib_ammo_drop;
-void ammo_vaporizercells_init(entity item)
+void ammo_vaporizercells_init(Pickup this, entity item)
{
- if(!item.ammo_cells)
- item.ammo_cells = autocvar_g_instagib_ammo_drop;
+ if(!GetResourceAmount(item, RESOURCE_CELLS))
+ SetResourceAmountExplicit(item, RESOURCE_CELLS, autocvar_g_instagib_ammo_drop);
}
#endif
REGISTER_ITEM(VaporizerCells, Ammo) {
this.m_canonical_spawnfunc = "item_vaporizer_cells";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
+ this.spawnflags = ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_VaporizerCells_ITEM;
this.m_sound = SND_VaporizerCells;
#endif
REGISTER_ITEM(ExtraLife, Powerup) {
this.m_canonical_spawnfunc = "item_extralife";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_INSTAGIB;
- this.m_model = MDL_ExtraLife_ITEM;
+ this.m_model = MDL_ExtraLife_ITEM;
this.m_sound = SND_ExtraLife;
#endif
this.netname = "extralife";
/// \brief Initializes the invisibility powerup.
/// \param[in,out] item Item to initialize.
/// \return No return.
-void powerup_invisibility_init(entity item);
+void powerup_invisibility_init(Pickup this, entity item);
#endif
REGISTER_ITEM(Invisibility, Powerup) {
this.m_canonical_spawnfunc = "item_invisibility";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
+ this.spawnflags = ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_Invisibility_ITEM;
this.m_sound = SND_Invisibility;
this.m_glow = true;
/// \brief Initializes the speed powerup.
/// \param[in,out] item Item to initialize.
/// \return No return.
-void powerup_speed_init(entity item);
+void powerup_speed_init(Pickup this, entity item);
#endif
REGISTER_ITEM(Speed, Powerup) {
this.m_canonical_spawnfunc = "item_speed";
#ifdef GAMEQC
- this.spawnflags = ITEM_FLAG_INSTAGIB | ITEM_FLAG_MUTATORBLOCKED;
+ this.spawnflags = ITEM_FLAG_MUTATORBLOCKED;
this.m_model = MDL_Speed_ITEM;
this.m_sound = SND_Speed;
this.m_glow = true;
#include "sv_instagib.qh"
+#include <server/client.qh>
+#include <common/items/_mod.qh>
+#include "../random_items/sv_random_items.qh"
+
bool autocvar_g_instagib_damagedbycontents = true;
bool autocvar_g_instagib_blaster_keepdamage = false;
bool autocvar_g_instagib_blaster_keepforce = false;
int autocvar_g_instagib_extralives;
float autocvar_g_instagib_speed_highspeed;
-#include <server/client.qh>
-
-#include <common/items/_mod.qh>
+IntrusiveList g_instagib_items;
+STATIC_INIT()
+{
+ g_instagib_items = IL_NEW();
+ IL_PUSH(g_instagib_items, ITEM_VaporizerCells);
+ IL_PUSH(g_instagib_items, ITEM_ExtraLife);
+ IL_PUSH(g_instagib_items, ITEM_Invisibility);
+ IL_PUSH(g_instagib_items, ITEM_Speed);
+}
void instagib_invisibility(entity this)
{
StartItem(this, ITEM_Speed);
}
+/// \brief Returns a random classname of the instagib item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the instagib item.
+string RandomItems_GetRandomInstagibItemClassName(string prefix)
+{
+ RandomSelection_Init();
+ IL_EACH(g_instagib_items, Item_IsDefinitionAllowed(it),
+ {
+ string cvar_name = sprintf("g_%s_%s_probability", prefix,
+ it.m_canonical_spawnfunc);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ continue;
+ }
+ RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+ });
+ return RandomSelection_chosen_string;
+}
+
.float instagib_nextthink;
.float instagib_needammo;
void instagib_stop_countdown(entity e)
FOREACH_CLIENT(IS_PLAYER(it), { instagib_stop_countdown(it); });
}
+MUTATOR_HOOKFUNCTION(mutator_instagib, RandomItems_GetRandomItemClassName)
+{
+ M_ARGV(1, string) = RandomItems_GetRandomInstagibItemClassName(
+ M_ARGV(0, string));
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(mutator_instagib, MonsterDropItem)
{
entity item = M_ARGV(1, entity);
/// \brief Time of speed powerup in seconds.
float autocvar_g_instagib_speed_time;
-void powerup_invisibility_init(entity item)
+void powerup_invisibility_init(Pickup this, entity item)
{
if(!item.strength_finished)
{
}
-void powerup_speed_init(entity item)
+void powerup_speed_init(Pickup this, entity item)
{
if(!item.invincible_finished)
{
{
entity proj = M_ARGV(1, entity);
- if(proj.health)
+ if(GetResourceAmount(proj, RESOURCE_HEALTH))
{
// disable health which in effect disables damage calculations
- proj.health = 0;
+ SetResourceAmountExplicit(proj, RESOURCE_HEALTH, 0);
}
}
+#include <common/effects/all.qh>
+
EFFECT(0, NADE_EXPLODE_RED, "nade_red_explode")
EFFECT(0, NADE_EXPLODE_BLUE, "nade_blue_explode")
EFFECT(0, NADE_EXPLODE_YELLOW, "nade_yellow_explode")
#include <common/gamemodes/_mod.qh>
#include <common/monsters/sv_spawn.qh>
#include <common/monsters/sv_monsters.qh>
-#include <server/g_subs.qh>
REGISTER_MUTATOR(nades, autocvar_g_nades);
settouch(_nade, nade_touch);
_nade.spawnshieldtime = time + 0.1; // prevent instantly picking up again
SetResourceAmount(_nade, RESOURCE_HEALTH, autocvar_g_nades_nade_health);
- _nade.max_health = _nade.health;
+ _nade.max_health = GetResourceAmount(_nade, RESOURCE_HEALTH);
_nade.takedamage = DAMAGE_AIM;
_nade.event_damage = nade_damage;
setcefc(_nade, func_null);
MUTATOR_HOOKABLE(Nade_Damage, EV_Nade_Damage);
#endif
+
+#ifdef CSQC
+bool Projectile_isnade(int proj); // TODO: remove
+
+void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time); // TODO: mutator
+#endif
e.draw = orb_draw;
IL_PUSH(g_drawables, e);
- e.health = 255;
+ SetResourceAmountExplicit(e, RESOURCE_HEALTH, 255);
set_movetype(e, MOVETYPE_NONE);
e.solid = SOLID_NOT;
e.drawmask = MASK_NORMAL;
.string new_toys;
float autocvar_g_new_toys_autoreplace;
-bool autocvar_g_new_toys_use_pickupsound = true;
+bool autocvar_g_new_toys_use_pickupsound = false;
const float NT_AUTOREPLACE_NEVER = 0;
const float NT_AUTOREPLACE_ALWAYS = 1;
const float NT_AUTOREPLACE_RANDOM = 2;
#include <common/mutators/mutator/overkill/oknex.qc>
#include <common/mutators/mutator/overkill/okrpc.qc>
#include <common/mutators/mutator/overkill/okshotgun.qc>
+#ifdef SVQC
+ #include <common/mutators/mutator/overkill/sv_weapons.qc>
+#endif
#endif
/* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair, string, "gfx/crosshairuzi");
/* crosshair */ ATTRIB(OverkillMachineGun, w_crosshair_size, float, 0.6);
-/* wepimg */ ATTRIB(OverkillMachineGun, model2, string, "weaponuzi");
+/* wepimg */ ATTRIB(OverkillMachineGun, model2, string, "ok_weapon_smg");
/* refname */ ATTRIB(OverkillMachineGun, netname, string, "okmachinegun");
/* wepname */ ATTRIB(OverkillMachineGun, m_name, string, _("Overkill MachineGun"));
/* crosshair */ ATTRIB(OverkillNex, w_crosshair, string, "gfx/crosshairnex");
/* crosshair */ ATTRIB(OverkillNex, w_crosshair_size, float, 0.65);
/* reticle */ ATTRIB(OverkillNex, w_reticle, string, "gfx/reticle_nex");
-/* wepimg */ ATTRIB(OverkillNex, model2, string, "weaponnex");
+/* wepimg */ ATTRIB(OverkillNex, model2, string, "ok_weapon_rail");
/* refname */ ATTRIB(OverkillNex, netname, string, "oknex");
/* wepname */ ATTRIB(OverkillNex, m_name, string, _("Overkill Nex"));
void W_OverkillRocketPropelledChainsaw_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, W_OverkillRocketPropelledChainsaw_Explode_think);
}
missile.takedamage = DAMAGE_YES;
missile.damageforcescale = WEP_CVAR_PRI(okrpc, damageforcescale);
- missile.health = WEP_CVAR_PRI(okrpc, health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_PRI(okrpc, health));
missile.event_damage = W_OverkillRocketPropelledChainsaw_Damage;
missile.damagedbycontents = true;
IL_PUSH(g_damagedbycontents, missile);
#endif
/* crosshair */ ATTRIB(OverkillShotgun, w_crosshair, string, "gfx/crosshairshotgun");
/* crosshair */ ATTRIB(OverkillShotgun, w_crosshair_size, float, 0.65);
-/* wepimg */ ATTRIB(OverkillShotgun, model2, string, "weaponshotgun");
+/* wepimg */ ATTRIB(OverkillShotgun, model2, string, "ok_weapon_shotgun");
/* refname */ ATTRIB(OverkillShotgun, netname, string, "okshotgun");
/* wepname */ ATTRIB(OverkillShotgun, m_name, string, _("Overkill Shotgun"));
#include "sv_overkill.qh"
+#include "okshotgun.qh"
+#include "okhmg.qh"
+#include "okrpc.qh"
+
bool autocvar_g_overkill_powerups_replace;
bool autocvar_g_overkill_itemwaypoints = true;
.Weapon ok_lastwep[MAX_WEAPONSLOTS];
+IntrusiveList g_overkill_items;
+STATIC_INIT()
+{
+ g_overkill_items = IL_NEW();
+ IL_PUSH(g_overkill_items, ITEM_HealthMega);
+ IL_PUSH(g_overkill_items, ITEM_ArmorSmall);
+ IL_PUSH(g_overkill_items, ITEM_ArmorMedium);
+ IL_PUSH(g_overkill_items, ITEM_ArmorBig);
+ IL_PUSH(g_overkill_items, ITEM_ArmorMega);
+}
+
+/// \brief Returns a random classname of the overkill item.
+/// \param[in] prefix Prefix of the cvars that hold probabilities.
+/// \return Random classname of the overkill item.
+string RandomItems_GetRandomOverkillItemClassName(string prefix)
+{
+ RandomSelection_Init();
+ IL_EACH(g_overkill_items, !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) &&
+ Item_IsDefinitionAllowed(it),
+ {
+ string cvar_name = sprintf("g_%s_%s_probability", prefix,
+ it.m_canonical_spawnfunc);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ continue;
+ }
+ RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
+ });
+ string cvar_name = sprintf("g_%s_weapon_okhmg_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1);
+ }
+ cvar_name = sprintf("g_%s_weapon_okrpc_probability", prefix);
+ if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
+ {
+ LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
+ }
+ else
+ {
+ RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1);
+ }
+ return RandomSelection_chosen_string;
+}
+
+
+MUTATOR_HOOKFUNCTION(ok, RandomItems_GetRandomItemClassName)
+{
+ M_ARGV(1, string) = RandomItems_GetRandomOverkillItemClassName(
+ M_ARGV(0, string));
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(ok, Damage_Calculate, CBC_ORDER_LAST)
{
entity frag_attacker = M_ARGV(1, entity);
#pragma once
-#include "okshotgun.qh"
-#include "okmachinegun.qh"
-#include "okhmg.qh"
-#include "okrpc.qh"
-
string autocvar_g_overkill;
bool autocvar_g_overkill_filter_healthmega;
bool autocvar_g_overkill_filter_armormedium;
{
ITEM_ArmorMega.spawnflags |= ITEM_FLAG_MUTATORBLOCKED;
}
-
- WEP_OVERKILL_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
- WEP_OVERKILL_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
-
- WEP_OVERKILL_SHOTGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
- WEP_OVERKILL_MACHINEGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
- WEP_OVERKILL_NEX.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
}
MUTATOR_ONREMOVE
ITEM_ArmorMedium.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
ITEM_ArmorBig.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
ITEM_ArmorMega.spawnflags &= ~ITEM_FLAG_MUTATORBLOCKED;
-
- WEP_OVERKILL_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
- WEP_OVERKILL_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
-
- WEP_OVERKILL_SHOTGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
- WEP_OVERKILL_MACHINEGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
- WEP_OVERKILL_NEX.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
}
}
--- /dev/null
+string autocvar_g_overkill_weapons;
+
+REGISTER_MUTATOR(ok_weapons, expr_evaluate(autocvar_g_overkill_weapons) || MUTATOR_IS_ENABLED(ok))
+{
+ MUTATOR_ONADD
+ {
+ WEP_OVERKILL_SHOTGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_MACHINEGUN.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_NEX.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_HMG.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_RPC.spawnflags &= ~WEP_FLAG_MUTATORBLOCKED;
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ WEP_OVERKILL_SHOTGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_MACHINEGUN.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_NEX.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_HMG.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+ WEP_OVERKILL_RPC.spawnflags |= WEP_FLAG_MUTATORBLOCKED;
+ }
+}
string RandomItems_GetRandomItemClassName(string prefix)
{
- if (MUTATOR_IS_ENABLED(mutator_instagib))
+ if (MUTATOR_CALLHOOK(RandomItems_GetRandomItemClassName, prefix))
{
- return RandomItems_GetRandomInstagibItemClassName(prefix);
- }
- if (MUTATOR_IS_ENABLED(ok))
- {
- return RandomItems_GetRandomOverkillItemClassName(prefix);
+ return M_ARGV(1, string);
}
return RandomItems_GetRandomVanillaItemClassName(prefix,
RANDOM_ITEM_TYPE_ALL);
return "";
}
-string RandomItems_GetRandomInstagibItemClassName(string prefix)
-{
- RandomSelection_Init();
- FOREACH(Items, it.spawnflags & ITEM_FLAG_INSTAGIB &&
- Item_IsDefinitionAllowed(it),
- {
- string cvar_name = sprintf("g_%s_%s_probability", prefix,
- it.m_canonical_spawnfunc);
- if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
- {
- LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
- continue;
- }
- RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
- });
- return RandomSelection_chosen_string;
-}
-
-string RandomItems_GetRandomOverkillItemClassName(string prefix)
-{
- RandomSelection_Init();
- FOREACH(Items, (it.spawnflags & ITEM_FLAG_OVERKILL) &&
- !(it.spawnflags & ITEM_FLAG_MUTATORBLOCKED) &&
- Item_IsDefinitionAllowed(it),
- {
- string cvar_name = sprintf("g_%s_overkill_%s_probability", prefix,
- it.m_canonical_spawnfunc);
- if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
- {
- LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
- continue;
- }
- RandomSelection_AddString(it.m_canonical_spawnfunc, cvar(cvar_name), 1);
- });
- string cvar_name = sprintf("g_%s_overkill_weapon_okhmg_probability", prefix);
- if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
- {
- LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
- }
- else
- {
- RandomSelection_AddString("weapon_okhmg", cvar(cvar_name), 1);
- }
- cvar_name = sprintf("g_%s_overkill_weapon_okrpc_probability", prefix);
- if (!(cvar_type(cvar_name) & CVAR_TYPEFLAG_EXISTS))
- {
- LOG_WARNF("Random items: cvar %s doesn't exist.", cvar_name);
- }
- else
- {
- RandomSelection_AddString("weapon_okrpc", cvar(cvar_name), 1);
- }
- return RandomSelection_chosen_string;
-}
-
//========================= Free functions ====================================
/// \brief Returns list of classnames to replace a map item with.
/// jetpack and new toys.
string RandomItems_GetRandomVanillaItemClassName(string prefix, int types);
-/// \brief Returns a random classname of the instagib item.
-/// \param[in] prefix Prefix of the cvars that hold probabilities.
-/// \return Random classname of the instagib item.
-string RandomItems_GetRandomInstagibItemClassName(string prefix);
-
-/// \brief Returns a random classname of the overkill item.
-/// \param[in] prefix Prefix of the cvars that hold probabilities.
-/// \return Random classname of the overkill item.
-string RandomItems_GetRandomOverkillItemClassName(string prefix);
+/// \brief Called when random item classname is requested.
+#define EV_RandomItems_GetRandomItemClassName(i, o) \
+ /** prefix */ i(string, MUTATOR_ARGV_0_string) \
+ /** classname */ o(string, MUTATOR_ARGV_1_string) \
+ /**/
+MUTATOR_HOOKABLE(RandomItems_GetRandomItemClassName,
+ EV_RandomItems_GetRandomItemClassName);
REGISTER_MUTATOR(random_items, (autocvar_g_random_items ||
autocvar_g_random_loot));
if (PHYS_INPUT_BUTTON_CHAT(it)) continue;
if (!SAME_TEAM(player, it)) continue;
- if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && it.health < autocvar_g_balance_health_regenstable) continue;
+ if (autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health && GetResourceAmount(it, RESOURCE_HEALTH) < autocvar_g_balance_health_regenstable) continue;
if (IS_DEAD(it)) continue;
if (time < it.msnt_timer) continue;
if (time < it.spawnshieldtime) continue;
if(!IS_DEAD(frag_target))
{
GiveResource(frag_attacker, RESOURCE_HEALTH,
- bound(0, damage_take, frag_target.health));
+ bound(0, damage_take, GetResourceAmount(frag_target, RESOURCE_HEALTH)));
}
}
if(!STAT(FROZEN, thehook.aiment))
if(time >= game_starttime)
if(DIFF_TEAM(thehook.owner, thehook.aiment) || autocvar_g_vampirehook_teamheal)
- if(thehook.aiment.health > 0)
+ if(GetResourceAmount(thehook.aiment, RESOURCE_HEALTH) > 0)
if(autocvar_g_vampirehook_damage)
{
thehook.last_dmg = time + autocvar_g_vampirehook_damagerate;
thehook.owner.damage_dealt += autocvar_g_vampirehook_damage;
Damage(dmgent, thehook, thehook.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, DMG_NOWEP, thehook.origin, '0 0 0');
- if(SAME_TEAM(thehook.owner, thehook.aiment))
- thehook.aiment.health = min(thehook.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
- else
- thehook.owner.health = min(thehook.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
+ entity targ = ((SAME_TEAM(thehook.owner, thehook.aiment)) ? thehook.aiment : thehook.owner);
+ Heal(targ, thehook.owner, autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max);
if(dmgent == thehook.owner)
- dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?!
+ TakeResource(dmgent, RESOURCE_HEALTH, autocvar_g_vampirehook_damage); // FIXME: friendly fire?!
}
}
{
if (this.max_health)
{
- WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
+ WriteByte(MSG_ENTITY, (GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 191.0);
}
else
{
int t = ReadByte();
if (t < 192)
{
- this.health = t / 191.0;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, t / 191.0);
this.build_finished = 0;
}
else
t = (t - 192) * 256 + ReadByte();
this.build_started = servertime;
if (this.build_finished)
- this.build_starthealth = bound(0, this.health, 1);
+ this.build_starthealth = bound(0, GetResourceAmount(this, RESOURCE_HEALTH), 1);
else
this.build_starthealth = 0;
this.build_finished = servertime + t / 32;
}
else
{
- this.health = -1;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
this.build_finished = 0;
}
if (time < this.build_finished + 0.25)
{
if (time < this.build_started)
- this.health = this.build_starthealth;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.build_starthealth);
else if (time < this.build_finished)
- this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth);
else
- this.health = 1;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 1);
}
else
- this.health = -1;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
}
o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
}
draw_beginBoldFont();
- if (this.health >= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) >= 0)
{
float align = 0, marg;
if (this.build_finished)
drawhealthbar(
o,
0,
- this.health,
+ GetResourceAmount(this, RESOURCE_HEALTH),
'0 0 0',
'0 0 0',
SPRITE_HEALTHBAR_WIDTH * t,
void WaypointSprite_UpdateHealth(entity e, float f)
{
f = bound(0, f, e.max_health);
- if (f != e.health || e.pain_finished)
+ if (f != GetResourceAmount(e, RESOURCE_HEALTH) || e.pain_finished)
{
- e.health = f;
+ SetResourceAmountExplicit(e, RESOURCE_HEALTH, f);
e.pain_finished = 0;
e.SendFlags |= 0x80;
}
{
WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
- if (carrier.health)
+ if (GetResourceAmount(carrier, RESOURCE_HEALTH))
{
WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
- WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+ WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(carrier, RESOURCE_HEALTH), GetResourceAmount(carrier, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
}
return e;
}
#ifdef CSQC
void cl_notice_read();
+
+void cl_notice_run();
#endif
MSG_CENTER_NOTIF(ITEM_BUFF_DROP, N_ENABLE, 0, 1, "item_buffname", CPID_ITEM, "item_centime 0", _("^BGYou dropped the %s^BG buff!"), "")
MSG_CENTER_NOTIF(ITEM_BUFF_GOT, N_ENABLE, 0, 1, "item_buffname", CPID_ITEM, "item_centime 0", _("^BGYou got the %s^BG buff!"), "")
MSG_CENTER_NOTIF(ITEM_FUELREGEN_GOT, N_ENABLE, 0, 0, "", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Fuel regenerator"), "")
- MSG_CENTER_NOTIF(ITEM_JETPACK_GOT, N_ENABLE, 0, 0, "", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Jet pack"), "")
+ MSG_CENTER_NOTIF(ITEM_JETPACK_GOT, N_ENABLE, 0, 0, "", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1Jetpack"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_DONTHAVE, N_ENABLE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou do not have the ^F1%s"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_DROP, N_ENABLE, 1, 1, "item_wepname item_wepammo", CPID_ITEM, "item_centime 0", _("^BGYou dropped the ^F1%s^BG%s"), "")
MSG_CENTER_NOTIF(ITEM_WEAPON_GOT, N_ENABLE, 0, 1, "item_wepname", CPID_ITEM, "item_centime 0", _("^BGYou got the ^F1%s"), "")
#include <common/constants.qh>
#include <common/teams.qh>
#include <common/util.qh>
+#include <common/sounds/sound.qh>
#ifdef CSQC
#include <client/autocvars.qh>
#ifdef SVQC
if (!(ITEMS_STAT(this) & IT_UNLIMITED_WEAPON_AMMO))
- this.ammo_fuel -= PHYS_JETPACK_FUEL(this) * dt * fvel * f;
+ TakeResource(this, RESOURCE_FUEL, PHYS_JETPACK_FUEL(this) * dt * fvel * f);
ITEMS_STAT(this) |= IT_USING_JETPACK;
/// \author Lyberta
/// \copyright GNU GPLv2 or any later version.
+/// \brief Unconditional maximum amount of resources the entity can have.
+const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
+const int RESOURCE_LIMIT_NONE = -1;
+
/// \brief Describes the available resource types.
enum
{
{
pickedup = true;
player.items |= its;
- Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname);
+ // TODO: we probably want to show a message in the console, but not this one!
+ //Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_ITEM_WEAPON_GOT, item.netname);
}
if (item.strength_finished)
if(item.itemdef.instanceOfWeaponPickup)
{
entity ammo = NULL;
- if(item.ammo_shells) { need_shells = true; ammo = ITEM_Shells; }
- else if(item.ammo_nails) { need_nails = true; ammo = ITEM_Bullets; }
- else if(item.ammo_rockets) { need_rockets = true; ammo = ITEM_Rockets; }
- else if(item.ammo_cells) { need_cells = true; ammo = ITEM_Cells; }
- else if(item.ammo_plasma) { need_plasma = true; ammo = ITEM_Plasma; }
- else if(item.ammo_fuel) { need_fuel = true; ammo = ITEM_JetpackFuel; }
+ if(GetResourceAmount(item, RESOURCE_SHELLS)) { need_shells = true; ammo = ITEM_Shells; }
+ else if(GetResourceAmount(item, RESOURCE_BULLETS)) { need_nails = true; ammo = ITEM_Bullets; }
+ else if(GetResourceAmount(item, RESOURCE_ROCKETS)) { need_rockets = true; ammo = ITEM_Rockets; }
+ else if(GetResourceAmount(item, RESOURCE_CELLS)) { need_cells = true; ammo = ITEM_Cells; }
+ else if(GetResourceAmount(item, RESOURCE_PLASMA)) { need_plasma = true; ammo = ITEM_Plasma; }
+ else if(GetResourceAmount(item, RESOURCE_FUEL)) { need_fuel = true; ammo = ITEM_JetpackFuel; }
if(!ammo)
return 0;
float noammorating = 0.5;
- if ((need_shells) && (item.ammo_shells) && (player.ammo_shells < g_pickup_shells_max))
- c = item.ammo_shells / max(noammorating, player.ammo_shells);
+ if ((need_shells) && GetResourceAmount(item, RESOURCE_SHELLS) && (GetResourceAmount(player, RESOURCE_SHELLS) < g_pickup_shells_max))
+ c = GetResourceAmount(item, RESOURCE_SHELLS) / max(noammorating, GetResourceAmount(player, RESOURCE_SHELLS));
- if ((need_nails) && (item.ammo_nails) && (player.ammo_nails < g_pickup_nails_max))
- c = item.ammo_nails / max(noammorating, player.ammo_nails);
+ if ((need_nails) && GetResourceAmount(item, RESOURCE_BULLETS) && (GetResourceAmount(player, RESOURCE_BULLETS) < g_pickup_nails_max))
+ c = GetResourceAmount(item, RESOURCE_BULLETS) / max(noammorating, GetResourceAmount(player, RESOURCE_BULLETS));
- if ((need_rockets) && (item.ammo_rockets) && (player.ammo_rockets < g_pickup_rockets_max))
- c = item.ammo_rockets / max(noammorating, player.ammo_rockets);
+ if ((need_rockets) && GetResourceAmount(item, RESOURCE_ROCKETS) && (GetResourceAmount(player, RESOURCE_ROCKETS) < g_pickup_rockets_max))
+ c = GetResourceAmount(item, RESOURCE_ROCKETS) / max(noammorating, GetResourceAmount(player, RESOURCE_ROCKETS));
- if ((need_cells) && (item.ammo_cells) && (player.ammo_cells < g_pickup_cells_max))
- c = item.ammo_cells / max(noammorating, player.ammo_cells);
+ if ((need_cells) && GetResourceAmount(item, RESOURCE_CELLS) && (GetResourceAmount(player, RESOURCE_CELLS) < g_pickup_cells_max))
+ c = GetResourceAmount(item, RESOURCE_CELLS) / max(noammorating, GetResourceAmount(player, RESOURCE_CELLS));
- if ((need_plasma) && (item.ammo_plasma) && (player.ammo_plasma < g_pickup_plasma_max))
- c = item.ammo_plasma / max(noammorating, player.ammo_plasma);
+ if ((need_plasma) && GetResourceAmount(item, RESOURCE_PLASMA) && (GetResourceAmount(player, RESOURCE_PLASMA) < g_pickup_plasma_max))
+ c = GetResourceAmount(item, RESOURCE_PLASMA) / max(noammorating, GetResourceAmount(player, RESOURCE_PLASMA));
- if ((need_fuel) && (item.ammo_fuel) && (player.ammo_fuel < g_pickup_fuel_max))
- c = item.ammo_fuel / max(noammorating, player.ammo_fuel);
+ if ((need_fuel) && GetResourceAmount(item, RESOURCE_FUEL) && (GetResourceAmount(player, RESOURCE_FUEL) < g_pickup_fuel_max))
+ c = GetResourceAmount(item, RESOURCE_FUEL) / max(noammorating, GetResourceAmount(player, RESOURCE_FUEL));
rating *= min(c, 2);
if(wpn)
float c = 0;
float rating = item.bot_pickupbasevalue;
- float itemarmor = item.armorvalue;
- float itemhealth = item.health;
+ float itemarmor = GetResourceAmount(item, RESOURCE_ARMOR);
+ float itemhealth = GetResourceAmount(item, RESOURCE_HEALTH);
if(item.item_group)
{
itemhealth *= min(4, item.item_group_count);
}
- if (itemarmor && (player.armorvalue < item.max_armorvalue))
- c = itemarmor / max(1, player.armorvalue * 2/3 + player.health * 1/3);
+ if (itemarmor && (GetResourceAmount(player, RESOURCE_ARMOR) < item.max_armorvalue))
+ c = itemarmor / max(1, GetResourceAmount(player, RESOURCE_ARMOR) * 2/3 + GetResourceAmount(player, RESOURCE_HEALTH) * 1/3);
- if (itemhealth && (player.health < item.max_health))
- c = itemhealth / max(1, player.health);
+ if (itemhealth && (GetResourceAmount(player, RESOURCE_HEALTH) < item.max_health))
+ c = itemhealth / max(1, GetResourceAmount(player, RESOURCE_HEALTH));
rating *= min(2, c);
return rating;
this.item_pickupsound_ent = pickupsound;
if(def.m_iteminit)
- def.m_iteminit(this);
+ def.m_iteminit(def, this);
if(!this.respawntime) // both need to be set
{
if(def.instanceOfPowerup)
this.ItemStatus |= ITS_ANIMATE1;
- if(this.armorvalue || this.health)
+ if(GetResourceAmount(this, RESOURCE_ARMOR) || GetResourceAmount(this, RESOURCE_HEALTH))
this.ItemStatus |= ITS_ANIMATE2;
}
this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, this.superweapons_finished * boolean(this.items & IT_SUPERWEAPON), "superweapons");
this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, boolean(this.items & ITEM_Jetpack.m_itemid), "jetpack");
this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, boolean(this.items & ITEM_JetpackRegen.m_itemid), "fuel_regen");
- if(this.ammo_shells != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_shells), "shells");
- if(this.ammo_nails != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_nails), "nails");
- if(this.ammo_rockets != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_rockets), "rockets");
- if(this.ammo_cells != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_cells), "cells");
- if(this.ammo_plasma != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_plasma), "plasma");
- if(this.ammo_fuel != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.ammo_fuel), "fuel");
- if(this.health != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.health), "health");
- if(this.armorvalue != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, this.armorvalue), "armor");
+ if(GetResourceAmount(this, RESOURCE_SHELLS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_SHELLS)), "shells");
+ if(GetResourceAmount(this, RESOURCE_BULLETS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_BULLETS)), "nails");
+ if(GetResourceAmount(this, RESOURCE_ROCKETS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_ROCKETS)), "rockets");
+ if(GetResourceAmount(this, RESOURCE_CELLS) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_CELLS)), "cells");
+ if(GetResourceAmount(this, RESOURCE_PLASMA) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_PLASMA)), "plasma");
+ if(GetResourceAmount(this, RESOURCE_FUEL) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_FUEL)), "fuel");
+ if(GetResourceAmount(this, RESOURCE_HEALTH) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_HEALTH)), "health");
+ if(GetResourceAmount(this, RESOURCE_ARMOR) != 0) this.netname = sprintf("%s %s%d %s", this.netname, valueprefix, max(0, GetResourceAmount(this, RESOURCE_ARMOR)), "armor");
FOREACH(Buffs, it != BUFF_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(BUFFS, this) & (it.m_itemid)), it.m_name));
FOREACH(Weapons, it != WEP_Null, this.netname = sprintf("%s %s%d %s", this.netname, itemprefix, !!(STAT(WEAPONS, this) & (it.m_wepset)), it.netname));
}
else if(v0 > v1)
e.(regenfield) = max(e.(regenfield), time + regentime);
}
+bool GiveResourceValue(entity e, int resource_type, int op, int val)
+{
+ int v0 = GetResourceAmount(e, resource_type);
+ switch (op)
+ {
+ case OP_SET:
+ SetResourceAmount(e, resource_type, val);
+ break;
+ case OP_MIN:
+ SetResourceAmount(e, resource_type, max(v0, val)); // min 100 cells = at least 100 cells
+ break;
+ case OP_MAX:
+ SetResourceAmount(e, resource_type, min(v0, val));
+ break;
+ case OP_PLUS:
+ SetResourceAmount(e, resource_type, v0 + val);
+ break;
+ case OP_MINUS:
+ SetResourceAmount(e, resource_type, v0 - val);
+ break;
+ }
+ int v1 = GetResourceAmount(e, resource_type);
+ return v0 != v1;
+}
+
float GiveItems(entity e, float beginarg, float endarg)
{
float got, i, val, op;
PREGIVE(e, strength_finished);
PREGIVE(e, invincible_finished);
PREGIVE(e, superweapons_finished);
- PREGIVE(e, ammo_nails);
- PREGIVE(e, ammo_cells);
- PREGIVE(e, ammo_plasma);
- PREGIVE(e, ammo_shells);
- PREGIVE(e, ammo_rockets);
- PREGIVE(e, ammo_fuel);
- PREGIVE(e, armorvalue);
- PREGIVE(e, health);
+ PREGIVE_RESOURCE(e, RESOURCE_BULLETS);
+ PREGIVE_RESOURCE(e, RESOURCE_CELLS);
+ PREGIVE_RESOURCE(e, RESOURCE_PLASMA);
+ PREGIVE_RESOURCE(e, RESOURCE_SHELLS);
+ PREGIVE_RESOURCE(e, RESOURCE_ROCKETS);
+ PREGIVE_RESOURCE(e, RESOURCE_FUEL);
+ PREGIVE_RESOURCE(e, RESOURCE_ARMOR);
+ PREGIVE_RESOURCE(e, RESOURCE_HEALTH);
for(i = beginarg; i < endarg; ++i)
{
got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
case "all":
got += GiveBit(e, items, ITEM_Jetpack.m_itemid, op, val);
- got += GiveValue(e, health, op, val);
- got += GiveValue(e, armorvalue, op, val);
+ got += GiveResourceValue(e, RESOURCE_HEALTH, op, val);
+ got += GiveResourceValue(e, RESOURCE_ARMOR, op, val);
case "allweapons":
FOREACH(Weapons, it != WEP_Null && !(it.spawnflags & WEP_FLAG_MUTATORBLOCKED), got += GiveWeapon(e, it.m_id, op, val));
//case "allbuffs": // all buffs makes a player god, do not want!
//FOREACH(Buffs, it != BUFF_Null, got += GiveBuff(e, it.m_itemid, op, val));
case "allammo":
- got += GiveValue(e, ammo_cells, op, val);
- got += GiveValue(e, ammo_plasma, op, val);
- got += GiveValue(e, ammo_shells, op, val);
- got += GiveValue(e, ammo_nails, op, val);
- got += GiveValue(e, ammo_rockets, op, val);
- got += GiveValue(e, ammo_fuel, op, val);
+ got += GiveResourceValue(e, RESOURCE_CELLS, op, val);
+ got += GiveResourceValue(e, RESOURCE_PLASMA, op, val);
+ got += GiveResourceValue(e, RESOURCE_SHELLS, op, val);
+ got += GiveResourceValue(e, RESOURCE_BULLETS, op, val);
+ got += GiveResourceValue(e, RESOURCE_ROCKETS, op, val);
+ got += GiveResourceValue(e, RESOURCE_FUEL, op, val);
break;
case "unlimited_ammo":
got += GiveBit(e, items, IT_UNLIMITED_AMMO, op, val);
got += GiveValue(e, superweapons_finished, op, val);
break;
case "cells":
- got += GiveValue(e, ammo_cells, op, val);
+ got += GiveResourceValue(e, RESOURCE_CELLS, op, val);
break;
case "plasma":
- got += GiveValue(e, ammo_plasma, op, val);
+ got += GiveResourceValue(e, RESOURCE_PLASMA, op, val);
break;
case "shells":
- got += GiveValue(e, ammo_shells, op, val);
+ got += GiveResourceValue(e, RESOURCE_SHELLS, op, val);
break;
case "nails":
case "bullets":
- got += GiveValue(e, ammo_nails, op, val);
+ got += GiveResourceValue(e, RESOURCE_BULLETS, op, val);
break;
case "rockets":
- got += GiveValue(e, ammo_rockets, op, val);
+ got += GiveResourceValue(e, RESOURCE_ROCKETS, op, val);
break;
case "health":
- got += GiveValue(e, health, op, val);
+ got += GiveResourceValue(e, RESOURCE_HEALTH, op, val);
break;
case "armor":
- got += GiveValue(e, armorvalue, op, val);
+ got += GiveResourceValue(e, RESOURCE_ARMOR, op, val);
break;
case "fuel":
- got += GiveValue(e, ammo_fuel, op, val);
+ got += GiveResourceValue(e, RESOURCE_FUEL, op, val);
break;
default:
FOREACH(Buffs, it != BUFF_Null && Buff_UndeprecateName(cmd) == it.m_name,
POSTGIVE_VALUE(e, strength_finished, 1, SND_POWERUP, SND_POWEROFF);
POSTGIVE_VALUE(e, invincible_finished, 1, SND_Shield, SND_POWEROFF);
//POSTGIVE_VALUE(e, superweapons_finished, 1, SND_Null, SND_Null);
- POSTGIVE_VALUE(e, ammo_nails, 0, SND_ITEMPICKUP, SND_Null);
- POSTGIVE_VALUE(e, ammo_cells, 0, SND_ITEMPICKUP, SND_Null);
- POSTGIVE_VALUE(e, ammo_plasma, 0, SND_ITEMPICKUP, SND_Null);
- POSTGIVE_VALUE(e, ammo_shells, 0, SND_ITEMPICKUP, SND_Null);
- POSTGIVE_VALUE(e, ammo_rockets, 0, SND_ITEMPICKUP, SND_Null);
- POSTGIVE_VALUE_ROT(e, ammo_fuel, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
- POSTGIVE_VALUE_ROT(e, armorvalue, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
- POSTGIVE_VALUE_ROT(e, health, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
+ POSTGIVE_RESOURCE(e, RESOURCE_BULLETS, 0, SND_ITEMPICKUP, SND_Null);
+ POSTGIVE_RESOURCE(e, RESOURCE_CELLS, 0, SND_ITEMPICKUP, SND_Null);
+ POSTGIVE_RESOURCE(e, RESOURCE_PLASMA, 0, SND_ITEMPICKUP, SND_Null);
+ POSTGIVE_RESOURCE(e, RESOURCE_SHELLS, 0, SND_ITEMPICKUP, SND_Null);
+ POSTGIVE_RESOURCE(e, RESOURCE_ROCKETS, 0, SND_ITEMPICKUP, SND_Null);
+ POSTGIVE_RESOURCE_ROT(e, RESOURCE_FUEL, 1, pauserotfuel_finished, autocvar_g_balance_pause_fuel_rot, pauseregen_finished, autocvar_g_balance_pause_fuel_regen, SND_ITEMPICKUP, SND_Null);
+ POSTGIVE_RESOURCE_ROT(e, RESOURCE_ARMOR, 1, pauserotarmor_finished, autocvar_g_balance_pause_armor_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_ARMOR25, SND_Null);
+ POSTGIVE_RESOURCE_ROT(e, RESOURCE_HEALTH, 1, pauserothealth_finished, autocvar_g_balance_pause_health_rot, pauseregen_finished, autocvar_g_balance_pause_health_regen, SND_MEGAHEALTH, SND_Null);
if(e.superweapons_finished <= 0)
if(STAT(WEAPONS, e) & WEPSET_SUPERWEAPONS)
void GiveRot(entity e, float v0, float v1, .float rotfield, float rottime, .float regenfield, float regentime);
+spawnfunc(target_items);
+
#define PREGIVE_WEAPONS(e) WepSet save_weapons; save_weapons = STAT(WEAPONS, e)
#define PREGIVE(e,f) float save_##f; save_##f = (e).f
+#define PREGIVE_RESOURCE(e,f) float save_##f = GetResourceAmount((e), (f))
#define POSTGIVE_WEAPON(e,b,snd_incr,snd_decr) GiveSound((e), !!(save_weapons & WepSet_FromWeapon(b)), !!(STAT(WEAPONS, e) & WepSet_FromWeapon(b)), 0, snd_incr, snd_decr)
#define POSTGIVE_BIT(e,f,b,snd_incr,snd_decr) GiveSound((e), save_##f & (b), (e).f & (b), 0, snd_incr, snd_decr)
+#define POSTGIVE_RESOURCE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, GetResourceAmount((e), (f)), t, snd_incr, snd_decr)
+#define POSTGIVE_RESOURCE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e),save_##f,GetResourceAmount((e),(f)),rotfield,rottime,regenfield,regentime);GiveSound((e),save_##f,GetResourceAmount((e),(f)),t,snd_incr,snd_decr)
#define POSTGIVE_VALUE(e,f,t,snd_incr,snd_decr) GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
#define POSTGIVE_VALUE_ROT(e,f,t,rotfield,rottime,regenfield,regentime,snd_incr,snd_decr) GiveRot((e), save_##f, (e).f, rotfield, rottime, regenfield, regentime); GiveSound((e), save_##f, (e).f, t, snd_incr, snd_decr)
this.tur_head.angles += dt * this.tur_head.avelocity;
- if (this.health < 127)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) < 127)
{
dt = random();
te_spark(this.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16);
}
- if(this.health < 85)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 85)
if(dt < 0.01)
pointparticles(EFFECT_SMOKE_LARGE, (this.origin + (randomvec() * 80)), '0 0 0', 1);
- if(this.health < 32)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 32)
if(dt < 0.015)
pointparticles(EFFECT_SMOKE_SMALL, (this.origin + (randomvec() * 80)), '0 0 0', 1);
drawhealthbar(
o,
0,
- this.health / 255,
+ GetResourceAmount(this, RESOURCE_HEALTH) / 255,
'0 0 0',
'0 0 0',
0.5 * SPRITE_HEALTHBAR_WIDTH * t,
set_movetype(this.tur_head, MOVETYPE_NOCLIP);
set_movetype(this, MOVETYPE_NOCLIP);
this.tur_head.angles = this.angles;
- this.health = 255;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 255);
this.solid = SOLID_BBOX;
this.tur_head.solid = SOLID_NOT;
set_movetype(this, MOVETYPE_NOCLIP);
}
_tmp = ReadByte();
- if(_tmp == 0 && this.health != 0)
+ float myhp = GetResourceAmount(this, RESOURCE_HEALTH);
+ if(_tmp == 0 && myhp != 0)
turret_die(this);
- else if(this.health && this.health != _tmp)
+ else if(myhp && myhp > _tmp)
this.helpme = servertime + 10;
+ else if(myhp && myhp < _tmp)
+ this.helpme = 0; // we're being healed, don't spam help me waypoints
- this.health = _tmp;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, _tmp);
}
- //this.enemy.health = this.health / 255;
return true;
}
this.tur_head.solid = this.solid;
this.event_damage = func_null;
+ this.event_heal = func_null;
this.takedamage = DAMAGE_NO;
- this.health = 0;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
// Go boom
//RadiusDamage (this,this, min(this.ammo,50),min(this.ammo,50) * 0.25,250,NULL,min(this.ammo,50)*5,DEATH_TURRET,NULL);
return;
}
- this.health -= damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
// thorw head slightly off aim when hit?
if (this.damage_flags & TFL_DMG_HEADSHAKE)
if (this.turret_flags & TUR_FLAG_MOVE)
this.velocity = this.velocity + vforce;
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
this.event_damage = func_null;
this.tur_head.event_damage = func_null;
+ this.event_heal = func_null;
+ this.tur_head.event_heal = func_null;
this.takedamage = DAMAGE_NO;
this.nextthink = time;
setthink(this, turret_die);
this.SendFlags |= TNSF_STATUS;
}
+bool turret_heal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ targ.SendFlags |= TNSF_STATUS;
+ return true;
+}
+
void turret_think(entity this);
void turret_respawn(entity this)
{
this.solid = SOLID_BBOX;
this.takedamage = DAMAGE_AIM;
this.event_damage = turret_damage;
+ this.event_heal = turret_heal;
this.avelocity = '0 0 0';
this.tur_head.avelocity = this.avelocity;
this.tur_head.angles = this.idle_aim;
- this.health = this.max_health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.max_health);
this.enemy = NULL;
this.volly_counter = this.shot_volly;
this.ammo = this.ammo_max;
{
WriteByte(MSG_ENTITY, this.team);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
WriteByte(MSG_ENTITY, 0);
else
- WriteByte(MSG_ENTITY, ceil((this.health / this.max_health) * 255));
+ WriteByte(MSG_ENTITY, ceil((GetResourceAmount(this, RESOURCE_HEALTH) / this.max_health) * 255));
}
return true;
ent.tur_head.angles = '0 0 0';
}
- ent.health = cvar(strcat(sbase,"_health")) * ent.turret_scale_health;
+ SetResourceAmountExplicit(ent, RESOURCE_HEALTH, cvar(strcat(sbase,"_health")) * ent.turret_scale_health);
ent.respawntime = cvar(strcat(sbase,"_respawntime")) * ent.turret_scale_respawn;
ent.shot_dmg = cvar(strcat(sbase,"_shot_dmg")) * ent.turret_scale_damage;
void turret_projectile_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector vforce)
{
this.velocity += vforce;
- this.health -= damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
//this.realowner = attacker; // Dont change realowner, it does not make much sense for turrets
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, this.owner, turret_projectile_explode);
}
PROJECTILE_MAKETRIGGER(proj);
if(_health)
{
- proj.health = _health;
+ SetResourceAmountExplicit(proj, RESOURCE_HEALTH, _health);
proj.takedamage = DAMAGE_YES;
proj.event_damage = turret_projectile_damage;
}
if (e_target.vehicle_health <= 0)
return -6;
}
- else if (e_target.health <= 0)
+ else if (GetResourceAmount(e_target, RESOURCE_HEALTH) <= 0)
return -6;
else if(STAT(FROZEN, e_target) > 0)
return -6;
#undef TRY
}
+bool turret_closetotarget(entity this, vector targ)
+{
+ vector path_extra_size = '64 64 64';
+ return boxesoverlap(targ - path_extra_size, targ + path_extra_size, this.absmin - path_extra_size, this.absmax + path_extra_size);
+}
+
void turret_findtarget(entity this)
{
entity e = find(NULL, classname, "turret_manager");
if(!this.team || !teamplay) { this.team = FLOAT_MAX; }
if(!this.ticrate) { this.ticrate = ((this.turret_flags & TUR_FLAG_SUPPORT) ? 0.2 : 0.1); }
- if(!this.health) { this.health = 1000; }
+ if(!GetResourceAmount(this, RESOURCE_HEALTH)) { SetResourceAmountExplicit(this, RESOURCE_HEALTH, 1000); }
if(!this.shot_refire) { this.shot_refire = 1; }
if(!this.tur_shotorg) { this.tur_shotorg = '50 0 50'; }
if(!this.turret_flags) { this.turret_flags = TUR_FLAG_SPLASH | TUR_FLAG_MEDPROJ | TUR_FLAG_PLAYER; }
this.effects = EF_NODRAW;
this.netname = tur.turret_name;
this.ticrate = bound(sys_frametime, this.ticrate, 60);
- this.max_health = this.health;
+ this.max_health = GetResourceAmount(this, RESOURCE_HEALTH);
this.target_validate_flags = this.target_select_flags;
this.ammo = this.ammo_max;
this.ammo_recharge *= this.ticrate;
this.idle_aim = '0 0 0';
this.turret_firecheckfunc = turret_firecheck;
this.event_damage = turret_damage;
+ this.event_heal = turret_heal;
this.use = turret_use;
this.bot_attack = true;
this.nextthink = time + 1;
bool turret_initialize(entity this, Turret tur);
+// returns true when box overlaps with a given location
+bool turret_closetotarget(entity this, vector targ);
+
/// Function to use for target evaluation. usualy turret_targetscore_generic
.float(entity _turret, entity _target) turret_score_target;
void ewheel_move_path(entity this)
{
// Are we close enough to a path node to switch to the next?
- if(vdist(this.origin - this.pathcurrent.origin, <, 64))
+ if(turret_closetotarget(this, this.pathcurrent.origin))
{
#ifdef EWHEEL_FANCYPATH
if (this.pathcurrent.path_next == NULL)
if (this.pathcurrent)
{
-
this.moveto = this.pathcurrent.origin;
this.steerto = steerlib_attract2(this, this.moveto, 0.5, 500, 0.95);
setorigin(this, this.origin + this.velocity * dt);
this.tur_head.angles += dt * this.tur_head.avelocity;
- if (this.health < 127)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 127)
if(random() < 0.05)
te_spark(this.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16);
}
- METHOD(EWheel, tr_setup, void(EWheel this, entity it))
- {
- it.gravity = 1;
- set_movetype(it, MOVETYPE_BOUNCE);
- it.move_time = time;
- it.draw = ewheel_draw;
- }
+METHOD(EWheel, tr_setup, void(EWheel this, entity it))
+{
+ it.gravity = 1;
+ set_movetype(it, MOVETYPE_BOUNCE);
+ it.move_time = time;
+ it.draw = ewheel_draw;
+}
#endif // CSQC
return false;
// Cant touch this
- if ((targ.takedamage == DAMAGE_NO) || (targ.health < 0))
+ if ((targ.takedamage == DAMAGE_NO) || (GetResourceAmount(targ, RESOURCE_HEALTH) < 0))
return false;
// player
void walker_rocket_damage(entity this, entity inflictor, entity attacker, float damage, float deathtype, .entity weaponentity, vector hitloc, vector vforce)
{
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
this.velocity = this.velocity + vforce;
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, this.owner, walker_rocket_explode);
}
rocket.bot_dodgerating = 50;
rocket.takedamage = DAMAGE_YES;
rocket.damageforcescale = 2;
- rocket.health = 25;
+ SetResourceAmountExplicit(rocket, RESOURCE_HEALTH, 25);
rocket.tur_shotorg = randomvec() * 512;
rocket.cnt = time + 1;
rocket.enemy = this.enemy;
{
#ifdef WALKER_FANCYPATHING
// Are we close enougth to a path node to switch to the next?
- if(vdist(this.origin - this.pathcurrent.origin, <, 64))
+ if(turret_closetotarget(this, this.pathcurrent.origin))
+ {
if (this.pathcurrent.path_next == NULL)
{
// Path endpoint reached
}
else
this.pathcurrent = this.pathcurrent.path_next;
+ }
this.moveto = this.pathcurrent.origin;
this.steerto = steerlib_attract2(this, this.moveto,0.5,500,0.95);
walker_move_to(this, this.moveto, 0);
#else
- if(vdist(this.origin - this.pathcurrent.origin, <, 64))
+ if(turret_closetotarget(this, this.pathcurrent.origin))
this.pathcurrent = this.pathcurrent.enemy;
if(!this.pathcurrent)
{
fixedmakevectors(it.angles);
- if (it.spawnflags & TSF_NO_PATHBREAK && it.pathcurrent)
+ if ((it.spawnflags & TSF_NO_PATHBREAK) && it.pathcurrent)
walker_move_path(it);
else if (it.enemy == NULL)
{
setorigin(this, this.origin + this.velocity * dt);
this.tur_head.angles += dt * this.tur_head.avelocity;
- if (this.health < 127)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 127)
if(random() < 0.15)
te_spark(this.origin + '0 0 40', randomvec() * 256 + '0 0 256', 16);
}
- METHOD(WalkerTurret, tr_setup, void(WalkerTurret this, entity it))
- {
- it.gravity = 1;
- set_movetype(it, MOVETYPE_BOUNCE);
- it.move_time = time;
- it.draw = walker_draw;
- }
+METHOD(WalkerTurret, tr_setup, void(WalkerTurret this, entity it))
+{
+ it.gravity = 1;
+ set_movetype(it, MOVETYPE_BOUNCE);
+ it.move_time = time;
+ it.draw = walker_draw;
+}
#endif // CSQC
float turret_tag_fire_update(entity this);
void FireImoBeam(entity this, vector start, vector end, vector smin, vector smax, float bforce, float f_dmg, float f_velfactor, float deathtype);
+#ifdef TURRET_DEBUG
+void mark_error(vector where,float lifetime);
+void mark_info(vector where,float lifetime);
+entity mark_misc(vector where,float lifetime);
+#endif
+
#endif
#if defined(CSQC)
#include "constants.qh"
- #include "../client/mutators/events.qh"
+ #include <client/mutators/_mod.qh>
#include "mapinfo.qh"
#include "notifications/all.qh"
#include "scores.qh"
#elif defined(MENUQC)
#elif defined(SVQC)
#include "constants.qh"
- #include "../server/mutators/events.qh"
+ #include <server/mutators/_mod.qh>
#include "notifications/all.qh"
#include <common/deathtypes/all.qh>
#include "scores.qh"
#include "mapinfo.qh"
#endif
+#ifdef SVQC
+float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
+{
+ vector pos, dir, t;
+ float nudge;
+ entity stopentity;
+
+ //nudge = 2 * cvar("collision_impactnudge"); // why not?
+ nudge = 0.5;
+
+ dir = normalize(v2 - v1);
+
+ pos = v1 + dir * nudge;
+
+ float c;
+ c = 0;
+
+ for (;;)
+ {
+ if(pos * dir >= v2 * dir)
+ {
+ // went too far
+ trace_fraction = 1;
+ trace_endpos = v2;
+ return c;
+ }
+
+ tracebox(pos, mi, ma, v2, nomonsters, forent);
+ ++c;
+
+ if(c == 50)
+ {
+ LOG_TRACE("When tracing from ", vtos(v1), " to ", vtos(v2));
+ LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos));
+ LOG_TRACE(" trace_endpos is ", vtos(trace_endpos));
+ LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos)));
+ }
+
+ stopentity = trace_ent;
+
+ if(trace_startsolid)
+ {
+ // we started inside solid.
+ // then trace from endpos to pos
+ t = trace_endpos;
+ tracebox(t, mi, ma, pos, nomonsters, forent);
+ ++c;
+ if(trace_startsolid)
+ {
+ // t is still inside solid? bad
+ // force advance, then, and retry
+ pos = t + dir * nudge;
+
+ // but if we hit an entity, stop RIGHT before it
+ if(stopatentity && stopentity && stopentity != ignorestopatentity)
+ {
+ trace_ent = stopentity;
+ trace_endpos = t;
+ trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+ return c;
+ }
+ }
+ else
+ {
+ // we actually LEFT solid!
+ trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+ return c;
+ }
+ }
+ else
+ {
+ // pos is outside solid?!? but why?!? never mind, just return it.
+ trace_endpos = pos;
+ trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
+ return c;
+ }
+ }
+}
+
+void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
+{
+ tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
+}
+#endif
+
#ifdef GAMEQC
+/*
+==================
+findbetterlocation
+
+Returns a point at least 12 units away from walls
+(useful for explosion animations, although the blast is performed where it really happened)
+Ripped from DPMod
+==================
+*/
+vector findbetterlocation (vector org, float mindist)
+{
+ vector vec = mindist * '1 0 0';
+ int c = 0;
+ while (c < 6)
+ {
+ traceline (org, org + vec, true, NULL);
+ vec = vec * -1;
+ if (trace_fraction < 1)
+ {
+ vector loc = trace_endpos;
+ traceline (loc, loc + vec, true, NULL);
+ if (trace_fraction >= 1)
+ org = loc + vec;
+ }
+ if (c & 1)
+ {
+ float h = vec.y;
+ vec.y = vec.x;
+ vec.x = vec.z;
+ vec.z = h;
+ }
+ c = c + 1;
+ }
+
+ return org;
+}
+
/*
* Get "real" origin, in worldspace, even if ent is attached to something else.
*/
#pragma once
+#ifdef SVQC
+ #include <server/autocvars.qh>
+#endif
+
+#ifdef SVQC
+float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking
+
+void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity);
+#endif
+
#ifdef GAMEQC
+/*
+==================
+findbetterlocation
+
+Returns a point at least 12 units away from walls
+(useful for explosion animations, although the blast is performed where it really happened)
+Ripped from DPMod
+==================
+*/
+vector findbetterlocation (vector org, float mindist);
+
vector real_origin(entity ent);
#endif
if(inflictor.owner == this.owner)
return;
- this.health -= damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
this.velocity += force;
- if(this.health < 1)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
{
this.takedamage = DAMAGE_NO;
this.event_damage = func_null;
{
proj.takedamage = DAMAGE_AIM;
proj.event_damage = vehicles_projectile_damage;
- proj.health = _health;
+ SetResourceAmountExplicit(proj, RESOURCE_HEALTH, _health);
}
else
proj.flags |= FL_NOTARGET;
}
}
+bool vehicles_heal(entity targ, entity inflictor, float amount, float limit)
+{
+ float true_limit = ((limit != RESOURCE_LIMIT_NONE) ? limit : targ.max_health);
+ //if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= true_limit)
+ if(targ.vehicle_health <= 0 || targ.vehicle_health >= true_limit)
+ return false;
+
+ targ.vehicle_health = min(targ.vehicle_health + amount, true_limit);
+ //GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, true_limit);
+ //if(targ.owner)
+ //targ.owner.vehicle_health = (targ.vehicle_health / targ.max_health) * 100;
+ return true;
+}
+
bool vehicles_crushable(entity e)
{
if(IS_PLAYER(e) && time >= e.vehicle_enter_delay)
setsize(pl, STAT(PL_MIN, pl), STAT(PL_MAX, pl));
veh.event_damage = vehicles_damage;
+ veh.event_heal = vehicles_heal;
veh.nextthink = 0;
pl.items &= ~IT_USING_JETPACK;
pl.angles = veh.angles;
this.owner = NULL;
settouch(this, vehicles_touch);
this.event_damage = vehicles_damage;
+ this.event_heal = vehicles_heal;
this.reset = vehicles_reset;
this.iscreature = true;
this.teleportable = false; // no teleporting for vehicles, too buggy
this.vehicleid = info.vehicleid;
this.PlayerPhysplug = info.PlayerPhysplug;
this.event_damage = func_null;
+ this.event_heal = func_null;
settouch(this, vehicles_touch);
setthink(this, vehicles_spawn);
this.nextthink = time;
.entity gunner1;
.entity gunner2;
-.float vehicle_health = _STAT(VEHICLESTAT_HEALTH); /// If ent is player this is 0..100 indicating precentage of health left on vehicle. If ent is vehile, this is the real health value.
-.float vehicle_energy = _STAT(VEHICLESTAT_ENERGY); /// If ent is player this is 0..100 indicating precentage of energy left on vehicle. If ent is vehile, this is the real energy value.
-.float vehicle_shield = _STAT(VEHICLESTAT_SHIELD); /// If ent is player this is 0..100 indicating precentage of shield left on vehicle. If ent is vehile, this is the real shield value.
+.float vehicle_health = _STAT(VEHICLESTAT_HEALTH); /// If ent is player this is 0..100 indicating precentage of health left on vehicle. If ent is vehicle, this is the real health value.
+.float vehicle_energy = _STAT(VEHICLESTAT_ENERGY); /// If ent is player this is 0..100 indicating precentage of energy left on vehicle. If ent is vehicle, this is the real energy value.
+.float vehicle_shield = _STAT(VEHICLESTAT_SHIELD); /// If ent is player this is 0..100 indicating precentage of shield left on vehicle. If ent is vehicle, this is the real shield value.
-.float vehicle_ammo1 = _STAT(VEHICLESTAT_AMMO1); /// If ent is player this is 0..100 indicating percentage of primary ammo left UNLESS value is already stored in vehicle_energy. If ent is vehile, this is the real ammo1 value.
-.float vehicle_reload1 = _STAT(VEHICLESTAT_RELOAD1); /// If ent is player this is 0..100 indicating percentage of primary reload status. If ent is vehile, this is the real reload1 value.
-.float vehicle_ammo2 = _STAT(VEHICLESTAT_AMMO2); /// If ent is player this is 0..100 indicating percentage of secondary ammo left. If ent is vehile, this is the real ammo2 value.
-.float vehicle_reload2 = _STAT(VEHICLESTAT_RELOAD2); /// If ent is player this is 0..100 indicating percentage of secondary reload status. If ent is vehile, this is the real reload2 value.
+.float vehicle_ammo1 = _STAT(VEHICLESTAT_AMMO1); /// If ent is player this is 0..100 indicating percentage of primary ammo left UNLESS value is already stored in vehicle_energy. If ent is vehicle, this is the real ammo1 value.
+.float vehicle_reload1 = _STAT(VEHICLESTAT_RELOAD1); /// If ent is player this is 0..100 indicating percentage of primary reload status. If ent is vehicle, this is the real reload1 value.
+.float vehicle_ammo2 = _STAT(VEHICLESTAT_AMMO2); /// If ent is player this is 0..100 indicating percentage of secondary ammo left. If ent is vehicle, this is the real ammo2 value.
+.float vehicle_reload2 = _STAT(VEHICLESTAT_RELOAD2); /// If ent is player this is 0..100 indicating percentage of secondary reload status. If ent is vehicle, this is the real reload2 value.
.float sound_nexttime;
const float VOL_VEHICLEENGINE = 1;
.float lock_strength;
.float lock_time;
.float lock_soundtime;
-const float DAMAGE_TARGETDRONE = 10;
// vehicle functions
.void(int _spawnflag) vehicle_spawn; /// Vehicles custom fucntion to be efecuted when vehicle (re)spawns
.void(entity this, int exit_flags) vehicle_exit;
.bool(entity this, entity player) vehicle_enter;
const int VHEF_NORMAL = 0; /// User pressed exit key
-const int VHEF_EJECT = 1; /// User pressed exit key 3 times fast (not implemented) or vehile is dying
+const int VHEF_EJECT = 1; /// User pressed exit key 3 times fast (not implemented) or vehicle is dying
const int VHEF_RELEASE = 2; /// Release ownership, client possibly allready dissconnected / went spec / changed team / used "kill" (not implemented)
float force_fromtag_power;
bool vehicle_impulse(entity this, int imp);
bool vehicles_crushable(entity e);
float vehicle_altitude(entity this, float amax);
+void vehicles_enter(entity pl, entity veh);
IntrusiveList g_vehicle_returners;
STATIC_INIT(g_vehicle_returners) { g_vehicle_returners = IL_NEW(); }
else
{
if(!IS_DEAD(trace_ent))
+ {
if((teamplay && trace_ent.team == this.team) || !teamplay)
{
+ if(autocvar_g_vehicle_bumblebee_healgun_hps)
+ {
+ float hplimit = ((IS_PLAYER(trace_ent)) ? autocvar_g_vehicle_bumblebee_healgun_hmax : RESOURCE_LIMIT_NONE);
+ Heal(trace_ent, this, autocvar_g_vehicle_bumblebee_healgun_hps * dt, hplimit);
+ }
if(IS_VEHICLE(trace_ent))
{
if(autocvar_g_vehicle_bumblebee_healgun_sps && trace_ent.vehicle_health <= trace_ent.max_health)
trace_ent.vehicle_shield = min(trace_ent.vehicle_shield + autocvar_g_vehicle_bumblebee_healgun_sps * dt, trace_ent.tur_head.max_health);
-
- if(autocvar_g_vehicle_bumblebee_healgun_hps)
- trace_ent.vehicle_health = min(trace_ent.vehicle_health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, trace_ent.max_health);
}
else if(IS_CLIENT(trace_ent))
{
- if(trace_ent.health <= autocvar_g_vehicle_bumblebee_healgun_hmax && autocvar_g_vehicle_bumblebee_healgun_hps)
- trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, autocvar_g_vehicle_bumblebee_healgun_hmax);
-
- if(trace_ent.armorvalue <= autocvar_g_vehicle_bumblebee_healgun_amax && autocvar_g_vehicle_bumblebee_healgun_aps)
- trace_ent.armorvalue = min(trace_ent.armorvalue + autocvar_g_vehicle_bumblebee_healgun_aps * dt, autocvar_g_vehicle_bumblebee_healgun_amax);
-
- trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, autocvar_g_vehicle_bumblebee_healgun_hmax);
- }
- else if(IS_TURRET(trace_ent))
- {
- if(trace_ent.health <= trace_ent.max_health && autocvar_g_vehicle_bumblebee_healgun_hps)
- trace_ent.health = min(trace_ent.health + autocvar_g_vehicle_bumblebee_healgun_hps * dt, trace_ent.max_health);
- //else ..hmmm what? ammo?
-
- trace_ent.SendFlags |= TNSF_STATUS;
+ if(GetResourceAmount(trace_ent, RESOURCE_ARMOR) <= autocvar_g_vehicle_bumblebee_healgun_amax && autocvar_g_vehicle_bumblebee_healgun_aps)
+ GiveResourceWithLimit(trace_ent, RESOURCE_ARMOR, autocvar_g_vehicle_bumblebee_healgun_aps * dt, autocvar_g_vehicle_bumblebee_healgun_amax);
}
}
+ }
}
}
Send_Effect(EFFECT_EXPLOSION_MEDIUM, findbetterlocation(instance.origin, 16), '0 0 0', 1);
- instance.health = 0;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
instance.event_damage = func_null;
instance.solid = SOLID_NOT;
instance.takedamage = DAMAGE_NO;
{
#ifdef SVQC
setSendEntity(instance, func_null); // stop networking this racer (for now)
- instance.health = 0;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
instance.event_damage = func_null;
instance.solid = SOLID_CORPSE;
instance.takedamage = DAMAGE_NO;
}
METHOD(Raptor, vr_death, void(Raptor thisveh, entity instance))
{
- instance.health = 0;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
instance.event_damage = func_null;
instance.solid = SOLID_CORPSE;
instance.takedamage = DAMAGE_NO;
_flare.solid = SOLID_CORPSE;
_flare.takedamage = DAMAGE_YES;
_flare.event_damage = raptor_flare_damage;
- _flare.health = 20;
+ SetResourceAmountExplicit(_flare, RESOURCE_HEALTH, 20);
_flare.tur_impacttime = time + autocvar_g_vehicle_raptor_flare_lifetime;
settouch(_flare, raptor_flare_touch);
}
void raptor_flare_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- this.health -= damage;
- if(this.health <= 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
delete(this);
}
}
METHOD(Spiderbot, vr_death, void(Spiderbot thisveh, entity instance))
{
- instance.health = 0;
+ SetResourceAmountExplicit(instance, RESOURCE_HEALTH, 0);
instance.event_damage = func_null;
instance.takedamage = DAMAGE_NO;
settouch(instance, func_null);
vector shotorg_adjust_values(vector vecs, bool y_is_right, bool visual, int algn);
void CL_WeaponEntity_SetModel(entity this, string name, bool _anim);
+
+#ifdef SVQC
+void wframe_send(entity actor, entity weaponentity, vector a, bool restartanim);
+#endif
#endif
#include "arc.qh"
#ifdef SVQC
+#include <common/gamemodes/gamemode/onslaught/sv_onslaught.qh>
+#include <common/gamemodes/gamemode/onslaught/sv_generator.qh>
bool W_Arc_Beam_Send(entity this, entity to, int sf)
{
void W_Arc_Bolt_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1))
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
this.angles = vectoangles(this.velocity);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, getthink(this));
}
missile.bot_dodgerating = WEP_CVAR(arc, bolt_damage);
missile.takedamage = DAMAGE_YES;
- missile.health = WEP_CVAR(arc, bolt_health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(arc, bolt_health));
missile.damageforcescale = WEP_CVAR(arc, bolt_damageforcescale);
missile.event_damage = W_Arc_Bolt_Damage;
missile.damagedbycontents = true;
if(
!IS_PLAYER(own)
||
- (!thiswep.wr_checkammo1(thiswep, own, weaponentity) && !(own.items & IT_UNLIMITED_WEAPON_AMMO))
- ||
IS_DEAD(own)
||
- forbidWeaponUse(own)
+ !weapon_prepareattack_check(thiswep, own, weaponentity, this.beam_bursting, -1)
||
own.(weaponentity).m_switchweapon != WEP_ARC
||
beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir);
- float is_player = (
+ bool is_player = (
IS_PLAYER(trace_ent)
||
trace_ent.classname == "body"
IS_MONSTER(trace_ent)
);
- if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
+ if(trace_ent)
{
- // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
- // NO. trace_endpos should be just fine. If not,
- // that's an engine bug that needs proper debugging.
- vector hitorigin = trace_endpos;
-
- float falloff = ExponentialFalloff(
- WEP_CVAR(arc, beam_falloff_mindist),
- WEP_CVAR(arc, beam_falloff_maxdist),
- WEP_CVAR(arc, beam_falloff_halflifedist),
- vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
- );
-
- if(is_player && SAME_TEAM(own, trace_ent))
+ if(SAME_TEAM(own, trace_ent))
{
- float roothealth, rootarmor;
- if(burst)
- {
- roothealth = WEP_CVAR(arc, burst_healing_hps);
- rootarmor = WEP_CVAR(arc, burst_healing_aps);
- }
- else
+ float roothealth = ((burst) ? WEP_CVAR(arc, burst_healing_hps) : WEP_CVAR(arc, beam_healing_hps));
+ float rootarmor = ((burst) ? WEP_CVAR(arc, burst_healing_aps) : WEP_CVAR(arc, beam_healing_aps));
+ float hplimit = ((IS_PLAYER(trace_ent)) ? WEP_CVAR(arc, beam_healing_hmax) : RESOURCE_LIMIT_NONE);
+ Heal(trace_ent, own, (roothealth * coefficient), hplimit);
+ if(IS_PLAYER(trace_ent) && rootarmor)
{
- roothealth = WEP_CVAR(arc, beam_healing_hps);
- rootarmor = WEP_CVAR(arc, beam_healing_aps);
+ if(GetResourceAmount(trace_ent, RESOURCE_ARMOR) <= WEP_CVAR(arc, beam_healing_amax))
+ {
+ GiveResourceWithLimit(trace_ent, RESOURCE_ARMOR, (rootarmor * coefficient), WEP_CVAR(arc, beam_healing_amax));
+ trace_ent.pauserotarmor_finished = max(
+ trace_ent.pauserotarmor_finished,
+ time + autocvar_g_balance_pause_armor_rot
+ );
+ }
}
-
- if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
- {
- trace_ent.health = min(
- trace_ent.health + (roothealth * coefficient),
- WEP_CVAR(arc, beam_healing_hmax)
- );
- }
- if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
- {
- trace_ent.armorvalue = min(
- trace_ent.armorvalue + (rootarmor * coefficient),
- WEP_CVAR(arc, beam_healing_amax)
- );
- }
-
- // stop rot, set visual effect
if(roothealth || rootarmor)
- {
- trace_ent.pauserothealth_finished = max(
- trace_ent.pauserothealth_finished,
- time + autocvar_g_balance_pause_health_rot
- );
- trace_ent.pauserotarmor_finished = max(
- trace_ent.pauserotarmor_finished,
- time + autocvar_g_balance_pause_armor_rot
- );
new_beam_type = ARC_BT_HEAL;
- }
}
- else
+ else if(trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
{
+ // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
+ // NO. trace_endpos should be just fine. If not,
+ // that's an engine bug that needs proper debugging.
+ vector hitorigin = trace_endpos;
+
+ float falloff = ExponentialFalloff(
+ WEP_CVAR(arc, beam_falloff_mindist),
+ WEP_CVAR(arc, beam_falloff_maxdist),
+ WEP_CVAR(arc, beam_falloff_halflifedist),
+ vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
+ );
+
float rootdamage;
if(is_player)
{
.entity queuenext;
.entity queueprev;
+
+void W_Crylink_Dequeue(entity e);
#endif
void W_Devastator_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
this.angles = vectoangles(this.velocity);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, W_Devastator_Explode_think);
}
-void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity)
+void W_Devastator_Attack(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
W_DecreaseAmmo(thiswep, actor, WEP_CVAR(devastator, ammo), weaponentity);
missile.takedamage = DAMAGE_YES;
missile.damageforcescale = WEP_CVAR(devastator, damageforcescale);
- missile.health = WEP_CVAR(devastator, health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(devastator, health));
missile.event_damage = W_Devastator_Damage;
missile.damagedbycontents = true;
IL_PUSH(g_damagedbycontents, missile);
setthink(missile, W_Devastator_Think);
missile.nextthink = time;
missile.cnt = time + WEP_CVAR(devastator, lifetime);
+ missile.rl_detonate_later = (fire & 2); // allow instant detonation
missile.flags = FL_PROJECTILE;
IL_PUSH(g_projectiles, missile);
IL_PUSH(g_bot_dodge, missile);
// common properties
MUTATOR_CALLHOOK(EditProjectile, actor, missile);
+
+ if (time >= missile.nextthink)
+ {
+ getthink(missile)(missile);
+ }
}
METHOD(Devastator, wr_aim, void(entity thiswep, entity actor, .entity weaponentity))
// but don't fire a new shot at the same time!
if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
PHYS_INPUT_BUTTON_ATCK2(actor) = true;
- if((skill > 6.5) && (selfdamage > actor.health))
+ if((skill > 6.5) && (selfdamage > GetResourceAmount(actor, RESOURCE_HEALTH)))
PHYS_INPUT_BUTTON_ATCK2(actor) = false;
//if(PHYS_INPUT_BUTTON_ATCK2(actor) == true)
// dprint(ftos(desirabledamage),"\n");
if(actor.(weaponentity).rl_release || WEP_CVAR(devastator, guidestop))
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(devastator, refire)))
{
- W_Devastator_Attack(thiswep, actor, weaponentity);
+ W_Devastator_Attack(thiswep, actor, weaponentity, fire);
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
actor.(weaponentity).rl_release = 0;
}
newproj.takedamage = this.takedamage;
newproj.damageforcescale = this.damageforcescale;
- newproj.health = this.health;
+ SetResourceAmountExplicit(newproj, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
newproj.event_damage = this.event_damage;
newproj.spawnshieldtime = this.spawnshieldtime;
newproj.damagedbycontents = true;
void W_Electro_Orb_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
// note: combos are usually triggered by W_Electro_TriggerCombo, not damage
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_combo ? 1 : -1)))
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
- if(this.health <= 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
this.takedamage = DAMAGE_NO;
this.nextthink = time;
setsize(proj, '-4 -4 -4', '4 4 4');
proj.takedamage = DAMAGE_YES;
proj.damageforcescale = WEP_CVAR_SEC(electro, damageforcescale);
- proj.health = WEP_CVAR_SEC(electro, health);
+ SetResourceAmountExplicit(proj, RESOURCE_HEALTH, WEP_CVAR_SEC(electro, health));
proj.event_damage = W_Electro_Orb_Damage;
proj.flags = FL_PROJECTILE;
IL_PUSH(g_projectiles, proj);
this.takedamage = DAMAGE_NO;
// 1. dist damage
- d = (this.realowner.health + this.realowner.armorvalue);
+ d = (GetResourceAmount(this.realowner, RESOURCE_HEALTH) + GetResourceAmount(this.realowner, RESOURCE_ARMOR));
RadiusDamage(this, this.realowner, WEP_CVAR_PRI(fireball, damage), WEP_CVAR_PRI(fireball, edgedamage), WEP_CVAR_PRI(fireball, radius), NULL, NULL, WEP_CVAR_PRI(fireball, force), this.projectiledeathtype, this.weaponentity_fld, directhitentity);
- if(this.realowner.health + this.realowner.armorvalue >= d)
+ if(GetResourceAmount(this.realowner, RESOURCE_HEALTH) + GetResourceAmount(this.realowner, RESOURCE_ARMOR) >= d)
if(!this.cnt)
{
modeleffect_spawn("models/sphere/sphere.md3", 0, 0, this.origin, '0 0 0', '0 0 0', '0 0 0', 0, WEP_CVAR_PRI(fireball, bfgradius), 0.2, 0.05, 0.25);
void W_Fireball_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
- if(this.health <= 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
this.cnt = 1;
W_PrepareExplosionByDamage(this, attacker, W_Fireball_Explode_think);
proj.use = W_Fireball_Explode_use;
setthink(proj, W_Fireball_Think);
proj.nextthink = time;
- proj.health = WEP_CVAR_PRI(fireball, health);
+ SetResourceAmountExplicit(proj, RESOURCE_HEALTH, WEP_CVAR_PRI(fireball, health));
proj.team = actor.team;
proj.event_damage = W_Fireball_Damage;
proj.takedamage = DAMAGE_YES;
void W_Hagar_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
float is_linkexplode = ( ((inflictor.owner != NULL) ? (inflictor.owner == this.owner) : true)
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, is_linkexplode))
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
this.angles = vectoangles(this.velocity);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, getthink(this));
}
missile.bot_dodgerating = WEP_CVAR_PRI(hagar, damage);
missile.takedamage = DAMAGE_YES;
- missile.health = WEP_CVAR_PRI(hagar, health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_PRI(hagar, health));
missile.damageforcescale = WEP_CVAR_PRI(hagar, damageforcescale);
missile.event_damage = W_Hagar_Damage;
missile.damagedbycontents = true;
missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
missile.takedamage = DAMAGE_YES;
- missile.health = WEP_CVAR_SEC(hagar, health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_SEC(hagar, health));
missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
missile.event_damage = W_Hagar_Damage;
missile.damagedbycontents = true;
missile.bot_dodgerating = WEP_CVAR_SEC(hagar, damage);
missile.takedamage = DAMAGE_YES;
- missile.health = WEP_CVAR_SEC(hagar, health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR_SEC(hagar, health));
missile.damageforcescale = WEP_CVAR_SEC(hagar, damageforcescale);
missile.event_damage = W_Hagar_Damage;
missile.damagedbycontents = true;
void W_Hagar_Attack2_Load(Weapon thiswep, entity actor, .entity weaponentity)
{
// loadable hagar secondary attack, must always run each frame
- if(round_handler_IsActive() && !round_handler_IsRoundStarted())
- return;
- if(time < game_starttime)
+ if(!weapon_prepareattack_check(thiswep, actor, weaponentity, true, -1))
return;
bool loaded = actor.(weaponentity).hagar_load >= WEP_CVAR_SEC(hagar, load_max);
void W_Hagar_Attack_Auto(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
- if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != WEP_HAGAR)
+ if(!(fire & 1) || actor.(weaponentity).hagar_load || actor.(weaponentity).hagar_loadblock || actor.(weaponentity).m_switchweapon != WEP_HAGAR || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
{
w_ready(thiswep, actor, weaponentity, fire);
return;
void W_Hook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, this.realowner, W_Hook_Explode2);
}
settouch(gren, W_Hook_Touch2);
gren.takedamage = DAMAGE_YES;
- gren.health = WEP_CVAR_SEC(hook, health);
+ SetResourceAmountExplicit(gren, RESOURCE_HEALTH, WEP_CVAR_SEC(hook, health));
gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
gren.event_damage = W_Hook_Damage;
gren.damagedbycontents = true;
{
if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
{
- if( actor.ammo_fuel >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
+ if( GetResourceAmount(actor, RESOURCE_FUEL) >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
{
W_DecreaseAmmo(thiswep, actor, (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel, weaponentity);
actor.(weaponentity).hook_time_fueldecrease = time;
}
else
{
- actor.ammo_fuel = 0;
+ SetResourceAmount(actor, RESOURCE_FUEL, 0);
actor.(weaponentity).hook_state |= HOOK_REMOVING;
if(actor.(weaponentity).m_weapon != WEP_Null) // offhand
W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
if (!thiswep.ammo_factor) return true;
if(actor.(weaponentity).hook)
- return actor.ammo_fuel > 0;
+ return GetResourceAmount(actor, RESOURCE_FUEL) > 0;
- return actor.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
+ return GetResourceAmount(actor, RESOURCE_FUEL) >= WEP_CVAR_PRI(hook, ammo);
}
METHOD(Hook, wr_checkammo2, bool(Hook thiswep, entity actor, .entity weaponentity))
{
// weapon frames
void W_MachineGun_Attack_Frame(Weapon thiswep, entity actor, .entity weaponentity, int fire)
{
- if(actor.(weaponentity).m_weapon != actor.(weaponentity).m_switchweapon) // abort immediately if switching
+ if(actor.(weaponentity).m_weapon != actor.(weaponentity).m_switchweapon || !weapon_prepareattack_check(thiswep, actor, weaponentity, (fire & 2), -1)) // abort immediately if switching
{
w_ready(thiswep, actor, weaponentity, fire);
return;
{
float machinegun_spread;
- if(!(fire & 1))
+ if(!(fire & 1) || !weapon_prepareattack_check(thiswep, actor, weaponentity, false, -1))
{
w_ready(thiswep, actor, weaponentity, fire);
return;
newmine.takedamage = this.takedamage;
newmine.damageforcescale = this.damageforcescale;
- newmine.health = this.health;
+ SetResourceAmountExplicit(newmine, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
newmine.event_damage = this.event_damage;
newmine.spawnshieldtime = this.spawnshieldtime;
newmine.damagedbycontents = true;
void W_MineLayer_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
float is_from_enemy = (inflictor.realowner != this.realowner);
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, (is_from_enemy ? 1 : -1)))
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
this.angles = vectoangles(this.velocity);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, W_MineLayer_Explode_think);
}
mine.takedamage = DAMAGE_YES;
mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale);
- mine.health = WEP_CVAR(minelayer, health);
+ SetResourceAmountExplicit(mine, RESOURCE_HEALTH, WEP_CVAR(minelayer, health));
mine.event_damage = W_MineLayer_Damage;
mine.damagedbycontents = true;
IL_PUSH(g_damagedbycontents, mine);
// but don't fire a new shot at the same time!
if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
PHYS_INPUT_BUTTON_ATCK2(actor) = true;
- if((skill > 6.5) && (selfdamage > actor.health))
+ if((skill > 6.5) && (selfdamage > GetResourceAmount(actor, RESOURCE_HEALTH)))
PHYS_INPUT_BUTTON_ATCK2(actor) = false;
//if(PHYS_INPUT_BUTTON_ATCK2(actor) == true)
// dprint(ftos(desirabledamage),"\n");
void W_Mortar_Grenade_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, adaptor_think2use);
}
settouch(gren, W_Mortar_Grenade_Touch1);
gren.takedamage = DAMAGE_YES;
- gren.health = WEP_CVAR_PRI(mortar, health);
+ SetResourceAmountExplicit(gren, RESOURCE_HEALTH, WEP_CVAR_PRI(mortar, health));
gren.damageforcescale = WEP_CVAR_PRI(mortar, damageforcescale);
gren.event_damage = W_Mortar_Grenade_Damage;
gren.damagedbycontents = true;
settouch(gren, W_Mortar_Grenade_Touch2);
gren.takedamage = DAMAGE_YES;
- gren.health = WEP_CVAR_SEC(mortar, health);
+ SetResourceAmountExplicit(gren, RESOURCE_HEALTH, WEP_CVAR_SEC(mortar, health));
gren.damageforcescale = WEP_CVAR_SEC(mortar, damageforcescale);
gren.event_damage = W_Mortar_Grenade_Damage;
gren.damagedbycontents = true;
#ifdef SVQC
#include <common/mapobjects/trigger/jumppads.qh>
+#include <server/weapons/throwing.qh>
REGISTER_MUTATOR(porto_ticker, true);
MUTATOR_HOOKFUNCTION(porto_ticker, SV_StartFrame) {
delete(this);
}
-string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vector velo, .entity weaponentity);
void W_Porto_Fail(entity this, float failhard)
{
if(this.realowner == NULL)
.float porto_v_angle_held;
.vector right_vector;
.float porto_forbidden;
+
+void W_Porto_Fail(entity this, float failhard);
#endif
void W_Seeker_Missile_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_projectiles_damage says to halt
if(this.realowner == attacker)
- this.health = this.health - (damage * 0.25);
+ TakeResource(this, RESOURCE_HEALTH, (damage * 0.25));
else
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_PrepareExplosionByDamage(this, attacker, W_Seeker_Missile_Explode_think);
}
missile.scale = 2;
missile.takedamage = DAMAGE_YES;
missile.weaponentity_fld = weaponentity;
- missile.health = WEP_CVAR(seeker, missile_health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(seeker, missile_health));
missile.damageforcescale = WEP_CVAR(seeker, missile_damageforcescale);
missile.damagedbycontents = true;
IL_PUSH(g_damagedbycontents, missile);
void W_Seeker_Tag_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
- this.health = this.health - damage;
- if(this.health <= 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
W_Seeker_Tag_Explode(this);
}
missile.takedamage = DAMAGE_YES;
missile.event_damage = W_Seeker_Tag_Damage;
- missile.health = WEP_CVAR(seeker, tag_health);
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, WEP_CVAR(seeker, tag_health));
missile.damageforcescale = WEP_CVAR(seeker, tag_damageforcescale);
setorigin(missile, w_shotorg);
} else if(WEP_CVAR(vaporizer, reload_ammo) && actor.(weaponentity).clip_load < vaporizer_ammo) { // forced reload
thiswep.wr_reload(thiswep, actor, weaponentity);
}
- if((fire & 1) && (actor.ammo_cells || !autocvar_g_rm) && !forbidWeaponUse(actor))
+ if((fire & 1) && (GetResourceAmount(actor, RESOURCE_CELLS) || !autocvar_g_rm) && !forbidWeaponUse(actor))
{
if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR_PRI(vaporizer, refire)))
{
weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready);
}
}
- if((fire & 2) || ((fire & 1) && !actor.ammo_cells && autocvar_g_rm))
+ if((fire & 2) || ((fire & 1) && !GetResourceAmount(actor, RESOURCE_CELLS) && autocvar_g_rm))
{
if((autocvar_g_rm && autocvar_g_rm_laser) || autocvar_g_rm_laser == 2)
{
// note: these two only work in WIP2, but are harmless in WIP1
if (PHYS_HEALTH(NULL) <= 0 && PHYS_HEALTH(NULL) != -666 && PHYS_HEALTH(NULL) != -2342) refdefflags |= REFDEFFLAG_DEAD;
if (intermission) refdefflags |= REFDEFFLAG_INTERMISSION;
- V_CalcRefdef(view, refdefflags);
+ V_CalcRefdef(view, refdefflags); // TODO? uses .health stat in the engine when this isn't called here, may be broken!
}
else
{
#include "../menu.qh"
#include "../item.qh"
-#include "../mutators/events.qh"
+#include <menu/mutators/_mod.qh>
#include <common/command/_mod.qh>
// generated file; do not modify
+#include <menu/mutators/events.qc>
// generated file; do not modify
+#include <menu/mutators/events.qh>
--- /dev/null
+#include "events.qh"
if(cvar("g_bloodloss") > 0)
s = cons_mid(s, ", ", _("Blood loss"));
if(cvar("g_jetpack"))
- s = cons_mid(s, ", ", _("Jet pack"));
+ s = cons_mid(s, ", ", _("Jetpack"));
if(cvar("g_buffs") > 0)
s = cons_mid(s, ", ", _("Buffs"));
if(cvar("g_overkill"))
_("Players spawn with the grappling hook")));
me.TR(me);
me.TDempty(me, 0.2);
- me.TD(me, 1, 1.8, e = makeXonoticCheckBox_T(0, "g_jetpack", _("Jet pack"),
+ me.TD(me, 1, 1.8, e = makeXonoticCheckBox_T(0, "g_jetpack", _("Jetpack"),
_("Players spawn with the jetpack")));
me.TR(me);
me.TDempty(me, 0.2);
w = Weapons_from(i);
if(w.spawnflags & WEP_FLAG_HIDDEN)
continue;
- if((j & 1) == 0)
- me.TDempty(me, 0.2);
- else
+ if ((j % 3) == 0)
{
me.TR(me);
me.TDempty(me, 0.4);
}
- me.TD(me, 1, 1.7, e = makeXonoticWeaponarenaCheckBox(strzone(w.netname), strzone(w.m_name)));
+ me.TD(me, 1, 1.2, e = makeXonoticWeaponarenaCheckBox(strzone(w.netname), strzone(w.m_name)));
setDependentWeird(e, checkCompatibility_weaponarena_weapon);
++j;
}
KEYBIND_DEF("+jump" , _("jump / swim"));
KEYBIND_DEF("+crouch" , _("crouch / sink"));
KEYBIND_DEF("+hook" , _("off-hand hook"));
- KEYBIND_DEF("+jetpack" , _("jet pack"));
+ KEYBIND_DEF("+jetpack" , _("jetpack"));
KEYBIND_DEF("" , "");
KEYBIND_DEF("" , _("Attacking"));
KEYBIND_DEF("+fire" , _("primary fire"));
#include "mainwindow.qh"
-#include "../mutators/events.qh"
+#include <menu/mutators/_mod.qh>
#include "nexposee.qh"
#include "inputbox.qh"
#include <server/client.qc>
#include <server/g_damage.qc>
#include <server/g_hook.qc>
-#include <server/g_subs.qc>
#include <server/g_world.qc>
#include <server/handicap.qc>
#include <server/impulse.qc>
#include <server/client.qh>
#include <server/g_damage.qh>
#include <server/g_hook.qh>
-#include <server/g_subs.qh>
#include <server/g_world.qh>
#include <server/handicap.qh>
#include <server/impulse.qh>
#include <server/defs.qh>
#include <common/state.qh>
#include <common/vehicles/all.qh>
+ #include <lib/warpzone/common.qh>
#include "antilag.qh"
#endif
antilag_restore(it, it);
});
}
+
+/*
+==================
+traceline_antilag
+
+A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
+Additionally it moves players back into the past before the trace and restores them afterward.
+==================
+*/
+void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
+{
+ // check whether antilagged traces are enabled
+ if (lag < 0.001)
+ lag = 0;
+ if (!IS_REAL_CLIENT(forent))
+ lag = 0; // only antilag for clients
+
+ // change shooter to SOLID_BBOX so the shot can hit corpses
+ int oldsolid = source.dphitcontentsmask;
+ if(source)
+ source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
+
+ if (lag)
+ antilag_takeback_all(forent, lag);
+
+ // do the trace
+ if(wz)
+ WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
+ else
+ tracebox (v1, mi, ma, v2, nomonst, forent);
+
+ // restore players to current positions
+ if (lag)
+ antilag_restore_all(forent);
+
+ // restore shooter solid type
+ if(source)
+ source.dphitcontentsmask = oldsolid;
+}
+void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+ tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
+}
+void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+ bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+ if (autocvar_g_antilag != 2 || noantilag)
+ lag = 0;
+ traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
+}
+void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
+{
+ bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+ if (autocvar_g_antilag != 2 || noantilag)
+ lag = 0;
+ tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
+}
+void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+ tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true);
+}
+void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
+{
+ bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+ if (autocvar_g_antilag != 2 || noantilag)
+ lag = 0;
+ WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
+}
+void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
+{
+ bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
+ if (autocvar_g_antilag != 2 || noantilag)
+ lag = 0;
+ tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true);
+}
#define ANTILAG_LATENCY(e) min(0.4, CS(e).ping * 0.001)
// add one ticrate?
+
+/*
+==================
+traceline_antilag
+
+A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
+Additionally it moves players back into the past before the trace and restores them afterward.
+==================
+*/
+void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz);
+void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
+void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
+void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
//int autocvar_leadlimit;
int autocvar_leadlimit_and_fraglimit;
int autocvar_leadlimit_override;
-int autocvar_loddebug;
int autocvar_minplayers;
string autocvar_nextmap;
string autocvar_quit_and_redirect;
vector havocbot_middlepoint;
float havocbot_middlepoint_radius;
-vector havocbot_symmetryaxis_equation;
+float havocbot_symmetry_axis_m;
+float havocbot_symmetry_axis_q;
+float havocbot_symmetry_origin_order;
.float goalentity_lock_timeout;
.float ignoregoaltime;
#include "../../weapons/weaponsystem.qh"
-#include "../../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
// traces multiple trajectories to find one that will impact the target
// 'end' vector is the place it aims for,
#include "../../race.qh"
#include <common/t_items.qh>
-#include "../../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "../../weapons/accuracy.qh"
this.bot_nextthink = max(time, this.bot_nextthink) + max(0.01, autocvar_bot_ai_thinkinterval * (0.5 ** this.bot_aiskill) * min(14 / (skill + 14), 1));
- //if (this.bot_painintensity > 0)
- // this.bot_painintensity = this.bot_painintensity - (skill + 1) * 40 * frametime;
-
- //this.bot_painintensity = this.bot_painintensity + this.bot_oldhealth - this.health;
- //this.bot_painintensity = bound(0, this.bot_painintensity, 100);
-
if (!IS_PLAYER(this) || (autocvar_g_campaign && !campaign_bots_may_start))
{
CS(this).movement = '0 0 0';
// Jetpack navigation
if(this.navigation_jetpack_goal)
if(this.goalcurrent==this.navigation_jetpack_goal)
- if(this.ammo_fuel)
+ if(GetResourceAmount(this, RESOURCE_FUEL))
{
if(autocvar_bot_debug_goalstack)
{
return;
}
- else if(this.health + this.armorvalue > ROCKETJUMP_DAMAGE())
+ else if(GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR) > ROCKETJUMP_DAMAGE())
{
if(this.velocity.z < 0)
{
traceline(this.origin+this.view_ofs, ( this.enemy.absmin + this.enemy.absmax ) * 0.5,false,NULL);
if (trace_ent == this.enemy || trace_fraction == 1)
if (vdist(((this.enemy.absmin + this.enemy.absmax) * 0.5) - this.origin, <, 1000))
- if (this.health > 30)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) > 30)
{
// remain tracking him for a shot while (case he went after a small corner or pilar
this.havocbot_chooseenemy_finished = time + 0.5;
#include <server/defs.qh>
#include <server/miscfunctions.qh>
#include <server/items.qh>
+#include <server/resources.qh>
#include "havocbot.qh"
#include "../cvars.qh"
bool havocbot_goalrating_item_can_be_left_to_teammate(entity this, entity player, entity item)
{
- if (item.health && player.health <= this.health) {return true;}
- if (item.armorvalue && player.armorvalue <= this.armorvalue) {return true;}
+ if (GetResourceAmount(item, RESOURCE_HEALTH) && GetResourceAmount(player, RESOURCE_HEALTH) <= GetResourceAmount(this, RESOURCE_HEALTH)) {return true;}
+ if (GetResourceAmount(item, RESOURCE_ARMOR) && GetResourceAmount(player, RESOURCE_ARMOR) <= GetResourceAmount(this, RESOURCE_ARMOR)) {return true;}
if (STAT(WEAPONS, item) && !(STAT(WEAPONS, player) & STAT(WEAPONS, item))) {return true;}
- if (item.ammo_shells && player.ammo_shells <= this.ammo_shells) {return true;}
- if (item.ammo_nails && player.ammo_nails <= this.ammo_nails) {return true;}
- if (item.ammo_rockets && player.ammo_rockets <= this.ammo_rockets) {return true;}
- if (item.ammo_cells && player.ammo_cells <= this.ammo_cells) {return true;}
- if (item.ammo_plasma && player.ammo_plasma <= this.ammo_plasma) {return true;}
+ if (GetResourceAmount(item, RESOURCE_SHELLS) && GetResourceAmount(player, RESOURCE_SHELLS) <= GetResourceAmount(this, RESOURCE_SHELLS)) {return true;}
+ if (GetResourceAmount(item, RESOURCE_BULLETS) && GetResourceAmount(player, RESOURCE_BULLETS) <= GetResourceAmount(this, RESOURCE_BULLETS)) {return true;}
+ if (GetResourceAmount(item, RESOURCE_ROCKETS) && GetResourceAmount(player, RESOURCE_ROCKETS) <= GetResourceAmount(this, RESOURCE_ROCKETS)) {return true;}
+ if (GetResourceAmount(item, RESOURCE_CELLS) && GetResourceAmount(player, RESOURCE_CELLS) <= GetResourceAmount(this, RESOURCE_CELLS)) {return true;}
+ if (GetResourceAmount(item, RESOURCE_PLASMA) && GetResourceAmount(player, RESOURCE_PLASMA) <= GetResourceAmount(this, RESOURCE_PLASMA)) {return true;}
if (item.itemdef.instanceOfPowerup) {return true;}
return false;
continue;
*/
- t = ((this.health + this.armorvalue) - (it.health + it.armorvalue)) / 150;
+ t = ((GetResourceAmount(this, RESOURCE_HEALTH) + GetResourceAmount(this, RESOURCE_ARMOR)) - (GetResourceAmount(it, RESOURCE_HEALTH) + GetResourceAmount(it, RESOURCE_ARMOR))) / 150;
t = bound(0, 1 + t, 3);
if (skill > 3)
{
// rough simulation of walking from one point to another to test if a path
// can be traveled, used for waypoint linking and havocbot
// if end_height is > 0 destination is any point in the vertical segment [end, end + end_height * eZ]
+// INFO: the command sv_cmd trace walk is useful to test this function in game
bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
{
if(autocvar_bot_debug_tracewalk)
t += xydistance / autocvar_g_jetpack_maxspeed_side;
fuel = t * autocvar_g_jetpack_fuel * 0.8;
- LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), " this.ammo_fuel ", ftos(this.ammo_fuel));
+ LOG_DEBUG("jetpack ai: required fuel ", ftos(fuel), ", have ", ftos(GetResourceAmount(this, RESOURCE_FUEL)));
// enough fuel ?
- if(this.ammo_fuel>fuel || (this.items & IT_UNLIMITED_WEAPON_AMMO))
+ if(GetResourceAmount(this, RESOURCE_FUEL) > fuel || (this.items & IT_UNLIMITED_WEAPON_AMMO))
{
// Estimate cost
// (as onground costs calculation is mostly based on distances, here we do the same establishing some relationship
float d = vlen2(this.origin - bot_waypoint_queue_goal.origin);
LOG_DEBUG(this.netname, " evaluating ", bot_waypoint_queue_goal.classname, " with distance ", ftos(d));
set_tracewalk_dest(bot_waypoint_queue_goal, this.origin, false);
- if (tracewalk(bot_waypoint_queue_goal, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
+ if (tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
tracewalk_dest, tracewalk_dest_height, bot_navigation_movemode))
{
if( d > bot_waypoint_queue_bestgoalrating)
return cvar(substring(expr, 5, strlen(expr)));
// Search for fields
+ // TODO: expand with support for more fields (key carrier, ball carrier, armor etc)
switch(expr)
{
case "health":
- return this.health;
+ return GetResourceAmount(this, RESOURCE_HEALTH);
case "speed":
return vlen(this.velocity);
case "flagcarrier":
}
else if (fabs(autocvar_g_waypointeditor_symmetrical) == 2)
{
- float m = havocbot_symmetryaxis_equation.x;
- float q = havocbot_symmetryaxis_equation.y;
+ float m = havocbot_symmetry_axis_m;
+ float q = havocbot_symmetry_axis_q;
if (autocvar_g_waypointeditor_symmetrical == -2)
{
m = autocvar_g_waypointeditor_symmetrical_axis.x;
{
entity e;
vector org = pl.origin;
- int ctf_flags = havocbot_symmetryaxis_equation.z;
+ int ctf_flags = havocbot_symmetry_origin_order;
bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
|| (autocvar_g_waypointeditor_symmetrical < 0));
- int order = ctf_flags;
if(autocvar_g_waypointeditor_symmetrical_order >= 2)
- {
- order = autocvar_g_waypointeditor_symmetrical_order;
- ctf_flags = order;
- }
+ ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+ int wp_num = ctf_flags;
if(!PHYS_INPUT_BUTTON_CROUCH(pl))
{
org = waypoint_getSymmetricalOrigin(e.origin, ctf_flags);
if (vdist(org - pl.origin, >, 32))
{
- if(order > 2)
- order--;
+ if(wp_num > 2)
+ wp_num--;
else
sym = false;
goto add_wp;
{
entity e = navigation_findnearestwaypoint(pl, false);
- int ctf_flags = havocbot_symmetryaxis_equation.z;
+ int ctf_flags = havocbot_symmetry_origin_order;
bool sym = ((autocvar_g_waypointeditor_symmetrical > 0 && ctf_flags >= 2)
|| (autocvar_g_waypointeditor_symmetrical < 0));
- int order = ctf_flags;
if(autocvar_g_waypointeditor_symmetrical_order >= 2)
- {
- order = autocvar_g_waypointeditor_symmetrical_order;
- ctf_flags = order;
- }
+ ctf_flags = autocvar_g_waypointeditor_symmetrical_order;
+ int wp_num = ctf_flags;
LABEL(remove_wp);
if (!e) return;
if (sym && wp_sym)
{
e = wp_sym;
- if(order > 2)
- order--;
+ if(wp_num > 2)
+ wp_num--;
else
sym = false;
goto remove_wp;
bot_calculate_stepheightvec();
+ int dphitcontentsmask_save = this.dphitcontentsmask;
+ this.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+
bot_navigation_movemode = ((autocvar_bot_navigation_ignoreplayers) ? MOVE_NOMONSTERS : MOVE_NORMAL);
//dprint("waypoint_think wpisbox = ", ftos(this.wpisbox), "\n");
relink_walkculled += 0.5;
else
{
- if (tracewalk(it, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS))
+ if (tracewalk(this, ev, PL_MIN_CONST, PL_MAX_CONST, sv2, sv2_height, MOVE_NOMONSTERS))
waypoint_addlink(it, this);
else
relink_walkculled += 0.5;
});
navigation_testtracewalk = 0;
this.wplinked = true;
+ this.dphitcontentsmask = dphitcontentsmask_save;
}
void waypoint_clearlinks(entity wp)
#include <server/defs.qh>
#include <server/miscfunctions.qh>
#include <common/effects/all.qh>
+#include <server/resources.qh>
#include "g_damage.qh"
+#include "player.qh"
#include "race.qh"
#include "../common/mapobjects/teleporters.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "weapons/tracing.qh"
#include "../lib/warpzone/anglestransform.qh"
#include "../lib/warpzone/util_server.qh"
-void CopyBody(entity this, float keepvelocity);
-
#ifdef NOCHEATS
float CheatImpulse(entity this, int imp) { return 0; }
this.personal.origin = this.origin;
this.personal.v_angle = this.v_angle;
this.personal.velocity = this.velocity;
- this.personal.ammo_rockets = this.ammo_rockets;
- this.personal.ammo_nails = this.ammo_nails;
- this.personal.ammo_cells = this.ammo_cells;
- this.personal.ammo_plasma = this.ammo_plasma;
- this.personal.ammo_shells = this.ammo_shells;
- this.personal.ammo_fuel = this.ammo_fuel;
- this.personal.health = max(1, this.health);
- this.personal.armorvalue = this.armorvalue;
+ SetResourceAmount(this.personal, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS));
+ SetResourceAmount(this.personal, RESOURCE_BULLETS, GetResourceAmount(this, RESOURCE_BULLETS));
+ SetResourceAmount(this.personal, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS));
+ SetResourceAmount(this.personal, RESOURCE_PLASMA, GetResourceAmount(this, RESOURCE_PLASMA));
+ SetResourceAmount(this.personal, RESOURCE_SHELLS, GetResourceAmount(this, RESOURCE_SHELLS));
+ SetResourceAmount(this.personal, RESOURCE_FUEL, GetResourceAmount(this, RESOURCE_FUEL));
+ SetResourceAmount(this.personal, RESOURCE_HEALTH, max(1, GetResourceAmount(this, RESOURCE_HEALTH)));
+ SetResourceAmount(this.personal, RESOURCE_ARMOR, GetResourceAmount(this, RESOURCE_ARMOR));
STAT(WEAPONS, this.personal) = STAT(WEAPONS, this);
this.personal.items = this.items;
this.personal.pauserotarmor_finished = this.pauserotarmor_finished;
MUTATOR_CALLHOOK(AbortSpeedrun, this);
}
- this.ammo_rockets = this.personal.ammo_rockets;
- this.ammo_nails = this.personal.ammo_nails;
- this.ammo_cells = this.personal.ammo_cells;
- this.ammo_plasma = this.personal.ammo_plasma;
- this.ammo_shells = this.personal.ammo_shells;
- this.ammo_fuel = this.personal.ammo_fuel;
- this.health = this.personal.health;
- this.armorvalue = this.personal.armorvalue;
+ SetResourceAmount(this, RESOURCE_ROCKETS, GetResourceAmount(this.personal, RESOURCE_ROCKETS));
+ SetResourceAmount(this, RESOURCE_BULLETS, GetResourceAmount(this.personal, RESOURCE_BULLETS));
+ SetResourceAmount(this, RESOURCE_CELLS, GetResourceAmount(this.personal, RESOURCE_CELLS));
+ SetResourceAmount(this, RESOURCE_PLASMA, GetResourceAmount(this.personal, RESOURCE_PLASMA));
+ SetResourceAmount(this, RESOURCE_SHELLS, GetResourceAmount(this.personal, RESOURCE_SHELLS));
+ SetResourceAmount(this, RESOURCE_FUEL, GetResourceAmount(this.personal, RESOURCE_FUEL));
+ SetResourceAmount(this, RESOURCE_HEALTH, GetResourceAmount(this.personal, RESOURCE_HEALTH));
+ SetResourceAmount(this, RESOURCE_ARMOR, GetResourceAmount(this.personal, RESOURCE_ARMOR));
STAT(WEAPONS, this) = STAT(WEAPONS, this.personal);
this.items = this.personal.items;
this.pauserotarmor_finished = time + this.personal.pauserotarmor_finished - this.personal.teleport_time;
END_CHEAT_FUNCTION();
}
-void DragBox_Think(entity this);
float drag_lastcnt;
float CheatCommand(entity this, int argc)
{
entity e = spawn();
e.model = strzone(argv(1));
e.mdl = "rocket_explode";
- e.health = 1000;
+ SetResourceAmountExplicit(e, RESOURCE_HEALTH, 1000);
setorigin(e, trace_endpos);
e.effects = EF_NOMODELFLAGS;
if(f == 1)
END_CHEAT_FUNCTION();
}
-float Drag(entity this, float force_allow_pick, float ischeat);
-void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
-void Drag_Finish(entity dragger);
-float Drag_IsDraggable(entity draggee);
-float Drag_MayChangeAngles(entity draggee);
-void Drag_MoveForward(entity dragger);
-void Drag_SetSpeed(entity dragger, float s);
-void Drag_MoveBackward(entity dragger);
-void Drag_Update(entity dragger);
-float Drag_CanDrag(entity dragger);
-float Drag_IsDragging(entity dragger);
-void Drag_MoveDrag(entity from, entity to);
.entity dragentity;
float CheatFrame(entity this)
const float CHRAME_DRAG = 8;
void Drag_MoveDrag(entity from, entity to); // call this from CopyBody
+void DragBox_Think(entity this);
+float Drag(entity this, float force_allow_pick, float ischeat);
+void Drag_Begin(entity dragger, entity draggee, vector touchpoint);
+void Drag_Finish(entity dragger);
+float Drag_IsDraggable(entity draggee);
+float Drag_MayChangeAngles(entity draggee);
+void Drag_MoveForward(entity dragger);
+void Drag_SetSpeed(entity dragger, float s);
+void Drag_MoveBackward(entity dragger);
+void Drag_Update(entity dragger);
+float Drag_CanDrag(entity dragger);
+float Drag_IsDragging(entity dragger);
+void Drag_MoveDrag(entity from, entity to);
#include "handicap.qh"
#include "g_hook.qh"
#include "command/common.qh"
+#include "command/vote.qh"
#include "cheats.qh"
#include "g_world.qh"
#include "race.qh"
#include "../common/net_linked.qh"
#include "../common/physics/player.qh"
+#include <common/vehicles/sv_vehicles.qh>
+
#include "../common/items/_mod.qh"
#include "../common/mutators/mutator/waypoints/all.qh"
#include "../common/mutators/mutator/instagib/sv_instagib.qh"
+#include <common/gamemodes/_mod.qh>
#include "../common/mapobjects/subs.qh"
#include "../common/mapobjects/triggers.qh"
PutClientInServer(this);
}
-void PutObserverInServer(entity this);
-
STATIC_METHOD(Client, Remove, void(Client this))
{
TRANSMUTE(Observer, this);
});
}
-void SetSpectatee(entity this, entity spectatee);
-void SetSpectatee_status(entity this, int spectatee_num);
-
/*
=============
UpdatePlayerSounds(e);
}
-void FixPlayermodel(entity player);
/** putting a client as observer in the server */
void PutObserverInServer(entity this)
{
if (IS_PLAYER(this))
{
- if(this.health >= 1)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
{
// despawn effect
Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
if(this.damagedbycontents)
IL_REMOVE(g_damagedbycontents, this);
this.damagedbycontents = false;
- this.health = FRAGS_SPECTATOR;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, FRAGS_SPECTATOR);
SetSpectatee_status(this, etof(this));
this.takedamage = DAMAGE_NO;
this.solid = SOLID_NOT;
set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
this.flags = FL_CLIENT | FL_NOTARGET;
- this.armorvalue = 666;
this.effects = 0;
- this.armorvalue = autocvar_g_balance_armor_start;
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_balance_armor_start); // was 666?!
this.pauserotarmor_finished = 0;
this.pauserothealth_finished = 0;
this.pauseregen_finished = 0;
this.oldvelocity = this.velocity;
this.fire_endtime = -1;
this.event_damage = func_null;
+ this.event_heal = func_null;
for(int slot = 0; slot < MAX_AXH; ++slot)
{
this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
if (warmup_stage) {
- this.ammo_shells = warmup_start_ammo_shells;
- this.ammo_nails = warmup_start_ammo_nails;
- this.ammo_rockets = warmup_start_ammo_rockets;
- this.ammo_cells = warmup_start_ammo_cells;
- this.ammo_plasma = warmup_start_ammo_plasma;
- this.ammo_fuel = warmup_start_ammo_fuel;
- this.health = warmup_start_health;
- this.armorvalue = warmup_start_armorvalue;
+ SetResourceAmount(this, RESOURCE_SHELLS, warmup_start_ammo_shells);
+ SetResourceAmount(this, RESOURCE_BULLETS, warmup_start_ammo_nails);
+ SetResourceAmount(this, RESOURCE_ROCKETS, warmup_start_ammo_rockets);
+ SetResourceAmount(this, RESOURCE_CELLS, warmup_start_ammo_cells);
+ SetResourceAmount(this, RESOURCE_PLASMA, warmup_start_ammo_plasma);
+ SetResourceAmount(this, RESOURCE_FUEL, warmup_start_ammo_fuel);
+ SetResourceAmount(this, RESOURCE_HEALTH, warmup_start_health);
+ SetResourceAmount(this, RESOURCE_ARMOR, warmup_start_armorvalue);
STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
} else {
- this.ammo_shells = start_ammo_shells;
- this.ammo_nails = start_ammo_nails;
- this.ammo_rockets = start_ammo_rockets;
- this.ammo_cells = start_ammo_cells;
- this.ammo_plasma = start_ammo_plasma;
- this.ammo_fuel = start_ammo_fuel;
- this.health = start_health;
- this.armorvalue = start_armorvalue;
+ SetResourceAmount(this, RESOURCE_SHELLS, start_ammo_shells);
+ SetResourceAmount(this, RESOURCE_BULLETS, start_ammo_nails);
+ SetResourceAmount(this, RESOURCE_ROCKETS, start_ammo_rockets);
+ SetResourceAmount(this, RESOURCE_CELLS, start_ammo_cells);
+ SetResourceAmount(this, RESOURCE_PLASMA, start_ammo_plasma);
+ SetResourceAmount(this, RESOURCE_FUEL, start_ammo_fuel);
+ SetResourceAmount(this, RESOURCE_HEALTH, start_health);
+ SetResourceAmount(this, RESOURCE_ARMOR, start_armorvalue);
STAT(WEAPONS, this) = start_weapons;
if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
{
STAT(HUD, this) = HUD_NORMAL;
this.event_damage = PlayerDamage;
+ this.event_heal = PlayerHeal;
if(!this.bot_attack)
IL_PUSH(g_bot_targets, this);
this.(weaponentity).weaponname = "";
this.(weaponentity).m_switchingweapon = WEP_Null;
this.(weaponentity).cnt = -1;
+
+ W_WeaponFrame(this, weaponentity);
}
MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
}
}
-void ClientInit_misc(entity this);
-
// TODO do we need all these fields, or should we stop autodetecting runtime
// changes and just have a console command to update this?
bool ClientInit_SendEntity(entity this, entity to, int sf)
ClientKill_Now(this.owner);
return;
}
- else if(this.health == 1) // health == 1 means that it's silent
+ else if(this.count == 1) // count == 1 means that it's silent
{
this.nextthink = time + 1;
this.cnt -= 1;
void ClientKill (entity this)
{
+ // TODO: once .health is removed, will need to check it here for the "already dead" message!
+
if(game_stopped) return;
if(this.player_blocked) return;
if(STAT(FROZEN, this)) return;
JoinBestTeam(this, false); // if the team number is valid, keep it
this.playerid = playerid_save;
- if (autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) {
- TRANSMUTE(Observer, this);
- } else {
- if (!teamplay || autocvar_g_balance_teams) {
- TRANSMUTE(Player, this);
- campaign_bots_may_start = true;
- } else {
- TRANSMUTE(Observer, this); // do it anyway
- }
- }
+ TRANSMUTE(Observer, this);
PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
=============
*/
.entity chatbubbleentity;
-void ReadyCount();
void ClientDisconnect(entity this)
{
assert(IS_CLIENT(this), return);
PutClientInServer(this);
}
+void PrintToChat(entity client, string text)
+{
+ text = strcat("\{1}^7", text, "\n");
+ sprint(client, text);
+}
+
+void DebugPrintToChat(entity client, string text)
+{
+ if (autocvar_developer)
+ {
+ PrintToChat(client, text);
+ }
+}
+
+void PrintToChatAll(string text)
+{
+ text = strcat("\{1}^7", text, "\n");
+ bprint(text);
+}
+
+void DebugPrintToChatAll(string text)
+{
+ if (autocvar_developer)
+ {
+ PrintToChatAll(text);
+ }
+}
+
+void PrintToChatTeam(int team_num, string text)
+{
+ text = strcat("\{1}^7", text, "\n");
+ FOREACH_CLIENT(IS_REAL_CLIENT(it),
+ {
+ if (it.team == team_num)
+ {
+ sprint(it, text);
+ }
+ });
+}
+
+void DebugPrintToChatTeam(int team_num, string text)
+{
+ if (autocvar_developer)
+ {
+ PrintToChatTeam(team_num, text);
+ }
+}
+
void play_countdown(entity this, float finished, Sound samp)
{
TC(Sound, samp);
limith = limith * limit_mod;
limita = limita * limit_mod;
- this.armorvalue = CalcRotRegen(this.armorvalue, mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > this.pauserotarmor_finished), limita);
- this.health = CalcRotRegen(this.health, regen_health_stable, regen_health, regen_health_linear, regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, rot_mod * frametime * (time > this.pauserothealth_finished), limith);
+ SetResourceAmount(this, RESOURCE_ARMOR, CalcRotRegen(GetResourceAmount(this, RESOURCE_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear,
+ regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear,
+ rot_mod * frametime * (time > this.pauserotarmor_finished), limita));
+ SetResourceAmount(this, RESOURCE_HEALTH, CalcRotRegen(GetResourceAmount(this, RESOURCE_HEALTH), regen_health_stable, regen_health, regen_health_linear,
+ regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear,
+ rot_mod * frametime * (time > this.pauserothealth_finished), limith));
}
// if player rotted to death... die!
// check this outside above checks, as player may still be able to rot to death
- if(this.health < 1)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 1)
{
if(this.vehicle)
vehicles_exit(this.vehicle, VHEF_RELEASE);
minf = autocvar_g_balance_fuel_regenstable;
limitf = GetResourceLimit(this, RESOURCE_FUEL);
- this.ammo_fuel = CalcRotRegen(this.ammo_fuel, minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf);
- }
- // Ugly hack to make sure the health and armor don't go beyond hard limit.
- // TODO: Remove this hack when all code uses GivePlayerHealth and
- // GivePlayerArmor.
- if (this.health > RESOURCE_AMOUNT_HARD_LIMIT)
- {
- this.health = RESOURCE_AMOUNT_HARD_LIMIT;
- }
- if (this.armorvalue > RESOURCE_AMOUNT_HARD_LIMIT)
- {
- this.armorvalue = RESOURCE_AMOUNT_HARD_LIMIT;
+ SetResourceAmount(this, RESOURCE_FUEL, CalcRotRegen(GetResourceAmount(this, RESOURCE_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear,
+ frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0),
+ maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf));
}
- // End hack.
}
bool zoomstate_set;
MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
PS(this) = PS(spectatee);
this.armortype = spectatee.armortype;
- this.armorvalue = spectatee.armorvalue;
- this.ammo_cells = spectatee.ammo_cells;
- this.ammo_plasma = spectatee.ammo_plasma;
- this.ammo_shells = spectatee.ammo_shells;
- this.ammo_nails = spectatee.ammo_nails;
- this.ammo_rockets = spectatee.ammo_rockets;
- this.ammo_fuel = spectatee.ammo_fuel;
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, GetResourceAmount(spectatee, RESOURCE_ARMOR));
+ SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(spectatee, RESOURCE_CELLS));
+ SetResourceAmountExplicit(this, RESOURCE_PLASMA, GetResourceAmount(spectatee, RESOURCE_PLASMA));
+ SetResourceAmountExplicit(this, RESOURCE_SHELLS, GetResourceAmount(spectatee, RESOURCE_SHELLS));
+ SetResourceAmountExplicit(this, RESOURCE_BULLETS, GetResourceAmount(spectatee, RESOURCE_BULLETS));
+ SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(spectatee, RESOURCE_ROCKETS));
+ SetResourceAmountExplicit(this, RESOURCE_FUEL, GetResourceAmount(spectatee, RESOURCE_FUEL));
this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
- this.health = spectatee.health;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(spectatee, RESOURCE_HEALTH));
CS(this).impulse = 0;
this.items = spectatee.items;
STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
}
}
+const int MIN_SPEC_TIME = 1;
bool joinAllowed(entity this)
{
if (CS(this).version_mismatch) return false;
+ if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
if (!nJoinAllowed(this, this)) return false;
if (teamplay && lockteams) return false;
if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
}
this.items_added = 0;
- if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || this.ammo_fuel >= 0.01))
+ if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResourceAmount(this, RESOURCE_FUEL) >= 0.01))
this.items_added |= IT_FUEL;
this.items |= this.items_added;
this.flags |= FL_CLIENT | FL_NOTARGET;
}
-void vehicles_enter (entity pl, entity veh);
void PlayerUseKey(entity this)
{
if (!IS_PLAYER(this))
if (STAT(FROZEN, this) == 2)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
- this.health = max(1, STAT(REVIVE_PROGRESS, this) * start_health);
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
if (STAT(REVIVE_PROGRESS, this) >= 1)
else if (STAT(FROZEN, this) == 3)
{
STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
- this.health = max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this) );
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
- if (this.health < 1)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
{
if (this.vehicle)
vehicles_exit(this.vehicle, VHEF_RELEASE);
PrintWelcomeMessage(this);
if (IS_PLAYER(this)) {
+ if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
+ error("Client can't be spawned as player on connection!");
if(!PlayerThink(this))
return;
}
IntermissionThink(this);
return;
}
+ else if (IS_REAL_CLIENT(this) && !CS(this).autojoin_checked && time >= CS(this).jointime + MIN_SPEC_TIME)
+ {
+ CS(this).autojoin_checked = true;
+ // don't do this in ClientConnect
+ // many things can go wrong if a client is spawned as player on connection
+ if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
+ || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+ && (!teamplay || autocvar_g_balance_teams)))
+ {
+ campaign_bots_may_start = true;
+ Join(this);
+ return;
+ }
+ }
else if (IS_OBSERVER(this)) {
ObserverThink(this);
}
}
if (this.waypointsprite_attachedforcarrier) {
- vector v = healtharmor_maxdamage(this.health, this.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
+ vector v = healtharmor_maxdamage(GetResourceAmount(this, RESOURCE_HEALTH), GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id);
WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v);
}
ATTRIB(Client, cmd_floodtime, float, this.cmd_floodtime);
ATTRIB(Client, wasplayer, bool, this.wasplayer);
ATTRIB(Client, weaponorder_byimpulse, string, this.weaponorder_byimpulse);
+ ATTRIB(Client, autojoin_checked, bool, this.wasplayer);
// networked cvars
return false;
}
+/// \brief Print the string to the client's chat.
+/// \param[in] client Client to print to.
+/// \param[in] text Text to print.
+void PrintToChat(entity client, string text);
+
+/// \brief Print the string to the client's chat if the server cvar "developer"
+/// is not 0.
+/// \param[in] client Client to print to.
+/// \param[in] text Text to print.
+void DebugPrintToChat(entity client, string text);
+
+/// \brief Prints the string to all clients' chat.
+/// \param[in] text Text to print.
+void PrintToChatAll(string text);
+
+/// \brief Prints the string to all clients' chat if the server cvar "developer"
+/// is not 0.
+/// \param[in] text Text to print.
+void DebugPrintToChatAll(string text);
+
+/// \brief Print the string to chat of all clients of the specified team.
+/// \param[in] team_num Team to print to. See NUM_TEAM constants.
+/// \param[in] text Text to print.
+void PrintToChatTeam(int team_num, string text);
+
+/// \brief Print the string to chat of all clients of the specified team if the
+/// server cvar "developer" is not 0.
+/// \param[in] team_num Team to print to. See NUM_TEAM constants.
+/// \param[in] text Text to print.
+void DebugPrintToChatTeam(int team_num, string text);
+
void play_countdown(entity this, float finished, Sound samp);
float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit);
bool Spectate(entity this, entity pl);
+void ClientInit_Spawn();
+
+void PutObserverInServer(entity this);
+
+void SetSpectatee(entity this, entity spectatee);
+void SetSpectatee_status(entity this, int spectatee_num);
+
+void FixPlayermodel(entity player);
+
+void ClientInit_misc(entity this);
+
+void ClientKill_TeamChange(entity this, float targetteam); // 0 = don't change, -1 = auto, -2 = spec
+
+bool joinAllowed(entity this);
+void Join(entity this);
+
#define SPECTATE_COPY() ACCUMULATE void SpectateCopy(entity this, entity spectatee)
#define SPECTATE_COPYFIELD(fld) SPECTATE_COPY() { this.(fld) = spectatee.(fld); }
#define GET_BAN_ARG(v, d) if (argc > reason_arg) { if ((v = stof(argv(reason_arg))) != 0) ++reason_arg; else v = d; } else { v = d; }
#define GET_BAN_REASON(v, d) if (argc > reason_arg) v = substring(command, argv_start_index(reason_arg), strlen(command) - argv_start_index(reason_arg)); else v = d;
-void Ban_KickBanClient(entity client, float bantime, float masksize, string reason);
-void Ban_View();
-float Ban_Insert(string ip, float bantime, string reason, float dosync);
-float Ban_Delete(float i);
-
// used by common/command/generic.qc:GenericCommand_dumpcommands to list all commands into a .txt file
void BanCommand_macro_write_aliases(float fh);
#include "../campaign.qh"
#include "../cheats.qh"
+#include "../client.qh"
#include "../player.qh"
#include "../ipban.qh"
#include "../mapvoting.qh"
#include "../scores.qh"
#include "../teamplay.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
#ifdef SVQC
#include <common/vehicles/all.qh>
#include <lib/warpzone/common.qh>
-void ClientKill_TeamChange(entity this, float targetteam); // 0 = don't change, -1 = auto, -2 = spec
-
// =========================================================
// Server side networked commands code, reworked by Samual
// Last updated: December 28th, 2011
}
}
-bool joinAllowed(entity this);
-void Join(entity this);
void ClientCommand_join(entity caller, float request)
{
switch (request)
if (mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
if (!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
- Damage(mon, NULL, NULL, mon.health + mon.max_health + 200, DEATH_KILL.m_id, DMG_NOWEP, mon.origin, '0 0 0');
+ Damage(mon, NULL, NULL, GetResourceAmount(mon, RESOURCE_HEALTH) + mon.max_health + 200, DEATH_KILL.m_id, DMG_NOWEP, mon.origin, '0 0 0');
print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated"));
return;
}
#include <common/command/_mod.qh>
#include "../g_world.qh"
-#include "../g_subs.qh"
#include <common/util.qh>
#include "../bot/api.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
#include <common/constants.qh>
#include <common/net_linked.qh>
#include <common/monsters/sv_monsters.qh>
-
-void PutObserverInServer(entity this);
-
-// =====================================================
-// Server side game commands code, reworked by Samual
-// =====================================================
-
// used by GameCommand_make_mapinfo()
void make_mapinfo_Think(entity this)
{
if (argc == 4 || argc == 5)
{
e = nextent(NULL);
+ int dphitcontentsmask_save = e.dphitcontentsmask;
+ e.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
if (tracewalk(e, stov(argv(2)), e.mins, e.maxs, stov(argv(3)), stof(argv(4)), MOVE_NORMAL))
LOG_INFO("can walk");
else
LOG_INFO("cannot walk");
+ e.dphitcontentsmask = dphitcontentsmask_save;
return;
}
}
#include "../round_handler.qh"
#include "../scores.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
+#include <common/gamemodes/_mod.qh>
#include <common/constants.qh>
#include <common/net_linked.qh>
void ReadyCount();
void ReadyRestart_force();
void VoteCount(float first_count);
+void Nagger_Init();
#include <server/miscfunctions.qh>
#include <server/items.qh>
#include <server/resources.qh>
+#include <common/t_items.qh>
+#include <common/mapobjects/triggers.qh>
#include <common/weapons/_all.qh>
-spawnfunc(target_items);
-
//***********************
//QUAKE 3 ENTITIES - So people can play quake3 maps with the xonotic weapons
//***********************
IL_EACH(g_items, it.targetname == this.target,
{
if (it.classname == "weapon_devastator") {
- this.ammo_rockets += it.count * WEP_CVAR(devastator, ammo);
+ SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS) + it.count * WEP_CVAR_PRI(devastator, ammo)); // WEAPONTODO
this.netname = cons(this.netname, "devastator");
}
else if (it.classname == "weapon_vortex") {
- this.ammo_cells += it.count * WEP_CVAR_PRI(vortex, ammo); // WEAPONTODO
+ SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS) + it.count * WEP_CVAR_PRI(vortex, ammo)); // WEAPONTODO
this.netname = cons(this.netname, "vortex");
}
else if (it.classname == "weapon_electro") {
- this.ammo_cells += it.count * WEP_CVAR_PRI(electro, ammo); // WEAPONTODO
+ SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS) + it.count * WEP_CVAR_PRI(electro, ammo)); // WEAPONTODO
this.netname = cons(this.netname, "electro");
}
else if (it.classname == "weapon_hagar") {
- this.ammo_rockets += it.count * WEP_CVAR_PRI(hagar, ammo); // WEAPONTODO
+ SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS) + it.count * WEP_CVAR_PRI(hagar, ammo)); // WEAPONTODO
this.netname = cons(this.netname, "hagar");
}
else if (it.classname == "weapon_crylink") {
- this.ammo_cells += it.count * WEP_CVAR_PRI(crylink, ammo);
+ SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(this, RESOURCE_CELLS) + it.count * WEP_CVAR_PRI(crylink, ammo)); // WEAPONTODO
this.netname = cons(this.netname, "crylink");
}
else if (it.classname == "weapon_mortar") {
- this.ammo_rockets += it.count * WEP_CVAR_PRI(mortar, ammo); // WEAPONTODO
+ SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(this, RESOURCE_ROCKETS) + it.count * WEP_CVAR_PRI(mortar, ammo)); // WEAPONTODO
this.netname = cons(this.netname, "mortar");
}
else if (it.classname == "item_armor_mega")
- this.armorvalue = 100;
+ SetResourceAmountExplicit(this, RESOURCE_ARMOR, 100);
else if (it.classname == "item_health_mega")
- this.health = 200;
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 200);
//remove(it); // removing ents in init functions causes havoc, workaround:
setthink(it, SUB_Remove);
it.nextthink = time;
#pragma once
+
+bool DoesQ3ARemoveThisEntity(entity this);
.void(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) event_damage;
+.bool(entity targ, entity inflictor, float amount, float limit) event_heal;
+
//.string wad;
//.string map;
#include <common/effects/all.qh>
#include "bot/api.qh"
#include "g_hook.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "scores.qh"
#include "spawnpoints.qh"
#include "../common/state.qh"
#include "../common/items/_mod.qh"
#include "../common/mutators/mutator/waypoints/waypointsprites.qh"
#include "../common/mutators/mutator/instagib/sv_instagib.qh"
+#include "../common/mutators/mutator/buffs/buffs.qh"
#include "weapons/accuracy.qh"
#include "weapons/csqcprojectile.qh"
#include "weapons/selection.qh"
#include "../common/playerstats.qh"
#include "../common/teams.qh"
#include "../common/util.qh"
+#include <common/gamemodes/rules.qh>
#include <common/weapons/_all.qh>
#include "../lib/csqcmodel/sv_model.qh"
#include "../lib/warpzone/common.qh"
return MUTATOR_CALLHOOK(FragCenterMessage, attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target);
}
-entity buff_FirstFromFlags(int _buffs);
void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .entity weaponentity)
{
// Sanity check
// These are ALWAYS lethal
// No damage modification here
// Instead, prepare the victim for his death...
- SetResourceAmount(targ, RESOURCE_ARMOR, 0);
+ SetResourceAmountExplicit(targ, RESOURCE_ARMOR, 0);
targ.spawnshieldtime = 0;
- SetResourceAmount(targ, RESOURCE_HEALTH, 0.9); // this is < 1
+ SetResourceAmountExplicit(targ, RESOURCE_HEALTH, 0.9); // this is < 1
targ.flags -= targ.flags & FL_GODMODE;
damage = 100000;
}
if(autocvar_g_mirrordamage_virtual)
{
- vector v = healtharmor_applydamage(attacker.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
+ vector v = healtharmor_applydamage(GetResourceAmount(attacker, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, mirrordamage);
attacker.dmg_take += v.x;
attacker.dmg_save += v.y;
attacker.dmg_inflictor = inflictor;
if(autocvar_g_friendlyfire_virtual)
{
- vector v = healtharmor_applydamage(targ.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+ vector v = healtharmor_applydamage(GetResourceAmount(targ, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
targ.dmg_take += v.x;
targ.dmg_save += v.y;
targ.dmg_inflictor = inflictor;
return RadiusDamageForSource (inflictor, (inflictor.origin + (inflictor.mins + inflictor.maxs) * 0.5), inflictor.velocity, attacker, coredamage, edgedamage, rad, cantbe, mustbe, false, forceintensity, deathtype, weaponentity, directhitentity);
}
+bool Heal(entity targ, entity inflictor, float amount, float limit)
+{
+ if(game_stopped || (IS_CLIENT(targ) && CS(targ).killcount == FRAGS_SPECTATOR) || STAT(FROZEN, targ) || IS_DEAD(targ))
+ return false;
+
+ bool healed = false;
+ if(targ.event_heal)
+ healed = targ.event_heal(targ, inflictor, amount, limit);
+ // TODO: additional handling? what if the healing kills them? should this abort if healing would do so etc
+ // TODO: healing fx!
+ // TODO: armor healing?
+ return healed;
+}
+
float Fire_IsBurning(entity e)
{
return (time < e.fire_endtime);
#include "defs.qh"
#include <common/notifications/all.qh>
#include <common/deathtypes/all.qh>
- #include "mutators/_mod.qh"
+ #include <server/mutators/_mod.qh>
#include <common/turrets/sv_turrets.qh>
#include <common/vehicles/all.qh>
#include <lib/csqcmodel/sv_model.qh>
float RadiusDamage (entity inflictor, entity attacker, float coredamage, float edgedamage, float rad, entity cantbe, entity mustbe, float forceintensity, int deathtype, .entity weaponentity, entity directhitentity);
+// Calls .event_heal on the target so that they can handle healing themselves
+// a limit of RESOURCE_LIMIT_NONE should be handled by the entity as its max health (if applicable)
+bool Heal(entity targ, entity inflictor, float amount, float limit);
+
.float fire_damagepersec;
.float fire_endtime;
.float fire_deathtype;
RemoveHook(this);
}
-void GrapplingHookThink(entity this);
void GrapplingHook_Stop(entity this)
{
Send_Effect(EFFECT_HOOK_IMPACT, this.origin, '0 0 0', 1);
GrapplingHook_Stop(this);
if(toucher)
- if(toucher.move_movetype != MOVETYPE_NONE)
+ //if(toucher.move_movetype != MOVETYPE_NONE)
{
SetMovetypeFollow(this, toucher);
WarpZone_RefSys_BeginAddingIncrementally(this, this.aiment);
void GrapplingHook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
{
- if(this.health <= 0)
+ if(GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
return;
if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
return; // g_balance_projectiledamage says to halt
- this.health = this.health - damage;
+ TakeResource(this, RESOURCE_HEALTH, damage);
- if (this.health <= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= 0)
{
if(attacker != this.realowner)
{
if(forbidWeaponUse(actor)) return;
if(actor.vehicle) return;
- makevectors(actor.v_angle);
-
- int s = W_GunAlign(actor.(weaponentity), STAT(GUNALIGN, actor)) - 1;
- vector vs = hook_shotorigin[s];
-
- // UGLY WORKAROUND: play this on CH_WEAPON_B so it can't cut off fire sounds
- sound (actor, CH_WEAPON_B, SND_HOOK_FIRE, VOL_BASE, ATTEN_NORM);
- vector org = actor.origin + actor.view_ofs + v_forward * vs.x + v_right * -vs.y + v_up * vs.z;
-
- tracebox(actor.origin + actor.view_ofs, '-3 -3 -3', '3 3 3', org, MOVE_NORMAL, actor);
- org = trace_endpos;
-
- Send_Effect(EFFECT_HOOK_MUZZLEFLASH, org, '0 0 0', 1);
+ // TODO: offhand hook shoots from eye
+ W_SetupShot_ProjectileSize(actor, weaponentity, '-3 -3 -3', '3 3 3', true, 0, SND_HOOK_FIRE, CH_WEAPON_B, 0, WEP_HOOK.m_id);
+ Send_Effect(EFFECT_HOOK_MUZZLEFLASH, w_shotorg, '0 0 0', 1);
entity missile = WarpZone_RefSys_SpawnSameRefSys(actor);
missile.owner = missile.realowner = actor;
//setmodel (missile, MDL_HOOK); // precision set below
setsize (missile, '-3 -3 -3', '3 3 3');
- setorigin(missile, org);
+ setorigin(missile, w_shotorg);
missile.state = 0; // not latched onto anything
- W_SetupProjVelocity_Explicit(missile, v_forward, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false);
+ W_SetupProjVelocity_Explicit(missile, w_shotdir, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false);
missile.angles = vectoangles (missile.velocity);
//missile.glow_color = 250; // 244, 250
missile.effects = /*EF_FULLBRIGHT | EF_ADDITIVE |*/ EF_LOWPRECISION;
- missile.health = autocvar_g_balance_grapplehook_health;//120
+ SetResourceAmountExplicit(missile, RESOURCE_HEALTH, autocvar_g_balance_grapplehook_health);
missile.event_damage = GrapplingHook_Damage;
missile.takedamage = DAMAGE_AIM;
missile.damageforcescale = 0;
// Wazat's grappling hook
.entity hook;
+void GrapplingHookThink(entity this);
void RemoveGrapplingHooks(entity pl);
void RemoveHook(entity this);
// (note: you can change the hook impulse #'s to whatever you please)
+++ /dev/null
-#include "g_subs.qh"
-
-#include <server/defs.qh>
-#include <server/miscfunctions.qh>
-#include "antilag.qh"
-#include "command/common.qh"
-#include "../common/state.qh"
-#include "../lib/warpzone/common.qh"
-#include "../common/mapobjects/subs.qh"
-
-
-/*
-==================
-main
-
-unused but required by the engine
-==================
-*/
-void main ()
-{
-
-}
-
-// Misc
-
-/*
-==================
-traceline_antilag
-
-A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
-Additionally it moves players back into the past before the trace and restores them afterward.
-==================
-*/
-void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
-{
- // check whether antilagged traces are enabled
- if (lag < 0.001)
- lag = 0;
- if (!IS_REAL_CLIENT(forent))
- lag = 0; // only antilag for clients
-
- // change shooter to SOLID_BBOX so the shot can hit corpses
- int oldsolid = source.dphitcontentsmask;
- if(source)
- source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
-
- if (lag)
- antilag_takeback_all(forent, lag);
-
- // do the trace
- if(wz)
- WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
- else
- tracebox (v1, mi, ma, v2, nomonst, forent);
-
- // restore players to current positions
- if (lag)
- antilag_restore_all(forent);
-
- // restore shooter solid type
- if(source)
- source.dphitcontentsmask = oldsolid;
-}
-void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
- tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, false);
-}
-void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
- bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
- if (autocvar_g_antilag != 2 || noantilag)
- lag = 0;
- traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
-}
-void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
-{
- bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
- if (autocvar_g_antilag != 2 || noantilag)
- lag = 0;
- tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, false);
-}
-void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
- tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, true);
-}
-void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
-{
- bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
- if (autocvar_g_antilag != 2 || noantilag)
- lag = 0;
- WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
-}
-void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
-{
- bool noantilag = ((IS_CLIENT(source)) ? CS(source).cvar_cl_noantilag : false);
- if (autocvar_g_antilag != 2 || noantilag)
- lag = 0;
- tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, true);
-}
-
-float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity) // returns the number of traces done, for benchmarking
-{
- vector pos, dir, t;
- float nudge;
- entity stopentity;
-
- //nudge = 2 * cvar("collision_impactnudge"); // why not?
- nudge = 0.5;
-
- dir = normalize(v2 - v1);
-
- pos = v1 + dir * nudge;
-
- float c;
- c = 0;
-
- for (;;)
- {
- if(pos * dir >= v2 * dir)
- {
- // went too far
- trace_fraction = 1;
- trace_endpos = v2;
- return c;
- }
-
- tracebox(pos, mi, ma, v2, nomonsters, forent);
- ++c;
-
- if(c == 50)
- {
- LOG_TRACE("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2));
- LOG_TRACE(" Nudging gets us nowhere at ", vtos(pos));
- LOG_TRACE(" trace_endpos is ", vtos(trace_endpos));
- LOG_TRACE(" trace distance is ", ftos(vlen(pos - trace_endpos)));
- }
-
- stopentity = trace_ent;
-
- if(trace_startsolid)
- {
- // we started inside solid.
- // then trace from endpos to pos
- t = trace_endpos;
- tracebox(t, mi, ma, pos, nomonsters, forent);
- ++c;
- if(trace_startsolid)
- {
- // t is still inside solid? bad
- // force advance, then, and retry
- pos = t + dir * nudge;
-
- // but if we hit an entity, stop RIGHT before it
- if(stopatentity && stopentity && stopentity != ignorestopatentity)
- {
- trace_ent = stopentity;
- trace_endpos = t;
- trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
- return c;
- }
- }
- else
- {
- // we actually LEFT solid!
- trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
- return c;
- }
- }
- else
- {
- // pos is outside solid?!? but why?!? never mind, just return it.
- trace_endpos = pos;
- trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
- return c;
- }
- }
-}
-
-void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity)
-{
- tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
-}
-
-/*
-==================
-findbetterlocation
-
-Returns a point at least 12 units away from walls
-(useful for explosion animations, although the blast is performed where it really happened)
-Ripped from DPMod
-==================
-*/
-vector findbetterlocation (vector org, float mindist)
-{
- vector vec = mindist * '1 0 0';
- int c = 0;
- while (c < 6)
- {
- traceline (org, org + vec, true, NULL);
- vec = vec * -1;
- if (trace_fraction < 1)
- {
- vector loc = trace_endpos;
- traceline (loc, loc + vec, true, NULL);
- if (trace_fraction >= 1)
- org = loc + vec;
- }
- if (c & 1)
- {
- float h = vec.y;
- vec.y = vec.x;
- vec.x = vec.z;
- vec.z = h;
- }
- c = c + 1;
- }
-
- return org;
-}
-
-bool LOD_customize(entity this, entity client)
-{
- if(autocvar_loddebug)
- {
- int d = autocvar_loddebug;
- if(d == 1)
- this.modelindex = this.lodmodelindex0;
- else if(d == 2 || !this.lodmodelindex2)
- this.modelindex = this.lodmodelindex1;
- else // if(d == 3)
- this.modelindex = this.lodmodelindex2;
- return true;
- }
-
- // TODO csqc network this so it only gets sent once
- vector near_point = NearestPointOnBox(this, client.origin);
- if(vdist(near_point - client.origin, <, this.loddistance1))
- this.modelindex = this.lodmodelindex0;
- else if(!this.lodmodelindex2 || vdist(near_point - client.origin, <, this.loddistance2))
- this.modelindex = this.lodmodelindex1;
- else
- this.modelindex = this.lodmodelindex2;
-
- return true;
-}
-
-void LOD_uncustomize(entity this)
-{
- this.modelindex = this.lodmodelindex0;
-}
-
-void LODmodel_attach(entity this)
-{
- entity e;
-
- if(!this.loddistance1)
- this.loddistance1 = 1000;
- if(!this.loddistance2)
- this.loddistance2 = 2000;
- this.lodmodelindex0 = this.modelindex;
-
- if(this.lodtarget1 != "")
- {
- e = find(NULL, targetname, this.lodtarget1);
- if(e)
- {
- this.lodmodel1 = e.model;
- delete(e);
- }
- }
- if(this.lodtarget2 != "")
- {
- e = find(NULL, targetname, this.lodtarget2);
- if(e)
- {
- this.lodmodel2 = e.model;
- delete(e);
- }
- }
-
- if(autocvar_loddebug < 0)
- {
- this.lodmodel1 = this.lodmodel2 = ""; // don't even initialize
- }
-
- if(this.lodmodel1 != "")
- {
- vector mi, ma;
- mi = this.mins;
- ma = this.maxs;
-
- precache_model(this.lodmodel1);
- _setmodel(this, this.lodmodel1);
- this.lodmodelindex1 = this.modelindex;
-
- if(this.lodmodel2 != "")
- {
- precache_model(this.lodmodel2);
- _setmodel(this, this.lodmodel2);
- this.lodmodelindex2 = this.modelindex;
- }
-
- this.modelindex = this.lodmodelindex0;
- setsize(this, mi, ma);
- }
-
- if(this.lodmodelindex1)
- if (!getSendEntity(this))
- SetCustomizer(this, LOD_customize, LOD_uncustomize);
-}
+++ /dev/null
-#pragma once
-
-void SUB_NullThink(entity this);
-
-/*
-==================
-SUB_Friction
-
-Applies some friction to this
-==================
-*/
-.float friction;
-void SUB_Friction (entity this);
-
-/*
-==================
-SUB_VanishOrRemove
-
-Makes client invisible or removes non-client
-==================
-*/
-void SUB_VanishOrRemove (entity ent);
-
-void SUB_SetFade_Think (entity this);
-
-/*
-==================
-SUB_SetFade
-
-Fade 'ent' out when time >= 'when'
-==================
-*/
-void SUB_SetFade (entity ent, float when, float fadetime);
-
-/*
-==================
-main
-
-unused but required by the engine
-==================
-*/
-void main ();
-
-// Misc
-
-/*
-==================
-traceline_antilag
-
-A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
-Additionally it moves players back into the past before the trace and restores them afterward.
-==================
-*/
-void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz);
-void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
-void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag);
-
-float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity); // returns the number of traces done, for benchmarking
-
-void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent, float stopatentity, entity ignorestopatentity);
-
-/*
-==================
-findbetterlocation
-
-Returns a point at least 12 units away from walls
-(useful for explosion animations, although the blast is performed where it really happened)
-Ripped from DPMod
-==================
-*/
-vector findbetterlocation (vector org, float mindist);
-
-
-.string lodtarget1;
-.string lodtarget2;
-.string lodmodel1;
-.string lodmodel2;
-.float lodmodelindex0;
-.float lodmodelindex1;
-.float lodmodelindex2;
-.float loddistance1;
-.float loddistance2;
-
-bool LOD_customize(entity this, entity client);
-
-void LOD_uncustomize(entity this);
-
-void LODmodel_attach(entity this);
#include "g_hook.qh"
#include "ipban.qh"
#include "mapvoting.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "race.qh"
#include "scores.qh"
#include "teamplay.qh"
#include "../common/constants.qh"
#include <common/net_linked.qh>
#include "../common/deathtypes/all.qh"
+#include "../common/gamemodes/sv_rules.qh"
#include "../common/mapinfo.qh"
#include "../common/monsters/_mod.qh"
#include "../common/monsters/sv_monsters.qh"
string redirection_target;
float world_initialized;
-string GetGametype();
-void ShuffleMaplist();
-
void SetDefaultAlpha()
{
if (!MUTATOR_CALLHOOK(SetDefaultAlpha))
BADPREFIX("gameversion_");
BADPREFIX("g_chat_");
BADPREFIX("g_ctf_captimerecord_");
+ BADPREFIX("g_hats_");
BADPREFIX("g_maplist_");
BADPREFIX("g_mod_");
BADPREFIX("g_respawn_");
}
}
-void Map_MarkAsRecent(string m);
float world_already_spawned;
-void Nagger_Init();
-void ClientInit_Spawn();
-void WeaponStats_Init();
-void WeaponStats_Shutdown();
spawnfunc(worldspawn)
{
server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
// return codes of map selectors:
// -1 = temporary failure (that is, try some method that is guaranteed to succeed)
// -2 = permanent failure
-float() MaplistMethod_Iterate = // usual method
+float MaplistMethod_Iterate() // usual method
{
float pass, i;
return -1;
}
-float() MaplistMethod_Repeat = // fallback method
+float MaplistMethod_Repeat() // fallback method
{
LOG_TRACE("Trying MaplistMethod_Repeat");
return -2;
}
-float() MaplistMethod_Random = // random map selection
+float MaplistMethod_Random() // random map selection
{
float i, imax;
return -1;
}
-float(float exponent) MaplistMethod_Shuffle = // more clever shuffling
+float MaplistMethod_Shuffle(float exponent) // more clever shuffling
// the exponent sets a bias on the map selection:
// the higher the exponent, the less likely "shortly repeated" same maps are
{
if(!e.autoscreenshot) // initial call
{
e.autoscreenshot = time + 0.8; // used for autoscreenshot
- e.health = -2342;
+ SetResourceAmountExplicit(e, RESOURCE_HEALTH, -2342);
// first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
{
return true;
}
-void TargetMusic_RestoreGame();
void RestoreGame()
{
// Loaded from a save game
void GotoNextMap(float reinit);
void ReadyRestart();
+string GetGametype();
+
void DumpStats(float final);
float Map_IsRecent(string m);
string GetNextMap();
void ShuffleMaplist();
void Map_Goto_SetStr(string nextmapname);
void Map_Goto(float reinit);
+void Map_MarkAsRecent(string m);
float DoNextMapOverride(float reinit);
void CheckRules_World();
+float RedirectionThink();
#define MAX_IPBAN_URIS (URI_GET_IPBAN_END - URI_GET_IPBAN + 1)
-float Ban_Insert(string ip, float bantime, string reason, float dosync);
-
void OnlineBanList_SendBan(string ip, float bantime, string reason)
{
string uri;
float Ban_MaybeEnforceBanOnce(entity client);
float BanCommand(string command);
+float Ban_Insert(string ip, float bantime, string reason, float dosync);
+void Ban_KickBanClient(entity client, float bantime, float masksize, string reason);
+void Ban_View();
+float Ban_Delete(float i);
+
void OnlineBanList_URI_Get_Callback(float id, float status, string data);
#include "item_key.qh"
#include "../common/mapobjects/subs.qh"
+#include <common/mapobjects/triggers.qh>
#include "../common/monsters/_mod.qh"
#include "../common/notifications/all.qh"
#include "../common/util.qh"
this.message = "";
SUB_UseTargets(this, toucher, toucher); // TODO: should we be using toucher for the trigger here?
this.message = oldmsg;
-};
+}
/**
* Spawn a key with given model, key code and color.
}
settouch(this, item_key_touch);
-};
+}
/*QUAKED item_key (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
this.classname = "item_key";
this.itemkeys = ITEM_KEY_BIT(1);
spawnfunc_item_key(this);
-};
+}
/*QUAKED item_key2 (0 .5 .8) (-16 -16 -24) (16 16 32) FLOATING
GOLD key.
this.classname = "item_key";
this.itemkeys = ITEM_KEY_BIT(0);
spawnfunc_item_key(this);
-};
+}
/// game items.
/// \copyright GNU GPLv2 or any later version.
-#include "g_subs.qh"
-#include "mutators/events.qh"
+#include <server/mutators/_mod.qh>
#include <common/weapons/all.qh>
+#include <common/mapobjects/subs.qh>
.bool m_isloot; ///< Holds whether item is loot.
/// \brief Holds whether strength, shield or superweapon timers expire while
int totalvotes = 0;
FOREACH_CLIENT(IS_REAL_CLIENT(it), {
// hide scoreboard again
- if(it.health != 2342)
+ if(GetResourceAmount(it, RESOURCE_HEALTH) != 2342)
{
- it.health = 2342;
+ SetResourceAmountExplicit(it, RESOURCE_HEALTH, 2342);
CS(it).impulse = 0;
msg_entity = it;
#include "constants.qh"
#include "g_hook.qh"
#include "ipban.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "../common/t_items.qh"
#include "resources.qh"
#include "items.qh"
+#include "player.qh"
#include "weapons/accuracy.qh"
#include "weapons/csqcprojectile.qh"
#include "weapons/selection.qh"
#include "../common/command/_mod.qh"
#include "../common/constants.qh"
#include <common/net_linked.qh>
+#include <common/weapons/weapon/crylink.qh>
#include "../common/deathtypes/all.qh"
#include "../common/mapinfo.qh"
#include "../common/notifications/all.qh"
WarpZone_traceline_antilag(pl, CS(pl).cursor_trace_start, CS(pl).cursor_trace_start + normalize(CS(pl).cursor_trace_endpos - CS(pl).cursor_trace_start) * max_shot_distance, MOVE_NORMAL, pl, ANTILAG_LATENCY(pl));
}
+void dedicated_print(string input)
+{
+ if (server_is_dedicated) print(input);
+}
void GameLogEcho(string s)
{
case "%": replacement = "%"; break;
case "\\":replacement = "\\"; break;
case "n": replacement = "\n"; break;
- case "a": replacement = ftos(floor(this.armorvalue)); break;
- case "h": replacement = ftos(floor(this.health)); break;
+ case "a": replacement = ftos(floor(GetResourceAmount(this, RESOURCE_ARMOR))); break;
+ case "h": replacement = ftos(floor(GetResourceAmount(this, RESOURCE_HEALTH))); break;
case "l": replacement = NearestLocation(this.origin); break;
case "y": replacement = NearestLocation(cursor); break;
case "d": replacement = NearestLocation(this.death_origin); break;
if(trace_dphitcontents == 0)
{
LOG_TRACEF("A hit from a projectile happened with no hit contents! DEBUG THIS, this should never happen for projectiles! Projectile will self-destruct. (edict: %i, classname: %s, origin: %v)", this, this.classname, this.origin);
- checkclient(this);
+ checkclient(this); // TODO: .health is checked in the engine with this, possibly replace with a QC function?
}
if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
return true;
#define SUB_OwnerCheck(ent,oth) ((oth) && ((oth) == (ent).owner))
-void W_Crylink_Dequeue(entity e);
bool WarpZone_Projectile_Touch_ImpactFilter_Callback(entity this, entity toucher)
{
if(SUB_OwnerCheck(this, toucher))
#pragma once
#include <server/defs.qh>
+#include <server/g_world.qh>
#include <common/t_items.qh>
-#include "mutators/events.qh"
+#include <server/mutators/_mod.qh>
#include <common/constants.qh>
#include <common/mapinfo.qh>
string formatmessage(entity this, string msg);
+/** print(), but only print if the server is not local */
+void dedicated_print(string input);
+
void GameLogEcho(string s);
void GameLogInit();
float sv_autotaunt;
float sv_taunt;
-string GetGametype(); // g_world.qc
void readlevelcvars()
{
if(cvar("sv_allow_fullbright"))
// generated file; do not modify
+#include <server/mutators/events.qc>
#include <server/mutators/loader.qc>
-
-#include <server/mutators/mutator/_mod.inc>
// generated file; do not modify
+#include <server/mutators/events.qh>
#include <server/mutators/loader.qh>
-
-#include <server/mutators/mutator/_mod.qh>
--- /dev/null
+#include "events.qh"
/**/
MUTATOR_HOOKABLE(ForbidSpawn, EV_ForbidSpawn);
+/** returns true if client should be put as player on connection */
+#define EV_AutoJoinOnConnection(i, o) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /**/
+MUTATOR_HOOKABLE(AutoJoinOnConnection, EV_AutoJoinOnConnection);
+
/** called when player spawns to determine whether to give them random start weapons. Return true to forbid giving them. */
#define EV_ForbidRandomStartWeapons(i, o) \
/** player */ i(entity, MUTATOR_ARGV_0_entity) \
/**/
MUTATOR_HOOKABLE(GiveResourceWithLimit, EV_GiveResourceWithLimit);
+/** Called when some resource is being taken from an entity. See RESOURCE_* constants
+for resource types. Return true to forbid giving. */
+#define EV_TakeResource(i, o) \
+ /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+ /**/ o(int, MUTATOR_ARGV_1_int) \
+ /** amount */ i(float, MUTATOR_ARGV_2_float) \
+ /**/ o(float, MUTATOR_ARGV_2_float) \
+ /**/
+MUTATOR_HOOKABLE(TakeResource, EV_TakeResource);
+
+/** Called when some resource is being taken from an entity, with a limit. See
+RESOURCE_* constants for resource types. Return true to forbid giving. */
+#define EV_TakeResourceWithLimit(i, o) \
+ /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** resource type */ i(int, MUTATOR_ARGV_1_int) \
+ /**/ o(int, MUTATOR_ARGV_1_int) \
+ /** amount */ i(float, MUTATOR_ARGV_2_float) \
+ /**/ o(float, MUTATOR_ARGV_2_float) \
+ /** limit */ i(float, MUTATOR_ARGV_3_float) \
+ /**/ o(float, MUTATOR_ARGV_3_float) \
+ /**/
+MUTATOR_HOOKABLE(TakeResourceWithLimit, EV_TakeResourceWithLimit);
+
/** called at when a player connect */
#define EV_ClientConnect(i, o) \
/** player */ i(entity, MUTATOR_ARGV_0_entity) \
+++ /dev/null
-#pragma once
-
-#include <server/miscfunctions.qh>
-#include <server/g_world.qh>
-#include <server/round_handler.qh>
-#include <server/scores.qh>
-#include <server/scores_rules.qh>
-#include <server/teamplay.qh>
-#include <common/gamemodes/rules.qh>
-
-#include "mutator.qh"
-
-// TODO: trim
-
-#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/scores.qh>
-#include <common/stats.qh>
-#include <common/teams.qh>
-#include <common/util.qh>
-#include <common/command/_mod.qh>
-#include <common/net_notice.qh>
-#include <common/animdecide.qh>
-#include <common/monsters/_mod.qh>
-#include <common/monsters/sv_monsters.qh>
-#include <common/monsters/sv_spawn.qh>
-#include <common/weapons/config.qh>
-#include <common/weapons/_all.qh>
-#include <server/weapons/accuracy.qh>
-#include <server/weapons/common.qh>
-#include <server/weapons/csqcprojectile.qh>
-#include <server/weapons/hitplot.qh>
-#include <server/weapons/selection.qh>
-#include <server/weapons/spawning.qh>
-#include <server/weapons/throwing.qh>
-#include <server/weapons/tracing.qh>
-#include <server/weapons/weaponstats.qh>
-#include <server/weapons/weaponsystem.qh>
-#include <common/t_items.qh>
-#include <server/autocvars.qh>
-#include <server/constants.qh>
-#include <server/defs.qh>
-#include <common/notifications/all.qh>
-#include <common/deathtypes/all.qh>
-#include <common/turrets/sv_turrets.qh>
-#include <common/vehicles/all.qh>
-#include <server/campaign.qh>
-#include <common/campaign_common.qh>
-#include <common/mapinfo.qh>
-#include <server/command/common.qh>
-#include <server/command/banning.qh>
-#include <server/command/radarmap.qh>
-#include <server/command/vote.qh>
-#include <server/command/getreplies.qh>
-#include <server/command/cmd.qh>
-#include <server/command/sv_cmd.qh>
-#include <common/csqcmodel_settings.qh>
-#include <lib/csqcmodel/common.qh>
-#include <lib/csqcmodel/sv_model.qh>
-#include <server/anticheat.qh>
-#include <server/cheats.qh>
-#include <common/playerstats.qh>
-#include <server/portals.qh>
-#include <server/g_hook.qh>
-#include <server/spawnpoints.qh>
-#include <server/mapvoting.qh>
-#include <server/ipban.qh>
-#include <server/antilag.qh>
-#include <server/item_key.qh>
-#include <server/pathlib/pathlib.qh>
-#include <common/vehicles/all.qh>
-
-#include <common/mutators/mutator/waypoints/waypointsprites.qh>
-
-#include <server/client.qh>
-#include <server/player.qh>
-#include <server/impulse.qh>
-#include <server/cheats.qh>
-#include <server/g_damage.qh>
-
-#include <server/bot/api.qh>
-
-#include <server/command/_mod.qh>
-
-#include <common/monsters/_mod.qh>
-
-#include <server/weapons/tracing.qh>
-#include <server/weapons/weaponsystem.qh>
-
-#include <common/physics/player.qh>
-#include <common/effects/qc/_mod.qh>
-#include <common/deathtypes/all.qh>
-#include <common/notifications/all.qh>
-#include <common/mapobjects/teleporters.qh>
-#include <common/mapobjects/subs.qh>
-#include <common/stats.qh>
-#include <common/teams.qh>
-
-#include <lib/warpzone/server.qh>
-#include <lib/warpzone/util_server.qh>
-
-.float lastground;
-float total_players;
-float redalive, bluealive, yellowalive, pinkalive;
+++ /dev/null
-#pragma once
-
-#include <common/mutators/base.qh>
-
-#include <server/client.qh>
-#include <server/player.qh>
-#include <server/impulse.qh>
-#include <server/cheats.qh>
-#include <server/g_damage.qh>
-#include <server/round_handler.qh>
-#include <server/scores.qh>
-#include <server/scores_rules.qh>
-
-#include <server/bot/api.qh>
-
-#include <server/command/_mod.qh>
-
-#include <server/weapons/common.qh>
-#include <server/weapons/tracing.qh>
-#include <server/weapons/throwing.qh>
-#include <server/weapons/weaponsystem.qh>
-
-#include <common/deathtypes/all.qh>
-#include <common/notifications/all.qh>
-#include <common/mapobjects/teleporters.qh>
-#include <common/mapobjects/subs.qh>
-#include <common/stats.qh>
-#include <common/teams.qh>
-
-#include <common/monsters/_mod.qh>
-
-#include <lib/warpzone/anglestransform.qh>
-#include <lib/warpzone/server.qh>
-#include <lib/warpzone/util_server.qh>
+++ /dev/null
-// generated file; do not modify
-#include <server/mutators/mutator/gamemode_assault.qc>
-#include <server/mutators/mutator/gamemode_ca.qc>
-#include <server/mutators/mutator/gamemode_ctf.qc>
-#include <server/mutators/mutator/gamemode_cts.qc>
-#include <server/mutators/mutator/gamemode_deathmatch.qc>
-#include <server/mutators/mutator/gamemode_domination.qc>
-#include <server/mutators/mutator/gamemode_freezetag.qc>
-#include <server/mutators/mutator/gamemode_invasion.qc>
-#include <server/mutators/mutator/gamemode_keepaway.qc>
-#include <server/mutators/mutator/gamemode_keyhunt.qc>
-#include <server/mutators/mutator/gamemode_lms.qc>
-#include <server/mutators/mutator/gamemode_race.qc>
-#include <server/mutators/mutator/gamemode_tdm.qc>
+++ /dev/null
-// generated file; do not modify
-#include <server/mutators/mutator/gamemode_assault.qh>
-#include <server/mutators/mutator/gamemode_ca.qh>
-#include <server/mutators/mutator/gamemode_ctf.qh>
-#include <server/mutators/mutator/gamemode_cts.qh>
-#include <server/mutators/mutator/gamemode_deathmatch.qh>
-#include <server/mutators/mutator/gamemode_domination.qh>
-#include <server/mutators/mutator/gamemode_freezetag.qh>
-#include <server/mutators/mutator/gamemode_invasion.qh>
-#include <server/mutators/mutator/gamemode_keepaway.qh>
-#include <server/mutators/mutator/gamemode_keyhunt.qh>
-#include <server/mutators/mutator/gamemode_lms.qh>
-#include <server/mutators/mutator/gamemode_race.qh>
-#include <server/mutators/mutator/gamemode_tdm.qh>
+++ /dev/null
-#include "gamemode_assault.qh"
-
-#include <lib/float.qh>
-
-.entity sprite;
-#define AS_ROUND_DELAY 5
-
-IntrusiveList g_assault_destructibles;
-IntrusiveList g_assault_objectivedecreasers;
-IntrusiveList g_assault_objectives;
-STATIC_INIT(g_assault)
-{
- g_assault_destructibles = IL_NEW();
- g_assault_objectivedecreasers = IL_NEW();
- g_assault_objectives = IL_NEW();
-}
-
-// random functions
-void assault_objective_use(entity this, entity actor, entity trigger)
-{
- // activate objective
- this.health = 100;
- //print("^2Activated objective ", this.targetname, "=", etos(this), "\n");
- //print("Activator is ", actor.classname, "\n");
-
- IL_EACH(g_assault_objectivedecreasers, it.target == this.targetname,
- {
- target_objective_decrease_activate(it);
- });
-}
-
-vector target_objective_spawn_evalfunc(entity this, entity player, entity spot, vector current)
-{
- if(this.health < 0 || this.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(entity this)
-{
- this.health = ASSAULT_VALUE_INACTIVE;
-}
-
-// decrease the health of targeted objectives
-void assault_objective_decrease_use(entity this, entity actor, entity trigger)
-{
- if(actor.team != assault_attacker_team)
- {
- // wrong team triggered decrease
- return;
- }
-
- if(trigger.assault_sprite)
- {
- WaypointSprite_Disown(trigger.assault_sprite, waypointsprite_deadlifetime);
- if(trigger.classname == "func_assault_destructible")
- trigger.sprite = NULL; // TODO: just unsetting it?!
- }
- else
- return; // already activated! cannot activate again!
-
- if(this.enemy.health < ASSAULT_VALUE_INACTIVE)
- {
- if(this.enemy.health - this.dmg > 0.5)
- {
- GameRules_scoring_add_team(actor, SCORE, this.dmg);
- this.enemy.health = this.enemy.health - this.dmg;
- }
- else
- {
- GameRules_scoring_add_team(actor, SCORE, this.enemy.health);
- GameRules_scoring_add_team(actor, ASSAULT_OBJECTIVES, 1);
- this.enemy.health = -1;
-
- if(this.enemy.message)
- FOREACH_CLIENT(IS_PLAYER(it), { centerprint(it, this.enemy.message); });
-
- SUB_UseTargets(this.enemy, this, trigger);
- }
- }
-}
-
-void assault_setenemytoobjective(entity this)
-{
- IL_EACH(g_assault_objectives, it.targetname == this.target,
- {
- if(this.enemy == NULL)
- this.enemy = it;
- else
- objerror(this, "more than one objective as target - fix the map!");
- break;
- });
-
- if(this.enemy == NULL)
- objerror(this, "no objective as target - fix the map!");
-}
-
-bool assault_decreaser_sprite_visible(entity this, entity player, entity view)
-{
- if(this.assault_decreaser.enemy.health >= ASSAULT_VALUE_INACTIVE)
- return false;
-
- return true;
-}
-
-void target_objective_decrease_activate(entity this)
-{
- entity spr;
- this.owner = NULL;
- FOREACH_ENTITY_STRING(target, this.targetname,
- {
- if(it.assault_sprite != NULL)
- {
- WaypointSprite_Disown(it.assault_sprite, waypointsprite_deadlifetime);
- if(it.classname == "func_assault_destructible")
- it.sprite = NULL; // TODO: just unsetting it?!
- }
-
- spr = WaypointSprite_SpawnFixed(WP_AssaultDefend, 0.5 * (it.absmin + it.absmax), it, assault_sprite, RADARICON_OBJECTIVE);
- spr.assault_decreaser = this;
- spr.waypointsprite_visible_for_player = assault_decreaser_sprite_visible;
- spr.classname = "sprite_waypoint";
- WaypointSprite_UpdateRule(spr, assault_attacker_team, SPRITERULE_TEAMPLAY);
- if(it.classname == "func_assault_destructible")
- {
- WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultDestroy, WP_AssaultDestroy);
- WaypointSprite_UpdateMaxHealth(spr, it.max_health);
- WaypointSprite_UpdateHealth(spr, it.health);
- it.sprite = spr;
- }
- else
- WaypointSprite_UpdateSprites(spr, WP_AssaultDefend, WP_AssaultPush, WP_AssaultPush);
- });
-}
-
-void target_objective_decrease_findtarget(entity this)
-{
- assault_setenemytoobjective(this);
-}
-
-void target_assault_roundend_reset(entity this)
-{
- //print("round end reset\n");
- ++this.cnt; // up round counter
- this.winning = false; // up round
-}
-
-void target_assault_roundend_use(entity this, entity actor, entity trigger)
-{
- this.winning = 1; // round has been won by attackers
-}
-
-void assault_roundstart_use(entity this, entity actor, entity trigger)
-{
- SUB_UseTargets(this, this, trigger);
-
- //(Re)spawn all turrets
- IL_EACH(g_turrets, true,
- {
- // Swap turret teams
- if(it.team == NUM_TEAM_1)
- it.team = NUM_TEAM_2;
- else
- it.team = NUM_TEAM_1;
-
- // Doubles as teamchange
- turret_respawn(it);
- });
-}
-void assault_roundstart_use_this(entity this)
-{
- assault_roundstart_use(this, NULL, NULL);
-}
-
-void assault_wall_think(entity this)
-{
- if(this.enemy.health < 0)
- {
- this.model = "";
- this.solid = SOLID_NOT;
- }
- else
- {
- this.model = this.mdl;
- this.solid = SOLID_BSP;
- }
-
- this.nextthink = time + 0.2;
-}
-
-// trigger new round
-// reset objectives, toggle spawnpoints, reset triggers, ...
-void assault_new_round(entity this)
-{
- //bprint("ASSAULT: new round\n");
-
- // up round counter
- this.winning = this.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;
-
- IL_EACH(g_saved_team, !IS_CLIENT(it),
- {
- if(it.team_saved == NUM_TEAM_1)
- it.team_saved = NUM_TEAM_2;
- else if(it.team_saved == NUM_TEAM_2)
- it.team_saved = NUM_TEAM_1;
- });
-
- // reset the level with a countdown
- cvar_set("timelimit", ftos(ceil(time - AS_ROUND_DELAY - game_starttime) / 60));
- ReadyRestart_force(); // sets game_starttime
-}
-
-entity as_round;
-.entity ent_winning;
-void as_round_think()
-{
- game_stopped = false;
- assault_new_round(as_round.ent_winning);
- delete(as_round);
- as_round = NULL;
-}
-
-// Assault winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win. Otherwise the defending team wins once the timelimit passes.
-int WinningCondition_Assault()
-{
- if(as_round)
- return WINNING_NO;
-
- WinningConditionHelper(NULL); // set worldstatus
-
- int status = WINNING_NO;
- // as the timelimit has not yet passed just assume the defending team will win
- if(assault_attacker_team == NUM_TEAM_1)
- {
- SetWinners(team, NUM_TEAM_2);
- }
- else
- {
- SetWinners(team, NUM_TEAM_1);
- }
-
- entity ent;
- ent = find(NULL, classname, "target_assault_roundend");
- if(ent)
- {
- if(ent.winning) // round end has been triggered by attacking team
- {
- bprint("Assault: round completed.\n");
- SetWinners(team, assault_attacker_team);
-
- TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 666 - TeamScore_AddToTeam(assault_attacker_team, ST_ASSAULT_OBJECTIVES, 0));
-
- if(ent.cnt == 1 || autocvar_g_campaign) // this was the second round
- {
- status = WINNING_YES;
- }
- else
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ASSAULT_OBJ_DESTROYED, ceil(time - game_starttime));
- as_round = new(as_round);
- as_round.think = as_round_think;
- as_round.ent_winning = ent;
- as_round.nextthink = time + AS_ROUND_DELAY;
- game_stopped = true;
-
- // make sure timelimit isn't hit while the game is blocked
- if(autocvar_timelimit > 0)
- if(time + AS_ROUND_DELAY >= game_starttime + autocvar_timelimit * 60)
- cvar_set("timelimit", ftos(autocvar_timelimit + AS_ROUND_DELAY / 60));
- }
- }
- }
-
- return status;
-}
-
-// spawnfuncs
-spawnfunc(info_player_attacker)
-{
- if (!g_assault) { delete(this); return; }
-
- this.team = NUM_TEAM_1; // red, gets swapped every round
- spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(info_player_defender)
-{
- if (!g_assault) { delete(this); return; }
-
- this.team = NUM_TEAM_2; // blue, gets swapped every round
- spawnfunc_info_player_deathmatch(this);
-}
-
-spawnfunc(target_objective)
-{
- if (!g_assault) { delete(this); return; }
-
- this.classname = "target_objective";
- IL_PUSH(g_assault_objectives, this);
- this.use = assault_objective_use;
- this.reset = assault_objective_reset;
- this.reset(this);
- this.spawn_evalfunc = target_objective_spawn_evalfunc;
-}
-
-spawnfunc(target_objective_decrease)
-{
- if (!g_assault) { delete(this); return; }
-
- this.classname = "target_objective_decrease";
- IL_PUSH(g_assault_objectivedecreasers, this);
-
- if(!this.dmg)
- this.dmg = 101;
-
- this.use = assault_objective_decrease_use;
- this.health = ASSAULT_VALUE_INACTIVE;
- this.max_health = ASSAULT_VALUE_INACTIVE;
- this.enemy = NULL;
-
- InitializeEntity(this, 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) { delete(this); return; }
-
- this.spawnflags = 3;
- this.classname = "func_assault_destructible";
- IL_PUSH(g_assault_destructibles, this);
-
- if(assault_attacker_team == NUM_TEAM_1)
- this.team = NUM_TEAM_2;
- else
- this.team = NUM_TEAM_1;
-
- spawnfunc_func_breakable(this);
-}
-
-spawnfunc(func_assault_wall)
-{
- if (!g_assault) { delete(this); return; }
-
- this.classname = "func_assault_wall";
- this.mdl = this.model;
- _setmodel(this, this.mdl);
- this.solid = SOLID_BSP;
- setthink(this, assault_wall_think);
- this.nextthink = time;
- InitializeEntity(this, assault_setenemytoobjective, INITPRIO_FINDTARGET);
-}
-
-spawnfunc(target_assault_roundend)
-{
- if (!g_assault) { delete(this); return; }
-
- this.winning = 0; // round not yet won by attackers
- this.classname = "target_assault_roundend";
- this.use = target_assault_roundend_use;
- this.cnt = 0; // first round
- this.reset = target_assault_roundend_reset;
-}
-
-spawnfunc(target_assault_roundstart)
-{
- if (!g_assault) { delete(this); return; }
-
- assault_attacker_team = NUM_TEAM_1;
- this.classname = "target_assault_roundstart";
- this.use = assault_roundstart_use;
- this.reset2 = assault_roundstart_use_this;
- InitializeEntity(this, assault_roundstart_use_this, INITPRIO_FINDTARGET);
-}
-
-// legacy bot code
-void havocbot_goalrating_ast_targets(entity this, float ratingscale)
-{
- IL_EACH(g_assault_destructibles, it.bot_attack,
- {
- if (it.target == "")
- continue;
-
- bool found = false;
- entity destr = it;
- IL_EACH(g_assault_objectivedecreasers, it.targetname == destr.target,
- {
- if(it.enemy.health > 0 && it.enemy.health < ASSAULT_VALUE_INACTIVE)
- {
- found = true;
- break;
- }
- });
-
- if(!found)
- continue;
-
- vector p = 0.5 * (it.absmin + it.absmax);
-
- // Find and rate waypoints around it
- found = false;
- entity best = NULL;
- float bestvalue = 99999999999;
- entity des = it;
- for(float radius = 0; radius < 1500 && !found; radius += 500)
- {
- FOREACH_ENTITY_RADIUS(p, radius, it.classname == "waypoint" && !(it.wpflags & WAYPOINTFLAG_GENERATED),
- {
- if(checkpvs(it.origin, des))
- {
- found = true;
- if(it.cnt < bestvalue)
- {
- best = it;
- bestvalue = it.cnt;
- }
- }
- });
- }
-
- if(best)
- {
- /// dprint("waypoints around target were found\n");
- // te_lightning2(NULL, '0 0 0', best.origin);
- // te_knightspike(best.origin);
-
- navigation_routerating(this, best, ratingscale, 4000);
- best.cnt += 1;
-
- this.havocbot_attack_time = 0;
-
- if(checkpvs(this.origin + this.view_ofs, it))
- if(checkpvs(this.origin + this.view_ofs, best))
- {
- // dprint("increasing attack time for this target\n");
- this.havocbot_attack_time = time + 2;
- }
- }
- });
-}
-
-void havocbot_role_ast_offense(entity this)
-{
- if(IS_DEAD(this))
- {
- this.havocbot_attack_time = 0;
- havocbot_ast_reset_role(this);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ast_reset_role(this);
- return;
- }
-
- if(this.havocbot_attack_time>time)
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
- havocbot_goalrating_ast_targets(this, 20000);
- havocbot_goalrating_items(this, 15000, this.origin, 10000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ast_defense(entity this)
-{
- if(IS_DEAD(this))
- {
- this.havocbot_attack_time = 0;
- havocbot_ast_reset_role(this);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ast_reset_role(this);
- return;
- }
-
- if(this.havocbot_attack_time>time)
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 3000);
- havocbot_goalrating_ast_targets(this, 20000);
- havocbot_goalrating_items(this, 15000, this.origin, 10000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ast_setrole(entity this, float role)
-{
- switch(role)
- {
- case HAVOCBOT_AST_ROLE_DEFENSE:
- this.havocbot_role = havocbot_role_ast_defense;
- this.havocbot_role_flags = HAVOCBOT_AST_ROLE_DEFENSE;
- this.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_AST_ROLE_OFFENSE:
- this.havocbot_role = havocbot_role_ast_offense;
- this.havocbot_role_flags = HAVOCBOT_AST_ROLE_OFFENSE;
- this.havocbot_role_timeout = 0;
- break;
- }
-}
-
-void havocbot_ast_reset_role(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if(this.team == assault_attacker_team)
- havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_OFFENSE);
- else
- havocbot_role_ast_setrole(this, HAVOCBOT_AST_ROLE_DEFENSE);
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(as, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.team == assault_attacker_team)
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_ATTACKING);
- else
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ASSAULT_DEFENDING);
-}
-
-MUTATOR_HOOKFUNCTION(as, TurretSpawn)
-{
- entity turret = M_ARGV(0, entity);
-
- if(!turret.team || turret.team == FLOAT_MAX)
- turret.team = 5; // this gets reversed when match starts?
-}
-
-MUTATOR_HOOKFUNCTION(as, VehicleInit)
-{
- entity veh = M_ARGV(0, entity);
-
- veh.nextthink = time + 0.5;
-}
-
-MUTATOR_HOOKFUNCTION(as, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- havocbot_ast_reset_role(bot);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, PlayHitsound)
-{
- entity frag_victim = M_ARGV(0, entity);
-
- return (frag_victim.classname == "func_assault_destructible");
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckAllowedTeams)
-{
- // assault always has 2 teams
- c1 = c2 = 0;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, CheckRules_World)
-{
- M_ARGV(0, float) = WinningCondition_Assault();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadLevelCvars)
-{
- // incompatible
- warmup_stage = 0;
- sv_ready_restart_after_countdown = 0;
-}
-
-MUTATOR_HOOKFUNCTION(as, OnEntityPreSpawn)
-{
- entity ent = M_ARGV(0, entity);
-
- switch(ent.classname)
- {
- case "info_player_team1":
- case "info_player_team2":
- case "info_player_team3":
- case "info_player_team4":
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(as, ReadyRestart_Deny)
-{
- // readyrestart not supported (yet)
- return true;
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-const int ASSAULT_VALUE_INACTIVE = 1000;
-
-const int ST_ASSAULT_OBJECTIVES = 1;
-
-REGISTER_MUTATOR(as, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- int teams = BITS(2); // always red vs blue
- GameRules_scoring(teams, SFL_SORT_PRIO_SECONDARY, SFL_SORT_PRIO_SECONDARY, {
- field_team(ST_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
- field(SP_ASSAULT_OBJECTIVES, "objectives", SFL_SORT_PRIO_PRIMARY);
- });
- }
- return 0;
-}
-
-// 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(entity this) havocbot_role_ast_defense;
-void(entity this) havocbot_role_ast_offense;
-
-void(entity bot) havocbot_ast_reset_role;
-
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(entity this, float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-// predefined spawnfuncs
-void target_objective_decrease_activate(entity this);
+++ /dev/null
-#include "gamemode_ca.qh"
-
-float autocvar_g_ca_damage2score_multiplier;
-bool autocvar_g_ca_spectate_enemies;
-
-void CA_count_alive_players()
-{
- total_players = redalive = bluealive = yellowalive = pinkalive = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- switch(it.team)
- {
- case NUM_TEAM_1: ++total_players; if(!IS_DEAD(it)) ++redalive; break;
- case NUM_TEAM_2: ++total_players; if(!IS_DEAD(it)) ++bluealive; break;
- case NUM_TEAM_3: ++total_players; if(!IS_DEAD(it)) ++yellowalive; break;
- case NUM_TEAM_4: ++total_players; if(!IS_DEAD(it)) ++pinkalive; break;
- }
- });
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- STAT(REDALIVE, it) = redalive;
- STAT(BLUEALIVE, it) = bluealive;
- STAT(YELLOWALIVE, it) = yellowalive;
- STAT(PINKALIVE, it) = 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
-}
-
-void nades_Clear(entity player);
-
-#define CA_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define CA_ALIVE_TEAMS_OK() (CA_ALIVE_TEAMS() == NumTeams(ca_teams))
-float CA_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
- FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
- allowed_to_spawn = false;
- game_stopped = true;
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
- return 1;
- }
-
- CA_count_alive_players();
- if(CA_ALIVE_TEAMS() > 1)
- return 0;
-
- int winner_team = CA_GetWinnerTeam();
- if(winner_team > 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
- }
- else if(winner_team == -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
- }
-
- allowed_to_spawn = false;
- game_stopped = true;
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-
- FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
- return 1;
-}
-
-void CA_RoundStart()
-{
- allowed_to_spawn = boolean(warmup_stage);
-}
-
-bool CA_CheckTeams()
-{
- static int 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return true;
- }
- if(total_players == 0)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return false;
- }
- int missing_teams_mask = 0;
- if(ca_teams & BIT(0))
- missing_teams_mask += (!redalive) * 1;
- if(ca_teams & BIT(1))
- missing_teams_mask += (!bluealive) * 2;
- if(ca_teams & BIT(2))
- missing_teams_mask += (!yellowalive) * 4;
- if(ca_teams & BIT(3))
- missing_teams_mask += (!pinkalive) * 8;
- if(prev_missing_teams_mask != missing_teams_mask)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
- prev_missing_teams_mask = missing_teams_mask;
- }
- return false;
-}
-
-bool ca_isEliminated(entity e)
-{
- if(e.caplayer == 1 && (IS_DEAD(e) || 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;
- // continue from current player
- for (entity e = start; (e = find(e, classname, STR_PLAYER)); )
- {
- if (SAME_TEAM(player, e)) return e;
- }
- // restart from begining
- for (entity e = NULL; (e = find(e, classname, STR_PLAYER)); )
- {
- if (SAME_TEAM(player, e)) return e;
- }
- return start;
-}
-
-
-MUTATOR_HOOKFUNCTION(ca, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- player.caplayer = 1;
- if (!warmup_stage)
- eliminatedPlayers.SendFlags |= 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- // spectators / observers that weren't playing can join; they are
- // immediately forced to observe in the PutClientInServer hook
- // this way they are put in a team and can play in the next round
- if (!allowed_to_spawn && player.caplayer)
- return true;
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
- {
- TRANSMUTE(Observer, player);
- if (CS(player).jointime != time && !player.caplayer) // not when connecting
- {
- player.caplayer = 0.5;
- Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_players)
-{
- FOREACH_CLIENT(true, {
- CS(it).killcount = 0;
- if (!it.caplayer && IS_BOT_CLIENT(it))
- {
- it.team = -1;
- it.caplayer = 1;
- }
- if (it.caplayer)
- {
- TRANSMUTE(Player, it);
- it.caplayer = 1;
- PutClientInServer(it);
- }
- });
- bot_relinkplayerlist();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- TRANSMUTE(Observer, player);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, reset_map_global)
-{
- allowed_to_spawn = true;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = ca_teams;
-}
-
-entity ca_LastPlayerForTeam(entity this)
-{
- entity last_pl = NULL;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
- if (!IS_DEAD(it))
- if (SAME_TEAM(this, it))
- if (!last_pl)
- last_pl = it;
- else
- return NULL;
- });
- return last_pl;
-}
-
-void ca_LastPlayerForTeam_Notify(entity this)
-{
- if (round_handler_IsActive())
- if (round_handler_IsRoundStarted())
- {
- entity pl = ca_LastPlayerForTeam(this);
- if (pl)
- Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
- }
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- ca_LastPlayerForTeam_Notify(frag_target);
- if (!allowed_to_spawn)
- {
- frag_target.respawn_flags = RESPAWN_SILENT;
- // prevent unwanted sudden rejoin as spectator and movement of spectator camera
- frag_target.respawn_time = time + 2;
- }
- frag_target.respawn_flags |= RESPAWN_FORCE;
- if (!warmup_stage)
- eliminatedPlayers.SendFlags |= 1;
- if(IS_BOT_CLIENT(frag_target))
- bot_clear(frag_target);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- if (player.caplayer == 1)
- ca_LastPlayerForTeam_Notify(player);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if (!IS_DEAD(player))
- ca_LastPlayerForTeam_Notify(player);
- if (player.killindicator_teamchange == -2) // player wants to spectate
- player.caplayer = 0;
- if (player.caplayer)
- player.frags = FRAGS_LMS_LOSER;
- if (!warmup_stage)
- eliminatedPlayers.SendFlags |= 1;
- if (!player.caplayer)
- return false; // allow team reset
- return true; // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(ca, ForbidThrowCurrentWeapon)
-{
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, GiveFragsForKill, CBC_ORDER_FIRST)
-{
- M_ARGV(2, float) = 0; // score will be given to the winner team when the round ends
- return true;
-}
-
-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");
-}
-
-MUTATOR_HOOKFUNCTION(ca, Damage_Calculate)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_deathtype = M_ARGV(3, float);
- float frag_damage = M_ARGV(4, float);
- float frag_mirrordamage = M_ARGV(5, float);
-
- if (IS_PLAYER(frag_target))
- if (!IS_DEAD(frag_target))
- if (frag_target == frag_attacker || SAME_TEAM(frag_target, frag_attacker) || frag_deathtype == DEATH_FALL.m_id)
- frag_damage = 0;
-
- frag_mirrordamage = 0;
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(5, float) = frag_mirrordamage;
-}
-
-MUTATOR_HOOKFUNCTION(ca, FilterItem)
-{
- entity item = M_ARGV(0, entity);
-
- if (autocvar_g_powerups <= 0)
- if (item.flags & FL_POWERUP)
- return true;
-
- if (autocvar_g_pickup_items <= 0)
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(7, float);
- float damage_take = M_ARGV(4, float);
- float damage_save = M_ARGV(5, float);
-
- float excess = max(0, frag_damage - damage_take - damage_save);
-
- if (frag_target != frag_attacker && IS_PLAYER(frag_attacker))
- GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
-}
-
-MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
-{
- // no respawn calculations needed, player is forced to spectate anyway
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
-{
- // no regeneration in CA
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateSet)
-{
- entity client = M_ARGV(0, entity);
- entity targ = M_ARGV(1, entity);
-
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
- if (DIFF_TEAM(targ, client))
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectateNext)
-{
- entity client = M_ARGV(0, entity);
-
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
- {
- entity targ = M_ARGV(1, entity);
- M_ARGV(1, entity) = CA_SpectateNext(client, targ);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
-{
- entity client = M_ARGV(0, entity);
- entity targ = M_ARGV(1, entity);
- entity first = M_ARGV(2, entity);
-
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
- {
- do { targ = targ.chain; }
- while(targ && DIFF_TEAM(targ, client));
-
- if (!targ)
- {
- for (targ = first; targ && DIFF_TEAM(targ, client); targ = targ.chain);
-
- if (targ == client.enemy)
- return MUT_SPECPREV_RETURN;
- }
- }
-
- M_ARGV(1, entity) = targ;
-
- return MUT_SPECPREV_FOUND;
-}
-
-MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- if (IS_PLAYER(it) || it.caplayer == 1)
- ++M_ARGV(0, int);
- ++M_ARGV(1, int);
- });
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ca, ClientCommand_Spectate)
-{
- entity player = M_ARGV(0, entity);
-
- if (player.caplayer)
- {
- // they're going to spec, we can do other checks
- if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
- Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
- return MUT_SPECCMD_FORCE;
- }
-
- return MUT_SPECCMD_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(ca, WantWeapon)
-{
- M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(ca, HideTeamNagger)
-{
- return true; // doesn't work well with the whole spectator as player thing
-}
-
-MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
-{
- entity player = M_ARGV(0, entity);
-
- return player.caplayer == 1;
-}
-
-MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
-{
- // most weapons arena
- if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") M_ARGV(0, string) = "most";
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-int autocvar_g_ca_point_limit;
-int autocvar_g_ca_point_leadlimit;
-float autocvar_g_ca_round_timelimit;
-bool autocvar_g_ca_team_spawns;
-//int autocvar_g_ca_teams;
-int autocvar_g_ca_teams_override;
-float autocvar_g_ca_warmup;
-
-
-int ca_teams;
-bool allowed_to_spawn;
-
-const int ST_CA_ROUNDS = 1;
-
-bool CA_CheckTeams();
-bool CA_CheckWinner();
-void CA_RoundStart();
-bool ca_isEliminated(entity e);
-
-REGISTER_MUTATOR(ca, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_ca_team_spawns);
- GameRules_limit_score(autocvar_g_ca_point_limit);
- GameRules_limit_lead(autocvar_g_ca_point_leadlimit);
-
- ca_teams = autocvar_g_ca_teams_override;
- if (ca_teams < 2)
- ca_teams = cvar("g_ca_teams"); // read the cvar directly as it gets written earlier in the same frame
-
- ca_teams = BITS(bound(2, ca_teams, 4));
- GameRules_scoring(ca_teams, SFL_SORT_PRIO_PRIMARY, 0, {
- field_team(ST_CA_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
- });
-
- allowed_to_spawn = true;
- round_handler_Spawn(CA_CheckTeams, CA_CheckWinner, CA_RoundStart);
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
- EliminatedPlayers_Init(ca_isEliminated);
- }
- return 0;
-}
-
-// 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
+++ /dev/null
-#include "gamemode_ctf.qh"
-
-#include <common/effects/all.qh>
-#include <common/vehicles/all.qh>
-#include <server/teamplay.qh>
-
-#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;
-bool autocvar_g_ctf_flag_return_carrying;
-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;
-bool autocvar_g_ctf_score_ignore_fields;
-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 != NULL) ? ftos(actor.playerid) : "")));
- //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (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, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
- else if(!ctf_captimerecord)
- Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
- else if(cap_time < cap_record)
- Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
-
- // 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, flag.ctf_pickuptime, cap_time);
- }
-
- if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
- race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
-}
-
-bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
-{
- int num_perteam = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
-
- // automatically return if there's only 1 player on the team
- return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
- && flag.team);
-}
-
-bool ctf_Return_Customize(entity this, entity client)
-{
- // only to the carrier
- return boolean(client == this.owner);
-}
-
-void ctf_FlagcarrierWaypoints(entity player)
-{
- WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 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));
-
- if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
- {
- if(!player.wps_enemyflagcarrier)
- {
- entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
- wp.colormod = WPCOLOR_ENEMYFC(player.team);
- setcefc(wp, ctf_Stalemate_Customize);
-
- if(IS_REAL_CLIENT(player) && !ctf_stalemate)
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
- }
-
- if(!player.wps_flagreturn)
- {
- entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
- owp.colormod = '0 0.8 0.8';
- //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
- setcefc(owp, ctf_Return_Customize);
- }
- }
-}
-
-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;
- int players_worseeq, players_total;
-
- if(ctf_captureshield_max_ratio <= 0)
- return false;
-
- s = GameRules_scoring_add(p, CTF_CAPS, 0);
- s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
- s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
- s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
-
- sr = ((s - s2) + (s3 + s4));
-
- if(sr >= -ctf_captureshield_min_negscore)
- return false;
-
- players_total = players_worseeq = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(DIFF_TEAM(it, p))
- continue;
- se = GameRules_scoring_add(it, CTF_CAPS, 0);
- se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
- se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
- se4 = GameRules_scoring_add(it, 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(entity this, entity client)
-{
- if(!client.ctf_captureshielded) { return false; }
- if(CTF_SAMETEAM(this, client)) { return false; }
-
- return true;
-}
-
-void ctf_CaptureShield_Touch(entity this, entity toucher)
-{
- if(!toucher.ctf_captureshielded) { return; }
- if(CTF_SAMETEAM(this, toucher)) { return; }
-
- vector mymid = (this.absmin + this.absmax) * 0.5;
- vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
-
- Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
- if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
-}
-
-void ctf_CaptureShield_Spawn(entity flag)
-{
- entity shield = new(ctf_captureshield);
-
- shield.enemy = flag;
- shield.team = flag.team;
- settouch(shield, ctf_CaptureShield_Touch);
- setcefc(shield, ctf_CaptureShield_Customize);
- shield.effects = EF_ADDITIVE;
- set_movetype(shield, MOVETYPE_NOCLIP);
- shield.solid = SOLID_TRIGGER;
- shield.avelocity = '7 0 11';
- shield.scale = 0.5;
-
- setorigin(shield, flag.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
- set_movetype(flag, 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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
- _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
- ctf_EventLog("dropped", player.team, player);
-
- // scoring
- GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
- GameRules_scoring_add(player, CTF_DROPS, 1);
-
- // waypoints
- if(autocvar_g_ctf_flag_dropped_waypoint) {
- entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((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 = NULL;
- flag.pass_target = NULL;
- }
-}
-
-void ctf_Handle_Retrieve(entity flag, entity player)
-{
- entity sender = flag.pass_sender;
-
- // transfer flag to player
- flag.owner = player;
- flag.owner.flagcarried = flag;
- GameRules_scoring_vip(player, true);
-
- // 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);
- }
- set_movetype(flag, 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);
-
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
- if(it == sender)
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
- else if(it == player)
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
- else if(SAME_TEAM(it, sender))
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), 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 = NULL;
- flag.pass_target = NULL;
-}
-
-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, NULL, "");
- setorigin(flag, player.origin + FLAG_DROP_OFFSET);
- flag.owner.flagcarried = NULL;
- GameRules_scoring_vip(flag.owner, false);
- flag.owner = NULL;
- flag.solid = SOLID_TRIGGER;
- flag.ctf_dropper = player;
- flag.ctf_droptime = time;
- navigation_dynamicgoal_set(flag);
-
- 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
- set_movetype(flag, 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(NULL, _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, 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, 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);
-
- if(player.wps_flagreturn)
- WaypointSprite_Kill(player.wps_flagreturn);
-
- // captureshield
- ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
-}
-
-void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
-{
- return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
-}
-
-// ==============
-// Event Handlers
-// ==============
-
-void nades_GiveBonus(entity player, float score);
-
-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 = NULL, 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((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
-
- if (toucher.goalentity == flag.bot_basewaypoint)
- toucher.goalentity_lock_timeout = 0;
-
- 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, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
- 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
- float pscore = 0;
- if(enemy_flag.score_capture || flag.score_capture)
- pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
- GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
- float capscore = 0;
- if(enemy_flag.score_team_capture || flag.score_team_capture)
- capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
- GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
-
- old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
- new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
- if(!old_time || new_time < old_time)
- GameRules_scoring_add(player, 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))
- { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
- }
-
- flag.enemy = toucher;
-
- // 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, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
- }
- else if(flag.team)
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, 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))
- {
- GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
- GameRules_scoring_add(player, 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)
- {
- GameRules_scoring_add(flag.ctf_dropper, 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);
-
- flag.enemy = player;
-
- // 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
-
- // attach the flag to the player
- flag.owner = player;
- player.flagcarried = flag;
- GameRules_scoring_vip(player, true);
- 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
- set_movetype(flag, 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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), 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_NUM(flag.team, CENTER_CTF_PICKUP));
- else
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
-
- Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-
- if(!flag.team)
- FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
-
- if(flag.team)
- FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
- if(CTF_SAMETEAM(flag, it))
- if(SAME_TEAM(player, it))
- Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
- else
- Send_Notification(NOTIF_ONE, it, 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
- GameRules_scoring_add(player, CTF_PICKUPS, 1);
- nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
- switch(pickuptype)
- {
- case PICKUP_BASE:
- {
- GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : 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));
- GameRules_scoring_add_team(player, SCORE, 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, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
- case RETURN_DAMAGE:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
- case RETURN_SPEEDRUN:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
- case RETURN_NEEDKILL:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
- default:
- case RETURN_TIMEOUT:
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
- }
- _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
- ctf_EventLog("returned", flag.team, NULL);
- flag.enemy = NULL;
- ctf_RespawnFlag(flag);
- }
- }
-}
-
-bool ctf_Stalemate_Customize(entity this, entity client)
-{
- // make spectators see what the player would see
- entity e = WaypointSprite_getviewentity(client);
- entity wp_owner = this.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()
-{
- // 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 = NULL; // 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, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
- wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
- setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
- }
- }
-
- if (!wpforenemy_announced)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
-
- wpforenemy_announced = true;
- }
- }
-}
-
-void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
- if(ITEM_DAMAGE_NEEDKILL(deathtype))
- {
- if(autocvar_g_ctf_flag_return_damage_delay)
- this.ctf_flagdamaged_byworld = true;
- else
- {
- this.health = 0;
- ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
- }
- return;
- }
- if(autocvar_g_ctf_flag_return_damage)
- {
- // reduce health and check if it should be returned
- this.health = this.health - damage;
- ctf_CheckFlagReturn(this, RETURN_DAMAGE);
- return;
- }
-}
-
-void ctf_FlagThink(entity this)
-{
- // declarations
- entity tmp_entity;
-
- this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
-
- // captureshield
- if(this == ctf_worldflaglist) // only for the first flag
- FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
-
- // sanity checks
- if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
- LOG_TRACE("wtf the flag got squashed?");
- tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
- if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
- setsize(this, this.m_mins, this.m_maxs);
- }
-
- // main think method
- switch(this.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(vdist(this.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(this, tmp_entity, CAPTURE_DROPPED);
- }
- return;
- }
-
- case FLAG_DROPPED:
- {
- this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
-
- if(autocvar_g_ctf_flag_dropped_floatinwater)
- {
- vector midpoint = ((this.absmin + this.absmax) * 0.5);
- if(pointcontents(midpoint) == CONTENT_WATER)
- {
- this.velocity = this.velocity * 0.5;
-
- if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
- { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
- else
- { set_movetype(this, MOVETYPE_FLY); }
- }
- else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
- }
- if(autocvar_g_ctf_flag_return_dropped)
- {
- if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
- {
- this.health = 0;
- ctf_CheckFlagReturn(this, RETURN_DROPPED);
- return;
- }
- }
- if(this.ctf_flagdamaged_byworld)
- {
- this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
- ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
- return;
- }
- else if(autocvar_g_ctf_flag_return_time)
- {
- this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
- ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
- return;
- }
- return;
- }
-
- case FLAG_CARRY:
- {
- if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
- {
- this.health = 0;
- ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
-
- CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
- ImpulseCommands(this.owner);
- }
- 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(this, this.owner) && this.team)
- {
- if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
- ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
- else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
- ctf_Handle_Return(this, this.owner);
- }
- return;
- }
-
- case FLAG_PASSING:
- {
- vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
- targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
- WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
-
- if((this.pass_target == NULL)
- || (IS_DEAD(this.pass_target))
- || (this.pass_target.flagcarried)
- || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
- || ((trace_fraction < 1) && (trace_ent != this.pass_target))
- || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
- {
- // give up, pass failed
- ctf_Handle_Drop(this, NULL, DROP_PASS);
- }
- else
- {
- // still a viable target, go for it
- ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
- }
- return;
- }
-
- default: // this should never happen
- {
- LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
- return;
- }
- }
-}
-
-METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
-{
- return = false;
- if(game_stopped) return;
- if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
-
- bool is_not_monster = (!IS_MONSTER(toucher));
-
- // 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)
- {
- flag.health = 0;
- ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
- }
- if(!flag.ctf_flagdamaged_byworld) { return; }
- }
-
- // special touch behaviors
- if(STAT(FROZEN, toucher)) { 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 > flag.wait) // if we haven't in a while, play a sound/effect
- {
- Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
- _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
- flag.wait = time + FLAG_TOUCHRATE;
- }
- return;
- }
- else if(IS_DEAD(toucher)) { return; }
-
- switch(flag.ctf_status)
- {
- case FLAG_BASE:
- {
- if(ctf_oneflag)
- {
- if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
- ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
- else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
- ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
- }
- else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
- ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
- else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
- {
- ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
- ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
- }
- else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
- ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
- break;
- }
-
- case FLAG_DROPPED:
- {
- if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
- ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
- else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
- ctf_Handle_Pickup(flag, 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?");
- break;
- }
-
- case FLAG_PASSING:
- {
- if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
- {
- if(DIFF_TEAM(toucher, flag.pass_sender))
- {
- if(ctf_Immediate_Return_Allowed(flag, toucher))
- ctf_Handle_Return(flag, toucher);
- else if(is_not_monster && (!toucher.flagcarried))
- ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
- }
- else if(!toucher.flagcarried)
- ctf_Handle_Retrieve(flag, 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.owner.wps_flagreturn);
- WaypointSprite_Kill(flag.wps_flagcarrier);
-
- flag.owner.flagcarried = NULL;
- GameRules_scoring_vip(flag.owner, false);
-
- 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, NULL, "");
- setorigin(flag, flag.ctf_spawnorigin);
-
- set_movetype(flag, ((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 = NULL;
- flag.pass_distance = 0;
- flag.pass_sender = NULL;
- flag.pass_target = NULL;
- flag.ctf_dropper = NULL;
- flag.ctf_pickuptime = 0;
- flag.ctf_droptime = 0;
- flag.ctf_flagdamaged_byworld = false;
- navigation_dynamicgoal_unset(flag);
-
- ctf_CheckStalemate();
-}
-
-void ctf_Reset(entity this)
-{
- if(this.owner && IS_PLAYER(this.owner))
- ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
-
- this.enemy = NULL;
- ctf_RespawnFlag(this);
-}
-
-bool ctf_FlagBase_Customize(entity this, entity client)
-{
- entity e = WaypointSprite_getviewentity(client);
- entity wp_owner = this.owner;
- entity flag = e.flagcarried;
- if(flag && CTF_SAMETEAM(e, flag))
- return false;
- if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
- return false;
- return true;
-}
-
-void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
-{
- // bot waypoints
- waypoint_spawnforitem_force(this, this.origin);
- navigation_dynamicgoal_init(this, true);
-
- // waypointsprites
- entity basename;
- switch (this.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, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
- wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
- WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
- setcefc(wp, ctf_FlagBase_Customize);
-
- // captureshield setup
- ctf_CaptureShield_Spawn(this);
-}
-
-.bool pushable;
-
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
-{
- // main setup
- flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
- ctf_worldflaglist = flag;
-
- setattachment(flag, NULL, "");
-
- 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;
- IL_PUSH(g_items, flag);
- 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;
- flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
- flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
- if(flag.damagedbycontents)
- IL_PUSH(g_damagedbycontents, flag);
- flag.velocity = '0 0 0';
- flag.mangle = flag.angles;
- flag.reset = ctf_Reset;
- settouch(flag, ctf_FlagTouch);
- setthink(flag, ctf_FlagThink);
- flag.nextthink = time + FLAG_THINKRATE;
- flag.ctf_status = FLAG_BASE;
-
- // crudely force them all to 0
- if(autocvar_g_ctf_score_ignore_fields)
- flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
-
- 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)); }
- if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
- if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
- if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
-
- // sounds
-#define X(s,b) \
- if(flag.s == "") flag.s = b; \
- precache_sound(flag.s);
-
- X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
- X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
- X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
- X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
- X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
- X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
- X(snd_flag_pass, strzone(SND(CTF_PASS)))
-#undef X
-
- // precache
- precache_model(flag.model);
-
- // appearence
- _setmodel(flag, flag.model); // precision set below
- setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
- flag.m_mins = flag.mins; // store these for squash checks
- flag.m_maxs = flag.maxs;
- 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;
- set_movetype(flag, MOVETYPE_NONE);
- }
- else // drop to floor, automatically find a platform and set that as spawn origin
- {
- flag.noalign = false;
- droptofloor(flag);
- set_movetype(flag, MOVETYPE_NONE);
- }
-
- InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-// NOTE: LEGACY CODE, needs to be re-written!
-
-void havocbot_ctf_calculate_middlepoint()
-{
- entity f;
- vector s = '0 0 0';
- vector fo = '0 0 0';
- int n = 0;
-
- f = ctf_worldflaglist;
- while (f)
- {
- fo = f.origin;
- s = s + fo;
- f = f.ctf_worldflagnext;
- n++;
- }
- if(!n)
- return;
-
- havocbot_middlepoint = s / n;
- havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
-
- havocbot_symmetryaxis_equation = '0 0 0';
- if(n == 2)
- {
- // for symmetrical editing of waypoints
- entity f1 = ctf_worldflaglist;
- entity f2 = f1.ctf_worldflagnext;
- float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
- float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
- havocbot_symmetryaxis_equation.x = m;
- havocbot_symmetryaxis_equation.y = q;
- }
- // store number of flags in this otherwise unused vector component
- havocbot_symmetryaxis_equation.z = n;
-}
-
-
-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 NULL;
-}
-
-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 NULL;
-}
-
-int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
-{
- if (!teamplay)
- return 0;
-
- int c = 0;
-
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
- continue;
-
- if(vdist(it.origin - org, <, tc_radius))
- ++c;
- });
-
- return c;
-}
-
-// unused
-#if 0
-void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (CTF_SAMETEAM(this, head))
- break;
- head = head.ctf_worldflagnext;
- }
- if (head)
- navigation_routerating(this, head, ratingscale, 10000);
-}
-#endif
-
-void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if (CTF_SAMETEAM(this, head))
- {
- if (this.flagcarried)
- if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
- {
- head = head.ctf_worldflagnext; // skip base if it has a different group
- continue;
- }
- break;
- }
- head = head.ctf_worldflagnext;
- }
- if (!head)
- return;
-
- navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
-{
- entity head;
- head = ctf_worldflaglist;
- while (head)
- {
- if(ctf_oneflag)
- {
- if(CTF_DIFFTEAM(this, head))
- {
- if(head.team)
- {
- if(this.flagcarried)
- break;
- }
- else if(!this.flagcarried)
- break;
- }
- }
- else if(CTF_DIFFTEAM(this, head))
- break;
- head = head.ctf_worldflagnext;
- }
- if (head)
- navigation_routerating(this, head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
-{
- if (!bot_waypoints_for_items)
- {
- havocbot_goalrating_ctf_enemyflag(this, ratingscale);
- return;
- }
-
- entity head;
-
- head = havocbot_ctf_find_enemy_flag(this);
-
- if (!head)
- return;
-
- navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
-{
- entity mf;
-
- mf = havocbot_ctf_find_flag(this);
-
- if(mf.ctf_status == FLAG_BASE)
- return;
-
- if(mf.tag_entity)
- navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_droppedflags(entity this, 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==NULL) // dropped
- {
- if(df_radius)
- {
- if(vdist(org - head.origin, <, df_radius))
- navigation_routerating(this, head, ratingscale, 10000);
- }
- else
- navigation_routerating(this, head, ratingscale, 10000);
- }
-
- head = head.ctf_worldflagnext;
- }
-}
-
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
- IL_EACH(g_items, it.bot_pickup,
- {
- // gather health and armor only
- if (it.solid)
- if (it.health || it.armorvalue)
- if (vdist(it.origin - org, <, sradius))
- {
- // get the value of the item
- float t = it.bot_pickupevalfunc(this, it) * 0.0001;
- if (t > 0)
- navigation_routerating(this, it, t * ratingscale, 500);
- }
- });
-}
-
-void havocbot_ctf_reset_role(entity this)
-{
- float cdefense, cmiddle, coffense;
- entity mf, ef;
- float c;
-
- if(IS_DEAD(this))
- return;
-
- // Check ctf flags
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- mf = havocbot_ctf_find_flag(this);
- ef = havocbot_ctf_find_enemy_flag(this);
-
- // Retrieve stolen flag
- if(mf.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, 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(this, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
- // if there is only me on the team switch to offense
- c = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
-
- if(c==1)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
- return;
- }
-
- // Evaluate best position to take
- // Count mates on middle position
- cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
-
- // Count mates on defense position
- cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
-
- // Count mates on offense position
- coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
-
- if(cdefense<=coffense)
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
- else if(coffense<=cmiddle)
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
- else
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-}
-
-void havocbot_role_ctf_carrier(entity this)
-{
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried == NULL)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- if(ctf_oneflag)
- havocbot_goalrating_ctf_enemybase(this, 50000);
- else
- havocbot_goalrating_ctf_ourbase(this, 50000);
-
- if(this.health<100)
- havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
-
- entity head = ctf_worldflaglist;
- while (head)
- {
- if (this.goalentity == head.bot_basewaypoint)
- {
- this.goalentity_lock_timeout = time + 5;
- break;
- }
- head = head.ctf_worldflagnext;
- }
-
- if (this.goalentity)
- this.havocbot_cantfindflag = time + 10;
- else if (time > this.havocbot_cantfindflag)
- {
- // Can't navigate to my own base, suicide!
- // TODO: drop it and wander around
- Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
- return;
- }
- }
-}
-
-void havocbot_role_ctf_escort(entity this)
-{
- entity mf, ef;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If enemy flag is back on the base switch to previous role
- ef = havocbot_ctf_find_enemy_flag(this);
- if(ef.ctf_status==FLAG_BASE)
- {
- this.havocbot_role = this.havocbot_previous_role;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- // If the flag carrier reached the base switch to defense
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- if(vdist(ef.origin - mf.dropped_origin, <, 300))
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- {
- this.havocbot_role_timeout = time + random() * 30 + 60;
- }
-
- // If nothing happened just switch to previous role
- if (time > this.havocbot_role_timeout)
- {
- this.havocbot_role = this.havocbot_previous_role;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- // Chase the flag carrier
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- havocbot_goalrating_ctf_enemyflag(this, 30000);
- havocbot_goalrating_ctf_ourstolenflag(this, 40000);
- havocbot_goalrating_items(this, 10000, this.origin, 10000);
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ctf_offense(entity this)
-{
- entity mf, ef;
- vector pos;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // Check flags
- mf = havocbot_ctf_find_flag(this);
- ef = havocbot_ctf_find_enemy_flag(this);
-
- // 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(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
- {
- havocbot_role_ctf_setrole(this, 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(vdist(pos - mf.dropped_origin, >, 700))
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
- return;
- }
- }
-
- // About to fail, switch to middlefield
- if(this.health<50)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
- return;
- }
-
- // Set the role timeout if necessary
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 120;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- havocbot_goalrating_ctf_ourstolenflag(this, 50000);
- havocbot_goalrating_ctf_enemybase(this, 20000);
- havocbot_goalrating_items(this, 5000, this.origin, 1000);
- havocbot_goalrating_items(this, 1000, this.origin, 10000);
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever(entity this)
-{
- entity mf;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If flag is back on the base switch to previous role
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status==FLAG_BASE)
- {
- if (mf.enemy == this) // did this bot return the flag?
- navigation_goalrating_timeout_force(this);
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 20;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- float rt_radius;
- rt_radius = 10000;
-
- navigation_goalrating_start(this);
-
- havocbot_goalrating_ctf_ourstolenflag(this, 50000);
- havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
- havocbot_goalrating_ctf_enemybase(this, 30000);
- havocbot_goalrating_items(this, 500, this.origin, rt_radius);
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ctf_middle(entity this)
-{
- entity mf;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 10;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- vector org;
-
- org = havocbot_middlepoint;
- org.z = this.origin.z;
-
- navigation_goalrating_start(this);
-
- havocbot_goalrating_ctf_ourstolenflag(this, 50000);
- havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
- havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
- havocbot_goalrating_items(this, 2500, this.origin, 10000);
- havocbot_goalrating_ctf_enemybase(this, 2500);
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ctf_defense(entity this)
-{
- entity mf;
-
- if(IS_DEAD(this))
- {
- havocbot_ctf_reset_role(this);
- return;
- }
-
- if (this.flagcarried)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
- return;
- }
-
- // If own flag was captured
- mf = havocbot_ctf_find_flag(this);
- if(mf.ctf_status!=FLAG_BASE)
- {
- havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + 30;
-
- if (time > this.havocbot_role_timeout)
- {
- havocbot_ctf_reset_role(this);
- return;
- }
- if (navigation_goalrating_timeout(this))
- {
- vector org = mf.dropped_origin;
-
- navigation_goalrating_start(this);
-
- // if enemies are closer to our base, go there
- entity closestplayer = NULL;
- float distance, bestdistance = 10000;
- FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
- distance = vlen(org - it.origin);
- if(distance<bestdistance)
- {
- closestplayer = it;
- bestdistance = distance;
- }
- });
-
- if(closestplayer)
- if(DIFF_TEAM(closestplayer, this))
- if(vdist(org - this.origin, >, 1000))
- if(checkpvs(this.origin,closestplayer)||random()<0.5)
- havocbot_goalrating_ctf_ourbase(this, 30000);
-
- havocbot_goalrating_ctf_ourstolenflag(this, 20000);
- havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
- havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
- havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
- havocbot_goalrating_items(this, 5000, this.origin, 10000);
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ctf_setrole(entity bot, int role)
-{
- string s = "(null)";
- switch(role)
- {
- case HAVOCBOT_CTF_ROLE_CARRIER:
- s = "carrier";
- bot.havocbot_role = havocbot_role_ctf_carrier;
- bot.havocbot_role_timeout = 0;
- bot.havocbot_cantfindflag = time + 10;
- if (bot.havocbot_previous_role != bot.havocbot_role)
- navigation_goalrating_timeout_force(bot);
- break;
- case HAVOCBOT_CTF_ROLE_DEFENSE:
- s = "defense";
- bot.havocbot_role = havocbot_role_ctf_defense;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_MIDDLE:
- s = "middle";
- bot.havocbot_role = havocbot_role_ctf_middle;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_OFFENSE:
- s = "offense";
- bot.havocbot_role = havocbot_role_ctf_offense;
- bot.havocbot_role_timeout = 0;
- break;
- case HAVOCBOT_CTF_ROLE_RETRIEVER:
- s = "retriever";
- bot.havocbot_previous_role = bot.havocbot_role;
- bot.havocbot_role = havocbot_role_ctf_retriever;
- bot.havocbot_role_timeout = time + 10;
- if (bot.havocbot_previous_role != bot.havocbot_role)
- navigation_goalrating_timeout_expire(bot, 2);
- break;
- case HAVOCBOT_CTF_ROLE_ESCORT:
- s = "escort";
- bot.havocbot_previous_role = bot.havocbot_role;
- bot.havocbot_role = havocbot_role_ctf_escort;
- bot.havocbot_role_timeout = time + 30;
- if (bot.havocbot_previous_role != bot.havocbot_role)
- navigation_goalrating_timeout_expire(bot, 2);
- break;
- }
- LOG_TRACE(bot.netname, " switched to ", s);
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- int t = 0, t2 = 0, t3 = 0;
- bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
-
- // initially clear items so they can be set as necessary later.
- STAT(CTF_FLAGSTATUS, player) &= ~(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 | CTF_STALEMATE);
-
- // scan through all the flags and notify the client about them
- for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
- {
- if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
- if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
- if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
- if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
- if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
-
- switch(flag.ctf_status)
- {
- case FLAG_PASSING:
- case FLAG_CARRY:
- {
- if((flag.owner == player) || (flag.pass_sender == player))
- STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
- else
- STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
- break;
- }
- case FLAG_DROPPED:
- {
- STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
- break;
- }
- }
- }
-
- // item for stopping players from capturing the flag too often
- if(player.ctf_captureshielded)
- STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
-
- if(ctf_stalemate)
- STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
-
- // update the health of the flag carrier waypointsprite
- if(player.wps_flagcarrier)
- WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
-}
-
-MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(4, float);
- vector frag_force = M_ARGV(6, vector);
-
- 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;
- }
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(6, vector) = frag_force;
- }
- else if(frag_target.flagcarried && !IS_DEAD(frag_target) && 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?
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
-
- if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
- {
- GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
- GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
- }
-
- if(frag_target.flagcarried)
- {
- entity tmp_entity = frag_target.flagcarried;
- ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
- tmp_entity.ctf_dropper = NULL;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
-{
- M_ARGV(2, float) = 0; // frag score
- 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, NULL, DROP_NORMAL); }
-
- for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
- {
- if(flag.pass_sender == player) { flag.pass_sender = NULL; }
- if(flag.pass_target == player) { flag.pass_target = NULL; }
- if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- ctf_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
-{
- if(!autocvar_g_ctf_leaderboard)
- return;
-
- entity player = M_ARGV(0, entity);
-
- if(IS_REAL_CLIENT(player))
- {
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (int i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
-{
- if(!autocvar_g_ctf_leaderboard)
- return;
-
- entity player = M_ARGV(0, entity);
-
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.flagcarried)
- if(!autocvar_g_ctf_portalteleport)
- { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
-{
- if(MUTATOR_RETURNVALUE || game_stopped) return;
-
- entity player = M_ARGV(0, entity);
-
- if((time > player.throw_antispam) && !IS_DEAD(player) && !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 = NULL;
- 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) && !IS_DEAD(head))
- 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 && !head.flagcarried)
- {
- if(closest_target)
- {
- vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
- if(vlen2(passer_center - head_center) < vlen2(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, NULL, 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, NULL, DROP_THROW);
- return true;
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
- {
- player.wps_helpme_time = time;
- WaypointSprite_HelpMePing(player.wps_flagcarrier);
- }
- else // create a normal help me waypointsprite
- {
- WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
- WaypointSprite_Ping(player.wps_helpme);
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
-{
- entity player = M_ARGV(0, entity);
- entity veh = M_ARGV(1, entity);
-
- if(player.flagcarried)
- {
- if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
- {
- ctf_Handle_Throw(player, NULL, DROP_NORMAL);
- }
- else
- {
- player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
- setattachment(player.flagcarried, veh, "");
- setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
- player.flagcarried.scale = VEHICLE_FLAG_SCALE;
- //player.flagcarried.angles = '0 0 0';
- }
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.flagcarried)
- {
- setattachment(player.flagcarried, player, "");
- setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
- player.flagcarried.scale = FLAG_SCALE;
- player.flagcarried.angles = '0 0 0';
- player.flagcarried.nodrawtoclient = NULL;
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.flagcarried)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
- ctf_RespawnFlag(player.flagcarried);
- return true;
- }
-}
-
-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
- set_movetype(flag, 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;
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- havocbot_ctf_reset_role(bot);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
-{
- //M_ARGV(0, float) = ctf_teams;
- M_ARGV(1, string) = "ctf_team";
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(ctf, GetRecords)
-{
- int record_page = M_ARGV(0, int);
- string ret_string = M_ARGV(1, string);
-
- 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");
- }
- }
-
- M_ARGV(1, string) = ret_string;
-}
-
-bool superspec_Spectate(entity this, entity targ); // TODO
-void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
-MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
-{
- entity player = M_ARGV(0, entity);
- string cmd_name = M_ARGV(1, string);
- int cmd_argc = M_ARGV(2, int);
-
- if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
-
- if(cmd_name == "followfc")
- {
- if(!g_ctf)
- return true;
-
- int _team = 0;
- bool found = false;
-
- if(cmd_argc == 2)
- {
- switch(argv(1))
- {
- case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
- case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
- case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
- case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
- }
- }
-
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(it.flagcarried && (it.team == _team || _team == 0))
- {
- found = true;
- if(_team == 0 && IS_SPEC(player) && player.enemy == it)
- continue; // already spectating this fc, try another
- return superspec_Spectate(player, it);
- }
- });
-
- if(!found)
- superspec_msg("", "", player, "No active flag carrier\n", 1);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
-{
- entity frag_target = M_ARGV(0, entity);
-
- if(frag_target.flagcarried)
- ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
-}
-
-
-// ==========
-// 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) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_1, this);
-}
-
-/*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) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_2, this);
-}
-
-/*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) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_3, this);
-}
-
-/*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) { delete(this); return; }
-
- ctf_FlagSetup(NUM_TEAM_4, this);
-}
-
-/*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) { delete(this); return; }
- if(!cvar("g_ctf_oneflag")) { delete(this); return; }
-
- ctf_FlagSetup(0, this);
-}
-
-/*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) { delete(this); return; }
-
- this.classname = "ctf_team";
- this.team = this.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); }
-
-spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
-spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
-
-// compatibility for wop maps
-spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
-spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
-spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
-spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
-spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
-spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
-
-
-// ==============
-// Initialization
-// ==============
-
-// scoreboard setup
-void ctf_ScoreRules(int teams)
-{
- CheckAllowedTeams(NULL);
- GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
- field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
- field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
- field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_CTF_PICKUPS, "pickups", 0);
- field(SP_CTF_FCKILLS, "fckills", 0);
- field(SP_CTF_RETURNS, "returns", 0);
- field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
- });
-}
-
-// 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_pure(ctf_team);
- this.netname = teamname;
- this.cnt = teamcolor - 1;
- this.spawnfunc_checked = true;
- this.team = teamcolor;
-}
-
-void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
- ctf_teams = 0;
-
- 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); }
-
- switch(tmp_entity.team)
- {
- case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
- case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
- case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
- case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
- }
- if(tmp_entity.team == 0) { ctf_oneflag = true; }
- }
-
- havocbot_ctf_calculate_middlepoint();
-
- if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
- {
- ctf_teams = 0; // so set the default red and blue teams
- BITSET_ASSIGN(ctf_teams, BIT(0));
- BITSET_ASSIGN(ctf_teams, BIT(1));
- }
-
- //ctf_teams = bound(2, ctf_teams, 4);
-
- // if no teams are found, spawn defaults
- if(find(NULL, classname, "ctf_team") == NULL)
- {
- LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
- if(ctf_teams & BIT(0))
- ctf_SpawnTeam("Red", NUM_TEAM_1);
- if(ctf_teams & BIT(1))
- ctf_SpawnTeam("Blue", NUM_TEAM_2);
- if(ctf_teams & BIT(2))
- ctf_SpawnTeam("Yellow", NUM_TEAM_3);
- if(ctf_teams & BIT(3))
- ctf_SpawnTeam("Pink", NUM_TEAM_4);
- }
-
- 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;
-
- InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
-}
+++ /dev/null
-#pragma once
-
-#ifdef SVQC
-
-#include "../gamemode.qh"
-
-void ctf_Initialize();
-
-REGISTER_MUTATOR(ctf, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_limit_score(autocvar_capturelimit_override);
- GameRules_limit_lead(autocvar_captureleadlimit_override);
-
- ctf_Initialize();
- }
- return 0;
-}
-
-// used in cheats.qc
-void ctf_RespawnFlag(entity flag);
-
-// score rule declarations
-const int ST_CTF_CAPS = 1;
-
-CLASS(Flag, Pickup)
- ATTRIB(Flag, m_mins, vector, (PL_MIN_CONST + '0 0 -13') * 1.4); // scaling be damned
- ATTRIB(Flag, m_maxs, vector, (PL_MAX_CONST + '0 0 -13') * 1.4);
-ENDCLASS(Flag)
-Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
-void ctf_FlagTouch(entity this, entity toucher) { ITEM_HANDLE(Pickup, CTF_FLAG, this, toucher); }
-
-// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
-
-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;
-
-// score fields
-.float score_assist;
-.float score_capture;
-.float score_drop; // note: negated
-.float score_pickup;
-.float score_return;
-.float score_team_capture; // shouldn't be too high
-
-// 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 wps_helpme;
-.entity wps_flagbase;
-.entity wps_flagcarrier;
-.entity wps_flagdropped;
-.entity wps_flagreturn;
-.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;
-
-bool ctf_Stalemate_Customize(entity this, entity client);
-
-void ctf_Handle_Throw(entity player, entity receiver, float droptype);
-
-// 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_byworld;
-int ctf_teams;
-.entity enemy; // when flag is back in the base, it remembers last player who carried/touched the flag, useful to bots
-
-// 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;
-
-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))
-#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;
-const int CTF_STALEMATE = 8192;
+++ /dev/null
-#include "gamemode_cts.qh"
-
-#include <server/race.qh>
-#include <server/items.qh>
-
-float autocvar_g_cts_finish_kill_delay;
-bool autocvar_g_cts_selfdamage;
-
-// legacy bot roles
-.float race_checkpoint;
-void havocbot_role_cts(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- bool raw_touch_check = true;
- int cp = this.race_checkpoint;
-
- LABEL(search_racecheckpoints)
- IL_EACH(g_racecheckpoints, true,
- {
- if(it.cnt == cp || cp == -1)
- {
- // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
- // e.g. checkpoint in front of Stormkeep's warpzone
- // the same workaround is applied in Race game mode
- if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
- {
- cp = race_NextCheckpoint(cp);
- raw_touch_check = false;
- goto search_racecheckpoints;
- }
- navigation_routerating(this, it, 1000000, 5000);
- }
- });
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void cts_ScoreRules()
-{
- GameRules_score_enabled(false);
- GameRules_scoring(0, 0, 0, {
- if (g_race_qualifying) {
- field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- } else {
- field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
- }
- });
-}
-
-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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void KillIndicator_Think(entity this);
-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;
- setthink(e.killindicator, 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)
-{
- entity player = M_ARGV(0, entity);
- float dt = M_ARGV(1, float);
-
- player.race_movetime_frac += dt;
- float f = floor(player.race_movetime_frac);
- player.race_movetime_frac -= f;
- player.race_movetime_count += f;
- player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
- if(IS_PLAYER(player))
- {
- if (player.race_penalty)
- if (time > player.race_penalty)
- player.race_penalty = 0;
- if(player.race_penalty)
- {
- player.velocity = '0 0 0';
- set_movetype(player, MOVETYPE_NONE);
- player.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(CS(player).movement.x);
- wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
- CS(player).movement_x = wishspeed;
- else
- CS(player).movement_x = -wishspeed;
- CS(player).movement_y = 0;
- }
- else if(wishvel.y >= 2 * wishvel.x)
- {
- // pure Y motion
- CS(player).movement_x = 0;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = wishspeed;
- else
- CS(player).movement_y = -wishspeed;
- }
- else
- {
- // diagonal
- if(CS(player).movement.x > 0)
- CS(player).movement_x = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_x = -M_SQRT1_2 * wishspeed;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_y = -M_SQRT1_2 * wishspeed;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, reset_map_global)
-{
- float s;
-
- Score_NicePrint(NULL);
-
- race_ClearRecords();
- PlayerScore_Sort(race_place, 0, 1, 0);
-
- FOREACH_CLIENT(true, {
- if(it.race_place)
- {
- s = GameRules_scoring_add(it, RACE_FASTEST, 0);
- if(!s)
- it.race_place = 0;
- }
- cts_EventLog(ftos(it.race_place), it);
- });
-
- 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();
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-
- if(IS_REAL_CLIENT(player))
- {
- string rr = CTS_RECORD;
-
- msg_entity = player;
- 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;
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
-{
- entity player = M_ARGV(0, entity);
-
- if(autocvar_g_allow_checkpoints)
- race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(cts, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if(GameRules_scoring_add(player, RACE_FASTEST, 0))
- player.frags = FRAGS_LMS_LOSER;
- else
- player.frags = FRAGS_SPECTATOR;
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
- entity spawn_spot = M_ARGV(1, entity);
-
- if(spawn_spot.target == "")
- // Emergency: this wasn't a real spawnpoint. Can this ever happen?
- race_PreparePlayer(player);
-
- // if we need to respawn, do it right
- player.race_respawn_checkpoint = player.race_checkpoint;
- player.race_respawn_spotref = spawn_spot;
-
- player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(cts, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if(IS_PLAYER(player))
- if(!game_stopped)
- {
- if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
- race_PreparePlayer(player);
- else // respawn
- race_RetractPlayer(player);
-
- race_AbandonRaceCheck(player);
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- frag_target.respawn_flags |= RESPAWN_FORCE;
- race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- bot.havocbot_role = havocbot_role_cts;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetPressedKeys)
-{
- entity player = M_ARGV(0, entity);
-
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
-
- if (!IS_OBSERVER(player))
- {
- if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
- {
- speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
- speedaward_holder = player.netname;
- speedaward_uid = player.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);
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
-{
- // no weapon dropping in CTS
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, FilterItem)
-{
- entity item = M_ARGV(0, entity);
-
- if (Item_IsLoot(item))
- {
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Damage_Calculate)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_deathtype = M_ARGV(3, float);
- float frag_damage = M_ARGV(4, float);
-
- if(frag_target == frag_attacker || frag_deathtype == DEATH_FALL.m_id)
- if(!autocvar_g_cts_selfdamage)
- {
- frag_damage = 0;
- M_ARGV(4, float) = frag_damage;
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidPlayerScore_Clear)
-{
- return true; // in CTS, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(cts, GetRecords)
-{
- int record_page = M_ARGV(0, int);
- string ret_string = M_ARGV(1, string);
-
- 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");
- }
- }
-
- M_ARGV(1, string) = ret_string;
-}
-
-void ClientKill_Now(entity this);
-MUTATOR_HOOKFUNCTION(cts, ClientKill)
-{
- entity player = M_ARGV(0, entity);
-
- M_ARGV(1, float) = 0; // kill delay
-
- if(player.killindicator && player.killindicator.health == 1) // player.killindicator.health == 1 means that the kill indicator was spawned by CTS_ClientKill
- {
- delete(player.killindicator);
- player.killindicator = NULL;
-
- ClientKill_Now(player); // allow instant kill in this case
- return;
- }
-}
-
-MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
-{
- entity player = M_ARGV(0, entity);
-
- if(autocvar_g_cts_finish_kill_delay)
- CTS_ClientKill(player);
-}
-
-MUTATOR_HOOKFUNCTION(cts, HideTeamNagger)
-{
- return true; // doesn't work so well (but isn't cts a teamless mode?)
-}
-
-MUTATOR_HOOKFUNCTION(cts, FixClientCvars)
-{
- entity player = M_ARGV(0, entity);
-
- stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(cts, WantWeapon)
-{
- M_ARGV(1, float) = (M_ARGV(0, entity) == WEP_SHOTGUN); // want weapon = weapon info
- M_ARGV(3, bool) = true; // want mutator blocked
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(cts, ForbidDropCurrentWeapon)
-{
- return true;
-}
-
-void cts_Initialize()
-{
- cts_ScoreRules();
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-#include <server/race.qh>
-
-void cts_Initialize();
-
-REGISTER_MUTATOR(cts, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- g_race_qualifying = true;
- independent_players = 1;
- GameRules_limit_score(0);
- GameRules_limit_lead(0);
-
- cts_Initialize();
- }
- return 0;
-}
-
-// scores
-const float ST_CTS_LAPS = 1;
+++ /dev/null
-#include "gamemode_deathmatch.qh"
-
-MUTATOR_HOOKFUNCTION(dm, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-REGISTER_MUTATOR(dm, false)
-{
- MUTATOR_STATIC();
- return 0;
-}
+++ /dev/null
-#include "gamemode_domination.qh"
-
-#include <server/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;
-float autocvar_g_domination_round_timelimit;
-float autocvar_g_domination_warmup;
-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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void set_dom_state(entity e)
-{
- STAT(DOM_TOTAL_PPS, e) = total_pps;
- STAT(DOM_PPS_RED, e) = pps_red;
- STAT(DOM_PPS_BLUE, e) = pps_blue;
- if(domination_teams >= 3)
- STAT(DOM_PPS_YELLOW, e) = pps_yellow;
- if(domination_teams >= 4)
- STAT(DOM_PPS_PINK, e) = pps_pink;
-}
-
-void dompoint_captured(entity this)
-{
- float old_delay, old_team, real_team;
-
- // now that the delay has expired, switch to the latest team to lay claim to this point
- entity head = this.owner;
-
- real_team = this.cnt;
- this.cnt = -1;
-
- dom_EventLog("taken", this.team, this.dmg_inflictor);
- this.dmg_inflictor = NULL;
-
- this.goalentity = head;
- this.model = head.mdl;
- this.modelindex = head.dmg;
- this.skin = head.skin;
-
- float points, wait_time;
- if (autocvar_g_domination_point_amt)
- points = autocvar_g_domination_point_amt;
- else
- points = this.frags;
- if (autocvar_g_domination_point_rate)
- wait_time = autocvar_g_domination_point_rate;
- else
- wait_time = this.wait;
-
- if(domination_roundbased)
- bprint(sprintf("^3%s^3%s\n", head.netname, this.message));
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_DOMINATION_CAPTURE_TIME, head.netname, this.message, points, wait_time);
-
- if(this.enemy.playerid == this.enemy_playerid)
- GameRules_scoring_add(this.enemy, DOM_TAKES, 1);
- else
- this.enemy = NULL;
-
- if (head.noise != "")
- if(this.enemy)
- _sound(this.enemy, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
- else
- _sound(this, CH_TRIGGER, head.noise, VOL_BASE, ATTEN_NORM);
- if (head.noise1 != "")
- play2all(head.noise1);
-
- this.delay = time + wait_time;
-
- // do trigger work
- old_delay = this.delay;
- old_team = this.team;
- this.team = real_team;
- this.delay = 0;
- SUB_UseTargets (this, this, NULL);
- this.delay = old_delay;
- this.team = old_team;
-
- entity msg = WP_DomNeut;
- switch(real_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(this.sprite, msg, WP_Null, WP_Null);
-
- total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
- IL_EACH(g_dompoints, true,
- {
- if (autocvar_g_domination_point_amt)
- points = autocvar_g_domination_point_amt;
- else
- points = it.frags;
- if (autocvar_g_domination_point_rate)
- wait_time = autocvar_g_domination_point_rate;
- else
- wait_time = it.wait;
- switch(it.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(this.sprite, RADARICON_DOMPOINT, colormapPaletteColor(this.goalentity.team - 1, 0));
- WaypointSprite_Ping(this.sprite);
-
- this.captime = time;
-
- FOREACH_CLIENT(IS_REAL_CLIENT(it), { set_dom_state(it); });
-}
-
-void AnimateDomPoint(entity this)
-{
- if(this.pain_finished > time)
- return;
- this.pain_finished = time + this.t_width;
- if(this.nextthink > this.pain_finished)
- this.nextthink = this.pain_finished;
-
- this.frame = this.frame + 1;
- if(this.frame > this.t_length)
- this.frame = 0;
-}
-
-void dompointthink(entity this)
-{
- float fragamt;
-
- this.nextthink = time + 0.1;
-
- //this.frame = this.frame + 1;
- //if(this.frame > 119)
- // this.frame = 0;
- AnimateDomPoint(this);
-
- // give points
-
- if (game_stopped || this.delay > time || time < game_starttime) // game has ended, don't keep giving points
- return;
-
- if(autocvar_g_domination_point_rate)
- this.delay = time + autocvar_g_domination_point_rate;
- else
- this.delay = time + this.wait;
-
- // give credit to the team
- // NOTE: this defaults to 0
- if (!domination_roundbased)
- if (this.goalentity.netname != "")
- {
- if(autocvar_g_domination_point_amt)
- fragamt = autocvar_g_domination_point_amt;
- else
- fragamt = this.frags;
- TeamScore_AddToTeam(this.goalentity.team, ST_SCORE, fragamt);
- TeamScore_AddToTeam(this.goalentity.team, ST_DOM_TICKS, fragamt);
-
- // give credit to the individual player, if he is still there
- if (this.enemy.playerid == this.enemy_playerid)
- {
- GameRules_scoring_add(this.enemy, SCORE, fragamt);
- GameRules_scoring_add(this.enemy, DOM_TICKS, fragamt);
- }
- else
- this.enemy = NULL;
- }
-}
-
-void dompointtouch(entity this, entity toucher)
-{
- if (!IS_PLAYER(toucher))
- return;
- if (toucher.health < 1)
- return;
-
- if(round_handler_IsActive() && !round_handler_IsRoundStarted())
- return;
-
- if(time < this.captime + 0.3)
- return;
-
- // only valid teams can claim it
- entity head = find(NULL, classname, "dom_team");
- while (head && head.team != toucher.team)
- head = find(head, classname, "dom_team");
- if (!head || head.netname == "" || head == this.goalentity)
- return;
-
- // delay capture
-
- this.team = this.goalentity.team; // this stores the PREVIOUS team!
-
- this.cnt = toucher.team;
- this.owner = head; // team to switch to after the delay
- this.dmg_inflictor = toucher;
-
- // this.state = 1;
- // this.delay = time + cvar("g_domination_point_capturetime");
- //this.nextthink = time + cvar("g_domination_point_capturetime");
- //this.think = dompoint_captured;
-
- // go to neutral team in the mean time
- head = find(NULL, classname, "dom_team");
- while (head && head.netname != "")
- head = find(head, classname, "dom_team");
- if(head == NULL)
- return;
-
- WaypointSprite_UpdateSprites(this.sprite, WP_DomNeut, WP_Null, WP_Null);
- WaypointSprite_UpdateTeamRadar(this.sprite, RADARICON_DOMPOINT, '0 1 1');
- WaypointSprite_Ping(this.sprite);
-
- this.goalentity = head;
- this.model = head.mdl;
- this.modelindex = head.dmg;
- this.skin = head.skin;
-
- this.enemy = toucher; // individual player scoring
- this.enemy_playerid = toucher.playerid;
- dompoint_captured(this);
-}
-
-void dom_controlpoint_setup(entity this)
-{
- entity head;
- // find the spawnfunc_dom_team representing unclaimed points
- head = find(NULL, classname, "dom_team");
- while(head && head.netname != "")
- head = find(head, classname, "dom_team");
- if (!head)
- objerror(this, "no spawnfunc_dom_team with netname \"\" found\n");
-
- // copy important properties from spawnfunc_dom_team entity
- this.goalentity = head;
- _setmodel(this, head.mdl); // precision already set
- this.skin = head.skin;
-
- this.cnt = -1;
-
- if(this.message == "")
- this.message = " has captured a control point";
-
- if(this.frags <= 0)
- this.frags = 1;
- if(this.wait <= 0)
- this.wait = 5;
-
- float points, waittime;
- if (autocvar_g_domination_point_amt)
- points = autocvar_g_domination_point_amt;
- else
- points = this.frags;
- if (autocvar_g_domination_point_rate)
- waittime = autocvar_g_domination_point_rate;
- else
- waittime = this.wait;
-
- total_pps += points/waittime;
-
- if(!this.t_width)
- this.t_width = 0.02; // frame animation rate
- if(!this.t_length)
- this.t_length = 239; // maximum frame
-
- setthink(this, dompointthink);
- this.nextthink = time;
- settouch(this, dompointtouch);
- this.solid = SOLID_TRIGGER;
- if(!this.flags & FL_ITEM)
- IL_PUSH(g_items, this);
- this.flags = FL_ITEM;
- setsize(this, '-32 -32 -32', '32 32 32');
- setorigin(this, this.origin + '0 0 20');
- droptofloor(this);
-
- waypoint_spawnforitem(this);
- WaypointSprite_SpawnFixed(WP_DomNeut, this.origin + '0 0 32', this, sprite, RADARICON_DOMPOINT);
-}
-
-float total_controlpoints;
-void Domination_count_controlpoints()
-{
- total_controlpoints = redowned = blueowned = yellowowned = pinkowned = 0;
- IL_EACH(g_dompoints, true,
- {
- ++total_controlpoints;
- redowned += (it.goalentity.team == NUM_TEAM_1);
- blueowned += (it.goalentity.team == NUM_TEAM_2);
- yellowowned += (it.goalentity.team == NUM_TEAM_3);
- pinkowned += (it.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, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-
- game_stopped = true;
- 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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- TeamScore_AddToTeam(winner_team, ST_DOM_CAPS, +1);
- }
- else if(winner_team == -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
- }
-
- game_stopped = true;
- round_handler_Init(5, autocvar_g_domination_warmup, autocvar_g_domination_round_timelimit);
-
- return 1;
-}
-
-float Domination_CheckPlayers()
-{
- return 1;
-}
-
-void Domination_RoundStart()
-{
- FOREACH_CLIENT(IS_PLAYER(it), { it.player_blocked = false; });
-}
-
-//go to best items, or control points you don't own
-void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
-{
- IL_EACH(g_dompoints, vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
- {
- if(it.cnt > -1) // this is just being fought
- navigation_routerating(this, it, ratingscale, 5000);
- else if(it.goalentity.cnt == 0) // unclaimed
- navigation_routerating(this, it, ratingscale * 0.5, 5000);
- else if(it.goalentity.team != this.team) // other team's point
- navigation_routerating(this, it, ratingscale * 0.2, 5000);
- });
-}
-
-void havocbot_role_dom(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_controlpoints(this, 10000, this.origin, 15000);
- havocbot_goalrating_items(this, 8000, this.origin, 8000);
- //havocbot_goalrating_enemyplayers(this, 3000, this.origin, 2000);
- havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-MUTATOR_HOOKFUNCTION(dom, CheckAllowedTeams)
-{
- // fallback?
- M_ARGV(0, float) = domination_teams;
- string ret_string = "dom_team";
-
- entity head = find(NULL, 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);
- }
-
- M_ARGV(1, string) = string_null;
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, reset_map_players)
-{
- total_pps = 0, pps_red = 0, pps_blue = 0, pps_yellow = 0, pps_pink = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- PutClientInServer(it);
- if(domination_roundbased)
- it.player_blocked = 1;
- if(IS_REAL_CLIENT(it))
- set_dom_state(it);
- });
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(dom, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(domination_roundbased)
- if(!round_handler_IsRoundStarted())
- player.player_blocked = 1;
- else
- player.player_blocked = 0;
-}
-
-MUTATOR_HOOKFUNCTION(dom, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- set_dom_state(player);
-}
-
-MUTATOR_HOOKFUNCTION(dom, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- bot.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)
- {
- delete(this);
- return;
- }
- setthink(this, dom_controlpoint_setup);
- this.nextthink = time + 0.1;
- this.reset = dom_controlpoint_setup;
-
- if(!this.scale)
- this.scale = 0.6;
-
- this.effects = this.effects | EF_LOWPRECISION;
- if (autocvar_g_domination_point_fullbright)
- this.effects |= EF_FULLBRIGHT;
-
- IL_PUSH(g_dompoints, this);
-}
-
-/*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)
- {
- delete(this);
- return;
- }
- precache_model(this.model);
- if (this.noise != "")
- precache_sound(this.noise);
- if (this.noise1 != "")
- precache_sound(this.noise1);
- this.classname = "dom_team";
- _setmodel(this, this.model); // precision not needed
- this.mdl = this.model;
- this.dmg = this.modelindex;
- this.model = "";
- this.modelindex = 0;
- // this would have to be changed if used in quakeworld
- if(this.cnt)
- this.team = this.cnt + 1; // WHY are these different anyway?
-}
-
-// scoreboard setup
-void ScoreRules_dom(int teams)
-{
- if(domination_roundbased)
- {
- GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
- field_team(ST_DOM_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
- field(SP_DOM_TAKES, "takes", 0);
- });
- }
- 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;
- GameRules_scoring(teams, sp_score, sp_score, {
- field_team(ST_DOM_TICKS, "ticks", sp_domticks);
- field(SP_DOM_TICKS, "ticks", sp_domticks);
- field(SP_DOM_TAKES, "takes", 0);
- });
- }
-}
-
-// 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, Sound capsound, string capnarration, string capmessage)
-{
- TC(Sound, capsound);
- entity e = new_pure(dom_team);
- e.netname = strzone(teamname);
- e.cnt = teamcolor;
- e.model = pointmodel;
- e.skin = pointskin;
- e.noise = strzone(Sound_fixpath(capsound));
- e.noise1 = strzone(capnarration);
- e.message = strzone(capmessage);
-
- // this code is identical to spawnfunc_dom_team
- _setmodel(e, e.model); // precision not needed
- e.mdl = e.model;
- e.dmg = e.modelindex;
- e.model = "";
- e.modelindex = 0;
- // this would have to be changed if used in quakeworld
- e.team = e.cnt + 1;
-
- //eprint(e);
-}
-
-void dom_spawnpoint(vector org)
-{
- entity e = spawn();
- e.classname = "dom_controlpoint";
- setthink(e, spawnfunc_dom_controlpoint);
- e.nextthink = time;
- setorigin(e, org);
- spawnfunc_dom_controlpoint(e);
-}
-
-// spawn some default teams if the map is not set up for domination
-void dom_spawnteams(int teams)
-{
- TC(int, teams);
- dom_spawnteam(Team_ColoredFullName(NUM_TEAM_1), NUM_TEAM_1-1, "models/domination/dom_red.md3", 0, SND_DOM_CLAIM, "", "Red team has captured a control point");
- dom_spawnteam(Team_ColoredFullName(NUM_TEAM_2), 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(Team_ColoredFullName(NUM_TEAM_3), 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(Team_ColoredFullName(NUM_TEAM_4), 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, SND_Null, "", "");
-}
-
-void dom_DelayedInit(entity this) // 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(NULL, classname, "dom_team") == NULL || autocvar_g_domination_teams_override >= 2)
- {
- LOG_TRACE("No \"dom_team\" entities found on this map, creating them anyway.");
- domination_teams = autocvar_g_domination_teams_override;
- if (domination_teams < 2)
- domination_teams = autocvar_g_domination_default_teams;
- domination_teams = bound(2, domination_teams, 4);
- dom_spawnteams(domination_teams);
- }
-
- CheckAllowedTeams(NULL);
- //domination_teams = ((c4>=0) ? 4 : (c3>=0) ? 3 : 2);
-
- int teams = 0;
- if(c1 >= 0) teams |= BIT(0);
- if(c2 >= 0) teams |= BIT(1);
- if(c3 >= 0) teams |= BIT(2);
- if(c4 >= 0) teams |= BIT(3);
- domination_teams = teams;
-
- 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(NULL, dom_DelayedInit, INITPRIO_GAMETYPE);
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-#define autocvar_g_domination_point_limit cvar("g_domination_point_limit")
-bool autocvar_g_domination_roundbased;
-int autocvar_g_domination_roundbased_point_limit;
-int autocvar_g_domination_point_leadlimit;
-
-void dom_Initialize();
-
-REGISTER_MUTATOR(dom, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- 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;
-
- GameRules_teams(true);
- GameRules_limit_score(fraglimit_override);
- GameRules_limit_lead(autocvar_g_domination_point_leadlimit);
-
- dom_Initialize();
- }
- return 0;
-}
-
-// score rule declarations
-const float ST_DOM_TICKS = 1;
-const float ST_DOM_CAPS = 1;
-
-// pps: points per second
-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;
-
-void AnimateDomPoint(entity this);
-
-IntrusiveList g_dompoints;
-STATIC_INIT(g_dompoints) { g_dompoints = IL_NEW(); }
+++ /dev/null
-#include "gamemode_freezetag.qh"
-
-float autocvar_g_freezetag_frozen_maxtime;
-float autocvar_g_freezetag_revive_clearspeed;
-float autocvar_g_freezetag_round_timelimit;
-//int autocvar_g_freezetag_teams;
-int autocvar_g_freezetag_teams_override;
-float autocvar_g_freezetag_warmup;
-
-void freezetag_count_alive_players()
-{
- total_players = redalive = bluealive = yellowalive = pinkalive = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- switch(it.team)
- {
- case NUM_TEAM_1: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++redalive; break;
- case NUM_TEAM_2: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++bluealive; break;
- case NUM_TEAM_3: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++yellowalive; break;
- case NUM_TEAM_4: ++total_players; if(it.health >= 1 && STAT(FROZEN, it) != 1) ++pinkalive; break;
- }
- });
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- STAT(REDALIVE, it) = redalive;
- STAT(BLUEALIVE, it) = bluealive;
- STAT(YELLOWALIVE, it) = yellowalive;
- STAT(PINKALIVE, it) = pinkalive;
- });
-
- eliminatedPlayers.SendFlags |= 1;
-}
-#define FREEZETAG_ALIVE_TEAMS() ((redalive > 0) + (bluealive > 0) + (yellowalive > 0) + (pinkalive > 0))
-#define FREEZETAG_ALIVE_TEAMS_OK() (FREEZETAG_ALIVE_TEAMS() == NumTeams(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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return 1;
- }
- if(total_players == 0)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- return 0;
- }
- int missing_teams_mask = 0;
- if(freezetag_teams & BIT(0))
- missing_teams_mask += (!redalive) * 1;
- if(freezetag_teams & BIT(1))
- missing_teams_mask += (!bluealive) * 2;
- if(freezetag_teams & BIT(2))
- missing_teams_mask += (!yellowalive) * 4;
- if(freezetag_teams & BIT(3))
- missing_teams_mask += (!pinkalive) * 8;
- if(prev_missing_teams_mask != missing_teams_mask)
- {
- Send_Notification(NOTIF_ALL, NULL, 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
-}
-
-void nades_Clear(entity);
-void nades_GiveBonus(entity player, float score);
-
-float freezetag_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
- FOREACH_CLIENT(IS_PLAYER(it), {
- it.freezetag_frozen_timeout = 0;
- nades_Clear(it);
- });
- game_stopped = true;
- round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
- return 1;
- }
-
- if(FREEZETAG_ALIVE_TEAMS() > 1)
- return 0;
-
- int winner_team = freezetag_getWinnerTeam();
- if(winner_team > 0)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- TeamScore_AddToTeam(winner_team, ST_SCORE, +1);
- }
- else if(winner_team == -1)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
- }
-
- FOREACH_CLIENT(IS_PLAYER(it), {
- it.freezetag_frozen_timeout = 0;
- nades_Clear(it);
- });
-
- game_stopped = true;
- round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
- return 1;
-}
-
-entity freezetag_LastPlayerForTeam(entity this)
-{
- entity last_pl = NULL;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
- if(it.health >= 1)
- if(!STAT(FROZEN, it))
- if(SAME_TEAM(it, this))
- if(!last_pl)
- last_pl = it;
- else
- return NULL;
- });
- return last_pl;
-}
-
-void freezetag_LastPlayerForTeam_Notify(entity this)
-{
- if(round_handler_IsActive())
- if(round_handler_IsRoundStarted())
- {
- entity pl = freezetag_LastPlayerForTeam(this);
- if(pl)
- Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
- }
-}
-
-void freezetag_Add_Score(entity targ, entity attacker)
-{
- if(attacker == targ)
- {
- // you froze your own dumb targ
- // counted as "suicide" already
- GameRules_scoring_add(targ, SCORE, -1);
- }
- else if(IS_PLAYER(attacker))
- {
- // got frozen by an enemy
- // counted as "kill" and "death" already
- GameRules_scoring_add(targ, SCORE, -1);
- GameRules_scoring_add(attacker, SCORE, +1);
- }
- // else nothing - got frozen by the game type rules themselves
-}
-
-void freezetag_Freeze(entity targ, entity attacker)
-{
- if(STAT(FROZEN, targ))
- return;
-
- if(autocvar_g_freezetag_frozen_maxtime > 0)
- targ.freezetag_frozen_timeout = time + autocvar_g_freezetag_frozen_maxtime;
-
- Freeze(targ, 0, 1, true);
-
- freezetag_count_alive_players();
-
- freezetag_Add_Score(targ, attacker);
-}
-
-void freezetag_Unfreeze(entity this)
-{
- this.freezetag_frozen_time = 0;
- this.freezetag_frozen_timeout = 0;
-
- Unfreeze(this);
-}
-
-float freezetag_isEliminated(entity e)
-{
- if(IS_PLAYER(e) && (STAT(FROZEN, e) == 1 || IS_DEAD(e)))
- return true;
- return false;
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void(entity this) havocbot_role_ft_freeing;
-void(entity this) havocbot_role_ft_offense;
-
-void havocbot_goalrating_freeplayers(entity this, float ratingscale, vector org, float sradius)
-{
- float t;
- FOREACH_CLIENT(IS_PLAYER(it) && it != this && SAME_TEAM(it, this), {
- if (STAT(FROZEN, it) == 1)
- {
- if(vdist(it.origin - org, >, sradius))
- continue;
- navigation_routerating(this, it, ratingscale, 2000);
- }
- else if(vdist(it.origin - org, >, 400)) // avoid gathering all teammates in one place
- {
- // If teamate is not frozen still seek them out as fight better
- // in a group.
- t = 0.2 * 150 / (this.health + this.armorvalue);
- navigation_routerating(this, it, t * ratingscale, 2000);
- }
- });
-}
-
-void havocbot_role_ft_offense(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
-
- // Count how many players on team are unfrozen.
- int unfrozen = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this) && !(STAT(FROZEN, it) != 1), { unfrozen++; });
-
- // If only one left on team or if role has timed out then start trying to free players.
- if (((unfrozen == 0) && (!STAT(FROZEN, this))) || (time > this.havocbot_role_timeout))
- {
- LOG_TRACE("changing role to freeing");
- this.havocbot_role = havocbot_role_ft_freeing;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
- havocbot_goalrating_freeplayers(this, 9000, this.origin, 10000);
- havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_ft_freeing(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
-
- if (time > this.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to offense");
- this.havocbot_role = havocbot_role_ft_offense;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_items(this, 8000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 10000, this.origin, 10000);
- havocbot_goalrating_freeplayers(this, 20000, this.origin, 10000);
- havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-void ft_RemovePlayer(entity this)
-{
- this.health = 0; // neccessary to update correctly alive stats
- if(!STAT(FROZEN, this))
- freezetag_LastPlayerForTeam_Notify(this);
- freezetag_Unfreeze(this);
- freezetag_count_alive_players();
-}
-
-MUTATOR_HOOKFUNCTION(ft, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- ft_RemovePlayer(player);
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- ft_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_deathtype = M_ARGV(3, float);
-
- if(round_handler_IsActive())
- if(round_handler_CountdownRunning())
- {
- if(STAT(FROZEN, frag_target))
- freezetag_Unfreeze(frag_target);
- freezetag_count_alive_players();
- return true; // 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(STAT(FROZEN, frag_target) != 1)
- {
- freezetag_Add_Score(frag_target, frag_attacker);
- freezetag_count_alive_players();
- freezetag_LastPlayerForTeam_Notify(frag_target);
- }
- else
- freezetag_Unfreeze(frag_target); // remove ice
- frag_target.health = 0; // Unfreeze resets health
- frag_target.freezetag_frozen_timeout = -2; // freeze on respawn
- return true;
- }
-
- if(STAT(FROZEN, frag_target))
- return true;
-
- freezetag_Freeze(frag_target, frag_attacker);
- freezetag_LastPlayerForTeam_Notify(frag_target);
-
- if(frag_attacker == frag_target || frag_attacker == NULL)
- {
- if(IS_PLAYER(frag_target))
- Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_FREEZETAG_SELF);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_SELF, frag_target.netname);
- }
- else
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_FREEZE, frag_target.netname, frag_attacker.netname);
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.freezetag_frozen_timeout == -1) // if PlayerSpawn is called by reset_map_players
- return true; // do nothing, round is starting right now
-
- if(player.freezetag_frozen_timeout == -2) // player was dead
- {
- freezetag_Freeze(player, NULL);
- return true;
- }
-
- freezetag_count_alive_players();
-
- if(round_handler_IsActive())
- if(round_handler_IsRoundStarted())
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_SPAWN_LATE);
- freezetag_Freeze(player, NULL);
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, reset_map_players)
-{
- FOREACH_CLIENT(IS_PLAYER(it), {
- CS(it).killcount = 0;
- it.freezetag_frozen_timeout = -1;
- PutClientInServer(it);
- it.freezetag_frozen_timeout = 0;
- });
- freezetag_count_alive_players();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, GiveFragsForKill, CBC_ORDER_FIRST)
-{
- M_ARGV(2, float) = 0; // no frags counted in Freeze Tag
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, PlayerPreThink, CBC_ORDER_FIRST)
-{
- if(game_stopped)
- return true;
-
- if(round_handler_IsActive())
- if(!round_handler_IsRoundStarted())
- return true;
-
- int n;
- entity o = NULL;
- entity player = M_ARGV(0, entity);
- //if(STAT(FROZEN, player))
- //if(player.freezetag_frozen_timeout > 0 && time < player.freezetag_frozen_timeout)
- //player.iceblock.alpha = ICE_MIN_ALPHA + (ICE_MAX_ALPHA - ICE_MIN_ALPHA) * (player.freezetag_frozen_timeout - time) / (player.freezetag_frozen_timeout - player.freezetag_frozen_time);
-
- if(player.freezetag_frozen_timeout > 0 && time >= player.freezetag_frozen_timeout)
- n = -1;
- else
- {
- vector revive_extra_size = '1 1 1' * autocvar_g_freezetag_revive_extra_size;
- n = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
- if(STAT(FROZEN, it) == 0)
- if(!IS_DEAD(it))
- if(SAME_TEAM(it, player))
- if(boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
- {
- if(!o)
- o = it;
- if(STAT(FROZEN, player) == 1)
- it.reviving = true;
- ++n;
- }
- });
-
- }
-
- if(n && STAT(FROZEN, player) == 1) // OK, there is at least one teammate reviving us
- {
- STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * max(1/60, autocvar_g_freezetag_revive_speed), 1);
- player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
-
- if(STAT(REVIVE_PROGRESS, player) >= 1)
- {
- freezetag_Unfreeze(player);
- freezetag_count_alive_players();
-
- if(n == -1)
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_AUTO_REVIVED, autocvar_g_freezetag_frozen_maxtime);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_AUTO_REVIVED, player.netname, autocvar_g_freezetag_frozen_maxtime);
- return true;
- }
-
- // EVERY team mate nearby gets a point (even if multiple!)
- FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
- GameRules_scoring_add(it, FREEZETAG_REVIVALS, +1);
- GameRules_scoring_add(it, SCORE, +1);
- nades_GiveBonus(it,autocvar_g_nades_bonus_score_low);
- });
-
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_FREEZETAG_REVIVED, o.netname);
- Send_Notification(NOTIF_ONE, o, MSG_CENTER, CENTER_FREEZETAG_REVIVE, player.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_FREEZETAG_REVIVED, player.netname, o.netname);
- }
-
- FOREACH_CLIENT(IS_PLAYER(it) && it.reviving, {
- STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
- it.reviving = false;
- });
- }
- else if(!n && STAT(FROZEN, player) == 1) // only if no teammate is nearby will we reset
- {
- STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * autocvar_g_freezetag_revive_clearspeed, 1);
- player.health = max(1, STAT(REVIVE_PROGRESS, player) * ((warmup_stage) ? warmup_start_health : start_health));
- }
- else if(!n && !STAT(FROZEN, player))
- {
- STAT(REVIVE_PROGRESS, player) = 0; // thawing nobody
- }
-
- return true;
-}
-
-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");
-}
-
-MUTATOR_HOOKFUNCTION(ft, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- if (!IS_DEAD(bot))
- {
- if (random() < 0.5)
- bot.havocbot_role = havocbot_role_ft_freeing;
- else
- bot.havocbot_role = havocbot_role_ft_offense;
- }
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ft, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = freezetag_teams;
-}
-
-MUTATOR_HOOKFUNCTION(ft, SetWeaponArena)
-{
- // most weapons arena
- if(M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")
- M_ARGV(0, string) = "most";
-}
-
-MUTATOR_HOOKFUNCTION(ft, FragCenterMessage)
-{
- entity frag_attacker = M_ARGV(0, entity);
- entity frag_target = M_ARGV(1, entity);
- //float frag_deathtype = M_ARGV(2, float);
- int kill_count_to_attacker = M_ARGV(3, int);
- int kill_count_to_target = M_ARGV(4, int);
-
- if(STAT(FROZEN, frag_target))
- return; // target was already frozen, so this is just pushing them off the cliff
-
- Send_Notification(NOTIF_ONE, frag_attacker, MSG_CHOICE, CHOICE_FRAG_FREEZE, frag_target.netname, kill_count_to_attacker, (IS_BOT_CLIENT(frag_target) ? -1 : CS(frag_target).ping));
- Send_Notification(NOTIF_ONE, frag_target, MSG_CHOICE, CHOICE_FRAGGED_FREEZE, frag_attacker.netname, kill_count_to_target, frag_attacker.health, frag_attacker.armorvalue, (IS_BOT_CLIENT(frag_attacker) ? -1 : CS(frag_attacker).ping));
-
- return true;
-}
-
-void freezetag_Initialize()
-{
- freezetag_teams = autocvar_g_freezetag_teams_override;
- if(freezetag_teams < 2)
- freezetag_teams = cvar("g_freezetag_teams"); // read the cvar directly as it gets written earlier in the same frame
-
- freezetag_teams = BITS(bound(2, freezetag_teams, 4));
- GameRules_scoring(freezetag_teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
- field(SP_FREEZETAG_REVIVALS, "revivals", 0);
- });
-
- round_handler_Spawn(freezetag_CheckTeams, freezetag_CheckWinner, func_null);
- round_handler_Init(5, autocvar_g_freezetag_warmup, autocvar_g_freezetag_round_timelimit);
-
- EliminatedPlayers_Init(freezetag_isEliminated);
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-int autocvar_g_freezetag_point_limit;
-int autocvar_g_freezetag_point_leadlimit;
-bool autocvar_g_freezetag_team_spawns;
-void freezetag_Initialize();
-
-REGISTER_MUTATOR(ft, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_freezetag_team_spawns);
- GameRules_limit_score(autocvar_g_freezetag_point_limit);
- GameRules_limit_lead(autocvar_g_freezetag_point_leadlimit);
-
- freezetag_Initialize();
- }
- return 0;
-}
-
-.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
-
-float autocvar_g_freezetag_revive_extra_size;
-float autocvar_g_freezetag_revive_speed;
-bool autocvar_g_freezetag_revive_nade;
-float autocvar_g_freezetag_revive_nade_health;
+++ /dev/null
-#include "gamemode_invasion.qh"
-
-#include <common/monsters/sv_spawn.qh>
-#include <common/monsters/sv_monsters.qh>
-
-#include <server/teamplay.qh>
-
-IntrusiveList g_invasion_roundends;
-IntrusiveList g_invasion_waves;
-IntrusiveList g_invasion_spawns;
-STATIC_INIT(g_invasion)
-{
- g_invasion_roundends = IL_NEW();
- g_invasion_waves = IL_NEW();
- g_invasion_spawns = IL_NEW();
-}
-
-float autocvar_g_invasion_round_timelimit;
-float autocvar_g_invasion_spawnpoint_spawn_delay;
-float autocvar_g_invasion_warmup;
-int autocvar_g_invasion_monster_count;
-bool autocvar_g_invasion_zombies_only;
-float autocvar_g_invasion_spawn_delay;
-
-bool victent_present;
-.bool inv_endreached;
-
-bool inv_warning_shown; // spammy
-
-.string spawnmob;
-
-void target_invasion_roundend_use(entity this, entity actor, entity trigger)
-{
- if(!IS_PLAYER(actor)) { return; }
-
- actor.inv_endreached = true;
-
- int plnum = 0;
- int realplnum = 0;
- // let's not count bots
- FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
- ++realplnum;
- if(it.inv_endreached)
- ++plnum;
- });
- if(plnum < ceil(realplnum * min(1, this.count))) // 70% of players
- return;
-
- this.winning = true;
-}
-
-spawnfunc(target_invasion_roundend)
-{
- if(!g_invasion) { delete(this); return; }
-
- victent_present = true; // a victory entity is present, we don't need to rely on monster count TODO: merge this with the intrusive list (can check empty)
-
- if(!this.count) { this.count = 0.7; } // require at least 70% of the players to reach the end before triggering victory
-
- this.use = target_invasion_roundend_use;
-
- IL_PUSH(g_invasion_roundends, this);
-}
-
-spawnfunc(invasion_wave)
-{
- if(!g_invasion) { delete(this); return; }
-
- IL_PUSH(g_invasion_waves, this);
-}
-
-spawnfunc(invasion_spawnpoint)
-{
- if(!g_invasion) { delete(this); return; }
-
- this.classname = "invasion_spawnpoint";
- IL_PUSH(g_invasion_spawns, this);
-}
-
-void ClearWinners();
-
-// Invasion stage mode winning condition: If the attackers triggered a round end (by fulfilling all objectives)
-// they win.
-int WinningCondition_Invasion()
-{
- WinningConditionHelper(NULL); // set worldstatus
-
- int status = WINNING_NO;
-
- if(autocvar_g_invasion_type == INV_TYPE_STAGE)
- {
- SetWinners(inv_endreached, true);
-
- int found = 0;
- IL_EACH(g_invasion_roundends, true,
- {
- ++found;
- if(it.winning)
- {
- bprint("Invasion: round completed.\n");
- // winners already set (TODO: teamplay support)
-
- status = WINNING_YES;
- break;
- }
- });
-
- if(!found)
- status = WINNING_YES; // just end it? TODO: should warn mapper!
- }
- else if(autocvar_g_invasion_type == INV_TYPE_HUNT)
- {
- ClearWinners();
-
- int found = 0; // NOTE: this ends the round if no monsters are placed
- IL_EACH(g_monsters, !(it.spawnflags & MONSTERFLAG_RESPAWNED),
- {
- ++found;
- });
-
- if(found <= 0)
- {
- FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
- {
- it.winning = true;
- });
- status = WINNING_YES;
- }
- }
-
- return status;
-}
-
-Monster invasion_PickMonster(int supermonster_count)
-{
- RandomSelection_Init();
-
- FOREACH(Monsters, it != MON_Null,
- {
- if((it.spawnflags & MON_FLAG_HIDDEN) || (it.spawnflags & MONSTER_TYPE_PASSIVE) || (it.spawnflags & MONSTER_TYPE_FLY) || (it.spawnflags & MONSTER_TYPE_SWIM) ||
- (it.spawnflags & MONSTER_SIZE_QUAKE) || ((it.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
- continue;
- if(autocvar_g_invasion_zombies_only && !(it.spawnflags & MONSTER_TYPE_UNDEAD))
- continue;
- RandomSelection_AddEnt(it, 1, 1);
- });
-
- return RandomSelection_chosen_ent;
-}
-
-entity invasion_PickSpawn()
-{
- RandomSelection_Init();
-
- IL_EACH(g_invasion_spawns, true,
- {
- RandomSelection_AddEnt(it, 1, ((time < it.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
- it.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
- });
-
- return RandomSelection_chosen_ent;
-}
-
-entity invasion_GetWaveEntity(int wavenum)
-{
- IL_EACH(g_invasion_waves, it.cnt == wavenum,
- {
- return it; // found one
- });
-
- // if no specific one is found, find the last existing wave ent
- entity best = NULL;
- IL_EACH(g_invasion_waves, it.cnt <= wavenum,
- {
- if(!best || it.cnt > best.cnt)
- best = it;
- });
-
- return best;
-}
-
-void invasion_SpawnChosenMonster(Monster mon)
-{
- entity monster;
- entity spawn_point = invasion_PickSpawn();
- entity wave_ent = invasion_GetWaveEntity(inv_roundcnt);
-
- string tospawn = "";
- if(wave_ent && wave_ent.spawnmob && wave_ent.spawnmob != "")
- {
- RandomSelection_Init();
- FOREACH_WORD(wave_ent.spawnmob, true,
- {
- RandomSelection_AddString(it, 1, 1);
- });
-
- tospawn = RandomSelection_chosen_string;
- }
-
- if(spawn_point == NULL)
- {
- if(!inv_warning_shown)
- {
- inv_warning_shown = true;
- LOG_TRACE("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations");
- }
- entity e = spawn();
- setsize(e, mon.m_mins, mon.m_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(e, tospawn, mon.monsterid, NULL, NULL, e.origin, false, false, 2);
- else
- {
- delete(e);
- return;
- }
- }
- else // if spawnmob field falls through (unset), fallback to mon (relying on spawnmonster for that behaviour)
- monster = spawnmonster(spawn(), ((spawn_point.spawnmob && spawn_point.spawnmob != "") ? spawn_point.spawnmob : tospawn), mon.monsterid, spawn_point, spawn_point, spawn_point.origin, false, false, 2);
-
- if(!monster)
- return;
-
- monster.spawnshieldtime = time;
-
- if(spawn_point)
- {
- if(spawn_point.target_range)
- monster.target_range = spawn_point.target_range;
- monster.target2 = spawn_point.target2;
- }
-
- 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_AddFloat(NUM_TEAM_1, 1, 1);
- if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_AddFloat(NUM_TEAM_2, 1, 1);
- if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_AddFloat(NUM_TEAM_3, 1, 1); }
- if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_AddFloat(NUM_TEAM_4, 1, 1); }
-
- monster.team = RandomSelection_chosen_float;
- }
-
- 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;
- }
- }
-
- if(monster.monster_attack)
- IL_REMOVE(g_monster_targets, monster);
- 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(int supermonster_count)
-{
- Monster chosen_monster = invasion_PickMonster(supermonster_count);
-
- invasion_SpawnChosenMonster(chosen_monster);
-}
-
-bool Invasion_CheckWinner()
-{
- if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
- {
- IL_EACH(g_monsters, true,
- {
- Monster_Remove(it);
- });
- IL_CLEAR(g_monsters);
-
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, 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;
-
- IL_EACH(g_monsters, it.health > 0,
- {
- if((get_monsterinfo(it.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
- ++supermonster_count;
- ++total_alive_monsters;
-
- if(teamplay)
- switch(it.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 = NULL;
- 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
- {
- FOREACH_CLIENT(IS_PLAYER(it), {
- float cs = GameRules_scoring_add(it, KILLS, 0);
- if(cs > winning_score)
- {
- winning_score = cs;
- winner = it;
- }
- });
- }
-
- IL_EACH(g_monsters, true,
- {
- Monster_Remove(it);
- });
- IL_CLEAR(g_monsters);
-
- if(teamplay)
- {
- if(winner_team)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
- }
- }
- else if(winner)
- {
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
- }
-
- round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
-
- return 1;
-}
-
-bool Invasion_CheckPlayers()
-{
- return true;
-}
-
-void Invasion_RoundStart()
-{
- int numplayers = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- it.player_blocked = false;
- ++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)
-{
- entity frag_target = M_ARGV(0, entity);
- entity frag_attacker = M_ARGV(1, entity);
-
- if(!(frag_target.spawnflags & MONSTERFLAG_RESPAWNED))
- {
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- {
- inv_numkilled += 1;
- inv_maxcurrent -= 1;
- }
- if(teamplay) { inv_monsters_perteam[frag_target.team] -= 1; }
-
- if(IS_PLAYER(frag_attacker))
- if(SAME_TEAM(frag_attacker, frag_target)) // in non-teamplay modes, same team = same player, so this works
- GameRules_scoring_add(frag_attacker, KILLS, -1);
- else
- {
- GameRules_scoring_add(frag_attacker, KILLS, +1);
- if(teamplay)
- TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, MonsterSpawn)
-{
- entity mon = M_ARGV(0, entity);
- mon.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
-
- if(autocvar_g_invasion_type == INV_TYPE_HUNT)
- return false; // allowed
-
- if(!(mon.spawnflags & MONSTERFLAG_SPAWNED))
- return true;
-
- if(!(mon.spawnflags & MONSTERFLAG_RESPAWNED))
- {
- inv_numspawned += 1;
- inv_maxcurrent += 1;
- }
-
- mon.monster_skill = inv_monsterskill;
-
- if((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, mon.monster_name);
-}
-
-MUTATOR_HOOKFUNCTION(inv, SV_StartFrame)
-{
- if(autocvar_g_invasion_type != INV_TYPE_ROUND)
- return; // uses map spawned monsters
-
- monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
- monsters_killed = inv_numkilled;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerRegen)
-{
- // no regeneration in invasion, regardless of the game type
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.bot_attack)
- IL_REMOVE(g_bot_targets, player);
- player.bot_attack = false;
-}
-
-MUTATOR_HOOKFUNCTION(inv, Damage_Calculate)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(4, float);
- vector frag_force = M_ARGV(6, vector);
-
- if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
- {
- frag_damage = 0;
- frag_force = '0 0 0';
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(6, vector) = frag_force;
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, BotShouldAttack)
-{
- entity targ = M_ARGV(1, entity);
-
- if(!IS_MONSTER(targ))
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, SetStartItems)
-{
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- {
- start_health = 200;
- start_armorvalue = 200;
- }
-}
-
-MUTATOR_HOOKFUNCTION(inv, AccuracyTargetValid)
-{
- entity frag_target = M_ARGV(1, entity);
-
- if(IS_MONSTER(frag_target))
- return MUT_ACCADD_INVALID;
- return MUT_ACCADD_INDIFFERENT;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobSpawning)
-{
- // monster spawning disabled during an invasion
- M_ARGV(1, string) = "You cannot spawn monsters during an invasion!";
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckRules_World)
-{
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- return false;
-
- M_ARGV(0, float) = WinningCondition_Invasion();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(inv, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = invasion_teams;
-}
-
-MUTATOR_HOOKFUNCTION(inv, AllowMobButcher)
-{
- M_ARGV(0, string) = "This command does not work during an invasion!";
- return true;
-}
-
-void invasion_ScoreRules(int inv_teams)
-{
- if(inv_teams) { CheckAllowedTeams(NULL); }
- GameRules_score_enabled(false);
- GameRules_scoring(inv_teams, 0, 0, {
- if (inv_teams) {
- field_team(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
- }
- field(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
- });
-}
-
-void invasion_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
-{
- if(autocvar_g_invasion_type == INV_TYPE_HUNT || autocvar_g_invasion_type == INV_TYPE_STAGE)
- cvar_set("fraglimit", "0");
-
- if(autocvar_g_invasion_teams)
- {
- invasion_teams = BITS(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;
-
- if(autocvar_g_invasion_type == INV_TYPE_ROUND)
- {
- 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()
-{
- InitializeEntity(NULL, invasion_DelayedInit, INITPRIO_GAMETYPE);
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-#define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
-int autocvar_g_invasion_teams;
-int autocvar_g_invasion_type;
-bool autocvar_g_invasion_team_spawns;
-bool g_invasion;
-void invasion_Initialize();
-
-REGISTER_MUTATOR(inv, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- if (autocvar_g_invasion_teams >= 2) {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_invasion_team_spawns);
- }
- GameRules_limit_score(autocvar_g_invasion_point_limit);
-
- g_invasion = true;
- cvar_settemp("g_monsters", "1");
- invasion_Initialize();
- }
- return 0;
-}
-
-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;
-
-const int INV_TYPE_ROUND = 0; // round-based waves of enemies
-const int INV_TYPE_HUNT = 1; // clear the map of placed enemies
-const int INV_TYPE_STAGE = 2; // reach the end of the level
+++ /dev/null
-#include "gamemode_keepaway.qh"
-
-#include <common/effects/all.qh>
-
-.entity ballcarried;
-
-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;
-
-bool ka_ballcarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs on waypoints which are attached to ballcarriers, updates once per frame
-{
- if(view.ballcarried)
- if(IS_SPEC(player))
- 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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-void ka_TouchEvent(entity this, entity toucher);
-void ka_RespawnBall(entity this) // runs whenever the ball needs to be relocated
-{
- if(game_stopped) return;
- vector oldballorigin = this.origin;
-
- if(!MoveToRandomMapLocation(this, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
- {
- entity spot = SelectSpawnPoint(this, true);
- setorigin(this, spot.origin);
- this.angles = spot.angles;
- }
-
- makevectors(this.angles);
- set_movetype(this, MOVETYPE_BOUNCE);
- this.velocity = '0 0 200';
- this.angles = '0 0 0';
- this.effects = autocvar_g_keepawayball_effects;
- settouch(this, ka_TouchEvent);
- setthink(this, ka_RespawnBall);
- this.nextthink = time + autocvar_g_keepawayball_respawntime;
- navigation_dynamicgoal_set(this);
-
- Send_Effect(EFFECT_ELECTRO_COMBO, oldballorigin, '0 0 0', 1);
- Send_Effect(EFFECT_ELECTRO_COMBO, this.origin, '0 0 0', 1);
-
- WaypointSprite_Spawn(WP_KaBall, 0, 0, this, '0 0 64', NULL, this.team, this, waypointsprite_attachedforcarrier, false, RADARICON_FLAGCARRIER);
- WaypointSprite_Ping(this.waypointsprite_attachedforcarrier);
-
- sound(this, CH_TRIGGER, SND_KA_RESPAWN, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-}
-
-void ka_TimeScoring(entity this)
-{
- if(this.owner.ballcarried)
- { // add points for holding the ball after a certain amount of time
- if(autocvar_g_keepaway_score_timepoints)
- GameRules_scoring_add(this.owner, SCORE, autocvar_g_keepaway_score_timepoints);
-
- GameRules_scoring_add(this.owner, KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
- this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
- }
-}
-
-void ka_TouchEvent(entity this, entity toucher) // runs any time that the ball comes in contact with something
-{
- if(game_stopped) return;
- if(!this) return;
- if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
- { // The ball fell off the map, respawn it since players can't get to it
- ka_RespawnBall(this);
- return;
- }
- if(IS_DEAD(toucher)) { return; }
- if(STAT(FROZEN, toucher)) { return; }
- if (!IS_PLAYER(toucher))
- { // The ball just touched an object, most likely the world
- Send_Effect(EFFECT_BALL_SPARKS, this.origin, '0 0 0', 1);
- sound(this, CH_TRIGGER, SND_KA_TOUCH, VOL_BASE, ATTEN_NORM);
- return;
- }
- else if(this.wait > time) { return; }
-
- // attach the ball to the player
- this.owner = toucher;
- toucher.ballcarried = this;
- GameRules_scoring_vip(toucher, true);
- setattachment(this, toucher, "");
- setorigin(this, '0 0 0');
-
- // make the ball invisible/unable to do anything/set up time scoring
- this.velocity = '0 0 0';
- set_movetype(this, MOVETYPE_NONE);
- this.effects |= EF_NODRAW;
- settouch(this, func_null);
- setthink(this, ka_TimeScoring);
- this.nextthink = time + autocvar_g_keepaway_score_timeinterval;
- this.takedamage = DAMAGE_NO;
- navigation_dynamicgoal_unset(this);
-
- // apply effects to player
- toucher.glow_color = autocvar_g_keepawayball_trail_color;
- toucher.glow_trail = true;
- toucher.effects |= autocvar_g_keepaway_ballcarrier_effects;
-
- // messages and sounds
- ka_EventLog("pickup", toucher);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_KEEPAWAY_PICKUP, toucher.netname);
- Send_Notification(NOTIF_ALL_EXCEPT, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP, toucher.netname);
- Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_KEEPAWAY_PICKUP_SELF);
- sound(this.owner, CH_TRIGGER, SND_KA_PICKEDUP, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
- // scoring
- GameRules_scoring_add(toucher, KEEPAWAY_PICKUPS, 1);
-
- // waypoints
- WaypointSprite_AttachCarrier(WP_KaBallCarrier, toucher, RADARICON_FLAGCARRIER);
- toucher.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
- WaypointSprite_UpdateRule(toucher.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
- WaypointSprite_Ping(toucher.waypointsprite_attachedforcarrier);
- WaypointSprite_Kill(this.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, NULL, "");
- set_movetype(ball, MOVETYPE_BOUNCE);
- ball.wait = time + 1;
- settouch(ball, ka_TouchEvent);
- setthink(ball, 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();
- entity e = ball.owner; ball.owner = NULL;
- e.ballcarried = NULL;
- GameRules_scoring_vip(e, false);
- navigation_dynamicgoal_set(ball);
-
- // 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, NULL, MSG_INFO, INFO_KEEPAWAY_DROPPED, plyr.netname);
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_KEEPAWAY_DROPPED, plyr.netname);
- sound(NULL, CH_TRIGGER, SND_KA_DROPPED, VOL_BASE, ATTEN_NONE); // ATTEN_NONE (it's a sound intended to be heard anywhere)
-
- // scoring
- // GameRules_scoring_add(plyr, 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', NULL, 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);
-}
-
-/** used to clear the ballcarrier whenever the match switches from warmup to normal */
-void ka_Reset(entity this)
-{
- if((this.owner) && (IS_PLAYER(this.owner)))
- ka_DropEvent(this.owner);
-
- if(time < game_starttime)
- {
- setthink(this, ka_RespawnBall);
- settouch(this, func_null);
- this.nextthink = game_starttime;
- }
- else
- ka_RespawnBall(this);
-}
-
-
-// ================
-// Bot player logic
-// ================
-
-void havocbot_goalrating_ball(entity this, float ratingscale, vector org)
-{
- float t;
- entity ball_owner;
- ball_owner = ka_ball.owner;
-
- if (ball_owner == this)
- return;
-
- // If ball is carried by player then hunt them down.
- if (ball_owner)
- {
- t = (this.health + this.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
- navigation_routerating(this, ball_owner, t * ratingscale, 2000);
- }
- else // Ball has been dropped so collect.
- navigation_routerating(this, ka_ball, ratingscale, 2000);
-}
-
-void havocbot_role_ka_carrier(entity this)
-{
- if (IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
- havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-
- if (!this.ballcarried)
- {
- this.havocbot_role = havocbot_role_ka_collector;
- navigation_goalrating_timeout_expire(this, 2);
- }
-}
-
-void havocbot_role_ka_collector(entity this)
-{
- if (IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
- havocbot_goalrating_items(this, 10000, this.origin, 10000);
- havocbot_goalrating_enemyplayers(this, 1000, this.origin, 10000);
- havocbot_goalrating_ball(this, 20000, this.origin);
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-
- if (this.ballcarried)
- {
- this.havocbot_role = havocbot_role_ka_carrier;
- navigation_goalrating_timeout_expire(this, 2);
- }
-}
-
-
-// ==============
-// Hook Functions
-// ==============
-
-MUTATOR_HOOKFUNCTION(ka, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
-
- if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)))
- {
- if(frag_target.ballcarried) { // add to amount of times killing carrier
- GameRules_scoring_add(frag_attacker, KEEPAWAY_CARRIERKILLS, 1);
- if(autocvar_g_keepaway_score_bckill) // add bckills to the score
- GameRules_scoring_add(frag_attacker, 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
- GameRules_scoring_add(frag_attacker, SCORE, autocvar_g_keepaway_score_killac);
- }
-
- if(frag_target.ballcarried) { ka_DropEvent(frag_target); } // a player with the ball has died, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, GiveFragsForKill)
-{
- M_ARGV(2, float) = 0; // no frags counted in keepaway
- return true; // you deceptive little bugger ;3 This needs to be true in order for this function to even count.
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- // clear the item used for the ball in keepaway
- player.items &= ~IT_KEY1;
-
- // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
- if(player.ballcarried)
- player.items |= IT_KEY1;
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerUseKey)
-{
- entity player = M_ARGV(0, entity);
-
- if(MUTATOR_RETURNVALUE == 0)
- if(player.ballcarried)
- {
- ka_DropEvent(player);
- return true;
- }
-}
-
-MUTATOR_HOOKFUNCTION(ka, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
- float frag_damage = M_ARGV(4, float);
- vector frag_force = M_ARGV(6, vector);
-
- 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;
- }
- }
-
- M_ARGV(4, float) = frag_damage;
- M_ARGV(6, vector) = frag_force;
-}
-
-MUTATOR_HOOKFUNCTION(ka, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.ballcarried) { ka_DropEvent(player); } // a player with the ball has left the match, drop it
-}
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPowerups)
-{
- entity player = M_ARGV(0, entity);
-
- // 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()
-
- player.effects &= ~autocvar_g_keepaway_ballcarrier_effects;
-
- if(player.ballcarried)
- player.effects |= autocvar_g_keepaway_ballcarrier_effects;
-}
-
-
-MUTATOR_HOOKFUNCTION(ka, PlayerPhysics_UpdateStats)
-{
- entity player = M_ARGV(0, entity);
- // these automatically reset, no need to worry
-
- if(player.ballcarried)
- STAT(MOVEVARS_HIGHSPEED, player) *= autocvar_g_keepaway_ballcarrier_highspeed;
-}
-
-MUTATOR_HOOKFUNCTION(ka, BotShouldAttack)
-{
- entity bot = M_ARGV(0, entity);
- entity targ = M_ARGV(1, entity);
-
- // if neither player has ball then don't attack unless the ball is on the ground
- if(!targ.ballcarried && !bot.ballcarried && ka_ball.owner)
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- if (bot.ballcarried)
- bot.havocbot_role = havocbot_role_ka_carrier;
- else
- bot.havocbot_role = havocbot_role_ka_collector;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(ka, DropSpecialItems)
-{
- entity frag_target = M_ARGV(0, entity);
-
- if(frag_target.ballcarried)
- ka_DropEvent(frag_target);
-}
-
-.bool pushable;
-
-// ==============
-// Initialization
-// ==============
-
-MODEL(KA_BALL, "models/orbs/orbblue.md3");
-
-void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
-{
- entity e = new(keepawayball);
- setmodel(e, MDL_KA_BALL);
- 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.damageforcescale = autocvar_g_keepawayball_damageforcescale;
- e.takedamage = DAMAGE_YES;
- e.solid = SOLID_TRIGGER;
- set_movetype(e, MOVETYPE_BOUNCE);
- e.glow_color = autocvar_g_keepawayball_trail_color;
- e.glow_trail = true;
- e.flags = FL_ITEM;
- IL_PUSH(g_items, e);
- e.pushable = true;
- e.reset = ka_Reset;
- settouch(e, ka_TouchEvent);
- e.owner = NULL;
- ka_ball = e;
- navigation_dynamicgoal_init(ka_ball, false);
-
- InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So.
-}
-
-void ka_Initialize() // run at the start of a match, initiates game mode
-{
- ka_SpawnBall();
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-void ka_Initialize();
-
-REGISTER_MUTATOR(ka, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
- field(SP_KEEPAWAY_PICKUPS, "pickups", 0);
- field(SP_KEEPAWAY_CARRIERKILLS, "bckills", 0);
- field(SP_KEEPAWAY_BCTIME, "bctime", SFL_SORT_PRIO_SECONDARY);
- });
-
- ka_Initialize();
- }
- return false;
-}
-
-
-entity ka_ball;
-
-void(entity this) havocbot_role_ka_carrier;
-void(entity this) havocbot_role_ka_collector;
-
-void ka_DropEvent(entity plyr);
+++ /dev/null
-#include "gamemode_keyhunt.qh"
-
-float autocvar_g_balance_keyhunt_damageforcescale;
-float autocvar_g_balance_keyhunt_delay_collect;
-float autocvar_g_balance_keyhunt_delay_damage_return;
-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_return_when_unreachable;
-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_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;
-
-bool 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 siren_time; // time delay the siren
-//.float stuff_time; // time delay to stuffcmd a cvar
-
-int 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";
-
-int kh_Team_ByID(int 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;
-//bool kh_tracking_enabled;
-int kh_teams;
-int kh_interferemsg_team;
-float kh_interferemsg_time;
-.entity kh_next, kh_prev; // linked list
-.float kh_droptime;
-.int kh_dropperteam;
-.entity kh_previous_owner;
-.int kh_previous_owner_playerid;
-
-int kh_key_dropped, kh_key_carried;
-
-int kh_Key_AllOwnedByWhichTeam();
-
-const int ST_KH_CAPS = 1;
-void kh_ScoreRules(int teams)
-{
- GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, SFL_SORT_PRIO_PRIMARY, {
- field_team(ST_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
- field(SP_KH_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
- field(SP_KH_PUSHES, "pushes", 0);
- field(SP_KH_DESTROYS, "destroyed", SFL_LOWER_IS_BETTER);
- field(SP_KH_PICKUPS, "pickups", 0);
- field(SP_KH_KCKILLS, "kckills", 0);
- field(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER);
- });
-}
-
-bool kh_KeyCarrier_waypointsprite_visible_for_player(entity this, entity player, entity view) // runs all the time
-{
- if(!IS_PLAYER(view) || DIFF_TEAM(this, view))
- if(!kh_tracking_enabled)
- return false;
-
- return true;
-}
-
-bool kh_Key_waypointsprite_visible_for_player(entity this, entity player, entity view)
-{
- if(!kh_tracking_enabled)
- return false;
- if(!this.owner)
- return true;
- if(!this.owner.owner)
- return true;
- return false; // draw only when key is not owned
-}
-
-void kh_update_state()
-{
- entity key;
- int f;
- int s = 0;
- FOR_EACH_KH_KEY(key)
- {
- if(key.owner)
- f = key.team;
- else
- f = 30;
- s |= (32 ** key.count) * f;
- }
-
- FOREACH_CLIENT(true, { STAT(KH_KEYS, it) = s; });
-
- FOR_EACH_KH_KEY(key)
- {
- if(key.owner)
- STAT(KH_KEYS, key.owner) |= (32 ** key.count) * 31;
- }
- //print(ftos((nextent(NULL)).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(entity this) // called a lot
-{
- if(game_stopped)
- return;
- if(this.cnt > 0)
- {
- if(getthink(this) != kh_WaitForPlayers)
- this.cnt -= 1;
- }
- else if(this.cnt == 0)
- {
- this.cnt -= 1;
- kh_Controller_Thinkfunc();
- }
- this.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(game_stopped)
- 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 = 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;
- if(IL_CONTAINS(g_items, key))
- IL_REMOVE(g_items, key);
- key.solid = SOLID_NOT;
- set_movetype(key, MOVETYPE_NONE);
- key.team = key.owner.team;
- key.nextthink = time;
- key.damageforcescale = 0;
- key.takedamage = DAMAGE_NO;
- key.modelindex = kh_key_carried;
- navigation_dynamicgoal_unset(key);
-}
-
-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 = 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, NULL, "");
- setorigin(key, key.owner.origin + '0 0 1' * (STAT(PL_MIN, key.owner).z - KH_KEY_MIN_z));
- key.angles = key.owner.angles;
-#else
- setorigin(key, key.owner.origin + key.origin.z * '0 0 1');
- setattachment(key, NULL, "");
- key.angles_y += key.owner.angles.y;
-#endif
- key.flags = FL_ITEM;
- if(!IL_CONTAINS(g_items, key))
- IL_PUSH(g_items, key);
- key.solid = SOLID_TRIGGER;
- set_movetype(key, 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;
- navigation_dynamicgoal_set(key);
- 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
-{
- if(key.owner == player)
- return;
-
- int 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 = NULL;
- key.kh_prev = NULL;
-
- if(key.owner.kh_next == NULL)
- {
- // 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 == NULL)
- {
- // 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 = NULL;
-
- int ownerteam = kh_Key_AllOwnedByWhichTeam();
- if(ownerteam != ownerteam0)
- {
- entity k;
- 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, { first = it; break; });
- entity third = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { 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 "Key Carrier"
- FOR_EACH_KH_KEY(k)
- {
- if (!k.owner) continue;
- entity first = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model1, { first = it; break; });
- entity third = WP_Null;
- FOREACH(Waypoints, it.netname == k.owner.waypointsprite_attachedforcarrier.model3, { third = it; break; });
- WaypointSprite_UpdateSprites(k.owner.waypointsprite_attachedforcarrier, first, WP_KeyCarrierFriend, third);
- }
- }
- }
-}
-
-void kh_Key_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
-{
- if(this.owner)
- return;
- if(ITEM_DAMAGE_NEEDKILL(deathtype))
- {
- this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
- return;
- }
- if(force == '0 0 0')
- return;
- if(time > this.pushltime)
- if(IS_PLAYER(attacker))
- this.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);
- GameRules_scoring_add(player, KH_PICKUPS, 1);
- }
- key.kh_dropperteam = 0;
- int realteam = kh_Team_ByID(key.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PICKUP), player.netname);
-
- kh_Key_AssignTo(key, player); // this also updates .kh_state
-}
-
-void kh_Key_Touch(entity this, entity toucher) // runs many, many times when a key has been dropped and can be picked up
-{
- if(game_stopped)
- return;
-
- if(this.owner) // already carried
- return;
-
- if(ITEM_TOUCH_NEEDKILL())
- {
- this.pain_finished = bound(time, time + autocvar_g_balance_keyhunt_delay_damage_return, this.pain_finished);
- return;
- }
-
- if (!IS_PLAYER(toucher))
- return;
- if(IS_DEAD(toucher))
- return;
- if(toucher == this.enemy)
- if(time < this.kh_droptime + autocvar_g_balance_keyhunt_delay_collect)
- return; // you just dropped it!
- kh_Key_Collect(this, toucher);
-}
-
-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 = key.owner;
- kh_Key_AssignTo(key, NULL);
- 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;
- }
- }
-
- delete(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, NULL, MSG_CENTER, CENTER_KEYHUNT_ROUNDSTART, autocvar_g_balance_keyhunt_delay_round);
- kh_Controller_SetThink(autocvar_g_balance_keyhunt_delay_round, kh_StartRound);
-}
-
-void nades_GiveBonus(entity player, float score);
-
-void kh_WinnerTeam(int winner_team) // runs when a team wins
-{
- // all key carriers get some points
- entity key;
- float score = (NumTeams(kh_teams) - 1) * autocvar_g_balance_keyhunt_score_capture;
- DistributeEvenly_Init(score, NumTeams(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 = DistributeEvenly_Get(1);
- kh_Scores_Event(key.owner, key, "capture", f, 0);
- GameRules_scoring_add_team(key.owner, KH_CAPS, 1);
- nades_GiveBonus(key.owner, autocvar_g_nades_bonus_score_high);
- }
-
- bool 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, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_KEYHUNT_CAPTURE), keyowner);
-
- first = true;
- vector firstorigin = '0 0 0', lastorigin = '0 0 0', midpoint = '0 0 0';
- FOR_EACH_KH_KEY(key)
- {
- vector thisorigin = kh_AttachedOrigin(key);
- //dprint("Key origin: ", vtos(thisorigin), "\n");
- midpoint += thisorigin;
-
- if(!first)
- te_lightning2(NULL, lastorigin, thisorigin);
- lastorigin = thisorigin;
- if(first)
- firstorigin = thisorigin;
- first = false;
- }
- if(NumTeams(kh_teams) > 2)
- {
- te_lightning2(NULL, lastorigin, firstorigin);
- }
- midpoint = midpoint * (1 / NumTeams(kh_teams));
- te_customflash(midpoint, 1000, 1, Team_ColorRGB(winner_team) * 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(int loser_team, entity lostkey) // runs when a player pushes a flag carrier off the map
-{
- float f;
- entity attacker = NULL;
- if(lostkey.pusher)
- if(lostkey.pusher.team != loser_team)
- if(IS_PLAYER(lostkey.pusher))
- attacker = lostkey.pusher;
-
- if(attacker)
- {
- if(lostkey.kh_previous_owner)
- kh_Scores_Event(lostkey.kh_previous_owner, NULL, "pushed", 0, -autocvar_g_balance_keyhunt_score_push);
- // don't actually GIVE him the -nn points, just log
- kh_Scores_Event(attacker, NULL, "push", autocvar_g_balance_keyhunt_score_push, 0);
- GameRules_scoring_add(attacker, KH_PUSHES, 1);
- //centerprint(attacker, "Your push is the best!"); // does this really need to exist?
- }
- else
- {
- int players = 0;
- float of = autocvar_g_balance_keyhunt_score_destroyed_ownfactor;
-
- FOREACH_CLIENT(IS_PLAYER(it) && it.team != loser_team, { ++players; });
-
- entity key;
- int keys = 0;
- FOR_EACH_KH_KEY(key)
- if(key.owner && key.team != loser_team)
- ++keys;
-
- if(lostkey.kh_previous_owner)
- kh_Scores_Event(lostkey.kh_previous_owner, NULL, "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)
- GameRules_scoring_add(lostkey.kh_previous_owner, KH_DESTROYS, 1);
-
- DistributeEvenly_Init(autocvar_g_balance_keyhunt_score_destroyed, keys * of + players);
-
- FOR_EACH_KH_KEY(key)
- if(key.owner && key.team != loser_team)
- {
- f = DistributeEvenly_Get(of);
- kh_Scores_Event(key.owner, NULL, "destroyed_holdingkey", f, 0);
- }
-
- int fragsleft = DistributeEvenly_Get(players);
-
- // Now distribute these among all other teams...
- int j = NumTeams(kh_teams) - 1;
- for(int i = 0; i < NumTeams(kh_teams); ++i)
- {
- int thisteam = kh_Team_ByID(i);
- if(thisteam == loser_team) // bad boy, no cookie - this WILL happen
- continue;
-
- players = 0;
- FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, { ++players; });
-
- DistributeEvenly_Init(fragsleft, j);
- fragsleft = DistributeEvenly_Get(j - 1);
- DistributeEvenly_Init(DistributeEvenly_Get(1), players);
-
- FOREACH_CLIENT(IS_PLAYER(it) && it.team == thisteam, {
- f = DistributeEvenly_Get(1);
- kh_Scores_Event(it, NULL, "destroyed", f, 0);
- });
-
- --j;
- }
- }
-
- int realteam = kh_Team_ByID(lostkey.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(loser_team, CENTER_ROUND_TEAM_LOSS));
- if(attacker)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_PUSHED), attacker.netname, lostkey.kh_previous_owner.netname);
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DESTROYED), lostkey.kh_previous_owner.netname);
-
- play2all(SND(KH_DESTROY));
- te_tarexplosion(lostkey.origin);
-
- kh_FinishRound();
-}
-
-void kh_Key_Think(entity this) // runs all the time
-{
- if(game_stopped)
- return;
-
- if(this.owner)
- {
-#ifndef KH_PLAYER_USE_ATTACHMENT
- makevectors('0 1 0' * (this.cnt + (time % 360) * KH_KEY_XYSPEED));
- setorigin(this, v_forward * KH_KEY_XYDIST + '0 0 1' * this.origin.z);
-#endif
- }
-
- // if in nodrop or time over, end the round
- if(!this.owner)
- if(time > this.pain_finished)
- kh_LoserTeam(this.team, this);
-
- if(this.owner)
- if(kh_Key_AllOwnedByWhichTeam() != -1)
- {
- if(this.siren_time < time)
- {
- sound(this.owner, CH_TRIGGER, SND_KH_ALARM, VOL_BASE, ATTEN_NORM); // play a simple alarm
- this.siren_time = time + 2.5; // repeat every 2.5 seconds
- }
-
- entity key;
- vector p = this.owner.origin;
- FOR_EACH_KH_KEY(key)
- if(vdist(key.owner.origin - p, >, autocvar_g_balance_keyhunt_maxdist))
- goto not_winning;
- kh_WinnerTeam(this.team);
-LABEL(not_winning)
- }
-
- if(kh_interferemsg_time && time > kh_interferemsg_time)
- {
- kh_interferemsg_time = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(it.team == kh_interferemsg_team)
- if(it.kh_next)
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_MEET);
- else
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_KEYHUNT_HELP);
- else
- Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_TEAM_NUM(kh_interferemsg_team, CENTER_KEYHUNT_INTERFERE));
- });
- }
-
- this.nextthink = time + 0.05;
-}
-
-void key_reset(entity this)
-{
- kh_Key_AssignTo(this, NULL);
- kh_Key_Remove(this);
-}
-
-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 = spawn();
- key.count = i;
- key.classname = STR_ITEM_KH_KEY;
- settouch(key, kh_Key_Touch);
- setthink(key, 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.damagedbytriggers = autocvar_g_balance_keyhunt_return_when_unreachable;
- key.damagedbycontents = autocvar_g_balance_keyhunt_return_when_unreachable;
- 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;
- navigation_dynamicgoal_init(key, false);
-
- 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(initial_owner.team, CENTER_KEYHUNT_START));
-
- WaypointSprite_Spawn(WP_KeyDropped, 0, 0, key, '0 0 1' * KH_KEY_WP_ZSHIFT, NULL, 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
-int kh_Key_AllOwnedByWhichTeam() // constantly called. check to see if all the keys are owned by the same team
-{
- entity key;
- int teem = -1;
- int keys = NumTeams(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 = key.owner;
-
- key.kh_droptime = time;
- key.enemy = player;
-
- kh_Scores_Event(player, key, "dropkey", 0, 0);
- GameRules_scoring_add(player, KH_LOSSES, 1);
- int realteam = kh_Team_ByID(key.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_DROP), player.netname);
-
- kh_Key_AssignTo(key, NULL);
- makevectors(player.v_angle);
- key.velocity = W_CalculateProjectileVelocity(player, player.velocity, autocvar_g_balance_keyhunt_throwvelocity * v_forward, false);
- key.pusher = NULL;
- 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
-{
- if(player.kh_next)
- {
- entity mypusher = NULL;
- if(player.pusher)
- if(time < player.pushltime)
- mypusher = player.pusher;
-
- entity key;
- while((key = player.kh_next))
- {
- kh_Scores_Event(player, key, "losekey", 0, 0);
- GameRules_scoring_add(player, KH_LOSSES, 1);
- int realteam = kh_Team_ByID(key.count);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(realteam, INFO_KEYHUNT_LOST), player.netname);
- kh_Key_AssignTo(key, NULL);
- makevectors('-1 0 0' * (45 + 45 * random()) + '0 360 0' * random());
- key.velocity = W_CalculateProjectileVelocity(player, 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);
- }
-}
-
-int kh_GetMissingTeams()
-{
- int missing_teams = 0;
- for(int i = 0; i < NumTeams(kh_teams); ++i)
- {
- int teem = kh_Team_ByID(i);
- int players = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
- ++players;
- });
- if (!players)
- missing_teams |= (2 ** i);
- }
- return missing_teams;
-}
-
-void kh_WaitForPlayers() // delay start of the round until enough players are present
-{
- static int prev_missing_teams_mask;
- if(time < game_starttime)
- {
- if (prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
- return;
- }
-
- int missing_teams_mask = kh_GetMissingTeams();
- if(!missing_teams_mask)
- {
- if(prev_missing_teams_mask > 0)
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
- prev_missing_teams_mask = -1;
- }
- else
- {
- if(prev_missing_teams_mask != missing_teams_mask)
- {
- Send_Notification(NOTIF_ALL, NULL, 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, NULL, MSG_CENTER, CPID_KEYHUNT);
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
- kh_tracking_enabled = true;
-}
-
-void kh_StartRound() // runs at the start of each round
-{
- if(time < game_starttime)
- {
- kh_Controller_SetThink(game_starttime - time + 0.1, kh_WaitForPlayers);
- return;
- }
-
- if(kh_GetMissingTeams())
- {
- kh_Controller_SetThink(1, kh_WaitForPlayers);
- return;
- }
-
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT);
- Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_KEYHUNT_OTHER);
-
- for(int i = 0; i < NumTeams(kh_teams); ++i)
- {
- int teem = kh_Team_ByID(i);
- int players = 0;
- entity my_player = NULL;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if(!IS_DEAD(it) && !PHYS_INPUT_BUTTON_CHAT(it) && it.team == teem)
- {
- ++players;
- if(random() * players <= 1)
- my_player = it;
- }
- });
- kh_Key_Spawn(my_player, 360 * i / NumTeams(kh_teams), i);
- }
-
- kh_tracking_enabled = false;
- Send_Notification(NOTIF_ALL, NULL, 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)
- {
- int nk = 0;
- for(entity k = targ.kh_next; k != NULL; 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);
- GameRules_scoring_add(attacker, 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 = cvar("g_keyhunt_teams"); // read the cvar directly as it gets written earlier in the same frame
- kh_teams = BITS(bound(2, kh_teams, 4));
-
- // make a KH entity for controlling the game
- kh_controller = spawn();
- setthink(kh_controller, 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;
-
- kh_ScoreRules(kh_teams);
-}
-
-void kh_finalize()
-{
- // to be called before intermission
- kh_FinishRound();
- delete(kh_controller);
- kh_controller = NULL;
-}
-
-// legacy bot role
-
-void(entity this) havocbot_role_kh_carrier;
-void(entity this) havocbot_role_kh_defense;
-void(entity this) havocbot_role_kh_offense;
-void(entity this) havocbot_role_kh_freelancer;
-
-
-void havocbot_goalrating_kh(entity this, float ratingscale_team, float ratingscale_dropped, float ratingscale_enemy)
-{
- entity head;
- for (head = kh_worldkeylist; head; head = head.kh_worldkeynext)
- {
- if(head.owner == this)
- 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 != this.team)
- {
- traceline(this.origin + this.view_ofs, head.origin, MOVE_NOMONSTERS, this);
- if (trace_fraction < 1 && trace_ent != head)
- continue; // skip what I can't see
- }
- }
- if(!head.owner)
- navigation_routerating(this, head, ratingscale_dropped * 10000, 100000);
- else if(head.team == this.team)
- navigation_routerating(this, head.owner, ratingscale_team * 10000, 100000);
- else
- navigation_routerating(this, head.owner, ratingscale_enemy * 10000, 100000);
- }
-
- havocbot_goalrating_items(this, 1, this.origin, 10000);
-}
-
-void havocbot_role_kh_carrier(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (!(this.kh_next))
- {
- LOG_TRACE("changing role to freelancer");
- this.havocbot_role = havocbot_role_kh_freelancer;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- if(kh_Key_AllOwnedByWhichTeam() == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // bring home
- else
- havocbot_goalrating_kh(this, 4, 4, 1); // play defensively
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_kh_defense(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (this.kh_next)
- {
- LOG_TRACE("changing role to carrier");
- this.havocbot_role = havocbot_role_kh_carrier;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
- if (time > this.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to freelancer");
- this.havocbot_role = havocbot_role_kh_freelancer;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- float key_owner_team;
- navigation_goalrating_start(this);
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend key carriers
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 4, 1, 0.1); // play defensively
- else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_kh_offense(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (this.kh_next)
- {
- LOG_TRACE("changing role to carrier");
- this.havocbot_role = havocbot_role_kh_carrier;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 20;
- if (time > this.havocbot_role_timeout)
- {
- LOG_TRACE("changing role to freelancer");
- this.havocbot_role = havocbot_role_kh_freelancer;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- float key_owner_team;
-
- navigation_goalrating_start(this);
-
- key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 0.1, 1, 4); // play offensively
- else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK! EMERGENCY!
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void havocbot_role_kh_freelancer(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (this.kh_next)
- {
- LOG_TRACE("changing role to carrier");
- this.havocbot_role = havocbot_role_kh_carrier;
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (!this.havocbot_role_timeout)
- this.havocbot_role_timeout = time + random() * 10 + 10;
- if (time > this.havocbot_role_timeout)
- {
- if (random() < 0.5)
- {
- LOG_TRACE("changing role to offense");
- this.havocbot_role = havocbot_role_kh_offense;
- }
- else
- {
- LOG_TRACE("changing role to defense");
- this.havocbot_role = havocbot_role_kh_defense;
- }
- this.havocbot_role_timeout = 0;
- return;
- }
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- int key_owner_team = kh_Key_AllOwnedByWhichTeam();
- if(key_owner_team == this.team)
- havocbot_goalrating_kh(this, 10, 0.1, 0.1); // defend anyway
- else if(key_owner_team == -1)
- havocbot_goalrating_kh(this, 1, 10, 4); // prefer dropped keys
- else
- havocbot_goalrating_kh(this, 0.1, 0.1, 10); // ATTACK ANYWAY
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-
-// register this as a mutator
-
-MUTATOR_HOOKFUNCTION(kh, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- kh_Key_DropAll(player, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerDies)
-{
- entity frag_attacker = M_ARGV(1, entity);
- entity frag_target = M_ARGV(2, entity);
-
- if(frag_target == frag_attacker)
- kh_Key_DropAll(frag_target, true);
- else if(IS_PLAYER(frag_attacker))
- kh_Key_DropAll(frag_target, false);
- else
- kh_Key_DropAll(frag_target, true);
-}
-
-MUTATOR_HOOKFUNCTION(kh, GiveFragsForKill, CBC_ORDER_FIRST)
-{
- entity frag_attacker = M_ARGV(0, entity);
- entity frag_target = M_ARGV(1, entity);
- float frag_score = M_ARGV(2, float);
- M_ARGV(2, float) = kh_HandleFrags(frag_attacker, frag_target, frag_score);
-}
-
-MUTATOR_HOOKFUNCTION(kh, MatchEnd)
-{
- kh_finalize();
-}
-
-MUTATOR_HOOKFUNCTION(kh, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = kh_teams;
-}
-
-MUTATOR_HOOKFUNCTION(kh, SpectateCopy)
-{
- entity spectatee = M_ARGV(0, entity);
- entity client = M_ARGV(1, entity);
-
- STAT(KH_KEYS, client) = STAT(KH_KEYS, spectatee);
-}
-
-MUTATOR_HOOKFUNCTION(kh, PlayerUseKey)
-{
- entity player = M_ARGV(0, entity);
-
- if(MUTATOR_RETURNVALUE == 0)
- {
- entity k = player.kh_next;
- if(k)
- {
- kh_Key_DropOne(k);
- return true;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(kh, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- if(IS_DEAD(bot))
- return true;
-
- float r = random() * 3;
- if (r < 1)
- bot.havocbot_role = havocbot_role_kh_offense;
- else if (r < 2)
- bot.havocbot_role = havocbot_role_kh_defense;
- else
- bot.havocbot_role = havocbot_role_kh_freelancer;
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(kh, DropSpecialItems)
-{
- entity frag_target = M_ARGV(0, entity);
-
- kh_Key_DropAll(frag_target, false);
-}
-
-MUTATOR_HOOKFUNCTION(kh, reset_map_global)
-{
- kh_WaitForPlayers(); // takes care of killing the "missing teams" message
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-#define autocvar_g_keyhunt_point_limit cvar("g_keyhunt_point_limit")
-int autocvar_g_keyhunt_point_leadlimit;
-bool autocvar_g_keyhunt_team_spawns;
-void kh_Initialize();
-
-REGISTER_MUTATOR(kh, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_keyhunt_team_spawns);
- GameRules_limit_score(autocvar_g_keyhunt_point_limit);
- GameRules_limit_lead(autocvar_g_keyhunt_point_leadlimit);
-
- kh_Initialize();
- }
- return 0;
-}
-
-#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:
-bool kh_tracking_enabled;
-.entity kh_next;
-
-USING(kh_Think_t, void());
-void kh_StartRound();
-void kh_Controller_SetThink(float t, kh_Think_t func);
+++ /dev/null
-#include "gamemode_lms.qh"
-
-#include <common/mutators/mutator/instagib/items.qh>
-#include <server/campaign.qh>
-#include <server/command/_mod.qh>
-
-int autocvar_g_lms_extra_lives;
-bool autocvar_g_lms_join_anytime;
-int autocvar_g_lms_last_join;
-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);
-}
-
-void ClearWinners();
-
-// LMS winning condition: game terminates if and only if there's at most one
-// one player who's living lives. Top two scores being equal cancels the time
-// limit.
-int WinningCondition_LMS()
-{
- entity first_player = NULL;
- int total_players = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- if (!total_players)
- first_player = it;
- ++total_players;
- });
-
- if (total_players)
- {
- if (total_players > 1)
- {
- // two or more active players - continue with the game
-
- if (autocvar_g_campaign)
- {
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- float pl_lives = GameRules_scoring_add(it, LMS_LIVES, 0);
- if (!pl_lives)
- return WINNING_YES; // human player lost, game over
- break;
- });
- }
- }
- else
- {
- // exactly one player?
-
- ClearWinners();
- SetWinners(winning, 0); // NOTE: exactly one player is still "player", so this works out
-
- if (LMS_NewPlayerLives())
- {
- // game still running (that is, nobody got removed from the game by a frag yet)? then continue
- return WINNING_NO;
- }
- else
- {
- // a winner!
- // and assign him his first place
- GameRules_scoring_add(first_player, LMS_RANK, 1);
- if(warmup_stage)
- return WINNING_NO;
- else
- return WINNING_YES;
- }
- }
- }
- else
- {
- // nobody is playing at all...
- if (LMS_NewPlayerLives())
- {
- // wait for players...
- }
- else
- {
- // SNAFU (maybe a draw game?)
- ClearWinners();
- LOG_TRACE("No players, ending game.");
- return WINNING_YES;
- }
- }
-
- // When we get here, we have at least two players who are actually LIVING,
- // now check if the top two players have equal score.
- WinningConditionHelper(NULL);
-
- ClearWinners();
- if(WinningConditionHelper_winner)
- WinningConditionHelper_winner.winning = true;
- if(WinningConditionHelper_topscore == WinningConditionHelper_secondscore)
- return WINNING_NEVER;
-
- // Top two have different scores? Way to go for our beloved TIMELIMIT!
- return WINNING_NO;
-}
-
-// mutator hooks
-MUTATOR_HOOKFUNCTION(lms, reset_map_global)
-{
- lms_lowest_lives = 999;
-}
-
-MUTATOR_HOOKFUNCTION(lms, reset_map_players)
-{
- FOREACH_CLIENT(true, {
- TRANSMUTE(Player, it);
- it.frags = FRAGS_PLAYER;
- GameRules_scoring_add(it, LMS_LIVES, LMS_NewPlayerLives());
- PutClientInServer(it);
- });
-}
-
-MUTATOR_HOOKFUNCTION(lms, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.frags == FRAGS_SPECTATOR)
- TRANSMUTE(Observer, player);
- else
- {
- float tl = GameRules_scoring_add(player, LMS_LIVES, 0);
- if(tl < lms_lowest_lives)
- lms_lowest_lives = tl;
- if(tl <= 0)
- TRANSMUTE(Observer, player);
- if(warmup_stage)
- GameRules_scoring_add(player, LMS_RANK, -GameRules_scoring_add(player, LMS_RANK, 0));
- }
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidSpawn)
-{
- entity player = M_ARGV(0, entity);
-
- if(warmup_stage)
- return false;
- if(player.frags == FRAGS_SPECTATOR)
- return true;
- if(GameRules_scoring_add(player, LMS_LIVES, 0) <= 0)
- {
- Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_LMS_NOLIVES);
- return true;
- }
- return false;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- frag_target.respawn_flags |= RESPAWN_FORCE;
-}
-
-void lms_RemovePlayer(entity player)
-{
- static int quitters = 0;
- float player_rank = GameRules_scoring_add(player, LMS_RANK, 0);
- if (!player_rank)
- {
- int pl_cnt = 0;
- FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
- if (player.lms_spectate_warning != 2)
- {
- if(IS_BOT_CLIENT(player))
- bot_clear(player);
- player.frags = FRAGS_LMS_LOSER;
- GameRules_scoring_add(player, LMS_RANK, pl_cnt + 1);
- }
- else
- {
- lms_lowest_lives = 999;
- FOREACH_CLIENT(true, {
- if (it.frags == FRAGS_LMS_LOSER)
- {
- float it_rank = GameRules_scoring_add(it, LMS_RANK, 0);
- if (it_rank > player_rank && it_rank <= 256)
- GameRules_scoring_add(it, LMS_RANK, -1);
- lms_lowest_lives = 0;
- }
- else if (it.frags != FRAGS_SPECTATOR)
- {
- float tl = GameRules_scoring_add(it, LMS_LIVES, 0);
- if(tl < lms_lowest_lives)
- lms_lowest_lives = tl;
- }
- });
- GameRules_scoring_add(player, LMS_RANK, 665 - quitters); // different from 666
- if(!warmup_stage)
- {
- GameRules_scoring_add(player, LMS_LIVES, -GameRules_scoring_add(player, LMS_LIVES, 0));
- ++quitters;
- }
- player.frags = FRAGS_LMS_LOSER;
- TRANSMUTE(Observer, player);
- }
- if (pl_cnt == 2 && !warmup_stage) // a player is forfeiting leaving only one player
- lms_lowest_lives = 0; // end the game now!
- }
-
- if(CS(player).killcount != FRAGS_SPECTATOR)
- if(GameRules_scoring_add(player, LMS_RANK, 0) > 0 && player.lms_spectate_warning != 2)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_NOLIVES, player.netname);
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_LMS_FORFEIT, player.netname);
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientDisconnect)
-{
- entity player = M_ARGV(0, entity);
-
- lms_RemovePlayer(player);
-}
-
-MUTATOR_HOOKFUNCTION(lms, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- lms_RemovePlayer(player);
- return true; // prevent team reset
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- TRANSMUTE(Player, player);
- campaign_bots_may_start = true;
-
- if(GameRules_scoring_add(player, LMS_LIVES, LMS_NewPlayerLives()) <= 0)
- {
- GameRules_scoring_add(player, LMS_RANK, 666); // mark as forced spectator for the hud code
- player.frags = FRAGS_SPECTATOR;
- }
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- if(player.deadflag == DEAD_DYING)
- player.deadflag = DEAD_RESPAWNING;
-}
-
-MUTATOR_HOOKFUNCTION(lms, PlayerRegen)
-{
- if(autocvar_g_lms_regenerate)
- return false;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidThrowCurrentWeapon)
-{
- // forbode!
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, GiveFragsForKill)
-{
- entity frag_target = M_ARGV(1, entity);
-
- if (!warmup_stage)
- {
- // remove a life
- int tl = GameRules_scoring_add(frag_target, LMS_LIVES, -1);
- if(tl < lms_lowest_lives)
- lms_lowest_lives = tl;
- if(tl <= 0)
- {
- int pl_cnt = 0;
- FOREACH_CLIENT(IS_PLAYER(it), { pl_cnt++; });
- if(IS_BOT_CLIENT(frag_target))
- bot_clear(frag_target);
- frag_target.frags = FRAGS_LMS_LOSER;
- GameRules_scoring_add(frag_target, LMS_RANK, pl_cnt);
- }
- }
- M_ARGV(2, float) = 0; // frag score
-
- 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");
-}
-
-MUTATOR_HOOKFUNCTION(lms, ForbidPlayerScore_Clear)
-{
- // don't clear player score
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, FilterItemDefinition)
-{
- entity definition = M_ARGV(0, entity);
-
- if (autocvar_g_lms_extra_lives && definition == ITEM_ExtraLife)
- {
- return false;
- }
- return true;
-}
-
-void lms_extralife(entity this)
-{
- StartItem(this, ITEM_ExtraLife);
-}
-
-MUTATOR_HOOKFUNCTION(lms, OnEntityPreSpawn)
-{
- if (!autocvar_g_powerups) return false;
- if (!autocvar_g_lms_extra_lives) return false;
-
- entity ent = M_ARGV(0, entity);
-
- // Can't use .itemdef here
- if (ent.classname != "item_health_mega") return false;
-
- entity e = spawn();
- setthink(e, lms_extralife);
-
- e.nextthink = time + 0.1;
- e.spawnflags = ent.spawnflags;
- e.noalign = ent.noalign;
- setorigin(e, ent.origin);
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ItemTouch)
-{
- entity item = M_ARGV(0, entity);
- entity toucher = M_ARGV(1, entity);
-
- if(item.itemdef == ITEM_ExtraLife)
- {
- Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_EXTRALIVES);
- GameRules_scoring_add(toucher, LMS_LIVES, autocvar_g_lms_extra_lives);
- return MUT_ITEMTOUCH_PICKUP;
- }
-
- return MUT_ITEMTOUCH_CONTINUE;
-}
-
-MUTATOR_HOOKFUNCTION(lms, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
-{
- FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- ++M_ARGV(0, int); // activerealplayers
- ++M_ARGV(1, int); // realplayers
- });
-
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, ClientCommand_Spectate)
-{
- entity player = M_ARGV(0, entity);
-
- if(warmup_stage || player.lms_spectate_warning)
- {
- // for the forfeit message...
- player.lms_spectate_warning = 2;
- }
- else
- {
- if(player.frags != FRAGS_SPECTATOR && player.frags != FRAGS_LMS_LOSER)
- {
- player.lms_spectate_warning = 1;
- sprint(player, "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)
-{
- M_ARGV(0, float) = WinningCondition_LMS();
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, WantWeapon)
-{
- M_ARGV(2, bool) = true; // all weapons
-}
-
-MUTATOR_HOOKFUNCTION(lms, GetPlayerStatus)
-{
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(lms, AddPlayerScore)
-{
- if(game_stopped)
- if(M_ARGV(0, entity) == SP_LMS_RANK) // score field
- return true; // allow writing to this field in intermission as it is needed for newly joining players
-}
-
-void lms_Initialize()
-{
- lms_lowest_lives = 9999;
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-.float lms_spectate_warning;
-#define autocvar_g_lms_lives_override cvar("g_lms_lives_override")
-void lms_Initialize();
-
-REGISTER_MUTATOR(lms, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_limit_score(((!autocvar_g_lms_lives_override) ? -1 : autocvar_g_lms_lives_override));
- GameRules_limit_lead(0);
- GameRules_score_enabled(false);
- GameRules_scoring(0, 0, 0, {
- field(SP_LMS_LIVES, "lives", SFL_SORT_PRIO_SECONDARY);
- field(SP_LMS_RANK, "rank", SFL_LOWER_IS_BETTER | SFL_RANK | SFL_SORT_PRIO_PRIMARY | SFL_ALLOW_HIDE);
- });
-
- lms_Initialize();
- }
- return 0;
-}
-
-// lives related defs
-float lms_lowest_lives;
-float LMS_NewPlayerLives();
+++ /dev/null
-#include "gamemode_race.qh"
-
-#include <server/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(entity this)
-{
- if(IS_DEAD(this))
- return;
-
- if (navigation_goalrating_timeout(this))
- {
- navigation_goalrating_start(this);
-
- bool raw_touch_check = true;
- int cp = this.race_checkpoint;
-
- LABEL(search_racecheckpoints)
- IL_EACH(g_racecheckpoints, true,
- {
- if(it.cnt == cp || cp == -1)
- {
- // redirect bot to next goal if it touched the waypoint of an untouchable checkpoint
- // e.g. checkpoint in front of Stormkeep's warpzone
- // the same workaround is applied in CTS game mode
- if (raw_touch_check && vdist(this.origin - it.nearestwaypoint.origin, <, 30))
- {
- cp = race_NextCheckpoint(cp);
- raw_touch_check = false;
- goto search_racecheckpoints;
- }
- navigation_routerating(this, it, 1000000, 5000);
- }
- });
-
- navigation_goalrating_end(this);
-
- navigation_goalrating_timeout_set(this);
- }
-}
-
-void race_ScoreRules()
-{
- GameRules_score_enabled(false);
- GameRules_scoring(race_teams, 0, 0, {
- if (race_teams) {
- field_team(ST_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
- } else if (g_race_qualifying) {
- field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- } else {
- field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
- field(SP_RACE_TIME, "time", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME);
- field(SP_RACE_FASTEST, "fastest", SFL_LOWER_IS_BETTER | SFL_TIME);
- }
- });
-}
-
-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 != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
-}
-
-float WinningCondition_Race(float fraglimit)
-{
- float wc;
- float n, c;
-
- n = 0;
- c = 0;
- FOREACH_CLIENT(IS_PLAYER(it), {
- ++n;
- if(CS(it).race_completed)
- ++c;
- });
- if(n && (n == c))
- return WINNING_YES;
- wc = WinningCondition_Scores(fraglimit, 0);
-
- // ALWAYS initiate overtime, unless EVERYONE has finished the race!
- if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
- // do NOT support equality when the laps are all raced!
- return WINNING_STARTSUDDENDEATHOVERTIME;
- else
- return WINNING_NEVER;
-}
-
-float WinningCondition_QualifyingThenRace(float limit)
-{
- float wc;
- wc = WinningCondition_Scores(limit, 0);
-
- // NEVER initiate overtime
- if(wc == WINNING_YES || wc == WINNING_STARTSUDDENDEATHOVERTIME)
- {
- return WINNING_YES;
- }
-
- return wc;
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientKill)
-{
- if(g_race_qualifying)
- M_ARGV(1, float) = 0; // killtime
-}
-
-MUTATOR_HOOKFUNCTION(rc, AbortSpeedrun)
-{
- entity player = M_ARGV(0, entity);
-
- if(autocvar_g_allow_checkpoints)
- race_PreparePlayer(player); // nice try
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerPhysics)
-{
- entity player = M_ARGV(0, entity);
- float dt = M_ARGV(1, float);
-
- player.race_movetime_frac += dt;
- float f = floor(player.race_movetime_frac);
- player.race_movetime_frac -= f;
- player.race_movetime_count += f;
- player.race_movetime = player.race_movetime_frac + player.race_movetime_count;
-
-#ifdef SVQC
- if(IS_PLAYER(player))
- {
- if (player.race_penalty)
- if (time > player.race_penalty)
- player.race_penalty = 0;
- if(player.race_penalty)
- {
- player.velocity = '0 0 0';
- set_movetype(player, MOVETYPE_NONE);
- player.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(CS(player).movement.x);
- wishvel.y = fabs(CS(player).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(CS(player).movement.x > 0)
- CS(player).movement_x = wishspeed;
- else
- CS(player).movement_x = -wishspeed;
- CS(player).movement_y = 0;
- }
- else if(wishvel.y >= 2 * wishvel.x)
- {
- // pure Y motion
- CS(player).movement_x = 0;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = wishspeed;
- else
- CS(player).movement_y = -wishspeed;
- }
- else
- {
- // diagonal
- if(CS(player).movement.x > 0)
- CS(player).movement_x = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_x = -M_SQRT1_2 * wishspeed;
- if(CS(player).movement.y > 0)
- CS(player).movement_y = M_SQRT1_2 * wishspeed;
- else
- CS(player).movement_y = -M_SQRT1_2 * wishspeed;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, reset_map_global)
-{
- float s;
-
- Score_NicePrint(NULL);
-
- race_ClearRecords();
- PlayerScore_Sort(race_place, 0, 1, 0);
-
- FOREACH_CLIENT(true, {
- if(it.race_place)
- {
- s = GameRules_scoring_add(it, RACE_FASTEST, 0);
- if(!s)
- it.race_place = 0;
- }
- race_EventLog(ftos(it.race_place), it);
- });
-
- 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();
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ClientConnect)
-{
- entity player = M_ARGV(0, entity);
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-
- string rr = RACE_RECORD;
-
- if(IS_REAL_CLIENT(player))
- {
- msg_entity = player;
- 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;
- int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
- race_send_rankings_cnt(MSG_ONE);
- for (i = 1; i <= m; ++i)
- {
- race_SendRankings(i, 0, 0, MSG_ONE);
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, MakePlayerObserver)
-{
- entity player = M_ARGV(0, entity);
-
- if(g_race_qualifying)
- if(GameRules_scoring_add(player, RACE_FASTEST, 0))
- player.frags = FRAGS_LMS_LOSER;
- else
- player.frags = FRAGS_SPECTATOR;
-
- race_PreparePlayer(player);
- player.race_checkpoint = -1;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerSpawn)
-{
- entity player = M_ARGV(0, entity);
- entity spawn_spot = M_ARGV(1, entity);
-
- if(spawn_spot.target == "")
- // Emergency: this wasn't a real spawnpoint. Can this ever happen?
- race_PreparePlayer(player);
-
- // if we need to respawn, do it right
- player.race_respawn_checkpoint = player.race_checkpoint;
- player.race_respawn_spotref = spawn_spot;
-
- player.race_place = 0;
-}
-
-MUTATOR_HOOKFUNCTION(rc, PutClientInServer)
-{
- entity player = M_ARGV(0, entity);
-
- if(IS_PLAYER(player))
- if(!game_stopped)
- {
- if(CS(player).killcount == FRAGS_SPECTATOR /* initial spawn */ || g_race_qualifying) // spawn
- race_PreparePlayer(player);
- else // respawn
- race_RetractPlayer(player);
-
- race_AbandonRaceCheck(player);
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, PlayerDies)
-{
- entity frag_target = M_ARGV(2, entity);
-
- frag_target.respawn_flags |= RESPAWN_FORCE;
- race_AbandonRaceCheck(frag_target);
-}
-
-MUTATOR_HOOKFUNCTION(rc, HavocBot_ChooseRole)
-{
- entity bot = M_ARGV(0, entity);
-
- bot.havocbot_role = havocbot_role_race;
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetPressedKeys)
-{
- entity player = M_ARGV(0, entity);
-
- if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
- {
- if (!player.stored_netname)
- player.stored_netname = strzone(uid2name(player.crypto_idfp));
- if(player.stored_netname != player.netname)
- {
- db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
- strcpy(player.stored_netname, player.netname);
- }
- }
-
- if (!IS_OBSERVER(player))
- {
- if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
- {
- speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
- speedaward_holder = player.netname;
- speedaward_uid = player.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);
- }
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ForbidPlayerScore_Clear)
-{
- if(g_race_qualifying)
- return true; // in qualifying, you don't lose score by observing
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(0, float) = race_teams;
-}
-
-MUTATOR_HOOKFUNCTION(rc, Scores_CountFragsRemaining)
-{
- // announce remaining frags if not in qualifying mode
- if(!g_race_qualifying)
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(rc, GetRecords)
-{
- int record_page = M_ARGV(0, int);
- string ret_string = M_ARGV(1, string);
-
- 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");
- }
- }
-
- M_ARGV(1, string) = ret_string;
-}
-
-MUTATOR_HOOKFUNCTION(rc, HideTeamNagger)
-{
- return true; // doesn't work so well
-}
-
-MUTATOR_HOOKFUNCTION(rc, FixClientCvars)
-{
- entity player = M_ARGV(0, entity);
-
- stuffcmd(player, "cl_cmd settemp cl_movecliptokeyboard 2\n");
-}
-
-MUTATOR_HOOKFUNCTION(rc, CheckRules_World)
-{
- float checkrules_timelimit = M_ARGV(1, float);
- float checkrules_fraglimit = M_ARGV(2, float);
-
- if(checkrules_timelimit >= 0)
- {
- if(!g_race_qualifying)
- {
- M_ARGV(0, float) = WinningCondition_Race(checkrules_fraglimit);
- return true;
- }
- else if(g_race_qualifying == 2)
- {
- M_ARGV(0, float) = WinningCondition_QualifyingThenRace(checkrules_fraglimit);
- return true;
- }
- }
-}
-
-MUTATOR_HOOKFUNCTION(rc, ReadLevelCvars)
-{
- if(g_race_qualifying == 2)
- warmup_stage = 0;
-}
-
-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)
- {
- GameRules_teams(true);
- race_teams = BITS(bound(2, autocvar_g_race_teams, 4));
- }
- 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 = autocvar_timelimit_override;
-
- 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(want_qualifying)
- {
- g_race_qualifying = 2;
- independent_players = 1;
- race_fraglimit = (fraglimit_override >= 0) ? fraglimit_override : autocvar_fraglimit;
- race_leadlimit = (leadlimit_override >= 0) ? leadlimit_override : autocvar_leadlimit;
- race_timelimit = (timelimit_override >= 0) ? timelimit_override : autocvar_timelimit;
- qualifying_override = (qualifying_override >= 0) ? qualifying_override : autocvar_g_race_qualifying_timelimit;
- fraglimit_override = 0;
- leadlimit_override = 0;
- timelimit_override = qualifying_override;
- }
- else
- g_race_qualifying = 0;
- GameRules_limit_score(fraglimit_override);
- GameRules_limit_lead(leadlimit_override);
- GameRules_limit_time(timelimit_override);
- GameRules_limit_time_qualifying(qualifying_override);
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-void rc_SetLimits();
-void race_Initialize();
-
-REGISTER_MUTATOR(rc, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- rc_SetLimits();
-
- race_Initialize();
- }
- return 0;
-}
+++ /dev/null
-#include "gamemode_tdm.qh"
-
-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 || !this.cnt) { delete(this); return; }
-
- this.classname = "tdm_team";
- this.team = this.cnt + 1;
-}
-
-// code from here on is just to support maps that don't have team entities
-void tdm_SpawnTeam (string teamname, int teamcolor)
-{
- entity this = new_pure(tdm_team);
- this.netname = teamname;
- this.cnt = teamcolor - 1;
- this.team = teamcolor;
- this.spawnfunc_checked = true;
- //spawnfunc_tdm_team(this);
-}
-
-void tdm_DelayedInit(entity this)
-{
- // if no teams are found, spawn defaults
- if(find(NULL, classname, "tdm_team") == NULL)
- {
- LOG_TRACE("No \"tdm_team\" entities found on this map, creating them anyway.");
-
- int numteams = autocvar_g_tdm_teams_override;
- if(numteams < 2) { numteams = autocvar_g_tdm_teams; }
-
- int teams = BITS(bound(2, numteams, 4));
- if(teams & BIT(0))
- tdm_SpawnTeam("Red", NUM_TEAM_1);
- if(teams & BIT(1))
- tdm_SpawnTeam("Blue", NUM_TEAM_2);
- if(teams & BIT(2))
- tdm_SpawnTeam("Yellow", NUM_TEAM_3);
- if(teams & BIT(3))
- tdm_SpawnTeam("Pink", NUM_TEAM_4);
- }
-}
-
-MUTATOR_HOOKFUNCTION(tdm, CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
-{
- M_ARGV(1, string) = "tdm_team";
- return true;
-}
-
-MUTATOR_HOOKFUNCTION(tdm, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
+++ /dev/null
-#pragma once
-
-#include "../gamemode.qh"
-
-int autocvar_g_tdm_point_limit;
-int autocvar_g_tdm_point_leadlimit;
-bool autocvar_g_tdm_team_spawns;
-void tdm_DelayedInit(entity this);
-
-REGISTER_MUTATOR(tdm, false)
-{
- MUTATOR_STATIC();
- MUTATOR_ONADD
- {
- GameRules_teams(true);
- GameRules_spawning_teams(autocvar_g_tdm_team_spawns);
- GameRules_limit_score(autocvar_g_tdm_point_limit);
- GameRules_limit_lead(autocvar_g_tdm_point_leadlimit);
-
- InitializeEntity(NULL, tdm_DelayedInit, INITPRIO_GAMETYPE);
- }
- return 0;
-}
MODEL(EDGE, "models/pathlib/edge.md3");
#ifdef TURRET_DEBUG
-void mark_error(vector where,float lifetime);
-void mark_info(vector where,float lifetime);
-entity mark_misc(vector where,float lifetime);
+#include <common/turrets/util.qh>
#endif
void pathlib_showpath(entity start)
#pragma once
#include "pathlib.qh"
+
+#if DEBUGPATHING
+void pathlib_showpath(entity start);
+void pathlib_showpath2(entity path);
+void pathlib_showsquare(vector where,float goodsquare,float _lifetime);
+#endif
}
#if DEBUGPATHING
-void pathlib_showpath(entity start);
-void pathlib_showpath2(entity path);
-void pathlib_showsquare(vector where,float goodsquare,float _lifetime);
+#include "debug.qh"
#endif
const vector PLIB_RIGHT = '1 0 0';
//#define PLIB_LEFT '-1 0 0'
-#if DEBUGPATHING
-void pathlib_showpath(entity start);
-void pathlib_showpath2(entity path);
-#endif
-
entity openlist;
entity closedlist;
#include "cheats.qh"
#include "g_damage.qh"
#include "handicap.qh"
-#include "g_subs.qh"
#include "miscfunctions.qh"
#include "portals.qh"
#include "teamplay.qh"
#include "../common/anim.qh"
#include "../common/animdecide.qh"
#include "../common/csqcmodel_settings.qh"
+#include "../common/gamemodes/sv_rules.qh"
#include "../common/deathtypes/all.qh"
#include "../common/mapobjects/subs.qh"
#include "../common/playerstats.qh"
clone.effects = this.effects;
clone.glowmod = this.glowmod;
clone.event_damage = this.event_damage;
+ clone.event_heal = this.event_heal;
clone.anim_state = this.anim_state;
clone.anim_time = this.anim_time;
clone.anim_lower_action = this.anim_lower_action;
clone.dphitcontentsmask = this.dphitcontentsmask;
clone.death_time = this.death_time;
clone.pain_finished = this.pain_finished;
- clone.health = this.health;
- clone.armorvalue = this.armorvalue;
+ SetResourceAmountExplicit(clone, RESOURCE_HEALTH, GetResourceAmount(this, RESOURCE_HEALTH));
+ SetResourceAmountExplicit(clone, RESOURCE_ARMOR, GetResourceAmount(this, RESOURCE_ARMOR));
clone.armortype = this.armortype;
clone.model = this.model;
clone.modelindex = this.modelindex;
vector v;
Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
- v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+ v = healtharmor_applydamage(GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
take = v.x;
save = v.y;
if (take > 100)
Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
- this.armorvalue = this.armorvalue - save;
- this.health = this.health - take;
+ TakeResource(this, RESOURCE_ARMOR, save);
+ TakeResource(this, RESOURCE_HEALTH, take);
// pause regeneration for 5 seconds
this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
this.dmg_inflictor = inflictor;
- if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) <= -autocvar_sv_gibhealth && this.alpha >= 0)
{
// don't use any animations as a gib
this.frame = 0;
vector v;
float excess;
- dh = max(this.health, 0);
- da = max(this.armorvalue, 0);
+ dh = max(GetResourceAmount(this, RESOURCE_HEALTH), 0);
+ da = max(GetResourceAmount(this, RESOURCE_ARMOR), 0);
if(!DEATH_ISSPECIAL(deathtype))
{
else
Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
- v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+ v = healtharmor_applydamage(GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, deathtype, damage);
take = v.x;
save = v.y;
}
MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage);
- take = bound(0, M_ARGV(4, float), this.health);
- save = bound(0, M_ARGV(5, float), this.armorvalue);
+ take = bound(0, M_ARGV(4, float), GetResourceAmount(this, RESOURCE_HEALTH));
+ save = bound(0, M_ARGV(5, float), GetResourceAmount(this, RESOURCE_ARMOR));
excess = max(0, damage - take - save);
if(sound_allowed(MSG_BROADCAST, attacker))
{
if (!(this.flags & FL_GODMODE))
{
- this.armorvalue = this.armorvalue - save;
- this.health = this.health - take;
+ TakeResource(this, RESOURCE_ARMOR, save);
+ TakeResource(this, RESOURCE_HEALTH, take);
// pause regeneration for 5 seconds
if(take)
this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
animdecide_setaction(this, ANIMACTION_PAIN2, true);
}
}
-
+ float myhp = GetResourceAmount(this, RESOURCE_HEALTH);
+ if(myhp > 1)
+ if(myhp < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
if(sound_allowed(MSG_BROADCAST, attacker))
- if(this.health < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
- if(this.health > 1)
// exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
{
if(deathtype == DEATH_FALL.m_id)
PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
- else if(this.health > 75)
+ else if(myhp > 75)
PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
- else if(this.health > 50)
+ else if(myhp > 50)
PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
- else if(this.health > 25)
+ else if(myhp > 25)
PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
else
PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
// throw off bot aim temporarily
float shake;
- if(IS_BOT_CLIENT(this) && this.health >= 1)
+ if(IS_BOT_CLIENT(this) && GetResourceAmount(this, RESOURCE_HEALTH) >= 1)
{
shake = damage * 5 / (bound(0,skill,100) + 1);
this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake;
valid_damage_for_weaponstats = true;
}
- dh = dh - max(this.health, 0);
- da = da - max(this.armorvalue, 0);
+ dh = dh - max(GetResourceAmount(this, RESOURCE_HEALTH), 0);
+ da = da - max(GetResourceAmount(this, RESOURCE_ARMOR), 0);
if(valid_damage_for_weaponstats)
{
WeaponStats_LogDamage(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot, dh + da);
MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype, damage);
- if (this.health < 1)
+ if (GetResourceAmount(this, RESOURCE_HEALTH) < 1)
{
float defer_ClientKill_Now_TeamChange;
defer_ClientKill_Now_TeamChange = false;
// player could have been miraculously resuscitated ;)
// e.g. players in freezetag get frozen, they don't really die
- if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
+ if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
return;
if (!this.respawn_time) // can be set in the mutator hook PlayerDies
// when we get here, player actually dies
Unfreeze(this); // remove any icy remains
- this.health = 0; // Unfreeze resets health, so we need to set it back
+ SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0); // Unfreeze resets health, so we need to set it back
// clear waypoints
WaypointSprite_PlayerDead(this);
// set damage function to corpse damage
this.event_damage = PlayerCorpseDamage;
+ this.event_heal = func_null;
// call the corpse damage function just in case it wants to gib
this.event_damage(this, inflictor, attacker, excess, deathtype, weaponentity, hitloc, force);
}
}
+bool PlayerHeal(entity targ, entity inflictor, float amount, float limit)
+{
+ if(GetResourceAmount(targ, RESOURCE_HEALTH) <= 0 || GetResourceAmount(targ, RESOURCE_HEALTH) >= limit)
+ return false;
+
+ GiveResourceWithLimit(targ, RESOURCE_HEALTH, amount, limit);
+ return true;
+}
+
bool MoveToTeam(entity client, int team_colour, int type)
{
int lockteams_backup = lockteams; // backup any team lock
return true;
}
-/** print(), but only print if the server is not local */
-void dedicated_print(string input)
-{
- if (server_is_dedicated) print(input);
-}
-
-void PrintToChat(entity player, string text)
-{
- text = strcat("\{1}^7", text, "\n");
- sprint(player, text);
-}
-
-void DebugPrintToChat(entity player, string text)
-{
- if (autocvar_developer)
- {
- PrintToChat(player, text);
- }
-}
-
-void PrintToChatAll(string text)
-{
- text = strcat("\{1}^7", text, "\n");
- bprint(text);
-}
-
-void DebugPrintToChatAll(string text)
-{
- if (autocvar_developer)
- {
- PrintToChatAll(text);
- }
-}
-
-void PrintToChatTeam(int teamnum, string text)
-{
- text = strcat("\{1}^7", text, "\n");
- FOREACH_CLIENT(IS_REAL_CLIENT(it),
- {
- if (it.team == teamnum)
- {
- sprint(it, text);
- }
- });
-}
-
-void DebugPrintToChatTeam(int teamnum, string text)
-{
- if (autocvar_developer)
- {
- PrintToChatTeam(teamnum, text);
- }
-}
-
/**
* message "": do not say, just test flood control
* return value:
void CopyBody_Think(entity this);
void CopyBody(entity this, float keepvelocity);
-void dedicated_print(string input);
-
-/// \brief Print the string to player's chat.
-/// \param[in] player Player to print to.
-/// \param[in] text Text to print.
-/// \return No return.
-void PrintToChat(entity player, string text);
-
-/// \brief Print the string to player's chat if the server cvar "developer" is
-/// not 0.
-/// \param[in] player Player to print to.
-/// \param[in] text Text to print.
-/// \return No return.
-void DebugPrintToChat(entity player, string text);
-
-/// \brief Prints the string to all players' chat.
-/// \param[in] text Text to print.
-/// \return No return.
-void PrintToChatAll(string text);
-
-/// \brief Prints the string to all players' chat if the server cvar "developer"
-/// is not 0.
-/// \param[in] text Text to print.
-/// \return No return.
-void DebugPrintToChatAll(string text);
-
-/// \brief Print the string to chat of all players of the specified team.
-/// \param[in] teamnum Team to print to. See NUM_TEAM constants.
-/// \param[in] text Text to print.
-/// \return No return.
-void PrintToChatTeam(int teamnum, string text);
-
-/// \brief Print the string to chat of all players of the specified team if the
-/// server cvar "developer" is not 0.
-/// \param[in] teamnum Team to print to. See NUM_TEAM constants.
-/// \param[in] text Text to print.
-/// \return No return.
-void DebugPrintToChatTeam(int teamnum, string text);
-
void player_setupanimsformodel(entity this);
void player_anim(entity this);
void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force);
+bool PlayerHeal(entity targ, entity inflictor, float amount, float limit);
+
int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
// reset fade counter
teleporter.portal_wants_to_vanish = 0;
teleporter.fade_time = time + autocvar_g_balance_portal_lifetime;
- teleporter.health = autocvar_g_balance_portal_health;
- teleporter.enemy.health = autocvar_g_balance_portal_health;
+ SetResourceAmountExplicit(teleporter, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
+ SetResourceAmountExplicit(teleporter.enemy, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
return 1;
}
toucher.effects += EF_BLUE - EF_RED;
}
-void Portal_Think(entity this);
void Portal_MakeBrokenPortal(entity portal)
{
portal.skin = 2;
if(attacker != this.aiment)
if(IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(this.aiment))
return;
- this.health -= damage;
- if(this.health < 0)
+ TakeResource(this, RESOURCE_HEALTH, damage);
+ if(GetResourceAmount(this, RESOURCE_HEALTH) < 0)
Portal_Remove(this, 1);
}
portal.takedamage = DAMAGE_AIM;
portal.event_damage = Portal_Damage;
portal.fade_time = time + autocvar_g_balance_portal_lifetime;
- portal.health = autocvar_g_balance_portal_health;
+ SetResourceAmountExplicit(portal, RESOURCE_HEALTH, autocvar_g_balance_portal_health);
setmodel(portal, MDL_PORTAL);
portal.savemodelindex = portal.modelindex;
setcefc(portal, Portal_Customize);
vector Portal_ApplyTransformToPlayerAngle(vector transform, vector vangle);
void Portal_ClearAll_PortalsOnly(entity own);
+
+void Portal_Think(entity this);
#include <common/gamemodes/rules.qh>
#include <common/net_linked.qh>
#include <common/state.qh>
+#include <common/weapons/weapon/porto.qh>
#include "../common/mapobjects/subs.qh"
+#include <common/mapobjects/triggers.qh>
#include "../lib/warpzone/util_server.qh"
#include "../lib/warpzone/common.qh"
#include "../common/mutators/mutator/waypoints/waypointsprites.qh"
race_SendNextCheckpoint(msg_entity.enemy, 1);
}
-void W_Porto_Fail(entity this, float failhard);
-
float race_readTime(string map, float pos)
{
string rr = ((g_cts) ? CTS_RECORD : ((g_ctf) ? CTF_RECORD : RACE_RECORD));
const float MAX_CHECKPOINTS = 255;
-spawnfunc(target_checkpoint);
-
.float race_penalty;
.float race_penalty_accumulator;
.string race_penalty_reason;
void race_RetractPlayer(entity this);
void race_InitSpectator();
+
+spawnfunc(target_checkpoint);
float GetResourceLimit(entity e, int resource_type)
{
+ if(!IS_PLAYER(e))
+ return RESOURCE_LIMIT_NONE; // no limits on non-players
+
float limit;
switch (resource_type)
{
return e.(resource_field);
}
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount)
+{
+ .float resource_field = GetResourceField(resource_type);
+ if (e.(resource_field) != amount)
+ {
+ e.(resource_field) = amount;
+ return true;
+ }
+ return false;
+}
+
void SetResourceAmount(entity e, int resource_type, float amount)
{
bool forbid = MUTATOR_CALLHOOK(SetResourceAmount, e, resource_type, amount);
}
resource_type = M_ARGV(1, int);
amount = M_ARGV(2, float);
- float max_amount = GetResourceLimit(e, resource_type);
+ float max_amount = GetResourceLimit(e, resource_type); // TODO: should allow overriding these limits if cheats are enabled!
float amount_wasted = 0;
- if (amount > max_amount)
+ if (amount > max_amount && max_amount != RESOURCE_LIMIT_NONE)
{
amount_wasted = amount - max_amount;
amount = max_amount;
}
- .float resource_field = GetResourceField(resource_type);
- if (e.(resource_field) != amount)
+ bool changed = SetResourceAmountExplicit(e, resource_type, amount);
+ if (changed)
{
- e.(resource_field) = amount;
MUTATOR_CALLHOOK(ResourceAmountChanged, e, resource_type, amount);
}
if (amount_wasted == 0)
void GiveResource(entity receiver, int resource_type, float amount)
{
- if (amount == 0)
+ if (amount <= 0)
{
return;
}
void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
float limit)
{
- if (amount == 0)
+ if (amount <= 0)
{
return;
}
resource_type = M_ARGV(1, int);
amount = M_ARGV(2, float);
limit = M_ARGV(3, float);
- if (amount == 0)
+ if (amount <= 0)
{
return;
}
float current_amount = GetResourceAmount(receiver, resource_type);
- if (current_amount + amount > limit)
+ if (current_amount + amount > limit && limit != RESOURCE_LIMIT_NONE)
{
amount = limit - current_amount;
}
GiveResource(receiver, resource_type, amount);
}
+void TakeResource(entity receiver, int resource_type, float amount)
+{
+ if (amount <= 0)
+ {
+ return;
+ }
+ bool forbid = MUTATOR_CALLHOOK(TakeResource, receiver, resource_type,
+ amount);
+ if (forbid)
+ {
+ return;
+ }
+ resource_type = M_ARGV(1, int);
+ amount = M_ARGV(2, float);
+ if (amount <= 0)
+ {
+ return;
+ }
+ SetResourceAmount(receiver, resource_type,
+ GetResourceAmount(receiver, resource_type) - amount);
+}
+
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+ float limit)
+{
+ if (amount <= 0)
+ {
+ return;
+ }
+ bool forbid = MUTATOR_CALLHOOK(TakeResourceWithLimit, receiver,
+ resource_type, amount, limit);
+ if (forbid)
+ {
+ return;
+ }
+ resource_type = M_ARGV(1, int);
+ amount = M_ARGV(2, float);
+ limit = M_ARGV(3, float);
+ if (amount <= 0)
+ {
+ return;
+ }
+ float current_amount = GetResourceAmount(receiver, resource_type);
+ if (current_amount - amount < limit)
+ {
+ amount = limit + current_amount;
+ }
+ TakeResource(receiver, resource_type, amount);
+}
+
int GetResourceType(.float resource_field)
{
switch (resource_field)
#include <common/resources.qh>
-/// \brief Unconditional maximum amount of resources the entity can have.
-const int RESOURCE_AMOUNT_HARD_LIMIT = 999;
-
// ============================ Public API ====================================
/// \brief Returns the maximum amount of the given resource.
/// \return Current amount of resource the given entity has.
float GetResourceAmount(entity e, int resource_type);
+/// \brief Sets the resource amount of an entity without calling any hooks.
+/// \param[in,out] e Entity to adjust.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to set.
+/// \return Boolean for whether the ammo amount was changed
+bool SetResourceAmountExplicit(entity e, int resource_type, float amount);
+
/// \brief Sets the current amount of resource the given entity will have.
/// \param[in,out] e Entity to adjust.
/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
void GiveResourceWithLimit(entity receiver, int resource_type, float amount,
float limit);
+/// \brief Takes an entity some resource.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \return No return.
+void TakeResource(entity receiver, int resource_type, float amount);
+
+/// \brief Takes an entity some resource but not less than a limit.
+/// \param[in,out] receiver Entity to take resource from.
+/// \param[in] resource_type Type of the resource (a RESOURCE_* constant).
+/// \param[in] amount Amount of resource to take.
+/// \param[in] limit Limit of resources to take.
+/// \return No return.
+void TakeResourceWithLimit(entity receiver, int resource_type, float amount,
+ float limit);
+
// ===================== Legacy and/or internal API ===========================
/// \brief Converts an entity field to resource type.
#include "scores.qh"
#include "command/common.qh"
-#include "mutators/_mod.qh"
+#include "defs.qh"
+#include <server/g_world.qh>
+#include <server/miscfunctions.qh>
+#include <server/mutators/_mod.qh>
#include <common/net_linked.qh>
#include "../common/playerstats.qh"
#include "../common/teams.qh"
+#include <common/mapinfo.qh>
+#include <common/mutators/base.qh>
#include <common/scores.qh>
+#include <common/state.qh>
+#include <common/stats.qh>
.entity scorekeeper;
entity teamscorekeepers[16];
int ScoreRules_teams;
-void CheckAllowedTeams (entity for_whom);
-
int NumTeams(int teams)
{
return boolean(teams & BIT(0)) + boolean(teams & BIT(1)) + boolean(teams & BIT(2)) + boolean(teams & BIT(3));
#include "spawnpoints.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "g_world.qh"
#include "race.qh"
+#include "defs.qh"
#include "../common/constants.qh"
#include <common/net_linked.qh>
#include "../common/teams.qh"
#include "../common/util.qh"
#include "../lib/warpzone/common.qh"
#include "../lib/warpzone/util_server.qh"
+#include <server/utils.qh>
bool SpawnPoint_Send(entity this, entity to, int sf)
{
**/
vector steerlib_arrive(entity this, vector point, float maximal_distance)
{
- float distance;
- vector direction;
-
- distance = bound(0.001,vlen(this.origin - point),maximal_distance);
- direction = normalize(point - this.origin);
+ float distance = bound(0.001,vlen(this.origin - point),maximal_distance);
+ vector direction = normalize(point - this.origin);
return direction * (distance / maximal_distance);
}
**/
vector steerlib_attract(entity this, vector point, float maximal_distance)
{
- float distance;
- vector direction;
-
- distance = bound(0.001,vlen(this.origin - point),maximal_distance);
- direction = normalize(point - this.origin);
+ float distance = bound(0.001,vlen(this.origin - point),maximal_distance);
+ vector direction = normalize(point - this.origin);
return direction * (1-(distance / maximal_distance));
}
vector steerlib_attract2(entity this, vector point, float min_influense,float max_distance,float max_influense)
{
- float distance;
- vector direction;
- float influense;
-
- distance = bound(0.00001,vlen(this.origin - point),max_distance);
- direction = normalize(point - this.origin);
+ float distance = bound(0.00001,vlen(this.origin - point),max_distance);
+ vector direction = normalize(point - this.origin);
- influense = 1 - (distance / max_distance);
+ float influense = 1 - (distance / max_distance);
influense = min_influense + (influense * (max_influense - min_influense));
return direction * influense;
#include "anticheat.qh"
#include "g_hook.qh"
+#include "g_damage.qh"
#include "g_world.qh"
#include "bot/api.qh"
#include "command/common.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "weapons/csqcprojectile.qh"
+#include <server/compat/quake3.qh>
#include "../common/constants.qh"
#include "../common/deathtypes/all.qh"
#include "../common/util.qh"
#include "../common/vehicles/all.qh"
+#include <common/monsters/sv_monsters.qh>
#include <common/weapons/_all.qh>
#include "../lib/csqcmodel/sv_model.qh"
bool game_delay_last;
bool autocvar_sv_autopause = false;
-float RedirectionThink();
void systems_update();
void sys_phys_update(entity this, float dt);
void StartFrame()
.float anglejitter;
.string gametypefilter;
.string cvarfilter;
-bool DoesQ3ARemoveThisEntity(entity this);
/**
* Evaluate an expression of the form: [+ | -]? [var[op]val | [op]var | val | var] ...
}
delete(tracetest_ent);
}
+
+/*
+==================
+main
+
+unused but required by the engine
+==================
+*/
+void main ()
+{
+
+}
#pragma once
bool expr_evaluate(string s);
+
+/*
+==================
+main
+
+unused but required by the engine
+==================
+*/
+void main ();
#include "command/vote.qh"
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "../common/deathtypes/all.qh"
-#include "../common/gamemodes/_mod.qh"
+#include <common/gamemodes/_mod.qh>
#include "../common/teams.qh"
void TeamchangeFrags(entity e)
if(g_weapon_stay && !g_cts)
modifications = strcat(modifications, ", Weapons stay");
if(g_jetpack)
- modifications = strcat(modifications, ", Jet pack");
+ modifications = strcat(modifications, ", Jetpack");
if(autocvar_g_powerups == 0)
modifications = strcat(modifications, ", No powerups");
if(autocvar_g_powerups > 0)
void test_weapons_hurt(entity this)
{
- EXPECT_NE(100, this.health);
+ EXPECT_NE(100, GetResourceAmount(this, RESOURCE_HEALTH));
delete(this.enemy);
delete(this);
}
#include "accuracy.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include <common/constants.qh>
#include <common/net_linked.qh>
#include <common/teams.qh>
int mutator_check = MUTATOR_CALLHOOK(AccuracyTargetValid, attacker, targ);
if (warmup_stage) return false;
+ if (game_stopped) return false;
// damage to dead/frozen players is good only if it happens in the frame they get killed / frozen
// so that stats for weapons that shoot multiple projectiles per shot are properly counted
#include <server/defs.qh>
#include <server/miscfunctions.qh>
#include "../antilag.qh"
-#include "../g_subs.qh"
#include <common/weapons/_all.qh>
#include <common/state.qh>
#include <common/wepent.qh>
#include "weaponsystem.qh"
#include "../resources.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include <common/t_items.qh>
#include <server/items.qh>
#include <common/weapons/_all.qh>
#include "weaponsystem.qh"
#include "../resources.qh"
#include "../items.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include <common/t_items.qh>
#include "../g_damage.qh"
#include <common/items/item.qh>
#include "weaponsystem.qh"
#include "../g_damage.qh"
-#include "../g_subs.qh"
#include "../antilag.qh"
#include <common/constants.qh>
#include "selection.qh"
#include "../command/common.qh"
-#include "../mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
#include "../round_handler.qh"
-#include "../resources.qh"
+#include <server/resources.qh>
#include <common/t_items.qh>
#include <common/animdecide.qh>
#include <common/constants.qh>
return false;
}
-void wframe_send(entity actor, entity weaponentity, vector a, bool restartanim);
-
/**
* @param t defer thinking until time + t
* @param func next think function
entity this = actor.(weaponentity);
if (frametime) this.weapon_frametime = frametime;
- if (!this || actor.health < 1) return; // Dead player can't use weapons and injure impulse commands
+ if (!this || GetResourceAmount(actor, RESOURCE_HEALTH) < 1) return; // Dead player can't use weapons and injure impulse commands
int button_atck = PHYS_INPUT_BUTTON_ATCK(actor);
int button_atck2 = PHYS_INPUT_BUTTON_ATCK2(actor);
--- /dev/null
+// Random items mutator config for Overkill ruleset
+
+// Map items
+
+set g_random_items_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill."
+set g_random_items_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill."
+set g_random_items_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill."
+set g_random_items_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill."
+set g_random_items_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill."
+set g_random_items_weapon_okhmg_probability 0.5 "Probability of random overkill HMG spawning in the map during overkill."
+set g_random_items_weapon_okrpc_probability 0.5 "Probability of random overkill RPC spawning in the map during overkill."
+
+// Loot
+
+set g_random_loot_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill."
+set g_random_loot_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill."
+set g_random_loot_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill."
+set g_random_loot_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill."
+set g_random_loot_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill."
+set g_random_loot_weapon_okhmg_probability 1 "Probability of random overkill HMG spawning as loot during overkill."
+set g_random_loot_weapon_okrpc_probability 1 "Probability of random overkill RPC spawning as loot during overkill."
set g_random_items_weapon_rifle_probability 0 "Probability of random rifle spawning in the map."
set g_random_items_weapon_seeker_probability 0 "Probability of random TAG seeker spawning in the map."
set g_random_items_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning in the map."
+set g_random_items_weapon_okshotgun_probability 0 "Probability of random overkill shotgun spawning in the map."
+set g_random_items_weapon_okmachinegun_probability 0 "Probability of random overkill machinegun spawning in the map."
+set g_random_items_weapon_oknex_probability 0 "Probability of random overkill nex spawning in the map."
+set g_random_items_weapon_okhmg_probability 0 "Probability of random overkill HMG spawning in the map."
+set g_random_items_weapon_okrpc_probability 0 "Probability of random overkill RPC spawning in the map."
set g_random_items_item_strength_probability 1 "Probability of random strength spawning in the map."
set g_random_items_item_shield_probability 1 "Probability of random shield spawning in the map."
set g_random_items_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning in the map."
set g_random_items_item_invisibility_probability 1 "Probability of random invisibility spawning in the map."
set g_random_items_item_extralife_probability 1 "Probability of random extra life spawning in the map."
set g_random_items_item_speed_probability 1 "Probability of random speed spawning in the map."
-set g_random_items_overkill_item_health_mega_probability 1 "Probability of random mega health spawning in the map during overkill."
-set g_random_items_overkill_item_armor_small_probability 10 "Probability of random small armor spawning in the map during overkill."
-set g_random_items_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning in the map during overkill."
-set g_random_items_overkill_item_armor_big_probability 2 "Probability of random big armor spawning in the map during overkill."
-set g_random_items_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning in the map during overkill."
-set g_random_items_overkill_weapon_okhmg_probability 0.5 "Probability of random overkill HMG spawning in the map during overkill."
-set g_random_items_overkill_weapon_okrpc_probability 0.5 "Probability of random overkill RPC spawning in the map during overkill."
// Loot
set g_random_loot_weapon_rifle_probability 0 "Probability of random rifle spawning as loot."
set g_random_loot_weapon_seeker_probability 0 "Probability of random TAG seeker spawning as loot."
set g_random_loot_weapon_vaporizer_probability 0 "Probability of random vaporizer spawning as loot."
+set g_random_loot_weapon_okshotgun_probability 0 "Probability of random overkill shotgun spawning as loot."
+set g_random_loot_weapon_okmachinegun_probability 0 "Probability of random overkill machinegun spawning as loot."
+set g_random_loot_weapon_oknex_probability 0 "Probability of random overkill nex spawning as loot."
+set g_random_loot_weapon_okhmg_probability 0 "Probability of random overkill HMG spawning as loot."
+set g_random_loot_weapon_okrpc_probability 0 "Probability of random overkill RPC spawning as loot."
set g_random_loot_item_strength_probability 1 "Probability of random strength spawning as loot."
set g_random_loot_item_shield_probability 1 "Probability of random shield spawning as loot."
set g_random_loot_item_fuel_regen_probability 0 "Probability of random fuel regeneration spawning as loot."
set g_random_loot_item_invisibility_probability 1 "Probability of random invisibility spawning as loot."
set g_random_loot_item_extralife_probability 1 "Probability of random extra life spawning as loot."
set g_random_loot_item_speed_probability 1 "Probability of random speed spawning as loot."
-set g_random_loot_overkill_item_health_mega_probability 1 "Probability of random mega health spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_small_probability 10 "Probability of random small armor spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_medium_probability 4 "Probability of random medium armor spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_big_probability 2 "Probability of random big armor spawning as loot during overkill."
-set g_random_loot_overkill_item_armor_mega_probability 1 "Probability of random mega armor spawning as loot during overkill."
-set g_random_loot_overkill_weapon_okhmg_probability 1 "Probability of random overkill HMG spawning as loot during overkill."
-set g_random_loot_overkill_weapon_okrpc_probability 1 "Probability of random overkill RPC spawning as loot during overkill."
exec physicsXDF.cfg
// general gameplay
-set g_jump_grunt 1 // make enemies even easier to hear when they're jumping around
+// set g_jump_grunt 1 // just no
set g_shootfromcenter 1 // hit where you point at with the crosshair (almost so, no shooteye because it's really ugly)
set g_balance_kill_antispam 0
set g_forced_respawn 1
set g_turrets 0
set g_vehicles 0
set sv_showspectators 0
+set sv_taunt 0
exec xonotic-server.cfg
exec balance-overkill.cfg
exec physicsOverkill.cfg
+exec randomitems-overkill.cfg
// general gameplay
set g_overkill 1
set menu_updatecheck_getpacks 1 "get update packs from update server"
-set cl_loddistance1 1024
-set cl_loddistance2 3072
+seta cl_loddistance1 1024
+seta cl_loddistance2 3072
seta cl_playerdetailreduction 4 "the higher, the less detailed player models are displayed (LOD)"
seta cl_modeldetailreduction 1 "the higher, the less detailed certain map models are displayed (LOD)"
set sv_timeout_leadtime 4 "how long the players will be informed that a timeout was called before it starts, in seconds"
set sv_timeout_resumetime 3 "how long the remaining timeout-time will be after a player called the timein command"
-set g_allow_oldvortexbeam 0 "If enabled, clients are allowed to use old v2.3 Vortex beam"
+set g_allow_oldvortexbeam 1 "If enabled, clients are allowed to use old v2.3 Vortex beam"
set g_telefrags 1 "telefragging, i.e. killing someone who stands in the way of someone who is teleporting"
set g_telefrags_teamplay 1 "never telefrag team mates"
set sv_vote_gametype 0 "show a vote screen for gametypes before map vote screen"
set sv_vote_gametype_keeptwotime 10 "show only 2 options after this amount of time during gametype vote screen"
-set sv_vote_gametype_options "dm ctf ca lms tdm ft"
+set sv_vote_gametype_options "dm tdm ctf"
set sv_vote_gametype_timeout 20
set sv_vote_gametype_default_current 1 "Keep the current gametype if no one votes"