#include "hud.qh"
+#include "panel/scoreboard.qh"
+
#include "hud_config.qh"
#include "../mapvoting.qh"
#include "../teamradar.qh"
#include <common/t_items.qh>
#include <common/deathtypes/all.qh>
-#include <common/items/all.qc>
+#include <common/items/_mod.qh>
#include <common/mapinfo.qh>
#include <common/vehicles/all.qh>
#include <common/mutators/mutator/waypoints/all.qh>
DrawNumIcon_expanding(myPos, mySize, x, icon, vertical, icon_right_align, color, theAlpha, 0);
}
-#include "all.inc"
-
/*
==================
Main HUD system
}
}
+bool HUD_Minigame_Showpanels();
+
bool HUD_Panel_CheckFlags(int showflags)
{
TC(int, showflags);
return true;
}
+entity CSQCModel_server2csqc(int i);
void calc_followmodel_ofs(entity view);
void Hud_Dynamic_Frame()
{
{
int i;
// global hud alpha fade (scoreboard-related panels behave differently and override it temporarly)
- if(menu_enabled == 1)
+ if(hud_configure_menu_open == 1)
hud_fade_alpha = 1;
else if(!autocvar__hud_configure)
hud_fade_alpha = (1 - scoreboard_fade_alpha) * (1 - autocvar__menu_alpha);
#pragma once
-#include <common/weapons/all.qh>
+#include <common/weapons/_all.qh>
bool HUD_Radar_Clickable();
void HUD_Radar_Mouse();
float teamnagger;
- float hud_configure_checkcollisions;
- float hud_configure_prev;
- vector hud_configure_gridSize;
- vector hud_configure_realGridSize;
-
int hudShiftState;
const int S_SHIFT = 1;
const int S_CTRL = 2;
const int S_ALT = 4;
- float menu_enabled; // 1 showing the entire HUD, 2 showing only the clicked panel
-
float hud_fade_alpha;
string hud_skin_path;
if (autocvar__hud_configure) { \
if (!panel_enabled) \
panel_bg_alpha = 0.25; \
- else if (menu_enabled == 2 && panel == highlightedPanel) \
+ else if (hud_configure_menu_open == 2 && panel == highlightedPanel) \
panel_bg_alpha = (1 - autocvar__menu_alpha) * max(cvar("hud_configure_bg_minalpha"), panel_bg_alpha) + autocvar__menu_alpha * panel_bg_alpha;\
else \
panel_bg_alpha = max(cvar("hud_configure_bg_minalpha"), panel_bg_alpha); \
// return smoothly faded pos and size of given panel when a dialog is active
// don't center too wide panels, it doesn't work with different resolutions
#define HUD_Panel_UpdatePosSize_ForMenu() MACRO_BEGIN { \
- vector menu_enable_size = panel_size; \
+ vector new_size = panel_size; \
float max_panel_width = 0.52 * vid_conwidth; \
if(panel_size.x > max_panel_width) \
{ \
- menu_enable_size.x = max_panel_width; \
- menu_enable_size.y = panel_size.y * (menu_enable_size.x / panel_size.x); \
+ new_size.x = max_panel_width; \
+ new_size.y = panel_size.y * (new_size.x / panel_size.x); \
} \
- vector menu_enable_pos = eX * (panel_bg_border + 0.5 * max_panel_width) + eY * 0.5 * vid_conheight - 0.5 * menu_enable_size; \
- panel_pos = (1 - autocvar__menu_alpha) * panel_pos + (autocvar__menu_alpha) * menu_enable_pos; \
- panel_size = (1 - autocvar__menu_alpha) * panel_size + (autocvar__menu_alpha) * menu_enable_size; \
+ vector new_pos = eX * (panel_bg_border + 0.5 * max_panel_width) + eY * 0.5 * vid_conheight - 0.5 * new_size; \
+ panel_pos = (1 - autocvar__menu_alpha) * panel_pos + (autocvar__menu_alpha) * new_pos; \
+ panel_size = (1 - autocvar__menu_alpha) * panel_size + (autocvar__menu_alpha) * new_size; \
} MACRO_END
// Scale the pos and size vectors to absolute coordinates
HUD_Panel_GetPadding(); \
panel.current_panel_bg_alpha = panel_bg_alpha; \
panel.current_panel_fg_alpha = panel_fg_alpha; \
- if (menu_enabled == 2 && panel == highlightedPanel) { \
+ if (hud_configure_menu_open == 2 && panel == highlightedPanel) { \
HUD_Panel_UpdatePosSize_ForMenu(); \
} else { \
panel_bg_alpha *= hud_fade_alpha; \
panel_pos = stov(cvar_string(strcat("hud_panel_", panel.panel_name, "_pos"))); \
panel_size = stov(cvar_string(strcat("hud_panel_", panel.panel_name, "_size"))); \
HUD_Panel_ScalePosSize(); \
- if (menu_enabled == 2 && panel == highlightedPanel) { \
+ if (hud_configure_menu_open == 2 && panel == highlightedPanel) { \
HUD_Panel_UpdatePosSize_ForMenu(); \
} \
panel_bg_border_str = cvar_string(strcat("hud_panel_", panel.panel_name, "_bg_border")); \
#include "powerups.qh"
-#include <common/items/all.qc>
+#include <common/items/_mod.qh>
// Powerups (#2)
// Initialize items
if(!autocvar__hud_configure)
{
- if(!autocvar_hud_panel_powerups) return;
- if(spectatee_status == -1) return;
- if(STAT(HEALTH) <= 0) return;
+ if((!autocvar_hud_panel_powerups) || (spectatee_status == -1))
+ return;
+ if(STAT(HEALTH) <= 0 && autocvar_hud_panel_powerups_hide_ondeath)
+ return;
if(!(allItems & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON)) && !allBuffs) return;
strengthTime = bound(0, STAT(STRENGTH_FINISHED) - time, 99);
#pragma once
#include <common/constants.qh>
-#include <common/weapons/all.qh>
+#include <common/weapons/_all.qh>
// --------------------------------------------------------------------------
// MENU Functionality
float g_trueaim_minrange;
- float hud;
+ int hud;
float view_quality;
int num_spectators;
#include "view.qh"
#include "announcer.qh"
-#include "hud/all.qh"
+#include "hud/_mod.qh"
#include "mapvoting.qh"
#include "shownames.qh"
#include "hud/panel/scoreboard.qh"
#include <common/constants.qh>
#include <common/debug.qh>
#include <common/mapinfo.qh>
-#include <common/gamemodes/all.qh>
+#include <common/gamemodes/_mod.qh>
#include <common/physics/player.qh>
#include <common/stats.qh>
#include <common/triggers/target/music.qh>
#include <common/teams.qh>
#include <common/vehicles/all.qh>
-#include <common/weapons/all.qh>
+#include <common/weapons/_all.qh>
#include <common/viewloc.qh>
#include <common/minigames/cl_minigames.qh>
#include <common/minigames/cl_minigames_hud.qh>
float f, i, j;
vector v;
if(!scoreboard_active && !camera_active && intermission != 2 &&
- spectatee_status != -1 && !csqcplayer.viewloc &&
+ spectatee_status != -1 && !csqcplayer.viewloc && !MUTATOR_CALLHOOK(DrawCrosshair) &&
!HUD_MinigameMenu_IsOpened() )
{
if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(NADE_TIMER), '0.25 0.90 1' + ('1 0 0' * STAT(NADE_TIMER)) - ('0 1 1' * STAT(NADE_TIMER)), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
drawstring_aspect(eY * 0.64 * vid_conheight, ((autocvar_cl_nade_timer == 2) ? _("Nade timer") : ""), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
}
+ else if(STAT(CAPTURE_PROGRESS))
+ {
+ DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(CAPTURE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
+ drawstring_aspect(eY * 0.64 * vid_conheight, _("Capture progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
+ }
else if(STAT(REVIVE_PROGRESS))
{
DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", STAT(REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE);
float oldr_useinfinitefarclip;
void cl_notice_run();
+
float prev_myteam;
int lasthud;
float vh_notice_time;
else if(csqcplayer.viewloc) { setproperty(VF_FOV, GetViewLocationFOV(110)); } // enforce 110 fov, so things dont look odd
else { setproperty(VF_FOV, GetCurrentFov(fov)); }
- // Camera for demo playback
- if(camera_active)
+ if(camera_active) // Camera for demo playback
{
if(autocvar_camera_enable)
CSQC_Demo_Camera();
--- /dev/null
- it.ons_roundlost = true;
+#include "sv_controlpoint.qh"
+#include "sv_generator.qh"
+
+bool g_onslaught;
+
+float autocvar_g_onslaught_teleport_wait;
+bool autocvar_g_onslaught_spawn_at_controlpoints;
+bool autocvar_g_onslaught_spawn_at_generator;
+float autocvar_g_onslaught_cp_proxydecap;
+float autocvar_g_onslaught_cp_proxydecap_distance = 512;
+float autocvar_g_onslaught_cp_proxydecap_dps = 100;
+float autocvar_g_onslaught_spawn_at_controlpoints_chance = 0.5;
+float autocvar_g_onslaught_spawn_at_controlpoints_random;
+float autocvar_g_onslaught_spawn_at_generator_chance;
+float autocvar_g_onslaught_spawn_at_generator_random;
+float autocvar_g_onslaught_cp_buildhealth;
+float autocvar_g_onslaught_cp_buildtime;
+float autocvar_g_onslaught_cp_health;
+float autocvar_g_onslaught_cp_regen;
+float autocvar_g_onslaught_gen_health;
+float autocvar_g_onslaught_shield_force = 100;
+float autocvar_g_onslaught_allow_vehicle_touch;
+float autocvar_g_onslaught_round_timelimit;
+float autocvar_g_onslaught_warmup;
+float autocvar_g_onslaught_teleport_radius;
+float autocvar_g_onslaught_spawn_choose;
+float autocvar_g_onslaught_click_radius;
+
+void FixSize(entity e);
+
+// =======================
+// CaptureShield Functions
+// =======================
+
+bool ons_CaptureShield_Customize(entity this, entity client)
+{
+ entity e = WaypointSprite_getviewentity(client);
+
+ if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, e.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return false; }
+ if(SAME_TEAM(this, e)) { return false; }
+
+ return true;
+}
+
+void ons_CaptureShield_Touch(entity this, entity toucher)
+{
+ if(!this.enemy.isshielded && (ons_ControlPoint_Attackable(this.enemy, toucher.team) > 0 || this.enemy.classname != "onslaught_controlpoint")) { return; }
+ if(!IS_PLAYER(toucher)) { return; }
+ if(SAME_TEAM(toucher, this)) { 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, mymid, normalize(theirmid - mymid) * ons_captureshield_force);
+
+ if(IS_REAL_CLIENT(toucher))
+ {
+ play2(toucher, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+
+ if(this.enemy.classname == "onslaught_generator")
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED);
+ else
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED);
+ }
+}
+
+void ons_CaptureShield_Reset(entity this)
+{
+ this.colormap = this.enemy.colormap;
+ this.team = this.enemy.team;
+}
+
+void ons_CaptureShield_Spawn(entity generator, bool is_generator)
+{
+ entity shield = new(ons_captureshield);
+
+ shield.enemy = generator;
+ shield.team = generator.team;
+ shield.colormap = generator.colormap;
+ shield.reset = ons_CaptureShield_Reset;
+ settouch(shield, ons_CaptureShield_Touch);
+ setcefc(shield, ons_CaptureShield_Customize);
+ shield.effects = EF_ADDITIVE;
+ set_movetype(shield, MOVETYPE_NOCLIP);
+ shield.solid = SOLID_TRIGGER;
+ shield.avelocity = '7 0 11';
+ shield.scale = 1;
+ shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3");
+
+ precache_model(shield.model);
+ setorigin(shield, generator.origin);
+ _setmodel(shield, shield.model);
+ setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+}
+
+
+// ==========
+// Junk Pile
+// ==========
+
+void setmodel_fixsize(entity e, Model m)
+{
+ setmodel(e, m);
+ FixSize(e);
+}
+
+void onslaught_updatelinks()
+{
+ entity l;
+ // first check if the game has ended
+ LOG_DEBUG("--- updatelinks ---");
+ // mark generators as being shielded and networked
+ for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+ {
+ if (l.iscaptured)
+ LOG_DEBUG(etos(l), " (generator) belongs to team ", ftos(l.team));
+ else
+ LOG_DEBUG(etos(l), " (generator) is destroyed");
+ l.islinked = l.iscaptured;
+ l.isshielded = l.iscaptured;
+ l.sprite.SendFlags |= 16;
+ }
+ // mark points as shielded and not networked
+ for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+ {
+ l.islinked = false;
+ l.isshielded = true;
+ int i;
+ for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; }
+ LOG_DEBUG(etos(l), " (point) belongs to team ", ftos(l.team));
+ l.sprite.SendFlags |= 16;
+ }
+ // flow power outward from the generators through the network
+ bool stop = false;
+ while (!stop)
+ {
+ stop = true;
+ for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
+ {
+ // if both points are captured by the same team, and only one of
+ // them is powered, mark the other one as powered as well
+ if (l.enemy.iscaptured && l.goalentity.iscaptured)
+ if (l.enemy.islinked != l.goalentity.islinked)
+ if(SAME_TEAM(l.enemy, l.goalentity))
+ {
+ if (!l.goalentity.islinked)
+ {
+ stop = false;
+ l.goalentity.islinked = true;
+ LOG_DEBUG(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)");
+ }
+ else if (!l.enemy.islinked)
+ {
+ stop = false;
+ l.enemy.islinked = true;
+ LOG_DEBUG(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)");
+ }
+ }
+ }
+ }
+ // now that we know which points are powered we can mark their neighbors
+ // as unshielded if team differs
+ for(l = ons_worldlinklist; l; l = l.ons_worldlinknext)
+ {
+ if (l.goalentity.islinked)
+ {
+ if(DIFF_TEAM(l.goalentity, l.enemy))
+ {
+ LOG_DEBUG(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)");
+ l.enemy.isshielded = false;
+ }
+ if(l.goalentity.classname == "onslaught_generator")
+ l.enemy.isgenneighbor[l.goalentity.team] = true;
+ else
+ l.enemy.iscpneighbor[l.goalentity.team] = true;
+ }
+ if (l.enemy.islinked)
+ {
+ if(DIFF_TEAM(l.goalentity, l.enemy))
+ {
+ LOG_DEBUG(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)");
+ l.goalentity.isshielded = false;
+ }
+ if(l.enemy.classname == "onslaught_generator")
+ l.goalentity.isgenneighbor[l.enemy.team] = true;
+ else
+ l.goalentity.iscpneighbor[l.enemy.team] = true;
+ }
+ }
+ // now update the generators
+ for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+ {
+ if (l.isshielded)
+ {
+ LOG_DEBUG(etos(l), " (generator) is shielded");
+ l.takedamage = DAMAGE_NO;
+ l.bot_attack = false;
+ }
+ else
+ {
+ LOG_DEBUG(etos(l), " (generator) is not shielded");
+ l.takedamage = DAMAGE_AIM;
+ l.bot_attack = true;
+ }
+
+ ons_Generator_UpdateSprite(l);
+ }
+ // now update the takedamage and alpha variables on control point icons
+ for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+ {
+ if (l.isshielded)
+ {
+ LOG_DEBUG(etos(l), " (point) is shielded");
+ if (l.goalentity)
+ {
+ l.goalentity.takedamage = DAMAGE_NO;
+ l.goalentity.bot_attack = false;
+ }
+ }
+ else
+ {
+ LOG_DEBUG(etos(l), " (point) is not shielded");
+ if (l.goalentity)
+ {
+ l.goalentity.takedamage = DAMAGE_AIM;
+ l.goalentity.bot_attack = true;
+ }
+ }
+ ons_ControlPoint_UpdateSprite(l);
+ }
+ FOREACH_ENTITY_CLASS("ons_captureshield", true,
+ {
+ it.team = it.enemy.team;
+ it.colormap = it.enemy.colormap;
+ });
+}
+
+
+// ===================
+// Main Link Functions
+// ===================
+
+bool ons_Link_Send(entity this, entity to, int sendflags)
+{
+ WriteHeader(MSG_ENTITY, ENT_CLIENT_RADARLINK);
+ WriteByte(MSG_ENTITY, sendflags);
+ if(sendflags & 1)
+ {
+ WriteCoord(MSG_ENTITY, this.goalentity.origin_x);
+ WriteCoord(MSG_ENTITY, this.goalentity.origin_y);
+ WriteCoord(MSG_ENTITY, this.goalentity.origin_z);
+ }
+ if(sendflags & 2)
+ {
+ WriteCoord(MSG_ENTITY, this.enemy.origin_x);
+ WriteCoord(MSG_ENTITY, this.enemy.origin_y);
+ WriteCoord(MSG_ENTITY, this.enemy.origin_z);
+ }
+ if(sendflags & 4)
+ {
+ WriteByte(MSG_ENTITY, this.clientcolors); // which is goalentity's color + enemy's color * 16
+ }
+ return true;
+}
+
+void ons_Link_CheckUpdate(entity this)
+{
+ // TODO check if the two sides have moved (currently they won't move anyway)
+ float cc = 0, cc1 = 0, cc2 = 0;
+
+ if(this.goalentity.islinked || this.goalentity.iscaptured) { cc1 = (this.goalentity.team - 1) * 0x01; }
+ if(this.enemy.islinked || this.enemy.iscaptured) { cc2 = (this.enemy.team - 1) * 0x10; }
+
+ cc = cc1 + cc2;
+
+ if(cc != this.clientcolors)
+ {
+ this.clientcolors = cc;
+ this.SendFlags |= 4;
+ }
+
+ this.nextthink = time;
+}
+
+void ons_DelayedLinkSetup(entity this)
+{
+ this.goalentity = find(NULL, targetname, this.target);
+ this.enemy = find(NULL, targetname, this.target2);
+ if(!this.goalentity) { objerror(this, "can not find target\n"); }
+ if(!this.enemy) { objerror(this, "can not find target2\n"); }
+
+ LOG_DEBUG(etos(this.goalentity), " linked with ", etos(this.enemy));
+ this.SendFlags |= 3;
+ setthink(this, ons_Link_CheckUpdate);
+ this.nextthink = time;
+}
+
+
+// =============================
+// Main Control Point Functions
+// =============================
+
+int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber)
+{
+ if(cp.isgenneighbor[teamnumber]) { return 2; }
+ if(cp.iscpneighbor[teamnumber]) { return 1; }
+
+ return 0;
+}
+
+int ons_ControlPoint_Attackable(entity cp, int teamnumber)
+ // -2: SAME TEAM, attackable by enemy!
+ // -1: SAME TEAM!
+ // 0: off limits
+ // 1: attack it
+ // 2: touch it
+ // 3: attack it (HIGH PRIO)
+ // 4: touch it (HIGH PRIO)
+{
+ int a;
+
+ if(cp.isshielded)
+ {
+ return 0;
+ }
+ else if(cp.goalentity)
+ {
+ // if there's already an icon built, nothing happens
+ if(cp.team == teamnumber)
+ {
+ a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
+ if(a) // attackable by enemy?
+ return -2; // EMERGENCY!
+ return -1;
+ }
+ // we know it can be linked, so no need to check
+ // but...
+ a = ons_ControlPoint_CanBeLinked(cp, teamnumber);
+ if(a == 2) // near our generator?
+ return 3; // EMERGENCY!
+ return 1;
+ }
+ else
+ {
+ // free point
+ if(ons_ControlPoint_CanBeLinked(cp, teamnumber))
+ {
+ a = ons_ControlPoint_CanBeLinked(cp, teamnumber); // why was this here NUM_TEAM_1 + NUM_TEAM_2 - t
+ if(a == 2)
+ return 4; // GET THIS ONE NOW!
+ else
+ return 2; // TOUCH ME
+ }
+ }
+ return 0;
+}
+
+void ons_ControlPoint_Icon_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+ if(damage <= 0) { return; }
+
+ if (this.owner.isshielded)
+ {
+ // this is protected by a shield, so ignore the damage
+ if (time > this.pain_finished)
+ if (IS_PLAYER(attacker))
+ {
+ play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+ this.pain_finished = time + 1;
+ attacker.typehitsound += 1; // play both sounds (shield is way too quiet)
+ }
+
+ return;
+ }
+
+ if(IS_PLAYER(attacker))
+ if(time - ons_notification_time[this.team] > 10)
+ {
+ play2team(this.team, SND(ONS_CONTROLPOINT_UNDERATTACK));
+ ons_notification_time[this.team] = time;
+ }
+
+ this.health = this.health - damage;
+ if(this.owner.iscaptured)
+ WaypointSprite_UpdateHealth(this.owner.sprite, this.health);
+ else
+ WaypointSprite_UpdateBuildFinished(this.owner.sprite, time + (this.max_health - this.health) / (this.count / ONS_CP_THINKRATE));
+ this.pain_finished = time + 1;
+ // particles on every hit
+ pointparticles(EFFECT_SPARKS, hitloc, force*-1, 1);
+ //sound on every hit
+ if (random() < 0.5)
+ sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE+0.3, ATTEN_NORM);
+ else
+ sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE+0.3, ATTEN_NORM);
+
+ if (this.health < 0)
+ {
+ sound(this, CH_TRIGGER, SND_GRENADE_IMPACT, VOL_BASE, ATTEN_NORM);
+ pointparticles(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_CPDESTROYED), this.owner.message, attacker.netname);
+
+ PlayerScore_Add(attacker, SP_ONS_TAKES, 1);
+ PlayerScore_Add(attacker, SP_SCORE, 10);
+
+ this.owner.goalentity = NULL;
+ this.owner.islinked = false;
+ this.owner.iscaptured = false;
+ this.owner.team = 0;
+ this.owner.colormap = 1024;
+
+ WaypointSprite_UpdateMaxHealth(this.owner.sprite, 0);
+
+ onslaught_updatelinks();
+
+ // Use targets now (somebody make sure this is in the right place..)
+ SUB_UseTargets(this.owner, this, NULL);
+
+ this.owner.waslinked = this.owner.islinked;
+ if(this.owner.model != "models/onslaught/controlpoint_pad.md3")
+ setmodel_fixsize(this.owner, MDL_ONS_CP_PAD1);
+ //setsize(this, '-32 -32 0', '32 32 8');
+
+ delete(this);
+ }
+
+ this.SendFlags |= CPSF_STATUS;
+}
+
+void ons_ControlPoint_Icon_Think(entity this)
+{
+ this.nextthink = time + ONS_CP_THINKRATE;
+
+ if(autocvar_g_onslaught_cp_proxydecap)
+ {
+ int _enemy_count = 0;
+ int _friendly_count = 0;
+
+ FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
+ if(vdist(it.origin - this.origin, <, autocvar_g_onslaught_cp_proxydecap_distance))
+ {
+ if(SAME_TEAM(it, this))
+ ++_friendly_count;
+ else
+ ++_enemy_count;
+ }
+ });
+
+ _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
+ _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE);
+
+ this.health = bound(0, this.health + (_friendly_count - _enemy_count), this.max_health);
+ this.SendFlags |= CPSF_STATUS;
+ if(this.health <= 0)
+ {
+ ons_ControlPoint_Icon_Damage(this, this, this, 1, 0, this.origin, '0 0 0');
+ return;
+ }
+ }
+
+ if (time > this.pain_finished + 5)
+ {
+ if(this.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);
+ }
+ }
+
+ if(this.owner.islinked != this.owner.waslinked)
+ {
+ // unteam the spawnpoint if needed
+ int t = this.owner.team;
+ if(!this.owner.islinked)
+ this.owner.team = 0;
+
+ SUB_UseTargets(this.owner, this, NULL);
+
+ this.owner.team = t;
+
+ this.owner.waslinked = this.owner.islinked;
+ }
+
+ // damaged fx
+ if(random() < 0.6 - this.health / this.max_health)
+ {
+ Send_Effect(EFFECT_ELECTRIC_SPARKS, this.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1);
+
+ if(random() > 0.8)
+ sound(this, CH_PAIN, SND_ONS_SPARK1, VOL_BASE, ATTEN_NORM);
+ else if (random() > 0.5)
+ sound(this, CH_PAIN, SND_ONS_SPARK2, VOL_BASE, ATTEN_NORM);
+ }
+}
+
+void ons_ControlPoint_Icon_BuildThink(entity this)
+{
+ int a;
+
+ this.nextthink = time + ONS_CP_THINKRATE;
+
+ // only do this if there is power
+ a = ons_ControlPoint_CanBeLinked(this.owner, this.owner.team);
+ if(!a)
+ return;
+
+ this.health = this.health + this.count;
+
+ this.SendFlags |= CPSF_STATUS;
+
+ if (this.health >= this.max_health)
+ {
+ this.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);
+ this.owner.iscaptured = true;
+ this.solid = SOLID_BBOX;
+
+ 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);
+
+ if(IS_PLAYER(this.owner.ons_toucher))
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, this.owner.ons_toucher.netname, this.owner.message);
+ Send_Notification(NOTIF_ALL_EXCEPT, this.owner.ons_toucher, MSG_CENTER, APP_TEAM_NUM(this.owner.ons_toucher.team, CENTER_ONS_CAPTURE), this.owner.message);
+ Send_Notification(NOTIF_ONE, this.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, this.owner.message);
+ PlayerScore_Add(this.owner.ons_toucher, SP_ONS_CAPS, 1);
+ PlayerTeamScore_AddScore(this.owner.ons_toucher, 10);
+ }
+
+ this.owner.ons_toucher = NULL;
+
+ onslaught_updatelinks();
+
+ // Use targets now (somebody make sure this is in the right place..)
+ SUB_UseTargets(this.owner, this, NULL);
+
+ this.SendFlags |= CPSF_SETUP;
+ }
+ 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)
+ Send_Effect(EFFECT_RAGE, this.origin + 10 * randomvec(), '0 0 -1', 1);
+}
+
+void onslaught_controlpoint_icon_link(entity e, void(entity this) spawnproc);
+
+void ons_ControlPoint_Icon_Spawn(entity cp, entity player)
+{
+ entity e = new(onslaught_controlpoint_icon);
+
+ setsize(e, CPICON_MIN, CPICON_MAX);
+ setorigin(e, cp.origin + CPICON_OFFSET);
+
+ e.owner = cp;
+ e.max_health = autocvar_g_onslaught_cp_health;
+ e.health = autocvar_g_onslaught_cp_buildhealth;
+ e.solid = SOLID_NOT;
+ e.takedamage = DAMAGE_AIM;
+ e.bot_attack = true;
+ e.event_damage = ons_ControlPoint_Icon_Damage;
+ e.team = player.team;
+ e.colormap = 1024 + (e.team - 1) * 17;
+ e.count = (e.max_health - e.health) * ONS_CP_THINKRATE / autocvar_g_onslaught_cp_buildtime; // how long it takes to build
+
+ sound(e, CH_TRIGGER, SND_ONS_CONTROLPOINT_BUILD, VOL_BASE, ATTEN_NORM);
+
+ cp.goalentity = e;
+ cp.team = e.team;
+ cp.colormap = e.colormap;
+
+ Send_Effect(EFFECT_FLAG_TOUCH(player.team), e.origin, '0 0 0', 1);
+
+ WaypointSprite_UpdateBuildFinished(cp.sprite, time + (e.max_health - e.health) / (e.count / ONS_CP_THINKRATE));
+ WaypointSprite_UpdateRule(cp.sprite,cp.team,SPRITERULE_TEAMPLAY);
+ cp.sprite.SendFlags |= 16;
+
+ onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink);
+}
+
+entity ons_ControlPoint_Waypoint(entity e)
+{
+ if(e.team)
+ {
+ int a = ons_ControlPoint_Attackable(e, e.team);
+
+ if(a == -2) { return WP_OnsCPDefend; } // defend now
+ if(a == -1 || a == 1 || a == 2) { return WP_OnsCP; } // touch
+ if(a == 3 || a == 4) { return WP_OnsCPAttack; } // attack
+ }
+ else
+ return WP_OnsCP;
+
+ return WP_Null;
+}
+
+void ons_ControlPoint_UpdateSprite(entity e)
+{
+ entity s1 = ons_ControlPoint_Waypoint(e);
+ WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
+
+ bool sh;
+ sh = !(ons_ControlPoint_CanBeLinked(e, NUM_TEAM_1) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_2) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_3) || ons_ControlPoint_CanBeLinked(e, NUM_TEAM_4));
+
+ if(e.lastteam != e.team + 2 || e.lastshielded != sh || e.iscaptured != e.lastcaptured)
+ {
+ if(e.iscaptured) // don't mess up build bars!
+ {
+ if(sh)
+ {
+ WaypointSprite_UpdateMaxHealth(e.sprite, 0);
+ }
+ else
+ {
+ WaypointSprite_UpdateMaxHealth(e.sprite, e.goalentity.max_health);
+ WaypointSprite_UpdateHealth(e.sprite, e.goalentity.health);
+ }
+ }
+ if(e.lastshielded)
+ {
+ if(e.team)
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, false));
+ else
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.5 0.5 0.5');
+ }
+ else
+ {
+ if(e.team)
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false));
+ else
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75');
+ }
+ WaypointSprite_Ping(e.sprite);
+
+ e.lastteam = e.team + 2;
+ e.lastshielded = sh;
+ e.lastcaptured = e.iscaptured;
+ }
+}
+
+void ons_ControlPoint_Touch(entity this, entity toucher)
+{
+ int attackable;
+
+ if(IS_VEHICLE(toucher) && toucher.owner)
+ if(autocvar_g_onslaught_allow_vehicle_touch)
+ toucher = toucher.owner;
+ else
+ return;
+
+ if(!IS_PLAYER(toucher)) { return; }
+ if(STAT(FROZEN, toucher)) { return; }
+ if(IS_DEAD(toucher)) { return; }
+
+ if ( SAME_TEAM(this,toucher) )
+ if ( this.iscaptured )
+ {
+ if(time <= toucher.teleport_antispam)
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time));
+ else
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
+ }
+
+ attackable = ons_ControlPoint_Attackable(this, toucher.team);
+ if(attackable != 2 && attackable != 4)
+ return;
+ // we've verified that this player has a legitimate claim to this point,
+ // so start building the captured point icon (which only captures this
+ // point if it successfully builds without being destroyed first)
+ ons_ControlPoint_Icon_Spawn(this, toucher);
+
+ this.ons_toucher = toucher;
+
+ onslaught_updatelinks();
+}
+
+void ons_ControlPoint_Think(entity this)
+{
+ this.nextthink = time + ONS_CP_THINKRATE;
+ CSQCMODEL_AUTOUPDATE(this);
+}
+
+void ons_ControlPoint_Reset(entity this)
+{
+ if(this.goalentity)
+ delete(this.goalentity);
+
+ this.goalentity = NULL;
+ this.team = 0;
+ this.colormap = 1024;
+ this.iscaptured = false;
+ this.islinked = false;
+ this.isshielded = true;
+ setthink(this, ons_ControlPoint_Think);
+ this.ons_toucher = NULL;
+ this.nextthink = time + ONS_CP_THINKRATE;
+ setmodel_fixsize(this, MDL_ONS_CP_PAD1);
+
+ WaypointSprite_UpdateMaxHealth(this.sprite, 0);
+ WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
+
+ onslaught_updatelinks();
+
+ SUB_UseTargets(this, this, NULL); // to reset the structures, playerspawns etc.
+
+ CSQCMODEL_AUTOUPDATE(this);
+}
+
+void ons_DelayedControlPoint_Setup(entity this)
+{
+ onslaught_updatelinks();
+
+ // captureshield setup
+ ons_CaptureShield_Spawn(this, false);
+
+ CSQCMODEL_AUTOINIT(this);
+}
+
+void ons_ControlPoint_Setup(entity cp)
+{
+ // main setup
+ cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist
+ ons_worldcplist = cp;
+
+ cp.netname = "Control point";
+ cp.team = 0;
+ cp.solid = SOLID_BBOX;
+ set_movetype(cp, MOVETYPE_NONE);
+ settouch(cp, ons_ControlPoint_Touch);
+ setthink(cp, ons_ControlPoint_Think);
+ cp.nextthink = time + ONS_CP_THINKRATE;
+ cp.reset = ons_ControlPoint_Reset;
+ cp.colormap = 1024;
+ cp.iscaptured = false;
+ cp.islinked = false;
+ cp.isshielded = true;
+
+ if(cp.message == "") { cp.message = "a"; }
+
+ // appearence
+ setmodel_fixsize(cp, MDL_ONS_CP_PAD1);
+
+ // control point placement
+ if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location
+ {
+ cp.noalign = true;
+ set_movetype(cp, MOVETYPE_NONE);
+ }
+ else // drop to floor, automatically find a platform and set that as spawn origin
+ {
+ setorigin(cp, cp.origin + '0 0 20');
+ cp.noalign = false;
+ droptofloor(cp);
+ set_movetype(cp, MOVETYPE_TOSS);
+ }
+
+ // waypointsprites
+ WaypointSprite_SpawnFixed(WP_Null, cp.origin + CPGEN_WAYPOINT_OFFSET, cp, sprite, RADARICON_NONE);
+ WaypointSprite_UpdateRule(cp.sprite, cp.team, SPRITERULE_TEAMPLAY);
+
+ InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION);
+}
+
+
+// =========================
+// Main Generator Functions
+// =========================
+
+entity ons_Generator_Waypoint(entity e)
+{
+ if (e.isshielded)
+ return WP_OnsGenShielded;
+ return WP_OnsGen;
+}
+
+void ons_Generator_UpdateSprite(entity e)
+{
+ entity s1 = ons_Generator_Waypoint(e);
+ WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1);
+
+ if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded)
+ {
+ e.lastteam = e.team + 2;
+ e.lastshielded = e.isshielded;
+ if(e.lastshielded)
+ {
+ if(e.team)
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, false));
+ else
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.5 0.5 0.5');
+ }
+ else
+ {
+ if(e.team)
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false));
+ else
+ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75');
+ }
+ WaypointSprite_Ping(e.sprite);
+ }
+}
+
+void ons_GeneratorDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+ if(damage <= 0) { return; }
+ if(warmup_stage || gameover) { return; }
+ if(!round_handler_IsRoundStarted()) { return; }
+
+ if (attacker != this)
+ {
+ if (this.isshielded)
+ {
+ // this is protected by a shield, so ignore the damage
+ if (time > this.pain_finished)
+ if (IS_PLAYER(attacker))
+ {
+ play2(attacker, SND(ONS_DAMAGEBLOCKEDBYSHIELD));
+ attacker.typehitsound += 1;
+ this.pain_finished = time + 1;
+ }
+ return;
+ }
+ if (time > this.pain_finished)
+ {
+ this.pain_finished = time + 10;
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && SAME_TEAM(it, this), Send_Notification(NOTIF_ONE, it, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK));
+ play2team(this.team, SND(ONS_GENERATOR_UNDERATTACK));
+ }
+ }
+ this.health = this.health - damage;
+ WaypointSprite_UpdateHealth(this.sprite, this.health);
+ // choose an animation frame based on health
+ this.frame = 10 * bound(0, (1 - this.health / this.max_health), 1);
+ // see if the generator is still functional, or dying
+ if (this.health > 0)
+ {
+ this.lasthealth = this.health;
+ }
+ else
+ {
+ if (attacker == this)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME));
+ else
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_ONSLAUGHT_GENDESTROYED));
+ PlayerScore_Add(attacker, SP_SCORE, 100);
+ }
+ this.iscaptured = false;
+ this.islinked = false;
+ this.isshielded = false;
+ this.takedamage = DAMAGE_NO; // can't be hurt anymore
+ this.event_damage = func_null; // won't do anything if hurt
+ this.count = 0; // reset counter
+ setthink(this, func_null);
+ this.nextthink = 0;
+ //this.think(); // do the first explosion now
+
+ WaypointSprite_UpdateMaxHealth(this.sprite, 0);
+ WaypointSprite_Ping(this.sprite);
+ //WaypointSprite_Kill(this.sprite); // can't do this yet, code too poor
+
+ onslaught_updatelinks();
+ }
+
+ // Throw some flaming gibs on damage, more damage = more chance for gib
+ if(random() < damage/220)
+ {
+ sound(this, CH_TRIGGER, SND_ROCKET_IMPACT, VOL_BASE, ATTEN_NORM);
+ }
+ else
+ {
+ // particles on every hit
+ Send_Effect(EFFECT_SPARKS, hitloc, force * -1, 1);
+
+ //sound on every hit
+ if (random() < 0.5)
+ sound(this, CH_TRIGGER, SND_ONS_HIT1, VOL_BASE, ATTEN_NORM);
+ else
+ sound(this, CH_TRIGGER, SND_ONS_HIT2, VOL_BASE, ATTEN_NORM);
+ }
+
+ this.SendFlags |= GSF_STATUS;
+}
+
+void ons_GeneratorThink(entity this)
+{
+ this.nextthink = time + GEN_THINKRATE;
+ if (!gameover)
+ {
+ if(!this.isshielded && this.wait < time)
+ {
+ 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));
+ });
+ }
+ }
+}
+
+void ons_GeneratorReset(entity this)
+{
+ this.team = this.team_saved;
+ this.lasthealth = this.max_health = this.health = autocvar_g_onslaught_gen_health;
+ this.takedamage = DAMAGE_AIM;
+ this.bot_attack = true;
+ this.iscaptured = true;
+ this.islinked = true;
+ this.isshielded = true;
+ this.event_damage = ons_GeneratorDamage;
+ setthink(this, ons_GeneratorThink);
+ this.nextthink = time + GEN_THINKRATE;
+
+ Net_LinkEntity(this, false, 0, generator_send);
+
+ this.SendFlags = GSF_SETUP; // just incase
+ this.SendFlags |= GSF_STATUS;
+
+ WaypointSprite_UpdateMaxHealth(this.sprite, this.max_health);
+ WaypointSprite_UpdateHealth(this.sprite, this.health);
+ WaypointSprite_UpdateRule(this.sprite,this.team,SPRITERULE_TEAMPLAY);
+
+ onslaught_updatelinks();
+}
+
+void ons_DelayedGeneratorSetup(entity this)
+{
+ // bot waypoints
+ waypoint_spawnforitem_force(this, this.origin);
+ this.nearestwaypointtimeout = 0; // activate waypointing again
+ this.bot_basewaypoint = this.nearestwaypoint;
+
+ // captureshield setup
+ ons_CaptureShield_Spawn(this, true);
+
+ onslaught_updatelinks();
+
+ Net_LinkEntity(this, false, 0, generator_send);
+}
+
+
+void onslaught_generator_touch(entity this, entity toucher)
+{
+ if ( IS_PLAYER(toucher) )
+ if ( SAME_TEAM(this,toucher) )
+ if ( this.iscaptured )
+ {
+ Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT);
+ }
+}
+
+void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc
+{
+ // declarations
+ int teamnumber = gen.team;
+
+ // main setup
+ gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist
+ ons_worldgeneratorlist = gen;
+
+ gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber));
+ gen.classname = "onslaught_generator";
+ gen.solid = SOLID_BBOX;
+ gen.team_saved = teamnumber;
+ set_movetype(gen, MOVETYPE_NONE);
+ gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health;
+ gen.takedamage = DAMAGE_AIM;
+ gen.bot_attack = true;
+ gen.event_damage = ons_GeneratorDamage;
+ gen.reset = ons_GeneratorReset;
+ setthink(gen, ons_GeneratorThink);
+ gen.nextthink = time + GEN_THINKRATE;
+ gen.iscaptured = true;
+ gen.islinked = true;
+ gen.isshielded = true;
+ settouch(gen, onslaught_generator_touch);
+
+ // appearence
+ // model handled by CSQC
+ setsize(gen, GENERATOR_MIN, GENERATOR_MAX);
+ setorigin(gen, (gen.origin + CPGEN_SPAWN_OFFSET));
+ gen.colormap = 1024 + (teamnumber - 1) * 17;
+
+ // generator placement
+ droptofloor(gen);
+
+ // waypointsprites
+ 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);
+
+ InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ===============
+// Round Handler
+// ===============
+
+int total_generators;
+void Onslaught_count_generators()
+{
+ entity e;
+ total_generators = redowned = blueowned = yellowowned = pinkowned = 0;
+ for(e = ons_worldgeneratorlist; e; e = e.ons_worldgeneratornext)
+ {
+ ++total_generators;
+ redowned += (e.team == NUM_TEAM_1 && e.health > 0);
+ blueowned += (e.team == NUM_TEAM_2 && e.health > 0);
+ yellowowned += (e.team == NUM_TEAM_3 && e.health > 0);
+ pinkowned += (e.team == NUM_TEAM_4 && e.health > 0);
+ }
+}
+
+int Onslaught_GetWinnerTeam()
+{
+ int winner_team = 0;
+ if(redowned > 0)
+ winner_team = NUM_TEAM_1;
+ if(blueowned > 0)
+ {
+ if(winner_team) return 0;
+ winner_team = NUM_TEAM_2;
+ }
+ if(yellowowned > 0)
+ {
+ if(winner_team) return 0;
+ winner_team = NUM_TEAM_3;
+ }
+ if(pinkowned > 0)
+ {
+ if(winner_team) return 0;
+ winner_team = NUM_TEAM_4;
+ }
+ if(winner_team)
+ return winner_team;
+ return -1; // no generators left?
+}
+
+void nades_Clear(entity e);
+
+#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0))
+#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1)
+bool Onslaught_CheckWinner()
+{
+ if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0))
+ {
+ ons_stalemate = true;
+
+ if (!wpforenemy_announced)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT);
+ sound(NULL, CH_INFO, SND_ONS_GENERATOR_DECAY, VOL_BASE, ATTEN_NONE);
+
+ wpforenemy_announced = true;
+ }
+
+ entity tmp_entity; // temporary entity
+ float d;
+ for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay)
+ {
+ // tmp_entity.max_health / 300 gives 5 minutes of overtime.
+ // control points reduce the overtime duration.
+ d = 1;
+ entity e;
+ for(e = ons_worldcplist; e; e = e.ons_worldcpnext)
+ {
+ if(DIFF_TEAM(e, tmp_entity))
+ if(e.islinked)
+ d = d + 1;
+ }
+
+ if(autocvar_g_campaign && autocvar__campaign_testrun)
+ d = d * tmp_entity.max_health;
+ else
+ d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath);
+
+ Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER.m_id, tmp_entity.origin, '0 0 0');
+
+ tmp_entity.sprite.SendFlags |= 16;
+
+ tmp_entity.ons_overtime_damagedelay = time + 1;
+ }
+ }
+ else { wpforenemy_announced = false; ons_stalemate = false; }
+
+ Onslaught_count_generators();
+
+ if(ONS_OWNED_GENERATORS_OK())
+ return 0;
+
+ int winner_team = Onslaught_GetWinnerTeam();
+
+ if(winner_team > 0)
+ {
+ Send_Notification(NOTIF_ALL, 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_ONS_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);
+ }
+
+ ons_stalemate = false;
+
+ play2all(SND(CTF_CAPTURE(winner_team)));
+
+ round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
+
+ FOREACH_CLIENT(IS_PLAYER(it), {
- it.ons_roundlost = false;
++ STAT(ROUNDLOST, it) = true;
+ it.player_blocked = true;
+
+ nades_Clear(it);
+ });
+
+ return 1;
+}
+
+bool Onslaught_CheckPlayers()
+{
+ return 1;
+}
+
+void Onslaught_RoundStart()
+{
+ entity tmp_entity;
+ FOREACH_CLIENT(IS_PLAYER(it), it.player_blocked = false);
+
+ for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
+ tmp_entity.sprite.SendFlags |= 16;
+
+ for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
+ tmp_entity.sprite.SendFlags |= 16;
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_goalrating_ons_offenseitems(entity this, float ratingscale, vector org, float sradius)
+{
+ bool needarmor = false, needweapons = false;
+
+ // Needs armor/health?
+ if(this.health<100)
+ needarmor = true;
+
+ // Needs weapons?
+ int c = 0;
+ FOREACH(Weapons, it != WEP_Null, {
+ if(this.weapons & (it.m_wepset))
+ if(++c >= 4)
+ break;
+ });
+
+ if(c<4)
+ needweapons = true;
+
+ if(!needweapons && !needarmor)
+ return;
+
+ LOG_DEBUG(this.netname, " needs weapons ", ftos(needweapons));
+ LOG_DEBUG(this.netname, " needs armor ", ftos(needarmor));
+
+ // See what is around
+ FOREACH_ENTITY_FLOAT(bot_pickup, true,
+ {
+ // gather health and armor only
+ if (it.solid)
+ if ( ((it.health || it.armorvalue) && needarmor) || (it.weapons && needweapons ) )
+ if (vdist(it.origin - org, <, sradius))
+ {
+ int t = it.bot_pickupevalfunc(this, it);
+ if (t > 0)
+ navigation_routerating(this, it, t * ratingscale, 500);
+ }
+ });
+}
+
+void havocbot_role_ons_setrole(entity this, int role)
+{
+ LOG_DEBUG(this.netname," switched to ");
+ switch(role)
+ {
+ case HAVOCBOT_ONS_ROLE_DEFENSE:
+ LOG_DEBUG("defense");
+ this.havocbot_role = havocbot_role_ons_defense;
+ this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
+ this.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_ONS_ROLE_ASSISTANT:
+ LOG_DEBUG("assistant");
+ this.havocbot_role = havocbot_role_ons_assistant;
+ this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
+ this.havocbot_role_timeout = 0;
+ break;
+ case HAVOCBOT_ONS_ROLE_OFFENSE:
+ LOG_DEBUG("offense");
+ this.havocbot_role = havocbot_role_ons_offense;
+ this.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
+ this.havocbot_role_timeout = 0;
+ break;
+ }
+ LOG_DEBUG("");
+}
+
+void havocbot_goalrating_ons_controlpoints_attack(entity this, float ratingscale)
+{
+ entity cp, cp1, cp2, best, wp;
+ float radius, bestvalue;
+ int c;
+ bool found;
+
+ // Filter control points
+ for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext)
+ {
+ cp2.wpcost = c = 0;
+ cp2.wpconsidered = false;
+
+ if(cp2.isshielded)
+ continue;
+
+ // Ignore owned controlpoints
+ if(!(cp2.isgenneighbor[this.team] || cp2.iscpneighbor[this.team]))
+ continue;
+
+ // Count team mates interested in this control point
+ // (easier and cleaner than keeping counters per cp and teams)
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ if(SAME_TEAM(it, this))
+ if(it.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
+ if(it.havocbot_ons_target == cp2)
+ ++c;
+ });
+
+ // NOTE: probably decrease the cost of attackable control points
+ cp2.wpcost = c;
+ cp2.wpconsidered = true;
+ }
+
+ // We'll consider only the best case
+ bestvalue = 99999999999;
+ cp = NULL;
+ for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext)
+ {
+ if (!cp1.wpconsidered)
+ continue;
+
+ if(cp1.wpcost<bestvalue)
+ {
+ bestvalue = cp1.wpcost;
+ cp = cp1;
+ this.havocbot_ons_target = cp1;
+ }
+ }
+
+ if (!cp)
+ return;
+
+ LOG_DEBUG(this.netname, " chose cp ranked ", ftos(bestvalue));
+
+ if(cp.goalentity)
+ {
+ // Should be attacked
+ // Rate waypoints near it
+ found = false;
+ best = NULL;
+ bestvalue = 99999999999;
+ for(radius=0; radius<1000 && !found; radius+=500)
+ {
+ for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
+ {
+ if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin,cp))
+ {
+ found = true;
+ if(wp.cnt<bestvalue)
+ {
+ best = wp;
+ bestvalue = wp.cnt;
+ }
+ }
+ }
+ }
+
+ if(best)
+ {
+ navigation_routerating(this, best, ratingscale, 10000);
+ best.cnt += 1;
+
+ this.havocbot_attack_time = 0;
+ if(checkpvs(this.view_ofs,cp))
+ if(checkpvs(this.view_ofs,best))
+ this.havocbot_attack_time = time + 2;
+ }
+ else
+ {
+ navigation_routerating(this, cp, ratingscale, 10000);
+ }
+ LOG_DEBUG(this.netname, " found an attackable controlpoint at ", vtos(cp.origin));
+ }
+ else
+ {
+ // Should be touched
+ LOG_DEBUG(this.netname, " found a touchable controlpoint at ", vtos(cp.origin));
+ found = false;
+
+ // Look for auto generated waypoint
+ if (!bot_waypoints_for_items)
+ for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
+ {
+ if(wp.classname=="waypoint")
+ {
+ navigation_routerating(this, wp, ratingscale, 10000);
+ found = true;
+ }
+ }
+
+ // Nothing found, rate the controlpoint itself
+ if (!found)
+ navigation_routerating(this, cp, ratingscale, 10000);
+ }
+}
+
+bool havocbot_goalrating_ons_generator_attack(entity this, float ratingscale)
+{
+ entity g, wp, bestwp;
+ bool found;
+ int best;
+
+ for(g = ons_worldgeneratorlist; g; g = g.ons_worldgeneratornext)
+ {
+ if(SAME_TEAM(g, this) || g.isshielded)
+ continue;
+
+ // Should be attacked
+ // Rate waypoints near it
+ found = false;
+ bestwp = NULL;
+ best = 99999999999;
+
+ for(wp=findradius(g.origin,400); wp; wp=wp.chain)
+ {
+ if(wp.classname=="waypoint")
+ if(checkpvs(wp.origin,g))
+ {
+ found = true;
+ if(wp.cnt<best)
+ {
+ bestwp = wp;
+ best = wp.cnt;
+ }
+ }
+ }
+
+ if(bestwp)
+ {
+ LOG_DEBUG("waypoints found around generator");
+ navigation_routerating(this, bestwp, ratingscale, 10000);
+ bestwp.cnt += 1;
+
+ this.havocbot_attack_time = 0;
+ if(checkpvs(this.view_ofs,g))
+ if(checkpvs(this.view_ofs,bestwp))
+ this.havocbot_attack_time = time + 5;
+
+ return true;
+ }
+ else
+ {
+ LOG_DEBUG("generator found without waypoints around");
+ // if there aren't waypoints near the generator go straight to it
+ navigation_routerating(this, g, ratingscale, 10000);
+ this.havocbot_attack_time = 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+void havocbot_role_ons_offense(entity this)
+{
+ if(IS_DEAD(this))
+ {
+ this.havocbot_attack_time = 0;
+ havocbot_ons_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_ons_reset_role(this);
+ return;
+ }
+
+ if(this.havocbot_attack_time>time)
+ return;
+
+ if (this.bot_strategytime < time)
+ {
+ navigation_goalrating_start(this);
+ havocbot_goalrating_enemyplayers(this, 20000, this.origin, 650);
+ if(!havocbot_goalrating_ons_generator_attack(this, 20000))
+ havocbot_goalrating_ons_controlpoints_attack(this, 20000);
+ havocbot_goalrating_ons_offenseitems(this, 10000, this.origin, 10000);
+ navigation_goalrating_end(this);
+
+ this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+ }
+}
+
+void havocbot_role_ons_assistant(entity this)
+{
+ havocbot_ons_reset_role(this);
+}
+
+void havocbot_role_ons_defense(entity this)
+{
+ havocbot_ons_reset_role(this);
+}
+
+void havocbot_ons_reset_role(entity this)
+{
+ if(IS_DEAD(this))
+ return;
+
+ this.havocbot_ons_target = NULL;
+
+ // TODO: Defend control points or generator if necessary
+
+ havocbot_role_ons_setrole(this, HAVOCBOT_ONS_ROLE_OFFENSE);
+}
+
+
+/*
+ * Find control point or generator owned by the same team self which is nearest to pos
+ * if max_dist is positive, only control points within this range will be considered
+ */
+entity ons_Nearest_ControlPoint(entity this, vector pos, float max_dist)
+{
+ entity closest_target = NULL;
+ FOREACH_ENTITY_CLASS("onslaught_controlpoint", true,
+ {
+ if(SAME_TEAM(it, this))
+ if(it.iscaptured)
+ if(max_dist <= 0 || vdist(it.origin - pos, <=, max_dist))
+ if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
+ closest_target = it;
+ });
+ FOREACH_ENTITY_CLASS("onslaught_generator", true,
+ {
+ if(SAME_TEAM(it, this))
+ if(max_dist <= 0 || vdist(it.origin - pos, <, max_dist))
+ if(vlen2(it.origin - pos) <= vlen2(closest_target.origin - pos) || closest_target == NULL)
+ closest_target = it;
+ });
+
+ return closest_target;
+}
+
+/*
+ * Find control point or generator owned by the same team self which is nearest to pos
+ * if max_dist is positive, only control points within this range will be considered
+ * This function only check distances on the XY plane, disregarding Z
+ */
+entity ons_Nearest_ControlPoint_2D(entity this, vector pos, float max_dist)
+{
+ entity closest_target = NULL;
+ vector delta;
+ float smallest_distance = 0, distance;
+
+ FOREACH_ENTITY_CLASS("onslaught_controlpoint", true,
+ {
+ delta = it.origin - pos;
+ delta_z = 0;
+ distance = vlen(delta);
+
+ if(SAME_TEAM(it, this))
+ if(it.iscaptured)
+ if(max_dist <= 0 || distance <= max_dist)
+ if(closest_target == NULL || distance <= smallest_distance )
+ {
+ closest_target = it;
+ smallest_distance = distance;
+ }
+ });
+ FOREACH_ENTITY_CLASS("onslaught_generator", true,
+ {
+ delta = it.origin - pos;
+ delta_z = 0;
+ distance = vlen(delta);
+
+ if(SAME_TEAM(it, this))
+ if(max_dist <= 0 || distance <= max_dist)
+ if(closest_target == NULL || distance <= smallest_distance )
+ {
+ closest_target = it;
+ smallest_distance = distance;
+ }
+ });
+
+ return closest_target;
+}
+/**
+ * find the number of control points and generators in the same team as this
+ */
+int ons_Count_SelfControlPoints(entity this)
+{
+ int n = 0;
+ FOREACH_ENTITY_CLASS("onslaught_controlpoint", true,
+ {
+ if(SAME_TEAM(it, this))
+ if(it.iscaptured)
+ n++;
+ });
+ FOREACH_ENTITY_CLASS("onslaught_generator", true,
+ {
+ if(SAME_TEAM(it, this))
+ n++;
+ });
+ return n;
+}
+
+/**
+ * Teleport player to a random position near tele_target
+ * if tele_effects is true, teleport sound+particles are created
+ * return false on failure
+ */
+bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects)
+{
+ if ( !tele_target )
+ return false;
+
+ int i;
+ vector loc;
+ float theta;
+ // narrow the range for each iteration to increase chances that a spawnpoint
+ // can be found even if there's little room around the control point
+ float iteration_scale = 1;
+ for(i = 0; i < 16; ++i)
+ {
+ iteration_scale -= i / 16;
+ theta = random() * 2 * M_PI;
+ loc_y = sin(theta);
+ loc_x = cos(theta);
+ loc_z = 0;
+ loc *= random() * range * iteration_scale;
+
+ loc += tele_target.origin + '0 0 128' * iteration_scale;
+
+ tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player);
+ if(trace_fraction == 1.0 && !trace_startsolid)
+ {
+ traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the NULL
+ if(trace_fraction == 1.0 && !trace_startsolid)
+ {
+ if ( tele_effects )
+ {
+ Send_Effect(EFFECT_TELEPORT, player.origin, '0 0 0', 1);
+ sound (player, CH_TRIGGER, SND_TELEPORT, VOL_BASE, ATTEN_NORM);
+ }
+ setorigin(player, loc);
+ player.angles = '0 1 0' * ( theta * RAD2DEG + 180 );
+ makevectors(player.angles);
+ player.fixangle = true;
+ player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait;
+
+ if ( tele_effects )
+ Send_Effect(EFFECT_TELEPORT, player.origin + v_forward * 32, '0 0 0', 1);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ons, reset_map_global)
+{
+ FOREACH_CLIENT(IS_PLAYER(it), {
- client.ons_roundlost = spectatee.ons_roundlost; // make spectators see it too
++ STAT(ROUNDLOST, it) = false;
+ it.ons_deathloc = '0 0 0';
+ PutClientInServer(it);
+ });
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, ClientDisconnect)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.ons_deathloc = '0 0 0';
+}
+
+MUTATOR_HOOKFUNCTION(ons, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.ons_deathloc = '0 0 0';
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(!round_handler_IsRoundStarted())
+ {
+ player.player_blocked = true;
+ return false;
+ }
+
+ entity l;
+ for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+ {
+ l.sprite.SendFlags |= 16;
+ }
+ for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+ {
+ l.sprite.SendFlags |= 16;
+ }
+
+ if(ons_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); }
+
+ if ( autocvar_g_onslaught_spawn_choose )
+ if ( player.ons_spawn_by )
+ if ( ons_Teleport(player,player.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) )
+ {
+ player.ons_spawn_by = NULL;
+ return false;
+ }
+
+ if(autocvar_g_onslaught_spawn_at_controlpoints)
+ if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance)
+ {
+ float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random;
+ entity tmp_entity, closest_target = NULL;
+ vector spawn_loc = player.ons_deathloc;
+
+ // new joining player or round reset, don't bother checking
+ if(spawn_loc == '0 0 0') { return false; }
+
+ if(random_target) { RandomSelection_Init(); }
+
+ for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext)
+ {
+ if(SAME_TEAM(tmp_entity, player))
+ if(random_target)
+ RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
+ else if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL)
+ closest_target = tmp_entity;
+ }
+
+ if(random_target) { closest_target = RandomSelection_chosen_ent; }
+
+ if(closest_target)
+ {
+ float i;
+ vector loc;
+ float iteration_scale = 1;
+ for(i = 0; i < 10; ++i)
+ {
+ iteration_scale -= i / 10;
+ loc = closest_target.origin + '0 0 96' * iteration_scale;
+ loc += ('0 1 0' * random()) * 128 * iteration_scale;
+ tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player);
+ if(trace_fraction == 1.0 && !trace_startsolid)
+ {
+ traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL
+ if(trace_fraction == 1.0 && !trace_startsolid)
+ {
+ setorigin(player, loc);
+ player.angles = normalize(loc - closest_target.origin) * RAD2DEG;
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ if(autocvar_g_onslaught_spawn_at_generator)
+ if(random() <= autocvar_g_onslaught_spawn_at_generator_chance)
+ {
+ float random_target = autocvar_g_onslaught_spawn_at_generator_random;
+ entity tmp_entity, closest_target = NULL;
+ vector spawn_loc = player.ons_deathloc;
+
+ // new joining player or round reset, don't bother checking
+ if(spawn_loc == '0 0 0') { return false; }
+
+ if(random_target) { RandomSelection_Init(); }
+
+ for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
+ {
+ if(random_target)
+ RandomSelection_Add(tmp_entity, 0, string_null, 1, 1);
+ else
+ {
+ if(SAME_TEAM(tmp_entity, player))
+ if(vlen2(tmp_entity.origin - spawn_loc) <= vlen2(closest_target.origin - spawn_loc) || closest_target == NULL)
+ closest_target = tmp_entity;
+ }
+ }
+
+ if(random_target) { closest_target = RandomSelection_chosen_ent; }
+
+ if(closest_target)
+ {
+ float i;
+ vector loc;
+ float iteration_scale = 1;
+ for(i = 0; i < 10; ++i)
+ {
+ iteration_scale -= i / 10;
+ loc = closest_target.origin + '0 0 128' * iteration_scale;
+ loc += ('0 1 0' * random()) * 256 * iteration_scale;
+ tracebox(loc, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), loc, MOVE_NORMAL, player);
+ if(trace_fraction == 1.0 && !trace_startsolid)
+ {
+ traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the NULL
+ if(trace_fraction == 1.0 && !trace_startsolid)
+ {
+ setorigin(player, loc);
+ player.angles = normalize(loc - closest_target.origin) * RAD2DEG;
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ frag_target.ons_deathloc = frag_target.origin;
+ entity l;
+ for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext)
+ {
+ l.sprite.SendFlags |= 16;
+ }
+ for(l = ons_worldcplist; l; l = l.ons_worldcpnext)
+ {
+ l.sprite.SendFlags |= 16;
+ }
+
+ if ( autocvar_g_onslaught_spawn_choose )
+ if ( ons_Count_SelfControlPoints(frag_target) > 1 )
+ stuffcmd(frag_target, "qc_cmd_cl hud clickradar\n");
+
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, MonsterMove)
+{
+ entity mon = M_ARGV(0, entity);
+
+ entity e = find(NULL, targetname, mon.target);
+ if (e != NULL)
+ mon.team = e.team;
+}
+
+void ons_MonsterSpawn_Delayed(entity this)
+{
+ entity own = this.owner;
+
+ if(!own) { delete(this); return; }
+
+ if(own.targetname)
+ {
+ entity e = find(NULL, target, own.targetname);
+ if(e != NULL)
+ {
+ own.team = e.team;
+
+ own.use(own, e, NULL);
+ }
+ }
+
+ delete(this);
+}
+
+MUTATOR_HOOKFUNCTION(ons, MonsterSpawn)
+{
+ entity mon = M_ARGV(0, entity);
+
+ entity e = spawn();
+ e.owner = mon;
+ InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET);
+}
+
+void ons_TurretSpawn_Delayed(entity this)
+{
+ entity own = this.owner;
+
+ if(!own) { delete(this); return; }
+
+ if(own.targetname)
+ {
+ entity e = find(NULL, target, own.targetname);
+ if(e != NULL)
+ {
+ own.team = e.team;
+ own.active = ACTIVE_NOT;
+
+ own.use(own, e, NULL);
+ }
+ }
+
+ delete(this);
+}
+
+MUTATOR_HOOKFUNCTION(ons, TurretSpawn)
+{
+ entity turret = M_ARGV(0, entity);
+
+ entity e = spawn();
+ e.owner = turret;
+ InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET);
+
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, HavocBot_ChooseRole)
+{
+ entity bot = M_ARGV(0, entity);
+
+ havocbot_ons_reset_role(bot);
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ons, GetTeamCount)
+{
+ // onslaught is special
+ for(entity tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext)
+ {
+ switch(tmp_entity.team)
+ {
+ case NUM_TEAM_1: c1 = 0; break;
+ case NUM_TEAM_2: c2 = 0; break;
+ case NUM_TEAM_3: c3 = 0; break;
+ case NUM_TEAM_4: c4 = 0; break;
+ }
+ }
+
+ return true;
+}
+
+MUTATOR_HOOKFUNCTION(ons, SpectateCopy)
+{
+ entity spectatee = M_ARGV(0, entity);
+ entity client = M_ARGV(1, entity);
+
++ STAT(ROUNDLOST, client) = STAT(ROUNDLOST, spectatee); // make spectators see it too
+}
+
+MUTATOR_HOOKFUNCTION(ons, SV_ParseClientCommand)
+{
+ if(MUTATOR_RETURNVALUE) // command was already handled?
+ return false;
+
+ entity player = M_ARGV(0, entity);
+ string cmd_name = M_ARGV(1, string);
+ int cmd_argc = M_ARGV(2, int);
+
+ if ( cmd_name == "ons_spawn" )
+ {
+ vector pos = player.origin;
+ if(cmd_argc > 1)
+ pos_x = stof(argv(1));
+ if(cmd_argc > 2)
+ pos_y = stof(argv(2));
+ if(cmd_argc > 3)
+ pos_z = stof(argv(3));
+
+ if ( IS_PLAYER(player) )
+ {
+ if ( !STAT(FROZEN, player) )
+ {
+ entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
+
+ if ( !source_point && player.health > 0 )
+ {
+ sprint(player, "\nYou need to be next to a control point\n");
+ return true;
+ }
+
+
+ entity closest_target = ons_Nearest_ControlPoint_2D(player, pos, autocvar_g_onslaught_click_radius);
+
+ if ( closest_target == NULL )
+ {
+ sprint(player, "\nNo control point found\n");
+ return true;
+ }
+
+ if ( player.health <= 0 )
+ {
+ player.ons_spawn_by = closest_target;
+ player.respawn_flags = player.respawn_flags | RESPAWN_FORCE;
+ }
+ else
+ {
+ if ( source_point == closest_target )
+ {
+ sprint(player, "\nTeleporting to the same point\n");
+ return true;
+ }
+
+ if ( !ons_Teleport(player,closest_target,autocvar_g_onslaught_teleport_radius,true) )
+ sprint(player, "\nUnable to teleport there\n");
+ }
+
+ return true;
+ }
+
+ sprint(player, "\nNo teleportation for you\n");
+ }
+
+ return true;
+ }
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayerUseKey)
+{
+ if(MUTATOR_RETURNVALUE || gameover) { return false; }
+
+ entity player = M_ARGV(0, entity);
+
+ if((time > player.teleport_antispam) && (!IS_DEAD(player)) && !player.vehicle)
+ {
+ entity source_point = ons_Nearest_ControlPoint(player, player.origin, autocvar_g_onslaught_teleport_radius);
+ if ( source_point )
+ {
+ stuffcmd(player, "qc_cmd_cl hud clickradar\n");
+ return true;
+ }
+ }
+}
+
+MUTATOR_HOOKFUNCTION(ons, PlayHitsound)
+{
+ entity frag_victim = M_ARGV(0, entity);
+
+ return (frag_victim.classname == "onslaught_generator" && !frag_victim.isshielded)
+ || (frag_victim.classname == "onslaught_controlpoint_icon" && !frag_victim.owner.isshielded);
+}
+
+MUTATOR_HOOKFUNCTION(ons, SendWaypoint)
+{
+ entity wp = M_ARGV(0, entity);
+ entity to = M_ARGV(1, entity);
+ int sf = M_ARGV(2, int);
+ int wp_flag = M_ARGV(3, int);
+
+ if(sf & 16)
+ {
+ if(wp.owner.classname == "onslaught_controlpoint")
+ {
+ 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(!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; }
+ }
+ }
+
+ M_ARGV(3, int) = wp_flag;
+}
+
+MUTATOR_HOOKFUNCTION(ons, TurretValidateTarget)
+{
+ entity turret_target = M_ARGV(1, entity);
+
+ if(substring(turret_target.classname, 0, 10) == "onslaught_") // don't attack onslaught targets, that's the player's job!
+ {
+ M_ARGV(3, float) = -3;
+ return true;
+ }
+
+ return false;
+}
+
+MUTATOR_HOOKFUNCTION(ons, TurretThink)
+{
+ entity turret = M_ARGV(0, entity);
+
+ // ONS uses somewhat backwards linking.
+ if(turret.target)
+ {
+ entity e = find(NULL, targetname, turret.target);
+ if (e != NULL)
+ turret.team = e.team;
+ }
+
+ if(turret.team != turret.tur_head.team)
+ turret_respawn(turret);
+}
+
+
+// ==========
+// Spawnfuncs
+// ==========
+
+/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16)
+ Link between control points.
+
+ This entity targets two different spawnfunc_onslaught_controlpoint or spawnfunc_onslaught_generator entities, and suppresses shielding on both if they are owned by different teams.
+
+keys:
+"target" - first control point.
+"target2" - second control point.
+ */
+spawnfunc(onslaught_link)
+{
+ if(!g_onslaught) { delete(this); return; }
+
+ if (this.target == "" || this.target2 == "")
+ objerror(this, "target and target2 must be set\n");
+
+ this.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist
+ ons_worldlinklist = this;
+
+ InitializeEntity(this, ons_DelayedLinkSetup, INITPRIO_FINDTARGET);
+ Net_LinkEntity(this, false, 0, ons_Link_Send);
+}
+
+/*QUAKED spawnfunc_onslaught_controlpoint (0 .5 .8) (-32 -32 0) (32 32 128)
+ Control point. Be sure to give this enough clearance so that the shootable part has room to exist
+
+ This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity.
+
+keys:
+"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
+"target" - target any entities that are tied to this control point, such as vehicles and buildable structure entities.
+"message" - name of this control point (should reflect the location in the map, such as "center bridge", "north tower", etc)
+ */
+
+spawnfunc(onslaught_controlpoint)
+{
+ if(!g_onslaught) { delete(this); return; }
+
+ ons_ControlPoint_Setup(this);
+}
+
+/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64)
+ Base generator.
+
+ spawnfunc_onslaught_link entities can target this.
+
+keys:
+"team" - team that owns this generator (5 = red, 14 = blue, etc), MUST BE SET.
+"targetname" - name that spawnfunc_onslaught_link entities will use to target this.
+ */
+spawnfunc(onslaught_generator)
+{
+ if(!g_onslaught) { delete(this); return; }
+ if(!this.team) { objerror(this, "team must be set"); }
+
+ ons_GeneratorSetup(this);
+}
+
+// scoreboard setup
+void ons_ScoreRules()
+{
+ CheckAllowedTeams(NULL);
+ 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);
+ ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
+ ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0);
+ ScoreRules_basics_end();
+}
+
+void ons_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up
+{
+ ons_ScoreRules();
+
+ round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart);
+ round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit);
+}
+
+void ons_Initialize()
+{
+ g_onslaught = true;
+ ons_captureshield_force = autocvar_g_onslaught_shield_force;
+
+ InitializeEntity(NULL, ons_DelayedInit, INITPRIO_GAMETYPE);
+}
--- /dev/null
- .bool ons_roundlost = _STAT(ROUNDLOST);
-
+#pragma once
+
+float autocvar_g_onslaught_point_limit;
+void ons_Initialize();
+
+REGISTER_MUTATOR(ons, false)
+{
+ MUTATOR_ONADD
+ {
+ if (time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ ons_Initialize();
+
+ ActivateTeamplay();
+ SetLimits(autocvar_g_onslaught_point_limit, autocvar_leadlimit_override, autocvar_timelimit_override, -1);
+ have_team_spawns = -1; // request team spawns
+ }
+
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ // we actually cannot roll back ons_Initialize here
+ // BUT: we don't need to! If this gets called, adding always
+ // succeeds.
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ LOG_INFO("This is a game type and it cannot be removed at runtime.");
+ return -1;
+ }
+
+ return false;
+}
+
+.entity ons_toucher; // player who touched the control point
+
+// control point / generator constants
+const float ONS_CP_THINKRATE = 0.2;
+const float GEN_THINKRATE = 1;
+#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
+const vector CPGEN_WAYPOINT_OFFSET = ('0 0 128');
+const vector CPICON_OFFSET = ('0 0 96');
+
+// list of generators on the map
+entity ons_worldgeneratorlist;
+.entity ons_worldgeneratornext;
+.entity ons_stalegeneratornext;
+
+// list of control points on the map
+entity ons_worldcplist;
+.entity ons_worldcpnext;
+.entity ons_stalecpnext;
+
+// list of links on the map
+entity ons_worldlinklist;
+.entity ons_worldlinknext;
+.entity ons_stalelinknext;
+
+// definitions
+.entity sprite;
+.string target2;
+.int iscaptured;
+.int islinked;
+.int isshielded;
+.float lasthealth;
+.int lastteam;
+.int lastshielded;
+.int lastcaptured;
+
+.bool waslinked;
+
+bool ons_stalemate;
+
+.float teleport_antispam;
+
+// waypoint sprites
+.entity bot_basewaypoint; // generator waypointsprite
+
+.bool isgenneighbor[17];
+.bool iscpneighbor[17];
+float ons_notification_time[17];
+
+.float ons_overtime_damagedelay;
+
+.vector ons_deathloc;
+
+.entity ons_spawn_by;
+
+// declarations for functions used outside gamemode_onslaught.qc
+void ons_Generator_UpdateSprite(entity e);
+void ons_ControlPoint_UpdateSprite(entity e);
+bool ons_ControlPoint_Attackable(entity cp, int teamnumber);
+
+// CaptureShield: Prevent capturing or destroying control point/generator if it is not available yet
+float ons_captureshield_force; // push force of the shield
+
+// bot player logic
+const int HAVOCBOT_ONS_ROLE_NONE = 0;
+const int HAVOCBOT_ONS_ROLE_DEFENSE = 2;
+const int HAVOCBOT_ONS_ROLE_ASSISTANT = 4;
+const int HAVOCBOT_ONS_ROLE_OFFENSE = 8;
+
+.entity havocbot_ons_target;
+
+.int havocbot_role_flags;
+.float havocbot_attack_time;
+
+void havocbot_role_ons_defense(entity this);
+void havocbot_role_ons_offense(entity this);
+void havocbot_role_ons_assistant(entity this);
+
+void havocbot_ons_reset_role(entity this);
+void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius);
+void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius);
+
+// score rule declarations
+const int ST_ONS_CAPS = 1;
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include <lib/warpzone/common.qh>
- #include "../constants.qh"
- #include "../teams.qh"
- #include "../util.qh"
- #include "all.qh"
- #include "sv_monsters.qh"
- #include "../physics/movelib.qh"
- #include "../weapons/all.qh"
- #include <server/autocvars.qh>
- #include <server/defs.qh>
- #include "../deathtypes/all.qh"
- #include <server/mutators/all.qh>
- #include <server/steerlib.qh>
- #include "../turrets/sv_turrets.qh"
- #include "../turrets/util.qh"
- #include "../vehicles/all.qh"
- #include <server/campaign.qh>
- #include <server/command/common.qh>
- #include <server/command/cmd.qh>
- #include "../triggers/triggers.qh"
- #include <lib/csqcmodel/sv_model.qh>
- #include <server/round_handler.qh>
-#endif
+#include "sv_monsters.qh"
+
+#include <server/g_subs.qh>
+#include <lib/warpzone/common.qh>
+#include "../constants.qh"
+#include "../teams.qh"
+#include "../util.qh"
+#include "all.qh"
+#include "../physics/movelib.qh"
+#include "../weapons/_mod.qh"
+#include <server/autocvars.qh>
+#include <server/defs.qh>
+#include "../deathtypes/all.qh"
+#include <server/mutators/_mod.qh>
+#include <server/steerlib.qh>
+#include "../turrets/sv_turrets.qh"
+#include "../turrets/util.qh"
+#include "../vehicles/all.qh"
+#include <server/campaign.qh>
+#include <server/command/_mod.qh>
+#include "../triggers/triggers.qh"
+#include <lib/csqcmodel/sv_model.qh>
+#include <server/round_handler.qh>
+#include <server/weapons/_mod.qh>
void monsters_setstatus(entity this)
{
|| (SAME_TEAM(targ, this))
|| (STAT(FROZEN, targ))
|| (targ.alpha != 0 && targ.alpha < 0.5)
+ || (MUTATOR_CALLHOOK(MonsterValidTarget, this, targ))
)
{
// if any of the above checks fail, target is not valid
void Monster_Respawn(entity this) { Monster_Spawn(this, this.monsterid); }
+.vector pos1, pos2;
+
void Monster_Dead_Fade(entity this)
{
if(Monster_Respawn_Check(this))
if(Monster_ValidTarget(this, actor)) { this.enemy = actor; }
}
+.float pass_distance;
vector Monster_Move_Target(entity this, entity targ)
{
// enemy is always preferred target
}
.entity draggedby;
+.entity target2;
void Monster_Move(entity this, float runspeed, float walkspeed, float stpspeed)
{
--- /dev/null
+#include "sv_campcheck.qh"
+
+float autocvar_g_campcheck_damage;
+float autocvar_g_campcheck_distance;
+float autocvar_g_campcheck_interval;
+
+REGISTER_MUTATOR(campcheck, cvar("g_campcheck"));
+
+.float campcheck_nextcheck;
+.float campcheck_traveled_distance;
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerDies)
+{
+ entity frag_target = M_ARGV(2, entity);
+
+ Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_CAMPCHECK);
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerDamage_Calculate)
+{
+ entity frag_attacker = M_ARGV(1, entity);
+ entity frag_target = M_ARGV(2, entity);
+
+ if(IS_PLAYER(frag_target))
+ if(IS_PLAYER(frag_attacker))
+ if(frag_attacker != frag_target)
+ {
+ frag_target.campcheck_traveled_distance = autocvar_g_campcheck_distance;
+ frag_attacker.campcheck_traveled_distance = autocvar_g_campcheck_distance;
+ }
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerPreThink)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(!gameover)
+ if(!warmup_stage) // don't consider it camping during warmup?
+ if(time >= game_starttime)
+ if(IS_PLAYER(player))
+ if(IS_REAL_CLIENT(player)) // bots may camp, but that's no reason to constantly kill them
+ if(!IS_DEAD(player))
++ if(!forbidWeaponUse(player))
+ if(!STAT(FROZEN, player))
+ if(!PHYS_INPUT_BUTTON_CHAT(player))
+ if(autocvar_g_campcheck_interval)
+ {
+ vector dist;
+
+ // calculate player movement (in 2 dimensions only, so jumping on one spot doesn't count as movement)
+ dist = player.prevorigin - player.origin;
+ dist.z = 0;
+ player.campcheck_traveled_distance += fabs(vlen(dist));
+
+ if((autocvar_g_campaign && !campaign_bots_may_start) || (time < game_starttime) || (round_handler_IsActive() && !round_handler_IsRoundStarted()))
+ {
+ player.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2;
+ player.campcheck_traveled_distance = 0;
+ }
+
+ if(time > player.campcheck_nextcheck)
+ {
+ if(player.campcheck_traveled_distance < autocvar_g_campcheck_distance)
+ {
+ Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CAMPCHECK);
+ if(player.vehicle)
+ Damage(player.vehicle, NULL, NULL, autocvar_g_campcheck_damage * 2, DEATH_CAMP.m_id, player.vehicle.origin, '0 0 0');
+ else
+ Damage(player, NULL, NULL, bound(0, autocvar_g_campcheck_damage, player.health + player.armorvalue * autocvar_g_balance_armor_blockpercent + 5), DEATH_CAMP.m_id, player.origin, '0 0 0');
+ }
+ player.campcheck_nextcheck = time + autocvar_g_campcheck_interval;
+ player.campcheck_traveled_distance = 0;
+ }
+
+ return;
+ }
+
+ player.campcheck_nextcheck = time + autocvar_g_campcheck_interval; // one of the above checks failed, so keep the timer up to date
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, PlayerSpawn)
+{
+ entity player = M_ARGV(0, entity);
+
+ player.campcheck_nextcheck = time + autocvar_g_campcheck_interval * 2;
+ player.campcheck_traveled_distance = 0;
+}
+
+MUTATOR_HOOKFUNCTION(campcheck, BuildMutatorsString)
+{
+ M_ARGV(0, string) = strcat(M_ARGV(0, string), ":CampCheck");
+}
--- /dev/null
- if (menu_enabled != 2)
+#include "itemstime.qh"
+
+REGISTER_MUTATOR(itemstime, true);
+
+REGISTER_NET_TEMP(itemstime)
+
+#ifdef SVQC
+void IT_Write(entity e, int i, float f) {
+ if (!IS_REAL_CLIENT(e)) return;
+ msg_entity = e;
+ WriteHeader(MSG_ONE, itemstime);
+ WriteByte(MSG_ONE, i);
+ WriteFloat(MSG_ONE, f);
+}
+#endif
+
+#ifdef CSQC
+// reserve one more spot for superweapons time
+float ItemsTime_time[Items_MAX + 1];
+float ItemsTime_availableTime[Items_MAX + 1];
+NET_HANDLE(itemstime, bool isNew)
+{
+ int i = ReadByte();
+ float f = ReadFloat();
+ return = true;
+ ItemsTime_time[i] = f;
+}
+#endif
+
+#ifdef CSQC
+void Item_ItemsTime_Init()
+{
+ FOREACH(Items, true, LAMBDA(
+ ItemsTime_time[it.m_id] = -1;
+ ));
+ ItemsTime_time[Items_MAX] = -1;
+}
+
+STATIC_INIT(ItemsTime_Init) {
+ Item_ItemsTime_Init();
+}
+
+int autocvar_hud_panel_itemstime = 2;
+float autocvar_hud_panel_itemstime_dynamicsize = 1;
+float autocvar_hud_panel_itemstime_ratio = 2;
+int autocvar_hud_panel_itemstime_iconalign;
+bool autocvar_hud_panel_itemstime_progressbar = 0;
+float autocvar_hud_panel_itemstime_progressbar_maxtime = 30;
+string autocvar_hud_panel_itemstime_progressbar_name = "progressbar";
+float autocvar_hud_panel_itemstime_progressbar_reduced;
+bool autocvar_hud_panel_itemstime_hidespawned = 1;
+bool autocvar_hud_panel_itemstime_hidelarge = false;
+int autocvar_hud_panel_itemstime_text = 1;
+#define hud_panel_itemstime_hidelarge autocvar_hud_panel_itemstime_hidelarge
+#else
+#define hud_panel_itemstime_hidelarge false
+#endif
+
+bool Item_ItemsTime_SpectatorOnly(GameItem it)
+{
+ return (false
+ || it == ITEM_ArmorMega || (it == ITEM_ArmorLarge && !hud_panel_itemstime_hidelarge)
+ || it == ITEM_HealthMega || (it == ITEM_HealthLarge && !hud_panel_itemstime_hidelarge)
+ );
+}
+
+bool Item_ItemsTime_Allow(GameItem it)
+{
+ return (false
+ || it.instanceOfPowerup
+ || Item_ItemsTime_SpectatorOnly(it)
+ );
+}
+
+#ifdef SVQC
+
+// reserve one more spot for superweapons time
+float it_times[Items_MAX + 1];
+
+void Item_ItemsTime_Init()
+{
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ it_times[it.m_id] = -1;
+ ));
+ it_times[Items_MAX] = -1;
+}
+
+STATIC_INIT(ItemsTime_Init) {
+ // items time
+ Item_ItemsTime_Init();
+}
+
+void Item_ItemsTime_ResetTimes()
+{
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ it_times[it.m_id] = (it_times[it.m_id] == -1) ? -1 : 0;
+ ));
+ it_times[Items_MAX] = (it_times[Items_MAX] == -1) ? -1 : 0;
+}
+
+void Item_ItemsTime_ResetTimesForPlayer(entity e)
+{
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ IT_Write(e, it.m_id, (it_times[it.m_id] == -1) ? -1 : 0);
+ ));
+ IT_Write(e, Items_MAX, (it_times[Items_MAX] == -1) ? -1 : 0);
+}
+
+void Item_ItemsTime_SetTimesForPlayer(entity e)
+{
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ IT_Write(e, it.m_id, it_times[it.m_id]);
+ ));
+ IT_Write(e, Items_MAX, it_times[Items_MAX]);
+}
+
+void Item_ItemsTime_SetTime(entity e, float t)
+{
+ if (!autocvar_sv_itemstime)
+ return;
+
+ GameItem item = e.itemdef;
+ if (item.instanceOfGameItem)
+ {
+ if (!item.instanceOfWeaponPickup)
+ it_times[item.m_id] = t;
+ else if (e.weapons & WEPSET_SUPERWEAPONS)
+ it_times[Items_MAX] = t;
+ }
+}
+
+void Item_ItemsTime_SetTimesForAllPlayers()
+{
+ FOREACH_CLIENT(IS_REAL_CLIENT(it) && (warmup_stage || !IS_PLAYER(it) || autocvar_sv_itemstime == 2), LAMBDA(Item_ItemsTime_SetTimesForPlayer(it)));
+}
+
+float Item_ItemsTime_UpdateTime(entity e, float t)
+{
+ bool isavailable = (t == 0);
+ FOREACH_ENTITY_FLOAT(pure_data, false,
+ {
+ if(!(it.itemdef == e.itemdef || ((e.weapons & WEPSET_SUPERWEAPONS) && (it.weapons & WEPSET_SUPERWEAPONS) && clienttype(it) == CLIENTTYPE_NOTACLIENT)))
+ continue;
+ if (e == it) continue;
+ if (it.scheduledrespawntime <= time)
+ isavailable = true;
+ else if (t == 0 || it.scheduledrespawntime < t)
+ t = it.scheduledrespawntime;
+ });
+ if (isavailable)
+ t = -t; // let know the client there's another available item
+ return t;
+}
+
+MUTATOR_HOOKFUNCTION(itemstime, reset_map_global)
+{
+ Item_ItemsTime_ResetTimes();
+ // ALL the times need to be reset before .reset()ing each item
+ // since Item_Reset schedules respawn of superweapons and powerups
+ FOREACH_ENTITY_FLOAT(pure_data, false,
+ {
+ if(IS_CLIENT(it))
+ continue;
+ if (it.reset) Item_ItemsTime_SetTime(it, 0);
+ });
+ Item_ItemsTime_SetTimesForAllPlayers();
+}
+
+MUTATOR_HOOKFUNCTION(itemstime, MakePlayerObserver)
+{
+ entity player = M_ARGV(0, entity);
+
+ Item_ItemsTime_SetTimesForPlayer(player);
+}
+
+MUTATOR_HOOKFUNCTION(itemstime, ClientConnect, CBC_ORDER_LAST)
+{
+ entity player = M_ARGV(0, entity);
+
+ if(IS_PLAYER(player))
+ {
+ // client became player on connection skipping putObserverInServer step
+ if (IS_REAL_CLIENT(player))
+ if (warmup_stage || autocvar_sv_itemstime == 2)
+ Item_ItemsTime_SetTimesForPlayer(player);
+ }
+}
+
+MUTATOR_HOOKFUNCTION(itemstime, PlayerSpawn)
+{
+ if (warmup_stage || autocvar_sv_itemstime == 2) return;
+ entity player = M_ARGV(0, entity);
+
+ Item_ItemsTime_ResetTimesForPlayer(player);
+}
+
+#endif
+
+#ifdef CSQC
+
+void DrawItemsTimeItem(vector myPos, vector mySize, float ar, string item_icon, float item_time, bool item_available, float item_availableTime)
+{
+ float t = 0;
+ vector color = '0 0 0';
+ float picalpha;
+
+ if (autocvar_hud_panel_itemstime_hidespawned == 2)
+ picalpha = 1;
+ else if (item_available)
+ {
+ float BLINK_FACTOR = 0.15;
+ float BLINK_BASE = 0.85;
+ float BLINK_FREQ = 5;
+ picalpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
+ }
+ else
+ picalpha = 0.5;
+ t = floor(item_time - time + 0.999);
+ if (t < 5)
+ color = '0.7 0 0';
+ else if (t < 10)
+ color = '0.7 0.7 0';
+ else
+ color = '1 1 1';
+
+ vector picpos, numpos;
+ if (autocvar_hud_panel_itemstime_iconalign)
+ {
+ numpos = myPos;
+ picpos = myPos + eX * (ar - 1) * mySize_y;
+ }
+ else
+ {
+ numpos = myPos + eX * mySize_y;
+ picpos = myPos;
+ }
+
+ if (t > 0 && autocvar_hud_panel_itemstime_progressbar)
+ {
+ vector p_pos, p_size;
+ if (autocvar_hud_panel_itemstime_progressbar_reduced)
+ {
+ p_pos = numpos;
+ p_size = eX * ((ar - 1)/ar) * mySize_x + eY * mySize_y;
+ }
+ else
+ {
+ p_pos = myPos;
+ p_size = mySize;
+ }
+ HUD_Panel_DrawProgressBar(p_pos, p_size, autocvar_hud_panel_itemstime_progressbar_name, t/autocvar_hud_panel_itemstime_progressbar_maxtime, 0, autocvar_hud_panel_itemstime_iconalign, color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+
+ if(autocvar_hud_panel_itemstime_text)
+ {
+ if(t > 0)
+ drawstring_aspect(numpos, ftos(t), eX * ((ar - 1)/ar) * mySize_x + eY * mySize_y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
+ else if(precache_pic("gfx/hud/default/checkmark")) // COMPAT: check if this image exists, as 0.8.1 clients lack it
+ drawpic_aspect_skin(numpos, "checkmark", eX * (ar - 1) * mySize_y + eY * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL);
+ else // legacy code, if the image is missing just center the icon
+ picpos.x = myPos.x + mySize.x / 2 - mySize.y / 2;
+ }
+ if (item_availableTime)
+ drawpic_aspect_skin_expanding(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL, item_availableTime);
+ drawpic_aspect_skin(picpos, item_icon, '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * picalpha, DRAWFLAG_NORMAL);
+}
+
+void HUD_ItemsTime()
+{
+ if (!autocvar__hud_configure)
+ {
+ if (!(
+ (autocvar_hud_panel_itemstime == 1 && spectatee_status != 0)
+ || (autocvar_hud_panel_itemstime == 2 && (spectatee_status != 0 || warmup_stage || STAT(ITEMSTIME) == 2))
+ )) { return; }
+ }
+ else
+ {
+ ItemsTime_time[ITEM_ArmorMega.m_id] = time + 0;
+ ItemsTime_time[ITEM_HealthMega.m_id] = time + 8;
+ ItemsTime_time[ITEM_Strength.m_id] = time + 0;
+ ItemsTime_time[ITEM_Shield.m_id] = time + 4;
+ }
+
+ int count = 0;
+ if (autocvar_hud_panel_itemstime_hidespawned == 1)
+ {
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ count += (ItemsTime_time[it.m_id] > time || -ItemsTime_time[it.m_id] > time);
+ ));
+ count += (ItemsTime_time[Items_MAX] > time || -ItemsTime_time[Items_MAX] > time);
+ }
+ else if (autocvar_hud_panel_itemstime_hidespawned == 2)
+ {
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ count += (ItemsTime_time[it.m_id] > time);
+ ));
+ count += (ItemsTime_time[Items_MAX] > time);
+ }
+ else
+ {
+ FOREACH(Items, Item_ItemsTime_Allow(it), LAMBDA(
+ count += (ItemsTime_time[it.m_id] != -1);
+ ));
+ count += (ItemsTime_time[Items_MAX] != -1);
+ }
+ if (count == 0)
+ return;
+
+ HUD_Panel_UpdateCvars();
+
+ vector pos, mySize;
+ pos = panel_pos;
+ mySize = panel_size;
+
+ if (panel_bg_padding)
+ {
+ pos += '1 1 0' * panel_bg_padding;
+ mySize -= '2 2 0' * panel_bg_padding;
+ }
+
+ float rows, columns;
+ float ar = max(2, autocvar_hud_panel_itemstime_ratio) + 1;
+ rows = HUD_GetRowCount(count, mySize, ar);
+ columns = ceil(count/rows);
+
+ vector itemstime_size = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
+
+ vector offset = '0 0 0';
+ float newSize;
+ if (autocvar_hud_panel_itemstime_dynamicsize)
+ {
+ if (autocvar__hud_configure)
++ if (hud_configure_menu_open != 2)
+ HUD_Panel_DrawBg(1); // also draw the bg of the entire panel
+
+ // reduce panel to avoid spacing items
+ if (itemstime_size.x / itemstime_size.y < ar)
+ {
+ newSize = rows * itemstime_size.x / ar;
+ pos.y += (mySize.y - newSize) / 2;
+ mySize.y = newSize;
+ itemstime_size.y = mySize.y / rows;
+ }
+ else
+ {
+ newSize = columns * itemstime_size.y * ar;
+ pos.x += (mySize.x - newSize) / 2;
+ mySize.x = newSize;
+ itemstime_size.x = mySize.x / columns;
+ }
+ panel_pos = pos - '1 1 0' * panel_bg_padding;
+ panel_size = mySize + '2 2 0' * panel_bg_padding;
+ }
+ else
+ {
+ if (itemstime_size.x/itemstime_size.y > ar)
+ {
+ newSize = ar * itemstime_size.y;
+ offset.x = itemstime_size.x - newSize;
+ pos.x += offset.x/2;
+ itemstime_size.x = newSize;
+ }
+ else
+ {
+ newSize = 1/ar * itemstime_size.x;
+ offset.y = itemstime_size.y - newSize;
+ pos.y += offset.y/2;
+ itemstime_size.y = newSize;
+ }
+ }
+
+ HUD_Scale_Enable();
+ HUD_Panel_DrawBg(1);
+
+ float row = 0, column = 0;
+ bool item_available;
+ int id = 0;
+ string icon = "";
+ FOREACH(Items, Item_ItemsTime_Allow(it) && ItemsTime_time[it.m_id] != -1, LAMBDA(
+ id = it.m_id;
+ icon = it.m_icon;
+
+LABEL(iteration)
+ float item_time = ItemsTime_time[id];
+ if (item_time < -1)
+ {
+ item_available = true;
+ item_time = -item_time;
+ }
+ else
+ item_available = (item_time <= time);
+
+ if (ItemsTime_time[id] >= 0)
+ {
+ if (time <= ItemsTime_time[id])
+ ItemsTime_availableTime[id] = 0;
+ else if (ItemsTime_availableTime[id] == 0)
+ ItemsTime_availableTime[id] = time;
+ }
+ else if (ItemsTime_availableTime[id] == 0)
+ ItemsTime_availableTime[id] = time;
+
+ float f = (time - ItemsTime_availableTime[id]) * 2;
+ f = (f > 1) ? 0 : bound(0, f, 1);
+
+ if (autocvar_hud_panel_itemstime_hidespawned == 1)
+ if (!(ItemsTime_time[id] > time || -ItemsTime_time[id] > time))
+ continue;
+
+ if (autocvar_hud_panel_itemstime_hidespawned == 2)
+ if (!(ItemsTime_time[id] > time))
+ continue;
+
+ DrawItemsTimeItem(pos + eX * column * (itemstime_size.x + offset.x) + eY * row * (itemstime_size.y + offset.y), itemstime_size, ar, icon, item_time, item_available, f);
+ ++row;
+ if (row >= rows)
+ {
+ row = 0;
+ column = column + 1;
+ }
+ if(id == Items_MAX) // can happen only in the last fake iteration
+ break;
+ ));
+ // add another fake iteration for superweapons time
+ if(id < Items_MAX && ItemsTime_time[Items_MAX] != -1)
+ {
+ id = Items_MAX;
+ icon = "superweapons";
+ goto iteration;
+ }
+}
+
+#endif
--- /dev/null
+#include "sv_spawn_near_teammate.qh"
+
+float autocvar_g_spawn_near_teammate_distance;
+int autocvar_g_spawn_near_teammate_ignore_spawnpoint;
+float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
+float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
+int autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health;
+bool autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath;
+
+REGISTER_MUTATOR(spawn_near_teammate, cvar("g_spawn_near_teammate"));
+
+.entity msnt_lookat;
+
+.float msnt_timer;
+.vector msnt_deathloc;
+
+.float cvar_cl_spawn_near_teammate;
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, Spawn_Score)
+{
+ entity player = M_ARGV(0, entity);
+ entity spawn_spot = M_ARGV(1, entity);
+ vector spawn_score = M_ARGV(2, vector);
+
+ if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && player.cvar_cl_spawn_near_teammate))
+ return;
+
+ spawn_spot.msnt_lookat = NULL;
+
+ if(!teamplay)
+ return;
+
+ RandomSelection_Init();
+ FOREACH_CLIENT(IS_PLAYER(it) && it != player && SAME_TEAM(it, player) && !IS_DEAD(it), LAMBDA(
+ if(vdist(spawn_spot.origin - it.origin, >, autocvar_g_spawn_near_teammate_distance))
+ continue;
+ if(vdist(spawn_spot.origin - it.origin, <, 48))
+ continue;
+ if(!checkpvs(spawn_spot.origin, it))
+ continue;
+ RandomSelection_Add(it, 0, string_null, 1, 1);
+ ));
+
+ if(RandomSelection_chosen_ent)
+ {
+ spawn_spot.msnt_lookat = RandomSelection_chosen_ent;
+ spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND;
+ }
+ else if(player.team == spawn_spot.team)
+ spawn_score.x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate
+
+ M_ARGV(2, vector) = spawn_score;
+}
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerSpawn)
+{
+ if(!teamplay) { return; }
+ entity player = M_ARGV(0, entity);
+ entity spawn_spot = M_ARGV(1, entity);
+
+ int num_red = 0, num_blue = 0, num_yellow = 0, num_pink = 0;
+ FOREACH_CLIENT(IS_PLAYER(it),
+ {
+ switch(it.team)
+ {
+ case NUM_TEAM_1: ++num_red; break;
+ case NUM_TEAM_2: ++num_blue; break;
+ case NUM_TEAM_3: ++num_yellow; break;
+ case NUM_TEAM_4: ++num_pink; break;
+ }
+ });
+
+ if(num_red == 1 || num_blue == 1 || num_yellow == 1 || num_pink == 1)
+ return; // at least 1 team has only 1 player, let's not give the bigger team too much of an advantage!
+
+ // Note: when entering this, fixangle is already set.
+ if(autocvar_g_spawn_near_teammate_ignore_spawnpoint == 1 || (autocvar_g_spawn_near_teammate_ignore_spawnpoint == 2 && player.cvar_cl_spawn_near_teammate))
+ {
+ if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death)
+ player.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
+
+ entity best_mate = NULL;
+ vector best_spot = '0 0 0';
+ float pc = 0, best_dist = 0, dist = 0;
+ FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
+ if((autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health >= 0 && it.health >= autocvar_g_balance_health_regenstable) || autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health == 0)
+ if(!IS_DEAD(it))
+ if(it.msnt_timer < time)
+ if(SAME_TEAM(player, it))
+ if(time > it.spawnshieldtime) // spawn shielding
++ if(!forbidWeaponUse(it))
+ if(STAT(FROZEN, it) == 0)
+ if(it != player)
+ {
+ tracebox(it.origin, STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - '0 0 100', MOVE_NOMONSTERS, it);
+ if(trace_fraction != 1.0)
+ if(!(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
+ {
+ pc = pointcontents(trace_endpos + '0 0 1');
+ if(pc == CONTENT_EMPTY)
+ {
+ if(vdist(it.velocity, >, 5))
+ fixedmakevectors(vectoangles(it.velocity));
+ else
+ fixedmakevectors(it.angles);
+
+ for(pc = 0; pc < 4; ++pc) // test 4 diffrent spots close to mate
+ {
+ switch(pc)
+ {
+ case 0:
+ tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128, MOVE_NOMONSTERS, it);
+ break;
+ case 1:
+ tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 , MOVE_NOMONSTERS, it);
+ break;
+ case 2:
+ tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin + v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it);
+ break;
+ case 3:
+ tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_right * 128 - v_forward * 64, MOVE_NOMONSTERS, it);
+ break;
+ //case 4:
+ //tracebox(it.origin , STAT(PL_MIN, NULL), STAT(PL_MAX, NULL), it.origin - v_forward * 128, MOVE_NOMONSTERS, it);
+ //break;
+ }
+
+ if(trace_fraction == 1.0)
+ {
+ traceline(trace_endpos + '0 0 4', trace_endpos - '0 0 100', MOVE_NOMONSTERS, it);
+ if(trace_fraction != 1.0)
+ {
+ if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath)
+ {
+ dist = vlen(trace_endpos - player.msnt_deathloc);
+ if(dist < best_dist || best_dist == 0)
+ {
+ best_dist = dist;
+ best_spot = trace_endpos;
+ best_mate = it;
+ }
+ }
+ else
+ {
+ setorigin(player, trace_endpos);
+ player.angles = it.angles;
+ player.angles_z = 0; // never spawn tilted even if the spot says to
+ it.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ));
+
+ if(autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath)
+ if(best_dist)
+ {
+ setorigin(player, best_spot);
+ player.angles = best_mate.angles;
+ player.angles_z = 0; // never spawn tilted even if the spot says to
+ best_mate.msnt_timer = time + autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
+ }
+ }
+ else if(spawn_spot.msnt_lookat)
+ {
+ player.angles = vectoangles(spawn_spot.msnt_lookat.origin - player.origin);
+ player.angles_x = -player.angles.x;
+ player.angles_z = 0; // never spawn tilted even if the spot says to
+ /*
+ sprint(player, "You should be looking at ", spawn_spot.msnt_lookat.netname, "^7.\n");
+ sprint(player, "distance: ", vtos(spawn_spot.msnt_lookat.origin - player.origin), "\n");
+ sprint(player, "angles: ", vtos(player.angles), "\n");
+ */
+ }
+}
+
+MUTATOR_HOOKFUNCTION(spawn_near_teammate, PlayerDies)
+{
+ entity frag_target = M_ARGV(0, entity);
+
+ frag_target.msnt_deathloc = frag_target.origin;
+}
+
+REPLICATE(cvar_cl_spawn_near_teammate, bool, "cl_spawn_near_teammate");
#pragma once
#ifdef SVQC
-#include <server/cl_client.qh>
+#include <server/client.qh>
#endif
// Full list of all stat constants, included in a single location for easy reference
REGISTER_STAT(ROUNDLOST, int)
REGISTER_STAT(BUFF_TIME, float)
REGISTER_STAT(CTF_FLAGSTATUS, int)
+ REGISTER_STAT(CAPTURE_PROGRESS, float)
REGISTER_STAT(ENTRAP_ORB, float)
REGISTER_STAT(ENTRAP_ORB_ALPHA, float)
REGISTER_STAT(ITEMSTIME, int, autocvar_sv_itemstime)
#pragma once
float warmup_limit;
-#include <common/weapons/all.qh>
+#include <common/weapons/_all.qh>
#include <common/stats.qh>
#define INDEPENDENT_ATTACK_FINISHED 1
float g_warmup_allguns;
float g_warmup_allow_timeout;
float warmup_stage;
-PROPERTY(float, g_pickup_respawntime_weapon)
-PROPERTY(float, g_pickup_respawntime_superweapon)
-PROPERTY(float, g_pickup_respawntime_ammo)
-PROPERTY(float, g_pickup_respawntime_short)
-PROPERTY(float, g_pickup_respawntime_medium)
-PROPERTY(float, g_pickup_respawntime_long)
-PROPERTY(float, g_pickup_respawntime_powerup)
-PROPERTY(float, g_pickup_respawntimejitter_weapon)
-PROPERTY(float, g_pickup_respawntimejitter_superweapon)
-PROPERTY(float, g_pickup_respawntimejitter_ammo)
-PROPERTY(float, g_pickup_respawntimejitter_short)
-PROPERTY(float, g_pickup_respawntimejitter_medium)
-PROPERTY(float, g_pickup_respawntimejitter_long)
-PROPERTY(float, g_pickup_respawntimejitter_powerup)
float g_jetpack;
float sv_clones;
#define MISSILE_IS_GUIDED(m) ((m.missile_flags & MIF_GUIDED_ALL) ? true : false)
#define MISSILE_IS_TRACKING(m) ((m.missile_flags & MIF_GUIDED_TRACKING) ? true : false)
-
////
.entity player_stats;
#include "bot/api.qh"
#include "campaign.qh"
#include "cheats.qh"
-#include "cl_client.qh"
+#include "client.qh"
#include "command/common.qh"
#include "command/getreplies.qh"
#include "command/sv_cmd.qh"
#include "g_hook.qh"
#include "ipban.qh"
#include "mapvoting.qh"
-#include "mutators/all.qh"
+#include "mutators/_mod.qh"
#include "race.qh"
#include "scores.qh"
#include "teamplay.qh"
#include "../common/triggers/trigger/secret.qh"
#include "../common/triggers/target/music.qh"
#include "../common/util.qh"
-#include "../common/items/all.qh"
-#include "../common/weapons/all.qh"
+#include "../common/items/_mod.qh"
+#include <common/weapons/_all.qh>
#include "../common/state.qh"
const float LATENCY_THINKRATE = 10;
return "Map switch will happen after scoreboard.";
}
+ bool autocvar_sv_gameplayfix_multiplethinksperframe;
+ void RunThink(entity this)
+ {
+ // don't let things stay in the past.
+ // it is possible to start that way by a trigger with a local time.
+ if(this.nextthink <= 0 || this.nextthink > time + frametime)
+ return;
+
+ float oldtime = time; // do we need to save this?
+
+ for (int iterations = 0; iterations < 128 && !wasfreed(this); iterations++)
+ {
+ time = max(oldtime, this.nextthink);
+ this.nextthink = 0;
+
+ if(getthink(this))
+ getthink(this)(this);
+ // mods often set nextthink to time to cause a think every frame,
+ // we don't want to loop in that case, so exit if the new nextthink is
+ // <= the time the qc was told, also exit if it is past the end of the
+ // frame
+ if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe)
+ break;
+ }
+
+ time = oldtime;
+ }
+
bool autocvar_sv_freezenonclients;
bool autocvar_sv_gameplayfix_delayprojectiles;
void Physics_Frame()
if(it.move_qcphysics)
Movetype_Physics_NoMatchTicrate(it, PHYS_INPUT_TIMELENGTH, false);
+
+ if(it.movetype >= MOVETYPE_USER_FIRST && it.movetype <= MOVETYPE_USER_LAST) // these cases have no think handling
+ {
+ // handle thinking here
+ if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
+ RunThink(it);
+ }
});
if(autocvar_sv_gameplayfix_delayprojectiles >= 0)
#include "constants.qh"
#include "g_hook.qh"
#include "ipban.qh"
-#include "mutators/all.qh"
+#include "mutators/_mod.qh"
#include "../common/t_items.qh"
#include "weapons/accuracy.qh"
#include "weapons/csqcprojectile.qh"
#include "weapons/selection.qh"
-#include "../common/command/generic.qh"
+#include "../common/command/_mod.qh"
#include "../common/constants.qh"
#include "../common/deathtypes/all.qh"
#include "../common/mapinfo.qh"
#include "../common/triggers/subs.qh"
#include "../common/util.qh"
#include "../common/turrets/sv_turrets.qh"
-#include "../common/weapons/all.qh"
+#include <common/weapons/_all.qh>
#include "../common/vehicles/sv_vehicles.qh"
#include "../common/vehicles/vehicle.qh"
-#include "../common/items/all.qc"
+#include "../common/items/_mod.qh"
#include "../common/state.qh"
#include "../common/effects/qc/globalsound.qh"
#include "../lib/csqcmodel/sv_model.qh"
}
if (autocvar_sv_eventlog_console)
{
- LOG_INFO(s, "\n");
+ dedicated_print(strcat(s, "\n"));
}
}
/** called when a monster looks for another target */
MUTATOR_HOOKABLE(MonsterFindTarget, EV_NO_ARGS);
+ /**
+ * called when validating a monster's target
+ */
+ #define EV_MonsterValidTarget(i, o) \
+ /** monster */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** target */ i(entity, MUTATOR_ARGV_1_entity) \
+ /**/
+ MUTATOR_HOOKABLE(MonsterValidTarget, EV_MonsterValidTarget);
+
/** called to change a random monster to a miniboss */
#define EV_MonsterCheckBossFlag(i, o) \
/** monster */ i(entity, MUTATOR_ARGV_0_entity) \
/**/
MUTATOR_HOOKABLE(W_Reload, EV_W_Reload);
-/** called at the end of player_powerups() in cl_client.qc, used for manipulating the values which are set by powerup items. */
+/** called at the end of player_powerups() in client.qc, used for manipulating the values which are set by powerup items. */
#define EV_PlayerPowerups(i, o) \
/** player */ i(entity, MUTATOR_ARGV_0_entity) \
/** old items */ i(int, MUTATOR_ARGV_1_int) \
MUTATOR_HOOKABLE(PortalTeleport, EV_PortalTeleport);
/**
- * called whenever a player uses impulse 33 (help me) in cl_impulse.qc
+ * called whenever a player uses impulse 33 (help me) in impulse.qc
* normally help me ping uses .waypointsprite_attachedforcarrier,
* but if your mutator uses something different then you can handle it
* in a special manner using this hook
/** data */ i(string, MUTATOR_ARGV_2_string) \
/**/
MUTATOR_HOOKABLE(URI_GetCallback, EV_URI_GetCallback);
+
+ /**
+ * return true to prevent weapon use for a player
+ */
+ #define EV_ForbidWeaponUse(i, o) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /**/
+ MUTATOR_HOOKABLE(ForbidWeaponUse, EV_ForbidWeaponUse);
+
+ /** called when a player spawns as player, after shared setup, before his weapon is chosen (so items may be changed in here) */
+ #define EV_CopyBody(i, o) \
+ /** player */ i(entity, MUTATOR_ARGV_0_entity) \
+ /** newly created clone */ i(entity, MUTATOR_ARGV_1_entity) \
+ /** keepvelocity? */ i(bool, MUTATOR_ARGV_2_bool) \
+ /**/
+ MUTATOR_HOOKABLE(CopyBody, EV_CopyBody);
#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_ONADD
+ {
+ if (time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ dom_Initialize();
+
+ int fraglimit_override = autocvar_g_domination_point_limit;
+ if (autocvar_g_domination_roundbased && autocvar_g_domination_roundbased_point_limit)
+ fraglimit_override = autocvar_g_domination_roundbased_point_limit;
+
+ ActivateTeamplay();
+ SetLimits(fraglimit_override, autocvar_g_domination_point_leadlimit, autocvar_timelimit_override, -1);
+ have_team_spawns = -1; // request team spawns
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ LOG_INFO("This is a game type and it cannot be removed at runtime.");
+ return -1;
+ }
+
+ return 0;
+}
+
+// score rule declarations
+const float ST_DOM_TICKS = 1;
+const float ST_DOM_CAPS = 1;
+
+// pps: points per second
+.float dom_total_pps = _STAT(DOM_TOTAL_PPS);
+.float dom_pps_red = _STAT(DOM_PPS_RED);
+.float dom_pps_blue = _STAT(DOM_PPS_BLUE);
+.float dom_pps_yellow = _STAT(DOM_PPS_YELLOW);
+.float dom_pps_pink = _STAT(DOM_PPS_PINK);
+float total_pps;
+float pps_red;
+float pps_blue;
+float pps_yellow;
+float pps_pink;
+
+// capture declarations
+.float enemy_playerid;
+.entity sprite;
+.float captime;
+
+// misc globals
+float domination_roundbased;
+float domination_teams;
++
+ void AnimateDomPoint(entity this);
--- /dev/null
+#include "player.qh"
+
+#include "bot/api.qh"
+#include "cheats.qh"
+#include "g_damage.qh"
+#include "g_subs.qh"
+#include "miscfunctions.qh"
+#include "portals.qh"
+#include "teamplay.qh"
+#include "weapons/throwing.qh"
+#include "command/common.qh"
+#include "../common/state.qh"
+#include "../common/anim.qh"
+#include "../common/animdecide.qh"
+#include "../common/csqcmodel_settings.qh"
+#include "../common/deathtypes/all.qh"
+#include "../common/triggers/subs.qh"
+#include "../common/playerstats.qh"
+#include "../lib/csqcmodel/sv_model.qh"
+
+#include "../common/minigames/sv_minigames.qh"
+
+#include "../common/physics/player.qh"
+#include "../common/effects/qc/all.qh"
+#include "../common/mutators/mutator/waypoints/waypointsprites.qh"
+#include "../common/triggers/include.qh"
+
+#include "weapons/weaponstats.qh"
+
+#include "../common/animdecide.qh"
+
+void Drop_Special_Items(entity player)
+{
+ // called when the player has become stuck or frozen
+ // so objective items aren't stuck with the player
+
+ MUTATOR_CALLHOOK(DropSpecialItems, player);
+}
+
+void CopyBody_Think(entity this)
+{
+ if(this.CopyBody_nextthink && time > this.CopyBody_nextthink)
+ {
+ this.CopyBody_think(this);
+ if(wasfreed(this))
+ return;
+ this.CopyBody_nextthink = this.nextthink;
+ this.CopyBody_think = getthink(this);
+ setthink(this, CopyBody_Think);
+ }
+ CSQCMODEL_AUTOUPDATE(this);
+ this.nextthink = time;
+}
+void CopyBody(entity this, float keepvelocity)
+{
+ if (this.effects & EF_NODRAW)
+ return;
+ entity clone = new(body);
+ clone.enemy = this;
+ clone.lip = this.lip;
+ clone.colormap = this.colormap;
+ clone.iscreature = this.iscreature;
+ clone.teleportable = this.teleportable;
+ clone.damagedbycontents = this.damagedbycontents;
+ clone.angles = this.angles;
+ clone.v_angle = this.v_angle;
+ clone.avelocity = this.avelocity;
+ clone.damageforcescale = this.damageforcescale;
+ clone.effects = this.effects;
+ clone.glowmod = this.glowmod;
+ clone.event_damage = this.event_damage;
+ clone.anim_state = this.anim_state;
+ clone.anim_time = this.anim_time;
+ clone.anim_lower_action = this.anim_lower_action;
+ clone.anim_lower_time = this.anim_lower_time;
+ clone.anim_upper_action = this.anim_upper_action;
+ clone.anim_upper_time = this.anim_upper_time;
+ clone.anim_implicit_state = this.anim_implicit_state;
+ clone.anim_implicit_time = this.anim_implicit_time;
+ clone.anim_lower_implicit_action = this.anim_lower_implicit_action;
+ clone.anim_lower_implicit_time = this.anim_lower_implicit_time;
+ clone.anim_upper_implicit_action = this.anim_upper_implicit_action;
+ clone.anim_upper_implicit_time = this.anim_upper_implicit_time;
+ clone.dphitcontentsmask = this.dphitcontentsmask;
+ clone.death_time = this.death_time;
+ clone.pain_finished = this.pain_finished;
+ clone.health = this.health;
+ clone.armorvalue = this.armorvalue;
+ clone.armortype = this.armortype;
+ clone.model = this.model;
+ clone.modelindex = this.modelindex;
+ clone.skin = this.skin;
+ clone.species = this.species;
+ clone.move_qcphysics = false; // don't run gamecode logic on clones, too many
+ set_movetype(clone, this.move_movetype);
+ clone.solid = this.solid;
+ clone.ballistics_density = this.ballistics_density;
+ clone.takedamage = this.takedamage;
+ setcefc(clone, getcefc(this));
+ clone.uncustomizeentityforclient = this.uncustomizeentityforclient;
+ clone.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set;
+ if (keepvelocity == 1)
+ clone.velocity = this.velocity;
+ clone.oldvelocity = clone.velocity;
+ clone.alpha = this.alpha;
+ clone.fade_time = this.fade_time;
+ clone.fade_rate = this.fade_rate;
+ //clone.weapon = this.weapon;
+ setorigin(clone, this.origin);
+ setsize(clone, this.mins, this.maxs);
+ clone.prevorigin = this.origin;
+ clone.reset = SUB_Remove;
+ clone._ps = this._ps;
+
+ Drag_MoveDrag(this, clone);
+
+ if(clone.colormap <= maxclients && clone.colormap > 0)
+ clone.colormap = 1024 + this.clientcolors;
+
+ CSQCMODEL_AUTOINIT(clone);
+ clone.CopyBody_nextthink = this.nextthink;
+ clone.CopyBody_think = getthink(this);
+ clone.nextthink = time;
+ setthink(clone, CopyBody_Think);
+ // "bake" the current animation frame for clones (they don't get clientside animation)
+ animdecide_load_if_needed(clone);
+ animdecide_setframes(clone, false, frame, frame1time, frame2, frame2time);
++
++ MUTATOR_CALLHOOK(CopyBody, this, clone, keepvelocity);
+}
+
+void player_setupanimsformodel(entity this)
+{
+ // load animation info
+ animdecide_load_if_needed(this);
+ animdecide_setstate(this, 0, false);
+}
+
+void player_anim(entity this)
+{
+ int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
+ if(IS_DEAD(this)) {
+ if (!deadbits) {
+ // Decide on which death animation to use.
+ if(random() < 0.5)
+ deadbits = ANIMSTATE_DEAD1;
+ else
+ deadbits = ANIMSTATE_DEAD2;
+ }
+ } else {
+ // Clear a previous death animation.
+ deadbits = 0;
+ }
+ int animbits = deadbits;
+ if(STAT(FROZEN, this))
+ animbits |= ANIMSTATE_FROZEN;
+ if(this.move_movetype == MOVETYPE_FOLLOW)
+ animbits |= ANIMSTATE_FOLLOW;
+ if(this.crouch)
+ animbits |= ANIMSTATE_DUCK;
+ animdecide_setstate(this, animbits, false);
+ animdecide_setimplicitstate(this, IS_ONGROUND(this));
+}
+
+void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+ float take, save;
+ vector v;
+ Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
+
+ // damage resistance (ignore most of the damage from a bullet or similar)
+ damage = max(damage - 5, 1);
+
+ v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
+ take = v.x;
+ save = v.y;
+
+ if(sound_allowed(MSG_BROADCAST, attacker))
+ {
+ if (save > 10)
+ sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM);
+ else if (take > 30)
+ sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM);
+ else if (take > 10)
+ sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM);
+ }
+
+ if (take > 50)
+ Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker);
+ if (take > 100)
+ Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
+
+ this.armorvalue = this.armorvalue - save;
+ this.health = this.health - take;
+ // pause regeneration for 5 seconds
+ this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
+
+ this.dmg_save = this.dmg_save + save;//max(save - 10, 0);
+ this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
+ this.dmg_inflictor = inflictor;
+
+ if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0)
+ {
+ // don't use any animations as a gib
+ this.frame = 0;
+ // view just above the floor
+ this.view_ofs = '0 0 4';
+
+ Violence_GibSplash(this, 1, 1, attacker);
+ this.alpha = -1;
+ this.solid = SOLID_NOT; // restore later
+ this.takedamage = DAMAGE_NO; // restore later
+ this.damagedbycontents = false;
+ }
+}
+
+void calculate_player_respawn_time(entity this)
+{
+ if(g_ca)
+ return;
+
+ float gametype_setting_tmp;
+ float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
+ float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
+ float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
+ float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
+ float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
+ float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
+
+ float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
+ if (teamplay)
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA(
+ if(it.team == this.team)
+ ++pcount;
+ ));
+ if (sdelay_small_count == 0)
+ sdelay_small_count = 1;
+ if (sdelay_large_count == 0)
+ sdelay_large_count = 1;
+ }
+ else
+ {
+ FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA(
+ ++pcount;
+ ));
+ if (sdelay_small_count == 0)
+ {
+ if (g_cts)
+ {
+ // Players play independently. No point in requiring enemies.
+ sdelay_small_count = 1;
+ }
+ else
+ {
+ // Players play AGAINST each other. Enemies required.
+ sdelay_small_count = 2;
+ }
+ }
+ if (sdelay_large_count == 0)
+ {
+ if (g_cts)
+ {
+ // Players play independently. No point in requiring enemies.
+ sdelay_large_count = 1;
+ }
+ else
+ {
+ // Players play AGAINST each other. Enemies required.
+ sdelay_large_count = 2;
+ }
+ }
+ }
+
+ float sdelay;
+
+ if (pcount <= sdelay_small_count)
+ sdelay = sdelay_small;
+ else if (pcount >= sdelay_large_count)
+ sdelay = sdelay_large;
+ else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
+ sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
+
+ if(waves)
+ this.respawn_time = ceil((time + sdelay) / waves) * waves;
+ else
+ this.respawn_time = time + sdelay;
+
+ if(sdelay < sdelay_max)
+ this.respawn_time_max = time + sdelay_max;
+ else
+ this.respawn_time_max = this.respawn_time;
+
+ if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
+ this.respawn_countdown = 10; // first number to count down from is 10
+ else
+ this.respawn_countdown = -1; // do not count down
+
+ if(autocvar_g_forced_respawn)
+ this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
+}
+
+void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+ float take, save, dh, da;
+ vector v;
+ float valid_damage_for_weaponstats;
+ float excess;
+
+ dh = max(this.health, 0);
+ da = max(this.armorvalue, 0);
+
+ if(!DEATH_ISSPECIAL(deathtype))
+ {
+ damage *= sqrt(bound(1.0, this.cvar_cl_handicap, 100.0));
+ if(this != attacker)
+ damage /= sqrt(bound(1.0, attacker.cvar_cl_handicap, 100.0));
+ }
+
+ if(DEATH_ISWEAPON(deathtype, WEP_TUBA))
+ {
+ // tuba causes blood to come out of the ears
+ vector ear1, ear2;
+ vector d;
+ float f;
+ ear1 = this.origin;
+ ear1_z += 0.125 * this.view_ofs.z + 0.875 * this.maxs.z; // 7/8
+ ear2 = ear1;
+ makevectors(this.angles);
+ ear1 += v_right * -10;
+ ear2 += v_right * +10;
+ d = inflictor.origin - this.origin;
+ if (d)
+ f = (d * v_right) / vlen(d); // this is cos of angle of d and v_right!
+ else
+ f = 0; // Assum ecenter.
+ force = v_right * vlen(force);
+ Violence_GibSplash_At(ear1, force * -1, 2, bound(0, damage, 25) / 2 * (0.5 - 0.5 * f), this, attacker);
+ Violence_GibSplash_At(ear2, force, 2, bound(0, damage, 25) / 2 * (0.5 + 0.5 * f), this, attacker);
+ if(f > 0)
+ {
+ hitloc = ear1;
+ force = force * -1;
+ }
+ else
+ {
+ hitloc = ear2;
+ // force is already good
+ }
+ }
+ 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);
+ take = v.x;
+ save = v.y;
+
+ if(attacker == this)
+ {
+ // don't reset pushltime for this damage as it may be an attempt to
+ // escape a lava pit or similar
+ //this.pushltime = 0;
+ this.istypefrag = 0;
+ }
+ else if(IS_PLAYER(attacker))
+ {
+ this.pusher = attacker;
+ this.pushltime = time + autocvar_g_maxpushtime;
+ this.istypefrag = PHYS_INPUT_BUTTON_CHAT(this);
+ }
+ else if(time < this.pushltime)
+ {
+ attacker = this.pusher;
+ this.pushltime = max(this.pushltime, time + 0.6);
+ }
+ else
+ {
+ this.pushltime = 0;
+ this.istypefrag = 0;
+ }
+
+ if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
+ {
+ vector v = healtharmor_applydamage(this.armorvalue, max(0, autocvar_g_spawnshield_blockdamage), 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);
+ excess = max(0, damage - take - save);
+
+ if(sound_allowed(MSG_BROADCAST, attacker))
+ {
+ if (save > 10)
+ sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM);
+ else if (take > 30)
+ sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM);
+ else if (take > 10)
+ sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME possibly remove them?
+ }
+
+ if (take > 50)
+ Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker);
+ if (take > 100)
+ Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
+
+ if (time >= this.spawnshieldtime || autocvar_g_spawnshield_blockdamage < 1)
+ {
+ if (!(this.flags & FL_GODMODE))
+ {
+ this.armorvalue = this.armorvalue - save;
+ this.health = this.health - take;
+ // pause regeneration for 5 seconds
+ if(take)
+ this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
+
+ if (time > this.pain_finished) //Don't switch pain sequences like crazy
+ {
+ this.pain_finished = time + 0.5; //Supajoe
+
+ if(autocvar_sv_gentle < 1) {
+ if(this.classname != "body") // pain anim is BORKED on our ZYMs, FIXME remove this once we have good models
+ {
+ if (!this.animstate_override)
+ {
+ if (random() > 0.5)
+ animdecide_setaction(this, ANIMACTION_PAIN1, true);
+ else
+ animdecide_setaction(this, ANIMACTION_PAIN2, true);
+ }
+ }
+
+ if(sound_allowed(MSG_BROADCAST, attacker))
+ if((this.health < 2 * WEP_CVAR_PRI(blaster, damage) * autocvar_g_balance_selfdamagepercent + 1) || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || attacker != this) // WEAPONTODO: create separate limit for pain notification with laser
+ 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)
+ PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
+ else if(this.health > 50)
+ PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
+ else if(this.health > 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)
+ {
+ shake = damage * 5 / (bound(0,skill,100) + 1);
+ this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake;
+ this.v_angle_y = this.v_angle.y + (random() * 2 - 1) * shake;
+ this.v_angle_x = bound(-90, this.v_angle.x, 90);
+ }
+ }
+ else
+ this.max_armorvalue += (save + take);
+ }
+ this.dmg_save = this.dmg_save + save;//max(save - 10, 0);
+ this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
+ this.dmg_inflictor = inflictor;
+
+ if (this != attacker) {
+ float realdmg = damage - excess;
+ if (IS_PLAYER(attacker)) {
+ PlayerScore_Add(attacker, SP_DMG, realdmg);
+ }
+ if (IS_PLAYER(this)) {
+ PlayerScore_Add(this, SP_DMGTAKEN, realdmg);
+ }
+ }
+
+ bool abot = (IS_BOT_CLIENT(attacker));
+ bool vbot = (IS_BOT_CLIENT(this));
+
+ valid_damage_for_weaponstats = 0;
+ Weapon awep = WEP_Null;
+
+ if(vbot || IS_REAL_CLIENT(this))
+ if(abot || IS_REAL_CLIENT(attacker))
+ if(attacker && this != attacker)
+ if(DIFF_TEAM(this, attacker))
+ {
+ if(DEATH_ISSPECIAL(deathtype))
+ awep = PS(attacker).m_weapon;
+ else
+ awep = DEATH_WEAPONOF(deathtype);
+ valid_damage_for_weaponstats = 1;
+ }
+
+ dh = dh - max(this.health, 0);
+ da = da - max(this.armorvalue, 0);
+ if(valid_damage_for_weaponstats)
+ {
+ WeaponStats_LogDamage(awep.m_id, abot, PS(this).m_weapon.m_id, vbot, dh + da);
+ }
+ if (dh + da)
+ {
+ MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype);
+ }
+
+ if (this.health < 1)
+ {
+ float defer_ClientKill_Now_TeamChange;
+ defer_ClientKill_Now_TeamChange = false;
+
+ if(this.alivetime)
+ {
+ PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
+ this.alivetime = 0;
+ }
+
+ if(valid_damage_for_weaponstats)
+ WeaponStats_LogKill(awep.m_id, abot, PS(this).m_weapon.m_id, vbot);
+
+ if(autocvar_sv_gentle < 1)
+ if(sound_allowed(MSG_BROADCAST, attacker))
+ {
+ if(deathtype == DEATH_DROWN.m_id)
+ PlayerSound(this, playersound_drown, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
+ else
+ PlayerSound(this, playersound_death, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
+ }
+
+ // get rid of kill indicator
+ if(this.killindicator)
+ {
+ delete(this.killindicator);
+ this.killindicator = NULL;
+ if(this.killindicator_teamchange)
+ defer_ClientKill_Now_TeamChange = true;
+
+ if(this.classname == "body")
+ if(deathtype == DEATH_KILL.m_id)
+ {
+ // for the lemmings fans, a small harmless explosion
+ Send_Effect(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
+ }
+ }
+
+ // print an obituary message
+ if(this.classname != "body")
+ Obituary (attacker, inflictor, this, deathtype);
+
+ // increment frag counter for used weapon type
+ Weapon w = DEATH_WEAPONOF(deathtype);
+ if(w != WEP_Null)
+ if(accuracy_isgooddamage(attacker, this))
+ attacker.accuracy.(accuracy_frags[w.m_id-1]) += 1;
+
+ MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage);
+ excess = M_ARGV(4, float);
+
+ Weapon wep = PS(this).m_weapon;
+ /*for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+ {
+ .entity weaponentity = weaponentities[slot];
+ wep.wr_playerdeath(wep, this, weaponentity);
+ }*/
+ .entity weaponentity = weaponentities[0]; // TODO: unhardcode
+ wep.wr_playerdeath(wep, this, weaponentity);
+
+ RemoveGrapplingHook(this);
+
+ Portal_ClearAllLater(this);
+
+ this.fixangle = true;
+
+ if(defer_ClientKill_Now_TeamChange)
+ ClientKill_Now_TeamChange(this); // can turn player into spectator
+
+ // 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"))
+ return;
+
+ // 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
+
+ // clear waypoints
+ WaypointSprite_PlayerDead(this);
+ // throw a weapon
+ SpawnThrownWeapon(this, this.origin + (this.mins + this.maxs) * 0.5, PS(this).m_switchweapon.m_id);
+
+ // become fully visible
+ this.alpha = default_player_alpha;
+ // make the corpse upright (not tilted)
+ this.angles_x = 0;
+ this.angles_z = 0;
+ // don't spin
+ this.avelocity = '0 0 0';
+ // view from the floor
+ this.view_ofs = '0 0 -8';
+ // toss the corpse
+ set_movetype(this, MOVETYPE_TOSS);
+ // shootable corpse
+ this.solid = SOLID_CORPSE;
+ this.ballistics_density = autocvar_g_ballistics_density_corpse;
+ // don't stick to the floor
+ UNSET_ONGROUND(this);
+ // dying animation
+ this.deadflag = DEAD_DYING;
+
+ // when to allow respawn
+ calculate_player_respawn_time(this);
+
+ this.death_time = time;
+ if (random() < 0.5)
+ animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD1, true);
+ else
+ animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD2, true);
+ if (this.maxs.z > 5)
+ {
+ this.maxs_z = 5;
+ setsize(this, this.mins, this.maxs);
+ }
+ // set damage function to corpse damage
+ this.event_damage = PlayerCorpseDamage;
+ // call the corpse damage function just in case it wants to gib
+ this.event_damage(this, inflictor, attacker, excess, deathtype, hitloc, force);
+
+ // set up to fade out later
+ SUB_SetFade (this, time + 6 + random (), 1);
+ // reset body think wrapper broken by SUB_SetFade
+ if(this.classname == "body" && getthink(this) != CopyBody_Think) {
+ this.CopyBody_think = getthink(this);
+ this.CopyBody_nextthink = this.nextthink;
+ setthink(this, CopyBody_Think);
+ this.nextthink = time;
+ }
+
+ if(autocvar_sv_gentle > 0 || autocvar_ekg || this.classname == "body") {
+ // remove corpse
+ // clones don't run any animation code any more, so we must gib them when they die :(
+ PlayerCorpseDamage(this, inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force);
+ }
+
+ // reset fields the weapons may use just in case
+ FOREACH(Weapons, it != WEP_Null, LAMBDA(
+ it.wr_resetplayer(it, this);
+ for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
+ {
+ ATTACK_FINISHED_FOR(this, it.m_id, slot) = 0;
+ }
+ ));
+ }
+}
+
+void MoveToTeam(entity client, int team_colour, int type)
+{
+ int lockteams_backup = lockteams; // backup any team lock
+ lockteams = 0; // disable locked teams
+ TeamchangeFrags(client); // move the players frags
+ SetPlayerColors(client, team_colour - 1); // set the players colour
+ Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0'); // kill the player
+ lockteams = lockteams_backup; // restore the team lock
+ LogTeamchange(client.playerid, client.team, type);
+}
+
+/** print(), but only print if the server is not local */
+void dedicated_print(string input)
+{
+ if (server_is_dedicated) print(input);
+}
+
+/**
+ * message "": do not say, just test flood control
+ * return value:
+ * 1 = accept
+ * 0 = reject
+ * -1 = fake accept
+ */
+int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
+{
+ if (!teamsay && !privatesay) if (substring(msgin, 0, 1) == " ")
+ msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
+
+ msgin = formatmessage(source, msgin);
+
+ string colorstr;
+ if (!IS_PLAYER(source))
+ colorstr = "^0"; // black for spectators
+ else if(teamplay)
+ colorstr = Team_ColorCode(source.team);
+ else
+ {
+ colorstr = "";
+ teamsay = false;
+ }
+
+ if(intermission_running)
+ teamsay = false;
+
+ if (!source) {
+ colorstr = "";
+ teamsay = false;
+ }
+
+ if(msgin != "")
+ msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
+
+ /*
+ * using bprint solves this... me stupid
+ // how can we prevent the message from appearing in a listen server?
+ // for now, just give "say" back and only handle say_team
+ if(!teamsay)
+ {
+ clientcommand(source, strcat("say ", msgin));
+ return;
+ }
+ */
+
+ string namestr = "";
+ if (source)
+ namestr = autocvar_g_chat_teamcolors ? playername(source) : source.netname;
+
+ string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
+
+ string msgstr, cmsgstr;
+ string privatemsgprefix = string_null;
+ int privatemsgprefixlen = 0;
+ if (msgin == "") {
+ msgstr = cmsgstr = "";
+ } else {
+ if(privatesay)
+ {
+ msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
+ privatemsgprefixlen = strlen(msgstr);
+ msgstr = strcat(msgstr, msgin);
+ cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
+ if(autocvar_g_chat_teamcolors)
+ privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay), ": ^7");
+ else
+ privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", privatesay.netname, ": ^7");
+ }
+ else if(teamsay)
+ {
+ if(strstrofs(msgin, "/me", 0) >= 0)
+ {
+ //msgin = strreplace("/me", "", msgin);
+ //msgin = substring(msgin, 3, strlen(msgin));
+ msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
+ msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
+ }
+ else
+ msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
+ cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
+ }
+ else
+ {
+ if(strstrofs(msgin, "/me", 0) >= 0)
+ {
+ //msgin = strreplace("/me", "", msgin);
+ //msgin = substring(msgin, 3, strlen(msgin));
+ msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
+ msgstr = strcat("\{1}^4* ", "^7", msgin);
+ }
+ else {
+ msgstr = "\{1}";
+ msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
+ msgstr = strcat(msgstr, msgin);
+ }
+ cmsgstr = "";
+ }
+ msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
+ }
+
+ string fullmsgstr = msgstr;
+ string fullcmsgstr = cmsgstr;
+
+ // FLOOD CONTROL
+ int flood = 0;
+ var .float flood_field = floodcontrol_chat;
+ if(floodcontrol)
+ {
+ float flood_spl;
+ float flood_burst;
+ float flood_lmax;
+ float lines;
+ if(privatesay)
+ {
+ flood_spl = autocvar_g_chat_flood_spl_tell;
+ flood_burst = autocvar_g_chat_flood_burst_tell;
+ flood_lmax = autocvar_g_chat_flood_lmax_tell;
+ flood_field = floodcontrol_chattell;
+ }
+ else if(teamsay)
+ {
+ flood_spl = autocvar_g_chat_flood_spl_team;
+ flood_burst = autocvar_g_chat_flood_burst_team;
+ flood_lmax = autocvar_g_chat_flood_lmax_team;
+ flood_field = floodcontrol_chatteam;
+ }
+ else
+ {
+ flood_spl = autocvar_g_chat_flood_spl;
+ flood_burst = autocvar_g_chat_flood_burst;
+ flood_lmax = autocvar_g_chat_flood_lmax;
+ flood_field = floodcontrol_chat;
+ }
+ flood_burst = max(0, flood_burst - 1);
+ // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
+
+ // do flood control for the default line size
+ if(msgstr != "")
+ {
+ getWrappedLine_remaining = msgstr;
+ msgstr = "";
+ lines = 0;
+ while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
+ {
+ msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
+ ++lines;
+ }
+ msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
+
+ if(getWrappedLine_remaining != "")
+ {
+ msgstr = strcat(msgstr, "\n");
+ flood = 2;
+ }
+
+ if (time >= source.(flood_field))
+ {
+ source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
+ }
+ else
+ {
+ flood = 1;
+ msgstr = fullmsgstr;
+ }
+ }
+ else
+ {
+ if (time >= source.(flood_field))
+ source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
+ else
+ flood = 1;
+ }
+
+ if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
+ source.(flood_field) = flood = 0;
+ }
+
+ string sourcemsgstr, sourcecmsgstr;
+ if(flood == 2) // cannot happen for empty msgstr
+ {
+ if(autocvar_g_chat_flood_notify_flooder)
+ {
+ sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
+ sourcecmsgstr = "";
+ }
+ else
+ {
+ sourcemsgstr = fullmsgstr;
+ sourcecmsgstr = fullcmsgstr;
+ }
+ cmsgstr = "";
+ }
+ else
+ {
+ sourcemsgstr = msgstr;
+ sourcecmsgstr = cmsgstr;
+ }
+
+ if (!privatesay && source && !IS_PLAYER(source))
+ {
+ if (!intermission_running)
+ if(teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !(warmup_stage || gameover)))
+ teamsay = -1; // spectators
+ }
+
+ if(flood)
+ LOG_INFO("NOTE: ", playername(source), "^7 is flooding.\n");
+
+ // build sourcemsgstr by cutting off a prefix and replacing it by the other one
+ if(privatesay)
+ sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
+
+ int ret;
+ if(source.muted)
+ {
+ // always fake the message
+ ret = -1;
+ }
+ else if(flood == 1)
+ {
+ if (autocvar_g_chat_flood_notify_flooder)
+ {
+ sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
+ ret = 0;
+ }
+ else
+ ret = -1;
+ }
+ else
+ {
+ ret = 1;
+ }
+
+ if(sourcemsgstr != "" && ret != 0)
+ {
+ if(ret < 0) // faked message, because the player is muted
+ {
+ sprint(source, sourcemsgstr);
+ if(sourcecmsgstr != "" && !privatesay)
+ centerprint(source, sourcecmsgstr);
+ }
+ else if(privatesay) // private message, between 2 people only
+ {
+ sprint(source, sourcemsgstr);
+ sprint(privatesay, msgstr);
+ if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
+ if(cmsgstr != "")
+ centerprint(privatesay, cmsgstr);
+ }
+ else if ( teamsay && source.active_minigame )
+ {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame, sprint(it, msgstr));
+ }
+ else if(teamsay > 0) // team message, only sent to team mates
+ {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ if(sourcecmsgstr != "")
+ centerprint(source, sourcecmsgstr);
+ FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team, {
+ sprint(it, msgstr);
+ if(cmsgstr != "")
+ centerprint(it, cmsgstr);
+ });
+ }
+ else if(teamsay < 0) // spectator message, only sent to spectators
+ {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
+ }
+ else
+ {
+ if (source) {
+ sprint(source, sourcemsgstr);
+ dedicated_print(msgstr); // send to server console too
+ MX_Say(strcat(playername(source), "^7: ", msgin));
+ }
+ FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source, sprint(it, msgstr));
+ }
+ }
+
+ return ret;
+}
--- /dev/null
+#pragma once
+
+.entity pusher;
+.float pushltime;
+.float istypefrag;
+
+.float CopyBody_nextthink;
+.void(entity this) CopyBody_think;
+void CopyBody_Think(entity this);
+void CopyBody(entity this, float keepvelocity);
+
++void dedicated_print(string input);
++
+void player_setupanimsformodel(entity this);
+
+void player_anim(entity this);
+
+void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
+
+// g_<gametype>_str:
+// If 0, default is used.
+// If <0, 0 is used.
+// Otherwise, g_str (default value) is used.
+// For consistency, negative values there are mapped to zero too.
+#define GAMETYPE_DEFAULTED_SETTING(str) \
+ ((gametype_setting_tmp = cvar(strcat("g_", GetGametype(), "_" #str))), \
+ (gametype_setting_tmp < 0) ? 0 \
+ : (gametype_setting_tmp == 0 || autocvar_g_respawn_delay_forced) ? max(0, autocvar_g_##str) \
+ : gametype_setting_tmp)
+
+void calculate_player_respawn_time(entity this);
+
+void ClientKill_Now_TeamChange(entity this);
+
+void MoveToTeam(entity client, float team_colour, float type);
+
+void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
+
+/** to be used by `prvm_edictset server playernumber muted 1` */
+.float muted;
+int Say(entity source, float teamsay, entity privatesay, string msgin, float floodcontrol);
#include "teamplay.qh"
-#include "cl_client.qh"
+#include "client.qh"
#include "race.qh"
#include "scores.qh"
#include "scores_rules.qh"
#include "command/vote.qh"
-#include "mutators/all.qh"
+#include "mutators/_mod.qh"
#include "../common/deathtypes/all.qh"
-#include "../common/gamemodes/all.qh"
+#include "../common/gamemodes/_mod.qh"
#include "../common/teams.qh"
void TeamchangeFrags(entity e)
{
if(autocvar_g_campaign && pl && IS_REAL_CLIENT(pl))
return 1; // special case for campaign and player joining
- else
- error(sprintf("Too few teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
+ else if(totalteams == 1) // single team
+ LOG_TRACEF("Only 1 team available for %s, you may need to fix your map", MapInfo_Type_ToString(MapInfo_CurrentGametype()));
+ else // no teams, major no no
+ error(sprintf("No teams available for %s\n", MapInfo_Type_ToString(MapInfo_CurrentGametype())));
}
// count how many players are in each team
SetPlayerColors(this, selectedteam - 1);
// when JoinBestTeam is called by client.qc/ClientKill_Now_TeamChange the players team is -1 and thus skipped
- // when JoinBestTeam is called by cl_client.qc/ClientConnect the player_id is 0 the log attempt is rejected
+ // when JoinBestTeam is called by client.qc/ClientConnect the player_id is 0 the log attempt is rejected
LogTeamchange(this.playerid, this.team, 99);
}
return selectedteam;
#include <common/constants.qh>
#include <common/util.qh>
-#include <common/weapons/all.qh>
+#include <common/weapons/_all.qh>
#include <common/state.qh>
#include <lib/warpzone/common.qh>
// make sure you call makevectors first (FIXME?)
void W_SetupShot_Dir_ProjectileSize_Range(entity ent, .entity weaponentity, vector s_forward, vector mi, vector ma, float antilag, float recoil, Sound snd, float chan, float maxdamage, float range)
{
- TC(Sound, snd);
+ TC(Sound, snd);
float nudge = 1; // added to traceline target and subtracted from result TOOD(divVerent): do we still need this? Doesn't the engine do this now for us?
float oldsolid;
vector vecs, dv;
break;
float dist_taken = max(autocvar_g_ballistics_mindistance, vlen(trace_endpos - start));
- solid_penetration_left *= (dist_taken / maxdist);
+ // fraction_used_of_what_is_left = dist_taken / maxdist
+ // solid_penetration_left = solid_penetration_left - solid_penetration_left * fraction_used_of_what_is_left
+ solid_penetration_left *= 1 - dist_taken / maxdist;
+ solid_penetration_left = max(solid_penetration_left, 0);
// Only show effect when going through a player (invisible otherwise)
if (hit && (hit.solid != SOLID_BSP))
#include "selection.qh"
#include "../command/common.qh"
-#include "../mutators/all.qh"
+#include "../mutators/_mod.qh"
#include "../round_handler.qh"
#include <common/t_items.qh>
#include <common/animdecide.qh>
#include <common/monsters/all.qh>
#include <common/notifications/all.qh>
#include <common/util.qh>
-#include <common/weapons/all.qh>
+#include <common/weapons/_all.qh>
#include <common/state.qh>
#include <lib/csqcmodel/sv_model.qh>
if (gameover) return true;
if (STAT(FROZEN, player)) return true;
if (player.weapon_blocked) return true;
+ if (MUTATOR_CALLHOOK(ForbidWeaponUse, player)) return true;
return false;
}