From: Mario Date: Mon, 6 Jan 2014 23:53:04 +0000 (+1100) Subject: Merge branch 'master' into samual/weapons X-Git-Tag: xonotic-v0.8.0~152^2~119 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=b38e2bae0a50554f26bf5af1fbd0e70b97cc0c71;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into samual/weapons --- b38e2bae0a50554f26bf5af1fbd0e70b97cc0c71 diff --cc qcsrc/client/weapons/projectile.qc index aa825a533,000000000..c98ab7bf4 mode 100644,000000..100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@@ -1,529 -1,0 +1,538 @@@ +.vector iorigin1, iorigin2; +.float spawntime; +.vector trail_oldorigin; +.float trail_oldtime; +.float fade_time, fade_rate; + +void SUB_Stop() +{ + self.move_velocity = self.move_avelocity = '0 0 0'; + self.move_movetype = MOVETYPE_NONE; +} + +.float alphamod; +.float count; // set if clientside projectile +.float cnt; // sound index +.float gravity; +.float snd_looping; +.float silent; + +void Projectile_ResetTrail(vector to) +{ + self.trail_oldorigin = to; + self.trail_oldtime = time; +} + +void Projectile_DrawTrail(vector to) +{ + vector from; + float t0; + + from = self.trail_oldorigin; + t0 = self.trail_oldtime; + self.trail_oldorigin = to; + self.trail_oldtime = time; + + // force the effect even for stationary firemine + if(self.cnt == PROJECTILE_FIREMINE) + if(from == to) + from_z += 1; + + if (self.traileffect) + { + particles_alphamin = particles_alphamax = particles_fade = sqrt(self.alpha); + boxparticles(self.traileffect, self, from, to, self.velocity, self.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL); + } +} + +void Projectile_Draw() +{ + vector rot; + vector trailorigin; + float f; + float drawn; + float t; + float a; + + f = self.move_flags; + + if(self.count & 0x80) + { + //self.move_flags &= ~FL_ONGROUND; + if(self.move_movetype == MOVETYPE_NONE || self.move_movetype == MOVETYPE_FLY) + Movetype_Physics_NoMatchServer(); + // the trivial movetypes do not have to match the + // server's ticrate as they are ticrate independent + // NOTE: this assumption is only true if MOVETYPE_FLY + // projectiles detonate on impact. If they continue + // moving, we might still be ticrate dependent. + else + Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy); + if(!(self.move_flags & FL_ONGROUND)) + if(self.velocity != '0 0 0') + self.move_angles = self.angles = vectoangles(self.velocity); + } + else + { + InterpolateOrigin_Do(); + } + + if(self.count & 0x80) + { + drawn = (time >= self.spawntime - 0.02); + t = max(time, self.spawntime); + } + else + { + drawn = (self.iflags & IFLAG_VALID); + t = time; + } + + if(!(f & FL_ONGROUND)) + { + rot = '0 0 0'; + switch(self.cnt) + { + /* + case PROJECTILE_GRENADE: + rot = '-2000 0 0'; // forward + break; + */ + case PROJECTILE_GRENADE_BOUNCING: + rot = '0 -1000 0'; // sideways + break; + case PROJECTILE_NADE_RED_BURN: + case PROJECTILE_NADE_RED: + case PROJECTILE_NADE_BLUE_BURN: + case PROJECTILE_NADE_BLUE: + case PROJECTILE_NADE_YELLOW_BURN: + case PROJECTILE_NADE_YELLOW: + case PROJECTILE_NADE_PINK_BURN: + case PROJECTILE_NADE_PINK: + case PROJECTILE_NADE_BURN: + case PROJECTILE_NADE: + rot = self.avelocity; + break; + case PROJECTILE_HOOKBOMB: + rot = '1000 0 0'; // forward + break; + default: + break; + } + self.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(self.angles), rot * (t - self.spawntime))); + } + + vector ang; + ang = self.angles; + ang_x = -ang_x; + makevectors(ang); + + a = 1 - (time - self.fade_time) * self.fade_rate; + self.alpha = bound(0, self.alphamod * a, 1); + if(self.alpha <= 0) + drawn = 0; + self.renderflags = 0; + + trailorigin = self.origin; + switch(self.cnt) + { + case PROJECTILE_NADE_RED_BURN: + case PROJECTILE_NADE_RED: + case PROJECTILE_NADE_BLUE_BURN: + case PROJECTILE_NADE_BLUE: + case PROJECTILE_NADE_YELLOW_BURN: + case PROJECTILE_NADE_YELLOW: + case PROJECTILE_NADE_PINK_BURN: + case PROJECTILE_NADE_PINK: + case PROJECTILE_NADE_BURN: + case PROJECTILE_NADE: + trailorigin += v_up * 4; + break; + case PROJECTILE_GRENADE: + case PROJECTILE_GRENADE_BOUNCING: + trailorigin += v_right * 1 + v_forward * -10; + break; + default: + break; + } + if(drawn) + Projectile_DrawTrail(trailorigin); + else + Projectile_ResetTrail(trailorigin); + + self.drawmask = 0; + + if(!drawn) + return; + + switch(self.cnt) + { + // Possibly add dlights here. + default: + break; + } + + self.drawmask = MASK_NORMAL; +} + +void loopsound(entity e, float ch, string samp, float vol, float attn) +{ + if(self.silent) + return; + + sound(e, ch, samp, vol, attn); + e.snd_looping = ch; +} + +void Ent_RemoveProjectile() +{ + if(self.count & 0x80) + { + tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.05, MOVE_NORMAL, self); + Projectile_DrawTrail(trace_endpos); + } +} + +void Ent_Projectile() +{ + float f; + + // projectile properties: + // kind (interpolated, or clientside) + // + // modelindex + // origin + // scale + // if clientside: + // velocity + // gravity + // soundindex (hardcoded list) + // effects + // + // projectiles don't send angles, because they always follow the velocity + + f = ReadByte(); + self.count = (f & 0x80); + self.iflags = (self.iflags & IFLAG_INTERNALMASK) | IFLAG_AUTOANGLES | IFLAG_ANGLES | IFLAG_ORIGIN; + self.solid = SOLID_TRIGGER; + //self.effects = EF_NOMODELFLAGS; + + // this should make collisions with bmodels more exact, but it leads to + // projectiles no longer being able to lie on a bmodel + self.move_nomonsters = MOVE_WORLDONLY; + if(f & 0x40) + self.move_flags |= FL_ONGROUND; + else + self.move_flags &= ~FL_ONGROUND; + + if(!self.move_time) + { + // for some unknown reason, we don't need to care for + // sv_gameplayfix_delayprojectiles here. + self.move_time = time; + self.spawntime = time; + } + else + self.move_time = max(self.move_time, time); + + if(!(self.count & 0x80)) + InterpolateOrigin_Undo(); + + if(f & 1) + { + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); + setorigin(self, self.origin); + if(self.count & 0x80) + { + self.velocity_x = ReadCoord(); + self.velocity_y = ReadCoord(); + self.velocity_z = ReadCoord(); + if(f & 0x10) + self.gravity = ReadCoord(); + else + self.gravity = 0; // none + self.move_origin = self.origin; + self.move_velocity = self.velocity; + } + + if(time == self.spawntime || (self.count & 0x80) || (f & 0x08)) + { + self.trail_oldorigin = self.origin; + if(!(self.count & 0x80)) + InterpolateOrigin_Reset(); + } + + if(f & 0x20) + { + self.fade_time = time + ReadByte() * ticrate; + self.fade_rate = 1 / (ReadByte() * ticrate); + } + else + { + self.fade_time = 0; + self.fade_rate = 0; + } + } + + if(f & 2) + { + self.cnt = ReadByte(); + + self.silent = (self.cnt & 0x80); + self.cnt = (self.cnt & 0x7F); + + self.scale = 1; + self.traileffect = 0; + switch(self.cnt) + { + case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_ROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 2; break; + case PROJECTILE_CRYLINK: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break; + case PROJECTILE_CRYLINK_BOUNCING: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break; + case PROJECTILE_ELECTRO_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_GRENADE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break; + case PROJECTILE_GRENADE_BOUNCING: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break; + case PROJECTILE_MINE: setmodel(self, "models/mine.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break; + case PROJECTILE_BLASTER: setmodel(self, "models/laser.mdl");self.traileffect = particleeffectnum(""); break; + case PROJECTILE_HLAC: setmodel(self, "models/hlac_bullet.md3");self.traileffect = particleeffectnum(""); break; + case PROJECTILE_PORTO_RED: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break; + case PROJECTILE_PORTO_BLUE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break; + case PROJECTILE_HOOKBOMB: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_KNIGHTSPIKE"); break; + case PROJECTILE_HAGAR: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break; + case PROJECTILE_HAGAR_BOUNCING: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break; + case PROJECTILE_FIREBALL: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("fireball"); break; // particle effect is good enough + case PROJECTILE_FIREMINE: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("firemine"); break; // particle effect is good enough + case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break; + case PROJECTILE_FLAC: setmodel(self, "models/hagarmissile.mdl"); self.scale = 0.4; self.traileffect = particleeffectnum("TR_SEEKER"); break; + case PROJECTILE_SEEKER: setmodel(self, "models/tagrocket.md3"); self.traileffect = particleeffectnum("TR_SEEKER"); break; + ++ case PROJECTILE_MAGE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_VORESPIKE"); break; ++ case PROJECTILE_SHAMBLER_LIGHTNING: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; ++ + case PROJECTILE_RAPTORBOMB: setmodel(self, "models/vehicles/clusterbomb.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break; + case PROJECTILE_RAPTORBOMBLET: setmodel(self, "models/vehicles/bomblet.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break; + case PROJECTILE_RAPTORCANNON: setmodel(self, "models/plasmatrail.mdl"); self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break; + + case PROJECTILE_SPIDERROCKET: setmodel(self, "models/vehicles/rocket02.md3"); self.traileffect = particleeffectnum("spiderbot_rocket_thrust"); break; + case PROJECTILE_WAKIROCKET: setmodel(self, "models/vehicles/rocket01.md3"); self.traileffect = particleeffectnum("wakizashi_rocket_thrust"); break; + case PROJECTILE_WAKICANNON: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum(""); break; + + case PROJECTILE_BUMBLE_GUN: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break; + + case PROJECTILE_NADE_RED: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red"); break; + case PROJECTILE_NADE_RED_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red_burn"); break; + case PROJECTILE_NADE_BLUE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue"); break; + case PROJECTILE_NADE_BLUE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue_burn"); break; + case PROJECTILE_NADE_YELLOW: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow"); break; + case PROJECTILE_NADE_YELLOW_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow_burn"); break; + case PROJECTILE_NADE_PINK: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink"); break; + case PROJECTILE_NADE_PINK_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink_burn"); break; + case PROJECTILE_NADE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade"); break; + case PROJECTILE_NADE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_burn"); break; + + default: + error("Received invalid CSQC projectile, can't work with this!"); + break; + } + + self.mins = '0 0 0'; + self.maxs = '0 0 0'; + self.colormod = '0 0 0'; + self.move_touch = SUB_Stop; + self.move_movetype = MOVETYPE_TOSS; + self.alphamod = 1; + + switch(self.cnt) + { + case PROJECTILE_ELECTRO: + // only new engines support sound moving with object + loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '0 0 -4'; + self.maxs = '0 0 -4'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.move_bounce_factor = g_balance_electro_secondary_bouncefactor; + self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop; + break; + case PROJECTILE_ROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/rocket_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; + case PROJECTILE_GRENADE: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; + case PROJECTILE_NADE_RED_BURN: + case PROJECTILE_NADE_RED: + case PROJECTILE_NADE_BLUE_BURN: + case PROJECTILE_NADE_BLUE: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.scale = 1.5; + self.avelocity = randomvec() * 720; + break; + case PROJECTILE_GRENADE_BOUNCING: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.move_bounce_factor = g_balance_grenadelauncher_bouncefactor; + self.move_bounce_stopspeed = g_balance_grenadelauncher_bouncestop; + break; + case PROJECTILE_NADE_RED_BURN: + case PROJECTILE_NADE_RED: + case PROJECTILE_NADE_BLUE_BURN: + case PROJECTILE_NADE_BLUE: + case PROJECTILE_NADE_YELLOW_BURN: + case PROJECTILE_NADE_YELLOW: + case PROJECTILE_NADE_PINK_BURN: + case PROJECTILE_NADE_PINK: + case PROJECTILE_NADE_BURN: + case PROJECTILE_NADE: + self.mins = '-16 -16 -16'; + self.maxs = '16 16 16'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.scale = 1.5; + self.avelocity = randomvec() * 720; + break; ++ case PROJECTILE_SHAMBLER_LIGHTNING: ++ self.mins = '-8 -8 -8'; ++ self.maxs = '8 8 8'; ++ self.scale = 2.5; ++ self.avelocity = randomvec() * 720; ++ break; + case PROJECTILE_MINE: + self.mins = '-4 -4 -4'; + self.maxs = '4 4 4'; + break; + case PROJECTILE_PORTO_RED: + self.colormod = '2 1 1'; + self.alphamod = 0.5; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_PORTO_BLUE: + self.colormod = '1 1 2'; + self.alphamod = 0.5; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_HAGAR_BOUNCING: + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_CRYLINK_BOUNCING: + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + break; + case PROJECTILE_FIREBALL: + loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly2.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-16 -16 -16'; + self.maxs = '16 16 16'; + break; + case PROJECTILE_FIREMINE: + loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly.wav", VOL_BASE, ATTEN_NORM); + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.mins = '-4 -4 -4'; + self.maxs = '4 4 4'; + break; + case PROJECTILE_TAG: + self.mins = '-2 -2 -2'; + self.maxs = '2 2 2'; + break; + case PROJECTILE_FLAC: + self.mins = '-2 -2 -2'; + self.maxs = '2 2 2'; + break; + case PROJECTILE_SEEKER: + loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '-4 -4 -4'; + self.maxs = '4 4 4'; + break; + case PROJECTILE_RAPTORBOMB: + self.mins = '-3 -3 -3'; + self.maxs = '3 3 3'; + break; + case PROJECTILE_RAPTORBOMBLET: + break; + case PROJECTILE_RAPTORCANNON: + break; + case PROJECTILE_SPIDERROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM); + break; + case PROJECTILE_WAKIROCKET: + loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM); + break; + /* + case PROJECTILE_WAKICANNON: + break; + case PROJECTILE_BUMBLE_GUN: + // only new engines support sound moving with object + loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM); + self.mins = '0 0 -4'; + self.maxs = '0 0 -4'; + self.move_movetype = MOVETYPE_BOUNCE; + self.move_touch = func_null; + self.move_bounce_factor = g_balance_electro_secondary_bouncefactor; + self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop; + break; + */ + default: + break; + } + setsize(self, self.mins, self.maxs); + } + + if(self.gravity) + { + if(self.move_movetype == MOVETYPE_FLY) + self.move_movetype = MOVETYPE_TOSS; + if(self.move_movetype == MOVETYPE_BOUNCEMISSILE) + self.move_movetype = MOVETYPE_BOUNCE; + } + else + { + if(self.move_movetype == MOVETYPE_TOSS) + self.move_movetype = MOVETYPE_FLY; + if(self.move_movetype == MOVETYPE_BOUNCE) + self.move_movetype = MOVETYPE_BOUNCEMISSILE; + } + + if(!(self.count & 0x80)) + InterpolateOrigin_Note(); + + self.draw = Projectile_Draw; + self.entremove = Ent_RemoveProjectile; +} + +void Projectile_Precache() +{ + precache_model("models/ebomb.mdl"); + precache_model("models/elaser.mdl"); + precache_model("models/grenademodel.md3"); + precache_model("models/mine.md3"); + precache_model("models/hagarmissile.mdl"); + precache_model("models/hlac_bullet.md3"); + precache_model("models/laser.mdl"); + precache_model("models/plasmatrail.mdl"); + precache_model("models/rocket.md3"); + precache_model("models/tagrocket.md3"); + precache_model("models/tracer.mdl"); + + precache_model("models/weapons/v_ok_grenade.md3"); + + precache_sound("weapons/electro_fly.wav"); + precache_sound("weapons/rocket_fly.wav"); + precache_sound("weapons/fireball_fly.wav"); + precache_sound("weapons/fireball_fly2.wav"); + precache_sound("weapons/tag_rocket_fly.wav"); + +} diff --cc qcsrc/common/monsters/monster/mage.qc index 000000000,2c8ebd547..77ff4026a mode 000000,100644..100644 --- a/qcsrc/common/monsters/monster/mage.qc +++ b/qcsrc/common/monsters/monster/mage.qc @@@ -1,0 -1,427 +1,427 @@@ + #ifdef REGISTER_MONSTER + REGISTER_MONSTER( + /* MON_##id */ MAGE, + /* function */ m_mage, + /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, + /* mins,maxs */ '-36 -36 -24', '36 36 50', + /* model */ "mage.dpm", + /* netname */ "mage", + /* fullname */ _("Mage") + ); + + #else + #ifdef SVQC + float autocvar_g_monster_mage_health; + float autocvar_g_monster_mage_attack_spike_damage; + float autocvar_g_monster_mage_attack_spike_radius; + float autocvar_g_monster_mage_attack_spike_delay; + float autocvar_g_monster_mage_attack_spike_accel; + float autocvar_g_monster_mage_attack_spike_decel; + float autocvar_g_monster_mage_attack_spike_turnrate; + float autocvar_g_monster_mage_attack_spike_speed_max; + float autocvar_g_monster_mage_attack_spike_smart; + float autocvar_g_monster_mage_attack_spike_smart_trace_min; + float autocvar_g_monster_mage_attack_spike_smart_trace_max; + float autocvar_g_monster_mage_attack_spike_smart_mindist; + float autocvar_g_monster_mage_attack_push_damage; + float autocvar_g_monster_mage_attack_push_radius; + float autocvar_g_monster_mage_attack_push_delay; + float autocvar_g_monster_mage_attack_push_force; + float autocvar_g_monster_mage_heal_self; + float autocvar_g_monster_mage_heal_allies; + float autocvar_g_monster_mage_heal_minhealth; + float autocvar_g_monster_mage_heal_range; + float autocvar_g_monster_mage_heal_delay; + float autocvar_g_monster_mage_shield_time; + float autocvar_g_monster_mage_shield_delay; + float autocvar_g_monster_mage_shield_blockpercent; + float autocvar_g_monster_mage_speed_stop; + float autocvar_g_monster_mage_speed_run; + float autocvar_g_monster_mage_speed_walk; + + const float mage_anim_idle = 0; + const float mage_anim_walk = 1; + const float mage_anim_attack = 2; + const float mage_anim_pain = 3; + const float mage_anim_death = 4; + const float mage_anim_run = 5; + + void() mage_heal; + void() mage_shield; + + .entity mage_spike; + .float shield_ltime; + + float friend_needshelp(entity e) + { + if(e == world) + return FALSE; + if(e.health <= 0) + return FALSE; + if(DIFF_TEAM(e, self) && e != self.monster_owner) + return FALSE; + if(e.freezetag_frozen) + return FALSE; + if(!IS_PLAYER(e)) + return ((e.flags & FL_MONSTER) && e.health < e.max_health); + if(e.items & IT_INVINCIBLE) + return FALSE; + + switch(self.skin) + { + case 0: return (e.health < autocvar_g_balance_health_regenstable); + case 1: return ((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max)); + case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable); + case 3: return (e.health > 0); + } + + return FALSE; + } + + void mage_spike_explode() + { + self.event_damage = func_null; + + sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM); + + self.realowner.mage_spike = world; + + pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1); - RadiusDamage (self, self.realowner, (autocvar_g_monster_mage_attack_spike_damage), (autocvar_g_monster_mage_attack_spike_damage) * 0.5, (autocvar_g_monster_mage_attack_spike_radius), world, 0, DEATH_MONSTER_MAGE, other); ++ RadiusDamage (self, self.realowner, (autocvar_g_monster_mage_attack_spike_damage), (autocvar_g_monster_mage_attack_spike_damage) * 0.5, (autocvar_g_monster_mage_attack_spike_radius), world, world, 0, DEATH_MONSTER_MAGE, other); + + remove (self); + } + + void mage_spike_touch() + { + PROJECTILE_TOUCH; + + mage_spike_explode(); + } + + // copied from W_Seeker_Think + void mage_spike_think() + { + entity e; + vector desireddir, olddir, newdir, eorg; + float turnrate; + float dist; + float spd; + + if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0) + { + self.projectiledeathtype |= HITTYPE_SPLASH; + mage_spike_explode(); + } + + spd = vlen(self.velocity); + spd = bound( + spd - (autocvar_g_monster_mage_attack_spike_decel) * frametime, + (autocvar_g_monster_mage_attack_spike_speed_max), + spd + (autocvar_g_monster_mage_attack_spike_accel) * frametime + ); + + if (self.enemy != world) + if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO) + self.enemy = world; + + if (self.enemy != world) + { + e = self.enemy; + eorg = 0.5 * (e.absmin + e.absmax); + turnrate = (autocvar_g_monster_mage_attack_spike_turnrate); // how fast to turn + desireddir = normalize(eorg - self.origin); + olddir = normalize(self.velocity); // get my current direction + dist = vlen(eorg - self.origin); + + // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P ) + if ((autocvar_g_monster_mage_attack_spike_smart) && (dist > (autocvar_g_monster_mage_attack_spike_smart_mindist))) + { + // Is it a better idea (shorter distance) to trace to the target itself? + if ( vlen(self.origin + olddir * self.wait) < dist) + traceline(self.origin, self.origin + olddir * self.wait, FALSE, self); + else + traceline(self.origin, eorg, FALSE, self); + + // Setup adaptive tracelength + self.wait = bound((autocvar_g_monster_mage_attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = (autocvar_g_monster_mage_attack_spike_smart_trace_max)); + + // Calc how important it is that we turn and add this to the desierd (enemy) dir. + desireddir = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5); + } + + newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy + self.velocity = newdir * spd; // make me fly in the new direction at my flight speed + } + else + dist = 0; + + /////////////// + + //self.angles = vectoangles(self.velocity); // turn model in the new flight direction + self.nextthink = time;// + 0.05; // csqc projectiles + UpdateCSQCProjectile(self); + } + + void mage_attack_spike() + { + entity missile; + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + makevectors(self.angles); + + missile = spawn (); + missile.owner = missile.realowner = self; + missile.think = mage_spike_think; + missile.ltime = time + 7; + missile.nextthink = time; + missile.solid = SOLID_BBOX; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.flags = FL_PROJECTILE; + setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14); + setsize (missile, '0 0 0', '0 0 0'); + missile.velocity = dir * 400; + missile.avelocity = '300 300 300'; + missile.enemy = self.enemy; + missile.touch = mage_spike_touch; + + self.mage_spike = missile; + + CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE); + } + + void mage_heal() + { + entity head; + float washealed = FALSE; + + for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(friend_needshelp(head)) + { + washealed = TRUE; + string fx = ""; + if(IS_PLAYER(head)) + { + switch(self.skin) + { + case 0: + if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_health_regenstable); + fx = "healing_fx"; + break; + case 1: + if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max); + if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max); + if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max); + if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max); + fx = "ammoregen_fx"; + break; + case 2: + if(head.armorvalue < autocvar_g_balance_armor_regenstable) + { + head.armorvalue = bound(0, head.armorvalue + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_armor_regenstable); + fx = "armorrepair_fx"; + } + break; + case 3: + head.health = bound(0, head.health - ((head == self) ? (autocvar_g_monster_mage_heal_self) : (autocvar_g_monster_mage_heal_allies)), autocvar_g_balance_health_regenstable); + fx = "rage"; + break; + } + + pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1); + } + else + { + pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1); + head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health); + WaypointSprite_UpdateHealth(head.sprite, head.health); + } + } + + if(washealed) + { + self.frame = mage_anim_attack; + self.attack_finished_single = time + (autocvar_g_monster_mage_heal_delay); + } + } + + void mage_push() + { + sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM); - RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy); ++ RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy); + pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1); + + self.frame = mage_anim_attack; + self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay); + } + + void mage_teleport() + { + if(vlen(self.enemy.origin - self.origin) >= 500) + return; + + makevectors(self.enemy.angles); + tracebox(self.enemy.origin + ((v_forward * -1) * 200), self.mins, self.maxs, self.origin, MOVE_NOMONSTERS, self); + + if(trace_fraction < 1) + return; + + pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); + setorigin(self, self.enemy.origin + ((v_forward * -1) * 200)); + + self.attack_finished_single = time + 0.2; + } + + void mage_shield_remove() + { + self.effects &= ~(EF_ADDITIVE | EF_BLUE); + self.armorvalue = 0; + self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent; + } + + void mage_shield() + { + self.effects |= (EF_ADDITIVE | EF_BLUE); + self.lastshielded = time + (autocvar_g_monster_mage_shield_delay); + self.m_armor_blockpercent = (autocvar_g_monster_mage_shield_blockpercent); + self.armorvalue = self.health; + self.shield_ltime = time + (autocvar_g_monster_mage_shield_time); + self.frame = mage_anim_attack; + self.attack_finished_single = time + 1; + } + + float mage_attack(float attack_type) + { + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + if(random() <= 0.7) + { + mage_push(); + return TRUE; + } + + return FALSE; + } + case MONSTER_ATTACK_RANGED: + { + if(!self.mage_spike) + { + if(random() <= 0.4) + { + mage_teleport(); + return TRUE; + } + else + { + self.frame = mage_anim_attack; + self.attack_finished_single = time + (autocvar_g_monster_mage_attack_spike_delay); + defer(0.2, mage_attack_spike); + return TRUE; + } + } + + if(self.mage_spike) + return TRUE; + else + return FALSE; + } + } + + return FALSE; + } + + void spawnfunc_monster_mage() + { + self.classname = "monster_mage"; + + self.monster_spawnfunc = spawnfunc_monster_mage; + + if(Monster_CheckAppearFlags(self)) + return; + + if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; } + } + + // compatibility with old spawns + void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); } + + float m_mage(float req) + { + switch(req) + { + case MR_THINK: + { + entity head; + float need_help = FALSE; + + for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) + if(head != self) + if(friend_needshelp(head)) + { + need_help = TRUE; + break; + } + + if(self.health < (autocvar_g_monster_mage_heal_minhealth) || need_help) + if(time >= self.attack_finished_single) + if(random() < 0.5) + mage_heal(); + + if(time >= self.shield_ltime && self.armorvalue) + mage_shield_remove(); + + if(self.enemy) + if(self.health < self.max_health) + if(time >= self.lastshielded) + if(random() < 0.5) + mage_shield(); + + monster_move((autocvar_g_monster_mage_speed_run), (autocvar_g_monster_mage_speed_walk), (autocvar_g_monster_mage_speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle); + return TRUE; + } + case MR_DEATH: + { + self.frame = mage_anim_death; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_mage_health); + + self.monster_loot = spawnfunc_item_health_large; + self.monster_attackfunc = mage_attack; + self.frame = mage_anim_walk; + + return TRUE; + } + case MR_PRECACHE: + { + precache_model ("models/monsters/mage.dpm"); + precache_sound ("weapons/grenade_impact.wav"); + precache_sound ("weapons/tagexp1.wav"); + return TRUE; + } + } + + return TRUE; + } + + #endif // SVQC + #ifdef CSQC + float m_mage(float req) + { + switch(req) + { + case MR_PRECACHE: + { + precache_model ("models/monsters/mage.dpm"); + return TRUE; + } + } + + return TRUE; + } + + #endif // CSQC + #endif // REGISTER_MONSTER diff --cc qcsrc/common/monsters/monster/shambler.qc index 000000000,866782d97..655648784 mode 000000,100644..100644 --- a/qcsrc/common/monsters/monster/shambler.qc +++ b/qcsrc/common/monsters/monster/shambler.qc @@@ -1,0 -1,260 +1,260 @@@ + #ifdef REGISTER_MONSTER + REGISTER_MONSTER( + /* MON_##id */ SHAMBLER, + /* function */ m_shambler, + /* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED, + /* mins,maxs */ '-41 -41 -31', '41 41 65', + /* model */ "shambler.mdl", + /* netname */ "shambler", + /* fullname */ _("Shambler") + ); + + #else + #ifdef SVQC + float autocvar_g_monster_shambler_health; + float autocvar_g_monster_shambler_attack_smash_damage; + float autocvar_g_monster_shambler_attack_claw_damage; + float autocvar_g_monster_shambler_attack_lightning_damage; + float autocvar_g_monster_shambler_attack_lightning_force; + float autocvar_g_monster_shambler_attack_lightning_radius; + float autocvar_g_monster_shambler_attack_lightning_radius_zap; + float autocvar_g_monster_shambler_attack_lightning_speed; + float autocvar_g_monster_shambler_attack_lightning_speed_up; + float autocvar_g_monster_shambler_speed_stop; + float autocvar_g_monster_shambler_speed_run; + float autocvar_g_monster_shambler_speed_walk; + + const float shambler_anim_stand = 0; + const float shambler_anim_walk = 1; + const float shambler_anim_run = 2; + const float shambler_anim_smash = 3; + const float shambler_anim_swingr = 4; + const float shambler_anim_swingl = 5; + const float shambler_anim_magic = 6; + const float shambler_anim_pain = 7; + const float shambler_anim_death = 8; + + .float shambler_lastattack; // delay attacks separately + + void shambler_smash() + { + makevectors(self.angles); + pointparticles(particleeffectnum("explosion_medium"), (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs_z), '0 0 0', 1); + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM); + + tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, MOVE_NORMAL, self); + + if(trace_ent.takedamage) + Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + } + + void shambler_swing() + { + float r = (random() < 0.5); + monster_melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, TRUE); + if(r) + { + defer(0.5, shambler_swing); + self.attack_finished_single += 0.5; + } + } + + void shambler_lightning_explode() + { + entity head; + + sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM); + pointparticles(particleeffectnum("electro_impact"), '0 0 0', '0 0 0', 1); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + self.movetype = MOVETYPE_NONE; + self.velocity = '0 0 0'; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + - RadiusDamage (self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_radius), world, (autocvar_g_monster_shambler_attack_lightning_force), self.projectiledeathtype, other); ++ RadiusDamage (self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_radius), world, world, (autocvar_g_monster_shambler_attack_lightning_force), self.projectiledeathtype, other); + + for(head = findradius(self.origin, (autocvar_g_monster_shambler_attack_lightning_radius_zap)); head; head = head.chain) if(head != self.realowner) if(head.takedamage) + { + te_csqc_lightningarc(self.origin, head.origin); + Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0'); + } + + self.think = SUB_Remove; + self.nextthink = time + 0.2; + } + + void shambler_lightning_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) + { + if (self.health <= 0) + return; + + if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions + return; // g_projectiles_damage says to halt + + self.health = self.health - damage; + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, self.use); + } + + void shambler_lightning_touch() + { + PROJECTILE_TOUCH; + + self.use (); + } + + void shambler_lightning_think() + { + self.nextthink = time; + if (time > self.cnt) + { + other = world; + shambler_lightning_explode(); + return; + } + } + + void shambler_lightning() + { + entity gren; + + monster_makevectors(self.enemy); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = (autocvar_g_monster_shambler_attack_lightning_damage); + gren.movetype = MOVETYPE_BOUNCE; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = DEATH_MONSTER_SHAMBLER_ZAP; + setorigin(gren, CENTER_OR_VIEWOFS(self)); + setsize(gren, '-8 -8 -8', '8 8 8'); + gren.scale = 2.5; + + gren.cnt = time + 5; + gren.nextthink = time; + gren.think = shambler_lightning_think; + gren.use = shambler_lightning_explode; + gren.touch = shambler_lightning_touch; + + gren.takedamage = DAMAGE_YES; + gren.health = 50; + gren.damageforcescale = 0; + gren.event_damage = shambler_lightning_damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SetupProjectileVelocityEx(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, FALSE); ++ W_SetupProjVelocity_Explicit(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, FALSE); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + CSQCProjectile(gren, TRUE, PROJECTILE_SHAMBLER_LIGHTNING, TRUE); + } + + float shambler_attack(float attack_type) + { + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + shambler_swing(); + return TRUE; + } + case MONSTER_ATTACK_RANGED: + { + if(time >= self.shambler_lastattack) // shambler doesn't attack much + if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500) + { + self.frame = shambler_anim_smash; + defer(0.7, shambler_smash); + self.attack_finished_single = time + 1.1; + self.shambler_lastattack = time + 3; + return TRUE; + } + else if(random() <= 0.1) // small chance, don't want this spammed + { + self.frame = shambler_anim_magic; + self.attack_finished_single = time + 1.1; + self.shambler_lastattack = time + 3; + defer(0.6, shambler_lightning); + return TRUE; + } + + return FALSE; + } + } + + return FALSE; + } + + void spawnfunc_monster_shambler() + { + self.classname = "monster_shambler"; + + self.monster_spawnfunc = spawnfunc_monster_shambler; + + if(Monster_CheckAppearFlags(self)) + return; + + if(!monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; } + } + + float m_shambler(float req) + { + switch(req) + { + case MR_THINK: + { + monster_move((autocvar_g_monster_shambler_speed_run), (autocvar_g_monster_shambler_speed_walk), (autocvar_g_monster_shambler_speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand); + return TRUE; + } + case MR_DEATH: + { + self.frame = shambler_anim_death; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_shambler_health); + if(!self.attack_range) self.attack_range = 150; + + self.monster_loot = spawnfunc_item_health_mega; + self.monster_attackfunc = shambler_attack; + self.frame = shambler_anim_stand; - self.weapon = WEP_NEX; ++ self.weapon = WEP_VORTEX; + + return TRUE; + } + case MR_PRECACHE: + { + precache_model ("models/monsters/shambler.mdl"); + return TRUE; + } + } + + return TRUE; + } + + #endif // SVQC + #ifdef CSQC + float m_shambler(float req) + { + switch(req) + { + case MR_PRECACHE: + { + precache_model ("models/monsters/shambler.mdl"); + return TRUE; + } + } + + return TRUE; + } + + #endif // CSQC + #endif // REGISTER_MONSTER diff --cc qcsrc/common/monsters/monster/spider.qc index 000000000,0f46a9620..84281157b mode 000000,100644..100644 --- a/qcsrc/common/monsters/monster/spider.qc +++ b/qcsrc/common/monsters/monster/spider.qc @@@ -1,0 -1,183 +1,183 @@@ + #ifdef REGISTER_MONSTER + REGISTER_MONSTER( + /* MON_##id */ SPIDER, + /* function */ m_spider, + /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED, + /* mins,maxs */ '-18 -18 -25', '18 18 30', + /* model */ "spider.dpm", + /* netname */ "spider", + /* fullname */ _("Spider") + ); + + #else + #ifdef SVQC + float autocvar_g_monster_spider_health; + float autocvar_g_monster_spider_attack_bite_damage; + float autocvar_g_monster_spider_attack_bite_delay; + float autocvar_g_monster_spider_attack_web_damagetime; + float autocvar_g_monster_spider_attack_web_speed; + float autocvar_g_monster_spider_attack_web_speed_up; + float autocvar_g_monster_spider_attack_web_delay; + float autocvar_g_monster_spider_speed_stop; + float autocvar_g_monster_spider_speed_run; + float autocvar_g_monster_spider_speed_walk; + + const float spider_anim_idle = 0; + const float spider_anim_walk = 1; + const float spider_anim_attack = 2; + const float spider_anim_attack2 = 3; + + .float spider_web_delay; + + void spider_web_explode() + { + entity e; + if(self) + { + pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1); - RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world); ++ RadiusDamage(self, self.realowner, 0, 0, 25, world, world, 25, self.projectiledeathtype, world); + + for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0) + e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime); + + remove(self); + } + } + + void spider_web_touch() + { + PROJECTILE_TOUCH; + + spider_web_explode(); + } + + void spider_shootweb() + { + monster_makevectors(self.enemy); + + sound(self, CH_SHOTS, "weapons/electro_fire2.wav", VOL_BASE, ATTEN_NORM); + + entity proj = spawn (); + proj.classname = "plasma"; + proj.owner = proj.realowner = self; + proj.use = spider_web_touch; + proj.think = adaptor_think2use_hittype_splash; + proj.bot_dodge = TRUE; + proj.bot_dodgerating = 0; + proj.nextthink = time + 5; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = DEATH_MONSTER_SPIDER; + setorigin(proj, CENTER_OR_VIEWOFS(self)); + + //proj.glow_size = 50; + //proj.glow_color = 45; + proj.movetype = MOVETYPE_BOUNCE; - W_SetupProjectileVelocityEx(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, FALSE); ++ W_SetupProjVelocity_Explicit(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, FALSE); + proj.touch = spider_web_touch; + setsize(proj, '-4 -4 -4', '4 4 4'); + proj.takedamage = DAMAGE_NO; + proj.damageforcescale = 0; + proj.health = 500; + proj.event_damage = func_null; + proj.flags = FL_PROJECTILE; + proj.damagedbycontents = TRUE; + + proj.bouncefactor = 0.3; + proj.bouncestop = 0.05; + proj.missile_flags = MIF_SPLASH | MIF_ARC; + + CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, TRUE); + } + + float spider_attack(float attack_type) + { + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + { + return monster_melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, TRUE); + } + case MONSTER_ATTACK_RANGED: + { + if(time >= self.spider_web_delay) + { + self.frame = spider_anim_attack2; + self.attack_finished_single = time + (autocvar_g_monster_spider_attack_web_delay); + spider_shootweb(); + self.spider_web_delay = time + 3; + return TRUE; + } + + return FALSE; + } + } + + return FALSE; + } + + void spawnfunc_monster_spider() + { + self.classname = "monster_spider"; + + self.monster_spawnfunc = spawnfunc_monster_spider; + + if(Monster_CheckAppearFlags(self)) + return; + + if(!monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; } + } + + float m_spider(float req) + { + switch(req) + { + case MR_THINK: + { + monster_move((autocvar_g_monster_spider_speed_run), (autocvar_g_monster_spider_speed_walk), (autocvar_g_monster_spider_speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle); + return TRUE; + } + case MR_DEATH: + { + self.frame = spider_anim_attack; + self.angles_x = 180; + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_spider_health); + + self.monster_loot = spawnfunc_item_health_medium; + self.monster_attackfunc = spider_attack; + self.frame = spider_anim_idle; + + return TRUE; + } + case MR_PRECACHE: + { + precache_model ("models/monsters/spider.dpm"); + precache_sound ("weapons/electro_fire2.wav"); + return TRUE; + } + } + + return TRUE; + } + + #endif // SVQC + #ifdef CSQC + float m_spider(float req) + { + switch(req) + { + case MR_PRECACHE: + { + precache_model ("models/monsters/spider.dpm"); + return TRUE; + } + } + + return TRUE; + } + + #endif // CSQC + #endif // REGISTER_MONSTER diff --cc qcsrc/common/monsters/monster/wyvern.qc index 000000000,2d72b4b05..ad8c6b02d mode 000000,100644..100644 --- a/qcsrc/common/monsters/monster/wyvern.qc +++ b/qcsrc/common/monsters/monster/wyvern.qc @@@ -1,0 -1,164 +1,164 @@@ + #ifdef REGISTER_MONSTER + REGISTER_MONSTER( + /* MON_##id */ WYVERN, + /* function */ m_wyvern, + /* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED, + /* mins,maxs */ '-20 -20 -58', '20 20 20', + /* model */ "wizard.mdl", + /* netname */ "wyvern", + /* fullname */ _("Wyvern") + ); + + #else + #ifdef SVQC + float autocvar_g_monster_wyvern_health; + float autocvar_g_monster_wyvern_attack_fireball_damage; + float autocvar_g_monster_wyvern_attack_fireball_edgedamage; + float autocvar_g_monster_wyvern_attack_fireball_damagetime; + float autocvar_g_monster_wyvern_attack_fireball_force; + float autocvar_g_monster_wyvern_attack_fireball_radius; + float autocvar_g_monster_wyvern_attack_fireball_speed; + float autocvar_g_monster_wyvern_speed_stop; + float autocvar_g_monster_wyvern_speed_run; + float autocvar_g_monster_wyvern_speed_walk; + + const float wyvern_anim_hover = 0; + const float wyvern_anim_fly = 1; + const float wyvern_anim_magic = 2; + const float wyvern_anim_pain = 3; + const float wyvern_anim_death = 4; + + void wyvern_fireball_explode() + { + entity e; + if(self) + { + pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1); + - RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world); ++ RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world); + + for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_wyvern_attack_fireball_radius)) + Fire_AddDamage(e, self, 5 * Monster_SkillModifier(), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype); + + remove(self); + } + } + + void wyvern_fireball_touch() + { + PROJECTILE_TOUCH; + + wyvern_fireball_explode(); + } + + void wyvern_fireball() + { + entity missile = spawn(); + vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin); + + monster_makevectors(self.enemy); + + missile.owner = missile.realowner = self; + missile.solid = SOLID_TRIGGER; + missile.movetype = MOVETYPE_FLYMISSILE; + missile.projectiledeathtype = DEATH_MONSTER_WYVERN; + setsize(missile, '-6 -6 -6', '6 6 6'); + setorigin(missile, self.origin + self.view_ofs + v_forward * 14); + missile.flags = FL_PROJECTILE; + missile.velocity = dir * (autocvar_g_monster_wyvern_attack_fireball_speed); + missile.avelocity = '300 300 300'; + missile.nextthink = time + 5; + missile.think = wyvern_fireball_explode; + missile.enemy = self.enemy; + missile.touch = wyvern_fireball_touch; + CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE); + } + + float wyvern_attack(float attack_type) + { + switch(attack_type) + { + case MONSTER_ATTACK_MELEE: + case MONSTER_ATTACK_RANGED: + { + self.attack_finished_single = time + 1.2; + + wyvern_fireball(); + + return TRUE; + } + } + + return FALSE; + } + + void spawnfunc_monster_wyvern() + { + self.classname = "monster_wyvern"; + + self.monster_spawnfunc = spawnfunc_monster_wyvern; + + if(Monster_CheckAppearFlags(self)) + return; + + if(!monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; } + } + + // compatibility with old spawns + void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); } + + float m_wyvern(float req) + { + switch(req) + { + case MR_THINK: + { + monster_move((autocvar_g_monster_wyvern_speed_run), (autocvar_g_monster_wyvern_speed_walk), (autocvar_g_monster_wyvern_speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover); + return TRUE; + } + case MR_DEATH: + { + self.frame = wyvern_anim_death; + self.velocity_x = -200 + 400 * random(); + self.velocity_y = -200 + 400 * random(); + self.velocity_z = 100 + 100 * random(); + return TRUE; + } + case MR_SETUP: + { + if(!self.health) self.health = (autocvar_g_monster_wyvern_health); + + self.monster_loot = spawnfunc_item_cells; + self.monster_attackfunc = wyvern_attack; + self.frame = wyvern_anim_hover; + + return TRUE; + } + case MR_PRECACHE: + { + precache_model ("models/monsters/wizard.mdl"); + return TRUE; + } + } + + return TRUE; + } + + #endif // SVQC + #ifdef CSQC + float m_wyvern(float req) + { + switch(req) + { + case MR_PRECACHE: + { + precache_model ("models/monsters/wizard.mdl"); + return TRUE; + } + } + + return TRUE; + } + + #endif // CSQC + #endif // REGISTER_MONSTER diff --cc qcsrc/common/monsters/sv_monsters.qc index 000000000,927501e65..013eea743 mode 000000,100644..100644 --- a/qcsrc/common/monsters/sv_monsters.qc +++ b/qcsrc/common/monsters/sv_monsters.qc @@@ -1,0 -1,1097 +1,1097 @@@ + // ========================= + // SVQC Monster Properties + // ========================= + + + void monster_item_spawn() + { + if(self.monster_loot) + self.monster_loot(); + + self.gravity = 1; + self.reset = SUB_Remove; + self.noalign = TRUE; + self.velocity = randomvec() * 175 + '0 0 325'; + self.classname = "droppedweapon"; // hax + self.item_spawnshieldtime = time + 0.7; + + SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1); + } + + void monster_dropitem() + { + if(!self.candrop || !self.monster_loot) + return; + + vector org = self.origin + ((self.mins + self.maxs) * 0.5); + entity e = spawn(); + + setorigin(e, org); + + e.monster_loot = self.monster_loot; + + other = e; + MUTATOR_CALLHOOK(MonsterDropItem); + e = other; + + if(e) + { + e.think = monster_item_spawn; + e.nextthink = time + 0.3; + } + } + + float Monster_SkillModifier() + { + float t = 0.5+self.monster_skill*((1.2-0.3)/10); + + return t; + } + + float monster_isvalidtarget (entity targ, entity ent) + { + if(!targ || !ent) + return FALSE; // someone doesn't exist + + if(targ == ent) + return FALSE; // don't attack ourselves + + traceline(ent.origin, targ.origin, MOVE_NORMAL, ent); + + if(trace_ent != targ) + return FALSE; + + if(targ.vehicle_flags & VHF_ISVEHICLE) + if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED)) + return FALSE; // melee attacks are useless against vehicles + + if(time < game_starttime) + return FALSE; // monsters do nothing before the match has started + + if(vlen(targ.origin - ent.origin) >= ent.target_range) + return FALSE; // enemy is too far away + + if(targ.takedamage == DAMAGE_NO) + return FALSE; // enemy can't be damaged + + if(targ.items & IT_INVISIBILITY) + return FALSE; // enemy is invisible + + if(substring(targ.classname, 0, 10) == "onslaught_") + return FALSE; // don't attack onslaught targets + + if(IS_SPEC(targ) || IS_OBSERVER(targ)) + return FALSE; // enemy is a spectator + + if(!(targ.vehicle_flags & VHF_ISVEHICLE)) + if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0) + return FALSE; // enemy/self is dead + + if(ent.monster_owner == targ) + return FALSE; // don't attack our master + + if(targ.monster_owner == ent) + return FALSE; // don't attack our pet + + if(!(targ.vehicle_flags & VHF_ISVEHICLE)) + if(targ.flags & FL_NOTARGET) + return FALSE; // enemy can't be targeted + + if(!autocvar_g_monsters_typefrag) + if(targ.BUTTON_CHAT) + return FALSE; // no typefragging! + + if(SAME_TEAM(targ, ent)) + return FALSE; // enemy is on our team + + if (targ.freezetag_frozen) + return FALSE; // ignore frozen + + if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT) + if(ent.enemy != targ) + { + float dot; + + makevectors (ent.angles); + dot = normalize (targ.origin - ent.origin) * v_forward; + + if(dot <= 0.3) + return FALSE; + } + + return TRUE; + } + + entity FindTarget (entity ent) + { + if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator + + entity head, closest_target = world; + head = findradius(ent.origin, ent.target_range); + + while(head) // find the closest acceptable target to pass to + { + if(head.monster_attack) + if(monster_isvalidtarget(head, ent)) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + vector head_center = CENTER_OR_VIEWOFS(head); + vector ent_center = CENTER_OR_VIEWOFS(ent); + + //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest)) + if(closest_target) + { + vector closest_target_center = CENTER_OR_VIEWOFS(closest_target); + if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center)) + { closest_target = head; } + } + else { closest_target = head; } + } + + head = head.chain; + } + + return closest_target; + } + + void MonsterTouch () + { + if(other == world) + return; + + if(self.enemy != other) + if(!(other.flags & FL_MONSTER)) + if(monster_isvalidtarget(other, self)) + self.enemy = other; + } + + string get_monster_model_datafilename(string m, float sk, string fil) + { + if(m) + m = strcat(m, "_"); + else + m = "models/monsters/*_"; + if(sk >= 0) + m = strcat(m, ftos(sk)); + else + m = strcat(m, "*"); + return strcat(m, ".", fil); + } + + void PrecacheMonsterSounds(string f) + { + float fh; + string s; + fh = fopen(f, FILE_READ); + if(fh < 0) + return; + while((s = fgets(fh))) + { + if(tokenize_console(s) != 3) + { + dprint("Invalid sound info line: ", s, "\n"); + continue; + } + PrecacheGlobalSound(strcat(argv(1), " ", argv(2))); + } + fclose(fh); + } + + void precache_monstersounds() + { + string m = (get_monsterinfo(self.monsterid)).model; + float globhandle, n, i; + string f; + + globhandle = search_begin(strcat(m, "_*.sounds"), TRUE, FALSE); + if (globhandle < 0) + return; + n = search_getsize(globhandle); + for (i = 0; i < n; ++i) + { + //print(search_getfilename(globhandle, i), "\n"); + f = search_getfilename(globhandle, i); + PrecacheMonsterSounds(f); + } + search_end(globhandle); + } + + void ClearMonsterSounds() + { + #define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; } + ALLMONSTERSOUNDS + #undef _MSOUND + } + + .string GetMonsterSoundSampleField(string type) + { + GetMonsterSoundSampleField_notFound = 0; + switch(type) + { + #define _MSOUND(m) case #m: return monstersound_##m; + ALLMONSTERSOUNDS + #undef _MSOUND + } + GetMonsterSoundSampleField_notFound = 1; + return string_null; + } + + float LoadMonsterSounds(string f, float first) + { + float fh; + string s; + var .string field; + fh = fopen(f, FILE_READ); + if(fh < 0) + { + dprint("Monster sound file not found: ", f, "\n"); + return 0; + } + while((s = fgets(fh))) + { + if(tokenize_console(s) != 3) + continue; + field = GetMonsterSoundSampleField(argv(0)); + if(GetMonsterSoundSampleField_notFound) + continue; + if(self.field) + strunzone(self.field); + self.field = strzone(strcat(argv(1), " ", argv(2))); + } + fclose(fh); + return 1; + } + + .float skin_for_monstersound; + void UpdateMonsterSounds() + { + entity mon = get_monsterinfo(self.monsterid); + + if(self.skin == self.skin_for_monstersound) + return; + self.skin_for_monstersound = self.skin; + ClearMonsterSounds(); + //LoadMonsterSounds("sound/monsters/default.sounds", 1); + if(!autocvar_g_debug_defaultsounds) + if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0)) + LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0); + } + + void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan) + { + if(delaytoo && time < self.msound_delay) + return; // too early + GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND); + + self.msound_delay = time + sound_delay; + } + + void monster_makevectors(entity e) + { + vector v; + + v = e.origin + (e.mins + e.maxs) * 0.5; + self.v_angle = vectoangles(v - (self.origin + self.view_ofs)); + self.v_angle_x = -self.v_angle_x; + + makevectors(self.v_angle); + } + + float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, float deathtype, float dostop) + { + if (self.health <= 0) + return FALSE; // attacking while dead?! + + if(dostop) + { + self.velocity_x = 0; + self.velocity_y = 0; + self.state = MONSTER_STATE_ATTACK_MELEE; + } + + self.frame = anim; + + if(anim_finished != 0) + self.attack_finished_single = time + anim_finished; + + monster_makevectors(targ); + + traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self); + + if(trace_ent.takedamage) + Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin)); + + return TRUE; + } + + void Monster_CheckMinibossFlag () + { + if(MUTATOR_CALLHOOK(MonsterCheckBossFlag)) + return; + + float chance = random() * 100; + + // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss + if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance)) + { + self.health += autocvar_g_monsters_miniboss_healthboost; + self.effects |= EF_RED; + if(!self.weapon) - self.weapon = WEP_NEX; ++ self.weapon = WEP_VORTEX; + } + } + + float Monster_CanRespawn(entity ent) + { + other = ent; + if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead + if(MUTATOR_CALLHOOK(MonsterRespawn)) + return TRUE; // enabled by a mutator + + if(ent.spawnflags & MONSTERFLAG_NORESPAWN) + return FALSE; + + if(!autocvar_g_monsters_respawn) + return FALSE; + + return TRUE; + } + + void Monster_Fade () + { + if(Monster_CanRespawn(self)) + { + self.spawnflags |= MONSTERFLAG_RESPAWNED; + self.think = self.monster_spawnfunc; + self.nextthink = time + self.respawntime; + self.ltime = 0; + self.deadflag = DEAD_RESPAWNING; + if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT) + { + self.pos1 = self.origin; + self.pos2 = self.angles; + } + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + setorigin(self, self.pos1); + self.angles = self.pos2; + self.health = self.max_health; + setmodel(self, "null"); + } + else + { + // number of monsters spawned with mobspawn command + totalspawned -= 1; + + if(IS_CLIENT(self.realowner)) + self.realowner.monstercount -= 1; + + SUB_SetFade(self, time + 3, 1); + } + } + + float Monster_CanJump (vector vel) + { + if(self.state) + return FALSE; // already attacking + if(!(self.flags & FL_ONGROUND)) + return FALSE; // not on the ground + if(self.health <= 0) + return FALSE; // called when dead? + if(time < self.attack_finished_single) + return FALSE; // still attacking + + vector old = self.velocity; + + self.velocity = vel; + tracetoss(self, self); + self.velocity = old; + if (trace_ent != self.enemy) + return FALSE; + + return TRUE; + } + + float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished) + { + if(!Monster_CanJump(vel)) + return FALSE; + + self.frame = anm; + self.state = MONSTER_STATE_ATTACK_LEAP; + self.touch = touchfunc; + self.origin_z += 1; + self.velocity = vel; + self.flags &= ~FL_ONGROUND; + + self.attack_finished_single = time + anim_finished; + + return TRUE; + } + + void monster_checkattack(entity e, entity targ) + { + if(e == world) + return; + if(targ == world) + return; + + if(!e.monster_attackfunc) + return; + + if(time < e.attack_finished_single) + return; + + if(vlen(targ.origin - e.origin) <= e.attack_range) + if(e.monster_attackfunc(MONSTER_ATTACK_MELEE)) + { + MonsterSound(monstersound_melee, 0, FALSE, CH_VOICE); + return; + } + + if(vlen(targ.origin - e.origin) > e.attack_range) + if(e.monster_attackfunc(MONSTER_ATTACK_RANGED)) + { + MonsterSound(monstersound_ranged, 0, FALSE, CH_VOICE); + return; + } + } + + void monster_use () + { + if(!self.enemy) + if(self.health > 0) + if(monster_isvalidtarget(activator, self)) + self.enemy = activator; + } + + .float last_trace; + .float last_enemycheck; // for checking enemy + vector monster_pickmovetarget(entity targ) + { + // enemy is always preferred target + if(self.enemy) + { + makevectors(self.angles); + self.monster_movestate = MONSTER_MOVE_ENEMY; + self.last_trace = time + 1.2; + return self.enemy.origin; + } + + switch(self.monster_moveflags) + { + case MONSTER_MOVE_OWNER: + { + self.monster_movestate = MONSTER_MOVE_OWNER; + self.last_trace = time + 0.3; + return (self.monster_owner) ? self.monster_owner.origin : self.origin; + } + case MONSTER_MOVE_SPAWNLOC: + { + self.monster_movestate = MONSTER_MOVE_SPAWNLOC; + self.last_trace = time + 2; + return self.pos1; + } + case MONSTER_MOVE_NOMOVE: + { + self.monster_movestate = MONSTER_MOVE_NOMOVE; + self.last_trace = time + 2; + return self.origin; + } + default: + case MONSTER_MOVE_WANDER: + { + vector pos; + self.monster_movestate = MONSTER_MOVE_WANDER; + self.last_trace = time + 2; + + self.angles_y = rint(random() * 500); + makevectors(self.angles); + pos = self.origin + v_forward * 600; + + if(self.flags & FL_FLY || self.flags & FL_SWIM) + if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL) + { + pos_z = random() * 200; + if(random() >= 0.5) + pos_z *= -1; + } + + if(targ) + { + self.last_trace = time + 0.5; + pos = targ.origin; + } + + return pos; + } + } + } + + void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle) + { + fixedmakevectors(self.angles); + + if(self.target2) + self.goalentity = find(world, targetname, self.target2); + + entity targ; + + if(self.flags & FL_SWIM) + { + if(self.waterlevel < WATERLEVEL_WETFEET) + { + if(time >= self.last_trace) + { + self.fish_wasdrowning = TRUE; + self.last_trace = time + 0.4; + + Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0'); + self.angles = '90 90 0'; + if(random() < 0.5) + { + self.velocity_y += random() * 50; + self.velocity_x -= random() * 50; + } + else + { + self.velocity_y -= random() * 50; + self.velocity_x += random() * 50; + } + self.velocity_z += random() * 150; + } + + + self.movetype = MOVETYPE_BOUNCE; + //self.velocity_z = -200; + + return; + } + else if(self.fish_wasdrowning) + { + self.fish_wasdrowning = FALSE; + self.angles_x = 0; + self.movetype = MOVETYPE_WALK; + } + } + + targ = self.goalentity; + + monster_target = targ; + monster_speed_run = runspeed; + monster_speed_walk = walkspeed; + + if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time) + { + runspeed = walkspeed = 0; + if(time >= self.spawn_time) + self.frame = manim_idle; + movelib_beak_simple(stopspeed); + return; + } + + targ = monster_target; + runspeed = bound(0, monster_speed_run * Monster_SkillModifier(), runspeed * 2); // limit maxspeed to prevent craziness + walkspeed = bound(0, monster_speed_walk * Monster_SkillModifier(), walkspeed * 2); // limit maxspeed to prevent craziness + + if(time < self.spider_slowness) + { + runspeed *= 0.5; + walkspeed *= 0.5; + } + + if(teamplay) + if(autocvar_g_monsters_teams) + if(DIFF_TEAM(self.monster_owner, self)) + self.monster_owner = world; + + if(self.enemy && self.enemy.health < 1) + self.enemy = world; // enough! + + if(time >= self.last_enemycheck) + { + if(!monster_isvalidtarget(self.enemy, self)) + self.enemy = world; + + if(!self.enemy) + { + self.enemy = FindTarget(self); + if(self.enemy) + MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE); + } + + self.last_enemycheck = time + 0.5; + } + + if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single) + self.state = 0; + + if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set + if(time >= self.last_trace || self.enemy) // update enemy instantly + self.moveto = monster_pickmovetarget(targ); + + if(!self.enemy) + MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE); + + if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE) + self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + + if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND)) + { + self.state = 0; + self.touch = MonsterTouch; + } + + //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95); + + float turny = 0; + vector real_angle = vectoangles(self.steerto) - self.angles; + + if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE) + turny = 20; + + if(self.flags & FL_SWIM) + turny = vlen(self.angles - self.moveto); + + if(turny) + { + turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny); + self.angles_y += turny; + } + + if(self.state == MONSTER_STATE_ATTACK_MELEE) + self.moveto = self.origin; + + if(self.enemy && self.enemy.vehicle) + runspeed = 0; + + if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL) + v_forward = normalize(self.moveto - self.origin); + else + self.moveto_z = self.origin_z; + + if(vlen(self.origin - self.moveto) > 64) + { + if(self.flags & FL_FLY || self.flags & FL_SWIM) + movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); + else + movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6); + + if(time > self.pain_finished) + if(time > self.attack_finished_single) + if(vlen(self.velocity) > 10) + self.frame = ((self.enemy) ? manim_run : manim_walk); + else + self.frame = manim_idle; + } + else + { + entity e = find(world, targetname, self.target2); + if(e.target2) + self.target2 = e.target2; + else if(e.target) + self.target2 = e.target; + + movelib_beak_simple(stopspeed); + if(time > self.attack_finished_single) + if(time > self.pain_finished) + if (vlen(self.velocity) <= 30) + self.frame = manim_idle; + } + + monster_checkattack(self, self.enemy); + } + + void monster_remove(entity mon) + { + if(!mon) + return; // nothing to remove + + pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1); + + if(mon.weaponentity) + remove(mon.weaponentity); + + WaypointSprite_Kill(mon.sprite); + + remove(mon); + } + + void monster_dead_think() + { + self.nextthink = time + self.ticrate; + + CSQCMODEL_AUTOUPDATE(); + + if(self.ltime != 0) + if(time >= self.ltime) + { + Monster_Fade(); + return; + } + } + + void monsters_setstatus() + { + self.stat_monsters_total = monsters_total; + self.stat_monsters_killed = monsters_killed; + } + + void Monster_Appear() + { + self.enemy = activator; + self.spawnflags &= ~MONSTERFLAG_APPEAR; + self.monster_spawnfunc(); + } + + float Monster_CheckAppearFlags(entity ent) + { + if(!(ent.spawnflags & MONSTERFLAG_APPEAR)) + return FALSE; + + ent.think = func_null; + ent.nextthink = 0; + ent.use = Monster_Appear; + ent.flags = FL_MONSTER; // set so this monster can get butchered + + return TRUE; + } + + void monsters_reset() + { + setorigin(self, self.pos1); + self.angles = self.pos2; + + self.health = self.max_health; + self.velocity = '0 0 0'; + self.enemy = world; + self.goalentity = world; + self.attack_finished_single = 0; + self.moveto = self.origin; + } + + void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) + { + self.health -= damage; + + Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); + + if(self.health <= -100) // 100 health until gone? + { + Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker); + + // number of monsters spawned with mobspawn command + totalspawned -= 1; + + if(IS_CLIENT(self.realowner)) + self.realowner.monstercount -= 1; + + self.think = SUB_Remove; + self.nextthink = time + 0.1; + } + } + + void monster_die(entity attacker, float gibbed) + { + self.think = monster_dead_think; + self.nextthink = time; + self.ltime = time + 5; + + monster_dropitem(); + + MonsterSound(monstersound_death, 0, FALSE, CH_VOICE); + + if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !(self.spawnflags & MONSTERFLAG_RESPAWNED)) + monsters_killed += 1; + + if(IS_PLAYER(attacker)) + if(autocvar_g_monsters_score_spawned || !((self.spawnflags & MONSTERFLAG_SPAWNED) || (self.spawnflags & MONSTERFLAG_RESPAWNED))) + PlayerScore_Add(attacker, SP_SCORE, +autocvar_g_monsters_score_kill); + + if(gibbed) + { + // number of monsters spawned with mobspawn command + totalspawned -= 1; + + if(IS_CLIENT(self.realowner)) + self.realowner.monstercount -= 1; + } + + if(self.candrop && self.weapon) + W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325'); + + self.event_damage = monsters_corpse_damage; + self.solid = SOLID_CORPSE; + self.takedamage = DAMAGE_AIM; + self.deadflag = DEAD_DEAD; + self.enemy = world; + self.movetype = MOVETYPE_TOSS; + self.moveto = self.origin; + self.touch = MonsterTouch; // reset incase monster was pouncing + self.reset = func_null; + self.state = 0; + self.attack_finished_single = 0; + + if(!(self.flags & FL_FLY)) + self.velocity = '0 0 0'; + + MON_ACTION(self.monsterid, MR_DEATH); + } + + void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) + { + if(time < self.pain_finished && deathtype != DEATH_KILL) + return; + + if(time < self.spawnshieldtime && deathtype != DEATH_KILL) + return; + + vector v; + float take, save; + + v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, deathtype, damage); + take = v_x; + save = v_y; + + self.health -= take; + + WaypointSprite_UpdateHealth(self.sprite, self.health); + + self.dmg_time = time; + + if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN) + spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM); // FIXME: PLACEHOLDER + + self.velocity += force * self.damageforcescale; + + if(deathtype != DEATH_DROWN) + { + Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker); + if (take > 50) + Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker); + if (take > 100) + Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker); + } + + if(self.health <= 0) + { + if(deathtype == DEATH_KILL) + self.candrop = FALSE; // killed by mobkill command + + // TODO: fix this? + activator = attacker; + other = self.enemy; + SUB_UseTargets(); + self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn + + monster_die(attacker, (self.health <= -100 || deathtype == DEATH_KILL)); + + WaypointSprite_Kill(self.sprite); + + frag_attacker = attacker; + frag_target = self; + MUTATOR_CALLHOOK(MonsterDies); + + if(self.health <= -100 || deathtype == DEATH_KILL) // check if we're already gibbed + { + Violence_GibSplash(self, 1, 0.5, attacker); + + self.think = SUB_Remove; + self.nextthink = time + 0.1; + } + } + } + + void monster_setupcolors() + { + if(IS_PLAYER(self.monster_owner)) + self.colormap = self.monster_owner.colormap; + else if(teamplay && self.team) + self.colormap = 1024 + (self.team - 1) * 17; + else + { + if(self.monster_skill <= MONSTER_SKILL_EASY) + self.colormap = 1029; + else if(self.monster_skill <= MONSTER_SKILL_MEDIUM) + self.colormap = 1027; + else if(self.monster_skill <= MONSTER_SKILL_HARD) + self.colormap = 1038; + else if(self.monster_skill <= MONSTER_SKILL_INSANE) + self.colormap = 1028; + else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE) + self.colormap = 1032; + else + self.colormap = 1024; + } + } + + void monster_think() + { + self.think = monster_think; + self.nextthink = self.ticrate; + + if(self.ltime) + if(time >= self.ltime) + { + Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin); + return; + } + + MON_ACTION(self.monsterid, MR_THINK); + + CSQCMODEL_AUTOUPDATE(); + } + + float monster_spawn() + { + MON_ACTION(self.monsterid, MR_SETUP); + + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) + { + Monster_CheckMinibossFlag(); + self.health *= Monster_SkillModifier(); + } + + self.max_health = self.health; + self.pain_finished = self.nextthink; + + if(IS_PLAYER(self.monster_owner)) + self.effects |= EF_DIMLIGHT; + + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) + if(!self.skin) + self.skin = rint(random() * 4); + + if(!self.attack_range) + self.attack_range = autocvar_g_monsters_attack_range; + + precache_monstersounds(); + UpdateMonsterSounds(); + + if(teamplay) + self.monster_attack = TRUE; // we can have monster enemies in team games + + MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE); + + WaypointSprite_Spawn(M_NAME(self.monsterid), 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, self, sprite, TRUE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0')); + WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health); + WaypointSprite_UpdateHealth(self.sprite, self.health); + + self.think = monster_think; + self.nextthink = time + self.ticrate; + + if(MUTATOR_CALLHOOK(MonsterSpawn)) + return FALSE; + + return TRUE; + } + + float monster_initialize(float mon_id, float nodrop) + { + if(!autocvar_g_monsters) + return FALSE; + + entity mon = get_monsterinfo(mon_id); + + if(!self.monster_skill) + self.monster_skill = cvar("g_monsters_skill"); + + // support for quake style removing monsters based on skill + if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { return FALSE; } + if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { return FALSE; } + if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { return FALSE; } + + if(self.team && !teamplay) + self.team = 0; + + if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) + monsters_total += 1; + + setmodel(self, mon.model); + setsize(self, mon.mins, mon.maxs); + self.flags = FL_MONSTER; + self.takedamage = DAMAGE_AIM; + self.bot_attack = TRUE; + self.iscreature = TRUE; + self.teleportable = TRUE; + self.damagedbycontents = TRUE; + self.monsterid = mon_id; + self.damageforcescale = 0; + self.event_damage = monsters_damage; + self.touch = MonsterTouch; + self.use = monster_use; + self.solid = SOLID_BBOX; + self.movetype = MOVETYPE_WALK; + self.spawnshieldtime = time + autocvar_g_monsters_spawnshieldtime; + self.enemy = world; + self.velocity = '0 0 0'; + self.moveto = self.origin; + self.pos1 = self.origin; + self.pos2 = self.angles; + self.reset = monsters_reset; + self.netname = mon.netname; + self.monster_name = M_NAME(mon_id); + self.candrop = TRUE; + self.view_ofs = '0 0 1' * (self.maxs_z * 0.5); + self.oldtarget2 = self.target2; + self.deadflag = DEAD_NO; + self.scale = 1; + self.noalign = nodrop; + self.spawn_time = time; + self.spider_slowness = 0; + self.gravity = 1; + self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP; + + if(autocvar_g_fullbrightplayers) + self.effects |= EF_FULLBRIGHT; + + if(autocvar_g_nodepthtestplayers) + self.effects |= EF_NODEPTHTEST; + + if(mon.spawnflags & MONSTER_TYPE_SWIM) + self.flags |= FL_SWIM; + + if(mon.spawnflags & MONSTER_TYPE_FLY) + { + self.flags |= FL_FLY; + self.movetype = MOVETYPE_FLY; + } + + if(mon.spawnflags & MONSTER_SIZE_BROKEN) + self.scale = 1.3; + + if(!self.ticrate) + self.ticrate = autocvar_g_monsters_think_delay; + + self.ticrate = bound(sys_frametime, self.ticrate, 60); + + if(!self.m_armor_blockpercent) + self.m_armor_blockpercent = 0.5; + + if(!self.target_range) + self.target_range = autocvar_g_monsters_target_range; + + if(!self.respawntime) + self.respawntime = autocvar_g_monsters_respawn_delay; + + if(!self.monster_moveflags) + self.monster_moveflags = MONSTER_MOVE_WANDER; + + if(!self.noalign) + { + setorigin(self, self.origin + '0 0 20'); + tracebox(self.origin + '0 0 64', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self); + setorigin(self, trace_endpos); + } + + if(!monster_spawn()) + return FALSE; + + if(!(self.spawnflags & MONSTERFLAG_RESPAWNED)) + monster_setupcolors(); + + CSQCMODEL_AUTOINIT(); + + return TRUE; + } diff --cc qcsrc/common/weapons/w_shockwave.qc index 59abc6838,000000000..6cf877ffd mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_shockwave.qc +++ b/qcsrc/common/weapons/w_shockwave.qc @@@ -1,749 -1,0 +1,749 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ SHOCKWAVE, +/* function */ W_Shockwave, +/* ammotype */ ammo_none, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_LOW, +/* color */ '0.5 0.25 0', +/* modelname */ "shotgun", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairshotgun 0.7", +/* wepimg */ "weaponshotgun", +/* refname */ "shockwave", +/* wepname */ _("Shockwave") +); + +#define SHOCKWAVE_SETTINGS(w_cvar,w_prop) SHOCKWAVE_SETTINGS_LIST(w_cvar, w_prop, SHOCKWAVE, shockwave) +#define SHOCKWAVE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, NONE, blast_animtime) \ + w_cvar(id, sn, NONE, blast_damage) \ + w_cvar(id, sn, NONE, blast_distance) \ + w_cvar(id, sn, NONE, blast_edgedamage) \ + w_cvar(id, sn, NONE, blast_force) \ + w_cvar(id, sn, NONE, blast_force_forwardbias) \ + w_cvar(id, sn, NONE, blast_force_zscale) \ + w_cvar(id, sn, NONE, blast_jump_damage) \ + w_cvar(id, sn, NONE, blast_jump_edgedamage) \ + w_cvar(id, sn, NONE, blast_jump_force) \ + w_cvar(id, sn, NONE, blast_jump_force_velocitybias) \ + w_cvar(id, sn, NONE, blast_jump_force_zscale) \ + w_cvar(id, sn, NONE, blast_jump_multiplier_accuracy) \ + w_cvar(id, sn, NONE, blast_jump_multiplier_distance) \ + w_cvar(id, sn, NONE, blast_jump_multiplier_min) \ + w_cvar(id, sn, NONE, blast_jump_radius) \ + w_cvar(id, sn, NONE, blast_multiplier_accuracy) \ + w_cvar(id, sn, NONE, blast_multiplier_distance) \ + w_cvar(id, sn, NONE, blast_multiplier_min) \ + w_cvar(id, sn, NONE, blast_refire) \ + w_cvar(id, sn, NONE, blast_splash_damage) \ + w_cvar(id, sn, NONE, blast_splash_edgedamage) \ + w_cvar(id, sn, NONE, blast_splash_force) \ + w_cvar(id, sn, NONE, blast_splash_force_forwardbias) \ + w_cvar(id, sn, NONE, blast_splash_multiplier_accuracy) \ + w_cvar(id, sn, NONE, blast_splash_multiplier_distance) \ + w_cvar(id, sn, NONE, blast_splash_multiplier_min) \ + w_cvar(id, sn, NONE, blast_splash_radius) \ + w_cvar(id, sn, NONE, blast_spread_max) \ + w_cvar(id, sn, NONE, blast_spread_min) \ + w_cvar(id, sn, NONE, melee_animtime) \ + w_cvar(id, sn, NONE, melee_damage) \ + w_cvar(id, sn, NONE, melee_delay) \ + w_cvar(id, sn, NONE, melee_force) \ + w_cvar(id, sn, NONE, melee_multihit) \ + w_cvar(id, sn, NONE, melee_no_doubleslap) \ + w_cvar(id, sn, NONE, melee_nonplayerdamage) \ + w_cvar(id, sn, NONE, melee_range) \ + w_cvar(id, sn, NONE, melee_refire) \ + w_cvar(id, sn, NONE, melee_swing_side) \ + w_cvar(id, sn, NONE, melee_swing_up) \ + w_cvar(id, sn, NONE, melee_time) \ + w_cvar(id, sn, NONE, melee_traces) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_shockwave() +{ + //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO + if(autocvar_sv_q3acompat_machineshotgunswap) + if(self.classname != "droppedweapon") + { + weapon_defaultspawnfunc(WEP_MACHINEGUN); + return; + } + weapon_defaultspawnfunc(WEP_SHOCKWAVE); +} + +#define MAX_SHOCKWAVE_HITS 10 +#define DEBUG_SHOCKWAVE + +.float swing_prev; +.entity swing_alreadyhit; +.float shockwave_blasttime; +entity shockwave_hit[MAX_SHOCKWAVE_HITS]; +float shockwave_hit_damage[MAX_SHOCKWAVE_HITS]; +vector shockwave_hit_force[MAX_SHOCKWAVE_HITS]; + +// MELEE ATTACK MODE +void W_Shockwave_Melee_Think() +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap)) + { + remove(self); + return; + } + + // set start time of melee + if(!self.cnt) + { + self.cnt = time; + W_PlayStrengthSound(self.realowner); + } + + // update values for v_* vectors + makevectors(self.realowner.v_angle); + + // calculate swing percentage based on time + meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces)); + + // perform the traces needed for this frame + for(i=self.swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * WEP_CVAR(shockwave, melee_range)) + + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up)) + + (v_right * swing_factor * WEP_CVAR(shockwave, melee_swing_side))); + + WarpZone_traceline_antilag( + self.realowner, + (self.realowner.origin + self.realowner.view_ofs), + targpos, + FALSE, + self.realowner, + ANTILAG_LATENCY(self.realowner) + ); + + // draw lightning beams for debugging + te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); + te_customflash(targpos, 40, 2, '1 1 1'); + - is_player = (trace_ent.classname == "player" || trace_ent.classname == "body"); ++ is_player = (trace_ent.classname == "player" || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER)); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self if necessary + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.swing_alreadyhit) + && (is_player || WEP_CVAR(shockwave, melee_nonplayerdamage))) + { + target_victim = trace_ent; // so it persists through other calls + + if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught + swing_damage = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1)); + else + swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1)); + + // trigger damage with this calculated info + Damage( + target_victim, + self.realowner, + self.realowner, + swing_damage, + (WEP_SHOCKWAVE | HITTYPE_SECONDARY), + (self.realowner.origin + self.realowner.view_ofs), + (v_forward * WEP_CVAR(shockwave, melee_force)) + ); + + // handle accuracy + if(accuracy_isgooddamage(self.realowner, target_victim)) + { accuracy_add(self.realowner, WEP_SHOCKWAVE, 0, swing_damage); } + + #ifdef DEBUG_SHOCKWAVE + print(sprintf( + "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.\n", + self.realowner.netname, + target_victim.netname, + swing_damage, + swing_factor, + time + )); + #endif + + // allow multiple hits with one swing, but not against the same player twice + if(WEP_CVAR(shockwave, melee_multihit)) + { + self.swing_alreadyhit = target_victim; + continue; // move along to next trace + } + else + { + remove(self); + return; + } + } + } + + if(time >= self.cnt + meleetime) + { + // melee is finished + remove(self); + return; + } + else + { + // set up next frame + self.swing_prev = i; + self.nextthink = time; + } +} + +void W_Shockwave_Melee() +{ + sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.owner = meleetemp.realowner = self; + meleetemp.think = W_Shockwave_Melee_Think; + meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range)); +} + +// SHOCKWAVE ATTACK MODE +float W_Shockwave_Attack_CheckSpread( + vector targetorg, + vector nearest_on_line, + vector sw_shotorg, + vector attack_endpos) +{ + float spreadlimit; + float distance_of_attack = vlen(sw_shotorg - attack_endpos); + float distance_from_line = vlen(targetorg - nearest_on_line); + + spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1); + spreadlimit = + ( + (WEP_CVAR(shockwave, blast_spread_min) * (1 - spreadlimit)) + + + (WEP_CVAR(shockwave, blast_spread_max) * spreadlimit) + ); + + if( + (spreadlimit && (distance_from_line <= spreadlimit)) + && + ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90) + ) + { return bound(0, (distance_from_line / spreadlimit), 1); } + else + { return FALSE; } +} + +float W_Shockwave_Attack_IsVisible( + entity head, + vector nearest_on_line, + vector sw_shotorg, + vector attack_endpos) +{ + vector nearest_to_attacker = head.WarpZone_findradius_nearest; + vector center = (head.origin + (head.mins + head.maxs) * 0.5); + vector corner; + float i; + + // STEP ONE: Check if the nearest point is clear + if(W_Shockwave_Attack_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage + } + + // STEP TWO: Check if shotorg to center point is clear + if(W_Shockwave_Attack_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage + } + + // STEP THREE: Check each corner to see if they are clear + for(i=1; i<=8; ++i) + { + corner = get_corner_position(head, i); + if(W_Shockwave_Attack_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos)) + { + WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self); + if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage + } + } + + return FALSE; +} + +float W_Shockwave_Attack_CheckHit( + float queue, + entity head, + vector final_force, + float final_damage) +{ + if(!head) { return FALSE; } + float i; + + for(i = 0; i <= queue; ++i) + { + if(shockwave_hit[i] == head) + { + if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; } + if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; } + return FALSE; + } + } + + shockwave_hit[queue] = head; + shockwave_hit_force[queue] = final_force; + shockwave_hit_damage[queue] = final_damage; + return TRUE; +} + +void W_Shockwave_Attack() +{ + // declarations + float multiplier, multiplier_from_accuracy, multiplier_from_distance; + float final_damage; + vector final_force, center, vel; + entity head; + + float i, queue = 0; + + // set up the shot direction + W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage)); + vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance))); + WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self); + vector attack_hitpos = trace_endpos; + float distance_to_end = vlen(w_shotorg - attack_endpos); + float distance_to_hit = vlen(w_shotorg - attack_hitpos); + //entity transform = WarpZone_trace_transform; + + // do the firing effect now + //SendCSQCShockwaveParticle(attack_endpos); // WEAPONTODO + Damage_DamageInfo( + attack_hitpos, + WEP_CVAR(shockwave, blast_splash_damage), + WEP_CVAR(shockwave, blast_splash_edgedamage), + WEP_CVAR(shockwave, blast_splash_radius), + w_shotdir * WEP_CVAR(shockwave, blast_splash_force), + WEP_SHOCKWAVE, + 0, + self + ); + + // splash damage/jumping trace + head = WarpZone_FindRadius( + attack_hitpos, + max( + WEP_CVAR(shockwave, blast_splash_radius), + WEP_CVAR(shockwave, blast_jump_radius) + ), + FALSE + ); + + while(head) + { + if(head.takedamage) + { + float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); + + if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius))) + { + // ======================== + // BLAST JUMP CALCULATION + // ======================== + + // calculate importance of distance and accuracy for this attack + multiplier_from_accuracy = (1 - + (distance_to_head ? + min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius))) + : + 0 + ) + ); + multiplier_from_distance = (1 - + (distance_to_hit ? + min(1, (distance_to_hit / distance_to_end)) + : + 0 + ) + ); + multiplier = + max( + WEP_CVAR(shockwave, blast_jump_multiplier_min), + ( + (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy)) + + + (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance)) + ) + ); + + // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage + final_damage = + ( + (WEP_CVAR(shockwave, blast_jump_damage) * multiplier) + + + (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier)) + ); + + // figure out the direction of force + vel = normalize(combine_to_vector(head.velocity_x, head.velocity_y, 0)); + vel *= + ( + bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1) + * + WEP_CVAR(shockwave, blast_jump_force_velocitybias) + ); + final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel); + + // now multiply the direction by force units + final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier); + final_force_z *= WEP_CVAR(shockwave, blast_jump_force_zscale); + + // trigger damage with this calculated info + Damage( + head, + self, + self, + final_damage, + WEP_SHOCKWAVE, + head.origin, + final_force + ); + + #ifdef DEBUG_SHOCKWAVE + print(sprintf( + "SELF HIT: multiplier = %f, damage = %f, force = %f... " + "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", + multiplier, + final_damage, + vlen(final_force), + multiplier_from_accuracy, + multiplier_from_distance + )); + #endif + } + else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius)) + { + // ========================== + // BLAST SPLASH CALCULATION + // ========================== + + // calculate importance of distance and accuracy for this attack + multiplier_from_accuracy = (1 - + (distance_to_head ? + min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius))) + : + 0 + ) + ); + multiplier_from_distance = (1 - + (distance_to_hit ? + min(1, (distance_to_hit / distance_to_end)) + : + 0 + ) + ); + multiplier = + max( + WEP_CVAR(shockwave, blast_splash_multiplier_min), + ( + (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy)) + + + (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance)) + ) + ); + + // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage + final_damage = + ( + (WEP_CVAR(shockwave, blast_splash_damage) * multiplier) + + + (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier)) + ); + + // figure out the direction of force + final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias)); + final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force)); + //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); + + // now multiply the direction by force units + final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier); + final_force_z *= WEP_CVAR(shockwave, blast_force_zscale); + + // queue damage with this calculated info + if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); } + + #ifdef DEBUG_SHOCKWAVE + print(sprintf( + "SPLASH HIT: multiplier = %f, damage = %f, force = %f... " + "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", + multiplier, + final_damage, + vlen(final_force), + multiplier_from_accuracy, + multiplier_from_distance + )); + #endif + } + } + head = head.chain; + } + + // cone damage trace + head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), FALSE); + while(head) + { + if((head != self) && head.takedamage) + { + // ======================== + // BLAST CONE CALCULATION + // ======================== + + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = CENTER_OR_VIEWOFS(head); + + // find the closest point on the enemy to the center of the attack + float ang; // angle between shotdir and h + float h; // hypotenuse, which is the distance between attacker to head + float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin + + h = vlen(center - self.origin); + ang = acos(dotproduct(normalize(center - self.origin), w_shotdir)); + a = h * cos(ang); + // WEAPONTODO: replace with simpler method + + vector nearest_on_line = (w_shotorg + a * w_shotdir); + vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); + + if((vlen(head.WarpZone_findradius_dist) <= WEP_CVAR(shockwave, blast_distance)) + && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) + { + // calculate importance of distance and accuracy for this attack + multiplier_from_accuracy = (1 - + W_Shockwave_Attack_CheckSpread( + nearest_to_attacker, + nearest_on_line, + w_shotorg, + attack_endpos + ) + ); + multiplier_from_distance = (1 - + (distance_to_hit ? + min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end)) + : + 0 + ) + ); + multiplier = + max( + WEP_CVAR(shockwave, blast_multiplier_min), + ( + (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy)) + + + (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance)) + ) + ); + + // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage + final_damage = + ( + (WEP_CVAR(shockwave, blast_damage) * multiplier) + + + (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier)) + ); + + // figure out the direction of force + final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias)); + final_force = normalize(center - (nearest_on_line - final_force)); + //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); + + // now multiply the direction by force units + final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier); + final_force_z *= WEP_CVAR(shockwave, blast_force_zscale); + + // queue damage with this calculated info + if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); } + + #ifdef DEBUG_SHOCKWAVE + print(sprintf( + "BLAST HIT: multiplier = %f, damage = %f, force = %f... " + "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n", + multiplier, + final_damage, + vlen(final_force), + multiplier_from_accuracy, + multiplier_from_distance + )); + #endif + } + } + head = head.chain; + } + + for(i = 1; i <= queue; ++i) + { + head = shockwave_hit[i-1]; + final_force = shockwave_hit_force[i-1]; + final_damage = shockwave_hit_damage[i-1]; + + Damage( + head, + self, + self, + final_damage, + WEP_SHOCKWAVE, + head.origin, + final_force + ); + + if(accuracy_isgooddamage(self.realowner, head)) + { + print("wtf\n"); + accuracy_add(self.realowner, WEP_SHOCKWAVE, 0, final_damage); + } + + #ifdef DEBUG_SHOCKWAVE + print(sprintf( + "SHOCKWAVE by %s: damage = %f, force = %f.\n", + self.netname, + final_damage, + vlen(final_force) + )); + #endif + + shockwave_hit[i-1] = world; + shockwave_hit_force[i-1] = '0 0 0'; + shockwave_hit_damage[i-1] = 0; + } +} + +float W_Shockwave(float req) +{ + switch(req) + { + case WR_AIM: + { + if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range)) + { self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); } + else + { self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); } + + return TRUE; + } + case WR_THINK: + { + if(self.BUTTON_ATCK) + { + if(time >= self.shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, WEP_CVAR(shockwave, blast_animtime))) + { + W_Shockwave_Attack(); + self.shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready); + } + } + } + else if(self.BUTTON_ATCK2) + { + //if(self.clip_load >= 0) // we are not currently reloading + if(!self.crouch) // no crouchmelee please + if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire))) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee); + } + } + + return TRUE; + } + case WR_INIT: + { + precache_model("models/uziflash.md3"); + precache_model("models/weapons/g_shockwave.md3"); + precache_model("models/weapons/v_shockwave.md3"); + precache_model("models/weapons/h_shockwave.iqm"); + precache_sound("misc/itempickup.wav"); + precache_sound("weapons/shockwave_fire.wav"); + precache_sound("weapons/shockwave_melee.wav"); + SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + // shockwave has infinite ammo + return TRUE; + } + case WR_CONFIG: + { + SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOCKWAVE_MURDER_SLAP; + else + return WEAPON_SHOCKWAVE_MURDER; + } + } + return FALSE; +} +#endif +#ifdef CSQC +float W_Shockwave(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1); // WEAPONTODO: replace with proper impact effect + return TRUE; + } + case WR_INIT: + { + //precache_sound("weapons/ric1.wav"); + //precache_sound("weapons/ric2.wav"); + //precache_sound("weapons/ric3.wav"); + return FALSE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; +} +#endif +#endif diff --cc qcsrc/common/weapons/w_shotgun.qc index 87aa92aaf,000000000..8353442fb mode 100644,000000..100644 --- a/qcsrc/common/weapons/w_shotgun.qc +++ b/qcsrc/common/weapons/w_shotgun.qc @@@ -1,329 -1,0 +1,329 @@@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ SHOTGUN, +/* function */ W_Shotgun, +/* ammotype */ ammo_none, +/* impulse */ 2, +/* flags */ WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED, +/* rating */ BOT_PICKUP_RATING_LOW, +/* color */ '0.5 0.25 0', +/* modelname */ "shotgun", +/* simplemdl */ "foobar", +/* crosshair */ "gfx/crosshairshotgun 0.65", +/* wepimg */ "weaponshotgun", +/* refname */ "shotgun", +/* wepname */ _("Shotgun") +); + +#define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun) +#define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \ + w_cvar(id, sn, PRI, ammo) \ + w_cvar(id, sn, BOTH, animtime) \ + w_cvar(id, sn, BOTH, refire) \ + w_cvar(id, sn, PRI, bullets) \ + w_cvar(id, sn, BOTH, damage) \ + w_cvar(id, sn, BOTH, force) \ + w_cvar(id, sn, PRI, solidpenetration) \ + w_cvar(id, sn, PRI, spread) \ + w_cvar(id, sn, NONE, secondary) \ + w_cvar(id, sn, SEC, melee_time) \ + w_cvar(id, sn, SEC, melee_no_doubleslap) \ + w_cvar(id, sn, SEC, melee_traces) \ + w_cvar(id, sn, SEC, melee_swing_up) \ + w_cvar(id, sn, SEC, melee_swing_side) \ + w_cvar(id, sn, SEC, melee_nonplayerdamage) \ + w_cvar(id, sn, SEC, melee_multihit) \ + w_cvar(id, sn, SEC, melee_delay) \ + w_cvar(id, sn, SEC, melee_range) \ + w_prop(id, sn, float, reloading_ammo, reload_ammo) \ + w_prop(id, sn, float, reloading_time, reload_time) \ + w_prop(id, sn, float, switchdelay_raise, switchdelay_raise) \ + w_prop(id, sn, float, switchdelay_drop, switchdelay_drop) \ + w_prop(id, sn, string, weaponreplace, weaponreplace) \ + w_prop(id, sn, float, weaponstart, weaponstart) \ + w_prop(id, sn, float, weaponstartoverride, weaponstartoverride) \ + w_prop(id, sn, float, weaponthrowable, weaponthrowable) + +#ifdef SVQC +SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) +#endif +#else +#ifdef SVQC +void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN); } + +void W_Shotgun_Attack (void) +{ + float sc; + entity flash; + + W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo)); + + W_SetupShot (self, TRUE, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets)); + for (sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1) + fireBullet(w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN, 0); + + pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo)); + + // casing code + if (autocvar_g_casings >= 1) + for (sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);sc = sc + 1) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self); + + // muzzle flash for 1st person view + flash = spawn(); + setmodel(flash, "models/uziflash.md3"); // precision set below + flash.think = SUB_Remove; + flash.nextthink = time + 0.06; + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); +} + +.float swing_prev; +.entity swing_alreadyhit; +void W_Shotgun_Melee_Think() +{ + // declarations + float i, f, swing, swing_factor, swing_damage, meleetime, is_player; + entity target_victim; + vector targpos; + + if(!self.cnt) // set start time of melee + { + self.cnt = time; + W_PlayStrengthSound(self.realowner); + } + + makevectors(self.realowner.v_angle); // update values for v_* vectors + + // calculate swing percentage based on time + meleetime = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces)); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, melee_no_doubleslap)) + { + remove(self); + return; + } + + // if okay, perform the traces needed for this frame + for(i=self.swing_prev; i < f; ++i) + { + swing_factor = ((1 - (i / WEP_CVAR_SEC(shotgun, melee_traces))) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * WEP_CVAR_SEC(shotgun, melee_range)) + + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up)) + + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_side))); + + WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner)); + + // draw lightning beams for debugging + //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); + //te_customflash(targpos, 40, 2, '1 1 1'); + - is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body"); ++ is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER)); + + if((trace_fraction < 1) // if trace is good, apply the damage and remove self + && (trace_ent.takedamage == DAMAGE_AIM) + && (trace_ent != self.swing_alreadyhit) + && (is_player || WEP_CVAR_SEC(shotgun, melee_nonplayerdamage))) + { + target_victim = trace_ent; // so it persists through other calls + + if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught. + swing_damage = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1)); + else + swing_damage = (WEP_CVAR_SEC(shotgun, melee_nonplayerdamage) * min(1, swing_factor + 1)); + + //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n")); + + Damage(target_victim, self.realowner, self.realowner, + swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY, + self.realowner.origin + self.realowner.view_ofs, + v_forward * WEP_CVAR_SEC(shotgun, force)); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); } + + // draw large red flash for debugging + //te_customflash(targpos, 200, 2, '15 0 0'); + + if(WEP_CVAR_SEC(shotgun, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice. + { + self.swing_alreadyhit = target_victim; + continue; // move along to next trace + } + else + { + remove(self); + return; + } + } + } + + if(time >= self.cnt + meleetime) + { + // melee is finished + remove(self); + return; + } + else + { + // set up next frame + self.swing_prev = i; + self.nextthink = time; + } +} + +void W_Shotgun_Attack2 (void) +{ + sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTEN_NORM); + weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.realowner = self; + meleetemp.think = W_Shotgun_Melee_Think; + meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range)); +} + +.float shotgun_primarytime; + +float W_Shotgun(float req) +{ + switch(req) + { + case WR_AIM: + { + if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range)) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + + return TRUE; + } + case WR_THINK: + { + if(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload + { + // don't force reload an empty shotgun if its melee attack is active + if (!WEP_CVAR(shotgun, secondary)) + WEP_ACTION(self.weapon, WR_RELOAD); + } + else + { + if (self.BUTTON_ATCK) + { + if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary + { + if(weapon_prepareattack(0, WEP_CVAR_PRI(shotgun, animtime))) + { + W_Shotgun_Attack(); + self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready); + } + } + } + } + if (self.clip_load >= 0) // we are not currently reloading + if (!self.crouch) // no crouchmelee please + if (self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary)) + if (weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, refire))) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2); + } + + return TRUE; + } + case WR_INIT: + { + precache_model ("models/uziflash.md3"); + precache_model ("models/weapons/g_shotgun.md3"); + precache_model ("models/weapons/v_shotgun.md3"); + precache_model ("models/weapons/h_shotgun.iqm"); + precache_sound ("misc/itempickup.wav"); + precache_sound ("weapons/shotgun_fire.wav"); + precache_sound ("weapons/shotgun_melee.wav"); + SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP) + return TRUE; + } + case WR_SETUP: + { + self.ammo_field = ammo_none; + return TRUE; + } + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + // shotgun has infinite ammo + return TRUE; + } + case WR_CONFIG: + { + SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS) + return TRUE; + } + case WR_RELOAD: + { + W_Reload(WEP_CVAR_PRI(shotgun, ammo), "weapons/reload.wav"); // WEAPONTODO + return TRUE; + } + case WR_SUICIDEMESSAGE: + { + return WEAPON_THINKING_WITH_PORTALS; + } + case WR_KILLMESSAGE: + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOTGUN_MURDER_SLAP; + else + return WEAPON_SHOTGUN_MURDER; + } + } + return FALSE; +} +#endif +#ifdef CSQC +.float prevric; +float W_Shotgun(float req) +{ + switch(req) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent && time - self.prevric > 0.25) + { + if(w_random < 0.0165) + sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTEN_NORM); + else if(w_random < 0.033) + sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTEN_NORM); + else if(w_random < 0.05) + sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTEN_NORM); + self.prevric = time; + } + + return TRUE; + } + case WR_INIT: + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + return TRUE; + } + case WR_ZOOMRETICLE: + { + // no weapon specific image for this weapon + return FALSE; + } + } + return FALSE; +} +#endif +#endif diff --cc qcsrc/menu/progs.src index 61e3ecba8,86cb9621c..9d2f69333 --- a/qcsrc/menu/progs.src +++ b/qcsrc/menu/progs.src @@@ -50,7 -51,8 +51,8 @@@ xonotic/util.q ../common/campaign_file.qc ../common/campaign_setup.qc ../common/mapinfo.qc -../common/items.qc +../common/weapons/weapons.qc // TODO ../common/urllib.qc + ../common/monsters/monsters.qc ../warpzonelib/mathlib.qc diff --cc qcsrc/server/autocvars.qh index 1dc2d08b3,fc9181d56..f406238b8 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@@ -117,7 -336,54 +117,6 @@@ float autocvar_g_balance_health_regenst float autocvar_g_balance_health_rot; float autocvar_g_balance_health_rotlinear; float autocvar_g_balance_health_rotstable; - float autocvar_g_balance_health_start; -float autocvar_g_balance_hlac_primary_ammo; -float autocvar_g_balance_hlac_primary_animtime; -float autocvar_g_balance_hlac_primary_damage; -float autocvar_g_balance_hlac_primary_edgedamage; -float autocvar_g_balance_hlac_primary_force; -float autocvar_g_balance_hlac_primary_lifetime; -float autocvar_g_balance_hlac_primary_radius; -float autocvar_g_balance_hlac_primary_refire; -float autocvar_g_balance_hlac_primary_speed; -float autocvar_g_balance_hlac_primary_spread_add; -float autocvar_g_balance_hlac_primary_spread_crouchmod; -float autocvar_g_balance_hlac_primary_spread_max; -float autocvar_g_balance_hlac_primary_spread_min; -float autocvar_g_balance_hlac_secondary; -float autocvar_g_balance_hlac_secondary_ammo; -float autocvar_g_balance_hlac_secondary_animtime; -float autocvar_g_balance_hlac_secondary_damage; -float autocvar_g_balance_hlac_secondary_edgedamage; -float autocvar_g_balance_hlac_secondary_force; -float autocvar_g_balance_hlac_secondary_lifetime; -float autocvar_g_balance_hlac_secondary_radius; -float autocvar_g_balance_hlac_secondary_refire; -float autocvar_g_balance_hlac_secondary_shots; -float autocvar_g_balance_hlac_secondary_speed; -float autocvar_g_balance_hlac_secondary_spread; -float autocvar_g_balance_hlac_secondary_spread_crouchmod; -float autocvar_g_balance_hlac_reload_ammo; -float autocvar_g_balance_hlac_reload_time; -float autocvar_g_balance_hook_primary_animtime; -float autocvar_g_balance_hook_primary_fuel; -float autocvar_g_balance_hook_primary_hooked_fuel; -float autocvar_g_balance_hook_primary_hooked_time_free; -float autocvar_g_balance_hook_primary_hooked_time_max; -float autocvar_g_balance_hook_primary_refire; -float autocvar_g_balance_hook_secondary_ammo; -float autocvar_g_balance_hook_secondary_animtime; -float autocvar_g_balance_hook_secondary_damage; -float autocvar_g_balance_hook_secondary_duration; -float autocvar_g_balance_hook_secondary_edgedamage; -float autocvar_g_balance_hook_secondary_force; -float autocvar_g_balance_hook_secondary_gravity; -float autocvar_g_balance_hook_secondary_lifetime; -float autocvar_g_balance_hook_secondary_power; -float autocvar_g_balance_hook_secondary_radius; -float autocvar_g_balance_hook_secondary_refire; -float autocvar_g_balance_hook_secondary_speed; -float autocvar_g_balance_hook_secondary_health; -float autocvar_g_balance_hook_secondary_damageforcescale; float autocvar_g_balance_keyhunt_damageforcescale; float autocvar_g_balance_keyhunt_delay_collect; float autocvar_g_balance_keyhunt_delay_return; diff --cc qcsrc/server/progs.src index 62e33f42e,1ae22e202..9198c2411 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@@ -23,22 -26,10 +23,25 @@@ sys-post.q ../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 @@@ -234,6 -227,13 +238,11 @@@ playerstats.q round_handler.qc -../common/explosion_equation.qc - + ../common/monsters/sv_monsters.qc + ../common/monsters/monsters.qc + + ../common/monsters/spawn.qc + mutators/base.qc mutators/gamemode_assault.qc mutators/gamemode_ca.qc diff --cc qcsrc/server/vehicles/raptor.qc index 50dc32bc6,a6ef52649..53e4d35f7 --- a/qcsrc/server/vehicles/raptor.qc +++ b/qcsrc/server/vehicles/raptor.qc @@@ -690,8 -695,10 +695,11 @@@ void raptor_blowup( { self.deadflag = DEAD_DEAD; self.vehicle_exit(VHEF_NORMAL); + - RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_RAPT_DEATH, world); + RadiusDamage(self, self.enemy, autocvar_g_vehicle_raptor_blowup_coredamage, + autocvar_g_vehicle_raptor_blowup_edgedamage, - autocvar_g_vehicle_raptor_blowup_radius, world, ++ autocvar_g_vehicle_raptor_blowup_radius, world, world, + autocvar_g_vehicle_raptor_blowup_forceintensity, DEATH_VH_RAPT_DEATH, world); self.alpha = -1; self.movetype = MOVETYPE_NONE; diff --cc qcsrc/server/vehicles/spiderbot.qc index 48e2bc277,1085a904e..d78ae30a2 --- a/qcsrc/server/vehicles/spiderbot.qc +++ b/qcsrc/server/vehicles/spiderbot.qc @@@ -718,7 -723,10 +723,10 @@@ void spiderbot_blowup( SUB_SetFade(g1, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10)); SUB_SetFade(g2, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10)); - RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_SPID_DEATH, world); + RadiusDamage(self, self.enemy, autocvar_g_vehicle_spiderbot_blowup_coredamage, + autocvar_g_vehicle_spiderbot_blowup_edgedamage, - autocvar_g_vehicle_spiderbot_blowup_radius, world, ++ autocvar_g_vehicle_spiderbot_blowup_radius, world, world, + autocvar_g_vehicle_spiderbot_blowup_forceintensity, DEATH_VH_SPID_DEATH, world); self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = -1; self.movetype = MOVETYPE_NONE; diff --cc qcsrc/server/weapons/accuracy.qc index 5b22669ce,000000000..09d42332a mode 100644,000000..100644 --- a/qcsrc/server/weapons/accuracy.qc +++ b/qcsrc/server/weapons/accuracy.qc @@@ -1,120 -1,0 +1,124 @@@ +float accuracy_byte(float n, float d) +{ + //printf("accuracy: %d / %d\n", n, d); + if(n <= 0) + return 0; + if(n > d) + return 255; + return 1 + rint(n * 100.0 / d); +} + +float accuracy_send(entity to, float sf) +{ + float w, f; + entity a; + WriteByte(MSG_ENTITY, ENT_CLIENT_ACCURACY); + + a = self.owner; + if(IS_SPEC(a)) + a = a.enemy; + a = a.accuracy; + + if(to != a.owner) + if (!(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share)) + sf = 0; + // note: zero sendflags can never be sent... so we can use that to say that we send no accuracy! + WriteInt24_t(MSG_ENTITY, sf); + if(sf == 0) + return TRUE; + // note: we know that client and server agree about SendFlags... + for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w) + { + if(sf & f) + WriteByte(MSG_ENTITY, accuracy_byte(self.(accuracy_hit[w]), self.(accuracy_fired[w]))); + if(f == 0x800000) + f = 1; + else + f *= 2; + } + return TRUE; +} + +// init/free +void accuracy_init(entity e) +{ + e.accuracy = spawn(); + e.accuracy.owner = e; + e.accuracy.classname = "accuracy"; + e.accuracy.drawonlytoclient = e; + Net_LinkEntity(e.accuracy, FALSE, 0, accuracy_send); +} + +void accuracy_free(entity e) +{ + remove(e.accuracy); +} + +// force a resend of a player's accuracy stats +void accuracy_resend(entity e) +{ + e.accuracy.SendFlags = 0xFFFFFF; +} + +// update accuracy stats +.float hit_time; +.float fired_time; + +void accuracy_add(entity e, float w, float fired, float hit) +{ + entity a; + float b; + if(IS_INDEPENDENT_PLAYER(e)) + return; + a = e.accuracy; + if(!a || !(hit || fired)) + return; + w -= WEP_FIRST; + b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])); + if(hit) + a.(accuracy_hit[w]) += hit; + if(fired) + a.(accuracy_fired[w]) += fired; + + if(hit && a.hit_time != time) // only run this once per frame + { + a.(accuracy_cnt_hit[w]) += 1; + a.hit_time = time; + } + + if(fired && a.fired_time != time) // only run this once per frame + { + a.(accuracy_cnt_fired[w]) += 1; + a.fired_time = time; + } + + if(b == accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w]))) + return; + w = pow(2, mod(w, 24)); + a.SendFlags |= w; + FOR_EACH_CLIENT(a) + if(IS_SPEC(a)) + if(a.enemy == e) + a.SendFlags |= w; +} + +float accuracy_isgooddamage(entity attacker, entity targ) +{ ++ frag_attacker = attacker; ++ frag_target = targ; ++ float mutator_check = MUTATOR_CALLHOOK(AccuracyTargetValid); ++ + if(!warmup_stage) - if(IS_CLIENT(targ)) + if(targ.deadflag == DEAD_NO) ++ if(mutator_check == MUT_ACCADD_INVALID || (mutator_check == MUT_ACCADD_VALID && IS_CLIENT(targ))) + if(DIFF_TEAM(attacker, targ)) + return TRUE; + return FALSE; +} + +float accuracy_canbegooddamage(entity attacker) +{ + if(!warmup_stage) + return TRUE; + return FALSE; +}