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