@@ -54,6 +54,19 @@ typedef struct CombatAiInfo {
54
54
int lastMove;
55
55
} CombatAiInfo;
56
56
57
+ typedef struct UnarmedHitDescription {
58
+ int requiredLevel;
59
+ int requiredSkill;
60
+ int requiredStats[PRIMARY_STAT_COUNT];
61
+ int minDamage;
62
+ int maxDamage;
63
+ int bonusDamage;
64
+ int bonusCriticalChance;
65
+ int actionPointCost;
66
+ bool isPenetrate;
67
+ bool isSecondary;
68
+ } UnarmedHitDescription;
69
+
57
70
static bool _combat_safety_invalidate_weapon_func (Object* critter, Object* weapon, int hitMode, Object* a4, int * a5, Object* a6);
58
71
static int aiInfoCopy (int srcIndex, int destIndex);
59
72
static void _combat_begin (Object* a1);
@@ -96,6 +109,10 @@ static void criticalsReset();
96
109
static void criticalsExit ();
97
110
static void burstModInit ();
98
111
static int burstModComputeRounds (int totalRounds, int * centerRoundsPtr, int * leftRoundsPtr, int * rightRoundsPtr);
112
+ static void unarmedInit ();
113
+ static void unarmedInitVanilla ();
114
+ static void unarmedInitCustom ();
115
+ static int unarmedGetHitModeInRange (int firstHitMode, int lastHitMode, bool isSecondary);
99
116
100
117
// 0x500B50
101
118
static char _a_1[] = " ." ;
@@ -1932,6 +1949,7 @@ static int gBurstModCenterMultiplier = SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MUL
1932
1949
static int gBurstModCenterDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR;
1933
1950
static int gBurstModTargetMultiplier = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_MULTIPLIER;
1934
1951
static int gBurstModTargetDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_DIVISOR;
1952
+ static UnarmedHitDescription gUnarmedHitDescriptions [HIT_MODE_COUNT];
1935
1953
1936
1954
// combat_init
1937
1955
// 0x420CC0
@@ -1974,6 +1992,7 @@ int combatInit()
1974
1992
// SFALL
1975
1993
criticalsInit ();
1976
1994
burstModInit ();
1995
+ unarmedInit ();
1977
1996
1978
1997
return 0 ;
1979
1998
}
@@ -3757,13 +3776,12 @@ static int attackCompute(Attack* attack)
3757
3776
damageMultiplier = 4 ;
3758
3777
}
3759
3778
3760
- if (((attack->hitMode == HIT_MODE_HAMMER_PUNCH || attack->hitMode == HIT_MODE_POWER_KICK) && randomBetween (1 , 100 ) <= 5 )
3761
- || ((attack->hitMode == HIT_MODE_JAB || attack->hitMode == HIT_MODE_HOOK_KICK) && randomBetween (1 , 100 ) <= 10 )
3762
- || (attack->hitMode == HIT_MODE_HAYMAKER && randomBetween (1 , 100 ) <= 15 )
3763
- || (attack->hitMode == HIT_MODE_PALM_STRIKE && randomBetween (1 , 100 ) <= 20 )
3764
- || (attack->hitMode == HIT_MODE_PIERCING_STRIKE && randomBetween (1 , 100 ) <= 40 )
3765
- || (attack->hitMode == HIT_MODE_PIERCING_KICK && randomBetween (1 , 100 ) <= 50 )) {
3766
- roll = ROLL_CRITICAL_SUCCESS;
3779
+ // SFALL
3780
+ int bonusCriticalChance = unarmedGetBonusCriticalChance (attack->hitMode );
3781
+ if (bonusCriticalChance != 0 ) {
3782
+ if (randomBetween (1 , 100 ) <= bonusCriticalChance) {
3783
+ roll = ROLL_CRITICAL_SUCCESS;
3784
+ }
3767
3785
}
3768
3786
}
3769
3787
}
@@ -4203,7 +4221,7 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in
4203
4221
bool isRangedWeapon = false ;
4204
4222
4205
4223
int accuracy;
4206
- if (weapon == NULL || hitMode == HIT_MODE_PUNCH || hitMode == HIT_MODE_KICK || (hitMode >= FIRST_ADVANCED_UNARMED_HIT_MODE && hitMode <= LAST_ADVANCED_UNARMED_HIT_MODE )) {
4224
+ if (weapon == NULL || isUnarmedHitMode (hitMode)) {
4207
4225
accuracy = skillGetValue (attacker, SKILL_UNARMED);
4208
4226
} else {
4209
4227
accuracy = _item_w_skill_level (attacker, hitMode);
@@ -4413,11 +4431,9 @@ static void attackComputeDamage(Attack* attack, int ammoQuantity, int bonusDamag
4413
4431
damageThreshold = 20 * damageThreshold / 100 ;
4414
4432
damageResistance = 20 * damageResistance / 100 ;
4415
4433
} else {
4434
+ // SFALL
4416
4435
if (weaponGetPerk (attack->weapon ) == PERK_WEAPON_PENETRATE
4417
- || attack->hitMode == HIT_MODE_PALM_STRIKE
4418
- || attack->hitMode == HIT_MODE_PIERCING_STRIKE
4419
- || attack->hitMode == HIT_MODE_HOOK_KICK
4420
- || attack->hitMode == HIT_MODE_PIERCING_KICK) {
4436
+ || unarmedIsPenetrating (attack->hitMode )) {
4421
4437
damageThreshold = 20 * damageThreshold / 100 ;
4422
4438
}
4423
4439
@@ -6189,3 +6205,306 @@ static int burstModComputeRounds(int totalRounds, int* centerRoundsPtr, int* lef
6189
6205
6190
6206
return mainTargetRounds;
6191
6207
}
6208
+
6209
+ static void unarmedInit ()
6210
+ {
6211
+ unarmedInitVanilla ();
6212
+ unarmedInitCustom ();
6213
+ }
6214
+
6215
+ static void unarmedInitVanilla ()
6216
+ {
6217
+ UnarmedHitDescription* hitDescription;
6218
+
6219
+ // Punch
6220
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_PUNCH]);
6221
+ hitDescription->minDamage = 1 ;
6222
+ hitDescription->maxDamage = 2 ;
6223
+ hitDescription->actionPointCost = 3 ;
6224
+
6225
+ // Strong Punch
6226
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_STRONG_PUNCH]);
6227
+ hitDescription->requiredSkill = 55 ;
6228
+ hitDescription->requiredStats [STAT_AGILITY] = 6 ;
6229
+ hitDescription->minDamage = 1 ;
6230
+ hitDescription->maxDamage = 2 ;
6231
+ hitDescription->bonusDamage = 3 ;
6232
+ hitDescription->actionPointCost = 3 ;
6233
+ hitDescription->isPenetrate = false ;
6234
+ hitDescription->isSecondary = false ;
6235
+
6236
+ // Hammer Punch
6237
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_HAMMER_PUNCH]);
6238
+ hitDescription->requiredLevel = 6 ;
6239
+ hitDescription->requiredSkill = 75 ;
6240
+ hitDescription->requiredStats [STAT_STRENGTH] = 5 ;
6241
+ hitDescription->requiredStats [STAT_AGILITY] = 6 ;
6242
+ hitDescription->minDamage = 1 ;
6243
+ hitDescription->maxDamage = 2 ;
6244
+ hitDescription->bonusDamage = 5 ;
6245
+ hitDescription->bonusCriticalChance = 5 ;
6246
+ hitDescription->actionPointCost = 3 ;
6247
+
6248
+ // Lightning Punch
6249
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_HAYMAKER]);
6250
+ hitDescription->requiredLevel = 9 ;
6251
+ hitDescription->requiredSkill = 100 ;
6252
+ hitDescription->requiredStats [STAT_STRENGTH] = 5 ;
6253
+ hitDescription->requiredStats [STAT_AGILITY] = 7 ;
6254
+ hitDescription->minDamage = 1 ;
6255
+ hitDescription->maxDamage = 2 ;
6256
+ hitDescription->bonusDamage = 7 ;
6257
+ hitDescription->bonusCriticalChance = 15 ;
6258
+ hitDescription->actionPointCost = 3 ;
6259
+
6260
+ // Chop Punch
6261
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_JAB]);
6262
+ hitDescription->requiredLevel = 5 ;
6263
+ hitDescription->requiredSkill = 75 ;
6264
+ hitDescription->requiredStats [STAT_STRENGTH] = 5 ;
6265
+ hitDescription->requiredStats [STAT_AGILITY] = 7 ;
6266
+ hitDescription->minDamage = 1 ;
6267
+ hitDescription->maxDamage = 2 ;
6268
+ hitDescription->bonusDamage = 3 ;
6269
+ hitDescription->bonusCriticalChance = 10 ;
6270
+ hitDescription->actionPointCost = 3 ;
6271
+ hitDescription->isSecondary = true ;
6272
+
6273
+ // Dragon Punch
6274
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_PALM_STRIKE]);
6275
+ hitDescription->requiredLevel = 12 ;
6276
+ hitDescription->requiredSkill = 115 ;
6277
+ hitDescription->requiredStats [STAT_STRENGTH] = 5 ;
6278
+ hitDescription->requiredStats [STAT_AGILITY] = 7 ;
6279
+ hitDescription->minDamage = 1 ;
6280
+ hitDescription->maxDamage = 2 ;
6281
+ hitDescription->bonusDamage = 7 ;
6282
+ hitDescription->bonusCriticalChance = 20 ;
6283
+ hitDescription->actionPointCost = 6 ;
6284
+ hitDescription->isPenetrate = true ;
6285
+ hitDescription->isSecondary = true ;
6286
+
6287
+ // Force Punch
6288
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_PIERCING_STRIKE]);
6289
+ hitDescription->requiredLevel = 16 ;
6290
+ hitDescription->requiredSkill = 130 ;
6291
+ hitDescription->requiredStats [STAT_STRENGTH] = 5 ;
6292
+ hitDescription->requiredStats [STAT_AGILITY] = 7 ;
6293
+ hitDescription->minDamage = 1 ;
6294
+ hitDescription->maxDamage = 2 ;
6295
+ hitDescription->bonusDamage = 10 ;
6296
+ hitDescription->bonusCriticalChance = 40 ;
6297
+ hitDescription->actionPointCost = 8 ;
6298
+ hitDescription->isPenetrate = true ;
6299
+ hitDescription->isSecondary = true ;
6300
+
6301
+ // Kick
6302
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_KICK]);
6303
+ hitDescription->minDamage = 1 ;
6304
+ hitDescription->maxDamage = 2 ;
6305
+ hitDescription->actionPointCost = 3 ;
6306
+
6307
+ // Strong Kick
6308
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_STRONG_KICK]);
6309
+ hitDescription->requiredSkill = 40 ;
6310
+ hitDescription->requiredStats [STAT_AGILITY] = 6 ;
6311
+ hitDescription->minDamage = 1 ;
6312
+ hitDescription->maxDamage = 2 ;
6313
+ hitDescription->bonusDamage = 5 ;
6314
+ hitDescription->actionPointCost = 4 ;
6315
+
6316
+ // Snap Kick
6317
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_SNAP_KICK]);
6318
+ hitDescription->requiredLevel = 6 ;
6319
+ hitDescription->requiredSkill = 60 ;
6320
+ hitDescription->requiredStats [STAT_AGILITY] = 6 ;
6321
+ hitDescription->minDamage = 1 ;
6322
+ hitDescription->maxDamage = 2 ;
6323
+ hitDescription->bonusDamage = 7 ;
6324
+ hitDescription->actionPointCost = 4 ;
6325
+
6326
+ // Roundhouse Kick
6327
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_POWER_KICK]);
6328
+ hitDescription->requiredLevel = 9 ;
6329
+ hitDescription->requiredSkill = 80 ;
6330
+ hitDescription->requiredStats [STAT_STRENGTH] = 6 ;
6331
+ hitDescription->requiredStats [STAT_AGILITY] = 6 ;
6332
+ hitDescription->minDamage = 1 ;
6333
+ hitDescription->maxDamage = 2 ;
6334
+ hitDescription->bonusDamage = 9 ;
6335
+ hitDescription->bonusCriticalChance = 5 ;
6336
+ hitDescription->actionPointCost = 4 ;
6337
+
6338
+ // Kip Kick
6339
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_HIP_KICK]);
6340
+ hitDescription->requiredLevel = 6 ;
6341
+ hitDescription->requiredSkill = 60 ;
6342
+ hitDescription->requiredStats [STAT_STRENGTH] = 6 ;
6343
+ hitDescription->requiredStats [STAT_AGILITY] = 7 ;
6344
+ hitDescription->minDamage = 1 ;
6345
+ hitDescription->maxDamage = 2 ;
6346
+ hitDescription->bonusDamage = 7 ;
6347
+ hitDescription->actionPointCost = 7 ;
6348
+ hitDescription->isSecondary = true ;
6349
+
6350
+ // Jump Kick
6351
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_HOOK_KICK]);
6352
+ hitDescription->requiredLevel = 12 ;
6353
+ hitDescription->requiredSkill = 100 ;
6354
+ hitDescription->requiredStats [STAT_STRENGTH] = 6 ;
6355
+ hitDescription->requiredStats [STAT_AGILITY] = 7 ;
6356
+ hitDescription->minDamage = 1 ;
6357
+ hitDescription->maxDamage = 2 ;
6358
+ hitDescription->bonusDamage = 9 ;
6359
+ hitDescription->bonusCriticalChance = 10 ;
6360
+ hitDescription->actionPointCost = 7 ;
6361
+ hitDescription->isPenetrate = true ;
6362
+ hitDescription->isSecondary = true ;
6363
+
6364
+ // Death Blossom Kick
6365
+ hitDescription = &(gUnarmedHitDescriptions [HIT_MODE_PIERCING_KICK]);
6366
+ hitDescription->requiredLevel = 15 ;
6367
+ hitDescription->requiredSkill = 125 ;
6368
+ hitDescription->requiredStats [STAT_STRENGTH] = 6 ;
6369
+ hitDescription->requiredStats [STAT_AGILITY] = 8 ;
6370
+ hitDescription->minDamage = 1 ;
6371
+ hitDescription->maxDamage = 2 ;
6372
+ hitDescription->bonusDamage = 12 ;
6373
+ hitDescription->bonusCriticalChance = 50 ;
6374
+ hitDescription->actionPointCost = 9 ;
6375
+ hitDescription->isPenetrate = true ;
6376
+ hitDescription->isSecondary = true ;
6377
+ }
6378
+
6379
+ static void unarmedInitCustom ()
6380
+ {
6381
+ char * unarmedFileName = NULL ;
6382
+ configGetString (&gSfallConfig , SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_UNARMED_FILE_KEY, &unarmedFileName);
6383
+ if (unarmedFileName != NULL && *unarmedFileName == ' \0 ' ) {
6384
+ unarmedFileName = NULL ;
6385
+ }
6386
+
6387
+ if (unarmedFileName == NULL ) {
6388
+ return ;
6389
+ }
6390
+
6391
+ Config unarmedConfig;
6392
+ if (configInit (&unarmedConfig)) {
6393
+ if (configRead (&unarmedConfig, unarmedFileName, false )) {
6394
+ char section[4 ];
6395
+ char statKey[6 ];
6396
+
6397
+ for (int hitMode = 0 ; hitMode < HIT_MODE_COUNT; hitMode++) {
6398
+ if (!isUnarmedHitMode (hitMode)) {
6399
+ continue ;
6400
+ }
6401
+
6402
+ UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions [hitMode]);
6403
+ sprintf (section, " %d" , hitMode);
6404
+
6405
+ configGetInt (&unarmedConfig, section, " ReqLevel" , &(hitDescription->requiredLevel ));
6406
+ configGetInt (&unarmedConfig, section, " SkillLevel" , &(hitDescription->requiredSkill ));
6407
+ configGetInt (&unarmedConfig, section, " MinDamage" , &(hitDescription->minDamage ));
6408
+ configGetInt (&unarmedConfig, section, " MaxDamage" , &(hitDescription->maxDamage ));
6409
+ configGetInt (&unarmedConfig, section, " BonusDamage" , &(hitDescription->bonusDamage ));
6410
+ configGetInt (&unarmedConfig, section, " BonusCrit" , &(hitDescription->bonusCriticalChance ));
6411
+ configGetInt (&unarmedConfig, section, " APCost" , &(hitDescription->actionPointCost ));
6412
+ configGetBool (&unarmedConfig, section, " BonusDamage" , &(hitDescription->isPenetrate ));
6413
+ configGetBool (&unarmedConfig, section, " Secondary" , &(hitDescription->isSecondary ));
6414
+
6415
+ for (int stat = 0 ; stat < PRIMARY_STAT_COUNT; stat++) {
6416
+ sprintf (statKey, " Stat%d" , stat);
6417
+ configGetInt (&unarmedConfig, section, statKey, &(hitDescription->requiredStats [stat]));
6418
+ }
6419
+ }
6420
+ }
6421
+
6422
+ configFree (&unarmedConfig);
6423
+ }
6424
+ }
6425
+
6426
+ int unarmedGetDamage (int hitMode, int * minDamagePtr, int * maxDamagePtr)
6427
+ {
6428
+ UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions [hitMode]);
6429
+ *minDamagePtr = hitDescription->minDamage ;
6430
+ *maxDamagePtr = hitDescription->maxDamage ;
6431
+ return hitDescription->bonusDamage ;
6432
+ }
6433
+
6434
+ int unarmedGetBonusCriticalChance (int hitMode)
6435
+ {
6436
+ UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions [hitMode]);
6437
+ return hitDescription->bonusCriticalChance ;
6438
+ }
6439
+
6440
+ int unarmedGetActionPointCost (int hitMode)
6441
+ {
6442
+ UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions [hitMode]);
6443
+ return hitDescription->actionPointCost ;
6444
+ }
6445
+
6446
+ bool unarmedIsPenetrating (int hitMode)
6447
+ {
6448
+ UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions [hitMode]);
6449
+ return hitDescription->isPenetrate ;
6450
+ }
6451
+
6452
+ int unarmedGetPunchHitMode (bool isSecondary)
6453
+ {
6454
+ int hitMode = unarmedGetHitModeInRange (FIRST_ADVANCED_PUNCH_HIT_MODE, LAST_ADVANCED_PUNCH_HIT_MODE, isSecondary);
6455
+ if (hitMode == -1 ) {
6456
+ hitMode = HIT_MODE_PUNCH;
6457
+ }
6458
+ return hitMode;
6459
+ }
6460
+
6461
+ int unarmedGetKickHitMode (bool isSecondary)
6462
+ {
6463
+ int hitMode = unarmedGetHitModeInRange (FIRST_ADVANCED_KICK_HIT_MODE, LAST_ADVANCED_KICK_HIT_MODE, isSecondary);
6464
+ if (hitMode == -1 ) {
6465
+ hitMode = HIT_MODE_KICK;
6466
+ }
6467
+ return hitMode;
6468
+ }
6469
+
6470
+ static int unarmedGetHitModeInRange (int firstHitMode, int lastHitMode, bool isSecondary)
6471
+ {
6472
+ int hitMode = -1 ;
6473
+
6474
+ int unarmed = skillGetValue (gDude , SKILL_UNARMED);
6475
+ int level = pcGetStat (PC_STAT_LEVEL);
6476
+ int stats[PRIMARY_STAT_COUNT];
6477
+ for (int stat = 0 ; stat < PRIMARY_STAT_COUNT; stat++) {
6478
+ stats[stat] = critterGetStat (gDude , stat);
6479
+ }
6480
+
6481
+ for (int candidateHitMode = firstHitMode; candidateHitMode <= lastHitMode; candidateHitMode++) {
6482
+ UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions [candidateHitMode]);
6483
+ if (isSecondary != hitDescription->isSecondary ) {
6484
+ continue ;
6485
+ }
6486
+
6487
+ if (unarmed < hitDescription->requiredSkill ) {
6488
+ continue ;
6489
+ }
6490
+
6491
+ if (level < hitDescription->requiredLevel ) {
6492
+ continue ;
6493
+ }
6494
+
6495
+ bool missingStats = false ;
6496
+ for (int stat = 0 ; stat < PRIMARY_STAT_COUNT; stat++) {
6497
+ if (stats[stat] < hitDescription->requiredStats [stat]) {
6498
+ missingStats = true ;
6499
+ break ;
6500
+ }
6501
+ }
6502
+ if (missingStats) {
6503
+ continue ;
6504
+ }
6505
+
6506
+ hitMode = candidateHitMode;
6507
+ }
6508
+
6509
+ return hitMode;
6510
+ }
0 commit comments