mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
This build includes the "Professional Sniper" skill and changes made to aiming and scopes in the CTH calculation functions. Modified Sniper skill and "per click" aim bonus functionality. Added some adjustments to how Scopes effect the CTH calculations. This should drastically reduce the effectiveness of sniper weapons and scopes, while still leaving them effective at closer ranges. Adjusted the scope vision bonus so that it plays on range with a minimum attainable reduction equal to the scopes minrange value. git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1937 3b4a5df2-a311-0410-b5c6-a8a6f20db521
5967 lines
201 KiB
C++
5967 lines
201 KiB
C++
#ifdef PRECOMPILEDHEADERS
|
|
#include "Tactical All.h"
|
|
#else
|
|
#include "sgp.h"
|
|
#include "overhead types.h"
|
|
#include "Sound Control.h"
|
|
|
|
#include "overhead.h"
|
|
#include "Event Pump.h"
|
|
#include "weapons.h"
|
|
#include "Animation Control.h"
|
|
#include "sys globals.h"
|
|
#include "Handle UI.h"
|
|
#include "Isometric Utils.h"
|
|
#include "worldman.h"
|
|
#include "math.h"
|
|
#include "points.h"
|
|
#include "ai.h"
|
|
#include "los.h"
|
|
#include "renderworld.h"
|
|
#include "opplist.h"
|
|
#include "interface.h"
|
|
#include "message.h"
|
|
#include "campaign.h"
|
|
#include "items.h"
|
|
#include "text.h"
|
|
#include "Soldier Profile.h"
|
|
#include "tile animation.h"
|
|
#include "Dialogue Control.h"
|
|
#include "SkillCheck.h"
|
|
#include "explosion control.h"
|
|
#include "Quests.h"
|
|
#include "Physics.h"
|
|
#include "Random.h"
|
|
#include "Vehicles.h"
|
|
#include "bullets.h"
|
|
#include "morale.h"
|
|
#include "meanwhile.h"
|
|
#include "SkillCheck.h"
|
|
#include "gamesettings.h"
|
|
#include "SaveLoadMap.h"
|
|
#include "Debug Control.h"
|
|
#include "expat.h"
|
|
#include "XML.h"
|
|
#include "Soldier macros.h"
|
|
#include "SmokeEffects.h"
|
|
#include "lighting.h"
|
|
#include "Auto Resolve.h"
|
|
#endif
|
|
|
|
//forward declarations of common classes to eliminate includes
|
|
class OBJECTTYPE;
|
|
class SOLDIERTYPE;
|
|
|
|
|
|
//rain
|
|
//#define WEAPON_RELIABILITY_REDUCTION_PER_RAIN_INTENSITY 0
|
|
extern INT8 gbCurrentRainIntensity;
|
|
//end rain
|
|
|
|
|
|
#define MINCHANCETOHIT 1
|
|
#define MAXCHANCETOHIT 99
|
|
|
|
// NB this is arbitrary, chances in DG ranged from 1 in 6 to 1 in 20
|
|
#define BASIC_DEPRECIATE_CHANCE 15
|
|
|
|
#define NORMAL_RANGE 90 // # world units considered an 'avg' shot
|
|
#define MIN_SCOPE_RANGE 60 // # world units after which scope's useful
|
|
|
|
#define MIN_TANK_RANGE 120 // range at which tank starts really having trouble aiming
|
|
|
|
// percent reduction in sight range per point of aiming
|
|
#define SNIPERSCOPE_AIM_BONUS 20
|
|
// bonus to hit with working laser scope
|
|
#define LASERSCOPE_BONUS 20
|
|
// percent reduction in sight range per point of aiming
|
|
#define REFLEXSCOPE_AIM_BONUS 10
|
|
|
|
#define MANDATORY_WEAPON_DELAY 1200
|
|
#define NO_WEAPON_SOUND 0
|
|
|
|
#define HEAD_DAMAGE_ADJUSTMENT( x ) ((x * 3) / 2)
|
|
#define LEGS_DAMAGE_ADJUSTMENT( x ) (x / 2)
|
|
|
|
#define CRITICAL_HIT_THRESHOLD 30
|
|
|
|
#define HTH_MODE_PUNCH 1
|
|
#define HTH_MODE_STAB 2
|
|
#define HTH_MODE_STEAL 3
|
|
|
|
extern void TeamChangesSides( UINT8 ubTeam, INT8 bSide );
|
|
|
|
extern BOOLEAN gfNextFireJam;
|
|
|
|
BOOLEAN WillExplosiveWeaponFail( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj );
|
|
|
|
BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo );
|
|
BOOLEAN UseBlade( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo );
|
|
BOOLEAN UseThrown( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo );
|
|
BOOLEAN UseLauncher( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo );
|
|
|
|
INT32 HTHImpact( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTarget, INT32 iHitBy, BOOLEAN fBladeAttack );
|
|
|
|
BOOLEAN gfNextShotKills = FALSE;
|
|
BOOLEAN gfReportHitChances = FALSE;
|
|
|
|
//GLOBALS
|
|
|
|
// TODO: Move strings to extern file
|
|
|
|
|
|
#define PISTOL( ammo, update, impact, rt, rof, burstrof, burstpenal, deadl, acc, clip, range, av, hv, sd, bsd) { HANDGUNCLASS, GUN_PISTOL, ammo, rt, rof, burstrof, burstpenal, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_PISTOL, S_LNL_PISTOL }
|
|
#define M_PISTOL( ammo, update, impact, rt, rof, burstrof, burstpenal, deadl, acc, clip, range, av, hv, sd, bsd) { HANDGUNCLASS, GUN_M_PISTOL, ammo, rt, rof, burstrof, burstpenal, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_PISTOL, S_LNL_PISTOL }
|
|
#define SMG( ammo, update, impact, rt, rof, burstrof, burstpenal, deadl, acc, clip, range, av, hv, sd, bsd ) { SMGCLASS, GUN_SMG, ammo, rt, rof, burstrof, burstpenal, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_SMG, S_LNL_SMG }
|
|
#define SN_RIFLE( ammo, update, impact, rt, rof, burstrof, deadl, acc, clip, range, av, hv, sd, bsd ) { RIFLECLASS, GUN_SN_RIFLE, ammo, rt, rof, burstrof, 0, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_RIFLE, S_LNL_RIFLE }
|
|
#define RIFLE( ammo, update, impact, rt, rof, burstrof, deadl, acc, clip, range, av, hv, sd, bsd ) { RIFLECLASS, GUN_RIFLE, ammo, rt, rof, burstrof, 0, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_RIFLE, S_LNL_RIFLE }
|
|
#define ASRIFLE( ammo, update, impact, rt, rof, burstrof, burstpenal, deadl, acc, clip, range, av, hv, sd, bsd ) { RIFLECLASS, GUN_AS_RIFLE, ammo, rt, rof, burstrof, burstpenal, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_RIFLE, S_LNL_RIFLE }
|
|
#define SHOTGUN( ammo, update, impact, rt, rof, burstrof, burstpenal, deadl, acc, clip, range, av, hv, sd, bsd ) { SHOTGUNCLASS, GUN_SHOTGUN, ammo, rt, rof, burstrof, burstpenal, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_SHOTGUN, S_LNL_SHOTGUN }
|
|
#define LMG( ammo, update, impact, rt, rof, burstrof, burstpenal, deadl, acc, clip, range, av, hv, sd, bsd ) { MGCLASS, GUN_LMG, ammo, rt, rof, burstrof, burstpenal, update, (UINT8) (impact), deadl, acc, clip, range, 200, av, hv, sd, bsd, S_RELOAD_LMG, S_LNL_LMG }
|
|
#define BLADE( impact, rof, deadl, range, av, sd ) { KNIFECLASS, NOT_GUN, 0, AP_READY_KNIFE, rof, 0, 0, 0, (UINT8) (impact), deadl, 0, 0, range, 200, av, 0, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
#define THROWINGBLADE( impact, rof, deadl, range, av, sd ) { KNIFECLASS, NOT_GUN, 0, AP_READY_KNIFE, rof, 0, 0, 0, (UINT8) (impact), deadl, 0, 0, range, 200, av, 0, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
#define PUNCHWEAPON( impact, rof, deadl, av, sd ) { KNIFECLASS, NOT_GUN, 0, 0, rof, 0, 0, 0, (UINT8) (impact), deadl, 0, 0, 10, 200, av, 0, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
#define LAUNCHER( update, rt, rof, deadl, acc, range, av, hv, sd) { RIFLECLASS, NOT_GUN, NOAMMO, rt, rof, 0, 0, update, 1, deadl, acc, 0, range, 200, av, hv, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
#define LAW( update, rt, rof, deadl, acc, range, av, hv, sd) { RIFLECLASS, NOT_GUN, NOAMMO, rt, rof, 0, 0, update, 80, deadl, acc, 1, range, 200, av, hv, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
#define CANNON( update, rt, rof, deadl, acc, range, av, hv, sd) { RIFLECLASS, NOT_GUN, NOAMMO, rt, rof, 0, 0, update, 80, deadl, acc, 1, range, 200, av, hv, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
#define MONSTSPIT( impact, rof, deadl, clip, range, av, hv, sd) { MONSTERCLASS, NOT_GUN, AMMOMONST, AP_READY_KNIFE, rof, 0, 0, 250, (UINT8) (impact), deadl, 0, clip, range, 200, av, hv, sd, NO_WEAPON_SOUND, NO_WEAPON_SOUND, NO_WEAPON_SOUND }
|
|
|
|
// ranges are in world units, calculated by:
|
|
// 100 + real-range-in-metres/10
|
|
// then I scaled them down... I forget how much by!
|
|
|
|
// Accuracy is based on probability of shot being within 10cm of bullseye target on chest at 25m
|
|
// from Compendium of Modern Firearms (Edge of the Sword Vol 1)
|
|
|
|
// JA2 GOLD: reduced pistol ready time to 0, tweaked sniper rifle values and G11 range
|
|
WEAPONTYPE Weapon[ MAXITEMS ];
|
|
|
|
// MADD MARKER
|
|
|
|
MAGTYPE Magazine[MAXITEMS+1];
|
|
// =
|
|
//{
|
|
// // calibre, mag size, ammo type
|
|
// { AMMO9, 15, AMMO_REGULAR },
|
|
// { AMMO9, 30, AMMO_REGULAR },
|
|
// { AMMO9, 15, AMMO_AP },
|
|
// { AMMO9, 30, AMMO_AP },
|
|
// { AMMO9, 15, AMMO_HP },
|
|
// { AMMO9, 30, AMMO_HP },
|
|
// { AMMO38, 6, AMMO_REGULAR },
|
|
// { AMMO38, 6, AMMO_AP },
|
|
// { AMMO38, 6, AMMO_HP },
|
|
// { AMMO45, 7, AMMO_REGULAR },
|
|
// { AMMO45, 30, AMMO_REGULAR },
|
|
// { AMMO45, 7, AMMO_AP },
|
|
// { AMMO45, 30, AMMO_AP },
|
|
// { AMMO45, 7, AMMO_HP },
|
|
// { AMMO45, 30, AMMO_HP },
|
|
// { AMMO357, 6, AMMO_REGULAR },
|
|
// { AMMO357, 9, AMMO_REGULAR },
|
|
// { AMMO357, 6, AMMO_AP },
|
|
// { AMMO357, 9, AMMO_AP },
|
|
// { AMMO357, 6, AMMO_HP },
|
|
// { AMMO357, 9, AMMO_HP },
|
|
// { AMMO545, 30, AMMO_AP },
|
|
// { AMMO545, 30, AMMO_HP },
|
|
// { AMMO556, 30, AMMO_AP },
|
|
// { AMMO556, 30, AMMO_HP },
|
|
// { AMMO762W, 10, AMMO_AP },
|
|
// { AMMO762W, 30, AMMO_AP },
|
|
// { AMMO762W, 10, AMMO_HP },
|
|
// { AMMO762W, 30, AMMO_HP },
|
|
// { AMMO762N, 5, AMMO_AP },
|
|
// { AMMO762N, 20, AMMO_AP },
|
|
// { AMMO762N, 5, AMMO_HP },
|
|
// { AMMO762N, 20, AMMO_HP },
|
|
// { AMMO47, 50, AMMO_SUPER_AP },
|
|
// { AMMO57, 50, AMMO_AP },
|
|
// { AMMO57, 50, AMMO_HP },
|
|
// { AMMO12G, 7, AMMO_REGULAR },
|
|
// { AMMO12G, 7, AMMO_BUCKSHOT },
|
|
// { AMMOCAWS, 10, AMMO_SUPER_AP },
|
|
// { AMMOCAWS, 10, AMMO_BUCKSHOT },
|
|
// { AMMOROCKET, 5, AMMO_SUPER_AP },
|
|
// { AMMOROCKET, 5, AMMO_HE },
|
|
// { AMMOROCKET, 5, AMMO_HEAT },
|
|
// { AMMODART, 1, AMMO_SLEEP_DART },
|
|
// { AMMOFLAME, 5, AMMO_BUCKSHOT },
|
|
//// MADD MARKER
|
|
// { AMMO50, 11, AMMO_SUPER_AP },
|
|
// { AMMO9H, 20, AMMO_SUPER_AP },
|
|
// { AMMO9, 50, AMMO_REGULAR },
|
|
// { AMMO9, 50, AMMO_AP },
|
|
// { AMMO9, 50, AMMO_HP },
|
|
// { AMMO545, 75, AMMO_AP },
|
|
// { AMMO545, 75, AMMO_HP },
|
|
// { AMMO556, 200, AMMO_AP },
|
|
// { AMMO556, 200, AMMO_HP },
|
|
// { AMMO762N, 100, AMMO_AP },
|
|
// { AMMO762N, 100, AMMO_HP },
|
|
// { AMMO57, 20, AMMO_AP },
|
|
// { AMMO57, 20, AMMO_HP },
|
|
// { NOAMMO, 0, 0 }
|
|
//};
|
|
|
|
ARMOURTYPE Armour[MAXITEMS+1];// =
|
|
//{
|
|
// // Class Protection Degradation% Description
|
|
// // ------------- ---------- ------------ ----------------
|
|
// { ARMOURCLASS_VEST, 10, 25 }, /* Flak jacket */
|
|
// { ARMOURCLASS_VEST, 13, 20 }, /* Flak jacket w X */
|
|
// { ARMOURCLASS_VEST, 16, 15 }, /* Flak jacket w Y */
|
|
// { ARMOURCLASS_VEST, 15, 20 }, /* Kevlar jacket */
|
|
// { ARMOURCLASS_VEST, 19, 15 }, /* Kevlar jack w X */
|
|
// { ARMOURCLASS_VEST, 24, 10 }, /* Kevlar jack w Y */
|
|
// { ARMOURCLASS_VEST, 30, 15 }, /* Spectra jacket */
|
|
// { ARMOURCLASS_VEST, 36, 10 }, /* Spectra jack w X*/
|
|
// { ARMOURCLASS_VEST, 42, 5 }, /* Spectra jack w Y*/
|
|
// { ARMOURCLASS_LEGGINGS, 15, 20 }, /* Kevlar leggings */
|
|
// { ARMOURCLASS_LEGGINGS, 19, 15 }, /* Kevlar legs w X */
|
|
//
|
|
// { ARMOURCLASS_LEGGINGS, 24, 10 }, /* Kevlar legs w Y */
|
|
// { ARMOURCLASS_LEGGINGS, 30, 15 }, /* Spectra leggings*/
|
|
// { ARMOURCLASS_LEGGINGS, 36, 10 }, /* Spectra legs w X*/
|
|
// { ARMOURCLASS_LEGGINGS, 42, 5 }, /* Spectra legs w Y*/
|
|
// { ARMOURCLASS_HELMET, 10, 5 }, /* Steel helmet */
|
|
// { ARMOURCLASS_HELMET, 15, 20 }, /* Kevlar helmet */
|
|
// { ARMOURCLASS_HELMET, 19, 15 }, /* Kevlar helm w X */
|
|
// { ARMOURCLASS_HELMET, 24, 10 }, /* Kevlar helm w Y */
|
|
// { ARMOURCLASS_HELMET, 30, 15 }, /* Spectra helmet */
|
|
// { ARMOURCLASS_HELMET, 36, 10 }, /* Spectra helm w X*/
|
|
//
|
|
// { ARMOURCLASS_HELMET, 42, 5 }, /* Spectra helm w Y*/
|
|
// { ARMOURCLASS_PLATE, 15, 200 }, /* Ceramic plates */
|
|
// { ARMOURCLASS_MONST, 3, 0 }, /* Infant creature hide */
|
|
// { ARMOURCLASS_MONST, 5, 0 }, /* Young male creature hide */
|
|
// { ARMOURCLASS_MONST, 6, 0 }, /* Male creature hide */
|
|
// { ARMOURCLASS_MONST, 20, 0 }, /* Queen creature hide */
|
|
// { ARMOURCLASS_VEST, 2, 25 }, /* Leather jacket */
|
|
// { ARMOURCLASS_VEST, 12, 30 }, /* Leather jacket w kevlar */
|
|
// { ARMOURCLASS_VEST, 16, 25 }, /* Leather jacket w kevlar & compound 18 */
|
|
// { ARMOURCLASS_VEST, 19, 20 }, /* Leather jacket w kevlar & queen blood */
|
|
//
|
|
// { ARMOURCLASS_MONST, 7, 0 }, /* Young female creature hide */
|
|
// { ARMOURCLASS_MONST, 8, 0 }, /* Old female creature hide */
|
|
// { ARMOURCLASS_VEST, 1, 25 }, /* T-shirt */
|
|
// { ARMOURCLASS_VEST, 22, 20 }, /* Kevlar 2 jacket */
|
|
// { ARMOURCLASS_VEST, 27, 15 }, /* Kevlar 2 jack w X */
|
|
// { ARMOURCLASS_VEST, 32, 10 }, /* Kevlar 2 jack w Y */
|
|
//};
|
|
|
|
EXPLOSIVETYPE Explosive[MAXITEMS+1];// =
|
|
//{
|
|
// // Type Yield Yield2 Radius Volume Volatility Animation Description
|
|
// // ----- ------- ------ ------ ---------- --------- ------------------
|
|
// { EXPLOSV_STUN, 1, 70*2, 4, 0, 0, STUN_BLAST /* stun grenade */},
|
|
// { EXPLOSV_TEARGAS, 0, 20*2, 4, 0, 0, TARGAS_EXP /* tear gas grenade */},
|
|
// { EXPLOSV_MUSTGAS, 15, 40, 4, 0, 0, MUSTARD_EXP /* mustard gas grenade*/},
|
|
// { EXPLOSV_NORMAL, 15, 7, 3, 15, 1, BLAST_1 /* mini hand grenade */},
|
|
// { EXPLOSV_NORMAL, 25, 10, 4, 25, 1, BLAST_1 /* reg hand grenade */},
|
|
// { EXPLOSV_NORMAL, 40, 12, 5, 20, 10, BLAST_2 /* RDX */},
|
|
// { EXPLOSV_NORMAL, 50, 15, 5, 50, 2, BLAST_2 /* TNT (="explosives")*/},
|
|
// { EXPLOSV_NORMAL, 60, 15, 6, 60, 2, BLAST_2 /* HMX (=RDX+TNT) */},
|
|
// { EXPLOSV_NORMAL, 55, 15, 6, 55, 0, BLAST_2 /* C1 (=RDX+min oil) */},
|
|
// { EXPLOSV_NORMAL, 50, 22, 6, 50, 2, BLAST_2 /* mortar shell */},
|
|
//
|
|
// { EXPLOSV_NORMAL, 30, 30, 2, 30, 2, BLAST_1 /* mine */},
|
|
// { EXPLOSV_NORMAL, 65, 30, 7, 65, 0, BLAST_1 /* C4 ("plastique") */},
|
|
// { EXPLOSV_FLARE, 0, 0, 10, 0, 0, BLAST_1 /* trip flare */},
|
|
// { EXPLOSV_NOISE, 0, 0, 50, 50, 0, BLAST_1 /* trip klaxon */},
|
|
// { EXPLOSV_NORMAL, 20, 0, 1, 20, 0, BLAST_1 /* shaped charge */},
|
|
// { EXPLOSV_FLARE, 0, 0, 10, 0, 0, BLAST_1, /* break light */},
|
|
// { EXPLOSV_NORMAL, 25, 5, 4, 25, 1, BLAST_1, /* GL grenade */},
|
|
// { EXPLOSV_TEARGAS, 0, 20*2, 3, 0, 0, TARGAS_EXP, /* GL tear gas grenade*/},
|
|
// { EXPLOSV_STUN, 1, 60*2, 4, 0, 0, STUN_BLAST, /* GL stun grenade */},
|
|
// { EXPLOSV_SMOKE, 0, 0, 3, 0, 0, SMOKE_EXP, /* GL smoke grenade */},
|
|
//
|
|
// { EXPLOSV_SMOKE, 0, 0, 4, 0, 0, SMOKE_EXP, /* smoke grenade */},
|
|
// { EXPLOSV_NORMAL, 60, 20, 6, 60, 2, BLAST_2, /* Tank Shell */},
|
|
// { EXPLOSV_NORMAL, 100, 0, 0, 0, 0, BLAST_1, /* Fake structure igniter*/},
|
|
// { EXPLOSV_NORMAL, 100, 0, 1, 0, 0, BLAST_1, /* creature cocktail */},
|
|
// { EXPLOSV_NORMAL, 50, 10, 5, 50, 2, BLAST_2, /* fake struct explosion*/},
|
|
// { EXPLOSV_NORMAL, 50, 10, 5, 50, 2, BLAST_3, /* fake vehicle explosion*/},
|
|
// { EXPLOSV_TEARGAS, 0, 40*2, 4, 0, 0, TARGAS_EXP /* big tear gas */},
|
|
// { EXPLOSV_CREATUREGAS, 5, 0, 1, 0, 0, NO_BLAST /* small creature gas*/},
|
|
// { EXPLOSV_CREATUREGAS, 8, 0, 3, 0, 0, NO_BLAST /* big creature gas*/},
|
|
// { EXPLOSV_CREATUREGAS, 0, 0, 0, 0, 0, NO_BLAST /* vry sm creature gas*/},
|
|
// { EXPLOSV_NORMAL, 55, 15, 6, 55, 0, BLAST_2 /* RPG_HE_ROCKET */},
|
|
// { EXPLOSV_NORMAL, 100, 10, 3, 75, 0, BLAST_1 /* RPG_AP_ROCKET */},
|
|
// { EXPLOSV_NORMAL, 25, 200, 8, 65, 0, BLAST_2 /* RPG_FRAG_ROCKET */},
|
|
//};
|
|
|
|
|
|
CHAR8 gzBurstSndStrings[MAXITEMS*2][128];// =
|
|
//{
|
|
// "", // NOAMMO
|
|
// "", // 38
|
|
// "9mm Burst ", // 9mm
|
|
// "45 Caliber Burst ", // 45
|
|
// "", // 357
|
|
// "", // 12G
|
|
// "Shotgun Burst ", // CAWS
|
|
// "5,45 Burst ", // 5.45
|
|
// "5,56 Burst ", // 5.56
|
|
// "7,62 NATO Burst ", // 7,62 N
|
|
// "7,62 WP Burst ", // 7,62 W
|
|
// "4,7 Caliber Burst ", // 4.7
|
|
// "5,7 Burst ", // 5,7
|
|
// "", // Monster
|
|
// "RL Automatic ", // Rocket
|
|
// "", // Dart
|
|
// "", // Flame (unused)
|
|
// "", // 50 Cal
|
|
// "", // 9MM HEavy
|
|
//};
|
|
|
|
AMMOTYPE AmmoTypes[MAXITEMS];
|
|
|
|
// the amount of momentum reduction for the head, torso, and legs
|
|
// used to determine whether the bullet will go through someone
|
|
UINT8 BodyImpactReduction[4] = { 0, 15, 30, 23 };
|
|
|
|
|
|
|
|
|
|
enum
|
|
{
|
|
WEAPON_ELEMENT_NONE = 0,
|
|
WEAPON_ELEMENT_WEAPONLIST,
|
|
WEAPON_ELEMENT_WEAPON,
|
|
WEAPON_ELEMENT_WEAPON_PROPERY,
|
|
}
|
|
typedef WEP_PARSE_STAGE;
|
|
|
|
struct
|
|
{
|
|
WEP_PARSE_STAGE curElement;
|
|
|
|
CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1];
|
|
WEAPONTYPE curWeapon;
|
|
WEAPONTYPE * curWeaponList;
|
|
UINT32 maxWeapons;
|
|
|
|
UINT32 currentDepth;
|
|
UINT32 maxReadDepth;
|
|
}
|
|
typedef weaponParseData;
|
|
|
|
|
|
static void XMLCALL
|
|
weaponStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts)
|
|
{
|
|
weaponParseData * pData = (weaponParseData *)userData;
|
|
|
|
if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element?
|
|
{
|
|
if(strcmp(name, "WEAPONLIST") == 0 && pData->curElement == WEAPON_ELEMENT_NONE)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPONLIST;
|
|
|
|
memset(pData->curWeaponList,0,sizeof(WEAPONTYPE)*pData->maxWeapons);
|
|
|
|
pData->maxReadDepth++; //we are not skipping this element
|
|
}
|
|
else if(strcmp(name, "WEAPON") == 0 && pData->curElement == WEAPON_ELEMENT_WEAPONLIST)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
|
|
memset(&pData->curWeapon,0,sizeof(WEAPONTYPE));
|
|
|
|
pData->maxReadDepth++; //we are not skipping this element
|
|
}
|
|
else if(pData->curElement == WEAPON_ELEMENT_WEAPON &&
|
|
(strcmp(name, "uiIndex") == 0 ||
|
|
strcmp(name, "szWeaponName") == 0 ||
|
|
strcmp(name, "ubWeaponClass") == 0 ||
|
|
strcmp(name, "ubWeaponType") == 0 ||
|
|
strcmp(name, "ubCalibre") == 0 ||
|
|
strcmp(name, "ubReadyTime") == 0 ||
|
|
strcmp(name, "ubShotsPer4Turns") == 0 ||
|
|
strcmp(name, "ubShotsPerBurst") == 0 ||
|
|
strcmp(name, "ubBurstPenalty") == 0 ||
|
|
strcmp(name, "ubBulletSpeed") == 0 ||
|
|
strcmp(name, "ubImpact") == 0 ||
|
|
strcmp(name, "ubDeadliness") == 0 ||
|
|
strcmp(name, "bAccuracy") == 0 ||
|
|
strcmp(name, "ubMagSize") == 0 ||
|
|
strcmp(name, "usRange") == 0 ||
|
|
strcmp(name, "usReloadDelay") == 0 ||
|
|
strcmp(name, "ubAttackVolume") == 0 ||
|
|
strcmp(name, "ubHitVolume") == 0 ||
|
|
strcmp(name, "sSound") == 0 ||
|
|
strcmp(name, "sBurstSound") == 0 ||
|
|
strcmp(name, "sSilencedBurstSound") == 0 ||
|
|
strcmp(name, "sReloadSound") == 0 ||
|
|
strcmp(name, "sLocknLoadSound") == 0 ||
|
|
strcmp(name, "bBurstAP") == 0 ||
|
|
strcmp(name, "bAutofireShotsPerFiveAP") == 0 ||
|
|
strcmp(name, "APsToReload") == 0 ||
|
|
strcmp(name, "SwapClips") == 0 ||
|
|
strcmp(name, "AutoPenalty") == 0 ||
|
|
strcmp(name, "NoSemiAuto") == 0 ||
|
|
strcmp(name, "MaxDistForMessyDeath") == 0 ||
|
|
strcmp(name, "SilencedSound") == 0 || // Lesh: add new field (OR operand)
|
|
strcmp(name, "BurstAniDelay") == 0 || // Lesh: add new field (field itself)
|
|
strcmp(name, "APsToReloadManually") == 0 ||
|
|
strcmp(name, "ManualReloadSound") == 0)
|
|
)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON_PROPERY;
|
|
|
|
pData->maxReadDepth++; //we are not skipping this element
|
|
}
|
|
|
|
pData->szCharData[0] = '\0';
|
|
}
|
|
|
|
pData->currentDepth++;
|
|
|
|
}
|
|
|
|
static void XMLCALL
|
|
weaponCharacterDataHandle(void *userData, const XML_Char *str, int len)
|
|
{
|
|
weaponParseData * pData = (weaponParseData *)userData;
|
|
|
|
if( (pData->currentDepth <= pData->maxReadDepth) &&
|
|
(strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH)
|
|
){
|
|
strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData)));
|
|
}
|
|
}
|
|
|
|
|
|
static void XMLCALL
|
|
weaponEndElementHandle(void *userData, const XML_Char *name)
|
|
{
|
|
weaponParseData * pData = (weaponParseData *)userData;
|
|
|
|
if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading
|
|
{
|
|
if(strcmp(name, "WEAPONLIST") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_NONE;
|
|
}
|
|
else if(strcmp(name, "WEAPON") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPONLIST;
|
|
|
|
if(pData->curWeapon.uiIndex < pData->maxWeapons)
|
|
{
|
|
pData->curWeaponList[pData->curWeapon.uiIndex] = pData->curWeapon; //write the weapon into the table
|
|
}
|
|
}
|
|
else if(strcmp(name, "uiIndex") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.uiIndex = atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "szWeaponName") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
|
|
if(MAX_WEAPON_NAME_LENGTH >= strlen(pData->szCharData))
|
|
strcpy(pData->curWeapon.szWeaponName,pData->szCharData);
|
|
else
|
|
{
|
|
strncpy(pData->curWeapon.szWeaponName,pData->szCharData,MAX_WEAPON_NAME_LENGTH);
|
|
pData->curWeapon.szWeaponName[MAX_WEAPON_NAME_LENGTH] = '\0';
|
|
}
|
|
}
|
|
else if(strcmp(name, "ubWeaponClass") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubWeaponClass = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubWeaponType") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubWeaponType = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubCalibre") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubCalibre = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubReadyTime") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubReadyTime = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubShotsPer4Turns") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubShotsPer4Turns = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubShotsPerBurst") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubShotsPerBurst = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubBurstPenalty") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubBurstPenalty = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubBulletSpeed") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubBulletSpeed = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubImpact") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubImpact = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubDeadliness") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubDeadliness = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "bAccuracy") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.bAccuracy = (INT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubMagSize") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubMagSize = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "usRange") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.usRange = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "usReloadDelay") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.usReloadDelay = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubAttackVolume") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubAttackVolume = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ubHitVolume") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ubHitVolume = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "sSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.sSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "sBurstSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.sBurstSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "sSilencedBurstSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.sSilencedBurstSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "sReloadSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.sReloadSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "sLocknLoadSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.sLocknLoadSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "bBurstAP") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.bBurstAP = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "bAutofireShotsPerFiveAP") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.bAutofireShotsPerFiveAP = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "APsToReload") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.APsToReload = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "SilencedSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.silencedSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "SwapClips") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.swapClips = (BOOLEAN) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "MaxDistForMessyDeath") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.maxdistformessydeath = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "AutoPenalty") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.AutoPenalty = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "NoSemiAuto") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.NoSemiAuto = (BOOLEAN) atol(pData->szCharData);
|
|
}
|
|
// Lesh: xmlreader must handle new field
|
|
else if(strcmp(name, "BurstAniDelay") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.sAniDelay = (INT16) atol(pData->szCharData);
|
|
}
|
|
// Lesh: end
|
|
else if(strcmp(name, "APsToReloadManually") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.APsToReloadManually = (UINT8) atol(pData->szCharData);
|
|
}
|
|
else if(strcmp(name, "ManualReloadSound") == 0)
|
|
{
|
|
pData->curElement = WEAPON_ELEMENT_WEAPON;
|
|
pData->curWeapon.ManualReloadSound = (UINT16) atol(pData->szCharData);
|
|
}
|
|
|
|
pData->maxReadDepth--;
|
|
}
|
|
|
|
pData->currentDepth--;
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOLEAN ReadInWeaponStats(STR fileName)
|
|
{
|
|
HWFILE hFile;
|
|
UINT32 uiBytesRead;
|
|
UINT32 uiFSize;
|
|
CHAR8 * lpcBuffer;
|
|
XML_Parser parser = XML_ParserCreate(NULL);
|
|
|
|
weaponParseData pData;
|
|
|
|
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading weapons.xml" );
|
|
|
|
// Open weapons file
|
|
hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE );
|
|
if ( !hFile )
|
|
return( FALSE );
|
|
|
|
uiFSize = FileGetSize(hFile);
|
|
lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1);
|
|
|
|
//Read in block
|
|
if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) )
|
|
{
|
|
MemFree(lpcBuffer);
|
|
return( FALSE );
|
|
}
|
|
|
|
lpcBuffer[uiFSize] = 0; //add a null terminator
|
|
|
|
FileClose( hFile );
|
|
|
|
|
|
XML_SetElementHandler(parser, weaponStartElementHandle, weaponEndElementHandle);
|
|
XML_SetCharacterDataHandler(parser, weaponCharacterDataHandle);
|
|
|
|
|
|
memset(&pData,0,sizeof(pData));
|
|
pData.curWeaponList = Weapon;
|
|
pData.maxWeapons = MAXITEMS;
|
|
|
|
XML_SetUserData(parser, &pData);
|
|
|
|
|
|
if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE))
|
|
{
|
|
CHAR8 errorBuf[511];
|
|
|
|
sprintf(errorBuf, "XML Parser Error in Weapons.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
|
|
LiveMessage(errorBuf);
|
|
|
|
MemFree(lpcBuffer);
|
|
return FALSE;
|
|
}
|
|
|
|
MemFree(lpcBuffer);
|
|
|
|
#ifdef JA2TESTVERSION
|
|
//Debug code; make sure that what we got from the file is the same as what's there
|
|
// Open a new file
|
|
hFile = FileOpen( "TABLEDATA\\~Weapons out.xml", FILE_ACCESS_WRITE | FILE_CREATE_ALWAYS, FALSE );
|
|
if ( !hFile )
|
|
return( FALSE );
|
|
|
|
{
|
|
UINT32 cnt;
|
|
|
|
FilePrintf(hFile,"<WEAPONLIST>\r\n");
|
|
for(cnt = 0;cnt < MAXITEMS;cnt++)
|
|
{
|
|
STR8 szRemainder = Weapon[cnt].szWeaponName; //the remaining string to be output (for making valid XML)
|
|
|
|
FilePrintf(hFile,"\t<WEAPON>\r\n");
|
|
FilePrintf(hFile,"\t\t<uiIndex>%d</uiIndex>\r\n", Weapon[cnt].uiIndex);
|
|
|
|
FilePrintf(hFile,"\t\t<szWeaponName>");
|
|
while(szRemainder[0] != '\0')
|
|
{
|
|
UINT32 uiCharLoc = strcspn(szRemainder,"&<>\'\"\0");
|
|
CHAR8 invChar = szRemainder[uiCharLoc];
|
|
|
|
if(uiCharLoc)
|
|
{
|
|
szRemainder[uiCharLoc] = '\0';
|
|
FilePrintf(hFile,"%s",szRemainder);
|
|
szRemainder[uiCharLoc] = invChar;
|
|
}
|
|
|
|
szRemainder += uiCharLoc;
|
|
|
|
switch(invChar)
|
|
{
|
|
case '&':
|
|
FilePrintf(hFile,"&");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '<':
|
|
FilePrintf(hFile,"<");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '>':
|
|
FilePrintf(hFile,">");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '\'':
|
|
FilePrintf(hFile,"'");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '\"':
|
|
FilePrintf(hFile,""");
|
|
szRemainder++;
|
|
break;
|
|
}
|
|
}
|
|
FilePrintf(hFile,"</szWeaponName>\r\n");
|
|
|
|
FilePrintf(hFile,"\t\t<ubWeaponClass>%d</ubWeaponClass>\r\n", Weapon[cnt].ubWeaponClass);
|
|
FilePrintf(hFile,"\t\t<ubWeaponType>%d</ubWeaponType>\r\n", Weapon[cnt].ubWeaponType);
|
|
FilePrintf(hFile,"\t\t<ubCalibre>%d</ubCalibre>\r\n", Weapon[cnt].ubCalibre);
|
|
FilePrintf(hFile,"\t\t<ubReadyTime>%d</ubReadyTime>\r\n", Weapon[cnt].ubReadyTime);
|
|
FilePrintf(hFile,"\t\t<ubShotsPer4Turns>%d</ubShotsPer4Turns>\r\n", Weapon[cnt].ubShotsPer4Turns);
|
|
FilePrintf(hFile,"\t\t<ubShotsPerBurst>%d</ubShotsPerBurst>\r\n", Weapon[cnt].ubShotsPerBurst);
|
|
FilePrintf(hFile,"\t\t<ubBurstPenalty>%d</ubBurstPenalty>\r\n", Weapon[cnt].ubBurstPenalty);
|
|
FilePrintf(hFile,"\t\t<ubBulletSpeed>%d</ubBulletSpeed>\r\n", Weapon[cnt].ubBulletSpeed);
|
|
FilePrintf(hFile,"\t\t<ubImpact>%d</ubImpact>\r\n", Weapon[cnt].ubImpact);
|
|
FilePrintf(hFile,"\t\t<ubDeadliness>%d</ubDeadliness>\r\n", Weapon[cnt].ubDeadliness);
|
|
FilePrintf(hFile,"\t\t<bAccuracy>%d</bAccuracy>\r\n", Weapon[cnt].bAccuracy);
|
|
FilePrintf(hFile,"\t\t<ubMagSize>%d</ubMagSize>\r\n", Weapon[cnt].ubMagSize);
|
|
FilePrintf(hFile,"\t\t<usRange>%d</usRange>\r\n", Weapon[cnt].usRange);
|
|
FilePrintf(hFile,"\t\t<usReloadDelay>%d</usReloadDelay>\r\n", Weapon[cnt].usReloadDelay);
|
|
FilePrintf(hFile,"\t\t<ubAttackVolume>%d</ubAttackVolume>\r\n", Weapon[cnt].ubAttackVolume);
|
|
FilePrintf(hFile,"\t\t<ubHitVolume>%d</ubHitVolume>\r\n", Weapon[cnt].ubHitVolume);
|
|
FilePrintf(hFile,"\t\t<sSound>%d</sSound>\r\n", Weapon[cnt].sSound);
|
|
FilePrintf(hFile,"\t\t<sBurstSound>%d</sBurstSound>\r\n", Weapon[cnt].sBurstSound);
|
|
FilePrintf(hFile,"\t\t<sSilencedBurstSound>%d</sSilencedBurstSound>\r\n", Weapon[cnt].sSilencedBurstSound);
|
|
FilePrintf(hFile,"\t\t<sReloadSound>%d</sReloadSound>\r\n", Weapon[cnt].sReloadSound);
|
|
FilePrintf(hFile,"\t\t<sLocknLoadSound>%d</sLocknLoadSound>\r\n", Weapon[cnt].sLocknLoadSound);
|
|
FilePrintf(hFile,"\t\t<bBurstAP>%d</bBurstAP>\r\n", Weapon[cnt].bBurstAP);
|
|
FilePrintf(hFile,"\t\t<bAutofireShotsPerFiveAP>%d</bAutofireShotsPerFiveAP>\r\n", Weapon[cnt].bAutofireShotsPerFiveAP);
|
|
FilePrintf(hFile,"\t\t<APsToReloadManually>%d</APsToReloadManually>\r\n", Weapon[cnt].APsToReloadManually);
|
|
FilePrintf(hFile,"\t\t<ManualReloadSound>%d</ManualReloadSound>\r\n", Weapon[cnt].ManualReloadSound);
|
|
FilePrintf(hFile,"\t</WEAPON>\r\n");
|
|
}
|
|
FilePrintf(hFile,"</WEAPONLIST>\r\n");
|
|
}
|
|
FileClose( hFile );
|
|
#endif
|
|
|
|
XML_ParserFree(parser);
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
BOOLEAN WriteWeaponStats()
|
|
{
|
|
HWFILE hFile;
|
|
|
|
//Debug code; make sure that what we got from the file is the same as what's there
|
|
// Open a new file
|
|
hFile = FileOpen( "TABLEDATA\\Weapons out.xml", FILE_ACCESS_WRITE | FILE_CREATE_ALWAYS, FALSE );
|
|
if ( !hFile )
|
|
return( FALSE );
|
|
|
|
{
|
|
UINT32 cnt;
|
|
|
|
FilePrintf(hFile,"<WEAPONLIST>\r\n");
|
|
for(cnt = 0;cnt < MAXITEMS;cnt++)
|
|
{
|
|
CHAR8 * szRemainder = Weapon[cnt].szWeaponName; //the remaining string to be output (for making valid XML)
|
|
|
|
FilePrintf(hFile,"\t<WEAPON>\r\n");
|
|
FilePrintf(hFile,"\t\t<uiIndex>%d</uiIndex>\r\n", cnt);//Weapon[cnt].uiIndex);
|
|
|
|
FilePrintf(hFile,"\t\t<szWeaponName>");
|
|
while(szRemainder[0] != '\0')
|
|
{
|
|
UINT32 uiCharLoc = strcspn(szRemainder,"&<>\'\"\0");
|
|
INT8 invChar = szRemainder[uiCharLoc];
|
|
|
|
if(uiCharLoc)
|
|
{
|
|
szRemainder[uiCharLoc] = '\0';
|
|
FilePrintf(hFile,"%s",szRemainder);
|
|
szRemainder[uiCharLoc] = invChar;
|
|
}
|
|
|
|
szRemainder += uiCharLoc;
|
|
|
|
switch(invChar)
|
|
{
|
|
case '&':
|
|
FilePrintf(hFile,"&");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '<':
|
|
FilePrintf(hFile,"<");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '>':
|
|
FilePrintf(hFile,">");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '\'':
|
|
FilePrintf(hFile,"'");
|
|
szRemainder++;
|
|
break;
|
|
|
|
case '\"':
|
|
FilePrintf(hFile,""");
|
|
szRemainder++;
|
|
break;
|
|
}
|
|
}
|
|
FilePrintf(hFile,"</szWeaponName>\r\n");
|
|
|
|
FilePrintf(hFile,"\t\t<ubWeaponClass>%d</ubWeaponClass>\r\n", Weapon[cnt].ubWeaponClass);
|
|
FilePrintf(hFile,"\t\t<ubWeaponType>%d</ubWeaponType>\r\n", Weapon[cnt].ubWeaponType);
|
|
FilePrintf(hFile,"\t\t<ubCalibre>%d</ubCalibre>\r\n", Weapon[cnt].ubCalibre);
|
|
FilePrintf(hFile,"\t\t<ubReadyTime>%d</ubReadyTime>\r\n", Weapon[cnt].ubReadyTime);
|
|
FilePrintf(hFile,"\t\t<ubShotsPer4Turns>%d</ubShotsPer4Turns>\r\n", Weapon[cnt].ubShotsPer4Turns);
|
|
FilePrintf(hFile,"\t\t<ubShotsPerBurst>%d</ubShotsPerBurst>\r\n", Weapon[cnt].ubShotsPerBurst);
|
|
FilePrintf(hFile,"\t\t<ubBurstPenalty>%d</ubBurstPenalty>\r\n", Weapon[cnt].ubBurstPenalty);
|
|
FilePrintf(hFile,"\t\t<ubBulletSpeed>%d</ubBulletSpeed>\r\n", Weapon[cnt].ubBulletSpeed);
|
|
FilePrintf(hFile,"\t\t<ubImpact>%d</ubImpact>\r\n", Weapon[cnt].ubImpact);
|
|
FilePrintf(hFile,"\t\t<ubDeadliness>%d</ubDeadliness>\r\n", Weapon[cnt].ubDeadliness);
|
|
FilePrintf(hFile,"\t\t<bAccuracy>%d</bAccuracy>\r\n", Weapon[cnt].bAccuracy);
|
|
FilePrintf(hFile,"\t\t<ubMagSize>%d</ubMagSize>\r\n", Weapon[cnt].ubMagSize);
|
|
FilePrintf(hFile,"\t\t<usRange>%d</usRange>\r\n", Weapon[cnt].usRange);
|
|
FilePrintf(hFile,"\t\t<usReloadDelay>%d</usReloadDelay>\r\n", Weapon[cnt].usReloadDelay);
|
|
FilePrintf(hFile,"\t\t<BurstAniDelay>%d</BurstAniDelay>\r\n", Weapon[cnt].sAniDelay);
|
|
FilePrintf(hFile,"\t\t<ubAttackVolume>%d</ubAttackVolume>\r\n", Weapon[cnt].ubAttackVolume);
|
|
FilePrintf(hFile,"\t\t<ubHitVolume>%d</ubHitVolume>\r\n", Weapon[cnt].ubHitVolume);
|
|
FilePrintf(hFile,"\t\t<sSound>%d</sSound>\r\n", Weapon[cnt].sSound);
|
|
FilePrintf(hFile,"\t\t<sBurstSound>%d</sBurstSound>\r\n", Weapon[cnt].sBurstSound);
|
|
FilePrintf(hFile,"\t\t<sSilencedBurstSound>%d</sSilencedBurstSound>\r\n", Weapon[cnt].sSilencedBurstSound);
|
|
FilePrintf(hFile,"\t\t<sReloadSound>%d</sReloadSound>\r\n", Weapon[cnt].sReloadSound);
|
|
FilePrintf(hFile,"\t\t<sLocknLoadSound>%d</sLocknLoadSound>\r\n", Weapon[cnt].sLocknLoadSound);
|
|
FilePrintf(hFile,"\t\t<SilencedSound>%d</SilencedSound>\r\n", Weapon[cnt].silencedSound );
|
|
FilePrintf(hFile,"\t\t<bBurstAP>%d</bBurstAP>\r\n", Weapon[cnt].bBurstAP);
|
|
FilePrintf(hFile,"\t\t<bAutofireShotsPerFiveAP>%d</bAutofireShotsPerFiveAP>\r\n", Weapon[cnt].bAutofireShotsPerFiveAP);
|
|
FilePrintf(hFile,"\t\t<APsToReload>%d</APsToReload>\r\n", Weapon[cnt].APsToReload);
|
|
FilePrintf(hFile,"\t\t<SwapClips>%d</SwapClips>\r\n", Weapon[cnt].swapClips );
|
|
FilePrintf(hFile,"\t\t<MaxDistForMessyDeath>%d</MaxDistForMessyDeath>\r\n", Weapon[cnt].maxdistformessydeath);
|
|
FilePrintf(hFile,"\t\t<AutoPenalty>%d</AutoPenalty>\r\n", Weapon[cnt].AutoPenalty);
|
|
FilePrintf(hFile,"\t\t<NoSemiAuto>%d</NoSemiAuto>\r\n", Weapon[cnt].NoSemiAuto);
|
|
|
|
|
|
|
|
FilePrintf(hFile,"\t</WEAPON>\r\n");
|
|
}
|
|
FilePrintf(hFile,"</WEAPONLIST>\r\n");
|
|
}
|
|
FileClose( hFile );
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
UINT16 GunRange( OBJECTTYPE * pObj )
|
|
{
|
|
//INT8 bAttachPos;
|
|
UINT16 rng;
|
|
|
|
if ( Item[ pObj->usItem ].usItemClass & IC_WEAPON )
|
|
{
|
|
|
|
|
|
//bAttachPos = FindAttachment( pObj, GUN_BARREL_EXTENDER );
|
|
|
|
//if ( bAttachPos == ITEM_NOT_FOUND )
|
|
//{
|
|
// rng = ( Weapon[ pObj->usItem ].usRange );
|
|
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: base rng=%d",rng));
|
|
//}
|
|
//else
|
|
//{
|
|
// rng = ( Weapon[ pObj->usItem ].usRange + (GUN_BARREL_RANGE_BONUS * WEAPON_STATUS_MOD(pObj->bAttachStatus[ bAttachPos ]) / 100 ) );
|
|
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: Found extender rng=%d",rng));
|
|
//}
|
|
|
|
//if ( Weapon [ pObj->usItem ].ubWeaponType == GUN_SHOTGUN && (*pObj)[0]->data.gun.ubGunAmmoType != AMMO_BUCKSHOT )
|
|
//{
|
|
// rng += SOLID_SLUG_RANGE_BONUS;
|
|
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: Found solid slug rng=%d",rng));
|
|
//}
|
|
//else
|
|
//{
|
|
// bAttachPos = FindAttachment( pObj, DUCKBILL );
|
|
// if ( bAttachPos != ITEM_NOT_FOUND )
|
|
// {
|
|
// rng += DUCKBILL_RANGE_BONUS * (WEAPON_STATUS_MOD(pObj->bAttachStatus[ bAttachPos ]) / 100 );
|
|
// DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("GunRange: Found duckbill rng=%d",rng));
|
|
// }
|
|
//}
|
|
|
|
|
|
// Snap: attachment status is factored into the range bonus calculation
|
|
rng = Weapon[ pObj->usItem ].usRange + GetRangeBonus(pObj);
|
|
|
|
return rng;
|
|
}
|
|
else
|
|
{
|
|
// return a minimal value of 1 tile
|
|
return( CELL_X_SIZE );
|
|
}
|
|
}
|
|
|
|
INT32 EffectiveArmour( OBJECTTYPE * pObj )
|
|
{
|
|
INT32 iValue;
|
|
|
|
if (pObj == NULL || Item[pObj->usItem].usItemClass != IC_ARMOUR)
|
|
{
|
|
return( 0 );
|
|
}
|
|
iValue = Armour[ Item[pObj->usItem].ubClassIndex ].ubProtection;
|
|
iValue = iValue * (*pObj)[0]->data.objectStatus * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000;
|
|
|
|
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
|
|
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
|
|
{
|
|
INT32 iValue2;
|
|
|
|
iValue2 = Armour[ Item[ iter->usItem ].ubClassIndex ].ubProtection;
|
|
iValue2 = iValue2 * (*iter)[0]->data.objectStatus * Armour[ Item[ iter->usItem ].ubClassIndex ].ubCoverage / 10000;
|
|
|
|
iValue += iValue2;
|
|
}
|
|
}
|
|
return( max(iValue,1) );
|
|
}
|
|
|
|
INT32 ArmourPercent( SOLDIERTYPE * pSoldier )
|
|
{
|
|
INT32 iVest, iHelmet, iLeg;
|
|
INT32 iDivideValue;
|
|
|
|
if (pSoldier->inv[VESTPOS].exists() == true)
|
|
{
|
|
iVest = EffectiveArmour( &(pSoldier->inv[VESTPOS]) );
|
|
iDivideValue = ( ( Armour[ Item[ SPECTRA_VEST_18 ].ubClassIndex ].ubProtection * Armour[ Item[ SPECTRA_VEST_18 ].ubClassIndex ].ubCoverage ) + ( Armour[ Item[ CERAMIC_PLATES ].ubClassIndex ].ubProtection * Armour[ Item[ CERAMIC_PLATES ].ubClassIndex ].ubCoverage ) );
|
|
|
|
// WANNE: Just to be on the save side
|
|
if (iDivideValue > 0)
|
|
{
|
|
// convert to % of best; ignoring bug-treated stuff
|
|
iVest = 6500 * iVest / iDivideValue;
|
|
}
|
|
else
|
|
{
|
|
iVest = 65 * iVest / ( Armour[ Item[ SPECTRA_VEST_18 ].ubClassIndex ].ubProtection + Armour[ Item[ CERAMIC_PLATES ].ubClassIndex ].ubProtection );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iVest = 0;
|
|
}
|
|
|
|
if (pSoldier->inv[HELMETPOS].exists() == true)
|
|
{
|
|
iHelmet = EffectiveArmour( &(pSoldier->inv[HELMETPOS]) );
|
|
iDivideValue = ( Armour[ Item[ SPECTRA_HELMET_18 ].ubClassIndex ].ubProtection * Armour[ Item[ SPECTRA_HELMET_18 ].ubClassIndex ].ubCoverage );
|
|
|
|
// WANNE: Just to be on the save side
|
|
if (iDivideValue > 0)
|
|
{
|
|
// convert to % of best; ignoring bug-treated stuff
|
|
iHelmet = 1500 * iHelmet / iDivideValue;
|
|
}
|
|
else
|
|
{
|
|
iHelmet = 15 * iHelmet / Armour[ Item[ SPECTRA_HELMET_18 ].ubClassIndex ].ubProtection;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iHelmet = 0;
|
|
}
|
|
|
|
if (pSoldier->inv[LEGPOS].exists() == true)
|
|
{
|
|
iLeg = EffectiveArmour( &(pSoldier->inv[LEGPOS]) );
|
|
iDivideValue = ( Armour[ Item[ SPECTRA_LEGGINGS_18 ].ubClassIndex ].ubProtection * Armour[ Item[ SPECTRA_LEGGINGS_18 ].ubClassIndex ].ubCoverage );
|
|
|
|
// WANNE: Just to be on the save side
|
|
if (iDivideValue > 0)
|
|
{
|
|
// convert to % of best; ignoring bug-treated stuff
|
|
iLeg = 2500 * iLeg / iDivideValue;
|
|
}
|
|
else
|
|
{
|
|
iLeg = 25 * iLeg / Armour[ Item[ SPECTRA_LEGGINGS_18 ].ubClassIndex ].ubProtection;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iLeg = 0;
|
|
}
|
|
return( (iHelmet + iVest + iLeg) );
|
|
}
|
|
|
|
INT32 ExplosiveEffectiveArmour( OBJECTTYPE * pObj )
|
|
{
|
|
INT32 iValue;
|
|
|
|
if (pObj == NULL || Item[pObj->usItem].usItemClass != IC_ARMOUR)
|
|
{
|
|
return( 0 );
|
|
}
|
|
iValue = Armour[ Item[pObj->usItem].ubClassIndex ].ubProtection;
|
|
iValue = iValue * (*pObj)[0]->data.objectStatus * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000;
|
|
if ( Item[pObj->usItem].flakjacket )
|
|
{
|
|
// increase value for flak jackets!
|
|
iValue *= 3;
|
|
}
|
|
|
|
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
|
|
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
|
|
{
|
|
INT32 iValue2;
|
|
|
|
iValue2 = Armour[ Item[ iter->usItem ].ubClassIndex ].ubProtection;
|
|
iValue2 *= (*iter)[0]->data.objectStatus * Armour[ Item[ iter->usItem ].ubClassIndex ].ubCoverage / 10000;
|
|
|
|
iValue += iValue2;
|
|
}
|
|
}
|
|
return( max(iValue,1) );
|
|
}
|
|
|
|
INT8 ArmourVersusExplosivesPercent( SOLDIERTYPE * pSoldier )
|
|
{
|
|
// returns the % damage reduction from grenades
|
|
INT32 iVest, iHelmet, iLeg;
|
|
|
|
if (pSoldier->inv[VESTPOS].exists() == true)
|
|
{
|
|
iVest = ExplosiveEffectiveArmour( &(pSoldier->inv[VESTPOS]) );
|
|
// convert to % of best; ignoring bug-treated stuff
|
|
iVest = __min( 65, 6500 * iVest / ( ( Armour[ Item[ SPECTRA_VEST_18 ].ubClassIndex ].ubProtection * Armour[ Item[ SPECTRA_VEST_18 ].ubClassIndex ].ubCoverage ) + ( Armour[ Item[ CERAMIC_PLATES ].ubClassIndex ].ubProtection * Armour[ Item[ CERAMIC_PLATES ].ubClassIndex ].ubCoverage ) ) );
|
|
}
|
|
else
|
|
{
|
|
iVest = 0;
|
|
}
|
|
|
|
if (pSoldier->inv[HELMETPOS].exists() == true)
|
|
{
|
|
iHelmet = ExplosiveEffectiveArmour( &(pSoldier->inv[HELMETPOS]) );
|
|
// convert to % of best; ignoring bug-treated stuff
|
|
iHelmet = __min( 15, 1500 * iHelmet / ( Armour[ Item[ SPECTRA_HELMET_18 ].ubClassIndex ].ubProtection * Armour[ Item[ SPECTRA_HELMET_18 ].ubClassIndex ].ubCoverage ) );
|
|
}
|
|
else
|
|
{
|
|
iHelmet = 0;
|
|
}
|
|
|
|
if (pSoldier->inv[LEGPOS].exists() == true)
|
|
{
|
|
iLeg = ExplosiveEffectiveArmour( &(pSoldier->inv[LEGPOS]) );
|
|
// convert to % of best; ignoring bug-treated stuff
|
|
iLeg = __min( 25, 2500 * iLeg / ( Armour[ Item[ SPECTRA_LEGGINGS_18 ].ubClassIndex ].ubProtection * Armour[ Item[ SPECTRA_LEGGINGS_18 ].ubClassIndex ].ubCoverage ) );
|
|
}
|
|
else
|
|
{
|
|
iLeg = 0;
|
|
}
|
|
return( (INT8) (iHelmet + iVest + iLeg) );
|
|
}
|
|
|
|
void AdjustImpactByHitLocation( INT32 iImpact, UINT8 ubHitLocation, INT32 * piNewImpact, INT32 * piImpactForCrits )
|
|
{
|
|
switch( ubHitLocation )
|
|
{
|
|
case AIM_SHOT_HEAD:
|
|
// 1.5x damage from successful hits to the head!
|
|
*piImpactForCrits = HEAD_DAMAGE_ADJUSTMENT( iImpact );
|
|
*piNewImpact = *piImpactForCrits;
|
|
break;
|
|
case AIM_SHOT_LEGS:
|
|
// half damage for determining critical hits
|
|
// quarter actual damage
|
|
*piImpactForCrits = LEGS_DAMAGE_ADJUSTMENT( iImpact );
|
|
*piNewImpact = LEGS_DAMAGE_ADJUSTMENT( *piImpactForCrits );
|
|
break;
|
|
default:
|
|
*piImpactForCrits = iImpact;
|
|
*piNewImpact = iImpact;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// #define TESTGUNJAM
|
|
|
|
//rain
|
|
extern INT8 gbCurrentRainIntensity;
|
|
//end rain
|
|
BOOLEAN CheckForGunJam( SOLDIERTYPE * pSoldier )
|
|
{
|
|
OBJECTTYPE * pObj;
|
|
INT32 iChance, iResult;
|
|
|
|
// should jams apply to enemies?
|
|
if (pSoldier->flags.uiStatusFlags & SOLDIER_PC)
|
|
{
|
|
if ( Item[pSoldier->usAttackingWeapon].usItemClass == IC_GUN && !EXPLOSIVE_GUN( pSoldier->usAttackingWeapon ) )
|
|
{
|
|
pObj = &(pSoldier->inv[pSoldier->ubAttackingHand]);
|
|
if ((*pObj)[0]->data.gun.bGunAmmoStatus > 0)
|
|
{
|
|
// gun might jam, figure out the chance
|
|
//iChance = (80 - (*pObj)[0]->data.gun.bGunStatus);
|
|
|
|
//rain
|
|
iChance = (80 - (*pObj)[0]->data.gun.bGunStatus) + gGameExternalOptions.ubWeaponReliabilityReductionPerRainIntensity * gbCurrentRainIntensity;
|
|
//end rain
|
|
|
|
|
|
// CJC: removed reliability from formula...
|
|
|
|
// jams can happen to unreliable guns "earlier" than normal or reliable ones.
|
|
//iChance = iChance - Item[pObj->usItem].bReliability * 2;
|
|
|
|
// decrease the chance of a jam by 20% per point of reliability;
|
|
// increased by 20% per negative point...
|
|
//iChance = iChance * (10 - Item[pObj->usItem].bReliability * 2) / 10;
|
|
|
|
//rain
|
|
// iChance = iChance * (10 - Item[pObj->usItem].bReliability * 2) / 10; // Madd: took it back out
|
|
//end rain
|
|
|
|
if (pSoldier->bDoBurst > 1)
|
|
{
|
|
// if at bullet in a burst after the first, higher chance
|
|
iChance -= PreRandom( 80 );
|
|
}
|
|
else
|
|
{
|
|
iChance -= PreRandom( 100 );
|
|
}
|
|
|
|
#ifdef TESTGUNJAM
|
|
if ( 1 )
|
|
#else
|
|
if ((INT32) PreRandom( 100 ) < iChance || gfNextFireJam )
|
|
#endif
|
|
{
|
|
gfNextFireJam = FALSE;
|
|
|
|
// jam! negate the gun ammo status.
|
|
(*pObj)[0]->data.gun.bGunAmmoStatus *= -1;
|
|
|
|
// Deduct AMMO!
|
|
DeductAmmo( pSoldier, pSoldier->ubAttackingHand );
|
|
|
|
TacticalCharacterDialogue( pSoldier, QUOTE_JAMMED_GUN );
|
|
return( TRUE );
|
|
}
|
|
}
|
|
else if ((*pObj)[0]->data.gun.bGunAmmoStatus < 0)
|
|
{
|
|
// try to unjam gun
|
|
iResult = SkillCheck( pSoldier, UNJAM_GUN_CHECK, (INT8) ((Item[pObj->usItem].bReliability + Item[(*pObj)[0]->data.gun.usGunAmmoItem].bReliability)* 4) );
|
|
if (iResult > 0)
|
|
{
|
|
// yay! unjammed the gun
|
|
(*pObj)[0]->data.gun.bGunAmmoStatus *= -1;
|
|
|
|
// MECHANICAL/DEXTERITY GAIN: Unjammed a gun
|
|
StatChange( pSoldier, MECHANAMT, 5, FALSE );
|
|
StatChange( pSoldier, DEXTAMT, 5, FALSE );
|
|
|
|
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
|
|
|
|
// We unjammed gun, return appropriate value!
|
|
return( 255 );
|
|
}
|
|
else
|
|
{
|
|
return( TRUE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
BOOLEAN OKFireWeapon( SOLDIERTYPE *pSoldier )
|
|
{
|
|
BOOLEAN bGunJamVal;
|
|
|
|
// 1) Are we attacking with our second hand?
|
|
if ( pSoldier->ubAttackingHand == SECONDHANDPOS )
|
|
{
|
|
if ( Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].NoSemiAuto ) // Madd: no autofire w/second hand (yet)
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( !EnoughAmmo( pSoldier, FALSE, pSoldier->ubAttackingHand ) )
|
|
{
|
|
if ( pSoldier->bTeam == gbPlayerNum )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[ STR_2ND_CLIP_DEPLETED ] );
|
|
return( FALSE );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
bGunJamVal = CheckForGunJam( pSoldier );
|
|
|
|
if ( bGunJamVal == 255 )
|
|
{
|
|
return( 255 );
|
|
}
|
|
|
|
if ( bGunJamVal )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo )
|
|
{
|
|
// ignore passed in target gridno for now
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireWeapon"));
|
|
// If realtime and we are reloading - do not fire until counter is done!
|
|
if ( ( ( gTacticalStatus.uiFlags & REALTIME ) || !( gTacticalStatus.uiFlags & INCOMBAT ) ) && !pSoldier->bDoBurst )
|
|
{
|
|
if ( pSoldier->flags.fReloading )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
}
|
|
|
|
// if target gridno is the same as ours, do not fire!
|
|
if (sTargetGridNo == pSoldier->sGridNo)
|
|
{
|
|
// FREE UP NPC!
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - attack on own gridno!") );
|
|
FreeUpAttacker( );
|
|
return( FALSE );
|
|
}
|
|
|
|
// SET ATTACKER TO NOBODY, WILL GET SET EVENTUALLY
|
|
pSoldier->ubOppNum = NOBODY ;
|
|
|
|
UINT32 usItemClass = Item[ pSoldier->usAttackingWeapon ].usItemClass;
|
|
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
|
|
usItemClass = IC_LAUNCHER;
|
|
|
|
switch( usItemClass )
|
|
{
|
|
case IC_THROWING_KNIFE:
|
|
case IC_GUN:
|
|
|
|
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL )
|
|
UseLauncher ( pSoldier, sTargetGridNo );
|
|
else
|
|
{
|
|
// ATE: PAtch up - bookkeeping for spreading done out of whak
|
|
if ( pSoldier->flags.fDoSpread)
|
|
{
|
|
|
|
// 0verhaul: This check does not work! All auto-fire has bDoBurst turned on. And only allowing a spread
|
|
// for a single-shot mode is useless.
|
|
//if (pSoldier->flags.fDoBurst )
|
|
//{
|
|
// pSoldier->flags.fDoSpread = FALSE;
|
|
//}
|
|
//else
|
|
// 0verhaul: The original code seemed brain damaged: If the current spread target was 0 it would shoot at the
|
|
// non-spread target grid # instead. Also fDoSpread is used as a counter from 1 to MAX_BURST_SPREAD_TARGETS,
|
|
// but was actually reset before it got there. So the final spread target would never be shot at. Hopefully this
|
|
// will work better.
|
|
if (pSoldier->flags.fDoSpread > MAX_BURST_SPREAD_TARGETS ||
|
|
pSoldier->sSpreadLocations[ pSoldier->flags.fDoSpread - 1 ] == 0)
|
|
{
|
|
if (pSoldier->flags.fDoSpread == 1)
|
|
{
|
|
// If no spread locations are defined, don't spread
|
|
pSoldier->flags.fDoSpread = 0;
|
|
}
|
|
else
|
|
{
|
|
// If we hit the end of the array, either by finding a 0 or by exceeding its size, reset
|
|
pSoldier->flags.fDoSpread = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( pSoldier->flags.fDoSpread )
|
|
{
|
|
UseGun( pSoldier, pSoldier->sSpreadLocations[ pSoldier->flags.fDoSpread - 1 ] );
|
|
pSoldier->flags.fDoSpread++;
|
|
}
|
|
else
|
|
{
|
|
UseGun( pSoldier, sTargetGridNo );
|
|
}
|
|
}
|
|
break;
|
|
case IC_BLADE:
|
|
|
|
UseBlade( pSoldier, sTargetGridNo );
|
|
break;
|
|
case IC_PUNCH:
|
|
UseHandToHand( pSoldier, sTargetGridNo, FALSE );
|
|
break;
|
|
|
|
case IC_LAUNCHER:
|
|
if ( Item[pSoldier->usAttackingWeapon].rocketlauncher )
|
|
UseGun( pSoldier, sTargetGridNo );
|
|
else
|
|
// ATE: PAtch up - bookkeeping for spreading done out of whak
|
|
if ( pSoldier->flags.fDoSpread)
|
|
{
|
|
if (!pSoldier->bDoBurst )
|
|
{
|
|
pSoldier->flags.fDoSpread = FALSE;
|
|
}
|
|
|
|
// 0verhaul: The original code seemed brain damaged: If the current spread target was 0 it would shoot at the
|
|
// non-spread target grid # instead. Also fDoSpread is used as a counter from 1 to MAX_BURST_SPREAD_TARGETS,
|
|
// but was actually reset before it got there. So the final spread target would never be shot at. Hopefully this
|
|
// will work better.
|
|
else if ( pSoldier->flags.fDoSpread > MAX_BURST_SPREAD_TARGETS ||
|
|
pSoldier->sSpreadLocations[ pSoldier->flags.fDoSpread - 1 ] == 0)
|
|
{
|
|
if (pSoldier->flags.fDoSpread == 1)
|
|
{
|
|
// If no spread locations are defined, don't spread
|
|
pSoldier->flags.fDoSpread = 0;
|
|
}
|
|
else
|
|
{
|
|
// If we hit the end of the array, either by finding a 0 or by exceeding its size, reset
|
|
pSoldier->flags.fDoSpread = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pSoldier->flags.fDoSpread )
|
|
{
|
|
UseLauncher( pSoldier, pSoldier->sSpreadLocations[ pSoldier->flags.fDoSpread - 1 ] );
|
|
pSoldier->flags.fDoSpread++;
|
|
}
|
|
else
|
|
{
|
|
UseLauncher( pSoldier, sTargetGridNo );
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
// attempt to throw
|
|
UseThrown( pSoldier, sTargetGridNo );
|
|
break;
|
|
}
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
void GetTargetWorldPositions( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo, FLOAT *pdXPos, FLOAT *pdYPos, FLOAT *pdZPos )
|
|
{
|
|
FLOAT dTargetX;
|
|
FLOAT dTargetY;
|
|
FLOAT dTargetZ;
|
|
SOLDIERTYPE *pTargetSoldier;
|
|
INT8 bStructHeight;
|
|
INT16 sXMapPos, sYMapPos;
|
|
UINT32 uiRoll;
|
|
|
|
pTargetSoldier = SimpleFindSoldier( sTargetGridNo, pSoldier->bTargetLevel );
|
|
if ( pTargetSoldier )
|
|
{
|
|
// SAVE OPP ID
|
|
pSoldier->ubOppNum = pTargetSoldier->ubID;
|
|
dTargetX = (FLOAT) CenterX( pTargetSoldier->sGridNo );
|
|
dTargetY = (FLOAT) CenterY( pTargetSoldier->sGridNo );
|
|
if (pSoldier->bAimShotLocation == AIM_SHOT_RANDOM)
|
|
{
|
|
uiRoll = PreRandom( 100 );
|
|
if (uiRoll < 15)
|
|
{
|
|
pSoldier->bAimShotLocation = AIM_SHOT_LEGS;
|
|
}
|
|
else if (uiRoll > 94)
|
|
{
|
|
pSoldier->bAimShotLocation = AIM_SHOT_HEAD;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bAimShotLocation = AIM_SHOT_TORSO;
|
|
}
|
|
if ( pSoldier->bAimShotLocation != AIM_SHOT_HEAD )
|
|
{
|
|
UINT32 uiChanceToGetThrough = SoldierToSoldierBodyPartChanceToGetThrough( pSoldier, pTargetSoldier, pSoldier->bAimShotLocation );
|
|
|
|
if ( uiChanceToGetThrough < 25 )
|
|
{
|
|
if ( SoldierToSoldierBodyPartChanceToGetThrough( pSoldier, pTargetSoldier, AIM_SHOT_HEAD ) > uiChanceToGetThrough * 2 )
|
|
{
|
|
// try for a head shot then
|
|
pSoldier->bAimShotLocation = AIM_SHOT_HEAD;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
switch( pSoldier->bAimShotLocation )
|
|
{
|
|
case AIM_SHOT_HEAD:
|
|
CalculateSoldierZPos( pTargetSoldier, HEAD_TARGET_POS, &dTargetZ );
|
|
break;
|
|
case AIM_SHOT_TORSO:
|
|
CalculateSoldierZPos( pTargetSoldier, TORSO_TARGET_POS, &dTargetZ );
|
|
break;
|
|
case AIM_SHOT_LEGS:
|
|
CalculateSoldierZPos( pTargetSoldier, LEGS_TARGET_POS, &dTargetZ );
|
|
break;
|
|
default:
|
|
// %)@#&(%?
|
|
CalculateSoldierZPos( pTargetSoldier, TARGET_POS, &dTargetZ );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// GET TARGET XY VALUES
|
|
ConvertGridNoToCenterCellXY( sTargetGridNo, &sXMapPos, &sYMapPos );
|
|
|
|
// fire at centre of tile
|
|
dTargetX = (FLOAT) sXMapPos;
|
|
dTargetY = (FLOAT) sYMapPos;
|
|
if (pSoldier->bTargetCubeLevel)
|
|
{
|
|
// fire at the centre of the cube specified
|
|
dTargetZ = ( (FLOAT) (pSoldier->bTargetCubeLevel + pSoldier->bTargetLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
|
|
}
|
|
else
|
|
{
|
|
bStructHeight = GetStructureTargetHeight( sTargetGridNo, (BOOLEAN) (pSoldier->bTargetLevel == 1) );
|
|
if (bStructHeight > 0)
|
|
{
|
|
// fire at the centre of the cube *one below* the tallest of the tallest structure
|
|
if (bStructHeight > 1)
|
|
{
|
|
// reduce target level by 1
|
|
bStructHeight--;
|
|
}
|
|
dTargetZ = ((FLOAT) (bStructHeight + pSoldier->bTargetLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
|
|
}
|
|
else
|
|
{
|
|
// fire at 1 unit above the level of the ground
|
|
dTargetZ = (FLOAT) (pSoldier->bTargetLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1;
|
|
}
|
|
}
|
|
// adjust for terrain height
|
|
dTargetZ += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sTargetGridNo].sHeight );
|
|
}
|
|
|
|
*pdXPos = dTargetX;
|
|
*pdYPos = dTargetY;
|
|
*pdZPos = dTargetZ;
|
|
}
|
|
|
|
|
|
BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo )
|
|
{
|
|
UINT32 uiHitChance, uiDiceRoll;
|
|
INT16 sXMapPos, sYMapPos;
|
|
INT16 sAPCost;
|
|
FLOAT dTargetX;
|
|
FLOAT dTargetY;
|
|
FLOAT dTargetZ;
|
|
UINT16 usItemNum;
|
|
BOOLEAN fBuckshot;
|
|
UINT8 ubVolume;
|
|
CHAR8 zBurstString[512];
|
|
UINT8 ubDirection;
|
|
INT16 sNewGridNo;
|
|
UINT8 ubMerc;
|
|
BOOLEAN fGonnaHit = FALSE;
|
|
UINT16 usExpGain = 0;
|
|
UINT32 uiDepreciateTest;
|
|
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("UseGun") );
|
|
// Deduct points!
|
|
sAPCost = CalcTotalAPsToAttack( pSoldier, sTargetGridNo, FALSE, pSoldier->aiData.bAimTime );
|
|
|
|
usItemNum = pSoldier->usAttackingWeapon;
|
|
|
|
if ( pSoldier->bDoBurst )
|
|
{
|
|
// ONly deduct points once
|
|
if ( pSoldier->bDoBurst == 1 )
|
|
{
|
|
INT8 bShotsToFire = pSoldier->bDoAutofire ?
|
|
pSoldier->bDoAutofire :
|
|
GetShotsPerBurst(&pSoldier->inv[HANDPOS]);
|
|
|
|
if ( Weapon[ usItemNum ].sBurstSound != NO_WEAPON_SOUND )
|
|
{
|
|
// IF we are silenced?
|
|
UINT16 noisefactor = GetPercentNoiseVolume( &pSoldier->inv[ pSoldier->ubAttackingHand ] );
|
|
if( noisefactor < MAX_PERCENT_NOISE_VOLUME_FOR_SILENCED_SOUND || Weapon[ usItemNum ].ubAttackVolume <= 10 )
|
|
{
|
|
// Pick sound file baed on how many bullets we are going to fire...
|
|
sprintf( zBurstString, gzBurstSndStrings[ Weapon[ usItemNum ].sSilencedBurstSound ], bShotsToFire );
|
|
|
|
// Try playing sound...
|
|
pSoldier->iBurstSoundID = PlayJA2SampleFromFile( zBurstString, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
}
|
|
else
|
|
{
|
|
// Pick sound file baed on how many bullets we are going to fire...
|
|
// Lesh: changed next line
|
|
sprintf( zBurstString, gzBurstSndStrings[ Weapon[ usItemNum ].sBurstSound ], bShotsToFire );
|
|
|
|
INT8 volume = HIGHVOLUME;
|
|
if ( noisefactor < 100 ) volume = (INT8) ((volume * noisefactor) / 100);
|
|
// Try playing sound...
|
|
pSoldier->iBurstSoundID = PlayJA2SampleFromFile( zBurstString, RATE_11025, SoundVolume( (INT8) volume, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
}
|
|
|
|
/*
|
|
//DIGICRAB: We don't need this anymore, because of the burst sound modification
|
|
// If we don't have the burst sound, a normal shot will be played for each shot
|
|
if ( pSoldier->iBurstSoundID == NO_SAMPLE )
|
|
{
|
|
// If failed, play normal default....
|
|
pSoldier->iBurstSoundID = PlayJA2Sample( Weapon[ usItemNum ].sBurstSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
}
|
|
*/
|
|
}
|
|
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// ONLY DEDUCT FOR THE FIRST HAND when doing two-pistol attacks
|
|
if ( pSoldier->IsValidSecondHandShot( ) && pSoldier->inv[ HANDPOS ][0]->data.gun.bGunStatus >= USABLE && pSoldier->inv[HANDPOS][0]->data.gun.bGunAmmoStatus > 0 )
|
|
{
|
|
// only deduct APs when the main gun fires
|
|
if ( pSoldier->ubAttackingHand == HANDPOS )
|
|
{
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
}
|
|
|
|
//PLAY SOUND
|
|
// ( For throwing knife.. it's earlier in the animation
|
|
if ( Weapon[ usItemNum ].sSound != NO_WEAPON_SOUND && Item[ usItemNum ].usItemClass != IC_THROWING_KNIFE )
|
|
{
|
|
// Switch on silencer...
|
|
UINT16 noisefactor = GetPercentNoiseVolume( &pSoldier->inv[ pSoldier->ubAttackingHand ] );
|
|
if( noisefactor < MAX_PERCENT_NOISE_VOLUME_FOR_SILENCED_SOUND || Weapon[ usItemNum ].ubAttackVolume <= 10 )
|
|
{
|
|
INT32 uiSound;
|
|
|
|
uiSound = Weapon [ usItemNum ].silencedSound ;
|
|
//if ( Weapon[ usItemNum ].ubCalibre == AMMO9 || Weapon[ usItemNum ].ubCalibre == AMMO38 || Weapon[ usItemNum ].ubCalibre == AMMO57 )
|
|
//{
|
|
// uiSound = S_SILENCER_1;
|
|
//}
|
|
//else
|
|
//{
|
|
// uiSound = S_SILENCER_2;
|
|
//}
|
|
|
|
PlayJA2Sample( uiSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
|
|
}
|
|
else
|
|
{
|
|
INT8 volume = HIGHVOLUME;
|
|
if ( noisefactor < 100 ) volume = (volume * noisefactor) / 100;
|
|
PlayJA2Sample( Weapon[ usItemNum ].sSound, RATE_11025, SoundVolume( volume, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// CALC CHANCE TO HIT
|
|
if ( Item[ usItemNum ].usItemClass == IC_THROWING_KNIFE )
|
|
{
|
|
uiHitChance = CalcThrownChanceToHit( pSoldier, sTargetGridNo, pSoldier->aiData.bAimTime, pSoldier->bAimShotLocation );
|
|
}
|
|
else
|
|
{
|
|
uiHitChance = CalcChanceToHitGun( pSoldier, sTargetGridNo, pSoldier->aiData.bAimTime, pSoldier->bAimShotLocation );
|
|
}
|
|
|
|
//DIGICRAB: Barrel extender wear code
|
|
// Relocated from CalcChanceToHitGun
|
|
if ( Item[ usItemNum ].usItemClass == IC_GUN )
|
|
{
|
|
OBJECTTYPE * pInHand = &(pSoldier->inv[pSoldier->ubAttackingHand]);
|
|
|
|
// lalien: search for barrel extender not for any item with range bonus. (else barrel extender will fall off even when none is attached)
|
|
OBJECTTYPE* pAttachment = FindAttachment( pInHand, GUN_BARREL_EXTENDER );
|
|
|
|
if ( pAttachment )
|
|
{
|
|
// reduce status and see if it falls off
|
|
(*pAttachment)[0]->data.objectStatus -= (INT8) Random( 2 );
|
|
|
|
if ( (*pAttachment)[0]->data.objectStatus - Random( 35 ) - Random( 35 ) < USABLE )
|
|
{
|
|
//CHRISL: This setup assumes that remove() will work successfully, but if it doesn't we'll duplicate the item.
|
|
// barrel extender falls off!
|
|
// drop it to ground
|
|
//AddItemToPool( pSoldier->sGridNo, pAttachment, 1, pSoldier->pathing.bLevel, 0, -1 );
|
|
|
|
// since barrel extenders are not removable we cannot call RemoveAttachment here
|
|
// and must create the item by hand
|
|
//(*pInHand)[0]->attachments.remove(*pAttachment);
|
|
//ADB ubWeight has been removed, see comments in OBJECTTYPE
|
|
//pInHand->ubWeight = CalculateObjectWeight( pInHand );
|
|
|
|
//CHRISL: Instead of the above, use this function which is basially redundant to what remove() does, but includes
|
|
// a failsafe so we don't cause an item duplication.
|
|
for(std::list<OBJECTTYPE>::iterator iter = (*pInHand)[0]->attachments.begin();
|
|
iter != (*pInHand)[0]->attachments.end(); ++iter){
|
|
if(*iter == *pAttachment)
|
|
{
|
|
AddItemToPool( pSoldier->sGridNo, pAttachment, 1, pSoldier->pathing.bLevel, 0, -1 );
|
|
iter = (*pInHand)[0]->attachments.erase(iter);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// big penalty to hit
|
|
if(uiHitChance < 30)
|
|
uiHitChance = MINCHANCETOHIT;
|
|
else
|
|
uiHitChance -= 30;
|
|
|
|
// curse!
|
|
if ( pSoldier->bTeam == OUR_TEAM )
|
|
{
|
|
pSoldier->DoMercBattleSound( BATTLE_SOUND_CURSE1 );
|
|
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[ 46 ], pSoldier->name );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//ATE: Added if we are in meanwhile, we always hit...
|
|
if ( AreInMeanwhile( ) )
|
|
{
|
|
uiHitChance = 100;
|
|
}
|
|
|
|
// ROLL DICE
|
|
uiDiceRoll = PreRandom( 100 );
|
|
|
|
#ifdef JA2BETAVERSION
|
|
if ( gfReportHitChances )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Hit chance was %ld, roll %ld (range %d)", uiHitChance, uiDiceRoll, PythSpacesAway( pSoldier->sGridNo, pSoldier->sTargetGridNo ) );
|
|
}
|
|
#endif
|
|
|
|
fGonnaHit = uiDiceRoll <= uiHitChance;
|
|
|
|
// GET TARGET XY VALUES
|
|
ConvertGridNoToCenterCellXY( sTargetGridNo, &sXMapPos, &sYMapPos );
|
|
|
|
// ATE; Moved a whole blotch if logic code for finding target positions to a function
|
|
// so other places can use it
|
|
GetTargetWorldPositions( pSoldier, sTargetGridNo, &dTargetX, &dTargetY, &dTargetZ );
|
|
|
|
// Some things we don't do for knives...
|
|
if ( Item[ usItemNum ].usItemClass != IC_THROWING_KNIFE )
|
|
{
|
|
// If realtime - set counter to freeup from attacking once done
|
|
if ( ( ( gTacticalStatus.uiFlags & REALTIME ) || !( gTacticalStatus.uiFlags & INCOMBAT ) ) )
|
|
{
|
|
|
|
// Set delay based on stats, weapon type, etc
|
|
pSoldier->sReloadDelay = (INT16)( Weapon[ usItemNum ].usReloadDelay + MANDATORY_WEAPON_DELAY );
|
|
|
|
// If a bad guy, double the delay!
|
|
if ( (pSoldier->flags.uiStatusFlags & SOLDIER_ENEMY ) )
|
|
{
|
|
pSoldier->sReloadDelay = ( pSoldier->sReloadDelay * 2 );
|
|
}
|
|
|
|
|
|
// slow down demo mode!
|
|
if ( gTacticalStatus.uiFlags & DEMOMODE )
|
|
{
|
|
pSoldier->sReloadDelay *= 2;
|
|
}
|
|
|
|
//pSoldier->flags.fReloading = TRUE;
|
|
//RESETTIMECOUNTER( pSoldier->timeCounters.ReloadCounter, pSoldier->sReloadDelay );
|
|
}
|
|
|
|
// Deduct AMMO!
|
|
DeductAmmo( pSoldier, pSoldier->ubAttackingHand );
|
|
|
|
// ATE: Check if we should say quote...
|
|
if ( pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft == 0 && !Item[pSoldier->usAttackingWeapon].rocketlauncher )
|
|
{
|
|
if ( pSoldier->bTeam == gbPlayerNum )
|
|
{
|
|
pSoldier->flags.fSayAmmoQuotePending = TRUE;
|
|
}
|
|
}
|
|
|
|
// NB bDoBurst will be 2 at this point for the first shot since it was incremented
|
|
// above
|
|
if ( PTR_OURTEAM && pSoldier->ubTargetID != NOBODY && (!pSoldier->bDoBurst || pSoldier->bDoBurst == 2 ) && (gTacticalStatus.uiFlags & INCOMBAT ) && ( SoldierToSoldierBodyPartChanceToGetThrough( pSoldier, MercPtrs[ pSoldier->ubTargetID ], pSoldier->bAimShotLocation ) > 0 ) )
|
|
{
|
|
if ( fGonnaHit )
|
|
{
|
|
// grant extra exp for hitting a difficult target
|
|
usExpGain += (UINT8) (100 - uiHitChance) / 25;
|
|
|
|
if ( pSoldier->aiData.bAimTime && !pSoldier->bDoBurst )
|
|
{
|
|
// gain extra exp for aiming, up to the amount from
|
|
// the difficulty of the shot
|
|
usExpGain += __min( pSoldier->aiData.bAimTime, usExpGain );
|
|
}
|
|
|
|
// base pts extra for hitting
|
|
usExpGain += 3;
|
|
}
|
|
|
|
// add base pts for taking a shot, whether it hits or misses
|
|
usExpGain += 3;
|
|
|
|
if ( pSoldier->IsValidSecondHandShot( ) && pSoldier->inv[ HANDPOS ][0]->data.gun.bGunStatus >= USABLE && pSoldier->inv[HANDPOS][0]->data.gun.bGunAmmoStatus > 0 )
|
|
{
|
|
// reduce exp gain for two pistol shooting since both shots give xp
|
|
usExpGain = (usExpGain * 2) / 3;
|
|
}
|
|
|
|
if ( MercPtrs[ pSoldier->ubTargetID ]->ubBodyType == COW || MercPtrs[ pSoldier->ubTargetID ]->ubBodyType == CROW )
|
|
{
|
|
usExpGain /= 2;
|
|
}
|
|
else if ( MercPtrs[ pSoldier->ubTargetID ]->flags.uiStatusFlags & SOLDIER_VEHICLE || AM_A_ROBOT( MercPtrs[ pSoldier->ubTargetID ] ) || TANK( MercPtrs[ pSoldier->ubTargetID ] ) )
|
|
{
|
|
// no exp from shooting a vehicle that you can't damage and can't move!
|
|
usExpGain = 0;
|
|
}
|
|
|
|
// MARKSMANSHIP GAIN: gun attack
|
|
StatChange( pSoldier, MARKAMT, usExpGain, ( UINT8 )( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
}
|
|
|
|
// set buckshot and muzzle flash
|
|
fBuckshot = FALSE;
|
|
if (!CREATURE_OR_BLOODCAT( pSoldier ) )
|
|
{
|
|
if ( IsFlashSuppressor(&pSoldier->inv[ pSoldier->ubAttackingHand ], pSoldier ) )
|
|
pSoldier->flags.fMuzzleFlash = FALSE;
|
|
else
|
|
pSoldier->flags.fMuzzleFlash = TRUE;
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("UseGun: Muzzle flash = %d",pSoldier->flags.fMuzzleFlash));
|
|
|
|
if ( AmmoTypes[pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].numberOfBullets > 1 )
|
|
fBuckshot = TRUE;
|
|
|
|
//switch ( pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType )
|
|
//{
|
|
// case AMMO_BUCKSHOT:
|
|
// fBuckshot = TRUE;
|
|
// break;
|
|
// case AMMO_SLEEP_DART:
|
|
// pSoldier->flags.fMuzzleFlash = FALSE;
|
|
// break;
|
|
// default:
|
|
// break;
|
|
//}
|
|
}
|
|
}
|
|
else // throwing knife
|
|
{
|
|
fBuckshot = FALSE;
|
|
pSoldier->flags.fMuzzleFlash = FALSE;
|
|
|
|
// Deduct knife from inv! (not here, later?)
|
|
|
|
// Improve for using a throwing knife....
|
|
if (PTR_OURTEAM && pSoldier->ubTargetID != NOBODY)
|
|
{
|
|
if ( fGonnaHit )
|
|
{
|
|
// grant extra exp for hitting a difficult target
|
|
usExpGain += (UINT8) (100 - uiHitChance) / 10;
|
|
|
|
if (pSoldier->aiData.bAimTime)
|
|
{
|
|
// gain extra exp for aiming, up to the amount from
|
|
// the difficulty of the throw
|
|
usExpGain += ( 2 * __min( pSoldier->aiData.bAimTime, usExpGain ) );
|
|
}
|
|
|
|
// base pts extra for hitting
|
|
usExpGain += 10;
|
|
}
|
|
|
|
// add base pts for taking a shot, whether it hits or misses
|
|
usExpGain += 10;
|
|
|
|
if ( MercPtrs[ pSoldier->ubTargetID ]->ubBodyType == COW || MercPtrs[ pSoldier->ubTargetID ]->ubBodyType == CROW )
|
|
{
|
|
usExpGain /= 2;
|
|
}
|
|
else if ( MercPtrs[ pSoldier->ubTargetID ]->flags.uiStatusFlags & SOLDIER_VEHICLE || AM_A_ROBOT( MercPtrs[ pSoldier->ubTargetID ] ) || TANK( MercPtrs[ pSoldier->ubTargetID ] ) )
|
|
{
|
|
// no exp from shooting a vehicle that you can't damage and can't move!
|
|
usExpGain = 0;
|
|
}
|
|
|
|
// MARKSMANSHIP/DEXTERITY GAIN: throwing knife attack
|
|
StatChange( pSoldier, MARKAMT, ( UINT16 )( usExpGain / 2 ), ( UINT8 )( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
StatChange( pSoldier, DEXTAMT, ( UINT16 )( usExpGain / 2 ), ( UINT8 )( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
}
|
|
}
|
|
|
|
if ( Item[usItemNum].rocketlauncher )
|
|
{
|
|
if ( WillExplosiveWeaponFail( pSoldier, &( pSoldier->inv[ HANDPOS ] ) ) )
|
|
{
|
|
if ( Item[usItemNum].singleshotrocketlauncher )
|
|
{
|
|
CreateItem( Item[usItemNum].discardedlauncheritem , pSoldier->inv[ HANDPOS ][0]->data.objectStatus,&(pSoldier->inv[ HANDPOS ] ) );
|
|
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
|
|
IgniteExplosion( pSoldier->ubID, (INT16)CenterX( pSoldier->sGridNo ), (INT16)CenterY( pSoldier->sGridNo ), 0, pSoldier->sGridNo, C1, pSoldier->pathing.bLevel );
|
|
}
|
|
else
|
|
{
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("StructureHit: RPG7 item: %d, Ammo: %d",pSoldier->inv[HANDPOS].usItem , pSoldier->inv[HANDPOS][0]->data.gun.usGunAmmoItem ) );
|
|
|
|
IgniteExplosion( pSoldier->ubID, (INT16)CenterX( pSoldier->sGridNo ), (INT16)CenterY( pSoldier->sGridNo ), 0, pSoldier->sGridNo, pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem, pSoldier->pathing.bLevel );
|
|
pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE;
|
|
}
|
|
// Reduce again for attack end 'cause it has been incremented for a normal attack
|
|
//
|
|
// Not anymore. Only the attack animation was increased, and it will decrease itself.
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - ATTACK ANIMATION %s ENDED BY BAD EXPLOSIVE CHECK, Now %d", gAnimControl[ pSoldier->usAnimState ].zAnimStr, gTacticalStatus.ubAttackBusyCount ) );
|
|
DebugAttackBusy( String("@@@@@@@ Freeing up attacker - ATTACK ANIMATION %s ENDED BY BAD EXPLOSIVE CHECK\n", gAnimControl[ pSoldier->usAnimState ].zAnimStr ) );
|
|
// ReduceAttackBusyCount( pSoldier->ubID, FALSE );
|
|
|
|
return( FALSE );
|
|
}
|
|
}
|
|
|
|
|
|
FireBulletGivenTarget( pSoldier, dTargetX, dTargetY, dTargetZ, pSoldier->usAttackingWeapon, (UINT16) (uiHitChance - uiDiceRoll), fBuckshot, FALSE );
|
|
|
|
ubVolume = Weapon[ pSoldier->usAttackingWeapon ].ubAttackVolume;
|
|
|
|
if ( Item[ usItemNum ].usItemClass == IC_THROWING_KNIFE )
|
|
{
|
|
// Here, remove the knife... or (for now) rocket launcher
|
|
pSoldier->inv[ HANDPOS ].RemoveObjectsFromStack(1);
|
|
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
|
|
}
|
|
else if ( Item[usItemNum].rocketlauncher )
|
|
{
|
|
if ( Item[usItemNum].singleshotrocketlauncher )
|
|
{
|
|
CreateItem( Item[usItemNum].discardedlauncheritem, pSoldier->inv[ HANDPOS ][0]->data.objectStatus, &(pSoldier->inv[ HANDPOS ] ) );
|
|
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
|
|
}
|
|
|
|
// Direction to center of explosion
|
|
ubDirection = gOppositeDirection[ pSoldier->ubDirection ];
|
|
sNewGridNo = NewGridNo( (INT16)pSoldier->sGridNo, (UINT16)(1 * DirectionInc( ubDirection ) ) );
|
|
|
|
// Check if a person exists here and is not prone....
|
|
ubMerc = WhoIsThere2( sNewGridNo, pSoldier->pathing.bLevel );
|
|
|
|
if ( ubMerc != NOBODY )
|
|
{
|
|
if ( gAnimControl[ MercPtrs[ ubMerc ]->usAnimState ].ubHeight != ANIM_PRONE )
|
|
{
|
|
// Increment attack counter...
|
|
// gTacticalStatus.ubAttackBusyCount++;
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Incrementing Attack: Exaust from LAW", gTacticalStatus.ubAttackBusyCount ) );
|
|
DebugAttackBusy( "Incrementing Attack: Exaust from LAW\n" );
|
|
|
|
MercPtrs[ ubMerc ]->EVENT_SoldierGotHit( MINI_GRENADE, 10, 200, pSoldier->ubDirection, 0, pSoldier->ubID, 0, ANIM_CROUCH, 0, sNewGridNo );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Snap: get cumulative noise reduction from the weapon and its attachments
|
|
UINT16 noisefactor = GetPercentNoiseVolume( &pSoldier->inv[ pSoldier->ubAttackingHand ] );
|
|
if ( ubVolume * noisefactor > 25000 )
|
|
{ // Snap: hack this to prevent overflow (damn miserly programmers!)
|
|
ubVolume = 250;
|
|
}
|
|
else
|
|
{
|
|
ubVolume = __max( 1, ( ubVolume * GetPercentNoiseVolume( &pSoldier->inv[ pSoldier->ubAttackingHand ] ) ) / 100 );
|
|
}
|
|
}
|
|
|
|
MakeNoise( pSoldier->ubID, pSoldier->sGridNo, pSoldier->pathing.bLevel, pSoldier->bOverTerrainType, ubVolume, NOISE_GUNFIRE );
|
|
|
|
if ( pSoldier->bDoBurst )
|
|
{
|
|
// done, if bursting, increment
|
|
pSoldier->bDoBurst++;
|
|
}
|
|
|
|
// CJC: since jamming is no longer affected by reliability, increase chance of status going down for really unreliabile guns
|
|
INT16 ammoReliability = 0; // Madd: ammo reliability affects gun
|
|
|
|
if ( Item[usItemNum].usItemClass == IC_GUN )
|
|
{
|
|
OBJECTTYPE *pGun = &(pSoldier->inv[pSoldier->ubAttackingHand]);
|
|
ammoReliability = Item[(*pGun)[0]->data.gun.usGunAmmoItem].bReliability;
|
|
}
|
|
|
|
uiDepreciateTest = BASIC_DEPRECIATE_CHANCE + 3 * (Item[ usItemNum ].bReliability + ammoReliability);
|
|
|
|
if ( !PreRandom( uiDepreciateTest ) && ( pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.objectStatus > 1) )
|
|
{
|
|
pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.objectStatus--;
|
|
}
|
|
|
|
// reduce monster smell (gunpowder smell)
|
|
if ( pSoldier->aiData.bMonsterSmell > 0 && Random( 2 ) == 0 )
|
|
{
|
|
pSoldier->aiData.bMonsterSmell--;
|
|
}
|
|
|
|
//<SB> manual recharge
|
|
if (Weapon[Item[usItemNum].ubClassIndex].APsToReloadManually > 0)
|
|
pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunState &= ~GS_CARTRIDGE_IN_CHAMBER;
|
|
//<SB>
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("UseGun: done"));
|
|
return( TRUE );
|
|
}
|
|
|
|
BOOLEAN UseBlade( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo )
|
|
{
|
|
SOLDIERTYPE * pTargetSoldier;
|
|
INT32 iHitChance, iDiceRoll;
|
|
INT16 sXMapPos, sYMapPos;
|
|
INT16 sAPCost;
|
|
EV_S_WEAPONHIT SWeaponHit;
|
|
INT32 iImpact, iImpactForCrits;
|
|
BOOLEAN fGonnaHit = FALSE;
|
|
UINT16 usExpGain = 0;
|
|
INT8 bMaxDrop;
|
|
BOOLEAN fSurpriseAttack;
|
|
|
|
// Deduct points!
|
|
sAPCost = CalcTotalAPsToAttack( pSoldier, sTargetGridNo, FALSE, pSoldier->aiData.bAimTime );
|
|
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
|
|
// GET TARGET XY VALUES
|
|
ConvertGridNoToCenterCellXY( sTargetGridNo, &sXMapPos, &sYMapPos );
|
|
|
|
// See if a guy is here!
|
|
pTargetSoldier = SimpleFindSoldier( sTargetGridNo, pSoldier->bTargetLevel );
|
|
if ( pTargetSoldier )
|
|
{
|
|
// set target as noticed attack
|
|
pSoldier->flags.uiStatusFlags |= SOLDIER_ATTACK_NOTICED;
|
|
pTargetSoldier->flags.fIntendedTarget = TRUE;
|
|
|
|
// SAVE OPP ID
|
|
pSoldier->ubOppNum = pTargetSoldier->ubID;
|
|
|
|
// CHECK IF BUDDY KNOWS ABOUT US
|
|
if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN || pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed )
|
|
{
|
|
iHitChance = 100;
|
|
fSurpriseAttack = TRUE;
|
|
}
|
|
else
|
|
{
|
|
iHitChance = CalcChanceToStab( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime );
|
|
fSurpriseAttack = FALSE;
|
|
}
|
|
|
|
// ROLL DICE
|
|
iDiceRoll = (INT32) PreRandom( 100 );
|
|
//sprintf( gDebugStr, "Hit Chance: %d %d", (int)uiHitChance, uiDiceRoll );
|
|
|
|
|
|
if ( iDiceRoll <= iHitChance )
|
|
{
|
|
fGonnaHit = TRUE;
|
|
|
|
// CALCULATE DAMAGE!
|
|
// attack HITS, calculate damage (base damage is 1-maximum knife sImpact)
|
|
iImpact = HTHImpact( pSoldier, pTargetSoldier, (iHitChance - iDiceRoll), TRUE );
|
|
|
|
// modify this by the knife's condition (if it's dull, not much good)
|
|
iImpact = ( iImpact * WEAPON_STATUS_MOD(pSoldier->inv[pSoldier->ubAttackingHand][0]->data.objectStatus) ) / 100;
|
|
|
|
// modify by hit location
|
|
AdjustImpactByHitLocation( iImpact, pSoldier->bAimShotLocation, &iImpact, &iImpactForCrits );
|
|
|
|
// bonus for surprise
|
|
if ( fSurpriseAttack )
|
|
{
|
|
iImpact = (iImpact * 3) / 2;
|
|
}
|
|
|
|
// any successful hit does at LEAST 1 pt minimum damage
|
|
if (iImpact < 1)
|
|
{
|
|
iImpact = 1;
|
|
}
|
|
|
|
if ( pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.objectStatus > USABLE )
|
|
{
|
|
bMaxDrop = (iImpact / 20);
|
|
|
|
// the duller they get, the slower they get any worse...
|
|
bMaxDrop = __min( bMaxDrop, pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.objectStatus / 10 );
|
|
|
|
// as long as its still > USABLE, it drops another point 1/2 the time
|
|
bMaxDrop = __max( bMaxDrop, 2 );
|
|
|
|
pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.objectStatus -= (INT8) Random( bMaxDrop ); // 0 to (maxDrop - 1)
|
|
}
|
|
|
|
// Send event for getting hit
|
|
memset( &(SWeaponHit), 0, sizeof( SWeaponHit ) );
|
|
SWeaponHit.usSoldierID = pTargetSoldier->ubID;
|
|
SWeaponHit.uiUniqueId = pTargetSoldier->uiUniqueSoldierIdValue;
|
|
SWeaponHit.usWeaponIndex = pSoldier->usAttackingWeapon;
|
|
SWeaponHit.sDamage = (INT16) iImpact;
|
|
SWeaponHit.usDirection = (UINT8)GetDirectionFromGridNo( pSoldier->sGridNo, pTargetSoldier );
|
|
SWeaponHit.sXPos = (INT16)pTargetSoldier->dXPos;
|
|
SWeaponHit.sYPos = (INT16)pTargetSoldier->dYPos;
|
|
SWeaponHit.sZPos = 20;
|
|
SWeaponHit.sRange = 1;
|
|
SWeaponHit.ubAttackerID = pSoldier->ubID;
|
|
SWeaponHit.fHit = TRUE;
|
|
SWeaponHit.ubSpecial = FIRE_WEAPON_NO_SPECIAL;
|
|
AddGameEvent( S_WEAPONHIT, (UINT16) 20, &SWeaponHit );
|
|
}
|
|
else
|
|
{
|
|
// if it was another team shooting at someone under our control
|
|
if ( (pSoldier->bTeam != Menptr[ pTargetSoldier->ubID ].bTeam ) )
|
|
{
|
|
if (pTargetSoldier->bTeam == gbPlayerNum)
|
|
{
|
|
// AGILITY GAIN (10): Target avoids a knife attack
|
|
// Snap: stat gains should match stat requirements
|
|
StatChange( MercPtrs[ pTargetSoldier->ubID ], DEXTAMT, 15, FALSE);
|
|
StatChange( MercPtrs[ pTargetSoldier->ubID ], AGILAMT, 5, FALSE);
|
|
StatChange( MercPtrs[ pTargetSoldier->ubID ], STRAMT, 5, FALSE);
|
|
}
|
|
}
|
|
// 0verhaul: Another case that is handled by the animation transition system.
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - missed in knife attack") );
|
|
// FreeUpAttacker( (UINT8) pSoldier->ubID );
|
|
}
|
|
|
|
if ( PTR_OURTEAM && pSoldier->ubTargetID != NOBODY)
|
|
{
|
|
if ( fGonnaHit )
|
|
{
|
|
// grant extra exp for hitting a difficult target
|
|
usExpGain += (UINT8) (100 - iHitChance) / 10;
|
|
|
|
if (pSoldier->aiData.bAimTime)
|
|
{
|
|
// gain extra exp for aiming, up to the amount from
|
|
// the difficulty of the attack
|
|
usExpGain += ( 2 * __min( pSoldier->aiData.bAimTime, usExpGain ) );
|
|
}
|
|
|
|
// base pts extra for hitting
|
|
usExpGain += 10;
|
|
}
|
|
|
|
// add base pts for taking a shot, whether it hits or misses
|
|
usExpGain += 10;
|
|
|
|
if ( MercPtrs[ pSoldier->ubTargetID ]->ubBodyType == COW || MercPtrs[ pSoldier->ubTargetID ]->ubBodyType == CROW )
|
|
{
|
|
usExpGain /= 2;
|
|
}
|
|
else if ( MercPtrs[ pSoldier->ubTargetID ]->flags.uiStatusFlags & SOLDIER_VEHICLE || AM_A_ROBOT( MercPtrs[ pSoldier->ubTargetID ] ) || TANK( MercPtrs[ pSoldier->ubTargetID ] ) )
|
|
{
|
|
// no exp from shooting a vehicle that you can't damage and can't move!
|
|
usExpGain = 0;
|
|
}
|
|
|
|
// DEXTERITY GAIN: Made a knife attack, successful or not
|
|
StatChange( pSoldier, DEXTAMT, usExpGain, ( UINT8 )( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
StatChange( pSoldier, AGILAMT, usExpGain/3, ( UINT8 )( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
StatChange( pSoldier, STRAMT, usExpGain/3, ( UINT8 )( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
}
|
|
}
|
|
// 0verhaul: This case is also handled by the animation translations
|
|
// else
|
|
// {
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - missed in knife attack") );
|
|
// FreeUpAttacker( (UINT8) pSoldier->ubID );
|
|
// }
|
|
|
|
// possibly reduce monster smell
|
|
if ( pSoldier->aiData.bMonsterSmell > 0 && Random( 5 ) == 0 )
|
|
{
|
|
pSoldier->aiData.bMonsterSmell--;
|
|
}
|
|
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo, BOOLEAN fStealing )
|
|
{
|
|
SOLDIERTYPE * pTargetSoldier;
|
|
INT32 iHitChance, iDiceRoll;
|
|
INT16 sXMapPos, sYMapPos;
|
|
INT16 sAPCost;
|
|
EV_S_WEAPONHIT SWeaponHit;
|
|
INT32 iImpact;
|
|
UINT16 usOldItem, usNewItem;
|
|
UINT8 ubExpGain;
|
|
UINT8 ubIndexRet;
|
|
BOOLEAN fFailure; // no stealing occured
|
|
BOOLEAN fNoMoreItems = FALSE; // The enemy has no more items to steal!
|
|
BOOLEAN fNoMoreItemInHand = FALSE;
|
|
BOOLEAN fSoldierCollapsed = FALSE;
|
|
// Deduct points!
|
|
// August 13 2002: unless stealing - APs already deducted elsewhere
|
|
|
|
// Punch the enemy
|
|
if (!fStealing)
|
|
{
|
|
sAPCost = CalcTotalAPsToAttack( pSoldier, sTargetGridNo, FALSE, pSoldier->aiData.bAimTime );
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
}
|
|
// Steal from the enemy
|
|
else
|
|
{
|
|
// APs were already reduced!
|
|
}
|
|
|
|
// See if a guy is here!
|
|
pTargetSoldier = SimpleFindSoldier( sTargetGridNo, pSoldier->bTargetLevel );
|
|
if ( pTargetSoldier )
|
|
{
|
|
// set target as noticed attack
|
|
pSoldier->flags.uiStatusFlags |= SOLDIER_ATTACK_NOTICED;
|
|
pTargetSoldier->flags.fIntendedTarget = TRUE;
|
|
|
|
// SAVE OPP ID
|
|
pSoldier->ubOppNum = pTargetSoldier->ubID;
|
|
|
|
if (fStealing)
|
|
{
|
|
// Calculate the possible chance to steal!
|
|
if ( AM_A_ROBOT( pTargetSoldier ) || TANK( pTargetSoldier ) || CREATURE_OR_BLOODCAT( pTargetSoldier ) || TANK( pTargetSoldier ) )
|
|
{
|
|
iHitChance = 0;
|
|
}
|
|
else if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN )
|
|
{
|
|
// give bonus for surprise, but not so much as struggle would still occur
|
|
iHitChance = CalcChanceToSteal( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime ) + 20;
|
|
}
|
|
else if ( pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed )
|
|
{
|
|
iHitChance = 100;
|
|
fSoldierCollapsed = TRUE;
|
|
}
|
|
else
|
|
{
|
|
iHitChance = CalcChanceToSteal( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pTargetSoldier->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN || pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed )
|
|
{
|
|
iHitChance = 100;
|
|
}
|
|
else
|
|
{
|
|
iHitChance = CalcChanceToPunch( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime );
|
|
}
|
|
}
|
|
|
|
// ROLL DICE
|
|
iDiceRoll = (INT32) PreRandom( 100 );
|
|
//sprintf( gDebugStr, "Hit Chance: %d %d", (int)uiHitChance, uiDiceRoll );
|
|
|
|
#ifdef JA2BETAVERSION
|
|
if ( gfReportHitChances )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Hit chance was %ld, roll %ld", iHitChance, iDiceRoll );
|
|
}
|
|
#endif
|
|
|
|
// GET TARGET XY VALUES
|
|
ConvertGridNoToCenterCellXY( sTargetGridNo, &sXMapPos, &sYMapPos );
|
|
|
|
// -----------------------------------
|
|
// Steal from the Enemy
|
|
// -----------------------------------
|
|
if (fStealing )
|
|
{
|
|
fFailure=FALSE;
|
|
|
|
// Do we have luck on stealing?
|
|
if ( iDiceRoll <= iHitChance && iHitChance > 0 )
|
|
{
|
|
// Do we have the chance to steal more than 1 item?
|
|
if (( iDiceRoll <= iHitChance * 2 / 3) || (pTargetSoldier->bCollapsed))
|
|
{
|
|
// The item that the enemy holds in his hand before the stealing
|
|
usOldItem = pTargetSoldier->inv[HANDPOS].usItem;
|
|
|
|
INT16 sNumStolenItems = StealItems(pSoldier, pTargetSoldier,&ubIndexRet);
|
|
|
|
// We have only stolen 1 item, because the enemy has not more than one item.
|
|
if ( sNumStolenItems == 1)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[ STR_STOLE_SOMETHING ], pSoldier->name, ShortItemNames[ pTargetSoldier->inv[ubIndexRet].usItem ] );
|
|
if (pTargetSoldier->inv[ubIndexRet].MoveThisObjectTo(gTempObject, 1) == 0) {
|
|
|
|
// Try to place the item in the merc inventory
|
|
if (!AutoPlaceObject( pSoldier, &gTempObject, TRUE ))
|
|
{
|
|
// Place the item on the ground
|
|
AddItemToPool( pSoldier->sGridNo, &gTempObject, 1, pSoldier->pathing.bLevel, 0, -1 );
|
|
}
|
|
}
|
|
|
|
// The item that the enemy holds in his hand before the stealing
|
|
usNewItem = pTargetSoldier->inv[HANDPOS].usItem;
|
|
|
|
if (usOldItem != usNewItem)
|
|
{
|
|
pTargetSoldier->ReLoadSoldierAnimationDueToHandItemChange( usOldItem, usNewItem );
|
|
}
|
|
}
|
|
// The enemy has no more items to steal
|
|
else if (sNumStolenItems == 0)
|
|
{
|
|
fNoMoreItems = TRUE;
|
|
}
|
|
}
|
|
// We had not much luck, so we can only steal 1 item.
|
|
else if ( pTargetSoldier->inv[HANDPOS].exists() == true )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[ STR_STOLE_SOMETHING ], pSoldier->name, ShortItemNames[ pTargetSoldier->inv[HANDPOS].usItem ] );
|
|
usOldItem = pTargetSoldier->inv[HANDPOS].usItem;
|
|
if ( pSoldier->bTeam == gbPlayerNum )
|
|
{
|
|
pSoldier->DoMercBattleSound( BATTLE_SOUND_CURSE1 );
|
|
}
|
|
|
|
// Item dropped somewhere... roll based on the same chance to determine where!
|
|
if (pTargetSoldier->inv[HANDPOS].MoveThisObjectTo(gTempObject, 1) == 0) {
|
|
iDiceRoll = (INT32) PreRandom( 100 );
|
|
if (iDiceRoll < iHitChance)
|
|
{
|
|
// Drop item in the our tile
|
|
AddItemToPool( pSoldier->sGridNo, &gTempObject, 1, pSoldier->pathing.bLevel, 0, -1 );
|
|
}
|
|
else
|
|
{
|
|
// Drop item in the target's tile
|
|
AddItemToPool( pTargetSoldier->sGridNo, &gTempObject, 1, pSoldier->pathing.bLevel, 0, -1 );
|
|
}
|
|
}
|
|
// Reload buddy's animation...
|
|
pTargetSoldier->ReLoadSoldierAnimationDueToHandItemChange( usOldItem, NOTHING );
|
|
}
|
|
// Enemy has no item in his hand.
|
|
else
|
|
{
|
|
fNoMoreItemInHand = TRUE;
|
|
}
|
|
// Reload buddy's animation...
|
|
//pTargetSoldier->ReLoadSoldierAnimationDueToHandItemChange( usOldItem, NOTHING );
|
|
}
|
|
// We could not steal from the enemy, we had no luck
|
|
else
|
|
{
|
|
fFailure=TRUE;
|
|
}
|
|
|
|
// We failed to steal something!
|
|
if (fFailure)
|
|
{
|
|
// Only report if it was not the Nada item!
|
|
if (pTargetSoldier->inv[HANDPOS].exists() == true)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
|
|
Message[ STR_FAILED_TO_STEAL_SOMETHING ],
|
|
pSoldier->name, ShortItemNames[ pTargetSoldier->inv[HANDPOS].usItem ] );
|
|
|
|
if ( pSoldier->bTeam == gbPlayerNum )
|
|
{
|
|
pSoldier->DoMercBattleSound( BATTLE_SOUND_CURSE1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
|
|
Message[ STR_NO_MORE_ITEM_IN_HAND ]);
|
|
}
|
|
}
|
|
// Enemy had no item in its hand
|
|
else if (fNoMoreItemInHand == TRUE)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
|
|
Message[ STR_NO_MORE_ITEM_IN_HAND ]);
|
|
}
|
|
|
|
// The enemy has no more items to steal
|
|
else if (fNoMoreItems == TRUE)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
|
|
Message[ STR_NO_MORE_ITEMS_TO_STEAL ]);
|
|
}
|
|
|
|
// Give some experience
|
|
if ( iHitChance > 0 && pSoldier->bTeam == gbPlayerNum && pTargetSoldier->bTeam != gbPlayerNum && !(pTargetSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) && !AM_A_ROBOT( pTargetSoldier ) && !TANK( pTargetSoldier ) )
|
|
{
|
|
if (fFailure == FALSE)
|
|
{
|
|
// We were successfull in stealing. Give some experience
|
|
StatChange( pSoldier, STRAMT, 8, FALSE );
|
|
StatChange( pSoldier, DEXTAMT, 3, FALSE );
|
|
StatChange( pSoldier, AGILAMT, 3, FALSE );
|
|
}
|
|
else
|
|
{
|
|
// failed a steal; give some experience
|
|
StatChange( pSoldier, STRAMT, 4, FROM_FAILURE );
|
|
StatChange( pSoldier, DEXTAMT, 1, FROM_FAILURE );
|
|
StatChange( pSoldier, AGILAMT, 1, FROM_FAILURE );
|
|
}
|
|
}
|
|
|
|
// 0verhaul: Also handled in the animation transition
|
|
// #ifdef JA2BETAVERSION
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - steal") );
|
|
// #endif
|
|
// FreeUpAttacker( (UINT8) pSoldier->ubID );
|
|
}
|
|
|
|
// -----------------------------------
|
|
// Punch, Kick the Enemy
|
|
// -----------------------------------
|
|
else
|
|
{
|
|
// ATE/CC: if doing ninja spin kick (only), automatically make it a hit
|
|
if ( pSoldier->usAnimState == NINJA_SPINKICK)
|
|
{
|
|
// Let him to succeed by a random amount
|
|
iDiceRoll = PreRandom( iHitChance );
|
|
}
|
|
|
|
if ( pSoldier->bTeam == gbPlayerNum && pTargetSoldier->bTeam != gbPlayerNum )
|
|
{
|
|
// made an HTH attack; give experience
|
|
if ( iDiceRoll <= iHitChance )
|
|
{
|
|
ubExpGain = 8;
|
|
|
|
if ( pTargetSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE || AM_A_ROBOT( pTargetSoldier ) || TANK( pTargetSoldier ) )
|
|
{
|
|
ubExpGain = 0;
|
|
}
|
|
else if ( pTargetSoldier->ubBodyType == COW || pTargetSoldier->ubBodyType == CROW )
|
|
{
|
|
ubExpGain /= 2;
|
|
}
|
|
|
|
|
|
StatChange( pSoldier, DEXTAMT, ubExpGain, FALSE );
|
|
StatChange( pSoldier, STRAMT, (ubExpGain+1)/3, FALSE );
|
|
StatChange( pSoldier, AGILAMT, (ubExpGain+1)/3, FALSE );
|
|
}
|
|
else
|
|
{
|
|
ubExpGain = 4;
|
|
|
|
if ( pTargetSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE || AM_A_ROBOT( pTargetSoldier ) || TANK( pTargetSoldier ) )
|
|
{
|
|
ubExpGain = 0;
|
|
}
|
|
else if ( pTargetSoldier->ubBodyType == COW || pTargetSoldier->ubBodyType == CROW )
|
|
{
|
|
ubExpGain /= 2;
|
|
}
|
|
|
|
|
|
StatChange( pSoldier, DEXTAMT, ubExpGain, FALSE );
|
|
StatChange( pSoldier, STRAMT, (ubExpGain+1)/3, FALSE );
|
|
StatChange( pSoldier, AGILAMT, (ubExpGain+1)/3, FALSE );
|
|
}
|
|
}
|
|
else if ( pSoldier->bTeam != gbPlayerNum && pTargetSoldier->bTeam == gbPlayerNum )
|
|
{
|
|
// being attacked... if successfully dodged, give experience
|
|
if ( iDiceRoll > iHitChance )
|
|
{
|
|
StatChange( pTargetSoldier, DEXTAMT, 8, FALSE );
|
|
StatChange( pTargetSoldier, STRAMT, 3, FALSE );
|
|
StatChange( pTargetSoldier, AGILAMT, 3, FALSE );
|
|
}
|
|
}
|
|
|
|
if ( iDiceRoll <= iHitChance || AreInMeanwhile( ) )
|
|
{
|
|
// CALCULATE DAMAGE!
|
|
iImpact = HTHImpact( pSoldier, pTargetSoldier, (iHitChance - iDiceRoll), FALSE );
|
|
|
|
// Send event for getting hit
|
|
memset( &(SWeaponHit), 0, sizeof( SWeaponHit ) );
|
|
SWeaponHit.usSoldierID = pTargetSoldier->ubID;
|
|
SWeaponHit.usWeaponIndex = pSoldier->usAttackingWeapon;
|
|
SWeaponHit.sDamage = (INT16) iImpact;
|
|
SWeaponHit.usDirection = (UINT8)GetDirectionFromGridNo( pSoldier->sGridNo, pTargetSoldier );
|
|
SWeaponHit.sXPos = (INT16)pTargetSoldier->dXPos;
|
|
SWeaponHit.sYPos = (INT16)pTargetSoldier->dYPos;
|
|
SWeaponHit.sZPos = 20;
|
|
SWeaponHit.sRange = 1;
|
|
SWeaponHit.ubAttackerID = pSoldier->ubID;
|
|
SWeaponHit.fHit = TRUE;
|
|
SWeaponHit.ubSpecial = FIRE_WEAPON_NO_SPECIAL;
|
|
AddGameEvent( S_WEAPONHIT, (UINT16) 20, &SWeaponHit );
|
|
}
|
|
// 0verhaul: And this too
|
|
// else
|
|
// {
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - missed in HTH attack") );
|
|
// FreeUpAttacker( (UINT8) pSoldier->ubID );
|
|
// }
|
|
}
|
|
}
|
|
|
|
// possibly reduce monster smell (gunpowder smell)
|
|
if ( pSoldier->aiData.bMonsterSmell > 0 && Random( 5 ) == 0 )
|
|
{
|
|
pSoldier->aiData.bMonsterSmell--;
|
|
}
|
|
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
BOOLEAN UseThrown( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo )
|
|
{
|
|
UINT32 uiHitChance, uiDiceRoll;
|
|
INT8 bLoop;
|
|
UINT8 ubTargetID;
|
|
SOLDIERTYPE * pTargetSoldier;
|
|
INT16 sAPCost = 0;
|
|
|
|
uiHitChance = CalcThrownChanceToHit( pSoldier, sTargetGridNo, pSoldier->aiData.bAimTime, AIM_SHOT_TORSO );
|
|
|
|
uiDiceRoll = PreRandom( 100 );
|
|
|
|
#ifdef JA2BETAVERSION
|
|
if ( gfReportHitChances )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Hit chance was %ld, roll %ld (range %d)", uiHitChance, uiDiceRoll, PythSpacesAway( pSoldier->sGridNo, sTargetGridNo ) );
|
|
}
|
|
#endif
|
|
|
|
if ( pSoldier->bTeam == gbPlayerNum && gTacticalStatus.uiFlags & INCOMBAT )
|
|
{
|
|
// check target gridno
|
|
ubTargetID = WhoIsThere2( pSoldier->sTargetGridNo, pSoldier->bTargetLevel );
|
|
if ( ubTargetID == NOBODY )
|
|
{
|
|
pTargetSoldier = NULL;
|
|
}
|
|
else
|
|
{
|
|
pTargetSoldier = MercPtrs[ ubTargetID ];
|
|
}
|
|
|
|
if ( pTargetSoldier && pTargetSoldier->bTeam == pSoldier->bTeam )
|
|
{
|
|
// ignore!
|
|
pTargetSoldier = NULL;
|
|
}
|
|
|
|
// Snap: Find the number of real targets for grenade attack
|
|
UINT16 usNumTargets = 0;
|
|
if ( pTargetSoldier != NULL )
|
|
{
|
|
usNumTargets++;
|
|
}
|
|
// search for an opponent near the target gridno
|
|
for ( bLoop = 0; bLoop < NUM_WORLD_DIRECTIONS; bLoop++ )
|
|
{
|
|
ubTargetID = WhoIsThere2( NewGridNo( pSoldier->sTargetGridNo, DirectionInc( bLoop ) ), pSoldier->bTargetLevel );
|
|
pTargetSoldier = NULL;
|
|
if ( ubTargetID != NOBODY )
|
|
{
|
|
pTargetSoldier = MercPtrs[ ubTargetID ];
|
|
if ( pTargetSoldier->bTeam != pSoldier->bTeam )
|
|
{
|
|
usNumTargets++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( usNumTargets )
|
|
{
|
|
// ok this is a real attack on someone, grant experience
|
|
// Snap: calculate experience points for strength: weight x distance
|
|
UINT16 usHandItem = pSoldier->inv[HANDPOS].usItem;
|
|
// calculate actual range (in world units)
|
|
INT32 iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sTargetGridNo );
|
|
// 10 points for a regular grenade thrown at 100 units (1 cell = 10 units)
|
|
UINT16 usExpGain = ( Item[usHandItem].ubWeight * iRange + 30) / 60;
|
|
StatChange( pSoldier, STRAMT, usExpGain, FALSE );
|
|
|
|
// Snap: calculate experience points for dexterity and marksmanship
|
|
usExpGain = 5 // For at least one target
|
|
+ 2*(usNumTargets-1); // For every additional target
|
|
BOOLEAN fGonnaHit = uiDiceRoll < uiHitChance;
|
|
if ( fGonnaHit ) usExpGain *= 2; // For actually hitting the target :)
|
|
usExpGain += (UINT16) (100 - uiHitChance) / 10; // Extra exp for a difficult target
|
|
StatChange( pSoldier, DEXTAMT, usExpGain / 2, ( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
StatChange( pSoldier, MARKAMT, usExpGain / 2, ( fGonnaHit ? FALSE : FROM_FAILURE ) );
|
|
|
|
/*if ( uiDiceRoll < uiHitChance )
|
|
{
|
|
usExpGain *= 2;
|
|
usExpGain += (UINT16) ( 100 - uiHitChance ) / 10;
|
|
StatChange( pSoldier, DEXTAMT, usExpGain / 2, FALSE );
|
|
StatChange( pSoldier, MARKAMT, usExpGain / 2, FALSE );
|
|
}
|
|
else
|
|
{
|
|
StatChange( pSoldier, DEXTAMT, usExpGain / 2, FROM_FAILURE );
|
|
StatChange( pSoldier, MARKAMT, usExpGain / 2, FROM_FAILURE );
|
|
}*/
|
|
}
|
|
}
|
|
|
|
|
|
CalculateLaunchItemParamsForThrow( pSoldier, sTargetGridNo, pSoldier->bTargetLevel, (INT16)(pSoldier->bTargetLevel * 256 ), &(pSoldier->inv[ HANDPOS ] ), (INT8)(uiDiceRoll - uiHitChance), THROW_ARM_ITEM, 0 );
|
|
|
|
//AXP 25.03.2007: Cleaned up throwing AP costs. Now only turning + stance change AP
|
|
// costs are deducted. Final throw cost is deducted on creating the grenade object
|
|
if ( (UINT8)GetDirectionFromGridNo( sTargetGridNo, pSoldier ) != pSoldier->ubDirection )
|
|
sAPCost += (INT16)GetAPsToLook( pSoldier );
|
|
sAPCost += (INT16)GetAPsToChangeStance( pSoldier, ANIM_STAND );
|
|
|
|
HandleSoldierThrowItem( pSoldier, pSoldier->sTargetGridNo );
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
pSoldier->inv[ HANDPOS ].RemoveObjectsFromStack(1);
|
|
|
|
/*
|
|
// Madd: Next 2 lines added: Deduct points!
|
|
|
|
sAPCost = CalcTotalAPsToAttack( pSoldier, sTargetGridNo, FALSE, pSoldier->aiData.bAimTime );
|
|
// Kaiden: Deducting points too early, moving the line down
|
|
//DeductPoints( pSoldier, sAPCost, 0 );
|
|
|
|
// OK, goto throw animation
|
|
HandleSoldierThrowItem( pSoldier, pSoldier->sTargetGridNo );
|
|
// Kaiden: Deducting points too early, moving the line down
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
RemoveObjs( &(pSoldier->inv[ HANDPOS ] ), 1 );
|
|
*/
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
BOOLEAN UseLauncher( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo )
|
|
{
|
|
UINT32 uiHitChance, uiDiceRoll;
|
|
INT16 sAPCost = 0;
|
|
OBJECTTYPE Launchable;
|
|
OBJECTTYPE * pObj;
|
|
UINT16 usItemNum;
|
|
INT32 iID;
|
|
REAL_OBJECT *pObject;
|
|
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("UseLauncher, target = %d", sTargetGridNo) );
|
|
usItemNum = pSoldier->usAttackingWeapon;
|
|
|
|
if ( !EnoughAmmo( pSoldier, TRUE, pSoldier->ubAttackingHand ) )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
pObj = &(pSoldier->inv[HANDPOS]);
|
|
attachmentList::iterator iter;
|
|
for (iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
|
|
if ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (iter == (*pObj)[0]->attachments.end())
|
|
{
|
|
// this should not happen!!
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( Weapon[GetAttachedGrenadeLauncher(pObj)].ubMagSize > 1 || ( Item[pObj->usItem].grenadelauncher && GetMagSize(pObj) > 1 ))
|
|
CreateItem( iter->usItem, 100, &Launchable );
|
|
else
|
|
CreateItem( iter->usItem, (*iter)[0]->data.objectStatus, &Launchable );
|
|
//if ( pSoldier->usAttackingWeapon == pObj->usItem )
|
|
//{
|
|
DeductAmmo( pSoldier, HANDPOS );
|
|
//}
|
|
//else
|
|
//{
|
|
// // Firing an attached grenade launcher... the attachment we found above
|
|
// // is the one to remove!
|
|
// RemoveAttachment( pObj, bAttachPos);
|
|
//}
|
|
|
|
// ATE: Check here if the launcher should fail 'cause of bad status.....
|
|
if ( WillExplosiveWeaponFail( pSoldier, pObj ) )
|
|
{
|
|
// Explode dude!
|
|
|
|
// So we still should have ABC > 0
|
|
// Begin explosion due to failure...
|
|
IgniteExplosion( pSoldier->ubID, (INT16)CenterX( pSoldier->sGridNo ), (INT16)CenterY( pSoldier->sGridNo ), 0, pSoldier->sGridNo, Launchable.usItem, pSoldier->pathing.bLevel );
|
|
|
|
// Reduce again for attack end 'cause it has been incremented for a normal attack
|
|
// Nope, not anymore.
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - ATTACK ANIMATION %s ENDED BY BAD EXPLOSIVE CHECK, Now %d", gAnimControl[ pSoldier->usAnimState ].zAnimStr, gTacticalStatus.ubAttackBusyCount ) );
|
|
DebugAttackBusy( String("@@@@@@@ Freeing up attacker - ATTACK ANIMATION %s ENDED BY BAD EXPLOSIVE CHECK\n", gAnimControl[ pSoldier->usAnimState ].zAnimStr ) );
|
|
// ReduceAttackBusyCount( pSoldier->ubID, FALSE );
|
|
|
|
// So all's well, should be good from here....
|
|
return( FALSE );
|
|
}
|
|
|
|
if ( Weapon[ usItemNum ].sSound != NO_WEAPON_SOUND )
|
|
{
|
|
if (Item[ usItemNum ].usItemClass == IC_GUN )
|
|
PlayJA2Sample( Weapon[ UNDER_GLAUNCHER ].sSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
else
|
|
PlayJA2Sample( Weapon[ usItemNum ].sSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
|
|
}
|
|
|
|
uiHitChance = CalcThrownChanceToHit( pSoldier, sTargetGridNo, pSoldier->aiData.bAimTime, AIM_SHOT_TORSO );
|
|
|
|
uiDiceRoll = PreRandom( 100 );
|
|
|
|
#ifdef JA2BETAVERSION
|
|
if ( gfReportHitChances )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Hit chance was %ld, roll %ld (range %d)", uiHitChance, uiDiceRoll, PythSpacesAway( pSoldier->sGridNo, sTargetGridNo ) );
|
|
}
|
|
#endif
|
|
|
|
|
|
if ( Item[ usItemNum ].usItemClass == IC_LAUNCHER )
|
|
{
|
|
// Preserve gridno!
|
|
//pSoldier->sLastTarget = sTargetGridNo;
|
|
|
|
//sAPCost = MinAPsToAttack( pSoldier, sTargetGridNo, TRUE );
|
|
|
|
sAPCost = CalcTotalAPsToAttack( pSoldier, sTargetGridNo, FALSE, 0 );
|
|
|
|
}
|
|
else
|
|
{
|
|
// Throw....
|
|
sAPCost = MinAPsToThrow( pSoldier, sTargetGridNo, FALSE );
|
|
}
|
|
|
|
if ( pSoldier->bDoBurst )
|
|
{
|
|
// ONly deduct points once
|
|
if ( pSoldier->bDoBurst == 1 )
|
|
{
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DeductPoints( pSoldier, sAPCost, 0 );
|
|
}
|
|
|
|
CalculateLaunchItemParamsForThrow( pSoldier, sTargetGridNo, pSoldier->bTargetLevel, 0, &Launchable, (INT8)(uiDiceRoll - uiHitChance), THROW_ARM_ITEM, 0 );
|
|
|
|
iID = CreatePhysicalObject( pSoldier->pTempObject, pSoldier->pThrowParams->dLifeSpan, pSoldier->pThrowParams->dX, pSoldier->pThrowParams->dY, pSoldier->pThrowParams->dZ, pSoldier->pThrowParams->dForceX, pSoldier->pThrowParams->dForceY, pSoldier->pThrowParams->dForceZ, pSoldier->ubID, pSoldier->pThrowParams->ubActionCode, pSoldier->pThrowParams->uiActionData, FALSE );
|
|
|
|
pObject = &( ObjectSlots[ iID ] );
|
|
//pObject->fPotentialForDebug = TRUE;
|
|
|
|
|
|
OBJECTTYPE::DeleteMe( &pSoldier->pTempObject );
|
|
|
|
MemFree( pSoldier->pThrowParams );
|
|
pSoldier->pThrowParams = NULL;
|
|
|
|
if ( pSoldier->bDoBurst )
|
|
{
|
|
// done, if bursting, increment
|
|
pSoldier->bDoBurst++;
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
BOOLEAN DoSpecialEffectAmmoMiss( UINT8 ubAttackerID, INT16 sGridNo, INT16 sXPos, INT16 sYPos, INT16 sZPos, BOOLEAN fSoundOnly, BOOLEAN fFreeupAttacker, INT32 iBullet )
|
|
{
|
|
ANITILE_PARAMS AniParams;
|
|
UINT8 ubAmmoType;
|
|
UINT16 usItem;
|
|
|
|
ubAmmoType = MercPtrs[ ubAttackerID ]->inv[ MercPtrs[ ubAttackerID ]->ubAttackingHand ][0]->data.gun.ubGunAmmoType;
|
|
usItem = MercPtrs[ ubAttackerID ]->inv[ MercPtrs[ ubAttackerID ]->ubAttackingHand ].usItem;
|
|
|
|
memset( &AniParams, 0, sizeof( ANITILE_PARAMS ) );
|
|
|
|
if ( AmmoTypes[ubAmmoType].explosionSize == 1 )
|
|
{
|
|
if ( !fSoundOnly )
|
|
{
|
|
AniParams.sGridNo = sGridNo;
|
|
AniParams.ubLevelID = ANI_TOPMOST_LEVEL;
|
|
AniParams.sDelay = (INT16)( 100 );
|
|
AniParams.sStartFrame = 0;
|
|
AniParams.uiFlags = ANITILE_CACHEDTILE | ANITILE_FORWARD | ANITILE_ALWAYS_TRANSLUCENT;
|
|
AniParams.sX = sXPos;
|
|
AniParams.sY = sYPos;
|
|
AniParams.sZ = sZPos;
|
|
|
|
strcpy( AniParams.zCachedFile, "TILECACHE\\MINIBOOM.STI" );
|
|
|
|
CreateAnimationTile( &AniParams );
|
|
|
|
if ( fFreeupAttacker )
|
|
{
|
|
RemoveBullet( iBullet );
|
|
// 0verhaul: No longer necessary
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - bullet hit structure - explosive ammo") );
|
|
// FreeUpAttacker( (UINT8) ubAttackerID );
|
|
}
|
|
}
|
|
|
|
if ( sGridNo != NOWHERE )
|
|
{
|
|
PlayJA2Sample( SMALL_EXPLODE_1 , RATE_11025, SoundVolume( (INT8)HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
|
|
}
|
|
else
|
|
{
|
|
PlayJA2Sample( SMALL_EXPLODE_1 , RATE_11025, MIDVOLUME, 1, MIDDLE );
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
else if ( AmmoTypes[ubAmmoType].explosionSize == 2 )
|
|
{
|
|
if ( !fSoundOnly )
|
|
{
|
|
AniParams.sGridNo = sGridNo;
|
|
AniParams.ubLevelID = ANI_TOPMOST_LEVEL;
|
|
AniParams.sDelay = (INT16)( 100 );
|
|
AniParams.sStartFrame = 0;
|
|
AniParams.uiFlags = ANITILE_CACHEDTILE | ANITILE_FORWARD | ANITILE_ALWAYS_TRANSLUCENT;
|
|
AniParams.sX = sXPos;
|
|
AniParams.sY = sYPos;
|
|
AniParams.sZ = sZPos;
|
|
|
|
strcpy( AniParams.zCachedFile, "TILECACHE\\ZGRAV_D.STI" );
|
|
|
|
CreateAnimationTile( &AniParams );
|
|
|
|
if ( fFreeupAttacker )
|
|
{
|
|
RemoveBullet( iBullet );
|
|
// 0verhaul: No longer necessary.
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - bullet hit structure - explosive ammo") );
|
|
// FreeUpAttacker( (UINT8) ubAttackerID );
|
|
}
|
|
}
|
|
|
|
if ( sGridNo != NOWHERE )
|
|
{
|
|
PlayJA2Sample( EXPLOSION_1 , RATE_11025, SoundVolume( (INT8)HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
|
|
}
|
|
else
|
|
{
|
|
PlayJA2Sample( EXPLOSION_1 , RATE_11025, MIDVOLUME, 1, MIDDLE );
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
else if ( AmmoTypes[ubAmmoType].explosionSize == 3 )
|
|
{
|
|
if ( !fSoundOnly )
|
|
{
|
|
AniParams.sGridNo = sGridNo;
|
|
AniParams.ubLevelID = ANI_TOPMOST_LEVEL;
|
|
AniParams.sDelay = (INT16)( 100 );
|
|
AniParams.sStartFrame = 0;
|
|
AniParams.uiFlags = ANITILE_CACHEDTILE | ANITILE_FORWARD | ANITILE_ALWAYS_TRANSLUCENT;
|
|
AniParams.sX = sXPos;
|
|
AniParams.sY = sYPos;
|
|
AniParams.sZ = sZPos;
|
|
|
|
strcpy( AniParams.zCachedFile, "TILECACHE\\ZGRAV_C.STI" );
|
|
|
|
CreateAnimationTile( &AniParams );
|
|
|
|
if ( fFreeupAttacker )
|
|
{
|
|
RemoveBullet( iBullet );
|
|
// 0verhaul: No longer necessary.
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - bullet hit structure - explosive ammo") );
|
|
// FreeUpAttacker( (UINT8) ubAttackerID );
|
|
}
|
|
}
|
|
|
|
if ( sGridNo != NOWHERE )
|
|
{
|
|
PlayJA2Sample( EXPLOSION_BLAST_2 , RATE_11025, SoundVolume( (INT8)HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
|
|
}
|
|
else
|
|
{
|
|
PlayJA2Sample( EXPLOSION_BLAST_2 , RATE_11025, MIDVOLUME, 1, MIDDLE );
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
else if ( AmmoTypes[ubAmmoType].monsterSpit )
|
|
{
|
|
// Increment attack busy...
|
|
// gTacticalStatus.ubAttackBusyCount++;
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Incrementing Attack: Explosion gone off, COunt now %d", gTacticalStatus.ubAttackBusyCount ) );
|
|
|
|
PlayJA2Sample( CREATURE_GAS_NOISE, RATE_11025, SoundVolume( HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
|
|
// 0verhaul: Not ready to simplify this to a single call yet. What we need in order to
|
|
// fix this correctly is to detach 'alien spit' as a special caliber and instead add 3 new calibers for actual "ammo":
|
|
// very small spit, small spit, and large spit. Then we need to add a special effect miss index to point to the
|
|
// appropriate smoke effect to disperse.
|
|
//NewSmokeEffect( sGridNo, usItem, 0, ubAttackerID);
|
|
|
|
// Do Spread effect.......
|
|
switch( usItem )
|
|
{
|
|
case CREATURE_YOUNG_MALE_SPIT:
|
|
case CREATURE_INFANT_SPIT:
|
|
|
|
NewSmokeEffect( sGridNo, VERY_SMALL_CREATURE_GAS, 0, ubAttackerID );
|
|
break;
|
|
case CREATURE_OLD_MALE_SPIT:
|
|
NewSmokeEffect( sGridNo, SMALL_CREATURE_GAS, 0, ubAttackerID );
|
|
break;
|
|
|
|
case CREATURE_QUEEN_SPIT:
|
|
NewSmokeEffect( sGridNo, LARGE_CREATURE_GAS, 0, ubAttackerID );
|
|
break;
|
|
}
|
|
}
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
void WeaponHit( UINT16 usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, UINT16 usDirection, INT16 sXPos, INT16 sYPos, INT16 sZPos, INT16 sRange , UINT8 ubAttackerID, BOOLEAN fHit, UINT8 ubSpecial, UINT8 ubHitLocation )
|
|
{
|
|
SOLDIERTYPE *pTargetSoldier, *pSoldier;
|
|
|
|
// Get attacker
|
|
pSoldier = MercPtrs[ ubAttackerID ];
|
|
|
|
// Get Target
|
|
pTargetSoldier = MercPtrs[ usSoldierID ];
|
|
|
|
MakeNoise( ubAttackerID, pTargetSoldier->sGridNo, pTargetSoldier->pathing.bLevel, gpWorldLevelData[pTargetSoldier->sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT );
|
|
|
|
// CALLAHAN START BUGFIX
|
|
if ( EXPLOSIVE_GUN( usWeaponIndex ) || AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].explosionSize > 1)
|
|
// CALLAHAN END BUGFIX
|
|
{
|
|
// Reduce attacker count!
|
|
//TODO: Madd --- I don't think this code will ever get called for the HE ammo -- the EXPLOSIVE_GUN check filters out regular guns
|
|
// marke test mag ammo type: pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType
|
|
// 2cond 'or' added
|
|
if ( Item[usWeaponIndex].rocketlauncher || AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].explosionSize > 1 )
|
|
{
|
|
if ( Item[usWeaponIndex].singleshotrocketlauncher )
|
|
{
|
|
IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), C1, pTargetSoldier->pathing.bLevel );
|
|
}
|
|
// changed rpg type to work only with two flags matching
|
|
else if ( !Item[usWeaponIndex].singleshotrocketlauncher && Item[usWeaponIndex].rocketlauncher)
|
|
{
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("WeaponHit: RPG7 item: %d, Ammo: %d",pSoldier->inv[HANDPOS].usItem , pSoldier->inv[HANDPOS][0]->data.gun.usGunAmmoItem ) );
|
|
|
|
IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem, pTargetSoldier->pathing.bLevel );
|
|
pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE;
|
|
}
|
|
else if ( AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].explosionSize > 1)
|
|
{
|
|
// re-routed the Highexplosive value to define exposion type
|
|
IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive , pTargetSoldier->pathing.bLevel );
|
|
// pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE;
|
|
}
|
|
}
|
|
else // tank cannon
|
|
{
|
|
IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), TANK_SHELL, pTargetSoldier->pathing.bLevel );
|
|
}
|
|
|
|
// 0verhaul: No longer necessary
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - end of LAW fire") );
|
|
// FreeUpAttacker( ubAttackerID );
|
|
return;
|
|
}
|
|
|
|
DoSpecialEffectAmmoMiss( ubAttackerID, pTargetSoldier->sGridNo, sXPos, sYPos, sZPos, FALSE, FALSE, 0 );
|
|
|
|
// OK, SHOT HAS HIT, DO THINGS APPROPRIATELY
|
|
// ATE: This is 'cause of that darn smoke effect that could potentially kill
|
|
// the poor bastard .. so check
|
|
if ( !pTargetSoldier->flags.fDoingExternalDeath )
|
|
{
|
|
pTargetSoldier->EVENT_SoldierGotHit( usWeaponIndex, sDamage, sBreathLoss, usDirection, sRange, ubAttackerID, ubSpecial, ubHitLocation, 0, NOWHERE );
|
|
}
|
|
// else
|
|
// {
|
|
// Buddy had died from additional damage - free up attacker here...
|
|
// ReduceAttackBusyCount( pTargetSoldier->ubAttackerID, FALSE );
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Special effect killed before bullet impact, attack count now %d", gTacticalStatus.ubAttackBusyCount) );
|
|
// OutputDebugString( "Special effect killed before bullet impact\n" );
|
|
// }
|
|
}
|
|
|
|
|
|
void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UINT8 ubAttackerID, UINT16 sXPos, INT16 sYPos, INT16 sZPos, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped )
|
|
{
|
|
BOOLEAN fDoMissForGun = FALSE;
|
|
ANITILE *pNode;
|
|
INT16 sGridNo;
|
|
ANITILE_PARAMS AniParams;
|
|
UINT16 usMissTileIndex, usMissTileType;
|
|
STRUCTURE *pStructure = NULL;
|
|
UINT32 uiMissVolume = MIDVOLUME;
|
|
BOOLEAN fHitSameStructureAsBefore;
|
|
BULLET * pBullet;
|
|
SOLDIERTYPE * pAttacker = NULL;
|
|
|
|
pBullet = GetBulletPtr( iBullet );
|
|
|
|
if ( fStopped && ubAttackerID != NOBODY )
|
|
{
|
|
pAttacker = MercPtrs[ ubAttackerID ];
|
|
|
|
if ( pAttacker->ubOppNum != NOBODY )
|
|
{
|
|
// if it was another team shooting at someone under our control
|
|
if ( (pAttacker->bTeam != Menptr[ pAttacker->ubOppNum ].bTeam ) )
|
|
{
|
|
// if OPPONENT is under our control
|
|
if (Menptr[ pAttacker->ubOppNum ].bTeam == gbPlayerNum )
|
|
{
|
|
// AGILITY GAIN: Opponent "dodged" a bullet shot at him (it missed)
|
|
StatChange( MercPtrs[ pAttacker->ubOppNum ], AGILAMT, 5, FROM_FAILURE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pBullet )
|
|
{
|
|
fHitSameStructureAsBefore = ( usStructureID == pBullet->usLastStructureHit );
|
|
}
|
|
else
|
|
{
|
|
// WTF?
|
|
fHitSameStructureAsBefore = FALSE;
|
|
}
|
|
|
|
sGridNo = MAPROWCOLTOPOS( (sYPos/CELL_Y_SIZE), (sXPos/CELL_X_SIZE) );
|
|
if ( !fHitSameStructureAsBefore )
|
|
{
|
|
|
|
|
|
if (sZPos > WALL_HEIGHT)
|
|
{
|
|
MakeNoise( ubAttackerID, sGridNo, 1, gpWorldLevelData[sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT );
|
|
}
|
|
else
|
|
{
|
|
MakeNoise( ubAttackerID, sGridNo, 0, gpWorldLevelData[sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT );
|
|
}
|
|
|
|
}
|
|
|
|
if (fStopped)
|
|
{
|
|
// marke need another attacker id assignment
|
|
SOLDIERTYPE *pSoldier;
|
|
|
|
// Get attacker
|
|
pSoldier = MercPtrs[ ubAttackerID ];
|
|
// marke added one 'or' to get this working with HE ammo
|
|
if ( Item[usWeaponIndex].rocketlauncher || AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].explosionSize > 1)
|
|
{
|
|
// Reduce attacker count!
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - end of LAW fire") );
|
|
// FreeUpAttacker( ubAttackerID );
|
|
if ( Item[usWeaponIndex].singleshotrocketlauncher )
|
|
{
|
|
IgniteExplosion( ubAttackerID, (INT16)CenterX( sGridNo ), (INT16)CenterY( sGridNo ), 0, sGridNo, C1, (INT8)( sZPos >= WALL_HEIGHT ) );
|
|
}
|
|
// changed too to use 2 flag to determine
|
|
else if ( !Item[usWeaponIndex].singleshotrocketlauncher && Item[usWeaponIndex].rocketlauncher)
|
|
{
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("StructureHit: RPG7 item: %d, Ammo: %d",pAttacker->inv[HANDPOS].usItem , pAttacker->inv[HANDPOS][0]->data.gun.usGunAmmoItem ) );
|
|
IgniteExplosion( ubAttackerID, (INT16)CenterX( sGridNo ), (INT16)CenterY( sGridNo ), 0, sGridNo, pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem , (INT8)( sZPos >= WALL_HEIGHT ) );
|
|
pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE;
|
|
}
|
|
else if ( AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].explosionSize > 1)
|
|
{
|
|
// re-routed the Highexplosive value to define exposion type
|
|
IgniteExplosion( ubAttackerID, (INT16)CenterX( sGridNo ), (INT16)CenterY( sGridNo ), 0, sGridNo, AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive , (INT8)( sZPos >= WALL_HEIGHT ) );
|
|
// pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE;
|
|
}
|
|
|
|
// Moved here to make sure ABC stays >0 until everything done
|
|
RemoveBullet( iBullet );
|
|
|
|
return;
|
|
}
|
|
|
|
if ( Item[usWeaponIndex].cannon )
|
|
{
|
|
// Reduce attacker count!
|
|
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - end of TANK fire") );
|
|
//FreeUpAttacker( ubAttackerID );
|
|
|
|
IgniteExplosion( ubAttackerID, (INT16)CenterX( sGridNo ), (INT16)CenterY( sGridNo ), 0, sGridNo, TANK_SHELL, (INT8)( sZPos >= WALL_HEIGHT ) );
|
|
//FreeUpAttacker( (UINT8) ubAttackerID );
|
|
|
|
// Moved here to keep ABC >0 as long as possible
|
|
RemoveBullet( iBullet );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get Structure pointer and damage it!
|
|
if ( usStructureID != INVALID_STRUCTURE_ID )
|
|
{
|
|
pStructure = FindStructureByID( sGridNo, usStructureID );
|
|
|
|
DamageStructure( pStructure, (UINT8)iImpact, STRUCTURE_DAMAGE_GUNFIRE, sGridNo, sXPos, sYPos, ubAttackerID );
|
|
}
|
|
|
|
switch( Weapon[ usWeaponIndex ].ubWeaponClass )
|
|
{
|
|
case HANDGUNCLASS:
|
|
case RIFLECLASS:
|
|
case SHOTGUNCLASS:
|
|
case SMGCLASS:
|
|
case MGCLASS:
|
|
|
|
// Guy has missed, play random sound
|
|
if ( MercPtrs[ ubAttackerID ]->bTeam == gbPlayerNum )
|
|
{
|
|
if ( !MercPtrs[ ubAttackerID ]->bDoBurst )
|
|
{
|
|
if ( Random( 40 ) == 0 )
|
|
{
|
|
MercPtrs[ ubAttackerID ]->DoMercBattleSound( BATTLE_SOUND_CURSE1 );
|
|
}
|
|
}
|
|
}
|
|
//fDoMissForGun = TRUE;
|
|
//break;
|
|
fDoMissForGun = TRUE;
|
|
break;
|
|
|
|
case MONSTERCLASS:
|
|
|
|
DoSpecialEffectAmmoMiss( ubAttackerID, sGridNo, sXPos, sYPos, sZPos, FALSE, TRUE, iBullet );
|
|
|
|
RemoveBullet( iBullet );
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - monster attack hit structure") );
|
|
// FreeUpAttacker( (UINT8) ubAttackerID );
|
|
|
|
//PlayJA2Sample( SPIT_RICOCHET , RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) );
|
|
break;
|
|
|
|
case KNIFECLASS:
|
|
|
|
// When it hits the ground, leave on map...
|
|
if ( Item[ usWeaponIndex ].usItemClass == IC_THROWING_KNIFE )
|
|
{
|
|
// OK, have we hit ground?
|
|
if ( usStructureID == INVALID_STRUCTURE_ID )
|
|
{
|
|
// Add item
|
|
CreateItem( usWeaponIndex, bWeaponStatus, &gTempObject );
|
|
|
|
AddItemToPool( sGridNo, &gTempObject, -1 , 0, 0, -1 );
|
|
|
|
// Make team look for items
|
|
NotifySoldiersToLookforItems( );
|
|
}
|
|
|
|
if ( !fHitSameStructureAsBefore )
|
|
{
|
|
PlayJA2Sample( MISS_KNIFE, RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) );
|
|
}
|
|
|
|
RemoveBullet( iBullet );
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - knife attack hit structure") );
|
|
// FreeUpAttacker( (UINT8) ubAttackerID );
|
|
}
|
|
}
|
|
|
|
if ( fDoMissForGun )
|
|
{
|
|
// OK, are we a shotgun, if so , make sounds lower...
|
|
if ( Weapon[ usWeaponIndex ].ubWeaponClass == SHOTGUNCLASS )
|
|
{
|
|
uiMissVolume = LOWVOLUME;
|
|
}
|
|
|
|
// Free guy!
|
|
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - bullet hit structure") );
|
|
//FreeUpAttacker( (UINT8) ubAttackerID );
|
|
|
|
|
|
// PLAY SOUND AND FLING DEBRIS
|
|
// RANDOMIZE SOUND SYSTEM
|
|
|
|
// IF WE HIT THE GROUND
|
|
|
|
if ( fHitSameStructureAsBefore )
|
|
{
|
|
if ( fStopped )
|
|
{
|
|
RemoveBullet( iBullet );
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - bullet hit same structure twice") );
|
|
// FreeUpAttacker( (UINT8) ubAttackerID );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if ( !fStopped || !DoSpecialEffectAmmoMiss( ubAttackerID, sGridNo, sXPos, sYPos, sZPos, FALSE, TRUE, iBullet ) )
|
|
{
|
|
if ( sZPos == 0 )
|
|
{
|
|
PlayJA2Sample( MISS_G2 , RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) );
|
|
}
|
|
else
|
|
{
|
|
PlayJA2Sample( MISS_1 + Random(8), RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) );
|
|
}
|
|
|
|
// Default hit is the ground
|
|
usMissTileIndex = FIRSTMISS1;
|
|
usMissTileType = FIRSTMISS;
|
|
|
|
// Check if we are in water...
|
|
if ( TERRAIN_IS_WATER( gpWorldLevelData[ sGridNo ].ubTerrainID) )
|
|
{
|
|
usMissTileIndex = SECONDMISS1;
|
|
usMissTileType = SECONDMISS;
|
|
|
|
// Add ripple
|
|
memset( &AniParams, 0, sizeof( ANITILE_PARAMS ) );
|
|
AniParams.sGridNo = sGridNo;
|
|
AniParams.ubLevelID = ANI_STRUCT_LEVEL;
|
|
AniParams.usTileType = THIRDMISS;
|
|
AniParams.usTileIndex = THIRDMISS1;
|
|
AniParams.sDelay = 50;
|
|
AniParams.sStartFrame = 0;
|
|
AniParams.uiFlags = ANITILE_FORWARD;
|
|
|
|
pNode = CreateAnimationTile( &AniParams );
|
|
|
|
// Adjust for absolute positioning
|
|
pNode->pLevelNode->uiFlags |= LEVELNODE_USEABSOLUTEPOS;
|
|
pNode->pLevelNode->sRelativeX = sXPos;
|
|
pNode->pLevelNode->sRelativeY = sYPos;
|
|
pNode->pLevelNode->sRelativeZ = sZPos;
|
|
|
|
}
|
|
|
|
memset( &AniParams, 0, sizeof( ANITILE_PARAMS ) );
|
|
AniParams.sGridNo = sGridNo;
|
|
AniParams.ubLevelID = ANI_STRUCT_LEVEL;
|
|
AniParams.usTileType = usMissTileType;
|
|
AniParams.usTileIndex = usMissTileIndex;
|
|
AniParams.sDelay = 80;
|
|
AniParams.sStartFrame = 0;
|
|
if (fStopped)
|
|
{
|
|
AniParams.uiFlags = ANITILE_FORWARD | ANITILE_RELEASE_ATTACKER_WHEN_DONE;
|
|
}
|
|
else
|
|
{
|
|
AniParams.uiFlags = ANITILE_FORWARD;
|
|
}
|
|
// Save bullet ID!
|
|
AniParams.uiUserData3 = iBullet;
|
|
|
|
pNode = CreateAnimationTile( &AniParams );
|
|
|
|
// Set attacker ID
|
|
pNode->usMissAnimationPlayed = usMissTileType;
|
|
pNode->ubAttackerMissed = ubAttackerID;
|
|
// Adjust for absolute positioning
|
|
pNode->pLevelNode->uiFlags |= LEVELNODE_USEABSOLUTEPOS;
|
|
pNode->pLevelNode->sRelativeX = sXPos;
|
|
pNode->pLevelNode->sRelativeY = sYPos;
|
|
pNode->pLevelNode->sRelativeZ = sZPos;
|
|
|
|
// ATE: Show misses...( if our team )
|
|
if ( gGameSettings.fOptions[ TOPTION_SHOW_MISSES ] )
|
|
{
|
|
if ( ubAttackerID != NOBODY )
|
|
{
|
|
if ( MercPtrs[ ubAttackerID ]->bTeam == gbPlayerNum )
|
|
{
|
|
LocateGridNo( sGridNo );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pBullet->usLastStructureHit = usStructureID;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void WindowHit( INT16 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce )
|
|
{
|
|
STRUCTURE * pWallAndWindow;
|
|
DB_STRUCTURE * pWallAndWindowInDB;
|
|
INT16 sShatterGridNo;
|
|
UINT16 usTileIndex;
|
|
ANITILE * pNode;
|
|
ANITILE_PARAMS AniParams;
|
|
|
|
|
|
// ATE: Make large force always for now ( feel thing )
|
|
fLargeForce = TRUE;
|
|
|
|
// we have to do two things here: swap the window structure
|
|
// (right now just using the partner stuff in a chain from
|
|
// intact to cracked to shattered) and display the
|
|
// animation if we've reached shattered
|
|
|
|
// find the wall structure, and go one length along the chain
|
|
pWallAndWindow = FindStructureByID( sGridNo, usStructureID );
|
|
if (pWallAndWindow == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pWallAndWindow = SwapStructureForPartner( sGridNo, pWallAndWindow);
|
|
if (pWallAndWindow == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// record window smash
|
|
AddWindowHitToMapTempFile( sGridNo );
|
|
|
|
pWallAndWindowInDB = pWallAndWindow->pDBStructureRef->pDBStructure;
|
|
|
|
if ( fLargeForce )
|
|
{
|
|
// Force to destruction animation!
|
|
if (pWallAndWindowInDB->bPartnerDelta != NO_PARTNER_STRUCTURE )
|
|
{
|
|
pWallAndWindow = SwapStructureForPartner( sGridNo, pWallAndWindow);
|
|
if ( pWallAndWindow )
|
|
{
|
|
// record 2nd window smash
|
|
AddWindowHitToMapTempFile( sGridNo );
|
|
|
|
pWallAndWindowInDB = pWallAndWindow->pDBStructureRef->pDBStructure;
|
|
}
|
|
}
|
|
}
|
|
|
|
SetRenderFlags( RENDER_FLAG_FULL );
|
|
|
|
if (pWallAndWindowInDB->ubArmour == MATERIAL_THICKER_METAL_WITH_SCREEN_WINDOWS)
|
|
{
|
|
// don't play any sort of animation or sound
|
|
return;
|
|
}
|
|
|
|
if (pWallAndWindowInDB->bPartnerDelta != NO_PARTNER_STRUCTURE )
|
|
{ // just cracked; don't display the animation
|
|
MakeNoise( NOBODY, sGridNo, 0, gpWorldLevelData[sGridNo].ubTerrainID, WINDOW_CRACK_VOLUME, NOISE_BULLET_IMPACT );
|
|
return;
|
|
}
|
|
MakeNoise( NOBODY, sGridNo, 0, gpWorldLevelData[sGridNo].ubTerrainID, WINDOW_SMASH_VOLUME, NOISE_BULLET_IMPACT );
|
|
if (pWallAndWindowInDB->ubWallOrientation == INSIDE_TOP_RIGHT || pWallAndWindowInDB->ubWallOrientation == OUTSIDE_TOP_RIGHT)
|
|
{
|
|
/*
|
|
sShatterGridNo = sGridNo + 1;
|
|
// check for wrapping around edge of map
|
|
if (sShatterGridNo % WORLD_COLS == 0)
|
|
{
|
|
// in which case we don't play the animation!
|
|
return;
|
|
}*/
|
|
if (fBlowWindowSouth)
|
|
{
|
|
usTileIndex = WINDOWSHATTER1;
|
|
sShatterGridNo = sGridNo + 1;
|
|
}
|
|
else
|
|
{
|
|
usTileIndex = WINDOWSHATTER11;
|
|
sShatterGridNo = sGridNo;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
sShatterGridNo = sGridNo + WORLD_COLS;
|
|
// check for wrapping around edge of map
|
|
if (sShatterGridNo % WORLD_ROWS == 0)
|
|
{
|
|
// in which case we don't play the animation!
|
|
return;
|
|
}*/
|
|
if (fBlowWindowSouth)
|
|
{
|
|
usTileIndex = WINDOWSHATTER6;
|
|
sShatterGridNo = sGridNo + WORLD_COLS;
|
|
}
|
|
else
|
|
{
|
|
usTileIndex = WINDOWSHATTER16;
|
|
sShatterGridNo = sGridNo;
|
|
}
|
|
}
|
|
|
|
memset( &AniParams, 0, sizeof( ANITILE_PARAMS ) );
|
|
AniParams.sGridNo = sShatterGridNo;
|
|
AniParams.ubLevelID = ANI_STRUCT_LEVEL;
|
|
AniParams.usTileType = WINDOWSHATTER;
|
|
AniParams.usTileIndex = usTileIndex;
|
|
AniParams.sDelay = 50;
|
|
AniParams.sStartFrame = 0;
|
|
AniParams.uiFlags = ANITILE_FORWARD;
|
|
|
|
pNode = CreateAnimationTile( &AniParams );
|
|
|
|
PlayJA2Sample( GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir( sGridNo ) );
|
|
|
|
}
|
|
|
|
|
|
BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT16 sGridNo )
|
|
{
|
|
INT16 sRange;
|
|
UINT16 usInHand;
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("InRange"));
|
|
usInHand = pSoldier->inv[HANDPOS].usItem;
|
|
INVTYPE* pItemInHand = &Item[ usInHand ];
|
|
|
|
if ( pItemInHand->usItemClass == IC_GUN || pItemInHand->usItemClass == IC_THROWING_KNIFE || (pItemInHand->rocketlauncher && !pItemInHand->singleshotrocketlauncher))
|
|
{
|
|
// Determine range
|
|
sRange = (INT16)GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo );
|
|
|
|
if ( pItemInHand->usItemClass == IC_THROWING_KNIFE )
|
|
{
|
|
// NB CalcMaxTossRange returns range in tiles, not in world units
|
|
if ( sRange <= CalcMaxTossRange( pSoldier, usInHand, TRUE ) * CELL_X_SIZE )
|
|
{
|
|
return( TRUE );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For given weapon, check range
|
|
if ( sRange <= GunRange( &(pSoldier->inv[HANDPOS]) ) )
|
|
{
|
|
return( TRUE );
|
|
}
|
|
}
|
|
}
|
|
return( FALSE );
|
|
}
|
|
|
|
UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubAimTime, UINT8 ubAimPos )
|
|
{
|
|
//SOLDIERTYPE *vicpSoldier;
|
|
SOLDIERTYPE * pTarget;
|
|
INT32 iChance, iRange, iSightRange, iMaxRange, iBonus; //, minRange;
|
|
//rain
|
|
//INT32 iScopeBonus;
|
|
//end rain
|
|
|
|
INT32 iGunCondition, iMarksmanship;
|
|
INT32 iPenalty;
|
|
UINT16 usInHand;
|
|
OBJECTTYPE * pInHand;
|
|
// INT8 bAttachPos;
|
|
INT8 bBandaged;
|
|
INT16 sDistVis;
|
|
UINT8 ubAdjAimPos;
|
|
UINT8 ubTargetID;
|
|
bool highPowerScope = false;
|
|
UINT32 pScope;
|
|
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun"));
|
|
|
|
if ( pSoldier->stats.bMarksmanship == 0 )
|
|
{
|
|
// always min chance
|
|
return( MINCHANCETOHIT );
|
|
}
|
|
|
|
// make sure the guy's actually got a weapon in his hand!
|
|
pInHand = &(pSoldier->inv[pSoldier->ubAttackingHand]);
|
|
usInHand = pSoldier->usAttackingWeapon;
|
|
//CHRISL: We have new modifiers when using a high power scope so find out if we have one from the start
|
|
for(attachmentList::iterator iter = (*pInHand)[0]->attachments.begin(); iter != (*pInHand)[0]->attachments.end(); iter++)
|
|
{
|
|
if(Item[iter->usItem].aimbonus > HIGH_POWER_SCOPE)
|
|
{
|
|
highPowerScope = true;
|
|
pScope = iter->usItem;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// DETERMINE BASE CHANCE OF HITTING
|
|
iGunCondition = WEAPON_STATUS_MOD( (*pInHand)[0]->data.gun.bGunStatus );
|
|
|
|
if (Item[usInHand].rocketlauncher )
|
|
{
|
|
// use the same calculation as for mechanical thrown weapons
|
|
iMarksmanship = ( EffectiveDexterity( pSoldier ) + EffectiveMarksmanship( pSoldier ) + EffectiveWisdom( pSoldier ) + (10 * EffectiveExpLevel( pSoldier ) )) / 4;
|
|
// heavy weapons trait helps out
|
|
if (HAS_SKILL_TRAIT( pSoldier, HEAVY_WEAPS ))
|
|
{
|
|
iMarksmanship += gbSkillTraitBonus[HEAVY_WEAPS] * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPS );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iMarksmanship = EffectiveMarksmanship( pSoldier );
|
|
|
|
if ( AM_A_ROBOT( pSoldier ) )
|
|
{
|
|
SOLDIERTYPE * pSoldier2;
|
|
|
|
pSoldier2 = pSoldier->GetRobotController( );
|
|
if ( pSoldier2 )
|
|
{
|
|
iMarksmanship = __max( iMarksmanship, EffectiveMarksmanship( pSoldier2 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// modify chance to hit by morale
|
|
iMarksmanship += GetMoraleModifier( pSoldier );
|
|
|
|
// penalize marksmanship for fatigue
|
|
iMarksmanship -= GetSkillCheckPenaltyForFatigue( pSoldier, iMarksmanship );
|
|
|
|
//if ( PTR_OURTEAM )
|
|
//{
|
|
// iChance = ( iMarksmanship * iGunCondition ) / 100;
|
|
//}
|
|
//else
|
|
{
|
|
if (iGunCondition >= iMarksmanship)
|
|
// base chance is equal to the shooter's marksmanship skill
|
|
iChance = iMarksmanship;
|
|
else
|
|
// base chance is equal to the average of marksmanship & gun's condition!
|
|
iChance = (iMarksmanship + iGunCondition) / 2;
|
|
}
|
|
// if shooting same target as the last shot
|
|
if (sGridNo == pSoldier->sLastTarget )
|
|
iChance += AIM_BONUS_SAME_TARGET; // give a bonus to hit
|
|
|
|
if ( pSoldier->ubProfile != NO_PROFILE && gMercProfiles[ pSoldier->ubProfile ].bPersonalityTrait == PSYCHO )
|
|
{
|
|
iChance += AIM_BONUS_PSYCHO;
|
|
}
|
|
|
|
// calculate actual range (in units, 10 units = 1 tile)
|
|
iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo );
|
|
|
|
if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND )
|
|
{
|
|
//CHRISL: Incur a penalty if using a highPowerScope
|
|
if(highPowerScope == true && iRange > Item[pScope].minrangeforaimbonus)
|
|
{
|
|
iPenalty = (Item[pScope].aimbonus * (iRange-Item[pScope].minrangeforaimbonus)) / 1000;
|
|
if (iPenalty > AIM_BONUS_PRONE)
|
|
{
|
|
iPenalty = AIM_BONUS_PRONE;
|
|
}
|
|
iChance -= iPenalty;
|
|
}
|
|
}
|
|
// if shooter is crouched, he aims slightly better (to max of AIM_BONUS_CROUCHING)
|
|
else if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH )
|
|
{
|
|
iBonus = iRange / 10;
|
|
if (iBonus > AIM_BONUS_CROUCHING)
|
|
{
|
|
iBonus = AIM_BONUS_CROUCHING;
|
|
}
|
|
iChance += iBonus;
|
|
//CHRISL: Incur a penalty if using a highPowerScope
|
|
if(highPowerScope == true && iRange > Item[pScope].minrangeforaimbonus)
|
|
{
|
|
iPenalty = ((Item[pScope].aimbonus/2) * (iRange-Item[pScope].minrangeforaimbonus)) / 1000;
|
|
if (iPenalty > AIM_BONUS_CROUCHING)
|
|
{
|
|
iPenalty = AIM_BONUS_CROUCHING;
|
|
}
|
|
iChance -= iPenalty;
|
|
}
|
|
}
|
|
// if shooter is prone, he aims even better, except at really close range
|
|
else if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE )
|
|
{
|
|
if (iRange > MIN_PRONE_RANGE)
|
|
{
|
|
iBonus = iRange / 10;
|
|
if (iBonus > AIM_BONUS_PRONE)
|
|
{
|
|
iBonus = AIM_BONUS_PRONE;
|
|
}
|
|
// Snap: bipod bonus is now calculated in GetToHitBonus
|
|
//bAttachPos = FindAttachment( pInHand, BIPOD );
|
|
//if (bAttachPos != ITEM_NOT_FOUND)
|
|
//{ // extra bonus to hit for a bipod, up to half the prone bonus itself
|
|
// iBonus += (iBonus * WEAPON_STATUS_MOD(GetBipodStatus(pInHand)) / 100) / 2;
|
|
//}
|
|
iChance += iBonus;
|
|
}
|
|
}
|
|
|
|
//Madd: inherent weapon accuracy bonus
|
|
iChance += Weapon[usInHand].bAccuracy;
|
|
|
|
// if ( !(Item[ usInHand ].fFlags & ITEM_TWO_HANDED) )
|
|
if ( !(Item[ usInHand ].twohanded ) )
|
|
{
|
|
if (pSoldier->inv[SECONDHANDPOS].exists() == false)
|
|
{
|
|
// firing with gun in right hand, and second hand empty (ie: no grenade, med kit or anything there)
|
|
// Madd: easier to fire pistol/smg w/one hand free, essentially this will make pistols a little bit more accurate, and hopefully still useful later in the game
|
|
if (Weapon[usInHand].ubWeaponClass == HANDGUNCLASS)
|
|
iChance += AIM_BONUS_TWO_HANDED_PISTOL;
|
|
}
|
|
else
|
|
{
|
|
// Madd: harder to fire smgs w/something in other hand
|
|
// SMGs are treated as pistols for these purpose except there is a -5 penalty;
|
|
if (Weapon[usInHand].ubWeaponClass == SMGCLASS)
|
|
{
|
|
iChance -= AIM_PENALTY_SMG;
|
|
}
|
|
|
|
if ( !HAS_SKILL_TRAIT( pSoldier, AMBIDEXT ) )
|
|
{
|
|
if ( pSoldier->IsValidSecondHandShot( ) )
|
|
{
|
|
// penalty to aim when firing two pistols
|
|
iChance -= AIM_PENALTY_DUAL_PISTOLS;
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
// penalty to aim with pistol being fired one-handed
|
|
iChance -= AIM_PENALTY_ONE_HANDED_PISTOL;
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
// If in burst mode, deduct points for change to hit for each shot after the first
|
|
if ( pSoldier->bDoBurst )
|
|
{
|
|
// Snap: bipod may reduce burst penalty
|
|
iPenalty = GetBurstPenalty(pInHand, gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)
|
|
* (pSoldier->bDoBurst - 1);
|
|
|
|
// halve the penalty for people with the autofire trait
|
|
if ( HAS_SKILL_TRAIT( pSoldier, AUTO_WEAPS ) )
|
|
{
|
|
iPenalty /= 2 * NUM_SKILL_TRAITS( pSoldier, AUTO_WEAPS );
|
|
}
|
|
iChance -= iPenalty;
|
|
}
|
|
else if ( pSoldier->bDoAutofire > 0 )
|
|
{
|
|
// Snap: bipod may reduce auto penalty
|
|
iPenalty = GetAutoPenalty(pInHand, gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)
|
|
* (pSoldier->bDoBurst - 1);
|
|
|
|
// halve the penalty for people with the autofire trait
|
|
if ( HAS_SKILL_TRAIT( pSoldier, AUTO_WEAPS ) )
|
|
{
|
|
iPenalty /= 2 * NUM_SKILL_TRAITS( pSoldier, AUTO_WEAPS );
|
|
}
|
|
iChance -= iPenalty;
|
|
}
|
|
|
|
//ADB we need to calculate the distance visible and SoldierTo...LOSTests that we want to
|
|
//calculate it when firing, so the scope can be accounted for when the weapon is raised
|
|
//I consider this a hack to change the global, but passing a var alllll the way down to GetVisionBonus was worse
|
|
//if anyone wants to change it go for it, I don't know what usAnimState should be set
|
|
|
|
//store old flag
|
|
UINT32 oldFlag = gAnimControl[ pSoldier->usAnimState ].uiFlags;
|
|
//add the necessary bits to the flag
|
|
gAnimControl[ pSoldier->usAnimState ].uiFlags |= (ANIM_FIREREADY | ANIM_FIRE);
|
|
|
|
// 0verhaul: Changed to take expanded range from shooting at different levels into account
|
|
//ADB this change does nothing - either way it is random - we don't know what level we are shooting to, which is
|
|
//what the last parameter is, and the soldier's current level is as good a guess as ground level.
|
|
//so if you really want to fix this, pass in a value
|
|
sDistVis = pSoldier->GetMaxDistanceVisible(sGridNo, pSoldier->bTargetLevel, CALC_FROM_ALL_DIRS );
|
|
|
|
// CJC August 13 2002: Wow, this has been wrong the whole time. bTargetCubeLevel seems to be generally set to 2 -
|
|
// but if a character is shooting at an enemy in a particular spot, then we should be using the target position on the body.
|
|
|
|
// CJC August 13, 2002
|
|
// If the start soldier has a body part they are aiming at, and know about the person in the tile, then use that height instead
|
|
iSightRange = -1;
|
|
|
|
// give some leeway to allow people to spot for each other...
|
|
//so make the range to calculate the bullet at 255+
|
|
ubTargetID = WhoIsThere2( sGridNo, pSoldier->bTargetLevel );
|
|
// best to use team knowledge as well, in case of spotting for someone else
|
|
// 0verhaul: Why not use the distance visible as the max for line of sight testing?
|
|
//ADB because A) the bullet can travel farther than I can see and B) I might have a spotter
|
|
|
|
//We are firing a gun, and so the gun will be pointed and the scope will be used, even if it isn't now, so don't forget that we are in a firing animation
|
|
if (ubTargetID != NOBODY && pSoldier->aiData.bOppList[ubTargetID] == SEEN_CURRENTLY || gbPublicOpplist[pSoldier->bTeam][ubTargetID] == SEEN_CURRENTLY)
|
|
{
|
|
iSightRange = SoldierToSoldierLineOfSightTest( pSoldier, MercPtrs[ubTargetID], TRUE, NO_DISTANCE_LIMIT, pSoldier->bAimShotLocation, false );
|
|
}
|
|
if (iSightRange == -1) // didn't do a bodypart-based test
|
|
{
|
|
iSightRange = SoldierTo3DLocationLineOfSightTest( pSoldier, sGridNo, pSoldier->bTargetLevel, pSoldier->bTargetCubeLevel, TRUE, NO_DISTANCE_LIMIT, false );
|
|
}
|
|
|
|
//restore old flag
|
|
gAnimControl[ pSoldier->usAnimState ].uiFlags = oldFlag;
|
|
|
|
if ( iSightRange > (sDistVis * CELL_X_SIZE) )
|
|
{
|
|
//CHRISL: Because of the changes I've made to how scopes modify iSightRange, this penalty makes it basically impossible
|
|
// to shot at targets you can't see yourself. While this isn't an issue for most weapons, it overly restricts the
|
|
// effectiveness of sniper rifles. So pull this penalty but leave the one that comes later in the code.
|
|
// shooting beyond max normal vision... penalize such distance at double (also later we halve the remaining chance)
|
|
//iSightRange += (iSightRange - sDistVis * CELL_X_SIZE);
|
|
}
|
|
|
|
// if shooter spent some extra time aiming and can see the target
|
|
if (iSightRange > 0 && ubAimTime && !pSoldier->bDoBurst )
|
|
{
|
|
// CHRISL: Rather then a flat +10/click bonus, we're going to try a bonus that's based on MRK and Lvl which gets
|
|
// progressivly less the more we aim. Everything is based on the maxBonus that a merc can possibly get which
|
|
// uses the equation: 20+(MRK/20*LVL)+Accuracy+(Sniper trait * 10). This value is then split between the 8
|
|
// possible AimTime's using a max aimTime bonus of 10.
|
|
INT16 bonusProgression[8] = {500,500,600,600,750,750,750,1000};
|
|
FLOAT maxBonus = 20+((FLOAT)iMarksmanship/20*pSoldier->stats.bExpLevel)+(Weapon[Item[pInHand->usItem].ubClassIndex].bAccuracy*2)+(NUM_SKILL_TRAITS( pSoldier, PROF_SNIPER )*10);
|
|
INT8 maxClickBonus = 10;
|
|
FLOAT aimTimeBonus;
|
|
for(int i = 0; i < ubAimTime; i++)
|
|
{
|
|
aimTimeBonus = __min((maxBonus*bonusProgression[i]/1000),maxClickBonus);
|
|
maxBonus -= aimTimeBonus;
|
|
iChance += (INT32)floor(aimTimeBonus+.5);
|
|
}
|
|
/* if(highPowerScope == true)
|
|
{
|
|
{
|
|
if ( NUM_SKILL_TRAITS( pSoldier, PROF_SNIPER ) == 2 )
|
|
aimTimeBonus = -((__max(0,75-pSoldier->stats.bMarksmanship))/(pSoldier->stats.bExpLevel*2));
|
|
else if( NUM_SKILL_TRAITS( pSoldier, PROF_SNIPER ) == 1 )
|
|
aimTimeBonus = -((__max(0,85-pSoldier->stats.bMarksmanship))/(pSoldier->stats.bExpLevel*2));
|
|
else
|
|
aimTimeBonus = -((__max(0,100-pSoldier->stats.bMarksmanship))/(pSoldier->stats.bExpLevel));
|
|
aimTimeBonus = __max(-9, aimTimeBonus);
|
|
aimTimeBonus = __min(0, aimTimeBonus);
|
|
aimTimeBonus += AIM_BONUS_PER_AP;
|
|
}
|
|
else
|
|
aimTimeBonus = AIM_BONUS_PER_AP;
|
|
iChance += (aimTimeBonus * ubAimTime); // bonus for every pt of aiming*/
|
|
}
|
|
|
|
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_PC ) ) // if this is a computer AI controlled enemy
|
|
{
|
|
if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY )
|
|
{
|
|
// On easy, penalize all enemies by 5%
|
|
iChance -= 5;
|
|
}
|
|
else
|
|
{
|
|
// max with 0 to prevent this being a bonus, for JA2 it's just a penalty to make early enemies easy
|
|
// CJC note: IDIOT! This should have been a min. It's kind of too late now...
|
|
// CJC 2002-05-17: changed the max to a min to make this work.
|
|
iChance += __min( 0, gbDiff[ DIFF_ENEMY_TO_HIT_MOD ][ SoldierDifficultyLevel( pSoldier ) ] );
|
|
}
|
|
}
|
|
|
|
// if shooter is being affected by gas
|
|
if ( pSoldier->flags.uiStatusFlags & SOLDIER_GASSED )
|
|
{
|
|
iChance -= AIM_PENALTY_GASSED;
|
|
}
|
|
|
|
// if shooter is being bandaged at the same time, his concentration is off
|
|
if (pSoldier->ubServiceCount > 0)
|
|
iChance -= AIM_PENALTY_GETTINGAID;
|
|
|
|
// if shooter is still in shock
|
|
if (pSoldier->aiData.bShock)
|
|
iChance -= (pSoldier->aiData.bShock * AIM_PENALTY_PER_SHOCK);
|
|
|
|
// WANNE: Changed this, because RPGs are not in the calculation, only guns
|
|
//if ( Item[ usInHand ].usItemClass == IC_GUN )
|
|
if ( Item[ usInHand ].usItemClass == IC_GUN || Item[ usInHand ].usItemClass == IC_LAUNCHER)
|
|
{
|
|
iMaxRange = GunRange( pInHand );
|
|
}
|
|
else
|
|
{
|
|
iMaxRange = CELL_X_SIZE; // one tile
|
|
}
|
|
|
|
if ( iSightRange > 0 )
|
|
{
|
|
|
|
// Madd: Spacific item bonuses are no longer hard-coded
|
|
|
|
// Snap: use tohitbonus or aimbonus instead
|
|
/*if (pSoldier->inv[HEAD1POS].usItem == SUNGOGGLES || pSoldier->inv[HEAD2POS].usItem == SUNGOGGLES)
|
|
{
|
|
// decrease effective range by 10% when using sungoggles (w or w/o scope)
|
|
iSightRange -= iRange / 10; //basically, +1% to hit per every 2 squares
|
|
}*/
|
|
|
|
//bAttachPos = FindAttachment( pInHand, SNIPERSCOPE );
|
|
|
|
//// does gun have scope, long range recommends its use, and shooter's aiming?
|
|
//if (bAttachPos != NO_SLOT && (iRange > MIN_SCOPE_RANGE) && (ubAimTime > 0))
|
|
//{
|
|
// // reduce effective sight range by 20% per extra aiming time AP of the distance
|
|
// // beyond MIN_SCOPE_RANGE. Max reduction is 80% of the range beyond.
|
|
// iScopeBonus = ((SNIPERSCOPE_AIM_BONUS * ubAimTime) * (iRange - MIN_SCOPE_RANGE)) / 100;
|
|
|
|
// // adjust for scope condition, only has full affect at 100%
|
|
// iScopeBonus = (iScopeBonus * WEAPON_STATUS_MOD(pInHand->bAttachStatus[bAttachPos])) / 100;
|
|
|
|
//rain
|
|
// reduce scope effectiveness when it's raining
|
|
// iScopeBonus /= 1 + gbCurrentRainIntensity; //madd: commented out -- didn't do anything but cause a warning :S
|
|
//end rain
|
|
|
|
// // reduce effective range by the bonus obtained from the scope
|
|
// iSightRange -= iScopeBonus;
|
|
// if (iSightRange < 1)
|
|
// {
|
|
// iSightRange = 1;
|
|
// }
|
|
//}
|
|
|
|
//bAttachPos = FindAttachment( pInHand, LASERSCOPE );
|
|
//if (usInHand == ROCKET_RIFLE || usInHand == AUTO_ROCKET_RIFLE || bAttachPos != NO_SLOT) // rocket rifle has one built in
|
|
//{
|
|
// INT8 bLaserStatus;
|
|
|
|
// if ( usInHand == ROCKET_RIFLE || usInHand == AUTO_ROCKET_RIFLE )
|
|
// {
|
|
// bLaserStatus = WEAPON_STATUS_MOD((*pInHand)[0]->data.gun.bGunStatus);
|
|
// }
|
|
// else
|
|
// {
|
|
// bLaserStatus = WEAPON_STATUS_MOD(pInHand->bAttachStatus[ bAttachPos ]);
|
|
// }
|
|
//
|
|
// // laser scope isn't of much use in high light levels; add something for that
|
|
// if (bLaserStatus > 50)
|
|
// {
|
|
// iScopeBonus = LASERSCOPE_BONUS * (bLaserStatus - 50) / 50;
|
|
// }
|
|
// else
|
|
// {
|
|
// // laser scope in bad condition creates aim penalty!
|
|
// iScopeBonus = - LASERSCOPE_BONUS * (50 - bLaserStatus) / 50;
|
|
// }
|
|
//
|
|
// iChance += iScopeBonus;
|
|
|
|
//}
|
|
|
|
//Madd: Some gear can affect the to-hit bonus and the bonus from aiming
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: before bonus - ubAimTime = %d, iSightRange = %d, iChance = %d ", ubAimTime, iSightRange, iChance));
|
|
iSightRange -= GetGearAimBonus ( pSoldier, iRange, ubAimTime );
|
|
iChance += GetGearToHitBonus ( pSoldier );
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: after gear bonus - ubAimTime = %d, iSightRange = %d, iChance = %d ", ubAimTime, iSightRange, iChance));
|
|
|
|
// Reduce effective range by the aiming bonus (e.g. from sniper scope)
|
|
iSightRange -= GetAimBonus( pInHand, iRange, ubAimTime );
|
|
//CHRISL: If we have the Sniper skill, reduce effective range
|
|
if ( HAS_SKILL_TRAIT( pSoldier, PROF_SNIPER ) )
|
|
{
|
|
iSightRange -= ((gbSkillTraitBonus[ PROF_SNIPER ] * NUM_SKILL_TRAITS( pSoldier, PROF_SNIPER )) * iRange) /100;
|
|
}
|
|
if (iSightRange < 1)
|
|
{
|
|
iSightRange = 1;
|
|
}
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: after scope bonus - ubAimTime = %d, iSightRange = %d, iChance = %d ", ubAimTime, iSightRange, iChance));
|
|
|
|
UINT8 bLightLevel = LightTrueLevel(sGridNo, pSoldier->bTargetLevel);
|
|
// Apply flat to-hit bonus (e.g. from laser scope)
|
|
// Snap: If prone and range is good, apply bipod bonus here as well
|
|
iChance += GetToHitBonus( pInHand, iSightRange, bLightLevel,
|
|
gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE && iRange > MIN_PRONE_RANGE );
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: after to hit bonus - ubAimTime = %d, iSightRange = %d, iChance = %d ", ubAimTime, iSightRange, iChance));
|
|
}
|
|
|
|
// if aiming at the head, reduce chance to hit
|
|
if (ubAimPos == AIM_SHOT_HEAD)
|
|
{
|
|
// penalty of 3% per tile
|
|
iPenalty = 3 * iSightRange / 10;
|
|
iChance -= iPenalty;
|
|
}
|
|
else if (ubAimPos == AIM_SHOT_LEGS)
|
|
{
|
|
// penalty of 1% per tile
|
|
iPenalty = iSightRange / 10;
|
|
iChance -= iPenalty;
|
|
}
|
|
|
|
//NumMessage("EFFECTIVE RANGE = ",range);
|
|
|
|
// ADJUST FOR RANGE
|
|
// bonus if range is less than normal range, penalty if it's more
|
|
//iChance += (NORMAL_RANGE - iRange) / (CELL_X_SIZE / 5); // 5% per tile
|
|
|
|
// Effects of actual gun max range... the numbers are based on wanting -40%
|
|
// at range 26for a pistol with range 13, and -0 for a sniper rifle with range 80
|
|
iPenalty = ((iMaxRange - iRange * 3) * 10) / (17 * CELL_X_SIZE);
|
|
if ( iPenalty < 0 )
|
|
{
|
|
iChance += iPenalty;
|
|
}
|
|
//iChance -= 20 * iRange / iMaxRange;
|
|
|
|
if ( TANK( pSoldier ) && ( iRange / CELL_X_SIZE < MaxNormalDistanceVisible() ) )
|
|
{
|
|
// tank; penalize at close range!
|
|
// 2 percent per tile closer than max visible distance
|
|
iChance -= 2 * ( MaxNormalDistanceVisible() - (iRange / CELL_X_SIZE) );
|
|
}
|
|
|
|
if (iSightRange == 0)
|
|
{
|
|
// firing blind!
|
|
iChance -= AIM_PENALTY_BLIND;
|
|
}
|
|
else
|
|
{
|
|
// Effects based on aiming & sight
|
|
// From for JA2.5: 3% bonus/penalty for each tile different from range NORMAL_RANGE.
|
|
// This doesn't provide a bigger bonus at close range, but stretches it out, making medium
|
|
// range less penalized, and longer range more penalized
|
|
iChance += 3 * ( NORMAL_RANGE - iSightRange ) / CELL_X_SIZE;
|
|
/*
|
|
if (iSightRange < NORMAL_RANGE)
|
|
{
|
|
// bonus to hit of 20% at point blank (would be 25% at range 0);
|
|
//at NORMAL_RANGE, bonus is 0
|
|
iChance += 25 * (NORMAL_RANGE - iSightRange) / NORMAL_RANGE;
|
|
}
|
|
else
|
|
{
|
|
// penalty of 2% / tile
|
|
iChance -= (iSightRange - NORMAL_RANGE) / 5;
|
|
}
|
|
*/
|
|
}
|
|
|
|
// adjust for roof/not on roof
|
|
if ( pSoldier->pathing.bLevel == 0 )
|
|
{
|
|
if ( pSoldier->bTargetLevel > 0 )
|
|
{
|
|
// penalty for firing up
|
|
iChance -= AIM_PENALTY_FIRING_UP;
|
|
}
|
|
}
|
|
else // pSoldier->pathing.bLevel > 0 )
|
|
{
|
|
if ( pSoldier->bTargetLevel == 0 )
|
|
{
|
|
iChance += AIM_BONUS_FIRING_DOWN;
|
|
}
|
|
}
|
|
|
|
|
|
pTarget = SimpleFindSoldier( sGridNo, pSoldier->bTargetLevel );
|
|
if (pTarget != NULL)
|
|
{
|
|
// targeting a merc
|
|
// adjust for crouched/prone target
|
|
switch( gAnimControl[ pTarget->usAnimState ].ubHeight )
|
|
{
|
|
case ANIM_CROUCH:
|
|
if ( TANK( pSoldier ) && iRange < MIN_TANK_RANGE )
|
|
{
|
|
// 13% penalty per tile closer than min range
|
|
iChance -= 13 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE );
|
|
}
|
|
else
|
|
{
|
|
// at anything other than point-blank range
|
|
if (iRange > POINT_BLANK_RANGE + 10 * (AIM_PENALTY_TARGET_CROUCHED / 3) )
|
|
{
|
|
iChance -= AIM_PENALTY_TARGET_CROUCHED;
|
|
}
|
|
else if (iRange > POINT_BLANK_RANGE)
|
|
{
|
|
// at close range give same bonus as prone, up to maximum of AIM_PENALTY_TARGET_CROUCHED
|
|
iChance -= 3 * ((iRange - POINT_BLANK_RANGE) / CELL_X_SIZE); // penalty -3%/tile
|
|
}
|
|
}
|
|
break;
|
|
case ANIM_PRONE:
|
|
if ( TANK( pSoldier ) && iRange < MIN_TANK_RANGE )
|
|
{
|
|
// 25% penalty per tile closer than min range
|
|
iChance -= 25 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE );
|
|
}
|
|
else
|
|
{
|
|
// at anything other than point-blank range
|
|
if (iRange > POINT_BLANK_RANGE)
|
|
{
|
|
// reduce chance to hit with distance to the prone/immersed target
|
|
iPenalty = 3 * ((iRange - POINT_BLANK_RANGE) / CELL_X_SIZE); // penalty -3%/tile
|
|
iPenalty = __min( iPenalty, AIM_PENALTY_TARGET_PRONE );
|
|
|
|
iChance -= iPenalty;
|
|
}
|
|
}
|
|
break;
|
|
case ANIM_STAND:
|
|
// if we are prone and at close range, then penalize shots to the torso or head!
|
|
if ( iRange <= MIN_PRONE_RANGE && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE )
|
|
{
|
|
if ( ubAimPos == AIM_SHOT_RANDOM || ubAimPos == AIM_SHOT_GLAND )
|
|
{
|
|
ubAdjAimPos = AIM_SHOT_TORSO;
|
|
}
|
|
else
|
|
{
|
|
ubAdjAimPos = ubAimPos;
|
|
}
|
|
// lose 10% per height difference, lessened by distance
|
|
// e.g. 30% to aim at head at range 1, only 10% at range 3
|
|
// or 20% to aim at torso at range 1, no penalty at range 3
|
|
// NB torso aim position is 2, so (5-aimpos) is 3, for legs it's 2, for head 4
|
|
iChance -= (5 - ubAdjAimPos - iRange / CELL_X_SIZE) * 10;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// penalty for amount that enemy has moved
|
|
iPenalty = __min( ((pTarget->bTilesMoved * 3) / 2), 30 );
|
|
iChance -= iPenalty;
|
|
|
|
// if target sees us, he may have a chance to dodge before the gun goes off
|
|
// but ability to dodge is reduced if crouched or prone!
|
|
if (pTarget->aiData.bOppList[pSoldier->ubID] == SEEN_CURRENTLY && !TANK( pTarget ) && !(pSoldier->ubBodyType != QUEENMONSTER) )
|
|
{
|
|
iPenalty = ( EffectiveAgility( pTarget ) / 5 + EffectiveExpLevel( pTarget ) * 2);
|
|
switch( gAnimControl[ pTarget->usAnimState ].ubHeight )
|
|
{
|
|
case ANIM_CROUCH:
|
|
iPenalty = iPenalty * 2 / 3;
|
|
break;
|
|
case ANIM_PRONE:
|
|
iPenalty /= 3;
|
|
break;
|
|
}
|
|
|
|
// reduce dodge ability by the attacker's stats
|
|
iBonus = ( EffectiveDexterity( pSoldier ) / 5 + EffectiveExpLevel( pSoldier ) * 2);
|
|
if ( TANK( pTarget ) || (pSoldier->ubBodyType != QUEENMONSTER) )
|
|
{
|
|
// reduce ability to track shots
|
|
iBonus = iBonus / 2;
|
|
}
|
|
|
|
if ( iPenalty > iBonus )
|
|
{
|
|
iChance -= (iPenalty - iBonus);
|
|
}
|
|
}
|
|
}
|
|
else if ( TANK( pSoldier ) && iRange < MIN_TANK_RANGE )
|
|
{
|
|
// 25% penalty per tile closer than min range
|
|
iChance -= 25 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE );
|
|
}
|
|
|
|
|
|
// add camo effects
|
|
|
|
#if 0
|
|
if ((victim = WhoIsThere(sGridNo)) < TOTAL_SOLDIERS)
|
|
{
|
|
|
|
// if victim is 5 or more tiles away and camouflaged, reduce
|
|
// chance to hit by 5% (ALREADY HAVE THIS INFO)
|
|
if (range > 75 && vicpSoldier->camouflage)
|
|
{
|
|
switch(vicpSoldier->terrtype)
|
|
{
|
|
case GROUNDTYPE:
|
|
case SANDTYPE :
|
|
case GRASSTYPE :
|
|
case TGRASSTYPE:
|
|
case DGRASSTYPE:
|
|
case ROUGHTYPE : iChance += CAMOUFLAGE_TO_HIT_PENALTY;
|
|
break;
|
|
|
|
case FLOORTYPE :
|
|
case LAKETYPE :
|
|
case OCEANTYPE : break;
|
|
|
|
#ifdef BETAVERSION
|
|
default : NumMessage("CHANCE TO HIT ERROR: Unknown camo terrtype ",vicpSoldier->terrtype);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// IF CHANCE EXISTS, BUT SHOOTER IS INJURED
|
|
if ((iChance > 0) && (pSoldier->stats.bLife < pSoldier->stats.bLifeMax))
|
|
{
|
|
// if bandaged, give 1/2 of the bandaged life points back into equation
|
|
bBandaged = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding;
|
|
|
|
// injury penalty is based on % damage taken (max 2/3rds chance)
|
|
iPenalty = (iChance * 2 * (pSoldier->stats.bLifeMax - pSoldier->stats.bLife + (bBandaged / 2))) /
|
|
(3 * pSoldier->stats.bLifeMax);
|
|
|
|
// reduce injury penalty due to merc's experience level (he can take it!)
|
|
iChance -= (iPenalty * (100 - (10 * ( EffectiveExpLevel( pSoldier ) - 1)))) / 100;
|
|
}
|
|
|
|
// IF CHANCE EXISTS, BUT SHOOTER IS LOW ON BREATH
|
|
if ((iChance > 0) && (pSoldier->bBreath < 100))
|
|
{
|
|
// breath penalty is based on % breath missing (max 1/2 chance)
|
|
iPenalty = (iChance * (100 - pSoldier->bBreath)) / 200;
|
|
// reduce breath penalty due to merc's dexterity (he can compensate!)
|
|
iChance -= (iPenalty * (100 - ( EffectiveDexterity( pSoldier ) - 10))) / 100;
|
|
}
|
|
|
|
|
|
// CHECK IF TARGET IS WITHIN GUN'S EFFECTIVE MAXIMUM RANGE
|
|
if ( iRange > iMaxRange )
|
|
{
|
|
// a bullet WILL travel that far if not blocked, but it's NOT accurate,
|
|
// because beyond maximum range, the bullet drops rapidly
|
|
|
|
// This won't cause the bullet to be off to the left or right, only make it
|
|
// drop in flight.
|
|
iChance /= 2;
|
|
}
|
|
if ( iSightRange > (sDistVis * CELL_X_SIZE) )
|
|
{
|
|
// penalize out of sight shots, cumulative to effective range penalty
|
|
iChance /= 2;
|
|
}
|
|
|
|
// MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS
|
|
if (iChance < MINCHANCETOHIT)
|
|
{
|
|
if ( TANK( pSoldier ) )
|
|
{
|
|
// allow absolute minimums
|
|
iChance = 0;
|
|
}
|
|
else
|
|
{
|
|
iChance = MINCHANCETOHIT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (iChance > MAXCHANCETOHIT)
|
|
iChance = MAXCHANCETOHIT;
|
|
}
|
|
|
|
// NumMessage("ChanceToHit = ",chance);
|
|
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: ichance = %d",iChance));
|
|
return (iChance);
|
|
}
|
|
|
|
UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubAimTime, UINT8 ubAimPos )
|
|
{
|
|
UINT16 usTrueState;
|
|
UINT32 uiChance;
|
|
|
|
// same as CCTHG but fakes the attacker always standing
|
|
usTrueState = pSoldier->usAnimState;
|
|
pSoldier->usAnimState = STANDING;
|
|
uiChance = CalcChanceToHitGun( pSoldier, sGridNo, ubAimTime, ubAimPos );
|
|
pSoldier->usAnimState = usTrueState;
|
|
return( uiChance );
|
|
}
|
|
|
|
INT32 CalcBodyImpactReduction( UINT8 ubAmmoType, UINT8 ubHitLocation )
|
|
{
|
|
// calculate how much bullets are slowed by passing through someone
|
|
INT32 iReduction = BodyImpactReduction[ubHitLocation];
|
|
|
|
iReduction = (INT32)(iReduction * AmmoTypes[ubAmmoType].armourImpactReductionMultiplier / max(1,AmmoTypes[ubAmmoType].armourImpactReductionDivisor));
|
|
//switch (ubAmmoType)
|
|
//{
|
|
// case AMMO_HP:
|
|
// iReduction = AMMO_ARMOUR_ADJUSTMENT_HP( iReduction );
|
|
// break;
|
|
// case AMMO_AP:
|
|
// case AMMO_HEAT:
|
|
// iReduction = AMMO_ARMOUR_ADJUSTMENT_AP( iReduction );
|
|
// break;
|
|
// case AMMO_SUPER_AP:
|
|
// iReduction = AMMO_ARMOUR_ADJUSTMENT_SAP( iReduction );
|
|
// break;
|
|
// default:
|
|
// break;
|
|
//}
|
|
return( iReduction );
|
|
}
|
|
|
|
INT32 ArmourProtection( SOLDIERTYPE * pTarget, UINT16 ubArmourType, INT16 * pbStatus, INT32 iImpact, UINT8 ubAmmoType, BOOLEAN *plateHit )
|
|
{
|
|
INT32 iProtection, iAppliedProtection, iFailure, iCoverage;
|
|
|
|
iProtection = Armour[ ubArmourType ].ubProtection;
|
|
iCoverage = Armour [ ubArmourType ].ubCoverage;
|
|
if ( *plateHit ) iCoverage = 100;
|
|
|
|
if ( !AM_A_ROBOT( pTarget ) )
|
|
{
|
|
// check for the bullet missing armor due to coverage
|
|
iFailure = PreRandom( 100 ) + 1 - iCoverage;
|
|
if (iFailure > 0 )
|
|
{
|
|
if (Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_VEST && !AmmoTypes[ubAmmoType].ignoreArmour)
|
|
{
|
|
return ( iImpact/2 );
|
|
}
|
|
else return ( 0 );
|
|
}
|
|
// check for the bullet hitting a weak spot in the armour
|
|
iFailure = PreRandom( 100 ) + 1 - *pbStatus;
|
|
if (iFailure > 0)
|
|
{
|
|
iProtection -= iFailure;
|
|
if (iProtection < 0)
|
|
{
|
|
if (Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_PLATE ) *plateHit=true;
|
|
return( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// adjust protection of armour due to different ammo types
|
|
iProtection = (INT32)(iProtection * AmmoTypes[ubAmmoType].armourImpactReductionMultiplier / max(1,AmmoTypes[ubAmmoType].armourImpactReductionDivisor) );
|
|
|
|
//switch (ubAmmoType)
|
|
//{
|
|
// case AMMO_HP:
|
|
// iProtection = AMMO_ARMOUR_ADJUSTMENT_HP( iProtection );
|
|
// break;
|
|
// case AMMO_AP:
|
|
// case AMMO_HEAT:
|
|
// iProtection = AMMO_ARMOUR_ADJUSTMENT_AP( iProtection );
|
|
// break;
|
|
// case AMMO_SUPER_AP:
|
|
// iProtection = AMMO_ARMOUR_ADJUSTMENT_SAP( iProtection );
|
|
// break;
|
|
// default:
|
|
// break;
|
|
//}
|
|
|
|
// figure out how much of the armour's protection value is necessary
|
|
// in defending against this bullet
|
|
if (iProtection > iImpact )
|
|
{
|
|
iAppliedProtection = iImpact;
|
|
}
|
|
else
|
|
{
|
|
// applied protection is the full strength of the armour, before AP/HP changes
|
|
iAppliedProtection = Armour[ ubArmourType ].ubProtection;
|
|
}
|
|
|
|
// reduce armour condition
|
|
|
|
if ( AmmoTypes[ubAmmoType].acidic )
|
|
{
|
|
// creature spit damages armour a lot! an extra 3x for a total of 4x normal
|
|
*pbStatus -= 3 * (iAppliedProtection * Armour[ubArmourType].ubDegradePercent) / 100;
|
|
|
|
// reduce amount of protection from armour
|
|
iProtection /= 2;
|
|
}
|
|
else if ( AmmoTypes[ubAmmoType].ignoreArmour )
|
|
{
|
|
// knives and darts damage armour but are not stopped by kevlar
|
|
if (Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_VEST || Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_LEGGINGS)
|
|
{
|
|
iProtection = 0;
|
|
}
|
|
}
|
|
|
|
if ( !AM_A_ROBOT( pTarget ) )
|
|
{
|
|
*pbStatus -= (iAppliedProtection * Armour[ubArmourType].ubDegradePercent) / 100;
|
|
}
|
|
|
|
// return armour protection
|
|
if (Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_PLATE ) *plateHit=true;
|
|
return( iProtection );
|
|
}
|
|
|
|
|
|
INT32 TotalArmourProtection( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 iImpact, UINT8 ubAmmoType )
|
|
{
|
|
INT32 iTotalProtection = 0, iSlot;
|
|
OBJECTTYPE * pArmour;
|
|
BOOLEAN plateHit = false;
|
|
|
|
if (pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE)
|
|
{
|
|
INT16 bDummyStatus = 100;
|
|
BOOLEAN dummyCoverage = true;
|
|
|
|
//bDummyStatus = pVehicleList[ pTarget->bVehicleID ].sExternalArmorLocationsStatus[ ubHitLocation ];
|
|
|
|
iTotalProtection += ArmourProtection( pTarget, (UINT8) pVehicleList[ pTarget->bVehicleID ].sArmourType, &bDummyStatus, iImpact, ubAmmoType, &dummyCoverage );
|
|
|
|
//pVehicleList[ pTarget->bVehicleID ].sExternalArmorLocationsStatus[ ubHitLocation ] = bDummyStatus;
|
|
|
|
}
|
|
else
|
|
{
|
|
switch( ubHitLocation )
|
|
{
|
|
case AIM_SHOT_GLAND:
|
|
// creature hit in the glands!!! no armour there!
|
|
return( 0 );
|
|
case AIM_SHOT_HEAD:
|
|
iSlot = HELMETPOS;
|
|
break;
|
|
case AIM_SHOT_LEGS:
|
|
iSlot = LEGPOS;
|
|
break;
|
|
case AIM_SHOT_TORSO:
|
|
default:
|
|
iSlot = VESTPOS;
|
|
break;
|
|
|
|
}
|
|
|
|
pArmour = &(pTarget->inv[ iSlot ]);
|
|
if (pArmour->exists() == true)
|
|
{
|
|
for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) {
|
|
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
|
|
{
|
|
// bullet got through jacket; apply ceramic plate armour
|
|
iTotalProtection += ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit );
|
|
if ( (*iter)[0]->data.objectStatus < USABLE )
|
|
{
|
|
// destroy plates!
|
|
pArmour->RemoveAttachment(&(*iter));
|
|
DirtyMercPanelInterface( pTarget, DIRTYLEVEL2 );
|
|
//#ifdef ENGLISH
|
|
if ( pTarget->bTeam == gbPlayerNum )
|
|
{
|
|
// report plates destroyed!
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[61], pTarget->name );
|
|
}
|
|
//#endif
|
|
}
|
|
break;//original code only used the first ceramic plate
|
|
}
|
|
}
|
|
|
|
|
|
// if the plate didn't stop the bullet...
|
|
if ( iImpact > iTotalProtection )
|
|
{
|
|
iTotalProtection += ArmourProtection( pTarget, Item[pArmour->usItem].ubClassIndex, &((*pArmour)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit );
|
|
if ( (*pArmour)[0]->data.objectStatus < USABLE )
|
|
{
|
|
//Madd: put any attachments that someone might have added to the armour in the merc's inventory
|
|
for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) {
|
|
if ( !AutoPlaceObject( pTarget, &(*iter), FALSE ) )
|
|
{ // put it on the ground
|
|
AddItemToPool( pTarget->sGridNo, &(*iter), 1, pTarget->pathing.bLevel, 0 , -1 );
|
|
}
|
|
}
|
|
|
|
DeleteObj( pArmour );//takes care of attachments, no need to remove() or erase()
|
|
DirtyMercPanelInterface( pTarget, DIRTYLEVEL2 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return( iTotalProtection );
|
|
}
|
|
|
|
INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 iOrigImpact, INT16 sHitBy, UINT8 * pubSpecial )
|
|
{
|
|
INT32 iImpact, iFluke, iBonus, iImpactForCrits = 0;
|
|
INT8 bStatLoss;
|
|
UINT8 ubAmmoType;
|
|
|
|
// NOTE: reduction of bullet impact due to range and obstacles is handled
|
|
// in MoveBullet.
|
|
|
|
// Set a few things up:
|
|
if ( Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE )
|
|
{
|
|
ubAmmoType = AMMO_KNIFE;
|
|
}
|
|
else
|
|
{
|
|
ubAmmoType = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType;
|
|
}
|
|
|
|
if ( TANK( pTarget ) )
|
|
{
|
|
if ( !AmmoTypes[ubAmmoType].antiTank )
|
|
{
|
|
// ping!
|
|
return( 0 );
|
|
}
|
|
}
|
|
|
|
// plus/minus up to 25% due to "random" factors (major organs hit or missed,
|
|
// lucky lighter in breast pocket, divine intervention on behalf of "Rev"...)
|
|
iFluke = PreRandom(51) - 25; // gives (0 to 50 -25)->-25% to +25%
|
|
//NumMessage("Fluke = ",fluke);
|
|
|
|
// up to 50% extra impact for making particularly accurate successful shots
|
|
iBonus = sHitBy / 2;
|
|
//NumMessage("Bonus = ",bonus);
|
|
|
|
iOrigImpact = iOrigImpact * (100 + iFluke + iBonus) / 100;
|
|
|
|
// at very long ranges (1.5x maxRange and beyond) impact could go negative
|
|
if (iOrigImpact < 1)
|
|
{
|
|
iOrigImpact = 1; // raise impact to a minimum of 1 for any hit
|
|
}
|
|
|
|
// adjust for HE rounds
|
|
if ( AmmoTypes[ubAmmoType].highExplosive )
|
|
{
|
|
// iOrigImpact = AMMO_DAMAGE_ADJUSTMENT_HE( iOrigImpact );
|
|
iOrigImpact = (INT32)(iOrigImpact * AmmoTypes[ubAmmoType].beforeArmourDamageMultiplier / max(1,AmmoTypes[ubAmmoType].beforeArmourDamageDivisor) );
|
|
|
|
if ( TANK( pTarget ) )
|
|
{
|
|
// HEAT round on tank, divide by 3 for damage
|
|
iOrigImpact /= 2;
|
|
}
|
|
}
|
|
|
|
if (pubSpecial && *pubSpecial == FIRE_WEAPON_BLINDED_BY_SPIT_SPECIAL)
|
|
{
|
|
iImpact = iOrigImpact;
|
|
}
|
|
else
|
|
{
|
|
iImpact = iOrigImpact - TotalArmourProtection( pFirer, pTarget, ubHitLocation, iOrigImpact, ubAmmoType );
|
|
}
|
|
|
|
// calc minimum damage
|
|
if ( AmmoTypes[ubAmmoType].zeroMinimumDamage )
|
|
{
|
|
if (iImpact < 0)
|
|
{
|
|
iImpact = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (iImpact < ((iOrigImpact + 5) / 10) )
|
|
{
|
|
iImpact = (iOrigImpact + 5) / 10;
|
|
}
|
|
|
|
if ( (AmmoTypes[ubAmmoType].numberOfBullets > 1 ) && (pTarget->bNumPelletsHitBy > 0) )
|
|
{
|
|
iImpact += (pTarget->bNumPelletsHitBy - 1) / 2;
|
|
}
|
|
|
|
}
|
|
|
|
if (gfNextShotKills)
|
|
{
|
|
// big time cheat key effect!
|
|
iImpact = 100;
|
|
gfNextShotKills = FALSE;
|
|
}
|
|
|
|
if ( iImpact > 0 && !TANK( pTarget ) )
|
|
{
|
|
|
|
if ( AmmoTypes[ubAmmoType].dart && sHitBy > 20 )
|
|
{
|
|
if (pubSpecial)
|
|
{
|
|
*pubSpecial = FIRE_WEAPON_SLEEP_DART_SPECIAL;
|
|
}
|
|
return( iImpact );
|
|
}
|
|
|
|
iImpact = (INT32)(iImpact * AmmoTypes[ubAmmoType].afterArmourDamageMultiplier / max(1,AmmoTypes[ubAmmoType].afterArmourDamageDivisor) ) ;
|
|
//if (ubAmmoType == AMMO_HP)
|
|
//{ // good solid hit with a hollow-point bullet, which got through armour!
|
|
// iImpact = AMMO_DAMAGE_ADJUSTMENT_HP( iImpact );
|
|
//}
|
|
|
|
AdjustImpactByHitLocation( iImpact, ubHitLocation, &iImpact, &iImpactForCrits );
|
|
|
|
switch( ubHitLocation )
|
|
{
|
|
case AIM_SHOT_HEAD:
|
|
// is the blow deadly enough for an instant kill?
|
|
if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT ))
|
|
{
|
|
if (iImpactForCrits > MIN_DAMAGE_FOR_INSTANT_KILL && iImpactForCrits < pTarget->stats.bLife)
|
|
{
|
|
// blow to the head is so deadly that it causes instant death;
|
|
// the target has more life than iImpact so we increase it
|
|
iImpact = pTarget->stats.bLife + Random( 10 );
|
|
iImpactForCrits = iImpact;
|
|
}
|
|
|
|
if (pubSpecial)
|
|
{
|
|
// is the blow deadly enough to cause a head explosion?
|
|
if ( iImpactForCrits >= pTarget->stats.bLife )
|
|
{
|
|
if (iImpactForCrits > MIN_DAMAGE_FOR_HEAD_EXPLOSION )
|
|
{
|
|
*pubSpecial = FIRE_WEAPON_HEAD_EXPLODE_SPECIAL;
|
|
}
|
|
else if ( iImpactForCrits > (MIN_DAMAGE_FOR_HEAD_EXPLOSION / 2) && ( PreRandom( MIN_DAMAGE_FOR_HEAD_EXPLOSION / 2 ) < (UINT32)(iImpactForCrits - MIN_DAMAGE_FOR_HEAD_EXPLOSION / 2) ) )
|
|
{
|
|
*pubSpecial = FIRE_WEAPON_HEAD_EXPLODE_SPECIAL;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
break;
|
|
case AIM_SHOT_LEGS:
|
|
// is the damage enough to make us fall over?
|
|
if ( pubSpecial && IS_MERC_BODY_TYPE( pTarget ) && gAnimControl[ pTarget->usAnimState ].ubEndHeight == ANIM_STAND && !pTarget->MercInWater() )
|
|
{
|
|
if (iImpactForCrits > MIN_DAMAGE_FOR_AUTO_FALL_OVER )
|
|
{
|
|
*pubSpecial = FIRE_WEAPON_LEG_FALLDOWN_SPECIAL;
|
|
}
|
|
// else ramping up chance from 1/2 the automatic value onwards
|
|
else if ( iImpactForCrits > (MIN_DAMAGE_FOR_AUTO_FALL_OVER / 2) && ( PreRandom( MIN_DAMAGE_FOR_AUTO_FALL_OVER / 2 ) < (UINT32)(iImpactForCrits - MIN_DAMAGE_FOR_AUTO_FALL_OVER / 2) ) )
|
|
{
|
|
*pubSpecial = FIRE_WEAPON_LEG_FALLDOWN_SPECIAL;
|
|
}
|
|
}
|
|
break;
|
|
case AIM_SHOT_TORSO:
|
|
// normal damage to torso
|
|
// is the blow deadly enough for an instant kill?
|
|
// since this value is much lower than the others, it only applies at short range...
|
|
if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT ))
|
|
{
|
|
if (iImpact > MIN_DAMAGE_FOR_INSTANT_KILL && iImpact < pTarget->stats.bLife)
|
|
{
|
|
// blow to the chest is so deadly that it causes instant death;
|
|
// the target has more life than iImpact so we increase it
|
|
iImpact = pTarget->stats.bLife + Random( 10 );
|
|
iImpactForCrits = iImpact;
|
|
}
|
|
// special thing for hitting chest - allow cumulative damage to count
|
|
else if ( (iImpact + pTarget->sDamage) > (MIN_DAMAGE_FOR_BLOWN_AWAY + MIN_DAMAGE_FOR_INSTANT_KILL) )
|
|
{
|
|
iImpact = pTarget->stats.bLife + Random( 10 );
|
|
iImpactForCrits = iImpact;
|
|
}
|
|
|
|
// is the blow deadly enough to cause a chest explosion?
|
|
if (pubSpecial)
|
|
{
|
|
if (iImpact > MIN_DAMAGE_FOR_BLOWN_AWAY && iImpact >= pTarget->stats.bLife)
|
|
{
|
|
*pubSpecial = FIRE_WEAPON_CHEST_EXPLODE_SPECIAL;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( AM_A_ROBOT( pTarget ) )
|
|
{
|
|
iImpactForCrits = 0;
|
|
}
|
|
|
|
// don't do critical hits against people who are gonna die!
|
|
if( !IsAutoResolveActive() )
|
|
{
|
|
|
|
if ( AmmoTypes[ubAmmoType].knife && pFirer->aiData.bOppList[ pTarget->ubID ] == SEEN_CURRENTLY )
|
|
{
|
|
// is this a stealth attack?
|
|
if ( pTarget->aiData.bOppList[ pFirer->ubID ] == NOT_HEARD_OR_SEEN && !CREATURE_OR_BLOODCAT( pTarget ) && (ubHitLocation == AIM_SHOT_HEAD || ubHitLocation == AIM_SHOT_TORSO ) )
|
|
{
|
|
if ( PreRandom( 100 ) < (UINT32)(sHitBy + 10 * NUM_SKILL_TRAITS( pFirer, THROWING )) )
|
|
{
|
|
// instant death!
|
|
iImpact = pTarget->stats.bLife + Random( 10 );
|
|
iImpactForCrits = iImpact;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iImpactForCrits > 0 && iImpactForCrits < pTarget->stats.bLife )
|
|
{
|
|
if (PreRandom( iImpactForCrits / 2 + pFirer->aiData.bAimTime * 5) + 1 > CRITICAL_HIT_THRESHOLD)
|
|
{
|
|
bStatLoss = (INT8) PreRandom( iImpactForCrits / 2 ) + 1;
|
|
switch( ubHitLocation )
|
|
{
|
|
case AIM_SHOT_HEAD:
|
|
if (bStatLoss >= pTarget->stats.bWisdom)
|
|
{
|
|
bStatLoss = pTarget->stats.bWisdom - 1;
|
|
}
|
|
if ( bStatLoss > 0 )
|
|
{
|
|
pTarget->stats.bWisdom -= bStatLoss;
|
|
|
|
if (pTarget->ubProfile != NO_PROFILE)
|
|
{
|
|
gMercProfiles[ pTarget->ubProfile ].bWisdom = pTarget->stats.bWisdom;
|
|
}
|
|
|
|
|
|
if (pTarget->name[0] && pTarget->bVisible == TRUE)
|
|
{
|
|
// make stat RED for a while...
|
|
pTarget->timeChanges.uiChangeWisdomTime = GetJA2Clock();
|
|
pTarget->usValueGoneUp &= ~( WIS_INCREASE );
|
|
|
|
if (bStatLoss == 1)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_WISDOM], pTarget->name );
|
|
}
|
|
else
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_WISDOM], pTarget->name, bStatLoss );
|
|
}
|
|
}
|
|
}
|
|
else if ( pTarget->bNumPelletsHitBy == 0 )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_HEAD_HIT], pTarget->name );
|
|
}
|
|
break;
|
|
case AIM_SHOT_TORSO:
|
|
if (PreRandom( 1 ) == 0 && !(pTarget->flags.uiStatusFlags & SOLDIER_MONSTER) )
|
|
{
|
|
if (bStatLoss >= pTarget->stats.bDexterity)
|
|
{
|
|
bStatLoss = pTarget->stats.bDexterity - 1;
|
|
}
|
|
if ( bStatLoss > 0 )
|
|
{
|
|
pTarget->stats.bDexterity -= bStatLoss;
|
|
|
|
if (pTarget->ubProfile != NO_PROFILE)
|
|
{
|
|
gMercProfiles[ pTarget->ubProfile ].bDexterity = pTarget->stats.bDexterity;
|
|
}
|
|
|
|
if (pTarget->name[0] && pTarget->bVisible == TRUE)
|
|
{
|
|
// make stat RED for a while...
|
|
pTarget->timeChanges.uiChangeDexterityTime = GetJA2Clock();
|
|
pTarget->usValueGoneUp &= ~( DEX_INCREASE );
|
|
|
|
if (bStatLoss == 1)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_DEX], pTarget->name );
|
|
}
|
|
else
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_DEX], pTarget->name, bStatLoss );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (bStatLoss >= pTarget->stats.bStrength)
|
|
{
|
|
bStatLoss = pTarget->stats.bStrength - 1;
|
|
}
|
|
if ( bStatLoss > 0 )
|
|
{
|
|
pTarget->stats.bStrength -= bStatLoss;
|
|
|
|
if (pTarget->ubProfile != NO_PROFILE)
|
|
{
|
|
gMercProfiles[ pTarget->ubProfile ].bStrength = pTarget->stats.bStrength;
|
|
}
|
|
|
|
if (pTarget->name[0] && pTarget->bVisible == TRUE)
|
|
{
|
|
// make stat RED for a while...
|
|
pTarget->timeChanges.uiChangeStrengthTime = GetJA2Clock();
|
|
pTarget->usValueGoneUp &= ~( STRENGTH_INCREASE );
|
|
|
|
if (bStatLoss == 1)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_STRENGTH], pTarget->name );
|
|
}
|
|
else
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_STRENGTH], pTarget->name, bStatLoss );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AIM_SHOT_LEGS:
|
|
if (bStatLoss >= pTarget->stats.bAgility)
|
|
{
|
|
bStatLoss = pTarget->stats.bAgility - 1;
|
|
}
|
|
if ( bStatLoss > 0 )
|
|
{
|
|
pTarget->stats.bAgility -= bStatLoss;
|
|
|
|
if (pTarget->ubProfile != NO_PROFILE)
|
|
{
|
|
gMercProfiles[ pTarget->ubProfile ].bAgility = pTarget->stats.bAgility;
|
|
}
|
|
|
|
if (pTarget->name[0] && pTarget->bVisible == TRUE)
|
|
{
|
|
// make stat RED for a while...
|
|
pTarget->timeChanges.uiChangeAgilityTime = GetJA2Clock();
|
|
pTarget->usValueGoneUp &= ~( AGIL_INCREASE );
|
|
|
|
if (bStatLoss == 1)
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_AGIL], pTarget->name );
|
|
}
|
|
else
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_AGIL], pTarget->name, bStatLoss );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else if ( ubHitLocation == AIM_SHOT_HEAD && pTarget->bNumPelletsHitBy == 0 )
|
|
{
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_HEAD_HIT], pTarget->name );
|
|
}
|
|
}
|
|
}
|
|
|
|
return( iImpact );
|
|
}
|
|
|
|
INT32 HTHImpact( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTarget, INT32 iHitBy, BOOLEAN fBladeAttack )
|
|
{
|
|
INT32 iImpact, iFluke, iBonus;
|
|
|
|
if (fBladeAttack)
|
|
{
|
|
iImpact = ( EffectiveExpLevel( pSoldier ) / 2); // 0 to 4 for level
|
|
iImpact += GetDamage(&pSoldier->inv[HANDPOS]);
|
|
iImpact += EffectiveStrength( pSoldier ) / 20; // 0 to 5 for strength, adjusted by damage taken
|
|
|
|
if ( AM_A_ROBOT( pTarget ) )
|
|
{
|
|
iImpact /= 4;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iImpact = ( EffectiveExpLevel( pSoldier ) / 2); // 0 to 4 for level
|
|
iImpact += EffectiveStrength( pSoldier ) / 5; // 0 to 20 for strength, adjusted by damage taken
|
|
|
|
// NB martial artists don't get a bonus for using brass knuckles!
|
|
if (pSoldier->usAttackingWeapon && !( HAS_SKILL_TRAIT( pSoldier, MARTIALARTS ) ) )
|
|
{
|
|
iImpact += GetDamage(&pSoldier->inv[HANDPOS]);
|
|
|
|
if ( AM_A_ROBOT( pTarget ) )
|
|
{
|
|
iImpact /= 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// base HTH damage
|
|
iImpact += 5;
|
|
if ( AM_A_ROBOT( pTarget ) )
|
|
{
|
|
iImpact = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
iFluke = PreRandom( 51 ) - 25; // +/-25% bonus due to random factors
|
|
iBonus = iHitBy / 2; // up to 50% extra impact for accurate attacks
|
|
|
|
iImpact = iImpact * (100 + iFluke + iBonus) / 100;
|
|
|
|
if (!fBladeAttack)
|
|
{
|
|
// add bonuses for hand-to-hand and martial arts
|
|
if ( HAS_SKILL_TRAIT( pSoldier, MARTIALARTS ) )
|
|
{
|
|
iImpact = iImpact * ( 100 + gbSkillTraitBonus[MARTIALARTS] * NUM_SKILL_TRAITS( pSoldier, MARTIALARTS ) ) / 100;
|
|
if (pSoldier->usAnimState == NINJA_SPINKICK)
|
|
{
|
|
iImpact *= 2;
|
|
}
|
|
}
|
|
if ( HAS_SKILL_TRAIT( pSoldier, HANDTOHAND ) )
|
|
{
|
|
// SPECIAL - give TRIPLE bonus for damage for hand-to-hand trait
|
|
// because the HTH bonus is half that of martial arts, and gets only 1x for to-hit bonus
|
|
iImpact = iImpact * ( 100 + 3 * gbSkillTraitBonus[HANDTOHAND] * NUM_SKILL_TRAITS( pSoldier, HANDTOHAND ) ) / 100;
|
|
}
|
|
}
|
|
|
|
return( iImpact );
|
|
}
|
|
|
|
void ShotMiss( UINT8 ubAttackerID, INT32 iBullet )
|
|
{
|
|
BOOLEAN fDoMissForGun = FALSE;
|
|
SOLDIERTYPE *pAttacker;
|
|
BULLET *pBullet;
|
|
|
|
pAttacker = MercPtrs[ ubAttackerID ];
|
|
|
|
if ( pAttacker->ubOppNum != NOBODY )
|
|
{
|
|
// if it was another team shooting at someone under our control
|
|
if ( (pAttacker->bTeam != Menptr[ pAttacker->ubOppNum ].bTeam ) )
|
|
{
|
|
// if OPPONENT is under our control
|
|
if (Menptr[ pAttacker->ubOppNum ].bTeam == gbPlayerNum )
|
|
{
|
|
// AGILITY GAIN: Opponent "dodged" a bullet shot at him (it missed)
|
|
StatChange( MercPtrs[ pAttacker->ubOppNum ], AGILAMT, 5, FROM_FAILURE );
|
|
}
|
|
}
|
|
}
|
|
|
|
switch( Weapon[ MercPtrs[ ubAttackerID ]->usAttackingWeapon ].ubWeaponClass )
|
|
{
|
|
case HANDGUNCLASS:
|
|
case RIFLECLASS:
|
|
case SHOTGUNCLASS:
|
|
case SMGCLASS:
|
|
case MGCLASS:
|
|
|
|
// Guy has missed, play random sound
|
|
if ( MercPtrs[ ubAttackerID ]->bTeam == gbPlayerNum )
|
|
{
|
|
if ( Random(40) == 0 )
|
|
{
|
|
MercPtrs[ ubAttackerID ]->DoMercBattleSound( BATTLE_SOUND_CURSE1 );
|
|
}
|
|
}
|
|
fDoMissForGun = TRUE;
|
|
break;
|
|
|
|
case MONSTERCLASS:
|
|
PlayJA2Sample( SPIT_RICOCHET, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
|
|
break;
|
|
}
|
|
|
|
if ( fDoMissForGun )
|
|
{
|
|
// PLAY SOUND AND FLING DEBRIS
|
|
// RANDOMIZE SOUND SYSTEM
|
|
|
|
if ( !DoSpecialEffectAmmoMiss( ubAttackerID, NOWHERE, 0, 0, 0, TRUE, TRUE, 0 ) )
|
|
{
|
|
PlayJA2Sample( MISS_1 + Random(8), RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
|
|
}
|
|
|
|
// ATE: Show misses...( if our team )
|
|
if ( gGameSettings.fOptions[ TOPTION_SHOW_MISSES ] )
|
|
{
|
|
pBullet = GetBulletPtr( iBullet );
|
|
|
|
if ( pAttacker->bTeam == gbPlayerNum )
|
|
{
|
|
LocateGridNo( (INT16)pBullet->sGridNo );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - bullet missed") );
|
|
// FreeUpAttacker( ubAttackerID );
|
|
}
|
|
|
|
UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, UINT8 ubAimTime, UINT8 ubMode )
|
|
{
|
|
UINT16 usInHand;
|
|
UINT8 ubBandaged;
|
|
INT32 iAttRating, iDefRating;
|
|
INT32 iChance;
|
|
|
|
usInHand = pAttacker->usAttackingWeapon;
|
|
|
|
if ( (usInHand != CREATURE_QUEEN_TENTACLES ) && (pDefender->stats.bLife < OKLIFE || pDefender->bBreath < OKBREATH) )
|
|
{
|
|
// there is NO way to miss
|
|
return( 100 );
|
|
}
|
|
|
|
if (ubMode == HTH_MODE_STAB)
|
|
{
|
|
// safety check
|
|
if (Weapon[usInHand].ubWeaponClass != KNIFECLASS)
|
|
{
|
|
#ifdef BETAVERSION
|
|
NumMessage("CalcChanceToStab: ERROR - Attacker isn't holding a knife, usInHand = ",usInHand);
|
|
#endif
|
|
return(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( Item[ usInHand ].usItemClass != IC_PUNCH )
|
|
{
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
// CALCULATE ATTACKER'S CLOSE COMBAT RATING (1-100)
|
|
if (ubMode == HTH_MODE_STEAL)
|
|
{
|
|
// this is more of a brute force strength-vs-strength check
|
|
iAttRating = ( EffectiveDexterity( pAttacker ) + // coordination, accuracy
|
|
EffectiveAgility( pAttacker ) + // speed & reflexes
|
|
3 * pAttacker->stats.bStrength + // physical strength (TRIPLED!)
|
|
(10 * EffectiveExpLevel( pAttacker ) ) ); // experience, knowledge
|
|
}
|
|
else
|
|
{
|
|
iAttRating = (3 * EffectiveDexterity( pAttacker ) + // coordination, accuracy (TRIPLED!)
|
|
EffectiveAgility( pAttacker ) + // speed & reflexes
|
|
pAttacker->stats.bStrength + // physical strength
|
|
(10 * EffectiveExpLevel( pAttacker ) ) ); // experience, knowledge
|
|
}
|
|
|
|
iAttRating /= 6; // convert from 6-600 to 1-100
|
|
|
|
// psycho bonus
|
|
if ( pAttacker->ubProfile != NO_PROFILE && gMercProfiles[ pAttacker->ubProfile ].bPersonalityTrait == PSYCHO )
|
|
{
|
|
iAttRating += AIM_BONUS_PSYCHO;
|
|
}
|
|
|
|
// modify chance to hit by morale
|
|
iAttRating += GetMoraleModifier( pAttacker );
|
|
|
|
// modify for fatigue
|
|
iAttRating -= GetSkillCheckPenaltyForFatigue( pAttacker, iAttRating );
|
|
|
|
// if attacker spent some extra time aiming
|
|
if (ubAimTime)
|
|
{
|
|
// use only HALF of the normal aiming bonus for knife aiming.
|
|
// since there's no range penalty, the bonus is otherwise too generous
|
|
iAttRating += ((AIM_BONUS_PER_AP * ubAimTime) / 2); //bonus for aiming
|
|
}
|
|
|
|
if (! (pAttacker->flags.uiStatusFlags & SOLDIER_PC) ) // if attacker is a computer AI controlled enemy
|
|
{
|
|
iAttRating += gbDiff[ DIFF_ENEMY_TO_HIT_MOD ][ SoldierDifficultyLevel( pAttacker ) ];
|
|
}
|
|
|
|
// if attacker is being affected by gas
|
|
if ( pAttacker->flags.uiStatusFlags & SOLDIER_GASSED )
|
|
iAttRating -= AIM_PENALTY_GASSED;
|
|
|
|
// if attacker is being bandaged at the same time, his concentration is off
|
|
if (pAttacker->ubServiceCount > 0)
|
|
iAttRating -= AIM_PENALTY_GETTINGAID;
|
|
|
|
// if attacker is still in shock
|
|
if (pAttacker->aiData.bShock)
|
|
iAttRating -= (pAttacker->aiData.bShock * AIM_PENALTY_PER_SHOCK);
|
|
|
|
/*
|
|
// if the attacker is an A.I.M. mercenary
|
|
if (pAttacker->characternum < MAX_AIM_MERCS) // exclude Gus
|
|
iAttRating += AdjustChanceForProfile(pAttacker,pDefender);
|
|
*/
|
|
|
|
// If attacker injured, reduce chance accordingly (by up to 2/3rds)
|
|
if ((iAttRating > 0) && (pAttacker->stats.bLife < pAttacker->stats.bLifeMax))
|
|
{
|
|
// if bandaged, give 1/2 of the bandaged life points back into equation
|
|
ubBandaged = pAttacker->stats.bLifeMax - pAttacker->stats.bLife - pAttacker->bBleeding;
|
|
|
|
iAttRating -= (2 * iAttRating * (pAttacker->stats.bLifeMax - pAttacker->stats.bLife + (ubBandaged / 2))) /
|
|
(3 * pAttacker->stats.bLifeMax);
|
|
}
|
|
|
|
// If attacker tired, reduce chance accordingly (by up to 1/2)
|
|
if ((iAttRating > 0) && (pAttacker->bBreath < 100))
|
|
iAttRating -= (iAttRating * (100 - pAttacker->bBreath)) / 200;
|
|
|
|
if (pAttacker->ubProfile != NO_PROFILE)
|
|
{
|
|
if (ubMode == HTH_MODE_STAB)
|
|
{
|
|
if (HAS_SKILL_TRAIT( pAttacker, KNIFING ))
|
|
{
|
|
iAttRating += gbSkillTraitBonus[KNIFING] * NUM_SKILL_TRAITS( pAttacker, KNIFING );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// add bonuses for hand-to-hand and martial arts
|
|
if (HAS_SKILL_TRAIT( pAttacker, MARTIALARTS ))
|
|
{
|
|
iAttRating += gbSkillTraitBonus[MARTIALARTS] * NUM_SKILL_TRAITS( pAttacker, MARTIALARTS );
|
|
}
|
|
if ( HAS_SKILL_TRAIT( pAttacker, HANDTOHAND ) )
|
|
{
|
|
iAttRating += gbSkillTraitBonus[HANDTOHAND] * NUM_SKILL_TRAITS( pAttacker, HANDTOHAND );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (iAttRating < 1)
|
|
iAttRating = 1;
|
|
|
|
|
|
// CALCULATE DEFENDER'S CLOSE COMBAT RATING (0-100)
|
|
if (ubMode == HTH_MODE_STEAL)
|
|
{
|
|
iDefRating = (EffectiveAgility( pDefender )) + // speed & reflexes
|
|
EffectiveDexterity( pDefender ) + // coordination, accuracy
|
|
3 * pDefender->stats.bStrength + // physical strength (TRIPLED!)
|
|
(10 * EffectiveExpLevel( pDefender ) ); // experience, knowledge
|
|
}
|
|
else
|
|
{
|
|
iDefRating = (3 * EffectiveAgility( pDefender ) ) + // speed & reflexes (TRIPLED!)
|
|
EffectiveDexterity( pDefender ) + // coordination, accuracy
|
|
pDefender->stats.bStrength + // physical strength
|
|
(10 * EffectiveExpLevel( pDefender ) ); // experience, knowledge
|
|
}
|
|
|
|
iDefRating /= 6; // convert from 6-600 to 1-100
|
|
|
|
// modify chance to dodge by morale
|
|
iDefRating += GetMoraleModifier( pDefender );
|
|
|
|
// modify for fatigue
|
|
iDefRating -= GetSkillCheckPenaltyForFatigue( pDefender, iDefRating );
|
|
|
|
// if attacker is being affected by gas
|
|
if ( pDefender->flags.uiStatusFlags & SOLDIER_GASSED )
|
|
iDefRating -= AIM_PENALTY_GASSED;
|
|
|
|
// if defender is being bandaged at the same time, his concentration is off
|
|
if (pDefender->ubServiceCount > 0)
|
|
iDefRating -= AIM_PENALTY_GETTINGAID;
|
|
|
|
// if defender is still in shock
|
|
if (pDefender->aiData.bShock)
|
|
iDefRating -= (pDefender->aiData.bShock * AIM_PENALTY_PER_SHOCK);
|
|
|
|
/*
|
|
// if the defender is an A.I.M. mercenary
|
|
if (pDefender->characternum < MAX_AIM_MERCS) // exclude Gus
|
|
iDefRating += AdjustChanceForProfile(pDefender,pAttacker);
|
|
*/
|
|
|
|
// If defender injured, reduce chance accordingly (by up to 2/3rds)
|
|
if ((iDefRating > 0) && (pDefender->stats.bLife < pDefender->stats.bLifeMax))
|
|
{
|
|
// if bandaged, give 1/2 of the bandaged life points back into equation
|
|
ubBandaged = pDefender->stats.bLifeMax - pDefender->stats.bLife - pDefender->bBleeding;
|
|
|
|
iDefRating -= (2 * iDefRating * (pDefender->stats.bLifeMax - pDefender->stats.bLife + (ubBandaged / 2))) /
|
|
(3 * pDefender->stats.bLifeMax);
|
|
|
|
}
|
|
|
|
// If defender tired, reduce chance accordingly (by up to 1/2)
|
|
if ((iDefRating > 0) && (pDefender->bBreath < 100))
|
|
iDefRating -= (iDefRating * (100 - pDefender->bBreath)) / 200;
|
|
|
|
if ( usInHand == CREATURE_QUEEN_TENTACLES && pDefender->ubBodyType == LARVAE_MONSTER || pDefender->ubBodyType == INFANT_MONSTER )
|
|
{
|
|
// try to prevent queen from killing the kids, ever!
|
|
iDefRating += 10000;
|
|
}
|
|
|
|
if (gAnimControl[ pDefender->usAnimState ].ubEndHeight < ANIM_STAND)
|
|
{
|
|
if (usInHand == CREATURE_QUEEN_TENTACLES)
|
|
{
|
|
if ( gAnimControl[ pDefender->usAnimState ].ubEndHeight == ANIM_PRONE )
|
|
{
|
|
// make it well-nigh impossible to hit someone who is prone!
|
|
iDefRating += 1000;
|
|
}
|
|
else
|
|
{
|
|
iDefRating += BAD_DODGE_POSITION_PENALTY * 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if defender crouched, reduce chance accordingly (harder to dodge)
|
|
iDefRating -= BAD_DODGE_POSITION_PENALTY;
|
|
// If our target is prone, double the penalty!
|
|
if ( gAnimControl[ pDefender->usAnimState ].ubEndHeight == ANIM_PRONE )
|
|
{
|
|
iDefRating -= BAD_DODGE_POSITION_PENALTY;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (pDefender->ubProfile != NO_PROFILE)
|
|
{
|
|
if (ubMode == HTH_MODE_STAB)
|
|
{
|
|
if (Item[pDefender->inv[HANDPOS].usItem].usItemClass == IC_BLADE)
|
|
{
|
|
if ( HAS_SKILL_TRAIT( pDefender, KNIFING ) )
|
|
{
|
|
// good with knives, got one, so we're good at parrying
|
|
iDefRating += gbSkillTraitBonus[KNIFING] * NUM_SKILL_TRAITS( pDefender, KNIFING );
|
|
}
|
|
if (HAS_SKILL_TRAIT( pDefender, MARTIALARTS ))
|
|
{
|
|
// the knife gets in the way but we're still better than nobody
|
|
iDefRating += ( gbSkillTraitBonus[MARTIALARTS] * NUM_SKILL_TRAITS( pDefender, MARTIALARTS ) ) / 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( HAS_SKILL_TRAIT( pDefender, KNIFING ) )
|
|
{
|
|
// good with knives, don't have one, but we know a bit about dodging
|
|
iDefRating += ( gbSkillTraitBonus[KNIFING] * NUM_SKILL_TRAITS( pDefender, KNIFING ) ) / 3;
|
|
}
|
|
if (HAS_SKILL_TRAIT( pDefender, MARTIALARTS ))
|
|
{
|
|
// bonus for dodging knives
|
|
iDefRating += ( gbSkillTraitBonus[MARTIALARTS] * NUM_SKILL_TRAITS( pDefender, MARTIALARTS ) )/ 2;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // punch/hand-to-hand/martial arts attack/steal
|
|
if (Item[pDefender->inv[HANDPOS].usItem].usItemClass == IC_BLADE && ubMode != HTH_MODE_STEAL)
|
|
{
|
|
if (HAS_SKILL_TRAIT( pDefender, KNIFING ))
|
|
{
|
|
// with our knife, we get some bonus at defending from HTH attacks
|
|
iDefRating += ( gbSkillTraitBonus[KNIFING] * NUM_SKILL_TRAITS( pDefender, KNIFING ) ) / 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (HAS_SKILL_TRAIT( pDefender, MARTIALARTS ))
|
|
{
|
|
iDefRating += gbSkillTraitBonus[MARTIALARTS] * NUM_SKILL_TRAITS( pDefender, MARTIALARTS );
|
|
}
|
|
if (HAS_SKILL_TRAIT( pDefender, HANDTOHAND ))
|
|
{
|
|
iDefRating += gbSkillTraitBonus[HANDTOHAND] * NUM_SKILL_TRAITS( pDefender, HANDTOHAND );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iDefRating < 1)
|
|
iDefRating = 1;
|
|
|
|
|
|
//NumMessage("CalcChanceToStab - Attacker's Rating = ",iAttRating);
|
|
//NumMessage("CalcChanceToStab - Defender's Rating = ",iDefRating);
|
|
|
|
// calculate chance to hit by comparing the 2 opponent's ratings
|
|
// iChance = (100 * iAttRating) / (iAttRating + iDefRating);
|
|
|
|
|
|
if (ubMode == HTH_MODE_STEAL)
|
|
{
|
|
// make this more extreme so that weak people have a harder time stealing from
|
|
// the stronger
|
|
iChance = 50 * iAttRating / iDefRating;
|
|
}
|
|
else
|
|
{
|
|
// Changed from DG by CJC to give higher chances of hitting with a stab or punch
|
|
iChance = 67 + (iAttRating - iDefRating) / 3;
|
|
|
|
if ( pAttacker->bAimShotLocation == AIM_SHOT_HEAD )
|
|
{
|
|
// make this harder!
|
|
iChance -= 20;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS
|
|
if (iChance < MINCHANCETOHIT)
|
|
{
|
|
iChance = MINCHANCETOHIT;
|
|
}
|
|
else
|
|
{
|
|
if (iChance > MAXCHANCETOHIT)
|
|
iChance = MAXCHANCETOHIT;
|
|
}
|
|
|
|
//NumMessage("ChanceToStab = ",chance);
|
|
|
|
return (iChance);
|
|
}
|
|
|
|
UINT32 CalcChanceToStab(SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, UINT8 ubAimTime)
|
|
{
|
|
return( CalcChanceHTH( pAttacker, pDefender, ubAimTime, HTH_MODE_STAB ) );
|
|
}
|
|
|
|
UINT32 CalcChanceToPunch(SOLDIERTYPE *pAttacker, SOLDIERTYPE * pDefender, UINT8 ubAimTime)
|
|
{
|
|
return( CalcChanceHTH( pAttacker, pDefender, ubAimTime, HTH_MODE_PUNCH ) );
|
|
}
|
|
|
|
UINT32 CalcChanceToSteal(SOLDIERTYPE *pAttacker, SOLDIERTYPE * pDefender, UINT8 ubAimTime)
|
|
{
|
|
return( CalcChanceHTH( pAttacker, pDefender, ubAimTime, HTH_MODE_STEAL ) );
|
|
}
|
|
|
|
|
|
void ReloadWeapon( SOLDIERTYPE *pSoldier, UINT8 ubHandPos )
|
|
{
|
|
// NB this is a cheat function, don't award experience
|
|
|
|
if ( pSoldier->inv[ ubHandPos ].exists() == true )
|
|
{
|
|
pSoldier->inv[ ubHandPos ][0]->data.gun.ubGunShotsLeft = GetMagSize(&pSoldier->inv[ ubHandPos ]);
|
|
// Dirty Bars
|
|
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 );
|
|
}
|
|
}
|
|
|
|
// Changed by ADB, rev 1513
|
|
//BOOLEAN IsGunWeaponModeCapable( SOLDIERTYPE *pSoldier, UINT8 ubHandPos , UINT8 bWpnMode)
|
|
BOOLEAN IsGunWeaponModeCapable( OBJECTTYPE* pObject, WeaponMode bWpnMode, SOLDIERTYPE *pSoldier )
|
|
{
|
|
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("IsGunWeaponModeCapable: weapon mode=%d",bWpnMode));
|
|
|
|
switch(bWpnMode)
|
|
{
|
|
case WM_NORMAL:
|
|
//return (pSoldier->inv[ ubHandPos ].exists() == true && Item[ pSoldier->inv[ ubHandPos ].usItem ].usItemClass & IC_WEAPON && !Weapon[ pSoldier->inv[ ubHandPos ].usItem ].NoSemiAuto ); // Check for being a weapon....
|
|
return (pObject->exists() == true && Item[ pObject->usItem ].usItemClass & IC_WEAPON && !Weapon[ pObject->usItem ].NoSemiAuto ); // Check for being a weapon....
|
|
|
|
case WM_BURST:
|
|
//return IsGunBurstCapable(pSoldier, ubHandPos, FALSE);
|
|
return IsGunBurstCapable(pObject, FALSE, pSoldier);
|
|
|
|
case WM_AUTOFIRE:
|
|
//return ((IsGunAutofireCapable(pSoldier, ubHandPos) || Weapon[ pSoldier->inv[ ubHandPos ].usItem ].NoSemiAuto )&& !Item[pSoldier->inv[ubHandPos].usItem].grenadelauncher );
|
|
return ((IsGunAutofireCapable(pObject) || Weapon[ pObject->usItem ].NoSemiAuto )&& !Item[pObject->usItem].grenadelauncher );
|
|
|
|
case WM_ATTACHED_GL:
|
|
// return (FindAttachment( &(pSoldier->inv[ubHandPos]), UNDER_GLAUNCHER ) != 0 && FindLaunchableAttachment( &(pSoldier->inv[ubHandPos]), UNDER_GLAUNCHER ) != 0 );
|
|
|
|
//return (!Item[pSoldier->inv[ubHandPos].usItem].grenadelauncher && IsGrenadeLauncherAttached( &(pSoldier->inv[ubHandPos]) ) && FindLaunchableAttachment( &(pSoldier->inv[ubHandPos]), GetAttachedGrenadeLauncher( &(pSoldier->inv[ubHandPos]) )) != 0 );
|
|
return (!Item[pObject->usItem].grenadelauncher && IsGrenadeLauncherAttached( pObject ) && FindLaunchableAttachment( pObject, GetAttachedGrenadeLauncher( pObject )) != 0 );
|
|
|
|
case WM_ATTACHED_GL_BURST:
|
|
//return (!Item[pSoldier->inv[ubHandPos].usItem].grenadelauncher && IsGrenadeLauncherAttached( &(pSoldier->inv[ubHandPos]) ) && Weapon[GetAttachedGrenadeLauncher(&pSoldier->inv[ubHandPos])].ubShotsPerBurst > 0 && FindLaunchableAttachment( &(pSoldier->inv[ubHandPos]), GetAttachedGrenadeLauncher( &(pSoldier->inv[ubHandPos]))) != 0 );
|
|
return (!Item[pObject->usItem].grenadelauncher && IsGrenadeLauncherAttached( pObject ) && Weapon[GetAttachedGrenadeLauncher(pObject)].ubShotsPerBurst > 0 && FindLaunchableAttachment( pObject, GetAttachedGrenadeLauncher( pObject)) != 0 );
|
|
|
|
case WM_ATTACHED_GL_AUTO:
|
|
return FALSE;
|
|
// return (!Item[pSoldier->inv[ubHandPos].usItem].grenadelauncher && Weapon[GetAttachedGrenadeLauncher(&pSoldier->inv[ubHandPos])].bAutofireShotsPerFiveAP > 0 && FindLaunchableAttachment( &(pSoldier->inv[ubHandPos]), GetAttachedGrenadeLauncher( &(pSoldier->inv[ubHandPos]) ) != ITEM_NOT_FOUND );
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Changed by ADB, rev 1513
|
|
//BOOLEAN IsGunAutofireCapable( SOLDIERTYPE *pSoldier, UINT8 ubHandPos )
|
|
BOOLEAN IsGunAutofireCapable( OBJECTTYPE* pObject )
|
|
{
|
|
BOOLEAN fCapable = FALSE;
|
|
|
|
//if ( pSoldier->inv[ ubHandPos ].exists() == true )
|
|
if ( pObject->exists() == true )
|
|
{
|
|
// ATE: Check for being a weapon....
|
|
//if ( Item[ pSoldier->inv[ ubHandPos ].usItem ].usItemClass & IC_WEAPON )
|
|
if ( Item[ pObject->usItem ].usItemClass & IC_WEAPON )
|
|
{
|
|
//if ( GetAutofireShotsPerFiveAPs(&pSoldier->inv[ ubHandPos ]) > 0 )
|
|
if ( GetAutofireShotsPerFiveAPs(pObject) > 0 )
|
|
{
|
|
fCapable = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return( fCapable );
|
|
}
|
|
|
|
// Changed by ADB, rev 1513
|
|
//BOOLEAN IsGunBurstCapable( SOLDIERTYPE *pSoldier, UINT8 ubHandPos , BOOLEAN fNotify )
|
|
BOOLEAN IsGunBurstCapable(OBJECTTYPE* pObject, BOOLEAN fNotify, SOLDIERTYPE* pSoldier )
|
|
{
|
|
BOOLEAN fCapable = FALSE;
|
|
|
|
//if ( pSoldier->inv[ ubHandPos ].exists() == true )
|
|
if ( pObject->exists() == true )
|
|
{
|
|
// ATE: Check for being a weapon....
|
|
//if ( Item[ pSoldier->inv[ ubHandPos ].usItem ].usItemClass & IC_WEAPON )
|
|
if ( Item[ pObject->usItem ].usItemClass & IC_WEAPON )
|
|
{
|
|
//if ( GetShotsPerBurst(&pSoldier->inv[ ubHandPos ]) > 0 )
|
|
if ( GetShotsPerBurst(pObject) > 0 )
|
|
{
|
|
fCapable = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//ADB fNotify is always false, so notify and pSoldier are not necessary parameters
|
|
if ( fNotify && !fCapable && pSoldier != NULL )
|
|
{
|
|
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, Message[ STR_NOT_BURST_CAPABLE ], pSoldier->name );
|
|
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, Message[ STR_NOT_BURST_CAPABLE ], pSoldier->name );
|
|
}
|
|
|
|
return( fCapable );
|
|
}
|
|
|
|
void HandleTacticalEffectsOfEquipmentChange( SOLDIERTYPE *pSoldier, UINT32 uiInvPos, UINT16 usOldItem, UINT16 usNewItem )
|
|
{
|
|
#ifdef ADB_TODO
|
|
SetBurstAndAutoFireMode(pSoldier, GetWeaponMode(&pSoldier->inv[uiInvPos]));
|
|
#endif
|
|
// if in attached weapon mode and don't have weapon with GL attached in hand, reset weapon mode
|
|
if ( (pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )&& !IsGrenadeLauncherAttached( &(pSoldier->inv[ HANDPOS ]) ) )
|
|
{
|
|
if ( !Weapon[pSoldier->inv[ HANDPOS ].usItem].NoSemiAuto )
|
|
{
|
|
pSoldier->bWeaponMode = WM_NORMAL;
|
|
pSoldier->bDoBurst = FALSE;
|
|
pSoldier->bDoAutofire = 0;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bWeaponMode = WM_AUTOFIRE;
|
|
pSoldier->bDoBurst = TRUE;
|
|
pSoldier->bDoAutofire = 1;
|
|
}
|
|
}
|
|
|
|
// if he is loaded tactically
|
|
if ( pSoldier->bInSector )
|
|
{
|
|
// If this is our main hand
|
|
if ( uiInvPos == HANDPOS || uiInvPos == SECONDHANDPOS )
|
|
{
|
|
// check if we need to change animation!
|
|
pSoldier->ReLoadSoldierAnimationDueToHandItemChange( usOldItem, usNewItem );
|
|
}
|
|
|
|
// if this is head gear
|
|
if ( uiInvPos == HEAD1POS || uiInvPos == HEAD2POS )
|
|
{
|
|
// Could be because of GOGGLES change... Re-create light...
|
|
pSoldier->DeleteSoldierLight( );
|
|
pSoldier->PositionSoldierLight( );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// as a minimum
|
|
if ( (Item[ pSoldier->inv[ HANDPOS ].usItem ].usItemClass & IC_WEAPON) && GetShotsPerBurst(&pSoldier->inv[ HANDPOS ]) == 0 )
|
|
{
|
|
if ( !Weapon[pSoldier->inv[ HANDPOS ].usItem].NoSemiAuto )
|
|
{
|
|
pSoldier->bWeaponMode = WM_NORMAL;
|
|
pSoldier->bDoBurst = FALSE;
|
|
pSoldier->bDoAutofire = 0;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bWeaponMode = WM_AUTOFIRE;
|
|
pSoldier->bDoAutofire = 1;
|
|
pSoldier->bDoBurst = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
INT32 CalcMaxTossRange( SOLDIERTYPE * pSoldier, UINT16 usItem, BOOLEAN fArmed )
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcmaxtossrange");
|
|
INT32 iRange;
|
|
UINT16 usSubItem;
|
|
|
|
if ( EXPLOSIVE_GUN( usItem ) )
|
|
{
|
|
// oops! return value in weapons table
|
|
return( Weapon[ usItem ].usRange / CELL_X_SIZE );
|
|
}
|
|
|
|
// if item's fired mechanically
|
|
// ATE: If we are sent in a LAUNCHABLE, get the LAUCNHER, and sub ONLY if we are armed...
|
|
usSubItem = GetLauncherFromLaunchable( usItem );
|
|
|
|
if ( fArmed && usSubItem != NOTHING )
|
|
{
|
|
usItem = usSubItem;
|
|
}
|
|
|
|
if ( Item[ usItem ].usItemClass == IC_LAUNCHER && fArmed )
|
|
{
|
|
// this function returns range in tiles so, stupidly, we have to divide by 10 here
|
|
iRange = Weapon[usItem].usRange / CELL_X_SIZE;
|
|
}
|
|
else
|
|
{
|
|
// if ( Item[ usItem ].fFlags & ITEM_UNAERODYNAMIC )
|
|
if ( Item[ usItem ].unaerodynamic )
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcmaxtossrange: not aerodynamic");
|
|
iRange = 1;
|
|
}
|
|
else if ( Item[ usItem ].usItemClass == IC_GRENADE )
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcmaxtossrange: grenade");
|
|
// start with the range based on the soldier's strength and the item's weight
|
|
// Altered by Digicrab on 14 March, 2004
|
|
// Reversed a Ja2Gold change that made grenades of weight 3 or more have the same throw distance as those of weight 3.
|
|
INT32 iThrowingStrength = ( EffectiveStrength( pSoldier ) * 2 + 100 ) / 3;
|
|
iRange = 2 + ( iThrowingStrength / (3 + (Item[usItem].ubWeight) / 3 ));
|
|
}
|
|
else
|
|
{ // not as aerodynamic!
|
|
// start with the range based on the soldier's strength and the item's weight
|
|
iRange = 2 + ( ( EffectiveStrength( pSoldier ) / ( 5 + Item[usItem].ubWeight) ) );
|
|
}
|
|
|
|
// adjust for thrower's remaining breath (lose up to 1/2 of range)
|
|
iRange -= (iRange * (100 - pSoldier->bBreath)) / 200;
|
|
|
|
if ( HAS_SKILL_TRAIT( pSoldier, THROWING ) )
|
|
{
|
|
// better max range due to expertise
|
|
iRange = iRange * (100 + gbSkillTraitBonus[THROWING] * NUM_SKILL_TRAITS( pSoldier, THROWING ) ) / 100;
|
|
}
|
|
|
|
// Adjust for thrower's stance
|
|
if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight < ANIM_STAND)
|
|
{
|
|
// For now we just assume the thrower is crouched since we don't allow prone tossing at the moment
|
|
// So dock 30% from the distance
|
|
iRange = (iRange * 70) / 100;
|
|
}
|
|
}
|
|
|
|
if (iRange < 1)
|
|
{
|
|
iRange = 1;
|
|
}
|
|
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcmaxtossrange done");
|
|
return( iRange );
|
|
}
|
|
|
|
|
|
UINT32 CalcThrownChanceToHit(SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubAimTime, UINT8 ubAimPos )
|
|
{
|
|
INT32 iChance, iMaxRange, iRange;
|
|
UINT16 usHandItem;
|
|
INT8 bPenalty, bBandaged;
|
|
|
|
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
|
|
{
|
|
usHandItem = GetAttachedGrenadeLauncher (&pSoldier->inv[HANDPOS]);// UNDER_GLAUNCHER;
|
|
}
|
|
else
|
|
{
|
|
usHandItem = pSoldier->inv[HANDPOS].usItem;
|
|
}
|
|
|
|
/*
|
|
// CJC: Grenade Launchers don't fire in a straight line!
|
|
#ifdef BETAVERSION
|
|
if (usHandItem == GLAUNCHER)
|
|
{
|
|
PopMessage("CalcThrownChanceToHit: DOESN'T WORK ON GLAUNCHERs!");
|
|
return(0);
|
|
}
|
|
#endif
|
|
*/
|
|
|
|
if ( Item[ usHandItem ].usItemClass != IC_LAUNCHER && pSoldier->bWeaponMode != WM_ATTACHED_GL && pSoldier->bWeaponMode != WM_ATTACHED_GL_BURST && pSoldier->bWeaponMode != WM_ATTACHED_GL_AUTO )
|
|
{
|
|
// PHYSICALLY THROWN arced projectile (ie. grenade)
|
|
// for lack of anything better, base throwing accuracy on dex & marskmanship
|
|
iChance = ( EffectiveDexterity( pSoldier ) + EffectiveMarksmanship( pSoldier ) ) / 2;
|
|
// throwing trait helps out
|
|
if ( HAS_SKILL_TRAIT( pSoldier, THROWING ) )
|
|
{
|
|
iChance += gbSkillTraitBonus[THROWING] * NUM_SKILL_TRAITS( pSoldier, THROWING );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// MECHANICALLY FIRED arced projectile (ie. mortar), need brains & know-how
|
|
iChance = ( EffectiveDexterity( pSoldier ) + EffectiveMarksmanship( pSoldier ) + EffectiveWisdom( pSoldier ) + pSoldier->stats.bExpLevel ) / 4;
|
|
|
|
// heavy weapons trait helps out
|
|
if (HAS_SKILL_TRAIT( pSoldier, HEAVY_WEAPS ))
|
|
{
|
|
iChance += gbSkillTraitBonus[HEAVY_WEAPS] * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPS );
|
|
}
|
|
}
|
|
|
|
if ( pSoldier->bDoBurst )
|
|
{
|
|
int iPenalty = ( Weapon[usHandItem].ubBurstPenalty
|
|
- GetBurstToHitBonus(&pSoldier->inv[HANDPOS], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE) )
|
|
* (pSoldier->bDoBurst - 1);
|
|
|
|
// halve the penalty for people with the autofire trait
|
|
if ( HAS_SKILL_TRAIT( pSoldier, AUTO_WEAPS ) )
|
|
{
|
|
iPenalty /= 2 * NUM_SKILL_TRAITS( pSoldier, AUTO_WEAPS );
|
|
}
|
|
iChance -= iPenalty;
|
|
}
|
|
|
|
// modify based on morale
|
|
iChance += GetMoraleModifier( pSoldier );
|
|
|
|
// modify by fatigue
|
|
iChance -= GetSkillCheckPenaltyForFatigue( pSoldier, iChance );
|
|
|
|
// if shooting same target from same position as the last shot
|
|
if (sGridNo == pSoldier->sLastTarget)
|
|
{
|
|
iChance += AIM_BONUS_SAME_TARGET; // give a bonus to hit
|
|
}
|
|
|
|
// ADJUST FOR EXTRA AIMING TIME
|
|
if (ubAimTime)
|
|
{
|
|
iChance += (AIM_BONUS_PER_AP * ubAimTime); // bonus for every pt of aiming
|
|
}
|
|
|
|
/*
|
|
if (!pSoldier->human) // if this is a computer AI controlled enemy
|
|
{
|
|
iChance += Diff[DIFF_ENEMY_TO_HIT_MOD][GameOption[ENEMYDIFFICULTY]];
|
|
}
|
|
*/
|
|
|
|
// if shooter is being affected by gas
|
|
if ( pSoldier->flags.uiStatusFlags & SOLDIER_GASSED )
|
|
{
|
|
iChance -= AIM_PENALTY_GASSED;
|
|
}
|
|
|
|
// if shooter is being bandaged at the same time, his concentration is off
|
|
if (pSoldier->ubServiceCount > 0)
|
|
{
|
|
iChance -= AIM_PENALTY_GETTINGAID;
|
|
}
|
|
|
|
// if shooter is still in shock
|
|
if (pSoldier->aiData.bShock)
|
|
{
|
|
iChance -= (pSoldier->aiData.bShock * AIM_PENALTY_PER_SHOCK);
|
|
}
|
|
|
|
// calculate actual range (in world units)
|
|
iRange = (INT16)GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo );
|
|
|
|
//NumMessage("ACTUAL RANGE = ",range);
|
|
|
|
// Snap: Use tohitbonus or aimbonus instead
|
|
/*if ( GetDayVisionRangeBonus (pSoldier) >0) // pSoldier->inv[HEAD1POS].usItem == SUNGOGGLES || pSoldier->inv[HEAD2POS].usItem == SUNGOGGLES)
|
|
{
|
|
// decrease effective range by 10% when using sungoggles (w or w/o scope)
|
|
iRange -= iRange / 10; //basically, +1% to hit per every 2 squares
|
|
}*/
|
|
|
|
//NumMessage("EFFECTIVE RANGE = ",range);
|
|
|
|
// ADJUST FOR RANGE
|
|
|
|
if ( Item[usHandItem].mortar && iRange < MIN_MORTAR_RANGE)
|
|
{
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
iMaxRange = CalcMaxTossRange( pSoldier, usHandItem , TRUE ) * CELL_X_SIZE;
|
|
|
|
//NumMessage("MAX RANGE = ",maxRange);
|
|
|
|
// bonus if range is less than 1/2 maximum range, penalty if it's more
|
|
|
|
// bonus is 50% at range 0, -50% at maximum range
|
|
|
|
iChance += 50 * 2 * ( (iMaxRange / 2) - iRange ) / iMaxRange;
|
|
//iChance += ((iMaxRange / 2) - iRange); // increments of 1% per pixel
|
|
|
|
// IF TARGET IS BEYOND MAXIMUM THROWING RANGE
|
|
if (iRange > iMaxRange)
|
|
{
|
|
// the object CAN travel that far if not blocked, but it's NOT accurate!
|
|
iChance /= 2;
|
|
}
|
|
}
|
|
|
|
// IF CHANCE EXISTS, BUT ATTACKER IS INJURED
|
|
if ((iChance > 0) && (pSoldier->stats.bLife < pSoldier->stats.bLifeMax))
|
|
{
|
|
// if bandaged, give 1/2 of the bandaged life points back into equation
|
|
bBandaged = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding;
|
|
|
|
// injury penalty is based on % damage taken (max 2/3rds iChance)
|
|
bPenalty = (2 * iChance * (pSoldier->stats.bLifeMax - pSoldier->stats.bLife + (bBandaged / 2))) /
|
|
(3 * pSoldier->stats.bLifeMax);
|
|
|
|
// for mechanically-fired projectiles, reduce penalty in half
|
|
if ( Item[ usHandItem ].usItemClass == IC_LAUNCHER )
|
|
{
|
|
bPenalty /= 2;
|
|
}
|
|
|
|
// reduce injury penalty due to merc's experience level (he can take it!)
|
|
iChance -= (bPenalty * (100 - (10 * ( EffectiveExpLevel( pSoldier ) - 1)))) / 100;
|
|
}
|
|
|
|
// IF CHANCE EXISTS, BUT ATTACKER IS LOW ON BREATH
|
|
if ((iChance > 0) && (pSoldier->bBreath < 100))
|
|
{
|
|
// breath penalty is based on % breath missing (max 1/2 iChance)
|
|
bPenalty = (iChance * (100 - pSoldier->bBreath)) / 200;
|
|
|
|
// for mechanically-fired projectiles, reduce penalty in half
|
|
if ( Item[ usHandItem ].usItemClass == IC_LAUNCHER )
|
|
bPenalty /= 2;
|
|
|
|
// reduce breath penalty due to merc's dexterity (he can compensate!)
|
|
iChance -= (bPenalty * (100 - ( EffectiveDexterity( pSoldier ) - 10))) / 100;
|
|
}
|
|
|
|
// if iChance exists, but it's a mechanical item being used
|
|
if ((iChance > 0) && (Item[ usHandItem ].usItemClass == IC_LAUNCHER ))
|
|
// reduce iChance to hit DIRECTLY by the item's working condition
|
|
iChance = (iChance * WEAPON_STATUS_MOD(pSoldier->inv[HANDPOS][0]->data.objectStatus)) / 100;
|
|
|
|
// MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS
|
|
if (iChance < MINCHANCETOHIT)
|
|
iChance = MINCHANCETOHIT;
|
|
else
|
|
{
|
|
if (iChance > MAXCHANCETOHIT)
|
|
iChance = MAXCHANCETOHIT;
|
|
}
|
|
|
|
|
|
//NumMessage("ThrownChanceToHit = ",iChance);
|
|
return (iChance);
|
|
}
|
|
|
|
|
|
void ChangeWeaponMode( SOLDIERTYPE * pSoldier )
|
|
{
|
|
// ATE: Don't do this if in a fire amimation.....
|
|
if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIRE )
|
|
{
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
pSoldier->bWeaponMode++;
|
|
if(pSoldier->bWeaponMode == NUM_WEAPON_MODES)
|
|
{
|
|
if ( Weapon[pSoldier->inv[HANDPOS].usItem].NoSemiAuto )
|
|
pSoldier->bWeaponMode = WM_AUTOFIRE;
|
|
else
|
|
pSoldier->bWeaponMode = WM_NORMAL;
|
|
}
|
|
}
|
|
// Changed by ADB, rev 1513
|
|
//while(IsGunWeaponModeCapable( pSoldier, HANDPOS, pSoldier->bWeaponMode ) == FALSE && pSoldier->bWeaponMode != WM_NORMAL);
|
|
while(IsGunWeaponModeCapable( &pSoldier->inv[HANDPOS], static_cast<WeaponMode>(pSoldier->bWeaponMode), pSoldier ) == FALSE && pSoldier->bWeaponMode != WM_NORMAL);
|
|
|
|
if (pSoldier->bWeaponMode == WM_AUTOFIRE || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
|
|
{
|
|
pSoldier->bDoAutofire = 1;
|
|
pSoldier->bDoBurst = 1;
|
|
}
|
|
else if(pSoldier->bWeaponMode == WM_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST )
|
|
{
|
|
pSoldier->bDoAutofire = 0;
|
|
pSoldier->bDoBurst = 1;
|
|
}
|
|
else
|
|
{
|
|
pSoldier->bDoBurst = 0;
|
|
pSoldier->bDoAutofire = 0;
|
|
}
|
|
|
|
// Changed by ADB, rev 1513
|
|
//pSoldier->flags.fDoSpread = 0;
|
|
|
|
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
|
|
gfUIForceReExamineCursorData = TRUE;
|
|
}
|
|
|
|
void DishoutQueenSwipeDamage( SOLDIERTYPE *pQueenSoldier )
|
|
{
|
|
INT8 bValidDishoutDirs[3][3] = { NORTH, NORTHEAST, -1,
|
|
EAST, SOUTHEAST, -1,
|
|
SOUTH, -1, -1 };
|
|
|
|
UINT32 cnt, cnt2;
|
|
SOLDIERTYPE *pSoldier;
|
|
INT8 bDir;
|
|
INT32 iChance;
|
|
INT32 iImpact;
|
|
INT32 iHitBy;
|
|
|
|
// Loop through all mercs and make go
|
|
for ( cnt = 0; cnt < guiNumMercSlots; cnt++ )
|
|
{
|
|
pSoldier = MercSlots[ cnt ];
|
|
|
|
if (pSoldier != NULL )
|
|
{
|
|
if ( pSoldier->ubID != pQueenSoldier->ubID )
|
|
{
|
|
// ATE: Ok, lets check for some basic things here!
|
|
if ( pSoldier->stats.bLife >= OKLIFE && pSoldier->sGridNo != NOWHERE && pSoldier->bActive && pSoldier->bInSector )
|
|
{
|
|
// Get Pyth spaces away....
|
|
if ( GetRangeInCellCoordsFromGridNoDiff( pQueenSoldier->sGridNo, pSoldier->sGridNo ) <= Weapon[ CREATURE_QUEEN_TENTACLES].usRange )
|
|
{
|
|
// get direction
|
|
bDir = (INT8)GetDirectionFromGridNo( pSoldier->sGridNo, pQueenSoldier );
|
|
|
|
//
|
|
for ( cnt2 = 0; cnt2 < 2; cnt2++ )
|
|
{
|
|
if ( bValidDishoutDirs[ pQueenSoldier->aiData.uiPendingActionData1 ][ cnt2 ] == bDir )
|
|
{
|
|
iChance = CalcChanceToStab( pQueenSoldier, pSoldier, 0 );
|
|
|
|
// CC: Look here for chance to hit, damage, etc...
|
|
// May want to not hit if target is prone, etc....
|
|
iHitBy = iChance - (INT32) PreRandom( 100 );
|
|
if ( iHitBy > 0 )
|
|
{
|
|
// Hit!
|
|
iImpact = HTHImpact( pQueenSoldier, pSoldier, iHitBy, TRUE );
|
|
pSoldier->EVENT_SoldierGotHit( CREATURE_QUEEN_TENTACLES, (INT16) iImpact, (INT16) iImpact, gOppositeDirection[ bDir ], 50, pQueenSoldier->ubID, 0, ANIM_CROUCH, 0, 0 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pQueenSoldier->aiData.uiPendingActionData1++;
|
|
}
|
|
|
|
|
|
BOOLEAN WillExplosiveWeaponFail( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj )
|
|
{
|
|
if ( pSoldier->bTeam == gbPlayerNum || pSoldier->bVisible == 1 )
|
|
{
|
|
if ( (INT8)(PreRandom( 40 ) + PreRandom( 40 ) ) > (*pObj)[0]->data.objectStatus )
|
|
{
|
|
// Do second dice roll
|
|
if ( PreRandom( 2 ) == 1 )
|
|
{
|
|
// Fail
|
|
return( TRUE );
|
|
}
|
|
}
|
|
}
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
BOOLEAN IsWeapon ( UINT16 itemIndex )
|
|
{
|
|
UINT32 usItemClass = Item[itemIndex].usItemClass;
|
|
return (usItemClass == IC_GUN || usItemClass == IC_PUNCH || usItemClass == IC_BLADE ||
|
|
usItemClass == IC_LAUNCHER || usItemClass == IC_THROWING_KNIFE || usItemClass == IC_THROWN ||
|
|
usItemClass == IC_GRENADE || usItemClass == IC_TENTACLES);
|
|
}
|
|
|
|
UINT8 GetDamage ( OBJECTTYPE *pObj )
|
|
{
|
|
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetDamage"));
|
|
|
|
if ( Item[pObj->usItem].usItemClass == IC_BLADE || Item[pObj->usItem].usItemClass == IC_PUNCH || Item[pObj->usItem].usItemClass == IC_TENTACLES )
|
|
{
|
|
UINT8 ubDamage = Weapon[ pObj->usItem ].ubImpact + GetMeleeDamageBonus(pObj);
|
|
return min(255, (UINT8)( (ubDamage) + ( (double)ubDamage / 100) * gGameExternalOptions.ubMeleeDamageMultiplier ) );
|
|
}
|
|
else
|
|
{
|
|
UINT8 ubDamage = Weapon[ pObj->usItem ].ubImpact;
|
|
if (FitsInSmallPocket(pObj) == true)
|
|
{
|
|
ubDamage += GetDamageBonus(pObj);
|
|
}
|
|
return min(255, (UINT8)( (ubDamage) + ( (double)ubDamage / 100) * gGameExternalOptions.ubGunDamageMultiplier ) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UINT8 GetShotsPerBurst( OBJECTTYPE *pObj )
|
|
{
|
|
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetShotsPerBurst"));
|
|
|
|
return Weapon[ pObj->usItem ].ubShotsPerBurst + GetBurstSizeBonus(pObj);
|
|
|
|
}
|
|
UINT8 GetBurstPenalty( OBJECTTYPE *pObj, BOOLEAN fProneStance )
|
|
{
|
|
// Snap: Make sure burst bonus does not exceed burst penalty!
|
|
INT16 bns = GetBurstToHitBonus(pObj, fProneStance);
|
|
|
|
if ( bns > Weapon[ pObj->usItem ].ubBurstPenalty ) {
|
|
return 0;
|
|
}
|
|
|
|
return Weapon[ pObj->usItem ].ubBurstPenalty - bns;
|
|
}
|
|
UINT8 GetAutoPenalty( OBJECTTYPE *pObj, BOOLEAN fProneStance )
|
|
{
|
|
// Snap: Make sure burst bonus does not exceed burst penalty!
|
|
INT16 bns = GetAutoToHitBonus(pObj, fProneStance);
|
|
|
|
if ( bns > Weapon[ pObj->usItem ].AutoPenalty ) {
|
|
return 0;
|
|
}
|
|
|
|
return Weapon[ pObj->usItem ].AutoPenalty - bns;
|
|
}
|
|
UINT8 GetAutofireShotsPerFiveAPs( OBJECTTYPE *pObj )
|
|
{
|
|
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetAutofireShotsPerFiveAPs"));
|
|
|
|
return Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP;
|
|
|
|
}
|
|
UINT16 GetMagSize( OBJECTTYPE *pObj )
|
|
{
|
|
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetMagSize"));
|
|
|
|
return Weapon[ pObj->usItem ].ubMagSize + GetMagSizeBonus(pObj);
|
|
|
|
}
|
|
bool WeaponReady(SOLDIERTYPE * pSoldier)
|
|
{
|
|
#ifdef ROBOT_ALWAYS_READY
|
|
if ( AM_A_ROBOT( pSoldier) )
|
|
return true;
|
|
#endif
|
|
if ( (gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIREREADY ) ||
|
|
(gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIRE ) )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
INT8 GetAPsToReload( OBJECTTYPE *pObj )
|
|
{
|
|
// DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetAPsToReload"));
|
|
|
|
return ( Weapon[ pObj->usItem ].APsToReload *
|
|
( 100 - GetPercentReloadTimeAPReduction(pObj) ) ) / 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|