]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/weapons/weapon/devastator.qc
Merge branch 'master' into terencehill/dynamic_hud
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / devastator.qc
index 1faae844f9ad37240f2fef6b15243d38a9484c2d..e8697afd1e96fdb47041bcc9b50d7c7869d94314 100644 (file)
@@ -1,72 +1,78 @@
 #ifndef IMPLEMENTATION
-REGISTER_WEAPON(
-/* WEP_##id  */ DEVASTATOR,
-/* function  */ W_Devastator,
-/* ammotype  */ ammo_rockets,
-/* impulse   */ 9,
-/* flags     */ WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
-/* rating    */ BOT_PICKUP_RATING_HIGH,
-/* color     */ '1 1 0',
-/* modelname */ "rl",
-/* model     */ MDL_DEVASTATOR_ITEM,
-/* simplemdl */ "foobar",
-/* crosshair */ "gfx/crosshairrocketlauncher 0.7",
-/* wepimg    */ "weaponrocketlauncher",
-/* refname   */ "devastator",
-/* wepname   */ _("Devastator")
-);
-
-#define DEVASTATOR_SETTINGS(w_cvar,w_prop) DEVASTATOR_SETTINGS_LIST(w_cvar, w_prop, DEVASTATOR, devastator)
-#define DEVASTATOR_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
-       w_cvar(id, sn, NONE, ammo) \
-       w_cvar(id, sn, NONE, animtime) \
-       w_cvar(id, sn, NONE, damage) \
-       w_cvar(id, sn, NONE, damageforcescale) \
-       w_cvar(id, sn, NONE, detonatedelay) \
-       w_cvar(id, sn, NONE, edgedamage) \
-       w_cvar(id, sn, NONE, force) \
-       w_cvar(id, sn, NONE, guidedelay) \
-       w_cvar(id, sn, NONE, guidegoal) \
-       w_cvar(id, sn, NONE, guiderate) \
-       w_cvar(id, sn, NONE, guideratedelay) \
-       w_cvar(id, sn, NONE, guidestop) \
-       w_cvar(id, sn, NONE, health) \
-       w_cvar(id, sn, NONE, lifetime) \
-       w_cvar(id, sn, NONE, radius) \
-       w_cvar(id, sn, NONE, refire) \
-       w_cvar(id, sn, NONE, remote_damage) \
-       w_cvar(id, sn, NONE, remote_edgedamage) \
-       w_cvar(id, sn, NONE, remote_force) \
-       w_cvar(id, sn, NONE, remote_jump_damage) \
-       w_cvar(id, sn, NONE, remote_jump_radius) \
-       w_cvar(id, sn, NONE, remote_jump_velocity_z_add) \
-       w_cvar(id, sn, NONE, remote_jump_velocity_z_max) \
-       w_cvar(id, sn, NONE, remote_jump_velocity_z_min) \
-       w_cvar(id, sn, NONE, remote_radius) \
-       w_cvar(id, sn, NONE, speed) \
-       w_cvar(id, sn, NONE, speedaccel) \
-       w_cvar(id, sn, NONE, speedstart) \
-       w_prop(id, sn, float,  reloading_ammo, reload_ammo) \
-       w_prop(id, sn, float,  reloading_time, reload_time) \
-       w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
-       w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
-       w_prop(id, sn, string, weaponreplace, weaponreplace) \
-       w_prop(id, sn, float,  weaponstart, weaponstart) \
-       w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
-       w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
+CLASS(Devastator, Weapon)
+/* ammotype  */ ATTRIB(Devastator, ammo_field, .int, ammo_rockets)
+/* impulse   */ ATTRIB(Devastator, impulse, int, 9)
+/* flags     */ ATTRIB(Devastator, spawnflags, int, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH);
+/* rating    */ ATTRIB(Devastator, bot_pickupbasevalue, float, BOT_PICKUP_RATING_HIGH);
+/* color     */ ATTRIB(Devastator, wpcolor, vector, '1 1 0');
+/* modelname */ ATTRIB(Devastator, mdl, string, "rl");
+#ifndef MENUQC
+/* model     */ ATTRIB(Devastator, m_model, Model, MDL_DEVASTATOR_ITEM);
+#endif
+/* crosshair */ ATTRIB(Devastator, w_crosshair, string, "gfx/crosshairrocketlauncher");
+/* crosshair */ ATTRIB(Devastator, w_crosshair_size, float, 0.7);
+/* wepimg    */ ATTRIB(Devastator, model2, string, "weaponrocketlauncher");
+/* refname   */ ATTRIB(Devastator, netname, string, "devastator");
+/* wepname   */ ATTRIB(Devastator, m_name, string, _("Devastator"));
+
+#define X(BEGIN, P, END, class, prefix) \
+       BEGIN(class) \
+               P(class, prefix, ammo, float, NONE) \
+        P(class, prefix, animtime, float, NONE) \
+        P(class, prefix, damageforcescale, float, NONE) \
+        P(class, prefix, damage, float, NONE) \
+        P(class, prefix, detonatedelay, float, NONE) \
+        P(class, prefix, edgedamage, float, NONE) \
+        P(class, prefix, force, float, NONE) \
+        P(class, prefix, guidedelay, float, NONE) \
+        P(class, prefix, guidegoal, float, NONE) \
+        P(class, prefix, guideratedelay, float, NONE) \
+        P(class, prefix, guiderate, float, NONE) \
+        P(class, prefix, guidestop, float, NONE) \
+        P(class, prefix, health, float, NONE) \
+        P(class, prefix, lifetime, float, NONE) \
+        P(class, prefix, radius, float, NONE) \
+        P(class, prefix, refire, float, NONE) \
+               P(class, prefix, reload_ammo, float, NONE) \
+               P(class, prefix, reload_time, float, NONE) \
+        P(class, prefix, remote_damage, float, NONE) \
+        P(class, prefix, remote_edgedamage, float, NONE) \
+        P(class, prefix, remote_force, float, NONE) \
+        P(class, prefix, remote_jump_damage, float, NONE) \
+        P(class, prefix, remote_jump_radius, float, NONE) \
+        P(class, prefix, remote_jump_velocity_z_add, float, NONE) \
+        P(class, prefix, remote_jump_velocity_z_max, float, NONE) \
+        P(class, prefix, remote_jump_velocity_z_min, float, NONE) \
+        P(class, prefix, remote_radius, float, NONE) \
+        P(class, prefix, speedaccel, float, NONE) \
+        P(class, prefix, speedstart, float, NONE) \
+        P(class, prefix, speed, float, NONE) \
+               P(class, prefix, switchdelay_drop, float, NONE) \
+               P(class, prefix, switchdelay_raise, float, NONE) \
+               P(class, prefix, weaponreplace, string,NONE) \
+               P(class, prefix, weaponstartoverride, float, NONE) \
+               P(class, prefix, weaponstart, float, NONE) \
+               P(class, prefix, weaponthrowable, float, NONE) \
+       END()
+       W_PROPS(X, Devastator, devastator)
+#undef X
+
+ENDCLASS(Devastator)
+REGISTER_WEAPON(DEVASTATOR, devastator, NEW(Devastator));
 
 #ifdef SVQC
-DEVASTATOR_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 .float rl_release;
 .float rl_detonate_later;
 #endif
 #endif
 #ifdef IMPLEMENTATION
 #ifdef SVQC
-void spawnfunc_weapon_devastator(void) { weapon_defaultspawnfunc(WEP_DEVASTATOR.m_id); }
-void spawnfunc_weapon_rocketlauncher(void) { spawnfunc_weapon_devastator(); }
+spawnfunc(weapon_devastator) { weapon_defaultspawnfunc(this, WEP_DEVASTATOR); }
+spawnfunc(weapon_rocketlauncher) { spawnfunc_weapon_devastator(this); }
+
+.entity lastrocket;
 
-void W_Devastator_Unregister(void)
+void W_Devastator_Unregister()
 {SELFPARAM();
        if(self.realowner && self.realowner.lastrocket == self)
        {
@@ -75,14 +81,14 @@ void W_Devastator_Unregister(void)
        }
 }
 
-void W_Devastator_Explode(void)
+void W_Devastator_Explode()
 {SELFPARAM();
        W_Devastator_Unregister();
 
        if(other.takedamage == DAMAGE_AIM)
                if(IS_PLAYER(other))
                        if(DIFF_TEAM(self.realowner, other))
-                               if(other.deadflag == DEAD_NO)
+                               if(!IS_DEAD(other))
                                        if(IsFlying(other))
                                                Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
 
@@ -102,20 +108,22 @@ void W_Devastator_Explode(void)
                other
        );
 
-       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
+       Weapon thiswep = WEP_DEVASTATOR;
+       if(PS(self.realowner).m_weapon == thiswep)
        {
-               if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
+               if(self.realowner.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo))
                if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
                {
                        self.realowner.cnt = WEP_DEVASTATOR.m_id;
-                       ATTACK_FINISHED(self.realowner) = time;
-                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
+                       int slot = 0; // TODO: unhardcode
+                       ATTACK_FINISHED(self.realowner, slot) = time;
+                       PS(self.realowner).m_switchweapon = w_getbestweapon(self.realowner);
                }
        }
        remove(self);
 }
 
-void W_Devastator_DoRemoteExplode(void)
+void W_Devastator_DoRemoteExplode(.entity weaponentity)
 {SELFPARAM();
        W_Devastator_Unregister();
 
@@ -181,30 +189,32 @@ void W_Devastator_DoRemoteExplode(void)
                world
        );
 
-       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
+       Weapon thiswep = WEP_DEVASTATOR;
+       if(PS(self.realowner).m_weapon == thiswep)
        {
-               if(self.realowner.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
+               if(self.realowner.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo))
                if(!(self.realowner.items & IT_UNLIMITED_WEAPON_AMMO))
                {
                        self.realowner.cnt = WEP_DEVASTATOR.m_id;
-                       ATTACK_FINISHED(self.realowner) = time;
-                       self.realowner.switchweapon = w_getbestweapon(self.realowner);
+                       int slot = weaponslot(weaponentity);
+                       ATTACK_FINISHED(self.realowner, slot) = time;
+                       PS(self.realowner).m_switchweapon = w_getbestweapon(self.realowner);
                }
        }
        remove(self);
 }
 
-void W_Devastator_RemoteExplode(void)
+void W_Devastator_RemoteExplode(.entity weaponentity)
 {SELFPARAM();
-       if(self.realowner.deadflag == DEAD_NO)
+       if(!IS_DEAD(self.realowner))
        if(self.realowner.lastrocket)
        {
                if((self.spawnshieldtime >= 0)
                        ? (time >= self.spawnshieldtime) // timer
-                       : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(devastator, remote_radius)) // safety device
+                       : (vdist(NearestPointOnBox(self.realowner, self.origin) - self.origin, >, WEP_CVAR(devastator, remote_radius))) // safety device
                )
                {
-                       W_Devastator_DoRemoteExplode();
+                       W_Devastator_DoRemoteExplode(weaponentity);
                }
        }
 }
@@ -242,7 +252,7 @@ vector W_Devastator_SteerTo(vector thisdir, vector goaldir, float maxturn_cos)
 //   normalize(thisdir + goaldir)
 //   normalize(0)
 
-void W_Devastator_Think(void)
+void W_Devastator_Think()
 {SELFPARAM();
        vector desireddir, olddir, newdir, desiredorigin, goal;
        float velspeed, f;
@@ -262,14 +272,14 @@ void W_Devastator_Think(void)
                self.velocity = self.velocity + v_forward * min(WEP_CVAR(devastator, speedaccel) * W_WeaponSpeedFactor() * frametime, velspeed);
 
        // laser guided, or remote detonation
-       if(self.realowner.weapon == WEP_DEVASTATOR.m_id)
+       if(PS(self.realowner).m_weapon == WEP_DEVASTATOR)
        {
                if(self == self.realowner.lastrocket)
                if(!self.realowner.rl_release)
-               if(!self.BUTTON_ATCK2)
+               if(!PHYS_INPUT_BUTTON_ATCK2(self))
                if(WEP_CVAR(devastator, guiderate))
                if(time > self.pushltime)
-               if(self.realowner.deadflag == DEAD_NO)
+               if(!IS_DEAD(self.realowner))
                {
                        f = WEP_CVAR(devastator, guideratedelay);
                        if(f)
@@ -301,15 +311,16 @@ void W_Devastator_Think(void)
                        }
                }
 
+               .entity weaponentity = weaponentities[0]; // TODO: unhardcode
                if(self.rl_detonate_later)
-                       W_Devastator_RemoteExplode();
+                       W_Devastator_RemoteExplode(weaponentity);
        }
 
        if(self.csqcprojectile_clientanimate == 0)
                UpdateCSQCProjectile(self);
 }
 
-void W_Devastator_Touch(void)
+void W_Devastator_Touch()
 {SELFPARAM();
        if(WarpZone_Projectile_Touch())
        {
@@ -321,29 +332,29 @@ void W_Devastator_Touch(void)
        W_Devastator_Explode();
 }
 
-void W_Devastator_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{SELFPARAM();
-       if(self.health <= 0)
+void W_Devastator_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+       if(this.health <= 0)
                return;
 
-       if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
+       if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
                return; // g_projectiles_damage says to halt
 
-       self.health = self.health - damage;
-       self.angles = vectoangles(self.velocity);
+       this.health = this.health - damage;
+       this.angles = vectoangles(this.velocity);
 
-       if(self.health <= 0)
-               W_PrepareExplosionByDamage(attacker, W_Devastator_Explode);
+       if(this.health <= 0)
+               WITH(entity, self, this, W_PrepareExplosionByDamage(attacker, W_Devastator_Explode));
 }
 
-void W_Devastator_Attack(void)
+void W_Devastator_Attack(Weapon thiswep)
 {SELFPARAM();
        entity missile;
        entity flash;
 
-       W_DecreaseAmmo(WEP_CVAR(devastator, ammo));
+       W_DecreaseAmmo(thiswep, self, WEP_CVAR(devastator, ammo));
 
-       W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 5, SND(ROCKET_FIRE), CH_WEAPON_A, WEP_CVAR(devastator, damage));
+       W_SetupShot_ProjectileSize(self, '-3 -3 -3', '3 3 3', false, 5, SND_ROCKET_FIRE, CH_WEAPON_A, WEP_CVAR(devastator, damage));
        Send_Effect(EFFECT_ROCKET_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1);
 
        missile = WarpZone_RefSys_SpawnSameRefSys(self);
@@ -387,291 +398,245 @@ void W_Devastator_Attack(void)
        setmodel(flash, MDL_DEVASTATOR_MUZZLEFLASH); // precision set below
        SUB_SetFade(flash, time, 0.1);
        flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
-       W_AttachToShotorg(flash, '5 0 0');
+       W_AttachToShotorg(self, flash, '5 0 0');
 
        // common properties
        MUTATOR_CALLHOOK(EditProjectile, self, missile);
 }
 
-bool W_Devastator(entity thiswep, int req)
-{SELFPARAM();
-       entity rock;
-       float rockfound;
-       float ammo_amount;
-       switch(req)
-       {
-               #if 0
-               case WR_AIM:
-               {
-                       // aim and decide to fire if appropriate
-                       self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), false);
-                       if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
-                       {
-                               // decide whether to detonate rockets
-                               entity missile, targetlist, targ;
-                               targetlist = findchainfloat(bot_attack, true);
-                               for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self)
-                               {
-                                       targ = targetlist;
-                                       while(targ)
-                                       {
-                                               if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius))
-                                               {
-                                                       self.BUTTON_ATCK2 = true;
-                                                       break;
-                                               }
-                                               targ = targ.chain;
-                                       }
-                               }
-
-                               if(self.BUTTON_ATCK2) self.BUTTON_ATCK = false;
-                       }
-
-                       return true;
-               }
-               #else
-               case WR_AIM:
-               {
-                       // aim and decide to fire if appropriate
-                       self.BUTTON_ATCK = bot_aim(WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, 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 = WEP_CVAR(devastator, edgedamage);
-                               coredamage = WEP_CVAR(devastator, damage);
-                               edgeradius = WEP_CVAR(devastator, 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;
-                       }
-
-                       return true;
-               }
-               #endif
-               case WR_THINK:
-               {
-                       if(WEP_CVAR(devastator, reload_ammo) && self.clip_load < WEP_CVAR(devastator, ammo)) // forced reload
-                               _WEP_ACTION(self.weapon, WR_RELOAD);
-                       else
-                       {
-                               if(self.BUTTON_ATCK)
-                               {
-                                       if(self.rl_release || WEP_CVAR(devastator, guidestop))
-                                       if(weapon_prepareattack(0, WEP_CVAR(devastator, refire)))
-                                       {
-                                               W_Devastator_Attack();
-                                               weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
-                                               self.rl_release = 0;
-                                       }
-                               }
-                               else
-                                       self.rl_release = 1;
-
-                               if(self.BUTTON_ATCK2)
-                               if(self.switchweapon == WEP_DEVASTATOR.m_id)
-                               {
-                                       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, SND_ROCKET_DET, VOL_BASE, ATTN_NORM);
-                               }
-                       }
-
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       DEVASTATOR_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
-                       return true;
-               }
-               case WR_SETUP:
-               {
-                       self.rl_release = 1;
-                       return true;
-               }
-               case WR_CHECKAMMO1:
-               {
-                       #if 0
-                       // don't switch while guiding a missile
-                       if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_DEVASTATOR.m_id)
-                       {
-                               ammo_amount = false;
-                               if(WEP_CVAR(devastator, reload_ammo))
-                               {
-                                       if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo))
-                                               ammo_amount = true;
-                               }
-                               else if(self.WEP_AMMO(DEVASTATOR) < WEP_CVAR(devastator, ammo))
-                                       ammo_amount = true;
-                               return !ammo_amount;
-                       }
-                       #endif
-                       #if 0
-                       if(self.rl_release == 0)
-                       {
-                               LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo));
-                               return true;
-                       }
-                       else
-                       {
-                               ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo);
-                               ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
-                               LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s\n", self.rl_release, self.WEP_AMMO(DEVASTATOR), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE"));
-                               return ammo_amount;
-                       }
-                       #else
-                       ammo_amount = self.WEP_AMMO(DEVASTATOR) >= WEP_CVAR(devastator, ammo);
-                       ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
-                       return ammo_amount;
-                       #endif
-               }
-               case WR_CHECKAMMO2:
-               {
-                       return false;
-               }
-               case WR_CONFIG:
-               {
-                       DEVASTATOR_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
-                       return true;
-               }
-               case WR_RESETPLAYER:
-               {
-                       self.rl_release = 0;
-                       return true;
-               }
-               case WR_RELOAD:
-               {
-                       W_Reload(WEP_CVAR(devastator, ammo), SND(RELOAD));
-                       return true;
-               }
-               case WR_SUICIDEMESSAGE:
-               {
-                       return WEAPON_DEVASTATOR_SUICIDE;
-               }
-               case WR_KILLMESSAGE:
-               {
-                       if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
-                               return WEAPON_DEVASTATOR_MURDER_SPLASH;
-                       else
-                               return WEAPON_DEVASTATOR_MURDER_DIRECT;
-               }
-       }
-       return false;
+#if 0
+METHOD(Devastator, wr_aim, void(entity thiswep))
+{
+    // aim and decide to fire if appropriate
+    PHYS_INPUT_BUTTON_ATCK(self) = bot_aim(self, WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), false);
+    if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
+    {
+        // decide whether to detonate rockets
+        entity missile, targetlist, targ;
+        targetlist = findchainfloat(bot_attack, true);
+        for(missile = world; (missile = find(missile, classname, "rocket")); ) if(missile.realowner == self)
+        {
+            targ = targetlist;
+            while(targ)
+            {
+                if(targ != missile.realowner && vlen(targ.origin - missile.origin) < WEP_CVAR(devastator, radius))
+                {
+                    PHYS_INPUT_BUTTON_ATCK2(self) = true;
+                    break;
+                }
+                targ = targ.chain;
+            }
+        }
+
+        if(PHYS_INPUT_BUTTON_ATCK2(self)) PHYS_INPUT_BUTTON_ATCK(self) = false;
+    }
+}
+#else
+METHOD(Devastator, wr_aim, void(entity thiswep))
+{
+    SELFPARAM();
+    // aim and decide to fire if appropriate
+    PHYS_INPUT_BUTTON_ATCK(self) = bot_aim(self, WEP_CVAR(devastator, speed), 0, WEP_CVAR(devastator, lifetime), false);
+    if(skill >= 2) // skill 0 and 1 bots won't detonate rockets!
+    {
+        // decide whether to detonate rockets
+        entity targetlist, targ;
+        float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
+        float selfdamage, teamdamage, enemydamage;
+        edgedamage = WEP_CVAR(devastator, edgedamage);
+        coredamage = WEP_CVAR(devastator, damage);
+        edgeradius = WEP_CVAR(devastator, radius);
+        recipricoledgeradius = 1 / edgeradius;
+        selfdamage = 0;
+        teamdamage = 0;
+        enemydamage = 0;
+        targetlist = findchainfloat(bot_attack, true);
+        FOREACH_ENTITY_ENT(realowner, self,
+        {
+            if(it.classname != "rocket") continue;
+
+            targ = targetlist;
+            while(targ)
+            {
+                d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - it.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(self, targ))
+                    enemydamage = enemydamage + d;
+                targ = targ.chain;
+            }
+        });
+        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;
+
+        FOREACH_ENTITY_ENT(realowner, self,
+        {
+            if(it.classname != "rocket") continue;
+
+            makevectors(it.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(it.origin - targ.origin)< 0.1)
+                        && desirabledamage > 0.1*coredamage
+                    ) PHYS_INPUT_BUTTON_ATCK2(self) = 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(it.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))
+                                PHYS_INPUT_BUTTON_ATCK2(self) = true;
+            // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
+            }
+        });
+        // 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
+            PHYS_INPUT_BUTTON_ATCK2(self) = true;
+        if((skill > 6.5) && (selfdamage > self.health))
+            PHYS_INPUT_BUTTON_ATCK2(self) = false;
+        //if(PHYS_INPUT_BUTTON_ATCK2(self) == true)
+        //     dprint(ftos(desirabledamage),"\n");
+        if(PHYS_INPUT_BUTTON_ATCK2(self)) PHYS_INPUT_BUTTON_ATCK(self) = false;
+    }
+}
+#endif
+METHOD(Devastator, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
+{
+    if(WEP_CVAR(devastator, reload_ammo) && actor.clip_load < WEP_CVAR(devastator, ammo)) { // forced reload
+        thiswep.wr_reload(thiswep, actor, weaponentity);
+    } else {
+        if(fire & 1)
+        {
+            if(actor.rl_release || WEP_CVAR(devastator, guidestop))
+            if(weapon_prepareattack(thiswep, actor, weaponentity, false, WEP_CVAR(devastator, refire)))
+            {
+                W_Devastator_Attack(thiswep);
+                weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR(devastator, animtime), w_ready);
+                actor.rl_release = 0;
+            }
+        }
+        else
+            actor.rl_release = 1;
+
+        if(fire & 2)
+        if(PS(actor).m_switchweapon == WEP_DEVASTATOR)
+        {
+            entity rock;
+            bool rockfound = false;
+            for(rock = world; (rock = find(rock, classname, "rocket")); ) if(rock.realowner == actor)
+            {
+                if(!rock.rl_detonate_later)
+                {
+                    rock.rl_detonate_later = true;
+                    rockfound = true;
+                }
+            }
+            if(rockfound)
+                sound(actor, CH_WEAPON_B, SND_ROCKET_DET, VOL_BASE, ATTN_NORM);
+        }
+    }
+}
+METHOD(Devastator, wr_setup, void(entity thiswep))
+{
+    SELFPARAM();
+    self.rl_release = 1;
+}
+METHOD(Devastator, wr_checkammo1, bool(entity thiswep))
+{
+    SELFPARAM();
+    #if 0
+    // don't switch while guiding a missile
+    if(ATTACK_FINISHED(self, slot) <= time || PS(self).m_weapon != WEP_DEVASTATOR)
+    {
+        ammo_amount = false;
+        if(WEP_CVAR(devastator, reload_ammo))
+        {
+            if(self.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo) && self.(weapon_load[WEP_DEVASTATOR.m_id]) < WEP_CVAR(devastator, ammo))
+                ammo_amount = true;
+        }
+        else if(self.(thiswep.ammo_field) < WEP_CVAR(devastator, ammo))
+            ammo_amount = true;
+        return !ammo_amount;
+    }
+    #endif
+    #if 0
+    if(self.rl_release == 0)
+    {
+        LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: TRUE\n", self.rl_release, self.(thiswep.ammo_field), WEP_CVAR(devastator, ammo));
+        return true;
+    }
+    else
+    {
+        ammo_amount = self.(thiswep.ammo_field) >= WEP_CVAR(devastator, ammo);
+        ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
+        LOG_INFOF("W_Devastator(WR_CHECKAMMO1): %d, %.2f, %d: %s\n", self.rl_release, self.(thiswep.ammo_field), WEP_CVAR(devastator, ammo), (ammo_amount ? "TRUE" : "FALSE"));
+        return ammo_amount;
+    }
+    #else
+    float ammo_amount = self.(thiswep.ammo_field) >= WEP_CVAR(devastator, ammo);
+    ammo_amount += self.(weapon_load[WEP_DEVASTATOR.m_id]) >= WEP_CVAR(devastator, ammo);
+    return ammo_amount;
+    #endif
+}
+METHOD(Devastator, wr_checkammo2, bool(entity thiswep))
+{
+    return false;
 }
+METHOD(Devastator, wr_resetplayer, void(entity thiswep))
+{
+    SELFPARAM();
+    self.lastrocket = NULL; // stop rocket guiding, no revenge from the grave!
+    self.rl_release = 0;
+}
+METHOD(Devastator, wr_reload, void(entity thiswep, entity actor, .entity weaponentity))
+{
+    SELFPARAM();
+    W_Reload(self, WEP_CVAR(devastator, ammo), SND_RELOAD);
+}
+METHOD(Devastator, wr_suicidemessage, Notification(entity thiswep))
+{
+    return WEAPON_DEVASTATOR_SUICIDE;
+}
+METHOD(Devastator, wr_killmessage, Notification(entity thiswep))
+{
+    if((w_deathtype & HITTYPE_BOUNCE) || (w_deathtype & HITTYPE_SPLASH))
+        return WEAPON_DEVASTATOR_MURDER_SPLASH;
+    else
+        return WEAPON_DEVASTATOR_MURDER_DIRECT;
+}
+
 #endif
 #ifdef CSQC
-bool W_Devastator(entity thiswep, int req)
-{SELFPARAM();
-       switch(req)
-       {
-               case WR_IMPACTEFFECT:
-               {
-                       vector org2;
-                       org2 = w_org + w_backoff * 12;
-                       pointparticles(particleeffectnum(EFFECT_ROCKET_EXPLODE), org2, '0 0 0', 1);
-                       if(!w_issilent)
-                               sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTN_NORM);
 
-                       return true;
-               }
-               case WR_INIT:
-               {
-                       return true;
-               }
-               case WR_ZOOMRETICLE:
-               {
-                       // no weapon specific image for this weapon
-                       return false;
-               }
-       }
-       return false;
+METHOD(Devastator, wr_impacteffect, void(entity thiswep))
+{
+    SELFPARAM();
+    vector org2;
+    org2 = w_org + w_backoff * 12;
+    pointparticles(EFFECT_ROCKET_EXPLODE, org2, '0 0 0', 1);
+    if(!w_issilent)
+        sound(self, CH_SHOTS, SND_ROCKET_IMPACT, VOL_BASE, ATTN_NORM);
 }
+
 #endif
 #endif