X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fweapons%2Fweaponsystem.qc;h=21057e4a0b525a02a14c995b9294f78b64bee52d;hb=8879e39a219031ffcf8e427fa4f8610163753053;hp=762f9a0e3dc12b619b531128366b6d9f0c36506f;hpb=819247f875aa17ebcd2669676ee53195786a5457;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/weapons/weaponsystem.qc b/qcsrc/server/weapons/weaponsystem.qc index 762f9a0e3..21057e4a0 100644 --- a/qcsrc/server/weapons/weaponsystem.qc +++ b/qcsrc/server/weapons/weaponsystem.qc @@ -1,838 +1,638 @@ -/* -=========================================================================== +#include "weaponsystem.qh" - CLIENT WEAPONSYSTEM CODE - Bring back W_Weaponframe +#include "selection.qh" -=========================================================================== -*/ +#include "../command/common.qh" +#include "../mutators/all.qh" +#include "../round_handler.qh" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +.int state; .float weapon_frametime; -float W_WeaponRateFactor() +float W_WeaponRateFactor(entity this) { - float t; - t = 1.0 / g_weaponratefactor; + float t = 1.0 / g_weaponratefactor; - weapon_rate = t; - MUTATOR_CALLHOOK(WeaponRateFactor); - t = weapon_rate; + MUTATOR_CALLHOOK(WeaponRateFactor, t, this); + t = M_ARGV(0, float); return t; } -// VorteX: static frame globals -const float WFRAME_DONTCHANGE = -1; -const float WFRAME_FIRE1 = 0; -const float WFRAME_FIRE2 = 1; -const float WFRAME_IDLE = 2; -const float WFRAME_RELOAD = 3; -.float wframe; +float W_WeaponSpeedFactor(entity this) +{ + float t = 1.0 * g_weaponspeedfactor; -void(float fr, float t, void() func) weapon_thinkf; + MUTATOR_CALLHOOK(WeaponSpeedFactor, t, this); + t = M_ARGV(0, float); -float CL_Weaponentity_CustomizeEntityForClient() -{ - self.viewmodelforclient = self.owner; - if(IS_SPEC(other)) - if(other.enemy == self.owner) - self.viewmodelforclient = other; - return TRUE; + return t; } -/* - * supported formats: - * - * 1. simple animated model, muzzle flash handling on h_ model: - * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation - * tags: - * shot = muzzle end (shot origin, also used for muzzle flashes) - * shell = casings ejection point (must be on the right hand side of the gun) - * weapon = attachment for v_tuba.md3 - * v_tuba.md3 - first and third person model - * g_tuba.md3 - pickup model - * - * 2. simple animated model, muzzle flash handling on v_ model: - * h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation - * tags: - * weapon = attachment for v_tuba.md3 - * v_tuba.md3 - first and third person model - * tags: - * shot = muzzle end (shot origin, also used for muzzle flashes) - * shell = casings ejection point (must be on the right hand side of the gun) - * g_tuba.md3 - pickup model - * - * 3. fully animated model, muzzle flash handling on h_ model: - * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model - * tags: - * shot = muzzle end (shot origin, also used for muzzle flashes) - * shell = casings ejection point (must be on the right hand side of the gun) - * handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes) - * v_tuba.md3 - third person model - * g_tuba.md3 - pickup model - * - * 4. fully animated model, muzzle flash handling on v_ model: - * h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model - * tags: - * shot = muzzle end (shot origin) - * shell = casings ejection point (must be on the right hand side of the gun) - * v_tuba.md3 - third person model - * tags: - * shot = muzzle end (for muzzle flashes) - * g_tuba.md3 - pickup model - */ -// writes: -// self.origin, self.angles -// self.weaponentity -// self.movedir, self.view_ofs -// attachment stuff -// anim stuff -// to free: -// call again with "" -// remove the ent -void CL_WeaponEntity_SetModel(string name) +bool CL_Weaponentity_CustomizeEntityForClient(entity this) { - float v_shot_idx; - if (name != "") - { - // if there is a child entity, hide it until we're sure we use it - if (self.weaponentity) - self.weaponentity.model = ""; - setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below - v_shot_idx = gettagindex(self, "shot"); // used later - if(!v_shot_idx) - v_shot_idx = gettagindex(self, "tag_shot"); - - setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below - // preset some defaults that work great for renamed zym files (which don't need an animinfo) - self.anim_fire1 = animfixfps(self, '0 1 0.01', '0 0 0'); - self.anim_fire2 = animfixfps(self, '1 1 0.01', '0 0 0'); - self.anim_idle = animfixfps(self, '2 1 0.01', '0 0 0'); - self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0'); - - // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model) - // if we don't, this is a "real" animated model - if(gettagindex(self, "weapon")) - { - if (!self.weaponentity) - self.weaponentity = spawn(); - setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter - setattachment(self.weaponentity, self, "weapon"); - } - else if(gettagindex(self, "tag_weapon")) - { - if (!self.weaponentity) - self.weaponentity = spawn(); - setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter - setattachment(self.weaponentity, self, "tag_weapon"); - } - else - { - if(self.weaponentity) - remove(self.weaponentity); - self.weaponentity = world; - } - - setorigin(self,'0 0 0'); - self.angles = '0 0 0'; - self.frame = 0; - self.viewmodelforclient = world; - - float idx; - - if(v_shot_idx) // v_ model attached to invisible h_ model - { - self.movedir = gettaginfo(self.weaponentity, v_shot_idx); - } - else - { - idx = gettagindex(self, "shot"); - if(!idx) - idx = gettagindex(self, "tag_shot"); - if(idx) - self.movedir = gettaginfo(self, idx); - else - { - print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n"); - self.movedir = '0 0 0'; - } - } - - if(self.weaponentity) // v_ model attached to invisible h_ model - { - idx = gettagindex(self.weaponentity, "shell"); - if(!idx) - idx = gettagindex(self.weaponentity, "tag_shell"); - if(idx) - self.spawnorigin = gettaginfo(self.weaponentity, idx); - } - else - idx = 0; - if(!idx) - { - idx = gettagindex(self, "shell"); - if(!idx) - idx = gettagindex(self, "tag_shell"); - if(idx) - self.spawnorigin = gettaginfo(self, idx); - else - { - print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n"); - self.spawnorigin = self.movedir; - } - } - - if(v_shot_idx) - { - self.oldorigin = '0 0 0'; // use regular attachment - } - else - { - if(self.weaponentity) - { - idx = gettagindex(self, "weapon"); - if(!idx) - idx = gettagindex(self, "tag_weapon"); - } - else - { - idx = gettagindex(self, "handle"); - if(!idx) - idx = gettagindex(self, "tag_handle"); - } - if(idx) - { - self.oldorigin = self.movedir - gettaginfo(self, idx); - } - else - { - print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n"); - self.oldorigin = '0 0 0'; // there is no way to recover from this - } - } - - self.viewmodelforclient = self.owner; - } - else - { - self.model = ""; - if(self.weaponentity) - remove(self.weaponentity); - self.weaponentity = world; - self.movedir = '0 0 0'; - self.spawnorigin = '0 0 0'; - self.oldorigin = '0 0 0'; - self.anim_fire1 = '0 1 0.01'; - self.anim_fire2 = '0 1 0.01'; - self.anim_idle = '0 1 0.01'; - self.anim_reload = '0 1 0.01'; - } - - self.view_ofs = '0 0 0'; - - if(self.movedir_x >= 0) - { - vector v0; - v0 = self.movedir; - self.movedir = shotorg_adjust(v0, FALSE, FALSE); - self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0; - } - self.owner.stat_shotorg = compressShotOrigin(self.movedir); - self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly - - self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount - - // check if an instant weapon switch occurred - setorigin(self, self.view_ofs); - // reset animstate now - self.wframe = WFRAME_IDLE; - setanim(self, self.anim_idle, TRUE, FALSE, TRUE); + this.viewmodelforclient = this.owner; + if (IS_SPEC(other) && other.enemy == this.owner) this.viewmodelforclient = other; + return true; } -vector CL_Weapon_GetShotOrg(float wpn) +vector CL_Weapon_GetShotOrg(int wpn) { - entity wi, oldself; - vector ret; - wi = get_weaponinfo(wpn); - oldself = self; - self = spawn(); - CL_WeaponEntity_SetModel(wi.mdl); - ret = self.movedir; - CL_WeaponEntity_SetModel(""); - remove(self); - self = oldself; + entity wi = Weapons_from(wpn); + entity e = spawn(); + CL_WeaponEntity_SetModel(e, wi.mdl, false); + vector ret = e.movedir; + CL_WeaponEntity_SetModel(e, "", false); + remove(e); return ret; } -void CL_Weaponentity_Think() +..entity weaponentity_fld; +.float m_alpha; + +void CL_Weaponentity_Think(entity this) { - float tb; - self.nextthink = time; - if (intermission_running) - self.frame = self.anim_idle_x; - if (self.owner.weaponentity != self) - { - if (self.weaponentity) - remove(self.weaponentity); - remove(self); + this.nextthink = time; + if (intermission_running) this.frame = this.anim_idle.x; + .entity weaponentity = this.weaponentity_fld; + if (this.owner.(weaponentity) != this) + { + // owner has new gun; remove old one + if (this.weaponchild) remove(this.weaponchild); + remove(this); return; } - if (self.owner.deadflag != DEAD_NO) + if (IS_DEAD(this.owner)) { - self.model = ""; - if (self.weaponentity) - self.weaponentity.model = ""; + // owner died; disappear + this.model = ""; + if (this.weaponchild) this.weaponchild.model = ""; return; } - if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag) + if (this.weaponname != this.owner.weaponname + || this.dmg != this.owner.modelindex + || this.deadflag != this.owner.deadflag) { - self.weaponname = self.owner.weaponname; - self.dmg = self.owner.modelindex; - self.deadflag = self.owner.deadflag; + // owner changed weapons; update appearance + this.weaponname = this.owner.weaponname; + this.dmg = this.owner.modelindex; + this.deadflag = this.owner.deadflag; - CL_WeaponEntity_SetModel(self.owner.weaponname); + CL_WeaponEntity_SetModel(this, this.owner.weaponname, true); } - tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT)); - self.effects = self.owner.effects & EFMASK_CHEAP; - self.effects &= ~EF_LOWPRECISION; - self.effects &= ~EF_FULLBRIGHT; // can mask team color, so get rid of it - self.effects &= ~EF_TELEPORT_BIT; - self.effects &= ~EF_RESTARTANIM_BIT; - self.effects |= tb; + this.alpha = -1; // TODO: don't render this entity at all - if(self.owner.alpha == default_player_alpha) - self.alpha = default_weapon_alpha; - else if(self.owner.alpha != 0) - self.alpha = self.owner.alpha; - else - self.alpha = 1; + if (this.owner.alpha == default_player_alpha) this.m_alpha = default_weapon_alpha; + else if (this.owner.alpha != 0) this.m_alpha = this.owner.alpha; + else this.m_alpha = 1; - self.glowmod = self.owner.weaponentity_glowmod; - self.colormap = self.owner.colormap; - if (self.weaponentity) + if (this.weaponchild) { - self.weaponentity.effects = self.effects; - self.weaponentity.alpha = self.alpha; - self.weaponentity.colormap = self.colormap; - self.weaponentity.glowmod = self.glowmod; - } - - self.angles = '0 0 0'; - - float f = (self.owner.weapon_nextthink - time); - if (self.state == WS_RAISE && !intermission_running) - { - entity newwep = get_weaponinfo(self.owner.switchweapon); - f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise); - self.angles_x = -90 * f * f; - } - else if (self.state == WS_DROP && !intermission_running) - { - entity oldwep = get_weaponinfo(self.owner.weapon); - f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop); - self.angles_x = -90 * f * f; - } - else if (self.state == WS_CLEAR) - { - f = 1; - self.angles_x = -90 * f * f; + this.weaponchild.alpha = this.alpha; + this.weaponchild.effects = this.effects; } } -void CL_ExteriorWeaponentity_Think() +void CL_ExteriorWeaponentity_Think(entity this) { - float tag_found; - self.nextthink = time; - if (self.owner.exteriorweaponentity != self) + this.nextthink = time; + if (this.owner.exteriorweaponentity != this) { - remove(self); + remove(this); return; } - if (self.owner.deadflag != DEAD_NO) + if (IS_DEAD(this.owner)) { - self.model = ""; + this.model = ""; return; } - if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag) + if (this.weaponname != this.owner.weaponname || this.dmg != this.owner.modelindex + || this.deadflag != this.owner.deadflag) { - self.weaponname = self.owner.weaponname; - self.dmg = self.owner.modelindex; - self.deadflag = self.owner.deadflag; - if (self.owner.weaponname != "") - setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below - else - self.model = ""; + this.weaponname = this.owner.weaponname; + this.dmg = this.owner.modelindex; + this.deadflag = this.owner.deadflag; + if (this.owner.weaponname != "") + { + _setmodel(this, W_Model(strcat("v_", this.owner.weaponname, ".md3"))); + setsize(this, '0 0 0', '0 0 0'); + } + else this.model = ""; - if((tag_found = gettagindex(self.owner, "tag_weapon"))) + int tag_found; + if ((tag_found = gettagindex(this.owner, "tag_weapon"))) { - self.tag_index = tag_found; - self.tag_entity = self.owner; + this.tag_index = tag_found; + this.tag_entity = this.owner; } else - setattachment(self, self.owner, "bip01 r hand"); - } - self.effects = self.owner.effects; - self.effects |= EF_LOWPRECISION; - self.effects = self.effects & EFMASK_CHEAP; // eat performance - if(self.owner.alpha == default_player_alpha) - self.alpha = default_weapon_alpha; - else if(self.owner.alpha != 0) - self.alpha = self.owner.alpha; - else - self.alpha = 1; + { + setattachment(this, this.owner, "bip01 r hand"); + } + } + this.effects = this.owner.effects; + this.effects |= EF_LOWPRECISION; + this.effects = this.effects & EFMASK_CHEAP; // eat performance + if (this.owner.alpha == default_player_alpha) this.alpha = default_weapon_alpha; + else if (this.owner.alpha != 0) this.alpha = this.owner.alpha; + else this.alpha = 1; - self.glowmod = self.owner.weaponentity_glowmod; - self.colormap = self.owner.colormap; + Weapon wep = PS(this.owner).m_weapon; + if (wep) this.glowmod = weaponentity_glowmod(wep, this.owner.clientcolors); + this.colormap = this.owner.colormap; - CSQCMODEL_AUTOUPDATE(); + CSQCMODEL_AUTOUPDATE(this); } // spawning weaponentity for client -void CL_SpawnWeaponentity() +void CL_SpawnWeaponentity(entity actor, .entity weaponentity) { - self.weaponentity = spawn(); - self.weaponentity.classname = "weaponentity"; - self.weaponentity.solid = SOLID_NOT; - self.weaponentity.owner = self; - setmodel(self.weaponentity, ""); // precision set when changed - setorigin(self.weaponentity, '0 0 0'); - self.weaponentity.angles = '0 0 0'; - self.weaponentity.viewmodelforclient = self; - self.weaponentity.flags = 0; - self.weaponentity.think = CL_Weaponentity_Think; - self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient; - self.weaponentity.nextthink = time; - - self.exteriorweaponentity = spawn(); - self.exteriorweaponentity.classname = "exteriorweaponentity"; - self.exteriorweaponentity.solid = SOLID_NOT; - self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity; - self.exteriorweaponentity.owner = self; - setorigin(self.exteriorweaponentity, '0 0 0'); - self.exteriorweaponentity.angles = '0 0 0'; - self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think; - self.exteriorweaponentity.nextthink = time; - - { - entity oldself = self; - self = self.exteriorweaponentity; - CSQCMODEL_AUTOINIT(); - self = oldself; + entity view = actor.(weaponentity) = new(weaponentity); + view.solid = SOLID_NOT; + view.owner = actor; + setmodel(view, MDL_Null); // precision set when changed + setorigin(view, '0 0 0'); + view.weaponentity_fld = weaponentity; + setthink(view, CL_Weaponentity_Think); + view.nextthink = time; + view.viewmodelforclient = actor; + setcefc(view, CL_Weaponentity_CustomizeEntityForClient); + + if (weaponentity == weaponentities[0]) + { + entity exterior = actor.exteriorweaponentity = new(exteriorweaponentity); + exterior.solid = SOLID_NOT; + exterior.owner = actor; + setorigin(exterior, '0 0 0'); + setthink(exterior, CL_ExteriorWeaponentity_Think); + exterior.nextthink = time; + + CSQCMODEL_AUTOINIT(exterior); } } // Weapon subs -void w_clear() +void w_clear(Weapon thiswep, entity actor, .entity weaponentity, int fire) { - if (self.weapon != -1) - { - self.weapon = 0; - self.switchingweapon = 0; - } - if (self.weaponentity) + PS(actor).m_weapon = WEP_Null; + PS(actor).m_switchingweapon = WEP_Null; + entity this = actor.(weaponentity); + if (this) { - self.weaponentity.state = WS_CLEAR; - self.weaponentity.effects = 0; + this.state = WS_CLEAR; + this.effects = 0; } } -void w_ready() +void w_ready(Weapon thiswep, entity actor, .entity weaponentity, int fire) { - if (self.weaponentity) - self.weaponentity.state = WS_READY; - weapon_thinkf(WFRAME_IDLE, 1000000, w_ready); + entity this = actor.(weaponentity); + if (this) this.state = WS_READY; + weapon_thinkf(actor, weaponentity, WFRAME_IDLE, 1000000, w_ready); } .float prevdryfire; .float prevwarntime; -float weapon_prepareattack_checkammo(float secondary) +bool weapon_prepareattack_checkammo(Weapon thiswep, entity actor, bool secondary) { - if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) - if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary)) - { - // always keep the Mine Layer if we placed mines, so that we can detonate them - entity mine; - if(self.weapon == WEP_MINE_LAYER) - for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self) - return FALSE; + if ((actor.items & IT_UNLIMITED_WEAPON_AMMO)) return true; + bool ammo = false; + if (secondary) ammo = thiswep.wr_checkammo2(thiswep, actor); + else ammo = thiswep.wr_checkammo1(thiswep, actor); + if (ammo) return true; + // always keep the Mine Layer if we placed mines, so that we can detonate them + if (thiswep == WEP_MINE_LAYER) + for (entity mine; (mine = find(mine, classname, "mine")); ) + if (mine.owner == actor) return false; - if(self.weapon == WEP_SHOTGUN) - if(!secondary && WEP_CVAR(shotgun, secondary) == 1) - return FALSE; // no clicking, just allow + if (thiswep == WEP_SHOTGUN) + if (!secondary && WEP_CVAR(shotgun, secondary) == 1) return false; // no clicking, just allow - if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons - { - sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM); - self.prevdryfire = time; - } + if (thiswep == PS(actor).m_switchweapon && time - actor.prevdryfire > 1) // only play once BEFORE starting to switch weapons + { + sound(actor, CH_WEAPON_A, SND_DRYFIRE, VOL_BASE, ATTEN_NORM); + actor.prevdryfire = time; + } - if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo - { - if(time - self.prevwarntime > 1) - { - Send_Notification( - NOTIF_ONE, - self, - MSG_MULTI, - ITEM_WEAPON_PRIMORSEC, - self.weapon, - secondary, - (1 - secondary) - ); - } - self.prevwarntime = time; - } - else // this weapon is totally unable to fire, switch to another one + // check if the other firing mode has enough ammo + bool ammo_other = false; + if (secondary) ammo_other = thiswep.wr_checkammo1(thiswep, actor); + else ammo_other = thiswep.wr_checkammo2(thiswep, actor); + if (ammo_other) + { + if (time - actor.prevwarntime > 1) { - W_SwitchToOtherWeapon(self); + Send_Notification( + NOTIF_ONE, + actor, + MSG_MULTI, + ITEM_WEAPON_PRIMORSEC, + thiswep.m_id, + secondary, + (1 - secondary) + ); } - - return FALSE; + actor.prevwarntime = time; } - return TRUE; + else // this weapon is totally unable to fire, switch to another one + { + W_SwitchToOtherWeapon(actor); + } + + return false; } + .float race_penalty; -float weapon_prepareattack_check(float secondary, float attacktime) +bool weapon_prepareattack_check(Weapon thiswep, entity actor, .entity weaponentity, bool secondary, float attacktime) { - if(!weapon_prepareattack_checkammo(secondary)) - return FALSE; + if (actor.weaponentity == NULL) return true; + if (!weapon_prepareattack_checkammo(thiswep, actor, secondary)) return false; - //if sv_ready_restart_after_countdown is set, don't allow the player to shoot - //if all players readied up and the countdown is running - if(time < game_starttime || time < self.race_penalty) { - return FALSE; - } + // if sv_ready_restart_after_countdown is set, don't allow the player to shoot + // if all players readied up and the countdown is running + if (time < game_starttime || time < actor.race_penalty) return false; - if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused - return FALSE; + if (timeout_status == TIMEOUT_ACTIVE) // don't allow the player to shoot while game is paused + return false; // do not even think about shooting if switching - if(self.switchweapon != self.weapon) - return FALSE; + if (PS(actor).m_switchweapon != PS(actor).m_weapon) return false; - if(attacktime >= 0) + if (attacktime >= 0) { + int slot = weaponslot(weaponentity); // don't fire if previous attack is not finished - if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5) - return FALSE; + if (ATTACK_FINISHED(actor, slot) > time + actor.weapon_frametime * 0.5) return false; + entity this = actor.(weaponentity); // don't fire while changing weapon - if (self.weaponentity.state != WS_READY) - return FALSE; + if (this.state != WS_READY) return false; } - - return TRUE; + return true; } -float weapon_prepareattack_do(float secondary, float attacktime) + +void weapon_prepareattack_do(entity actor, .entity weaponentity, bool secondary, float attacktime) { - self.weaponentity.state = WS_INUSE; + entity this = actor.(weaponentity); + if (this == NULL) return; + this.state = WS_INUSE; - self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire + actor.spawnshieldtime = min(actor.spawnshieldtime, time); // kill spawn shield when you fire // if the weapon hasn't been firing continuously, reset the timer - if(attacktime >= 0) + if (attacktime >= 0) { - if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5) + int slot = weaponslot(weaponentity); + if (ATTACK_FINISHED(actor, slot) < time - actor.weapon_frametime * 1.5) { - ATTACK_FINISHED(self) = time; - //dprint("resetting attack finished to ", ftos(time), "\n"); + ATTACK_FINISHED(actor, slot) = time; + // dprint("resetting attack finished to ", ftos(time), "\n"); } - ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor(); + ATTACK_FINISHED(actor, slot) = ATTACK_FINISHED(actor, slot) + attacktime * W_WeaponRateFactor(actor); } - self.bulletcounter += 1; - //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n"); - return TRUE; + actor.bulletcounter += 1; + // dprint("attack finished ", ftos(ATTACK_FINISHED(actor, slot)), "\n"); } -float weapon_prepareattack(float secondary, float attacktime) + +bool weapon_prepareattack(Weapon thiswep, entity actor, .entity weaponentity, bool secondary, float attacktime) { - if(weapon_prepareattack_check(secondary, attacktime)) + if (weapon_prepareattack_check(thiswep, actor, weaponentity, secondary, attacktime)) { - weapon_prepareattack_do(secondary, attacktime); - return TRUE; + weapon_prepareattack_do(actor, weaponentity, secondary, attacktime); + return true; } - else - return FALSE; + return false; } -void weapon_thinkf(float fr, float t, void() func) -{ - vector a; - vector of, or, ou; - float restartanim; +void wframe_send(entity actor, entity weaponentity, vector a, bool restartanim); - if(fr == WFRAME_DONTCHANGE) +/** + * @param t defer thinking until time + t + * @param func next think function + */ +void weapon_thinkf(entity actor, .entity weaponentity, WFRAME fr, float t, void(Weapon thiswep, entity actor, + .entity weaponentity, int fire) func) +{ + entity this = actor.(weaponentity); + if (this == NULL) return; + bool restartanim; + if (fr == WFRAME_DONTCHANGE) { - fr = self.weaponentity.wframe; - restartanim = FALSE; + fr = this.wframe; + restartanim = false; } - else if (fr == WFRAME_IDLE) - restartanim = FALSE; else - restartanim = TRUE; - - of = v_forward; - or = v_right; - ou = v_up; - - if (self.weaponentity) { - self.weaponentity.wframe = fr; - a = '0 0 0'; - if (fr == WFRAME_IDLE) - a = self.weaponentity.anim_idle; - else if (fr == WFRAME_FIRE1) - a = self.weaponentity.anim_fire1; - else if (fr == WFRAME_FIRE2) - a = self.weaponentity.anim_fire2; - else // if (fr == WFRAME_RELOAD) - a = self.weaponentity.anim_reload; - a_z *= g_weaponratefactor; - setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim); + restartanim = fr != WFRAME_IDLE; } + vector of = v_forward; + vector or = v_right; + vector ou = v_up; + + vector a = '0 0 0'; + this.wframe = fr; + if (fr == WFRAME_IDLE) a = this.anim_idle; + else if (fr == WFRAME_FIRE1) a = this.anim_fire1; + else if (fr == WFRAME_FIRE2) a = this.anim_fire2; + else // if (fr == WFRAME_RELOAD) + a = this.anim_reload; + a.z *= g_weaponratefactor; + v_forward = of; v_right = or; v_up = ou; - if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE) - { - backtrace("Tried to override initial weapon think function - should this really happen?"); - } + if (this.weapon_think == w_ready && func != w_ready && this.state == WS_RAISE) backtrace( + "Tried to override initial weapon think function - should this really happen?"); - t *= W_WeaponRateFactor(); + t *= W_WeaponRateFactor(actor); // VorteX: haste can be added here - if (self.weapon_think == w_ready) + if (this.weapon_think == w_ready) { - self.weapon_nextthink = time; - //dprint("started firing at ", ftos(time), "\n"); + this.weapon_nextthink = time; + // dprint("started firing at ", ftos(time), "\n"); } - if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5) + if (this.weapon_nextthink < time - actor.weapon_frametime * 1.5 + || this.weapon_nextthink > time + actor.weapon_frametime * 1.5) { - self.weapon_nextthink = time; - //dprint("reset weapon animation timer at ", ftos(time), "\n"); + this.weapon_nextthink = time; + // dprint("reset weapon animation timer at ", ftos(time), "\n"); } - self.weapon_nextthink = self.weapon_nextthink + t; - self.weapon_think = func; - //dprint("next ", ftos(self.weapon_nextthink), "\n"); + this.weapon_nextthink += t; + if (weaponentity == weaponentities[0]) STAT(WEAPON_NEXTTHINK, actor) = this.weapon_nextthink; + this.weapon_think = func; + // dprint("next ", ftos(this.weapon_nextthink), "\n"); - if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t) + if (this) { - if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2) - animdecide_setaction(self, ANIMACTION_MELEE, restartanim); - else - animdecide_setaction(self, ANIMACTION_SHOOT, restartanim); + FOREACH_CLIENT(true, LAMBDA( + if(it == actor || (IS_SPEC(it) && it.enemy == actor)) + wframe_send(it, this, a, restartanim); + )); } - else + + if ((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t) { - if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE) - self.anim_upper_action = 0; + int act = (fr == WFRAME_FIRE2 && (PS(actor).m_weapon == WEP_SHOCKWAVE || PS(actor).m_weapon == WEP_SHOTGUN)) + ? ANIMACTION_MELEE + : ANIMACTION_SHOOT + ; + animdecide_setaction(actor, act, restartanim); + } + else if (actor.anim_upper_action == ANIMACTION_SHOOT || actor.anim_upper_action == ANIMACTION_MELEE) + { + actor.anim_upper_action = 0; } } -float forbidWeaponUse() +bool forbidWeaponUse(entity player) { - if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown) - return 1; - if(round_handler_IsActive() && !round_handler_IsRoundStarted()) - return 1; - if(self.player_blocked) - return 1; - if(self.frozen) - return 1; - if(self.weapon_blocked) - return 1; - return 0; + if (time < game_starttime && !autocvar_sv_ready_restart_after_countdown) return true; + if (round_handler_IsActive() && !round_handler_IsRoundStarted()) return true; + if (player.player_blocked) return true; + if (STAT(FROZEN, player)) return true; + if (player.weapon_blocked) return true; + return false; } -void W_WeaponFrame() +.bool hook_switchweapon; + +void W_WeaponFrame(Player actor) { - vector fo, ri, up; + TC(Player, actor); + TC(PlayerState, PS(actor)); + .entity weaponentity = weaponentities[0]; // TODO: unhardcode + entity this = actor.(weaponentity); + if (frametime) actor.weapon_frametime = frametime; - if (frametime) - self.weapon_frametime = frametime; + if (!this || actor.health < 1) return; // Dead player can't use weapons and injure impulse commands - if (!self.weaponentity || self.health < 1) - return; // Dead player can't use weapons and injure impulse commands - if(forbidWeaponUse()) - if(self.weaponentity.state != WS_CLEAR) + if (forbidWeaponUse(actor)) { - w_ready(); - return; + if (actor.(weaponentity).state != WS_CLEAR) + { + Weapon wpn = PS(actor).m_weapon; + w_ready(wpn, actor, weaponentity, PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1)); + return; + } } - if(!self.switchweapon) + if (PS(actor).m_switchweapon == WEP_Null) { - self.weapon = 0; - self.switchingweapon = 0; - self.weaponentity.state = WS_CLEAR; - self.weaponname = ""; - //self.items &= ~IT_AMMO; + PS(actor).m_weapon = WEP_Null; + PS(actor).m_switchingweapon = WEP_Null; + this.state = WS_CLEAR; + actor.weaponname = ""; + // actor.items &= ~IT_AMMO; return; } - makevectors(self.v_angle); - fo = v_forward; // save them in case the weapon think functions change it - ri = v_right; - up = v_up; + makevectors(actor.v_angle); + vector fo = v_forward; // save them in case the weapon think functions change it + vector ri = v_right; + vector up = v_up; // Change weapon - if (self.weapon != self.switchweapon) + if (PS(actor).m_weapon != PS(actor).m_switchweapon) { - if (self.weaponentity.state == WS_CLEAR) + switch (this.state) { - // end switching! - self.switchingweapon = self.switchweapon; - entity newwep = get_weaponinfo(self.switchweapon); - - // the two weapon entities will notice this has changed and update their models - self.weapon = self.switchweapon; - self.weaponname = newwep.mdl; - self.bulletcounter = 0; - //self.ammo_field = newwep.ammo_field; - WEP_ACTION(self.switchweapon, WR_SETUP); - self.weaponentity.state = WS_RAISE; - - // set our clip load to the load of the weapon we switched to, if it's reloadable - if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars + default: + LOG_WARNINGF("unhandled weaponentity (%i) state for player (%i): %d\n", this, actor, this.state); + break; + case WS_INUSE: + case WS_RAISE: + break; + case WS_CLEAR: { - self.clip_load = self.(weapon_load[self.switchweapon]); - self.clip_size = newwep.reloading_ammo; + // end switching! + Weapon newwep = PS(actor).m_switchweapon; + PS(actor).m_switchingweapon = newwep; + + // the two weapon entities will notice this has changed and update their models + PS(actor).m_weapon = newwep; + actor.weaponname = newwep.mdl; + actor.bulletcounter = 0; + actor.ammo_field = newwep.ammo_field; + newwep.wr_setup(newwep, actor); + this.state = WS_RAISE; + + // set our clip load to the load of the weapon we switched to, if it's reloadable + if ((newwep.spawnflags & WEP_FLAG_RELOADABLE) && newwep.reloading_ammo) // prevent accessing undefined cvars + { + actor.clip_load = actor.(weapon_load[PS(actor).m_switchweapon.m_id]); + actor.clip_size = newwep.reloading_ammo; + } + else + { + actor.clip_load = actor.clip_size = 0; + } + + weapon_thinkf(actor, weaponentity, WFRAME_IDLE, newwep.switchdelay_raise, w_ready); + break; } - else - self.clip_load = self.clip_size = 0; - - weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready); - } - else if (self.weaponentity.state == WS_DROP) - { - // in dropping phase we can switch at any time - self.switchingweapon = self.switchweapon; - } - else if (self.weaponentity.state == WS_READY) - { - // start switching! - self.switchingweapon = self.switchweapon; - entity oldwep = get_weaponinfo(self.weapon); - - // set up weapon switch think in the future, and start drop anim - #ifndef INDEPENDENT_ATTACK_FINISHED - if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5) + case WS_DROP: { - #endif - sound(self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM); - self.weaponentity.state = WS_DROP; - weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear); - #ifndef INDEPENDENT_ATTACK_FINISHED + // in dropping phase we can switch at any time + PS(actor).m_switchingweapon = PS(actor).m_switchweapon; + break; + } + case WS_READY: + { + // start switching! + PS(actor).m_switchingweapon = PS(actor).m_switchweapon; + entity oldwep = PS(actor).m_weapon; + + // set up weapon switch think in the future, and start drop anim + if (INDEPENDENT_ATTACK_FINISHED || ATTACK_FINISHED(actor, weaponslot(weaponentity)) <= time + actor.weapon_frametime * 0.5) + { + sound(actor, CH_WEAPON_SINGLE, SND_WEAPON_SWITCH, VOL_BASE, ATTN_NORM); + this.state = WS_DROP; + weapon_thinkf(actor, weaponentity, WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear); + } + break; } - #endif } } // LordHavoc: network timing test code - //if (self.button0) - // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n"); + // if (actor.button0) + // print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(actor, slot)), " >= ", ftos(this.weapon_nextthink), "\n"); - float w; - w = self.weapon; + Weapon w = PS(actor).m_weapon; // call the think code which may fire the weapon // and do so multiple times to resolve framerate dependency issues if the // server framerate is very low and the weapon fire rate very high - float c; - c = 0; - while (c < W_TICSPERFRAME) + for (int c = 0; c < W_TICSPERFRAME; ++c) { - c = c + 1; - if(w && !(self.weapons & WepSet_FromWeapon(w))) + if (w != WEP_Null && !(actor.weapons & WepSet_FromWeapon(w))) { - if(self.weapon == self.switchweapon) - W_SwitchWeapon_Force(self, w_getbestweapon(self)); - w = 0; + if (PS(actor).m_weapon == PS(actor).m_switchweapon) W_SwitchWeapon_Force(actor, w_getbestweapon(actor)); + w = WEP_Null; } v_forward = fo; v_right = ri; v_up = up; - if(w) - WEP_ACTION(self.weapon, WR_THINK); - else - WEP_ACTION(self.weapon, WR_GONETHINK); + bool block_weapon = false; + { + bool key_pressed = PHYS_INPUT_BUTTON_HOOK(actor) && !actor.vehicle; + Weapon off = actor.offhand; + if (off && !(actor.weapons & WEPSET(HOOK))) + { + if (off.offhand_think) off.offhand_think(off, actor, key_pressed); + } + else + { + if (key_pressed && PS(actor).m_switchweapon != WEP_HOOK && !actor.hook_switchweapon) + W_SwitchWeapon(actor, WEP_HOOK); + actor.hook_switchweapon = key_pressed; + Weapon h = WEP_HOOK; + block_weapon = (PS(actor).m_weapon == h && (PHYS_INPUT_BUTTON_ATCK(actor) || key_pressed)); + h.wr_think(h, actor, weaponentity, block_weapon ? 1 : 0); + } + } - if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink) + v_forward = fo; + v_right = ri; + v_up = up; + + if (!block_weapon) + { + Weapon e = PS(actor).m_weapon; + TC(Weapon, e); + if (w != WEP_Null) + { + e.wr_think(e, actor, weaponentity, PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1)); + } + else if (e) + { + e.wr_gonethink(e, actor); + } + } + + if (time + actor.weapon_frametime * 0.5 >= this.weapon_nextthink) { - if(self.weapon_think) + if (this.weapon_think) { v_forward = fo; v_right = ri; v_up = up; - self.weapon_think(); + Weapon wpn = PS(actor).m_weapon; + this.weapon_think(wpn, actor, weaponentity, + PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1)); } else - bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n"); + { + bprint("\{1}^1ERROR: undefined weapon think function for ", actor.netname, "\n"); + } } } } -void W_AttachToShotorg(entity flash, vector offset) +void W_AttachToShotorg(entity actor, entity flash, vector offset) { - entity xflash; - flash.owner = self; + .entity weaponentity = weaponentities[0]; + flash.owner = actor; flash.angles_z = random() * 360; - if(gettagindex(self.weaponentity, "shot")) - setattachment(flash, self.weaponentity, "shot"); - else - setattachment(flash, self.weaponentity, "tag_shot"); + entity view = actor.(weaponentity); + entity exterior = actor.exteriorweaponentity; + + if (gettagindex(view, "shot")) setattachment(flash, view, "shot"); + else setattachment(flash, view, "tag_shot"); setorigin(flash, offset); - xflash = spawn(); + entity xflash = spawn(); copyentity(flash, xflash); - flash.viewmodelforclient = self; + flash.viewmodelforclient = actor; - if(self.weaponentity.oldorigin_x > 0) + if (view.oldorigin.x > 0) { - setattachment(xflash, self.exteriorweaponentity, ""); - setorigin(xflash, self.weaponentity.oldorigin + offset); + setattachment(xflash, exterior, ""); + setorigin(xflash, view.oldorigin + offset); } else { - if(gettagindex(self.exteriorweaponentity, "shot")) - setattachment(xflash, self.exteriorweaponentity, "shot"); - else - setattachment(xflash, self.exteriorweaponentity, "tag_shot"); + if (gettagindex(exterior, "shot")) setattachment(xflash, exterior, "shot"); + else setattachment(xflash, exterior, "tag_shot"); setorigin(xflash, offset); } } -void W_DecreaseAmmo(float ammo_use) +void W_DecreaseAmmo(Weapon wep, entity actor, float ammo_use) { - entity wep = get_weaponinfo(self.weapon); - - if(cvar("g_overkill")) - if(self.ok_use_ammocharge) - { - ok_DecreaseCharge(self, self.weapon); - return; // TODO - } + if (MUTATOR_CALLHOOK(W_DecreaseAmmo, actor)) return; - if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo) - return; + if ((actor.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo) return; // if this weapon is reloadable, decrease its load. Else decrease the player's ammo - if(wep.reloading_ammo) + if (wep.reloading_ammo) { - self.clip_load -= ammo_use; - self.(weapon_load[self.weapon]) = self.clip_load; + actor.clip_load -= ammo_use; + actor.(weapon_load[PS(actor).m_weapon.m_id]) = actor.clip_load; } - else if(wep.ammo_field != ammo_none) + else if (wep.ammo_field != ammo_none) { - self.(wep.ammo_field) -= ammo_use; - if(self.(wep.ammo_field) < 0) + actor.(wep.ammo_field) -= ammo_use; + if (actor.(wep.ammo_field) < 0) { backtrace(sprintf( "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... " @@ -840,9 +640,9 @@ void W_DecreaseAmmo(float ammo_use) ammo_use, wep.netname, GetAmmoPicture(wep.ammo_field), - self.netname, - self.(wep.ammo_field) - )); + actor.netname, + actor.(wep.ammo_field) + )); } } } @@ -853,117 +653,119 @@ void W_DecreaseAmmo(float ammo_use) .float reload_complain; .string reload_sound; -void W_ReloadedAndReady() +void W_ReloadedAndReady(Weapon thiswep, entity actor, .entity weaponentity, int fire) { // finish the reloading process, and do the ammo transfer - self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading + actor.clip_load = actor.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load - if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO || self.ammo_field == ammo_none) - self.clip_load = self.reload_ammo_amount; + if (!actor.reload_ammo_min || actor.items & IT_UNLIMITED_WEAPON_AMMO || actor.ammo_field == ammo_none) + { + actor.clip_load = actor.reload_ammo_amount; + } else { - while(self.clip_load < self.reload_ammo_amount && self.(self.ammo_field)) // make sure we don't add more ammo than we have - { - self.clip_load += 1; - self.(self.ammo_field) -= 1; - } + // make sure we don't add more ammo than we have + float load = min(actor.reload_ammo_amount - actor.clip_load, actor.(actor.ammo_field)); + actor.clip_load += load; + actor.(actor.ammo_field) -= load; } - self.(weapon_load[self.weapon]) = self.clip_load; + actor.(weapon_load[PS(actor).m_weapon.m_id]) = actor.clip_load; // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon, // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there, // so your weapon is disabled for a few seconds without reason - //ATTACK_FINISHED(self) -= self.reload_time - 1; + // ATTACK_FINISHED(actor, slot) -= actor.reload_time - 1; - w_ready(); + Weapon wpn = Weapons_from(PS(actor).m_weapon.m_id); + w_ready(wpn, actor, weaponentity, PHYS_INPUT_BUTTON_ATCK(actor) | (PHYS_INPUT_BUTTON_ATCK2(actor) << 1)); } -void W_Reload(float sent_ammo_min, string sent_sound) +void W_Reload(entity actor, float sent_ammo_min, Sound sent_sound) { + TC(Sound, sent_sound); + .entity weaponentity = weaponentities[0]; // set global values to work with - entity e; - e = get_weaponinfo(self.weapon); + Weapon e = PS(actor).m_weapon; - if(cvar("g_overkill")) - if(self.ok_use_ammocharge) - return; // TODO + if (MUTATOR_CALLHOOK(W_Reload, actor)) return; - self.reload_ammo_min = sent_ammo_min; - self.reload_ammo_amount = e.reloading_ammo;; - self.reload_time = e.reloading_time; - self.reload_sound = sent_sound; + actor.reload_ammo_min = sent_ammo_min; + actor.reload_ammo_amount = e.reloading_ammo; + actor.reload_time = e.reloading_time; + if (actor.reload_sound) strunzone(actor.reload_sound); + actor.reload_sound = strzone(Sound_fixpath(sent_sound)); // don't reload weapons that don't have the RELOADABLE flag if (!(e.spawnflags & WEP_FLAG_RELOADABLE)) { - dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n"); + LOG_TRACE( + "Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n"); return; } // return if reloading is disabled for this weapon - if(!self.reload_ammo_amount) - return; + if (!actor.reload_ammo_amount) return; // our weapon is fully loaded, no need to reload - if (self.clip_load >= self.reload_ammo_amount) - return; + if (actor.clip_load >= actor.reload_ammo_amount) return; // no ammo, so nothing to load - if(self.ammo_field != ammo_none) - if(!self.(self.ammo_field) && self.reload_ammo_min) - if (!(self.items & IT_UNLIMITED_WEAPON_AMMO)) + if (actor.ammo_field != ammo_none) { - if(IS_REAL_CLIENT(self) && self.reload_complain < time) - { - play2(self, "weapons/unavailable.wav"); - sprint(self, strcat("You don't have enough ammo to reload the ^2", WEP_NAME(self.weapon), "\n")); - self.reload_complain = time + 1; - } - // switch away if the amount of ammo is not enough to keep using this weapon - if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2))) + if (!actor.(actor.ammo_field) && actor.reload_ammo_min) { - self.clip_load = -1; // reload later - W_SwitchToOtherWeapon(self); + if (!(actor.items & IT_UNLIMITED_WEAPON_AMMO)) + { + if (IS_REAL_CLIENT(actor) && actor.reload_complain < time) + { + play2(actor, SND(UNAVAILABLE)); + sprint(actor, strcat("You don't have enough ammo to reload the ^2", PS(actor).m_weapon.m_name, "\n")); + actor.reload_complain = time + 1; + } + // switch away if the amount of ammo is not enough to keep using this weapon + Weapon w = PS(actor).m_weapon; + if (!(w.wr_checkammo1(w, actor) + w.wr_checkammo2(w, actor))) + { + actor.clip_load = -1; // reload later + W_SwitchToOtherWeapon(actor); + } + return; + } } - return; } - if (self.weaponentity) + entity this = actor.(weaponentity); + if (this) { - if (self.weaponentity.wframe == WFRAME_RELOAD) - return; + if (this.wframe == WFRAME_RELOAD) return; // allow switching away while reloading, but this will cause a new reload! - self.weaponentity.state = WS_READY; + this.state = WS_READY; } // now begin the reloading process - sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM); + _sound(actor, CH_WEAPON_SINGLE, actor.reload_sound, VOL_BASE, ATTEN_NORM); // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon, // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there, // so your weapon is disabled for a few seconds without reason - //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1; + // ATTACK_FINISHED(actor, slot) = max(time, ATTACK_FINISHED(actor, slot)) + actor.reload_time + 1; - weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady); + weapon_thinkf(actor, weaponentity, WFRAME_RELOAD, actor.reload_time, W_ReloadedAndReady); - if(self.clip_load < 0) - self.clip_load = 0; - self.old_clip_load = self.clip_load; - self.clip_load = self.(weapon_load[self.weapon]) = -1; + if (actor.clip_load < 0) actor.clip_load = 0; + actor.old_clip_load = actor.clip_load; + actor.clip_load = actor.(weapon_load[PS(actor).m_weapon.m_id]) = -1; } -entity weapon_dropevent_item; -void W_DropEvent(float event, entity player, float weapon_type, entity weapon_item) +void W_DropEvent(.void(Weapon, entity actor) event, entity player, float weapon_type, entity weapon_item) { - entity oldself = self; - self = player; + Weapon w = Weapons_from(weapon_type); weapon_dropevent_item = weapon_item; - WEP_ACTION(weapon_type, event); - self = oldself; + w.event(w, player); }