From: Mario Date: Tue, 3 Feb 2015 05:27:17 +0000 (+1100) Subject: Merge branch 'master' into Mario/ons_updates X-Git-Tag: xonotic-v0.8.2~2050^2~8 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=15a18e8ca7c55ac8187b9ea266d15c2d6e0cb869 Merge branch 'master' into Mario/ons_updates Conflicts: qcsrc/client/autocvars.qh qcsrc/client/hud.qh qcsrc/client/mapvoting.qc qcsrc/client/progs.src qcsrc/client/view.qc qcsrc/client/waypointsprites.qc qcsrc/client/waypointsprites.qh qcsrc/common/constants.qh qcsrc/common/mapinfo.qh qcsrc/common/stats.qh qcsrc/server/autocvars.qh qcsrc/server/miscfunctions.qc qcsrc/server/mutators/gamemode_onslaught.qc qcsrc/server/progs.src --- 15a18e8ca7c55ac8187b9ea266d15c2d6e0cb869 diff --cc qcsrc/client/autocvars.qh index 7957bfacc1,e35bf82dd2..c51edbd800 --- a/qcsrc/client/autocvars.qh +++ b/qcsrc/client/autocvars.qh @@@ -420,26 -424,24 +424,26 @@@ float autocvar_vid_conheight float autocvar_vid_conwidth; float autocvar_vid_pixelheight; float autocvar_viewsize; - float autocvar_cl_hitsound; - var float autocvar_cl_hitsound_min_pitch = 0.75; // minimal difference in minsta - var float autocvar_cl_hitsound_max_pitch = 1.5; - var float autocvar_cl_hitsound_nom_damage = 25; + int autocvar_cl_hitsound; + float autocvar_cl_hitsound_min_pitch = 0.75; + float autocvar_cl_hitsound_max_pitch = 1.5; + float autocvar_cl_hitsound_nom_damage = 25; float autocvar_cl_hitsound_antispam_time; - var float autocvar_cl_eventchase_death = 1; - var float autocvar_cl_eventchase_nexball = 1; - var float autocvar_cl_eventchase_distance = 140; - var float autocvar_cl_eventchase_speed = 1.3; - var vector autocvar_cl_eventchase_maxs = '12 12 8'; - var vector autocvar_cl_eventchase_mins = '-12 -12 -8'; - var vector autocvar_cl_eventchase_viewoffset = '0 0 20'; - var vector autocvar_cl_eventchase_generator_viewoffset = '0 0 80'; - var float autocvar_cl_eventchase_generator_distance = 400; - float autocvar_cl_lerpexcess; + int autocvar_cl_eventchase_death = 1; + int autocvar_cl_eventchase_nexball = 1; ++vector autocvar_cl_eventchase_generator_viewoffset = '0 0 80'; ++float autocvar_cl_eventchase_generator_distance = 400; + float autocvar_cl_eventchase_distance = 140; + float autocvar_cl_eventchase_speed = 1.3; + vector autocvar_cl_eventchase_maxs = '12 12 8'; + vector autocvar_cl_eventchase_mins = '-12 -12 -8'; + vector autocvar_cl_eventchase_viewoffset = '0 0 20'; + float autocvar_cl_lerpexcess; // TODO: int? string autocvar__togglezoom; - float autocvar_cl_damageeffect; + int autocvar_cl_damageeffect; float autocvar_cl_damageeffect_ticrate; float autocvar_cl_damageeffect_bones; - float autocvar_cl_damageeffect_distribute; + bool autocvar_cl_damageeffect_distribute; float autocvar_cl_damageeffect_lifetime; float autocvar_cl_damageeffect_lifetime_min; float autocvar_cl_damageeffect_lifetime_max; diff --cc qcsrc/client/controlpoint.qc index c01835c9f8,0000000000..0eb49899d3 mode 100644,000000..100644 --- a/qcsrc/client/controlpoint.qc +++ b/qcsrc/client/controlpoint.qc @@@ -1,210 -1,0 +1,212 @@@ - float cpicon_precached; - .float count; ++#include "controlpoint.qh" ++#include "gibs.qh" ++#include "teamradar.qh" ++ ++bool cpicon_precached; ++.int count; +.float pain_finished; + - .float iscaptured; ++.bool iscaptured; + +.vector cp_origin, cp_bob_origin; +.float cp_bob_spd; + +.vector cp_bob_dmg; + +.vector punchangle; + +.float max_health; + +.entity icon_realmodel; + +void cpicon_precache() +{ + if(cpicon_precached) + return; // already precached + + precache_model("models/onslaught/controlpoint_icon_dmg3.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg2.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg1.md3"); + precache_model("models/onslaught/controlpoint_icon.md3"); + - cpicon_precached = TRUE; ++ cpicon_precached = true; +} + +void cpicon_draw() +{ + if(time < self.move_time) { return; } + + if(self.cp_bob_dmg_z > 0) + self.cp_bob_dmg_z = self.cp_bob_dmg_z - 3 * frametime; + else + self.cp_bob_dmg_z = 0; + self.cp_bob_origin_z = 4 * PI * (1 - cos(self.cp_bob_spd)); + self.cp_bob_spd = self.cp_bob_spd + 1.875 * frametime; + self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); + + if(!self.iscaptured) self.alpha = self.health / self.max_health; + + if(self.iscaptured) + { + if (self.punchangle_x > 0) + { + self.punchangle_x = self.punchangle_x - 60 * frametime; + if (self.punchangle_x < 0) + self.punchangle_x = 0; + } + else if (self.punchangle_x < 0) + { + self.punchangle_x = self.punchangle_x + 60 * frametime; + if (self.punchangle_x > 0) + self.punchangle_x = 0; + } + + if (self.punchangle_y > 0) + { + self.punchangle_y = self.punchangle_y - 60 * frametime; + if (self.punchangle_y < 0) + self.punchangle_y = 0; + } + else if (self.punchangle_y < 0) + { + self.punchangle_y = self.punchangle_y + 60 * frametime; + if (self.punchangle_y > 0) + self.punchangle_y = 0; + } + + if (self.punchangle_z > 0) + { + self.punchangle_z = self.punchangle_z - 60 * frametime; + if (self.punchangle_z < 0) + self.punchangle_z = 0; + } + else if (self.punchangle_z < 0) + { + self.punchangle_z = self.punchangle_z + 60 * frametime; + if (self.punchangle_z > 0) + self.punchangle_z = 0; + } + + self.angles_x = self.punchangle_x; + self.angles_y = self.punchangle_y + self.move_angles_y; + self.angles_z = self.punchangle_z; + self.move_angles_y = self.move_angles_y + 45 * frametime; + } + + setorigin(self, self.cp_origin + self.cp_bob_origin + self.cp_bob_dmg); +} + +void cpicon_damage(float hp) +{ + if(!self.iscaptured) { return; } + + if(hp < self.max_health * 0.25) + setmodel(self, "models/onslaught/controlpoint_icon_dmg3.md3"); + else if(hp < self.max_health * 0.50) + setmodel(self, "models/onslaught/controlpoint_icon_dmg2.md3"); + else if(hp < self.max_health * 0.75) + setmodel(self, "models/onslaught/controlpoint_icon_dmg1.md3"); + else if(hp <= self.max_health || hp >= self.max_health) + setmodel(self, "models/onslaught/controlpoint_icon.md3"); + + self.punchangle = (2 * randomvec() - '1 1 1') * 45; + + self.cp_bob_dmg_z = (2 * random() - 1) * 15; + self.pain_finished = time + 1; + self.colormod = '2 2 2'; + + setsize(self, CPICON_MIN, CPICON_MAX); +} + +void cpicon_construct() +{ + self.netname = "Control Point Icon"; + + setmodel(self, "models/onslaught/controlpoint_icon.md3"); + setsize(self, CPICON_MIN, CPICON_MAX); + + if(self.icon_realmodel == world) + { + self.icon_realmodel = spawn(); + setmodel(self.icon_realmodel, "null"); + setorigin(self.icon_realmodel, self.origin); + setsize(self.icon_realmodel, CPICON_MIN, CPICON_MAX); + self.icon_realmodel.movetype = MOVETYPE_NOCLIP; + self.icon_realmodel.solid = SOLID_NOT; + self.icon_realmodel.move_origin = self.icon_realmodel.origin; + } + + if(self.iscaptured) { self.icon_realmodel.solid = SOLID_BBOX; } + + self.move_movetype = MOVETYPE_NOCLIP; + self.solid = SOLID_NOT; + self.movetype = MOVETYPE_NOCLIP; + self.move_origin = self.origin; + self.move_time = time; + self.drawmask = MASK_NORMAL; + self.alpha = 1; + self.draw = cpicon_draw; + self.cp_origin = self.origin; + self.cp_bob_origin = '0 0 0.1'; + self.cp_bob_spd = 0; +} + +.vector glowmod; +void cpicon_changeteam() +{ + if(self.team) + { + self.glowmod = Team_ColorRGB(self.team - 1); + self.teamradar_color = Team_ColorRGB(self.team - 1); + self.colormap = 1024 + (self.team - 1) * 17; + } + else + { + self.colormap = 1024; + self.glowmod = '1 1 0'; + self.teamradar_color = '1 1 0'; + } +} + +void ent_cpicon() +{ - float sf; - sf = ReadByte(); ++ int sf = ReadByte(); + + if(sf & CPSF_SETUP) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.health = ReadByte(); + self.max_health = ReadByte(); + self.count = ReadByte(); + self.team = ReadByte(); + self.iscaptured = ReadByte(); + + if(!self.count) + self.count = (self.health - self.max_health) * frametime; + + cpicon_changeteam(); + cpicon_precache(); + cpicon_construct(); + } + + if(sf & CPSF_STATUS) + { - float _tmp; - _tmp = ReadByte(); ++ int _tmp = ReadByte(); + if(_tmp != self.team) + { + self.team = _tmp; + cpicon_changeteam(); + } + + _tmp = ReadByte(); + + if(_tmp != self.health) + cpicon_damage(_tmp); + + self.health = _tmp; + } +} diff --cc qcsrc/client/controlpoint.qh index eef4bd45f5,0000000000..8469b5226f mode 100644,000000..100644 --- a/qcsrc/client/controlpoint.qh +++ b/qcsrc/client/controlpoint.qh @@@ -1,8 -1,0 +1,13 @@@ ++#ifndef CONTROLPOINT_H ++#define CONTROLPOINT_H ++ +const vector CPICON_MIN = '-32 -32 -9'; +const vector CPICON_MAX = '32 32 25'; + - float CPSF_STATUS = 4; - float CPSF_SETUP = 8; ++const int CPSF_STATUS = 4; ++const int CPSF_SETUP = 8; + +void ent_cpicon(); - void cpicon_precache(); ++void cpicon_precache(); ++ ++#endif diff --cc qcsrc/client/generator.qc index c7cbf6c687,0000000000..fe19cc65ca mode 100644,000000..100644 --- a/qcsrc/client/generator.qc +++ b/qcsrc/client/generator.qc @@@ -1,260 -1,0 +1,261 @@@ - float generator_precached; - .float count; ++#include "generator.qh" ++ ++bool generator_precached; ++.int count; +.float max_health; + +vector randompos(vector m1, vector m2) +{ + vector v; + m2 = m2 - m1; + v_x = m2_x * random() + m1_x; + v_y = m2_y * random() + m1_y; + v_z = m2_z * random() + m1_z; + return v; +} + +void generator_precache() +{ + if(generator_precached) + return; // already precached + + precache_model("models/onslaught/generator.md3"); + precache_model("models/onslaught/generator_dead.md3"); + precache_model("models/onslaught/generator_dmg1.md3"); + precache_model("models/onslaught/generator_dmg2.md3"); + precache_model("models/onslaught/generator_dmg3.md3"); + precache_model("models/onslaught/generator_dmg4.md3"); + precache_model("models/onslaught/generator_dmg5.md3"); + precache_model("models/onslaught/generator_dmg6.md3"); + precache_model("models/onslaught/generator_dmg7.md3"); + precache_model("models/onslaught/generator_dmg8.md3"); + precache_model("models/onslaught/generator_dmg9.md3"); + precache_model("models/onslaught/generator_dead.md3"); + + precache_model("models/onslaught/ons_ray.md3"); + precache_sound("onslaught/shockwave.wav"); + precache_sound("weapons/grenade_impact.wav"); + precache_sound("weapons/rocket_impact.wav"); + precache_sound("onslaught/electricity_explode.wav"); + - generator_precached = TRUE; ++ generator_precached = true; +} + +void ons_generator_ray_draw() +{ + if(time < self.move_time) + return; + + self.move_time = time + 0.05; + + if(self.count > 10) + { + remove(self); + return; + } + + if(self.count > 5) + self.alpha -= 0.1; + else + self.alpha += 0.1; + + self.scale += 0.2; + self.count +=1; +} + +void ons_generator_ray_spawn(vector org) +{ + entity e; + e = spawn(); + e.classname = "ons_ray"; + setmodel(e, "models/onslaught/ons_ray.md3"); + setorigin(e, org); + e.angles = randomvec() * 360; + e.move_origin = org; + e.movetype = MOVETYPE_NONE; + e.alpha = 0; + e.scale = random() * 5 + 8; + e.move_time = time + 0.05; + e.drawmask = MASK_NORMAL; + e.draw = ons_generator_ray_draw; +} + +void generator_draw() +{ + if(time < self.move_time) + return; + + if(self.health > 0) + { + // damaged fx (less probable the more damaged is the generator) + if(random() < 0.9 - self.health / self.max_health) + if(random() < 0.01) + { + pointparticles(particleeffectnum("electro_ballexplode"), self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1); + sound(self, CH_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM); + } + else + pointparticles(particleeffectnum("torch_small"), self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1); + + self.move_time = time + 0.1; + + return; + } + + if(self.count <= 0) + return; + + vector org; - float i; ++ int i; + + // White shockwave + if(self.count==40||self.count==20) + { + sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 6); + } + + // rays + if(random() > 0.25) + { + ons_generator_ray_spawn(self.origin); + } + + // Spawn fire balls + for(i=0;i < 10;++i) + { + org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20'); + pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1); + } + + // Short explosion sound + small explosion + if(random() < 0.25) + { + te_explosion(self.origin); + sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); + } + + // Particles + org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8'); + pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1); + + // Final explosion + if(self.count==1) + { + org = self.origin; + te_explosion(org); + pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1); + sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + } + + self.move_time = time + 0.05; + + self.count -= 1; +} + +void generator_damage(float hp) +{ + if(hp <= 0) + setmodel(self, "models/onslaught/generator_dead.md3"); + else if(hp < self.max_health * 0.10) + setmodel(self, "models/onslaught/generator_dmg9.md3"); + else if(hp < self.max_health * 0.20) + setmodel(self, "models/onslaught/generator_dmg8.md3"); + else if(hp < self.max_health * 0.30) + setmodel(self, "models/onslaught/generator_dmg7.md3"); + else if(hp < self.max_health * 0.40) + setmodel(self, "models/onslaught/generator_dmg6.md3"); + else if(hp < self.max_health * 0.50) + setmodel(self, "models/onslaught/generator_dmg5.md3"); + else if(hp < self.max_health * 0.60) + setmodel(self, "models/onslaught/generator_dmg4.md3"); + else if(hp < self.max_health * 0.70) + setmodel(self, "models/onslaught/generator_dmg3.md3"); + else if(hp < self.max_health * 0.80) + setmodel(self, "models/onslaught/generator_dmg2.md3"); + else if(hp < self.max_health * 0.90) + setmodel(self, "models/onslaught/generator_dmg1.md3"); + else if(hp <= self.max_health || hp >= self.max_health) + setmodel(self, "models/onslaught/generator.md3"); + + setsize(self, GENERATOR_MIN, GENERATOR_MAX); +} + +void generator_construct() +{ + self.netname = "Generator"; + self.classname = "onslaught_generator"; + + setorigin(self, self.origin); + setmodel(self, "models/onslaught/generator.md3"); + setsize(self, GENERATOR_MIN, GENERATOR_MAX); + + self.move_movetype = MOVETYPE_NOCLIP; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_NOCLIP; + self.move_origin = self.origin; + self.move_time = time; + self.drawmask = MASK_NORMAL; + self.alpha = 1; + self.draw = generator_draw; +} + +.vector glowmod; +void generator_changeteam() +{ + if(self.team) + { + self.glowmod = Team_ColorRGB(self.team - 1); + self.teamradar_color = Team_ColorRGB(self.team - 1); + self.colormap = 1024 + (self.team - 1) * 17; + } + else + { + self.colormap = 1024; + self.glowmod = '1 1 0'; + self.teamradar_color = '1 1 0'; + } +} + +void ent_generator() +{ - float sf; - sf = ReadByte(); ++ int sf = ReadByte(); + + if(sf & GSF_SETUP) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + + self.health = ReadByte(); + self.max_health = ReadByte(); + self.count = ReadByte(); + self.team = ReadByte(); + + if(!self.count) + self.count = 40; + + generator_changeteam(); + generator_precache(); + generator_construct(); + } + + if(sf & GSF_STATUS) + { - float _tmp; ++ int _tmp; + _tmp = ReadByte(); + if(_tmp != self.team) + { + self.team = _tmp; + generator_changeteam(); + } + + _tmp = ReadByte(); + + if(_tmp != self.health) + generator_damage(_tmp); + + self.health = _tmp; + } +} diff --cc qcsrc/client/generator.qh index 6ba1ce858a,0000000000..e4e1611f31 mode 100644,000000..100644 --- a/qcsrc/client/generator.qh +++ b/qcsrc/client/generator.qh @@@ -1,8 -1,0 +1,11 @@@ ++#ifndef GENERATOR_H ++#define GENERATOR_H +const vector GENERATOR_MIN = '-52 -52 -14'; +const vector GENERATOR_MAX = '52 52 75'; + - float GSF_STATUS = 4; - float GSF_SETUP = 8; ++const int GSF_STATUS = 4; ++const int GSF_SETUP = 8; + +void ent_generator(); - void generator_precache(); ++void generator_precache(); ++#endif diff --cc qcsrc/client/hud.qc index a85fca6888,024a0761e4..ff8cb727e3 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@@ -1,3 -1,11 +1,12 @@@ ++#include "mapvoting.qh" + #include "scoreboard.qh" + #include "teamradar.qh" + #include "../common/buffs.qh" + #include "../common/counting.qh" + #include "../common/mapinfo.qh" + #include "../common/nades.qh" + #include "../server/t_items.qh" + /* ================== Misc HUD functions @@@ -1870,167 -1875,6 +1876,167 @@@ void HUD_Timer(void // Radar (#6) // + +float HUD_Radar_Clickable() +{ + return hud_panel_radar_mouse && !hud_panel_radar_temp_hidden; +} + +void HUD_Radar_Show_Maximized(float show,float clickable) +{ + hud_panel_radar_maximized = show; + hud_panel_radar_temp_hidden = 0; + + if ( show ) + { + if (clickable) + { + if(autocvar_hud_cursormode) + setcursormode(1); + hud_panel_radar_mouse = 1; + } + } + else if ( hud_panel_radar_mouse ) + { + hud_panel_radar_mouse = 0; + mouseClicked = 0; + if(autocvar_hud_cursormode) + if(!mv_active) + setcursormode(0); + } +} +void HUD_Radar_Hide_Maximized() +{ + HUD_Radar_Show_Maximized(false,false); +} + + +float HUD_Radar_InputEvent(float bInputType, float nPrimary, float nSecondary) +{ + if(!hud_panel_radar_maximized || !hud_panel_radar_mouse || + autocvar__hud_configure || mv_active) + return false; + + if(bInputType == 3) + { + mousepos_x = nPrimary; + mousepos_y = nSecondary; + return true; + } + + if(nPrimary == K_MOUSE1) + { + if(bInputType == 0) // key pressed + mouseClicked |= S_MOUSE1; + else if(bInputType == 1) // key released + mouseClicked -= (mouseClicked & S_MOUSE1); + } + else if(nPrimary == K_MOUSE2) + { + if(bInputType == 0) // key pressed + mouseClicked |= S_MOUSE2; + else if(bInputType == 1) // key released + mouseClicked -= (mouseClicked & S_MOUSE2); + } + else if ( nPrimary == K_ESCAPE && bInputType == 0 ) + { + HUD_Radar_Hide_Maximized(); + } + else + { + // allow console/use binds to work without hiding the map + string con_keys; + float keys; + float i; + con_keys = strcat(findkeysforcommand("toggleconsole", 0)," ",findkeysforcommand("+use", 0)) ; + keys = tokenize(con_keys); // findkeysforcommand returns data for this + for (i = 0; i < keys; ++i) + { + if(nPrimary == stof(argv(i))) + return false; + } + + if ( getstati(STAT_HEALTH) <= 0 ) + { + // Show scoreboard + if ( bInputType < 2 ) + { + con_keys = findkeysforcommand("+showscores", 0); + keys = tokenize(con_keys); + for (i = 0; i < keys; ++i) + { + if ( nPrimary == stof(argv(i)) ) + { + hud_panel_radar_temp_hidden = bInputType == 0; + return false; + } + } + } + } + else if ( bInputType == 0 ) + HUD_Radar_Hide_Maximized(); + + return false; + } + + return true; +} + +void HUD_Radar_Mouse() +{ + if ( !hud_panel_radar_mouse ) return; + if(mv_active) return; + + if ( intermission ) + { + HUD_Radar_Hide_Maximized(); + return; + } + + if(mouseClicked & S_MOUSE2) + { + HUD_Radar_Hide_Maximized(); + return; + } + + if(!autocvar_hud_cursormode) + { + mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed; + + mousepos_x = bound(0, mousepos_x, vid_conwidth); + mousepos_y = bound(0, mousepos_y, vid_conheight); + } + + HUD_Panel_UpdateCvars(); + + + panel_size = autocvar_hud_panel_radar_maximized_size; + panel_size_x = bound(0.2, panel_size_x, 1) * vid_conwidth; + panel_size_y = bound(0.2, panel_size_y, 1) * vid_conheight; + panel_pos_x = (vid_conwidth - panel_size_x) / 2; + panel_pos_y = (vid_conheight - panel_size_y) / 2; + + if(mouseClicked & S_MOUSE1) + { + // click outside + if ( mousepos_x < panel_pos_x || mousepos_x > panel_pos_x + panel_size_x || + mousepos_y < panel_pos_y || mousepos_y > panel_pos_y + panel_size_y ) + { + HUD_Radar_Hide_Maximized(); + return; + } + vector pos = teamradar_texcoord_to_3dcoord(teamradar_2dcoord_to_texcoord(mousepos),view_origin_z); + localcmd(sprintf("cmd ons_spawn %f %f %f",pos_x,pos_y,pos_z)); + + HUD_Radar_Hide_Maximized(); + return; + } + + - const vector cursorsize = '32 32 0'; - drawpic(mousepos-'8 4 0', strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), cursorsize, '1 1 1', 0.8, DRAWFLAG_NORMAL); ++ const vector cursor_size = '32 32 0'; ++ drawpic(mousepos-'8 4 0', strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), cursor_size, '1 1 1', 0.8, DRAWFLAG_NORMAL); +} + void HUD_Radar(void) { if (!autocvar__hud_configure) @@@ -2237,21 -2059,6 +2243,21 @@@ draw_teamradar_player(view_origin, view_angles, '1 1 1'); drawresetcliparea(); + + if ( hud_panel_radar_mouse ) + { + string message = "Click to select teleport destination"; + + if ( getstati(STAT_HEALTH) <= 0 ) + { + message = "Click to select spawn location"; + } + - drawcolorcodedstring(pos + '0.5 0 0' * (mySize_x - stringwidth(message, TRUE, hud_fontsize)) - '0 1 0' * hud_fontsize_y * 2, ++ drawcolorcodedstring(pos + '0.5 0 0' * (mySize_x - stringwidth(message, true, hud_fontsize)) - '0 1 0' * hud_fontsize_y * 2, + message, hud_fontsize, hud_panel_radar_foreground_alpha, DRAWFLAG_NORMAL); + + hud_panel_radar_bottom = pos_y + mySize_y + hud_fontsize_y; + } } // Score (#7) diff --cc qcsrc/client/hud.qh index f9bd859c52,16a7645fd9..251e9f23be --- a/qcsrc/client/hud.qh +++ b/qcsrc/client/hud.qh @@@ -1,21 -1,21 +1,26 @@@ - #define HUD_PANEL_MAX 24 + #ifndef HUD_H + #define HUD_H + + #include "../common/weapons/weapons.qh" + + const int HUD_PANEL_MAX = 24; entity hud_panel[HUD_PANEL_MAX]; - #define HUD_PANEL_FIRST 0 - float HUD_PANEL_NUM; - float HUD_PANEL_LAST; + const int HUD_PANEL_FIRST = 0; + int HUD_PANEL_NUM; + int HUD_PANEL_LAST; - float panel_order[HUD_PANEL_MAX]; + int panel_order[HUD_PANEL_MAX]; string hud_panelorder_prev; --float hud_draw_maximized; --float hud_panel_radar_maximized; - float hud_panel_radar_mouse; -float chat_panel_modified; -float radar_panel_modified; ++bool hud_draw_maximized; ++bool hud_panel_radar_maximized; ++bool hud_panel_radar_mouse; +float hud_panel_radar_bottom; - float hud_panel_radar_temp_hidden; - float chat_panel_modified; - float radar_panel_modified; ++bool hud_panel_radar_temp_hidden; ++bool chat_panel_modified; ++bool radar_panel_modified; + +void HUD_Radar_Hide_Maximized(); vector mousepos; vector panel_click_distance; // mouse cursor distance from the top left corner of the panel (saved only upon a click) diff --cc qcsrc/client/main.qc index 0000000000,13f9545faa..d0a0a418cb mode 000000,100644..100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@@ -1,0 -1,1319 +1,1327 @@@ ++#include "controlpoint.qh" ++#include "generator.qh" + #include "mapvoting.qh" + #include "modeleffects.qh" + #include "particles.qh" + #include "scoreboard.qh" + #include "shownames.qh" + #include "target_music.qh" + #include "tturrets.qh" + #include "tuba.qh" + #include "wall.qh" + #include "waypointsprites.qh" + + #include "vehicles/vehicles.qh" + + #include "../server/vehicles/bumblebee.qh" + + #include "../common/net_notice.qh" + + #include "../common/monsters/monsters.qh" + + #include "../warpzonelib/client.qh" + + // -------------------------------------------------------------------------- + // BEGIN REQUIRED CSQC FUNCTIONS + //include "main.qh" + + entity clearentity_ent; + void clearentity(entity e) + { + if (!clearentity_ent) + { + clearentity_ent = spawn(); + clearentity_ent.classname = "clearentity"; + } + int n = e.entnum; + copyentity(clearentity_ent, e); + e.entnum = n; + } + + #define DP_CSQC_ENTITY_REMOVE_IS_B0RKED + void menu_show_error() + { + drawstring('0 200 0', _("ERROR - MENU IS VISIBLE BUT NO MENU WAS DEFINED!"), '8 8 0', '1 0 0', 1, 0); + } + + // CSQC_Init : Called every time the CSQC code is initialized (essentially at map load) + // Useful for precaching things + + void menu_sub_null() + { + } + + string forcefog; + void WaypointSprite_Load(); + void ConsoleCommand_macro_init(); + void CSQC_Init(void) + { + prvm_language = cvar_string("prvm_language"); + + #ifdef WATERMARK + dprintf("^4CSQC Build information: ^1%s\n", WATERMARK); + #endif + + int i; + + binddb = db_create(); + tempdb = db_create(); + ClientProgsDB = db_load("client.db"); + compressShortVector_init(); + + draw_endBoldFont(); + menu_visible = false; + menu_show = menu_show_error; + menu_action = func_null; + + for(i = 0; i < 255; ++i) + if(getplayerkeyvalue(i, "viewentity") == "") + break; + maxclients = i; + + //registercommand("hud_configure"); + //registercommand("hud_save"); + //registercommand("menu_action"); + + ConsoleCommand_macro_init(); + + registercvar("hud_usecsqc", "1"); + registercvar("scoreboard_columns", "default"); + + registercvar("cl_nade_type", "3"); + registercvar("cl_pokenade_type", "zombie"); + + gametype = 0; + + // hud_fields uses strunzone on the titles! + for(i = 0; i < MAX_HUD_FIELDS; ++i) + hud_title[i] = strzone("(null)"); + + Cmd_HUD_SetFields(0); + + postinit = false; + + calledhooks = 0; + + teams = Sort_Spawn(); + players = Sort_Spawn(); + + GetTeam(NUM_SPECTATOR, true); // add specs first + + // needs to be done so early because of the constants they create + CALL_ACCUMULATED_FUNCTION(RegisterWeapons); + CALL_ACCUMULATED_FUNCTION(RegisterMonsters); + CALL_ACCUMULATED_FUNCTION(RegisterGametypes); + CALL_ACCUMULATED_FUNCTION(RegisterNotifications); + CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); + CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels); + CALL_ACCUMULATED_FUNCTION(RegisterBuffs); + + WaypointSprite_Load(); + + // precaches + precache_model("null"); + precache_sound("misc/hit.wav"); + precache_sound("misc/typehit.wav"); + ++ generator_precache(); + Projectile_Precache(); + Hook_Precache(); + GibSplash_Precache(); + Casings_Precache(); + Vehicles_Precache(); + turrets_precache(); + Tuba_Precache(); + CSQCPlayer_Precache(); + + if(autocvar_cl_reticle) + { + precache_pic("gfx/reticle_normal"); + // weapon reticles are precached in weapon files + } + + get_mi_min_max_texcoords(1); // try the CLEVER way first + minimapname = strcat("gfx/", mi_shortname, "_radar.tga"); + shortmapname = mi_shortname; + + if(precache_pic(minimapname) == "") + { + // but maybe we have a non-clever minimap + minimapname = strcat("gfx/", mi_shortname, "_mini.tga"); + if(precache_pic(minimapname) == "") + minimapname = ""; // FAIL + else + get_mi_min_max_texcoords(0); // load new texcoords + } + + mi_center = (mi_min + mi_max) * 0.5; + mi_scale = mi_max - mi_min; + minimapname = strzone(minimapname); + + WarpZone_Init(); + + hud_skin_path = strzone(strcat("gfx/hud/", autocvar_hud_skin)); + hud_configure_prev = -1; + + draw_currentSkin = strzone(strcat("gfx/menu/", cvar_string("menu_skin"))); + } + + // CSQC_Shutdown : Called every time the CSQC code is shutdown (changing maps, quitting, etc) + void Shutdown(void) + { + WarpZone_Shutdown(); + + remove(teams); + remove(players); + db_close(binddb); + db_close(tempdb); + if(autocvar_cl_db_saveasdump) + db_dump(ClientProgsDB, "client.db"); + else + db_save(ClientProgsDB, "client.db"); + db_close(ClientProgsDB); + + if(camera_active) + cvar_set("chase_active",ftos(chase_active_backup)); + + // unset the event chasecam's chase_active + if(autocvar_chase_active < 0) + cvar_set("chase_active", "0"); + + if (!isdemo()) + { + if (!(calledhooks & HOOK_START)) + localcmd("\n_cl_hook_gamestart nop\n"); + if (!(calledhooks & HOOK_END)) + localcmd("\ncl_hook_gameend\n"); + } + } + + .float has_team; + float SetTeam(entity o, int Team) + { + entity tm; + if(teamplay) + { + switch(Team) + { + case -1: + case NUM_TEAM_1: + case NUM_TEAM_2: + case NUM_TEAM_3: + case NUM_TEAM_4: + break; + default: + if(GetTeam(Team, false) == world) + { + dprintf("trying to switch to unsupported team %d\n", Team); + Team = NUM_SPECTATOR; + } + break; + } + } + else + { + switch(Team) + { + case -1: + case 0: + break; + default: + if(GetTeam(Team, false) == world) + { + dprintf("trying to switch to unsupported team %d\n", Team); + Team = NUM_SPECTATOR; + } + break; + } + } + if(Team == -1) // leave + { + if(o.has_team) + { + tm = GetTeam(o.team, false); + tm.team_size -= 1; + o.has_team = 0; + return true; + } + } + else + { + if (!o.has_team) + { + o.team = Team; + tm = GetTeam(Team, true); + tm.team_size += 1; + o.has_team = 1; + return true; + } + else if(Team != o.team) + { + tm = GetTeam(o.team, false); + tm.team_size -= 1; + o.team = Team; + tm = GetTeam(Team, true); + tm.team_size += 1; + return true; + } + } + return false; + } + + void Playerchecker_Think() + { + int i; + entity e; + for(i = 0; i < maxclients; ++i) + { + e = playerslots[i]; + if(GetPlayerName(i) == "") + { + if(e.sort_prev) + { + // player disconnected + SetTeam(e, -1); + RemovePlayer(e); + e.sort_prev = world; + //e.gotscores = 0; + } + } + else + { + if (!e.sort_prev) + { + // player connected + if (!e) + playerslots[i] = e = spawn(); + e.sv_entnum = i; + e.ping = 0; + e.ping_packetloss = 0; + e.ping_movementloss = 0; + //e.gotscores = 0; // we might already have the scores... + SetTeam(e, GetPlayerColor(i)); // will not hurt; later updates come with HUD_UpdatePlayerTeams + RegisterPlayer(e); + HUD_UpdatePlayerPos(e); + } + } + } + self.nextthink = time + 0.2; + } + + void Porto_Init(); + void TrueAim_Init(); + void PostInit(void) + { + entity playerchecker; + playerchecker = spawn(); + playerchecker.think = Playerchecker_Think; + playerchecker.nextthink = time + 0.2; + + Porto_Init(); + TrueAim_Init(); + + postinit = true; + } + + // CSQC_InputEvent : Used to perform actions based on any key pressed, key released and mouse on the client. + // Return value should be 1 if CSQC handled the input, otherwise return 0 to have the input passed to the engine. + // All keys are in ascii. + // bInputType = 0 is key pressed, 1 is key released, 2 and 3 are mouse input. + // In the case of keyboard input, nPrimary is the ascii code, and nSecondary is 0. + // In the case of mouse input, nPrimary is xdelta, nSecondary is ydelta. + // In the case of mouse input after a setcursormode(1) call, nPrimary is xpos, nSecondary is ypos. + float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary) + { + float bSkipKey; + bSkipKey = false; + + if (HUD_Panel_InputEvent(bInputType, nPrimary, nSecondary)) + return true; + ++ if ( HUD_Radar_InputEvent(bInputType, nPrimary, nSecondary) ) ++ return true; ++ + if (MapVote_InputEvent(bInputType, nPrimary, nSecondary)) + return true; + + if(menu_visible && menu_action) + if(menu_action(bInputType, nPrimary, nSecondary)) + return true; + + return bSkipKey; + } + + // END REQUIRED CSQC FUNCTIONS + // -------------------------------------------------------------------------- + + // -------------------------------------------------------------------------- + // BEGIN OPTIONAL CSQC FUNCTIONS + void Ent_RemoveEntCS() + { + entcs_receiver[self.sv_entnum] = world; + } + void Ent_ReadEntCS() + { + int sf; + InterpolateOrigin_Undo(); + + self.classname = "entcs_receiver"; + sf = ReadByte(); + + if(sf & 1) + self.sv_entnum = ReadByte(); + if(sf & 2) + { + self.origin_x = ReadShort(); + self.origin_y = ReadShort(); + self.origin_z = ReadShort(); + setorigin(self, self.origin); + } + if(sf & 4) + { + self.angles_y = ReadByte() * 360.0 / 256; + self.angles_x = self.angles_z = 0; + } + if(sf & 8) + self.healthvalue = ReadByte() * 10; + if(sf & 16) + self.armorvalue = ReadByte() * 10; + + entcs_receiver[self.sv_entnum] = self; + self.entremove = Ent_RemoveEntCS; + self.iflags |= IFLAG_ORIGIN; + + InterpolateOrigin_Note(); + } + + void Ent_Remove(); + + void Ent_RemovePlayerScore() + { + if(self.owner) { + SetTeam(self.owner, -1); + self.owner.gotscores = 0; + for(int i = 0; i < MAX_SCORE; ++i) { + self.owner.(scores[i]) = 0; // clear all scores + } + } + } + + void Ent_ReadPlayerScore() + { + int i, n; + bool isNew; + entity o; + + // damnit -.- don't want to go change every single .sv_entnum in hud.qc AGAIN + // (no I've never heard of M-x replace-string, sed, or anything like that) + isNew = !self.owner; // workaround for DP bug + n = ReadByte()-1; + + #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED + if(!isNew && n != self.sv_entnum) + { + //print("A CSQC entity changed its owner!\n"); + printf("A CSQC entity changed its owner! (edict: %d, classname: %s)\n", num_for_edict(self), self.classname); + isNew = true; + Ent_Remove(); + self.enttype = ENT_CLIENT_SCORES; + } + #endif + + self.sv_entnum = n; + + if (!(playerslots[self.sv_entnum])) + playerslots[self.sv_entnum] = spawn(); + o = self.owner = playerslots[self.sv_entnum]; + o.sv_entnum = self.sv_entnum; + o.gotscores = 1; + + //if (!o.sort_prev) + // RegisterPlayer(o); + //playerchecker will do this for us later, if it has not already done so + + int sf, lf; + #if MAX_SCORE <= 8 + sf = ReadByte(); + lf = ReadByte(); + #else + sf = ReadShort(); + lf = ReadShort(); + #endif + int p; + for(i = 0, p = 1; i < MAX_SCORE; ++i, p *= 2) + if(sf & p) + { + if(lf & p) + o.(scores[i]) = ReadInt24_t(); + else + o.(scores[i]) = ReadChar(); + } + + if(o.sort_prev) + HUD_UpdatePlayerPos(o); // if not registered, we cannot do this yet! + + self.entremove = Ent_RemovePlayerScore; + } + + void Ent_ReadTeamScore() + { + int i; + entity o; + + self.team = ReadByte(); + o = self.owner = GetTeam(self.team, true); // these team numbers can always be trusted + + int sf, lf; + #if MAX_TEAMSCORE <= 8 + sf = ReadByte(); + lf = ReadByte(); + #else + sf = ReadShort(); + lf = ReadShort(); + #endif + int p; + for(i = 0, p = 1; i < MAX_TEAMSCORE; ++i, p *= 2) + if(sf & p) + { + if(lf & p) + o.(teamscores[i]) = ReadInt24_t(); + else + o.(teamscores[i]) = ReadChar(); + } + + HUD_UpdateTeamPos(o); + } + + void Ent_ClientData() + { + float newspectatee_status; + + int f = ReadByte(); + + scoreboard_showscores_force = (f & 1); + + if(f & 2) + { + newspectatee_status = ReadByte(); + if(newspectatee_status == player_localnum + 1) + newspectatee_status = -1; // observing + } + else + newspectatee_status = 0; + + spectatorbutton_zoom = (f & 4); + + if(f & 8) + { + angles_held_status = 1; + angles_held.x = ReadAngle(); + angles_held.y = ReadAngle(); + angles_held.z = 0; + } + else + angles_held_status = 0; + + if(newspectatee_status != spectatee_status) + { + // clear race stuff + race_laptime = 0; + race_checkpointtime = 0; + } + if (autocvar_hud_panel_healtharmor_progressbar_gfx) + { + if ( (spectatee_status == -1 && newspectatee_status > 0) //before observing, now spectating + || (spectatee_status > 0 && newspectatee_status > 0 && spectatee_status != newspectatee_status) //changed spectated player + ) + prev_p_health = -1; + else if(spectatee_status && !newspectatee_status) //before observing/spectating, now playing + prev_health = -1; + } + spectatee_status = newspectatee_status; + + // we could get rid of spectatee_status, and derive it from player_localentnum and player_localnum + } + + void Ent_Nagger() + { + int i, j, b, f; + + int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS + + if(!(nags & 4)) + { + if(vote_called_vote) + strunzone(vote_called_vote); + vote_called_vote = string_null; + vote_active = 0; + } + else + { + vote_active = 1; + } + + if(nags & 64) + { + vote_yescount = ReadByte(); + vote_nocount = ReadByte(); + vote_needed = ReadByte(); + vote_highlighted = ReadChar(); + } + + if(nags & 128) + { + if(vote_called_vote) + strunzone(vote_called_vote); + vote_called_vote = strzone(ColorTranslateRGB(ReadString())); + } + + if(nags & 1) + { + for(j = 0; j < maxclients; ++j) + if(playerslots[j]) + playerslots[j].ready = 1; + for(i = 1; i <= maxclients; i += 8) + { + f = ReadByte(); + for(j = i-1, b = 1; b < 256; b *= 2, ++j) + if (!(f & b)) + if(playerslots[j]) + playerslots[j].ready = 0; + } + } + + ready_waiting = (nags & 1); + ready_waiting_for_me = (nags & 2); + vote_waiting = (nags & 4); + vote_waiting_for_me = (nags & 8); + warmup_stage = (nags & 16); + } + + void Ent_EliminatedPlayers() + { + int i, j, b, f; + + int sf = ReadByte(); + if(sf & 1) + { + for(j = 0; j < maxclients; ++j) + if(playerslots[j]) + playerslots[j].eliminated = 1; + for(i = 1; i <= maxclients; i += 8) + { + f = ReadByte(); + for(j = i-1, b = 1; b < 256; b *= 2, ++j) + if (!(f & b)) + if(playerslots[j]) + playerslots[j].eliminated = 0; + } + } + } + + void Ent_RandomSeed() + { + float s; + prandom_debug(); + s = ReadShort(); + psrandom(s); + } + + void Ent_ReadAccuracy(void) + { + int f, w; + int sf = ReadInt24_t(); + if(sf == 0) + { + for(w = 0; w <= WEP_LAST - WEP_FIRST; ++w) + weapon_accuracy[w] = -1; + return; + } + + for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w) + { + if(sf & f) + { + int b = ReadByte(); + if(b == 0) + weapon_accuracy[w] = -1; + else if(b == 255) + weapon_accuracy[w] = 1.0; // no better error handling yet, sorry + else + weapon_accuracy[w] = (b - 1.0) / 100.0; + } + if(f == 0x800000) + f = 1; + else + f *= 2; + } + } + + void Spawn_Draw(void) + { + pointparticles(self.cnt, self.origin + '0 0 28', '0 0 2', bound(0, frametime, 0.1)); + } + + void Ent_ReadSpawnPoint(float is_new) // entity for spawnpoint + { + float teamnum = (ReadByte() - 1); + vector spn_origin; + spn_origin.x = ReadShort(); + spn_origin.y = ReadShort(); + spn_origin.z = ReadShort(); + + if(is_new) + { + self.origin = spn_origin; + setsize(self, PL_MIN, PL_MAX); + droptofloor(); + + /*if(autocvar_cl_spawn_point_model) // needs a model first + { + self.mdl = "models/spawnpoint.md3"; + self.colormod = Team_ColorRGB(teamnum); + precache_model(self.mdl); + setmodel(self, self.mdl); + self.drawmask = MASK_NORMAL; + //self.movetype = MOVETYPE_NOCLIP; + //self.draw = Spawn_Draw; + }*/ + if(autocvar_cl_spawn_point_particles) + { + if((serverflags & SERVERFLAG_TEAMPLAY)) + { + switch(teamnum) + { + case NUM_TEAM_1: self.cnt = particleeffectnum("spawn_point_red"); break; + case NUM_TEAM_2: self.cnt = particleeffectnum("spawn_point_blue"); break; + case NUM_TEAM_3: self.cnt = particleeffectnum("spawn_point_yellow"); break; + case NUM_TEAM_4: self.cnt = particleeffectnum("spawn_point_pink"); break; + default: self.cnt = particleeffectnum("spawn_point_neutral"); break; + } + } + else { self.cnt = particleeffectnum("spawn_point_neutral"); } + + self.draw = Spawn_Draw; + } + } + + //printf("Ent_ReadSpawnPoint(is_new = %d); origin = %s, team = %d, effect = %d\n", is_new, vtos(self.origin), teamnum, self.cnt); + } + + void Ent_ReadSpawnEvent(float is_new) + { + // If entnum is 0, ONLY do the local spawn actions + // this way the server can disable the sending of + // spawn origin or such to clients if wanted. + float entnum = ReadByte(); + + if(entnum) + { + self.origin_x = ReadShort(); + self.origin_y = ReadShort(); + self.origin_z = ReadShort(); + + if(is_new) + { + float teamnum = GetPlayerColor(entnum - 1); + + if(autocvar_cl_spawn_event_particles) + { + switch(teamnum) + { + case NUM_TEAM_1: pointparticles(particleeffectnum("spawn_event_red"), self.origin, '0 0 0', 1); break; + case NUM_TEAM_2: pointparticles(particleeffectnum("spawn_event_blue"), self.origin, '0 0 0', 1); break; + case NUM_TEAM_3: pointparticles(particleeffectnum("spawn_event_yellow"), self.origin, '0 0 0', 1); break; + case NUM_TEAM_4: pointparticles(particleeffectnum("spawn_event_pink"), self.origin, '0 0 0', 1); break; + default: pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); break; + } + } + if(autocvar_cl_spawn_event_sound) + { + sound(self, CH_TRIGGER, "misc/spawn.wav", VOL_BASE, ATTEN_NORM); + } + } + } + + // local spawn actions + if(is_new && (!entnum || (entnum == player_localentnum))) + { + zoomin_effect = 1; + current_viewzoom = (1 / bound(1, autocvar_cl_spawnzoom_factor, 16)); + + if(autocvar_cl_unpress_zoom_on_spawn) + { + localcmd("-zoom\n"); + button_zoom = false; + } + } - ++ HUD_Radar_Hide_Maximized(); + //printf("Ent_ReadSpawnEvent(is_new = %d); origin = %s, entnum = %d, localentnum = %d\n", is_new, vtos(self.origin), entnum, player_localentnum); + } + + // CSQC_Ent_Update : Called every frame that the server has indicated an update to the SSQC / CSQC entity has occured. + // The only parameter reflects if the entity is "new" to the client, meaning it just came into the client's PVS. + void Ent_RadarLink(); + void Ent_Init(); + void Ent_ScoresInfo(); + void CSQC_Ent_Update(float bIsNewEntity) + { + float t; + float savetime; + t = ReadByte(); + + if(autocvar_developer_csqcentities) + printf("CSQC_Ent_Update(%d) with self=%i self.entnum=%d self.enttype=%d t=%d\n", bIsNewEntity, self, self.entnum, self.enttype, t); + + // set up the "time" global for received entities to be correct for interpolation purposes + savetime = time; + if(servertime) + { + time = servertime; + } + else + { + serverprevtime = time; + serverdeltatime = getstatf(STAT_MOVEVARS_TICRATE) * getstatf(STAT_MOVEVARS_TIMESCALE); + time = serverprevtime + serverdeltatime; + } + + #ifdef DP_CSQC_ENTITY_REMOVE_IS_B0RKED + if(self.enttype) + { + if(t != self.enttype || bIsNewEntity) + { + //print("A CSQC entity changed its type!\n"); + printf("A CSQC entity changed its type! (edict: %d, server: %d, type: %d -> %d)\n", num_for_edict(self), self.entnum, self.enttype, t); + Ent_Remove(); + clearentity(self); + bIsNewEntity = 1; + } + } + else + { + if(!bIsNewEntity) + { + printf("A CSQC entity appeared out of nowhere! (edict: %d, server: %d, type: %d)\n", num_for_edict(self), self.entnum, t); + bIsNewEntity = 1; + } + } + #endif + self.enttype = t; + switch(t) + { + case ENT_CLIENT_ENTCS: Ent_ReadEntCS(); break; + case ENT_CLIENT_SCORES: Ent_ReadPlayerScore(); break; + case ENT_CLIENT_TEAMSCORES: Ent_ReadTeamScore(); break; + case ENT_CLIENT_POINTPARTICLES: Ent_PointParticles(); break; + case ENT_CLIENT_RAINSNOW: Ent_RainOrSnow(); break; + case ENT_CLIENT_LASER: Ent_Laser(); break; + case ENT_CLIENT_NAGGER: Ent_Nagger(); break; + case ENT_CLIENT_ELIMINATEDPLAYERS: Ent_EliminatedPlayers(); break; + case ENT_CLIENT_WAYPOINT: Ent_WaypointSprite(); break; + case ENT_CLIENT_RADARLINK: Ent_RadarLink(); break; + case ENT_CLIENT_PROJECTILE: Ent_Projectile(); break; + case ENT_CLIENT_GIBSPLASH: Ent_GibSplash(bIsNewEntity); break; + case ENT_CLIENT_DAMAGEINFO: Ent_DamageInfo(bIsNewEntity); break; + case ENT_CLIENT_CASING: Ent_Casing(bIsNewEntity); break; + case ENT_CLIENT_INIT: Ent_Init(); break; + case ENT_CLIENT_SCORES_INFO: Ent_ScoresInfo(); break; + case ENT_CLIENT_MAPVOTE: Ent_MapVote(); break; + case ENT_CLIENT_CLIENTDATA: Ent_ClientData(); break; + case ENT_CLIENT_RANDOMSEED: Ent_RandomSeed(); break; + case ENT_CLIENT_WALL: Ent_Wall(); break; + case ENT_CLIENT_MODELEFFECT: Ent_ModelEffect(bIsNewEntity); break; + case ENT_CLIENT_TUBANOTE: Ent_TubaNote(bIsNewEntity); break; + case ENT_CLIENT_WARPZONE: WarpZone_Read(bIsNewEntity); break; + case ENT_CLIENT_WARPZONE_CAMERA: WarpZone_Camera_Read(bIsNewEntity); break; + case ENT_CLIENT_WARPZONE_TELEPORTED: WarpZone_Teleported_Read(bIsNewEntity); break; + case ENT_CLIENT_TRIGGER_MUSIC: Ent_ReadTriggerMusic(); break; + case ENT_CLIENT_HOOK: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_HOOK); break; + case ENT_CLIENT_ARC_BEAM: Ent_ReadArcBeam(bIsNewEntity); break; + case ENT_CLIENT_ACCURACY: Ent_ReadAccuracy(); break; + case ENT_CLIENT_AUXILIARYXHAIR: Net_AuXair2(bIsNewEntity); break; + case ENT_CLIENT_TURRET: ent_turret(); break; ++ case ENT_CLIENT_GENERATOR: ent_generator(); break; ++ case ENT_CLIENT_CONTROLPOINT_ICON: ent_cpicon(); break; + case ENT_CLIENT_MODEL: CSQCModel_Read(bIsNewEntity); break; + case ENT_CLIENT_ITEM: ItemRead(bIsNewEntity); break; + case ENT_CLIENT_BUMBLE_RAYGUN: bumble_raygun_read(bIsNewEntity); break; + case ENT_CLIENT_SPAWNPOINT: Ent_ReadSpawnPoint(bIsNewEntity); break; + case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break; + case ENT_CLIENT_NOTIFICATION: Read_Notification(bIsNewEntity); break; + case ENT_CLIENT_HEALING_ORB: ent_healer(); break; + + default: + //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype)); + error(sprintf("Unknown entity type in CSQC_Ent_Update (enttype: %d, edict: %d, classname: %s)\n", self.enttype, num_for_edict(self), self.classname)); + break; + } + + time = savetime; + } + // Destructor, but does NOT deallocate the entity by calling remove(). Also + // used when an entity changes its type. For an entity that someone interacts + // with others, make sure it can no longer do so. + void Ent_Remove() + { + if(self.entremove) + self.entremove(); + + if(self.skeletonindex) + { + skel_delete(self.skeletonindex); + self.skeletonindex = 0; + } + + if(self.snd_looping > 0) + { + sound(self, self.snd_looping, "misc/null.wav", VOL_BASE, autocvar_g_jetpack_attenuation); + self.snd_looping = 0; + } + + self.enttype = 0; + self.classname = ""; + self.draw = menu_sub_null; + self.entremove = menu_sub_null; + // TODO possibly set more stuff to defaults + } + // CSQC_Ent_Remove : Called when the server requests a SSQC / CSQC entity to be removed. Essentially call remove(self) as well. + void CSQC_Ent_Remove() + { + if(autocvar_developer_csqcentities) + printf("CSQC_Ent_Remove() with self=%i self.entnum=%d self.enttype=%d\n", self, self.entnum, self.enttype); + + if(wasfreed(self)) + { + print("WARNING: CSQC_Ent_Remove called for already removed entity. Packet loss?\n"); + return; + } + if(self.enttype) + Ent_Remove(); + remove(self); + } + + void Gamemode_Init() + { + if (!isdemo()) + { + if(!(calledhooks & HOOK_START)) + localcmd("\n_cl_hook_gamestart ", MapInfo_Type_ToString(gametype), "\n"); + calledhooks |= HOOK_START; + } + } + // CSQC_Parse_StuffCmd : Provides the stuffcmd string in the first parameter that the server provided. To execute standard behavior, simply execute localcmd with the string. + void CSQC_Parse_StuffCmd(string strMessage) + { + if(autocvar_developer_csqcentities) + printf("CSQC_Parse_StuffCmd(\"%s\")\n", strMessage); + + localcmd(strMessage); + } + // CSQC_Parse_Print : Provides the print string in the first parameter that the server provided. To execute standard behavior, simply execute print with the string. + void CSQC_Parse_Print(string strMessage) + { + if(autocvar_developer_csqcentities) + printf("CSQC_Parse_Print(\"%s\")\n", strMessage); + + print(ColorTranslateRGB(strMessage)); + } + + // CSQC_Parse_CenterPrint : Provides the centerprint_hud string in the first parameter that the server provided. + void CSQC_Parse_CenterPrint(string strMessage) + { + if(autocvar_developer_csqcentities) + printf("CSQC_Parse_CenterPrint(\"%s\")\n", strMessage); + + centerprint_hud(strMessage); + } + + string notranslate_fogcmd1 = "\nfog "; + string notranslate_fogcmd2 = "\nr_fog_exp2 0\nr_drawfog 1\n"; + void Fog_Force() + { + // TODO somehow thwart prvm_globalset client ... + + if(autocvar_cl_orthoview && autocvar_cl_orthoview_nofog) + { localcmd("\nr_drawfog 0\n"); } + else if(forcefog != "") + { localcmd(strcat(notranslate_fogcmd1, forcefog, notranslate_fogcmd2)); } + } + + void Gamemode_Init(); + void Ent_ScoresInfo() + { + int i; + self.classname = "ent_client_scores_info"; + gametype = ReadInt24_t(); + HUD_ModIcons_SetFunc(); + for(i = 0; i < MAX_SCORE; ++i) + { + if(scores_label[i]) + strunzone(scores_label[i]); + scores_label[i] = strzone(ReadString()); + scores_flags[i] = ReadByte(); + } + for(i = 0; i < MAX_TEAMSCORE; ++i) + { + if(teamscores_label[i]) + strunzone(teamscores_label[i]); + teamscores_label[i] = strzone(ReadString()); + teamscores_flags[i] = ReadByte(); + } + HUD_InitScores(); + Gamemode_Init(); + } + + void Ent_Init() + { + self.classname = "ent_client_init"; + + nb_pb_period = ReadByte() / 32; //Accuracy of 1/32th + + hook_shotorigin[0] = decompressShotOrigin(ReadInt24_t()); + hook_shotorigin[1] = decompressShotOrigin(ReadInt24_t()); + hook_shotorigin[2] = decompressShotOrigin(ReadInt24_t()); + hook_shotorigin[3] = decompressShotOrigin(ReadInt24_t()); + arc_shotorigin[0] = decompressShotOrigin(ReadInt24_t()); + arc_shotorigin[1] = decompressShotOrigin(ReadInt24_t()); + arc_shotorigin[2] = decompressShotOrigin(ReadInt24_t()); + arc_shotorigin[3] = decompressShotOrigin(ReadInt24_t()); + + if(forcefog) + strunzone(forcefog); + forcefog = strzone(ReadString()); + + armorblockpercent = ReadByte() / 255.0; + + g_balance_mortar_bouncefactor = ReadCoord(); + g_balance_mortar_bouncestop = ReadCoord(); + g_balance_electro_secondary_bouncefactor = ReadCoord(); + g_balance_electro_secondary_bouncestop = ReadCoord(); + + vortex_scope = !ReadByte(); + rifle_scope = !ReadByte(); + + serverflags = ReadByte(); + + minelayer_maxmines = ReadByte(); + + hagar_maxrockets = ReadByte(); + + g_trueaim_minrange = ReadCoord(); + g_balance_porto_secondary = ReadByte(); + + if(!postinit) + PostInit(); + } + + void Net_ReadRace() + { + float b; + + b = ReadByte(); + + switch(b) + { + case RACE_NET_CHECKPOINT_HIT_QUALIFYING: + race_checkpoint = ReadByte(); + race_time = ReadInt24_t(); + race_previousbesttime = ReadInt24_t(); + if(race_previousbestname) + strunzone(race_previousbestname); + race_previousbestname = strzone(ColorTranslateRGB(ReadString())); + + race_checkpointtime = time; + + if(race_checkpoint == 0 || race_checkpoint == 254) + { + race_penaltyaccumulator = 0; + race_laptime = time; // valid + } + + break; + + case RACE_NET_CHECKPOINT_CLEAR: + race_laptime = 0; + race_checkpointtime = 0; + break; + + case RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING: + race_laptime = ReadCoord(); + race_checkpointtime = -99999; + // fall through + case RACE_NET_CHECKPOINT_NEXT_QUALIFYING: + race_nextcheckpoint = ReadByte(); + + race_nextbesttime = ReadInt24_t(); + if(race_nextbestname) + strunzone(race_nextbestname); + race_nextbestname = strzone(ColorTranslateRGB(ReadString())); + break; + + case RACE_NET_CHECKPOINT_HIT_RACE: + race_mycheckpoint = ReadByte(); + race_mycheckpointtime = time; + race_mycheckpointdelta = ReadInt24_t(); + race_mycheckpointlapsdelta = ReadByte(); + if(race_mycheckpointlapsdelta >= 128) + race_mycheckpointlapsdelta -= 256; + if(race_mycheckpointenemy) + strunzone(race_mycheckpointenemy); + race_mycheckpointenemy = strzone(ColorTranslateRGB(ReadString())); + break; + + case RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT: + race_othercheckpoint = ReadByte(); + race_othercheckpointtime = time; + race_othercheckpointdelta = ReadInt24_t(); + race_othercheckpointlapsdelta = ReadByte(); + if(race_othercheckpointlapsdelta >= 128) + race_othercheckpointlapsdelta -= 256; + if(race_othercheckpointenemy) + strunzone(race_othercheckpointenemy); + race_othercheckpointenemy = strzone(ColorTranslateRGB(ReadString())); + break; + + case RACE_NET_PENALTY_RACE: + race_penaltyeventtime = time; + race_penaltytime = ReadShort(); + //race_penaltyaccumulator += race_penaltytime; + if(race_penaltyreason) + strunzone(race_penaltyreason); + race_penaltyreason = strzone(ReadString()); + break; + + case RACE_NET_PENALTY_QUALIFYING: + race_penaltyeventtime = time; + race_penaltytime = ReadShort(); + race_penaltyaccumulator += race_penaltytime; + if(race_penaltyreason) + strunzone(race_penaltyreason); + race_penaltyreason = strzone(ReadString()); + break; + + case RACE_NET_SERVER_RECORD: + race_server_record = ReadInt24_t(); + break; + case RACE_NET_SPEED_AWARD: + race_speedaward = ReadInt24_t(); + if(race_speedaward_holder) + strunzone(race_speedaward_holder); + race_speedaward_holder = strzone(ReadString()); + break; + case RACE_NET_SPEED_AWARD_BEST: + race_speedaward_alltimebest = ReadInt24_t(); + if(race_speedaward_alltimebest_holder) + strunzone(race_speedaward_alltimebest_holder); + race_speedaward_alltimebest_holder = strzone(ReadString()); + break; + case RACE_NET_SERVER_RANKINGS: + float prevpos, del; + int pos = ReadShort(); + prevpos = ReadShort(); + del = ReadShort(); + + // move other rankings out of the way + int i; + if (prevpos) { + for (i=prevpos-1;i>pos-1;--i) { + grecordtime[i] = grecordtime[i-1]; + if(grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i-1]); + } + } else if (del) { // a record has been deleted by the admin + for (i=pos-1; i<= RANKINGS_CNT-1; ++i) { + if (i == RANKINGS_CNT-1) { // clear out last record + grecordtime[i] = 0; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = string_null; + } + else { + grecordtime[i] = grecordtime[i+1]; + if (grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i+1]); + } + } + } else { // player has no ranked record yet + for (i=RANKINGS_CNT-1;i>pos-1;--i) { + grecordtime[i] = grecordtime[i-1]; + if(grecordholder[i]) + strunzone(grecordholder[i]); + grecordholder[i] = strzone(grecordholder[i-1]); + } + } + + // store new ranking + if(grecordholder[pos-1] != "") + strunzone(grecordholder[pos-1]); + grecordholder[pos-1] = strzone(ReadString()); + grecordtime[pos-1] = ReadInt24_t(); + if(grecordholder[pos-1] == GetPlayerName(player_localnum)) + race_myrank = pos; + break; + case RACE_NET_SERVER_STATUS: + race_status = ReadShort(); + if(race_status_name) + strunzone(race_status_name); + race_status_name = strzone(ReadString()); + } + } + + void Net_TeamNagger() + { + teamnagger = 1; + } + + void Net_ReadPingPLReport() + { + int e, pi, pl, ml; + e = ReadByte(); + pi = ReadShort(); + pl = ReadByte(); + ml = ReadByte(); + if (!(playerslots[e])) + return; + playerslots[e].ping = pi; + playerslots[e].ping_packetloss = pl / 255.0; + playerslots[e].ping_movementloss = ml / 255.0; + } + + void Net_WeaponComplain() + { + complain_weapon = ReadByte(); + + if(complain_weapon_name) + strunzone(complain_weapon_name); + complain_weapon_name = strzone(WEP_NAME(complain_weapon)); + + complain_weapon_type = ReadByte(); + + complain_weapon_time = time; + weapontime = time; // ping the weapon panel + + switch(complain_weapon_type) + { + case 0: Local_Notification(MSG_MULTI, ITEM_WEAPON_NOAMMO, complain_weapon); break; + case 1: Local_Notification(MSG_MULTI, ITEM_WEAPON_DONTHAVE, complain_weapon); break; + default: Local_Notification(MSG_MULTI, ITEM_WEAPON_UNAVAILABLE, complain_weapon); break; + } + } + + // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer. + // You must ALWAYS first acquire the temporary ID, which is sent as a byte. + // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event. + float CSQC_Parse_TempEntity() + { + float bHandled; + bHandled = true; + // Acquire TE ID + float nTEID; + nTEID = ReadByte(); + + if(autocvar_developer_csqcentities) + printf("CSQC_Parse_TempEntity() with nTEID=%d\n", nTEID); + + // NOTE: Could just do return instead of break... + switch(nTEID) + { + case TE_CSQC_TARGET_MUSIC: + Net_TargetMusic(); + bHandled = true; + break; + case TE_CSQC_PICTURE: + Net_MapVote_Picture(); + bHandled = true; + break; + case TE_CSQC_RACE: + Net_ReadRace(); + bHandled = true; + break; + case TE_CSQC_VORTEXBEAMPARTICLE: + Net_ReadVortexBeamParticle(); + bHandled = true; + break; + case TE_CSQC_TEAMNAGGER: + Net_TeamNagger(); + bHandled = true; + break; + case TE_CSQC_ARC: + Net_ReadArc(); + bHandled = true; + break; + case TE_CSQC_PINGPLREPORT: + Net_ReadPingPLReport(); + bHandled = true; + break; + case TE_CSQC_WEAPONCOMPLAIN: + Net_WeaponComplain(); + bHandled = true; + break; + case TE_CSQC_VEHICLESETUP: + Net_VehicleSetup(); + bHandled = true; + break; + case TE_CSQC_SVNOTICE: + cl_notice_read(); + bHandled = true; + break; + case TE_CSQC_SHOCKWAVEPARTICLE: + Net_ReadShockwaveParticle(); + bHandled = true; + break; + default: + // No special logic for this temporary entity; return 0 so the engine can handle it + bHandled = false; + break; + } + + return bHandled; + } + + string getcommandkey(string text, string command) + { + string keys; + float n, j, k, l = 0; + + if (!autocvar_hud_showbinds) + return text; + + keys = db_get(binddb, command); + if (keys == "") + { + n = tokenize(findkeysforcommand(command, 0)); // uses '...' strings + for(j = 0; j < n; ++j) + { + k = stof(argv(j)); + if(k != -1) + { + if ("" == keys) + keys = keynumtostring(k); + else + keys = strcat(keys, ", ", keynumtostring(k)); + + ++l; + if (autocvar_hud_showbinds_limit > 0 && autocvar_hud_showbinds_limit <= l) + break; + } + + } + if (keys == "") + keys = "NO_KEY"; + db_put(binddb, command, keys); + } + + if (keys == "NO_KEY") { + if (autocvar_hud_showbinds > 1) + return sprintf(_("%s (not bound)"), text); + else + return text; + } + else if (autocvar_hud_showbinds > 1) + return sprintf("%s (%s)", text, keys); + else + return keys; + } diff --cc qcsrc/client/main.qh index c9aa2fb40f,b8823e6296..5689d24284 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@@ -147,4 -148,5 +148,7 @@@ entity entcs_receiver[255]; // 255 is t float hud; float view_quality; - float framecount; + int framecount; ++.float health; ++ + #endif diff --cc qcsrc/client/progs.src index 3efcb16f34,f80da18d67..81d349ff6b --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@@ -1,124 -1,59 +1,61 @@@ ../../csprogs.dat ../common/util-pre.qh - sys-pre.qh - ../dpdefs/csprogsdefs.qc - sys-post.qh + ../dpdefs/csprogsdefs.qh - Defs.qc - ../dpdefs/keycodes.qc - ../common/constants.qh - ../common/stats.qh - - ../warpzonelib/anglestransform.qh - ../warpzonelib/mathlib.qh - ../warpzonelib/common.qh - ../warpzonelib/client.qh - - ../common/playerstats.qh - ../common/teams.qh - ../common/util.qh - ../common/nades.qh - ../common/buffs.qh - ../common/test.qh - ../common/counting.qh - ../common/weapons/weapons.qh // TODO - ../common/mapinfo.qh - ../common/command/markup.qh - ../common/command/rpn.qh - ../common/command/generic.qh - ../common/command/shared_defs.qh - ../common/urllib.qh - ../common/animdecide.qh - command/cl_cmd.qh - - ../common/monsters/monsters.qh - - autocvars.qh - - ../common/notifications.qh // must be after autocvars - ../common/deathtypes.qh // must be after notifications - - damage.qh - - ../csqcmodellib/interpolate.qh - teamradar.qh - hud.qh - scoreboard.qh - waypointsprites.qh - movetypes.qh - prandom.qh - bgmscript.qh - noise.qh - tturrets.qh - ../server/tturrets/include/turrets_early.qh - ../server/movelib.qc - generator.qh - controlpoint.qh - main.qh - vehicles/vehicles.qh - ../common/csqcmodel_settings.qh - ../csqcmodellib/common.qh - ../csqcmodellib/cl_model.qh - ../csqcmodellib/cl_player.qh - weapons/projectile.qh // TODO - player_skeleton.qh - - sortlist.qc - miscfunctions.qc - ../server/t_items.qh - ../server/t_items.qc - - teamradar.qc - hud_config.qc - hud.qc - scoreboard.qc - mapvoting.qc + announcer.qc + bgmscript.qc + casings.qc ++controlpoint.qc csqcmodel_hooks.qc - ../common/net_notice.qc - - rubble.qc - hook.qc - particles.qc - laser.qc - weapons/projectile.qc // TODO - gibs.qc damage.qc - casings.qc - ../csqcmodellib/cl_model.qc - ../csqcmodellib/cl_player.qc effects.qc - wall.qc ++generator.qc + gibs.qc + hook.qc + hud_config.qc + hud.qc + laser.qc + main.qc + mapvoting.qc + miscfunctions.qc modeleffects.qc - tuba.qc + movetypes.qc + noise.qc + particles.qc + player_skeleton.qc + prandom.qc + rubble.qc + scoreboard.qc + shownames.qc + sortlist.qc target_music.qc - + teamradar.qc + tturrets.qc + tuba.qc vehicles/vehicles.qc - ../server/vehicles/bumblebee.qc - shownames.qh - shownames.qc - - announcer.qc - Main.qc - View.qc - ../csqcmodellib/interpolate.qc + view.qc + wall.qc waypointsprites.qc - movetypes.qc - prandom.qc - bgmscript.qc - noise.qc + command/cl_cmd.qc + + weapons/projectile.qc // TODO + + ../common/animdecide.qc + ../common/buffs.qc + ../common/mapinfo.qc + ../common/nades.qc + ../common/net_notice.qc + ../common/notifications.qc + ../common/playerstats.qc ../common/test.qc + ../common/urllib.qc ../common/util.qc - ../common/playerstats.qc - ../common/notifications.qc + + ../common/command/generic.qc ../common/command/markup.qc ../common/command/rpn.qc - ../common/command/generic.qc - ../common/mapinfo.qc - ../common/weapons/weapons.qc // TODO - ../common/urllib.qc - command/cl_cmd.qc ../common/monsters/monsters.qc diff --cc qcsrc/client/teamradar.qc index ee5a5428d1,1822acb5e4..088ba60d9b --- a/qcsrc/client/teamradar.qc +++ b/qcsrc/client/teamradar.qc @@@ -46,37 -43,9 +43,37 @@@ vector teamradar_texcoord_to_2dcoord(ve return out; } + +vector teamradar_2dcoord_to_texcoord(vector in) +{ + vector out; + out = in; + + out -= teamradar_origin2d; + if(v_flipped) + out_x = -out_x; + out = out / teamradar_size; + + out_y = - out_y; // screen space is reversed + out = rotate(out, -teamradar_angle * DEG2RAD); + + out += teamradar_origin3d_in_texcoord; + + return out; +} + +vector teamradar_texcoord_to_3dcoord(vector in,float z) +{ + vector out; + out_x = in_x * (mi_picmax_x - mi_picmin_x) + mi_picmin_x; + out_y = in_y * (mi_picmax_y - mi_picmin_y) + mi_picmin_y; + out_z = z; + return out; +} + vector yinvert(vector v) { - v_y = 1 - v_y; + v.y = 1 - v.y; return v; } diff --cc qcsrc/client/teamradar.qh index 2dbdf89658,31defc8efb..da99652fee --- a/qcsrc/client/teamradar.qh +++ b/qcsrc/client/teamradar.qh @@@ -3,5 -6,50 +6,54 @@@ const int MAX_TEAMRADAR_TIMES = 32 // to make entities have dots on the team radar .float teamradar_icon; .float teamradar_times[MAX_TEAMRADAR_TIMES]; - .float teamradar_time_index; + .int teamradar_time_index; .vector teamradar_color; + + float teamradar_angle; // player yaw angle + vector teamradar_origin3d_in_texcoord; // player origin + vector teamradar_origin2d; // 2D origin + vector teamradar_size2d; // 2D size + vector teamradar_extraclip_mins, teamradar_extraclip_maxs; // for non-centered radar display + float teamradar_size; // 2D scale factor + float v_flipped; + + float hud_panel_radar_scale; // window size = ...qu + float hud_panel_radar_foreground_alpha; + float hud_panel_radar_rotation; + vector hud_panel_radar_size; + float hud_panel_radar_zoommode; + float hud_panel_radar_maximized_zoommode; + float hud_panel_radar_maximized_rotation; + + + float vlen2d(vector v); + + float vlen_maxnorm2d(vector v); + + float vlen_minnorm2d(vector v); + ++vector teamradar_2dcoord_to_texcoord(vector in); ++ + vector teamradar_3dcoord_to_texcoord(vector in); + + vector teamradar_texcoord_to_2dcoord(vector in); + ++vector teamradar_texcoord_to_3dcoord(vector in,float z); ++ + vector yinvert(vector v); + + void draw_teamradar_background(float fg); + + void draw_teamradar_player(vector coord3d, vector pangles, vector rgb); + + void draw_teamradar_icon(vector coord, float icon, entity pingdata, vector rgb, float a); + + void draw_teamradar_link(vector start, vector end, int colors); + + void teamradar_loadcvars(); + + // radar links + + void Ent_RadarLink(); + + #endif diff --cc qcsrc/client/view.qc index 0000000000,ee8ef320ae..0224b35f21 mode 000000,100644..100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@@ -1,0 -1,1962 +1,1988 @@@ + #if defined(CSQC) + #include "../dpdefs/csprogsdefs.qh" + #include "defs.qh" + #include "../common/constants.qh" + #include "../common/stats.qh" + #include "../warpzonelib/mathlib.qh" + #include "../warpzonelib/common.qh" + #include "../warpzonelib/client.qh" + #include "../common/teams.qh" + #include "../common/util.qh" + #include "../common/nades.qh" + #include "../common/weapons/weapons.qh" + #include "../common/mapinfo.qh" + #include "autocvars.qh" + #include "hud.qh" + #include "scoreboard.qh" + #include "noise.qh" + #include "main.qh" + #include "../csqcmodellib/cl_player.qh" + #elif defined(MENUQC) + #elif defined(SVQC) + #endif + + entity porto; + vector polyline[16]; + void Porto_Draw() + { + vector p, dir, ang, q, nextdir; + float portal_number, portal1_idx; + + if(activeweapon != WEP_PORTO || spectatee_status || gametype == MAPINFO_TYPE_NEXBALL) + return; + if(g_balance_porto_secondary) + return; + if(intermission == 1) + return; + if(intermission == 2) + return; + if (getstati(STAT_HEALTH) <= 0) + return; + + dir = view_forward; + + if(angles_held_status) + { + makevectors(angles_held); + dir = v_forward; + } + + p = view_origin; + + polyline[0] = p; + int idx = 1; + portal_number = 0; + nextdir = dir; + + for (;;) + { + dir = nextdir; + traceline(p, p + 65536 * dir, true, porto); + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) + return; + nextdir = dir - 2 * (dir * trace_plane_normal) * trace_plane_normal; // mirror dir at trace_plane_normal + p = trace_endpos; + polyline[idx] = p; + ++idx; + if(idx >= 16) + return; + if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SLICK || trace_dphitcontents & DPCONTENTS_PLAYERCLIP) + continue; + ++portal_number; + ang = vectoangles2(trace_plane_normal, dir); + ang.x = -ang.x; + makevectors(ang); + if(!CheckWireframeBox(porto, p - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 96 * v_forward)) + return; + if(portal_number == 1) + { + portal1_idx = idx; + if(portal_number >= 2) + break; + } + } + + while(idx >= 2) + { + p = polyline[idx-2]; + q = polyline[idx-1]; + if(idx == 2) + p = p - view_up * 16; + if(idx-1 >= portal1_idx) + { + Draw_CylindricLine(p, q, 4, "", 1, 0, '0 0 1', 0.5, DRAWFLAG_NORMAL, view_origin); + } + else + { + Draw_CylindricLine(p, q, 4, "", 1, 0, '1 0 0', 0.5, DRAWFLAG_NORMAL, view_origin); + } + --idx; + } + } + + void Porto_Init() + { + porto = spawn(); + porto.classname = "porto"; + porto.draw = Porto_Draw; + porto.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP; + } + + float drawtime; + float avgspeed; + vector GetCurrentFov(float fov) + { + float zoomsensitivity, zoomspeed, zoomfactor, zoomdir; + float velocityzoom, curspeed; + vector v; + + zoomsensitivity = autocvar_cl_zoomsensitivity; + zoomfactor = autocvar_cl_zoomfactor; + if(zoomfactor < 1 || zoomfactor > 16) + zoomfactor = 2.5; + zoomspeed = autocvar_cl_zoomspeed; + if(zoomspeed >= 0) + if(zoomspeed < 0.5 || zoomspeed > 16) + zoomspeed = 3.5; + + zoomdir = button_zoom; + if(hud == HUD_NORMAL) + if((activeweapon == WEP_VORTEX && vortex_scope) || (activeweapon == WEP_RIFLE && rifle_scope)) // do NOT use switchweapon here + zoomdir += button_attack2; + if(spectatee_status > 0 || isdemo()) + { + if(spectatorbutton_zoom) + { + if(zoomdir) + zoomdir = 0; + else + zoomdir = 1; + } + // fteqcc failed twice here already, don't optimize this + } + + if(zoomdir) { zoomin_effect = 0; } + + if(camera_active) + { + current_viewzoom = min(1, current_viewzoom + drawframetime); + } + else if(autocvar_cl_spawnzoom && zoomin_effect) + { + float spawnzoomfactor = bound(1, autocvar_cl_spawnzoom_factor, 16); + + current_viewzoom += (autocvar_cl_spawnzoom_speed * (spawnzoomfactor - current_viewzoom) * drawframetime); + current_viewzoom = bound(1 / spawnzoomfactor, current_viewzoom, 1); + if(current_viewzoom == 1) { zoomin_effect = 0; } + } + else + { + if(zoomspeed < 0) // instant zoom + { + if(zoomdir) + current_viewzoom = 1 / zoomfactor; + else + current_viewzoom = 1; + } + else + { + if(zoomdir) + current_viewzoom = 1 / bound(1, 1 / current_viewzoom + drawframetime * zoomspeed * (zoomfactor - 1), zoomfactor); + else + current_viewzoom = bound(1 / zoomfactor, current_viewzoom + drawframetime * zoomspeed * (1 - 1 / zoomfactor), 1); + } + } + + if(almost_equals(current_viewzoom, 1)) + current_zoomfraction = 0; + else if(almost_equals(current_viewzoom, 1/zoomfactor)) + current_zoomfraction = 1; + else + current_zoomfraction = (current_viewzoom - 1) / (1/zoomfactor - 1); + + if(zoomsensitivity < 1) + setsensitivityscale(pow(current_viewzoom, 1 - zoomsensitivity)); + else + setsensitivityscale(1); + + if(autocvar_cl_velocityzoom_enabled && autocvar_cl_velocityzoom_type) // _type = 0 disables velocity zoom too + { + if(intermission) { curspeed = 0; } + else + { + + makevectors(view_angles); + v = pmove_vel; + if(csqcplayer) + v = csqcplayer.velocity; + + switch(autocvar_cl_velocityzoom_type) + { + case 3: curspeed = max(0, v_forward * v); break; + case 2: curspeed = (v_forward * v); break; + case 1: default: curspeed = vlen(v); break; + } + } + + velocityzoom = bound(0, drawframetime / max(0.000000001, autocvar_cl_velocityzoom_time), 1); // speed at which the zoom adapts to player velocity + avgspeed = avgspeed * (1 - velocityzoom) + (curspeed / autocvar_cl_velocityzoom_speed) * velocityzoom; + velocityzoom = exp(float2range11(avgspeed * -autocvar_cl_velocityzoom_factor / 1) * 1); + + //print(ftos(avgspeed), " avgspeed, ", ftos(curspeed), " curspeed, ", ftos(velocityzoom), " return\n"); // for debugging + } + else + velocityzoom = 1; + + float frustumx, frustumy, fovx, fovy; + frustumy = tan(fov * M_PI / 360.0) * 0.75 * current_viewzoom * velocityzoom; + frustumx = frustumy * vid_width / vid_height / vid_pixelheight; + fovx = atan2(frustumx, 1) / M_PI * 360.0; + fovy = atan2(frustumy, 1) / M_PI * 360.0; + + return '1 0 0' * fovx + '0 1 0' * fovy; + } + + vector GetOrthoviewFOV(vector ov_worldmin, vector ov_worldmax, vector ov_mid, vector ov_org) + { + float fovx, fovy; + float width = (ov_worldmax.x - ov_worldmin.x); + float height = (ov_worldmax.y - ov_worldmin.y); + float distance_to_middle_of_world = vlen(ov_mid - ov_org); + fovx = atan2(width/2, distance_to_middle_of_world) / M_PI * 360.0; + fovy = atan2(height/2, distance_to_middle_of_world) / M_PI * 360.0; + return '1 0 0' * fovx + '0 1 0' * fovy; + } + + // this function must match W_SetupShot! + float zoomscript_caught; + + vector wcross_origin; + float wcross_scale_prev, wcross_alpha_prev; + vector wcross_color_prev; + float wcross_scale_goal_prev, wcross_alpha_goal_prev; + vector wcross_color_goal_prev; + float wcross_changedonetime; + + string wcross_name_goal_prev, wcross_name_goal_prev_prev; + float wcross_resolution_goal_prev, wcross_resolution_goal_prev_prev; + float wcross_name_changestarttime, wcross_name_changedonetime; + float wcross_name_alpha_goal_prev, wcross_name_alpha_goal_prev_prev; + + float wcross_ring_prev; + + entity trueaim; + entity trueaim_rifle; + + const float SHOTTYPE_HITTEAM = 1; + const float SHOTTYPE_HITOBSTRUCTION = 2; + const float SHOTTYPE_HITWORLD = 3; + const float SHOTTYPE_HITENEMY = 4; + + void TrueAim_Init() + { + trueaim = spawn(); + trueaim.classname = "trueaim"; + trueaim.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE; + trueaim_rifle = spawn(); + trueaim_rifle.classname = "trueaim_rifle"; + trueaim_rifle.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_CORPSE; + } + + float EnemyHitCheck() + { + float t, n; + wcross_origin = project_3d_to_2d(trace_endpos); + wcross_origin.z = 0; + if(trace_ent) + n = trace_ent.entnum; + else + n = trace_networkentity; + if(n < 1) + return SHOTTYPE_HITWORLD; + if(n > maxclients) + return SHOTTYPE_HITWORLD; + t = GetPlayerColor(n - 1); + if(teamplay) + if(t == myteam) + return SHOTTYPE_HITTEAM; + if(t == NUM_SPECTATOR) + return SHOTTYPE_HITWORLD; + return SHOTTYPE_HITENEMY; + } + + float TrueAimCheck() + { + 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? + vector vecs, trueaimpoint, w_shotorg; + vector mi, ma, dv; + float shottype; + entity ta; + float mv; + + mi = ma = '0 0 0'; + ta = trueaim; + mv = MOVE_NOMONSTERS; + + switch(activeweapon) // WEAPONTODO + { + case WEP_TUBA: // no aim + case WEP_PORTO: // shoots from eye + case WEP_HOOK: // no trueaim + case WEP_MORTAR: // toss curve + return SHOTTYPE_HITWORLD; + case WEP_VORTEX: + case WEP_VAPORIZER: + mv = MOVE_NORMAL; + break; + case WEP_RIFLE: + ta = trueaim_rifle; + mv = MOVE_NORMAL; + if(zoomscript_caught) + { + tracebox(view_origin, '0 0 0', '0 0 0', view_origin + view_forward * MAX_SHOT_DISTANCE, mv, ta); + return EnemyHitCheck(); + } + break; + case WEP_DEVASTATOR: // projectile has a size! + mi = '-3 -3 -3'; + ma = '3 3 3'; + break; + case WEP_FIREBALL: // projectile has a size! + mi = '-16 -16 -16'; + ma = '16 16 16'; + break; + case WEP_SEEKER: // projectile has a size! + mi = '-2 -2 -2'; + ma = '2 2 2'; + break; + case WEP_ELECTRO: // projectile has a size! + mi = '0 0 -3'; + ma = '0 0 -3'; + break; + } + + vector traceorigin = getplayerorigin(player_localentnum-1) + (eZ * getstati(STAT_VIEWHEIGHT)); + + vecs = decompressShotOrigin(getstati(STAT_SHOTORG)); + + traceline(traceorigin, traceorigin + view_forward * MAX_SHOT_DISTANCE, mv, ta); + trueaimpoint = trace_endpos; + + if(vlen(trueaimpoint - traceorigin) < g_trueaim_minrange) + trueaimpoint = traceorigin + view_forward * g_trueaim_minrange; + + if(vecs.x > 0) + vecs.y = -vecs.y; + else + vecs = '0 0 0'; + + dv = view_right * vecs.y + view_up * vecs.z; + w_shotorg = traceorigin + dv; + + // now move the vecs forward as much as requested if possible + tracebox(w_shotorg, mi, ma, w_shotorg + view_forward * (vecs.x + nudge), MOVE_NORMAL, ta); // FIXME this MOVE_NORMAL part will misbehave a little in csqc + w_shotorg = trace_endpos - view_forward * nudge; + + tracebox(w_shotorg, mi, ma, trueaimpoint, MOVE_NORMAL, ta); + shottype = EnemyHitCheck(); + if(shottype != SHOTTYPE_HITWORLD) + return shottype; + + #if 0 + // FIXME WHY DOES THIS NOT WORK FOR THE ROCKET LAUNCHER? + // or rather, I know why, but see no fix + if(vlen(trace_endpos - trueaimpoint) > vlen(ma) + vlen(mi) + 1) + // yes, this is an ugly hack... but it seems good enough to find out whether the test hits the same place as the initial trace + return SHOTTYPE_HITOBSTRUCTION; + #endif + + return SHOTTYPE_HITWORLD; + } + + void CSQC_common_hud(void); + + void PostInit(void); + void CSQC_Demo_Camera(); + float HUD_WouldDrawScoreboard(); + float camera_mode; + const float CAMERA_FREE = 1; + const float CAMERA_CHASE = 2; + float reticle_type; + string reticle_image; + string NextFrameCommand; + void CSQC_SPIDER_HUD(); + void CSQC_RAPTOR_HUD(); + + vector freeze_org, freeze_ang; + entity nightvision_noise, nightvision_noise2; + + const float MAX_TIME_DIFF = 5; + float pickup_crosshair_time, pickup_crosshair_size; + float hitindication_crosshair_size; + float use_vortex_chargepool; + + float myhealth, myhealth_prev; + float myhealth_flash; + + float old_blurradius, old_bluralpha; + float old_sharpen_intensity; + + vector myhealth_gentlergb; + + float contentavgalpha, liquidalpha_prev; + vector liquidcolor_prev; + + float eventchase_current_distance; + float eventchase_running; + float WantEventchase() + { + if(autocvar_cl_orthoview) + return false; + if(intermission) + return true; + if(spectatee_status >= 0) + { + if(autocvar_cl_eventchase_nexball && gametype == MAPINFO_TYPE_NEXBALL && !(WepSet_GetFromStat() & WepSet_FromWeapon(WEP_PORTO))) + return true; + if(autocvar_cl_eventchase_death && (getstati(STAT_HEALTH) <= 0)) + { + if(autocvar_cl_eventchase_death == 2) + { + // don't stop eventchase once it's started (even if velocity changes afterwards) + if(self.velocity == '0 0 0' || eventchase_running) + return true; + } + else return true; + } + } + return false; + } + + vector damage_blurpostprocess, content_blurpostprocess; + + float checkfail[16]; + + float unaccounted_damage = 0; + void UpdateDamage() + { + // accumulate damage with each stat update + static float damage_total_prev = 0; + float damage_total = getstati(STAT_DAMAGE_DEALT_TOTAL); + float unaccounted_damage_new = COMPARE_INCREASING(damage_total, damage_total_prev); + damage_total_prev = damage_total; + + static float damage_dealt_time_prev = 0; + float damage_dealt_time = getstatf(STAT_HIT_TIME); + if (damage_dealt_time != damage_dealt_time_prev) + { + unaccounted_damage += unaccounted_damage_new; + dprint("dmg total: ", ftos(unaccounted_damage), " (+", ftos(unaccounted_damage_new), ")", "\n"); + } + damage_dealt_time_prev = damage_dealt_time; + + // prevent hitsound when switching spectatee + static float spectatee_status_prev = 0; + if (spectatee_status != spectatee_status_prev) + unaccounted_damage = 0; + spectatee_status_prev = spectatee_status; + } + + void UpdateHitsound() + { + // varying sound pitch + + static float hitsound_time_prev = 0; + // HACK: the only way to get the arc to sound consistent with pitch shift is to ignore cl_hitsound_antispam_time + float arc_hack = activeweapon == WEP_ARC && autocvar_cl_hitsound >= 2; + if (arc_hack || COMPARE_INCREASING(time, hitsound_time_prev) > autocvar_cl_hitsound_antispam_time) + { + if (autocvar_cl_hitsound && unaccounted_damage) + { + // customizable gradient function that crosses (0,a), (c,1) and asymptotically approaches b + float a = autocvar_cl_hitsound_max_pitch; + float b = autocvar_cl_hitsound_min_pitch; + float c = autocvar_cl_hitsound_nom_damage; + float x = unaccounted_damage; + float pitch_shift = (b*x*(a-1) + a*c*(1-b)) / (x*(a-1) + c*(1-b)); + + // if sound variation is disabled, set pitch_shift to 1 + if (autocvar_cl_hitsound == 1) + pitch_shift = 1; + + // if pitch shift is reversed, mirror in (max-min)/2 + min + if (autocvar_cl_hitsound == 3) + { + float mirror_value = (a-b)/2 + b; + pitch_shift = mirror_value + (mirror_value - pitch_shift); + } + + dprint("dmg total (dmg): ", ftos(unaccounted_damage), " , pitch shift: ", ftos(pitch_shift), "\n"); + + // todo: avoid very long and very short sounds from wave stretching using different sound files? seems unnecessary + // todo: normalize sound pressure levels? seems unnecessary + + sound7(world, CH_INFO, "misc/hit.wav", VOL_BASE, ATTN_NONE, pitch_shift * 100, 0); + } + unaccounted_damage = 0; + hitsound_time_prev = time; + } + + static float typehit_time_prev = 0; + float typehit_time = getstatf(STAT_TYPEHIT_TIME); + if (COMPARE_INCREASING(typehit_time, typehit_time_prev) > autocvar_cl_hitsound_antispam_time) + { + sound(world, CH_INFO, "misc/typehit.wav", VOL_BASE, ATTN_NONE); + typehit_time_prev = typehit_time; + } + } + + void UpdateCrosshair() + { + static float rainbow_last_flicker; + static vector rainbow_prev_color; + entity e = self; + float f, i, j; + vector v; + if(getstati(STAT_FROZEN)) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, ((getstatf(STAT_REVIVE_PROGRESS)) ? ('0.25 0.90 1' + ('1 0 0' * getstatf(STAT_REVIVE_PROGRESS)) + ('0 1 1' * getstatf(STAT_REVIVE_PROGRESS) * -1)) : '0.25 0.90 1'), autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + else if (getstatf(STAT_HEALING_ORB)>time) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, Nade_Color(NADE_TYPE_HEAL), autocvar_hud_colorflash_alpha*getstatf(STAT_HEALING_ORB_ALPHA), DRAWFLAG_ADDITIVE); + if(!intermission) + if(getstatf(STAT_NADE_TIMER) && autocvar_cl_nade_timer) // give nade top priority, as it's a matter of life and death + { + DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_NADE_TIMER), '0.25 0.90 1' + ('1 0 0' * getstatf(STAT_NADE_TIMER)) - ('0 1 1' * getstatf(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(getstatf(STAT_REVIVE_PROGRESS)) + { + DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', autocvar_hud_colorflash_alpha, DRAWFLAG_ADDITIVE); + drawstring_aspect(eY * 0.64 * vid_conheight, _("Revival progress"), eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL); + } + + if(autocvar_r_letterbox == 0) + if(autocvar_viewsize < 120) + CSQC_common_hud(); + + // crosshair goes VERY LAST + if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL) + { + if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering + return; + + string wcross_style; + float wcross_alpha, wcross_resolution; + wcross_style = autocvar_crosshair; + if (wcross_style == "0") + return; + wcross_resolution = autocvar_crosshair_size; + if (wcross_resolution == 0) + return; + wcross_alpha = autocvar_crosshair_alpha; + if (wcross_alpha == 0) + return; + + // TrueAim check + float shottype; + + // wcross_origin = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight; + wcross_origin = project_3d_to_2d(view_origin + MAX_SHOT_DISTANCE * view_forward); + wcross_origin.z = 0; + if(autocvar_crosshair_hittest) + { + vector wcross_oldorigin; + wcross_oldorigin = wcross_origin; + shottype = TrueAimCheck(); + if(shottype == SHOTTYPE_HITWORLD) + { + v = wcross_origin - wcross_oldorigin; + v.x /= vid_conwidth; + v.y /= vid_conheight; + if(vlen(v) > 0.01) + shottype = SHOTTYPE_HITOBSTRUCTION; + } + if(!autocvar_crosshair_hittest_showimpact) + wcross_origin = wcross_oldorigin; + } + else + shottype = SHOTTYPE_HITWORLD; + + vector wcross_color = '0 0 0', wcross_size = '0 0 0'; + string wcross_name = ""; + float wcross_scale, wcross_blur; + + if(autocvar_crosshair_per_weapon || (autocvar_crosshair_color_special == 1)) + { + e = get_weaponinfo(switchingweapon); + if(e) + { + if(autocvar_crosshair_per_weapon) + { + // WEAPONTODO: access these through some general settings (with non-balance config settings) + //wcross_resolution *= cvar(strcat("crosshair_", wcross_wep, "_size")); + //if (wcross_resolution == 0) + //return; + + //wcross_style = cvar_string(strcat("crosshair_", wcross_wep)); + wcross_resolution *= e.w_crosshair_size; + wcross_name = e.w_crosshair; + } + } + } + + if(wcross_name == "") + wcross_name = strcat("gfx/crosshair", wcross_style); + + // MAIN CROSSHAIR COLOR DECISION + switch(autocvar_crosshair_color_special) + { + case 1: // crosshair_color_per_weapon + { + if(e) + { + wcross_color = e.wpcolor; + break; + } + else { goto normalcolor; } + } + + case 2: // crosshair_color_by_health + { + float x = getstati(STAT_HEALTH); + + //x = red + //y = green + //z = blue + + wcross_color.z = 0; + + if(x > 200) + { + wcross_color.x = 0; + wcross_color.y = 1; + } + else if(x > 150) + { + wcross_color.x = 0.4 - (x-150)*0.02 * 0.4; + wcross_color.y = 0.9 + (x-150)*0.02 * 0.1; + } + else if(x > 100) + { + wcross_color.x = 1 - (x-100)*0.02 * 0.6; + wcross_color.y = 1 - (x-100)*0.02 * 0.1; + wcross_color.z = 1 - (x-100)*0.02; + } + else if(x > 50) + { + wcross_color.x = 1; + wcross_color.y = 1; + wcross_color.z = 0.2 + (x-50)*0.02 * 0.8; + } + else if(x > 20) + { + wcross_color.x = 1; + wcross_color.y = (x-20)*90/27/100; + wcross_color.z = (x-20)*90/27/100 * 0.2; + } + else + { + wcross_color.x = 1; + wcross_color.y = 0; + } + break; + } + + case 3: // crosshair_color_rainbow + { + if(time >= rainbow_last_flicker) + { + rainbow_prev_color = randomvec() * autocvar_crosshair_color_special_rainbow_brightness; + rainbow_last_flicker = time + autocvar_crosshair_color_special_rainbow_delay; + } + wcross_color = rainbow_prev_color; + break; + } + :normalcolor + default: { wcross_color = stov(autocvar_crosshair_color); break; } + } + + if(autocvar_crosshair_effect_scalefade) + { + wcross_scale = wcross_resolution; + wcross_resolution = 1; + } + else + { + wcross_scale = 1; + } + + if(autocvar_crosshair_pickup) + { + float stat_pickup_time = getstatf(STAT_LAST_PICKUP); + + if(pickup_crosshair_time < stat_pickup_time) + { + if(time - stat_pickup_time < MAX_TIME_DIFF) // don't trigger the animation if it's too old + pickup_crosshair_size = 1; + + pickup_crosshair_time = stat_pickup_time; + } + + if(pickup_crosshair_size > 0) + pickup_crosshair_size -= autocvar_crosshair_pickup_speed * frametime; + else + pickup_crosshair_size = 0; + + wcross_scale += sin(pickup_crosshair_size) * autocvar_crosshair_pickup; + } + + // todo: make crosshair hit indication dependent on damage dealt + if(autocvar_crosshair_hitindication) + { + vector hitindication_color = ((autocvar_crosshair_color_special == 1) ? stov(autocvar_crosshair_hitindication_per_weapon_color) : stov(autocvar_crosshair_hitindication_color)); + + if(unaccounted_damage) + { + hitindication_crosshair_size = 1; + } + + if(hitindication_crosshair_size > 0) + hitindication_crosshair_size -= autocvar_crosshair_hitindication_speed * frametime; + else + hitindication_crosshair_size = 0; + + wcross_scale += sin(hitindication_crosshair_size) * autocvar_crosshair_hitindication; + wcross_color.x += sin(hitindication_crosshair_size) * hitindication_color.x; + wcross_color.y += sin(hitindication_crosshair_size) * hitindication_color.y; + wcross_color.z += sin(hitindication_crosshair_size) * hitindication_color.z; + } + + if(shottype == SHOTTYPE_HITENEMY) + wcross_scale *= autocvar_crosshair_hittest; // is not queried if hittest is 0 + if(shottype == SHOTTYPE_HITTEAM) + wcross_scale /= autocvar_crosshair_hittest; // is not queried if hittest is 0 + + f = fabs(autocvar_crosshair_effect_time); + if(wcross_scale != wcross_scale_goal_prev || wcross_alpha != wcross_alpha_goal_prev || wcross_color != wcross_color_goal_prev) + { + wcross_changedonetime = time + f; + } + if(wcross_name != wcross_name_goal_prev || wcross_resolution != wcross_resolution_goal_prev) + { + wcross_name_changestarttime = time; + wcross_name_changedonetime = time + f; + if(wcross_name_goal_prev_prev) + strunzone(wcross_name_goal_prev_prev); + wcross_name_goal_prev_prev = wcross_name_goal_prev; + wcross_name_goal_prev = strzone(wcross_name); + wcross_name_alpha_goal_prev_prev = wcross_name_alpha_goal_prev; + wcross_resolution_goal_prev_prev = wcross_resolution_goal_prev; + wcross_resolution_goal_prev = wcross_resolution; + } + + wcross_scale_goal_prev = wcross_scale; + wcross_alpha_goal_prev = wcross_alpha; + wcross_color_goal_prev = wcross_color; + + if(shottype == SHOTTYPE_HITTEAM || (shottype == SHOTTYPE_HITOBSTRUCTION && autocvar_crosshair_hittest_blur && !autocvar_chase_active)) + { + wcross_blur = 1; + wcross_alpha *= 0.75; + } + else + wcross_blur = 0; + // *_prev is at time-frametime + // * is at wcross_changedonetime+f + // what do we have at time? + if(time < wcross_changedonetime) + { + f = frametime / (wcross_changedonetime - time + frametime); + wcross_scale = f * wcross_scale + (1 - f) * wcross_scale_prev; + wcross_alpha = f * wcross_alpha + (1 - f) * wcross_alpha_prev; + wcross_color = f * wcross_color + (1 - f) * wcross_color_prev; + } + + wcross_scale_prev = wcross_scale; + wcross_alpha_prev = wcross_alpha; + wcross_color_prev = wcross_color; + + wcross_scale *= 1 - autocvar__menu_alpha; + wcross_alpha *= 1 - autocvar__menu_alpha; + wcross_size = draw_getimagesize(wcross_name) * wcross_scale; + + if(wcross_scale >= 0.001 && wcross_alpha >= 0.001) + { + // crosshair rings for weapon stats + if (autocvar_crosshair_ring || autocvar_crosshair_ring_reload) + { + // declarations and stats + float ring_value = 0, ring_scale = 0, ring_alpha = 0, ring_inner_value = 0, ring_inner_alpha = 0; + string ring_image = string_null, ring_inner_image = string_null; + vector ring_rgb = '0 0 0', ring_inner_rgb = '0 0 0'; + + ring_scale = autocvar_crosshair_ring_size; + + float weapon_clipload, weapon_clipsize; + weapon_clipload = getstati(STAT_WEAPON_CLIPLOAD); + weapon_clipsize = getstati(STAT_WEAPON_CLIPSIZE); + + float ok_ammo_charge, ok_ammo_chargepool; + ok_ammo_charge = getstatf(STAT_OK_AMMO_CHARGE); + ok_ammo_chargepool = getstatf(STAT_OK_AMMO_CHARGEPOOL); + + float vortex_charge, vortex_chargepool; + vortex_charge = getstatf(STAT_VORTEX_CHARGE); + vortex_chargepool = getstatf(STAT_VORTEX_CHARGEPOOL); + + float arc_heat = getstatf(STAT_ARC_HEAT); + + if(vortex_charge_movingavg == 0) // this should only happen if we have just loaded up the game + vortex_charge_movingavg = vortex_charge; + + + // handle the values + if (autocvar_crosshair_ring && activeweapon == WEP_VORTEX && vortex_charge && autocvar_crosshair_ring_vortex) // ring around crosshair representing velocity-dependent damage for the vortex + { + if (vortex_chargepool || use_vortex_chargepool) { + use_vortex_chargepool = 1; + ring_inner_value = vortex_chargepool; + } else { + vortex_charge_movingavg = (1 - autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate) * vortex_charge_movingavg + autocvar_crosshair_ring_vortex_currentcharge_movingavg_rate * vortex_charge; + ring_inner_value = bound(0, autocvar_crosshair_ring_vortex_currentcharge_scale * (vortex_charge - vortex_charge_movingavg), 1); + } + + ring_inner_alpha = autocvar_crosshair_ring_vortex_inner_alpha; + ring_inner_rgb = eX * autocvar_crosshair_ring_vortex_inner_color_red + eY * autocvar_crosshair_ring_vortex_inner_color_green + eZ * autocvar_crosshair_ring_vortex_inner_color_blue; + ring_inner_image = "gfx/crosshair_ring_inner.tga"; + + // draw the outer ring to show the current charge of the weapon + ring_value = vortex_charge; + ring_alpha = autocvar_crosshair_ring_vortex_alpha; + ring_rgb = wcross_color; + ring_image = "gfx/crosshair_ring_nexgun.tga"; + } + else if (autocvar_crosshair_ring && activeweapon == WEP_MINE_LAYER && minelayer_maxmines && autocvar_crosshair_ring_minelayer) + { + ring_value = bound(0, getstati(STAT_LAYED_MINES) / minelayer_maxmines, 1); // if you later need to use the count of bullets in another place, then add a float for it. For now, no need to. + ring_alpha = autocvar_crosshair_ring_minelayer_alpha; + ring_rgb = wcross_color; + ring_image = "gfx/crosshair_ring.tga"; + } + else if (activeweapon == WEP_HAGAR && getstati(STAT_HAGAR_LOAD) && autocvar_crosshair_ring_hagar) + { + ring_value = bound(0, getstati(STAT_HAGAR_LOAD) / hagar_maxrockets, 1); + ring_alpha = autocvar_crosshair_ring_hagar_alpha; + ring_rgb = wcross_color; + ring_image = "gfx/crosshair_ring.tga"; + } + else if (ok_ammo_charge) + { + ring_value = ok_ammo_chargepool; + ring_alpha = autocvar_crosshair_ring_reload_alpha; + ring_rgb = wcross_color; + ring_image = "gfx/crosshair_ring.tga"; + } + else if(autocvar_crosshair_ring_reload && weapon_clipsize) // forces there to be only an ammo ring + { + ring_value = bound(0, weapon_clipload / weapon_clipsize, 1); + ring_scale = autocvar_crosshair_ring_reload_size; + ring_alpha = autocvar_crosshair_ring_reload_alpha; + ring_rgb = wcross_color; + + // Note: This is to stop Taoki from complaining that the image doesn't match all potential balances. + // if a new image for another weapon is added, add the code (and its respective file/value) here + if ((activeweapon == WEP_RIFLE) && (weapon_clipsize == 80)) + ring_image = "gfx/crosshair_ring_rifle.tga"; + else + ring_image = "gfx/crosshair_ring.tga"; + } + else if ( autocvar_crosshair_ring && autocvar_crosshair_ring_arc && arc_heat && activeweapon == WEP_ARC ) + { + ring_value = arc_heat; + ring_alpha = (1-arc_heat)*autocvar_crosshair_ring_arc_cold_alpha + + arc_heat*autocvar_crosshair_ring_arc_hot_alpha; + ring_rgb = (1-arc_heat)*wcross_color + arc_heat*autocvar_crosshair_ring_arc_hot_color; + ring_image = "gfx/crosshair_ring.tga"; + } + + // if in weapon switch animation, fade ring out/in + if(autocvar_crosshair_effect_time > 0) + { + f = (time - wcross_name_changestarttime) / autocvar_crosshair_effect_time; + if (f >= 1) + { + wcross_ring_prev = ((ring_image) ? true : false); + } + + if(wcross_ring_prev) + { + if(f < 1) + ring_alpha *= fabs(1 - bound(0, f, 1)); + } + else + { + if(f < 1) + ring_alpha *= bound(0, f, 1); + } + } + + if (autocvar_crosshair_ring_inner && ring_inner_value) // lets draw a ring inside a ring so you can ring while you ring + DrawCircleClippedPic(wcross_origin, wcross_size.x * ring_scale, ring_inner_image, ring_inner_value, ring_inner_rgb, wcross_alpha * ring_inner_alpha, DRAWFLAG_ADDITIVE); + + if (ring_value) + DrawCircleClippedPic(wcross_origin, wcross_size.x * ring_scale, ring_image, ring_value, ring_rgb, wcross_alpha * ring_alpha, DRAWFLAG_ADDITIVE); + } + + #define CROSSHAIR_DO_BLUR(M,sz,wcross_name,wcross_alpha) \ + do \ + { \ + if(wcross_blur > 0) \ + { \ + for(i = -2; i <= 2; ++i) \ + for(j = -2; j <= 2; ++j) \ + M(i,j,sz,wcross_name,wcross_alpha*0.04); \ + } \ + else \ + { \ + M(0,0,sz,wcross_name,wcross_alpha); \ + } \ + } \ + while(0) + + #define CROSSHAIR_DRAW_SINGLE(i,j,sz,wcross_name,wcross_alpha) \ + drawpic(wcross_origin - ('0.5 0 0' * (sz * wcross_size.x + i * wcross_blur) + '0 0.5 0' * (sz * wcross_size.y + j * wcross_blur)), wcross_name, sz * wcross_size, wcross_color, wcross_alpha, DRAWFLAG_NORMAL) + + #define CROSSHAIR_DRAW(sz,wcross_name,wcross_alpha) \ + CROSSHAIR_DO_BLUR(CROSSHAIR_DRAW_SINGLE,sz,wcross_name,wcross_alpha) + + if(time < wcross_name_changedonetime && wcross_name != wcross_name_goal_prev_prev && wcross_name_goal_prev_prev) + { + f = (wcross_name_changedonetime - time) / (wcross_name_changedonetime - wcross_name_changestarttime); + wcross_size = draw_getimagesize(wcross_name_goal_prev_prev) * wcross_scale; + CROSSHAIR_DRAW(wcross_resolution_goal_prev_prev, wcross_name_goal_prev_prev, wcross_alpha * f * wcross_name_alpha_goal_prev_prev); + f = 1 - f; + } + else + { + f = 1; + } + wcross_name_alpha_goal_prev = f; + + wcross_size = draw_getimagesize(wcross_name) * wcross_scale; + CROSSHAIR_DRAW(wcross_resolution, wcross_name, wcross_alpha * f); + + if(autocvar_crosshair_dot) + { + vector wcross_color_old; + wcross_color_old = wcross_color; + + if((autocvar_crosshair_dot_color_custom) && (autocvar_crosshair_dot_color != "0")) + wcross_color = stov(autocvar_crosshair_dot_color); + + CROSSHAIR_DRAW(wcross_resolution * autocvar_crosshair_dot_size, "gfx/crosshairdot.tga", f * autocvar_crosshair_dot_alpha); + // FIXME why don't we use wcross_alpha here?cl_notice_run(); + wcross_color = wcross_color_old; + } + } + } + else + { + wcross_scale_prev = 0; + wcross_alpha_prev = 0; + wcross_scale_goal_prev = 0; + wcross_alpha_goal_prev = 0; + wcross_changedonetime = 0; + if(wcross_name_goal_prev) + strunzone(wcross_name_goal_prev); + wcross_name_goal_prev = string_null; + if(wcross_name_goal_prev_prev) + strunzone(wcross_name_goal_prev_prev); + wcross_name_goal_prev_prev = string_null; + wcross_name_changestarttime = 0; + wcross_name_changedonetime = 0; + wcross_name_alpha_goal_prev = 0; + wcross_name_alpha_goal_prev_prev = 0; + wcross_resolution_goal_prev = 0; + wcross_resolution_goal_prev_prev = 0; + } + } + + const int BUTTON_3 = 4; + const int BUTTON_4 = 8; + float cl_notice_run(); + float prev_myteam; + void CSQC_UpdateView(float w, float h) + { + entity e; + float fov; + float f; + int i; + vector vf_size, vf_min; + float a; + + execute_next_frame(); + + ++framecount; + + hud = getstati(STAT_HUD); + + if(autocvar__hud_showbinds_reload) // menu can set this one + { + db_close(binddb); + binddb = db_create(); + cvar_set("_hud_showbinds_reload", "0"); + } + + if(checkextension("DP_CSQC_MINFPS_QUALITY")) + view_quality = getproperty(VF_MINFPS_QUALITY); + else + view_quality = 1; + + button_attack2 = (input_buttons & BUTTON_3); + button_zoom = (input_buttons & BUTTON_4); + + #define CHECKFAIL_ASSERT(flag,func,parm,val) do { \ + float checkfailv = (func)(parm); \ + if (checkfailv != (val)) { \ + if (!checkfail[(flag)]) \ + localcmd(sprintf("\ncmd checkfail %s %s %d %d\n", #func, parm, val, checkfailv)); \ + checkfail[(flag)] = 1; \ + } \ + } while(0) + CHECKFAIL_ASSERT(0, cvar_type, "\{100}\{105}\{118}\{48}\{95}\{101}\{118}\{97}\{100}\{101}", 0); + CHECKFAIL_ASSERT(1, cvar_type, "\{97}\{97}\{95}\{101}\{110}\{97}\{98}\{108}\{101}", 0); + CHECKFAIL_ASSERT(2, cvar, "\{114}\{95}\{115}\{104}\{111}\{119}\{100}\{105}\{115}\{97}\{98}\{108}\{101}\{100}\{101}\{112}\{116}\{104}\{116}\{101}\{115}\{116}", 0); + CHECKFAIL_ASSERT(3, cvar, "\{114}\{95}\{115}\{104}\{111}\{119}\{111}\{118}\{101}\{114}\{100}\{114}\{97}\{119}", 0); + CHECKFAIL_ASSERT(4, cvar, "\{114}\{95}\{115}\{104}\{111}\{119}\{108}\{105}\{103}\{104}\{116}", 0); + CHECKFAIL_ASSERT(5, cvar, "\{114}\{95}\{115}\{104}\{111}\{119}\{115}\{104}\{97}\{100}\{111}\{119}\{118}\{111}\{108}\{117}\{109}\{101}\{115}", 0); + CHECKFAIL_ASSERT(6, cvar, "\{114}\{95}\{115}\{104}\{111}\{119}\{111}\{118}\{101}\{114}\{100}\{114}\{97}\{119}", 0); + + vf_size = getpropertyvec(VF_SIZE); + vf_min = getpropertyvec(VF_MIN); + vid_width = vf_size.x; + vid_height = vf_size.y; + + vector reticle_pos = '0 0 0', reticle_size = '0 0 0'; + vector splash_pos = '0 0 0', splash_size = '0 0 0'; + + WaypointSprite_Load(); + + CSQCPlayer_SetCamera(); + + myteam = GetPlayerColor(player_localentnum - 1); + + if(myteam != prev_myteam) + { + myteamcolors = colormapPaletteColor(myteam, 1); + for(i = 0; i < HUD_PANEL_NUM; ++i) + hud_panel[i].update_time = time; + prev_myteam = myteam; + } + + ticrate = getstatf(STAT_MOVEVARS_TICRATE) * getstatf(STAT_MOVEVARS_TIMESCALE); + + float is_dead = (getstati(STAT_HEALTH) <= 0); + + // FIXME do we need this hack? + if(isdemo()) + { + // in demos, input_buttons do not work + button_zoom = (autocvar__togglezoom == "-"); + } + else if(button_zoom + && autocvar_cl_unpress_zoom_on_death + && (spectatee_status >= 0) + && (is_dead || intermission)) + { + // no zoom while dead or in intermission please + localcmd("-zoom\n"); + button_zoom = false; + } + + // event chase camera + if(autocvar_chase_active <= 0) // greater than 0 means it's enabled manually, and this code is skipped + { - if(WantEventchase()) ++ float ons_roundlost = (gametype == MAPINFO_TYPE_ONSLAUGHT && getstati(STAT_ROUNDLOST)); ++ entity gen = world; ++ ++ if(ons_roundlost) ++ { ++ entity e; ++ for(e = world; (e = find(e, classname, "onslaught_generator")); ) ++ { ++ if(e.health <= 0) ++ { ++ gen = e; ++ break; ++ } ++ } ++ if(!gen) ++ ons_roundlost = FALSE; // don't enforce the 3rd person camera if there is no dead generator to show ++ } ++ if(WantEventchase() || (!autocvar_cl_orthoview && ons_roundlost)) + { + eventchase_running = true; + + // make special vector since we can't use view_origin (It is one frame old as of this code, it gets set later with the results this code makes.) + vector current_view_origin = (csqcplayer ? csqcplayer.origin : pmove_org); ++ if(ons_roundlost) { current_view_origin = gen.origin; } + + // detect maximum viewoffset and use it - if(autocvar_cl_eventchase_viewoffset) ++ vector view_offset = autocvar_cl_eventchase_viewoffset; ++ if(ons_roundlost) { view_offset = autocvar_cl_eventchase_generator_viewoffset; } ++ ++ if(view_offset) + { - WarpZone_TraceLine(current_view_origin, current_view_origin + autocvar_cl_eventchase_viewoffset + ('0 0 1' * autocvar_cl_eventchase_maxs.z), MOVE_WORLDONLY, self); - if(trace_fraction == 1) { current_view_origin += autocvar_cl_eventchase_viewoffset; } ++ WarpZone_TraceLine(current_view_origin, current_view_origin + view_offset + ('0 0 1' * autocvar_cl_eventchase_maxs.z), MOVE_WORLDONLY, self); ++ if(trace_fraction == 1) { current_view_origin += view_offset; } + else { current_view_origin.z += max(0, (trace_endpos.z - current_view_origin.z) - autocvar_cl_eventchase_maxs.z); } + } + + // We must enable chase_active to get a third person view (weapon viewmodel hidden and own player model showing). + // Ideally, there should be another way to enable third person cameras, such as through setproperty() + // -1 enables chase_active while marking it as set by this code, and not by the user (which would be 1) + if(!autocvar_chase_active) { cvar_set("chase_active", "-1"); } + + // make the camera smooth back - if(autocvar_cl_eventchase_speed && eventchase_current_distance < autocvar_cl_eventchase_distance) - eventchase_current_distance += autocvar_cl_eventchase_speed * (autocvar_cl_eventchase_distance - eventchase_current_distance) * frametime; // slow down the further we get - else if(eventchase_current_distance != autocvar_cl_eventchase_distance) - eventchase_current_distance = autocvar_cl_eventchase_distance; ++ float chase_distance = autocvar_cl_eventchase_distance; ++ if(ons_roundlost) { chase_distance = autocvar_cl_eventchase_generator_distance; } ++ ++ if(autocvar_cl_eventchase_speed && eventchase_current_distance < chase_distance) ++ eventchase_current_distance += autocvar_cl_eventchase_speed * (chase_distance - eventchase_current_distance) * frametime; // slow down the further we get ++ else if(eventchase_current_distance != chase_distance) ++ eventchase_current_distance = chase_distance; + + makevectors(view_angles); + + vector eventchase_target_origin = (current_view_origin - (v_forward * eventchase_current_distance)); + WarpZone_TraceBox(current_view_origin, autocvar_cl_eventchase_mins, autocvar_cl_eventchase_maxs, eventchase_target_origin, MOVE_WORLDONLY, self); + + // If the boxtrace fails, revert back to line tracing. + if(trace_startsolid) + { + eventchase_target_origin = (current_view_origin - (v_forward * eventchase_current_distance)); + WarpZone_TraceLine(current_view_origin, eventchase_target_origin, MOVE_WORLDONLY, self); + setproperty(VF_ORIGIN, (trace_endpos - (v_forward * autocvar_cl_eventchase_mins.z))); + } + else { setproperty(VF_ORIGIN, trace_endpos); } + + setproperty(VF_ANGLES, WarpZone_TransformVAngles(WarpZone_trace_transform, view_angles)); + } + else if(autocvar_chase_active < 0) // time to disable chase_active if it was set by this code + { + eventchase_running = false; + cvar_set("chase_active", "0"); + eventchase_current_distance = 0; // start from 0 next time + } + } + // workaround for camera stuck between player's legs when using chase_active 1 + // because the engine stops updating the chase_active camera when the game ends + else if(intermission) + { + cvar_settemp("chase_active", "-1"); + eventchase_current_distance = 0; + } + + // do lockview after event chase camera so that it still applies whenever necessary. + if(autocvar_cl_lockview || (!autocvar_hud_cursormode && (autocvar__hud_configure && spectatee_status <= 0 || intermission > 1))) + { + setproperty(VF_ORIGIN, freeze_org); + setproperty(VF_ANGLES, freeze_ang); + } + else + { + freeze_org = getpropertyvec(VF_ORIGIN); + freeze_ang = getpropertyvec(VF_ANGLES); + } + + WarpZone_FixView(); + //WarpZone_FixPMove(); + + vector ov_org = '0 0 0'; + vector ov_mid = '0 0 0'; + vector ov_worldmin = '0 0 0'; + vector ov_worldmax = '0 0 0'; + if(autocvar_cl_orthoview) + { + ov_worldmin = mi_picmin; + ov_worldmax = mi_picmax; + + float ov_width = (ov_worldmax.x - ov_worldmin.x); + float ov_height = (ov_worldmax.y - ov_worldmin.y); + float ov_distance = (max(vid_width, vid_height) * max(ov_width, ov_height)); + + ov_mid = ((ov_worldmax + ov_worldmin) * 0.5); + ov_org = vec3(ov_mid.x, ov_mid.y, (ov_mid.z + ov_distance)); + + float ov_nearest = vlen(ov_org - vec3( + bound(ov_worldmin.x, ov_org.x, ov_worldmax.x), + bound(ov_worldmin.y, ov_org.y, ov_worldmax.y), + bound(ov_worldmin.z, ov_org.z, ov_worldmax.z) + )); + + float ov_furthest = 0; + float dist = 0; + + if((dist = vlen(vec3(ov_worldmin.x, ov_worldmin.y, ov_worldmin.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmax.x, ov_worldmin.y, ov_worldmin.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmin.x, ov_worldmax.y, ov_worldmin.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmin.x, ov_worldmin.y, ov_worldmax.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmax.x, ov_worldmax.y, ov_worldmin.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmin.x, ov_worldmax.y, ov_worldmax.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmax.x, ov_worldmin.y, ov_worldmax.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + if((dist = vlen(vec3(ov_worldmax.x, ov_worldmax.y, ov_worldmax.z) - ov_org)) > ov_furthest) { ov_furthest = dist; } + + cvar_settemp("r_nearclip", ftos(ov_nearest)); + cvar_settemp("r_farclip_base", ftos(ov_furthest)); + cvar_settemp("r_farclip_world", "0"); + cvar_settemp("r_novis", "1"); + cvar_settemp("r_useportalculling", "0"); + cvar_settemp("r_useinfinitefarclip", "0"); + + setproperty(VF_ORIGIN, ov_org); + setproperty(VF_ANGLES, '90 0 0'); + + #if 0 + printf("OrthoView: org = %s, angles = %s, distance = %f, nearest = %f, furthest = %f\n", + vtos(ov_org), + vtos(getpropertyvec(VF_ANGLES)), + ov_distance, + ov_nearest, + ov_furthest); + #endif + } + + // Render the Scene + view_origin = getpropertyvec(VF_ORIGIN); + view_angles = getpropertyvec(VF_ANGLES); + makevectors(view_angles); + view_forward = v_forward; + view_right = v_right; + view_up = v_up; + + #ifdef BLURTEST + if(time > blurtest_time0 && time < blurtest_time1) + { + float r, t; + + t = (time - blurtest_time0) / (blurtest_time1 - blurtest_time0); + r = t * blurtest_radius; + f = 1 / pow(t, blurtest_power) - 1; + + cvar_set("r_glsl_postprocess", "1"); + cvar_set("r_glsl_postprocess_uservec1", strcat(ftos(r), " ", ftos(f), " 0 0")); + } + else + { + cvar_set("r_glsl_postprocess", "0"); + cvar_set("r_glsl_postprocess_uservec1", "0 0 0 0"); + } + #endif + + TargetMusic_Advance(); + Fog_Force(); + + if(drawtime == 0) + drawframetime = 0.01666667; // when we don't know fps yet, we assume 60fps + else + drawframetime = bound(0.000001, time - drawtime, 1); + drawtime = time; + + // watch for gametype changes here... + // in ParseStuffCMD the cmd isn't executed yet :/ + // might even be better to add the gametype to TE_CSQC_INIT...? + if(!postinit) + PostInit(); + + if(intermission && !isdemo() && !(calledhooks & HOOK_END)) + { + if(calledhooks & HOOK_START) + { + localcmd("\ncl_hook_gameend\n"); + calledhooks |= HOOK_END; + } + } + + Announcer(); + + fov = autocvar_fov; + if(fov <= 59.5) + { + if(!zoomscript_caught) + { + localcmd("+button9\n"); + zoomscript_caught = 1; + } + } + else + { + if(zoomscript_caught) + { + localcmd("-button9\n"); + zoomscript_caught = 0; + } + } + + ColorTranslateMode = autocvar_cl_stripcolorcodes; + + // next WANTED weapon (for HUD) + switchweapon = getstati(STAT_SWITCHWEAPON); + + // currently switching-to weapon (for crosshair) + switchingweapon = getstati(STAT_SWITCHINGWEAPON); + + // actually active weapon (for zoom) + activeweapon = getstati(STAT_ACTIVEWEAPON); + + f = (serverflags & SERVERFLAG_TEAMPLAY); + if(f != teamplay) + { + teamplay = f; + HUD_InitScores(); + } + + if(last_switchweapon != switchweapon) + { + weapontime = time; + last_switchweapon = switchweapon; + if(button_zoom && autocvar_cl_unpress_zoom_on_weapon_switch) + { + localcmd("-zoom\n"); + button_zoom = false; + } + if(autocvar_cl_unpress_attack_on_weapon_switch) + { + localcmd("-fire\n"); + localcmd("-fire2\n"); + button_attack2 = false; + } + } + if(last_activeweapon != activeweapon) + { + last_activeweapon = activeweapon; + + e = get_weaponinfo(activeweapon); + if(e.netname != "") + localcmd(strcat("\ncl_hook_activeweapon ", e.netname), "\n"); + else + localcmd("\ncl_hook_activeweapon none\n"); + } + + // ALWAYS Clear Current Scene First + clearscene(); + + setproperty(VF_ORIGIN, view_origin); + setproperty(VF_ANGLES, view_angles); + + // FIXME engine bug? VF_SIZE and VF_MIN are not restored to sensible values by this + setproperty(VF_SIZE, vf_size); + setproperty(VF_MIN, vf_min); + + // Assign Standard Viewflags + // Draw the World (and sky) + setproperty(VF_DRAWWORLD, 1); + + // Set the console size vars + vid_conwidth = autocvar_vid_conwidth; + vid_conheight = autocvar_vid_conheight; + vid_pixelheight = autocvar_vid_pixelheight; + + if(autocvar_cl_orthoview) { setproperty(VF_FOV, GetOrthoviewFOV(ov_worldmin, ov_worldmax, ov_mid, ov_org)); } + else { setproperty(VF_FOV, GetCurrentFov(fov)); } + + // Camera for demo playback + if(camera_active) + { + if(autocvar_camera_enable) + CSQC_Demo_Camera(); + else + { + cvar_set("chase_active", ftos(chase_active_backup)); + cvar_set("cl_demo_mousegrab", "0"); + camera_active = false; + } + } + else + { + #ifdef CAMERATEST + if(autocvar_camera_enable) + #else + if(autocvar_camera_enable && isdemo()) + #endif + { + // Enable required Darkplaces cvars + chase_active_backup = autocvar_chase_active; + cvar_set("chase_active", "2"); + cvar_set("cl_demo_mousegrab", "1"); + camera_active = true; + camera_mode = false; + } + } + + // Draw the Crosshair + setproperty(VF_DRAWCROSSHAIR, 0); //Make sure engine crosshairs are always hidden + + // Draw the Engine Status Bar (the default Quake HUD) + setproperty(VF_DRAWENGINESBAR, 0); + + // Update the mouse position + /* + mousepos_x = vid_conwidth; + mousepos_y = vid_conheight; + mousepos = mousepos*0.5 + getmousepos(); + */ + + e = self; + for(self = world; (self = nextent(self)); ) + if(self.draw) + self.draw(); + self = e; + + addentities(MASK_NORMAL | MASK_ENGINE | MASK_ENGINEVIEWMODELS); + renderscene(); + + // now switch to 2D drawing mode by calling a 2D drawing function + // then polygon drawing will draw as 2D stuff, and NOT get queued until the + // next R_RenderScene call + drawstring('0 0 0', "", '1 1 0', '1 1 1', 0, 0); + + if(autocvar_r_fakelight >= 2 || autocvar_r_fullbright) + if (!(serverflags & SERVERFLAG_ALLOW_FULLBRIGHT)) + { + // apply night vision effect + vector tc_00, tc_01, tc_10, tc_11; + vector rgb = '0 0 0'; + + if(!nightvision_noise) + { + nightvision_noise = spawn(); + nightvision_noise.classname = "nightvision_noise"; + } + if(!nightvision_noise2) + { + nightvision_noise2 = spawn(); + nightvision_noise2.classname = "nightvision_noise2"; + } + + // color tint in yellow + drawfill('0 0 0', autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', '0.5 1 0.3', 1, DRAWFLAG_MODULATE); + + // draw BG + a = Noise_Pink(nightvision_noise, frametime * 1.5) * 0.05 + 0.15; + rgb = '1 1 1'; + tc_00 = '0 0 0' + '0.2 0 0' * sin(time * 0.3) + '0 0.3 0' * cos(time * 0.7); + tc_01 = '0 2.25 0' + '0.6 0 0' * cos(time * 1.2) - '0 0.3 0' * sin(time * 2.2); + tc_10 = '1.5 0 0' - '0.2 0 0' * sin(time * 0.5) + '0 0.5 0' * cos(time * 1.7); + //tc_11 = '1 1 0' + '0.6 0 0' * sin(time * 0.6) + '0 0.3 0' * cos(time * 0.1); + tc_11 = tc_01 + tc_10 - tc_00; + R_BeginPolygon("gfx/nightvision-bg.tga", DRAWFLAG_ADDITIVE); + R_PolygonVertex('0 0 0', tc_00, rgb, a); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', tc_10, rgb, a); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', tc_11, rgb, a); + R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, a); + R_EndPolygon(); + + // draw FG + a = Noise_Pink(nightvision_noise2, frametime * 0.1) * 0.05 + 0.12; + rgb = '0.3 0.6 0.4' + '0.1 0.4 0.2' * Noise_White(nightvision_noise2, frametime); + tc_00 = '0 0 0' + '1 0 0' * Noise_White(nightvision_noise2, frametime) + '0 1 0' * Noise_White(nightvision_noise2, frametime); + tc_01 = tc_00 + '0 3 0' * (1 + Noise_White(nightvision_noise2, frametime) * 0.2); + tc_10 = tc_00 + '2 0 0' * (1 + Noise_White(nightvision_noise2, frametime) * 0.3); + tc_11 = tc_01 + tc_10 - tc_00; + R_BeginPolygon("gfx/nightvision-fg.tga", DRAWFLAG_ADDITIVE); + R_PolygonVertex('0 0 0', tc_00, rgb, a); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0', tc_10, rgb, a); + R_PolygonVertex(autocvar_vid_conwidth * '1 0 0' + autocvar_vid_conheight * '0 1 0', tc_11, rgb, a); + R_PolygonVertex(autocvar_vid_conheight * '0 1 0', tc_01, rgb, a); + R_EndPolygon(); + } + + if(autocvar_cl_reticle) + { + // Draw the aiming reticle for weapons that use it + // reticle_type is changed to the item we are zooming / aiming with, to decide which reticle to use + // It must be a persisted float for fading out to work properly (you let go of the zoom button for + // the view to go back to normal, so reticle_type would become 0 as we fade out) + if(spectatee_status || is_dead || hud != HUD_NORMAL) + { + // no zoom reticle while dead + reticle_type = 0; + } + else if(WEP_ACTION(activeweapon, WR_ZOOMRETICLE) && autocvar_cl_reticle_weapon) + { + if(reticle_image != "") { reticle_type = 2; } + else { reticle_type = 0; } + } + else if(button_zoom || zoomscript_caught) + { + // normal zoom + reticle_type = 1; + } + + if(reticle_type) + { + if(autocvar_cl_reticle_stretch) + { + reticle_size.x = vid_conwidth; + reticle_size.y = vid_conheight; + reticle_pos.x = 0; + reticle_pos.y = 0; + } + else + { + reticle_size.x = max(vid_conwidth, vid_conheight); + reticle_size.y = max(vid_conwidth, vid_conheight); + reticle_pos.x = (vid_conwidth - reticle_size.x) / 2; + reticle_pos.y = (vid_conheight - reticle_size.y) / 2; + } + + if(zoomscript_caught) + f = 1; + else + f = current_zoomfraction; + + if(f) + { + switch(reticle_type) + { + case 1: drawpic(reticle_pos, "gfx/reticle_normal", reticle_size, '1 1 1', f * autocvar_cl_reticle_normal_alpha, DRAWFLAG_NORMAL); break; + case 2: drawpic(reticle_pos, reticle_image, reticle_size, '1 1 1', f * autocvar_cl_reticle_weapon_alpha, DRAWFLAG_NORMAL); break; + } + } + } + } + else + { + if(reticle_type != 0) { reticle_type = 0; } + } + + + // improved polyblend + if(autocvar_hud_contents) + { + float contentalpha_temp, incontent, liquidalpha, contentfadetime; + vector liquidcolor; + + switch(pointcontents(view_origin)) + { + case CONTENT_WATER: + liquidalpha = autocvar_hud_contents_water_alpha; + liquidcolor = stov(autocvar_hud_contents_water_color); + incontent = 1; + break; + + case CONTENT_LAVA: + liquidalpha = autocvar_hud_contents_lava_alpha; + liquidcolor = stov(autocvar_hud_contents_lava_color); + incontent = 1; + break; + + case CONTENT_SLIME: + liquidalpha = autocvar_hud_contents_slime_alpha; + liquidcolor = stov(autocvar_hud_contents_slime_color); + incontent = 1; + break; + + default: + liquidalpha = 0; + liquidcolor = '0 0 0'; + incontent = 0; + break; + } + + if(incontent) // fade in/out at different speeds so you can do e.g. instant fade when entering water and slow when leaving it. + { // also lets delcare previous values for blending properties, this way it isn't reset until after you have entered a different content + contentfadetime = autocvar_hud_contents_fadeintime; + liquidalpha_prev = liquidalpha; + liquidcolor_prev = liquidcolor; + } + else + contentfadetime = autocvar_hud_contents_fadeouttime; + + contentalpha_temp = bound(0, drawframetime / max(0.0001, contentfadetime), 1); + contentavgalpha = contentavgalpha * (1 - contentalpha_temp) + incontent * contentalpha_temp; + + if(contentavgalpha) + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, liquidcolor_prev, contentavgalpha * liquidalpha_prev, DRAWFLAG_NORMAL); + + if(autocvar_hud_postprocessing) + { + if(autocvar_hud_contents_blur && contentavgalpha) + { + content_blurpostprocess.x = 1; + content_blurpostprocess.y = contentavgalpha * autocvar_hud_contents_blur; + content_blurpostprocess.z = contentavgalpha * autocvar_hud_contents_blur_alpha; + } + else + { + content_blurpostprocess.x = 0; + content_blurpostprocess.y = 0; + content_blurpostprocess.z = 0; + } + } + } + + if(autocvar_hud_damage && !getstati(STAT_FROZEN)) + { + splash_size.x = max(vid_conwidth, vid_conheight); + splash_size.y = max(vid_conwidth, vid_conheight); + splash_pos.x = (vid_conwidth - splash_size.x) / 2; + splash_pos.y = (vid_conheight - splash_size.y) / 2; + + float myhealth_flash_temp; + myhealth = getstati(STAT_HEALTH); + + // fade out + myhealth_flash = max(0, myhealth_flash - autocvar_hud_damage_fade_rate * frametime); + // add new damage + myhealth_flash = bound(0, myhealth_flash + dmg_take * autocvar_hud_damage_factor, autocvar_hud_damage_maxalpha); + + float pain_threshold, pain_threshold_lower, pain_threshold_lower_health; + pain_threshold = autocvar_hud_damage_pain_threshold; + pain_threshold_lower = autocvar_hud_damage_pain_threshold_lower; + pain_threshold_lower_health = autocvar_hud_damage_pain_threshold_lower_health; + + if(pain_threshold_lower && myhealth < pain_threshold_lower_health) + { + pain_threshold = pain_threshold - max(autocvar_hud_damage_pain_threshold_pulsating_min, fabs(sin(M_PI * time / autocvar_hud_damage_pain_threshold_pulsating_period))) * pain_threshold_lower * (1 - max(0, myhealth)/pain_threshold_lower_health); + } + + myhealth_flash_temp = bound(0, myhealth_flash - pain_threshold, 1); + + if(myhealth_prev < 1) + { + if(myhealth >= 1) + { + myhealth_flash = 0; // just spawned, clear the flash immediately + myhealth_flash_temp = 0; + } + else + { + myhealth_flash += autocvar_hud_damage_fade_rate * frametime; // dead + } + } + + if(spectatee_status == -1 || intermission) + { + myhealth_flash = 0; // observing, or match ended + myhealth_flash_temp = 0; + } + + myhealth_prev = myhealth; + + // IDEA: change damage color/picture based on player model for robot/alien species? + // pro: matches model better + // contra: it's not red because blood is red, but because red is an alarming color, so red should stay + // maybe different reddish pics? + if(autocvar_cl_gentle_damage || autocvar_cl_gentle) + { + if(autocvar_cl_gentle_damage == 2) + { + if(myhealth_flash < pain_threshold) // only randomize when the flash is gone + { + myhealth_gentlergb = eX * random() + eY * random() + eZ * random(); + } + } + else + myhealth_gentlergb = stov(autocvar_hud_damage_gentle_color); + + drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, myhealth_gentlergb, autocvar_hud_damage_gentle_alpha_multiplier * bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage, DRAWFLAG_NORMAL); + } + else + drawpic(splash_pos, "gfx/blood", splash_size, stov(autocvar_hud_damage_color), bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage, DRAWFLAG_NORMAL); + + if(autocvar_hud_postprocessing) // we still need to set this anyway even when chase_active is set, this way it doesn't get stuck on. + { + if(autocvar_hud_damage_blur && myhealth_flash_temp) + { + damage_blurpostprocess.x = 1; + damage_blurpostprocess.y = bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage_blur; + damage_blurpostprocess.z = bound(0, myhealth_flash_temp, 1) * autocvar_hud_damage_blur_alpha; + } + else + { + damage_blurpostprocess.x = 0; + damage_blurpostprocess.y = 0; + damage_blurpostprocess.z = 0; + } + } + } + + float e1 = (autocvar_hud_postprocessing_maxbluralpha != 0); + float e2 = (autocvar_hud_powerup != 0); + if(autocvar_hud_postprocessing && (e1 || e2)) // TODO: Remove this code and re-do the postprocess handling in the engine, where it properly belongs. + { + // enable or disable rendering types if they are used or not + if(cvar("r_glsl_postprocess_uservec1_enable") != e1) { cvar_set("r_glsl_postprocess_uservec1_enable", ftos(e1)); } + if(cvar("r_glsl_postprocess_uservec2_enable") != e2) { cvar_set("r_glsl_postprocess_uservec2_enable", ftos(e2)); } + + // blur postprocess handling done first (used by hud_damage and hud_contents) + if((damage_blurpostprocess.x || content_blurpostprocess.x)) + { + float blurradius = bound(0, damage_blurpostprocess.y + content_blurpostprocess.y, autocvar_hud_postprocessing_maxblurradius); + float bluralpha = bound(0, damage_blurpostprocess.z + content_blurpostprocess.z, autocvar_hud_postprocessing_maxbluralpha); + if(blurradius != old_blurradius || bluralpha != old_bluralpha) // reduce cvar_set spam as much as possible + { + cvar_set("r_glsl_postprocess_uservec1", strcat(ftos(blurradius), " ", ftos(bluralpha), " 0 0")); + old_blurradius = blurradius; + old_bluralpha = bluralpha; + } + } + else if(cvar_string("r_glsl_postprocess_uservec1") != "0 0 0 0") // reduce cvar_set spam as much as possible + { + cvar_set("r_glsl_postprocess_uservec1", "0 0 0 0"); + old_blurradius = 0; + old_bluralpha = 0; + } + + // edge detection postprocess handling done second (used by hud_powerup) + float sharpen_intensity = 0, strength_finished = getstatf(STAT_STRENGTH_FINISHED), invincible_finished = getstatf(STAT_INVINCIBLE_FINISHED); + if (strength_finished - time > 0) { sharpen_intensity += (strength_finished - time); } + if (invincible_finished - time > 0) { sharpen_intensity += (invincible_finished - time); } + + sharpen_intensity = bound(0, ((getstati(STAT_HEALTH) > 0) ? sharpen_intensity : 0), 5); // Check to see if player is alive (if not, set 0) - also bound to fade out starting at 5 seconds. + + if(autocvar_hud_powerup && sharpen_intensity > 0) + { + if(sharpen_intensity != old_sharpen_intensity) // reduce cvar_set spam as much as possible + { + cvar_set("r_glsl_postprocess_uservec2", strcat(ftos((sharpen_intensity / 5) * autocvar_hud_powerup), " ", ftos(-sharpen_intensity * autocvar_hud_powerup), " 0 0")); + old_sharpen_intensity = sharpen_intensity; + } + } + else if(cvar_string("r_glsl_postprocess_uservec2") != "0 0 0 0") // reduce cvar_set spam as much as possible + { + cvar_set("r_glsl_postprocess_uservec2", "0 0 0 0"); + old_sharpen_intensity = 0; + } + + if(cvar("r_glsl_postprocess") == 0) + cvar_set("r_glsl_postprocess", "2"); + } + else if(cvar("r_glsl_postprocess") == 2) + cvar_set("r_glsl_postprocess", "0"); + + if(menu_visible) + menu_show(); + + /*if(gametype == MAPINFO_TYPE_CTF) + { + ctf_view(); + } else */ + + // draw 2D entities + e = self; + for(self = world; (self = nextent(self)); ) + if(self.draw2d) + self.draw2d(); + self = e; + Draw_ShowNames_All(); + + scoreboard_active = HUD_WouldDrawScoreboard(); + + UpdateDamage(); + UpdateCrosshair(); + UpdateHitsound(); + + if(NextFrameCommand) + { + localcmd("\n", NextFrameCommand, "\n"); + NextFrameCommand = string_null; + } + + // we must do this check AFTER a frame was rendered, or it won't work + if(cs_project_is_b0rked == 0) + { + string w0, h0; + w0 = ftos(autocvar_vid_conwidth); + h0 = ftos(autocvar_vid_conheight); + //setproperty(VF_VIEWPORT, '0 0 0', '640 480 0'); + //setproperty(VF_FOV, '90 90 0'); + setproperty(VF_ORIGIN, '0 0 0'); + setproperty(VF_ANGLES, '0 0 0'); + setproperty(VF_PERSPECTIVE, 1); + makevectors('0 0 0'); + vector v1, v2; + cvar_set("vid_conwidth", "800"); + cvar_set("vid_conheight", "600"); + v1 = cs_project(v_forward); + cvar_set("vid_conwidth", "640"); + cvar_set("vid_conheight", "480"); + v2 = cs_project(v_forward); + if(v1 == v2) + cs_project_is_b0rked = 1; + else + cs_project_is_b0rked = -1; + cvar_set("vid_conwidth", w0); + cvar_set("vid_conheight", h0); + } + + if(autocvar__hud_configure) + HUD_Panel_Mouse(); ++ else ++ HUD_Radar_Mouse(); + + if(hud && !intermission) + { + if(hud == HUD_SPIDERBOT) + CSQC_SPIDER_HUD(); + else if(hud == HUD_WAKIZASHI) + CSQC_WAKIZASHI_HUD(); + else if(hud == HUD_RAPTOR) + CSQC_RAPTOR_HUD(); + else if(hud == HUD_BUMBLEBEE) + CSQC_BUMBLE_HUD(); + else if(hud == HUD_BUMBLEBEE_GUN) + CSQC_BUMBLE_GUN_HUD(); + } + + cl_notice_run(); + + // let's reset the view back to normal for the end + setproperty(VF_MIN, '0 0 0'); + setproperty(VF_SIZE, '1 0 0' * w + '0 1 0' * h); + } + + + void CSQC_common_hud(void) + { + if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) + Accuracy_LoadLevels(); + + HUD_Main(); // always run these functions for alpha checks + HUD_DrawScoreboard(); + + if (scoreboard_active) // scoreboard/accuracy + HUD_Reset(); + else if (intermission == 2) // map voting screen + { + MapVote_Draw(); + HUD_Reset(); + } + } + + + // following vectors must be global to allow seamless switching between camera modes + vector camera_offset, current_camera_offset, mouse_angles, current_angles, current_origin, current_position; + void CSQC_Demo_Camera() + { + float speed, attenuation, dimensions; + vector tmp, delta; + + if( autocvar_camera_reset || !camera_mode ) + { + camera_offset = '0 0 0'; + current_angles = '0 0 0'; + camera_direction = '0 0 0'; + camera_offset.z += 30; + camera_offset.x += 30 * -cos(current_angles.y * DEG2RAD); + camera_offset.y += 30 * -sin(current_angles.y * DEG2RAD); + current_origin = view_origin; + current_camera_offset = camera_offset; + cvar_set("camera_reset", "0"); + camera_mode = CAMERA_CHASE; + } + + // Camera angles + if( camera_roll ) + mouse_angles.z += camera_roll * autocvar_camera_speed_roll; + + if(autocvar_camera_look_player) + { + vector dir; + float n; + + dir = normalize(view_origin - current_position); + n = mouse_angles.z; + mouse_angles = vectoangles(dir); + mouse_angles.x = mouse_angles.x * -1; + mouse_angles.z = n; + } + else + { + tmp = getmousepos() * 0.1; + if(vlen(tmp)>autocvar_camera_mouse_threshold) + { + mouse_angles.x += tmp.y * cos(mouse_angles.z * DEG2RAD) + (tmp.x * sin(mouse_angles.z * DEG2RAD)); + mouse_angles.y -= tmp.x * cos(mouse_angles.z * DEG2RAD) + (tmp.y * -sin(mouse_angles.z * DEG2RAD)); + } + } + + while (mouse_angles.x < -180) mouse_angles.x = mouse_angles.x + 360; + while (mouse_angles.x > 180) mouse_angles.x = mouse_angles.x - 360; + while (mouse_angles.y < -180) mouse_angles.y = mouse_angles.y + 360; + while (mouse_angles.y > 180) mouse_angles.y = mouse_angles.y - 360; + + // Fix difference when angles don't have the same sign + delta = '0 0 0'; + if(mouse_angles.y < -60 && current_angles.y > 60) + delta = '0 360 0'; + if(mouse_angles.y > 60 && current_angles.y < -60) + delta = '0 -360 0'; + + if(autocvar_camera_look_player) + attenuation = autocvar_camera_look_attenuation; + else + attenuation = autocvar_camera_speed_attenuation; + + attenuation = 1 / max(1, attenuation); + current_angles += (mouse_angles - current_angles + delta) * attenuation; + + while (current_angles.x < -180) current_angles.x = current_angles.x + 360; + while (current_angles.x > 180) current_angles.x = current_angles.x - 360; + while (current_angles.y < -180) current_angles.y = current_angles.y + 360; + while (current_angles.y > 180) current_angles.y = current_angles.y - 360; + + // Camera position + tmp = '0 0 0'; + dimensions = 0; + + if( camera_direction.x ) + { + tmp.x = camera_direction.x * cos(current_angles.y * DEG2RAD); + tmp.y = camera_direction.x * sin(current_angles.y * DEG2RAD); + if( autocvar_camera_forward_follows && !autocvar_camera_look_player ) + tmp.z = camera_direction.x * -sin(current_angles.x * DEG2RAD); + ++dimensions; + } + + if( camera_direction.y ) + { + tmp.x += camera_direction.y * -sin(current_angles.y * DEG2RAD); + tmp.y += camera_direction.y * cos(current_angles.y * DEG2RAD) * cos(current_angles.z * DEG2RAD); + tmp.z += camera_direction.y * sin(current_angles.z * DEG2RAD); + ++dimensions; + } + + if( camera_direction.z ) + { + tmp.z += camera_direction.z * cos(current_angles.z * DEG2RAD); + ++dimensions; + } + + if(autocvar_camera_free) + speed = autocvar_camera_speed_free; + else + speed = autocvar_camera_speed_chase; + + if(dimensions) + { + speed = speed * sqrt(1 / dimensions); + camera_offset += tmp * speed; + } + + current_camera_offset += (camera_offset - current_camera_offset) * attenuation; + + // Camera modes + if( autocvar_camera_free ) + { + if ( camera_mode == CAMERA_CHASE ) + { + current_camera_offset = current_origin + current_camera_offset; + camera_offset = current_origin + camera_offset; + } + + camera_mode = CAMERA_FREE; + current_position = current_camera_offset; + } + else + { + if ( camera_mode == CAMERA_FREE ) + { + current_origin = view_origin; + camera_offset = camera_offset - current_origin; + current_camera_offset = current_camera_offset - current_origin; + } + + camera_mode = CAMERA_CHASE; + + if(autocvar_camera_chase_smoothly) + current_origin += (view_origin - current_origin) * attenuation; + else + current_origin = view_origin; + + current_position = current_origin + current_camera_offset; + } + + setproperty(VF_ANGLES, current_angles); + setproperty(VF_ORIGIN, current_position); + } diff --cc qcsrc/client/waypointsprites.qh index d52a048075,3822793da1..56fcd8e5f7 --- a/qcsrc/client/waypointsprites.qh +++ b/qcsrc/client/waypointsprites.qh @@@ -1,6 -1,86 +1,87 @@@ + #ifndef WAYPOINTSPRITES_H + #define WAYPOINTSPRITES_H + + + float waypointsprite_initialized; + float waypointsprite_fadedistance; + float waypointsprite_normdistance; + float waypointsprite_minscale; + float waypointsprite_minalpha; + float waypointsprite_distancealphaexponent; + float waypointsprite_timealphaexponent; + float waypointsprite_scale; + float waypointsprite_fontsize; + float waypointsprite_edgefadealpha; + float waypointsprite_edgefadescale; + float waypointsprite_edgefadedistance; + float waypointsprite_edgeoffset_bottom; + float waypointsprite_edgeoffset_left; + float waypointsprite_edgeoffset_right; + float waypointsprite_edgeoffset_top; + float waypointsprite_crosshairfadealpha; + float waypointsprite_crosshairfadescale; + float waypointsprite_crosshairfadedistance; + float waypointsprite_distancefadealpha; + float waypointsprite_distancefadescale; + float waypointsprite_distancefadedistance; + float waypointsprite_alpha; + + .float helpme; + .float rule; + .string netname; // primary picture + .string netname2; // secondary picture + .string netname3; // tertiary picture + .float team; // team that gets netname2 + .float lifetime; + .float fadetime; + .float maxdistance; + .int hideflags; + .float spawntime; + .float health; + .float build_started; + .float build_starthealth; + .float build_finished; + + const float SPRITE_HEALTHBAR_WIDTH = 144; + const float SPRITE_HEALTHBAR_HEIGHT = 9; + const float SPRITE_HEALTHBAR_MARGIN = 6; + const float SPRITE_HEALTHBAR_BORDER = 2; + const float SPRITE_HEALTHBAR_BORDERALPHA = 1; + const float SPRITE_HEALTHBAR_HEALTHALPHA = 0.5; + const float SPRITE_ARROW_SCALE = 1.0; + const float SPRITE_HELPME_BLINK = 2; + + float waypointsprite_count, waypointsprite_newcount; + + void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f); + + void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f); + + void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float height, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f); + + // returns location of sprite text + vector drawspritearrow(vector o, float ang, vector rgb, float a, float t); + + // returns location of sprite healthbar + vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s); + + float spritelookupblinkvalue(string s); + vector spritelookupcolor(string s, vector def); + string spritelookuptext(string s); + + vector fixrgbexcess_move(vector rgb, vector src, vector dst); + vector fixrgbexcess(vector rgb); + + void Draw_WaypointSprite(); + // they are drawn using a .draw function - void Ent_WaypointSprite(); + void Ent_RemoveWaypointSprite(); + + void Ent_WaypointSprite(); - .float health; + void WaypointSprite_Load_Frames(string ext); + + void WaypointSprite_Load(); + + #endif diff --cc qcsrc/common/constants.qh index e93f4b4004,45a65abbe9..df4f4ffc75 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@@ -1,126 -1,102 +1,104 @@@ - // COMMIT-TODO: Update if necessary before committing - // Revision 1: additional statistics sent (flag caps, returns, deaths) - // Revision 2: Mapvote preview pictures - // Revision 3: optimized map vote protocol - // Revision 4: CSQC config var system - // Revision 5: mapvote time fix - // Revision 6: more robust against packet loss/delays, also show not yet connected clients - // Revision 7: packet loss column - // Revision 8: race - // Revision 9: race delta - // Revision 10: scoreboard force - // Revision 11: scoreboard unforce; spectator support beginning - // Revision 12: smaller scores updates (SERVER: requires new engine) - // Revision 13: pointparticles - // Revision 14: laser - // Revision 15: zoom - // Revision 16: multi-weapons - // Revision 17: multi-weaponimpulses - // Revision 18: warmup - // Revision 19: fog - // Revision 20: naggers - // Revision 21: entcs for players optimized (position data down from 12 to 7 bytes); waypointsprites in csqc for team radar - // Revision 22: hook shot origin - #define CSQC_REVISION 22 - - const float AS_STRING = 1; - const float AS_INT = 2; - const float AS_FLOAT_TRUNCATED = 2; - const float AS_FLOAT = 8; - - const float TE_CSQC_PICTURE = 100; - const float TE_CSQC_RACE = 101; - const float TE_CSQC_VORTEXBEAMPARTICLE = 103; - const float TE_CSQC_ARC = 104; - const float TE_CSQC_TEAMNAGGER = 105; - const float TE_CSQC_PINGPLREPORT = 106; - const float TE_CSQC_TARGET_MUSIC = 107; - const float TE_CSQC_WEAPONCOMPLAIN = 108; - const float TE_CSQC_VORTEX_SCOPE = 109; - const float TE_CSQC_MINELAYER_MAXMINES = 110; - const float TE_CSQC_HAGAR_MAXROCKETS = 111; - const float TE_CSQC_VEHICLESETUP = 112; - const float TE_CSQC_SVNOTICE = 113; - const float TE_CSQC_SHOCKWAVEPARTICLE = 114; - - const float RACE_NET_CHECKPOINT_HIT_QUALIFYING = 0; // byte checkpoint, short time, short recordtime, string recordholder - const float RACE_NET_CHECKPOINT_CLEAR = 1; - const float RACE_NET_CHECKPOINT_NEXT_QUALIFYING = 2; // byte nextcheckpoint, short recordtime, string recordholder - const float RACE_NET_CHECKPOINT_HIT_RACE = 3; // byte checkpoint, short delta, byte lapsdelta, string opponent - const float RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT = 4; // byte checkpoint, short delta, byte lapsdelta, string opponent - const float RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING = 5; // byte nextcheckpoint, float laptime, short recordtime, string recordholder - const float RACE_NET_PENALTY_RACE = 6; // byte penaltytime, string reason - const float RACE_NET_PENALTY_QUALIFYING = 7; // byte penaltytime, string reason - const float RACE_NET_SERVER_RECORD = 8; // server record, sent to client - const float RACE_NET_SPEED_AWARD = 9; // speed award, sent to client - const float RACE_NET_SPEED_AWARD_BEST = 10; // all time best speed award, sent to client - const float RACE_NET_SERVER_RANKINGS = 11; - const float RACE_NET_SERVER_STATUS = 12; - const float RANKINGS_CNT = 15; - - const float ENT_CLIENT = 0; - const float ENT_CLIENT_DEAD = 1; - const float ENT_CLIENT_ENTCS = 2; - const float ENT_CLIENT_SCORES_INFO = 3; - const float ENT_CLIENT_SCORES = 4; - const float ENT_CLIENT_TEAMSCORES = 5; - const float ENT_CLIENT_POINTPARTICLES = 6; - const float ENT_CLIENT_RAINSNOW = 7; - const float ENT_CLIENT_LASER = 8; - const float ENT_CLIENT_NAGGER = 9; // flags [votecalledvote] - const float ENT_CLIENT_WAYPOINT = 10; // flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] - const float ENT_CLIENT_RADARLINK = 11; // flags [startorigin] [endorigin] [startcolor+16*endcolor] - const float ENT_CLIENT_PROJECTILE = 12; - const float ENT_CLIENT_GIBSPLASH = 13; - const float ENT_CLIENT_DAMAGEINFO = 14; - const float ENT_CLIENT_CASING = 15; - const float ENT_CLIENT_INIT = 16; - const float ENT_CLIENT_MAPVOTE = 17; - const float ENT_CLIENT_CLIENTDATA = 18; - const float ENT_CLIENT_RANDOMSEED = 19; - const float ENT_CLIENT_WALL = 20; - const float ENT_CLIENT_SPIDERBOT = 21; - const float ENT_CLIENT_MODELEFFECT = 22; - const float ENT_CLIENT_TUBANOTE = 23; - const float ENT_CLIENT_WARPZONE = 24; - const float ENT_CLIENT_WARPZONE_CAMERA = 25; - const float ENT_CLIENT_TRIGGER_MUSIC = 26; - const float ENT_CLIENT_HOOK = 27; - const float ENT_CLIENT_ARC_BEAM = 29; // WEAPONTODO: fix numbers - const float ENT_CLIENT_ACCURACY = 30; - const float ENT_CLIENT_SHOWNAMES = 31; - const float ENT_CLIENT_WARPZONE_TELEPORTED = 32; - const float ENT_CLIENT_MODEL = 33; - const float ENT_CLIENT_ITEM = 34; - const float ENT_CLIENT_BUMBLE_RAYGUN = 35; - const float ENT_CLIENT_SPAWNPOINT = 36; - const float ENT_CLIENT_SPAWNEVENT = 37; - const float ENT_CLIENT_NOTIFICATION = 38; - const float ENT_CLIENT_ELIMINATEDPLAYERS = 39; - const float ENT_CLIENT_TURRET = 40; - const float ENT_CLIENT_AUXILIARYXHAIR = 50; - const float ENT_CLIENT_VEHICLE = 60; - const float ENT_CLIENT_GENERATOR = 70; - const float ENT_CLIENT_CONTROLPOINT_ICON = 71; - - const float ENT_CLIENT_HEALING_ORB = 80; - - const float SPRITERULE_DEFAULT = 0; - const float SPRITERULE_TEAMPLAY = 1; - - const float RADARICON_NONE = 0; - const float RADARICON_FLAG = 1; - const float RADARICON_FLAGCARRIER = 1; - const float RADARICON_HERE = 1; // TODO make these 3 and 4, and make images for them - const float RADARICON_DANGER = 1; - const float RADARICON_WAYPOINT = 1; - const float RADARICON_HELPME = 1; - const float RADARICON_CONTROLPOINT = 1; - const float RADARICON_GENERATOR = 1; - const float RADARICON_OBJECTIVE = 1; - const float RADARICON_DOMPOINT = 1; - const float RADARICON_POWERUP = 1; - const float RADARICON_TAGGED = 1; + #ifndef CONSTANTS_H + #define CONSTANTS_H + + const int AS_STRING = 1; + const int AS_INT = 2; + const int AS_FLOAT_TRUNCATED = 2; + const int AS_FLOAT = 8; + + const int TE_CSQC_PICTURE = 100; + const int TE_CSQC_RACE = 101; + const int TE_CSQC_VORTEXBEAMPARTICLE = 103; + const int TE_CSQC_ARC = 104; + const int TE_CSQC_TEAMNAGGER = 105; + const int TE_CSQC_PINGPLREPORT = 106; + const int TE_CSQC_TARGET_MUSIC = 107; + const int TE_CSQC_WEAPONCOMPLAIN = 108; + const int TE_CSQC_VORTEX_SCOPE = 109; + const int TE_CSQC_MINELAYER_MAXMINES = 110; + const int TE_CSQC_HAGAR_MAXROCKETS = 111; + const int TE_CSQC_VEHICLESETUP = 112; + const int TE_CSQC_SVNOTICE = 113; + const int TE_CSQC_SHOCKWAVEPARTICLE = 114; + + const int RACE_NET_CHECKPOINT_HIT_QUALIFYING = 0; // byte checkpoint, short time, short recordtime, string recordholder + const int RACE_NET_CHECKPOINT_CLEAR = 1; + const int RACE_NET_CHECKPOINT_NEXT_QUALIFYING = 2; // byte nextcheckpoint, short recordtime, string recordholder + const int RACE_NET_CHECKPOINT_HIT_RACE = 3; // byte checkpoint, short delta, byte lapsdelta, string opponent + const int RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT = 4; // byte checkpoint, short delta, byte lapsdelta, string opponent + const int RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING = 5; // byte nextcheckpoint, float laptime, short recordtime, string recordholder + const int RACE_NET_PENALTY_RACE = 6; // byte penaltytime, string reason + const int RACE_NET_PENALTY_QUALIFYING = 7; // byte penaltytime, string reason + const int RACE_NET_SERVER_RECORD = 8; // server record, sent to client + const int RACE_NET_SPEED_AWARD = 9; // speed award, sent to client + const int RACE_NET_SPEED_AWARD_BEST = 10; // all time best speed award, sent to client + const int RACE_NET_SERVER_RANKINGS = 11; + const int RACE_NET_SERVER_STATUS = 12; + const int RANKINGS_CNT = 15; + + const int ENT_CLIENT = 0; + const int ENT_CLIENT_DEAD = 1; + const int ENT_CLIENT_ENTCS = 2; + const int ENT_CLIENT_SCORES_INFO = 3; + const int ENT_CLIENT_SCORES = 4; + const int ENT_CLIENT_TEAMSCORES = 5; + const int ENT_CLIENT_POINTPARTICLES = 6; + const int ENT_CLIENT_RAINSNOW = 7; + const int ENT_CLIENT_LASER = 8; + const int ENT_CLIENT_NAGGER = 9; // flags [votecalledvote] + const int ENT_CLIENT_WAYPOINT = 10; // flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] + const int ENT_CLIENT_RADARLINK = 11; // flags [startorigin] [endorigin] [startcolor+16*endcolor] + const int ENT_CLIENT_PROJECTILE = 12; + const int ENT_CLIENT_GIBSPLASH = 13; + const int ENT_CLIENT_DAMAGEINFO = 14; + const int ENT_CLIENT_CASING = 15; + const int ENT_CLIENT_INIT = 16; + const int ENT_CLIENT_MAPVOTE = 17; + const int ENT_CLIENT_CLIENTDATA = 18; + const int ENT_CLIENT_RANDOMSEED = 19; + const int ENT_CLIENT_WALL = 20; + const int ENT_CLIENT_SPIDERBOT = 21; + const int ENT_CLIENT_MODELEFFECT = 22; + const int ENT_CLIENT_TUBANOTE = 23; + const int ENT_CLIENT_WARPZONE = 24; + const int ENT_CLIENT_WARPZONE_CAMERA = 25; + const int ENT_CLIENT_TRIGGER_MUSIC = 26; + const int ENT_CLIENT_HOOK = 27; + const int ENT_CLIENT_ARC_BEAM = 29; // WEAPONTODO: fix numbers + const int ENT_CLIENT_ACCURACY = 30; + const int ENT_CLIENT_SHOWNAMES = 31; + const int ENT_CLIENT_WARPZONE_TELEPORTED = 32; + const int ENT_CLIENT_MODEL = 33; + const int ENT_CLIENT_ITEM = 34; + const int ENT_CLIENT_BUMBLE_RAYGUN = 35; + const int ENT_CLIENT_SPAWNPOINT = 36; + const int ENT_CLIENT_SPAWNEVENT = 37; + const int ENT_CLIENT_NOTIFICATION = 38; + const int ENT_CLIENT_ELIMINATEDPLAYERS = 39; + const int ENT_CLIENT_TURRET = 40; + const int ENT_CLIENT_AUXILIARYXHAIR = 50; + const int ENT_CLIENT_VEHICLE = 60; ++const int ENT_CLIENT_GENERATOR = 61; ++const int ENT_CLIENT_CONTROLPOINT_ICON = 62; + + const int ENT_CLIENT_HEALING_ORB = 80; + + const int SPRITERULE_DEFAULT = 0; + const int SPRITERULE_TEAMPLAY = 1; + + const int RADARICON_NONE = 0; + const int RADARICON_FLAG = 1; + const int RADARICON_FLAGCARRIER = 1; + const int RADARICON_HERE = 1; // TODO make these 3 and 4, and make images for them + const int RADARICON_DANGER = 1; + const int RADARICON_WAYPOINT = 1; + const int RADARICON_HELPME = 1; + const int RADARICON_CONTROLPOINT = 1; + const int RADARICON_GENERATOR = 1; + const int RADARICON_OBJECTIVE = 1; + const int RADARICON_DOMPOINT = 1; + const int RADARICON_POWERUP = 1; + const int RADARICON_TAGGED = 1; /////////////////////////// // keys pressed diff --cc qcsrc/common/mapinfo.qh index 16d9158834,6083350fa9..88f03c998b --- a/qcsrc/common/mapinfo.qh +++ b/qcsrc/common/mapinfo.qh @@@ -37,60 -40,60 +40,60 @@@ entity MapInfo_Type_last #define IS_GAMETYPE(NAME) \ (MapInfo_LoadedGametype == MAPINFO_TYPE_##NAME) - REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,FALSE,"timelimit=20 pointlimit=30 leadlimit=0",_("Kill all enemies")); + REGISTER_GAMETYPE(_("Deathmatch"),dm,g_dm,DEATHMATCH,false,"timelimit=20 pointlimit=30 leadlimit=0",_("Kill all enemies")); #define g_dm IS_GAMETYPE(DEATHMATCH) - REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,FALSE,"timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left")); + REGISTER_GAMETYPE(_("Last Man Standing"),lms,g_lms,LMS,false,"timelimit=20 lives=9 leadlimit=0",_("Survive and kill until the enemies have no lives left")); #define g_lms IS_GAMETYPE(LMS) - REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,FALSE,"timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line")); + REGISTER_GAMETYPE(_("Race"),rc,g_race,RACE,false,"timelimit=20 qualifying_timelimit=5 laplimit=7 teamlaplimit=15 leadlimit=0",_("Race against other players to the finish line")); #define g_race IS_GAMETYPE(RACE) - REGISTER_GAMETYPE(_("Race CTS"),cts,g_cts,CTS,FALSE,"timelimit=20 skill=-1",_("Race for fastest time")); + REGISTER_GAMETYPE(_("Race CTS"),cts,g_cts,CTS,false,"timelimit=20 skill=-1",_("Race for fastest time")); #define g_cts IS_GAMETYPE(CTS) - REGISTER_GAMETYPE(_("Team Deathmatch"),tdm,g_tdm,TEAM_DEATHMATCH,TRUE,"timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Kill all enemy teammates")); + REGISTER_GAMETYPE(_("Team Deathmatch"),tdm,g_tdm,TEAM_DEATHMATCH,true,"timelimit=20 pointlimit=50 teams=2 leadlimit=0",_("Kill all enemy teammates")); #define g_tdm IS_GAMETYPE(TEAM_DEATHMATCH) - REGISTER_GAMETYPE(_("Capture the Flag"),ctf,g_ctf,CTF,TRUE,"timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it")); + REGISTER_GAMETYPE(_("Capture the Flag"),ctf,g_ctf,CTF,true,"timelimit=20 caplimit=10 leadlimit=6",_("Find and bring the enemy flag to your base to capture it")); #define g_ctf IS_GAMETYPE(CTF) - REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,TRUE,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round")); + REGISTER_GAMETYPE(_("Clan Arena"),ca,g_ca,CA,true,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill all enemy teammates to win the round")); #define g_ca IS_GAMETYPE(CA) - REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,TRUE,"timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture all the control points to win")); + REGISTER_GAMETYPE(_("Domination"),dom,g_domination,DOMINATION,true,"timelimit=20 pointlimit=200 teams=2 leadlimit=0",_("Capture all the control points to win")); #define g_domination IS_GAMETYPE(DOMINATION) - REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,TRUE,"timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round")); + REGISTER_GAMETYPE(_("Key Hunt"),kh,g_keyhunt,KEYHUNT,true,"timelimit=20 pointlimit=1000 teams=3 leadlimit=0",_("Gather all the keys to win the round")); #define g_keyhunt IS_GAMETYPE(KEYHUNT) - REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,TRUE,"timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out")); + REGISTER_GAMETYPE(_("Assault"),as,g_assault,ASSAULT,true,"timelimit=20",_("Destroy obstacles to find and destroy the enemy power core before time runs out")); #define g_assault IS_GAMETYPE(ASSAULT) - REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,TRUE,"pointlimit = 1 timelimit=30",_("Capture control points to reach and destroy the enemy generator")); -REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,true,"timelimit=20",_("Capture control points to reach and destroy the enemy generator")); ++REGISTER_GAMETYPE(_("Onslaught"),ons,g_onslaught,ONSLAUGHT,true,"pointlimit=1 timelimit=20",_("Capture control points to reach and destroy the enemy generator")); #define g_onslaught IS_GAMETYPE(ONSLAUGHT) - REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,TRUE,"timelimit=20 pointlimit=5 leadlimit=0",_("XonSports")); + REGISTER_GAMETYPE(_("Nexball"),nb,g_nexball,NEXBALL,true,"timelimit=20 pointlimit=5 leadlimit=0",_("XonSports")); #define g_nexball IS_GAMETYPE(NEXBALL) - REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,TRUE,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to teammates to revive them")); + REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,true,"timelimit=20 pointlimit=10 teams=2 leadlimit=0",_("Kill enemies to freeze them, stand next to teammates to revive them")); #define g_freezetag IS_GAMETYPE(FREEZETAG) - REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,TRUE,"timelimit=20 pointlimit=30",_("Hold the ball to get points for kills")); + REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,true,"timelimit=20 pointlimit=30",_("Hold the ball to get points for kills")); #define g_keepaway IS_GAMETYPE(KEEPAWAY) - REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,FALSE,"pointlimit=50 teams=0",_("Survive against waves of monsters")); + REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,false,"pointlimit=50 teams=0",_("Survive against waves of monsters")); #define g_invasion IS_GAMETYPE(INVASION) - const float MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps - const float MAPINFO_FEATURE_VEHICLES = 2; - const float MAPINFO_FEATURE_TURRETS = 4; - const float MAPINFO_FEATURE_MONSTERS = 8; + const int MAPINFO_FEATURE_WEAPONS = 1; // not defined for instagib-only maps + const int MAPINFO_FEATURE_VEHICLES = 2; + const int MAPINFO_FEATURE_TURRETS = 4; + const int MAPINFO_FEATURE_MONSTERS = 8; - const float MAPINFO_FLAG_HIDDEN = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually - const float MAPINFO_FLAG_FORBIDDEN = 2; // don't even allow the map by a cvar setting that allows hidden maps - const float MAPINFO_FLAG_FRUSTRATING = 4; // this map is near impossible to play, enable at your own risk - const float MAPINFO_FLAG_NOAUTOMAPLIST = 8; // do not include when automatically building maplist (counts as hidden for maplist building purposes) + const int MAPINFO_FLAG_HIDDEN = 1; // not in lsmaps/menu/vcall/etc., can just be changed to manually + const int MAPINFO_FLAG_FORBIDDEN = 2; // don't even allow the map by a cvar setting that allows hidden maps + const int MAPINFO_FLAG_FRUSTRATING = 4; // this map is near impossible to play, enable at your own risk + const int MAPINFO_FLAG_NOAUTOMAPLIST = 8; // do not include when automatically building maplist (counts as hidden for maplist building purposes) float MapInfo_count; diff --cc qcsrc/common/notifications.qh index 7753288c0c,1cb1adf51f..95e13c137e --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@@ -1281,8 -1277,40 +1289,41 @@@ string notif_arg_spree_inf(float type, // Initialization/Create Declarations // ==================================== + enum { + NO_CPID + , CPID_ASSAULT_ROLE + , CPID_ROUND + , CPID_CAMPCHECK + , CPID_CTF_CAPSHIELD + , CPID_CTF_LOWPRIO + , CPID_CTF_PASS + , CPID_STALEMATE + , CPID_NADES + , CPID_IDLING + , CPID_ITEM + , CPID_PREVENT_JOIN + , CPID_KEEPAWAY + , CPID_KEEPAWAY_WARN + , CPID_KEYHUNT + , CPID_KEYHUNT_OTHER + , CPID_LMS + , CPID_MISSING_TEAMS + , CPID_MISSING_PLAYERS + , CPID_INSTAGIB_FINDAMMO + , CPID_MOTD + , CPID_NIX + , CPID_ONSLAUGHT ++, CPID_ONS_CAPSHIELD + , CPID_OVERTIME + , CPID_POWERUP + , CPID_RACE_FINISHLAP + , CPID_TEAMCHANGE + , CPID_TIMEOUT + // always last + , NOTIF_CPID_COUNT + }; // notification counts - #define NOTIF_FIRST 1 + const float NOTIF_FIRST = 1; float NOTIF_ANNCE_COUNT; float NOTIF_INFO_COUNT; float NOTIF_CENTER_COUNT; diff --cc qcsrc/common/stats.qh index 3f35a3a666,481713cc9c..cbb0d7ba9c --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@@ -34,62 -37,62 +37,62 @@@ const int STAT_VIEWZOOM // 29 empty? // 30 empty? // 31 empty? - const float STAT_KH_KEYS = 32; - const float STAT_CTF_STATE = 33; + const int STAT_KH_KEYS = 32; + const int STAT_CTF_STATE = 33; // 34 empty? - const float STAT_WEAPONS = 35; - const float STAT_SWITCHWEAPON = 36; - const float STAT_GAMESTARTTIME = 37; - const float STAT_STRENGTH_FINISHED = 38; - const float STAT_INVINCIBLE_FINISHED = 39; + const int STAT_WEAPONS = 35; + const int STAT_SWITCHWEAPON = 36; + const int STAT_GAMESTARTTIME = 37; + const int STAT_STRENGTH_FINISHED = 38; + const int STAT_INVINCIBLE_FINISHED = 39; // 40 empty? - const float STAT_ARC_HEAT = 41; - const float STAT_PRESSED_KEYS = 42; - const float STAT_ALLOW_OLDVORTEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config - const float STAT_FUEL = 44; - const float STAT_NB_METERSTART = 45; - const float STAT_SHOTORG = 46; // compressShotOrigin - const float STAT_LEADLIMIT = 47; - const float STAT_WEAPON_CLIPLOAD = 48; - const float STAT_WEAPON_CLIPSIZE = 49; - const float STAT_VORTEX_CHARGE = 50; - const float STAT_LAST_PICKUP = 51; - const float STAT_HUD = 52; - const float STAT_VORTEX_CHARGEPOOL = 53; - const float STAT_HIT_TIME = 54; - const float STAT_DAMAGE_DEALT_TOTAL = 55; - const float STAT_TYPEHIT_TIME = 56; - const float STAT_LAYED_MINES = 57; - const float STAT_HAGAR_LOAD = 58; - const float STAT_SWITCHINGWEAPON = 59; - const float STAT_SUPERWEAPONS_FINISHED = 60; - const float STAT_VEHICLESTAT_HEALTH = 61; - const float STAT_VEHICLESTAT_SHIELD = 62; - const float STAT_VEHICLESTAT_ENERGY = 63; - const float STAT_VEHICLESTAT_AMMO1 = 64; - const float STAT_VEHICLESTAT_RELOAD1 = 65; - const float STAT_VEHICLESTAT_AMMO2 = 66; - const float STAT_VEHICLESTAT_RELOAD2 = 67; - const float STAT_VEHICLESTAT_W2MODE = 68; - const float STAT_NADE_TIMER = 69; - const float STAT_SECRETS_TOTAL = 70; - const float STAT_SECRETS_FOUND = 71; - const float STAT_RESPAWN_TIME = 72; - const float STAT_ROUNDSTARTTIME = 73; - const float STAT_WEAPONS2 = 74; - const float STAT_WEAPONS3 = 75; - const float STAT_MONSTERS_TOTAL = 76; - const float STAT_MONSTERS_KILLED = 77; - const float STAT_BUFFS = 78; - const float STAT_NADE_BONUS = 79; - const float STAT_NADE_BONUS_TYPE = 80; - const float STAT_NADE_BONUS_SCORE = 81; - const float STAT_HEALING_ORB = 82; - const float STAT_HEALING_ORB_ALPHA = 83; - const float STAT_PLASMA = 84; - const float STAT_OK_AMMO_CHARGE = 85; - const float STAT_OK_AMMO_CHARGEPOOl = 86; - const float STAT_ROUNDLOST = 87; + const int STAT_ARC_HEAT = 41; + const int STAT_PRESSED_KEYS = 42; + const int STAT_ALLOW_OLDVORTEXBEAM = 43; // this stat could later contain some other bits of info, like, more server-side particle config + const int STAT_FUEL = 44; + const int STAT_NB_METERSTART = 45; + const int STAT_SHOTORG = 46; // compressShotOrigin + const int STAT_LEADLIMIT = 47; + const int STAT_WEAPON_CLIPLOAD = 48; + const int STAT_WEAPON_CLIPSIZE = 49; + const int STAT_VORTEX_CHARGE = 50; + const int STAT_LAST_PICKUP = 51; + const int STAT_HUD = 52; + const int STAT_VORTEX_CHARGEPOOL = 53; + const int STAT_HIT_TIME = 54; + const int STAT_DAMAGE_DEALT_TOTAL = 55; + const int STAT_TYPEHIT_TIME = 56; + const int STAT_LAYED_MINES = 57; + const int STAT_HAGAR_LOAD = 58; + const int STAT_SWITCHINGWEAPON = 59; + const int STAT_SUPERWEAPONS_FINISHED = 60; + const int STAT_VEHICLESTAT_HEALTH = 61; + const int STAT_VEHICLESTAT_SHIELD = 62; + const int STAT_VEHICLESTAT_ENERGY = 63; + const int STAT_VEHICLESTAT_AMMO1 = 64; + const int STAT_VEHICLESTAT_RELOAD1 = 65; + const int STAT_VEHICLESTAT_AMMO2 = 66; + const int STAT_VEHICLESTAT_RELOAD2 = 67; + const int STAT_VEHICLESTAT_W2MODE = 68; + const int STAT_NADE_TIMER = 69; + const int STAT_SECRETS_TOTAL = 70; + const int STAT_SECRETS_FOUND = 71; + const int STAT_RESPAWN_TIME = 72; + const int STAT_ROUNDSTARTTIME = 73; + const int STAT_WEAPONS2 = 74; + const int STAT_WEAPONS3 = 75; + const int STAT_MONSTERS_TOTAL = 76; + const int STAT_MONSTERS_KILLED = 77; + const int STAT_BUFFS = 78; + const int STAT_NADE_BONUS = 79; + const int STAT_NADE_BONUS_TYPE = 80; + const int STAT_NADE_BONUS_SCORE = 81; + const int STAT_HEALING_ORB = 82; + const int STAT_HEALING_ORB_ALPHA = 83; + const int STAT_PLASMA = 84; + const int STAT_OK_AMMO_CHARGE = 85; + const int STAT_OK_AMMO_CHARGEPOOL = 86; -// 87 empty? ++const int STAT_ROUNDLOST = 87; // 88 empty? // 89 empty? // 90 empty? diff --cc qcsrc/server/autocvars.qh index 460d8354b0,bcb9d28d70..6ca5e760ef --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@@ -898,3 -882,4 +901,5 @@@ float autocvar_g_buffs_vampire_damage_s float autocvar_g_buffs_invisible_alpha; float autocvar_g_buffs_flight_gravity; float autocvar_g_buffs_jump_height; ++ + #endif diff --cc qcsrc/server/controlpoint.qc index d088e2e82e,0000000000..7cf81d762e mode 100644,000000..100644 --- a/qcsrc/server/controlpoint.qc +++ b/qcsrc/server/controlpoint.qc @@@ -1,36 -1,0 +1,38 @@@ - float cpicon_send(entity to, float sf) ++#include "controlpoint.qh" ++ ++bool cpicon_send(entity to, int sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_CONTROLPOINT_ICON); + WriteByte(MSG_ENTITY, sf); + if(sf & CPSF_SETUP) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, self.health); + WriteByte(MSG_ENTITY, self.max_health); + WriteByte(MSG_ENTITY, self.count); + WriteByte(MSG_ENTITY, self.team); + WriteByte(MSG_ENTITY, self.owner.iscaptured); + } + + if(sf & CPSF_STATUS) + { + WriteByte(MSG_ENTITY, self.team); + + if(self.health <= 0) + WriteByte(MSG_ENTITY, 0); + else + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + } + - return TRUE; ++ return true; +} + +void onslaught_controlpoint_icon_link(entity e, void() spawnproc) +{ - Net_LinkEntity(e, TRUE, 0, cpicon_send); ++ Net_LinkEntity(e, true, 0, cpicon_send); + e.think = spawnproc; + e.nextthink = time * sys_frametime; +} diff --cc qcsrc/server/controlpoint.qh index e489f90ac2,0000000000..d76f0ea069 mode 100644,000000..100644 --- a/qcsrc/server/controlpoint.qh +++ b/qcsrc/server/controlpoint.qh @@@ -1,5 -1,0 +1,10 @@@ ++#ifndef CONTROLPOINT_H ++#define CONTROLPOINT_H ++ +const vector CPICON_MIN = '-32 -32 -9'; +const vector CPICON_MAX = '32 32 25'; + - float CPSF_STATUS = 4; - float CPSF_SETUP = 8; ++const int CPSF_STATUS = 4; ++const int CPSF_SETUP = 8; ++ ++#endif diff --cc qcsrc/server/generator.qc index 13aa35ab53,0000000000..3e0b5171f2 mode 100644,000000..100644 --- a/qcsrc/server/generator.qc +++ b/qcsrc/server/generator.qc @@@ -1,35 -1,0 +1,37 @@@ - float generator_send(entity to, float sf) ++#include "generator.qh" ++ ++bool generator_send(entity to, int sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_GENERATOR); + WriteByte(MSG_ENTITY, sf); + if(sf & GSF_SETUP) + { + WriteCoord(MSG_ENTITY, self.origin_x); + WriteCoord(MSG_ENTITY, self.origin_y); + WriteCoord(MSG_ENTITY, self.origin_z); + + WriteByte(MSG_ENTITY, self.health); + WriteByte(MSG_ENTITY, self.max_health); + WriteByte(MSG_ENTITY, self.count); + WriteByte(MSG_ENTITY, self.team); + } + + if(sf & GSF_STATUS) + { + WriteByte(MSG_ENTITY, self.team); + + if(self.health <= 0) + WriteByte(MSG_ENTITY, 0); + else + WriteByte(MSG_ENTITY, ceil((self.health / self.max_health) * 255)); + } + - return TRUE; ++ return true; +} + +void generator_link(void() spawnproc) +{ - Net_LinkEntity(self, TRUE, 0, generator_send); ++ Net_LinkEntity(self, true, 0, generator_send); + self.think = spawnproc; + self.nextthink = time; +} diff --cc qcsrc/server/generator.qh index a991874c49,0000000000..cbdb36e05f mode 100644,000000..100644 --- a/qcsrc/server/generator.qh +++ b/qcsrc/server/generator.qh @@@ -1,5 -1,0 +1,10 @@@ ++#ifndef GENERATOR_H ++#define GENERATOR_H +const vector GENERATOR_MIN = '-52 -52 -14'; +const vector GENERATOR_MAX = '52 52 75'; + - float GSF_STATUS = 4; - float GSF_SETUP = 8; ++const int GSF_STATUS = 4; ++const int GSF_SETUP = 8; ++ ++bool generator_send(entity to, int sf); ++#endif diff --cc qcsrc/server/miscfunctions.qc index f406938730,d998ea7999..c41c7b3b93 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@@ -544,88 -507,20 +507,10 @@@ string playername(entity p return p.netname; } - //#NO AUTOCVARS START - - float g_pickup_shells; - float g_pickup_shells_max; - float g_pickup_nails; - float g_pickup_nails_max; - float g_pickup_rockets; - float g_pickup_rockets_max; - float g_pickup_cells; - float g_pickup_cells_max; - float g_pickup_plasma; - float g_pickup_plasma_max; - float g_pickup_fuel; - float g_pickup_fuel_jetpack; - float g_pickup_fuel_max; - float g_pickup_armorsmall; - float g_pickup_armorsmall_max; - float g_pickup_armorsmall_anyway; - float g_pickup_armormedium; - float g_pickup_armormedium_max; - float g_pickup_armormedium_anyway; - float g_pickup_armorbig; - float g_pickup_armorbig_max; - float g_pickup_armorbig_anyway; - float g_pickup_armorlarge; - float g_pickup_armorlarge_max; - float g_pickup_armorlarge_anyway; - float g_pickup_healthsmall; - float g_pickup_healthsmall_max; - float g_pickup_healthsmall_anyway; - float g_pickup_healthmedium; - float g_pickup_healthmedium_max; - float g_pickup_healthmedium_anyway; - float g_pickup_healthlarge; - float g_pickup_healthlarge_max; - float g_pickup_healthlarge_anyway; - float g_pickup_healthmega; - float g_pickup_healthmega_max; - float g_pickup_healthmega_anyway; - float g_pickup_ammo_anyway; - float g_pickup_weapons_anyway; - float g_weaponarena; - WepSet g_weaponarena_weapons; - float g_weaponarena_random; - float g_weaponarena_random_with_blaster; - string g_weaponarena_list; - float g_weaponspeedfactor; - float g_weaponratefactor; - float g_weapondamagefactor; - float g_weaponforcefactor; - float g_weaponspreadfactor; - - WepSet start_weapons; - WepSet start_weapons_default; - WepSet start_weapons_defaultmask; - float start_items; - float start_ammo_shells; - float start_ammo_nails; - float start_ammo_rockets; - float start_ammo_cells; - float start_ammo_plasma; - float start_ammo_fuel; - float start_health; - float start_armorvalue; - WepSet warmup_start_weapons; - WepSet warmup_start_weapons_default; - WepSet warmup_start_weapons_defaultmask; - #define WARMUP_START_WEAPONS ((g_warmup_allguns == 1) ? (warmup_start_weapons & (weaponsInMap | start_weapons)) : warmup_start_weapons) - float warmup_start_ammo_shells; - float warmup_start_ammo_nails; - float warmup_start_ammo_rockets; - float warmup_start_ammo_cells; - float warmup_start_ammo_plasma; - float warmup_start_ammo_fuel; - float warmup_start_health; - float warmup_start_armorvalue; - float g_weapon_stay; - - float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done? -vector randompos(vector m1, vector m2) --{ - var float i = weaponinfo.weapon; - var float d = 0; - vector v; - m2 = m2 - m1; - v.x = m2_x * random() + m1_x; - v.y = m2_y * random() + m1_y; - v.z = m2_z * random() + m1_z; - return v; -} - + float want_weapon(entity weaponinfo, float allguns) // WEAPONTODO: what still needs done? + { + int i = weaponinfo.weapon; + int d = 0; if (!i) return 0; diff --cc qcsrc/server/mutators/gamemode_onslaught.qc index e47999e740,d1f20f7ee9..a05e6c1376 --- a/qcsrc/server/mutators/gamemode_onslaught.qc +++ b/qcsrc/server/mutators/gamemode_onslaught.qc @@@ -1,142 -1,129 +1,145 @@@ -float autocvar_g_onslaught_spawn_at_controlpoints; -float 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; ++#include "../controlpoint.qh" ++#include "../generator.qh" + -void onslaught_generator_updatesprite(entity e); -void onslaught_controlpoint_updatesprite(entity e); -void onslaught_link_checkupdate(); +// ======================= +// CaptureShield Functions +// ======================= - float ons_CaptureShield_Customize() -.entity sprite; -.string target2; -.float iscaptured; -.float islinked; -.float isgenneighbor_red; -.float isgenneighbor_blue; -.float iscpneighbor_red; -.float iscpneighbor_blue; -.float isshielded; -.float lasthealth; -.float lastteam; -.float lastshielded; -.float lastcaptured; ++bool ons_CaptureShield_Customize() +{ + entity e = WaypointSprite_getviewentity(other); - if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return FALSE; } - if(SAME_TEAM(self, e)) { return FALSE; } -entity ons_red_generator; -entity ons_blue_generator; ++ if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, e.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return false; } ++ if(SAME_TEAM(self, e)) { return false; } - return TRUE; -void ons_gib_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector vforce) -{ - self.velocity = self.velocity + vforce; ++ return true; } -.float giblifetime; -void ons_throwgib_think() +void ons_CaptureShield_Touch() { - float d; + if(!self.enemy.isshielded && (ons_ControlPoint_Attackable(self.enemy, other.team) > 0 || self.enemy.classname != "onslaught_controlpoint")) { return; } + if(!IS_PLAYER(other)) { return; } + if(SAME_TEAM(other, self)) { return; } - self.nextthink = time + 0.05; + vector mymid = (self.absmin + self.absmax) * 0.5; + vector othermid = (other.absmin + other.absmax) * 0.5; - d = self.giblifetime - time; + Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ons_captureshield_force); - if(d<0) + if(IS_REAL_CLIENT(other)) { - self.think = SUB_Remove; - return; + play2(other, "onslaught/damageblockedbyshield.wav"); + + if(self.enemy.classname == "onslaught_generator") + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_GENERATOR_SHIELDED); + else + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_CONTROLPOINT_SHIELDED); } - if(d<1) - self.alpha = d; +} - if(d>2) - if(random()<0.6) - pointparticles(particleeffectnum("onslaught_generator_gib_flame"), self.origin, '0 0 0', 1); +void ons_CaptureShield_Reset() +{ + self.colormap = self.enemy.colormap; + self.team = self.enemy.team; } - void ons_CaptureShield_Spawn(entity generator, float is_generator) -void ons_throwgib(vector v_from, vector v_to, string smodel, float f_lifetime, float b_burn) ++void ons_CaptureShield_Spawn(entity generator, bool is_generator) { - entity gib; + entity shield = spawn(); + + shield.enemy = generator; + shield.team = generator.team; + shield.colormap = generator.colormap; + shield.reset = ons_CaptureShield_Reset; + shield.touch = ons_CaptureShield_Touch; + shield.customizeentityforclient = ons_CaptureShield_Customize; + shield.classname = "ons_captureshield"; + shield.effects = EF_ADDITIVE; + shield.movetype = MOVETYPE_NOCLIP; + shield.solid = SOLID_TRIGGER; + shield.avelocity = '7 0 11'; + shield.scale = 1; + shield.model = ((is_generator) ? "models/onslaught/generator_shield.md3" : "models/onslaught/controlpoint_shield.md3"); + + precache_model(shield.model); + setorigin(shield, generator.origin); + setmodel(shield, shield.model); + setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs); +} - gib = spawn(); - setmodel(gib, smodel); - setorigin(gib, v_from); - gib.solid = SOLID_BBOX; - gib.movetype = MOVETYPE_BOUNCE; - gib.takedamage = DAMAGE_YES; - gib.event_damage = ons_gib_damage; - gib.health = -1; - gib.effects = EF_LOWPRECISION; - gib.flags = FL_NOTARGET; - gib.velocity = v_to; - gib.giblifetime = time + f_lifetime; +// ========== +// Junk Pile +// ========== - if (b_burn) +void ons_debug(string input) +{ + switch(autocvar_g_onslaught_debug) { - gib.think = ons_throwgib_think; - gib.nextthink = time + 0.05; + case 1: dprint(input); break; + case 2: print(input); break; } - else - SUB_SetFade(gib, gib.giblifetime, 2); +} + +void FixSize(entity e) +{ + e.mins_x = rint(e.mins_x); + e.mins_y = rint(e.mins_y); + e.mins_z = rint(e.mins_z); + + e.maxs_x = rint(e.maxs_x); + e.maxs_y = rint(e.maxs_y); + e.maxs_z = rint(e.maxs_z); +} + +vector randompos(vector m1, vector m2) +{ + vector v; + m2 = m2 - m1; + v_x = m2_x * random() + m1_x; + v_y = m2_y * random() + m1_y; + v_z = m2_z * random() + m1_z; + return v; +} + +void setmodel_fixsize(entity e, string m) +{ + setmodel(e, m); + FixSize(e); } void onslaught_updatelinks() { - entity l, links; - float stop, t1, t2, t3, t4; + entity l; // first check if the game has ended - dprint("--- updatelinks ---\n"); - links = findchain(classname, "onslaught_link"); + ons_debug("--- updatelinks ---\n"); // mark generators as being shielded and networked - l = findchain(classname, "onslaught_generator"); - while (l) + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) { if (l.iscaptured) - dprint(etos(l), " (generator) belongs to team ", ftos(l.team), "\n"); + ons_debug(strcat(etos(l), " (generator) belongs to team ", ftos(l.team), "\n")); else - dprint(etos(l), " (generator) is destroyed\n"); + ons_debug(strcat(etos(l), " (generator) is destroyed\n")); l.islinked = l.iscaptured; l.isshielded = l.iscaptured; - l = l.chain; + l.sprite.SendFlags |= 16; } // mark points as shielded and not networked - l = findchain(classname, "onslaught_controlpoint"); - while (l) + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) { - l.islinked = FALSE; - l.isshielded = TRUE; - float i; - for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = FALSE; l.iscpneighbor[i] = FALSE; } + l.islinked = false; + l.isshielded = true; - l.isgenneighbor_red = false; - l.isgenneighbor_blue = false; - l.iscpneighbor_red = false; - l.iscpneighbor_blue = false; - dprint(etos(l), " (point) belongs to team ", ftos(l.team), "\n"); - l = l.chain; ++ int i; ++ for(i = 0; i < 17; ++i) { l.isgenneighbor[i] = false; l.iscpneighbor[i] = false; } + ons_debug(strcat(etos(l), " (point) belongs to team ", ftos(l.team), "\n")); + l.sprite.SendFlags |= 16; } // flow power outward from the generators through the network - float stop = FALSE; - l = links; - while (l) - { - dprint(etos(l), " (link) connects ", etos(l.goalentity), " with ", etos(l.enemy), "\n"); - l = l.chain; - } - stop = false; ++ bool stop = false; while (!stop) { - stop = TRUE; + stop = true; - l = links; - while (l) + 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 @@@ -146,17 -133,18 +149,17 @@@ { if (!l.goalentity.islinked) { - stop = FALSE; - l.goalentity.islinked = TRUE; + stop = false; + l.goalentity.islinked = true; - dprint(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is marking ", etos(l.goalentity), " (point) because its team matches ", etos(l.enemy), " (point)\n")); } else if (!l.enemy.islinked) { - stop = FALSE; - l.enemy.islinked = TRUE; + stop = false; + l.enemy.islinked = true; - dprint(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is marking ", etos(l.enemy), " (point) because its team matches ", etos(l.goalentity), " (point)\n")); } } - l = l.chain; } } // now that we know which points are powered we can mark their neighbors @@@ -165,49 -154,73 +168,49 @@@ { if (l.goalentity.islinked) { - if (l.goalentity.team != l.enemy.team) + if(DIFF_TEAM(l.goalentity, l.enemy)) { - dprint(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.enemy), " (point) because its team does not match ", etos(l.goalentity), " (point)\n")); - l.enemy.isshielded = FALSE; + l.enemy.isshielded = false; } if(l.goalentity.classname == "onslaught_generator") - l.enemy.isgenneighbor[l.goalentity.team] = TRUE; - { - if(l.goalentity.team == NUM_TEAM_1) - l.enemy.isgenneighbor_red = true; - else if(l.goalentity.team == NUM_TEAM_2) - l.enemy.isgenneighbor_blue = true; - } ++ l.enemy.isgenneighbor[l.goalentity.team] = true; else - l.enemy.iscpneighbor[l.goalentity.team] = TRUE; - { - if(l.goalentity.team == NUM_TEAM_1) - l.enemy.iscpneighbor_red = true; - else if(l.goalentity.team == NUM_TEAM_2) - l.enemy.iscpneighbor_blue = true; - } ++ l.enemy.iscpneighbor[l.goalentity.team] = true; } if (l.enemy.islinked) { - if (l.goalentity.team != l.enemy.team) + if(DIFF_TEAM(l.goalentity, l.enemy)) { - dprint(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n"); + ons_debug(strcat(etos(l), " (link) is unshielding ", etos(l.goalentity), " (point) because its team does not match ", etos(l.enemy), " (point)\n")); - l.goalentity.isshielded = FALSE; + l.goalentity.isshielded = false; } if(l.enemy.classname == "onslaught_generator") - l.goalentity.isgenneighbor[l.enemy.team] = TRUE; - { - if(l.enemy.team == NUM_TEAM_1) - l.goalentity.isgenneighbor_red = true; - else if(l.enemy.team == NUM_TEAM_2) - l.goalentity.isgenneighbor_blue = true; - } ++ l.goalentity.isgenneighbor[l.enemy.team] = true; else - l.goalentity.iscpneighbor[l.enemy.team] = TRUE; - { - if(l.enemy.team == NUM_TEAM_1) - l.goalentity.iscpneighbor_red = true; - else if(l.enemy.team == NUM_TEAM_2) - l.goalentity.iscpneighbor_blue = true; - } ++ l.goalentity.iscpneighbor[l.enemy.team] = true; } - l = l.chain; } - // now update the takedamage and alpha variables on generator shields - l = findchain(classname, "onslaught_generator"); - while (l) + // now update the generators + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) { if (l.isshielded) { - dprint(etos(l), " (generator) is shielded\n"); - l.enemy.alpha = 1; + ons_debug(strcat(etos(l), " (generator) is shielded\n")); l.takedamage = DAMAGE_NO; - l.bot_attack = FALSE; + l.bot_attack = false; } else { - dprint(etos(l), " (generator) is not shielded\n"); - l.enemy.alpha = -1; + ons_debug(strcat(etos(l), " (generator) is not shielded\n")); l.takedamage = DAMAGE_AIM; - l.bot_attack = TRUE; + l.bot_attack = true; } - l = l.chain; + + ons_Generator_UpdateSprite(l); } // now update the takedamage and alpha variables on control point icons - l = findchain(classname, "onslaught_controlpoint"); - while (l) + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) { if (l.isshielded) { @@@ -224,94 -239,100 +227,94 @@@ if (l.goalentity) { l.goalentity.takedamage = DAMAGE_AIM; - l.goalentity.bot_attack = TRUE; + l.goalentity.bot_attack = true; } } - onslaught_controlpoint_updatesprite(l); - l = l.chain; + ons_ControlPoint_UpdateSprite(l); } - // count generators owned by each team - t1 = t2 = t3 = t4 = 0; - l = findchain(classname, "onslaught_generator"); - while (l) + l = findchain(classname, "ons_captureshield"); + while(l) { - if (l.iscaptured) - { - if (l.team == NUM_TEAM_1) t1 = 1; - if (l.team == NUM_TEAM_2) t2 = 1; - if (l.team == NUM_TEAM_3) t3 = 1; - if (l.team == NUM_TEAM_4) t4 = 1; - } - onslaught_generator_updatesprite(l); + l.team = l.enemy.team; + l.colormap = l.enemy.colormap; l = l.chain; } - // see if multiple teams remain (if not, it's game over) - if (t1 + t2 + t3 + t4 < 2) - dprint("--- game over ---\n"); - else - dprint("--- done updating links ---\n"); } -float onslaught_controlpoint_can_be_linked(entity cp, float t) + +// =================== +// Main Link Functions +// =================== + - float ons_Link_Send(entity to, float sendflags) ++bool ons_Link_Send(entity to, int sendflags) { - if(t == NUM_TEAM_1) + WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK); + WriteByte(MSG_ENTITY, sendflags); + if(sendflags & 1) { - if(cp.isgenneighbor_red) - return 2; - if(cp.iscpneighbor_red) - return 1; + WriteCoord(MSG_ENTITY, self.goalentity.origin_x); + WriteCoord(MSG_ENTITY, self.goalentity.origin_y); + WriteCoord(MSG_ENTITY, self.goalentity.origin_z); } - else if(t == NUM_TEAM_2) + if(sendflags & 2) { - if(cp.isgenneighbor_blue) - return 2; - if(cp.iscpneighbor_blue) - return 1; + WriteCoord(MSG_ENTITY, self.enemy.origin_x); + WriteCoord(MSG_ENTITY, self.enemy.origin_y); + WriteCoord(MSG_ENTITY, self.enemy.origin_z); } - return 0; - /* - entity e; - // check to see if this player has a legitimate claim to capture this - // control point - more specifically that there is a captured path of - // points leading back to the team generator - e = findchain(classname, "onslaught_link"); - while (e) - { - if (e.goalentity == cp) - { - dprint(etos(e), " (link) connects to ", etos(e.enemy), " (point)"); - if (e.enemy.islinked) - { - dprint(" which is linked"); - if (e.enemy.team == t) + if(sendflags & 4) { - dprint(" and has the correct team!\n"); - return 1; - } - else - dprint(" but has the wrong team\n"); - } - else - dprint("\n"); + WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16 } - return TRUE; - else if (e.enemy == cp) - { - dprint(etos(e), " (link) connects to ", etos(e.goalentity), " (point)"); - if (e.goalentity.islinked) - { - dprint(" which is linked"); - if (e.goalentity.team == t) ++ return true; +} + +void ons_Link_CheckUpdate() +{ + // TODO check if the two sides have moved (currently they won't move anyway) + float cc = 0, cc1 = 0, cc2 = 0; + + if(self.goalentity.islinked || self.goalentity.iscaptured) { cc1 = (self.goalentity.team - 1) * 0x01; } + if(self.enemy.islinked || self.enemy.iscaptured) { cc2 = (self.enemy.team - 1) * 0x10; } + + cc = cc1 + cc2; + + if(cc != self.clientcolors) { - dprint(" and has a team!\n"); - return 1; - } - else - dprint(" but has the wrong team\n"); - } - else - dprint("\n"); - } - e = e.chain; + self.clientcolors = cc; + self.SendFlags |= 4; } + + self.nextthink = time; +} + +void ons_DelayedLinkSetup() +{ + self.goalentity = find(world, targetname, self.target); + self.enemy = find(world, targetname, self.target2); + if(!self.goalentity) { objerror("can not find target\n"); } + if(!self.enemy) { objerror("can not find target2\n"); } + + ons_debug(strcat(etos(self.goalentity), " linked with ", etos(self.enemy), "\n")); + self.SendFlags |= 3; + self.think = ons_Link_CheckUpdate; + self.nextthink = time; +} + + +// ============================= +// Main Control Point Functions +// ============================= + - float ons_ControlPoint_CanBeLinked(entity cp, float teamnumber) ++int ons_ControlPoint_CanBeLinked(entity cp, int teamnumber) +{ + if(cp.isgenneighbor[teamnumber]) { return 2; } + if(cp.iscpneighbor[teamnumber]) { return 1; } + return 0; - */ } - float ons_ControlPoint_Attackable(entity cp, float teamnumber) -float onslaught_controlpoint_attackable(entity cp, float t) ++int ons_ControlPoint_Attackable(entity cp, int teamnumber) // -2: SAME TEAM, attackable by enemy! // -1: SAME TEAM! // 0: off limits @@@ -320,7 -341,7 +323,7 @@@ // 3: attack it (HIGH PRIO) // 4: touch it (HIGH PRIO) { -- float a; ++ int a; if(cp.isshielded) { @@@ -358,283 -379,428 +361,281 @@@ return 0; } - void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) -float overtime_msg_time; -void onslaught_generator_think() ++void ons_ControlPoint_Icon_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) { - float d; - entity e; - self.nextthink = ceil(time + 1); - if (!gameover) + entity oself; + + if(damage <= 0) { return; } + + if (self.owner.isshielded) { - if (autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) - { - if (!overtime_msg_time) - { - Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); - overtime_msg_time = time; - } - // self.max_health / 300 gives 5 minutes of overtime. - // control points reduce the overtime duration. - sound(self, CH_TRIGGER, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NORM); - d = 1; - e = findchain(classname, "onslaught_controlpoint"); - while (e) + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (IS_PLAYER(attacker)) { - if (e.team != self.team) - if (e.islinked) - d = d + 1; - e = e.chain; + play2(attacker, "onslaught/damageblockedbyshield.wav"); + self.pain_finished = time + 1; + attacker.typehitsound += 1; // play both sounds (shield is way too quiet) } - if(autocvar_g_campaign && autocvar__campaign_testrun) - d = d * self.max_health; - else - d = d * self.max_health / max(30, 60 * autocvar_timelimit_suddendeath); - - Damage(self, self, self, d, DEATH_HURTTRIGGER, self.origin, '0 0 0'); - } - else if (overtime_msg_time) - overtime_msg_time = 0; - - if(!self.isshielded && self.wait < time) - { - self.wait = time + 5; - FOR_EACH_REALPLAYER(e) - { - if(SAME_TEAM(e, self)) - { - Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED); - soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE); // FIXME: Uniqe sound? - } - } - } + return; } -} - -void onslaught_generator_ring_spawn(vector org) -{ - modeleffect_spawn("models/onslaught/shockwavetransring.md3", 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, -16, 0.1, 1.25, 0.25); -} -void onslaught_generator_ray_think() -{ - self.nextthink = time + 0.05; - if(self.count > 10) + if(IS_PLAYER(attacker)) + if(time - ons_notification_time[self.team] > 10) { - self.think = SUB_Remove; - return; + play2team(self.team, "onslaught/controlpoint_underattack.wav"); + ons_notification_time[self.team] = time; } - if(self.count > 5) - self.alpha -= 0.1; + self.health = self.health - damage; + if(self.owner.iscaptured) + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + else + WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / ONS_CP_THINKRATE)); + self.pain_finished = time + 1; + // particles on every hit + pointparticles(particleeffectnum("sparks"), hitloc, force*-1, 1); + //sound on every hit + if (random() < 0.5) + sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTEN_NORM); else - self.alpha += 0.1; + sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTEN_NORM); - self.scale += 0.2; - self.count +=1; -} + if (self.health < 0) + { + sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_CPDESTROYED_), self.owner.message, attacker.netname); + + PlayerScore_Add(attacker, SP_ONS_TAKES, 1); + PlayerScore_Add(attacker, SP_SCORE, 10); + + self.owner.goalentity = world; - self.owner.islinked = FALSE; - self.owner.iscaptured = FALSE; ++ self.owner.islinked = false; ++ self.owner.iscaptured = false; + self.owner.team = 0; + self.owner.colormap = 1024; -void onslaught_generator_ray_spawn(vector org) -{ - entity e; - e = spawn(); - setmodel(e, "models/onslaught/ons_ray.md3"); - setorigin(e, org); - e.angles = randomvec() * 360; - e.alpha = 0; - e.scale = random() * 5 + 8; - e.think = onslaught_generator_ray_think; - e.nextthink = time + 0.05; -} + WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0); -void onslaught_generator_shockwave_spawn(vector org) -{ - shockwave_spawn("models/onslaught/shockwave.md3", org, -64, 0.75, 0.5); -} + onslaught_updatelinks(); -void onslaught_generator_damage_think() -{ - if(self.owner.health < 0) - { - self.think = SUB_Remove; - return; - } - self.nextthink = time+0.1; + // Use targets now (somebody make sure this is in the right place..) + oself = self; + self = self.owner; + activator = self; + SUB_UseTargets (); + self = oself; - // damaged fx (less probable the more damaged is the generator) - if(random() < 0.9 - self.owner.health / self.owner.max_health) - if(random() < 0.01) - { - pointparticles(particleeffectnum("electro_ballexplode"), self.origin + randompos('-50 -50 -20', '50 50 50'), '0 0 0', 1); - sound(self, CH_TRIGGER, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM); - } - else - pointparticles(particleeffectnum("torch_small"), self.origin + randompos('-60 -60 -20', '60 60 60'), '0 0 0', 1); -} + self.owner.waslinked = self.owner.islinked; + if(self.owner.model != "models/onslaught/controlpoint_pad.md3") + setmodel_fixsize(self.owner, "models/onslaught/controlpoint_pad.md3"); + //setsize(self, '-32 -32 0', '32 32 8'); -void onslaught_generator_damage_spawn(entity gd_owner) -{ - entity e; - e = spawn(); - e.owner = gd_owner; - e.health = self.owner.health; - setorigin(e, gd_owner.origin); - e.think = onslaught_generator_damage_think; - e.nextthink = time+1; + remove(self); + } + + self.SendFlags |= CPSF_STATUS; } -void onslaught_generator_deaththink() +void ons_ControlPoint_Icon_Think() { - vector org; - float i; - - if (!self.count) - self.count = 40; + entity oself; + self.nextthink = time + ONS_CP_THINKRATE; - // White shockwave - if(self.count==40||self.count==20) + if(autocvar_g_onslaught_cp_proxydecap) { - float _enemy_count = 0; - float _friendly_count = 0; - onslaught_generator_ring_spawn(self.origin); - sound(self, CH_TRIGGER, "onslaught/shockwave.wav", VOL_BASE, ATTEN_NORM); - } ++ int _enemy_count = 0; ++ int _friendly_count = 0; + float _dist; + entity _player; - // Throw some gibs - if(random() < 0.3) - { - i = random(); - if(i < 0.3) - ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 11 + '0 0 20', "models/onslaught/gen_gib1.md3", 6, true); - else if(i > 0.7) - ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 12 + '0 0 20', "models/onslaught/gen_gib2.md3", 6, true); - else - ons_throwgib(self.origin + '0 0 40', (100 * randomvec() - '1 1 1') * 13 + '0 0 20', "models/onslaught/gen_gib3.md3", 6, true); - } + FOR_EACH_PLAYER(_player) + { + if(!_player.deadflag) + { + _dist = vlen(_player.origin - self.origin); + if(_dist < autocvar_g_onslaught_cp_proxydecap_distance) + { + if(SAME_TEAM(_player, self)) + ++_friendly_count; + else + ++_enemy_count; + } + } + } + + _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); + _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * ONS_CP_THINKRATE); - // Spawn fire balls - for(i=0;i < 10;++i) + self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health); + self.SendFlags |= CPSF_STATUS; + if(self.health <= 0) + { + ons_ControlPoint_Icon_Damage(self, self, 1, 0, self.origin, '0 0 0'); + return; + } + } + + if (time > self.pain_finished + 5) { - org = self.origin + randompos('-30 -30 -30' * i + '0 0 -20', '30 30 30' * i + '0 0 20'); - pointparticles(particleeffectnum("onslaught_generator_gib_explode"), org, '0 0 0', 1); + if(self.health < self.max_health) + { + self.health = self.health + self.count; + if (self.health >= self.max_health) + self.health = self.max_health; + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); + } } - // Short explosion sound + small explosion - if(random() < 0.25) + if(self.owner.islinked != self.owner.waslinked) { - te_explosion(self.origin); - sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); - } + // unteam the spawnpoint if needed - float t; - t = self.owner.team; ++ int t = self.owner.team; + if(!self.owner.islinked) + self.owner.team = 0; + + oself = self; + self = self.owner; + activator = self; + SUB_UseTargets (); + self = oself; - // Particles - org = self.origin + randompos(self.mins + '8 8 8', self.maxs + '-8 -8 -8'); - pointparticles(particleeffectnum("onslaught_generator_smallexplosion"), org, '0 0 0', 1); + self.owner.team = t; - // rays - if(random() > 0.25 ) - { - onslaught_generator_ray_spawn(self.origin); + self.owner.waslinked = self.owner.islinked; } - // Final explosion - if(self.count==1) + // damaged fx + if(random() < 0.6 - self.health / self.max_health) { - org = self.origin; - te_explosion(org); - onslaught_generator_shockwave_spawn(org); - pointparticles(particleeffectnum("onslaught_generator_finalexplosion"), org, '0 0 0', 1); - sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - } - else - self.nextthink = time + 0.05; + pointparticles(particleeffectnum("electricity_sparks"), self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); - self.count = self.count - 1; + if(random() > 0.8) + sound(self, CH_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTEN_NORM); + else if (random() > 0.5) + sound(self, CH_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTEN_NORM); + } } -void onslaught_generator_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +void ons_ControlPoint_Icon_BuildThink() { - float i; - if (damage <= 0) - return; - if(warmup_stage) + entity oself; - float a; ++ int a; + + self.nextthink = time + ONS_CP_THINKRATE; + + // only do this if there is power + a = ons_ControlPoint_CanBeLinked(self.owner, self.owner.team); + if(!a) return; - if (attacker != self) + + self.health = self.health + self.count; + + self.SendFlags |= CPSF_STATUS; + + if (self.health >= self.max_health) { - if (self.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > self.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, "onslaught/damageblockedbyshield.wav"); - self.pain_finished = time + 1; - } - return; - } - if (time > self.pain_finished) - { - self.pain_finished = time + 10; - bprint(Team_ColoredFullName(self.team), " generator under attack!\n"); - play2team(self.team, "onslaught/generator_underattack.wav"); - } - } - self.health = self.health - damage; - WaypointSprite_UpdateHealth(self.sprite, self.health); - // choose an animation frame based on health - self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1); - // see if the generator is still functional, or dying - if (self.health > 0) - { -#ifdef ONSLAUGHT_SPAM - float h, lh; - lh = ceil(self.lasthealth / 100) * 100; - h = ceil(self.health / 100) * 100; - if(lh != h) - bprint(Team_ColoredFullName(self.team), " generator has less than ", ftos(h), " health remaining\n"); -#endif - self.lasthealth = self.health; - } - else if (!warmup_stage) - { - if (attacker == self) - bprint(Team_ColoredFullName(self.team), " generator spontaneously exploded due to overtime!\n"); - else - { - string t; - t = Team_ColoredFullName(attacker.team); - bprint(Team_ColoredFullName(self.team), " generator destroyed by ", t, "!\n"); - } - self.iscaptured = false; - self.islinked = false; - self.isshielded = false; - self.takedamage = DAMAGE_NO; // can't be hurt anymore - self.event_damage = func_null; // won't do anything if hurt - self.count = 0; // reset counter - self.think = onslaught_generator_deaththink; // explosion sequence - self.nextthink = time; // start exploding immediately - self.think(); // do the first explosion now + self.health = self.max_health; + self.count = autocvar_g_onslaught_cp_regen * ONS_CP_THINKRATE; // slow repair rate from now on + self.think = ons_ControlPoint_Icon_Think; + sound(self, CH_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTEN_NORM); - self.owner.iscaptured = TRUE; ++ self.owner.iscaptured = true; + self.solid = SOLID_BBOX; - WaypointSprite_UpdateMaxHealth(self.sprite, 0); + pointparticles(particleeffectnum(sprintf("%s_cap", Static_Team_ColorName_Lower(self.owner.team))), self.owner.origin, '0 0 0', 1); - onslaught_updatelinks(); - } + WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - if(self.health <= 0) - setmodel(self, "models/onslaught/generator_dead.md3"); - else if(self.health < self.max_health * 0.10) - setmodel(self, "models/onslaught/generator_dmg9.md3"); - else if(self.health < self.max_health * 0.20) - setmodel(self, "models/onslaught/generator_dmg8.md3"); - else if(self.health < self.max_health * 0.30) - setmodel(self, "models/onslaught/generator_dmg7.md3"); - else if(self.health < self.max_health * 0.40) - setmodel(self, "models/onslaught/generator_dmg6.md3"); - else if(self.health < self.max_health * 0.50) - setmodel(self, "models/onslaught/generator_dmg5.md3"); - else if(self.health < self.max_health * 0.60) - setmodel(self, "models/onslaught/generator_dmg4.md3"); - else if(self.health < self.max_health * 0.70) - setmodel(self, "models/onslaught/generator_dmg3.md3"); - else if(self.health < self.max_health * 0.80) - setmodel(self, "models/onslaught/generator_dmg2.md3"); - else if(self.health < self.max_health * 0.90) - setmodel(self, "models/onslaught/generator_dmg1.md3"); - setsize(self, '-52 -52 -14', '52 52 75'); + if(IS_PLAYER(self.owner.ons_toucher)) + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ONSLAUGHT_CAPTURE, self.owner.ons_toucher.netname, self.owner.message); + Send_Notification(NOTIF_ALL_EXCEPT, self.owner.ons_toucher, MSG_CENTER, APP_TEAM_ENT_4(self.owner.ons_toucher, CENTER_ONS_CAPTURE_), self.owner.message); + Send_Notification(NOTIF_ONE, self.owner.ons_toucher, MSG_CENTER, CENTER_ONS_CAPTURE, self.owner.message); + PlayerScore_Add(self.owner.ons_toucher, SP_ONS_CAPS, 1); + PlayerTeamScore_AddScore(self.owner.ons_toucher, 10); + } + + self.owner.ons_toucher = world; - // Throw some flaming gibs on damage, more damage = more chance for gib - if(random() < damage/220) - { - sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); - i = random(); - if(i < 0.3) - ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib1.md3", 5, true); - else if(i > 0.7) - ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib2.md3", 5, true); - else - ons_throwgib(hitloc + '0 0 20', force * -1, "models/onslaught/gen_gib3.md3", 5, true); - } - else - { - // particles on every hit - pointparticles(particleeffectnum("sparks"), hitloc, force * -1, 1); + onslaught_updatelinks(); - //sound on every hit - if (random() < 0.5) - sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM); - else - sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); + // Use targets now (somebody make sure this is in the right place..) + oself = self; + self = self.owner; + activator = self; + SUB_UseTargets (); + self = oself; + + self.SendFlags |= CPSF_SETUP; } - - //throw some gibs on damage - if(random() < damage/200+0.2) - if(random() < 0.5) - ons_throwgib(hitloc + '0 0 20', randomvec()*360, "models/onslaught/gen_gib1.md3", 5, false); + if(self.owner.model != "models/onslaught/controlpoint_pad2.md3") + setmodel_fixsize(self.owner, "models/onslaught/controlpoint_pad2.md3"); + + if(random() < 0.9 - self.health / self.max_health) + pointparticles(particleeffectnum("rage"), self.origin + 10 * randomvec(), '0 0 -1', 1); } -// update links after a delay -void onslaught_generator_delayed() +void ons_ControlPoint_Icon_Spawn(entity cp, entity player) { - onslaught_updatelinks(); - // now begin normal thinking - self.think = onslaught_generator_think; - self.nextthink = time; -} + entity e = spawn(); + + setsize(e, CPICON_MIN, CPICON_MAX); + setorigin(e, cp.origin + CPICON_OFFSET); + + e.classname = "onslaught_controlpoint_icon"; + e.owner = cp; + e.max_health = autocvar_g_onslaught_cp_health; + e.health = autocvar_g_onslaught_cp_buildhealth; + e.solid = SOLID_NOT; + e.takedamage = DAMAGE_AIM; - e.bot_attack = TRUE; ++ e.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, "onslaught/controlpoint_build.wav", VOL_BASE, ATTEN_NORM); + + cp.goalentity = e; + cp.team = e.team; + cp.colormap = e.colormap; -string onslaught_generator_waypointsprite_for_team(entity e, float t) -{ - if(t == e.team) - { - if(e.team == NUM_TEAM_1) - return "ons-gen-red"; - else if(e.team == NUM_TEAM_2) - return "ons-gen-blue"; - } - if(e.isshielded) - return "ons-gen-shielded"; - if(e.team == NUM_TEAM_1) - return "ons-gen-red"; - else if(e.team == NUM_TEAM_2) - return "ons-gen-blue"; - return ""; -} + pointparticles(particleeffectnum(sprintf("%sflag_touch", Static_Team_ColorName_Lower(player.team))), e.origin, '0 0 0', 1); -void onslaught_generator_updatesprite(entity e) -{ - string s1, s2, s3; - s1 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_1); - s2 = onslaught_generator_waypointsprite_for_team(e, NUM_TEAM_2); - s3 = onslaught_generator_waypointsprite_for_team(e, -1); - WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3); + 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; - 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 == NUM_TEAM_1 || e.team == NUM_TEAM_2) - 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 == NUM_TEAM_1 || e.team == NUM_TEAM_2) - 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); - } + onslaught_controlpoint_icon_link(e, ons_ControlPoint_Icon_BuildThink); } -string onslaught_controlpoint_waypointsprite_for_team(entity e, float t) +string ons_ControlPoint_Waypoint(entity e) { -- float a; - if(t != -1) + if(e.team) { - a = ons_ControlPoint_Attackable(e, e.team); - a = onslaught_controlpoint_attackable(e, t); - if(a == 3 || a == 4) // ATTACK/TOUCH THIS ONE NOW - { - if(e.team == NUM_TEAM_1) - return "ons-cp-atck-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-atck-blue"; - else - return "ons-cp-atck-neut"; - } - else if(a == -2) // DEFEND THIS ONE NOW - { - if(e.team == NUM_TEAM_1) - return "ons-cp-dfnd-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-dfnd-blue"; - } - else if(e.team == t || a == -1 || a == 1) // own point, or fire at it - { - if(e.team == NUM_TEAM_1) - return "ons-cp-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-blue"; - } - else if(a == 2) // touch it - return "ons-cp-neut"; ++ int a = ons_ControlPoint_Attackable(e, e.team); + + if(a == -2) { return "ons-cp-dfnd"; } // defend now + if(a == -1 || a == 1 || a == 2) { return "ons-cp"; } // touch + if(a == 3 || a == 4) { return "ons-cp-atck"; } // attack } else - { - if(e.team == NUM_TEAM_1) - return "ons-cp-red"; - else if(e.team == NUM_TEAM_2) - return "ons-cp-blue"; - else - return "ons-cp-neut"; - } + return "ons-cp"; + return ""; } -void onslaught_controlpoint_updatesprite(entity e) +void ons_ControlPoint_UpdateSprite(entity e) { - string s1, s2, s3; - s1 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_1); - s2 = onslaught_controlpoint_waypointsprite_for_team(e, NUM_TEAM_2); - s3 = onslaught_controlpoint_waypointsprite_for_team(e, -1); - WaypointSprite_UpdateSprites(e.sprite, s1, s2, s3); + string s1; + s1 = ons_ControlPoint_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); -- float sh; - sh = !(onslaught_controlpoint_can_be_linked(e, NUM_TEAM_1) || onslaught_controlpoint_can_be_linked(e, NUM_TEAM_2)); ++ 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) { @@@ -652,15 -818,15 +653,15 @@@ } if(e.lastshielded) { - if(e.team == NUM_TEAM_1 || e.team == NUM_TEAM_2) + if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, 0.5 * colormapPaletteColor(e.team - 1, FALSE)); + 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 == NUM_TEAM_1 || e.team == NUM_TEAM_2) + if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, FALSE)); + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, colormapPaletteColor(e.team - 1, false)); else WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_CONTROLPOINT, '0.75 0.75 0.75'); } @@@ -672,1487 -838,855 +673,1491 @@@ } } -void onslaught_generator_reset() +void ons_ControlPoint_Touch() { - self.team = self.team_saved; - self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; - self.takedamage = DAMAGE_AIM; - self.bot_attack = true; - self.iscaptured = true; - self.islinked = true; - self.isshielded = true; - self.enemy.solid = SOLID_NOT; - self.think = onslaught_generator_delayed; - self.nextthink = time + 0.2; - setmodel(self, "models/onslaught/generator.md3"); - setsize(self, '-52 -52 -14', '52 52 75'); - - if(!self.noalign) - { - setorigin(self, self.origin + '0 0 20'); - droptofloor(); + entity toucher = other; - float attackable; ++ int attackable; + + if((toucher.vehicle_flags & VHF_ISVEHICLE) && toucher.owner) + if(autocvar_g_onslaught_allow_vehicle_touch) + toucher = toucher.owner; + else + return; + + if(!IS_PLAYER(toucher)) { return; } + if(toucher.frozen) { return; } + if(toucher.deadflag != DEAD_NO) { return; } + + if ( SAME_TEAM(self,toucher) ) + if ( self.iscaptured ) + { + if(time <= toucher.teleport_antispam) + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT_ANTISPAM, rint(toucher.teleport_antispam - time)); + else + Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_ONS_TELEPORT); } + + attackable = ons_ControlPoint_Attackable(self, toucher.team); + if(attackable != 2 && attackable != 4) + return; + // we've verified that this player has a legitimate claim to this point, + // so start building the captured point icon (which only captures this + // point if it successfully builds without being destroyed first) + ons_ControlPoint_Icon_Spawn(self, toucher); + + self.ons_toucher = toucher; - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); + onslaught_updatelinks(); } -/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) - Base generator. - - spawnfunc_onslaught_link entities can target this. +void ons_ControlPoint_Think() +{ + self.nextthink = time + ONS_CP_THINKRATE; + CSQCMODEL_AUTOUPDATE(); +} -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. - */ -void spawnfunc_onslaught_generator() +void ons_ControlPoint_Reset() { - if (!g_onslaught) - { - remove(self); - return; - } + if(self.goalentity) + remove(self.goalentity); - //entity e; - precache_model("models/onslaught/generator.md3"); - precache_model("models/onslaught/generator_shield.md3"); - precache_model("models/onslaught/generator_dmg1.md3"); - precache_model("models/onslaught/generator_dmg2.md3"); - precache_model("models/onslaught/generator_dmg3.md3"); - precache_model("models/onslaught/generator_dmg4.md3"); - precache_model("models/onslaught/generator_dmg5.md3"); - precache_model("models/onslaught/generator_dmg6.md3"); - precache_model("models/onslaught/generator_dmg7.md3"); - precache_model("models/onslaught/generator_dmg8.md3"); - precache_model("models/onslaught/generator_dmg9.md3"); - precache_model("models/onslaught/generator_dead.md3"); - precache_model("models/onslaught/shockwave.md3"); - precache_model("models/onslaught/shockwavetransring.md3"); - precache_model("models/onslaught/gen_gib1.md3"); - precache_model("models/onslaught/gen_gib2.md3"); - precache_model("models/onslaught/gen_gib3.md3"); - precache_model("models/onslaught/ons_ray.md3"); - precache_sound("onslaught/generator_decay.wav"); - precache_sound("weapons/grenade_impact.wav"); - precache_sound("weapons/rocket_impact.wav"); - precache_sound("onslaught/generator_underattack.wav"); - precache_sound("onslaught/shockwave.wav"); - precache_sound("onslaught/ons_hit1.wav"); - precache_sound("onslaught/ons_hit2.wav"); - precache_sound("onslaught/electricity_explode.wav"); - if (!self.team) - objerror("team must be set"); + self.goalentity = world; + self.team = 0; + self.colormap = 1024; - self.iscaptured = FALSE; - self.islinked = FALSE; - self.isshielded = TRUE; ++ self.iscaptured = false; ++ self.islinked = false; ++ self.isshielded = true; + self.think = ons_ControlPoint_Think; + self.ons_toucher = world; + self.nextthink = time + ONS_CP_THINKRATE; + setmodel_fixsize(self, "models/onslaught/controlpoint_pad.md3"); - if(self.team == NUM_TEAM_1) - ons_red_generator = self; + WaypointSprite_UpdateMaxHealth(self.sprite, 0); + WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); - if(self.team == NUM_TEAM_2) - ons_blue_generator = self; + onslaught_updatelinks(); - self.team_saved = self.team; - self.colormap = 1024 + (self.team - 1) * 17; - self.solid = SOLID_BBOX; - self.movetype = MOVETYPE_NONE; - self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; - setmodel(self, "models/onslaught/generator.md3"); - setsize(self, '-52 -52 -14', '52 52 75'); - setorigin(self, self.origin); - self.takedamage = DAMAGE_AIM; - self.bot_attack = true; - self.event_damage = onslaught_generator_damage; - self.iscaptured = true; - self.islinked = true; - self.isshielded = true; - // helper entity that create fx when generator is damaged - onslaught_generator_damage_spawn(self); - // spawn shield model which indicates whether this can be damaged - self.enemy = spawn(); - setattachment(self.enemy , self, ""); - self.enemy.classname = "onslaught_generator_shield"; - self.enemy.solid = SOLID_NOT; - self.enemy.movetype = MOVETYPE_NONE; - self.enemy.effects = EF_ADDITIVE; - setmodel(self.enemy, "models/onslaught/generator_shield.md3"); - //setorigin(e, self.origin); - self.enemy.colormap = self.colormap; - self.enemy.team = self.team; - //self.think = onslaught_generator_delayed; - //self.nextthink = time + 0.2; - InitializeEntity(self, onslaught_generator_delayed, INITPRIO_LAST); - - WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0'); - WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY); - WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.sprite, self.health); + activator = self; + SUB_UseTargets(); // to reset the structures, playerspawns etc. - waypoint_spawnforitem(self); + CSQCMODEL_AUTOUPDATE(); +} +void ons_DelayedControlPoint_Setup(void) +{ onslaught_updatelinks(); + + // captureshield setup - ons_CaptureShield_Spawn(self, FALSE); ++ ons_CaptureShield_Spawn(self, false); + + CSQCMODEL_AUTOINIT(); +} - self.reset = onslaught_generator_reset; +void ons_ControlPoint_Setup(entity cp) +{ + // declarations + self = cp; // for later usage with droptofloor() + + // main setup + cp.ons_worldcpnext = ons_worldcplist; // link control point into ons_worldcplist + ons_worldcplist = cp; + + cp.netname = "Control point"; + cp.team = 0; + cp.solid = SOLID_BBOX; + cp.movetype = MOVETYPE_NONE; + cp.touch = ons_ControlPoint_Touch; + cp.think = ons_ControlPoint_Think; + cp.nextthink = time + ONS_CP_THINKRATE; + cp.reset = ons_ControlPoint_Reset; + cp.colormap = 1024; - cp.iscaptured = FALSE; - cp.islinked = FALSE; - cp.isshielded = TRUE; ++ cp.iscaptured = false; ++ cp.islinked = false; ++ cp.isshielded = true; + + if(cp.message == "") { cp.message = "a"; } + + // precache - TODO: clean up! + precache_model("models/onslaught/controlpoint_pad.md3"); + precache_model("models/onslaught/controlpoint_pad2.md3"); + precache_model("models/onslaught/controlpoint_shield.md3"); + precache_model("models/onslaught/controlpoint_icon.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg1.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg2.md3"); + precache_model("models/onslaught/controlpoint_icon_dmg3.md3"); + precache_model("models/onslaught/controlpoint_icon_gib1.md3"); + precache_model("models/onslaught/controlpoint_icon_gib2.md3"); + precache_model("models/onslaught/controlpoint_icon_gib4.md3"); + precache_sound("onslaught/controlpoint_build.wav"); + precache_sound("onslaught/controlpoint_built.wav"); + precache_sound("weapons/grenade_impact.wav"); + precache_sound("onslaught/damageblockedbyshield.wav"); + precache_sound("onslaught/controlpoint_underattack.wav"); + precache_sound("onslaught/ons_spark1.wav"); + precache_sound("onslaught/ons_spark2.wav"); + + // appearence + setmodel_fixsize(cp, "models/onslaught/controlpoint_pad.md3"); + + // control point placement + if((cp.spawnflags & 1) || cp.noalign) // don't drop to floor, just stay at fixed location + { - cp.noalign = TRUE; ++ cp.noalign = true; + cp.movetype = MOVETYPE_NONE; + } + else // drop to floor, automatically find a platform and set that as spawn origin + { + setorigin(cp, cp.origin + '0 0 20'); - cp.noalign = FALSE; ++ cp.noalign = false; + self = cp; + droptofloor(); + cp.movetype = MOVETYPE_TOSS; + } + + // waypointsprites + WaypointSprite_SpawnFixed(string_null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE, '0 0 0'); + WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); + + InitializeEntity(cp, ons_DelayedControlPoint_Setup, INITPRIO_SETLOCATION); } -.float waslinked; -.float cp_bob_spd; -.vector cp_origin, cp_bob_origin, cp_bob_dmg; -float ons_notification_time_team1; -float ons_notification_time_team2; +// ========================= +// Main Generator Functions +// ========================= -void onslaught_controlpoint_icon_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +string ons_Generator_Waypoint(entity e) { - entity oself; - float nag; + if(e.isshielded) + return "ons-gen-shielded"; + return "ons-gen"; +} - if (damage <= 0) - return; - if (self.owner.isshielded) - { - // this is protected by a shield, so ignore the damage - if (time > self.pain_finished) - if (IS_PLAYER(attacker)) - { - play2(attacker, "onslaught/damageblockedbyshield.wav"); - self.pain_finished = time + 1; - } - return; - } +void ons_Generator_UpdateSprite(entity e) +{ - string s1; - s1 = ons_Generator_Waypoint(e); ++ string s1 = ons_Generator_Waypoint(e); + WaypointSprite_UpdateSprites(e.sprite, s1, s1, s1); - if (IS_PLAYER(attacker)) + if(e.lastteam != e.team + 2 || e.lastshielded != e.isshielded) { - nag = false; - if(self.team == NUM_TEAM_1) + e.lastteam = e.team + 2; + e.lastshielded = e.isshielded; + if(e.lastshielded) { - if(time - ons_notification_time_team1 > 10) - { - nag = true; - ons_notification_time_team1 = time; - } + if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, 0.5 * colormapPaletteColor(e.team - 1, FALSE)); ++ 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(self.team == NUM_TEAM_2) + else { - if(time - ons_notification_time_team2 > 10) - { - nag = true; - ons_notification_time_team2 = time; - } + if(e.team) - WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, FALSE)); ++ WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, colormapPaletteColor(e.team - 1, false)); + else + WaypointSprite_UpdateTeamRadar(e.sprite, RADARICON_GENERATOR, '0.75 0.75 0.75'); } - else - nag = true; - - if(nag) - play2team(self.team, "onslaught/controlpoint_underattack.wav"); + WaypointSprite_Ping(e.sprite); } +} - void ons_GeneratorDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) - self.health = self.health - damage; - if(self.owner.iscaptured) - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - else - WaypointSprite_UpdateBuildFinished(self.owner.sprite, time + (self.max_health - self.health) / (self.count / sys_frametime)); - self.pain_finished = time + 1; - self.punchangle = (2 * randomvec() - '1 1 1') * 45; - self.cp_bob_dmg_z = (2 * random() - 1) * 15; - // colormod flash when shot - self.colormod = '2 2 2'; - // particles on every hit - pointparticles(particleeffectnum("sparks"), hitloc, force*-1, 1); - //sound on every hit - if (random() < 0.5) - sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE+0.3, ATTEN_NORM); - else - sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE+0.3, ATTEN_NORM); ++void ons_GeneratorDamage(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 (self.health < 0) + if (attacker != self) { - sound(self, CH_TRIGGER, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); - pointparticles(particleeffectnum("rocket_explode"), self.origin, '0 0 0', 1); + if (self.isshielded) { - string t; - t = Team_ColoredFullName(attacker.team); - bprint(Team_ColoredFullName(self.team), " ", self.message, " control point destroyed by ", t, "\n"); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 25, "models/onslaught/controlpoint_icon_gib1.md3", 3, false); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, false); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 45, "models/onslaught/controlpoint_icon_gib2.md3", 3, false); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false); - ons_throwgib(self.origin, (2 * randomvec() - '1 1 1') * 75, "models/onslaught/controlpoint_icon_gib4.md3", 3, false); + // this is protected by a shield, so ignore the damage + if (time > self.pain_finished) + if (IS_PLAYER(attacker)) + { + play2(attacker, "onslaught/damageblockedbyshield.wav"); + attacker.typehitsound += 1; + self.pain_finished = time + 1; + } + return; } - self.owner.goalentity = world; - self.owner.islinked = false; - self.owner.iscaptured = false; - self.owner.team = 0; - self.owner.colormap = 1024; + if (time > self.pain_finished) + { + self.pain_finished = time + 10; + entity head; + FOR_EACH_REALPLAYER(head) if(SAME_TEAM(head, self)) { Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_GENERATOR_UNDERATTACK); } + play2team(self.team, "onslaught/generator_underattack.wav"); + } + } + self.health = self.health - damage; + WaypointSprite_UpdateHealth(self.sprite, self.health); + // choose an animation frame based on health + self.frame = 10 * bound(0, (1 - self.health / self.max_health), 1); + // see if the generator is still functional, or dying + if (self.health > 0) + { + self.lasthealth = self.health; + } + else + { + if (attacker == self) + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_OVERTIME_)); + else + { + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(self.team, INFO_ONSLAUGHT_GENDESTROYED_)); + PlayerScore_Add(attacker, SP_SCORE, 100); + } - self.iscaptured = FALSE; - self.islinked = FALSE; - self.isshielded = FALSE; ++ self.iscaptured = false; ++ self.islinked = false; ++ self.isshielded = false; + self.takedamage = DAMAGE_NO; // can't be hurt anymore + self.event_damage = func_null; // won't do anything if hurt + self.count = 0; // reset counter + self.think = func_null; + self.nextthink = 0; + //self.think(); // do the first explosion now - WaypointSprite_UpdateMaxHealth(self.owner.sprite, 0); + WaypointSprite_UpdateMaxHealth(self.sprite, 0); + WaypointSprite_Ping(self.sprite); + //WaypointSprite_Kill(self.sprite); // can't do this yet, code too poor onslaught_updatelinks(); + } - // Use targets now (somebody make sure this is in the right place..) - oself = self; - self = self.owner; - activator = self; - SUB_UseTargets (); - self = oself; - - - self.owner.waslinked = self.owner.islinked; - if(self.owner.model != "models/onslaught/controlpoint_pad.md3") - setmodel(self.owner, "models/onslaught/controlpoint_pad.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); + // Throw some flaming gibs on damage, more damage = more chance for gib + if(random() < damage/220) + { + sound(self, CH_TRIGGER, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + } + else + { + // particles on every hit + pointparticles(particleeffectnum("sparks"), hitloc, force * -1, 1); - remove(self); + //sound on every hit + if (random() < 0.5) + sound(self, CH_TRIGGER, "onslaught/ons_hit1.wav", VOL_BASE, ATTEN_NORM); + else + sound(self, CH_TRIGGER, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM); } + + self.SendFlags |= GSF_STATUS; } -void onslaught_controlpoint_icon_think() +void ons_GeneratorThink() { - entity oself; - self.nextthink = time + sys_frametime; - - if(autocvar_g_onslaught_cp_proxydecap) + entity e; + self.nextthink = time + GEN_THINKRATE; + if (!gameover) { - float _enemy_count = 0; - float _friendly_count = 0; - float _dist; - entity _player; - - FOR_EACH_PLAYER(_player) + if(!self.isshielded && self.wait < time) { - if(!_player.deadflag) + self.wait = time + 5; + FOR_EACH_REALPLAYER(e) { - _dist = vlen(_player.origin - self.origin); - if(_dist < autocvar_g_onslaught_cp_proxydecap_distance) - { - if(_player.team == self.team) - ++_friendly_count; - else - ++_enemy_count; + if(SAME_TEAM(e, self)) + { + Send_Notification(NOTIF_ONE, e, MSG_CENTER, CENTER_ONS_NOTSHIELDED_TEAM); + soundto(MSG_ONE, e, CHAN_AUTO, "kh/alarm.wav", VOL_BASE, ATTEN_NONE); // FIXME: unique sound? } + else + Send_Notification(NOTIF_ONE, e, MSG_CENTER, APP_TEAM_NUM_4(self.team, CENTER_ONS_NOTSHIELDED_)); } } + } +} - _friendly_count = _friendly_count * (autocvar_g_onslaught_cp_proxydecap_dps * sys_frametime); - _enemy_count = _enemy_count * (autocvar_g_onslaught_cp_proxydecap_dps * sys_frametime); +void ons_GeneratorReset() +{ + self.team = self.team_saved; + self.lasthealth = self.max_health = self.health = autocvar_g_onslaught_gen_health; + self.takedamage = DAMAGE_AIM; - self.bot_attack = TRUE; - self.iscaptured = TRUE; - self.islinked = TRUE; - self.isshielded = TRUE; ++ self.bot_attack = true; ++ self.iscaptured = true; ++ self.islinked = true; ++ self.isshielded = true; + self.event_damage = ons_GeneratorDamage; + self.think = ons_GeneratorThink; + self.nextthink = time + GEN_THINKRATE; + - Net_LinkEntity(self, FALSE, 0, generator_send); ++ Net_LinkEntity(self, false, 0, generator_send); + + self.SendFlags = GSF_SETUP; // just incase + self.SendFlags |= GSF_STATUS; - self.health = bound(0, self.health + (_friendly_count - _enemy_count), self.max_health); - if(self.health <= 0) - { - onslaught_controlpoint_icon_damage(self, self, 1, 0, self.origin, '0 0 0'); - return; - } - } + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + WaypointSprite_UpdateRule(self.sprite,self.team,SPRITERULE_TEAMPLAY); + + onslaught_updatelinks(); +} - if (time > self.pain_finished + 5) +void ons_DelayedGeneratorSetup() +{ + // bot waypoints + waypoint_spawnforitem_force(self, self.origin); + self.nearestwaypointtimeout = 0; // activate waypointing again + self.bot_basewaypoint = self.nearestwaypoint; + + // captureshield setup - ons_CaptureShield_Spawn(self, TRUE); ++ ons_CaptureShield_Spawn(self, true); + + onslaught_updatelinks(); + - Net_LinkEntity(self, FALSE, 0, generator_send); ++ Net_LinkEntity(self, false, 0, generator_send); +} + + +void onslaught_generator_touch() +{ + if ( IS_PLAYER(other) ) + if ( SAME_TEAM(self,other) ) + if ( self.iscaptured ) { - if(self.health < self.max_health) - { - self.health = self.health + self.count; - if (self.health >= self.max_health) - self.health = self.max_health; - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); - } + Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_ONS_TELEPORT); } - if (self.health < self.max_health * 0.25) - setmodel(self, "models/onslaught/controlpoint_icon_dmg3.md3"); - else if (self.health < self.max_health * 0.50) - setmodel(self, "models/onslaught/controlpoint_icon_dmg2.md3"); - else if (self.health < self.max_health * 0.75) - setmodel(self, "models/onslaught/controlpoint_icon_dmg1.md3"); - else if (self.health < self.max_health * 0.90) - setmodel(self, "models/onslaught/controlpoint_icon.md3"); - // colormod flash when shot - self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); +} - if(self.owner.islinked != self.owner.waslinked) - { - // unteam the spawnpoint if needed - float t; - t = self.owner.team; - if(!self.owner.islinked) - self.owner.team = 0; +void ons_GeneratorSetup(entity gen) // called when spawning a generator entity on the map as a spawnfunc +{ + // declarations - float teamnumber = gen.team; ++ int teamnumber = gen.team; + self = gen; // for later usage with droptofloor() + + // main setup + gen.ons_worldgeneratornext = ons_worldgeneratorlist; // link generator into ons_worldgeneratorlist + ons_worldgeneratorlist = gen; + + gen.netname = sprintf("%s generator", Team_ColoredFullName(teamnumber)); + gen.classname = "onslaught_generator"; + gen.solid = SOLID_BBOX; + gen.team_saved = teamnumber; + gen.movetype = MOVETYPE_NONE; + gen.lasthealth = gen.max_health = gen.health = autocvar_g_onslaught_gen_health; + gen.takedamage = DAMAGE_AIM; - gen.bot_attack = TRUE; ++ gen.bot_attack = true; + gen.event_damage = ons_GeneratorDamage; + gen.reset = ons_GeneratorReset; + gen.think = ons_GeneratorThink; + gen.nextthink = time + GEN_THINKRATE; - gen.iscaptured = TRUE; - gen.islinked = TRUE; - gen.isshielded = TRUE; ++ gen.iscaptured = true; ++ gen.islinked = true; ++ gen.isshielded = true; + gen.touch = onslaught_generator_touch; + + // precache - TODO: clean up! + precache_model("models/onslaught/generator_shield.md3"); + precache_model("models/onslaught/gen_gib1.md3"); + precache_model("models/onslaught/gen_gib2.md3"); + precache_model("models/onslaught/gen_gib3.md3"); + precache_sound("onslaught/generator_decay.wav"); + precache_sound("weapons/grenade_impact.wav"); + precache_sound("weapons/rocket_impact.wav"); + precache_sound("onslaught/generator_underattack.wav"); + precache_sound("onslaught/shockwave.wav"); + precache_sound("onslaught/ons_hit1.wav"); + precache_sound("onslaught/ons_hit2.wav"); + precache_sound("onslaught/generator_underattack.wav"); + + // 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 + self = gen; + droptofloor(); + + // waypointsprites + WaypointSprite_SpawnFixed(string_null, self.origin + CPGEN_WAYPOINT_OFFSET, self, sprite, RADARICON_NONE, '0 0 0'); + WaypointSprite_UpdateRule(self.sprite, self.team, SPRITERULE_TEAMPLAY); + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + + InitializeEntity(gen, ons_DelayedGeneratorSetup, INITPRIO_SETLOCATION); +} - oself = self; - self = self.owner; - activator = self; - SUB_UseTargets (); - self = oself; - self.owner.team = t; +// =============== +// Round Handler +// =============== - float total_generators, redowned, blueowned, yellowowned, pinkowned; - self.owner.waslinked = self.owner.islinked; ++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); } +} - float Onslaught_GetWinnerTeam() - if (self.punchangle.x > 0) ++int Onslaught_GetWinnerTeam() +{ - float winner_team = 0; ++ int winner_team = 0; + if(redowned > 0) + winner_team = NUM_TEAM_1; + if(blueowned > 0) { - self.punchangle_x = self.punchangle.x - 60 * sys_frametime; - if (self.punchangle.x < 0) - self.punchangle_x = 0; + if(winner_team) return 0; + winner_team = NUM_TEAM_2; } - else if (self.punchangle.x < 0) + if(yellowowned > 0) { - self.punchangle_x = self.punchangle.x + 60 * sys_frametime; - if (self.punchangle.x > 0) - self.punchangle_x = 0; + if(winner_team) return 0; + winner_team = NUM_TEAM_3; } - - if (self.punchangle.y > 0) + if(pinkowned > 0) { - self.punchangle_y = self.punchangle.y - 60 * sys_frametime; - if (self.punchangle.y < 0) - self.punchangle_y = 0; + if(winner_team) return 0; + winner_team = NUM_TEAM_4; } - else if (self.punchangle.y < 0) + if(winner_team) + return winner_team; + return -1; // no generators left? +} + +#define ONS_OWNED_GENERATORS() ((redowned > 0) + (blueowned > 0) + (yellowowned > 0) + (pinkowned > 0)) +#define ONS_OWNED_GENERATORS_OK() (ONS_OWNED_GENERATORS() > 1) - float Onslaught_CheckWinner() ++bool Onslaught_CheckWinner() +{ + entity e; + + if ((autocvar_timelimit && time > game_starttime + autocvar_timelimit * 60) || (round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)) { - ons_stalemate = TRUE; - self.punchangle_y = self.punchangle.y + 60 * sys_frametime; - if (self.punchangle.y > 0) - self.punchangle_y = 0; ++ ons_stalemate = true; + + if (!wpforenemy_announced) + { + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); + sound(world, CH_INFO, "onslaught/generator_decay.wav", VOL_BASE, ATTEN_NONE); + - wpforenemy_announced = TRUE; ++ wpforenemy_announced = true; + } + + entity tmp_entity; // temporary entity + float d; + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) if(time >= tmp_entity.ons_overtime_damagedelay) + { + // tmp_entity.max_health / 300 gives 5 minutes of overtime. + // control points reduce the overtime duration. + d = 1; + for(e = ons_worldcplist; e; e = e.ons_worldcpnext) + { + if(DIFF_TEAM(e, tmp_entity)) + if(e.islinked) + d = d + 1; + } + + if(autocvar_g_campaign && autocvar__campaign_testrun) + d = d * tmp_entity.max_health; + else + d = d * tmp_entity.max_health / max(30, 60 * autocvar_timelimit_suddendeath); + + Damage(tmp_entity, tmp_entity, tmp_entity, d, DEATH_HURTTRIGGER, 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; } ++ else { wpforenemy_announced = false; ons_stalemate = false; } + + Onslaught_count_generators(); + + if(ONS_OWNED_GENERATORS_OK()) + return 0; + - float winner_team = Onslaught_GetWinnerTeam(); ++ int winner_team = Onslaught_GetWinnerTeam(); - if (self.punchangle.z > 0) + if(winner_team > 0) { - self.punchangle_z = self.punchangle.z - 60 * sys_frametime; - if (self.punchangle.z < 0) - self.punchangle_z = 0; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_)); + Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_)); + TeamScore_AddToTeam(winner_team, ST_ONS_CAPS, +1); } - else if (self.punchangle.z < 0) + else if(winner_team == -1) { - self.punchangle_z = self.punchangle.z + 60 * sys_frametime; - if (self.punchangle.z > 0) - self.punchangle_z = 0; + Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_TIED); } + - ons_stalemate = FALSE; ++ ons_stalemate = false; - self.angles_x = self.punchangle.x; - self.angles_y = self.punchangle.y + self.mangle.y; - self.angles_z = self.punchangle.z; - self.mangle_y = self.mangle.y + 45 * sys_frametime; - - self.cp_bob_origin_z = 4 * PI * (1 - cos(self.cp_bob_spd)); - self.cp_bob_spd = self.cp_bob_spd + 1.875 * sys_frametime; - if(self.cp_bob_dmg.z > 0) - self.cp_bob_dmg_z = self.cp_bob_dmg.z - 3 * sys_frametime; - else - self.cp_bob_dmg_z = 0; - setorigin(self,self.cp_origin + self.cp_bob_origin + self.cp_bob_dmg); - - // damaged fx - if(random() < 0.6 - self.health / self.max_health) + play2all(sprintf("ctf/%s_capture.wav", Static_Team_ColorName_Lower(winner_team))); + + round_handler_Init(7, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); + + FOR_EACH_PLAYER(e) { - e.ons_roundlost = TRUE; - e.player_blocked = TRUE; - pointparticles(particleeffectnum("electricity_sparks"), self.origin + randompos('-10 -10 -20', '10 10 20'), '0 0 0', 1); ++ e.ons_roundlost = true; ++ e.player_blocked = true; - if(random() > 0.8) - sound(self, CH_PAIN, "onslaught/ons_spark1.wav", VOL_BASE, ATTEN_NORM); - else if (random() > 0.5) - sound(self, CH_PAIN, "onslaught/ons_spark2.wav", VOL_BASE, ATTEN_NORM); + nades_Clear(e); } + + return 1; } - float Onslaught_CheckPlayers() -void onslaught_controlpoint_icon_buildthink() ++bool Onslaught_CheckPlayers() { - entity oself; - float a; + return 1; +} - self.nextthink = time + sys_frametime; +void Onslaught_RoundStart() +{ + entity tmp_entity; - FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = FALSE; } ++ FOR_EACH_PLAYER(tmp_entity) { tmp_entity.player_blocked = false; } - // only do this if there is power - a = onslaught_controlpoint_can_be_linked(self.owner, self.owner.team); - if(!a) - return; + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + tmp_entity.sprite.SendFlags |= 16; - self.health = self.health + self.count; + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + tmp_entity.sprite.SendFlags |= 16; +} - if (self.health >= self.max_health) - { - self.health = self.max_health; - self.count = autocvar_g_onslaught_cp_regen * sys_frametime; // slow repair rate from now on - self.think = onslaught_controlpoint_icon_think; - sound(self, CH_TRIGGER, "onslaught/controlpoint_built.wav", VOL_BASE, ATTEN_NORM); - bprint(Team_ColoredFullName(self.team), " captured ", self.owner.message, " control point\n"); - self.owner.iscaptured = true; - WaypointSprite_UpdateMaxHealth(self.owner.sprite, self.max_health); - WaypointSprite_UpdateHealth(self.owner.sprite, self.health); +// ================ +// Bot player logic +// ================ - onslaught_updatelinks(); +// NOTE: LEGACY CODE, needs to be re-written! - // Use targets now (somebody make sure this is in the right place..) - oself = self; - self = self.owner; - activator = self; - SUB_UseTargets (); - self = oself; - self.cp_origin = self.origin; - self.cp_bob_origin = '0 0 0.1'; - self.cp_bob_spd = 0; +void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius) +{ + entity head; - float t, i, c, needarmor = FALSE, needweapons = FALSE; ++ float t, c; ++ int i; ++ bool needarmor = false, needweapons = false; + + // Needs armor/health? + if(self.health<100) - needarmor = TRUE; ++ needarmor = true; + + // Needs weapons? + c = 0; + for(i = WEP_FIRST; i <= WEP_LAST ; ++i) + { + // Find weapon + if(self.weapons & WepSet_FromWeapon(i)) + if(++c>=4) + break; } - self.alpha = self.health / self.max_health; - // colormod flash when shot - self.colormod = '1 1 1' * (2 - bound(0, (self.pain_finished - time) / 10, 1)); - if(self.owner.model != "models/onslaught/controlpoint_pad2.md3") - setmodel(self.owner, "models/onslaught/controlpoint_pad2.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); - if(random() < 0.9 - self.health / self.max_health) - pointparticles(particleeffectnum("rage"), self.origin + 10 * randomvec(), '0 0 -1', 1); -} + if(c<4) - needweapons = TRUE; ++ needweapons = true; + if(!needweapons && !needarmor) + return; + ons_debug(strcat(self.netname, " needs weapons ", ftos(needweapons) , "\n")); + ons_debug(strcat(self.netname, " needs armor ", ftos(needarmor) , "\n")); + // See what is around - head = findchainfloat(bot_pickup, TRUE); ++ head = findchainfloat(bot_pickup, true); + while (head) + { + // gather health and armor only + if (head.solid) + if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) ) + if (vlen(head.origin - org) < sradius) + { + t = head.bot_pickupevalfunc(self, head); + if (t > 0) + navigation_routerating(head, t * ratingscale, 500); + } + head = head.chain; + } +} - void havocbot_role_ons_setrole(entity bot, float role) -void onslaught_controlpoint_touch() ++void havocbot_role_ons_setrole(entity bot, int role) { - entity e; - float a; - if (!IS_PLAYER(other)) - return; - a = onslaught_controlpoint_attackable(self, other.team); - if(a != 2 && a != 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) - self.goalentity = e = spawn(); - e.classname = "onslaught_controlpoint_icon"; - e.owner = self; - e.max_health = autocvar_g_onslaught_cp_health; - e.health = autocvar_g_onslaught_cp_buildhealth; - e.solid = SOLID_BBOX; - e.movetype = MOVETYPE_NONE; - setmodel(e, "models/onslaught/controlpoint_icon.md3"); - setsize(e, '-32 -32 -32', '32 32 32'); - setorigin(e, self.origin + '0 0 96'); - e.takedamage = DAMAGE_AIM; - e.bot_attack = true; - e.event_damage = onslaught_controlpoint_icon_damage; - e.team = other.team; - e.colormap = 1024 + (e.team - 1) * 17; - e.think = onslaught_controlpoint_icon_buildthink; - e.nextthink = time + sys_frametime; - e.count = (e.max_health - e.health) * sys_frametime / autocvar_g_onslaught_cp_buildtime; // how long it takes to build - sound(e, CH_TRIGGER, "onslaught/controlpoint_build.wav", VOL_BASE, ATTEN_NORM); - self.team = e.team; - self.colormap = e.colormap; - WaypointSprite_UpdateBuildFinished(self.sprite, time + (e.max_health - e.health) / (e.count / sys_frametime)); - onslaught_updatelinks(); + ons_debug(strcat(bot.netname," switched to ")); + switch(role) + { + case HAVOCBOT_ONS_ROLE_DEFENSE: + ons_debug("defense"); + bot.havocbot_role = havocbot_role_ons_defense; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_ASSISTANT: + ons_debug("assistant"); + bot.havocbot_role = havocbot_role_ons_assistant; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT; + bot.havocbot_role_timeout = 0; + break; + case HAVOCBOT_ONS_ROLE_OFFENSE: + ons_debug("offense"); + bot.havocbot_role = havocbot_role_ons_offense; + bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE; + bot.havocbot_role_timeout = 0; + break; + } + ons_debug("\n"); } - float havocbot_ons_teamcount(entity bot, float role) -void onslaught_controlpoint_think() ++int havocbot_ons_teamcount(entity bot, int role) { - float c = 0; - self.nextthink = time; - CSQCMODEL_AUTOUPDATE(); ++ int c = 0; + entity head; + + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, self)) + if(head.havocbot_role_flags & role) + ++c; + + return c; } -void onslaught_controlpoint_reset() +void havocbot_goalrating_ons_controlpoints_attack(float ratingscale) { - if(self.goalentity && self.goalentity != world) - remove(self.goalentity); - self.goalentity = world; - self.team = 0; - self.colormap = 1024; - self.iscaptured = false; - self.islinked = false; - self.isshielded = true; - self.enemy.solid = SOLID_NOT; - self.enemy.colormap = self.colormap; - self.think = onslaught_controlpoint_think; - self.enemy.think = func_null; - self.nextthink = time; // don't like func_null :P - setmodel(self, "models/onslaught/controlpoint_pad.md3"); - //setsize(self, '-32 -32 0', '32 32 8'); + entity cp, cp1, cp2, best, pl, wp; - float radius, found, bestvalue, c; ++ float radius, bestvalue; ++ int c; ++ bool found; - WaypointSprite_UpdateMaxHealth(self.sprite, 0); + // Filter control points + for(cp2 = ons_worldcplist; cp2; cp2 = cp2.ons_worldcpnext) + { + cp2.wpcost = c = 0; - cp2.wpconsidered = FALSE; ++ cp2.wpconsidered = false; - onslaught_updatelinks(); + if(cp2.isshielded) + continue; - activator = self; - SUB_UseTargets(); // to reset the structures, playerspawns etc. + // Ignore owned controlpoints + if(!(cp2.isgenneighbor[self.team] || cp2.iscpneighbor[self.team])) + continue; - CSQCMODEL_AUTOUPDATE(); + // Count team mates interested in this control point + // (easier and cleaner than keeping counters per cp and teams) + FOR_EACH_PLAYER(pl) + if(SAME_TEAM(pl, self)) + if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE) + if(pl.havocbot_ons_target==cp2) + ++c; + + // NOTE: probably decrease the cost of attackable control points + cp2.wpcost = c; - cp2.wpconsidered = TRUE; ++ cp2.wpconsidered = true; + } + + // We'll consider only the best case + bestvalue = 99999999999; + cp = world; + for(cp1 = ons_worldcplist; cp1; cp1 = cp1.ons_worldcpnext) + { + if (!cp1.wpconsidered) + continue; + + if(cp1.wpcost self.havocbot_role_timeout) { - setorigin(self, self.origin + '0 0 20'); - droptofloor(); + havocbot_ons_reset_role(self); + return; } - self.touch = onslaught_controlpoint_touch; - self.team = 0; - self.colormap = 1024; - self.iscaptured = false; - self.islinked = false; - self.isshielded = true; - // spawn shield model which indicates whether this can be damaged - self.enemy = spawn(); - self.enemy.classname = "onslaught_controlpoint_shield"; - self.enemy.solid = SOLID_NOT; - self.enemy.movetype = MOVETYPE_NONE; - self.enemy.effects = EF_ADDITIVE; - setmodel(self.enemy , "models/onslaught/controlpoint_shield.md3"); + if(self.havocbot_attack_time>time) + return; + + if (self.bot_strategytime < time) + { + navigation_goalrating_start(); + havocbot_goalrating_enemyplayers(20000, self.origin, 650); + if(!havocbot_goalrating_ons_generator_attack(20000)) + havocbot_goalrating_ons_controlpoints_attack(20000); + havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000); + navigation_goalrating_end(); + + self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; + } +} + +void havocbot_role_ons_assistant() +{ + havocbot_ons_reset_role(self); +} + +void havocbot_role_ons_defense() +{ + havocbot_ons_reset_role(self); +} - setattachment(self.enemy , self, ""); - //setsize(e, '-32 -32 0', '32 32 128'); +void havocbot_ons_reset_role(entity bot) +{ + entity head; - float c; ++ int c = 0; - //setorigin(e, self.origin); - self.enemy.colormap = self.colormap; + if(self.deadflag != DEAD_NO) + return; - waypoint_spawnforitem(self); + bot.havocbot_ons_target = world; - self.think = onslaught_controlpoint_think; - self.nextthink = time; + // TODO: Defend control points or generator if necessary - WaypointSprite_SpawnFixed(string_null, self.origin + '0 0 128', self, sprite, RADARICON_NONE, '0 0 0'); - WaypointSprite_UpdateRule(self.sprite, NUM_TEAM_2, SPRITERULE_TEAMPLAY); + // if there is only me on the team switch to offense + c = 0; + FOR_EACH_PLAYER(head) + if(SAME_TEAM(head, self)) + ++c; - onslaught_updatelinks(); + if(c==1) + { + havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); + return; + } - self.reset = onslaught_controlpoint_reset; + havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE); +} - CSQCMODEL_AUTOINIT(); + +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + */ +entity ons_Nearest_ControlPoint(vector pos, float max_dist) +{ + entity tmp_entity, closest_target = world; + tmp_entity = findchain(classname, "onslaught_controlpoint"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) <= max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + if(SAME_TEAM(tmp_entity, self)) + if(max_dist <= 0 || vlen(tmp_entity.origin - pos) < max_dist) + if(vlen(tmp_entity.origin - pos) <= vlen(closest_target.origin - pos) || closest_target == world) + closest_target = tmp_entity; + tmp_entity = tmp_entity.chain; + } + + return closest_target; } -float onslaught_link_send(entity to, float sendflags) +/* + * Find control point or generator owned by the same team self which is nearest to pos + * if max_dist is positive, only control points within this range will be considered + * This function only check distances on the XY plane, disregarding Z + */ +entity ons_Nearest_ControlPoint_2D(vector pos, float max_dist) { - WriteByte(MSG_ENTITY, ENT_CLIENT_RADARLINK); - WriteByte(MSG_ENTITY, sendflags); - if(sendflags & 1) + entity tmp_entity, closest_target = world; + vector delta; + float smallest_distance = 0, distance; + + tmp_entity = findchain(classname, "onslaught_controlpoint"); + while(tmp_entity) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + + tmp_entity = tmp_entity.chain; + } + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) + { + delta = tmp_entity.origin - pos; + delta_z = 0; + distance = vlen(delta); + + if(SAME_TEAM(tmp_entity, self)) + if(max_dist <= 0 || distance <= max_dist) + if(closest_target == world || distance <= smallest_distance ) + { + closest_target = tmp_entity; + smallest_distance = distance; + } + + tmp_entity = tmp_entity.chain; + } + + return closest_target; +} +/** + * find the number of control points and generators in the same team as self + */ - float ons_Count_SelfControlPoints() ++int ons_Count_SelfControlPoints() +{ + entity tmp_entity; + tmp_entity = findchain(classname, "onslaught_controlpoint"); - float n = 0; ++ int n = 0; + while(tmp_entity) { - WriteCoord(MSG_ENTITY, self.goalentity.origin.x); - WriteCoord(MSG_ENTITY, self.goalentity.origin.y); - WriteCoord(MSG_ENTITY, self.goalentity.origin.z); + if(SAME_TEAM(tmp_entity, self)) + if(tmp_entity.iscaptured) + n++; + tmp_entity = tmp_entity.chain; } - if(sendflags & 2) + tmp_entity = findchain(classname, "onslaught_generator"); + while(tmp_entity) { - WriteCoord(MSG_ENTITY, self.enemy.origin.x); - WriteCoord(MSG_ENTITY, self.enemy.origin.y); - WriteCoord(MSG_ENTITY, self.enemy.origin.z); + if(SAME_TEAM(tmp_entity, self)) + n++; + tmp_entity = tmp_entity.chain; } - if(sendflags & 4) + 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 ++ * return false on failure + */ - float ons_Teleport(entity player, entity tele_target, float range, float tele_effects) ++bool ons_Teleport(entity player, entity tele_target, float range, bool tele_effects) +{ + if ( !tele_target ) - return FALSE; ++ return false; + - float i; ++ int i; + vector loc; + float theta; + for(i = 0; i < 16; ++i) + { + theta = random() * 2 * M_PI; + loc_y = sin(theta); + loc_x = cos(theta); + loc_z = 0; + loc *= random() * range; + + loc += tele_target.origin + '0 0 128'; + + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, player); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(tele_target.origin, loc, MOVE_NOMONSTERS, tele_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + if ( tele_effects ) + { + pointparticles(particleeffectnum("teleport"), player.origin, '0 0 0', 1); + sound (player, CH_TRIGGER, "misc/teleport.wav", VOL_BASE, ATTEN_NORM); + } + setorigin(player, loc); + player.angles = '0 1 0' * ( theta * RAD2DEG + 180 ); + makevectors(player.angles); - player.fixangle = TRUE; ++ player.fixangle = true; + player.teleport_antispam = time + autocvar_g_onslaught_teleport_wait; + + if ( tele_effects ) + pointparticles(particleeffectnum("teleport"), player.origin + v_forward * 32, '0 0 0', 1); - return TRUE; ++ return true; + } + } + } + - return FALSE; ++ return false; +} + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(ons_ResetMap) +{ + FOR_EACH_PLAYER(self) { - self.ons_roundlost = FALSE; - WriteByte(MSG_ENTITY, self.clientcolors); // which is goalentity's color + enemy's color * 16 ++ self.ons_roundlost = false; + self.ons_deathloc = '0 0 0'; + PutClientInServer(); } - return FALSE; - return true; ++ return false; } -void onslaught_link_checkupdate() +MUTATOR_HOOKFUNCTION(ons_RemovePlayer) { - // TODO check if the two sides have moved (currently they won't move anyway) - float redpower, bluepower; + self.ons_deathloc = '0 0 0'; - return FALSE; ++ return false; +} - redpower = bluepower = 0; - if(self.goalentity.islinked) +MUTATOR_HOOKFUNCTION(ons_PlayerSpawn) +{ + if(!round_handler_IsRoundStarted()) + { - self.player_blocked = TRUE; - return FALSE; ++ self.player_blocked = true; ++ return false; + } + + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) { - if(self.goalentity.team == NUM_TEAM_1) - redpower = 1; - else if(self.goalentity.team == NUM_TEAM_2) - bluepower = 1; + l.sprite.SendFlags |= 16; } - if(self.enemy.islinked) + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) { - if(self.enemy.team == NUM_TEAM_1) - redpower = 2; - else if(self.enemy.team == NUM_TEAM_2) - bluepower = 2; + l.sprite.SendFlags |= 16; } - float cc; - if(redpower == 1 && bluepower == 2) - cc = (NUM_TEAM_1 - 1) * 0x01 + (NUM_TEAM_2 - 1) * 0x10; - else if(redpower == 2 && bluepower == 1) - cc = (NUM_TEAM_1 - 1) * 0x10 + (NUM_TEAM_2 - 1) * 0x01; - else if(redpower) - cc = (NUM_TEAM_1 - 1) * 0x11; - else if(bluepower) - cc = (NUM_TEAM_2 - 1) * 0x11; - else - cc = 0; + if(ons_stalemate) { Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_OVERTIME_CONTROLPOINT); } - //print(etos(self), " rp=", ftos(redpower), " bp=", ftos(bluepower), " "); - //print("cc=", ftos(cc), "\n"); + if ( autocvar_g_onslaught_spawn_choose ) + if ( self.ons_spawn_by ) - if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,FALSE) ) ++ if ( ons_Teleport(self,self.ons_spawn_by,autocvar_g_onslaught_teleport_radius,false) ) + { + self.ons_spawn_by = world; - return FALSE; ++ return false; + } + + if(autocvar_g_onslaught_spawn_at_controlpoints) + if(random() <= autocvar_g_onslaught_spawn_at_controlpoints_chance) + { + float random_target = autocvar_g_onslaught_spawn_at_controlpoints_random; + entity tmp_entity, closest_target = world; + vector spawn_loc = self.ons_deathloc; + + // new joining player or round reset, don't bother checking - if(spawn_loc == '0 0 0') { return FALSE; } ++ if(spawn_loc == '0 0 0') { return false; } - if(cc != self.clientcolors) + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldcplist; tmp_entity; tmp_entity = tmp_entity.ons_worldcpnext) + { + if(SAME_TEAM(tmp_entity, self)) + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 96'; + loc += ('0 1 0' * random()) * 128; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin) * RAD2DEG; - return FALSE; ++ return false; + } + } + } + } + } + + if(autocvar_g_onslaught_spawn_at_generator) + if(random() <= autocvar_g_onslaught_spawn_at_generator_chance) { - self.clientcolors = cc; - self.SendFlags |= 4; + float random_target = autocvar_g_onslaught_spawn_at_generator_random; + entity tmp_entity, closest_target = world; + vector spawn_loc = self.ons_deathloc; + + // new joining player or round reset, don't bother checking - if(spawn_loc == '0 0 0') { return FALSE; } ++ if(spawn_loc == '0 0 0') { return false; } + + if(random_target) { RandomSelection_Init(); } + + for(tmp_entity = ons_worldgeneratorlist; tmp_entity; tmp_entity = tmp_entity.ons_worldgeneratornext) + { + if(random_target) + RandomSelection_Add(tmp_entity, 0, string_null, 1, 1); + else + { + if(SAME_TEAM(tmp_entity, self)) + if(vlen(tmp_entity.origin - spawn_loc) <= vlen(closest_target.origin - spawn_loc) || closest_target == world) + closest_target = tmp_entity; + } + } + + if(random_target) { closest_target = RandomSelection_chosen_ent; } + + if(closest_target) + { + float i; + vector loc; + for(i = 0; i < 10; ++i) + { + loc = closest_target.origin + '0 0 128'; + loc += ('0 1 0' * random()) * 256; + tracebox(loc, PL_MIN, PL_MAX, loc, MOVE_NORMAL, self); + if(trace_fraction == 1.0 && !trace_startsolid) + { + traceline(closest_target.origin, loc, MOVE_NOMONSTERS, closest_target); // double check to make sure we're not spawning outside the world + if(trace_fraction == 1.0 && !trace_startsolid) + { + setorigin(self, loc); + self.angles = normalize(loc - closest_target.origin) * RAD2DEG; - return FALSE; ++ return false; + } + } + } + } } - return FALSE; - self.nextthink = time; ++ return false; } -void onslaught_link_delayed() +MUTATOR_HOOKFUNCTION(ons_PlayerDies) { - self.goalentity = find(world, targetname, self.target); - self.enemy = find(world, targetname, self.target2); - if (!self.goalentity) - objerror("can not find target\n"); - if (!self.enemy) - objerror("can not find target2\n"); - dprint(etos(self.goalentity), " linked with ", etos(self.enemy), "\n"); - self.SendFlags |= 3; - self.think = onslaught_link_checkupdate; - self.nextthink = time; + frag_target.ons_deathloc = frag_target.origin; + entity l; + for(l = ons_worldgeneratorlist; l; l = l.ons_worldgeneratornext) + { + l.sprite.SendFlags |= 16; + } + for(l = ons_worldcplist; l; l = l.ons_worldcpnext) + { + l.sprite.SendFlags |= 16; + } + + if ( autocvar_g_onslaught_spawn_choose ) + if ( ons_Count_SelfControlPoints() > 1 ) + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); + - return FALSE; ++ return false; } -/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) - Link between control points. +MUTATOR_HOOKFUNCTION(ons_MonsterThink) +{ + entity e = find(world, targetname, self.target); + if (e != world) + self.team = e.team; - return FALSE; - 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. ++ return false; +} -keys: -"target" - first control point. -"target2" - second control point. - */ -void spawnfunc_onslaught_link() +void ons_MonsterSpawn_Delayed() { - if (!g_onslaught) + entity e, own = self.owner; + + if(!own) { remove(self); return; } + + if(own.targetname) { - remove(self); - return; + e = find(world, target, own.targetname); + if(e != world) + { + own.team = e.team; + + activator = e; + own.use(); + } } - if (self.target == "" || self.target2 == "") - objerror("target and target2 must be set\n"); - InitializeEntity(self, onslaught_link_delayed, INITPRIO_FINDTARGET); - Net_LinkEntity(self, false, 0, onslaught_link_send); + + remove(self); } -MUTATOR_HOOKFUNCTION(ons_BuildMutatorsString) +MUTATOR_HOOKFUNCTION(ons_MonsterSpawn) { - ret_string = strcat(ret_string, ":ONS"); - return 0; + entity e = spawn(); + e.owner = self; + InitializeEntity(e, ons_MonsterSpawn_Delayed, INITPRIO_FINDTARGET); + - return FALSE; ++ return false; } -MUTATOR_HOOKFUNCTION(ons_BuildMutatorsPrettyString) +void ons_TurretSpawn_Delayed() { - ret_string = strcat(ret_string, ", Onslaught"); - return 0; + entity e, own = self.owner; + + if(!own) { remove(self); return; } + + if(own.targetname) + { + e = find(world, target, own.targetname); + if(e != world) + { + own.team = e.team; + own.active = ACTIVE_NOT; + + activator = e; + own.use(); + } + } + + remove(self); } -MUTATOR_HOOKFUNCTION(ons_Spawn_Score) +MUTATOR_HOOKFUNCTION(ons_TurretSpawn) { + entity e = spawn(); + e.owner = self; + InitializeEntity(e, ons_TurretSpawn_Delayed, INITPRIO_FINDTARGET); - return FALSE; - /* - float _neer_home = (random() > 0.5 ? true : false); ++ return false; +} - RandomSelection_Init(); +MUTATOR_HOOKFUNCTION(ons_BotRoles) +{ + havocbot_ons_reset_role(self); - return TRUE; ++ return true; +} - if(self.team == NUM_TEAM_1) - RandomSelection_Add(ons_red_generator, 0, string_null, 1, 1); +MUTATOR_HOOKFUNCTION(ons_GetTeamCount) +{ + // onslaught is special + entity tmp_entity; + for(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; - if(self.team == NUM_TEAM_2) - RandomSelection_Add(ons_blue_generator, 0, string_null, 1, 1); ++ return true; +} - entity _cp = findchain(classname, "onslaught_controlpoint"): - while _cp; - { - if(_cp.team == self.team) - RandomSelection_Add(_cp, 0, string_null, 1, 1); +MUTATOR_HOOKFUNCTION(ons_SpectateCopy) +{ + self.ons_roundlost = other.ons_roundlost; // make spectators see it too - return FALSE; ++ return false; +} - _cp = _cp.chain; +MUTATOR_HOOKFUNCTION(ons_SV_ParseClientCommand) +{ + if(MUTATOR_RETURNVALUE) // command was already handled? - return FALSE; ++ return false; + + if ( cmd_name == "ons_spawn" ) + { + vector pos = self.origin; + if(cmd_argc > 1) + pos_x = stof(argv(1)); + if(cmd_argc > 2) + pos_y = stof(argv(2)); + if(cmd_argc > 3) + pos_z = stof(argv(3)); + + if ( IS_PLAYER(self) ) + { + if ( !self.frozen ) + { + entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); + + if ( !source_point && self.health > 0 ) + { + sprint(self, "\nYou need to be next to a control point\n"); + return 1; + } + + + entity closest_target = ons_Nearest_ControlPoint_2D(pos, autocvar_g_onslaught_click_radius); + + if ( closest_target == world ) + { + sprint(self, "\nNo control point found\n"); + return 1; + } + + if ( self.health <= 0 ) + { + self.ons_spawn_by = closest_target; + self.respawn_flags = self.respawn_flags | RESPAWN_FORCE; + } + else + { + if ( source_point == closest_target ) + { + sprint(self, "\nTeleporting to the same point\n"); + return 1; + } + - if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,TRUE) ) ++ if ( !ons_Teleport(self,closest_target,autocvar_g_onslaught_teleport_radius,true) ) + sprint(self, "\nUnable to teleport there\n"); + } + + return 1; + } + + sprint(self, "\nNo teleportation for you\n"); + } + + return 1; } + return 0; +} - if(RandomSelection_chosen_ent) +MUTATOR_HOOKFUNCTION(ons_PlayerUseKey) +{ - if(MUTATOR_RETURNVALUE || gameover) { return FALSE; } ++ if(MUTATOR_RETURNVALUE || gameover) { return false; } + + if((time > self.teleport_antispam) && (self.deadflag == DEAD_NO) && !self.vehicle) { - self.tur_head = RandomSelection_chosen_ent; - spawn_score_x += SPAWN_PRIO_NEAR_TEAMMATE_FOUND; + entity source_point = ons_Nearest_ControlPoint(self.origin, autocvar_g_onslaught_teleport_radius); + if ( source_point ) + { + stuffcmd(self, "qc_cmd_cl hud clickradar\n"); - return TRUE; ++ return true; + } } - else if(self.team == spawn_spot.team) - spawn_score_x += SPAWN_PRIO_NEAR_TEAMMATE_SAMETEAM; // prefer same team, if we can't find a spawn near teammate + - return FALSE; ++ return false; +} - */ +// ========== +// Spawnfuncs +// ========== - return 0; -} +/*QUAKED spawnfunc_onslaught_link (0 .5 .8) (-16 -16 -16) (16 16 16) + Link between control points. -MUTATOR_HOOKFUNCTION(ons_PlayerSpawn) + 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. + */ +void spawnfunc_onslaught_link() { - if(!autocvar_g_onslaught_spawn_at_controlpoints) - return 0; + if(!g_onslaught) { remove(self); return; } + + if (self.target == "" || self.target2 == "") + objerror("target and target2 must be set\n"); - if(random() < 0.5) // 50/50 chane to use default spawnsystem. - return 0; + self.ons_worldlinknext = ons_worldlinklist; // link into ons_worldlinklist + ons_worldlinklist = self; - float _close_to_home = ((random() > 0.5) ? true : false); - entity _best = world, _trg_gen = world; - float _score, _best_score = MAX_SHOT_DISTANCE; + InitializeEntity(self, ons_DelayedLinkSetup, INITPRIO_FINDTARGET); - Net_LinkEntity(self, FALSE, 0, ons_Link_Send); ++ Net_LinkEntity(self, false, 0, ons_Link_Send); +} - RandomSelection_Init(); +/*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 - if(self.team == NUM_TEAM_1) - { - if(!_close_to_home) - _trg_gen = ons_blue_generator; - else - _trg_gen = ons_red_generator; - } + This should link to an spawnfunc_onslaught_controlpoint entity or spawnfunc_onslaught_generator entity. - if(self.team == NUM_TEAM_2) - { - if(_close_to_home) - _trg_gen = ons_blue_generator; - else - _trg_gen = ons_red_generator; - } +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) + */ - entity _cp = findchain(classname, "onslaught_controlpoint"); - while(_cp) - { - if(_cp.team == self.team) - { - _score = vlen(_trg_gen.origin - _cp.origin); - if(_score < _best_score) - { - _best = _cp; - _best_score = _score; - } - } - _cp = _cp.chain; - } +void spawnfunc_onslaught_controlpoint() +{ + if(!g_onslaught) { remove(self); return; } + + ons_ControlPoint_Setup(self); +} - vector _loc; - float i; - if(_best) - { - for(i = 0; i < 10; ++i) - { - _loc = _best.origin + '0 0 96'; - _loc += ('0 1 0' * random()) * 128; - tracebox(_loc, PL_MIN, PL_MAX, _loc, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(self, _loc); - self.angles = normalize(_loc - _best.origin) * RAD2DEG; - return 0; - } - } - } - else - { - if(!autocvar_g_onslaught_spawn_at_generator) - return 0; +/*QUAKED spawnfunc_onslaught_generator (0 .5 .8) (-32 -32 -24) (32 32 64) + Base generator. - _trg_gen = ((self.team == NUM_TEAM_1) ? ons_red_generator : ons_blue_generator); + spawnfunc_onslaught_link entities can target this. - for(i = 0; i < 10; ++i) - { - _loc = _trg_gen.origin + '0 0 96'; - _loc += ('0 1 0' * random()) * 128; - tracebox(_loc, PL_MIN, PL_MAX, _loc, MOVE_NORMAL, self); - if(trace_fraction == 1.0 && !trace_startsolid) - { - setorigin(self, _loc); - self.angles = normalize(_loc - _trg_gen.origin) * RAD2DEG; - return 0; - } - } - } +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. + */ +void spawnfunc_onslaught_generator() +{ + if(!g_onslaught) { remove(self); return; } + if(!self.team) { objerror("team must be set"); } - return 0; + ons_GeneratorSetup(self); } -MUTATOR_HOOKFUNCTION(ons_MonsterThink) + +// scoreboard setup +void ons_ScoreRules() { - entity e = find(world, targetname, self.target); - if (e != world) - self.team = e.team; + CheckAllowedTeams(world); - ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, TRUE); ++ ScoreRules_basics(((c4>=0) ? 4 : (c3>=0) ? 3 : 2), SFL_SORT_PRIO_PRIMARY, 0, true); + ScoreInfo_SetLabel_TeamScore (ST_ONS_CAPS, "destroyed", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_CAPS, "caps", SFL_SORT_PRIO_SECONDARY); + ScoreInfo_SetLabel_PlayerScore(SP_ONS_TAKES, "takes", 0); + ScoreRules_basics_end(); +} - return false; +void ons_DelayedInit() // Do this check with a delay so we can wait for teams to be set up +{ + ons_ScoreRules(); + + round_handler_Spawn(Onslaught_CheckPlayers, Onslaught_CheckWinner, Onslaught_RoundStart); + round_handler_Init(5, autocvar_g_onslaught_warmup, autocvar_g_onslaught_round_timelimit); } -MUTATOR_HOOKFUNCTION(ons_MonsterSpawn) +void ons_Initialize() { - entity e, ee = world; + precache_sound("ctf/red_capture.wav"); + precache_sound("ctf/blue_capture.wav"); + precache_sound("ctf/yellow_capture.wav"); + precache_sound("ctf/pink_capture.wav"); - if(self.targetname) - { - e = find(world,target,self.targetname); - if(e != world) - { - self.team = e.team; - ee = e; - } - } + ons_captureshield_force = autocvar_g_onslaught_shield_force; - if(ee) - { - activator = ee; - self.use(); - } + addstat(STAT_ROUNDLOST, AS_INT, ons_roundlost); - return false; + InitializeEntity(world, ons_DelayedInit, INITPRIO_GAMETYPE); } MUTATOR_DEFINITION(gamemode_onslaught) @@@ -2191,5 -1710,5 +2196,5 @@@ return -1; } - return FALSE; - return 0; ++ return false; } diff --cc qcsrc/server/mutators/gamemode_onslaught.qh index cb4aeb4cf4,0000000000..7cebb172da mode 100644,000000..100644 --- a/qcsrc/server/mutators/gamemode_onslaught.qh +++ b/qcsrc/server/mutators/gamemode_onslaught.qh @@@ -1,94 -1,0 +1,93 @@@ +// these are needed since mutators are compiled last + +#ifdef SVQC + +.entity ons_toucher; // player who touched the control point + +// control point / generator constants - #define ONS_CP_THINKRATE 0.2 - #define GEN_THINKRATE 1 ++const float ONS_CP_THINKRATE = 0.2; ++const float GEN_THINKRATE = 1; +#define CPGEN_SPAWN_OFFSET ('0 0 1' * (PL_MAX_z - 13)) - #define CPGEN_WAYPOINT_OFFSET ('0 0 128') - #define CPICON_OFFSET ('0 0 96') ++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; - .float iscaptured; - .float islinked; - .float isshielded; ++.int iscaptured; ++.int islinked; ++.int isshielded; +.float lasthealth; - .float lastteam; - .float lastshielded; - .float lastcaptured; ++.int lastteam; ++.int lastshielded; ++.int lastcaptured; + - .float waslinked; ++.bool waslinked; + - float ons_stalemate; ++bool ons_stalemate; + +.float teleport_antispam; + - .float ons_roundlost; ++.bool ons_roundlost; + +// waypoint sprites +.entity bot_basewaypoint; // generator waypointsprite - float wpforenemy_announced; + - .float isgenneighbor[17]; - .float iscpneighbor[17]; ++.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); - float ons_ControlPoint_Attackable(entity cp, float teamnumber); ++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 - #define HAVOCBOT_ONS_ROLE_NONE 0 - #define HAVOCBOT_ONS_ROLE_DEFENSE 2 - #define HAVOCBOT_ONS_ROLE_ASSISTANT 4 - #define HAVOCBOT_ONS_ROLE_OFFENSE 8 ++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; + - .float havocbot_role_flags; ++.int havocbot_role_flags; +.float havocbot_attack_time; + +void havocbot_role_ons_defense(); +void havocbot_role_ons_offense(); +void havocbot_role_ons_assistant(); + +void havocbot_ons_reset_role(entity bot); +void havocbot_goalrating_items(float ratingscale, vector org, float sradius); +void havocbot_goalrating_enemyplayers(float ratingscale, vector org, float sradius); + +// score rule declarations - #define ST_ONS_CAPS 1 - #define SP_ONS_CAPS 4 - #define SP_ONS_TAKES 6 ++const int ST_ONS_CAPS = 1; ++const int SP_ONS_CAPS = 4; ++const int SP_ONS_TAKES = 6; + +#endif diff --cc qcsrc/server/progs.src index d2df9dacc8,85e75cda9e..a496de32cb --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@@ -2,155 -2,76 +2,78 @@@ ../common/util-pre.qh sys-pre.qh - ../dpdefs/progsdefs.qc - ../dpdefs/dpextensions.qc + ../dpdefs/progsdefs.qh + ../dpdefs/dpextensions.qh sys-post.qh - ../warpzonelib/anglestransform.qh - ../warpzonelib/mathlib.qh - ../warpzonelib/common.qh - ../warpzonelib/util_server.qh - ../warpzonelib/server.qh - ../common/constants.qh - ../common/stats.qh - ../common/teams.qh - ../common/util.qh - ../common/nades.qh - ../common/buffs.qh - ../common/test.qh - ../common/counting.qh - ../common/urllib.qh - ../common/command/markup.qh - ../common/command/rpn.qh - ../common/command/generic.qh - ../common/command/shared_defs.qh - ../common/net_notice.qh - ../common/animdecide.qh - ../common/monsters/monsters.qh - ../common/monsters/sv_monsters.qh - ../common/monsters/spawn.qh - - ../common/weapons/config.qh - ../common/weapons/weapons.qh // TODO - weapons/accuracy.qh - weapons/common.qh - weapons/csqcprojectile.qh // TODO - weapons/hitplot.qh - weapons/selection.qh - weapons/spawning.qh - weapons/throwing.qh - weapons/tracing.qh - weapons/weaponstats.qh - weapons/weaponsystem.qh - - t_items.qh - - autocvars.qh - constants.qh - defs.qh // Should rename this, it has fields and globals - - ../common/notifications.qh // must be after autocvars - ../common/deathtypes.qh // must be after notifications - - mutators/mutators_include.qh - - //// tZork Turrets //// - tturrets/include/turrets_early.qh - vehicles/vehicles_def.qh - - generator.qh - controlpoint.qh - - campaign.qh - ../common/campaign_common.qh - ../common/mapinfo.qh - - command/common.qh - command/banning.qh - command/radarmap.qh - command/vote.qh - command/getreplies.qh - command/cmd.qh - command/sv_cmd.qh - - - ../common/csqcmodel_settings.qh - ../csqcmodellib/common.qh - ../csqcmodellib/sv_model.qh + anticheat.qc + antilag.qc + // assault.qc + campaign.qc + cheats.qc + cl_client.qc + cl_impulse.qc + cl_physics.qc + cl_player.qc ++controlpoint.qc csqceffects.qc - - anticheat.qh - cheats.qh - ../common/playerstats.qh - - portals.qh - - g_hook.qh // TODO - - scores.qh - - spawnpoints.qh - - mapvoting.qh - - ipban.qh - - race.qh - - antilag.qh - - playerdemo.qh - - round_handler.qh - - // singleplayer stuff - item_key.qh - secret.qh - - scores_rules.qc - - miscfunctions.qc - - mutators/mutators.qc - - waypointsprites.qc - - bot/bot.qc - + // ctf.qc + // domination.qc + ent_cs.qc + func_breakable.qc + g_casings.qc + g_damage.qc + g_hook.qc + g_models.qc g_subs.qc - g_tetris.qc - - //runematch.qc - + g_triggers.qc g_violence.qc - g_damage.qc - - teamplay.qc - - cl_physics.qc - - // tZork's libs - movelib.qc - steerlib.qc - pathlib/pathlib.qh - g_world.qc - g_casings.qc - ++generator.qc + ipban.qc + item_key.qc mapvoting.qc - + miscfunctions.qc + // mode_onslaught.qc + movelib.qc + // nexball.qc + playerdemo.qc + portals.qc + race.qc + round_handler.qc + // runematch.qc + scores.qc + scores_rules.qc + secret.qc + spawnpoints.qc + steerlib.qc + sv_main.qc + target_music.qc + target_spawn.qc + teamplay.qc + t_halflife.qc + t_items.qc t_jumppads.qc + t_plats.qc + t_quake3.qc + t_quake.qc + t_swamp.qc t_teleporters.qc + waypointsprites.qc - sv_main.qc + bot/bot.qc - g_triggers.qc - g_models.qc + command/banning.qc + command/cmd.qc + command/common.qc + command/getreplies.qc + command/radarmap.qc + command/sv_cmd.qc + command/vote.qc - // singleplayer stuff - item_key.qc - secret.qc + mutators/mutators_include.qc + mutators/mutators.qc weapons/accuracy.qc weapons/common.qc