float BOT_PICKUP_RATING_MID = 5000;\r
float BOT_PICKUP_RATING_HIGH = 10000;\r
\r
-float WEP_TYPE_OTHER = 0x00; // e.g: Grabber, etc\r
-float WEP_TYPE_SPLASH = 0x01;\r
-float WEP_TYPE_HITSCAN = 0x02;\r
-float WEP_TYPEMASK = 0x0F;\r
-float WEP_FLAG_CANCLIMB = 0x10;\r
-float WEP_FLAG_NORMAL = 0x20;\r
-float WEP_FLAG_HIDDEN = 0x40;\r
+float WEP_TYPE_OTHER = 0x00; // e.g: Grabber, etc\r
+float WEP_TYPE_SPLASH = 0x01;\r
+float WEP_TYPE_HITSCAN = 0x02;\r
+float WEP_TYPEMASK = 0x0F;\r
+float WEP_FLAG_CANCLIMB = 0x10;\r
+float WEP_FLAG_NORMAL = 0x20;\r
+float WEP_FLAG_HIDDEN = 0x40;\r
+float WEP_FLAG_RELOADABLE = 0x80;\r
\r
float IT_UNLIMITED_WEAPON_AMMO = 1;\r
// when this bit is set, using a weapon does not reduce ammo. Checkpoints can give this powerup.\r
bot_aimdir(v, -1);
}
havocbot_movetogoal();
+
+ // if the bot is not attacking, consider reloading weapons
+ if not(self.aistatus & AI_STATUS_ATTACKING)
+ {
+ float i;
+ entity e;
+
+ // we are currently holding a weapon that's not fully loaded, reload it
+ if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
+ if(self.clip_load < self.clip_size)
+ self.impulse = 20; // "press" the reload button, not sure if this is done right
+
+ // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
+ // the code above executes next frame, starting the reloading then
+ if(skill >= 5) // bots can only look for unloaded weapons past this skill
+ if(self.clip_load >= 0) // only if we're not reloading a weapon already
+ {
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ e = get_weaponinfo(i);
+ if(self.weapon_load[i] < cvar(strcat("g_balance_", e.netname, "_reload_ammo")))
+ self.switchweapon = i;
+ }
+ }
+ }
};
void havocbot_keyboard_movement(vector destorg)
self.havocbot_stickenemy = TRUE;
};
+float havocbot_chooseweapon_checkreload(float new_weapon)
+{
+ // bots under this skill cannot find unloaded weapons to reload idly when not in combat,
+ // so skip this for them, or they'll never get to reload their weapons at all.
+ // this also allows bots under this skill to be more stupid, and reload more often during combat :)
+ if(skill < 5)
+ return FALSE;
+
+ // if this weapon is scheduled for reloading, don't switch to it during combat
+ if (self.weapon_load[new_weapon] < 0)
+ {
+ local float i, other_weapon_available;
+ for(i = WEP_FIRST; i <= WEP_LAST; ++i)
+ {
+ // if we are out of ammo for all other weapons, it's an emergency to switch to anything else
+ if (weapon_action(i, WR_CHECKAMMO1) + weapon_action(i, WR_CHECKAMMO2))
+ other_weapon_available = TRUE;
+ }
+ if(other_weapon_available)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
void havocbot_chooseweapon()
{
local float i;
for(i=0; i < WEP_COUNT && bot_weapons_far[i] != -1 ; ++i){
w = bot_weapons_far[i];
if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
+ if ( self.weapon == w && combo || havocbot_chooseweapon_checkreload(w))
continue;
self.switchweapon = w;
return;
for(i=0; i < WEP_COUNT && bot_weapons_mid[i] != -1 ; ++i){
w = bot_weapons_mid[i];
if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
+ if ( self.weapon == w && combo || havocbot_chooseweapon_checkreload(w))
continue;
self.switchweapon = w;
return;
for(i=0; i < WEP_COUNT && bot_weapons_close[i] != -1 ; ++i){
w = bot_weapons_close[i];
if ( client_hasweapon(self, w, TRUE, FALSE) ){
- if ( self.weapon == w && combo)
+ if ( self.weapon == w && combo || havocbot_chooseweapon_checkreload(w))
continue;
self.switchweapon = w;
return;
\r
// reset fields the weapons may use\r
for (j = WEP_FIRST; j <= WEP_LAST; ++j)\r
+ {\r
weapon_action(j, WR_RESETPLAYER);\r
\r
+ // all weapons must be fully loaded when we spawn\r
+ entity e;\r
+ e = get_weaponinfo(j);\r
+ if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars\r
+ self.weapon_load[j] = cvar(strcat("g_balance_", e.netname, "_reload_ammo"));\r
+ }\r
+\r
oldself = self;\r
self = spot;\r
activator = oldself;\r
+void W_TriggerReload()\r
+{\r
+ weapon_action(self.weapon, WR_RELOAD);\r
+}\r
+\r
// switch between weapons\r
void W_SwitchWeapon(float imp)\r
{\r
if (client_hasweapon(self, imp, TRUE, TRUE))\r
W_SwitchWeapon_Force(self, imp);\r
}\r
+ else\r
+ {\r
+ W_TriggerReload();\r
+ }\r
};\r
\r
.float weaponcomplainindex;\r
wep.ammofield = thisammo;\r
own.ammofield -= thisammo;\r
s = strcat(s, " and ", ftos(thisammo), " ", Item_CounterFieldName(j));\r
+\r
+ // if our weapon is loaded, give its load back to the player\r
+ if(self.weapon_load[self.weapon] > 0)\r
+ {\r
+ own.ammofield += self.weapon_load[self.weapon];\r
+ self.weapon_load[self.weapon] = -1; // schedule the weapon for reloading\r
+ }\r
}\r
}\r
s = substring(s, 5, -1);\r
setanim(self, self.anim_draw, FALSE, TRUE, TRUE);\r
self.weaponentity.state = WS_RAISE;\r
weapon_action(self.switchweapon, WR_SETUP);\r
+\r
+ // set our clip load to the load of the weapon we switched to, if it's reloadable\r
+ entity e;\r
+ e = get_weaponinfo(self.switchweapon);\r
+ if(e.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", e.netname, "_reload_ammo"))) // prevent accessing undefined cvars\r
+ {\r
+ self.clip_load = self.weapon_load[self.switchweapon];\r
+ self.clip_size = cvar(strcat("g_balance_", e.netname, "_reload_ammo"));\r
+ }\r
+ else\r
+ self.clip_load = self.clip_size = 0;\r
+\r
// VorteX: add player model weapon select frame here\r
// setcustomframe(PlayerWeaponRaise);\r
weapon_thinkf(WFRAME_IDLE, cvar("g_balance_weaponswitchdelay"), w_ready);\r
};\r
\r
// perform weapon to attack (weaponstate and attack_finished check is here)\r
-.float race_penalty;\r
-float weapon_prepareattack(float secondary, float attacktime)\r
+void W_SwitchToOtherWeapon(entity pl)\r
{\r
- //if sv_ready_restart_after_countdown is set, don't allow the player to shoot\r
- //if all players readied up and the countdown is running\r
- if(time < game_starttime || time < self.race_penalty) {\r
- return FALSE;\r
- }\r
+ // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)\r
+ float w, ww;\r
+ w = W_WeaponBit(pl.weapon);\r
+ pl.weapons &~= w;\r
+ ww = w_getbestweapon(pl);\r
+ pl.weapons |= w;\r
+ if(ww)\r
+ W_SwitchWeapon_Force(pl, ww);\r
+}\r
\r
+.float prevdryfire;\r
+float weapon_prepareattack_checkammo(float secondary)\r
+{\r
if not(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary))\r
{\r
- // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway)\r
- float w, ww;\r
- w = W_WeaponBit(self.weapon);\r
- self.weapons &~= w;\r
- ww = w_getbestweapon(self);\r
- self.weapons |= w;\r
- if(ww)\r
- W_SwitchWeapon_Force(self, ww);\r
+ if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons\r
+ {\r
+ sound (self, CHAN_AUTO, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM);\r
+ self.prevdryfire = time;\r
+ }\r
+\r
+ W_SwitchToOtherWeapon(self);\r
+ return FALSE;\r
+ }\r
+ return TRUE;\r
+}\r
+.float race_penalty;\r
+float weapon_prepareattack_check(float secondary, float attacktime)\r
+{\r
+ if(!weapon_prepareattack_checkammo(secondary))\r
+ return FALSE;\r
+\r
+ //if sv_ready_restart_after_countdown is set, don't allow the player to shoot\r
+ //if all players readied up and the countdown is running\r
+ if(time < game_starttime || time < self.race_penalty) {\r
return FALSE;\r
}\r
\r
if (self.weaponentity.state != WS_READY)\r
return FALSE;\r
}\r
+\r
+ return TRUE;\r
+}\r
+float weapon_prepareattack_do(float secondary, float attacktime)\r
+{\r
self.weaponentity.state = WS_INUSE;\r
\r
self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire\r
}\r
//dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");\r
return TRUE;\r
-};\r
+}\r
+float weapon_prepareattack(float secondary, float attacktime)\r
+{\r
+ if(weapon_prepareattack_check(secondary, attacktime))\r
+ {\r
+ weapon_prepareattack_do(secondary, attacktime);\r
+ return TRUE;\r
+ }\r
+ else\r
+ return FALSE;\r
+}\r
\r
void weapon_thinkf(float fr, float t, void() func)\r
{\r
\r
#define W_SETUPPROJECTILEVELOCITY_UP(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), cvar(#s "_speed_up"), cvar(#s "_speed_z"), cvar(#s "_spread"))\r
#define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread"))\r
+\r
+void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload)\r
+{\r
+ if(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
+ return;\r
+\r
+ // if this weapon is reloadable, decrease its load. Else decrease the player's ammo\r
+ if(ammo_reload)\r
+ {\r
+ self.clip_load -= ammo_use;\r
+ self.weapon_load[self.weapon] = self.clip_load;\r
+ }\r
+ else\r
+ self.(self.current_ammo) -= ammo_use;\r
+}\r
+\r
+// weapon reloading code\r
+\r
+.float reload_ammo_amount, reload_ammo_min, reload_time;\r
+.float reload_complain;\r
+.string reload_sound;\r
+\r
+float W_ReloadCheck()\r
+{\r
+ // check if we meet the necessary conditions to reload\r
+\r
+ entity e;\r
+ e = get_weaponinfo(self.weapon);\r
+\r
+ // don't reload weapons that don't have the RELOADABLE flag\r
+ if not(e.spawnflags & WEP_FLAG_RELOADABLE)\r
+ {\r
+ dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");\r
+ return FALSE;\r
+ }\r
+\r
+ // return if reloading is disabled for this weapon\r
+ if(!self.reload_ammo_amount)\r
+ return FALSE;\r
+\r
+ // our weapon is fully loaded, no need to reload\r
+ if (self.clip_load >= self.reload_ammo_amount)\r
+ return FALSE;\r
+\r
+ // no ammo, so nothing to load\r
+ if(!self.(self.current_ammo) && self.reload_ammo_min)\r
+ {\r
+ if(clienttype(self) == CLIENTTYPE_REAL && self.reload_complain < time)\r
+ {\r
+ play2(self, "weapons/unavailable.wav");\r
+ sprint(self, strcat("You don't have enough ammo to reload the ^2", W_Name(self.weapon), "\n"));\r
+ self.reload_complain = time + 1;\r
+ }\r
+ // switch away if the amount of ammo is not enough to keep using this weapon\r
+ if not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2))\r
+ {\r
+ self.clip_load = -1; // reload later\r
+ W_SwitchToOtherWeapon(self);\r
+ }\r
+ return FALSE;\r
+ }\r
+\r
+ if (self.weaponentity)\r
+ {\r
+ if (self.weaponentity.wframe == WFRAME_RELOAD)\r
+ return FALSE;\r
+\r
+ // allow switching away while reloading, but this will cause a new reload!\r
+ self.weaponentity.state = WS_READY;\r
+ }\r
+\r
+ return TRUE;\r
+}\r
+\r
+void W_ReloadedAndReady()\r
+{\r
+ // finish the reloading process, and do the ammo transfer\r
+\r
+ self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading\r
+\r
+ // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load\r
+ if(!self.reload_ammo_min)\r
+ self.clip_load = self.reload_ammo_amount;\r
+ else\r
+ {\r
+ while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have\r
+ {\r
+ self.clip_load += 1;\r
+ self.(self.current_ammo) -= 1;\r
+ }\r
+ }\r
+ self.weapon_load[self.weapon] = self.clip_load;\r
+\r
+ // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,\r
+ // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,\r
+ // so your weapon is disabled for a few seconds without reason\r
+\r
+ //ATTACK_FINISHED(self) -= self.reload_time - 1;\r
+\r
+ w_ready();\r
+}\r
+\r
+void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound)\r
+{\r
+ // set global values to work with\r
+ self.reload_ammo_min = sent_ammo_min;\r
+ self.reload_ammo_amount = sent_ammo_amount;\r
+ self.reload_time = sent_time;\r
+ self.reload_sound = sent_sound;\r
+\r
+ if(!W_ReloadCheck())\r
+ return;\r
+\r
+ // now begin the reloading process\r
+\r
+ sound (self, CHAN_WEAPON2, self.reload_sound, VOL_BASE, ATTN_NORM);\r
+\r
+ // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,\r
+ // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,\r
+ // so your weapon is disabled for a few seconds without reason\r
+\r
+ //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;\r
+\r
+ weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);\r
+\r
+ if(self.clip_load >= 0)\r
+ self.old_clip_load = self.clip_load;\r
+ self.clip_load = self.weapon_load[self.weapon] = -1;\r
+}
\ No newline at end of file
float WR_SUICIDEMESSAGE = 7; // sets w_deathtypestring or leaves it alone (and may inspect w_deathtype for details)\r
float WR_KILLMESSAGE = 8; // sets w_deathtypestring or leaves it alone\r
float WR_RESETPLAYER = 9; // does not need to do anything\r
+float WR_RELOAD = 10; // used for reloading\r
\r
void weapon_defaultspawnfunc(float wpn);\r
\r
float client_cefc_accumulatortime;\r
#endif\r
\r
+..float current_ammo;\r
+\r
+.float weapon_load[WEP_MAXCOUNT]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(weapon_load);\r
+.float clip_load;\r
+.float old_clip_load;\r
+.float clip_size;\r
+\r
#define PROJECTILE_MAKETRIGGER(e) (e).solid = SOLID_CORPSE; (e).dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE\r
// #define PROJECTILE_MAKETRIGGER(e) (e).solid = SOLID_BBOX\r
\r
#ifdef REGISTER_WEAPON\r
-REGISTER_WEAPON(GRABBER, w_grabber, IT_FUEL, 0, WEP_FLAG_CANCLIMB | WEP_TYPE_HITSCAN, 0, "grabber", "grabber", "Grabber");\r
+REGISTER_WEAPON(GRABBER, w_grabber, IT_FUEL, 0, WEP_FLAG_CANCLIMB | WEP_TYPE_HITSCAN | WEP_FLAG_RELOADABLE, 0, "grabber", "grabber", "Grabber");\r
#else\r
.float dmg;\r
.float dmg_edge;\r
W_Grabber_UpdateStats(self, FALSE, TRUE); // the shot is recorded above\r
}\r
\r
- if not(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
- self.ammo_fuel = self.ammo_fuel - cvar("g_balance_grabber_secondary_ammo");\r
+ W_DecreaseAmmo(ammo_fuel, cvar("g_balance_grabber_secondary_ammo"), cvar("g_balance_grabber_reload_ammo"));\r
}\r
\r
void spawnfunc_weapon_grabber (void)\r
return FALSE;\r
}\r
\r
+ float ammo_amount;\r
float grabbered_time_max, grabbered_fuel;\r
\r
if (req == WR_AIM)\r
if (time > self.grabber_refire)\r
if (weapon_prepareattack(0, -1))\r
{\r
- if not(self.items & IT_UNLIMITED_WEAPON_AMMO)\r
- self.ammo_fuel = self.ammo_fuel - cvar("g_balance_grabber_primary_ammo");\r
+ W_DecreaseAmmo(ammo_fuel, cvar("g_balance_grabber_primary_ammo"), cvar("g_balance_grabber_reload_ammo"));\r
self.grabber_state |= GRABBER_FIRING;\r
weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_grabber_primary_animtime"), w_ready); \r
}\r
{\r
if ( self.ammo_fuel >= (time - self.grabber_time_fueldecrease) * grabbered_fuel )\r
{\r
- self.ammo_fuel -= (time - self.grabber_time_fueldecrease) * grabbered_fuel;\r
+ W_DecreaseAmmo(ammo_fuel, (time - self.grabber_time_fueldecrease) * grabbered_fuel, cvar("g_balance_grabber_reload_ammo"));\r
self.grabber_time_fueldecrease = time;\r
// decrease next frame again\r
}\r
else\r
{\r
self.ammo_fuel = 0;\r
+ self.weapon_load[WEP_GRABBER] = 0;\r
self.grabber_state |= GRABBER_REMOVING;\r
W_SwitchWeapon_Force(self, w_getbestweapon(self));\r
}\r
precache_sound ("weapons/grabber_impact.wav"); // done by g_grabber.qc\r
precache_sound ("weapons/grabber_fire.wav");\r
precache_sound ("weapons/grabber_altfire.wav");\r
+ precache_sound ("weapons/reload.wav");\r
}\r
else if (req == WR_SETUP)\r
{\r
weapon_setup(WEP_GRABBER);\r
self.grabber_state &~= GRABBER_WAITING_FOR_RELEASE;\r
+ self.current_ammo = ammo_fuel;\r
}\r
else if (req == WR_CHECKAMMO1)\r
{\r
if(self.grabber)\r
- return self.ammo_fuel > 0;\r
+ {\r
+ ammo_amount = self.ammo_fuel > 0;\r
+ ammo_amount += self.weapon_load[WEP_GRABBER] > 0;\r
+ }\r
else\r
- return self.ammo_fuel >= cvar("g_balance_grabber_primary_ammo");\r
+ {\r
+ ammo_amount = self.ammo_fuel >= cvar("g_balance_grabber_primary_ammo");\r
+ ammo_amount += self.weapon_load[WEP_GRABBER] >= cvar("g_balance_grabber_primary_ammo");\r
+ }\r
+ return ammo_amount;\r
}\r
else if (req == WR_CHECKAMMO2)\r
{\r
- return self.ammo_fuel >= cvar("g_balance_grabber_secondary_ammo");\r
+ ammo_amount = self.ammo_fuel >= cvar("g_balance_grabber_secondary_ammo");\r
+ ammo_amount += self.weapon_load[WEP_GRABBER] >= cvar("g_balance_grabber_secondary_ammo");\r
+ return ammo_amount;\r
+ }\r
+ else if (req == WR_RELOAD)\r
+ {\r
+ W_Reload(min(cvar("g_balance_hagar_primary_ammo"), cvar("g_balance_hagar_secondary_ammo")), cvar("g_balance_hagar_reload_ammo"), cvar("g_balance_hagar_reload_time"), "weapons/reload.wav");\r
}\r
else if (req == WR_SUICIDEMESSAGE)\r
w_deathtypestring = "did the impossible";\r