diff --git a/src/combat.cc b/src/combat.cc index f49c5be..9aab99c 100644 --- a/src/combat.cc +++ b/src/combat.cc @@ -54,6 +54,19 @@ typedef struct CombatAiInfo { int lastMove; } CombatAiInfo; +typedef struct UnarmedHitDescription { + int requiredLevel; + int requiredSkill; + int requiredStats[PRIMARY_STAT_COUNT]; + int minDamage; + int maxDamage; + int bonusDamage; + int bonusCriticalChance; + int actionPointCost; + bool isPenetrate; + bool isSecondary; +} UnarmedHitDescription; + static bool _combat_safety_invalidate_weapon_func(Object* critter, Object* weapon, int hitMode, Object* a4, int* a5, Object* a6); static int aiInfoCopy(int srcIndex, int destIndex); static void _combat_begin(Object* a1); @@ -96,6 +109,10 @@ static void criticalsReset(); static void criticalsExit(); static void burstModInit(); static int burstModComputeRounds(int totalRounds, int* centerRoundsPtr, int* leftRoundsPtr, int* rightRoundsPtr); +static void unarmedInit(); +static void unarmedInitVanilla(); +static void unarmedInitCustom(); +static int unarmedGetHitModeInRange(int firstHitMode, int lastHitMode, bool isSecondary); // 0x500B50 static char _a_1[] = "."; @@ -1932,6 +1949,7 @@ static int gBurstModCenterMultiplier = SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MUL static int gBurstModCenterDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR; static int gBurstModTargetMultiplier = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_MULTIPLIER; static int gBurstModTargetDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_DIVISOR; +static UnarmedHitDescription gUnarmedHitDescriptions[HIT_MODE_COUNT]; // combat_init // 0x420CC0 @@ -1974,6 +1992,7 @@ int combatInit() // SFALL criticalsInit(); burstModInit(); + unarmedInit(); return 0; } @@ -3757,13 +3776,12 @@ static int attackCompute(Attack* attack) damageMultiplier = 4; } - if (((attack->hitMode == HIT_MODE_HAMMER_PUNCH || attack->hitMode == HIT_MODE_POWER_KICK) && randomBetween(1, 100) <= 5) - || ((attack->hitMode == HIT_MODE_JAB || attack->hitMode == HIT_MODE_HOOK_KICK) && randomBetween(1, 100) <= 10) - || (attack->hitMode == HIT_MODE_HAYMAKER && randomBetween(1, 100) <= 15) - || (attack->hitMode == HIT_MODE_PALM_STRIKE && randomBetween(1, 100) <= 20) - || (attack->hitMode == HIT_MODE_PIERCING_STRIKE && randomBetween(1, 100) <= 40) - || (attack->hitMode == HIT_MODE_PIERCING_KICK && randomBetween(1, 100) <= 50)) { - roll = ROLL_CRITICAL_SUCCESS; + // SFALL + int bonusCriticalChance = unarmedGetBonusCriticalChance(attack->hitMode); + if (bonusCriticalChance != 0) { + if (randomBetween(1, 100) <= bonusCriticalChance) { + roll = ROLL_CRITICAL_SUCCESS; + } } } } @@ -4203,7 +4221,7 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in bool isRangedWeapon = false; int accuracy; - if (weapon == NULL || hitMode == HIT_MODE_PUNCH || hitMode == HIT_MODE_KICK || (hitMode >= FIRST_ADVANCED_UNARMED_HIT_MODE && hitMode <= LAST_ADVANCED_UNARMED_HIT_MODE)) { + if (weapon == NULL || isUnarmedHitMode(hitMode)) { accuracy = skillGetValue(attacker, SKILL_UNARMED); } else { accuracy = _item_w_skill_level(attacker, hitMode); @@ -4413,11 +4431,9 @@ static void attackComputeDamage(Attack* attack, int ammoQuantity, int bonusDamag damageThreshold = 20 * damageThreshold / 100; damageResistance = 20 * damageResistance / 100; } else { + // SFALL if (weaponGetPerk(attack->weapon) == PERK_WEAPON_PENETRATE - || attack->hitMode == HIT_MODE_PALM_STRIKE - || attack->hitMode == HIT_MODE_PIERCING_STRIKE - || attack->hitMode == HIT_MODE_HOOK_KICK - || attack->hitMode == HIT_MODE_PIERCING_KICK) { + || unarmedIsPenetrating(attack->hitMode)) { damageThreshold = 20 * damageThreshold / 100; } @@ -6189,3 +6205,306 @@ static int burstModComputeRounds(int totalRounds, int* centerRoundsPtr, int* lef return mainTargetRounds; } + +static void unarmedInit() +{ + unarmedInitVanilla(); + unarmedInitCustom(); +} + +static void unarmedInitVanilla() +{ + UnarmedHitDescription* hitDescription; + + // Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_PUNCH]); + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->actionPointCost = 3; + + // Strong Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_STRONG_PUNCH]); + hitDescription->requiredSkill = 55; + hitDescription->requiredStats[STAT_AGILITY] = 6; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 3; + hitDescription->actionPointCost = 3; + hitDescription->isPenetrate = false; + hitDescription->isSecondary = false; + + // Hammer Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_HAMMER_PUNCH]); + hitDescription->requiredLevel = 6; + hitDescription->requiredSkill = 75; + hitDescription->requiredStats[STAT_STRENGTH] = 5; + hitDescription->requiredStats[STAT_AGILITY] = 6; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 5; + hitDescription->bonusCriticalChance = 5; + hitDescription->actionPointCost = 3; + + // Lightning Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_HAYMAKER]); + hitDescription->requiredLevel = 9; + hitDescription->requiredSkill = 100; + hitDescription->requiredStats[STAT_STRENGTH] = 5; + hitDescription->requiredStats[STAT_AGILITY] = 7; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 7; + hitDescription->bonusCriticalChance = 15; + hitDescription->actionPointCost = 3; + + // Chop Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_JAB]); + hitDescription->requiredLevel = 5; + hitDescription->requiredSkill = 75; + hitDescription->requiredStats[STAT_STRENGTH] = 5; + hitDescription->requiredStats[STAT_AGILITY] = 7; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 3; + hitDescription->bonusCriticalChance = 10; + hitDescription->actionPointCost = 3; + hitDescription->isSecondary = true; + + // Dragon Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_PALM_STRIKE]); + hitDescription->requiredLevel = 12; + hitDescription->requiredSkill = 115; + hitDescription->requiredStats[STAT_STRENGTH] = 5; + hitDescription->requiredStats[STAT_AGILITY] = 7; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 7; + hitDescription->bonusCriticalChance = 20; + hitDescription->actionPointCost = 6; + hitDescription->isPenetrate = true; + hitDescription->isSecondary = true; + + // Force Punch + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_PIERCING_STRIKE]); + hitDescription->requiredLevel = 16; + hitDescription->requiredSkill = 130; + hitDescription->requiredStats[STAT_STRENGTH] = 5; + hitDescription->requiredStats[STAT_AGILITY] = 7; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 10; + hitDescription->bonusCriticalChance = 40; + hitDescription->actionPointCost = 8; + hitDescription->isPenetrate = true; + hitDescription->isSecondary = true; + + // Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_KICK]); + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->actionPointCost = 3; + + // Strong Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_STRONG_KICK]); + hitDescription->requiredSkill = 40; + hitDescription->requiredStats[STAT_AGILITY] = 6; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 5; + hitDescription->actionPointCost = 4; + + // Snap Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_SNAP_KICK]); + hitDescription->requiredLevel = 6; + hitDescription->requiredSkill = 60; + hitDescription->requiredStats[STAT_AGILITY] = 6; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 7; + hitDescription->actionPointCost = 4; + + // Roundhouse Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_POWER_KICK]); + hitDescription->requiredLevel = 9; + hitDescription->requiredSkill = 80; + hitDescription->requiredStats[STAT_STRENGTH] = 6; + hitDescription->requiredStats[STAT_AGILITY] = 6; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 9; + hitDescription->bonusCriticalChance = 5; + hitDescription->actionPointCost = 4; + + // Kip Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_HIP_KICK]); + hitDescription->requiredLevel = 6; + hitDescription->requiredSkill = 60; + hitDescription->requiredStats[STAT_STRENGTH] = 6; + hitDescription->requiredStats[STAT_AGILITY] = 7; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 7; + hitDescription->actionPointCost = 7; + hitDescription->isSecondary = true; + + // Jump Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_HOOK_KICK]); + hitDescription->requiredLevel = 12; + hitDescription->requiredSkill = 100; + hitDescription->requiredStats[STAT_STRENGTH] = 6; + hitDescription->requiredStats[STAT_AGILITY] = 7; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 9; + hitDescription->bonusCriticalChance = 10; + hitDescription->actionPointCost = 7; + hitDescription->isPenetrate = true; + hitDescription->isSecondary = true; + + // Death Blossom Kick + hitDescription = &(gUnarmedHitDescriptions[HIT_MODE_PIERCING_KICK]); + hitDescription->requiredLevel = 15; + hitDescription->requiredSkill = 125; + hitDescription->requiredStats[STAT_STRENGTH] = 6; + hitDescription->requiredStats[STAT_AGILITY] = 8; + hitDescription->minDamage = 1; + hitDescription->maxDamage = 2; + hitDescription->bonusDamage = 12; + hitDescription->bonusCriticalChance = 50; + hitDescription->actionPointCost = 9; + hitDescription->isPenetrate = true; + hitDescription->isSecondary = true; +} + +static void unarmedInitCustom() +{ + char* unarmedFileName = NULL; + configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_UNARMED_FILE_KEY, &unarmedFileName); + if (unarmedFileName != NULL && *unarmedFileName == '\0') { + unarmedFileName = NULL; + } + + if (unarmedFileName == NULL) { + return; + } + + Config unarmedConfig; + if (configInit(&unarmedConfig)) { + if (configRead(&unarmedConfig, unarmedFileName, false)) { + char section[4]; + char statKey[6]; + + for (int hitMode = 0; hitMode < HIT_MODE_COUNT; hitMode++) { + if (!isUnarmedHitMode(hitMode)) { + continue; + } + + UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]); + sprintf(section, "%d", hitMode); + + configGetInt(&unarmedConfig, section, "ReqLevel", &(hitDescription->requiredLevel)); + configGetInt(&unarmedConfig, section, "SkillLevel", &(hitDescription->requiredSkill)); + configGetInt(&unarmedConfig, section, "MinDamage", &(hitDescription->minDamage)); + configGetInt(&unarmedConfig, section, "MaxDamage", &(hitDescription->maxDamage)); + configGetInt(&unarmedConfig, section, "BonusDamage", &(hitDescription->bonusDamage)); + configGetInt(&unarmedConfig, section, "BonusCrit", &(hitDescription->bonusCriticalChance)); + configGetInt(&unarmedConfig, section, "APCost", &(hitDescription->actionPointCost)); + configGetBool(&unarmedConfig, section, "BonusDamage", &(hitDescription->isPenetrate)); + configGetBool(&unarmedConfig, section, "Secondary", &(hitDescription->isSecondary)); + + for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { + sprintf(statKey, "Stat%d", stat); + configGetInt(&unarmedConfig, section, statKey, &(hitDescription->requiredStats[stat])); + } + } + } + + configFree(&unarmedConfig); + } +} + +int unarmedGetDamage(int hitMode, int* minDamagePtr, int* maxDamagePtr) +{ + UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]); + *minDamagePtr = hitDescription->minDamage; + *maxDamagePtr = hitDescription->maxDamage; + return hitDescription->bonusDamage; +} + +int unarmedGetBonusCriticalChance(int hitMode) +{ + UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]); + return hitDescription->bonusCriticalChance; +} + +int unarmedGetActionPointCost(int hitMode) +{ + UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]); + return hitDescription->actionPointCost; +} + +bool unarmedIsPenetrating(int hitMode) +{ + UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[hitMode]); + return hitDescription->isPenetrate; +} + +int unarmedGetPunchHitMode(bool isSecondary) +{ + int hitMode = unarmedGetHitModeInRange(FIRST_ADVANCED_PUNCH_HIT_MODE, LAST_ADVANCED_PUNCH_HIT_MODE, isSecondary); + if (hitMode == -1) { + hitMode = HIT_MODE_PUNCH; + } + return hitMode; +} + +int unarmedGetKickHitMode(bool isSecondary) +{ + int hitMode = unarmedGetHitModeInRange(FIRST_ADVANCED_KICK_HIT_MODE, LAST_ADVANCED_KICK_HIT_MODE, isSecondary); + if (hitMode == -1) { + hitMode = HIT_MODE_KICK; + } + return hitMode; +} + +static int unarmedGetHitModeInRange(int firstHitMode, int lastHitMode, bool isSecondary) +{ + int hitMode = -1; + + int unarmed = skillGetValue(gDude, SKILL_UNARMED); + int level = pcGetStat(PC_STAT_LEVEL); + int stats[PRIMARY_STAT_COUNT]; + for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { + stats[stat] = critterGetStat(gDude, stat); + } + + for (int candidateHitMode = firstHitMode; candidateHitMode <= lastHitMode; candidateHitMode++) { + UnarmedHitDescription* hitDescription = &(gUnarmedHitDescriptions[candidateHitMode]); + if (isSecondary != hitDescription->isSecondary) { + continue; + } + + if (unarmed < hitDescription->requiredSkill) { + continue; + } + + if (level < hitDescription->requiredLevel) { + continue; + } + + bool missingStats = false; + for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { + if (stats[stat] < hitDescription->requiredStats[stat]) { + missingStats = true; + break; + } + } + if (missingStats) { + continue; + } + + hitMode = candidateHitMode; + } + + return hitMode; +} diff --git a/src/combat.h b/src/combat.h index 411487d..652c3fb 100644 --- a/src/combat.h +++ b/src/combat.h @@ -59,10 +59,24 @@ void _combatKillCritterOutsideCombat(Object* critter_obj, char* msg); int criticalsGetValue(int killType, int hitLocation, int effect, int dataMember); void criticalsSetValue(int killType, int hitLocation, int effect, int dataMember, int value); void criticalsResetValue(int killType, int hitLocation, int effect, int dataMember); +int unarmedGetDamage(int hitMode, int* minDamagePtr, int* maxDamagePtr); +int unarmedGetBonusCriticalChance(int hitMode); +int unarmedGetActionPointCost(int hitMode); +bool unarmedIsPenetrating(int hitMode); +int unarmedGetPunchHitMode(bool isSecondary); +int unarmedGetKickHitMode(bool isSecondary); +bool unarmedIsPenetrating(int hitMode); static inline bool isInCombat() { return (gCombatState & COMBAT_STATE_0x01) != 0; } +static inline bool isUnarmedHitMode(int hitMode) +{ + return hitMode == HIT_MODE_PUNCH + || hitMode == HIT_MODE_KICK + || (hitMode >= FIRST_ADVANCED_UNARMED_HIT_MODE && hitMode <= LAST_ADVANCED_UNARMED_HIT_MODE); +} + #endif /* COMBAT_H */ diff --git a/src/interface.cc b/src/interface.cc index 954c4da..50e7dc0 100644 --- a/src/interface.cc +++ b/src/interface.cc @@ -1264,30 +1264,9 @@ int interfaceUpdateItems(bool animated, int leftItemAction, int rightItemAction) leftItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; leftItemState->itemFid = -1; - int unarmed = skillGetValue(gDude, SKILL_UNARMED); - int agility = critterGetStat(gDude, STAT_AGILITY); - int strength = critterGetStat(gDude, STAT_STRENGTH); - int level = pcGetStat(PC_STAT_LEVEL); - - if (unarmed > 99 && agility > 6 && strength > 4 && level > 8) { - leftItemState->primaryHitMode = HIT_MODE_HAYMAKER; - } else if (unarmed > 74 && agility > 5 && strength > 4 && level > 5) { - leftItemState->primaryHitMode = HIT_MODE_HAMMER_PUNCH; - } else if (unarmed > 54 && agility > 5) { - leftItemState->primaryHitMode = HIT_MODE_STRONG_PUNCH; - } else { - leftItemState->primaryHitMode = HIT_MODE_PUNCH; - } - - if (unarmed > 129 && agility > 6 && strength > 4 && level > 15) { - leftItemState->secondaryHitMode = HIT_MODE_PIERCING_STRIKE; - } else if (unarmed > 114 && agility > 6 && strength > 4 && level > 11) { - leftItemState->secondaryHitMode = HIT_MODE_PALM_STRIKE; - } else if (unarmed > 74 && agility > 6 && strength > 4 && level > 4) { - leftItemState->secondaryHitMode = HIT_MODE_JAB; - } else { - leftItemState->secondaryHitMode = HIT_MODE_PUNCH; - } + // SFALL + leftItemState->primaryHitMode = unarmedGetPunchHitMode(false); + leftItemState->secondaryHitMode = unarmedGetPunchHitMode(true); } } @@ -1324,30 +1303,9 @@ int interfaceUpdateItems(bool animated, int leftItemAction, int rightItemAction) rightItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; rightItemState->itemFid = -1; - int unarmed = skillGetValue(gDude, SKILL_UNARMED); - int agility = critterGetStat(gDude, STAT_AGILITY); - int strength = critterGetStat(gDude, STAT_STRENGTH); - int level = pcGetStat(PC_STAT_LEVEL); - - if (unarmed > 79 && agility > 5 && strength > 5 && level > 8) { - rightItemState->primaryHitMode = HIT_MODE_POWER_KICK; - } else if (unarmed > 59 && agility > 5 && level > 5) { - rightItemState->primaryHitMode = HIT_MODE_SNAP_KICK; - } else if (unarmed > 39 && agility > 5) { - rightItemState->primaryHitMode = HIT_MODE_STRONG_KICK; - } else { - rightItemState->primaryHitMode = HIT_MODE_KICK; - } - - if (unarmed > 124 && agility > 7 && strength > 5 && level > 14) { - rightItemState->secondaryHitMode = HIT_MODE_PIERCING_KICK; - } else if (unarmed > 99 && agility > 6 && strength > 5 && level > 11) { - rightItemState->secondaryHitMode = HIT_MODE_HOOK_KICK; - } else if (unarmed > 59 && agility > 6 && strength > 5 && level > 5) { - rightItemState->secondaryHitMode = HIT_MODE_HIP_KICK; - } else { - rightItemState->secondaryHitMode = HIT_MODE_KICK; - } + // SFALL + rightItemState->primaryHitMode = unarmedGetKickHitMode(false); + rightItemState->secondaryHitMode = unarmedGetKickHitMode(true); } } diff --git a/src/inventory.cc b/src/inventory.cc index 83bd82e..c962936 100644 --- a/src/inventory.cc +++ b/src/inventory.cc @@ -2433,6 +2433,11 @@ static void inventoryRenderSummary() HIT_MODE_RIGHT_WEAPON_PRIMARY, }; + const int unarmedHitModes[2] = { + HIT_MODE_PUNCH, + HIT_MODE_KICK, + }; + offset += 499 * fontGetLineHeight(); for (int index = 0; index < 2; index += 1) { @@ -2451,10 +2456,33 @@ static void inventoryRenderSummary() // Unarmed dmg: messageListItem.num = 24; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { - // TODO: Figure out why it uses STAT_MELEE_DAMAGE instead of - // STAT_UNARMED_DAMAGE. - int damage = critterGetStat(_stack[0], STAT_MELEE_DAMAGE) + 2; - sprintf(formattedText, "%s 1-%d", messageListItem.text, damage); + // SFALL: Display the actual damage values of unarmed attacks. + // CE: Implementation is different. + int hitMode = unarmedHitModes[index]; + if (_stack[0] == gDude) { + int actions[2]; + interfaceGetItemActions(&(actions[0]), &(actions[1])); + + bool isSecondary = actions[index] == INTERFACE_ITEM_ACTION_SECONDARY || + actions[index] == INTERFACE_ITEM_ACTION_SECONDARY_AIMING; + + if (index == HAND_LEFT) { + hitMode = unarmedGetPunchHitMode(isSecondary); + } else { + hitMode = unarmedGetKickHitMode(isSecondary); + } + } + + // Formula is the same as in `weaponGetMeleeDamage`. + int minDamage; + int maxDamage; + int bonusDamage = unarmedGetDamage(hitMode, &minDamage, &maxDamage); + int meleeDamage = critterGetStat(_stack[0], STAT_MELEE_DAMAGE); + // TODO: Localize unarmed attack names. + sprintf(formattedText, "%s %d-%d", + messageListItem.text, + bonusDamage + minDamage, + bonusDamage + meleeDamage + maxDamage); } fontDrawText(windowBuffer + offset, formattedText, 120, 499, _colorTable[992]); diff --git a/src/item.cc b/src/item.cc index a25e47e..f4d3489 100644 --- a/src/item.cc +++ b/src/item.cc @@ -1261,7 +1261,7 @@ int weaponGetMeleeDamage(Object* critter, int hitMode) int minDamage = 0; int maxDamage = 0; int meleeDamage = 0; - int unarmedDamage = 0; + int bonusDamage = 0; // NOTE: Uninline. Object* weapon = critterGetWeaponForHitMode(critter, hitMode); @@ -1278,38 +1278,12 @@ int weaponGetMeleeDamage(Object* critter, int hitMode) meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE); } } else { - minDamage = 1; - maxDamage = critterGetStat(critter, STAT_MELEE_DAMAGE) + 2; - - switch (hitMode) { - case HIT_MODE_STRONG_PUNCH: - case HIT_MODE_JAB: - unarmedDamage = 3; - break; - case HIT_MODE_HAMMER_PUNCH: - case HIT_MODE_STRONG_KICK: - unarmedDamage = 4; - break; - case HIT_MODE_HAYMAKER: - case HIT_MODE_PALM_STRIKE: - case HIT_MODE_SNAP_KICK: - case HIT_MODE_HIP_KICK: - unarmedDamage = 7; - break; - case HIT_MODE_POWER_KICK: - case HIT_MODE_HOOK_KICK: - unarmedDamage = 9; - break; - case HIT_MODE_PIERCING_STRIKE: - unarmedDamage = 10; - break; - case HIT_MODE_PIERCING_KICK: - unarmedDamage = 12; - break; - } + // SFALL + bonusDamage = unarmedGetDamage(hitMode, &minDamage, &maxDamage); + meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE); } - return randomBetween(unarmedDamage + minDamage, unarmedDamage + meleeDamage + maxDamage); + return randomBetween(bonusDamage + minDamage, bonusDamage + meleeDamage + maxDamage); } // 0x478570 @@ -1678,28 +1652,11 @@ int _item_w_mp_cost(Object* critter, int hitMode, bool aiming) return 2; } - switch (hitMode) { - case HIT_MODE_PALM_STRIKE: - actionPoints = 6; - break; - case HIT_MODE_PIERCING_STRIKE: - actionPoints = 8; - break; - case HIT_MODE_STRONG_KICK: - case HIT_MODE_SNAP_KICK: - case HIT_MODE_POWER_KICK: - actionPoints = 4; - break; - case HIT_MODE_HIP_KICK: - case HIT_MODE_HOOK_KICK: - actionPoints = 7; - break; - case HIT_MODE_PIERCING_KICK: - actionPoints = 9; - break; - default: - // TODO: Inverse conditions. - if (weapon != NULL && hitMode != HIT_MODE_PUNCH && hitMode != HIT_MODE_KICK && hitMode != HIT_MODE_STRONG_PUNCH && hitMode != HIT_MODE_HAMMER_PUNCH && hitMode != HIT_MODE_HAYMAKER) { + // CE: The entire function is different in Sfall. + if (isUnarmedHitMode(hitMode)) { + actionPoints = unarmedGetActionPointCost(hitMode); + } else { + if (weapon != NULL) { if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) { // NOTE: Uninline. actionPoints = weaponGetActionPointCost1(weapon); @@ -1718,7 +1675,6 @@ int _item_w_mp_cost(Object* critter, int hitMode, bool aiming) } else { actionPoints = 3; } - break; } if (critter == gDude) { diff --git a/src/sfall_config.h b/src/sfall_config.h index 05bb1aa..569cf09 100644 --- a/src/sfall_config.h +++ b/src/sfall_config.h @@ -41,6 +41,7 @@ #define SFALL_CONFIG_PLASTIC_EXPLOSIVE_MAX_DAMAGE_KEY "PlasticExplosive_DmgMax" #define SFALL_CONFIG_EXPLOSION_EMITS_LIGHT_KEY "ExplosionsEmitLight" #define SFALL_CONFIG_CITY_REPUTATION_LIST_KEY "CityRepsList" +#define SFALL_CONFIG_UNARMED_FILE_KEY "UnarmedFile" #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1 #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3 diff --git a/src/stat.cc b/src/stat.cc index c756bf2..2be253a 100644 --- a/src/stat.cc +++ b/src/stat.cc @@ -772,6 +772,12 @@ int pcAddExperienceWithOptions(int xp, bool a2) interfaceRenderHitPoints(false); + // SFALL: Update unarmed attack after leveling up. + int leftItemAction; + int rightItemAction; + interfaceGetItemActions(&leftItemAction, &rightItemAction); + interfaceUpdateItems(false, leftItemAction, rightItemAction); + if (a2) { _partyMemberIncLevels(); }