SkillCheck.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. #ifdef PRECOMPILEDHEADERS
  2. #include "Tactical All.h"
  3. #else
  4. #include "SkillCheck.h"
  5. #include "Soldier Profile.h"
  6. #include "Random.h"
  7. #include "Items.h"
  8. #include "Dialogue Control.h"
  9. #include "Overhead.h"
  10. #include "Soldier macros.h"
  11. #include "Isometric Utils.h"
  12. #include "Morale.h"
  13. #include "drugs and alcohol.h"
  14. #include "strategicmap.h"
  15. #endif
  16. INT8 EffectiveStrength( SOLDIERTYPE * pSoldier )
  17. {
  18. INT8 bBandaged;
  19. INT32 iEffStrength;
  20. // Effective strength is:
  21. // 1/2 full strength
  22. // plus 1/2 strength scaled according to how hurt we are
  23. bBandaged = pSoldier->bLifeMax - pSoldier->bLife - pSoldier->bBleeding;
  24. iEffStrength = pSoldier->bStrength / 2;
  25. iEffStrength += (pSoldier->bStrength / 2) * (pSoldier->bLife + bBandaged / 2) / (pSoldier->bLifeMax);
  26. // ATE: Make sure at least 2...
  27. iEffStrength = __max( iEffStrength, 2 );
  28. return( (INT8) iEffStrength );
  29. }
  30. INT8 EffectiveWisdom( SOLDIERTYPE * pSoldier )
  31. {
  32. INT32 iEffWisdom;
  33. iEffWisdom = pSoldier->bWisdom;
  34. iEffWisdom = EffectStatForBeingDrunk( pSoldier, iEffWisdom );
  35. return( (INT8) iEffWisdom );
  36. }
  37. INT8 EffectiveAgility( SOLDIERTYPE * pSoldier )
  38. {
  39. INT32 iEffAgility;
  40. iEffAgility = pSoldier->bAgility;
  41. iEffAgility = EffectStatForBeingDrunk( pSoldier, iEffAgility );
  42. if ( pSoldier->sWeightCarriedAtTurnStart > 100 )
  43. {
  44. iEffAgility = (iEffAgility * 100) / pSoldier->sWeightCarriedAtTurnStart;
  45. }
  46. return( (INT8) iEffAgility );
  47. }
  48. INT8 EffectiveMechanical( SOLDIERTYPE * pSoldier )
  49. {
  50. INT32 iEffMechanical;
  51. iEffMechanical = pSoldier->bMechanical;
  52. iEffMechanical = EffectStatForBeingDrunk( pSoldier, iEffMechanical );
  53. return( (INT8) iEffMechanical );
  54. }
  55. INT8 EffectiveExplosive( SOLDIERTYPE * pSoldier )
  56. {
  57. INT32 iEffExplosive;
  58. iEffExplosive = pSoldier->bExplosive;
  59. iEffExplosive = EffectStatForBeingDrunk( pSoldier, iEffExplosive );
  60. return( (INT8) iEffExplosive );
  61. }
  62. INT8 EffectiveMedical( SOLDIERTYPE * pSoldier )
  63. {
  64. INT32 iEffMedical;
  65. iEffMedical = pSoldier->bMedical;
  66. iEffMedical = EffectStatForBeingDrunk( pSoldier, iEffMedical );
  67. return( (INT8) iEffMedical );
  68. }
  69. INT8 EffectiveLeadership( SOLDIERTYPE * pSoldier )
  70. {
  71. INT32 iEffLeadership;
  72. INT8 bDrunkLevel;
  73. iEffLeadership = pSoldier->bLeadership;
  74. // if we are drunk, effect leader ship in a +ve way...
  75. bDrunkLevel = GetDrunkLevel( pSoldier );
  76. if ( bDrunkLevel == FEELING_GOOD )
  77. {
  78. iEffLeadership = ( iEffLeadership * 120 / 100 );
  79. }
  80. return( (INT8) iEffLeadership );
  81. }
  82. INT8 EffectiveExpLevel( SOLDIERTYPE * pSoldier )
  83. {
  84. INT32 iEffExpLevel;
  85. INT8 bDrunkLevel;
  86. INT32 iExpModifier[] =
  87. { 0, // SOBER
  88. 0, // Feeling good
  89. -1, // Borderline
  90. -2, // Drunk
  91. 0, // Hung
  92. };
  93. iEffExpLevel = pSoldier->bExpLevel;
  94. bDrunkLevel = GetDrunkLevel( pSoldier );
  95. iEffExpLevel = iEffExpLevel + iExpModifier[ bDrunkLevel ];
  96. if (pSoldier->ubProfile != NO_PROFILE)
  97. {
  98. if ( (gMercProfiles[ pSoldier->ubProfile ].bPersonalityTrait == CLAUSTROPHOBIC) && pSoldier->bActive && pSoldier->bInSector && gbWorldSectorZ > 0)
  99. {
  100. // claustrophobic!
  101. iEffExpLevel--;
  102. }
  103. }
  104. if (iEffExpLevel < 1)
  105. {
  106. // can't go below 1
  107. return( 1 );
  108. }
  109. else
  110. {
  111. return( (INT8) iEffExpLevel );
  112. }
  113. }
  114. INT8 EffectiveMarksmanship( SOLDIERTYPE * pSoldier )
  115. {
  116. INT32 iEffMarksmanship;
  117. iEffMarksmanship = pSoldier->bMarksmanship;
  118. iEffMarksmanship = EffectStatForBeingDrunk( pSoldier, iEffMarksmanship );
  119. return( (INT8) iEffMarksmanship );
  120. }
  121. INT8 EffectiveDexterity( SOLDIERTYPE * pSoldier )
  122. {
  123. INT32 iEffDexterity;
  124. iEffDexterity = pSoldier->bDexterity;
  125. iEffDexterity = EffectStatForBeingDrunk( pSoldier, iEffDexterity );
  126. return( (INT8) iEffDexterity );
  127. }
  128. UINT8 GetPenaltyForFatigue( SOLDIERTYPE *pSoldier )
  129. {
  130. UINT8 ubPercentPenalty;
  131. if ( pSoldier->bBreathMax >= 85 ) ubPercentPenalty = 0;
  132. else if ( pSoldier->bBreathMax >= 70 ) ubPercentPenalty = 10;
  133. else if ( pSoldier->bBreathMax >= 50 ) ubPercentPenalty = 25;
  134. else if ( pSoldier->bBreathMax >= 30 ) ubPercentPenalty = 50;
  135. else if ( pSoldier->bBreathMax >= 15 ) ubPercentPenalty = 75;
  136. else if ( pSoldier->bBreathMax > 0 ) ubPercentPenalty = 90;
  137. else ubPercentPenalty = 100;
  138. return( ubPercentPenalty );
  139. }
  140. void ReducePointsForFatigue( SOLDIERTYPE *pSoldier, UINT16 *pusPoints )
  141. {
  142. *pusPoints -= (*pusPoints * GetPenaltyForFatigue( pSoldier )) / 100;
  143. }
  144. INT32 GetSkillCheckPenaltyForFatigue( SOLDIERTYPE *pSoldier, INT32 iSkill )
  145. {
  146. // use only half the full effect of fatigue for skill checks
  147. return( ( (iSkill * GetPenaltyForFatigue( pSoldier ) ) / 100) / 2 );
  148. }
  149. INT32 SkillCheck( SOLDIERTYPE * pSoldier, INT8 bReason, INT8 bChanceMod )
  150. {
  151. INT32 iSkill;
  152. INT32 iChance, iReportChance;
  153. INT32 iRoll, iMadeItBy;
  154. INT8 bSlot;
  155. INT32 iLoop;
  156. SOLDIERTYPE * pTeamSoldier;
  157. INT8 bBuddyIndex;
  158. BOOLEAN fForceDamnSound = FALSE;
  159. iReportChance = -1;
  160. switch (bReason)
  161. {
  162. case LOCKPICKING_CHECK:
  163. case ELECTRONIC_LOCKPICKING_CHECK:
  164. fForceDamnSound = TRUE;
  165. iSkill = EffectiveMechanical( pSoldier );
  166. if (iSkill == 0)
  167. {
  168. break;
  169. }
  170. // adjust skill based on wisdom (knowledge)
  171. iSkill = iSkill * (EffectiveWisdom( pSoldier ) + 100) / 200;
  172. // and dexterity (clumsy?)
  173. iSkill = iSkill * (EffectiveDexterity( pSoldier ) + 100) / 200;
  174. // factor in experience
  175. iSkill = iSkill + EffectiveExpLevel( pSoldier ) * 3;
  176. if (HAS_SKILL_TRAIT( pSoldier, LOCKPICKING ) )
  177. {
  178. // if we specialize in picking locks...
  179. iSkill += gbSkillTraitBonus[LOCKPICKING] * NUM_SKILL_TRAITS( pSoldier, LOCKPICKING );
  180. }
  181. if (bReason == ELECTRONIC_LOCKPICKING_CHECK && !(HAS_SKILL_TRAIT( pSoldier, ELECTRONICS)) )
  182. {
  183. // if we are unfamiliar with electronics...
  184. iSkill /= 2;
  185. }
  186. // adjust chance based on status of kit
  187. bSlot = FindObj( pSoldier, LOCKSMITHKIT );
  188. if (bSlot == NO_SLOT)
  189. {
  190. // this should never happen, but might as well check...
  191. iSkill = 0;
  192. }
  193. iSkill = iSkill * pSoldier->inv[bSlot].bStatus[0] / 100;
  194. break;
  195. case ATTACHING_DETONATOR_CHECK:
  196. case ATTACHING_REMOTE_DETONATOR_CHECK:
  197. iSkill = EffectiveExplosive( pSoldier );
  198. if (iSkill == 0)
  199. {
  200. break;
  201. }
  202. iSkill = (iSkill * 3 + EffectiveDexterity( pSoldier ) ) / 4;
  203. if ( bReason == ATTACHING_REMOTE_DETONATOR_CHECK && !(HAS_SKILL_TRAIT( pSoldier, ELECTRONICS )) )
  204. {
  205. iSkill /= 2;
  206. }
  207. break;
  208. case PLANTING_BOMB_CHECK:
  209. case PLANTING_REMOTE_BOMB_CHECK:
  210. iSkill = EffectiveExplosive( pSoldier ) * 7;
  211. iSkill += EffectiveWisdom( pSoldier ) * 2;
  212. iSkill += EffectiveExpLevel( pSoldier ) * 10;
  213. iSkill = iSkill / 10; // bring the value down to a percentage
  214. if ( bReason == PLANTING_REMOTE_BOMB_CHECK && !(HAS_SKILL_TRAIT( pSoldier, ELECTRONICS)) )
  215. {
  216. // deduct only a bit...
  217. iSkill = (iSkill * 3) / 4;
  218. }
  219. // Ok, this is really damn easy, so skew the values...
  220. // e.g. if calculated skill is 84, skewed up to 96
  221. // 51 to 84
  222. // 22 stays as is
  223. iSkill = (iSkill + 100 * (iSkill / 25) ) / (iSkill / 25 + 1);
  224. break;
  225. case DISARM_TRAP_CHECK:
  226. fForceDamnSound = TRUE;
  227. iSkill = EffectiveExplosive( pSoldier ) * 7;
  228. if ( iSkill == 0 )
  229. {
  230. break;
  231. }
  232. iSkill += EffectiveDexterity( pSoldier ) * 2;
  233. iSkill += EffectiveExpLevel( pSoldier ) * 10;
  234. iSkill = iSkill / 10; // bring the value down to a percentage
  235. // penalty based on poor wisdom
  236. iSkill -= (100 - EffectiveWisdom( pSoldier ) ) / 5;
  237. break;
  238. case DISARM_ELECTRONIC_TRAP_CHECK:
  239. fForceDamnSound = TRUE;
  240. iSkill = __max( EffectiveMechanical( pSoldier ) , EffectiveExplosive( pSoldier ) ) * 7;
  241. if ( iSkill == 0 )
  242. {
  243. break;
  244. }
  245. iSkill += EffectiveDexterity( pSoldier ) * 2;
  246. iSkill += EffectiveExpLevel( pSoldier ) * 10;
  247. iSkill = iSkill / 10; // bring the value down to a percentage
  248. // penalty based on poor wisdom
  249. iSkill -= (100 - EffectiveWisdom( pSoldier ) ) / 5;
  250. if ( !(HAS_SKILL_TRAIT( pSoldier, ELECTRONICS )) )
  251. {
  252. iSkill = (iSkill * 3) / 4;
  253. }
  254. break;
  255. case OPEN_WITH_CROWBAR:
  256. // Add for crowbar...
  257. iSkill = EffectiveStrength( pSoldier ) + 20;
  258. fForceDamnSound = TRUE;
  259. break;
  260. case SMASH_DOOR_CHECK:
  261. iSkill = EffectiveStrength( pSoldier );
  262. break;
  263. case UNJAM_GUN_CHECK:
  264. iSkill = 30 + EffectiveMechanical( pSoldier ) / 2;
  265. break;
  266. case NOTICE_DART_CHECK:
  267. // only a max of ~20% chance
  268. iSkill = EffectiveWisdom( pSoldier ) / 10 + EffectiveExpLevel( pSoldier );
  269. break;
  270. case LIE_TO_QUEEN_CHECK:
  271. // competitive check vs the queen's wisdom and leadership... poor guy!
  272. iSkill = 50 * ( EffectiveWisdom( pSoldier ) + EffectiveLeadership( pSoldier ) ) / ( gMercProfiles[ QUEEN ].bWisdom + gMercProfiles[ QUEEN ].bLeadership );
  273. break;
  274. case ATTACHING_SPECIAL_ITEM_CHECK:
  275. case ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK:
  276. iSkill = EffectiveMechanical( pSoldier );
  277. if (iSkill == 0)
  278. {
  279. break;
  280. }
  281. // adjust skill based on wisdom (knowledge)
  282. iSkill = iSkill * (EffectiveWisdom( pSoldier ) + 100) / 200;
  283. // and dexterity (clumsy?)
  284. iSkill = iSkill * (EffectiveDexterity( pSoldier ) + 100) / 200;
  285. // factor in experience
  286. iSkill = iSkill + EffectiveExpLevel( pSoldier ) * 3;
  287. if (bReason == ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK && !(HAS_SKILL_TRAIT( pSoldier, ELECTRONICS)) )
  288. {
  289. // if we are unfamiliar with electronics...
  290. iSkill /= 2;
  291. }
  292. break;
  293. default:
  294. iSkill = 0;
  295. break;
  296. }
  297. iSkill -= GetSkillCheckPenaltyForFatigue( pSoldier, iSkill );
  298. iChance = iSkill + bChanceMod;
  299. switch (bReason)
  300. {
  301. case LOCKPICKING_CHECK:
  302. case ELECTRONIC_LOCKPICKING_CHECK:
  303. case DISARM_TRAP_CHECK:
  304. case DISARM_ELECTRONIC_TRAP_CHECK:
  305. case OPEN_WITH_CROWBAR:
  306. case SMASH_DOOR_CHECK:
  307. case ATTACHING_SPECIAL_ITEM_CHECK:
  308. case ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK:
  309. // for lockpicking and smashing locks, if the chance isn't reasonable
  310. // we set it to 0 so they can never get through the door if they aren't
  311. // good enough
  312. if (iChance < 30)
  313. {
  314. iChance = 0;
  315. break;
  316. }
  317. // else fall through
  318. default:
  319. iChance += GetMoraleModifier( pSoldier );
  320. break;
  321. }
  322. if (iChance > 99)
  323. {
  324. iChance = 99;
  325. }
  326. else if (iChance < 0)
  327. {
  328. iChance = 0;
  329. }
  330. iRoll = PreRandom( 100 );
  331. iMadeItBy = iChance - iRoll;
  332. if (iMadeItBy < 0)
  333. {
  334. if ( (pSoldier->bLastSkillCheck == bReason) && (pSoldier->sGridNo == pSoldier->sSkillCheckGridNo) )
  335. {
  336. pSoldier->ubSkillCheckAttempts++;
  337. if (pSoldier->ubSkillCheckAttempts > 2)
  338. {
  339. if (iChance == 0)
  340. {
  341. // do we realize that we just can't do this?
  342. if ( (100 - (pSoldier->ubSkillCheckAttempts - 2) * 20) < EffectiveWisdom( pSoldier ) )
  343. {
  344. // say "I can't do this" quote
  345. TacticalCharacterDialogue( pSoldier, QUOTE_DEFINITE_CANT_DO );
  346. return( iMadeItBy );
  347. }
  348. }
  349. }
  350. }
  351. else
  352. {
  353. pSoldier->bLastSkillCheck = bReason;
  354. pSoldier->ubSkillCheckAttempts = 1;
  355. pSoldier->sSkillCheckGridNo = pSoldier->sGridNo;
  356. }
  357. if ( fForceDamnSound || Random( 100 ) < 40 )
  358. {
  359. switch( bReason )
  360. {
  361. case UNJAM_GUN_CHECK:
  362. case NOTICE_DART_CHECK:
  363. case LIE_TO_QUEEN_CHECK:
  364. // silent check
  365. break;
  366. default:
  367. DoMercBattleSound( pSoldier, BATTLE_SOUND_CURSE1 );
  368. break;
  369. }
  370. }
  371. }
  372. else
  373. {
  374. // A buddy might make a positive comment based on our success;
  375. // Increase the chance for people with higher skill and for more difficult tasks
  376. iChance = 15 + iSkill / 20 + (-bChanceMod) / 20;
  377. if (iRoll < iChance)
  378. {
  379. // If a buddy of this merc is standing around nearby, they'll make a positive comment.
  380. iLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
  381. for ( pTeamSoldier = MercPtrs[ iLoop ]; iLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; iLoop++,pTeamSoldier++ )
  382. {
  383. if ( OK_INSECTOR_MERC( pTeamSoldier ) )
  384. {
  385. bBuddyIndex = WhichBuddy( pTeamSoldier->ubProfile, pSoldier->ubProfile );
  386. if (bBuddyIndex >= 0 && SpacesAway( pSoldier->sGridNo, pTeamSoldier->sGridNo ) < 15)
  387. {
  388. switch( bBuddyIndex )
  389. {
  390. case 0:
  391. // buddy #1 did something good!
  392. TacticalCharacterDialogue( pTeamSoldier, QUOTE_BUDDY_1_GOOD );
  393. break;
  394. case 1:
  395. // buddy #2 did something good!
  396. TacticalCharacterDialogue( pTeamSoldier, QUOTE_BUDDY_2_GOOD );
  397. break;
  398. case 2:
  399. // learn to like buddy did something good!
  400. TacticalCharacterDialogue( pTeamSoldier, QUOTE_LEARNED_TO_LIKE_WITNESSED );
  401. break;
  402. default:
  403. break;
  404. }
  405. }
  406. }
  407. }
  408. }
  409. }
  410. return( iMadeItBy );
  411. }
  412. INT8 CalcTrapDetectLevel( SOLDIERTYPE * pSoldier, BOOLEAN fExamining )
  413. {
  414. // return the level of trap which the guy is able to detect
  415. INT8 bDetectLevel;
  416. // formula: 1 pt for every exp_level
  417. // plus 1 pt for every 40 explosives
  418. // less 1 pt for every 20 wisdom MISSING
  419. bDetectLevel = EffectiveExpLevel( pSoldier );
  420. bDetectLevel += (EffectiveExplosive( pSoldier ) / 40);
  421. bDetectLevel -= ((100 - EffectiveWisdom( pSoldier ) ) / 20);
  422. // if the examining flag is true, this isn't just a casual glance
  423. // and the merc should have a higher chance
  424. if (fExamining)
  425. {
  426. bDetectLevel += (INT8) PreRandom(bDetectLevel / 3 + 2);
  427. }
  428. // if substantially bleeding, or still in serious shock, randomly lower value
  429. if ((pSoldier->bBleeding > 20) || (pSoldier->bShock > 1))
  430. {
  431. bDetectLevel -= (INT8) PreRandom(3);
  432. }
  433. if (bDetectLevel < 1)
  434. {
  435. bDetectLevel = 1;
  436. }
  437. return( bDetectLevel );
  438. }