Add damage mod (#29)

This commit is contained in:
Alexander Batalov 2022-08-12 13:54:18 +03:00
parent 5b2a1d13a1
commit d86a887cf9
6 changed files with 314 additions and 35 deletions

View File

@ -2,6 +2,7 @@
#include "art.h" #include "art.h"
#include "color.h" #include "color.h"
#include "combat.h"
#include "core.h" #include "core.h"
#include "critter.h" #include "critter.h"
#include "cycle.h" #include "cycle.h"
@ -2801,7 +2802,13 @@ static void characterEditorDrawDerivedStats()
sprintf(t, "%s", messageListItemText); sprintf(t, "%s", messageListItemText);
fontDrawText(gCharacterEditorWindowBuffer + 640 * y + 194, t, 640, 640, color); fontDrawText(gCharacterEditorWindowBuffer + 640 * y + 194, t, 640, 640, color);
compat_itoa(critterGetStat(gDude, STAT_MELEE_DAMAGE), t, 10); // SFALL: Display melee damage without "Bonus HtH Damage" bonus.
int meleeDamage = critterGetStat(gDude, STAT_MELEE_DAMAGE);
if (!damageModGetDisplayBonusDamage()) {
meleeDamage -= 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE);
}
compat_itoa(meleeDamage, t, 10);
fontDrawText(gCharacterEditorWindowBuffer + 640 * y + 288, t, 640, 640, color); fontDrawText(gCharacterEditorWindowBuffer + 640 * y + 288, t, 640, 640, color);
// Damage Resistance // Damage Resistance
@ -4404,13 +4411,19 @@ static int characterPrintToFile(const char* fileName)
fileWriteString(title1, stream); fileWriteString(title1, stream);
fileWriteString("\n", stream); fileWriteString("\n", stream);
// SFALL: Display melee damage without "Bonus HtH Damage" bonus.
int meleeDamage = critterGetStat(gDude, STAT_MELEE_DAMAGE);
if (!damageModGetDisplayBonusDamage()) {
meleeDamage -= 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE);
}
// Charisma / Melee Damage / Carry Weight // Charisma / Melee Damage / Carry Weight
sprintf(title1, sprintf(title1,
"%s %.2d %s %.2d %s %.3d lbs.", "%s %.2d %s %.2d %s %.3d lbs.",
getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 633), getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 633),
critterGetStat(gDude, STAT_CHARISMA), critterGetStat(gDude, STAT_CHARISMA),
getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 634), getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 634),
critterGetStat(gDude, STAT_MELEE_DAMAGE), meleeDamage,
getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 635), getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 635),
critterGetStat(gDude, STAT_CARRY_WEIGHT)); critterGetStat(gDude, STAT_CARRY_WEIGHT));
fileWriteString(title1, stream); fileWriteString(title1, stream);

View File

@ -47,6 +47,13 @@
#define CALLED_SHOT_WINDOW_WIDTH (504) #define CALLED_SHOT_WINDOW_WIDTH (504)
#define CALLED_SHOT_WINDOW_HEIGHT (309) #define CALLED_SHOT_WINDOW_HEIGHT (309)
typedef enum DamageCalculationType {
DAMAGE_CALCULATION_TYPE_VANILLA = 0,
DAMAGE_CALCULATION_TYPE_GLOVZ = 1,
DAMAGE_CALCULATION_TYPE_GLOVZ_WITH_DAMAGE_MULTIPLIER_TWEAK = 2,
DAMAGE_CALCULATION_TYPE_YAAM = 5,
} DamageCalculationType;
typedef struct CombatAiInfo { typedef struct CombatAiInfo {
Object* friendlyDead; Object* friendlyDead;
Object* lastTarget; Object* lastTarget;
@ -67,6 +74,17 @@ typedef struct UnarmedHitDescription {
bool isSecondary; bool isSecondary;
} UnarmedHitDescription; } UnarmedHitDescription;
typedef struct DamageCalculationContext {
Attack* attack;
int* damagePtr;
int ammoQuantity;
int damageResistance;
int damageThreshold;
int damageBonus;
int bonusDamageMultiplier;
int combatDifficultyDamageModifier;
} DamageCalculationContext;
static bool _combat_safety_invalidate_weapon_func(Object* critter, Object* weapon, int hitMode, Object* a4, int* a5, Object* a6); 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 int aiInfoCopy(int srcIndex, int destIndex);
static void _combat_begin(Object* a1); static void _combat_begin(Object* a1);
@ -113,6 +131,10 @@ static void unarmedInit();
static void unarmedInitVanilla(); static void unarmedInitVanilla();
static void unarmedInitCustom(); static void unarmedInitCustom();
static int unarmedGetHitModeInRange(int firstHitMode, int lastHitMode, bool isSecondary); static int unarmedGetHitModeInRange(int firstHitMode, int lastHitMode, bool isSecondary);
static void damageModInit();
static void damageModCalculateGlovz(DamageCalculationContext* context);
static int damageModGlovzDivRound(int dividend, int divisor);
static void damageModCalculateYaam(DamageCalculationContext* context);
// 0x500B50 // 0x500B50
static char _a_1[] = "."; static char _a_1[] = ".";
@ -1950,6 +1972,9 @@ static int gBurstModCenterDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISO
static int gBurstModTargetMultiplier = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_MULTIPLIER; static int gBurstModTargetMultiplier = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_MULTIPLIER;
static int gBurstModTargetDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_DIVISOR; static int gBurstModTargetDivisor = SFALL_CONFIG_BURST_MOD_DEFAULT_TARGET_DIVISOR;
static UnarmedHitDescription gUnarmedHitDescriptions[HIT_MODE_COUNT]; static UnarmedHitDescription gUnarmedHitDescriptions[HIT_MODE_COUNT];
static int gDamageCalculationType;
static bool gBonusHthDamageFix;
static bool gDisplayBonusDamage;
// combat_init // combat_init
// 0x420CC0 // 0x420CC0
@ -1993,6 +2018,7 @@ int combatInit()
criticalsInit(); criticalsInit();
burstModInit(); burstModInit();
unarmedInit(); unarmedInit();
damageModInit();
return 0; return 0;
} }
@ -4464,6 +4490,21 @@ static void attackComputeDamage(Attack* attack, int ammoQuantity, int bonusDamag
} }
} }
// SFALL: Damage mod.
DamageCalculationContext context;
context.attack = attack;
context.damagePtr = damagePtr;
context.damageResistance = damageResistance;
context.damageThreshold = damageThreshold;
context.damageBonus = damageBonus;
context.bonusDamageMultiplier = bonusDamageMultiplier;
context.combatDifficultyDamageModifier = combatDifficultyDamageModifier;
if (gDamageCalculationType == DAMAGE_CALCULATION_TYPE_GLOVZ || gDamageCalculationType == DAMAGE_CALCULATION_TYPE_GLOVZ_WITH_DAMAGE_MULTIPLIER_TWEAK) {
damageModCalculateGlovz(&context);
} else if (gDamageCalculationType == DAMAGE_CALCULATION_TYPE_YAAM) {
damageModCalculateYaam(&context);
} else {
damageResistance += weaponGetAmmoDamageResistanceModifier(attack->weapon); damageResistance += weaponGetAmmoDamageResistanceModifier(attack->weapon);
if (damageResistance > 100) { if (damageResistance > 100) {
damageResistance = 100; damageResistance = 100;
@ -4501,6 +4542,7 @@ static void attackComputeDamage(Attack* attack, int ammoQuantity, int bonusDamag
*damagePtr += damage; *damagePtr += damage;
} }
} }
}
if (attack->attacker == gDude) { if (attack->attacker == gDude) {
if (perkGetRank(attack->attacker, PERK_LIVING_ANATOMY) != 0) { if (perkGetRank(attack->attacker, PERK_LIVING_ANATOMY) != 0) {
@ -6508,3 +6550,180 @@ static int unarmedGetHitModeInRange(int firstHitMode, int lastHitMode, bool isSe
return hitMode; return hitMode;
} }
static void damageModInit()
{
gDamageCalculationType = DAMAGE_CALCULATION_TYPE_VANILLA;
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DAMAGE_MOD_FORMULA_KEY, &gDamageCalculationType);
gBonusHthDamageFix = true;
configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_BONUS_HTH_DAMAGE_FIX_KEY, &gBonusHthDamageFix);
gDisplayBonusDamage = false;
configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_DISPLAY_BONUS_DAMAGE_KEY, &gDisplayBonusDamage);
}
bool damageModGetBonusHthDamageFix()
{
return gBonusHthDamageFix;
}
bool damageModGetDisplayBonusDamage()
{
return gDisplayBonusDamage;
}
static void damageModCalculateGlovz(DamageCalculationContext* context)
{
int ammoX = weaponGetAmmoDamageMultiplier(context->attack->weapon);
if (ammoX <= 0) {
ammoX = 1;
}
int ammoY = weaponGetAmmoDamageDivisor(context->attack->weapon);
if (ammoY <= 0) {
ammoY = 1;
}
int ammoDamageResistance = weaponGetAmmoDamageResistanceModifier(context->attack->weapon);
if (ammoDamageResistance > 0) {
ammoDamageResistance = -ammoDamageResistance;
}
int calculatedDamageThreshold = context->damageThreshold;
if (calculatedDamageThreshold > 0) {
calculatedDamageThreshold = damageModGlovzDivRound(calculatedDamageThreshold, ammoY);
}
int calculatedDamageResistance = context->damageResistance;
if (calculatedDamageResistance > 0) {
if (context->combatDifficultyDamageModifier > 100) {
calculatedDamageResistance -= 20;
} else if (context->combatDifficultyDamageModifier < 100) {
calculatedDamageResistance += 20;
}
calculatedDamageResistance += ammoDamageResistance;
calculatedDamageResistance = damageModGlovzDivRound(calculatedDamageResistance, ammoX);
if (calculatedDamageResistance >= 100) {
return;
}
}
for (int index = 0; index < context->ammoQuantity; index++) {
int damage = weaponGetMeleeDamage(context->attack->attacker, context->attack->hitMode);
damage += context->damageBonus;
if (damage <= 0) {
continue;
}
if (context->damageThreshold > 0) {
damage -= calculatedDamageThreshold;
if (damage <= 0) {
continue;
}
}
if (context->damageResistance > 0) {
damage -= damageModGlovzDivRound(damage * calculatedDamageResistance, 100);
if (damage <= 0) {
continue;
}
}
if (context->damageThreshold <= 0 && context->damageResistance <= 0) {
if (ammoX > 1 && ammoY > 1) {
damage += damageModGlovzDivRound(damage * 15, 100);
} else if (ammoX > 1) {
damage += damageModGlovzDivRound(damage * 20, 100);
} else if (ammoY > 1) {
damage += damageModGlovzDivRound(damage * 10, 100);
}
}
if (gDamageCalculationType == DAMAGE_CALCULATION_TYPE_GLOVZ_WITH_DAMAGE_MULTIPLIER_TWEAK) {
damage += damageModGlovzDivRound(damage * context->bonusDamageMultiplier * 25, 100);
} else {
damage += damage * context->bonusDamageMultiplier / 2;
}
if (damage > 0) {
*context->damagePtr += damage;
}
}
}
static int damageModGlovzDivRound(int dividend, int divisor)
{
if (dividend < divisor) {
return dividend != divisor && dividend * 2 <= divisor ? 0 : 1;
}
int quotient = dividend / divisor;
dividend %= divisor;
if (dividend == 0) {
return quotient;
}
dividend *= 2;
if (dividend > divisor || (dividend == divisor && (quotient & 1) != 0)) {
quotient += 1;
}
return quotient;
}
static void damageModCalculateYaam(DamageCalculationContext* context)
{
int damageMultiplier = context->bonusDamageMultiplier * weaponGetAmmoDamageMultiplier(context->attack->weapon);
int damageDivisor = weaponGetAmmoDamageDivisor(context->attack->weapon);
int ammoDamageResistance = weaponGetAmmoDamageResistanceModifier(context->attack->weapon);
int calculatedDamageThreshold = context->damageThreshold - ammoDamageResistance;
int damageResistance = calculatedDamageThreshold;
if (calculatedDamageThreshold >= 0) {
damageResistance = 0;
} else {
calculatedDamageThreshold = 0;
damageResistance *= 10;
}
int calculatedDamageResistance = context->damageResistance + damageResistance;
if (calculatedDamageResistance < 0) {
calculatedDamageResistance = 0;
} else if (calculatedDamageResistance >= 100) {
return;
}
for (int index = 0; index < context->ammoQuantity; index++) {
int damage = weaponGetMeleeDamage(context->attack->weapon, context->attack->hitMode);
damage += context->damageBonus;
damage -= calculatedDamageThreshold;
if (damage <= 0) {
continue;
}
damage *= damageMultiplier;
if (damageDivisor != 0) {
damage /= damageDivisor;
}
damage /= 2;
damage *= context->combatDifficultyDamageModifier;
damage /= 100;
damage -= damage * damageResistance / 100;
if (damage > 0) {
context->damagePtr += damage;
}
}
}

View File

@ -66,6 +66,8 @@ bool unarmedIsPenetrating(int hitMode);
int unarmedGetPunchHitMode(bool isSecondary); int unarmedGetPunchHitMode(bool isSecondary);
int unarmedGetKickHitMode(bool isSecondary); int unarmedGetKickHitMode(bool isSecondary);
bool unarmedIsPenetrating(int hitMode); bool unarmedIsPenetrating(int hitMode);
bool damageModGetBonusHthDamageFix();
bool damageModGetDisplayBonusDamage();
static inline bool isInCombat() static inline bool isInCombat()
{ {

View File

@ -2544,6 +2544,11 @@ static void inventoryRenderSummary()
int meleeDamage; int meleeDamage;
if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) {
meleeDamage = critterGetStat(_stack[0], STAT_MELEE_DAMAGE); meleeDamage = critterGetStat(_stack[0], STAT_MELEE_DAMAGE);
// SFALL: Display melee damage without "Bonus HtH Damage" bonus.
if (damageModGetBonusHthDamageFix() && !damageModGetDisplayBonusDamage()) {
meleeDamage -= 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE);
}
} else { } else {
meleeDamage = 0; meleeDamage = 0;
} }
@ -2551,11 +2556,33 @@ static void inventoryRenderSummary()
messageListItem.num = 15; // Dmg: messageListItem.num = 15; // Dmg:
if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { if (messageListGetItem(&gInventoryMessageList, &messageListItem)) {
if (attackType != 4 && range <= 1) { if (attackType != 4 && range <= 1) {
// SFALL: Display bonus damage.
if (damageModGetBonusHthDamageFix() && damageModGetDisplayBonusDamage()) {
// CE: Just in case check for attack type, however it looks
// like we cannot be here with anything besides melee or
// unarmed.
if (_stack[0] == gDude && (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED)) {
// See explanation in `weaponGetMeleeDamage`.
damageMin += 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE);
}
}
sprintf(formattedText, "%s %d-%d", messageListItem.text, damageMin, damageMax + meleeDamage); sprintf(formattedText, "%s %d-%d", messageListItem.text, damageMin, damageMax + meleeDamage);
} else { } else {
MessageListItem rangeMessageListItem; MessageListItem rangeMessageListItem;
rangeMessageListItem.num = 16; // Rng: rangeMessageListItem.num = 16; // Rng:
if (messageListGetItem(&gInventoryMessageList, &rangeMessageListItem)) { if (messageListGetItem(&gInventoryMessageList, &rangeMessageListItem)) {
// SFALL: Display bonus damage.
if (damageModGetDisplayBonusDamage()) {
// CE: There is a bug in Sfall diplaying wrong damage
// bonus for melee weapons with range > 1 (spears,
// sledgehammers) and throwables (secondary mode).
if (_stack[0] == gDude && attackType == ATTACK_TYPE_RANGED) {
int damageBonus = 2 * perkGetRank(gDude, PERK_BONUS_RANGED_DAMAGE);
damageMin += damageBonus;
damageMax += damageBonus;
}
}
sprintf(formattedText, "%s %d-%d %s %d", messageListItem.text, damageMin, damageMax + meleeDamage, rangeMessageListItem.text, range); sprintf(formattedText, "%s %d-%d %s %d", messageListItem.text, damageMin, damageMax + meleeDamage, rangeMessageListItem.text, range);
} }
} }

View File

@ -1267,20 +1267,35 @@ int weaponGetMeleeDamage(Object* critter, int hitMode)
Object* weapon = critterGetWeaponForHitMode(critter, hitMode); Object* weapon = critterGetWeaponForHitMode(critter, hitMode);
if (weapon != NULL) { if (weapon != NULL) {
Proto* proto; // NOTE: Uninline.
protoGetProto(weapon->pid, &proto); weaponGetDamageMinMax(weapon, &minDamage, &maxDamage);
minDamage = proto->item.data.weapon.minDamage;
maxDamage = proto->item.data.weapon.maxDamage;
int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode); int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode);
if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) {
meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE); meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE);
// SFALL: Bonus HtH Damage fix.
if (damageModGetBonusHthDamageFix()) {
if (critter == gDude) {
// See explanation below.
minDamage += 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE);
}
}
} }
} else { } else {
// SFALL // SFALL
bonusDamage = unarmedGetDamage(hitMode, &minDamage, &maxDamage); bonusDamage = unarmedGetDamage(hitMode, &minDamage, &maxDamage);
meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE); meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE);
// SFALL: Bonus HtH Damage fix.
if (damageModGetBonusHthDamageFix()) {
if (critter == gDude) {
// Increase only min damage. Max damage should not be changed.
// It is calculated later by adding `meleeDamage` which already
// includes damage bonus (via `perkAddEffect`).
minDamage += 2 * perkGetRank(gDude, PERK_BONUS_HTH_DAMAGE);
}
}
} }
return randomBetween(bonusDamage + minDamage, bonusDamage + meleeDamage + maxDamage); return randomBetween(bonusDamage + minDamage, bonusDamage + meleeDamage + maxDamage);

View File

@ -42,6 +42,9 @@
#define SFALL_CONFIG_EXPLOSION_EMITS_LIGHT_KEY "ExplosionsEmitLight" #define SFALL_CONFIG_EXPLOSION_EMITS_LIGHT_KEY "ExplosionsEmitLight"
#define SFALL_CONFIG_CITY_REPUTATION_LIST_KEY "CityRepsList" #define SFALL_CONFIG_CITY_REPUTATION_LIST_KEY "CityRepsList"
#define SFALL_CONFIG_UNARMED_FILE_KEY "UnarmedFile" #define SFALL_CONFIG_UNARMED_FILE_KEY "UnarmedFile"
#define SFALL_CONFIG_DAMAGE_MOD_FORMULA_KEY "DamageFormula"
#define SFALL_CONFIG_BONUS_HTH_DAMAGE_FIX_KEY "BonusHtHDamageFix"
#define SFALL_CONFIG_DISPLAY_BONUS_DAMAGE_KEY "DisplayBonusDamage"
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1 #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3 #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3