From: MirceaKitsune Date: Mon, 28 Feb 2011 14:43:33 +0000 (+0200) Subject: Port weapon reload code server side from Xonotic (code I made for all weapons there). X-Git-Url: http://de.git.xonotic.org/?a=commitdiff_plain;h=abce2a0c16b230d0694c0c1a53e7246ac0fed97f;p=voretournament%2Fvoretournament.git Port weapon reload code server side from Xonotic (code I made for all weapons there). --- diff --git a/data/qcsrc/common/items.qh b/data/qcsrc/common/items.qh index de400143..08d50910 100644 --- a/data/qcsrc/common/items.qh +++ b/data/qcsrc/common/items.qh @@ -2,13 +2,14 @@ float BOT_PICKUP_RATING_LOW = 2500; float BOT_PICKUP_RATING_MID = 5000; float BOT_PICKUP_RATING_HIGH = 10000; -float WEP_TYPE_OTHER = 0x00; // e.g: Grabber, etc -float WEP_TYPE_SPLASH = 0x01; -float WEP_TYPE_HITSCAN = 0x02; -float WEP_TYPEMASK = 0x0F; -float WEP_FLAG_CANCLIMB = 0x10; -float WEP_FLAG_NORMAL = 0x20; -float WEP_FLAG_HIDDEN = 0x40; +float WEP_TYPE_OTHER = 0x00; // e.g: Grabber, etc +float WEP_TYPE_SPLASH = 0x01; +float WEP_TYPE_HITSCAN = 0x02; +float WEP_TYPEMASK = 0x0F; +float WEP_FLAG_CANCLIMB = 0x10; +float WEP_FLAG_NORMAL = 0x20; +float WEP_FLAG_HIDDEN = 0x40; +float WEP_FLAG_RELOADABLE = 0x80; float IT_UNLIMITED_WEAPON_AMMO = 1; // when this bit is set, using a weapon does not reduce ammo. Checkpoints can give this powerup. diff --git a/data/qcsrc/server/bot/havocbot/havocbot.qc b/data/qcsrc/server/bot/havocbot/havocbot.qc index 62aa1d27..6a21f4fa 100644 --- a/data/qcsrc/server/bot/havocbot/havocbot.qc +++ b/data/qcsrc/server/bot/havocbot/havocbot.qc @@ -151,6 +151,31 @@ void havocbot_ai() 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) @@ -886,6 +911,31 @@ void havocbot_chooseenemy() 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; @@ -951,7 +1001,7 @@ void havocbot_chooseweapon() 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; @@ -964,7 +1014,7 @@ void havocbot_chooseweapon() 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; @@ -976,7 +1026,7 @@ void havocbot_chooseweapon() 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; diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index b5f1333f..19f8d0c9 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -992,8 +992,16 @@ void PutClientInServer (void) // reset fields the weapons may use for (j = WEP_FIRST; j <= WEP_LAST; ++j) + { weapon_action(j, WR_RESETPLAYER); + // all weapons must be fully loaded when we spawn + entity e; + e = get_weaponinfo(j); + if(e.spawnflags & WEP_FLAG_RELOADABLE) // prevent accessing undefined cvars + self.weapon_load[j] = cvar(strcat("g_balance_", e.netname, "_reload_ammo")); + } + oldself = self; self = spot; activator = oldself; diff --git a/data/qcsrc/server/cl_weapons.qc b/data/qcsrc/server/cl_weapons.qc index 0cd918ca..020a55cf 100644 --- a/data/qcsrc/server/cl_weapons.qc +++ b/data/qcsrc/server/cl_weapons.qc @@ -1,3 +1,8 @@ +void W_TriggerReload() +{ + weapon_action(self.weapon, WR_RELOAD); +} + // switch between weapons void W_SwitchWeapon(float imp) { @@ -6,6 +11,10 @@ void W_SwitchWeapon(float imp) if (client_hasweapon(self, imp, TRUE, TRUE)) W_SwitchWeapon_Force(self, imp); } + else + { + W_TriggerReload(); + } }; .float weaponcomplainindex; @@ -217,6 +226,13 @@ string W_ThrowNewWeapon(entity own, float wpn, float doreduce, vector org, vecto wep.ammofield = thisammo; own.ammofield -= thisammo; s = strcat(s, " and ", ftos(thisammo), " ", Item_CounterFieldName(j)); + + // if our weapon is loaded, give its load back to the player + if(self.weapon_load[self.weapon] > 0) + { + own.ammofield += self.weapon_load[self.weapon]; + self.weapon_load[self.weapon] = -1; // schedule the weapon for reloading + } } } s = substring(s, 5, -1); @@ -323,6 +339,18 @@ void W_WeaponFrame() setanim(self, self.anim_draw, FALSE, TRUE, TRUE); self.weaponentity.state = WS_RAISE; weapon_action(self.switchweapon, WR_SETUP); + + // set our clip load to the load of the weapon we switched to, if it's reloadable + entity e; + e = get_weaponinfo(self.switchweapon); + if(e.spawnflags & WEP_FLAG_RELOADABLE && cvar(strcat("g_balance_", e.netname, "_reload_ammo"))) // prevent accessing undefined cvars + { + self.clip_load = self.weapon_load[self.switchweapon]; + self.clip_size = cvar(strcat("g_balance_", e.netname, "_reload_ammo")); + } + else + self.clip_load = self.clip_size = 0; + // VorteX: add player model weapon select frame here // setcustomframe(PlayerWeaponRaise); weapon_thinkf(WFRAME_IDLE, cvar("g_balance_weaponswitchdelay"), w_ready); diff --git a/data/qcsrc/server/cl_weaponsystem.qc b/data/qcsrc/server/cl_weaponsystem.qc index 8c927429..4a3cea8f 100644 --- a/data/qcsrc/server/cl_weaponsystem.qc +++ b/data/qcsrc/server/cl_weaponsystem.qc @@ -974,26 +974,44 @@ void weapon_setup(float windex) }; // perform weapon to attack (weaponstate and attack_finished check is here) -.float race_penalty; -float weapon_prepareattack(float secondary, float attacktime) +void W_SwitchToOtherWeapon(entity pl) { - //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; - } + // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway) + float w, ww; + w = W_WeaponBit(pl.weapon); + pl.weapons &~= w; + ww = w_getbestweapon(pl); + pl.weapons |= w; + if(ww) + W_SwitchWeapon_Force(pl, ww); +} +.float prevdryfire; +float weapon_prepareattack_checkammo(float secondary) +{ if not(self.items & IT_UNLIMITED_WEAPON_AMMO) if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary)) { - // hack to ensure it switches to an OTHER weapon (in case the other fire mode still has ammo, we want that anyway) - float w, ww; - w = W_WeaponBit(self.weapon); - self.weapons &~= w; - ww = w_getbestweapon(self); - self.weapons |= w; - if(ww) - W_SwitchWeapon_Force(self, ww); + if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons + { + sound (self, CHAN_AUTO, "weapons/dryfire.wav", VOL_BASE, ATTN_NORM); + self.prevdryfire = time; + } + + W_SwitchToOtherWeapon(self); + return FALSE; + } + return TRUE; +} +.float race_penalty; +float weapon_prepareattack_check(float secondary, float attacktime) +{ + if(!weapon_prepareattack_checkammo(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; } @@ -1013,6 +1031,11 @@ float weapon_prepareattack(float secondary, float attacktime) if (self.weaponentity.state != WS_READY) return FALSE; } + + return TRUE; +} +float weapon_prepareattack_do(float secondary, float attacktime) +{ self.weaponentity.state = WS_INUSE; self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire @@ -1029,7 +1052,17 @@ float weapon_prepareattack(float secondary, float attacktime) } //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n"); return TRUE; -}; +} +float weapon_prepareattack(float secondary, float attacktime) +{ + if(weapon_prepareattack_check(secondary, attacktime)) + { + weapon_prepareattack_do(secondary, attacktime); + return TRUE; + } + else + return FALSE; +} void weapon_thinkf(float fr, float t, void() func) { @@ -1509,3 +1542,132 @@ void W_SetupProjectileVelocity(entity missile, float pSpeed, float spread) #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")) #define W_SETUPPROJECTILEVELOCITY(m,s) W_SetupProjectileVelocityEx(m, w_shotdir, v_up, cvar(#s "_speed"), 0, 0, cvar(#s "_spread")) + +void W_DecreaseAmmo(.float ammo_type, float ammo_use, float ammo_reload) +{ + if(self.items & IT_UNLIMITED_WEAPON_AMMO) + return; + + // if this weapon is reloadable, decrease its load. Else decrease the player's ammo + if(ammo_reload) + { + self.clip_load -= ammo_use; + self.weapon_load[self.weapon] = self.clip_load; + } + else + self.(self.current_ammo) -= ammo_use; +} + +// weapon reloading code + +.float reload_ammo_amount, reload_ammo_min, reload_time; +.float reload_complain; +.string reload_sound; + +float W_ReloadCheck() +{ + // check if we meet the necessary conditions to reload + + entity e; + e = get_weaponinfo(self.weapon); + + // don't reload weapons that don't have the RELOADABLE flag + if not(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"); + return FALSE; + } + + // return if reloading is disabled for this weapon + if(!self.reload_ammo_amount) + return FALSE; + + // our weapon is fully loaded, no need to reload + if (self.clip_load >= self.reload_ammo_amount) + return FALSE; + + // no ammo, so nothing to load + if(!self.(self.current_ammo) && self.reload_ammo_min) + { + if(clienttype(self) == CLIENTTYPE_REAL && self.reload_complain < time) + { + play2(self, "weapons/unavailable.wav"); + sprint(self, strcat("You don't have enough ammo to reload the ^2", W_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 not(weapon_action(self.weapon, WR_CHECKAMMO1) + weapon_action(self.weapon, WR_CHECKAMMO2)) + { + self.clip_load = -1; // reload later + W_SwitchToOtherWeapon(self); + } + return FALSE; + } + + if (self.weaponentity) + { + if (self.weaponentity.wframe == WFRAME_RELOAD) + return FALSE; + + // allow switching away while reloading, but this will cause a new reload! + self.weaponentity.state = WS_READY; + } + + return TRUE; +} + +void W_ReloadedAndReady() +{ + // 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 + + // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load + if(!self.reload_ammo_min) + self.clip_load = self.reload_ammo_amount; + else + { + while(self.clip_load < self.reload_ammo_amount && self.(self.current_ammo)) // make sure we don't add more ammo than we have + { + self.clip_load += 1; + self.(self.current_ammo) -= 1; + } + } + self.weapon_load[self.weapon] = self.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; + + w_ready(); +} + +void W_Reload(float sent_ammo_min, float sent_ammo_amount, float sent_time, string sent_sound) +{ + // set global values to work with + self.reload_ammo_min = sent_ammo_min; + self.reload_ammo_amount = sent_ammo_amount; + self.reload_time = sent_time; + self.reload_sound = sent_sound; + + if(!W_ReloadCheck()) + return; + + // now begin the reloading process + + sound (self, CHAN_WEAPON2, self.reload_sound, VOL_BASE, ATTN_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; + + weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady); + + if(self.clip_load >= 0) + self.old_clip_load = self.clip_load; + self.clip_load = self.weapon_load[self.weapon] = -1; +} \ No newline at end of file diff --git a/data/qcsrc/server/defs.qh b/data/qcsrc/server/defs.qh index b0143575..64025012 100644 --- a/data/qcsrc/server/defs.qh +++ b/data/qcsrc/server/defs.qh @@ -244,6 +244,7 @@ float WR_PRECACHE = 6; // precaches models/sounds used by this weapon float WR_SUICIDEMESSAGE = 7; // sets w_deathtypestring or leaves it alone (and may inspect w_deathtype for details) float WR_KILLMESSAGE = 8; // sets w_deathtypestring or leaves it alone float WR_RESETPLAYER = 9; // does not need to do anything +float WR_RELOAD = 10; // used for reloading void weapon_defaultspawnfunc(float wpn); @@ -618,6 +619,13 @@ float client_cefc_accumulator; float client_cefc_accumulatortime; #endif +..float current_ammo; + +.float weapon_load[WEP_MAXCOUNT]; FTEQCC_YOU_SUCK_THIS_IS_NOT_UNREFERENCED(weapon_load); +.float clip_load; +.float old_clip_load; +.float clip_size; + #define PROJECTILE_MAKETRIGGER(e) (e).solid = SOLID_CORPSE; (e).dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE // #define PROJECTILE_MAKETRIGGER(e) (e).solid = SOLID_BBOX diff --git a/data/qcsrc/server/w_grabber.qc b/data/qcsrc/server/w_grabber.qc index b1e4e13b..09a945f5 100644 --- a/data/qcsrc/server/w_grabber.qc +++ b/data/qcsrc/server/w_grabber.qc @@ -1,5 +1,5 @@ #ifdef REGISTER_WEAPON -REGISTER_WEAPON(GRABBER, w_grabber, IT_FUEL, 0, WEP_FLAG_CANCLIMB | WEP_TYPE_HITSCAN, 0, "grabber", "grabber", "Grabber"); +REGISTER_WEAPON(GRABBER, w_grabber, IT_FUEL, 0, WEP_FLAG_CANCLIMB | WEP_TYPE_HITSCAN | WEP_FLAG_RELOADABLE, 0, "grabber", "grabber", "Grabber"); #else .float dmg; .float dmg_edge; @@ -49,8 +49,7 @@ void W_Grabber_Attack2() W_Grabber_UpdateStats(self, FALSE, TRUE); // the shot is recorded above } - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - self.ammo_fuel = self.ammo_fuel - cvar("g_balance_grabber_secondary_ammo"); + W_DecreaseAmmo(ammo_fuel, cvar("g_balance_grabber_secondary_ammo"), cvar("g_balance_grabber_reload_ammo")); } void spawnfunc_weapon_grabber (void) @@ -66,6 +65,7 @@ float w_grabber(float req) return FALSE; } + float ammo_amount; float grabbered_time_max, grabbered_fuel; if (req == WR_AIM) @@ -85,8 +85,7 @@ float w_grabber(float req) if (time > self.grabber_refire) if (weapon_prepareattack(0, -1)) { - if not(self.items & IT_UNLIMITED_WEAPON_AMMO) - self.ammo_fuel = self.ammo_fuel - cvar("g_balance_grabber_primary_ammo"); + W_DecreaseAmmo(ammo_fuel, cvar("g_balance_grabber_primary_ammo"), cvar("g_balance_grabber_reload_ammo")); self.grabber_state |= GRABBER_FIRING; weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_grabber_primary_animtime"), w_ready); } @@ -129,13 +128,14 @@ float w_grabber(float req) { if ( self.ammo_fuel >= (time - self.grabber_time_fueldecrease) * grabbered_fuel ) { - self.ammo_fuel -= (time - self.grabber_time_fueldecrease) * grabbered_fuel; + W_DecreaseAmmo(ammo_fuel, (time - self.grabber_time_fueldecrease) * grabbered_fuel, cvar("g_balance_grabber_reload_ammo")); self.grabber_time_fueldecrease = time; // decrease next frame again } else { self.ammo_fuel = 0; + self.weapon_load[WEP_GRABBER] = 0; self.grabber_state |= GRABBER_REMOVING; W_SwitchWeapon_Force(self, w_getbestweapon(self)); } @@ -183,22 +183,37 @@ float w_grabber(float req) precache_sound ("weapons/grabber_impact.wav"); // done by g_grabber.qc precache_sound ("weapons/grabber_fire.wav"); precache_sound ("weapons/grabber_altfire.wav"); + precache_sound ("weapons/reload.wav"); } else if (req == WR_SETUP) { weapon_setup(WEP_GRABBER); self.grabber_state &~= GRABBER_WAITING_FOR_RELEASE; + self.current_ammo = ammo_fuel; } else if (req == WR_CHECKAMMO1) { if(self.grabber) - return self.ammo_fuel > 0; + { + ammo_amount = self.ammo_fuel > 0; + ammo_amount += self.weapon_load[WEP_GRABBER] > 0; + } else - return self.ammo_fuel >= cvar("g_balance_grabber_primary_ammo"); + { + ammo_amount = self.ammo_fuel >= cvar("g_balance_grabber_primary_ammo"); + ammo_amount += self.weapon_load[WEP_GRABBER] >= cvar("g_balance_grabber_primary_ammo"); + } + return ammo_amount; } else if (req == WR_CHECKAMMO2) { - return self.ammo_fuel >= cvar("g_balance_grabber_secondary_ammo"); + ammo_amount = self.ammo_fuel >= cvar("g_balance_grabber_secondary_ammo"); + ammo_amount += self.weapon_load[WEP_GRABBER] >= cvar("g_balance_grabber_secondary_ammo"); + return ammo_amount; + } + else if (req == WR_RELOAD) + { + 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"); } else if (req == WR_SUICIDEMESSAGE) w_deathtypestring = "did the impossible";