From: Samual Lenks Date: Mon, 10 Jun 2013 21:53:20 +0000 (-0400) Subject: Being renaming weapons X-Git-Tag: xonotic-v0.8.0~152^2~400 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=f802eeb7f635fbd4ab86cdae9ae1295f3350b9a7;p=xonotic%2Fxonotic-data.pk3dir.git Being renaming weapons --- diff --git a/qcsrc/common/weapons/arc.qc b/qcsrc/common/weapons/arc.qc new file mode 100644 index 000000000..e4b7230d6 --- /dev/null +++ b/qcsrc/common/weapons/arc.qc @@ -0,0 +1,296 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ LIGHTNING, +/* function */ w_lightning, +/* ammotype */ IT_CELLS, +/* impulse */ 5, +/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "lightning", +/* shortname */ "lightning", +/* fullname */ _("Lightning") +); +#else +#ifdef SVQC + +// Declarations ========================= +.vector hook_start, hook_end; // used for beam +.entity lightning_beam; // used for beam +.float BUTTON_ATCK_prev; // for better animation control +.float lg_fire_prev; // for better animation control + +// Lightning functions ========================= +float W_Lightning_Beam_Send(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_LIGHTNING_BEAM); + sf = sf & 0x7F; + if(sound_allowed(MSG_BROADCAST, self.owner)) + sf |= 0x80; + WriteByte(MSG_ENTITY, sf); + if(sf & 1) + { + WriteByte(MSG_ENTITY, num_for_edict(self.owner)); + WriteCoord(MSG_ENTITY, autocvar_g_balance_lightning_primary_range); + } + if(sf & 2) + { + WriteCoord(MSG_ENTITY, self.hook_start_x); + WriteCoord(MSG_ENTITY, self.hook_start_y); + WriteCoord(MSG_ENTITY, self.hook_start_z); + } + if(sf & 4) + { + WriteCoord(MSG_ENTITY, self.hook_end_x); + WriteCoord(MSG_ENTITY, self.hook_end_y); + WriteCoord(MSG_ENTITY, self.hook_end_z); + } + return TRUE; +} + +void W_Lightning_Beam_Think() +{ + self.owner.lg_fire_prev = time; + if (self != self.owner.lightning_beam) + { + remove(self); + return; + } + if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen) + { + if(self == self.owner.lightning_beam) + self.owner.lightning_beam = world; + remove(self); + return; + } + + self.nextthink = time; + + makevectors(self.owner.v_angle); + + float dt, f; + dt = frametime; + if not(self.owner.items & IT_UNLIMITED_WEAPON_AMMO) + { + if(autocvar_g_balance_lightning_primary_ammo) + { + dt = min(dt, self.owner.ammo_cells / autocvar_g_balance_lightning_primary_ammo); + self.owner.ammo_cells = max(0, self.owner.ammo_cells - autocvar_g_balance_lightning_primary_ammo * frametime); + } + } + + W_SetupShot_Range(self.owner, TRUE, 0, "", 0, autocvar_g_balance_lightning_primary_damage * dt, autocvar_g_balance_lightning_primary_range); + WarpZone_traceline_antilag(self.owner, w_shotorg, w_shotend, MOVE_NORMAL, self.owner, ANTILAG_LATENCY(self.owner)); + + // apply the damage + if(trace_ent) + { + vector force; + force = w_shotdir * autocvar_g_balance_lightning_primary_force; + + f = ExponentialFalloff(autocvar_g_balance_lightning_primary_falloff_mindist, autocvar_g_balance_lightning_primary_falloff_maxdist, autocvar_g_balance_lightning_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); + + if(accuracy_isgooddamage(self.owner, trace_ent)) + accuracy_add(self.owner, WEP_LIGHTNING, 0, autocvar_g_balance_lightning_primary_damage * dt * f); + Damage (trace_ent, self.owner, self.owner, autocvar_g_balance_lightning_primary_damage * dt * f, WEP_LIGHTNING, trace_endpos, force * dt); + } + + // draw effect + if(w_shotorg != self.hook_start) + { + self.SendFlags |= 2; + self.hook_start = w_shotorg; + } + if(w_shotend != self.hook_end) + { + self.SendFlags |= 4; + self.hook_end = w_shotend; + } +} + +// Attack functions ========================= +void W_Lightning_Attack1 (void) +{ + // only play fire sound if 0.5 sec has passed since player let go the fire button + if(time - self.lg_fire_prev > 0.5) + sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); + + entity beam, oldself; + + self.lightning_beam = beam = spawn(); + beam.classname = "W_Lightning_Beam"; + beam.solid = SOLID_NOT; + beam.think = W_Lightning_Beam_Think; + beam.owner = self; + beam.movetype = MOVETYPE_NONE; + beam.shot_spread = 1; + beam.bot_dodge = TRUE; + beam.bot_dodgerating = autocvar_g_balance_lightning_primary_damage; + Net_LinkEntity(beam, FALSE, 0, W_Lightning_Beam_Send); + + oldself = self; + self = beam; + self.think(); + self = oldself; +} + +float w_lightning(float req) +{ + if (req == WR_AIM) + { + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + /* + self.BUTTON_ATCK=FALSE; + self.BUTTON_ATCK2=FALSE; + if(vlen(self.origin-self.enemy.origin) > 1000) + self.bot_aim_whichfiretype = 0; + if(self.bot_aim_whichfiretype == 0) + { + float shoot; + + if(autocvar_g_balance_lightning_primary_speed) + shoot = bot_aim(autocvar_g_balance_lightning_primary_speed, 0, autocvar_g_balance_lightning_primary_lifetime, FALSE); + else + shoot = bot_aim(1000000, 0, 0.001, FALSE); + + if(shoot) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.01) self.bot_aim_whichfiretype = 1; + } + } + else // todo + { + //if(bot_aim(autocvar_g_balance_lightning_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_lightning_secondary_lifetime, TRUE)) + //{ + // self.BUTTON_ATCK2 = TRUE; + // if(random() < 0.03) self.bot_aim_whichfiretype = 0; + //} + } + */ + } + else if (req == WR_THINK) + { + if (self.BUTTON_ATCK) + { + if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this! + /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y) + weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_lightning_primary_animtime, w_ready); + else*/ + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); + + if (weapon_prepareattack(0, 0)) + { + if ((!self.lightning_beam) || wasfreed(self.lightning_beam)) + W_Lightning_Attack1(); + + if(!self.BUTTON_ATCK_prev) + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); + self.BUTTON_ATCK_prev = 1; + } + } + } + else // todo + { + if (self.BUTTON_ATCK_prev != 0) + { + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); + ATTACK_FINISHED(self) = time + autocvar_g_balance_lightning_primary_refire * W_WeaponRateFactor(); + } + self.BUTTON_ATCK_prev = 0; + } + + //if (self.BUTTON_ATCK2) + //if (weapon_prepareattack(1, autocvar_g_balance_lightning_secondary_refire)) + //{ + // W_Lightning_Attack2(); + // self.lightning_count = autocvar_g_balance_lightning_secondary_count; + // weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_lightning_secondary_animtime, w_lightning_checkattack); + // self.lightning_secondarytime = time + autocvar_g_balance_lightning_secondary_refire2 * W_WeaponRateFactor(); + //} + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_lightning.md3"); + precache_model ("models/weapons/v_lightning.md3"); + precache_model ("models/weapons/h_lightning.iqm"); + //precache_sound ("weapons/lightning_bounce.wav"); + precache_sound ("weapons/lightning_fire.wav"); + precache_sound ("weapons/lightning_fire2.wav"); + precache_sound ("weapons/lightning_impact.wav"); + //precache_sound ("weapons/lightning_impact_combo.wav"); + //precache_sound ("weapons/W_Lightning_Beam_fire.wav"); + } + else if (req == WR_SETUP) + weapon_setup(WEP_LIGHTNING); + else if (req == WR_CHECKAMMO1) + { + return !autocvar_g_balance_lightning_primary_ammo || (self.ammo_cells > 0); + } + else if (req == WR_CHECKAMMO2) + return self.ammo_cells >= autocvar_g_balance_lightning_secondary_ammo; + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + { + return WEAPON_ELECTRO_MURDER_ORBS; + } + else + { + if(w_deathtype & HITTYPE_BOUNCE) + return WEAPON_ELECTRO_MURDER_COMBO; + else + return WEAPON_ELECTRO_MURDER_BOLT; + } + } + else if (req == WR_RESETPLAYER) + { + //self.lightning_secondarytime = time; + } + return TRUE; +}; + +void LightningInit() +{ + weapon_action(WEP_LIGHTNING, WR_PRECACHE); + lightning_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 1); + lightning_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 2); + lightning_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 3); + lightning_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 4); +} + +void spawnfunc_weapon_lightning (void) // should this really be here? +{ + weapon_defaultspawnfunc(WEP_LIGHTNING); +} +#endif +#ifdef CSQC +float w_lightning(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 6; + + if(w_deathtype & HITTYPE_SECONDARY) + { + pointparticles(particleeffectnum("lightning_ballexplode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); + } + else + { + pointparticles(particleeffectnum("lightning_impact"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/lightning_impact.wav"); + precache_sound("weapons/lightning_impact_combo.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/arc.qh b/qcsrc/common/weapons/arc.qh new file mode 100644 index 000000000..57d6ceb0d --- /dev/null +++ b/qcsrc/common/weapons/arc.qh @@ -0,0 +1,2 @@ +void LightningInit(); +vector lightning_shotorigin[4]; diff --git a/qcsrc/common/weapons/blaster.qc b/qcsrc/common/weapons/blaster.qc new file mode 100644 index 000000000..c331c5b52 --- /dev/null +++ b/qcsrc/common/weapons/blaster.qc @@ -0,0 +1,576 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ LASER, +/* function */ W_Laser, +/* ammotype */ 0, +/* impulse */ 1, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ 0, +/* model */ "laser", +/* shortname */ "laser", +/* fullname */ _("Blaster") +); +#else +#ifdef SVQC +void(float imp) W_SwitchWeapon; +void() W_LastWeapon; +.float swing_prev; +.entity swing_alreadyhit; + +void SendCSQCShockwaveParticle(vector endpos) +{ + //endpos = WarpZone_UnTransformOrigin(transform, endpos); + + WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); + WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE); + WriteCoord(MSG_BROADCAST, w_shotorg_x); + WriteCoord(MSG_BROADCAST, w_shotorg_y); + WriteCoord(MSG_BROADCAST, w_shotorg_z); + WriteCoord(MSG_BROADCAST, endpos_x); + WriteCoord(MSG_BROADCAST, endpos_y); + WriteCoord(MSG_BROADCAST, endpos_z); + WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_max, 255)); + WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_min, 255)); + WriteByte(MSG_BROADCAST, num_for_edict(self)); +} + +void W_Laser_Touch() +{ + PROJECTILE_TOUCH; + + self.event_damage = func_null; + + if(self.dmg) + RadiusDamage(self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other); + else + RadiusDamage(self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other); + + remove(self); +} + +void W_Laser_Think() +{ + self.movetype = MOVETYPE_FLY; + self.think = SUB_Remove; + + if(self.dmg) + self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime; + else + self.nextthink = time + autocvar_g_balance_laser_primary_lifetime; + + CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE); +} + + +float W_Laser_Shockwave_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 = (autocvar_g_balance_laser_shockwave_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_shockwave_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_Laser_Shockwave_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_Laser_Shockwave_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_Laser_Shockwave_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_Laser_Shockwave_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; +} + +#define PLAYER_CENTER(ent) (ent.origin + ((ent.classname == "player") ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5))) + +entity shockwave_hit[32]; +float shockwave_hit_damage[32]; +vector shockwave_hit_force[32]; + +float W_Laser_Shockwave_CheckHit(float queue, entity head, vector final_force, float final_damage) +{ + if not(head) { return FALSE; } + float i; + + ++queue; + + for(i = 1; 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_Laser_Shockwave() +{ + // declarations + float multiplier, multiplier_from_accuracy, multiplier_from_distance; + float final_damage; //, final_spread; + vector final_force, center, vel; + entity head, next; + + float i, queue = 0; + + // set up the shot direction + W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_shockwave_damage); + vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_shockwave_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); + Damage_DamageInfo(attack_hitpos, autocvar_g_balance_laser_shockwave_splash_damage, autocvar_g_balance_laser_shockwave_splash_edgedamage, autocvar_g_balance_laser_shockwave_splash_radius, w_shotdir * autocvar_g_balance_laser_shockwave_splash_force, WEP_LASER, 0, self); + + // splash damage/jumping trace + head = WarpZone_FindRadius(attack_hitpos, max(autocvar_g_balance_laser_shockwave_splash_radius, autocvar_g_balance_laser_shockwave_jump_radius), FALSE); + while(head) + { + next = head.chain; + + if(head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = PLAYER_CENTER(head); + + float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); + + if((head == self) && (distance_to_head <= autocvar_g_balance_laser_shockwave_jump_radius)) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_jump_radius)) : 0)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); + multiplier = max(autocvar_g_balance_laser_shockwave_jump_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_jump_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_jump_multiplier_distance))); + + final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_shockwave_jump_force) * multiplier); + vel = head.velocity; vel_z = 0; + vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * autocvar_g_balance_laser_shockwave_jump_force_velocitybias; + final_force = (vlen(final_force) * normalize(normalize(final_force) + vel)); + final_force_z *= autocvar_g_balance_laser_shockwave_jump_force_zscale; + final_damage = (autocvar_g_balance_laser_shockwave_jump_damage * multiplier + autocvar_g_balance_laser_shockwave_jump_edgedamage * (1 - multiplier)); + + Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); + //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + else if (distance_to_head <= autocvar_g_balance_laser_shockwave_splash_radius) + { + multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_splash_radius)) : 0)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); + multiplier = max(autocvar_g_balance_laser_shockwave_splash_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_splash_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_splash_multiplier_distance))); + + final_force = normalize(center - (attack_hitpos - (w_shotdir * autocvar_g_balance_laser_shockwave_splash_force_forwardbias))); + //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); + final_force = ((final_force * autocvar_g_balance_laser_shockwave_splash_force) * multiplier); + final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; + final_damage = (autocvar_g_balance_laser_shockwave_splash_damage * multiplier + autocvar_g_balance_laser_shockwave_splash_edgedamage * (1 - multiplier)); + + if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + } + head = next; + } + + // cone damage trace + head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_shockwave_distance, FALSE); + while(head) + { + next = head.chain; + + if((head != self) && head.takedamage) + { + // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) + center = PLAYER_CENTER(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); + + vector nearest_on_line = (w_shotorg + a * w_shotdir); + vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); + float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this + + if((distance_to_target <= autocvar_g_balance_laser_shockwave_distance) + && (W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) + { + multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos)); + multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0)); + multiplier = max(autocvar_g_balance_laser_shockwave_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_multiplier_distance))); + + final_force = normalize(center - (nearest_on_line - (w_shotdir * autocvar_g_balance_laser_shockwave_force_forwardbias))); + //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); + final_force = ((final_force * autocvar_g_balance_laser_shockwave_force) * multiplier); + final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; + final_damage = (autocvar_g_balance_laser_shockwave_damage * multiplier + autocvar_g_balance_laser_shockwave_edgedamage * (1 - multiplier)); + + if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } + //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); + } + } + head = next; + } + + for(i = 1; i <= queue; ++i) + { + head = shockwave_hit[i]; + final_force = shockwave_hit_force[i]; + final_damage = shockwave_hit_damage[i]; + + Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); + print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n"); + + shockwave_hit[i] = world; + shockwave_hit_force[i] = '0 0 0'; + shockwave_hit_damage[i] = 0; + } + //print("queue was ", ftos(queue), ".\n\n"); +} + +void W_Laser_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 = autocvar_g_balance_laser_melee_time * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * autocvar_g_balance_laser_melee_traces); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_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 / autocvar_g_balance_laser_melee_traces)) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * autocvar_g_balance_laser_melee_range) + + (v_up * swing_factor * autocvar_g_balance_laser_melee_swing_up) + + (v_right * swing_factor * autocvar_g_balance_laser_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"); + + 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 || autocvar_g_balance_laser_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 = (autocvar_g_balance_laser_melee_damage * min(1, swing_factor + 1)); + else + swing_damage = (autocvar_g_balance_laser_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_LASER | HITTYPE_SECONDARY, + self.realowner.origin + self.realowner.view_ofs, + v_forward * autocvar_g_balance_laser_melee_force); + + if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); } + + if(autocvar_g_balance_laser_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_Laser_Melee() +{ + sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_melee_animtime, w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.owner = meleetemp.realowner = self; + meleetemp.think = W_Laser_Melee_Think; + meleetemp.nextthink = time + autocvar_g_balance_laser_melee_delay * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_melee_damage, autocvar_g_balance_laser_melee_range); +} + +void W_Laser_Attack(float issecondary) +{ + entity missile; + vector s_forward; + float a; + + a = autocvar_g_balance_laser_primary_shotangle; + s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD); + + //if(nodamage) + // W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0); + /*else*/if(issecondary == 1) + W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage); + else + W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage); + pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = spawn(); + missile.owner = missile.realowner = self; + missile.classname = "laserbolt"; + missile.dmg = 0; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage; + + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_LASER; + + setorigin(missile, w_shotorg); + setsize(missile, '0 0 0', '0 0 0'); + + W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary); + missile.angles = vectoangles(missile.velocity); + //missile.glow_color = 250; // 244, 250 + //missile.glow_size = 120; + missile.touch = W_Laser_Touch; + + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + missile.think = W_Laser_Think; + missile.nextthink = time + autocvar_g_balance_laser_primary_delay; + + other = missile; MUTATOR_CALLHOOK(EditProjectile); + + if(time >= missile.nextthink) + { + entity oldself; + oldself = self; + self = missile; + self.think(); + self = oldself; + } +} + +void spawnfunc_weapon_laser(void) +{ + weapon_defaultspawnfunc(WEP_LASER); +} + +float W_Laser(float request) +{ + switch(request) + { + case WR_AIM: + { + if((autocvar_g_balance_laser_secondary == 2) && (vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_laser_melee_range)) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE); + return TRUE; + } + + case WR_THINK: + { + if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if(self.BUTTON_ATCK) + { + if(weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire)) + { + W_DecreaseAmmo(ammo_none, 1, TRUE); + + if not(autocvar_g_balance_laser_primary) + W_Laser_Shockwave(); + else + W_Laser_Attack(FALSE); + + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready); + } + } + else if(self.BUTTON_ATCK2) + { + switch(autocvar_g_balance_laser_secondary) + { + case 0: // switch to last used weapon + { + if(self.switchweapon == WEP_LASER) // don't do this if already switching + W_LastWeapon(); + + break; + } + + case 1: // normal projectile secondary + { + if(weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire)) + { + W_DecreaseAmmo(ammo_none, 1, TRUE); + W_Laser_Attack(TRUE); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready); + } + + break; + } + + case 2: // melee attack secondary + { + if(!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense + if(weapon_prepareattack(1, autocvar_g_balance_laser_melee_refire)) + { + // attempt forcing playback of the anim by switching to another anim (that we never play) here... + W_Laser_Melee(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_melee_animtime, w_ready); + } + } + } + } + return TRUE; + } + + case WR_PRECACHE: + { + precache_model("models/weapons/g_laser.md3"); + precache_model("models/weapons/v_laser.md3"); + precache_model("models/weapons/h_laser.iqm"); + precache_sound("weapons/lasergun_fire.wav"); + return TRUE; + } + + case WR_SETUP: + { + weapon_setup(WEP_LASER); + self.current_ammo = ammo_none; + return TRUE; + } + + case WR_CHECKAMMO1: + case WR_CHECKAMMO2: + { + return TRUE; // laser has infinite ammo + } + + case WR_RELOAD: + { + W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav"); + return TRUE; + } + + case WR_SUICIDEMESSAGE: + { + return WEAPON_LASER_SUICIDE; + } + + case WR_KILLMESSAGE: + { + return WEAPON_LASER_MURDER; + } + } + + return TRUE; +} +#endif +#ifdef CSQC +float W_Laser(float request) +{ + switch(request) + { + case WR_IMPACTEFFECT: + { + vector org2; + org2 = w_org + w_backoff * 6; + pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent) { sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); } + return TRUE; + } + + case WR_PRECACHE: + { + precache_sound("weapons/laserimpact.wav"); + return TRUE; + } + } + + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/devastator.qc b/qcsrc/common/weapons/devastator.qc new file mode 100644 index 000000000..504167cda --- /dev/null +++ b/qcsrc/common/weapons/devastator.qc @@ -0,0 +1,491 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ ROCKET_LAUNCHER, +/* function */ w_rlauncher, +/* ammotype */ IT_ROCKETS, +/* impulse */ 9, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_HIGH, +/* model */ "rl", +/* shortname */ "rocketlauncher", +/* fullname */ _("Rocket Launcher") +); +#else +#ifdef SVQC +.float rl_release; +.float rl_detonate_later; + +void W_Rocket_Unregister() +{ + if(self.realowner && self.realowner.lastrocket == self) + { + self.realowner.lastrocket = world; + // self.realowner.rl_release = 1; + } +} + +void W_Rocket_Explode () +{ + W_Rocket_Unregister(); + + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other); + + if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) + { + if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) + { + self.realowner.cnt = WEP_ROCKET_LAUNCHER; + ATTACK_FINISHED(self.realowner) = time; + self.realowner.switchweapon = w_getbestweapon(self.realowner); + } + } + remove (self); +} + +void W_Rocket_DoRemoteExplode () +{ + W_Rocket_Unregister(); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) + { + if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) + { + self.realowner.cnt = WEP_ROCKET_LAUNCHER; + ATTACK_FINISHED(self.realowner) = time; + self.realowner.switchweapon = w_getbestweapon(self.realowner); + } + } + remove (self); +} + +void W_Rocket_RemoteExplode() +{ + if(self.realowner.deadflag == DEAD_NO) + if(self.realowner.lastrocket) + { + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device + ) + { + W_Rocket_DoRemoteExplode(); + } + } +} + +vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos) +{ + if(thisdir * goaldir > maxturn_cos) + return goaldir; + if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite + return thisdir; // refuse to guide (better than letting a numerical error happen) + float f, m2; + vector v; + // solve: + // g = normalize(thisdir + goaldir * X) + // thisdir * g = maxturn + // + // gg = thisdir + goaldir * X + // (thisdir * gg)^2 = maxturn^2 * (gg * gg) + // + // (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir) + f = thisdir * goaldir; + // (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f) + // 0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1) + m2 = maxturn_cos * maxturn_cos; + v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1); + return normalize(thisdir + goaldir * v_y); // the larger solution! +} +// assume thisdir == -goaldir: +// f == -1 +// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1) +// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0 +// x^2 - 2 * x + 1 = 0 +// (x - 1)^2 = 0 +// x = 1 +// normalize(thisdir + goaldir) +// normalize(0) + +void W_Rocket_Think (void) +{ + vector desireddir, olddir, newdir, desiredorigin, goal; +#if 0 + float cosminang, cosmaxang, cosang; +#endif + float velspeed, f; + self.nextthink = time; + if (time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Rocket_Explode (); + return; + } + + // accelerate + makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0'); + velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward); + if (velspeed > 0) + self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed); + + // laser guided, or remote detonation + if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) + { + if(self == self.realowner.lastrocket) + if not(self.realowner.rl_release) + if not(self.BUTTON_ATCK2) + if(autocvar_g_balance_rocketlauncher_guiderate) + if(time > self.pushltime) + if(self.realowner.deadflag == DEAD_NO) + { + f = autocvar_g_balance_rocketlauncher_guideratedelay; + if(f) + f = bound(0, (time - self.pushltime) / f, 1); + else + f = 1; + + velspeed = vlen(self.velocity); + + makevectors(self.realowner.v_angle); + desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward); + desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs); + olddir = normalize(self.velocity); + + // now it gets tricky... we want to move like some curve to approximate the target direction + // but we are limiting the rate at which we can turn! + goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir; + newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD)); + + self.velocity = newdir * velspeed; + self.angles = vectoangles(self.velocity); + + if(!self.count) + { + pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1); + // TODO add a better sound here + sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); + self.count = 1; + } + } + + if(self.rl_detonate_later) + W_Rocket_RemoteExplode(); + } + + if(self.csqcprojectile_clientanimate == 0) + UpdateCSQCProjectile(self); +} + +void W_Rocket_Touch (void) +{ + if(WarpZone_Projectile_Touch()) + { + if(wasfreed(self)) + W_Rocket_Unregister(); + return; + } + W_Rocket_Unregister(); + W_Rocket_Explode (); +} + +void W_Rocket_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; + self.angles = vectoangles(self.velocity); + + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_Rocket_Explode); +} + +void W_Rocket_Attack (void) +{ + entity missile; + entity flash; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage); + pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = WarpZone_RefSys_SpawnSameRefSys(self); + missile.owner = missile.realowner = self; + self.lastrocket = missile; + if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0) + missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay; + else + missile.spawnshieldtime = -1; + missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay; + missile.classname = "rocket"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous + + missile.takedamage = DAMAGE_YES; + missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale; + missile.health = autocvar_g_balance_rocketlauncher_health; + missile.event_damage = W_Rocket_Damage; + missile.damagedbycontents = TRUE; + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_ROCKET_LAUNCHER; + setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot + + setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point + W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0); + missile.angles = vectoangles (missile.velocity); + + missile.touch = W_Rocket_Touch; + missile.think = W_Rocket_Think; + missile.nextthink = time; + missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime; + missile.flags = FL_PROJECTILE; + missile.missile_flags = MIF_SPLASH; + + CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound + + // muzzle flash for 1st person view + flash = spawn (); + setmodel (flash, "models/flash.md3"); // precision set below + SUB_SetFade (flash, time, 0.1); + flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + W_AttachToShotorg(flash, '5 0 0'); + + // common properties + other = missile; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc + +float w_rlauncher(float req) +{ + entity rock; + float rockfound; + float ammo_amount; + + if (req == WR_AIM) + { + // aim and decide to fire if appropriate + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE); + if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! + { + // decide whether to detonate rockets + entity missile, targetlist, targ; + float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; + float selfdamage, teamdamage, enemydamage; + edgedamage = autocvar_g_balance_rocketlauncher_edgedamage; + coredamage = autocvar_g_balance_rocketlauncher_damage; + edgeradius = autocvar_g_balance_rocketlauncher_radius; + recipricoledgeradius = 1 / edgeradius; + selfdamage = 0; + teamdamage = 0; + enemydamage = 0; + targetlist = findchainfloat(bot_attack, TRUE); + missile = find(world, classname, "rocket"); + while (missile) + { + if (missile.realowner != self) + { + missile = find(missile, classname, "rocket"); + continue; + } + targ = targetlist; + while (targ) + { + d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin); + d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); + // count potential damage according to type of target + if (targ == self) + selfdamage = selfdamage + d; + else if (targ.team == self.team && teamplay) + teamdamage = teamdamage + d; + else if (bot_shouldattack(targ)) + enemydamage = enemydamage + d; + targ = targ.chain; + } + missile = find(missile, classname, "rocket"); + } + float desirabledamage; + desirabledamage = enemydamage; + if (time > self.invincible_finished && time > self.spawnshieldtime) + desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; + if (teamplay && self.team) + desirabledamage = desirabledamage - teamdamage; + + missile = find(world, classname, "rocket"); + while (missile) + { + if (missile.realowner != self) + { + missile = find(missile, classname, "rocket"); + continue; + } + makevectors(missile.v_angle); + targ = targetlist; + if (skill > 9) // normal players only do this for the target they are tracking + { + targ = targetlist; + while (targ) + { + if ( + (v_forward * normalize(missile.origin - targ.origin)< 0.1) + && desirabledamage > 0.1*coredamage + )self.BUTTON_ATCK2 = TRUE; + targ = targ.chain; + } + }else{ + float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); + //As the distance gets larger, a correct detonation gets near imposible + //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player + if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1) + if(IS_PLAYER(self.enemy)) + if(desirabledamage >= 0.1*coredamage) + if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) + self.BUTTON_ATCK2 = TRUE; + // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); + } + + missile = find(missile, classname, "rocket"); + } + // if we would be doing at X percent of the core damage, detonate it + // but don't fire a new shot at the same time! + if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events + self.BUTTON_ATCK2 = TRUE; + if ((skill > 6.5) && (selfdamage > self.health)) + self.BUTTON_ATCK2 = FALSE; + //if(self.BUTTON_ATCK2 == TRUE) + // dprint(ftos(desirabledamage),"\n"); + if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else + { + if (self.BUTTON_ATCK) + { + if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop) + if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire)) + { + W_Rocket_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready); + self.rl_release = 0; + } + } + else + self.rl_release = 1; + + if (self.BUTTON_ATCK2) + { + rockfound = 0; + for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self) + { + if(!rock.rl_detonate_later) + { + rock.rl_detonate_later = TRUE; + rockfound = 1; + } + } + if(rockfound) + sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/flash.md3"); + precache_model ("models/weapons/g_rl.md3"); + precache_model ("models/weapons/v_rl.md3"); + precache_model ("models/weapons/h_rl.iqm"); + precache_sound ("weapons/rocket_det.wav"); + precache_sound ("weapons/rocket_fire.wav"); + precache_sound ("weapons/rocket_mode.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_ROCKET_LAUNCHER); + self.current_ammo = ammo_rockets; + self.rl_release = 1; + } + else if (req == WR_CHECKAMMO1) + { + // don't switch while guiding a missile + if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER) + { + ammo_amount = FALSE; + if(autocvar_g_balance_rocketlauncher_reload_ammo) + { + if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo) + ammo_amount = TRUE; + } + else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) + ammo_amount = TRUE; + return !ammo_amount; + } + } + else if (req == WR_CHECKAMMO2) + return FALSE; + else if (req == WR_RESETPLAYER) + { + self.rl_release = 0; + } + else if (req == WR_RELOAD) + { + W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_ROCKETLAUNCHER_SUICIDE; + } + else if (req == WR_KILLMESSAGE) + { + if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) + return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH; + else + return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_rlauncher(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/rocket_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/grenadelauncher.qc b/qcsrc/common/weapons/grenadelauncher.qc deleted file mode 100644 index 8b8a1a062..000000000 --- a/qcsrc/common/weapons/grenadelauncher.qc +++ /dev/null @@ -1,414 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ GRENADE_LAUNCHER, -/* function */ w_glauncher, -/* ammotype */ IT_ROCKETS, -/* impulse */ 4, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "gl", -/* shortname */ "grenadelauncher", -/* fullname */ _("Mortar") -); -#else -#ifdef SVQC -.float gl_detonate_later; -.float gl_bouncecnt; - -void W_Grenade_Explode (void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - if(self.movetype == MOVETYPE_NONE) - self.velocity = self.oldvelocity; - - RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_Grenade_Explode2 (void) -{ - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - if(self.movetype == MOVETYPE_NONE) - self.velocity = self.oldvelocity; - - RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other); - - remove (self); -} - -void W_Grenade_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 W_Grenade_Think1 (void) -{ - self.nextthink = time; - if (time > self.cnt) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Grenade_Explode (); - return; - } - if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt) - W_Grenade_Explode(); -} - -void W_Grenade_Touch1 (void) -{ - PROJECTILE_TOUCH; - if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile - { - self.use (); - } - else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce - { - float r; - r = random() * 6; - if(r < 1) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); - else if(r < 2) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); - else if(r < 3) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); - else if(r < 4) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); - else if(r < 5) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); - else - spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); - self.projectiledeathtype |= HITTYPE_BOUNCE; - self.gl_bouncecnt += 1; - } - else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick - { - spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); - - // let it stick whereever it is - self.oldvelocity = self.velocity; - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; // also disables gravity - self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO - UpdateCSQCProjectile(self); - - // do not respond to any more touches - self.solid = SOLID_NOT; - - self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime_stick); - } -} - -void W_Grenade_Touch2 (void) -{ - PROJECTILE_TOUCH; - if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile - { - self.use (); - } - else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce - { - float r; - r = random() * 6; - if(r < 1) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); - else if(r < 2) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); - else if(r < 3) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); - else if(r < 4) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); - else if(r < 5) - spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); - else - spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); - self.projectiledeathtype |= HITTYPE_BOUNCE; - self.gl_bouncecnt += 1; - - if (autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce && self.gl_bouncecnt == 1) - self.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce; - - } - else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick - { - spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); - - // let it stick whereever it is - self.oldvelocity = self.velocity; - self.velocity = '0 0 0'; - self.movetype = MOVETYPE_NONE; // also disables gravity - self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO - UpdateCSQCProjectile(self); - - // do not respond to any more touches - self.solid = SOLID_NOT; - - self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime_stick); - } -} - -void W_Grenade_Attack (void) -{ - entity gren; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_primary_damage); - w_shotdir = v_forward; // no TrueAim for grenades please - - pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage; - gren.movetype = MOVETYPE_BOUNCE; - gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; - gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_GRENADE_LAUNCHER; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime; - gren.nextthink = time; - gren.think = W_Grenade_Think1; - gren.use = W_Grenade_Explode; - gren.touch = W_Grenade_Touch1; - - gren.takedamage = DAMAGE_YES; - gren.health = autocvar_g_balance_grenadelauncher_primary_health; - gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale; - gren.event_damage = W_Grenade_Damage; - gren.damagedbycontents = TRUE; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary); - - gren.angles = vectoangles (gren.velocity); - gren.flags = FL_PROJECTILE; - - if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2) - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); - else - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); - - other = gren; MUTATOR_CALLHOOK(EditProjectile); -} - -void W_Grenade_Attack2 (void) -{ - entity gren; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_secondary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_secondary_damage); - w_shotdir = v_forward; // no TrueAim for grenades please - - pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - gren = spawn (); - gren.owner = gren.realowner = self; - gren.classname = "grenade"; - gren.bot_dodge = TRUE; - gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage; - gren.movetype = MOVETYPE_BOUNCE; - gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; - gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; - PROJECTILE_MAKETRIGGER(gren); - gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY; - setorigin(gren, w_shotorg); - setsize(gren, '-3 -3 -3', '3 3 3'); - - gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime; - gren.think = adaptor_think2use_hittype_splash; - gren.use = W_Grenade_Explode2; - gren.touch = W_Grenade_Touch2; - - gren.takedamage = DAMAGE_YES; - gren.health = autocvar_g_balance_grenadelauncher_secondary_health; - gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale; - gren.event_damage = W_Grenade_Damage; - gren.damagedbycontents = TRUE; - gren.missile_flags = MIF_SPLASH | MIF_ARC; - W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary); - - gren.angles = vectoangles (gren.velocity); - gren.flags = FL_PROJECTILE; - - if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2) - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); - else - CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); - - other = gren; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_grenadelauncher (void) -{ - weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER); -} - -.float bot_secondary_grenademooth; -float w_glauncher(float req) -{ - entity nade; - float nadefound; - float ammo_amount; - - if (req == WR_AIM) - { - self.BUTTON_ATCK = FALSE; - self.BUTTON_ATCK2 = FALSE; - if (self.bot_secondary_grenademooth == 0) - { - if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE)) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.01) self.bot_secondary_grenademooth = 1; - } - } - else - { - if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE)) - { - self.BUTTON_ATCK2 = TRUE; - if(random() < 0.02) self.bot_secondary_grenademooth = 0; - } - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if (self.BUTTON_ATCK) - { - if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire)) - { - W_Grenade_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready); - } - } - else if (self.BUTTON_ATCK2) - { - if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary")) - { - nadefound = 0; - for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self) - { - if(!nade.gl_detonate_later) - { - nade.gl_detonate_later = TRUE; - nadefound = 1; - } - } - if(nadefound) - sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); - } - else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire)) - { - W_Grenade_Attack2(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_gl.md3"); - precache_model ("models/weapons/v_gl.md3"); - precache_model ("models/weapons/h_gl.iqm"); - precache_sound ("weapons/grenade_bounce1.wav"); - precache_sound ("weapons/grenade_bounce2.wav"); - precache_sound ("weapons/grenade_bounce3.wav"); - precache_sound ("weapons/grenade_bounce4.wav"); - precache_sound ("weapons/grenade_bounce5.wav"); - precache_sound ("weapons/grenade_bounce6.wav"); - precache_sound ("weapons/grenade_stick.wav"); - precache_sound ("weapons/grenade_fire.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_GRENADE_LAUNCHER); - self.current_ammo = ammo_rockets; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo; - ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo; - ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_secondary_ammo; - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo), autocvar_g_balance_grenadelauncher_reload_ammo, autocvar_g_balance_grenadelauncher_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MORTAR_SUICIDE_BOUNCE; - else - return WEAPON_MORTAR_SUICIDE_EXPLODE; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_MORTAR_MURDER_BOUNCE; - else - return WEAPON_MORTAR_MURDER_EXPLODE; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_glauncher(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/grenade_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/common/weapons/laser.qc b/qcsrc/common/weapons/laser.qc deleted file mode 100644 index c331c5b52..000000000 --- a/qcsrc/common/weapons/laser.qc +++ /dev/null @@ -1,576 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ LASER, -/* function */ W_Laser, -/* ammotype */ 0, -/* impulse */ 1, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ 0, -/* model */ "laser", -/* shortname */ "laser", -/* fullname */ _("Blaster") -); -#else -#ifdef SVQC -void(float imp) W_SwitchWeapon; -void() W_LastWeapon; -.float swing_prev; -.entity swing_alreadyhit; - -void SendCSQCShockwaveParticle(vector endpos) -{ - //endpos = WarpZone_UnTransformOrigin(transform, endpos); - - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE); - WriteCoord(MSG_BROADCAST, w_shotorg_x); - WriteCoord(MSG_BROADCAST, w_shotorg_y); - WriteCoord(MSG_BROADCAST, w_shotorg_z); - WriteCoord(MSG_BROADCAST, endpos_x); - WriteCoord(MSG_BROADCAST, endpos_y); - WriteCoord(MSG_BROADCAST, endpos_z); - WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_max, 255)); - WriteByte(MSG_BROADCAST, bound(0, autocvar_g_balance_laser_shockwave_spread_min, 255)); - WriteByte(MSG_BROADCAST, num_for_edict(self)); -} - -void W_Laser_Touch() -{ - PROJECTILE_TOUCH; - - self.event_damage = func_null; - - if(self.dmg) - RadiusDamage(self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other); - else - RadiusDamage(self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other); - - remove(self); -} - -void W_Laser_Think() -{ - self.movetype = MOVETYPE_FLY; - self.think = SUB_Remove; - - if(self.dmg) - self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime; - else - self.nextthink = time + autocvar_g_balance_laser_primary_lifetime; - - CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE); -} - - -float W_Laser_Shockwave_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 = (autocvar_g_balance_laser_shockwave_spread_min * (1 - spreadlimit) + autocvar_g_balance_laser_shockwave_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_Laser_Shockwave_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_Laser_Shockwave_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_Laser_Shockwave_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_Laser_Shockwave_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; -} - -#define PLAYER_CENTER(ent) (ent.origin + ((ent.classname == "player") ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5))) - -entity shockwave_hit[32]; -float shockwave_hit_damage[32]; -vector shockwave_hit_force[32]; - -float W_Laser_Shockwave_CheckHit(float queue, entity head, vector final_force, float final_damage) -{ - if not(head) { return FALSE; } - float i; - - ++queue; - - for(i = 1; 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_Laser_Shockwave() -{ - // declarations - float multiplier, multiplier_from_accuracy, multiplier_from_distance; - float final_damage; //, final_spread; - vector final_force, center, vel; - entity head, next; - - float i, queue = 0; - - // set up the shot direction - W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_shockwave_damage); - vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_shockwave_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); - Damage_DamageInfo(attack_hitpos, autocvar_g_balance_laser_shockwave_splash_damage, autocvar_g_balance_laser_shockwave_splash_edgedamage, autocvar_g_balance_laser_shockwave_splash_radius, w_shotdir * autocvar_g_balance_laser_shockwave_splash_force, WEP_LASER, 0, self); - - // splash damage/jumping trace - head = WarpZone_FindRadius(attack_hitpos, max(autocvar_g_balance_laser_shockwave_splash_radius, autocvar_g_balance_laser_shockwave_jump_radius), FALSE); - while(head) - { - next = head.chain; - - if(head.takedamage) - { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - center = PLAYER_CENTER(head); - - float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest); - - if((head == self) && (distance_to_head <= autocvar_g_balance_laser_shockwave_jump_radius)) - { - multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_jump_radius)) : 0)); - multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); - multiplier = max(autocvar_g_balance_laser_shockwave_jump_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_jump_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_jump_multiplier_distance))); - - final_force = ((normalize(center - attack_hitpos) * autocvar_g_balance_laser_shockwave_jump_force) * multiplier); - vel = head.velocity; vel_z = 0; - vel = normalize(vel) * bound(0, vlen(vel) / autocvar_sv_maxspeed, 1) * autocvar_g_balance_laser_shockwave_jump_force_velocitybias; - final_force = (vlen(final_force) * normalize(normalize(final_force) + vel)); - final_force_z *= autocvar_g_balance_laser_shockwave_jump_force_zscale; - final_damage = (autocvar_g_balance_laser_shockwave_jump_damage * multiplier + autocvar_g_balance_laser_shockwave_jump_edgedamage * (1 - multiplier)); - - Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); - //print("SELF HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); - } - else if (distance_to_head <= autocvar_g_balance_laser_shockwave_splash_radius) - { - multiplier_from_accuracy = (1 - (distance_to_head ? min(1, (distance_to_head / autocvar_g_balance_laser_shockwave_splash_radius)) : 0)); - multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_hit / distance_to_end)) : 0)); - multiplier = max(autocvar_g_balance_laser_shockwave_splash_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_splash_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_splash_multiplier_distance))); - - final_force = normalize(center - (attack_hitpos - (w_shotdir * autocvar_g_balance_laser_shockwave_splash_force_forwardbias))); - //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200))); - final_force = ((final_force * autocvar_g_balance_laser_shockwave_splash_force) * multiplier); - final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; - final_damage = (autocvar_g_balance_laser_shockwave_splash_damage * multiplier + autocvar_g_balance_laser_shockwave_splash_edgedamage * (1 - multiplier)); - - if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } - //print("SPLASH HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); - } - } - head = next; - } - - // cone damage trace - head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_shockwave_distance, FALSE); - while(head) - { - next = head.chain; - - if((head != self) && head.takedamage) - { - // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) - center = PLAYER_CENTER(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); - - vector nearest_on_line = (w_shotorg + a * w_shotdir); - vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line); - float distance_to_target = vlen(w_shotorg - nearest_to_attacker); // todo: use the findradius function for this - - if((distance_to_target <= autocvar_g_balance_laser_shockwave_distance) - && (W_Laser_Shockwave_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos))) - { - multiplier_from_accuracy = (1 - W_Laser_Shockwave_CheckSpread(nearest_to_attacker, nearest_on_line, w_shotorg, attack_endpos)); - multiplier_from_distance = (1 - (distance_to_hit ? min(1, (distance_to_target / distance_to_end)) : 0)); - multiplier = max(autocvar_g_balance_laser_shockwave_multiplier_min, ((multiplier_from_accuracy * autocvar_g_balance_laser_shockwave_multiplier_accuracy) + (multiplier_from_distance * autocvar_g_balance_laser_shockwave_multiplier_distance))); - - final_force = normalize(center - (nearest_on_line - (w_shotdir * autocvar_g_balance_laser_shockwave_force_forwardbias))); - //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200))); - final_force = ((final_force * autocvar_g_balance_laser_shockwave_force) * multiplier); - final_force_z *= autocvar_g_balance_laser_shockwave_force_zscale; - final_damage = (autocvar_g_balance_laser_shockwave_damage * multiplier + autocvar_g_balance_laser_shockwave_edgedamage * (1 - multiplier)); - - if(W_Laser_Shockwave_CheckHit(queue, head, final_force, final_damage)) { ++queue; } - //print("CONE HIT: multiplier = ", ftos(multiplier), strcat(", damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force))),"... multiplier_from_accuracy = ", ftos(multiplier_from_accuracy), ", multiplier_from_distance = ", ftos(multiplier_from_distance), ".\n"); - } - } - head = next; - } - - for(i = 1; i <= queue; ++i) - { - head = shockwave_hit[i]; - final_force = shockwave_hit_force[i]; - final_damage = shockwave_hit_damage[i]; - - Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force); - print("SHOCKWAVE by ", self.netname, ": damage = ", ftos(final_damage), ", force = ", ftos(vlen(final_force)), ".\n"); - - shockwave_hit[i] = world; - shockwave_hit_force[i] = '0 0 0'; - shockwave_hit_damage[i] = 0; - } - //print("queue was ", ftos(queue), ".\n\n"); -} - -void W_Laser_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 = autocvar_g_balance_laser_melee_time * W_WeaponRateFactor(); - swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); - f = ((1 - swing) * autocvar_g_balance_laser_melee_traces); - - // check to see if we can still continue, otherwise give up now - if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_laser_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 / autocvar_g_balance_laser_melee_traces)) * 2 - 1); - - targpos = (self.realowner.origin + self.realowner.view_ofs - + (v_forward * autocvar_g_balance_laser_melee_range) - + (v_up * swing_factor * autocvar_g_balance_laser_melee_swing_up) - + (v_right * swing_factor * autocvar_g_balance_laser_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"); - - 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 || autocvar_g_balance_laser_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 = (autocvar_g_balance_laser_melee_damage * min(1, swing_factor + 1)); - else - swing_damage = (autocvar_g_balance_laser_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_LASER | HITTYPE_SECONDARY, - self.realowner.origin + self.realowner.view_ofs, - v_forward * autocvar_g_balance_laser_melee_force); - - if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_LASER, 0, swing_damage); } - - if(autocvar_g_balance_laser_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_Laser_Melee() -{ - sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_melee_animtime, w_ready); - - entity meleetemp; - meleetemp = spawn(); - meleetemp.owner = meleetemp.realowner = self; - meleetemp.think = W_Laser_Melee_Think; - meleetemp.nextthink = time + autocvar_g_balance_laser_melee_delay * W_WeaponRateFactor(); - W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_laser_melee_damage, autocvar_g_balance_laser_melee_range); -} - -void W_Laser_Attack(float issecondary) -{ - entity missile; - vector s_forward; - float a; - - a = autocvar_g_balance_laser_primary_shotangle; - s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD); - - //if(nodamage) - // W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0); - /*else*/if(issecondary == 1) - W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage); - else - W_SetupShot_Dir(self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage); - pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = spawn(); - missile.owner = missile.realowner = self; - missile.classname = "laserbolt"; - missile.dmg = 0; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage; - - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_LASER; - - setorigin(missile, w_shotorg); - setsize(missile, '0 0 0', '0 0 0'); - - W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary); - missile.angles = vectoangles(missile.velocity); - //missile.glow_color = 250; // 244, 250 - //missile.glow_size = 120; - missile.touch = W_Laser_Touch; - - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - missile.think = W_Laser_Think; - missile.nextthink = time + autocvar_g_balance_laser_primary_delay; - - other = missile; MUTATOR_CALLHOOK(EditProjectile); - - if(time >= missile.nextthink) - { - entity oldself; - oldself = self; - self = missile; - self.think(); - self = oldself; - } -} - -void spawnfunc_weapon_laser(void) -{ - weapon_defaultspawnfunc(WEP_LASER); -} - -float W_Laser(float request) -{ - switch(request) - { - case WR_AIM: - { - if((autocvar_g_balance_laser_secondary == 2) && (vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_laser_melee_range)) - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); - else - self.BUTTON_ATCK = bot_aim(1000000, 0, 1, FALSE); - return TRUE; - } - - case WR_THINK: - { - if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) - { - if(weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire)) - { - W_DecreaseAmmo(ammo_none, 1, TRUE); - - if not(autocvar_g_balance_laser_primary) - W_Laser_Shockwave(); - else - W_Laser_Attack(FALSE); - - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready); - } - } - else if(self.BUTTON_ATCK2) - { - switch(autocvar_g_balance_laser_secondary) - { - case 0: // switch to last used weapon - { - if(self.switchweapon == WEP_LASER) // don't do this if already switching - W_LastWeapon(); - - break; - } - - case 1: // normal projectile secondary - { - if(weapon_prepareattack(1, autocvar_g_balance_laser_secondary_refire)) - { - W_DecreaseAmmo(ammo_none, 1, TRUE); - W_Laser_Attack(TRUE); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready); - } - - break; - } - - case 2: // melee attack secondary - { - if(!self.crouch) // we are not currently crouching; this fixes an exploit where your melee anim is not visible, and besides wouldn't make much sense - if(weapon_prepareattack(1, autocvar_g_balance_laser_melee_refire)) - { - // attempt forcing playback of the anim by switching to another anim (that we never play) here... - W_Laser_Melee(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_melee_animtime, w_ready); - } - } - } - } - return TRUE; - } - - case WR_PRECACHE: - { - precache_model("models/weapons/g_laser.md3"); - precache_model("models/weapons/v_laser.md3"); - precache_model("models/weapons/h_laser.iqm"); - precache_sound("weapons/lasergun_fire.wav"); - return TRUE; - } - - case WR_SETUP: - { - weapon_setup(WEP_LASER); - self.current_ammo = ammo_none; - return TRUE; - } - - case WR_CHECKAMMO1: - case WR_CHECKAMMO2: - { - return TRUE; // laser has infinite ammo - } - - case WR_RELOAD: - { - W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav"); - return TRUE; - } - - case WR_SUICIDEMESSAGE: - { - return WEAPON_LASER_SUICIDE; - } - - case WR_KILLMESSAGE: - { - return WEAPON_LASER_MURDER; - } - } - - return TRUE; -} -#endif -#ifdef CSQC -float W_Laser(float request) -{ - switch(request) - { - case WR_IMPACTEFFECT: - { - vector org2; - org2 = w_org + w_backoff * 6; - pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent) { sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM); } - return TRUE; - } - - case WR_PRECACHE: - { - precache_sound("weapons/laserimpact.wav"); - return TRUE; - } - } - - return TRUE; -} -#endif -#endif diff --git a/qcsrc/common/weapons/lightning.qc b/qcsrc/common/weapons/lightning.qc deleted file mode 100644 index e4b7230d6..000000000 --- a/qcsrc/common/weapons/lightning.qc +++ /dev/null @@ -1,296 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ LIGHTNING, -/* function */ w_lightning, -/* ammotype */ IT_CELLS, -/* impulse */ 5, -/* flags */ WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "lightning", -/* shortname */ "lightning", -/* fullname */ _("Lightning") -); -#else -#ifdef SVQC - -// Declarations ========================= -.vector hook_start, hook_end; // used for beam -.entity lightning_beam; // used for beam -.float BUTTON_ATCK_prev; // for better animation control -.float lg_fire_prev; // for better animation control - -// Lightning functions ========================= -float W_Lightning_Beam_Send(entity to, float sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_LIGHTNING_BEAM); - sf = sf & 0x7F; - if(sound_allowed(MSG_BROADCAST, self.owner)) - sf |= 0x80; - WriteByte(MSG_ENTITY, sf); - if(sf & 1) - { - WriteByte(MSG_ENTITY, num_for_edict(self.owner)); - WriteCoord(MSG_ENTITY, autocvar_g_balance_lightning_primary_range); - } - if(sf & 2) - { - WriteCoord(MSG_ENTITY, self.hook_start_x); - WriteCoord(MSG_ENTITY, self.hook_start_y); - WriteCoord(MSG_ENTITY, self.hook_start_z); - } - if(sf & 4) - { - WriteCoord(MSG_ENTITY, self.hook_end_x); - WriteCoord(MSG_ENTITY, self.hook_end_y); - WriteCoord(MSG_ENTITY, self.hook_end_z); - } - return TRUE; -} - -void W_Lightning_Beam_Think() -{ - self.owner.lg_fire_prev = time; - if (self != self.owner.lightning_beam) - { - remove(self); - return; - } - if (self.owner.weaponentity.state != WS_INUSE || (self.owner.ammo_cells <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen) - { - if(self == self.owner.lightning_beam) - self.owner.lightning_beam = world; - remove(self); - return; - } - - self.nextthink = time; - - makevectors(self.owner.v_angle); - - float dt, f; - dt = frametime; - if not(self.owner.items & IT_UNLIMITED_WEAPON_AMMO) - { - if(autocvar_g_balance_lightning_primary_ammo) - { - dt = min(dt, self.owner.ammo_cells / autocvar_g_balance_lightning_primary_ammo); - self.owner.ammo_cells = max(0, self.owner.ammo_cells - autocvar_g_balance_lightning_primary_ammo * frametime); - } - } - - W_SetupShot_Range(self.owner, TRUE, 0, "", 0, autocvar_g_balance_lightning_primary_damage * dt, autocvar_g_balance_lightning_primary_range); - WarpZone_traceline_antilag(self.owner, w_shotorg, w_shotend, MOVE_NORMAL, self.owner, ANTILAG_LATENCY(self.owner)); - - // apply the damage - if(trace_ent) - { - vector force; - force = w_shotdir * autocvar_g_balance_lightning_primary_force; - - f = ExponentialFalloff(autocvar_g_balance_lightning_primary_falloff_mindist, autocvar_g_balance_lightning_primary_falloff_maxdist, autocvar_g_balance_lightning_primary_falloff_halflifedist, vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos) - w_shotorg)); - - if(accuracy_isgooddamage(self.owner, trace_ent)) - accuracy_add(self.owner, WEP_LIGHTNING, 0, autocvar_g_balance_lightning_primary_damage * dt * f); - Damage (trace_ent, self.owner, self.owner, autocvar_g_balance_lightning_primary_damage * dt * f, WEP_LIGHTNING, trace_endpos, force * dt); - } - - // draw effect - if(w_shotorg != self.hook_start) - { - self.SendFlags |= 2; - self.hook_start = w_shotorg; - } - if(w_shotend != self.hook_end) - { - self.SendFlags |= 4; - self.hook_end = w_shotend; - } -} - -// Attack functions ========================= -void W_Lightning_Attack1 (void) -{ - // only play fire sound if 0.5 sec has passed since player let go the fire button - if(time - self.lg_fire_prev > 0.5) - sound (self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM); - - entity beam, oldself; - - self.lightning_beam = beam = spawn(); - beam.classname = "W_Lightning_Beam"; - beam.solid = SOLID_NOT; - beam.think = W_Lightning_Beam_Think; - beam.owner = self; - beam.movetype = MOVETYPE_NONE; - beam.shot_spread = 1; - beam.bot_dodge = TRUE; - beam.bot_dodgerating = autocvar_g_balance_lightning_primary_damage; - Net_LinkEntity(beam, FALSE, 0, W_Lightning_Beam_Send); - - oldself = self; - self = beam; - self.think(); - self = oldself; -} - -float w_lightning(float req) -{ - if (req == WR_AIM) - { - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); - /* - self.BUTTON_ATCK=FALSE; - self.BUTTON_ATCK2=FALSE; - if(vlen(self.origin-self.enemy.origin) > 1000) - self.bot_aim_whichfiretype = 0; - if(self.bot_aim_whichfiretype == 0) - { - float shoot; - - if(autocvar_g_balance_lightning_primary_speed) - shoot = bot_aim(autocvar_g_balance_lightning_primary_speed, 0, autocvar_g_balance_lightning_primary_lifetime, FALSE); - else - shoot = bot_aim(1000000, 0, 0.001, FALSE); - - if(shoot) - { - self.BUTTON_ATCK = TRUE; - if(random() < 0.01) self.bot_aim_whichfiretype = 1; - } - } - else // todo - { - //if(bot_aim(autocvar_g_balance_lightning_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_lightning_secondary_lifetime, TRUE)) - //{ - // self.BUTTON_ATCK2 = TRUE; - // if(random() < 0.03) self.bot_aim_whichfiretype = 0; - //} - } - */ - } - else if (req == WR_THINK) - { - if (self.BUTTON_ATCK) - { - if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this! - /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y) - weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_lightning_primary_animtime, w_ready); - else*/ - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); - - if (weapon_prepareattack(0, 0)) - { - if ((!self.lightning_beam) || wasfreed(self.lightning_beam)) - W_Lightning_Attack1(); - - if(!self.BUTTON_ATCK_prev) - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); - self.BUTTON_ATCK_prev = 1; - } - } - } - else // todo - { - if (self.BUTTON_ATCK_prev != 0) - { - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_lightning_primary_animtime, w_ready); - ATTACK_FINISHED(self) = time + autocvar_g_balance_lightning_primary_refire * W_WeaponRateFactor(); - } - self.BUTTON_ATCK_prev = 0; - } - - //if (self.BUTTON_ATCK2) - //if (weapon_prepareattack(1, autocvar_g_balance_lightning_secondary_refire)) - //{ - // W_Lightning_Attack2(); - // self.lightning_count = autocvar_g_balance_lightning_secondary_count; - // weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_lightning_secondary_animtime, w_lightning_checkattack); - // self.lightning_secondarytime = time + autocvar_g_balance_lightning_secondary_refire2 * W_WeaponRateFactor(); - //} - } - else if (req == WR_PRECACHE) - { - precache_model ("models/weapons/g_lightning.md3"); - precache_model ("models/weapons/v_lightning.md3"); - precache_model ("models/weapons/h_lightning.iqm"); - //precache_sound ("weapons/lightning_bounce.wav"); - precache_sound ("weapons/lightning_fire.wav"); - precache_sound ("weapons/lightning_fire2.wav"); - precache_sound ("weapons/lightning_impact.wav"); - //precache_sound ("weapons/lightning_impact_combo.wav"); - //precache_sound ("weapons/W_Lightning_Beam_fire.wav"); - } - else if (req == WR_SETUP) - weapon_setup(WEP_LIGHTNING); - else if (req == WR_CHECKAMMO1) - { - return !autocvar_g_balance_lightning_primary_ammo || (self.ammo_cells > 0); - } - else if (req == WR_CHECKAMMO2) - return self.ammo_cells >= autocvar_g_balance_lightning_secondary_ammo; - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - { - return WEAPON_ELECTRO_MURDER_ORBS; - } - else - { - if(w_deathtype & HITTYPE_BOUNCE) - return WEAPON_ELECTRO_MURDER_COMBO; - else - return WEAPON_ELECTRO_MURDER_BOLT; - } - } - else if (req == WR_RESETPLAYER) - { - //self.lightning_secondarytime = time; - } - return TRUE; -}; - -void LightningInit() -{ - weapon_action(WEP_LIGHTNING, WR_PRECACHE); - lightning_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 1); - lightning_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 2); - lightning_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 3); - lightning_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LIGHTNING), FALSE, FALSE, 4); -} - -void spawnfunc_weapon_lightning (void) // should this really be here? -{ - weapon_defaultspawnfunc(WEP_LIGHTNING); -} -#endif -#ifdef CSQC -float w_lightning(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 6; - - if(w_deathtype & HITTYPE_SECONDARY) - { - pointparticles(particleeffectnum("lightning_ballexplode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); - } - else - { - pointparticles(particleeffectnum("lightning_impact"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/lightning_impact.wav", VOL_BASE, ATTN_NORM); - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/lightning_impact.wav"); - precache_sound("weapons/lightning_impact_combo.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/common/weapons/lightning.qh b/qcsrc/common/weapons/lightning.qh deleted file mode 100644 index 57d6ceb0d..000000000 --- a/qcsrc/common/weapons/lightning.qh +++ /dev/null @@ -1,2 +0,0 @@ -void LightningInit(); -vector lightning_shotorigin[4]; diff --git a/qcsrc/common/weapons/machinegun.qc b/qcsrc/common/weapons/machinegun.qc new file mode 100644 index 000000000..923ed9504 --- /dev/null +++ b/qcsrc/common/weapons/machinegun.qc @@ -0,0 +1,341 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ UZI, +/* function */ w_uzi, +/* ammotype */ IT_NAILS, +/* impulse */ 3, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "uzi", +/* shortname */ "uzi", +/* fullname */ _("Machine Gun") +); +#else +#ifdef SVQC + +// leilei's fancy muzzleflash stuff +void UZI_Flash_Go() +{ + self.frame = self.frame + 2; + self.scale = self.scale * 0.5; + self.alpha = self.alpha - 0.25; + self.nextthink = time + 0.05; + + if (self.alpha <= 0) + { + self.think = SUB_Remove; + self.nextthink = time; + self.realowner.muzzle_flash = world; + return; + } + +} + +void UziFlash() +{ + if (self.muzzle_flash == world) + self.muzzle_flash = spawn(); + + // muzzle flash for 1st person view + setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below + + self.muzzle_flash.scale = 0.75; + self.muzzle_flash.think = UZI_Flash_Go; + self.muzzle_flash.nextthink = time + 0.02; + self.muzzle_flash.frame = 2; + self.muzzle_flash.alpha = 0.75; + self.muzzle_flash.angles_z = random() * 180; + self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; + self.muzzle_flash.owner = self.muzzle_flash.realowner = self; +} + +void W_UZI_Attack (float deathtype) +{ + W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? autocvar_g_balance_uzi_first_damage : autocvar_g_balance_uzi_sustained_damage)); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + // this attack_finished just enforces a cooldown at the end of a burst + ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); + + if (self.misc_bulletcounter == 1) + fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_first_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_first_damage, autocvar_g_balance_uzi_first_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); + else + fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_sustained_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); + endFireBallisticBullet(); + + pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + UziFlash(); + W_AttachToShotorg(self.muzzle_flash, '5 0 0'); + + // casing code + if (autocvar_g_casings >= 2) + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); + + if (self.misc_bulletcounter == 1) + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_first_ammo, autocvar_g_balance_uzi_reload_ammo); + else + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); +} + +// weapon frames +void uzi_fire1_02() +{ + if(self.weapon != self.switchweapon) // abort immediately if switching + { + w_ready(); + return; + } + if (self.BUTTON_ATCK) + { + if (!weapon_action(self.weapon, WR_CHECKAMMO2)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + self.misc_bulletcounter = self.misc_bulletcounter + 1; + W_UZI_Attack(WEP_UZI); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); + } + else + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, w_ready); +} + + +void uzi_mode1_fire_auto() +{ + float uzi_spread; + + if (!self.BUTTON_ATCK) + { + w_ready(); + return; + } + + if (!weapon_action(self.weapon, WR_CHECKAMMO1)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return; + } + + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); + + W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + uzi_spread = bound(autocvar_g_balance_uzi_spread_min, autocvar_g_balance_uzi_spread_min + (autocvar_g_balance_uzi_spread_add * self.misc_bulletcounter), autocvar_g_balance_uzi_spread_max); + fireBallisticBullet(w_shotorg, w_shotdir, uzi_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); + endFireBallisticBullet(); + + self.misc_bulletcounter = self.misc_bulletcounter + 1; + + pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + UziFlash(); + W_AttachToShotorg(self.muzzle_flash, '5 0 0'); + + if (autocvar_g_casings >= 2) // casing code + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); + + ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_mode1_fire_auto); +} + +void uzi_mode1_fire_burst() +{ + W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); + if (!g_norecoil) + { + self.punchangle_x = random () - 0.5; + self.punchangle_y = random () - 0.5; + } + + fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_burst_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); + endFireBallisticBullet(); + + + pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + UziFlash(); + W_AttachToShotorg(self.muzzle_flash, '5 0 0'); + + if (autocvar_g_casings >= 2) // casing code + SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); + + self.misc_bulletcounter = self.misc_bulletcounter + 1; + if (self.misc_bulletcounter == 0) + { + ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_burst_refire2 * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_animtime, w_ready); + } + else + { + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_refire, uzi_mode1_fire_burst); + } + +} + +void spawnfunc_weapon_machinegun(); // defined in t_items.qc + +float w_uzi(float req) +{ + float ammo_amount; + if (req == WR_AIM) + if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200) + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + else + { + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_uzi_reload_ammo && self.clip_load < min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if(autocvar_g_balance_uzi_mode == 1) + { + if (self.BUTTON_ATCK) + if (weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 0; + uzi_mode1_fire_auto(); + } + + if(self.BUTTON_ATCK2) + if(weapon_prepareattack(1, 0)) + { + if (!weapon_action(self.weapon, WR_CHECKAMMO2)) + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + { + W_SwitchWeapon_Force(self, w_getbestweapon(self)); + w_ready(); + return FALSE; + } + + W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_burst_ammo, autocvar_g_balance_uzi_reload_ammo); + + self.misc_bulletcounter = autocvar_g_balance_uzi_burst * -1; + uzi_mode1_fire_burst(); + } + } + else + { + + if (self.BUTTON_ATCK) + if (weapon_prepareattack(0, 0)) + { + self.misc_bulletcounter = 1; + W_UZI_Attack(WEP_UZI); // sets attack_finished + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); + } + + if (self.BUTTON_ATCK2 && autocvar_g_balance_uzi_first) + if (weapon_prepareattack(1, 0)) + { + self.misc_bulletcounter = 1; + W_UZI_Attack(WEP_UZI | HITTYPE_SECONDARY); // sets attack_finished + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_first_refire, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/uziflash.md3"); + precache_model ("models/weapons/g_uzi.md3"); + precache_model ("models/weapons/v_uzi.md3"); + precache_model ("models/weapons/h_uzi.iqm"); + precache_sound ("weapons/uzi_fire.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_UZI); + self.current_ammo = ammo_nails; + } + else if (req == WR_CHECKAMMO1) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_sustained_ammo; + else + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; + + if(autocvar_g_balance_uzi_reload_ammo) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_sustained_ammo; + else + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; + } + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_burst_ammo; + else + ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; + + if(autocvar_g_balance_uzi_reload_ammo) + { + if(autocvar_g_balance_uzi_mode == 1) + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_burst_ammo; + else + ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; + } + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo), autocvar_g_balance_uzi_reload_ammo, autocvar_g_balance_uzi_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_UZI_MURDER_SNIPE; + else + return WEAPON_UZI_MURDER_SPRAY; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_uzi(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 2; + pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1); + if(!w_issilent) + if(w_random < 0.05) + sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.1) + sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.2) + sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/mortar.qc b/qcsrc/common/weapons/mortar.qc new file mode 100644 index 000000000..8b8a1a062 --- /dev/null +++ b/qcsrc/common/weapons/mortar.qc @@ -0,0 +1,414 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ GRENADE_LAUNCHER, +/* function */ w_glauncher, +/* ammotype */ IT_ROCKETS, +/* impulse */ 4, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, +/* rating */ BOT_PICKUP_RATING_MID, +/* model */ "gl", +/* shortname */ "grenadelauncher", +/* fullname */ _("Mortar") +); +#else +#ifdef SVQC +.float gl_detonate_later; +.float gl_bouncecnt; + +void W_Grenade_Explode (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_primary_damage, autocvar_g_balance_grenadelauncher_primary_edgedamage, autocvar_g_balance_grenadelauncher_primary_radius, world, world, autocvar_g_balance_grenadelauncher_primary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_Grenade_Explode2 (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(IsDifferentTeam(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + + if(self.movetype == MOVETYPE_NONE) + self.velocity = self.oldvelocity; + + RadiusDamage (self, self.realowner, autocvar_g_balance_grenadelauncher_secondary_damage, autocvar_g_balance_grenadelauncher_secondary_edgedamage, autocvar_g_balance_grenadelauncher_secondary_radius, world, world, autocvar_g_balance_grenadelauncher_secondary_force, self.projectiledeathtype, other); + + remove (self); +} + +void W_Grenade_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 W_Grenade_Think1 (void) +{ + self.nextthink = time; + if (time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Grenade_Explode (); + return; + } + if(self.gl_detonate_later && self.gl_bouncecnt >= autocvar_g_balance_grenadelauncher_primary_remote_minbouncecnt) + W_Grenade_Explode(); +} + +void W_Grenade_Touch1 (void) +{ + PROJECTILE_TOUCH; + if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_primary_type == 0) // always explode when hitting a player, or if normal mortar projectile + { + self.use (); + } + else if (autocvar_g_balance_grenadelauncher_primary_type == 1) // bounce + { + float r; + r = random() * 6; + if(r < 1) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); + else if(r < 2) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); + else if(r < 3) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); + else if(r < 4) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); + else if(r < 5) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); + else + spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; + self.gl_bouncecnt += 1; + } + else if(autocvar_g_balance_grenadelauncher_primary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick + { + spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); + + // let it stick whereever it is + self.oldvelocity = self.velocity; + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; // also disables gravity + self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO + UpdateCSQCProjectile(self); + + // do not respond to any more touches + self.solid = SOLID_NOT; + + self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_primary_lifetime_stick); + } +} + +void W_Grenade_Touch2 (void) +{ + PROJECTILE_TOUCH; + if (other.takedamage == DAMAGE_AIM || autocvar_g_balance_grenadelauncher_secondary_type == 0) // always explode when hitting a player, or if normal mortar projectile + { + self.use (); + } + else if (autocvar_g_balance_grenadelauncher_secondary_type == 1) // bounce + { + float r; + r = random() * 6; + if(r < 1) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce1.wav", VOL_BASE, ATTN_NORM); + else if(r < 2) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce2.wav", VOL_BASE, ATTN_NORM); + else if(r < 3) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce3.wav", VOL_BASE, ATTN_NORM); + else if(r < 4) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce4.wav", VOL_BASE, ATTN_NORM); + else if(r < 5) + spamsound (self, CH_SHOTS, "weapons/grenade_bounce5.wav", VOL_BASE, ATTN_NORM); + else + spamsound (self, CH_SHOTS, "weapons/grenade_bounce6.wav", VOL_BASE, ATTN_NORM); + self.projectiledeathtype |= HITTYPE_BOUNCE; + self.gl_bouncecnt += 1; + + if (autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce && self.gl_bouncecnt == 1) + self.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime_bounce; + + } + else if(autocvar_g_balance_grenadelauncher_secondary_type == 2 && (!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))) // stick + { + spamsound (self, CH_SHOTS, "weapons/grenade_stick.wav", VOL_BASE, ATTN_NORM); + + // let it stick whereever it is + self.oldvelocity = self.velocity; + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; // also disables gravity + self.gravity = 0; // nope, it does NOT! maybe a bug in CSQC code? TODO + UpdateCSQCProjectile(self); + + // do not respond to any more touches + self.solid = SOLID_NOT; + + self.nextthink = min(self.nextthink, time + autocvar_g_balance_grenadelauncher_secondary_lifetime_stick); + } +} + +void W_Grenade_Attack (void) +{ + entity gren; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_primary_damage); + w_shotdir = v_forward; // no TrueAim for grenades please + + pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_primary_damage; + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; + gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_GRENADE_LAUNCHER; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.cnt = time + autocvar_g_balance_grenadelauncher_primary_lifetime; + gren.nextthink = time; + gren.think = W_Grenade_Think1; + gren.use = W_Grenade_Explode; + gren.touch = W_Grenade_Touch1; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_balance_grenadelauncher_primary_health; + gren.damageforcescale = autocvar_g_balance_grenadelauncher_primary_damageforcescale; + gren.event_damage = W_Grenade_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_primary); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + if(autocvar_g_balance_grenadelauncher_primary_type == 0 || autocvar_g_balance_grenadelauncher_primary_type == 2) + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +void W_Grenade_Attack2 (void) +{ + entity gren; + + W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_grenadelauncher_secondary_ammo, autocvar_g_balance_grenadelauncher_reload_ammo); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 4, "weapons/grenade_fire.wav", CH_WEAPON_A, autocvar_g_balance_grenadelauncher_secondary_damage); + w_shotdir = v_forward; // no TrueAim for grenades please + + pointparticles(particleeffectnum("grenadelauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + gren = spawn (); + gren.owner = gren.realowner = self; + gren.classname = "grenade"; + gren.bot_dodge = TRUE; + gren.bot_dodgerating = autocvar_g_balance_grenadelauncher_secondary_damage; + gren.movetype = MOVETYPE_BOUNCE; + gren.bouncefactor = autocvar_g_balance_grenadelauncher_bouncefactor; + gren.bouncestop = autocvar_g_balance_grenadelauncher_bouncestop; + PROJECTILE_MAKETRIGGER(gren); + gren.projectiledeathtype = WEP_GRENADE_LAUNCHER | HITTYPE_SECONDARY; + setorigin(gren, w_shotorg); + setsize(gren, '-3 -3 -3', '3 3 3'); + + gren.nextthink = time + autocvar_g_balance_grenadelauncher_secondary_lifetime; + gren.think = adaptor_think2use_hittype_splash; + gren.use = W_Grenade_Explode2; + gren.touch = W_Grenade_Touch2; + + gren.takedamage = DAMAGE_YES; + gren.health = autocvar_g_balance_grenadelauncher_secondary_health; + gren.damageforcescale = autocvar_g_balance_grenadelauncher_secondary_damageforcescale; + gren.event_damage = W_Grenade_Damage; + gren.damagedbycontents = TRUE; + gren.missile_flags = MIF_SPLASH | MIF_ARC; + W_SETUPPROJECTILEVELOCITY_UP(gren, g_balance_grenadelauncher_secondary); + + gren.angles = vectoangles (gren.velocity); + gren.flags = FL_PROJECTILE; + + if(autocvar_g_balance_grenadelauncher_secondary_type == 0 || autocvar_g_balance_grenadelauncher_secondary_type == 2) + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE, TRUE); + else + CSQCProjectile(gren, TRUE, PROJECTILE_GRENADE_BOUNCING, TRUE); + + other = gren; MUTATOR_CALLHOOK(EditProjectile); +} + +void spawnfunc_weapon_grenadelauncher (void) +{ + weapon_defaultspawnfunc(WEP_GRENADE_LAUNCHER); +} + +.float bot_secondary_grenademooth; +float w_glauncher(float req) +{ + entity nade; + float nadefound; + float ammo_amount; + + if (req == WR_AIM) + { + self.BUTTON_ATCK = FALSE; + self.BUTTON_ATCK2 = FALSE; + if (self.bot_secondary_grenademooth == 0) + { + if(bot_aim(autocvar_g_balance_grenadelauncher_primary_speed, autocvar_g_balance_grenadelauncher_primary_speed_up, autocvar_g_balance_grenadelauncher_primary_lifetime, TRUE)) + { + self.BUTTON_ATCK = TRUE; + if(random() < 0.01) self.bot_secondary_grenademooth = 1; + } + } + else + { + if(bot_aim(autocvar_g_balance_grenadelauncher_secondary_speed, autocvar_g_balance_grenadelauncher_secondary_speed_up, autocvar_g_balance_grenadelauncher_secondary_lifetime, TRUE)) + { + self.BUTTON_ATCK2 = TRUE; + if(random() < 0.02) self.bot_secondary_grenademooth = 0; + } + } + } + else if (req == WR_THINK) + { + if(autocvar_g_balance_grenadelauncher_reload_ammo && self.clip_load < min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo)) // forced reload + weapon_action(self.weapon, WR_RELOAD); + else if (self.BUTTON_ATCK) + { + if (weapon_prepareattack(0, autocvar_g_balance_grenadelauncher_primary_refire)) + { + W_Grenade_Attack(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_grenadelauncher_primary_animtime, w_ready); + } + } + else if (self.BUTTON_ATCK2) + { + if (cvar("g_balance_grenadelauncher_secondary_remote_detonateprimary")) + { + nadefound = 0; + for(nade = world; (nade = find(nade, classname, "grenade")); ) if(nade.realowner == self) + { + if(!nade.gl_detonate_later) + { + nade.gl_detonate_later = TRUE; + nadefound = 1; + } + } + if(nadefound) + sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + } + else if (weapon_prepareattack(1, autocvar_g_balance_grenadelauncher_secondary_refire)) + { + W_Grenade_Attack2(); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_grenadelauncher_secondary_animtime, w_ready); + } + } + } + else if (req == WR_PRECACHE) + { + precache_model ("models/weapons/g_gl.md3"); + precache_model ("models/weapons/v_gl.md3"); + precache_model ("models/weapons/h_gl.iqm"); + precache_sound ("weapons/grenade_bounce1.wav"); + precache_sound ("weapons/grenade_bounce2.wav"); + precache_sound ("weapons/grenade_bounce3.wav"); + precache_sound ("weapons/grenade_bounce4.wav"); + precache_sound ("weapons/grenade_bounce5.wav"); + precache_sound ("weapons/grenade_bounce6.wav"); + precache_sound ("weapons/grenade_stick.wav"); + precache_sound ("weapons/grenade_fire.wav"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_GRENADE_LAUNCHER); + self.current_ammo = ammo_rockets; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_primary_ammo; + ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + ammo_amount = self.ammo_rockets >= autocvar_g_balance_grenadelauncher_secondary_ammo; + ammo_amount += self.(weapon_load[WEP_GRENADE_LAUNCHER]) >= autocvar_g_balance_grenadelauncher_secondary_ammo; + return ammo_amount; + } + else if (req == WR_RELOAD) + { + W_Reload(min(autocvar_g_balance_grenadelauncher_primary_ammo, autocvar_g_balance_grenadelauncher_secondary_ammo), autocvar_g_balance_grenadelauncher_reload_ammo, autocvar_g_balance_grenadelauncher_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MORTAR_SUICIDE_BOUNCE; + else + return WEAPON_MORTAR_SUICIDE_EXPLODE; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_MORTAR_MURDER_BOUNCE; + else + return WEAPON_MORTAR_MURDER_EXPLODE; + } + return TRUE; +} +#endif +#ifdef CSQC +float w_glauncher(float req) +{ + if(req == WR_IMPACTEFFECT) + { + vector org2; + org2 = w_org + w_backoff * 12; + pointparticles(particleeffectnum("grenade_explode"), org2, '0 0 0', 1); + if(!w_issilent) + sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/grenade_impact.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/rocketlauncher.qc b/qcsrc/common/weapons/rocketlauncher.qc deleted file mode 100644 index 504167cda..000000000 --- a/qcsrc/common/weapons/rocketlauncher.qc +++ /dev/null @@ -1,491 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ ROCKET_LAUNCHER, -/* function */ w_rlauncher, -/* ammotype */ IT_ROCKETS, -/* impulse */ 9, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, -/* rating */ BOT_PICKUP_RATING_HIGH, -/* model */ "rl", -/* shortname */ "rocketlauncher", -/* fullname */ _("Rocket Launcher") -); -#else -#ifdef SVQC -.float rl_release; -.float rl_detonate_later; - -void W_Rocket_Unregister() -{ - if(self.realowner && self.realowner.lastrocket == self) - { - self.realowner.lastrocket = world; - // self.realowner.rl_release = 1; - } -} - -void W_Rocket_Explode () -{ - W_Rocket_Unregister(); - - if(other.takedamage == DAMAGE_AIM) - if(IS_PLAYER(other)) - if(IsDifferentTeam(self.realowner, other)) - if(other.deadflag == DEAD_NO) - if(IsFlying(other)) - Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_damage, autocvar_g_balance_rocketlauncher_edgedamage, autocvar_g_balance_rocketlauncher_radius, world, world, autocvar_g_balance_rocketlauncher_force, self.projectiledeathtype, other); - - if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) - { - if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) - { - self.realowner.cnt = WEP_ROCKET_LAUNCHER; - ATTACK_FINISHED(self.realowner) = time; - self.realowner.switchweapon = w_getbestweapon(self.realowner); - } - } - remove (self); -} - -void W_Rocket_DoRemoteExplode () -{ - W_Rocket_Unregister(); - - self.event_damage = func_null; - self.takedamage = DAMAGE_NO; - - RadiusDamage (self, self.realowner, autocvar_g_balance_rocketlauncher_remote_damage, autocvar_g_balance_rocketlauncher_remote_edgedamage, autocvar_g_balance_rocketlauncher_remote_radius, world, world, autocvar_g_balance_rocketlauncher_remote_force, self.projectiledeathtype | HITTYPE_BOUNCE, world); - - if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) - { - if(self.realowner.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) - { - self.realowner.cnt = WEP_ROCKET_LAUNCHER; - ATTACK_FINISHED(self.realowner) = time; - self.realowner.switchweapon = w_getbestweapon(self.realowner); - } - } - remove (self); -} - -void W_Rocket_RemoteExplode() -{ - if(self.realowner.deadflag == DEAD_NO) - if(self.realowner.lastrocket) - { - if((self.spawnshieldtime >= 0) - ? (time >= self.spawnshieldtime) // timer - : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > autocvar_g_balance_rocketlauncher_remote_radius) // safety device - ) - { - W_Rocket_DoRemoteExplode(); - } - } -} - -vector rocket_steerto(vector thisdir, vector goaldir, float maxturn_cos) -{ - if(thisdir * goaldir > maxturn_cos) - return goaldir; - if(thisdir * goaldir < -0.9998) // less than 1 degree and opposite - return thisdir; // refuse to guide (better than letting a numerical error happen) - float f, m2; - vector v; - // solve: - // g = normalize(thisdir + goaldir * X) - // thisdir * g = maxturn - // - // gg = thisdir + goaldir * X - // (thisdir * gg)^2 = maxturn^2 * (gg * gg) - // - // (1 + (thisdir * goaldir) * X)^2 = maxturn^2 * (1 + X*X + 2 * X * thisdir * goaldir) - f = thisdir * goaldir; - // (1 + f * X)^2 = maxturn^2 * (1 + X*X + 2 * X * f) - // 0 = (m^2 - f^2) * x^2 + (2 * f * (m^2 - 1)) * x + (m^2 - 1) - m2 = maxturn_cos * maxturn_cos; - v = solve_quadratic(m2 - f * f, 2 * f * (m2 - 1), m2 - 1); - return normalize(thisdir + goaldir * v_y); // the larger solution! -} -// assume thisdir == -goaldir: -// f == -1 -// v = solve_qadratic(m2 - 1, -2 * (m2 - 1), m2 - 1) -// (m2 - 1) x^2 - 2 * (m2 - 1) * x + (m2 - 1) = 0 -// x^2 - 2 * x + 1 = 0 -// (x - 1)^2 = 0 -// x = 1 -// normalize(thisdir + goaldir) -// normalize(0) - -void W_Rocket_Think (void) -{ - vector desireddir, olddir, newdir, desiredorigin, goal; -#if 0 - float cosminang, cosmaxang, cosang; -#endif - float velspeed, f; - self.nextthink = time; - if (time > self.cnt) - { - other = world; - self.projectiledeathtype |= HITTYPE_BOUNCE; - W_Rocket_Explode (); - return; - } - - // accelerate - makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0'); - velspeed = autocvar_g_balance_rocketlauncher_speed * g_weaponspeedfactor - (self.velocity * v_forward); - if (velspeed > 0) - self.velocity = self.velocity + v_forward * min(autocvar_g_balance_rocketlauncher_speedaccel * g_weaponspeedfactor * frametime, velspeed); - - // laser guided, or remote detonation - if (self.realowner.weapon == WEP_ROCKET_LAUNCHER) - { - if(self == self.realowner.lastrocket) - if not(self.realowner.rl_release) - if not(self.BUTTON_ATCK2) - if(autocvar_g_balance_rocketlauncher_guiderate) - if(time > self.pushltime) - if(self.realowner.deadflag == DEAD_NO) - { - f = autocvar_g_balance_rocketlauncher_guideratedelay; - if(f) - f = bound(0, (time - self.pushltime) / f, 1); - else - f = 1; - - velspeed = vlen(self.velocity); - - makevectors(self.realowner.v_angle); - desireddir = WarpZone_RefSys_TransformVelocity(self.realowner, self, v_forward); - desiredorigin = WarpZone_RefSys_TransformOrigin(self.realowner, self, self.realowner.origin + self.realowner.view_ofs); - olddir = normalize(self.velocity); - - // now it gets tricky... we want to move like some curve to approximate the target direction - // but we are limiting the rate at which we can turn! - goal = desiredorigin + ((self.origin - desiredorigin) * desireddir + autocvar_g_balance_rocketlauncher_guidegoal) * desireddir; - newdir = rocket_steerto(olddir, normalize(goal - self.origin), cos(autocvar_g_balance_rocketlauncher_guiderate * f * frametime * DEG2RAD)); - - self.velocity = newdir * velspeed; - self.angles = vectoangles(self.velocity); - - if(!self.count) - { - pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1); - // TODO add a better sound here - sound (self.realowner, CH_WEAPON_B, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); - self.count = 1; - } - } - - if(self.rl_detonate_later) - W_Rocket_RemoteExplode(); - } - - if(self.csqcprojectile_clientanimate == 0) - UpdateCSQCProjectile(self); -} - -void W_Rocket_Touch (void) -{ - if(WarpZone_Projectile_Touch()) - { - if(wasfreed(self)) - W_Rocket_Unregister(); - return; - } - W_Rocket_Unregister(); - W_Rocket_Explode (); -} - -void W_Rocket_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; - self.angles = vectoangles(self.velocity); - - if (self.health <= 0) - W_PrepareExplosionByDamage(attacker, W_Rocket_Explode); -} - -void W_Rocket_Attack (void) -{ - entity missile; - entity flash; - - W_DecreaseAmmo(ammo_rockets, autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo); - - W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", CH_WEAPON_A, autocvar_g_balance_rocketlauncher_damage); - pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - missile = WarpZone_RefSys_SpawnSameRefSys(self); - missile.owner = missile.realowner = self; - self.lastrocket = missile; - if(autocvar_g_balance_rocketlauncher_detonatedelay >= 0) - missile.spawnshieldtime = time + autocvar_g_balance_rocketlauncher_detonatedelay; - else - missile.spawnshieldtime = -1; - missile.pushltime = time + autocvar_g_balance_rocketlauncher_guidedelay; - missile.classname = "rocket"; - missile.bot_dodge = TRUE; - missile.bot_dodgerating = autocvar_g_balance_rocketlauncher_damage * 2; // * 2 because it can be detonated inflight which makes it even more dangerous - - missile.takedamage = DAMAGE_YES; - missile.damageforcescale = autocvar_g_balance_rocketlauncher_damageforcescale; - missile.health = autocvar_g_balance_rocketlauncher_health; - missile.event_damage = W_Rocket_Damage; - missile.damagedbycontents = TRUE; - - missile.movetype = MOVETYPE_FLY; - PROJECTILE_MAKETRIGGER(missile); - missile.projectiledeathtype = WEP_ROCKET_LAUNCHER; - setsize (missile, '-3 -3 -3', '3 3 3'); // give it some size so it can be shot - - setorigin (missile, w_shotorg - v_forward * 3); // move it back so it hits the wall at the right point - W_SetupProjectileVelocity(missile, autocvar_g_balance_rocketlauncher_speedstart, 0); - missile.angles = vectoangles (missile.velocity); - - missile.touch = W_Rocket_Touch; - missile.think = W_Rocket_Think; - missile.nextthink = time; - missile.cnt = time + autocvar_g_balance_rocketlauncher_lifetime; - missile.flags = FL_PROJECTILE; - missile.missile_flags = MIF_SPLASH; - - CSQCProjectile(missile, autocvar_g_balance_rocketlauncher_guiderate == 0 && autocvar_g_balance_rocketlauncher_speedaccel == 0, PROJECTILE_ROCKET, FALSE); // because of fly sound - - // muzzle flash for 1st person view - flash = spawn (); - setmodel (flash, "models/flash.md3"); // precision set below - SUB_SetFade (flash, time, 0.1); - flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - W_AttachToShotorg(flash, '5 0 0'); - - // common properties - other = missile; MUTATOR_CALLHOOK(EditProjectile); -} - -void spawnfunc_weapon_rocketlauncher (void); // defined in t_items.qc - -float w_rlauncher(float req) -{ - entity rock; - float rockfound; - float ammo_amount; - - if (req == WR_AIM) - { - // aim and decide to fire if appropriate - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_rocketlauncher_speed, 0, autocvar_g_balance_rocketlauncher_lifetime, FALSE); - if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! - { - // decide whether to detonate rockets - entity missile, targetlist, targ; - float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; - float selfdamage, teamdamage, enemydamage; - edgedamage = autocvar_g_balance_rocketlauncher_edgedamage; - coredamage = autocvar_g_balance_rocketlauncher_damage; - edgeradius = autocvar_g_balance_rocketlauncher_radius; - recipricoledgeradius = 1 / edgeradius; - selfdamage = 0; - teamdamage = 0; - enemydamage = 0; - targetlist = findchainfloat(bot_attack, TRUE); - missile = find(world, classname, "rocket"); - while (missile) - { - if (missile.realowner != self) - { - missile = find(missile, classname, "rocket"); - continue; - } - targ = targetlist; - while (targ) - { - d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - missile.origin); - d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000); - // count potential damage according to type of target - if (targ == self) - selfdamage = selfdamage + d; - else if (targ.team == self.team && teamplay) - teamdamage = teamdamage + d; - else if (bot_shouldattack(targ)) - enemydamage = enemydamage + d; - targ = targ.chain; - } - missile = find(missile, classname, "rocket"); - } - float desirabledamage; - desirabledamage = enemydamage; - if (time > self.invincible_finished && time > self.spawnshieldtime) - desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent; - if (teamplay && self.team) - desirabledamage = desirabledamage - teamdamage; - - missile = find(world, classname, "rocket"); - while (missile) - { - if (missile.realowner != self) - { - missile = find(missile, classname, "rocket"); - continue; - } - makevectors(missile.v_angle); - targ = targetlist; - if (skill > 9) // normal players only do this for the target they are tracking - { - targ = targetlist; - while (targ) - { - if ( - (v_forward * normalize(missile.origin - targ.origin)< 0.1) - && desirabledamage > 0.1*coredamage - )self.BUTTON_ATCK2 = TRUE; - targ = targ.chain; - } - }else{ - float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000); - //As the distance gets larger, a correct detonation gets near imposible - //Bots are assumed to use the rocket spawnfunc_light to see if the rocket gets near a player - if(v_forward * normalize(missile.origin - self.enemy.origin)< 0.1) - if(IS_PLAYER(self.enemy)) - if(desirabledamage >= 0.1*coredamage) - if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1)) - self.BUTTON_ATCK2 = TRUE; - // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n"); - } - - missile = find(missile, classname, "rocket"); - } - // if we would be doing at X percent of the core damage, detonate it - // but don't fire a new shot at the same time! - if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events - self.BUTTON_ATCK2 = TRUE; - if ((skill > 6.5) && (selfdamage > self.health)) - self.BUTTON_ATCK2 = FALSE; - //if(self.BUTTON_ATCK2 == TRUE) - // dprint(ftos(desirabledamage),"\n"); - if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE; - } - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_rocketlauncher_reload_ammo && self.clip_load < autocvar_g_balance_rocketlauncher_ammo) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else - { - if (self.BUTTON_ATCK) - { - if(self.rl_release || autocvar_g_balance_rocketlauncher_guidestop) - if(weapon_prepareattack(0, autocvar_g_balance_rocketlauncher_refire)) - { - W_Rocket_Attack(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_rocketlauncher_animtime, w_ready); - self.rl_release = 0; - } - } - else - self.rl_release = 1; - - if (self.BUTTON_ATCK2) - { - rockfound = 0; - for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == self) - { - if(!rock.rl_detonate_later) - { - rock.rl_detonate_later = TRUE; - rockfound = 1; - } - } - if(rockfound) - sound (self, CH_WEAPON_B, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/flash.md3"); - precache_model ("models/weapons/g_rl.md3"); - precache_model ("models/weapons/v_rl.md3"); - precache_model ("models/weapons/h_rl.iqm"); - precache_sound ("weapons/rocket_det.wav"); - precache_sound ("weapons/rocket_fire.wav"); - precache_sound ("weapons/rocket_mode.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_ROCKET_LAUNCHER); - self.current_ammo = ammo_rockets; - self.rl_release = 1; - } - else if (req == WR_CHECKAMMO1) - { - // don't switch while guiding a missile - if (ATTACK_FINISHED(self) <= time || self.weapon != WEP_ROCKET_LAUNCHER) - { - ammo_amount = FALSE; - if(autocvar_g_balance_rocketlauncher_reload_ammo) - { - if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo && self.(weapon_load[WEP_ROCKET_LAUNCHER]) < autocvar_g_balance_rocketlauncher_ammo) - ammo_amount = TRUE; - } - else if(self.ammo_rockets < autocvar_g_balance_rocketlauncher_ammo) - ammo_amount = TRUE; - return !ammo_amount; - } - } - else if (req == WR_CHECKAMMO2) - return FALSE; - else if (req == WR_RESETPLAYER) - { - self.rl_release = 0; - } - else if (req == WR_RELOAD) - { - W_Reload(autocvar_g_balance_rocketlauncher_ammo, autocvar_g_balance_rocketlauncher_reload_ammo, autocvar_g_balance_rocketlauncher_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_ROCKETLAUNCHER_SUICIDE; - } - else if (req == WR_KILLMESSAGE) - { - if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH)) - return WEAPON_ROCKETLAUNCHER_MURDER_SPLASH; - else - return WEAPON_ROCKETLAUNCHER_MURDER_DIRECT; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_rlauncher(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 12; - pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1); - if(!w_issilent) - sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/rocket_impact.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/common/weapons/shockwave.qc b/qcsrc/common/weapons/shockwave.qc new file mode 100644 index 000000000..6c6658d93 --- /dev/null +++ b/qcsrc/common/weapons/shockwave.qc @@ -0,0 +1,294 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON( +/* WEP_##id */ SHOTGUN, +/* function */ w_shotgun, +/* ammotype */ IT_SHELLS, +/* impulse */ 2, +/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, +/* rating */ BOT_PICKUP_RATING_LOW, +/* model */ "shotgun", +/* shortname */ "shotgun", +/* fullname */ _("Shotgun") +); +#else +#ifdef SVQC + +void W_Shotgun_Attack (void) +{ + float sc; + float ammoamount; + float bullets; + float d; + float f; + float spread; + float bulletspeed; + float bulletconstant; + entity flash; + + ammoamount = autocvar_g_balance_shotgun_primary_ammo; + bullets = autocvar_g_balance_shotgun_primary_bullets; + d = autocvar_g_balance_shotgun_primary_damage; + f = autocvar_g_balance_shotgun_primary_force; + spread = autocvar_g_balance_shotgun_primary_spread; + bulletspeed = autocvar_g_balance_shotgun_primary_speed; + bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant; + + W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo); + + W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets); + for (sc = 0;sc < bullets;sc = sc + 1) + fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant); + endFireBallisticBullet(); + + pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo); + + // casing code + if (autocvar_g_casings >= 1) + for (sc = 0;sc < ammoamount;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 shotgun_meleethink (void) +{ + // 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 = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor(); + swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); + f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces); + + // check to see if we can still continue, otherwise give up now + if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_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 / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1); + + targpos = (self.realowner.origin + self.realowner.view_ofs + + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range) + + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up) + + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_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"); + + 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 || autocvar_g_balance_shotgun_secondary_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 = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1)); + else + swing_damage = (autocvar_g_balance_shotgun_secondary_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 * autocvar_g_balance_shotgun_secondary_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(autocvar_g_balance_shotgun_secondary_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, ATTN_NORM); + weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready); + + entity meleetemp; + meleetemp = spawn(); + meleetemp.realowner = self; + meleetemp.think = shotgun_meleethink; + meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor(); + W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range); +} + +void spawnfunc_weapon_shotgun(); // defined in t_items.qc + +.float shotgun_primarytime; + +float w_shotgun(float req) +{ + float ammo_amount; + if (req == WR_AIM) + if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range) + self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); + else + { + if(autocvar_g_antilag_bullets) + self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); + else + self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE); + } + + else if (req == WR_THINK) + { + if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload + { + // don't force reload an empty shotgun if its melee attack is active + if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo) + weapon_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, autocvar_g_balance_shotgun_primary_animtime)) + { + W_Shotgun_Attack(); + self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor(); + weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready); + } + } + } + } + if (self.clip_load >= 0) // we are not currently reloading + if (!self.crouch) // no crouchmelee please + if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary) + if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_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); + } + } + else if (req == WR_PRECACHE) + { + 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"); + //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_SHOTGUN); + self.current_ammo = ammo_shells; + } + else if (req == WR_CHECKAMMO1) + { + ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo; + ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo; + return ammo_amount; + } + else if (req == WR_CHECKAMMO2) + { + // melee attack is always available + return TRUE; + } + else if (req == WR_RELOAD) + { + W_Reload(autocvar_g_balance_shotgun_primary_ammo, autocvar_g_balance_shotgun_reload_ammo, autocvar_g_balance_shotgun_reload_time, "weapons/reload.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + { + return WEAPON_THINKING_WITH_PORTALS; + } + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_SECONDARY) + return WEAPON_SHOTGUN_MURDER_SLAP; + else + return WEAPON_SHOTGUN_MURDER; + } + return TRUE; +} +#endif +#ifdef CSQC +.float prevric; +float w_shotgun(float req) +{ + if(req == 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, ATTN_NORM); + else if(w_random < 0.033) + sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); + else if(w_random < 0.05) + sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); + self.prevric = time; + } + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/ric1.wav"); + precache_sound("weapons/ric2.wav"); + precache_sound("weapons/ric3.wav"); + } + return TRUE; +} +#endif +#endif diff --git a/qcsrc/common/weapons/shotgun.qc b/qcsrc/common/weapons/shotgun.qc deleted file mode 100644 index 6c6658d93..000000000 --- a/qcsrc/common/weapons/shotgun.qc +++ /dev/null @@ -1,294 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ SHOTGUN, -/* function */ w_shotgun, -/* ammotype */ IT_SHELLS, -/* impulse */ 2, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_LOW, -/* model */ "shotgun", -/* shortname */ "shotgun", -/* fullname */ _("Shotgun") -); -#else -#ifdef SVQC - -void W_Shotgun_Attack (void) -{ - float sc; - float ammoamount; - float bullets; - float d; - float f; - float spread; - float bulletspeed; - float bulletconstant; - entity flash; - - ammoamount = autocvar_g_balance_shotgun_primary_ammo; - bullets = autocvar_g_balance_shotgun_primary_bullets; - d = autocvar_g_balance_shotgun_primary_damage; - f = autocvar_g_balance_shotgun_primary_force; - spread = autocvar_g_balance_shotgun_primary_spread; - bulletspeed = autocvar_g_balance_shotgun_primary_speed; - bulletconstant = autocvar_g_balance_shotgun_primary_bulletconstant; - - W_DecreaseAmmo(ammo_shells, ammoamount, autocvar_g_balance_shotgun_reload_ammo); - - W_SetupShot (self, autocvar_g_antilag_bullets && bulletspeed >= autocvar_g_antilag_bullets, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, d * bullets); - for (sc = 0;sc < bullets;sc = sc + 1) - fireBallisticBullet(w_shotorg, w_shotdir, spread, bulletspeed, 5, d, f, WEP_SHOTGUN, 0, 1, bulletconstant); - endFireBallisticBullet(); - - pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, autocvar_g_balance_shotgun_primary_ammo); - - // casing code - if (autocvar_g_casings >= 1) - for (sc = 0;sc < ammoamount;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 shotgun_meleethink (void) -{ - // 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 = autocvar_g_balance_shotgun_secondary_melee_time * W_WeaponRateFactor(); - swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10); - f = ((1 - swing) * autocvar_g_balance_shotgun_secondary_melee_traces); - - // check to see if we can still continue, otherwise give up now - if((self.realowner.deadflag != DEAD_NO) && autocvar_g_balance_shotgun_secondary_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 / autocvar_g_balance_shotgun_secondary_melee_traces)) * 2 - 1); - - targpos = (self.realowner.origin + self.realowner.view_ofs - + (v_forward * autocvar_g_balance_shotgun_secondary_melee_range) - + (v_up * swing_factor * autocvar_g_balance_shotgun_secondary_melee_swing_up) - + (v_right * swing_factor * autocvar_g_balance_shotgun_secondary_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"); - - 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 || autocvar_g_balance_shotgun_secondary_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 = (autocvar_g_balance_shotgun_secondary_damage * min(1, swing_factor + 1)); - else - swing_damage = (autocvar_g_balance_shotgun_secondary_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 * autocvar_g_balance_shotgun_secondary_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(autocvar_g_balance_shotgun_secondary_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, ATTN_NORM); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_shotgun_secondary_animtime, w_ready); - - entity meleetemp; - meleetemp = spawn(); - meleetemp.realowner = self; - meleetemp.think = shotgun_meleethink; - meleetemp.nextthink = time + autocvar_g_balance_shotgun_secondary_melee_delay * W_WeaponRateFactor(); - W_SetupShot_Range(self, TRUE, 0, "", 0, autocvar_g_balance_shotgun_secondary_damage, autocvar_g_balance_shotgun_secondary_melee_range); -} - -void spawnfunc_weapon_shotgun(); // defined in t_items.qc - -.float shotgun_primarytime; - -float w_shotgun(float req) -{ - float ammo_amount; - if (req == WR_AIM) - if(vlen(self.origin-self.enemy.origin) <= autocvar_g_balance_shotgun_secondary_melee_range) - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); - else - { - if(autocvar_g_antilag_bullets) - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); - else - self.BUTTON_ATCK = bot_aim(autocvar_g_balance_shotgun_primary_speed, 0, 0.001, FALSE); - } - - else if (req == WR_THINK) - { - if(autocvar_g_balance_shotgun_reload_ammo && self.clip_load < autocvar_g_balance_shotgun_primary_ammo) // forced reload - { - // don't force reload an empty shotgun if its melee attack is active - if not(autocvar_g_balance_shotgun_secondary && self.ammo_shells < autocvar_g_balance_shotgun_primary_ammo) - weapon_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, autocvar_g_balance_shotgun_primary_animtime)) - { - W_Shotgun_Attack(); - self.shotgun_primarytime = time + autocvar_g_balance_shotgun_primary_refire * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_shotgun_primary_animtime, w_ready); - } - } - } - } - if (self.clip_load >= 0) // we are not currently reloading - if (!self.crouch) // no crouchmelee please - if (self.BUTTON_ATCK2 && autocvar_g_balance_shotgun_secondary) - if (weapon_prepareattack(1, autocvar_g_balance_shotgun_secondary_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); - } - } - else if (req == WR_PRECACHE) - { - 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"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_SHOTGUN); - self.current_ammo = ammo_shells; - } - else if (req == WR_CHECKAMMO1) - { - ammo_amount = self.ammo_shells >= autocvar_g_balance_shotgun_primary_ammo; - ammo_amount += self.(weapon_load[WEP_SHOTGUN]) >= autocvar_g_balance_shotgun_primary_ammo; - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - // melee attack is always available - return TRUE; - } - else if (req == WR_RELOAD) - { - W_Reload(autocvar_g_balance_shotgun_primary_ammo, autocvar_g_balance_shotgun_reload_ammo, autocvar_g_balance_shotgun_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_SHOTGUN_MURDER_SLAP; - else - return WEAPON_SHOTGUN_MURDER; - } - return TRUE; -} -#endif -#ifdef CSQC -.float prevric; -float w_shotgun(float req) -{ - if(req == 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, ATTN_NORM); - else if(w_random < 0.033) - sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.05) - sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); - self.prevric = time; - } - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/ric1.wav"); - precache_sound("weapons/ric2.wav"); - precache_sound("weapons/ric3.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/common/weapons/uzi.qc b/qcsrc/common/weapons/uzi.qc deleted file mode 100644 index 923ed9504..000000000 --- a/qcsrc/common/weapons/uzi.qc +++ /dev/null @@ -1,341 +0,0 @@ -#ifdef REGISTER_WEAPON -REGISTER_WEAPON( -/* WEP_##id */ UZI, -/* function */ w_uzi, -/* ammotype */ IT_NAILS, -/* impulse */ 3, -/* flags */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN, -/* rating */ BOT_PICKUP_RATING_MID, -/* model */ "uzi", -/* shortname */ "uzi", -/* fullname */ _("Machine Gun") -); -#else -#ifdef SVQC - -// leilei's fancy muzzleflash stuff -void UZI_Flash_Go() -{ - self.frame = self.frame + 2; - self.scale = self.scale * 0.5; - self.alpha = self.alpha - 0.25; - self.nextthink = time + 0.05; - - if (self.alpha <= 0) - { - self.think = SUB_Remove; - self.nextthink = time; - self.realowner.muzzle_flash = world; - return; - } - -} - -void UziFlash() -{ - if (self.muzzle_flash == world) - self.muzzle_flash = spawn(); - - // muzzle flash for 1st person view - setmodel(self.muzzle_flash, "models/uziflash.md3"); // precision set below - - self.muzzle_flash.scale = 0.75; - self.muzzle_flash.think = UZI_Flash_Go; - self.muzzle_flash.nextthink = time + 0.02; - self.muzzle_flash.frame = 2; - self.muzzle_flash.alpha = 0.75; - self.muzzle_flash.angles_z = random() * 180; - self.muzzle_flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION; - self.muzzle_flash.owner = self.muzzle_flash.realowner = self; -} - -void W_UZI_Attack (float deathtype) -{ - W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, ((self.misc_bulletcounter == 1) ? autocvar_g_balance_uzi_first_damage : autocvar_g_balance_uzi_sustained_damage)); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - // this attack_finished just enforces a cooldown at the end of a burst - ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); - - if (self.misc_bulletcounter == 1) - fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_first_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_first_damage, autocvar_g_balance_uzi_first_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); - else - fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_sustained_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, deathtype, 0, 1, autocvar_g_balance_uzi_bulletconstant); - endFireBallisticBullet(); - - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - UziFlash(); - W_AttachToShotorg(self.muzzle_flash, '5 0 0'); - - // casing code - if (autocvar_g_casings >= 2) - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); - - if (self.misc_bulletcounter == 1) - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_first_ammo, autocvar_g_balance_uzi_reload_ammo); - else - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); -} - -// weapon frames -void uzi_fire1_02() -{ - if(self.weapon != self.switchweapon) // abort immediately if switching - { - w_ready(); - return; - } - if (self.BUTTON_ATCK) - { - if (!weapon_action(self.weapon, WR_CHECKAMMO2)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - self.misc_bulletcounter = self.misc_bulletcounter + 1; - W_UZI_Attack(WEP_UZI); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); - } - else - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, w_ready); -} - - -void uzi_mode1_fire_auto() -{ - float uzi_spread; - - if (!self.BUTTON_ATCK) - { - w_ready(); - return; - } - - if (!weapon_action(self.weapon, WR_CHECKAMMO1)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return; - } - - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_reload_ammo); - - W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - uzi_spread = bound(autocvar_g_balance_uzi_spread_min, autocvar_g_balance_uzi_spread_min + (autocvar_g_balance_uzi_spread_add * self.misc_bulletcounter), autocvar_g_balance_uzi_spread_max); - fireBallisticBullet(w_shotorg, w_shotdir, uzi_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); - endFireBallisticBullet(); - - self.misc_bulletcounter = self.misc_bulletcounter + 1; - - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - UziFlash(); - W_AttachToShotorg(self.muzzle_flash, '5 0 0'); - - if (autocvar_g_casings >= 2) // casing code - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); - - ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_first_refire * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_mode1_fire_auto); -} - -void uzi_mode1_fire_burst() -{ - W_SetupShot (self, autocvar_g_antilag_bullets && autocvar_g_balance_uzi_speed >= autocvar_g_antilag_bullets, 0, "weapons/uzi_fire.wav", CH_WEAPON_A, autocvar_g_balance_uzi_sustained_damage); - if (!g_norecoil) - { - self.punchangle_x = random () - 0.5; - self.punchangle_y = random () - 0.5; - } - - fireBallisticBullet(w_shotorg, w_shotdir, autocvar_g_balance_uzi_burst_spread, autocvar_g_balance_uzi_speed, 5, autocvar_g_balance_uzi_sustained_damage, autocvar_g_balance_uzi_sustained_force, WEP_UZI, 0, 1, autocvar_g_balance_uzi_bulletconstant); - endFireBallisticBullet(); - - - pointparticles(particleeffectnum("uzi_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); - - UziFlash(); - W_AttachToShotorg(self.muzzle_flash, '5 0 0'); - - if (autocvar_g_casings >= 2) // casing code - SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 70) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 3, self); - - self.misc_bulletcounter = self.misc_bulletcounter + 1; - if (self.misc_bulletcounter == 0) - { - ATTACK_FINISHED(self) = time + autocvar_g_balance_uzi_burst_refire2 * W_WeaponRateFactor(); - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_animtime, w_ready); - } - else - { - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_burst_refire, uzi_mode1_fire_burst); - } - -} - -void spawnfunc_weapon_machinegun(); // defined in t_items.qc - -float w_uzi(float req) -{ - float ammo_amount; - if (req == WR_AIM) - if(vlen(self.origin-self.enemy.origin) < 3000 - bound(0, skill, 10) * 200) - self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); - else - { - self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); - } - else if (req == WR_THINK) - { - if(autocvar_g_balance_uzi_reload_ammo && self.clip_load < min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo)) // forced reload - weapon_action(self.weapon, WR_RELOAD); - else if(autocvar_g_balance_uzi_mode == 1) - { - if (self.BUTTON_ATCK) - if (weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 0; - uzi_mode1_fire_auto(); - } - - if(self.BUTTON_ATCK2) - if(weapon_prepareattack(1, 0)) - { - if (!weapon_action(self.weapon, WR_CHECKAMMO2)) - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - { - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w_ready(); - return FALSE; - } - - W_DecreaseAmmo(ammo_nails, autocvar_g_balance_uzi_burst_ammo, autocvar_g_balance_uzi_reload_ammo); - - self.misc_bulletcounter = autocvar_g_balance_uzi_burst * -1; - uzi_mode1_fire_burst(); - } - } - else - { - - if (self.BUTTON_ATCK) - if (weapon_prepareattack(0, 0)) - { - self.misc_bulletcounter = 1; - W_UZI_Attack(WEP_UZI); // sets attack_finished - weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_uzi_sustained_refire, uzi_fire1_02); - } - - if (self.BUTTON_ATCK2 && autocvar_g_balance_uzi_first) - if (weapon_prepareattack(1, 0)) - { - self.misc_bulletcounter = 1; - W_UZI_Attack(WEP_UZI | HITTYPE_SECONDARY); // sets attack_finished - weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_uzi_first_refire, w_ready); - } - } - } - else if (req == WR_PRECACHE) - { - precache_model ("models/uziflash.md3"); - precache_model ("models/weapons/g_uzi.md3"); - precache_model ("models/weapons/v_uzi.md3"); - precache_model ("models/weapons/h_uzi.iqm"); - precache_sound ("weapons/uzi_fire.wav"); - //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else - } - else if (req == WR_SETUP) - { - weapon_setup(WEP_UZI); - self.current_ammo = ammo_nails; - } - else if (req == WR_CHECKAMMO1) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_sustained_ammo; - else - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; - - if(autocvar_g_balance_uzi_reload_ammo) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_sustained_ammo; - else - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; - } - return ammo_amount; - } - else if (req == WR_CHECKAMMO2) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_burst_ammo; - else - ammo_amount = self.ammo_nails >= autocvar_g_balance_uzi_first_ammo; - - if(autocvar_g_balance_uzi_reload_ammo) - { - if(autocvar_g_balance_uzi_mode == 1) - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_burst_ammo; - else - ammo_amount += self.(weapon_load[WEP_UZI]) >= autocvar_g_balance_uzi_first_ammo; - } - return ammo_amount; - } - else if (req == WR_RELOAD) - { - W_Reload(min(max(autocvar_g_balance_uzi_sustained_ammo, autocvar_g_balance_uzi_first_ammo), autocvar_g_balance_uzi_burst_ammo), autocvar_g_balance_uzi_reload_ammo, autocvar_g_balance_uzi_reload_time, "weapons/reload.wav"); - } - else if (req == WR_SUICIDEMESSAGE) - { - return WEAPON_THINKING_WITH_PORTALS; - } - else if (req == WR_KILLMESSAGE) - { - if(w_deathtype & HITTYPE_SECONDARY) - return WEAPON_UZI_MURDER_SNIPE; - else - return WEAPON_UZI_MURDER_SPRAY; - } - return TRUE; -} -#endif -#ifdef CSQC -float w_uzi(float req) -{ - if(req == WR_IMPACTEFFECT) - { - vector org2; - org2 = w_org + w_backoff * 2; - pointparticles(particleeffectnum("machinegun_impact"), org2, w_backoff * 1000, 1); - if(!w_issilent) - if(w_random < 0.05) - sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.1) - sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTN_NORM); - else if(w_random < 0.2) - sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTN_NORM); - } - else if(req == WR_PRECACHE) - { - precache_sound("weapons/ric1.wav"); - precache_sound("weapons/ric2.wav"); - precache_sound("weapons/ric3.wav"); - } - return TRUE; -} -#endif -#endif diff --git a/qcsrc/common/weapons/weapons.qh b/qcsrc/common/weapons/weapons.qh index bd698b007..beaebaf55 100644 --- a/qcsrc/common/weapons/weapons.qh +++ b/qcsrc/common/weapons/weapons.qh @@ -1,17 +1,19 @@ // ONLY EVER ADD NEW WEAPONS AT THE END. IF YOU REMOVE ONE, PUT THE LAST ONE ON // ITS PLACE. THIS IS TO AVOID UNNECESSARY RENUMBERING OF WEAPON IMPULSES. // IF YOU DISREGARD THIS NOTICE, I'LL KILL YOU WITH THE @!#%'N TUBA -#include "laser.qc" -#include "shotgun.qc" -#include "uzi.qc" -#include "grenadelauncher.qc" + +// core weapons +#include "blaster.qc" +#include "shockwave.qc" +#include "machinegun.qc" +#include "mortar.qc" #include "minelayer.qc" #include "electro.qc" -#include "lightning.qc" +#include "arc.qc" #include "crylink.qc" #include "nex.qc" #include "hagar.qc" -#include "rocketlauncher.qc" +#include "devastator.qc" #include "porto.qc" #include "minstanex.qc" #include "hook.qc" @@ -20,3 +22,5 @@ #include "rifle.qc" #include "fireball.qc" #include "seeker.qc" + +// other weapons