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