#ifdef PRECOMPILEDHEADERS #include "Tactical All.h" #else #include "sgp.h" #include "overhead types.h" #include "Sound Control.h" #include "Soldier 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 //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,"\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\r\n"); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].uiIndex); FilePrintf(hFile,"\t\t"); 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,"\r\n"); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubWeaponClass); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubWeaponType); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubCalibre); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubReadyTime); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubShotsPer4Turns); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubShotsPerBurst); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubBurstPenalty); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubBulletSpeed); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubImpact); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubDeadliness); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].bAccuracy); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubMagSize); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].usRange); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].usReloadDelay); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubAttackVolume); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubHitVolume); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sBurstSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sSilencedBurstSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sReloadSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sLocknLoadSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].bBurstAP); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].bAutofireShotsPerFiveAP); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].APsToReloadManually); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ManualReloadSound); FilePrintf(hFile,"\t\r\n"); } FilePrintf(hFile,"\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,"\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\r\n"); FilePrintf(hFile,"\t\t%d\r\n", cnt);//Weapon[cnt].uiIndex); FilePrintf(hFile,"\t\t"); 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,"\r\n"); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubWeaponClass); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubWeaponType); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubCalibre); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubReadyTime); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubShotsPer4Turns); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubShotsPerBurst); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubBurstPenalty); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubBulletSpeed); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubImpact); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubDeadliness); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].bAccuracy); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubMagSize); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].usRange); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].usReloadDelay); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sAniDelay); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubAttackVolume); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].ubHitVolume); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sBurstSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sSilencedBurstSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sReloadSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].sLocknLoadSound); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].silencedSound ); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].bBurstAP); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].bAutofireShotsPerFiveAP); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].APsToReload); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].swapClips ); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].maxdistformessydeath); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].AutoPenalty); FilePrintf(hFile,"\t\t%d\r\n", Weapon[cnt].NoSemiAuto); FilePrintf(hFile,"\t\r\n"); } FilePrintf(hFile,"\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->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; INT8 bPlate; if (pObj == NULL || Item[pObj->usItem].usItemClass != IC_ARMOUR) { return( 0 ); } iValue = Armour[ Item[pObj->usItem].ubClassIndex ].ubProtection; iValue = iValue * pObj->bStatus[0] * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000; // bPlate = FindAttachment( pObj, CERAMIC_PLATES ); bPlate = FindFirstArmourAttachment( pObj ); if ( bPlate != ITEM_NOT_FOUND ) { INT32 iValue2; iValue2 = Armour[ Item[ pObj->usAttachItem[bPlate] ].ubClassIndex ].ubProtection; iValue2 = iValue2 * pObj->bAttachStatus[ bPlate ] * Armour[ Item[ pObj->usAttachItem[bPlate] ].ubClassIndex ].ubCoverage / 10000; iValue += iValue2; } return( max(iValue,1) ); } INT32 ArmourPercent( SOLDIERTYPE * pSoldier ) { INT32 iVest, iHelmet, iLeg; INT32 iDivideValue; if (pSoldier->inv[VESTPOS].usItem) { 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].usItem) { 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].usItem) { 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; INT8 bPlate; if (pObj == NULL || Item[pObj->usItem].usItemClass != IC_ARMOUR) { return( 0 ); } iValue = Armour[ Item[pObj->usItem].ubClassIndex ].ubProtection; iValue = iValue * pObj->bStatus[0] * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000; if ( Item[pObj->usItem].flakjacket ) { // increase value for flak jackets! iValue *= 3; } bPlate = FindFirstArmourAttachment( pObj ); if ( bPlate != ITEM_NOT_FOUND ) { INT32 iValue2; iValue2 = Armour[ Item[ pObj->usAttachItem[bPlate] ].ubClassIndex ].ubProtection; iValue2 = iValue2 * pObj->bAttachStatus[ bPlate ] * Armour[ Item[ pObj->usAttachItem[bPlate] ].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].usItem) { 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].usItem) { 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].usItem) { 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->uiStatusFlags & SOLDIER_PC) { if ( Item[pSoldier->usAttackingWeapon].usItemClass == IC_GUN && !EXPLOSIVE_GUN( pSoldier->usAttackingWeapon ) ) { pObj = &(pSoldier->inv[pSoldier->ubAttackingHand]); if (pObj->bGunAmmoStatus > 0) { // gun might jam, figure out the chance //iChance = (80 - pObj->bGunStatus); //rain iChance = (80 - pObj->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->bGunAmmoStatus *= -1; // Deduct AMMO! DeductAmmo( pSoldier, pSoldier->ubAttackingHand ); TacticalCharacterDialogue( pSoldier, QUOTE_JAMMED_GUN ); return( TRUE ); } } else if (pObj->bGunAmmoStatus < 0) { // try to unjam gun iResult = SkillCheck( pSoldier, UNJAM_GUN_CHECK, (INT8) ((Item[pObj->usItem].bReliability + Item[pObj->usGunAmmoItem].bReliability)* 4) ); if (iResult > 0) { // yay! unjammed the gun pObj->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->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 ; UINT16 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->fDoSpread && !pSoldier->bDoBurst ) { pSoldier->fDoSpread = FALSE; } if ( pSoldier->fDoSpread >= MAX_BURST_SPREAD_TARGETS ) { //If we have more than MAX_BURST_SPREAD_TARGETS bullets, loop over and start again from the first target location pSoldier->fDoSpread = 1; } if ( pSoldier->fDoSpread ) { if ( pSoldier->sSpreadLocations[ pSoldier->fDoSpread - 1 ] != 0 ) { UseGun( pSoldier, pSoldier->sSpreadLocations[ pSoldier->fDoSpread - 1 ] ); } else { UseGun( pSoldier, sTargetGridNo ); } pSoldier->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->fDoSpread && !pSoldier->bDoBurst ) { pSoldier->fDoSpread = FALSE; } if ( pSoldier->fDoSpread >= MAX_BURST_SPREAD_TARGETS ) { //If we have more than MAX_BURST_SPREAD_TARGETS bullets, loop over and start again from the first target location pSoldier->fDoSpread = 1; } if ( pSoldier->fDoSpread ) { if ( pSoldier->sSpreadLocations[ pSoldier->fDoSpread - 1 ] != 0 ) { UseLauncher( pSoldier, pSoldier->sSpreadLocations[ pSoldier->fDoSpread - 1 ] ); } else { UseLauncher( pSoldier, sTargetGridNo ); } pSoldier->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->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 = (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 ( IsValidSecondHandShot( pSoldier ) && pSoldier->inv[ HANDPOS ].bGunStatus >= USABLE && pSoldier->inv[HANDPOS].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->bAimTime, pSoldier->bAimShotLocation ); } else { uiHitChance = CalcChanceToHitGun( pSoldier, sTargetGridNo, pSoldier->bAimTime, pSoldier->bAimShotLocation ); } //DIGICRAB: Barrel extender wear code // Relocated from CalcChanceToHitGun if ( Item[ usItemNum ].usItemClass == IC_GUN ) { INT8 bAttachPos; OBJECTTYPE * pInHand; pInHand = &(pSoldier->inv[pSoldier->ubAttackingHand]); UINT32 item; //item = FindRangeBonusAttachment(pInHand); //bAttachPos = FindAttachment( pInHand, item ); // lalien: search for barrel extender not for any item with range bonus. (else barrel extender will fall off even when none is attached) bAttachPos = FindAttachment( pInHand, GUN_BARREL_EXTENDER ); if ( bAttachPos != ITEM_NOT_FOUND ) { item = Item[pInHand->usAttachItem[bAttachPos]].uiIndex; // reduce status and see if it falls off pInHand->bAttachStatus[ bAttachPos ] -= (INT8) Random( 2 ); if ( pInHand->bAttachStatus[ bAttachPos ] - Random( 35 ) - Random( 35 ) < USABLE ) { // barrel extender falls off! OBJECTTYPE Temp; // since barrel extenders are not removable we cannot call RemoveAttachment here // and must create the item by hand CreateItem( item, pInHand->bAttachStatus[ bAttachPos ], &Temp ); pInHand->usAttachItem[ bAttachPos ] = NOTHING; pInHand->bAttachStatus[ bAttachPos ] = 0; // drop it to ground AddItemToPool( pSoldier->sGridNo, &Temp, 1, pSoldier->bLevel, 0, -1 ); // big penalty to hit if(uiHitChance < 30) uiHitChance = MINCHANCETOHIT; else uiHitChance -= 30; // curse! if ( pSoldier->bTeam == OUR_TEAM ) { DoMercBattleSound( pSoldier, 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->uiStatusFlags & SOLDIER_ENEMY ) ) { pSoldier->sReloadDelay = ( pSoldier->sReloadDelay * 2 ); } // slow down demo mode! if ( gTacticalStatus.uiFlags & DEMOMODE ) { pSoldier->sReloadDelay *= 2; } //pSoldier->fReloading = TRUE; //RESETTIMECOUNTER( pSoldier->ReloadCounter, pSoldier->sReloadDelay ); } // Deduct AMMO! DeductAmmo( pSoldier, pSoldier->ubAttackingHand ); // ATE: Check if we should say quote... if ( pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunShotsLeft == 0 && !Item[pSoldier->usAttackingWeapon].rocketlauncher ) { if ( pSoldier->bTeam == gbPlayerNum ) { pSoldier->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->bAimTime && !pSoldier->bDoBurst ) { // gain extra exp for aiming, up to the amount from // the difficulty of the shot usExpGain += __min( pSoldier->bAimTime, usExpGain ); } // base pts extra for hitting usExpGain += 3; } // add base pts for taking a shot, whether it hits or misses usExpGain += 3; if ( IsValidSecondHandShot( pSoldier ) && pSoldier->inv[ HANDPOS ].bGunStatus >= USABLE && pSoldier->inv[HANDPOS].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 ]->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->fMuzzleFlash = FALSE; else pSoldier->fMuzzleFlash = TRUE; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("UseGun: Muzzle flash = %d",pSoldier->fMuzzleFlash)); if ( AmmoTypes[pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunAmmoType].numberOfBullets > 1 ) fBuckshot = TRUE; //switch ( pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunAmmoType ) //{ // case AMMO_BUCKSHOT: // fBuckshot = TRUE; // break; // case AMMO_SLEEP_DART: // pSoldier->fMuzzleFlash = FALSE; // break; // default: // break; //} } } else // throwing knife { fBuckshot = FALSE; pSoldier->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->bAimTime) { // gain extra exp for aiming, up to the amount from // the difficulty of the throw usExpGain += ( 2 * __min( pSoldier->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 ]->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 ].bStatus[ 0 ],&(pSoldier->inv[ HANDPOS ] ) ); DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 ); IgniteExplosion( pSoldier->ubID, (INT16)CenterX( pSoldier->sGridNo ), (INT16)CenterY( pSoldier->sGridNo ), 0, pSoldier->sGridNo, C1, pSoldier->bLevel ); } else { DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("StructureHit: RPG7 item: %d, Ammo: %d",pSoldier->inv[HANDPOS].usItem , pSoldier->inv[HANDPOS].usGunAmmoItem ) ); IgniteExplosion( pSoldier->ubID, (INT16)CenterX( pSoldier->sGridNo ), (INT16)CenterY( pSoldier->sGridNo ), 0, pSoldier->sGridNo, pSoldier->inv[pSoldier->ubAttackingHand ].usGunAmmoItem, pSoldier->bLevel ); pSoldier->inv[pSoldier->ubAttackingHand ].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 RemoveObjs( &(pSoldier->inv[ HANDPOS ] ), 1 ); DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 ); } else if ( Item[usItemNum].rocketlauncher ) { if ( Item[usItemNum].singleshotrocketlauncher ) { CreateItem( Item[usItemNum].discardedlauncheritem, pSoldier->inv[ HANDPOS ].bStatus[ 0 ], &(pSoldier->inv[ HANDPOS ] ) ); DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 ); } // Direction to center of explosion ubDirection = gOppositeDirection[ pSoldier->bDirection ]; sNewGridNo = NewGridNo( (UINT16)pSoldier->sGridNo, (UINT16)(1 * DirectionInc( ubDirection ) ) ); // Check if a person exists here and is not prone.... ubMerc = WhoIsThere2( sNewGridNo, pSoldier->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" ); EVENT_SoldierGotHit( MercPtrs[ ubMerc ], MINI_GRENADE, 10, 200, pSoldier->bDirection, 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->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->usGunAmmoItem].bReliability; } uiDepreciateTest = BASIC_DEPRECIATE_CHANCE + 3 * (Item[ usItemNum ].bReliability + ammoReliability); if ( !PreRandom( uiDepreciateTest ) && ( pSoldier->inv[ pSoldier->ubAttackingHand ].bStatus[0] > 1) ) { pSoldier->inv[ pSoldier->ubAttackingHand ].bStatus[ 0 ]--; } // reduce monster smell (gunpowder smell) if ( pSoldier->bMonsterSmell > 0 && Random( 2 ) == 0 ) { pSoldier->bMonsterSmell--; } // manual recharge if (Weapon[Item[usItemNum].ubClassIndex].APsToReloadManually > 0) pSoldier->inv[ pSoldier->ubAttackingHand ].ubGunState &= ~GS_CARTRIDGE_IN_CHAMBER; // 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->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->uiStatusFlags |= SOLDIER_ATTACK_NOTICED; pTargetSoldier->fIntendedTarget = TRUE; // SAVE OPP ID pSoldier->ubOppNum = pTargetSoldier->ubID; // CHECK IF BUDDY KNOWS ABOUT US if ( pTargetSoldier->bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN || pTargetSoldier->bLife < OKLIFE || pTargetSoldier->bCollapsed ) { iHitChance = 100; fSurpriseAttack = TRUE; } else { iHitChance = CalcChanceToStab( pSoldier, pTargetSoldier, pSoldier->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].bStatus[0]) ) / 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 ].bStatus[ 0 ] > USABLE ) { bMaxDrop = (iImpact / 20); // the duller they get, the slower they get any worse... bMaxDrop = __min( bMaxDrop, pSoldier->inv[ pSoldier->ubAttackingHand ].bStatus[ 0 ] / 10 ); // as long as its still > USABLE, it drops another point 1/2 the time bMaxDrop = __max( bMaxDrop, 2 ); pSoldier->inv[ pSoldier->ubAttackingHand ].bStatus[ 0 ] -= (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 = 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->bAimTime) { // gain extra exp for aiming, up to the amount from // the difficulty of the attack usExpGain += ( 2 * __min( pSoldier->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 ]->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->bMonsterSmell > 0 && Random( 5 ) == 0 ) { pSoldier->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->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->uiStatusFlags |= SOLDIER_ATTACK_NOTICED; pTargetSoldier->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->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->bAimTime ) + 20; } else if ( pTargetSoldier->bLife < OKLIFE || pTargetSoldier->bCollapsed ) { iHitChance = 100; fSoldierCollapsed = TRUE; } else { iHitChance = CalcChanceToSteal( pSoldier, pTargetSoldier, pSoldier->bAimTime ); } } else { if ( pTargetSoldier->bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN || pTargetSoldier->bLife < OKLIFE || pTargetSoldier->bCollapsed ) { iHitChance = 100; } else { iHitChance = CalcChanceToPunch( pSoldier, pTargetSoldier, pSoldier->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 ] ); // Try to place the item in the merc inventory if (!AutoPlaceObject( pSoldier, &(pTargetSoldier->inv[ubIndexRet]), TRUE )) { // Place the item on the ground AddItemToPool( pSoldier->sGridNo, &(pTargetSoldier->inv[HANDPOS]), 1, pSoldier->bLevel, 0, -1 ); } DeleteObj( &(pTargetSoldier->inv[ubIndexRet]) ); // The item that the enemy holds in his hand before the stealing usNewItem = pTargetSoldier->inv[HANDPOS].usItem; if (usOldItem != usNewItem) { ReLoadSoldierAnimationDueToHandItemChange(pTargetSoldier, 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].usItem != NOTHING ) { 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 ) { DoMercBattleSound( pSoldier, BATTLE_SOUND_CURSE1 ); } // Item dropped somewhere... roll based on the same chance to determine where! iDiceRoll = (INT32) PreRandom( 100 ); if (iDiceRoll < iHitChance) { // Drop item in the our tile AddItemToPool( pSoldier->sGridNo, &(pTargetSoldier->inv[HANDPOS]), 1, pSoldier->bLevel, 0, -1 ); } else { // Drop item in the target's tile AddItemToPool( pTargetSoldier->sGridNo, &(pTargetSoldier->inv[HANDPOS]), 1, pSoldier->bLevel, 0, -1 ); } DeleteObj( &(pTargetSoldier->inv[HANDPOS]) ); // Reload buddy's animation... ReLoadSoldierAnimationDueToHandItemChange( pTargetSoldier, usOldItem, NOTHING ); } // Enemy has no item in his hand. else { fNoMoreItemInHand = TRUE; } // Reload buddy's animation... //ReLoadSoldierAnimationDueToHandItemChange( pTargetSoldier, 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].usItem != NOTHING) { ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[ STR_FAILED_TO_STEAL_SOMETHING ], pSoldier->name, ShortItemNames[ pTargetSoldier->inv[HANDPOS].usItem ] ); if ( pSoldier->bTeam == gbPlayerNum ) { DoMercBattleSound( pSoldier, 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->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->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->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 = 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->bMonsterSmell > 0 && Random( 5 ) == 0 ) { pSoldier->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->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->bDirection ) sAPCost += (INT16)GetAPsToLook( pSoldier ); sAPCost += (INT16)GetAPsToChangeStance( pSoldier, ANIM_STAND ); HandleSoldierThrowItem( pSoldier, pSoldier->sTargetGridNo ); DeductPoints( pSoldier, sAPCost, 0 ); RemoveObjs( &(pSoldier->inv[ HANDPOS ] ), 1 ); /* // Madd: Next 2 lines added: Deduct points! sAPCost = CalcTotalAPsToAttack( pSoldier, sTargetGridNo, FALSE, pSoldier->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; INT8 bAttachPos; 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]); for (bAttachPos = 0; bAttachPos < MAX_ATTACHMENTS; bAttachPos++) { if (pObj->usAttachItem[ bAttachPos ] != NOTHING) { if ( Item[ pObj->usAttachItem[ bAttachPos ] ].usItemClass & IC_EXPLOSV ) { break; } } } if (bAttachPos == MAX_ATTACHMENTS) { // this should not happen!! return( FALSE ); } if ( Weapon[GetAttachedGrenadeLauncher(pObj)].ubMagSize > 1 || ( Item[pObj->usItem].grenadelauncher && GetMagSize(pObj) > 1 )) CreateItem( pObj->usAttachItem[ bAttachPos ], 100, &Launchable ); else CreateItem( pObj->usAttachItem[ bAttachPos ], pObj->bAttachStatus[ bAttachPos ], &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, NULL ); //} // 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->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->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; MemFree( pSoldier->pTempObject ); pSoldier->pTempObject = NULL; 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 ].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 ) ); // WDS fix 07/25/2007 // Don't have monsters cause explosions // 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->bLevel, gpWorldLevelData[pTargetSoldier->sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT ); if ( EXPLOSIVE_GUN( usWeaponIndex ) ) { // 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 ].ubGunAmmoType // 2cond 'or' added if ( Item[usWeaponIndex].rocketlauncher || AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ].ubGunAmmoType].explosionSize > 1 ) { if ( Item[usWeaponIndex].singleshotrocketlauncher ) { IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), C1, pTargetSoldier->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].usGunAmmoItem ) ); IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), pSoldier->inv[pSoldier->ubAttackingHand ].usGunAmmoItem, pTargetSoldier->bLevel ); pSoldier->inv[pSoldier->ubAttackingHand ].usGunAmmoItem = NONE; } else if ( AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ].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 ].ubGunAmmoType].highExplosive , pTargetSoldier->bLevel ); // pSoldier->inv[pSoldier->ubAttackingHand ].usGunAmmoItem = NONE; } } else // tank cannon { IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, (INT16) (GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos )), TANK_SHELL, pTargetSoldier->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 potnetially kill // the poor bastard .. so check if ( !pTargetSoldier->fDoingExternalDeath ) { EVENT_SoldierGotHit( pTargetSoldier, usWeaponIndex, sDamage, sBreathLoss, usDirection, sRange, ubAttackerID, ubSpecial, ubHitLocation, 0, NOWHERE ); } // else // { // Buddy had died from additional dammage - 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, INT8 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 ].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].usGunAmmoItem ) ); IgniteExplosion( ubAttackerID, (INT16)CenterX( sGridNo ), (INT16)CenterY( sGridNo ), 0, sGridNo, pAttacker->inv[pAttacker->ubAttackingHand ].usGunAmmoItem , (INT8)( sZPos >= WALL_HEIGHT ) ); pAttacker->inv[pAttacker->ubAttackingHand ].usGunAmmoItem = NONE; } else if ( AmmoTypes[pSoldier->inv[pSoldier->ubAttackingHand ].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 ].ubGunAmmoType].highExplosive , (INT8)( sZPos >= WALL_HEIGHT ) ); // pSoldier->inv[pSoldier->ubAttackingHand ].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 ) { DoMercBattleSound( MercPtrs[ ubAttackerID ], 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 ) { OBJECTTYPE Object; // OK, have we hit ground? if ( usStructureID == INVALID_STRUCTURE_ID ) { // Add item CreateItem( usWeaponIndex, bWeaponStatus, &Object ); AddItemToPool( sGridNo, &Object, -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 ( gpWorldLevelData[ sGridNo ].ubTerrainID == LOW_WATER || gpWorldLevelData[ sGridNo ].ubTerrainID == DEEP_WATER ) { 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; if ( Item[ usInHand ].usItemClass == IC_GUN || Item[ usInHand ].usItemClass == IC_THROWING_KNIFE || (Item[usInHand].rocketlauncher && !Item[usInHand].singleshotrocketlauncher)) { // Determine range sRange = (INT16)GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ); if ( Item[ usInHand ].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, UINT16 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; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun")); if ( pSoldier->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; // DETERMINE BASE CHANCE OF HITTING iGunCondition = WEAPON_STATUS_MOD( pInHand->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 = GetRobotController( pSoldier ); 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 shooter is crouched, he aims slightly better (to max of AIM_BONUS_CROUCHING) if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH ) { iBonus = iRange / 10; if (iBonus > AIM_BONUS_CROUCHING) { iBonus = AIM_BONUS_CROUCHING; } iChance += iBonus; } // 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].usItem == NOTHING) { // 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 ( IsValidSecondHandShot( pSoldier ) ) { // 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; } sDistVis = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sGridNo, 0, pSoldier ); // give some leeway to allow people to spot for each other... // use distance limitation for LOS routine of 2 x maximum distance EVER visible, so that we get accurate // calculations out to around 50 tiles. Because we multiply max distance by 2, we must divide by 2 later // 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; ubTargetID = WhoIsThere2( sGridNo, pSoldier->bTargetLevel ); // best to use team knowledge as well, in case of spotting for someone else if (ubTargetID != NOBODY && pSoldier->bOppList[ubTargetID] == SEEN_CURRENTLY || gbPublicOpplist[pSoldier->bTeam][ubTargetID] == SEEN_CURRENTLY) { iSightRange = SoldierToBodyPartLineOfSightTest( pSoldier, sGridNo, pSoldier->bTargetLevel, pSoldier->bAimShotLocation, (UINT8) (MaxDistanceVisible() * 2), TRUE ); } if (iSightRange == -1) // didn't do a bodypart-based test { iSightRange = SoldierTo3DLocationLineOfSightTest( pSoldier, sGridNo, pSoldier->bTargetLevel, pSoldier->bTargetCubeLevel, (UINT8) (MaxDistanceVisible() * 2), TRUE ); } iSightRange *= 2; if ( iSightRange > (sDistVis * CELL_X_SIZE) ) { // 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 ) iChance += (AIM_BONUS_PER_AP * ubAimTime); // bonus for every pt of aiming if ( !(pSoldier->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->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->bShock) iChance -= (pSoldier->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->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 ); 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 < MaxDistanceVisible() ) ) { // tank; penalize at close range! // 2 percent per tile closer than max visible distance iChance -= 2 * ( MaxDistanceVisible() - (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->bLevel == 0 ) { if ( pSoldier->bTargetLevel > 0 ) { // penalty for firing up iChance -= AIM_PENALTY_FIRING_UP; } } else // pSoldier->bLevel > 0 ) { if ( pSoldier->bTargetLevel == 0 ) { iChance += AIM_BONUS_FIRING_DOWN; } // if have roof trait, give bonus if ( HAS_SKILL_TRAIT( pSoldier, ONROOF ) ) { iChance += gbSkillTraitBonus[ ONROOF ] * NUM_SKILL_TRAITS( pSoldier, ONROOF ); } } 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->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)) < NOBODY) { // 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->bLife < pSoldier->bLifeMax)) { // if bandaged, give 1/2 of the bandaged life points back into equation bBandaged = pSoldier->bLifeMax - pSoldier->bLife - pSoldier->bBleeding; // injury penalty is based on % damage taken (max 2/3rds chance) iPenalty = (iChance * 2 * (pSoldier->bLifeMax - pSoldier->bLife + (bBandaged / 2))) / (3 * pSoldier->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, UINT16 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, INT8 * 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; INT8 bPlatePos = -1; BOOLEAN plateHit = false; if (pTarget->uiStatusFlags & SOLDIER_VEHICLE) { INT8 bDummyStatus = 100; BOOLEAN dummyCoverage = true; //bDummyStatus = (INT8) 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->usItem != NOTHING) { // check plates first //if ( iSlot == VESTPOS ) //{ // bPlatePos = FindAttachment( pArmour, CERAMIC_PLATES ); bPlatePos = FindFirstArmourAttachment( pArmour); if (bPlatePos != -1) { // bullet got through jacket; apply ceramic plate armour iTotalProtection += ArmourProtection( pTarget, Item[pArmour->usAttachItem[bPlatePos]].ubClassIndex, &(pArmour->bAttachStatus[bPlatePos]), iImpact, ubAmmoType, &plateHit ); if ( pArmour->bAttachStatus[bPlatePos] < USABLE ) { // destroy plates! pArmour->usAttachItem[ bPlatePos ] = NOTHING; pArmour->bAttachStatus[ bPlatePos ] = 0; DirtyMercPanelInterface( pTarget, DIRTYLEVEL2 ); #ifdef ENGLISH if ( pTarget->bTeam == gbPlayerNum ) { // report plates destroyed! ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[61], pTarget->name ); } #endif } } //} // if the plate didn't stop the bullet... if ( iImpact > iTotalProtection ) { iTotalProtection += ArmourProtection( pTarget, Item[pArmour->usItem].ubClassIndex, &(pArmour->bStatus[0]), iImpact, ubAmmoType, &plateHit ); if ( pArmour->bStatus[ 0 ] < USABLE ) { //Madd: put any attachments that someone might have added to the armour in the merc's inventory for (int bLoop = 0; bLoop < MAX_ATTACHMENTS; bLoop++) { OBJECTTYPE newObj; CreateItem(pArmour->usAttachItem[bLoop], pArmour->bAttachStatus[bLoop], &newObj); if ( !AutoPlaceObject( pTarget, &newObj, FALSE ) ) { // put it on the ground AddItemToPool( pTarget->sGridNo, &newObj, 1, pTarget->bLevel, 0 , -1 ); } } DeleteObj( pArmour ); 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].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->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->bLife + Random( 10 ); iImpactForCrits = iImpact; } if (pubSpecial) { // is the blow deadly enough to cause a head explosion? if ( iImpactForCrits >= pTarget->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->bOverTerrainType != LOW_WATER && pTarget->bOverTerrainType != MED_WATER && pTarget->bOverTerrainType != DEEP_WATER ) { 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->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->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->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->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->bOppList[ pTarget->ubID ] == SEEN_CURRENTLY ) { // is this a stealth attack? if ( pTarget->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->bLife + Random( 10 ); iImpactForCrits = iImpact; } } } if (iImpactForCrits > 0 && iImpactForCrits < pTarget->bLife ) { if (PreRandom( iImpactForCrits / 2 + pFirer->bAimTime * 5) + 1 > CRITICAL_HIT_THRESHOLD) { bStatLoss = (INT8) PreRandom( iImpactForCrits / 2 ) + 1; switch( ubHitLocation ) { case AIM_SHOT_HEAD: if (bStatLoss >= pTarget->bWisdom) { bStatLoss = pTarget->bWisdom - 1; } if ( bStatLoss > 0 ) { pTarget->bWisdom -= bStatLoss; if (pTarget->ubProfile != NO_PROFILE) { gMercProfiles[ pTarget->ubProfile ].bWisdom = pTarget->bWisdom; } if (pTarget->name[0] && pTarget->bVisible == TRUE) { // make stat RED for a while... pTarget->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->uiStatusFlags & SOLDIER_MONSTER) ) { if (bStatLoss >= pTarget->bDexterity) { bStatLoss = pTarget->bDexterity - 1; } if ( bStatLoss > 0 ) { pTarget->bDexterity -= bStatLoss; if (pTarget->ubProfile != NO_PROFILE) { gMercProfiles[ pTarget->ubProfile ].bDexterity = pTarget->bDexterity; } if (pTarget->name[0] && pTarget->bVisible == TRUE) { // make stat RED for a while... pTarget->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->bStrength) { bStatLoss = pTarget->bStrength - 1; } if ( bStatLoss > 0 ) { pTarget->bStrength -= bStatLoss; if (pTarget->ubProfile != NO_PROFILE) { gMercProfiles[ pTarget->ubProfile ].bStrength = pTarget->bStrength; } if (pTarget->name[0] && pTarget->bVisible == TRUE) { // make stat RED for a while... pTarget->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->bAgility) { bStatLoss = pTarget->bAgility - 1; } if ( bStatLoss > 0 ) { pTarget->bAgility -= bStatLoss; if (pTarget->ubProfile != NO_PROFILE) { gMercProfiles[ pTarget->ubProfile ].bAgility = pTarget->bAgility; } if (pTarget->name[0] && pTarget->bVisible == TRUE) { // make stat RED for a while... pTarget->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 ) { DoMercBattleSound( MercPtrs[ ubAttackerID ], 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->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->bStrength + // physical strength (TRIPLED!) (10 * EffectiveExpLevel( pAttacker ) ) ); // experience, knowledge } else { iAttRating = (3 * EffectiveDexterity( pAttacker ) + // coordination, accuracy (TRIPLED!) EffectiveAgility( pAttacker ) + // speed & reflexes pAttacker->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->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->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->bShock) iAttRating -= (pAttacker->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->bLife < pAttacker->bLifeMax)) { // if bandaged, give 1/2 of the bandaged life points back into equation ubBandaged = pAttacker->bLifeMax - pAttacker->bLife - pAttacker->bBleeding; iAttRating -= (2 * iAttRating * (pAttacker->bLifeMax - pAttacker->bLife + (ubBandaged / 2))) / (3 * pAttacker->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->bStrength + // physical strength (TRIPLED!) (10 * EffectiveExpLevel( pDefender ) ); // experience, knowledge } else { iDefRating = (3 * EffectiveAgility( pDefender ) ) + // speed & reflexes (TRIPLED!) EffectiveDexterity( pDefender ) + // coordination, accuracy pDefender->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->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->bShock) iDefRating -= (pDefender->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->bLife < pDefender->bLifeMax)) { // if bandaged, give 1/2 of the bandaged life points back into equation ubBandaged = pDefender->bLifeMax - pDefender->bLife - pDefender->bBleeding; iDefRating -= (2 * iDefRating * (pDefender->bLifeMax - pDefender->bLife + (ubBandaged / 2))) / (3 * pDefender->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 ].usItem != NOTHING ) { pSoldier->inv[ ubHandPos ].ubGunShotsLeft = GetMagSize(&pSoldier->inv[ ubHandPos ]); // Dirty Bars DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 ); } } BOOLEAN IsGunWeaponModeCapable( SOLDIERTYPE *pSoldier, UINT8 ubHandPos , UINT8 bWpnMode) { DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("IsGunWeaponModeCapable: weapon mode=%d",bWpnMode)); switch(bWpnMode) { case WM_NORMAL: return (pSoldier->inv[ ubHandPos ].usItem != NOTHING && Item[ pSoldier->inv[ ubHandPos ].usItem ].usItemClass & IC_WEAPON && !Weapon[ pSoldier->inv[ ubHandPos ].usItem ].NoSemiAuto ); // Check for being a weapon.... case WM_BURST: return IsGunBurstCapable(pSoldier, ubHandPos, FALSE); case WM_AUTOFIRE: return ((IsGunAutofireCapable(pSoldier, ubHandPos) || Weapon[ pSoldier->inv[ ubHandPos ].usItem ].NoSemiAuto )&& !Item[pSoldier->inv[ubHandPos].usItem].grenadelauncher ); case WM_ATTACHED_GL: // return (FindAttachment( &(pSoldier->inv[ubHandPos]), UNDER_GLAUNCHER ) != ITEM_NOT_FOUND && FindLaunchableAttachment( &(pSoldier->inv[ubHandPos]), UNDER_GLAUNCHER ) != ITEM_NOT_FOUND ); return (!Item[pSoldier->inv[ubHandPos].usItem].grenadelauncher && IsGrenadeLauncherAttached( &(pSoldier->inv[ubHandPos]) ) && FindLaunchableAttachment( &(pSoldier->inv[ubHandPos]), GetAttachedGrenadeLauncher( &(pSoldier->inv[ubHandPos]) )) != ITEM_NOT_FOUND ); 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]))) != ITEM_NOT_FOUND ); 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; } } BOOLEAN IsGunAutofireCapable( SOLDIERTYPE *pSoldier, UINT8 ubHandPos ) { BOOLEAN fCapable = FALSE; if ( pSoldier->inv[ ubHandPos ].usItem != NOTHING ) { // ATE: Check for being a weapon.... if ( Item[ pSoldier->inv[ ubHandPos ].usItem ].usItemClass & IC_WEAPON ) { if ( GetAutofireShotsPerFiveAPs(&pSoldier->inv[ ubHandPos ]) > 0 ) { fCapable = TRUE; } } } return( fCapable ); } BOOLEAN IsGunBurstCapable( SOLDIERTYPE *pSoldier, UINT8 ubHandPos , BOOLEAN fNotify ) { BOOLEAN fCapable = FALSE; if ( pSoldier->inv[ ubHandPos ].usItem != NOTHING ) { // ATE: Check for being a weapon.... if ( Item[ pSoldier->inv[ ubHandPos ].usItem ].usItemClass & IC_WEAPON ) { if ( GetShotsPerBurst(&pSoldier->inv[ ubHandPos ]) > 0 ) { fCapable = TRUE; } } } if ( fNotify && !fCapable ) { ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, Message[ STR_NOT_BURST_CAPABLE ], pSoldier->name ); } return( fCapable ); } 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; } } 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->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->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->bShock) { iChance -= (pSoldier->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->bLife < pSoldier->bLifeMax)) { // if bandaged, give 1/2 of the bandaged life points back into equation bBandaged = pSoldier->bLifeMax - pSoldier->bLife - pSoldier->bBleeding; // injury penalty is based on % damage taken (max 2/3rds iChance) bPenalty = (2 * iChance * (pSoldier->bLifeMax - pSoldier->bLife + (bBandaged / 2))) / (3 * pSoldier->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].bStatus[0])) / 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; } } while(IsGunWeaponModeCapable( pSoldier, HANDPOS, pSoldier->bWeaponMode ) == 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; } pSoldier->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->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->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 ); EVENT_SoldierGotHit( pSoldier, CREATURE_QUEEN_TENTACLES, (INT16) iImpact, (INT16) iImpact, gOppositeDirection[ bDir ], 50, pQueenSoldier->ubID, 0, ANIM_CROUCH, 0, 0 ); } } } } } } } } pQueenSoldier->uiPendingActionData1++; } BOOLEAN WillExplosiveWeaponFail( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj ) { if ( pSoldier->bTeam == gbPlayerNum || pSoldier->bVisible == 1 ) { if ( (INT8)(PreRandom( 40 ) + PreRandom( 40 ) ) > pObj->bStatus[0] ) { // Do second dice roll if ( PreRandom( 2 ) == 1 ) { // Fail return( TRUE ); } } } return( FALSE ); } BOOLEAN IsWeapon ( UINT16 itemIndex ) { UINT16 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 (Item[ pObj->usItem ].ubPerPocket == 0) { 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; } UINT8 GetMagSize( OBJECTTYPE *pObj ) { // DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetMagSize")); return Weapon[ pObj->usItem ].ubMagSize + GetMagSizeBonus(pObj); } BOOLEAN WeaponReady(SOLDIERTYPE * pSoldier) { #ifdef ROBOT_ALWAYS_READY if ( AM_A_ROBOT( pSoldier) ) return TRUE; #endif if ( gAnimControl[ pSoldier->usAnimState ].uiFlags & ANIM_FIREREADY ) 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; }