From 4ab47ae52e69afa22af48d2d583a918c00c1b643 Mon Sep 17 00:00:00 2001 From: Mircea Kitsune Date: Sun, 26 Sep 2010 00:20:48 +0300 Subject: [PATCH] First step toward a mine layer weapon; Make a copy of the Rocket Launcher gun, and a test map to test the gun on (to be removed when this branch is merged). Next step is a proper mine projectile. --- balanceXonotic.cfg | 30 + maps/minelayer_test.bsp | Bin 0 -> 125912 bytes maps/minelayer_test.map | 79 +++ qcsrc/client/hud.qc | 1 + .../dialog_multiplayer_create_mutators.c | 2 +- qcsrc/qc-server.cbp | 1 + qcsrc/server/miscfunctions.qc | 2 +- qcsrc/server/w_all.qc | 1 + qcsrc/server/w_minelayer.qc | 525 ++++++++++++++++++ 9 files changed, 639 insertions(+), 2 deletions(-) create mode 100644 maps/minelayer_test.bsp create mode 100644 maps/minelayer_test.map create mode 100644 qcsrc/server/w_minelayer.qc diff --git a/balanceXonotic.cfg b/balanceXonotic.cfg index 076a4935b..61a5db0b4 100644 --- a/balanceXonotic.cfg +++ b/balanceXonotic.cfg @@ -303,6 +303,36 @@ set g_balance_grenadelauncher_secondary_damageforcescale 0 set g_balance_grenadelauncher_secondary_bouncefactor 0.7 set g_balance_grenadelauncher_secondary_bouncestop 0.12 // }}} +// {{{ rocketlauncher // TODO +set g_balance_minelayer_damage 100 +set g_balance_minelayer_edgedamage 33 +set g_balance_minelayer_force 350 +set g_balance_minelayer_radius 125 +set g_balance_minelayer_speed 1000 +set g_balance_minelayer_speedaccel 0 +set g_balance_minelayer_speedstart 1000 +set g_balance_minelayer_lifetime 5 +set g_balance_minelayer_refire 1 +set g_balance_minelayer_animtime 0.4 +set g_balance_minelayer_ammo 3 +set g_balance_minelayer_health 0 +set g_balance_minelayer_damageforcescale 0 +set g_balance_minelayer_detonatedelay -1 // positive: timer till detonation is allowed, negative: "security device" that prevents ANY remote detonation if it could hurt its owner, zero: detonatable at any time +set g_balance_minelayer_guiderate 45 // max degrees per second +set g_balance_minelayer_guideratedelay 0.01 // immediate +set g_balance_minelayer_guidegoal 512 // goal distance for (non-laser) guiding (higher = less control, lower = erratic) +set g_balance_minelayer_guidedelay 0.15 // delay before guiding kicks in +set g_balance_minelayer_guidestop 0 // stop guiding when firing again +set g_balance_minelayer_laserguided_speed 1000 //650 +set g_balance_minelayer_laserguided_speedaccel 0 +set g_balance_minelayer_laserguided_speedstart 1000 +set g_balance_minelayer_laserguided_turnrate 0.75 //0.5 +set g_balance_minelayer_laserguided_allow_steal 1 +set g_balance_minelayer_remote_damage 50 +set g_balance_minelayer_remote_edgedamage 16.5 +set g_balance_minelayer_remote_radius 120 +set g_balance_minelayer_remote_force 350 +// }}} // {{{ electro // TODO set g_balance_electro_lightning 1 set g_balance_electro_primary_damage 90 diff --git a/maps/minelayer_test.bsp b/maps/minelayer_test.bsp new file mode 100644 index 0000000000000000000000000000000000000000..8d778ddae1bcbb6fef0d8d44d408a6bc338d6984 GIT binary patch literal 125912 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 Ie^dPb2XFd4MgRZ+ literal 0 HcmV?d00001 diff --git a/maps/minelayer_test.map b/maps/minelayer_test.map new file mode 100644 index 000000000..ee20966c0 --- /dev/null +++ b/maps/minelayer_test.map @@ -0,0 +1,79 @@ + +// entity 0 +{ +"classname" "worldspawn" +// brush 0 +{ +( 1024 1024 1152 ) ( 1024 -1024 1152 ) ( -1024 1024 1152 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 1024 1152 ) ( -1024 1024 1152 ) ( 1024 1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 1024 1152 ) ( 1024 1024 1024 ) ( 1024 -1024 1152 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 1024 ) ( 1024 -1024 1024 ) ( -1024 1024 1024 ) skies/exosystem 0 0 0 0.5 0.5 0 0 0 +( -1024 -1024 1024 ) ( -1024 -1024 1152 ) ( 1024 -1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 1024 ) ( -1024 1024 1024 ) ( -1024 -1024 1152 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +} +// brush 1 +{ +( -1024 1024 1024 ) ( -1024 -1024 1024 ) ( -1152 1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 1024 1024 ) ( -1152 1024 1024 ) ( -1024 1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 1024 1024 ) ( -1024 1024 0 ) ( -1024 -1024 1024 ) eX/eXmetalrib01 0 0 0 1 1 0 0 0 +( -1152 -1024 0 ) ( -1024 -1024 0 ) ( -1152 1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1152 -1024 0 ) ( -1152 -1024 1024 ) ( -1024 -1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1152 -1024 0 ) ( -1152 1024 0 ) ( -1152 -1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +} +// brush 2 +{ +( 1152 1024 1024 ) ( 1152 -1024 1024 ) ( 1024 1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1152 1024 1024 ) ( 1024 1024 1024 ) ( 1152 1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1152 1024 1024 ) ( 1152 1024 0 ) ( 1152 -1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 -1024 0 ) ( 1152 -1024 0 ) ( 1024 1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 -1024 0 ) ( 1024 -1024 1024 ) ( 1152 -1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 -1024 0 ) ( 1024 1024 0 ) ( 1024 -1024 1024 ) eX/eXmetalrib01 0 0 0 1 1 0 0 0 +} +// brush 3 +{ +( -1024 -1024 1024 ) ( 1024 -1024 1024 ) ( -1024 -1152 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 1024 ) ( -1024 -1152 1024 ) ( -1024 -1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 1024 ) ( -1024 -1024 0 ) ( 1024 -1024 1024 ) eX/eXmetalrib01 0 0 0 1 1 0 0 0 +( 1024 -1152 0 ) ( 1024 -1024 0 ) ( -1024 -1152 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 -1152 0 ) ( 1024 -1152 1024 ) ( 1024 -1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 -1152 0 ) ( -1024 -1152 0 ) ( 1024 -1152 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +} +// brush 4 +{ +( -1024 1152 1024 ) ( 1024 1152 1024 ) ( -1024 1024 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 1152 1024 ) ( -1024 1024 1024 ) ( -1024 1152 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 1152 1024 ) ( -1024 1152 0 ) ( 1024 1152 1024 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 1024 0 ) ( 1024 1152 0 ) ( -1024 1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 1024 0 ) ( 1024 1024 1024 ) ( 1024 1152 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 1024 0 ) ( -1024 1024 0 ) ( 1024 1024 1024 ) eX/eXmetalrib01 0 0 0 1 1 0 0 0 +} +// brush 5 +{ +( 1024 1024 0 ) ( 1024 -1024 0 ) ( -1024 1024 0 ) eX/eXmetalFloor02 0 0 0 1 1 0 0 0 +( 1024 1024 0 ) ( -1024 1024 0 ) ( 1024 1024 -128 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( 1024 1024 0 ) ( 1024 1024 -128 ) ( 1024 -1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 -128 ) ( 1024 -1024 -128 ) ( -1024 1024 -128 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 -128 ) ( -1024 -1024 0 ) ( 1024 -1024 -128 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +( -1024 -1024 -128 ) ( -1024 1024 -128 ) ( -1024 -1024 0 ) common/caulk 0 0 0 0.25 0.25 0 0 0 +} +} +// entity 1 +{ +"classname" "info_player_deathmatch" +"origin" "0.000000 0.000000 32.000000" +} +// entity 2 +{ +"classname" "item_rockets" +"origin" "256.000000 128.000000 32.000000" +} +// entity 3 +{ +"classname" "item_rockets" +"origin" "256.000000 -128.000000 32.000000" +} +// entity 4 +{ +"classname" "weapon_minelayer" +"origin" "256.000000 0.000000 32.000000" +} diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index c1dbede92..0f54a2857 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -1478,6 +1478,7 @@ float GetAmmoTypeForWep(float i) case WEP_UZI: return 1; case WEP_CAMPINGRIFLE: return 1; case WEP_GRENADE_LAUNCHER: return 2; + case WEP_MINE_LAYER: return 2; case WEP_ELECTRO: return 3; case WEP_CRYLINK: return 3; case WEP_HLAC: return 3; diff --git a/qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.c b/qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.c index 517f68bd3..c0d16a959 100644 --- a/qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.c +++ b/qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.c @@ -268,7 +268,7 @@ void XonoticMutatorsDialog_fill(entity me) me.TDempty(me, 0.2); me.TD(me, 1, 2, e = makeXonoticRadioButton(1, "g_start_weapon_laser", "0", "No start weapons")); e.cvarOffValue = "-1"; - makeMulti(e, "g_start_weapon_shotgun g_start_weapon_uzi g_start_weapon_grenadelauncher g_start_weapon_electro g_start_weapon_crylink g_start_weapon_nex g_start_weapon_hagar g_start_weapon_rocketlauncher g_start_weapon_campingrifle g_start_weapon_hlac g_start_weapon_seeker g_start_weapon_minstanex g_start_weapon_hook g_start_weapon_porto g_start_weapon_tuba"); + makeMulti(e, "g_start_weapon_shotgun g_start_weapon_uzi g_start_weapon_grenadelauncher g_start_weapon_minelayer g_start_weapon_electro g_start_weapon_crylink g_start_weapon_nex g_start_weapon_hagar g_start_weapon_rocketlauncher g_start_weapon_campingrifle g_start_weapon_hlac g_start_weapon_seeker g_start_weapon_minstanex g_start_weapon_hook g_start_weapon_porto g_start_weapon_tuba"); me.gotoRC(me, me.rows - 1, 0); me.TD(me, 1, me.columns, e = makeXonoticButton("OK", '0 0 0')); diff --git a/qcsrc/qc-server.cbp b/qcsrc/qc-server.cbp index 00912bea3..eaa30d2d4 100644 --- a/qcsrc/qc-server.cbp +++ b/qcsrc/qc-server.cbp @@ -192,6 +192,7 @@ + diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index 3db4e47be..d42324240 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -989,7 +989,7 @@ void readplayerstartcvars() if (g_weaponarena) { start_weapons = g_weaponarena; - if (g_weaponarena & (WEPBIT_GRENADE_LAUNCHER | WEPBIT_HAGAR | WEPBIT_ROCKET_LAUNCHER)) + if (g_weaponarena & (WEPBIT_GRENADE_LAUNCHER | WEPBIT_MINE_LAYER | WEPBIT_HAGAR | WEPBIT_ROCKET_LAUNCHER)) start_ammo_rockets = 999; if (g_weaponarena & WEPBIT_SHOTGUN) start_ammo_shells = 999; diff --git a/qcsrc/server/w_all.qc b/qcsrc/server/w_all.qc index 9a9b38302..83fc8acb5 100644 --- a/qcsrc/server/w_all.qc +++ b/qcsrc/server/w_all.qc @@ -2,6 +2,7 @@ #include "w_shotgun.qc" #include "w_uzi.qc" #include "w_grenadelauncher.qc" +#include "w_minelayer.qc" #include "w_electro.qc" #include "w_crylink.qc" #include "w_nex.qc" diff --git a/qcsrc/server/w_minelayer.qc b/qcsrc/server/w_minelayer.qc new file mode 100644 index 000000000..c1b80af1c --- /dev/null +++ b/qcsrc/server/w_minelayer.qc @@ -0,0 +1,525 @@ +#ifdef REGISTER_WEAPON +REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 9, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer"); +#else +#ifdef SVQC +.float rl_release; +.float rl_detonate_later; + +void spawnfunc_weapon_minelayer (void) +{ + weapon_defaultspawnfunc(WEP_MINE_LAYER); +} + +void W_Mine_Unregister() +{ + if(self.owner && self.owner.lastrocket == self) + { + self.owner.lastrocket = world; + // self.owner.rl_release = 1; + } +} + +void W_Mine_Explode () +{ + W_Mine_Unregister(); + + if(other.takedamage == DAMAGE_AIM) + if(other.classname == "player") + if(IsDifferentTeam(self.owner, other)) + if(IsFlying(other)) + AnnounceTo(self.owner, "airshot"); + + self.event_damage = SUB_Null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.owner, cvar("g_balance_minelayer_damage"), cvar("g_balance_minelayer_edgedamage"), cvar("g_balance_minelayer_radius"), world, cvar("g_balance_minelayer_force"), self.projectiledeathtype, other); + + if (self.owner.weapon == WEP_MINE_LAYER) + { + if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo")) + { + self.owner.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self.owner) = time; + self.owner.switchweapon = w_getbestweapon(self.owner); + } + if(g_laserguided_missile) + ATTACK_FINISHED(self.owner) = time + cvar("g_balance_minelayer_refire") * W_WeaponRateFactor(); + } + remove (self); +} + +void W_Mine_DoRemoteExplode () +{ + W_Mine_Unregister(); + + self.event_damage = SUB_Null; + self.takedamage = DAMAGE_NO; + + RadiusDamage (self, self.owner, cvar("g_balance_minelayer_remote_damage"), cvar("g_balance_minelayer_remote_edgedamage"), cvar("g_balance_minelayer_remote_radius"), world, cvar("g_balance_minelayer_remote_force"), self.projectiledeathtype | HITTYPE_BOUNCE, world); + + if (self.owner.weapon == WEP_MINE_LAYER) + { + if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo")) + { + self.owner.cnt = WEP_MINE_LAYER; + ATTACK_FINISHED(self.owner) = time; + self.owner.switchweapon = w_getbestweapon(self.owner); + } + if(g_laserguided_missile) + ATTACK_FINISHED(self.owner) = time + cvar("g_balance_minelayer_refire") * W_WeaponRateFactor(); + } + remove (self); +} + +void W_Mine_RemoteExplode() +{ + if(self.owner.deadflag == DEAD_NO) + if(self.owner.lastrocket) + { + if((self.spawnshieldtime >= 0) + ? (time >= self.spawnshieldtime) // timer + : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device + ) + { + W_Mine_DoRemoteExplode(); + } + } +} + +void W_Mine_Think (void) +{ + entity e; + vector desireddir, olddir, newdir, desiredorigin, goal; +#if 0 + float cosminang, cosmaxang, cosang; +#endif + float turnrate, velspeed, f; + self.nextthink = time; + if (time > self.cnt) + { + other = world; + self.projectiledeathtype |= HITTYPE_BOUNCE; + W_Mine_Explode (); + return; + } + + if(g_laserguided_missile) + { + // accelerate + makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0'); + velspeed = cvar("g_balance_minelayer_laserguided_speed") * g_weaponspeedfactor - (self.velocity * v_forward); + if (velspeed > 0) + self.velocity = self.velocity + v_forward * min(cvar("g_balance_minelayer_laserguided_speedaccel") * frametime, velspeed); + } + else + { + // accelerate + makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0'); + velspeed = cvar("g_balance_minelayer_speed") * g_weaponspeedfactor - (self.velocity * v_forward); + if (velspeed > 0) + self.velocity = self.velocity + v_forward * min(cvar("g_balance_minelayer_speedaccel") * g_weaponspeedfactor * frametime, velspeed); + } + + // laser guided, or remote detonation + if (self.owner.weapon == WEP_MINE_LAYER) + { + if(g_laserguided_missile) + { + if(self.rl_detonate_later) + W_Mine_RemoteExplode(); + + if(cvar("g_balance_minelayer_laserguided_allow_steal")) + { + if(self.owner.laser_on) + { + if(self.attack_finished_single < time) + { + self.attack_finished_single = time + 0.2 + random()*0.3; + //self.enemy = FindLaserTarget(self, 0.7, 0.7); + } + + if(!self.enemy) + self.enemy = self.owner.weaponentity.lasertarget; + } + else self.enemy = world; + } + else // don't allow stealing: always target my owner's laser (if it exists) + self.enemy = self.owner.weaponentity.lasertarget; + + if(self.enemy != world) + { + //bprint("Targeting ", self.enemy.owner.netname, "'s laser\n"); + velspeed = vlen(self.velocity); + e = self.enemy;//self.owner.weaponentity.lasertarget; + turnrate = cvar("g_balance_minelayer_laserguided_turnrate");//0.65; // how fast to turn + desireddir = normalize(e.origin - self.origin); // get direction from my position to the laser target + olddir = normalize(self.velocity); // get my current direction + newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy + self.velocity = newdir * velspeed; // make me fly in the new direction at my flight speed + self.angles = vectoangles(self.velocity); // turn model in the new flight direction + + ATTACK_FINISHED(self.owner) = time + 0.2 * W_WeaponRateFactor(); + } + } + else + { + if(self == self.owner.lastrocket) + if not(self.owner.rl_release) + if not(self.BUTTON_ATCK2) + if(cvar("g_balance_minelayer_guiderate")) + if(time > self.pushltime) + if(self.owner.deadflag == DEAD_NO) + { + f = cvar("g_balance_minelayer_guideratedelay"); + if(f) + f = bound(0, (time - self.pushltime) / f, 1); + else + f = 1; + + velspeed = vlen(self.velocity); + + makevectors(self.owner.v_angle); + desireddir = WarpZone_RefSys_TransformVelocity(self.owner, self, v_forward); + desiredorigin = WarpZone_RefSys_TransformOrigin(self.owner, self, self.owner.origin + self.owner.view_ofs); + olddir = normalize(self.velocity); + +#if 0 + // disabled this code because it doesn't do what I want it to do :P + cosminang = cos(cvar("g_balance_minelayer_guidefadeangle") * DEG2RAD); + cosmaxang = cos(cvar("g_balance_minelayer_guidemaxangle") * DEG2RAD); + cosang = desireddir * normalize(self.origin - desiredorigin); + if(cosminang == cosmaxang) + f *= (cosang >= cosminang); + else + f *= bound(0, (cosang - cosmaxang) / (cosminang - cosmaxang), 1); +#endif + + if(!self.count) + { + pointparticles(particleeffectnum("rocket_guide"), self.origin, self.velocity, 1); + // TODO add a better sound here + sound (self.owner, CHAN_WEAPON2, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); + self.count = 1; + } + } + + if(self.rl_detonate_later) + W_Mine_RemoteExplode(); + } + } + + if(self.csqcprojectile_clientanimate == 0) + UpdateCSQCProjectile(self); +} + +void W_Mine_Touch (void) +{ + W_Mine_Unregister(); + + PROJECTILE_TOUCH; + W_Mine_Explode (); +} + +void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force) +{ + if (self.health <= 0) + return; + self.health = self.health - damage; + self.angles = vectoangles(self.velocity); + if (self.health <= 0) + W_PrepareExplosionByDamage(attacker, W_Mine_Explode); +} + +void W_Mine_Attack (void) +{ + local entity missile; + local entity flash; + + if not(self.items & IT_UNLIMITED_WEAPON_AMMO) + self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo"); + + W_SetupShot_ProjectileSize (self, '-3 -3 -3', '3 3 3', FALSE, 5, "weapons/rocket_fire.wav", cvar("g_balance_minelayer_damage")); + pointparticles(particleeffectnum("minelayer_muzzleflash"), w_shotorg, w_shotdir * 1000, 1); + + missile = WarpZone_RefSys_SpawnSameRefSys(self); + missile.owner = self; + self.lastrocket = missile; + if(cvar("g_balance_minelayer_detonatedelay") >= 0) + missile.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay"); + else + missile.spawnshieldtime = -1; + missile.pushltime = time + cvar("g_balance_minelayer_guidedelay"); + missile.classname = "rocket"; + missile.bot_dodge = TRUE; + missile.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can be detonated inflight which makes it even more dangerous + + missile.takedamage = DAMAGE_YES; + missile.damageforcescale = cvar("g_balance_minelayer_damageforcescale"); + missile.health = cvar("g_balance_minelayer_health"); + missile.event_damage = W_Mine_Damage; + + missile.movetype = MOVETYPE_FLY; + PROJECTILE_MAKETRIGGER(missile); + missile.projectiledeathtype = WEP_MINE_LAYER; + 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 + if(g_laserguided_missile && self.laser_on) + W_SetupProjectileVelocity(missile, cvar("g_balance_minelayer_laserguided_speedstart"), 0); + else + W_SetupProjectileVelocity(missile, cvar("g_balance_minelayer_speedstart"), 0); + missile.angles = vectoangles (missile.velocity); + + missile.touch = W_Mine_Touch; + missile.think = W_Mine_Think; + missile.nextthink = time; + missile.cnt = time + cvar("g_balance_minelayer_lifetime"); + missile.flags = FL_PROJECTILE; + + CSQCProjectile(missile, cvar("g_balance_minelayer_guiderate") == 0 && cvar("g_balance_minelayer_speedaccel") == 0 && !g_laserguided_missile, 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 +} + +void spawnfunc_weapon_minelayer (void); // defined in t_items.qc + +float w_minelayer(float req) +{ + entity rock; + float rockfound; + if (req == WR_AIM) + { + // aim and decide to fire if appropriate + self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE); + if(skill >= 2) // skill 0 and 1 bots won't detonate rockets! + { + // decide whether to detonate rockets + local entity missile, targetlist, targ; + local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d; + local float selfdamage, teamdamage, enemydamage; + edgedamage = cvar("g_balance_minelayer_edgedamage"); + coredamage = cvar("g_balance_minelayer_damage"); + edgeradius = cvar("g_balance_minelayer_radius"); + recipricoledgeradius = 1 / edgeradius; + selfdamage = 0; + teamdamage = 0; + enemydamage = 0; + targetlist = findchainfloat(bot_attack, TRUE); + missile = find(world, classname, "rocket"); + while (missile) + { + if (missile.owner != 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 && teams_matter) + teamdamage = teamdamage + d; + else if (bot_shouldattack(targ)) + enemydamage = enemydamage + d; + targ = targ.chain; + } + missile = find(missile, classname, "rocket"); + } + local float desirabledamage; + desirabledamage = enemydamage; + if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime) + desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent"); + if (self.team && teamplay != 1) + desirabledamage = desirabledamage - teamdamage; + + missile = find(world, classname, "rocket"); + while (missile) + { + if (missile.owner != 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{ + local 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(self.enemy.classname == "player") + 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(g_laserguided_missile) + { + if (self.BUTTON_ATCK && self.rl_release) + { + rockfound = 0; + for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.owner == self) + { + if(!rock.rl_detonate_later) + { + rock.rl_detonate_later = TRUE; + rockfound = 1; + } + } + if(rockfound) + sound (self, CHAN_WEAPON2, "weapons/rocket_det.wav", VOL_BASE, ATTN_NORM); + else + { + if (weapon_prepareattack(0, cvar("g_balance_minelayer_refire"))) + { + W_Mine_Attack(); + weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready); + } + } + self.rl_release = 0; + } + if (!self.BUTTON_ATCK) + self.rl_release = 1; + if (self.BUTTON_ATCK2) + if(self.exteriorweaponentity.attack_finished_single < time) + { + self.exteriorweaponentity.attack_finished_single = time + 0.4; + self.laser_on = !self.laser_on; + // UGLY WORKAROUND: play this on CHAN_WEAPON2 so it can't cut off fire sounds + sound (self, CHAN_WEAPON2, "weapons/rocket_mode.wav", VOL_BASE, ATTN_NORM); + } + } + else + { + if (self.BUTTON_ATCK) + { + if(self.rl_release || cvar("g_balance_minelayer_guidestop")) + if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire"))) + { + W_Mine_Attack(); + weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_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.owner == self) + { + if(!rock.rl_detonate_later) + { + rock.rl_detonate_later = TRUE; + rockfound = 1; + } + } + if(rockfound) + sound (self, CHAN_WEAPON2, "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"); + if (g_laserguided_missile) + { + precache_model ("models/laser_dot.mdl"); // mine layer + } + } + else if (req == WR_SETUP) + { + weapon_setup(WEP_MINE_LAYER); + self.rl_release = 1; + } + else if (req == WR_CHECKAMMO1) + { + // don't switch while guiding a missile + if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER) + && self.ammo_rockets < cvar("g_balance_minelayer_ammo")) + return FALSE; + } + else if (req == WR_CHECKAMMO2) + return FALSE; + else if (req == WR_RESETPLAYER) + { + self.rl_release = 0; + } + return TRUE; +}; +#endif +#ifdef CSQC +float w_minelayer(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, CHAN_PROJECTILE, "weapons/rocket_impact.wav", VOL_BASE, ATTN_NORM); + } + else if(req == WR_PRECACHE) + { + precache_sound("weapons/rocket_impact.wav"); + } + else if (req == WR_SUICIDEMESSAGE) + w_deathtypestring = "%s exploded"; + else if (req == WR_KILLMESSAGE) + { + if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation) + w_deathtypestring = "%s got too close to %s's rocket"; + else if(w_deathtype & HITTYPE_SPLASH) + w_deathtypestring = "%s almost dodged %s's rocket"; + else + w_deathtypestring = "%s ate %s's rocket"; + } + return TRUE; +} +#endif +#endif -- 2.39.2