Merge branch 'main' into add-loading-dialog

This commit is contained in:
Alexander Batalov 2022-08-17 00:10:02 +03:00
commit 1de1cdddc1
35 changed files with 1547 additions and 382 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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 */

View File

@ -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) {

View File

@ -17,11 +17,13 @@
#include <limits.h>
#include <string.h>
// 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);
}

View File

@ -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();

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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]));
}

View File

@ -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;
}
}
}

View File

@ -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");
}

View File

@ -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<Object*>(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<Object*>(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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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 */

View File

@ -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 <ctype.h>
#include <stdio.h>
@ -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;
}
}
}
}

View File

@ -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 */

View File

@ -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;
}

View File

@ -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 */

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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);