diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index ff3e0f0..f2a2f8f 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -54,6 +54,7 @@ jobs: key: android-cmake-v1 - name: Setup signing config + if: env.KEYSTORE_FILE_BASE64 != '' && env.KEYSTORE_PROPERTIES_FILE_BASE64 != '' run: | cd os/android echo "$KEYSTORE_FILE_BASE64" | base64 --decode > debug.keystore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e28054..980fbc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,7 @@ jobs: key: android-cmake-v1 - name: Setup signing config + if: env.KEYSTORE_FILE_BASE64 != '' && env.KEYSTORE_PROPERTIES_FILE_BASE64 != '' run: | cd os/android echo "$KEYSTORE_FILE_BASE64" | base64 --decode > release.keystore diff --git a/src/actions.cc b/src/actions.cc index b732324..a570d85 100644 --- a/src/actions.cc +++ b/src/actions.cc @@ -25,6 +25,7 @@ #include "proto_types.h" #include "random.h" #include "scripts.h" +#include "sfall_config.h" #include "skill.h" #include "stat.h" #include "text_object.h" @@ -36,6 +37,12 @@ #define MAX_KNOCKDOWN_DISTANCE 20 +typedef enum ScienceRepairTargetType { + SCIENCE_REPAIR_TARGET_TYPE_DEFAULT, + SCIENCE_REPAIR_TARGET_TYPE_DUDE, + SCIENCE_REPAIR_TARGET_TYPE_ANYONE, +} ScienceRepairTargetType; + // 0x5106D0 static int _action_in_explode = 0; @@ -1382,6 +1389,19 @@ int actionUseSkill(Object* a1, Object* a2, int skill) break; } + // SFALL: Science on critters patch. + if (1) { + int targetType = SCIENCE_REPAIR_TARGET_TYPE_DEFAULT; + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_SCIENCE_REPAIR_TARGET_TYPE_KEY, &targetType); + if (targetType == SCIENCE_REPAIR_TARGET_TYPE_DUDE) { + if (a2 == gDude) { + break; + } + } else if (targetType == SCIENCE_REPAIR_TARGET_TYPE_ANYONE) { + break; + } + } + return -1; case SKILL_SNEAK: dudeToggleState(0); diff --git a/src/art.cc b/src/art.cc index 80b5420..b81422b 100644 --- a/src/art.cc +++ b/src/art.cc @@ -146,15 +146,31 @@ int artInit() gArtLanguageInitialized = true; } + bool critterDbSelected = false; for (int objectType = 0; objectType < OBJ_TYPE_COUNT; objectType++) { gArtListDescriptions[objectType].flags = 0; sprintf(path, "%s%s%s\\%s.lst", _cd_path_base, "art\\", gArtListDescriptions[objectType].name, gArtListDescriptions[objectType].name); + int oldDb; + if (objectType == OBJ_TYPE_CRITTER) { + oldDb = _db_current(); + critterDbSelected = true; + _db_select(_critter_db_handle); + } + if (artReadList(path, &(gArtListDescriptions[objectType].fileNames), &(gArtListDescriptions[objectType].fileNamesLength)) != 0) { debugPrint("art_read_lst failed in art_init\n"); + if (critterDbSelected) { + _db_select(oldDb); + } cacheFree(&gArtCache); return -1; } + + if (objectType == OBJ_TYPE_CRITTER) { + critterDbSelected = false; + _db_select(oldDb); + } } _anon_alias = (int*)internal_malloc(sizeof(*_anon_alias) * gArtListDescriptions[OBJ_TYPE_CRITTER].fileNamesLength); @@ -698,12 +714,6 @@ static int artReadList(const char* path, char** artListPtr, int* artListSizePtr) fileClose(stream); return 0; - -err: - - fileClose(stream); - - return -1; } // 0x419760 @@ -854,8 +864,8 @@ bool artExists(int fid) int oldDb = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { - oldDb = _db_current(1); - _db_current(_critter_db_handle); + oldDb = _db_current(); + _db_select(_critter_db_handle); } char* filePath = artBuildFilePath(fid); @@ -867,27 +877,38 @@ bool artExists(int fid) } if (oldDb != -1) { - _db_current(oldDb); + _db_select(oldDb); } return result; } +// NOTE: Exactly the same implementation as `artExists`. +// // 0x419930 bool _art_fid_valid(int fid) { - // NOTE: Original Code involves calling some unknown function. Check in debugger in mapper. + bool result = false; + int oldDb = -1; + + if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { + oldDb = _db_current(); + _db_select(_critter_db_handle); + } + char* filePath = artBuildFilePath(fid); - if (filePath == NULL) { - return false; + if (filePath != NULL) { + int fileSize; + if (dbGetFileSize(filePath, &fileSize) != -1) { + result = true; + } } - int fileSize; - if (dbGetFileSize(filePath, &fileSize) == -1) { - return false; + if (oldDb != -1) { + _db_select(oldDb); } - return true; + return result; } // 0x419998 @@ -937,8 +958,8 @@ static int artCacheGetFileSizeImpl(int fid, int* sizePtr) int result = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { - oldDb = _db_current(1); - _db_current(_critter_db_handle); + oldDb = _db_current(); + _db_select(_critter_db_handle); } char* artFilePath = artBuildFilePath(fid); @@ -973,7 +994,7 @@ static int artCacheGetFileSizeImpl(int fid, int* sizePtr) } if (oldDb != -1) { - _db_current(oldDb); + _db_select(oldDb); } return result; @@ -986,8 +1007,8 @@ static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data) int result = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { - oldDb = _db_current(1); - _db_current(_critter_db_handle); + oldDb = _db_current(); + _db_select(_critter_db_handle); } char* artFileName = artBuildFilePath(fid); @@ -1021,7 +1042,7 @@ static int artCacheReadDataImpl(int fid, int* sizePtr, unsigned char* data) } if (oldDb != -1) { - _db_current(oldDb); + _db_select(oldDb); } return result; diff --git a/src/character_editor.cc b/src/character_editor.cc index f01bc91..fc14d74 100644 --- a/src/character_editor.cc +++ b/src/character_editor.cc @@ -2,6 +2,7 @@ #include "art.h" #include "color.h" +#include "combat.h" #include "core.h" #include "critter.h" #include "cycle.h" @@ -2801,7 +2802,13 @@ static void characterEditorDrawDerivedStats() sprintf(t, "%s", messageListItemText); 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); // Damage Resistance @@ -4404,13 +4411,19 @@ static int characterPrintToFile(const char* fileName) fileWriteString(title1, 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 sprintf(title1, "%s %.2d %s %.2d %s %.3d lbs.", getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 633), critterGetStat(gDude, STAT_CHARISMA), getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 634), - critterGetStat(gDude, STAT_MELEE_DAMAGE), + meleeDamage, getmsg(&gCharacterEditorMessageList, &gCharacterEditorMessageListItem, 635), critterGetStat(gDude, STAT_CARRY_WEIGHT)); fileWriteString(title1, stream); diff --git a/src/combat.cc b/src/combat.cc index f49c5be..3e48336 100644 --- a/src/combat.cc +++ b/src/combat.cc @@ -47,6 +47,13 @@ #define CALLED_SHOT_WINDOW_WIDTH (504) #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 { Object* friendlyDead; Object* lastTarget; @@ -54,6 +61,30 @@ 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; + +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 int aiInfoCopy(int srcIndex, int destIndex); static void _combat_begin(Object* a1); @@ -96,6 +127,14 @@ 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); +static void damageModInit(); +static void damageModCalculateGlovz(DamageCalculationContext* context); +static int damageModGlovzDivRound(int dividend, int divisor); +static void damageModCalculateYaam(DamageCalculationContext* context); // 0x500B50 static char _a_1[] = "."; @@ -1932,6 +1971,10 @@ 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]; +static int gDamageCalculationType; +static bool gBonusHthDamageFix; +static bool gDisplayBonusDamage; // combat_init // 0x420CC0 @@ -1974,6 +2017,8 @@ int combatInit() // SFALL criticalsInit(); burstModInit(); + unarmedInit(); + damageModInit(); return 0; } @@ -2126,7 +2171,9 @@ int combatLoad(File* stream) if (a2 == -1) { aiInfo->friendlyDead = NULL; } else { - aiInfo->friendlyDead = objectFindById(a2); + // SFALL: Fix incorrect object type search when loading a game in + // combat mode. + aiInfo->friendlyDead = objectTypedFindById(a2, OBJ_TYPE_CRITTER); if (aiInfo->friendlyDead == NULL) return -1; } @@ -2135,7 +2182,9 @@ int combatLoad(File* stream) if (a2 == -1) { aiInfo->lastTarget = NULL; } else { - aiInfo->lastTarget = objectFindById(a2); + // SFALL: Fix incorrect object type search when loading a game in + // combat mode. + aiInfo->lastTarget = objectTypedFindById(a2, OBJ_TYPE_CRITTER); if (aiInfo->lastTarget == NULL) return -1; } @@ -2144,7 +2193,9 @@ int combatLoad(File* stream) if (a2 == -1) { aiInfo->lastItem = NULL; } else { - aiInfo->lastItem = objectFindById(a2); + // SFALL: Fix incorrect object type search when loading a game in + // combat mode. + aiInfo->lastItem = objectTypedFindById(a2, OBJ_TYPE_ITEM); if (aiInfo->lastItem == NULL) return -1; } @@ -2777,7 +2828,9 @@ void _combat_give_exps(int exp_points) return; } - pcAddExperience(exp_points); + // SFALL: Display actual xp received. + int xpGained; + pcAddExperience(exp_points, &xpGained); v7.num = 621; // %s you earn %d exp. points. if (!messageListGetItem(&gProtoMessageList, &v7)) { @@ -2796,7 +2849,7 @@ void _combat_give_exps(int exp_points) return; } - sprintf(text, v7.text, v9.text, exp_points); + sprintf(text, v7.text, v9.text, xpGained); displayMonitorAddMessage(text); } @@ -3757,13 +3810,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; + } } } } @@ -3793,6 +3845,16 @@ static int attackCompute(Attack* attack) switch (roll) { case ROLL_CRITICAL_SUCCESS: damageMultiplier = attackComputeCriticalHit(attack); + + // SFALL: Fix Silent Death bonus not being applied to critical hits. + if ((attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) && attack->attacker == gDude) { + if (perkHasRank(gDude, PERK_SILENT_DEATH) + && !_is_hit_from_front(gDude, attack->defender) + && dudeHasState(DUDE_STATE_SNEAKING) + && gDude != attack->defender->data.critter.combat.whoHitMe) { + damageMultiplier *= 2; + } + } // FALLTHROUGH case ROLL_SUCCESS: attack->attackerFlags |= DAM_HIT; @@ -4203,7 +4265,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 +4475,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; } @@ -4448,41 +4508,57 @@ static void attackComputeDamage(Attack* attack, int ammoQuantity, int bonusDamag } } - damageResistance += weaponGetAmmoDamageResistanceModifier(attack->weapon); - if (damageResistance > 100) { - damageResistance = 100; - } else if (damageResistance < 0) { - damageResistance = 0; - } + // 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; - int damageMultiplier = bonusDamageMultiplier * weaponGetAmmoDamageMultiplier(attack->weapon); - int damageDivisor = weaponGetAmmoDamageDivisor(attack->weapon); - - for (int index = 0; index < ammoQuantity; index++) { - int damage = weaponGetMeleeDamage(attack->attacker, attack->hitMode); - - damage += damageBonus; - - damage *= damageMultiplier; - - if (damageDivisor != 0) { - damage /= damageDivisor; + 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); + if (damageResistance > 100) { + damageResistance = 100; + } else if (damageResistance < 0) { + damageResistance = 0; } - // TODO: Why we're halving it? - damage /= 2; + int damageMultiplier = bonusDamageMultiplier * weaponGetAmmoDamageMultiplier(attack->weapon); + int damageDivisor = weaponGetAmmoDamageDivisor(attack->weapon); - damage *= combatDifficultyDamageModifier; - damage /= 100; + for (int index = 0; index < ammoQuantity; index++) { + int damage = weaponGetMeleeDamage(attack->attacker, attack->hitMode); - damage -= damageThreshold; + damage += damageBonus; - if (damage > 0) { - damage -= damage * damageResistance / 100; - } + damage *= damageMultiplier; - if (damage > 0) { - *damagePtr += damage; + if (damageDivisor != 0) { + damage /= damageDivisor; + } + + // TODO: Why we're halving it? + damage /= 2; + + damage *= combatDifficultyDamageModifier; + damage /= 100; + + damage -= damageThreshold; + + if (damage > 0) { + damage -= damage * damageResistance / 100; + } + + if (damage > 0) { + *damagePtr += damage; + } } } @@ -6189,3 +6265,483 @@ 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; +} + +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; + } + } +} diff --git a/src/combat.h b/src/combat.h index 411487d..158a76e 100644 --- a/src/combat.h +++ b/src/combat.h @@ -59,10 +59,26 @@ 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); +bool damageModGetBonusHthDamageFix(); +bool damageModGetDisplayBonusDamage(); 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/combat_ai.cc b/src/combat_ai.cc index d76a1ab..9a42bd8 100644 --- a/src/combat_ai.cc +++ b/src/combat_ai.cc @@ -988,8 +988,7 @@ static int _ai_check_drugs(Object* critter) } int drugPid = drug->pid; - if ((drugPid == PROTO_ID_STIMPACK || drugPid == PROTO_ID_SUPER_STIMPACK || drugPid == PROTO_ID_HEALING_POWDER) - && itemRemove(critter, drug, 1) == 0) { + if (itemIsHealing(drugPid) && itemRemove(critter, drug, 1) == 0) { if (_item_d_take_drug(critter, drug) == -1) { itemAdd(critter, drug, 1); } else { @@ -1027,8 +1026,7 @@ static int _ai_check_drugs(Object* critter) } if (index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT) { - if (drugPid != PROTO_ID_STIMPACK && drugPid != PROTO_ID_SUPER_STIMPACK && drugPid != 273 - && itemRemove(critter, drug, 1) == 0) { + if (!itemIsHealing(drugPid) && itemRemove(critter, drug, 1) == 0) { if (_item_d_take_drug(critter, drug) == -1) { itemAdd(critter, drug, 1); } else { @@ -1716,17 +1714,18 @@ static Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon return NULL; } - avgDamage1 = (maxDamage - minDamage) / 2; + // SFALL: Fix avg damage calculation. + avgDamage1 = (maxDamage + minDamage) / 2; if (_item_w_area_damage_radius(weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY) > 0 && defender != NULL) { attack.weapon = weapon1; _compute_explosion_on_extras(&attack, 0, weaponIsGrenade(weapon1), 1); avgDamage1 *= attack.extrasLength + 1; } - // TODO: Probably an error, why it takes [weapon2], should likely use - // [weapon1]. - if (weaponGetPerk(weapon2) != -1) { - avgDamage1 *= 5; + // SFALL: Fix for the incorrect item being checked. + if (weaponGetPerk(weapon1) != -1) { + // SFALL: Lower weapon score multiplier for having perk. + avgDamage1 *= 2; } if (defender != NULL) { @@ -1760,7 +1759,8 @@ static Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon return NULL; } - avgDamage2 = (maxDamage - minDamage) / 2; + // SFALL: Fix avg damage calculation. + avgDamage2 = (maxDamage + minDamage) / 2; if (_item_w_area_damage_radius(weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY) > 0 && defender != NULL) { attack.weapon = weapon2; _compute_explosion_on_extras(&attack, 0, weaponIsGrenade(weapon2), 1); @@ -1768,7 +1768,8 @@ static Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon } if (weaponGetPerk(weapon2) != -1) { - avgDamage2 *= 5; + // SFALL: Lower weapon score multiplier for having perk. + avgDamage2 *= 2; } if (defender != NULL) { diff --git a/src/core.cc b/src/core.cc index d22b221..6778d7c 100644 --- a/src/core.cc +++ b/src/core.cc @@ -17,11 +17,13 @@ #include #include -// NOT USED. -void (*_idle_func)() = NULL; +static void idleImpl(); -// NOT USED. -void (*_focus_func)(int) = NULL; +// 0x51E234 +IdleFunc* _idle_func = NULL; + +// 0x51E238 +FocusFunc* _focus_func = NULL; // 0x51E23C int gKeyboardKeyRepeatRate = 80; @@ -411,6 +413,10 @@ int coreInit(int a1) gTickerListHead = NULL; gScreenshotKeyCode = KEY_ALT_C; + // SFALL: Set idle function. + // CE: Prevents frying CPU when window is not focused. + inputSetIdleFunc(idleImpl); + return 0; } @@ -922,6 +928,70 @@ unsigned int _get_bk_time() return gTickerLastTimestamp; } +// NOTE: Unused. +// +// 0x4C9418 +void inputSetKeyboardKeyRepeatRate(int value) +{ + gKeyboardKeyRepeatRate = value; +} + +// NOTE: Unused. +// +// 0x4C9420 +int inputGetKeyboardKeyRepeatRate() +{ + return gKeyboardKeyRepeatRate; +} + +// NOTE: Unused. +// +// 0x4C9428 +void inputSetKeyboardKeyRepeatDelay(int value) +{ + gKeyboardKeyRepeatDelay = value; +} + +// NOTE: Unused. +// +// 0x4C9430 +int inputGetKeyboardKeyRepeatDelay() +{ + return gKeyboardKeyRepeatDelay; +} + +// NOTE: Unused. +// +// 0x4C9438 +void inputSetFocusFunc(FocusFunc* func) +{ + _focus_func = func; +} + +// NOTE: Unused. +// +// 0x4C9440 +FocusFunc* inputGetFocusFunc() +{ + return _focus_func; +} + +// NOTE: Unused. +// +// 0x4C9448 +void inputSetIdleFunc(IdleFunc* func) +{ + _idle_func = func; +} + +// NOTE: Unused. +// +// 0x4C9450 +IdleFunc* inputGetIdleFunc() +{ + return _idle_func; +} + // 0x4C9490 void buildNormalizedQwertyKeys() { @@ -1375,7 +1445,7 @@ void _GNW95_process_key(KeyboardData* data) void _GNW95_lost_focus() { if (_focus_func != NULL) { - _focus_func(0); + _focus_func(false); } while (!gProgramIsActive) { @@ -1387,7 +1457,7 @@ void _GNW95_lost_focus() } if (_focus_func != NULL) { - _focus_func(1); + _focus_func(true); } } @@ -4843,3 +4913,8 @@ void convertMouseWheelToArrowKey(int* keyCodePtr) } } } + +static void idleImpl() +{ + SDL_Delay(125); +} diff --git a/src/core.h b/src/core.h index 575a1c9..9210f52 100644 --- a/src/core.h +++ b/src/core.h @@ -414,7 +414,9 @@ typedef struct InputEvent { int mouseY; } InputEvent; -typedef void TickerProc(); +typedef void(IdleFunc)(); +typedef void(FocusFunc)(bool focus); +typedef void(TickerProc)(); typedef struct TickerListNode { int flags; @@ -461,8 +463,8 @@ typedef int(PauseHandler)(); typedef int(ScreenshotHandler)(int width, int height, unsigned char* buffer, unsigned char* palette); typedef void(VcrPlaybackCompletionCallback)(int reason); -extern void (*_idle_func)(); -extern void (*_focus_func)(int); +extern IdleFunc* _idle_func; +extern FocusFunc* _focus_func; extern int gKeyboardKeyRepeatRate; extern int gKeyboardKeyRepeatDelay; extern bool _keyboard_hooked; @@ -596,6 +598,14 @@ void coreDelay(unsigned int ms); unsigned int getTicksSince(unsigned int a1); unsigned int getTicksBetween(unsigned int a1, unsigned int a2); unsigned int _get_bk_time(); +void inputSetKeyboardKeyRepeatRate(int value); +int inputGetKeyboardKeyRepeatRate(); +void inputSetKeyboardKeyRepeatDelay(int value); +int inputGetKeyboardKeyRepeatDelay(); +void inputSetFocusFunc(FocusFunc* func); +FocusFunc* inputGetFocusFunc(); +void inputSetIdleFunc(IdleFunc* func); +IdleFunc* inputGetIdleFunc(); void buildNormalizedQwertyKeys(); int _GNW95_input_init(); void _GNW95_process_message(); diff --git a/src/critter.cc b/src/critter.cc index abdbb25..9cb9c1b 100644 --- a/src/critter.cc +++ b/src/critter.cc @@ -568,6 +568,13 @@ void _process_rads(Object* obj, int radiationLevel, bool isHealing) if (obj == gDude) { // Radiation level message, higher is worse. messageListItem.num = 1000 + radiationLevelIndex; + + // SFALL: Fix radiation message when removing radiation effects. + if (isHealing) { + // You feel better. + messageListItem.num = 3003; + } + if (messageListGetItem(&gMiscMessageList, &messageListItem)) { displayMonitorAddMessage(messageListItem.text); } @@ -579,15 +586,18 @@ void _process_rads(Object* obj, int radiationLevel, bool isHealing) critterSetBonusStat(obj, gRadiationEffectStats[effect], value); } - if ((obj->data.critter.combat.results & DAM_DEAD) == 0) { - // Loop thru effects affecting primary stats. If any of the primary stat - // dropped below minimal value, kill it. - for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) { - int base = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]); - int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]); - if (base + bonus < PRIMARY_STAT_MIN) { - critterKill(obj, -1, 1); - break; + // SFALL: Prevent death when removing radiation effects. + if (!isHealing) { + if ((obj->data.critter.combat.results & DAM_DEAD) == 0) { + // Loop thru effects affecting primary stats. If any of the primary stat + // dropped below minimal value, kill it. + for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) { + int base = critterGetBaseStatWithTraitModifier(obj, gRadiationEffectStats[effect]); + int bonus = critterGetBonusStat(obj, gRadiationEffectStats[effect]); + if (base + bonus < PRIMARY_STAT_MIN) { + critterKill(obj, -1, 1); + break; + } } } } diff --git a/src/datafile.cc b/src/datafile.cc index bccd470..00e9824 100644 --- a/src/datafile.cc +++ b/src/datafile.cc @@ -89,7 +89,7 @@ unsigned char* datafileReadRaw(char* path, int* widthPtr, int* heightPtr) char* mangledPath = gDatafileNameMangler(path); char* dot = strrchr(mangledPath, '.'); if (dot != NULL) { - if (compat_stricmp(dot + 1, "pcx")) { + if (compat_stricmp(dot + 1, "pcx") == 0) { return pcxRead(mangledPath, widthPtr, heightPtr, gDatafilePalette); } } diff --git a/src/db.cc b/src/db.cc index b0399b0..647a3fb 100644 --- a/src/db.cc +++ b/src/db.cc @@ -61,20 +61,22 @@ int dbOpen(const char* filePath1, int a2, const char* filePath2, int a4) return 0; } -// NOTE: This function simply returns 0, but it definitely accept one parameter -// via eax, as seen at every call site. This value is ignored. It's impossible -// to guess it's name. -// // 0x4C5D54 -int _db_current(int a1) +int _db_select(int dbHandle) +{ + return 0; +} + +// NOTE: Uncollapsed 0x4C5D54. +int _db_current() { return 0; } // 0x4C5D58 -bool _db_total() +int _db_total() { - return true; + return 0; } // 0x4C5D60 diff --git a/src/db.h b/src/db.h index f6206a6..986feb2 100644 --- a/src/db.h +++ b/src/db.h @@ -11,8 +11,9 @@ typedef void FileReadProgressHandler(); typedef char* StrdupProc(const char* string); int dbOpen(const char* filePath1, int a2, const char* filePath2, int a4); -int _db_current(int a1); -bool _db_total(); +int _db_select(int dbHandle); +int _db_current(); +int _db_total(); void dbExit(); int dbGetFileSize(const char* filePath, int* sizePtr); int dbGetFileContents(const char* filePath, void* ptr); diff --git a/src/game.cc b/src/game.cc index e77acd8..38ae806 100644 --- a/src/game.cc +++ b/src/game.cc @@ -1251,6 +1251,7 @@ static int gameDbInit() _critter_db_handle = dbOpen(main_file_name, 0, patch_file_name, 1); if (_critter_db_handle == -1) { + _db_select(_master_db_handle); showMesageBox("Could not find the critter datafile. Please make sure the FALLOUT CD is in the drive and that you are running FALLOUT from the directory you installed it to."); return -1; } @@ -1263,6 +1264,8 @@ static int gameDbInit() } } + _db_select(_master_db_handle); + return 0; } diff --git a/src/game_dialog.cc b/src/game_dialog.cc index 2ede61c..1e55248 100644 --- a/src/game_dialog.cc +++ b/src/game_dialog.cc @@ -26,6 +26,7 @@ #include "proto.h" #include "random.h" #include "scripts.h" +#include "sfall_config.h" #include "skill.h" #include "stat.h" #include "text_font.h" @@ -670,10 +671,16 @@ static void gameDialogHighlightsExit(); static void gameDialogRedButtonsInit(); static void gameDialogRedButtonsExit(); +static bool gGameDialogFix; + // gdialog_init // 0x444D1C int gameDialogInit() { + // SFALL: Prevents from using 0 to escape from dialogue at any time. + gGameDialogFix = true; + configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_GAME_DIALOG_FIX_KEY, &gGameDialogFix); + return 0; } @@ -1946,6 +1953,11 @@ int _gdProcess() } else if (keyCode >= 1300 && keyCode <= 1330) { gameDialogOptionOnMouseExit(keyCode - 1300); } else if (keyCode >= 48 && keyCode <= 57) { + // SFALL: Prevents from using 0 to escape from dialogue at any time. + if (keyCode == KEY_0 && gGameDialogFix) { + continue; + } + int v11 = keyCode - 49; if (v11 < gGameDialogOptionEntriesLength) { pageCount = 0; diff --git a/src/game_mouse.cc b/src/game_mouse.cc index caecddd..aa2ae58 100644 --- a/src/game_mouse.cc +++ b/src/game_mouse.cc @@ -16,6 +16,7 @@ #include "object.h" #include "proto.h" #include "proto_instance.h" +#include "sfall_config.h" #include "skill.h" #include "skilldex.h" #include "text_font.h" @@ -314,6 +315,8 @@ static int gameMouseHandleScrolling(int x, int y, int cursor); static int objectIsDoor(Object* object); static bool gameMouseClickOnInterfaceBar(); +static void customMouseModeFrmsInit(); + // 0x44B2B0 int gameMouseInit() { @@ -330,6 +333,9 @@ int gameMouseInit() gameMouseSetCursor(MOUSE_CURSOR_ARROW); + // SFALL + customMouseModeFrmsInit(); + return 0; } @@ -2409,3 +2415,14 @@ int objectIsDoor(Object* object) return proto->scenery.type == SCENERY_TYPE_DOOR; } + +static void customMouseModeFrmsInit() +{ + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_FIRST_AID_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_FIRST_AID])); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_DOCTOR_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_DOCTOR])); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_LOCKPICK_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_LOCKPICK])); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_STEAL_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_STEAL])); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_TRAPS_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_TRAPS])); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_SCIENCE_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_SCIENCE])); + configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_REPAIR_FRM_KEY, &(gGameMouseModeFrmIds[GAME_MOUSE_MODE_USE_REPAIR])); +} diff --git a/src/interface.cc b/src/interface.cc index 954c4da..f0f9e35 100644 --- a/src/interface.cc +++ b/src/interface.cc @@ -459,7 +459,7 @@ int interfaceInit() goto err; } - gInventoryButton = buttonCreate(gInterfaceBarWindow, 211, 41, 32, 21, -1, -1, -1, KEY_LOWERCASE_I, gInventoryButtonUpFrmData, gInventoryButtonDownFrmData, NULL, 0); + gInventoryButton = buttonCreate(gInterfaceBarWindow, 211, 40, 32, 21, -1, -1, -1, KEY_LOWERCASE_I, gInventoryButtonUpFrmData, gInventoryButtonDownFrmData, NULL, 0); if (gInventoryButton == -1) { goto err; } @@ -478,7 +478,7 @@ int interfaceInit() goto err; } - gOptionsButton = buttonCreate(gInterfaceBarWindow, 210, 62, 34, 34, -1, -1, -1, KEY_LOWERCASE_O, gOptionsButtonUpFrmData, gOptionsButtonDownFrmData, NULL, 0); + gOptionsButton = buttonCreate(gInterfaceBarWindow, 210, 61, 34, 34, -1, -1, -1, KEY_LOWERCASE_O, gOptionsButtonUpFrmData, gOptionsButtonDownFrmData, NULL, 0); if (gOptionsButton == -1) { goto err; } @@ -529,7 +529,7 @@ int interfaceInit() goto err; } - gMapButton = buttonCreate(gInterfaceBarWindow, 526, 40, 41, 19, -1, -1, -1, KEY_TAB, gMapButtonUpFrmData, gMapButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); + gMapButton = buttonCreate(gInterfaceBarWindow, 526, 39, 41, 19, -1, -1, -1, KEY_TAB, gMapButtonUpFrmData, gMapButtonDownFrmData, NULL, BUTTON_FLAG_TRANSPARENT); if (gMapButton == -1) { goto err; } @@ -549,7 +549,7 @@ int interfaceInit() goto err; } - gPipboyButton = buttonCreate(gInterfaceBarWindow, 526, 78, 41, 19, -1, -1, -1, KEY_LOWERCASE_P, gPipboyButtonUpFrmData, gPipboyButtonDownFrmData, NULL, 0); + gPipboyButton = buttonCreate(gInterfaceBarWindow, 526, 77, 41, 19, -1, -1, -1, KEY_LOWERCASE_P, gPipboyButtonUpFrmData, gPipboyButtonDownFrmData, NULL, 0); if (gPipboyButton == -1) { goto err; } @@ -569,7 +569,7 @@ int interfaceInit() goto err; } - gCharacterButton = buttonCreate(gInterfaceBarWindow, 526, 59, 41, 19, -1, -1, -1, KEY_LOWERCASE_C, gCharacterButtonUpFrmData, gCharacterButtonDownFrmData, NULL, 0); + gCharacterButton = buttonCreate(gInterfaceBarWindow, 526, 58, 41, 19, -1, -1, -1, KEY_LOWERCASE_C, gCharacterButtonUpFrmData, gCharacterButtonDownFrmData, NULL, 0); if (gCharacterButton == -1) { goto err; } @@ -1240,7 +1240,11 @@ int interfaceUpdateItems(bool animated, int leftItemAction, int rightItemAction) leftItemState->itemFid = itemGetInventoryFid(item1); } } else { + Object* oldItem = leftItemState->item; + int oldAction = leftItemState->action; + leftItemState->item = item1; + if (item1 != NULL) { leftItemState->isDisabled = _can_use_weapon(item1); leftItemState->primaryHitMode = HIT_MODE_LEFT_WEAPON_PRIMARY; @@ -1264,29 +1268,14 @@ 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); + // SFALL + leftItemState->primaryHitMode = unarmedGetPunchHitMode(false); + leftItemState->secondaryHitMode = unarmedGetPunchHitMode(true); - 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: Keep selected attack mode. + // CE: Implementation is different. + if (oldItem == NULL) { + leftItemState->action = oldAction; } } } @@ -1300,6 +1289,9 @@ int interfaceUpdateItems(bool animated, int leftItemAction, int rightItemAction) rightItemState->itemFid = itemGetInventoryFid(rightItemState->item); } } else { + Object* oldItem = rightItemState->item; + int oldAction = rightItemState->action; + rightItemState->item = item2; if (item2 != NULL) { @@ -1324,29 +1316,14 @@ 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); + // SFALL + rightItemState->primaryHitMode = unarmedGetKickHitMode(false); + rightItemState->secondaryHitMode = unarmedGetKickHitMode(true); - 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: Keep selected attack mode. + // CE: Implementation is different. + if (oldItem == NULL) { + rightItemState->action = oldAction; } } } diff --git a/src/interpreter.cc b/src/interpreter.cc index 805f375..7e96d74 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -1339,6 +1339,16 @@ static void opConditionalOperatorGreaterThan(Program* program) assert(false && "Should be unreachable"); } break; + // Sonora folks tend to use "object > 0" to test objects for nulls. + case VALUE_TYPE_PTR: + switch (value[0].opcode) { + case VALUE_TYPE_INT: + result = (intptr_t)value[1].pointerValue > (intptr_t)value[0].integerValue; + break; + default: + assert(false && "Should be unreachable"); + } + break; default: assert(false && "Should be unreachable"); } diff --git a/src/interpreter_extra.cc b/src/interpreter_extra.cc index 958a9fd..4decf4c 100644 --- a/src/interpreter_extra.cc +++ b/src/interpreter_extra.cc @@ -3103,7 +3103,11 @@ static void _op_inven_cmds(Program* program) static void opFloatMessage(Program* program) { int floatingMessageType = programStackPopInteger(program); - char* string = programStackPopString(program); + ProgramValue stringValue = programStackPopValue(program); + char* string = NULL; + if ((stringValue.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { + string = programGetString(program, stringValue.opcode, stringValue.integerValue); + } Object* obj = static_cast(programStackPopPointer(program)); int color = _colorTable[32747]; @@ -3340,10 +3344,26 @@ static void opMetarule(Program* program) // 0x4598BC static void opAnim(Program* program) { - int frame = programStackPopInteger(program); + ProgramValue frameValue = programStackPopValue(program); int anim = programStackPopInteger(program); Object* obj = static_cast(programStackPopPointer(program)); + // CE: There is a bug in the `animate_rotation` macro in the user-space + // sсripts - instead of passing direction, it passes object. The direction + // argument is thrown away by preprocessor. Original code ignores this bug + // since there is no distiction between integers and pointers. In addition + // there is a guard in the code path below which simply ignores any value + // greater than 6 (so rotation does not change when pointer is passed). + int frame; + if (frameValue.opcode == VALUE_TYPE_INT) { + frame = frameValue.integerValue; + } else if (anim == 1000 && frameValue.opcode == VALUE_TYPE_PTR) { + // Force code path below to skip setting rotation. + frame = ROTATION_COUNT; + } else { + programFatalError("script error: %s: invalid arg 2 to anim", program->name); + } + if (obj == NULL) { scriptPredefinedError(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); return; @@ -3584,7 +3604,8 @@ static void opAddMultipleObjectsToInventory(Program* program) if (quantity < 0) { quantity = 1; } else if (quantity > 99999) { - quantity = 500; + // SFALL + quantity = 99999; } if (itemAdd(object, item, quantity) == 0) { diff --git a/src/inventory.cc b/src/inventory.cc index 83bd82e..e4dbabc 100644 --- a/src/inventory.cc +++ b/src/inventory.cc @@ -72,20 +72,26 @@ #define INVENTORY_ARMOR_SLOT_MAX_X (INVENTORY_ARMOR_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_ARMOR_SLOT_MAX_Y (INVENTORY_ARMOR_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) -#define INVENTORY_TRADE_LEFT_SCROLLER_X 29 -#define INVENTORY_TRADE_RIGHT_SCROLLER_X 395 - -#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X 165 -#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X 250 - -#define INVENTORY_TRADE_OUTER_SCROLLER_Y 35 +#define INVENTORY_TRADE_SCROLLER_Y 30 #define INVENTORY_TRADE_INNER_SCROLLER_Y 20 -#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X 165 +#define INVENTORY_TRADE_LEFT_SCROLLER_X 29 +#define INVENTORY_TRADE_LEFT_SCROLLER_Y INVENTORY_TRADE_SCROLLER_Y + +#define INVENTORY_TRADE_RIGHT_SCROLLER_X 388 +#define INVENTORY_TRADE_RIGHT_SCROLLER_Y INVENTORY_TRADE_SCROLLER_Y + +#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X 165 +#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y INVENTORY_TRADE_INNER_SCROLLER_Y + +#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X 250 +#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y INVENTORY_TRADE_INNER_SCROLLER_Y + +#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X 0 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) -#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X 0 +#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X 165 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) @@ -97,7 +103,7 @@ #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) -#define INVENTORY_LOOT_LEFT_SCROLLER_X 176 +#define INVENTORY_LOOT_LEFT_SCROLLER_X 180 #define INVENTORY_LOOT_LEFT_SCROLLER_Y 37 #define INVENTORY_LOOT_LEFT_SCROLLER_MAX_X (INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_WIDTH) @@ -105,7 +111,7 @@ #define INVENTORY_LOOT_RIGHT_SCROLLER_Y 37 #define INVENTORY_LOOT_RIGHT_SCROLLER_MAX_X (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_WIDTH) -#define INVENTORY_SCROLLER_X 44 +#define INVENTORY_SCROLLER_X 46 #define INVENTORY_SCROLLER_Y 35 #define INVENTORY_SCROLLER_MAX_X (INVENTORY_SCROLLER_X + INVENTORY_SLOT_WIDTH) @@ -123,6 +129,46 @@ #define INVENTORY_LOOT_LEFT_BODY_VIEW_X 44 #define INVENTORY_LOOT_LEFT_BODY_VIEW_Y 35 +#define INVENTORY_SUMMARY_X 297 +#define INVENTORY_SUMMARY_Y 44 +#define INVENTORY_SUMMARY_MAX_X 440 + +#define INVENTORY_WINDOW_WIDTH 499 +#define INVENTORY_USE_ON_WINDOW_WIDTH 292 +#define INVENTORY_LOOT_WINDOW_WIDTH 537 +#define INVENTORY_TRADE_WINDOW_WIDTH 480 +#define INVENTORY_TIMER_WINDOW_WIDTH 259 + +#define INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH 640 +#define INVENTORY_TRADE_BACKGROUND_WINDOW_HEIGHT 480 +#define INVENTORY_TRADE_WINDOW_OFFSET ((INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH - INVENTORY_TRADE_WINDOW_WIDTH) / 2) + +#define INVENTORY_SLOT_PADDING 4 + +#define INVENTORY_SCROLLER_X_PAD (INVENTORY_SCROLLER_X + INVENTORY_SLOT_PADDING) +#define INVENTORY_SCROLLER_Y_PAD (INVENTORY_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_LOOT_LEFT_SCROLLER_X_PAD (INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_PADDING) +#define INVENTORY_LOOT_LEFT_SCROLLER_Y_PAD (INVENTORY_LOOT_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_LOOT_RIGHT_SCROLLER_X_PAD (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING) +#define INVENTORY_LOOT_RIGHT_SCROLLER_Y_PAD (INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_TRADE_LEFT_SCROLLER_X_PAD (INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) +#define INVENTORY_TRADE_LEFT_SCROLLER_Y_PAD (INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_TRADE_RIGHT_SCROLLER_X_PAD (INVENTORY_TRADE_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING) +#define INVENTORY_TRADE_RIGHT_SCROLLER_Y_PAD (INVENTORY_TRADE_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD (INVENTORY_TRADE_INNER_LEFT_SCROLLER_X + INVENTORY_SLOT_PADDING) +#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD (INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X + INVENTORY_SLOT_PADDING) +#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y + INVENTORY_SLOT_PADDING) + +#define INVENTORY_SLOT_WIDTH_PAD (INVENTORY_SLOT_WIDTH - INVENTORY_SLOT_PADDING * 2) +#define INVENTORY_SLOT_HEIGHT_PAD (INVENTORY_SLOT_HEIGHT - INVENTORY_SLOT_PADDING * 2) + #define INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY (1000U / ROTATION_COUNT) #define OFF_59E7BC_COUNT 12 @@ -275,12 +321,12 @@ static int _inven_display_msg_line = 1; // 0x519068 static const InventoryWindowDescription gInventoryWindowDescriptions[INVENTORY_WINDOW_TYPE_COUNT] = { - { 48, 499, 377, 80, 0 }, - { 113, 292, 376, 80, 0 }, - { 114, 537, 376, 80, 0 }, - { 111, 480, 180, 80, 290 }, - { 305, 259, 162, 140, 80 }, - { 305, 259, 162, 140, 80 }, + { 48, INVENTORY_WINDOW_WIDTH, 377, 80, 0 }, + { 113, INVENTORY_USE_ON_WINDOW_WIDTH, 376, 80, 0 }, + { 114, INVENTORY_LOOT_WINDOW_WIDTH, 376, 80, 0 }, + { 111, INVENTORY_TRADE_WINDOW_WIDTH, 180, 80, 290 }, + { 305, INVENTORY_TIMER_WINDOW_WIDTH, 162, 140, 80 }, + { 305, INVENTORY_TIMER_WINDOW_WIDTH, 162, 140, 80 }, }; // 0x5190E0 @@ -711,15 +757,15 @@ static bool _setup_inventory(int inventoryWindowType) gInventorySlotsCount = 3; // Trade inventory window is a part of game dialog, which is 640x480. - int tradeWindowX = (screenGetWidth() - 640) / 2 + INVENTORY_TRADE_WINDOW_X; - int tradeWindowY = (screenGetHeight() - 480) / 2 + INVENTORY_TRADE_WINDOW_Y; + int tradeWindowX = (screenGetWidth() - INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH) / 2 + INVENTORY_TRADE_WINDOW_X; + int tradeWindowY = (screenGetHeight() - INVENTORY_TRADE_BACKGROUND_WINDOW_HEIGHT) / 2 + INVENTORY_TRADE_WINDOW_Y; gInventoryWindow = windowCreate(tradeWindowX, tradeWindowY, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, 257, 0); gInventoryWindowMaxX = tradeWindowX + INVENTORY_TRADE_WINDOW_WIDTH; gInventoryWindowMaxY = tradeWindowY + INVENTORY_TRADE_WINDOW_HEIGHT; unsigned char* dest = windowGetBuffer(gInventoryWindow); unsigned char* src = windowGetBuffer(_barter_back_win); - blitBufferToBuffer(src + INVENTORY_TRADE_WINDOW_X, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, 640, dest, INVENTORY_TRADE_WINDOW_WIDTH); + blitBufferToBuffer(src + INVENTORY_TRADE_WINDOW_X, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, dest, INVENTORY_TRADE_WINDOW_WIDTH); gInventoryPrintItemDescriptionHandler = gameDialogRenderSupplementaryMessage; } @@ -754,7 +800,7 @@ static bool _setup_inventory(int inventoryWindowType) y -= INVENTORY_SLOT_HEIGHT; } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { - int y1 = INVENTORY_TRADE_OUTER_SCROLLER_Y; + int y1 = INVENTORY_TRADE_SCROLLER_Y; int y2 = INVENTORY_TRADE_INNER_SCROLLER_Y; for (int index = 0; index < gInventorySlotsCount; index++) { @@ -1192,9 +1238,8 @@ static void _display_inventory(int a1, int a2, int inventoryWindowType) unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); int pitch; - int v49 = 0; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { - pitch = 499; + pitch = INVENTORY_WINDOW_WIDTH; int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); @@ -1202,7 +1247,7 @@ static void _display_inventory(int a1, int a2, int inventoryWindowType) unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { // Clear scroll view background. - blitBufferToBuffer(backgroundFrmData + pitch * 35 + 44, INVENTORY_SLOT_WIDTH, gInventorySlotsCount * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * 35 + 44, pitch); + blitBufferToBuffer(backgroundFrmData + pitch * INVENTORY_SCROLLER_Y + INVENTORY_SCROLLER_X, INVENTORY_SLOT_WIDTH, gInventorySlotsCount * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_SCROLLER_Y + INVENTORY_SCROLLER_X, pitch); // Clear armor button background. blitBufferToBuffer(backgroundFrmData + pitch * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, pitch); @@ -1228,7 +1273,7 @@ static void _display_inventory(int a1, int a2, int inventoryWindowType) artUnlock(backgroundFrmHandle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON) { - pitch = 292; + pitch = INVENTORY_USE_ON_WINDOW_WIDTH; int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 113, 0, 0, 0); @@ -1236,11 +1281,11 @@ static void _display_inventory(int a1, int a2, int inventoryWindowType) unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { // Clear scroll view background. - blitBufferToBuffer(backgroundFrmData + pitch * 35 + 44, 64, gInventorySlotsCount * 48, pitch, windowBuffer + pitch * 35 + 44, pitch); + blitBufferToBuffer(backgroundFrmData + pitch * INVENTORY_SCROLLER_Y + INVENTORY_SCROLLER_X, INVENTORY_SLOT_WIDTH, gInventorySlotsCount * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_SCROLLER_Y + INVENTORY_SCROLLER_X, pitch); artUnlock(backgroundFrmHandle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { - pitch = 537; + pitch = INVENTORY_LOOT_WINDOW_WIDTH; int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); @@ -1248,16 +1293,15 @@ static void _display_inventory(int a1, int a2, int inventoryWindowType) unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { // Clear scroll view background. - blitBufferToBuffer(backgroundFrmData + pitch * 37 + 176, 64, gInventorySlotsCount * 48, pitch, windowBuffer + pitch * 37 + 176, pitch); + blitBufferToBuffer(backgroundFrmData + pitch * INVENTORY_LOOT_LEFT_SCROLLER_Y + INVENTORY_LOOT_LEFT_SCROLLER_X, INVENTORY_SLOT_WIDTH, gInventorySlotsCount * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_LOOT_LEFT_SCROLLER_Y + INVENTORY_LOOT_LEFT_SCROLLER_X, pitch); artUnlock(backgroundFrmHandle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { - pitch = 480; + pitch = INVENTORY_TRADE_WINDOW_WIDTH; windowBuffer = windowGetBuffer(gInventoryWindow); - blitBufferToBuffer(windowGetBuffer(_barter_back_win) + 35 * 640 + 100, 64, 48 * gInventorySlotsCount, 640, windowBuffer + pitch * 35 + 20, pitch); - v49 = -20; + blitBufferToBuffer(windowGetBuffer(_barter_back_win) + INVENTORY_TRADE_LEFT_SCROLLER_Y * INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH + INVENTORY_TRADE_LEFT_SCROLLER_X + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + pitch * INVENTORY_TRADE_LEFT_SCROLLER_Y + INVENTORY_TRADE_LEFT_SCROLLER_X, pitch); } else { assert(false && "Should be unreachable"); } @@ -1286,54 +1330,95 @@ static void _display_inventory(int a1, int a2, int inventoryWindowType) for (int v19 = 0; v19 + a1 < _pud->length && v19 < gInventorySlotsCount; v19 += 1) { int v21 = v19 + a1 + 1; - int width; int offset; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { - offset = pitch * (y + 39) + 26; - width = 59; + offset = pitch * (y + INVENTORY_TRADE_LEFT_SCROLLER_Y_PAD) + INVENTORY_TRADE_LEFT_SCROLLER_X_PAD; } else { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { - offset = pitch * (y + 41) + 180; + offset = pitch * (y + INVENTORY_LOOT_LEFT_SCROLLER_Y_PAD) + INVENTORY_LOOT_LEFT_SCROLLER_X_PAD; } else { - offset = pitch * (y + 39) + 48; + offset = pitch * (y + INVENTORY_SCROLLER_Y_PAD) + INVENTORY_SCROLLER_X_PAD; } - width = 56; } InventoryItem* inventoryItem = &(_pud->items[_pud->length - v21]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); - artRender(inventoryFid, windowBuffer + offset, width, 40, pitch); + artRender(inventoryFid, windowBuffer + offset, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, pitch); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { - offset = pitch * (y + 41) + 180 + v49; + offset = pitch * (y + INVENTORY_LOOT_LEFT_SCROLLER_Y_PAD) + INVENTORY_LOOT_LEFT_SCROLLER_X_PAD; + } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { + offset = pitch * (y + INVENTORY_TRADE_LEFT_SCROLLER_Y_PAD) + INVENTORY_TRADE_LEFT_SCROLLER_X_PAD; } else { - offset = pitch * (y + 39) + 48 + v49; + offset = pitch * (y + INVENTORY_SCROLLER_Y_PAD) + INVENTORY_SCROLLER_X_PAD; } _display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, v19 == a2); - y += 48; + y += INVENTORY_SLOT_HEIGHT; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { if (gInventoryRightHandItem != NULL) { int width = gInventoryRightHandItem == gInventoryLeftHandItem ? INVENTORY_LARGE_SLOT_WIDTH * 2 : INVENTORY_LARGE_SLOT_WIDTH; int inventoryFid = itemGetInventoryFid(gInventoryRightHandItem); - artRender(inventoryFid, windowBuffer + 499 * INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_RIGHT_HAND_SLOT_X, width, INVENTORY_LARGE_SLOT_HEIGHT, 499); + artRender(inventoryFid, windowBuffer + INVENTORY_WINDOW_WIDTH * INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_RIGHT_HAND_SLOT_X, width, INVENTORY_LARGE_SLOT_HEIGHT, INVENTORY_WINDOW_WIDTH); } if (gInventoryLeftHandItem != NULL && gInventoryLeftHandItem != gInventoryRightHandItem) { int inventoryFid = itemGetInventoryFid(gInventoryLeftHandItem); - artRender(inventoryFid, windowBuffer + 499 * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 499); + artRender(inventoryFid, windowBuffer + INVENTORY_WINDOW_WIDTH * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, INVENTORY_WINDOW_WIDTH); } if (gInventoryArmor != NULL) { int inventoryFid = itemGetInventoryFid(gInventoryArmor); - artRender(inventoryFid, windowBuffer + 499 * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 499); + artRender(inventoryFid, windowBuffer + INVENTORY_WINDOW_WIDTH * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, INVENTORY_WINDOW_WIDTH); } } + // CE: Show items weight. + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + char formattedText[20]; + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); + + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + int x = INVENTORY_LOOT_LEFT_SCROLLER_X; + int y = INVENTORY_LOOT_LEFT_SCROLLER_Y + gInventorySlotsCount * INVENTORY_SLOT_HEIGHT + 2; + blitBufferToBuffer(backgroundFrmData + pitch * y + x, INVENTORY_SLOT_WIDTH, fontGetLineHeight(), pitch, windowBuffer + pitch * y + x, pitch); + artUnlock(backgroundFrmHandle); + } + + Object* object = _stack[0]; + + int color = _colorTable[992]; + if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { + int carryWeight = critterGetStat(object, STAT_CARRY_WEIGHT); + int inventoryWeight = objectGetInventoryWeight(object); + sprintf(formattedText, "%d/%d", inventoryWeight, carryWeight); + + if (critterIsEncumbered(object)) { + color = _colorTable[31744]; + } + } else { + int inventoryWeight = objectGetInventoryWeight(object); + sprintf(formattedText, "%d", inventoryWeight); + } + + int width = fontGetStringWidth(formattedText); + int x = INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_WIDTH / 2 - width / 2; + int y = INVENTORY_LOOT_LEFT_SCROLLER_Y + INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + 2; + fontDrawText(windowBuffer + pitch * y + x, formattedText, width, pitch, color); + + fontSetCurrent(oldFont); + } + windowRefresh(gInventoryWindow); } @@ -1349,21 +1434,21 @@ static void _display_target_inventory(int a1, int a2, Inventory* inventory, int int pitch; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { - pitch = 537; + pitch = INVENTORY_LOOT_WINDOW_WIDTH; int fid = buildFid(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); CacheEntry* handle; unsigned char* data = artLockFrameData(fid, 0, 0, &handle); if (data != NULL) { - blitBufferToBuffer(data + 537 * 37 + 297, 64, 48 * gInventorySlotsCount, 537, windowBuffer + 537 * 37 + 297, 537); + blitBufferToBuffer(data + pitch * INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_LOOT_RIGHT_SCROLLER_X, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount, pitch, windowBuffer + pitch * INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_LOOT_RIGHT_SCROLLER_X, pitch); artUnlock(handle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { - pitch = 480; + pitch = INVENTORY_TRADE_WINDOW_WIDTH; unsigned char* src = windowGetBuffer(_barter_back_win); - blitBufferToBuffer(src + 640 * 35 + 475, 64, 48 * gInventorySlotsCount, 640, windowBuffer + 480 * 35 + 395, 480); + blitBufferToBuffer(src + INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH * INVENTORY_TRADE_RIGHT_SCROLLER_Y + INVENTORY_TRADE_RIGHT_SCROLLER_X + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_RIGHT_SCROLLER_Y + INVENTORY_TRADE_RIGHT_SCROLLER_X, INVENTORY_TRADE_WINDOW_WIDTH); } else { assert(false && "Should be unreachable"); } @@ -1377,19 +1462,19 @@ static void _display_target_inventory(int a1, int a2, Inventory* inventory, int int offset; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { - offset = pitch * (y + 41) + 301; + offset = pitch * (y + INVENTORY_LOOT_RIGHT_SCROLLER_Y_PAD) + INVENTORY_LOOT_RIGHT_SCROLLER_X_PAD; } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { - offset = pitch * (y + 39) + 397; + offset = pitch * (y + INVENTORY_TRADE_RIGHT_SCROLLER_Y_PAD) + INVENTORY_TRADE_RIGHT_SCROLLER_X_PAD; } else { assert(false && "Should be unreachable"); } InventoryItem* inventoryItem = &(inventory->items[inventory->length - (v27 + 1)]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); - artRender(inventoryFid, windowBuffer + offset, 56, 40, pitch); + artRender(inventoryFid, windowBuffer + offset, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, pitch); _display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, index == a2); - y += 48; + y += INVENTORY_SLOT_HEIGHT; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { @@ -1409,6 +1494,55 @@ static void _display_target_inventory(int a1, int a2, Inventory* inventory, int } } } + + // CE: Show items weight. + if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { + char formattedText[20]; + formattedText[0] = '\0'; + + int oldFont = fontGetCurrent(); + fontSetCurrent(101); + + int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); + + CacheEntry* backgroundFrmHandle; + unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); + if (backgroundFrmData != NULL) { + int x = INVENTORY_LOOT_RIGHT_SCROLLER_X; + int y = INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + 2; + blitBufferToBuffer(backgroundFrmData + pitch * y + x, INVENTORY_SLOT_WIDTH, fontGetLineHeight(), pitch, windowBuffer + pitch * y + x, pitch); + artUnlock(backgroundFrmHandle); + } + + Object* object = _target_stack[_target_curr_stack]; + + int color = _colorTable[992]; + if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { + int currentWeight = objectGetInventoryWeight(object); + int maxWeight = critterGetStat(object, STAT_CARRY_WEIGHT); + sprintf(formattedText, "%d/%d", currentWeight, maxWeight); + + if (critterIsEncumbered(object)) { + color = _colorTable[31744]; + } + } else if (PID_TYPE(object->pid) == OBJ_TYPE_ITEM) { + if (itemGetType(object) == ITEM_TYPE_CONTAINER) { + int currentSize = containerGetTotalSize(object); + int maxSize = containerGetMaxSize(object); + sprintf(formattedText, "%d/%d", currentSize, maxSize); + } + } else { + int inventoryWeight = objectGetInventoryWeight(object); + sprintf(formattedText, "%d", inventoryWeight); + } + + int width = fontGetStringWidth(formattedText); + int x = INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_WIDTH / 2 - width / 2; + int y = INVENTORY_LOOT_RIGHT_SCROLLER_Y + INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + 2; + fontDrawText(windowBuffer + pitch * y + x, formattedText, width, pitch, color); + + fontSetCurrent(oldFont); + } } // Renders inventory item quantity. @@ -1584,10 +1718,10 @@ static void _display_body(int fid, int inventoryWindowType) int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); unsigned char* src = artLockFrameData(backgroundFid, 0, 0, &backrgroundFrmHandle); if (src != NULL) { - blitBufferToBuffer(src + 537 * rect.top + rect.left, + blitBufferToBuffer(src + INVENTORY_LOOT_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, - 537, + INVENTORY_LOOT_WINDOW_WIDTH, windowBuffer + windowPitch * rect.top + rect.left, windowPitch); } @@ -1791,8 +1925,8 @@ static void _inven_pickup(int keyCode, int a2) // NOTE: Original code a little bit different, this code path // is only for key codes below 1006. v3 = keyCode - 1000; - rect.left = 44; - rect.top = 48 * v3 + 35; + rect.left = INVENTORY_SCROLLER_X; + rect.top = INVENTORY_SLOT_HEIGHT * v3 + INVENTORY_SCROLLER_Y; break; } @@ -1813,7 +1947,7 @@ static void _inven_pickup(int keyCode, int a2) int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { - blitBufferToBuffer(backgroundFrmData + 499 * rect.top + rect.left, width, height, 499, windowBuffer + 499 * rect.top + rect.left, 499); + blitBufferToBuffer(backgroundFrmData + INVENTORY_WINDOW_WIDTH * rect.top + rect.left, width, height, INVENTORY_WINDOW_WIDTH, windowBuffer + INVENTORY_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_WINDOW_WIDTH); artUnlock(backgroundFrmHandle); } @@ -1824,7 +1958,7 @@ static void _inven_pickup(int keyCode, int a2) int backgroundFid = buildFid(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); unsigned char* backgroundFrmData = artLockFrameData(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { - blitBufferToBuffer(backgroundFrmData + 499 * 286 + 154, 180, 61, 499, windowBuffer + 499 * 286 + 154, 499); + blitBufferToBuffer(backgroundFrmData + INVENTORY_WINDOW_WIDTH * 286 + 154, 180, 61, INVENTORY_WINDOW_WIDTH, windowBuffer + INVENTORY_WINDOW_WIDTH * 286 + 154, INVENTORY_WINDOW_WIDTH); artUnlock(backgroundFrmHandle); } @@ -1868,7 +2002,7 @@ static void _inven_pickup(int keyCode, int a2) int y; mouseGetPositionInWindow(gInventoryWindow, &x, &y); - int v18 = (y - 39) / 48 + a2; + int v18 = (y - 39) / INVENTORY_SLOT_HEIGHT + a2; if (v18 < _pud->length) { Object* v19 = _pud->items[v18].item; if (v19 != a1a) { @@ -2365,44 +2499,44 @@ static void inventoryRenderSummary() CacheEntry* backgroundHandle; unsigned char* backgroundData = artLockFrameData(fid, 0, 0, &backgroundHandle); if (backgroundData != NULL) { - blitBufferToBuffer(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499); + blitBufferToBuffer(backgroundData + INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X, 152, 188, INVENTORY_WINDOW_WIDTH, windowBuffer + INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X, INVENTORY_WINDOW_WIDTH); } artUnlock(backgroundHandle); // Render character name. const char* critterName = critterGetName(_stack[0]); - fontDrawText(windowBuffer + 499 * 44 + 297, critterName, 80, 499, _colorTable[992]); + fontDrawText(windowBuffer + INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X, critterName, 80, INVENTORY_WINDOW_WIDTH, _colorTable[992]); bufferDrawLine(windowBuffer, - 499, - 297, - 3 * fontGetLineHeight() / 2 + 44, - 440, - 3 * fontGetLineHeight() / 2 + 44, + INVENTORY_WINDOW_WIDTH, + INVENTORY_SUMMARY_X, + 3 * fontGetLineHeight() / 2 + INVENTORY_SUMMARY_Y, + INVENTORY_SUMMARY_MAX_X, + 3 * fontGetLineHeight() / 2 + INVENTORY_SUMMARY_Y, _colorTable[992]); MessageListItem messageListItem; - int offset = 499 * 2 * fontGetLineHeight() + 499 * 44 + 297; + int offset = INVENTORY_WINDOW_WIDTH * 2 * fontGetLineHeight() + INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X; for (int stat = 0; stat < 7; stat++) { messageListItem.num = stat; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { - fontDrawText(windowBuffer + offset, messageListItem.text, 80, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset, messageListItem.text, 80, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } int value = critterGetStat(_stack[0], stat); sprintf(formattedText, "%d", value); - fontDrawText(windowBuffer + offset + 24, formattedText, 80, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset + 24, formattedText, 80, INVENTORY_WINDOW_WIDTH, _colorTable[992]); - offset += 499 * fontGetLineHeight(); + offset += INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); } - offset -= 499 * 7 * fontGetLineHeight(); + offset -= INVENTORY_WINDOW_WIDTH * 7 * fontGetLineHeight(); for (int index = 0; index < 7; index += 1) { messageListItem.num = 7 + index; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { - fontDrawText(windowBuffer + offset + 40, messageListItem.text, 80, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset + 40, messageListItem.text, 80, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } if (v57[index] == -1) { @@ -2415,13 +2549,13 @@ static void inventoryRenderSummary() sprintf(formattedText, format, value1, value2); } - fontDrawText(windowBuffer + offset + 104, formattedText, 80, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset + 104, formattedText, 80, INVENTORY_WINDOW_WIDTH, _colorTable[992]); - offset += 499 * fontGetLineHeight(); + offset += INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); } - bufferDrawLine(windowBuffer, 499, 297, 18 * fontGetLineHeight() / 2 + 48, 440, 18 * fontGetLineHeight() / 2 + 48, _colorTable[992]); - bufferDrawLine(windowBuffer, 499, 297, 26 * fontGetLineHeight() / 2 + 48, 440, 26 * fontGetLineHeight() / 2 + 48, _colorTable[992]); + bufferDrawLine(windowBuffer, INVENTORY_WINDOW_WIDTH, INVENTORY_SUMMARY_X, 18 * fontGetLineHeight() / 2 + 48, INVENTORY_SUMMARY_MAX_X, 18 * fontGetLineHeight() / 2 + 48, _colorTable[992]); + bufferDrawLine(windowBuffer, INVENTORY_WINDOW_WIDTH, INVENTORY_SUMMARY_X, 26 * fontGetLineHeight() / 2 + 48, INVENTORY_SUMMARY_MAX_X, 26 * fontGetLineHeight() / 2 + 48, _colorTable[992]); Object* itemsInHands[2] = { gInventoryLeftHandItem, @@ -2433,7 +2567,17 @@ static void inventoryRenderSummary() HIT_MODE_RIGHT_WEAPON_PRIMARY, }; - offset += 499 * fontGetLineHeight(); + const int secondaryHitModes[2] = { + HIT_MODE_LEFT_WEAPON_SECONDARY, + HIT_MODE_RIGHT_WEAPON_SECONDARY, + }; + + const int unarmedHitModes[2] = { + HIT_MODE_PUNCH, + HIT_MODE_KICK, + }; + + offset += INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); for (int index = 0; index < 2; index += 1) { Object* item = itemsInHands[index]; @@ -2443,30 +2587,53 @@ static void inventoryRenderSummary() // No item messageListItem.num = 14; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { - fontDrawText(windowBuffer + offset, messageListItem.text, 120, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset, messageListItem.text, 120, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } - offset += 499 * fontGetLineHeight(); + offset += INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); // 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]); + fontDrawText(windowBuffer + offset, formattedText, 120, INVENTORY_WINDOW_WIDTH, _colorTable[992]); - offset += 3 * 499 * fontGetLineHeight(); + offset += 3 * INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); continue; } const char* itemName = itemGetName(item); - fontDrawText(windowBuffer + offset, itemName, 140, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset, itemName, 140, INVENTORY_WINDOW_WIDTH, _colorTable[992]); - offset += 499 * fontGetLineHeight(); + offset += INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); int itemType = itemGetType(item); if (itemType != ITEM_TYPE_WEAPON) { @@ -2474,27 +2641,48 @@ static void inventoryRenderSummary() // (Not worn) messageListItem.num = 18; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { - fontDrawText(windowBuffer + offset, messageListItem.text, 120, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset, messageListItem.text, 120, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } } - offset += 3 * 499 * fontGetLineHeight(); + offset += 3 * INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); continue; } - int range = _item_w_range(_stack[0], hitModes[index]); + // SFALL: Fix displaying secondary mode weapon range. + int hitMode = hitModes[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 (isSecondary) { + hitMode = secondaryHitModes[index]; + } + } + + int range = _item_w_range(_stack[0], hitMode); int damageMin; int damageMax; weaponGetDamageMinMax(item, &damageMin, &damageMax); - int attackType = weaponGetAttackTypeForHitMode(item, hitModes[index]); + // CE: Fix displaying secondary mode weapon damage (affects throwable + // melee weapons - knifes, spears, etc.). + int attackType = weaponGetAttackTypeForHitMode(item, hitMode); formattedText[0] = '\0'; int meleeDamage; if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { 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 { meleeDamage = 0; } @@ -2502,19 +2690,41 @@ static void inventoryRenderSummary() messageListItem.num = 15; // Dmg: if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { 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); } else { MessageListItem rangeMessageListItem; rangeMessageListItem.num = 16; // Rng: 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); } } - fontDrawText(windowBuffer + offset, formattedText, 140, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset, formattedText, 140, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } - offset += 499 * fontGetLineHeight(); + offset += INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); if (ammoGetCapacity(item) > 0) { int ammoTypePid = weaponGetAmmoTypePid(item); @@ -2541,10 +2751,10 @@ static void inventoryRenderSummary() sprintf(formattedText, "%s %d/%d", messageListItem.text, quantity, capacity); } - fontDrawText(windowBuffer + offset, formattedText, 140, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset, formattedText, 140, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } - offset += 2 * 499 * fontGetLineHeight(); + offset += 2 * INVENTORY_WINDOW_WIDTH * fontGetLineHeight(); } // Total wt: @@ -2560,12 +2770,12 @@ static void inventoryRenderSummary() color = _colorTable[31744]; } - fontDrawText(windowBuffer + offset + 15, formattedText, 120, 499, color); + fontDrawText(windowBuffer + offset + 15, formattedText, 120, INVENTORY_WINDOW_WIDTH, color); } else { int inventoryWeight = objectGetInventoryWeight(_stack[0]); sprintf(formattedText, "%s %d", messageListItem.text, inventoryWeight); - fontDrawText(windowBuffer + offset + 30, formattedText, 80, 499, _colorTable[992]); + fontDrawText(windowBuffer + offset + 30, formattedText, 80, INVENTORY_WINDOW_WIDTH, _colorTable[992]); } } @@ -2949,7 +3159,7 @@ static void inventoryRenderItemDescription(char* string) fontSetCurrent(101); unsigned char* windowBuffer = windowGetBuffer(gInventoryWindow); - windowBuffer += 499 * 44 + 297; + windowBuffer += INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X; char* c = string; while (c != NULL && *c != '\0') { @@ -2971,7 +3181,7 @@ static void inventoryRenderItemDescription(char* string) // This was the last line containing very long word. Text // drawing routine will silently truncate it after reaching // desired length. - fontDrawText(windowBuffer + 499 * _inven_display_msg_line * fontGetLineHeight(), c, 152, 499, _colorTable[992]); + fontDrawText(windowBuffer + INVENTORY_WINDOW_WIDTH * _inven_display_msg_line * fontGetLineHeight(), c, 152, INVENTORY_WINDOW_WIDTH, _colorTable[992]); return; } @@ -3012,7 +3222,7 @@ static void inventoryRenderItemDescription(char* string) return; } - fontDrawText(windowBuffer + 499 * _inven_display_msg_line * fontGetLineHeight(), c, 152, 499, _colorTable[992]); + fontDrawText(windowBuffer + INVENTORY_WINDOW_WIDTH * _inven_display_msg_line * fontGetLineHeight(), c, 152, INVENTORY_WINDOW_WIDTH, _colorTable[992]); if (space != NULL) { c = space + 1; @@ -3043,7 +3253,7 @@ static void inventoryExamineItem(Object* critter, Object* item) CacheEntry* handle; unsigned char* backgroundData = artLockFrameData(backgroundFid, 0, 0, &handle); if (backgroundData != NULL) { - blitBufferToBuffer(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499); + blitBufferToBuffer(backgroundData + INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X, 152, 188, INVENTORY_WINDOW_WIDTH, windowBuffer + INVENTORY_WINDOW_WIDTH * INVENTORY_SUMMARY_Y + INVENTORY_SUMMARY_X, INVENTORY_WINDOW_WIDTH); } artUnlock(handle); @@ -3061,10 +3271,10 @@ static void inventoryExamineItem(Object* critter, Object* item) // Draw separator. bufferDrawLine(windowBuffer, - 499, - 297, + INVENTORY_WINDOW_WIDTH, + INVENTORY_SUMMARY_X, 3 * lineHeight / 2 + 49, - 440, + INVENTORY_SUMMARY_MAX_X, 3 * lineHeight / 2 + 49, _colorTable[992]); @@ -3253,8 +3463,8 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType) if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { unsigned char* src = windowGetBuffer(_barter_back_win); - int pitch = 640; - blitBufferToBuffer(src + pitch * rect.top + rect.left + 80, + int pitch = INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH; + blitBufferToBuffer(src + pitch * rect.top + rect.left + INVENTORY_TRADE_WINDOW_OFFSET, cursorData->width, menuButtonHeight, pitch, @@ -3825,15 +4035,17 @@ int inventoryOpenLooting(Object* a1, Object* a2) stealingXp = std::min(300 - skillGetValue(a1, SKILL_STEAL), stealingXp); debugPrint("\n[[[%d]]]", 300 - skillGetValue(a1, SKILL_STEAL)); + // SFALL: Display actual xp received. + int xpGained; + pcAddExperience(stealingXp, &xpGained); + // You gain %d experience points for successfully using your Steal skill. messageListItem.num = 29; if (messageListGetItem(&gInventoryMessageList, &messageListItem)) { char formattedText[200]; - sprintf(formattedText, messageListItem.text, stealingXp); + sprintf(formattedText, messageListItem.text, xpGained); displayMonitorAddMessage(formattedText); } - - pcAddExperience(stealingXp); } } } @@ -3920,7 +4132,7 @@ static int _move_inventory(Object* a1, int a2, Object* a3, bool a4) int fid = buildFid(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); unsigned char* data = artLockFrameData(fid, 0, 0, &handle); if (data != NULL) { - blitBufferToBuffer(data + 537 * rect.top + rect.left, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 537, windowBuffer + 537 * rect.top + rect.left, 537); + blitBufferToBuffer(data + INVENTORY_LOOT_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, INVENTORY_LOOT_WINDOW_WIDTH, windowBuffer + INVENTORY_LOOT_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_LOOT_WINDOW_WIDTH); artUnlock(handle); } @@ -4121,10 +4333,10 @@ static void _barter_move_inventory(Object* a1, int quantity, int a3, int a4, Obj Rect rect; if (a7) { rect.left = 23; - rect.top = 48 * a3 + 34; + rect.top = INVENTORY_SLOT_HEIGHT * a3 + 34; } else { rect.left = 395; - rect.top = 48 * a3 + 31; + rect.top = INVENTORY_SLOT_HEIGHT * a3 + 31; } if (quantity > 1) { @@ -4137,8 +4349,8 @@ static void _barter_move_inventory(Object* a1, int quantity, int a3, int a4, Obj unsigned char* dest = windowGetBuffer(gInventoryWindow); unsigned char* src = windowGetBuffer(_barter_back_win); - int pitch = 640; - blitBufferToBuffer(src + pitch * rect.top + rect.left + 80, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + 480 * rect.top + rect.left, 480); + int pitch = INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH; + blitBufferToBuffer(src + pitch * rect.top + rect.left + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + INVENTORY_TRADE_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_TRADE_WINDOW_WIDTH); rect.right = rect.left + INVENTORY_SLOT_WIDTH - 1; rect.bottom = rect.top + INVENTORY_SLOT_HEIGHT - 1; @@ -4168,7 +4380,7 @@ static void _barter_move_inventory(Object* a1, int quantity, int a3, int a4, Obj MessageListItem messageListItem; if (a7) { - if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)) { + if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)) { int quantityToMove = quantity > 1 ? inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1; if (quantityToMove != -1) { if (_item_move_force(_inven_dude, a6, a1, quantityToMove) == -1) { @@ -4203,11 +4415,11 @@ static void _barter_move_from_table_inventory(Object* a1, int quantity, int a3, { Rect rect; if (a6) { - rect.left = 169; - rect.top = 48 * a3 + 24; + rect.left = INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD; + rect.top = INVENTORY_SLOT_HEIGHT * a3 + INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD; } else { - rect.left = 254; - rect.top = 48 * a3 + 24; + rect.left = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD; + rect.top = INVENTORY_SLOT_HEIGHT * a3 + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD; } if (quantity > 1) { @@ -4220,8 +4432,8 @@ static void _barter_move_from_table_inventory(Object* a1, int quantity, int a3, unsigned char* dest = windowGetBuffer(gInventoryWindow); unsigned char* src = windowGetBuffer(_barter_back_win); - int pitch = 640; - blitBufferToBuffer(src + pitch * rect.top + rect.left + 80, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + 480 * rect.top + rect.left, 480); + int pitch = INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH; + blitBufferToBuffer(src + pitch * rect.top + rect.left + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + INVENTORY_TRADE_WINDOW_WIDTH * rect.top + rect.left, INVENTORY_TRADE_WINDOW_WIDTH); rect.right = rect.left + INVENTORY_SLOT_WIDTH - 1; rect.bottom = rect.top + INVENTORY_SLOT_HEIGHT - 1; @@ -4251,7 +4463,7 @@ static void _barter_move_from_table_inventory(Object* a1, int quantity, int a3, MessageListItem messageListItem; if (a6) { - if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)) { + if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)) { int quantityToMove = quantity > 1 ? inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1; if (quantityToMove != -1) { if (_item_move_force(a5, _inven_dude, a1, quantityToMove) == -1) { @@ -4290,21 +4502,21 @@ static void inventoryWindowRenderInnerInventories(int win, Object* a2, Object* a fontSetCurrent(101); char formattedText[80]; - int v45 = fontGetLineHeight() + 48 * gInventorySlotsCount; + int v45 = fontGetLineHeight() + INVENTORY_SLOT_HEIGHT * gInventorySlotsCount; if (a2 != NULL) { unsigned char* src = windowGetBuffer(win); - blitBufferToBuffer(src + 640 * 20 + 249, 64, v45 + 1, 640, windowBuffer + 480 * 20 + 169, 480); + blitBufferToBuffer(src + INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH * INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, v45 + 1, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD, INVENTORY_TRADE_WINDOW_WIDTH); - unsigned char* dest = windowBuffer + 480 * 24 + 169; + unsigned char* dest = windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD; Inventory* inventory = &(a2->data.inventory); for (int index = 0; index < gInventorySlotsCount && index + _ptable_offset < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + _ptable_offset + 1)]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); - artRender(inventoryFid, dest, 56, 40, 480); - _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4); + artRender(inventoryFid, dest, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, INVENTORY_TRADE_WINDOW_WIDTH); + _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, INVENTORY_TRADE_WINDOW_WIDTH, index == a4); - dest += 480 * 48; + dest += INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_SLOT_HEIGHT; } if (gGameDialogSpeakerIsPartyMember) { @@ -4320,29 +4532,30 @@ static void inventoryWindowRenderInnerInventories(int win, Object* a2, Object* a sprintf(formattedText, "$%d", cost); } - fontDrawText(windowBuffer + 480 * (48 * gInventorySlotsCount + 24) + 169, formattedText, 80, 480, _colorTable[32767]); + fontDrawText(windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * (INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD) + INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD, formattedText, 80, INVENTORY_TRADE_WINDOW_WIDTH, _colorTable[32767]); Rect rect; - rect.left = 169; - rect.top = 24; - rect.right = 223; + rect.left = INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD; + rect.top = INVENTORY_TRADE_INNER_LEFT_SCROLLER_Y_PAD; + // NOTE: Odd math, the only way to get 223 is to subtract 2. + rect.right = INVENTORY_TRADE_INNER_LEFT_SCROLLER_X_PAD + INVENTORY_SLOT_WIDTH_PAD - 2; rect.bottom = rect.top + v45; windowRefreshRect(gInventoryWindow, &rect); } if (a3 != NULL) { unsigned char* src = windowGetBuffer(win); - blitBufferToBuffer(src + 640 * 20 + 334, 64, v45 + 1, 640, windowBuffer + 480 * 20 + 254, 480); + blitBufferToBuffer(src + INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH * INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD + INVENTORY_TRADE_WINDOW_OFFSET, INVENTORY_SLOT_WIDTH, v45 + 1, INVENTORY_TRADE_BACKGROUND_WINDOW_WIDTH, windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD, INVENTORY_TRADE_WINDOW_WIDTH); - unsigned char* dest = windowBuffer + 480 * 24 + 254; + unsigned char* dest = windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD; Inventory* inventory = &(a3->data.inventory); for (int index = 0; index < gInventorySlotsCount && index + _btable_offset < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + _btable_offset + 1)]); int inventoryFid = itemGetInventoryFid(inventoryItem->item); - artRender(inventoryFid, dest, 56, 40, 480); - _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4); + artRender(inventoryFid, dest, INVENTORY_SLOT_WIDTH_PAD, INVENTORY_SLOT_HEIGHT_PAD, INVENTORY_TRADE_WINDOW_WIDTH); + _display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, INVENTORY_TRADE_WINDOW_WIDTH, index == a4); - dest += 480 * 48; + dest += INVENTORY_TRADE_WINDOW_WIDTH * INVENTORY_SLOT_HEIGHT; } if (gGameDialogSpeakerIsPartyMember) { @@ -4358,12 +4571,13 @@ static void inventoryWindowRenderInnerInventories(int win, Object* a2, Object* a sprintf(formattedText, "$%d", cost); } - fontDrawText(windowBuffer + 480 * (48 * gInventorySlotsCount + 24) + 254, formattedText, 80, 480, _colorTable[32767]); + fontDrawText(windowBuffer + INVENTORY_TRADE_WINDOW_WIDTH * (INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD) + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD, formattedText, 80, INVENTORY_TRADE_WINDOW_WIDTH, _colorTable[32767]); Rect rect; - rect.left = 254; - rect.top = 24; - rect.right = 318; + rect.left = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD; + rect.top = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_Y_PAD; + // NOTE: Odd math, likely should be `INVENTORY_SLOT_WIDTH_PAD`. + rect.right = INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X_PAD + INVENTORY_SLOT_WIDTH; rect.bottom = rect.top + v45; windowRefreshRect(gInventoryWindow, &rect); } @@ -4605,7 +4819,7 @@ void inventoryOpenTrade(int win, Object* a2, Object* a3, Object* a4, int a5) keyCode = -1; } } else if ((mouseGetEvent() & MOUSE_EVENT_WHEEL) != 0) { - if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)) { + if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); @@ -4620,7 +4834,7 @@ void inventoryOpenTrade(int win, Object* a2, Object* a3, Object* a4, int a5) _display_inventory(_stack_offset[_curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } - } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)) { + } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X, INVENTORY_SLOT_HEIGHT * gInventorySlotsCount + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); diff --git a/src/item.cc b/src/item.cc index a25e47e..405a645 100644 --- a/src/item.cc +++ b/src/item.cc @@ -67,6 +67,10 @@ static void explosionsInit(); static void explosionsReset(); static void explosionsExit(); +static void healingItemsInit(); +static void healingItemsInitVanilla(); +static void healingItemsInitCustom(); + typedef struct DrugDescription { int drugPid; int gvar; @@ -179,6 +183,7 @@ static int gExplosionFrm; static int gExplosionRadius; static int gExplosionDamageType; static int gExplosionMaxTargets; +static int gHealingItemPids[HEALING_ITEM_COUNT]; // 0x4770E0 int itemsInit() @@ -197,6 +202,7 @@ int itemsInit() // SFALL booksInit(); explosionsInit(); + healingItemsInit(); return 0; } @@ -1261,55 +1267,44 @@ 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); if (weapon != NULL) { - Proto* proto; - protoGetProto(weapon->pid, &proto); - - minDamage = proto->item.data.weapon.minDamage; - maxDamage = proto->item.data.weapon.maxDamage; + // NOTE: Uninline. + weaponGetDamageMinMax(weapon, &minDamage, &maxDamage); int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode); if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { 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 { - minDamage = 1; - maxDamage = critterGetStat(critter, STAT_MELEE_DAMAGE) + 2; + // SFALL + bonusDamage = unarmedGetDamage(hitMode, &minDamage, &maxDamage); + meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE); - 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: 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(unarmedDamage + minDamage, unarmedDamage + meleeDamage + maxDamage); + return randomBetween(bonusDamage + minDamage, bonusDamage + meleeDamage + maxDamage); } // 0x478570 @@ -1678,28 +1673,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 +1696,6 @@ int _item_w_mp_cost(Object* critter, int hitMode, bool aiming) } else { actionPoints = 3; } - break; } if (critter == gDude) { @@ -2829,7 +2806,8 @@ int _item_d_take_drug(Object* critter, Object* item) dudeClearAddiction(PROTO_ID_JET); } - return 0; + // SFALL: Fix for Jet antidote not being removed. + return 1; } } @@ -3613,3 +3591,51 @@ void explosionSetMaxTargets(int maxTargets) { gExplosionMaxTargets = maxTargets; } + +static void healingItemsInit() +{ + healingItemsInitVanilla(); + healingItemsInitCustom(); +} + +static void healingItemsInitVanilla() +{ + gHealingItemPids[HEALING_ITEM_STIMPACK] = PROTO_ID_STIMPACK; + gHealingItemPids[HEALING_ITEM_SUPER_STIMPACK] = PROTO_ID_SUPER_STIMPACK; + gHealingItemPids[HEALING_ITEM_HEALING_POWDER] = PROTO_ID_HEALING_POWDER; +} + +static void healingItemsInitCustom() +{ + char* tweaksFilePath = NULL; + configGetString(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_TWEAKS_FILE_KEY, &tweaksFilePath); + if (tweaksFilePath != NULL && *tweaksFilePath == '\0') { + tweaksFilePath = NULL; + } + + if (tweaksFilePath == NULL) { + return; + } + + Config tweaksConfig; + if (configInit(&tweaksConfig)) { + if (configRead(&tweaksConfig, tweaksFilePath, false)) { + configGetInt(&gSfallConfig, "Items", "STIMPAK", &(gHealingItemPids[HEALING_ITEM_STIMPACK])); + configGetInt(&gSfallConfig, "Items", "SUPER_STIMPAK", &(gHealingItemPids[HEALING_ITEM_SUPER_STIMPACK])); + configGetInt(&gSfallConfig, "Items", "HEALING_POWDER", &(gHealingItemPids[HEALING_ITEM_HEALING_POWDER])); + } + + configFree(&tweaksConfig); + } +} + +bool itemIsHealing(int pid) +{ + for (int index = 0; index < HEALING_ITEM_COUNT; index++) { + if (gHealingItemPids[index] == pid) { + return true; + } + } + + return false; +} diff --git a/src/item.h b/src/item.h index 2bd2189..012590d 100644 --- a/src/item.h +++ b/src/item.h @@ -13,6 +13,13 @@ typedef enum _WeaponClass { ATTACK_TYPE_COUNT, } WeaponClass; +typedef enum HealingItem { + HEALING_ITEM_STIMPACK, + HEALING_ITEM_SUPER_STIMPACK, + HEALING_ITEM_HEALING_POWDER, + HEALING_ITEM_COUNT, +} HealingItem; + int itemsInit(); void itemsReset(); void itemsExit(); @@ -143,5 +150,6 @@ int explosionGetDamageType(); void explosionSetDamageType(int damageType); int explosionGetMaxTargets(); void explosionSetMaxTargets(int maxTargets); +bool itemIsHealing(int pid); #endif /* ITEM_H */ diff --git a/src/message.cc b/src/message.cc index 96280c5..dadfd8a 100644 --- a/src/message.cc +++ b/src/message.cc @@ -4,7 +4,9 @@ #include "game_config.h" #include "memory.h" #include "platform_compat.h" +#include "proto_types.h" #include "random.h" +#include "sfall_config.h" #include #include @@ -200,6 +202,15 @@ bool messageListLoad(MessageList* messageList, const char* path) sprintf(localized_path, "%s\\%s\\%s", "text", language, path); file_ptr = fileOpen(localized_path, "rt"); + + // SFALL: Fallback to english if requested localization does not exist. + if (file_ptr == NULL) { + if (compat_stricmp(language, ENGLISH) != 0) { + sprintf(localized_path, "%s\\%s\\%s", "text", ENGLISH, path); + file_ptr = fileOpen(localized_path, "rt"); + } + } + if (file_ptr == NULL) { return false; } @@ -560,3 +571,46 @@ bool messageListFilterBadwords(MessageList* messageList) return true; } + +void messageListFilterGenderWords(MessageList* messageList, int gender) +{ + if (messageList == NULL) { + return; + } + + bool enabled = false; + configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_GAME_DIALOG_GENDER_WORDS_KEY, &enabled); + if (!enabled) { + return; + } + + for (int index = 0; index < messageList->entries_num; index++) { + MessageListItem* item = &(messageList->entries[index]); + char* text = item->text; + char* sep; + + while ((sep = strchr(text, '^')) != NULL) { + *sep = '\0'; + char* start = strrchr(text, '<'); + char* end = strchr(sep + 1, '>'); + *sep = '^'; + + if (start != NULL && end != NULL) { + char* src; + size_t length; + if (gender == GENDER_FEMALE) { + src = sep + 1; + length = end - sep - 1; + } else { + src = start + 1; + length = sep - start - 1; + } + + strncpy(start, src, length); + strcpy(start + length, end + 1); + } else { + text = sep + 1; + } + } + } +} diff --git a/src/message.h b/src/message.h index a1fab3a..ff70d76 100644 --- a/src/message.h +++ b/src/message.h @@ -27,4 +27,6 @@ bool _message_make_path(char* dest, const char* path); char* getmsg(MessageList* msg, MessageListItem* entry, int num); bool messageListFilterBadwords(MessageList* messageList); +void messageListFilterGenderWords(MessageList* messageList, int gender); + #endif /* MESSAGE_H */ diff --git a/src/object.cc b/src/object.cc index 6d1e6f5..5a1f908 100644 --- a/src/object.cc +++ b/src/object.cc @@ -5222,3 +5222,16 @@ static int _obj_preload_sort(const void* a1, const void* a2) cmp = ((v1 & 0xFF0000) >> 16) - (((v2 & 0xFF0000) >> 16)); return cmp; } + +Object* objectTypedFindById(int id, int type) +{ + Object* obj = objectFindFirst(); + while (obj != NULL) { + if (obj->id == id && PID_TYPE(obj->pid) == type) { + return obj; + } + obj = objectFindNext(); + } + + return NULL; +} diff --git a/src/object.h b/src/object.h index 93c62b3..18ae033 100644 --- a/src/object.h +++ b/src/object.h @@ -96,4 +96,6 @@ int _obj_save_dude(File* stream); int _obj_load_dude(File* stream); void _obj_fix_violence_settings(int* fid); +Object* objectTypedFindById(int id, int type); + #endif /* OBJECT_H */ diff --git a/src/proto.cc b/src/proto.cc index 0b7427f..0445b26 100644 --- a/src/proto.cc +++ b/src/proto.cc @@ -1863,6 +1863,10 @@ int _ResetPlayer() pcStatsReset(); protoCritterDataResetStats(&(proto->critter.data)); + + // SFALL: Fix base EMP DR not being properly initialized. + proto->critter.data.baseStats[STAT_DAMAGE_RESISTANCE_EMP] = 100; + critterReset(); characterEditorReset(); protoCritterDataResetSkills(&(proto->critter.data)); diff --git a/src/proto_instance.cc b/src/proto_instance.cc index e3f782d..72be2e6 100644 --- a/src/proto_instance.cc +++ b/src/proto_instance.cc @@ -8,6 +8,7 @@ #include "debug.h" #include "display_monitor.h" #include "game.h" +#include "game_dialog.h" #include "game_sound.h" #include "geometry.h" #include "interface.h" @@ -499,6 +500,12 @@ int _obj_examine_func(Object* critter, Object* target, void (*fn)(char* string)) fn(formattedText); } } else if (itemType == ITEM_TYPE_AMMO) { + // SFALL: Fix ammo details when examining in barter screen. + // CE: Underlying `gameDialogRenderSupplementaryMessage` cannot + // accumulate strings like `inventoryRenderItemDescription` does. + char ammoFormattedText[260 * 3]; + ammoFormattedText[0] = '\0'; + MessageListItem ammoMessageListItem; ammoMessageListItem.num = 510; @@ -510,7 +517,11 @@ int _obj_examine_func(Object* critter, Object* target, void (*fn)(char* string)) sprintf(formattedText, ammoMessageListItem.text, ammoGetArmorClassModifier(target)); - fn(formattedText); + if (fn == gameDialogRenderSupplementaryMessage) { + strcat(ammoFormattedText, formattedText); + } else { + fn(formattedText); + } ammoMessageListItem.num++; if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) { @@ -521,7 +532,12 @@ int _obj_examine_func(Object* critter, Object* target, void (*fn)(char* string)) sprintf(formattedText, ammoMessageListItem.text, ammoGetDamageResistanceModifier(target)); - fn(formattedText); + if (fn == gameDialogRenderSupplementaryMessage) { + strcat(ammoFormattedText, ", "); + strcat(ammoFormattedText, formattedText); + } else { + fn(formattedText); + } ammoMessageListItem.num++; if (!messageListGetItem(&gProtoMessageList, &ammoMessageListItem)) { @@ -533,7 +549,14 @@ int _obj_examine_func(Object* critter, Object* target, void (*fn)(char* string)) ammoMessageListItem.text, ammoGetDamageMultiplier(target), ammoGetDamageDivisor(target)); - fn(formattedText); + if (fn == gameDialogRenderSupplementaryMessage) { + strcat(ammoFormattedText, ", "); + strcat(ammoFormattedText, formattedText); + strcat(ammoFormattedText, "."); + fn(ammoFormattedText); + } else { + fn(formattedText); + } } } @@ -1208,7 +1231,7 @@ static int _protinst_default_use_item(Object* a1, Object* a2, Object* item) messageListItem.num = 582; if (messageListGetItem(&gProtoMessageList, &messageListItem)) { - sprintf(formattedText, messageListItem.text); + sprintf(formattedText, "%s", messageListItem.text); displayMonitorAddMessage(formattedText); } return -1; diff --git a/src/scripts.cc b/src/scripts.cc index 6b6f30b..dd31748 100644 --- a/src/scripts.cc +++ b/src/scripts.cc @@ -24,6 +24,7 @@ #include "proto.h" #include "proto_instance.h" #include "queue.h" +#include "stat.h" #include "tile.h" #include "window_manager.h" #include "window_manager_private.h" @@ -36,6 +37,10 @@ #define SCRIPT_LIST_EXTENT_SIZE 16 +// SFALL: Increase number of message lists for scripted dialogs. +// CE: In Sfall this increase is configurable with `BoostScriptDialogLimit`. +#define SCRIPT_DIALOG_MESSAGE_LIST_CAPACITY 10000 + typedef struct ScriptsListEntry { char name[16]; int local_vars_num; @@ -242,7 +247,7 @@ static Object* gScriptsRequestedStealingBy; static Object* gScriptsRequestedStealingFrom; // 0x6649D4 -static MessageList _script_dialog_msgs[1450]; +static MessageList _script_dialog_msgs[SCRIPT_DIALOG_MESSAGE_LIST_CAPACITY]; // scr.msg // @@ -1494,7 +1499,7 @@ int scriptsInit() return -1; } - for (int index = 0; index < 1450; index++) { + for (int index = 0; index < SCRIPT_DIALOG_MESSAGE_LIST_CAPACITY; index++) { if (!messageListInit(&(_script_dialog_msgs[index]))) { return -1; } @@ -1541,7 +1546,7 @@ int _scr_game_init() return -1; } - for (i = 0; i < 1450; i++) { + for (i = 0; i < SCRIPT_DIALOG_MESSAGE_LIST_CAPACITY; i++) { if (!messageListInit(&(_script_dialog_msgs[i]))) { debugPrint("\nERROR IN SCRIPT_DIALOG_MSGS!"); return -1; @@ -1611,7 +1616,7 @@ int scriptsExit() // 0x4A52F4 int _scr_message_free() { - for (int index = 0; index < 1450; index++) { + for (int index = 0; index < SCRIPT_DIALOG_MESSAGE_LIST_CAPACITY; index++) { MessageList* messageList = &(_script_dialog_msgs[index]); if (messageList->entries_num != 0) { if (!messageListFree(messageList)) { @@ -2657,6 +2662,10 @@ static int scriptsGetMessageList(int a1, MessageList** messageListPtr) debugPrint("\nError filtering script dialog message file!"); return -1; } + + // SFALL: Gender-specific words. + int gender = critterGetStat(gDude, STAT_GENDER); + messageListFilterGenderWords(messageList, gender); } *messageListPtr = messageList; diff --git a/src/sfall_config.h b/src/sfall_config.h index 05bb1aa..c6a64fd 100644 --- a/src/sfall_config.h +++ b/src/sfall_config.h @@ -41,6 +41,22 @@ #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_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_USE_LOCKPICK_FRM_KEY "Lockpick" +#define SFALL_CONFIG_USE_STEAL_FRM_KEY "Steal" +#define SFALL_CONFIG_USE_TRAPS_FRM_KEY "Traps" +#define SFALL_CONFIG_USE_FIRST_AID_FRM_KEY "FirstAid" +#define SFALL_CONFIG_USE_DOCTOR_FRM_KEY "Doctor" +#define SFALL_CONFIG_USE_SCIENCE_FRM_KEY "Science" +#define SFALL_CONFIG_USE_REPAIR_FRM_KEY "Repair" +#define SFALL_CONFIG_SCIENCE_REPAIR_TARGET_TYPE_KEY "ScienceOnCritters" +#define SFALL_CONFIG_GAME_DIALOG_FIX_KEY "DialogueFix" +#define SFALL_CONFIG_TWEAKS_FILE_KEY "TweaksFile" +#define SFALL_CONFIG_GAME_DIALOG_GENDER_WORDS_KEY "DialogGenderWords" +#define SFALL_CONFIG_TOWN_MAP_HOTKEYS_FIX "TownMapHotkeysFix" #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..9f33b44 100644 --- a/src/stat.cc +++ b/src/stat.cc @@ -717,14 +717,16 @@ int statRoll(Object* critter, int stat, int modifier, int* howMuch) } // 0x4AFAA8 -int pcAddExperience(int xp) +int pcAddExperience(int xp, int* xpGained) { - return pcAddExperienceWithOptions(xp, true); + return pcAddExperienceWithOptions(xp, true, xpGained); } // 0x4AFAB8 -int pcAddExperienceWithOptions(int xp, bool a2) +int pcAddExperienceWithOptions(int xp, bool a2, int* xpGained) { + int oldXp = gPcStatValues[PC_STAT_EXPERIENCE]; + int newXp = gPcStatValues[PC_STAT_EXPERIENCE]; newXp += xp; newXp += perkGetRank(gDude, PERK_SWIFT_LEARNER) * 5 * xp / 100; @@ -772,12 +774,22 @@ 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(); } } } + if (xpGained != NULL) { + *xpGained = newXp - oldXp; + } + return 0; } diff --git a/src/stat.h b/src/stat.h index d5132b9..afe5444 100644 --- a/src/stat.h +++ b/src/stat.h @@ -35,8 +35,8 @@ char* pcStatGetName(int pcStat); char* pcStatGetDescription(int pcStat); int statGetFrmId(int stat); int statRoll(Object* critter, int stat, int modifier, int* howMuch); -int pcAddExperience(int xp); -int pcAddExperienceWithOptions(int xp, bool a2); +int pcAddExperience(int xp, int* xpGained = NULL); +int pcAddExperienceWithOptions(int xp, bool a2, int* xpGained = NULL); int pcSetExperience(int a1); static inline bool statIsValid(int stat) diff --git a/src/window_manager.cc b/src/window_manager.cc index 9840c71..5411ab1 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -132,7 +132,7 @@ int windowManagerInit(VideoSystemInitProc* videoSystemInitProc, VideoSystemExitP gOrderedWindowIds[index] = -1; } - if (!_db_total()) { + if (_db_total() == 0) { if (dbOpen(NULL, 0, _path_patches, 1) == -1) { return WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE; } diff --git a/src/world_map.cc b/src/world_map.cc index 21e1d3a..3922895 100644 --- a/src/world_map.cc +++ b/src/world_map.cc @@ -28,6 +28,7 @@ #include "queue.h" #include "random.h" #include "scripts.h" +#include "sfall_config.h" #include "skill.h" #include "stat.h" #include "string_parsers.h" @@ -902,6 +903,8 @@ static int _wmMaxEncBaseTypes; // 0x67303C static int gEncounterTablesLength; +static bool gTownMapHotkeysFix; + static int _wmGenDataInit(); static int _wmGenDataReset(); static int _wmWorldMapSaveTempData(); @@ -1026,6 +1029,10 @@ int worldmapInit() _wmMarkSubTileRadiusVisited(_world_xpos, _world_ypos); _wmWorldMapSaveTempData(); + // SFALL + gTownMapHotkeysFix = true; + configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_TOWN_MAP_HOTKEYS_FIX, &gTownMapHotkeysFix); + return 0; } @@ -3678,21 +3685,21 @@ int _wmRndEncounterOccurred() int xp = 100 - outdoorsman; if (xp > 0) { + // SFALL: Display actual xp received. + debugPrint("WorldMap: Giving Player [%d] Experience For Catching Rnd Encounter!", xp); + + int xpGained; + pcAddExperience(xp, &xpGained); + MessageListItem messageListItem; char* text = getmsg(&gMiscMessageList, &messageListItem, 8500); if (strlen(text) < 110) { char formattedText[120]; - sprintf(formattedText, text, xp); + sprintf(formattedText, text, xpGained); displayMonitorAddMessage(formattedText); } else { debugPrint("WorldMap: Error: Rnd Encounter string too long!"); } - - debugPrint("WorldMap: Giving Player [%d] Experience For Catching Rnd Encounter!", xp); - - if (xp < 100) { - pcAddExperience(xp); - } } } } else { @@ -6104,6 +6111,14 @@ int worldmapCityMapViewSelect(int* mapIndexPtr) if (keyCode >= KEY_1 && keyCode < KEY_1 + city->entrancesLength) { EntranceInfo* entrance = &(city->entrances[keyCode - KEY_1]); + // SFALL: Prevent using number keys to enter unvisited areas on + // a town map. + if (gTownMapHotkeysFix) { + if (entrance->state == 0 || entrance->x == -1 || entrance->y == -1) { + continue; + } + } + *mapIndexPtr = entrance->map; mapSetEnteringLocation(entrance->elevation, entrance->tile, entrance->rotation);