ts/src/shared/weapons.qc

2403 lines
80 KiB
Plaintext

#define ASSIGN_WEAPONDATA(arg_constName, arg_weaponName) ary_weaponData[WEAPON_ID::##arg_constName] = (weapondata_basic_t*) &weapon_##arg_weaponName;
// NOTICE - the ID lacks the "_akimbo" suffix. The actual variable name has the "_akimbo" suffix.
// Just provide the name of the weapon without the "_akimbo" suffix and it will be added as needed
// automatically.
#define ASSIGN_AKIMBOUPGRADEDATA(arg_constName, arg_weaponName) ary_akimboUpgradeData[WEAPON_AKIMBO_UPGRADE_ID::##arg_constName] = (weapondata_basic_t*) &weapon_##arg_weaponName##_akimbo;
weapon_t w_null = {};
// Populate each slot with a member of the enum early on in runtime instead.
weapon_t g_weapons[WEAPON_ID::LAST_ID];
void
weaponconfig_data_init(weaponconfig_data_t* arg_this)
{
arg_this->ary_myWeapons_softMax = 0;
for(int i = 0; i < AMMO_ID::LAST_ID; i++){
arg_this->ary_ammoTotal[i] = 0;
}
arg_this->iTotalSlots = 0;
arg_this->iTotalPrice = 0;
// Not a class, no need for this anymore.
// Any need to run init-calls on each one to set things to 0?
// That was never needed before at least.
/*
for(int i = 0; i < ary_myWeapons_length; i++){
ary_myWeapons[i] = spawn(weaponconfig_weapon_t);
}
*/
}// weaponconfig_data_t constructor
void
weapon_base_setWholeAttackDelay(player pl, float amount)
{
pl.w_attack_next = amount;
pl.w_attack_akimbo_next = amount;
//#ifdef CLIENT
//SAVE_STATE(pl.w_attack_next);
//SAVE_STATE(pl.w_attack_akimbo_next);
//#endif
}// weapon_base_setWholeAttackDelay
// normal delay only?
void
weapon_base_setLeftAttackDelay(player pl, float amount)
{
pl.w_attack_next = amount;
//SAVE_STATE(pl.w_attack_next);
}
// akimbo secondary delay only?
void
weapon_base_setRightAttackDelay(player pl, float amount)
{
pl.w_attack_akimbo_next = amount;
//SAVE_STATE(pl.w_attack_akimbo_next);
}
// Only set the delay if it is below the given amount.
// If there are 2 seconds left until firing is allowed, ignore a request
// to set it to 0.3 seconds.
void
weapon_base_setLeftAttackDelay_AtLeast(player pl, float amount)
{
if(pl.w_attack_next <= amount){
pl.w_attack_next = amount;
//SAVE_STATE(pl.w_attack_next);
}
}
// akimbo secondary delay only?
void
weapon_base_setRightAttackDelay_AtLeast(player pl, float amount)
{
if(pl.w_attack_akimbo_next <= amount){
pl.w_attack_akimbo_next = amount;
//SAVE_STATE(pl.w_attack_akimbo_next);
}
}
#ifdef SERVER
// Server-only helper method for melee hit effects. Handles calling FX_Impact_melee
// and gives the hit response for custom weapons script to work with.
// Assumes a recent "traceline" call has filled trace_ent and trace_endpos.
MELEE_HIT_RESPONSE
weapon_base_meleeHitEffect(void)
{
// Not very useful
//FX_Impact( IMPACT_MELEE, trace_endpos, trace_plane_normal );
MELEE_HIT_RESPONSE resultHit = MELEE_HIT_RESPONSE::NONE;
float surf;
string texName;
string texFilter;
surf = getsurfacenearpoint(trace_ent, trace_endpos);
texName = getsurfacetexture(trace_ent, surf);
texFilter = Materials_FixName(texName);
//--- HELPFUL PRINTOUT ---
//printfline("I hit: %s ::: %s", floatToChar((float)hash_get(hashMaterials, texFilter)), texFilter );
//printfline("weapon_base_meleeHitEffect - I hit: %s", chr2str((float)hash_get(hashMaterials, texFilter)) );
// FLESH, BLOODYFLESH, SLOSH, FOLIAGE, ALIEN, and CONCRETE are all guesses.
// Several others might be anyway, just extra unsure of these, if any TS map
// even uses them.
switch ((float)hash_get(hashMaterials, texFilter)) {
case MATID_FLESH:
case MATID_BLOODYFLESH:
case MATID_SLOSH:
case MATID_FOLIAGE:
FX_Impact_Melee(IMPACT_METAL, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::SOFT;
break;
case MATID_ALIEN:
case MATID_CONCRETE:
case MATID_GRATE:
case MATID_VENT:
FX_Impact_Melee(IMPACT_METAL, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::METAL;
break;
case MATID_METAL:
FX_Impact_Melee(IMPACT_METAL, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::METAL;
break;
case MATID_COMPUTER:
FX_Impact_Melee(IMPACT_COMPUTER, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::METAL;
break;
case MATID_DIRT:
FX_Impact_Melee(IMPACT_DIRT, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::SOFT;
break;
case MATID_WOOD:
FX_Impact_Melee(IMPACT_WOOD, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::SOFT;
break;
case MATID_GLASS:
FX_Impact_Melee(IMPACT_GLASS, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::METAL;
break;
case MATID_SNOW:
//'N' means snow apparently.
FX_Impact_Melee(IMPACT_DEFAULT, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::SOFT;
break;
case MATID_TILE:
FX_Impact_Melee(IMPACT_DEFAULT, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::METAL;
break;
default:
FX_Impact_Melee(IMPACT_DEFAULT, trace_endpos, trace_plane_normal);
resultHit = MELEE_HIT_RESPONSE::METAL;
break;
}
return resultHit;
}
#endif
// For the melee attack some guns have.
// Why no hit sound? It's that way in original TS, probably fine to just pipe calls to
// weapon_base_onPrimaryAttack_melee instead if those are wanted, nothing special in here
MELEE_HIT_RESPONSE
weapon_base_coldcock(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon,
float damageToDeal, float range
){
MELEE_HIT_RESPONSE resultHit = MELEE_HIT_RESPONSE::NONE;
#ifdef SERVER
trace_ent = NULL;
weapondata_basic_t baseRef = *basePRef;
makevectors( GET_VIEW_ANGLES );
vector vSource = (pl.origin + pl.view_ofs);
traceline(vSource, vSource + (v_forward * range), MOVE_HITMODEL, pl);
if ( trace_fraction == 1.0 ) {
//did not hit anything
return MELEE_HIT_RESPONSE::NONE;
}else{
// ? For what purpose now?
//vOrigin = trace_endpos - vTraceDirection * 2;
if ( trace_ent.takedamage ) {
if ( trace_ent.iBleeds == TRUE ) {
//TAGGG - TODO, CHECK. Do FX_Impact parameters still line up with this.
FX_Impact( IMPACT_FLESH, trace_endpos, trace_plane_normal );
resultHit = MELEE_HIT_RESPONSE::FLESH;
}else{
// assume metal then?
resultHit = MELEE_HIT_RESPONSE::METAL;
}
Damage_Apply(trace_ent, pl, damageToDeal, (int)pl.activeweapon, DMG_BLUNT);
} else {
resultHit = MELEE_HIT_RESPONSE::NONE;
}
}
#endif
return resultHit;
}//weapon_base_coldcock
MELEE_HIT_RESPONSE
weapon_base_onPrimaryAttack_melee(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon,
float damageToDeal, float range
){
makevectors( GET_VIEW_ANGLES );
return weapon_base_onPrimaryAttack_melee_fromCustomDirection(pl, basePRef, arg_thisWeapon, damageToDeal, range, v_forward);
}// weapon_base_onPrimaryAttack_melee
// After the server calls this method (possibly client later?),
// we expect trace_ent to store the thing we recently hit for playing custom sounds.
// Anything that doesn't want the general material-hit sounds / sparks on hitting metalic/generic stuff
// should not use this method, just copy it and work as needed (looking at you karate).
MELEE_HIT_RESPONSE
weapon_base_onPrimaryAttack_melee_fromCustomDirection(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon,
float damageToDeal, float range, vector vTraceDirection
){
MELEE_HIT_RESPONSE resultHit = MELEE_HIT_RESPONSE::NONE;
#ifdef SERVER
weapondata_basic_t baseRef = *basePRef;
vector vSource;
// safe default in case this method finds nothing.
// Might be unnecessary to do this?
trace_ent = NULL;
vSource = ( pl.origin + pl.view_ofs );
traceline( vSource, vSource + ( vTraceDirection * range ), MOVE_HITMODEL, pl );
if ( trace_fraction == 1.0 ) {
// did not hit anything
return MELEE_HIT_RESPONSE::NONE;
}
// For what purpose now? Where to put a hit-effect cloud sometime maybe like
// original TS does?
//vOrigin = trace_endpos - vTraceDirection * 2;
if ( trace_ent.takedamage ){
if ( trace_ent.iBleeds == TRUE ) {
// The thing I hit takes damage and is organic?
// Do something different from weapon_base_meleeHitEffect.
// Also calling the damage type 'SLASH' for now, all dedicated melee weapons are knife or sword-like
FX_Impact( IMPACT_FLESH, trace_endpos, trace_plane_normal );
resultHit = MELEE_HIT_RESPONSE::FLESH;
Damage_Apply(trace_ent, pl, damageToDeal, (int)pl.activeweapon, DMG_SLASH);
// other Damage_Apply examples:
// snark hit
//Damage_Apply(this, world, 1, 0, DMG_GENERIC);
// crossbow bolt touch
//Damage_Apply(other, self.owner, Skill_GetValue("plr_xbow_bolt_monster", 50), WEAPON_CROSSBOW, DMG_BLUNT);
}else{
// don't assume metal
// resultHit = MELEE_HIT_RESPONSE::METAL;
resultHit = weapon_base_meleeHitEffect();
Damage_Apply(trace_ent, pl, damageToDeal, (int)pl.activeweapon, DMG_SLASH);
}
}else{
// Does not take damage? That is all
resultHit = weapon_base_meleeHitEffect();
}
#endif
return resultHit;
}// weapon_base_onPrimaryAttack_melee
void
weapon_base_onAttack(player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon, int attackTypeUsed)
{
weapon_base_onAttack_multi(pl, basePRef, arg_thisWeapon, 1, attackTypeUsed);
}// weapon_base_onAttack
// For firing once the left or right has been picked for firing, does not matter which as this has
// no effect on the weapon stats. Also applies kickback for firing once.
void
weapon_base_onAttack_individual(player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon, int shellCount){
weapondata_gun_t baseRef = *((weapondata_gun_t*)basePRef);
float randoAngFactor;
float finalAng;
float finalAmount;
float angleRange = 115;
float baseAcc = baseRef.firestats.fAccuracy;
float kickbackToApply = pl.fAccuracyKickback;
if(baseAcc < 0.001){
// minimum enforced, per notes in weapons_official.txt
baseAcc = 0.001;
}
int legalBuyOpts = (arg_thisWeapon.iBitsUpgrade & baseRef.iBitsUpgrade);
// The lasersight can be turned off, so check for "_on" instead in that case.
int legalBuyOpts_on = (arg_thisWeapon.iBitsUpgrade_on & legalBuyOpts);
if(legalBuyOpts_on & BITS_WEAPONOPT_LASERSIGHT){
baseAcc *= 0.72;
kickbackToApply *= 0.87;
}
if(legalBuyOpts & BITS_WEAPONOPT_SILENCER){
baseAcc *= 0.92;
kickbackToApply *= 0.94;
}
float acc = baseAcc + kickbackToApply;
vector toGo;
float miner = 0.65 * 1;
float maxer = 1.00 * 1;
//float miner = 0.75/128;
//float maxer = 1.00/128;
#ifdef SERVER
//TAGGG - NOTE!
// Nuclide does not support custom range as of now, nor how to tell how far penetration can go.
// Old FreeTS did penetration distance by reducing range by 3 times as far as any solid object
// in the way, so range doubles as penetration amount.
// Now, only amount of times penetration can happen is specified.
// Use the old TS weapon stat's range to determine that simply for now.
// Higher = more penetrations allowed, lower = fewer allowed.
// Not that the stats are really customized per weapon at the moment anyway, most if not all
// are still 4096.
int myPenetrationCount;
if(baseRef.fRange >= 8192){
myPenetrationCount = 3;
}else if(baseRef.fRange >= 4096){
myPenetrationCount = 2;
}else if(baseRef.fRange >= 2048){
myPenetrationCount = 1;
}else{
myPenetrationCount = 0;
}
TraceAttack_SetPenetrationPower(myPenetrationCount);
#endif
////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef SERVER
// Now, adjust the v_angle to account for the knockback I expet
// the user to have at this point, might not have completely
// accurate view angles from the client t the moment.
// BEWARE: using only a vViewAngleOffsetTotalChangeAlt given
// by the client is asking to get this modified, unsure of
// alternatives yet, see where this is received from the client
// in ts/src/shared/player.qc.
vector oldAng = pl.v_angle;
pl.v_angle += pl.vViewAngleOffsetTotalChangeAlt;
TraceAttack_FireBullets(shellCount, (pl.origin + pl.view_ofs), baseRef.fAttackDamage, [acc, acc], (int)pl.activeweapon);
pl.v_angle = oldAng;
#endif
//TAGGG - shared random test
//randoAngFactor = randomInRange_f(0, 1);
//finalAmount = baseRef.firestats.fViewKickback * randomInRange_f(miner, maxer);
randoAngFactor = 0.5;
finalAmount = baseRef.firestats.fViewKickback * 0.7;
finalAng = ( 90 - (angleRange/2) + randoAngFactor*( angleRange ) ) * (M_PI/180);
pl.addViewAngleOffset(finalAng, finalAmount);
// TODO - be better shared later?
// Syncing view kickback would need the random-ness to be handled between server/client,
// maybe seeded randoms? I forget.
// Properly predicted shotgun firing would be a good demonstration of that too,
// so what happens instantly (client sees) and what actually happened (server sees; everyone
// else sees) line up.
pl.fAccuracyKickbackStartCooldown = 0.14;
pl.fAccuracyKickback += baseRef.firestats.fAccuracyKickback;
if(pl.fAccuracyKickback >= 0.1){
//cap it.
pl.fAccuracyKickback = 0.1;
}
}// weapon_base_onAttack_individual
// Most weapons firing should call this to start, turns LEFT and RIGHT akimbo choices into their
// own "individual" calls, so twice if both are fired in the same frame. Same for view/accuracy
// kickback applied
void
weapon_base_onAttack_multi(player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon, int shellCount, int attackTypeUsed)
{
//TAGGG - TODO! Re-picking the angle to kickback per hit may make more sense than varrying the intensity
// of the exact same direction for both shots, just a thought
if(attackTypeUsed & BITS_AKIMBOCHOICE_LEFT){
//left
weapon_base_onAttack_individual(pl, basePRef, arg_thisWeapon, shellCount);
arg_thisWeapon.iClipLeft -= 1;
}
if(attackTypeUsed & BITS_AKIMBOCHOICE_RIGHT){
weapon_base_onAttack_individual(pl, basePRef, arg_thisWeapon, shellCount);
arg_thisWeapon.iClipAkimboLeft -= 1;
}
// TODO - third person. This is mad old isn't it
//Animation_ShootWeapon( pl );
}// weapon_base_onAttack
// shotCount is how many consecutive shots to fire (no more than 3 expected),
// shotDelay is the time between them, and
// attackDelay is the time that must pass before firing is allowed again
// (as in left-click), not including the delays between shots after the 1st.
void
weapon_gun_burstFire(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon,
int attackTypeUsed, int shotCount, float shotDelay, float attackDelay
){
float totalAttackDelay = shotDelay * (shotCount-1) + attackDelay;
weapon_base_setWholeAttackDelay(pl, totalAttackDelay);
pl.aryNextBurstShotTime_softLength = shotCount;
for(int i = 0; i < shotCount; i++){
pl.aryNextBurstShotTime[i] = totalAttackDelay - shotDelay*(i);
//printfline("WELL #%i: %.2f", i, pl.aryNextBurstShotTime[i]);
}
//printfline("arg_thisWeapon.iFireMode %i", (arg_thisWeapon.iFireMode));
pl.aryNextBurstShotTime_listenIndex = 0;
// and fire this very first frame.
weapon_gun_fireBurstBullet(pl, basePRef, arg_thisWeapon);
}// weapon_gun_burstFire
// This method is called instead of firing when left-clicking while reloading.
// How do we handle this at different phases of the reload?
// This also returns "FALSE" if it didn't even have a reload to interrupt, conveniece feature.
BOOL
weapon_shotgun_onInterrupt(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon
){
if(pl.shotgunReloadIndex == 0){
// not reloading, nothing to interrupt.
return FALSE;
}
if(pl.shotgunReloadIndex == 1 || pl.shotgunReloadIndex == 2){
// pre-reload or shell-load sequences? Going to the end instead next time.
pl.shotgunReloadIndex = 3;
}
return TRUE;
}// weapon_shotgun_onInterrupt
// Method assumes the player's iShotgunExtraDataID has been set first, most likely
// in the weapon's draw (equip) method.
void
weapon_shotgun_reload(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon
){
weapondata_gun_t baseRef = *((weapondata_gun_t*)basePRef);
//printfline("weapon_shotgun_reload %d %i", pl.isReloading, pl.shotgunReloadIndex);
// Mostly to forbid reloading while doing a pump-mode pump followed by firing, bit jarring looking
// to interrupt that.
if (pl.w_attack_next > 0.0) {
return;
}
// do we even need this.
if ( pl.isReloading || pl.isChangingIronsight){
//blocked
return;
}
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] <= 0){
//also no. Having no ammo in the pool to take from means no reloading.
return;
}
if(!pl.weaponEquippedAkimbo){
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax){
return; //don't reload!
}
}else{
//akimbo, allows reloading for either being less than full
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax && arg_thisWeapon.iClipAkimboLeft >= baseRef.iClipMax){
return; //both full? don't reload!
}
}
//not reloading at all or doing fire-pumps? We can start.
////if(pl.shotgunReloadIndex == 0 && (pl.shotgunPumpEndTime == -1 || time >= pl.shotgunPumpEndTime) ){
////if(pl.shotgunReloadIndex == 0 && (pl.shotgunPumpEndTime == -1 || pl.w_attack_next <= 0) ){
if(pl.shotgunReloadIndex == 0 ){
}else{
// no.
return;
}
pl.isChangingIronsight = FALSE;
pl.resetZoomSoft();
pl.aryNextBurstShotTime_softLength = 0;
pl.aryNextBurstShotTime_listenIndex = -1;
pl.isReloading = TRUE;
weapondata_shotgun_extra_t* shotgunExtraRef = ary_shotgunExtra[pl.iShotgunExtraDataID];
TS_Weapons_ViewAnimation((*shotgunExtraRef).shotgunReload1_seq, (*shotgunExtraRef).shotgunReload1_Duration );
weapon_base_setWholeAttackDelay(pl, (*shotgunExtraRef).shotgunReload1_Duration);
pl.shotgunReloadIndex = 1;
// In original TS, something about ever starting a reload stops the need
// for a pump. Or some part of reloading, good enough I think
arg_thisWeapon.bNeedsPump = FALSE;
}//weapon_shotgun_reload
// NOTICE - shotguns with typical shotgun reload logic should use this method at all
// times. It includes checks for whether the shotgun is actually reloading or not
// (don't do anything if not of course)
// Also, this is meant to completely replace the weapon_gun_onThink call that most would have used.
void
weapon_shotgun_onThink_reloadLogic(player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon)
{
weapondata_gun_t baseRef = *basePRef;
weapondata_shotgun_extra_t* shotgunExtraRef;
if(pl.shotgunAddAmmoTime != -1 && pl.w_attack_next <= pl.shotgunAddAmmoTime){
// add the ammo!!
arg_thisWeapon.iClipLeft++;
pl.ary_ammoTotal[baseRef.iAmmoDataID] -= 1;
printfline("I ADDED AMMO TO SHOTGUN. %i - %i", arg_thisWeapon.iClipLeft, pl.ary_ammoTotal[baseRef.iAmmoDataID]);
pl.shotgunAddAmmoTime = -1; //don't keep doing it.
}
if(pl.shotgunAddAmmoSoundTime != -1 && pl.w_attack_next <= pl.shotgunAddAmmoSoundTime){
//TS_Weapons_PlaySoundChannelDirect(pl, "weapons/insert-shell.wav", CHAN_AUTO);
// TODO: if you want other players to hear this, the server should play it for all players
// except the localone here because it already played clientside, again with a delay would be
// pointless for the one that already heard it.
/*
#ifdef CLIENT
sound(pl, CHAN_AUTO, "weapons/insert-shell.wav", 1, ATTN_NONE);
#endif
*/
pl.shotgunAddAmmoSoundTime = -1;
}
if(pl.w_attack_next > 0.0){
return;
}
// in case this weapon is ironsight.
if(pl.isChangingIronsight){
weapon_gun_endOfIronSight(pl, basePRef, arg_thisWeapon);
return;
}
if(pl.shotgunReloadIndex == 0){
// nothing to do here
}else if(pl.shotgunReloadIndex == 1 || pl.shotgunReloadIndex == 2){
// end of pre-reload anim (bringing the shotgun into place) or a shell-load anim.
// Same thing wanted in either case: start another shell-load anim
printfline("w_attack_next pass!!!");
if (pl.ary_ammoTotal[baseRef.iAmmoDataID] <= 0 || arg_thisWeapon.iClipLeft >= baseRef.iClipMax) {
//pl.shotgunReloadIndex = 3;
shotgunExtraRef = ary_shotgunExtra[pl.iShotgunExtraDataID];
TS_Weapons_ViewAnimation((*shotgunExtraRef).shotgunReload3_seq, (*shotgunExtraRef).shotgunReload3_Duration);
weapon_base_setWholeAttackDelay(pl, (*shotgunExtraRef).shotgunReload3_Duration);
pl.shotgunReloadIndex = 0;
pl.isReloading = FALSE;
}else{
shotgunExtraRef = ary_shotgunExtra[pl.iShotgunExtraDataID];
TS_Weapons_ViewAnimation((*shotgunExtraRef).shotgunReload2_seq, (*shotgunExtraRef).shotgunReload2_Duration);
weapon_base_setWholeAttackDelay(pl, (*shotgunExtraRef).shotgunReload2_Duration);
pl.shotgunReloadIndex = 2;
printfline("I SET shotgunAddAmmoTime!");
//pl.shotgunAddAmmoSoundTime = (*shotgunExtraRef).shotgunReload2_Duration - ((*shotgunExtraRef).shotgunReload2_ammoLoadDelay - 0.03f);
#ifdef CLIENT
View_AddEvent(viewEv_playShotgunInsertShellSound, (*shotgunExtraRef).shotgunReload2_Duration - ((*shotgunExtraRef).shotgunReload2_ammoLoadDelay - 0.03f));
#endif
pl.shotgunAddAmmoTime = (*shotgunExtraRef).shotgunReload2_Duration - (*shotgunExtraRef).shotgunReload2_ammoLoadDelay;
//arg_thisWeapon.iClipLeft++;
//pl.ary_ammoTotal[baseRef.iAmmoDataID]--;
}
}else if(pl.shotgunReloadIndex == 3){
// end of reload anim wanted!
shotgunExtraRef = ary_shotgunExtra[pl.iShotgunExtraDataID];
TS_Weapons_ViewAnimation((*shotgunExtraRef).shotgunReload3_seq, (*shotgunExtraRef).shotgunReload3_Duration);
weapon_base_setWholeAttackDelay(pl, (*shotgunExtraRef).shotgunReload3_Duration);
pl.shotgunReloadIndex = 0;
// this will be true when the fire delay expires, but doesn't hurt happening earlier.
pl.isReloading = FALSE;
}
}//weapon_shotgun_onThink_reloadLogic
// Handles the primaryAttack method for akimbo weapons, mostly checking whether to
// use the semiAttackDualHack or skip straight to funAttack.
// arg_funAttack is what handles firing for the akimbo weapon, something like
// "weapon_socommk23_akimbo_attack".
// No need for a separate version for ironsight for primary, nothing different
// happens there.
void
weapon_gun_akimbo_semi_primaryAttack(
MethodType_WeaponAttack arg_funAttack
){
player pl = (player)self;
weapondynamic_t arg_thisWeapon = pl.ary_myWeapons[pl.inventoryEquippedIndex];
// reset
pl.akimboTest = 0;
#ifndef AKIMBO_SEMI_DUAL_TOLERANCE
if(input_buttons & INPUT_BUTTON3){
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_BOTH);
}else{
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_LEFT);
}
#else
// in addition to the primary input, which lead to '_primary' being called at all.
if(input_buttons & INPUT_BUTTON3){
if(!weapon_akimbo_semiAttackDualHack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_BOTH, arg_funAttack)){
// nothing unusual here.
pl.akimboTest = 0;
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_BOTH);
}else{
}
}else{
if(!weapon_akimbo_semiAttackDualHack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT, arg_funAttack)){
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_LEFT);
pl.akimboFirePrev = BITS_AKIMBOCHOICE_LEFT;
}
}
#endif
}
// Same as above but for akimbo semi secondary, has a separate ironsight version that
// needs to be used by ironsights.
// That version also needs the weapon type ID given for the ironsight-toggle call
void
weapon_gun_akimbo_semi_secondaryAttack(
MethodType_WeaponAttack arg_funAttack
){
player pl = (player)self;
weapondynamic_t arg_thisWeapon = pl.ary_myWeapons[pl.inventoryEquippedIndex];
// reset
pl.akimboTest = 0;
#ifndef AKIMBO_SEMI_DUAL_TOLERANCE
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
// no ironsights, nothing to do here.
}else{
// fires the right weapon always
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT);
}
#else
// NOTE - holding primary is impossible, would've called _primary above if so.
// Only secondary could possibly be held (and, is).
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
// no ironsights, nothing to do here.
}else{
// fires the right weapon always
//weapon_socommk23_akimbo_attack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT);
//return;
if(!weapon_akimbo_semiAttackDualHack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_LEFT, arg_funAttack)){
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT);
pl.akimboFirePrev = BITS_AKIMBOCHOICE_RIGHT;
}
}
#endif
}
void
weapon_ironsight_akimbo_semi_secondaryAttack(
MethodType_WeaponAttack arg_funAttack,
int arg_weaponTypeID
){
player pl = (player)self;
weapondynamic_t arg_thisWeapon = pl.ary_myWeapons[pl.inventoryEquippedIndex];
// reset
pl.akimboTest = 0;
#ifndef AKIMBO_SEMI_DUAL_TOLERANCE
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
// since secondary fire does nothing in semi-auto, we let it do ironsight stuff here
weapon_ironsight_ToggleIronsight(pl, (weapondata_ironsight_t*)ary_weaponData[arg_weaponTypeID], arg_thisWeapon);
}else{
// fires the right weapon always
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT);
}
#else
// NOTE - holding primary is impossible, would've called _primary above if so.
// Only secondary could possibly be held (and, is).
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
// since secondary fire does nothing in semi-auto, we let it do ironsight stuff here
weapon_ironsight_ToggleIronsight(pl, (weapondata_ironsight_t*)ary_weaponData[arg_weaponTypeID], arg_thisWeapon);
}else{
// fires the right weapon always
if(!weapon_akimbo_semiAttackDualHack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_LEFT, arg_funAttack)){
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT);
pl.akimboFirePrev = BITS_AKIMBOCHOICE_RIGHT;
}
}
#endif
}
// And now for full semi weapons (holding down the mouse fires continuously)
// They don't use the DUAL_TOLERANCE system regardless of the constant.
// Also, no ironsight version, that combo never happens.
void
weapon_gun_akimbo_full_primaryAttack(
MethodType_WeaponAttack arg_funAttack
){
player pl = (player)self;
weapondynamic_t arg_thisWeapon = pl.ary_myWeapons[pl.inventoryEquippedIndex];
pl.akimboTest = 0;
if(input_buttons & INPUT_BUTTON3){
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_BOTH);
}else{
// Only intent given inputs, firing both will still happen in
// the "Full-auto" firemode
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_LEFT);
}
}
void
weapon_gun_akimbo_full_secondaryAttack(
MethodType_WeaponAttack arg_funAttack
){
player pl = (player)self;
weapondynamic_t arg_thisWeapon = pl.ary_myWeapons[pl.inventoryEquippedIndex];
pl.akimboTest = 0;
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_FREE_FULL){
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_RIGHT);
//pl.akimboFirePrev = BITS_AKIMBOCHOICE_RIGHT;
}
}
BOOL
weapon_akimbo_AttackDualHack(
player pl, weapondynamic_t arg_thisWeapon, int arg_flagger,
MethodType_WeaponAttack arg_funAttack
){
//printfline("WELL WHAT. %.2f, %i, %i", pl.akimboDualFireToleranceTime, pl.akimboFirePrev, arg_flagger);
if(arg_flagger == 0){
// ???
return FALSE;
}
//printfline("here I go akprev:%i aktest:%i - flagg:%i", pl.akimboFirePrev, pl.akimboTest, arg_flagger);
if(
pl.akimboDualFireToleranceTime > 0 &&
pl.akimboFirePrev != BITS_AKIMBOCHOICE_BOTH &&
(pl.akimboFirePrev & arg_flagger) //&&
//(arg_thisWeapon.iClipLeft > 0 && arg_thisWeapon.iClipAkimboLeft > 0)
){
/*
// Check the opposites
if(pl.akimboFirePrev & BITS_AKIMBOCHOICE_LEFT){
if(arg_thisWeapon.iClipAkimboLeft <= 0){
// can't be used for this
printfline("YOU FAIL A");
return FALSE;
}
}
if(pl.akimboFirePrev & BITS_AKIMBOCHOICE_RIGHT){
if(arg_thisWeapon.iClipLeft <= 0){
// can't be used for this
printfline("YOU FAIL B");
return FALSE;
}
}
*/
// Fired the other way very rececntly? LETS GO
pl.akimboTest = pl.akimboFirePrev; //pl.arg_flagger;
weapon_base_setWholeAttackDelay(pl, 0);
// unpacking the method to call at runtime out of paranoia knowing FTE.
// Could make a table to know what to use per akimbo weapon ID, having a table
// with one space for every single weapon in the game seems pretty wasteful.
// Calls like weapon_socommk23_akimbo_attack, etc.
//BOOL(player pl, weapondynamic_t arg_thisWeapon, int attackTypeUsed)* tempRef = arg_akimboFireCallback;
//(*tempRef)(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_BOTH);
arg_funAttack(pl, arg_thisWeapon, BITS_AKIMBOCHOICE_BOTH);
//printfline("I WENT, akprev:%i aktest:%i - flagg:%i", pl.akimboFirePrev, pl.akimboTest, arg_flagger);
// fu king what
//weapon_base_setWholeAttackDelay(pl, 0.5);
pl.akimboDualFireToleranceTime = 0;
return TRUE;
}else{
// nothing special.
return FALSE;
}
}
// If the user clicks both mouse buttons (or whatever way of calling primary/secondary)
// in very rapid succession, go ahead and quickly correct that to the dual firing
// animation, as that's otherwise hard to make work. This gives a little tolerance
// for that, the user clearly meant to do that.
// Returns whether the conditions were met to do the dual correction. If not, the
// default behavior should be done by the caller instead.
BOOL
weapon_akimbo_semiAttackDualHack(
player pl, weapondynamic_t arg_thisWeapon, int arg_flagger,
MethodType_WeaponAttack arg_funAttack
){
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
return FALSE;
}
return weapon_akimbo_AttackDualHack(pl, arg_thisWeapon, arg_flagger, arg_funAttack);
}
// WARNING! Don't do this, full-firing akimbos really don't need this,
// original TS doesn't even allow separate fire-delays between the full-
// fire akimbo weapons.
BOOL
weapon_akimbo_fullAttackDualHack(
player pl, weapondynamic_t arg_thisWeapon, int arg_flagger,
MethodType_WeaponAttack arg_funAttack
){
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_FULL_AUTO){
return FALSE;
}
return weapon_akimbo_AttackDualHack(pl, arg_thisWeapon, arg_flagger, arg_funAttack);
}
// Used for akimbo weapons with semi fire modes (all akimbo pistols I think).
// Some akimbo SMG supports hold-fire in akimbo though, that will be handled a little differently.
// Same method with a choice or not, unsure what's best yet.
// TAGGG - TODO, of course.
// -----------------------------
// In any case, this method returns which akimbo weapon was picked to be fired (BITS_AKIMBOCHOICE_... LEFT or RIGHT typically).
// It may return _NONE, which still means to play a click noise (no ammo).
// But if the reason it failed is because no weapon with a fire delay that allows firing (fired too soon) could be found,
// then we don't want to even do that. This method returns "-1", not linked to any constant,
// to mean "stop what you're doing, return FALSE, don't send an even to the client at all", to whatever called this.
// Also note that this may edit pl.nextAkimboAttackPreference, for which gun to check next time an ambiguous button is pressed
// (that is primary fire for semi-auto mode; free-auto lets primary/secondary fire pick the left/right weapon consistently)
int
weapon_akimbo_semiAttackChoice(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon,
int attackTypeUsed
){
int originalInput = attackTypeUsed;
int finalAkimboChoice = BITS_AKIMBOCHOICE_NONE;
weapondata_basic_t baseRef = *basePRef;
//printfline("PRE - weapon_akimbo_semiAttackChoice x - next:%d", pl.nextAkimboAttackPreference);
/*
if(pl.akimboTest != 0){
return BITS_AKIMBOCHOICE_BOTH;
}
*/
//if(pl.akimboTest == 0)
if(attackTypeUsed & BITS_AKIMBOCHOICE_LEFT){
// only allow it if this is a fresh button press
// We could also check firemode here, but both require a tap.
if(INPUT_PRIMARY_TAP_CHECK_NOT(pl)){
attackTypeUsed &= ~BITS_AKIMBOCHOICE_LEFT;
}
}
if(attackTypeUsed & BITS_AKIMBOCHOICE_RIGHT){
if (INPUT_SECONDARY_TAP_CHECK_NOT(pl)){
attackTypeUsed &= ~BITS_AKIMBOCHOICE_RIGHT;
}
}
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
//alternate. So use the preference but try whichever one has ammo too.
if( !(attackTypeUsed & BITS_AKIMBOCHOICE_LEFT) ||
(pl.w_attack_next > 0 && pl.w_attack_akimbo_next > 0) )
{
// In this fire mode, if the primary fire wasn't pressed we don't register at all.
// Also still don't click because we're in a changing animation (ironsight or reloading)
// or only either weapon is out of ammo.
//return FALSE;
return -1;
}
if(pl.nextAkimboAttackPreference == BITS_AKIMBOCHOICE_RIGHT){
//try the right first
if(arg_thisWeapon.iClipAkimboLeft > 0 && pl.w_attack_akimbo_next <= 0){
finalAkimboChoice = BITS_AKIMBOCHOICE_RIGHT;
//printfline("1a - BITS_AKIMBOCHOICE_RIGHT");
}else if(arg_thisWeapon.iClipLeft > 0 && pl.w_attack_next <= 0){
finalAkimboChoice = BITS_AKIMBOCHOICE_LEFT;
//printfline("1a - BITS_AKIMBOCHOICE_LEFT");
}else{
//nope
}
}else{
//try the left first
if(arg_thisWeapon.iClipLeft > 0 && pl.w_attack_next <= 0){
finalAkimboChoice = BITS_AKIMBOCHOICE_LEFT;
//printfline("1b - BITS_AKIMBOCHOICE_LEFT");
}else if(arg_thisWeapon.iClipAkimboLeft > 0 && pl.w_attack_akimbo_next <= 0){
finalAkimboChoice = BITS_AKIMBOCHOICE_RIGHT;
//printfline("1b - BITS_AKIMBOCHOICE_RIGHT");
}else{
//nope
}
}// nextAkimboattackPreference check
if(finalAkimboChoice == BITS_AKIMBOCHOICE_NONE){
// We should stop, but need to tell whether to play a click noise or not.
// If either clip is not empty, we assume the reason we did not fire is
// because whichever non-empty weapon wasn't ready to fire.
// If they are both 0, we stopped because the weapon is empty.
//pl.recentAttackHadAmmo = (finalAkimboChoice != BITS_AKIMBOCHOICE_NONE);
//return( arg_thisWeapon.iClipLeft == 0 && arg_thisWeapon.iClipAkimboLeft == 0);
if(arg_thisWeapon.iClipLeft == 0 && arg_thisWeapon.iClipAkimboLeft == 0){
// If both clips have no ammo, we assume the reason for failure is being out of ammo.
// Since we would've picked a gun to use that had ammo otherwise.
// No need to return here, we fall through with this finalAkimboChoice, same effect.
//return BITS_AKIMBOCHOICE_NONE;
// Just try the one opposite of this preference next time. This lets clicking
// alternate between the weapons.
if(pl.nextAkimboAttackPreference == BITS_AKIMBOCHOICE_LEFT){
//yes, == left. As in not both, only firing left.
pl.nextAkimboAttackPreference = BITS_AKIMBOCHOICE_RIGHT;
//printfline("1c - BITS_AKIMBOCHOICE_RIGHT");
}else{
pl.nextAkimboAttackPreference = BITS_AKIMBOCHOICE_LEFT;
//printfline("1c - BITS_AKIMBOCHOICE_LEFT");
}
}else{
//apparently some fire delay was not cooperating.
return -1;
}
}else{
// Dont do it here nimrod!
//next time try the opposite of what we actually used this time
if(finalAkimboChoice == BITS_AKIMBOCHOICE_LEFT){
//yes, == left. As in not both, only firing left.
pl.nextAkimboAttackPreference = BITS_AKIMBOCHOICE_RIGHT;
//printfline("1d - BITS_AKIMBOCHOICE_RIGHT");
}else{
pl.nextAkimboAttackPreference = BITS_AKIMBOCHOICE_LEFT;
//printfline("1d - BITS_AKIMBOCHOICE_LEFT");
}
}// no choice in finalAkimboChoice
}else if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_FREE_SEMI){
if(pl.w_attack_next > 0){
attackTypeUsed &= ~BITS_AKIMBOCHOICE_LEFT;
}
//printfline("ARE YOU %.2f", pl.w_attack_akimbo_next);
if(pl.w_attack_akimbo_next > 0){
attackTypeUsed &= ~BITS_AKIMBOCHOICE_RIGHT;
}
if(attackTypeUsed == BITS_AKIMBOCHOICE_NONE){
//similar to not having the primary key tapped in a non-akimbo weapon (ordinary),
//having neither key tapped, regardless of pressed signifies to end without playing
//a clip sound.
return -1;
}
if(arg_thisWeapon.iClipLeft <= 0){
//exclude
attackTypeUsed &= ~BITS_AKIMBOCHOICE_LEFT;
}
if(arg_thisWeapon.iClipAkimboLeft <= 0){
//exclude
attackTypeUsed &= ~BITS_AKIMBOCHOICE_RIGHT;
}
if(attackTypeUsed == BITS_AKIMBOCHOICE_BOTH){
//special case. still valid to attack with both weapons? ok. Mark this.
finalAkimboChoice = BITS_AKIMBOCHOICE_BOTH;
}else if(attackTypeUsed & BITS_AKIMBOCHOICE_LEFT){
finalAkimboChoice = BITS_AKIMBOCHOICE_LEFT;
}else if(attackTypeUsed & BITS_AKIMBOCHOICE_RIGHT){
finalAkimboChoice = BITS_AKIMBOCHOICE_RIGHT;
}
//no need to edit nextAkimboAttackPreference.
//ehh, in case we switch back start from the left again...
pl.nextAkimboAttackPreference = BITS_AKIMBOCHOICE_LEFT;
}else{
//no appropriate firemode....... wat
}// firemode check
if(finalAkimboChoice != -1){
//we expect these things to be set.
if(finalAkimboChoice != BITS_AKIMBOCHOICE_NONE && pl.waterlevel < 3){
//we trust the finalAkimboChoice we're left with has ammo & hasn't fired too soon (attackDelay)
pl.recentAttackHadAmmo = TRUE;
}else{
//If we failed, we still have to decide which of the weapons clicks.
pl.recentAttackHadAmmo = FALSE;
//so which one will the click noise apply a fire delay to? Depends on firemode.
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_SEMI_AUTO){
finalAkimboChoice = pl.nextAkimboAttackPreference;
}else{
finalAkimboChoice = originalInput;
}
}
}
//printfline("POST - weapon_akimbo_semiAttackChoice %i - next:%d", finalAkimboChoice, pl.nextAkimboAttackPreference);
return finalAkimboChoice;
}// weapon_akimbo_semiAttackChoice
// A clone of semiAttackChoice for... you guessed it, akimbo full-fire weapons
// (continuously-firing weapons as the input is held).
// Which looks to be only the akimbo uzi's right now.
// Expect firemodes BITS_FIREMODE_AKIMBO_FULL_AUTO and BITS_FIREMODE_AKIMBO_FREE_FULL.
// Kindof mirror the semi fire ones, FULL_AUTO fires both weapons with holding left-click,
// FREE_FULL lets holding left & right triggers fire either weapon independently.
// Holding both down behaves exactly as though FULL_AUTO were used with left-click.
// It looks like there is no alternating fire supported, any firing of either weapon
// causes a fire delay that blocks either from firing (but holding the fire trigger for the
// non-fired one during that time will make it fire the next time the fire delay's are up).
// In other words both weapons share the same fire delay for either firing.
// This might make our job easier!
int
weapon_akimbo_fullAttackChoice(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon,
int attackTypeUsed
){
int originalInput = attackTypeUsed;
int finalAkimboChoice = BITS_AKIMBOCHOICE_NONE;
// DEFAULT
pl.recentAttackHadAmmo = TRUE;
weapondata_basic_t baseRef = *basePRef;
//notice: never use "pl.nextAkimboAttackPreference". We don't alternate fire
// in any firemode for full akimbo.
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_FULL_AUTO){
//Try firing both anytime we can.
if( !(attackTypeUsed & BITS_AKIMBOCHOICE_LEFT) ||
(pl.w_attack_next > 0 && pl.w_attack_akimbo_next > 0) )
{
// In this fire mode, if the primary fire wasn't pressed we don't register at all.
// Also still don't click because we're in a changing animation (ironsight or reloading)
// or only either weapon is out of ammo.
//return FALSE;
return -1;
}
if( !(pl.w_attack_next <= 0 && pl.w_attack_akimbo_next <= 0) ){
//actually require both fire delays. If either is not ready, stop.
return -1;
}
BOOL canFireLeft = (arg_thisWeapon.iClipLeft > 0 && pl.w_attack_next <= 0);
BOOL canFireRight = (arg_thisWeapon.iClipAkimboLeft > 0 && pl.w_attack_akimbo_next <= 0);
if(canFireLeft && canFireRight){
//splended! Fire with both.
finalAkimboChoice = BITS_AKIMBOCHOICE_BOTH;
}else if(canFireLeft){
finalAkimboChoice = BITS_AKIMBOCHOICE_LEFT;
}else if(canFireRight){
finalAkimboChoice = BITS_AKIMBOCHOICE_RIGHT;
}
if(finalAkimboChoice == BITS_AKIMBOCHOICE_NONE){
// We should stop, but need to tell whether to play a click noise or not.
// If either clip is not empty, we assume the reason we did not fire is
// because whichever non-empty weapon wasn't ready to fire.
// If they are both 0, we stopped because the weapon is empty.
//pl.recentAttackHadAmmo = (finalAkimboChoice != BITS_AKIMBOCHOICE_NONE);
//return( arg_thisWeapon.iClipLeft == 0 && arg_thisWeapon.iClipAkimboLeft == 0);
if(arg_thisWeapon.iClipLeft == 0 && arg_thisWeapon.iClipAkimboLeft == 0){
// If both clips have no ammo, we assume the reason for failure is being out of ammo.
// Since we would've picked a gun to use that had ammo otherwise.
// No need to return here, we fall through with this finalAkimboChoice, same effect.
//return BITS_AKIMBOCHOICE_NONE;
}else{
//apparently some fire delay was not cooperating.
return -1;
}
}
}else if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_FREE_FULL){
if( !(pl.w_attack_next <= 0 && pl.w_attack_akimbo_next <= 0) ){
// actually require both fire delays. If either is not ready, stop.
// And yes, for Free Full too. Just go with it.
return -1;
}
if(pl.w_attack_next > 0){
attackTypeUsed &= ~BITS_AKIMBOCHOICE_LEFT;
}
if(pl.w_attack_akimbo_next > 0){
attackTypeUsed &= ~BITS_AKIMBOCHOICE_RIGHT;
}
if(attackTypeUsed == BITS_AKIMBOCHOICE_NONE){
//similar to not having the primary key tapped in a non-akimbo weapon (ordinary),
//having neither key tapped, regardless of pressed signifies to end without playing
//a clip sound.
//return FALSE;
return -1;
}
if(arg_thisWeapon.iClipLeft <= 0){
//exclude
attackTypeUsed &= ~BITS_AKIMBOCHOICE_LEFT;
}
if(arg_thisWeapon.iClipAkimboLeft <= 0){
//exclude
attackTypeUsed &= ~BITS_AKIMBOCHOICE_RIGHT;
}
if(attackTypeUsed == BITS_AKIMBOCHOICE_BOTH){
//special case. still valid to attack with both weapons? ok. Mark this.
finalAkimboChoice = BITS_AKIMBOCHOICE_BOTH;
}else if(attackTypeUsed & BITS_AKIMBOCHOICE_LEFT){
finalAkimboChoice = BITS_AKIMBOCHOICE_LEFT;
}else if(attackTypeUsed & BITS_AKIMBOCHOICE_RIGHT){
finalAkimboChoice = BITS_AKIMBOCHOICE_RIGHT;
}
// determine: do we want to play the click sound (pass through), or not (return -1)?
if(finalAkimboChoice == BITS_AKIMBOCHOICE_NONE){
//if(arg_thisWeapon.iClipLeft == 0 && arg_thisWeapon.iClipAkimboLeft == 0){
if(
/*
(arg_thisWeapon.iClipLeft == 0 && originalInput == BITS_AKIMBOCHOICE_LEFT) ||
(arg_thisWeapon.iClipAkimboLeft == 0 && originalInput == BITS_AKIMBOCHOICE_RIGHT) ||
(arg_thisWeapon.iClipLeft == 0 && arg_thisWeapon.iClipAkimboLeft == 0 && originalInput == BITS_AKIMBOCHOICE_BOTH)
*/
((originalInput & BITS_AKIMBOCHOICE_LEFT) && arg_thisWeapon.iClipLeft != 0) ||
((originalInput & BITS_AKIMBOCHOICE_RIGHT) && arg_thisWeapon.iClipAkimboLeft != 0)
)
{
//apparently some fire delay was not cooperating.
return -1;
}else{
}
}
//printfline("CLUSTER cluck: %i %i", attackTypeUsed, finalAkimboChoice);
}else{
//no appropriate firemode....... wat
}// firemode check
if(finalAkimboChoice != -1){
//we expect these things to be set.
// Sure you too, why not. Full-auto weapons fire fast enough that we can't really tell the difference anyway,
// and original TS requires this too.
if( !(pl.w_attack_next <= time && pl.w_attack_akimbo_next <= time) ){
// actually require both fire delays. If either is not ready, stop.
// And yes, for Free Full too. Just go with it.
return -1;
}
if(finalAkimboChoice != BITS_AKIMBOCHOICE_NONE && pl.waterlevel < 3){
//we trust the finalAkimboChoice we're left with has ammo & hasn't fired too soon (attackDelay)
pl.recentAttackHadAmmo = TRUE;
}else{
//printfline("WHAT THE heck M8");
//If we failed, we still have to decide which of the weapons clicks.
pl.recentAttackHadAmmo = FALSE;
//so which one will the click noise apply a fire delay to? Depends on firemode.
//uhhh.. whatever.
finalAkimboChoice = originalInput;
/*
if(arg_thisWeapon.iFireModeAkimbo == BITS_FIREMODE_AKIMBO_FULL_AUTO){
finalAkimboChoice = pl.nextAkimboAttackPreference;
}else{
finalAkimboChoice = originalInput;
}
*/
}
}
return finalAkimboChoice;
}//weapon_akimbo_fullAttackChoice
void
weapon_ironsight_ToggleIronsight(
player pl, weapondata_ironsight_t* basePRef,
weapondynamic_t arg_thisWeapon
){
//weapondata_ironsight_t* basePRef = (weapondata_ironsight_t*)ary_weaponData[WEAPON_ID::SOCOMMK23];
weapondata_ironsight_t baseRef = *basePRef;
//arg_thisWeapon.iIronSight = 0;
if(pl.isReloading == FALSE && pl.isChangingIronsight == FALSE){
//printfline("time: %.2f weapon_ironsight_ToggleIronsight PASS. CURRENT:%i", time, arg_thisWeapon.iIronSight);
pl.isChangingIronsight = TRUE;
//TAGGG - QUESTION.
// Why does only from ironSight == 0, going towards 1, use the "_EndIdle" version, but
// the other way around doesn't? Does this make sense for all weapons?
if(arg_thisWeapon.iIronSight == 0){
weapon_base_setWholeAttackDelay(pl, baseRef.ironsightdata.fAnim_Change_Duration * 0.9 );
TS_Weapons_ViewAnimation_EndIdle( baseRef.ironsightdata.iAnim_Change_Index, baseRef.ironsightdata.fAnim_Change_Duration );
//TS_Weapons_ViewAnimation( baseRef.ironsightdata.iAnim_Change_Index, baseRef.ironsightdata.fAnim_Change_Duration );
}else{
weapon_base_setWholeAttackDelay(pl, baseRef.ironsightdata.fAnim_ReChange_Duration * 0.9);
TS_Weapons_ViewAnimation( baseRef.ironsightdata.iAnim_ReChange_Index, baseRef.ironsightdata.fAnim_ReChange_Duration );
}
//pl.recentAttackHadAmmo = arg_thisWeapon.iIronSight; //haaaaaaacky sax
//arg_thisWeapon.iIronSight = pl.recentAttackHadAmmo;
//arg_thisWeapon.iIronSight = !arg_thisWeapon.iIronSight;
return;
}else{
//printfline("time: %.2f weapon_ironsight_ToggleIronsight FAILED (%d, %d). current:%i", time, pl.isReloading, pl.isChangingIronsight, arg_thisWeapon.iIronSight);
}
}// weapon_ironsight_ToggleIronsight
void
weapon_gun_Reload(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon
){
weapondata_gun_t baseRef = *basePRef;
if (pl.isReloading || pl.isChangingIronsight){
return;
}
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] <= 0){
// also no. Having no ammo in the pool to take from means no reloading.
return;
}
if(!pl.weaponEquippedAkimbo){
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax){
return; //don't reload!
}
}else{
// akimbo, allows reloading for either being less than full
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax && arg_thisWeapon.iClipAkimboLeft >= baseRef.iClipMax){
return; //both full? don't reload!
}
}
pl.isChangingIronsight = FALSE;
pl.resetZoomSoft();
pl.aryNextBurstShotTime_softLength = 0;
pl.aryNextBurstShotTime_listenIndex = -1;
pl.isReloading = TRUE;
weapon_base_setWholeAttackDelay(pl, baseRef.fAnim_Reload_Duration);
TS_Weapons_ViewAnimation(baseRef.iAnim_Reload_Index, baseRef.fAnim_Reload_Duration);
//TAGGG TODO - third person model anim
//Animation_ReloadWeapon( self );
}//weapon_gun_Reload
// like above, but pick a sequence/time to use instead of using config info.
void
weapon_gun_Reload_CustomSequence(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon,
int arg_iReloadSeq, float arg_flReloadSeqTime
){
weapondata_gun_t baseRef = *basePRef;
if (pl.isReloading || pl.isChangingIronsight){
return;
}
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] <= 0){
// also no. Having no ammo in the pool to take from means no reloading.
return;
}
if(!pl.weaponEquippedAkimbo){
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax){
return; //don't reload!
}
}else{
// akimbo, allows reloading for either being less than full
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax && arg_thisWeapon.iClipAkimboLeft >= baseRef.iClipMax){
return; //both full? don't reload!
}
}
pl.isChangingIronsight = FALSE;
pl.resetZoomSoft();
pl.aryNextBurstShotTime_softLength = 0;
pl.aryNextBurstShotTime_listenIndex = -1;
pl.isReloading = TRUE;
weapon_base_setWholeAttackDelay(pl, arg_flReloadSeqTime);
TS_Weapons_ViewAnimation(arg_iReloadSeq, arg_flReloadSeqTime);
//TAGGG TODO - third person model anim
//Animation_ReloadWeapon( self );
}//weapon_gun_Reload_CustomSequence
void
weapon_ironsight_Reload(
player pl, weapondata_ironsight_t* basePRef,
weapondynamic_t arg_thisWeapon
){
weapondata_ironsight_t baseRef = *basePRef;
/*
if (pl.w_attack_next > 0.0){
return;
}
*/
if ( pl.isReloading || pl.isChangingIronsight){
//blocked
return;
}
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] <= 0){
//also no. Having no ammo in the pool to take from means no reloading.
return;
}
if(!pl.weaponEquippedAkimbo){
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax){
return; //don't reload!
}
}else{
//akimbo, allows reloading for either being less than full
if(arg_thisWeapon.iClipLeft >= baseRef.iClipMax && arg_thisWeapon.iClipAkimboLeft >= baseRef.iClipMax){
return; //both full? don't reload!
}
}
pl.isChangingIronsight = FALSE;
pl.resetZoomSoft();
pl.aryNextBurstShotTime_softLength = 0;
pl.aryNextBurstShotTime_listenIndex = -1;
pl.isReloading = TRUE;
weapon_base_setWholeAttackDelay(pl, baseRef.fAnim_Reload_Duration);
//printfline("not ironsite rite %i", arg_thisWeapon.iIronSight);
if(!arg_thisWeapon.iIronSight){
TS_Weapons_ViewAnimation(baseRef.iAnim_Reload_Index, baseRef.fAnim_Reload_Duration );
}else{
TS_Weapons_ViewAnimation(baseRef.ironsightdata.iAnim_Reload_Index, baseRef.ironsightdata.fAnim_Reload_Duration );
}
//TAGGG TODO - third person model anim.
//Animation_ReloadWeapon( self );
}//weapon_ironsight_Reload
void
weapon_gun_endOfReload(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon
){
weapondata_gun_t baseRef = *basePRef;
int ammoReserve = pl.ary_ammoTotal[baseRef.iAmmoDataID];
if(!pl.weaponEquippedAkimbo){
int ammoToFill = (baseRef.iClipMax - arg_thisWeapon.iClipLeft);
if(ammoToFill <= ammoReserve){
// ok
}else{
ammoToFill = ammoReserve; //can't fill more than we got.
}
arg_thisWeapon.iClipLeft += ammoToFill;
pl.ary_ammoTotal[baseRef.iAmmoDataID] -= ammoToFill;
}else{
// if akimbo, we have to fill baseRef.iClipLeft and iClipAkimboLeft.
int ammoToFillClip1 = (baseRef.iClipMax - arg_thisWeapon.iClipLeft);
int ammoToFillClip2 = (baseRef.iClipMax - arg_thisWeapon.iClipAkimboLeft);
int totalAmmoToFill = ammoToFillClip1 + ammoToFillClip2;
if(totalAmmoToFill <= ammoReserve){
// well that was easy
}else{
float halfUp = ceil(ammoReserve/2);
if(ammoToFillClip1 < halfUp ){
// If we had enough ammo to fill clip1 to the top, put the remainder in clip2.
ammoToFillClip2 = ammoReserve - ammoToFillClip1;
}else if(ammoToFillClip2 < halfUp){
ammoToFillClip1 = ammoReserve - ammoToFillClip2;
}else{
// both are above half? Just divy it out
ammoToFillClip1 = halfUp; //ceil, in case an odd number is used, this one gets +1.
ammoToFillClip2 = ammoReserve/2;
}
}// totalAmmoToFill <= ammoReserve compare
printfline("***Akimbo ammo refill debug: remaining before fill:%i to fill left:%i right:%i", pl.ary_ammoTotal[baseRef.iAmmoDataID], ammoToFillClip1, ammoToFillClip2);
arg_thisWeapon.iClipLeft += ammoToFillClip1;
arg_thisWeapon.iClipAkimboLeft += ammoToFillClip2;
pl.ary_ammoTotal[baseRef.iAmmoDataID] -= (ammoToFillClip1 + ammoToFillClip2);
}// pl.weaponEquippedAkimbo check
}// weapon_gun_endOfReload
void
weapon_gun_endOfIronSight(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon
){
printfline("weapon_ironsight_ToggleIronsight was:%i", arg_thisWeapon.iIronSight);
if(arg_thisWeapon.iIronSight == 0){
arg_thisWeapon.iIronSight = 1;
}else{
arg_thisWeapon.iIronSight = 0;
}
pl.isChangingIronsight = FALSE;
}
// CRITICAL. Good to go for Nuclide support!!!
// Given a pointer to some element of ary_weaponData or ary_akimboUpgradeData (weapon
// info), and the actual inventory space of the user, do the basic deploy behavior.
void
weapon_base_onEquip(
player pl, weapondata_basic_t* basePRef, weapondynamic_t arg_thisWeapon
){
weapondata_basic_t baseRef = *basePRef;
printfline("weapon_base_onEquip: activeweap:%d sName:%s sName2:%s", pl.activeweapon, baseRef.sDisplayName, g_weapons[pl.activeweapon].name);
#ifdef CLIENT
TS_View_SetViewModelFromStats();
//sound(pl, CHAN_ITEM, "weapons/draw.wav", 1, ATTN_NORM, 100, SOUNDFLAG_PLAYER_COMMON);
localsound("weapons/draw.wav", CHAN_AUTO, 1.0f);
//printfline("weapon_base_onEquip: playing deploy anim");
#endif
TS_Weapons_ViewAnimation(baseRef.iAnim_Deploy_Index, baseRef.fAnim_Deploy_Duration);
pl.nextAkimboAttackPreference = BITS_AKIMBOCHOICE_LEFT;
arg_thisWeapon.iIronSight = 0; //reset ironsight each time.
//TAGGG TODO - decision
// If we notice any weapon keeps ironsight changes between picking different weapons / coming back,
// this needs to stay specific to the weapon. If it's always reset it may as well be specific
// to the player though.
weapon_base_setWholeAttackDelay(pl, baseRef.fAnim_Deploy_Duration);
}// weapon_base_onEquip
// really, just check to see if we're reloading or changing ironsight
// when w_attack_next expires.
void
weapon_gun_onThink(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon
){
if(pl.w_attack_next <= 0){
if(pl.isReloading){
// end of reloading
weapon_gun_endOfReload(pl, basePRef, arg_thisWeapon);
pl.isReloading = FALSE;
}
if(pl.isChangingIronsight){
weapon_gun_endOfIronSight(pl, basePRef, arg_thisWeapon);
}
}
}// weapon_gun_onThink
void
weapon_gun_fireBurstBullet(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon
){
weapondata_gun_t basicRef = *(basePRef);
float oldFireDelay = pl.w_attack_next;
pl.w_attack_next = 0;
pl.aryNextBurstShotTime_listenIndex++;
#ifdef CLIENT
//printfline("What now2 %i m:%i", pl.aryNextBurstShotTime_listenIndex, pl.aryNextBurstShotTime_softLength);
#endif
// this tells my Weapons_Primary to ignore normal delay checks.
// The weapon's specific method for this must still check for this firemode
// for it to be effective, it's not magic
arg_thisWeapon.iFireMode = BITS_FIREMODE_NONE;
Weapons_Primary();
arg_thisWeapon.iFireMode = BITS_FIREMODE_BURST;
// forget any changes to fire delay. HACKY.
pl.w_attack_next = oldFireDelay;
if(pl.aryNextBurstShotTime_listenIndex >= pl.aryNextBurstShotTime_softLength){
// out of shots.
pl.aryNextBurstShotTime_listenIndex = -1;
pl.aryNextBurstShotTime_softLength = 0;
}
}
// NOTE! Meant to be called in addition to normal weapon_gun_onThink
// in a weapon's onThink method, do not replace that entirely
void
weapon_gun_onThink_burstFireLogic(
player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon
){
weapondata_gun_t basicRef = *(basePRef);
if(pl.aryNextBurstShotTime_listenIndex >= 0){
//printfline("weapon_gun_onThink_burstFireLogic, lisenting: %.2f <= [%i]%.2f", pl.w_attack_next, pl.aryNextBurstShotTime_listenIndex, pl.aryNextBurstShotTime[pl.aryNextBurstShotTime_listenIndex]);
if(pl.w_attack_next <= pl.aryNextBurstShotTime[pl.aryNextBurstShotTime_listenIndex] ){
weapon_gun_fireBurstBullet(pl, basePRef, arg_thisWeapon);
}
}
}// weapon_gun_onThink
void
weapon_gun_onDrawHUD(player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon)
{
#ifdef CLIENT
weapondata_gun_t baseRef = *basePRef;
//DUMMY
vector vEquippedWeaponInfoDraw = [video_res[0] - 128 - 8, video_res[1] - 19 - 8];
//int iBitsCurrentWeaponOpts = BITS_WEAPONOPT_SILENCER | BITS_WEAPONOPT_LASERSIGHT | BITS_WEAPONOPT_FLASHLIGHT | BITS_WEAPONOPT_SCOPE;
int iBitsCurrentWeaponOpts = arg_thisWeapon.iBitsUpgrade;
int iCurrentWeaponOptsCount = 0;
if(iBitsCurrentWeaponOpts & BITS_WEAPONOPT_SILENCER){
//Note that if we have the silencer at all, it's alwas on.
drawWeaponOptionBar(vEquippedWeaponInfoDraw, "Silencer", TRUE, 0.79f);
vEquippedWeaponInfoDraw.y -= 20;
iCurrentWeaponOptsCount++;
}
if(iBitsCurrentWeaponOpts & BITS_WEAPONOPT_LASERSIGHT){
drawWeaponOptionBar(vEquippedWeaponInfoDraw, "Lasersight", (arg_thisWeapon.iBitsUpgrade_on & BITS_WEAPONOPT_LASERSIGHT), 0.79f);
vEquippedWeaponInfoDraw.y -= 20;
iCurrentWeaponOptsCount++;
}
if(iBitsCurrentWeaponOpts & BITS_WEAPONOPT_FLASHLIGHT){
drawWeaponOptionBar(vEquippedWeaponInfoDraw, "Flashlight", (arg_thisWeapon.iBitsUpgrade_on & BITS_WEAPONOPT_FLASHLIGHT), 0.79f);
vEquippedWeaponInfoDraw.y -= 20;
iCurrentWeaponOptsCount++;
}
if(iBitsCurrentWeaponOpts & BITS_WEAPONOPT_SCOPE){
drawWeaponOptionBar(vEquippedWeaponInfoDraw, "Scope", TRUE, 0.79f);
vEquippedWeaponInfoDraw.y -= 20;
iCurrentWeaponOptsCount++;
}
ammodata_t* ammoPRef = ary_ammoData[baseRef.iAmmoDataID];
ammodata_t ammoRef = *ammoPRef;
vector clrDraw;
if(!pl.weaponEquippedAkimbo){
// ammo: clip count / total count (for the type of ammo)
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
if(arg_thisWeapon.iClipLeft > 0){
clrDraw = clrPaleBlue;
}else{
clrDraw = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 0, arg_thisWeapon.iClipLeft, 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
Gfx_Text( [vEquippedWeaponInfoDraw.x + 64, vEquippedWeaponInfoDraw.y + 4], "/", vHUDFontSize, clrPaleBlue, 0.86f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] > 0){
clrDraw = clrPaleBlue;
}else{
clrDraw = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 94, vEquippedWeaponInfoDraw.y + 0, pl.ary_ammoTotal[baseRef.iAmmoDataID], 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
vEquippedWeaponInfoDraw.y -= 20;
}else{
// TODO - Could make a CVar for the current way (drawing akimbo weapon clips
// per weapon separately) or combined like they were one weapon (original TS
// way). Regardless of being combined or not, that's only how they're
// portrayed in the HUD here. With 12-ammo-clipped weapons, combined does
// not mean a single one can fire 24 times without reloading.
// drawing from the bottom, so akimbo (right) gun comes first.
// ammo: clip count / total count (for the type of ammo)
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
if(arg_thisWeapon.iClipAkimboLeft > 0){
clrDraw = clrPaleBlue;
}else{
clrDraw = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 0, arg_thisWeapon.iClipAkimboLeft, 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
Gfx_Text( [vEquippedWeaponInfoDraw.x + 64, vEquippedWeaponInfoDraw.y + 4], "/", vHUDFontSize, clrPaleBlue, 0.86f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] > 0){
clrDraw = clrPaleBlue;
}else{
clrDraw = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 94, vEquippedWeaponInfoDraw.y + 0, pl.ary_ammoTotal[baseRef.iAmmoDataID], 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
vEquippedWeaponInfoDraw.y -= 20;
// ammo: clip count / total count (for the type of ammo)
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
if(arg_thisWeapon.iClipLeft > 0){
clrDraw = clrPaleBlue;
}else{
clrDraw = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 0, arg_thisWeapon.iClipLeft, 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
Gfx_Text( [vEquippedWeaponInfoDraw.x + 64, vEquippedWeaponInfoDraw.y + 4], "/", vHUDFontSize, clrPaleBlue, 0.86f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
if(pl.ary_ammoTotal[baseRef.iAmmoDataID] > 0){
clrDraw = clrPaleBlue;
}else{
clrDraw = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 94, vEquippedWeaponInfoDraw.y + 0, pl.ary_ammoTotal[baseRef.iAmmoDataID], 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
vEquippedWeaponInfoDraw.y -= 20;
}// pl.weaponEquippedAkimbo check
// What firemode is the player using for this weapon now?
int* fireModeVar;
if(!pl.weaponEquippedAkimbo){
fireModeVar = &arg_thisWeapon.iFireMode;
}else{
fireModeVar = &arg_thisWeapon.iFireModeAkimbo;
}
string fireModeName;
if(!pl.weaponEquippedAkimbo){
fireModeName = getFiremodeName((*fireModeVar));
}else{
fireModeName = getAkimboFiremodeName((*fireModeVar));
}
// fire mode name?
// IMPORTANT. If this weapon is akimbo we have to use the "getAkimboFiremodeName"
// variant!
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
Gfx_Text( [vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 4], fireModeName, vHUDFontSize, clrPaleBlue, 0.86f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
vEquippedWeaponInfoDraw.y -= 20;
// ammo type name
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
Gfx_Text( [vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 4], ammoRef.sDisplayName, vHUDFontSize, clrPaleBlue, 0.86f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
vEquippedWeaponInfoDraw.y -= 20;
// title
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
Gfx_Text( [vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 4], baseRef.sDisplayName, vHUDFontSize, clrPaleBlue, 0.98f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
vEquippedWeaponInfoDraw.y -= 20;
#endif
}// weapon_gun_onDrawHUD
void
weapon_throwable_onDrawHUD(player pl, weapondata_throwable_t* basePRef, weapondynamic_t arg_thisWeapon)
{
#ifdef CLIENT
weapondata_throwable_t baseRef = *basePRef;
// DUMMY
vector vEquippedWeaponInfoDraw = [video_res[0] - 128 - 8, video_res[1] - 19 - 8];
int iBitsCurrentWeaponOpts = arg_thisWeapon.iBitsUpgrade;
vector clrDraw;
clrDraw = clrPaleBlue;
// draw the count
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 128 - 2 - 11*3, vEquippedWeaponInfoDraw.y + 0, arg_thisWeapon.iCount, 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
vEquippedWeaponInfoDraw.y -= 20;
// title
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
Gfx_Text( [vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 4], baseRef.sDisplayName, vHUDFontSize, clrPaleBlue, 0.98f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
vEquippedWeaponInfoDraw.y -= 20;
#endif
}// weapon_throwable_onDrawHUD
void
weapon_melee_onDrawHUD(player pl, weapondata_melee_t* basePRef, weapondynamic_t arg_thisWeapon)
{
#ifdef CLIENT
weapondata_melee_t baseRef = *basePRef;
// DUMMY
vector vEquippedWeaponInfoDraw = [video_res[0] - 128 - 8, video_res[1] - 19 - 8];
int iBitsCurrentWeaponOpts = arg_thisWeapon.iBitsUpgrade;
vector clrDraw;
clrDraw = clrPaleBlue;
// empty box? ok.
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
//drawSpriteNumber(ary_LCD_numberSet, vEquippedWeaponInfoDraw.x + 128 - 2 - 11*3, vEquippedWeaponInfoDraw.y + 0, arg_thisWeapon.iCount, 3, BITS_DIGITOPT_DEFAULT, clrDraw, 0.92f);
vEquippedWeaponInfoDraw.y -= 20;
// title
drawfill( vEquippedWeaponInfoDraw, [128, 19], clrPaleBlue, 0.86f - 0.60f );
Gfx_Text( [vEquippedWeaponInfoDraw.x + 2, vEquippedWeaponInfoDraw.y + 4], baseRef.sDisplayName, vHUDFontSize, clrPaleBlue, 0.98f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
vEquippedWeaponInfoDraw.y -= 20;
#endif
}// weapon_throwable_onDrawHUD
void
weapon_ShowMuzzleFlash(int arg_muzzleFlashType)
{
weapon_ShowMuzzleFlashAkimbo(arg_muzzleFlashType, BITS_AKIMBOCHOICE_NONE);
}
void
weapon_ShowMuzzleFlashAkimbo(int arg_muzzleFlashType, int arg_akimboChoice)
{
#ifdef CLIENT
if(arg_muzzleFlashType != MUZZLEFLASH_ID::NONE){
// Set up an event to do this instead so that being called over and over by the client
// with packet delays (realistic network delays in multiplayer) don't cause the flash
// to appear frozen until a server update.
player pl = (player)self;
pl.iMuzzleFlashType = arg_muzzleFlashType;
pl.iMuzzleFlashAkimboChoice = arg_akimboChoice;
View_AddEvent(viewEv_weapon_ShowMuzzleFlash, 0.0f);
}
#else
// anything for other players to see, or will this be per animation in clientside rendering
// (player predraw) for whoever sees another player firing?
#endif
}
// Not to be confused with the clientside-only event "viewEv_weapon_EjectShell".
// This is called by weapons to add the event, handles setting the shell-type global
// for it to see. If shell-ejects are done serverside, that would not be an amazing
// idea. Per-player would be better.
void
weapon_EjectShell(int arg_shellEjectType)
{
weapon_EjectShellAkimbo(arg_shellEjectType, BITS_AKIMBOCHOICE_NONE);
}
void
weapon_EjectShellAkimbo(int arg_shellEjectType, int arg_akimboChoice)
{
#ifdef CLIENT
if(arg_shellEjectType != SHELLEJECT_ID::NONE){
player pl = (player)self;
pl.iShellEjectType = arg_shellEjectType;
pl.iShellEjectAkimboChoice = arg_akimboChoice;
View_AddEvent(viewEv_weapon_EjectShell, 0.0f);
}
#else
// anything for the playermodel?
#endif
}
void
weapon_ClientEffects(int arg_muzzleFlashType, int arg_shellEjectType)
{
weapon_ClientEffectsAkimbo(arg_muzzleFlashType, arg_shellEjectType, BITS_AKIMBOCHOICE_NONE);
}
// Both weapon_ShowMuzzleFlash and weapon_EjectShell in one call for scheduling
// one event that calls both, otherwise, only either can happen (one event allowed;
// any more and they just overwrite each other on being set)
void
weapon_ClientEffectsAkimbo(int arg_muzzleFlashType, int arg_shellEjectType, int arg_akimboChoice)
{
#ifdef CLIENT
player pl = (player)self;
if(arg_shellEjectType != SHELLEJECT_ID::NONE){
pl.iShellEjectType = arg_shellEjectType;
pl.iShellEjectAkimboChoice = arg_akimboChoice;
}
if(arg_muzzleFlashType != MUZZLEFLASH_ID::NONE){
pl.iMuzzleFlashType = arg_muzzleFlashType;
pl.iMuzzleFlashAkimboChoice = arg_akimboChoice;
}
View_AddEvent(viewEv_weapon_ClientEffects, 0.0f);
#else
// ?
#endif
}
// Precache models and the HUD icon given in FreeTS weapondata.
// Weapon sounds and anything else not in the struct should be precached
// in the weapon's own precache method or globally (precache.qc) if reused
// between different weapons like shell models/sounds.
void
weapon_precache(weapondata_basic_t* basePRef)
{
#ifdef SERVER
if((*basePRef).sWorldModelPath != NULL){
precache_model((*basePRef).sWorldModelPath);
}
#else //CLIENT
if((*basePRef).sIconFilePath != NULL){
precache_model((*basePRef).sIconFilePath);
}
if((*basePRef).sViewModelPath != NULL){
precache_model((*basePRef).sViewModelPath);
}
if((*basePRef).sPlayerModelPath != NULL){
precache_model((*basePRef).sPlayerModelPath);
}
if((*basePRef).sPlayerSilencerModelPath != NULL){
precache_model((*basePRef).sPlayerSilencerModelPath);
}
#endif
}
// Not sure how to handle the "Weapons_UpdateAmmo" calls for FreeTS.
// Guessing to leave them dummied as HUD drawing already knows where to grab clip and ammo-pool
// values from, the Nuclide base_player's ammo1, 2, 3 vars are never used.
//----------------------
// More typical way.
/*
void weapon_gun_updateAmmo(player pl, weapondata_gun_t* basePRef, weapondynamic_t arg_thisWeapon){
Weapons_UpdateAmmo(pl, arg_thisWeapon.iClipLeft, pl.ary_ammoTotal[(*basePRef).iAmmoDataID], -1);
}
*/
// Easier way for the caller.
void
weapon_gun_updateAmmo(player pl, int weaponTypeID)
{
if(pl.inventoryEquippedIndex != -1){
weapondynamic_t arg_thisWeapon = pl.ary_myWeapons[pl.inventoryEquippedIndex];
// no need to hardcode ammo names when they're given by weapondata
//Weapons_UpdateAmmo(pl, arg_thisWeapon.iClipLeft, pl.ary_ammoTotal[AMMO_ID::_9x19mm], -1);
weapondata_gun_t* basicP = (weapondata_gun_t*)ary_weaponData[weaponTypeID];
// route to the above method, give a different name? No, not worth it.
//weapon_gun_updateAmmo(pl, basicP, arg_thisWeapon);
int myAmmoID = (*basicP).iAmmoDataID;
Weapons_UpdateAmmo(pl, arg_thisWeapon.iClipLeft, pl.ary_ammoTotal[myAmmoID], -1);
}else{
printfline("!!! weapon_gun_updateAmmo: WARNING: inventoryEquippedIndex was -1");
}
}
void
weapons_dummyfun(player pl, weapondynamic_t arg_thisWeapon){
}
//blank weapon.
weapondata_basic_t weapon_none =
{
WEAPONDATA_TYPEID_BASIC,
"_NONE_",
"",
"",
"",
"",
"",
weapons_dummyfun,
weapons_dummyfun,
weapons_dummyfun,
weapons_dummyfun,
NULL,
0,
0,
0.0f,
0.0f, //fire delay
0.0f,
FALSE,
BITS_WEAPONOPT_NONE,
BITS_WEAPONOPT_NONE,
0,
0,
0, //BUYCATEGORY_,
0, //inv slot. normal minimum is 1
{0.022000, 1.500000, 0.007000},
WEAPON_AKIMBO_UPGRADE_ID::NONE
};
//blank weapon.
weapondata_basic_t weapon_none_akimbo =
{
WEAPONDATA_TYPEID_BASIC,
"_NONE_",
"",
"",
"",
"",
"",
weapons_dummyfun,
weapons_dummyfun,
weapons_dummyfun,
weapons_dummyfun,
NULL,
0,
0,
0.0f,
0.0f, //fire delay. this is most certainly wrong. find something more accurate later.
0.0f,
FALSE,
BITS_WEAPONOPT_NONE,
BITS_WEAPONOPT_NONE,
0,
0,
0, //BUYCATEGORY_,
0, //inv slot. normal minimum is 1
{0.022000, 1.500000, 0.007000},
WEAPON_AKIMBO_UPGRADE_ID::NONE
};
void
setupWeaponData(void)
{
// for reference, old way. REDO THAT MACRO to involve g_weapons, described more below
//#define ASSIGN_WEAPONDATA(arg_constName) ary_weaponData[WEAPON_ID::##arg_constName] = (weapondata_basic_t*) &weapon_##arg_constName;
//TAGGG - CRITICAL
// If there are any issues with this, make copy methods to assign the slots.
// Or try the initializer array format g_weapons = {w_karate, w_whatever}... etc. w_karate is not the first weapon at all though.
// Really just do it by ID's with a new ASSIGN_WEAWPONDATA because init lists for offsets that may skip around sounds hellish
g_weapons[WEAPON_ID::NONE] = w_null;
g_weapons[WEAPON_ID::KARATE] = w_karate;
g_weapons[WEAPON_ID::GLOCK18] = w_glock18;
g_weapons[WEAPON_ID::SOCOMMK23] = w_socommk23;
g_weapons[WEAPON_ID::SOCOMMK23_AKIMBO] = w_socommk23_akimbo;
g_weapons[WEAPON_ID::DESERTEAGLE] = w_deserteagle;
g_weapons[WEAPON_ID::FIVESEVEN] = w_fiveseven;
g_weapons[WEAPON_ID::FIVESEVEN_AKIMBO] = w_fiveseven_akimbo;
g_weapons[WEAPON_ID::BERETTA] = w_beretta;
g_weapons[WEAPON_ID::BERETTA_AKIMBO] = w_beretta_akimbo;
g_weapons[WEAPON_ID::AKIMBOCOLTS] = w_akimbocolts;
g_weapons[WEAPON_ID::GLOCK20] = w_glock20;
g_weapons[WEAPON_ID::RUGERMK1] = w_rugermk1;
g_weapons[WEAPON_ID::RAGINGBULL] = w_ragingbull;
g_weapons[WEAPON_ID::CONTENDERG2] = w_contenderg2;
g_weapons[WEAPON_ID::MINIUZI] = w_miniuzi;
g_weapons[WEAPON_ID::MINIUZI_AKIMBO] = w_miniuzi_akimbo;
g_weapons[WEAPON_ID::MP5SD] = w_mp5sd;
g_weapons[WEAPON_ID::MP5K] = w_mp5k;
g_weapons[WEAPON_ID::STEYRTMP] = w_steyrtmp;
g_weapons[WEAPON_ID::HKPDW] = w_hkpdw; //MP7-PDW
g_weapons[WEAPON_ID::UMP] = w_ump;
g_weapons[WEAPON_ID::SKORPION] = w_skorpion;
g_weapons[WEAPON_ID::SKORPION_AKIMBO] = w_skorpion_akimbo;
g_weapons[WEAPON_ID::MAC10] = w_mac10;
g_weapons[WEAPON_ID::M4A1] = w_m4a1;
g_weapons[WEAPON_ID::AK47] = w_ak47;
g_weapons[WEAPON_ID::STEYRAUG] = w_steyraug;
g_weapons[WEAPON_ID::M16A4] = w_m16a4;
g_weapons[WEAPON_ID::BARRETTM82] = w_barrettm82;
g_weapons[WEAPON_ID::BENELLIM3] = w_benellim3;
g_weapons[WEAPON_ID::SPAS12] = w_spas12;
g_weapons[WEAPON_ID::USAS12] = w_usas12;
g_weapons[WEAPON_ID::MOSSBERG500] = w_mossberg500;
g_weapons[WEAPON_ID::SAWEDOFF] = w_sawedoff;
g_weapons[WEAPON_ID::M61GRENADE] = w_m61grenade;
g_weapons[WEAPON_ID::COMBATKNIFE] = w_combatknife;
g_weapons[WEAPON_ID::SEALKNIFE] = w_sealknife;
g_weapons[WEAPON_ID::KATANA] = w_katana;
g_weapons[WEAPON_ID::M60] = w_m60;
/////////////////////////////////////////////////////////
ASSIGN_WEAPONDATA(NONE, none)
ASSIGN_AKIMBOUPGRADEDATA(NONE, none)
ASSIGN_WEAPONDATA(KARATE, karate)
ASSIGN_WEAPONDATA(GLOCK18, glock18)
ASSIGN_WEAPONDATA(SOCOMMK23, socommk23)
ASSIGN_WEAPONDATA(SOCOMMK23_AKIMBO, socommk23_akimbo)
ASSIGN_AKIMBOUPGRADEDATA(SOCOMMK23, socommk23)
ASSIGN_WEAPONDATA(DESERTEAGLE, deserteagle)
ASSIGN_WEAPONDATA(FIVESEVEN, fiveseven)
ASSIGN_WEAPONDATA(FIVESEVEN_AKIMBO, fiveseven_akimbo)
ASSIGN_AKIMBOUPGRADEDATA(FIVESEVEN, fiveseven)
ASSIGN_WEAPONDATA(BERETTA, beretta)
ASSIGN_WEAPONDATA(BERETTA_AKIMBO, beretta_akimbo)
ASSIGN_AKIMBOUPGRADEDATA(BERETTA, beretta)
ASSIGN_WEAPONDATA(AKIMBOCOLTS, akimbocolts)
ASSIGN_WEAPONDATA(GLOCK20, glock20)
ASSIGN_WEAPONDATA(RUGERMK1, rugermk1)
ASSIGN_WEAPONDATA(RAGINGBULL, ragingbull)
ASSIGN_WEAPONDATA(CONTENDERG2, contenderg2)
ASSIGN_WEAPONDATA(MINIUZI, miniuzi)
ASSIGN_WEAPONDATA(MINIUZI_AKIMBO, miniuzi_akimbo)
ASSIGN_AKIMBOUPGRADEDATA(MINIUZI, miniuzi)
ASSIGN_WEAPONDATA(MP5SD, mp5sd)
ASSIGN_WEAPONDATA(MP5K, mp5k)
ASSIGN_WEAPONDATA(STEYRTMP, steyrtmp)
ASSIGN_WEAPONDATA(HKPDW, hkpdw)
ASSIGN_WEAPONDATA(UMP, ump)
ASSIGN_WEAPONDATA(SKORPION, skorpion)
ASSIGN_WEAPONDATA(SKORPION_AKIMBO, skorpion_akimbo)
ASSIGN_AKIMBOUPGRADEDATA(SKORPION, skorpion)
ASSIGN_WEAPONDATA(MAC10, mac10)
ASSIGN_WEAPONDATA(M4A1, m4a1)
ASSIGN_WEAPONDATA(AK47, ak47)
ASSIGN_WEAPONDATA(STEYRAUG, steyraug)
ASSIGN_WEAPONDATA(M16A4, m16a4)
ASSIGN_WEAPONDATA(BARRETTM82, barrettm82)
ASSIGN_WEAPONDATA(BENELLIM3, benellim3)
ASSIGN_WEAPONDATA(USAS12, usas12)
ASSIGN_WEAPONDATA(SPAS12, spas12)
ASSIGN_WEAPONDATA(MOSSBERG500, mossberg500)
ASSIGN_WEAPONDATA(SAWEDOFF, sawedoff)
ASSIGN_WEAPONDATA(M61GRENADE, m61grenade)
ASSIGN_WEAPONDATA(COMBATKNIFE, combatknife)
ASSIGN_WEAPONDATA(M60, m60)
ASSIGN_WEAPONDATA(KATANA, katana)
ASSIGN_WEAPONDATA(SEALKNIFE, sealknife)
ary_shotgunExtra[SHOTGUN_EXTRA_ID::BENELLIM3] = &weapon_benellim3_shotgunExtra;
ary_shotgunExtra[SHOTGUN_EXTRA_ID::MOSSBERG500] = &weapon_mossberg500_shotgunExtra;
ary_shotgunExtra[SHOTGUN_EXTRA_ID::MOSSBERG500_IRONSIGHT] = &weapon_mossberg500_shotgunExtra_ironsight;
ary_shotgunExtra[SHOTGUN_EXTRA_ID::SPAS12] = &weapon_spas12_shotgunExtra;
}//setupWeaponData
weapondata_basic_t*
getWeaponData(int arg_weaponID, BOOL arg_akimbo)
{
weapondata_basic_t* basicP;
// no cap check for now
if(arg_weaponID >= 0){
basicP = (weapondata_basic_t*) ary_weaponData[arg_weaponID];
// No need to check for this here, weapon ID's received (often pl.activeweapon) are
// already set to the akimbo versions if they're wanted elsewhere.
/*
if(!arg_akimbo){
// that was easy. Fall thru, leave basicP as it is.
}else{
// oh.
int myAkimboUpgradeID = (*basicP).iAkimboID;
if(myAkimboUpgradeID > 0 && myAkimboUpgradeID < WEAPON_AKIMBO_UPGRADE_ID::LAST_ID ){
int linearAkimboID = ary_AKIMBO_UPGRADE_TO_WEAPON[myAkimboUpgradeID];
basicP = (weapondata_basic_t*) ary_weaponData[linearAkimboID];
}
// otherwise, leave basicP as it is
}
*/
}else{
// ???
basicP = NULL;
}
return basicP;
}
weapondata_basic_t*
getWeaponData_Singular(int arg_weaponID)
{
// Not quite this simple.
// See if we landed on the akimbo form of a weapon. If so, backtrack to
// its singular form.
//return (weapondata_basic_t*) ary_weaponData[arg_weaponID];
weapondata_basic_t* basicP;
// no cap check for now
if(arg_weaponID >= 0){
basicP = (weapondata_basic_t*) ary_weaponData[arg_weaponID];
if( (*basicP).iAkimboID >= 0){
// None or positive (singular variant that refers to an akimbo form)? Not interested
}else{
// < 0? Negate it for the intention and use to go from akimbo -> singular.
int mySingularUpgradeID = -(*basicP).iAkimboID;
basicP = (weapondata_basic_t*) ary_weaponData[mySingularUpgradeID];
}
}else{
// ???
basicP = NULL;
}
return basicP;
}
// If this weapon does not use ammo, this returns -1.
int
getAmmoTypeOfWeapon(int arg_weaponID)
{
weapondata_basic_t* basicPointer = (weapondata_basic_t*)ary_weaponData[arg_weaponID];
weapondata_basic_t basicRef = *basicPointer;
if(basicRef.typeID == WEAPONDATA_TYPEID_GUN || basicRef.typeID == WEAPONDATA_TYPEID_IRONSIGHT){
weapondata_gun_t gunRef = *((weapondata_gun_t*)basicPointer);
return gunRef.iAmmoDataID;
}else{
return -1; // does not use ammo for all we care.
}
}// getAmmoTypeOfWeapon
// This is based off what the HL glock does, adjust with custom shell-choices per
// different weapons that use them.
// Is HL's "w_shotgun_ejectshell" even any different from this besides shell-model
// choice? Not that this is an exact match for how TS does any shell eject logic,
// maybe, but an example of how to do it at all in FTE at least, exactly-accurate
// TS shell ejection is not a huge priority.
// In fact it isn't even supported serverside yet I think?
// Try testing FreeHL or FreeCS, do you see your own shells in thirdperson on firing
// and (multiplayer) do other players see your shells on firing?
#ifdef CLIENT
void
viewEv_playShotgunInsertShellSound(void)
{
// is it wise to trust 'self' in events? No idea
//player pl = (player)self;
player pl = (player)pSeat->m_ePlayer;
sound(pl, CHAN_AUTO, "weapons/insert-shell.wav", 1, ATTN_NONE);
}
void
viewEv_weapon_EjectShell(void)
{
// Actually, queue a shell eject during the next LatePreDraw call
// (TS_View_DrawCustom to be more specific). This makes the generated shell
// position more accurate. It will use the player's tempvars to set up
// the shell for the akimbo choice like here would have.
player pl = (player)pSeat->m_ePlayer;
pl.bShellEjectScheduled = TRUE;
/*
if(pl.iShellEjectType != SHELLEJECT_ID::NONE){
CTSShellEject::generateForViewModelAkimbo(pl.iShellEjectType, pl.iShellEjectAkimboChoice);
}
*/
}
var int testCount1 = 0;
var int testCount2 = 0;
void
viewEv_weapon_ShowMuzzleFlash(void)
{
player pl = (player)pSeat->m_ePlayer;
if(pl.iMuzzleFlashType != MUZZLEFLASH_ID::NONE){
muzzleflashdata_t* tempRef = ary_muzzleFlashData[pl.iMuzzleFlashType];
TS_View_ShowMuzzleflash(tempRef->iSpritePrecacheID, pl.iMuzzleFlashAkimboChoice);
if(pl.iMuzzleFlashAkimboChoice==BITS_AKIMBOCHOICE_LEFT){
testCount1++;
}else if(pl.iMuzzleFlashAkimboChoice==BITS_AKIMBOCHOICE_RIGHT){
testCount2++;
}
}
}
void
viewEv_weapon_ClientEffects(void)
{
viewEv_weapon_EjectShell();
viewEv_weapon_ShowMuzzleFlash();
}
// also clientside only, dealing with configs
void
copyWeaponConfig(weaponconfig_weapon_t* arg_dest, weaponconfig_weapon_t* arg_src)
{
arg_dest->weaponID = arg_src->weaponID;
arg_dest->weaponTypeID = arg_src->weaponTypeID;
arg_dest->iBitsUpgrade = arg_src->iBitsUpgrade;
arg_dest->iCount = arg_src->iCount;
arg_dest->iPrice = arg_src->iPrice;
arg_dest->iSlots = arg_src->iSlots;
}//copyWeaponConfig
void
copyConfig(weaponconfig_data_t* arg_dest, weaponconfig_data_t* arg_src)
{
arg_dest->ary_myWeapons_softMax = arg_src->ary_myWeapons_softMax;
for(int i = 0; i < arg_src->ary_myWeapons_softMax; i++){
copyWeaponConfig(&arg_dest.ary_myWeapons[i], &arg_src.ary_myWeapons[i]);
}
for(int i = 0; i < AMMO_ID::LAST_ID; i++){
arg_dest->ary_ammoTotal[i] = arg_src->ary_ammoTotal[i];
}
arg_dest->iTotalSlots = arg_src->iTotalSlots;
arg_dest->iTotalPrice = arg_src->iTotalPrice;
}//copyConfig
#endif // CLIENT