Merge branch 'main' into bugfix/f2-nevada-pointers

This commit is contained in:
Alexander Batalov 2024-01-16 15:30:36 +03:00
commit a7fa8da1cd
107 changed files with 8306 additions and 1020 deletions

View File

@ -1,2 +1,3 @@
BasedOnStyle: WebKit
AllowShortIfStatementsOnASingleLine: WithoutElse
FixNamespaceComments: true

View File

@ -4,7 +4,7 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(EXECUTABLE_NAME fallout2-ce)
if (APPLE)
if(APPLE)
if(IOS)
set(CMAKE_OSX_DEPLOYMENT_TARGET "11" CACHE STRING "")
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "")
@ -20,7 +20,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)
if (ANDROID)
if(ANDROID)
add_library(${EXECUTABLE_NAME} SHARED)
else()
add_executable(${EXECUTABLE_NAME} WIN32 MACOSX_BUNDLE)
@ -253,6 +253,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
target_sources(${EXECUTABLE_NAME} PUBLIC
"src/audio_engine.cc"
"src/audio_engine.h"
"src/delay.cc"
"src/delay.h"
"src/fps_limiter.cc"
"src/fps_limiter.h"
"src/platform_compat.cc"
@ -267,10 +269,20 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
"src/sfall_config.h"
"src/sfall_global_vars.cc"
"src/sfall_global_vars.h"
"src/sfall_global_scripts.cc"
"src/sfall_global_scripts.h"
"src/sfall_ini.cc"
"src/sfall_ini.h"
"src/sfall_kb_helpers.cc"
"src/sfall_kb_helpers.h"
"src/sfall_lists.cc"
"src/sfall_lists.h"
"src/sfall_metarules.cc"
"src/sfall_metarules.h"
"src/sfall_opcodes.cc"
"src/sfall_opcodes.h"
"src/sfall_arrays.cc"
"src/sfall_arrays.h"
"src/touch.cc"
"src/touch.h"
)
@ -297,7 +309,7 @@ if(WIN32)
)
endif()
if (WIN32)
if(WIN32)
target_sources(${EXECUTABLE_NAME} PUBLIC
"os/windows/fallout2-ce.ico"
"os/windows/fallout2-ce.rc"
@ -352,7 +364,7 @@ add_subdirectory("third_party/fpattern")
target_link_libraries(${EXECUTABLE_NAME} ${FPATTERN_LIBRARY})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${FPATTERN_INCLUDE_DIR})
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux")
if((NOT ${CMAKE_SYSTEM_NAME} MATCHES "Linux") AND (NOT ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") AND (NOT ${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD"))
add_subdirectory("third_party/zlib")
add_subdirectory("third_party/sdl2")
else()
@ -377,7 +389,7 @@ if(APPLE)
else()
install(TARGETS ${EXECUTABLE_NAME} DESTINATION .)
if (CPACK_BUNDLE_APPLE_CERT_APP)
if(CPACK_BUNDLE_APPLE_CERT_APP)
install(CODE "
execute_process(COMMAND codesign --deep --force --options runtime --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" ${CMAKE_BINARY_DIR}/Fallout II Community Edition.app)
"

View File

@ -48,6 +48,9 @@ typedef enum ScienceRepairTargetType {
// 0x5106D0
static bool _action_in_explode = false;
// 0x5106D4
int rotation;
// 0x5106E0
static const int gNormalDeathAnimations[DAMAGE_TYPE_COUNT] = {
ANIM_DANCING_AUTOFIRE,

View File

@ -6,6 +6,8 @@
namespace fallout {
extern int rotation;
int _action_attack(Attack* attack);
int _action_use_an_item_on_object(Object* a1, Object* a2, Object* a3);
int _action_use_an_object(Object* a1, Object* a2);

View File

@ -1711,6 +1711,7 @@ int _make_path(Object* object, int from, int to, unsigned char* rotations, int a
return pathfinderFindPath(object, from, to, rotations, a5, _obj_blocking_at);
}
// TODO: move pathfinding into another unit
// 0x415EFC
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback)
{
@ -1939,18 +1940,18 @@ static int _tile_idistance(int tile1, int tile2)
}
// 0x4163AC
int _make_straight_path(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6)
int _make_straight_path(Object* obj, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6)
{
return _make_straight_path_func(a1, from, to, straightPathNodeList, obstaclePtr, a6, _obj_blocking_at);
return _make_straight_path_func(obj, from, to, straightPathNodeList, obstaclePtr, a6, _obj_blocking_at);
}
// TODO: Rather complex, but understandable, needs testing.
//
// 0x4163C8
int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback)
int _make_straight_path_func(Object* obj, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback)
{
if (obstaclePtr != NULL) {
Object* obstacle = callback(a1, from, a1->elevation);
Object* obstacle = callback(obj, from, obj->elevation);
if (obstacle != NULL) {
if (obstacle != *obstaclePtr && (a6 != 32 || (obstacle->flags & OBJECT_SHOOT_THRU) == 0)) {
*obstaclePtr = obstacle;
@ -1961,13 +1962,13 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
int fromX;
int fromY;
tileToScreenXY(from, &fromX, &fromY, a1->elevation);
tileToScreenXY(from, &fromX, &fromY, obj->elevation);
fromX += 16;
fromY += 8;
int toX;
int toY;
tileToScreenXY(to, &toX, &toY, a1->elevation);
tileToScreenXY(to, &toX, &toY, obj->elevation);
toX += 16;
toY += 8;
@ -2005,7 +2006,7 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
if (ddx <= ddy) {
int middle = ddx - ddy / 2;
while (true) {
tile = tileFromScreenXY(tileX, tileY, a1->elevation);
tile = tileFromScreenXY(tileX, tileY, obj->elevation);
v22 += 1;
if (v22 == a6) {
@ -2016,9 +2017,9 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
if (straightPathNodeList != NULL) {
StraightPathNode* pathNode = &(straightPathNodeList[pathNodeIndex]);
pathNode->tile = tile;
pathNode->elevation = a1->elevation;
pathNode->elevation = obj->elevation;
tileToScreenXY(tile, &fromX, &fromY, a1->elevation);
tileToScreenXY(tile, &fromX, &fromY, obj->elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@ -2044,10 +2045,10 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
if (tile != prevTile) {
if (obstaclePtr != NULL) {
Object* obj = callback(a1, tile, a1->elevation);
if (obj != NULL) {
if (obj != *obstaclePtr && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) {
*obstaclePtr = obj;
Object* obstacle = callback(obj, tile, obj->elevation);
if (obstacle != NULL) {
if (obstacle != *obstaclePtr && (a6 != 32 || (obstacle->flags & OBJECT_SHOOT_THRU) == 0)) {
*obstaclePtr = obstacle;
break;
}
}
@ -2058,7 +2059,7 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
} else {
int middle = ddy - ddx / 2;
while (true) {
tile = tileFromScreenXY(tileX, tileY, a1->elevation);
tile = tileFromScreenXY(tileX, tileY, obj->elevation);
v22 += 1;
if (v22 == a6) {
@ -2069,9 +2070,9 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
if (straightPathNodeList != NULL) {
StraightPathNode* pathNode = &(straightPathNodeList[pathNodeIndex]);
pathNode->tile = tile;
pathNode->elevation = a1->elevation;
pathNode->elevation = obj->elevation;
tileToScreenXY(tile, &fromX, &fromY, a1->elevation);
tileToScreenXY(tile, &fromX, &fromY, obj->elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@ -2097,10 +2098,10 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
if (tile != prevTile) {
if (obstaclePtr != NULL) {
Object* obj = callback(a1, tile, a1->elevation);
if (obj != NULL) {
if (obj != *obstaclePtr && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) {
*obstaclePtr = obj;
Object* obstacle = callback(obj, tile, obj->elevation);
if (obstacle != NULL) {
if (obstacle != *obstaclePtr && (a6 != 32 || (obstacle->flags & OBJECT_SHOOT_THRU) == 0)) {
*obstaclePtr = obstacle;
break;
}
}
@ -2118,9 +2119,9 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
if (straightPathNodeList != NULL) {
StraightPathNode* pathNode = &(straightPathNodeList[pathNodeIndex]);
pathNode->tile = tile;
pathNode->elevation = a1->elevation;
pathNode->elevation = obj->elevation;
tileToScreenXY(tile, &fromX, &fromY, a1->elevation);
tileToScreenXY(tile, &fromX, &fromY, obj->elevation);
pathNode->x = tileX - fromX - 16;
pathNode->y = tileY - fromY - 8;
}
@ -2128,7 +2129,7 @@ int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* str
pathNodeIndex += 1;
} else {
if (pathNodeIndex > 0 && straightPathNodeList != NULL) {
straightPathNodeList[pathNodeIndex - 1].elevation = a1->elevation;
straightPathNodeList[pathNodeIndex - 1].elevation = obj->elevation;
}
}

View File

@ -145,8 +145,8 @@ int animationRegisterAnimateForever(Object* owner, int anim, int delay);
int animationRegisterPing(int flags, int delay);
int _make_path(Object* object, int from, int to, unsigned char* a4, int a5);
int pathfinderFindPath(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback);
int _make_straight_path(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6);
int _make_straight_path_func(Object* a1, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback);
int _make_straight_path(Object* object, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6);
int _make_straight_path_func(Object* object, int from, int to, StraightPathNode* straightPathNodeList, Object** obstaclePtr, int a6, PathBuilderCallback* callback);
void _object_animate();
int _check_move(int* actionPointsPtr);
int _dude_move(int actionPoints);

View File

@ -438,6 +438,14 @@ void artRender(int fid, unsigned char* dest, int width, int height, int pitch)
artUnlock(handle);
}
// mapper2.exe: 0x40A03C
int art_list_str(int fid, char* name)
{
// TODO: Incomplete.
return -1;
}
// 0x419160
Art* artLock(int fid, CacheEntry** handlePtr)
{

View File

@ -123,6 +123,7 @@ char* artGetObjectTypeName(int objectType);
int artIsObjectTypeHidden(int objectType);
int artGetFidgetCount(int headFid);
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
int art_list_str(int fid, char* name);
Art* artLock(int fid, CacheEntry** cache_entry);
unsigned char* artLockFrameData(int fid, int frame, int direction, CacheEntry** out_cache_entry);
unsigned char* artLockFrameDataReturningSize(int fid, CacheEntry** out_cache_entry, int* widthPtr, int* heightPtr);

View File

@ -16,6 +16,7 @@
#include "db.h"
#include "dbox.h"
#include "debug.h"
#include "delay.h"
#include "draw.h"
#include "game.h"
#include "game_mouse.h"
@ -1991,7 +1992,7 @@ static int _get_input_str(int win, int cancelKeyCode, char* text, int maxLength,
windowRefresh(win);
while (getTicksSince(_frame_time) < 1000 / 24) { }
delay_ms(1000 / 24 - (getTicks() - _frame_time));
renderPresent();
sharedFpsLimiter.throttle();
@ -2281,8 +2282,7 @@ static void characterEditorDrawBigNumber(int x, int y, int flags, int value, int
windowWidth);
windowRefreshRect(windowHandle, &rect);
renderPresent();
while (getTicksSince(_frame_time) < BIG_NUM_ANIMATION_DELAY)
;
delay_ms(BIG_NUM_ANIMATION_DELAY - (getTicks() - _frame_time));
}
blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones,
@ -2304,8 +2304,7 @@ static void characterEditorDrawBigNumber(int x, int y, int flags, int value, int
windowWidth);
windowRefreshRect(windowHandle, &rect);
renderPresent();
while (getTicksSince(_frame_time) < BIG_NUM_ANIMATION_DELAY)
;
delay_ms(BIG_NUM_ANIMATION_DELAY - (getTicks() - _frame_time));
}
blitBufferToBuffer(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens,
@ -3530,11 +3529,9 @@ static int characterEditorEditAge()
}
if (v33 > dbl_50170B) {
while (getTicksSince(_frame_time) < 1000 / _repFtime)
;
delay_ms(1000 / _repFtime - (getTicks() - _frame_time));
} else {
while (getTicksSince(_frame_time) < 1000 / 24)
;
delay_ms(1000 / 24 - (getTicks() - _frame_time));
}
keyCode = inputGetInput();
@ -3548,8 +3545,7 @@ static int characterEditorEditAge()
} else {
windowRefresh(win);
while (getTicksSince(_frame_time) < 1000 / 24)
;
delay_ms(1000 / 24 - (getTicks() - _frame_time));
}
renderPresent();
@ -3699,8 +3695,7 @@ static void characterEditorEditGender()
windowRefresh(win);
while (getTicksSince(_frame_time) < 41)
;
delay_ms(41 - (getTicks() - _frame_time));
renderPresent();
sharedFpsLimiter.throttle();
@ -3778,12 +3773,9 @@ static void characterEditorAdjustPrimaryStat(int eventCode)
}
if (v11 >= 19.2) {
unsigned int delay = 1000 / _repFtime;
while (getTicksSince(_frame_time) < delay) {
}
delay_ms(1000 / _repFtime - (getTicks() - _frame_time));
} else {
while (getTicksSince(_frame_time) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - _frame_time));
}
renderPresent();
@ -5279,11 +5271,9 @@ static void characterEditorHandleAdjustSkillButtonPressed(int keyCode)
if (!isUsingKeyboard) {
unspentSp = pcGetStat(PC_STAT_UNSPENT_SKILL_POINTS);
if (repeatDelay >= dbl_5018F0) {
while (getTicksSince(_frame_time) < 1000 / _repFtime) {
}
delay_ms(1000 / _repFtime - (getTicks() - _frame_time));
} else {
while (getTicksSince(_frame_time) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - _frame_time));
}
int keyCode = inputGetInput();
@ -6141,11 +6131,9 @@ static int perkDialogHandleInput(int count, void (*refreshProc)())
}
if (v19 < dbl_5019BE) {
while (getTicksSince(_frame_time) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - _frame_time));
} else {
while (getTicksSince(_frame_time) < 1000 / _repFtime) {
}
delay_ms(1000 / _repFtime - (getTicks() - _frame_time));
}
renderPresent();
@ -6188,11 +6176,9 @@ static int perkDialogHandleInput(int count, void (*refreshProc)())
}
if (v19 < dbl_5019BE) {
while (getTicksSince(_frame_time) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - _frame_time));
} else {
while (getTicksSince(_frame_time) < 1000 / _repFtime) {
}
delay_ms(1000 / _repFtime - (getTicks() - _frame_time));
}
renderPresent();
@ -6224,11 +6210,9 @@ static int perkDialogHandleInput(int count, void (*refreshProc)())
}
if (v19 < dbl_5019BE) {
while (getTicksSince(_frame_time) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - _frame_time));
} else {
while (getTicksSince(_frame_time) < 1000 / _repFtime) {
}
delay_ms(1000 / _repFtime - (getTicks() - _frame_time));
}
renderPresent();

View File

@ -37,6 +37,7 @@
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "sfall_global_scripts.h"
#include "skill.h"
#include "stat.h"
#include "svga.h"
@ -115,7 +116,7 @@ static int attackComputeCriticalHit(Attack* a1);
static int _attackFindInvalidFlags(Object* a1, Object* a2);
static int attackComputeCriticalFailure(Attack* attack);
static void _do_random_cripple(int* flagsPtr);
static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool a6);
static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool useDistance);
static void attackComputeDamage(Attack* attack, int ammoQuantity, int a3);
static void _check_for_death(Object* a1, int a2, int* a3);
static void _set_new_results(Object* a1, int a2);
@ -3144,6 +3145,10 @@ static int _combat_input()
}
int keyCode = inputGetInput();
// SFALL: CombatLoopHook.
sfall_gl_scr_process_main();
if (_action_explode_running()) {
// NOTE: Uninline.
_combat_turn_run();
@ -3471,16 +3476,16 @@ void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode,
}
// 0x422F3C
int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation)
int _combat_attack(Object* attacker, Object* defender, int hitMode, int hitLocation)
{
if (a1 != gDude && hitMode == HIT_MODE_PUNCH && randomBetween(1, 4) == 1) {
int fid = buildFid(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_KICK_LEG, (a1->fid & 0xF000) >> 12, (a1->fid & 0x70000000) >> 28);
if (attacker != gDude && hitMode == HIT_MODE_PUNCH && randomBetween(1, 4) == 1) {
int fid = buildFid(OBJ_TYPE_CRITTER, attacker->fid & 0xFFF, ANIM_KICK_LEG, (attacker->fid & 0xF000) >> 12, (attacker->fid & 0x70000000) >> 28);
if (artExists(fid)) {
hitMode = HIT_MODE_KICK;
}
}
attackInit(&_main_ctd, a1, a2, hitMode, hitLocation);
attackInit(&_main_ctd, attacker, defender, hitMode, hitLocation);
debugPrint("computing attack...\n");
if (attackCompute(&_main_ctd) == -1) {
@ -3508,7 +3513,7 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation)
bool aiming;
if (_main_ctd.defenderHitLocation == HIT_LOCATION_TORSO || _main_ctd.defenderHitLocation == HIT_LOCATION_UNCALLED) {
if (a1 == gDude) {
if (attacker == gDude) {
interfaceGetCurrentHitMode(&hitMode, &aiming);
} else {
aiming = false;
@ -3517,22 +3522,22 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation)
aiming = true;
}
int actionPoints = weaponGetActionPointCost(a1, _main_ctd.hitMode, aiming);
int actionPoints = weaponGetActionPointCost(attacker, _main_ctd.hitMode, aiming);
debugPrint("sequencing attack...\n");
if (_action_attack(&_main_ctd) == -1) {
return -1;
}
if (actionPoints > a1->data.critter.combat.ap) {
a1->data.critter.combat.ap = 0;
if (actionPoints > attacker->data.critter.combat.ap) {
attacker->data.critter.combat.ap = 0;
} else {
a1->data.critter.combat.ap -= actionPoints;
attacker->data.critter.combat.ap -= actionPoints;
}
if (a1 == gDude) {
interfaceRenderActionPoints(a1->data.critter.combat.ap, _combat_free_move);
_critter_set_who_hit_me(a1, a2);
if (attacker == gDude) {
interfaceRenderActionPoints(attacker->data.critter.combat.ap, _combat_free_move);
_critter_set_who_hit_me(attacker, defender);
}
// SFALL
@ -3540,19 +3545,19 @@ int _combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation)
_combat_call_display = 1;
_combat_cleanup_enabled = 1;
aiInfoSetLastTarget(a1, a2);
aiInfoSetLastTarget(attacker, defender);
debugPrint("running attack...\n");
return 0;
}
// Returns tile one step closer from [a1] to [a2]
// Returns tile one step closer from [attacker] to [target]
//
// 0x423104
int _combat_bullet_start(const Object* a1, const Object* a2)
int _combat_bullet_start(const Object* attacker, const Object* target)
{
int rotation = tileGetRotationTo(a1->tile, a2->tile);
return tileGetTileInDirection(a1->tile, rotation, 1);
int rotation = tileGetRotationTo(attacker->tile, target->tile);
return tileGetTileInDirection(attacker->tile, rotation, 1);
}
// 0x423128
@ -3937,17 +3942,17 @@ static int attackCompute(Attack* attack)
attack->tile = tile;
Object* v25 = attack->defender;
_make_straight_path_func(v25, attack->defender->tile, attack->tile, NULL, &v25, 32, _obj_shoot_blocking_at);
if (v25 != NULL && v25 != attack->defender) {
attack->tile = v25->tile;
Object* accidentalTarget = attack->defender;
_make_straight_path_func(accidentalTarget, attack->defender->tile, attack->tile, NULL, &accidentalTarget, 32, _obj_shoot_blocking_at);
if (accidentalTarget != NULL && accidentalTarget != attack->defender) {
attack->tile = accidentalTarget->tile;
} else {
v25 = _obj_blocking_at(NULL, attack->tile, attack->defender->elevation);
accidentalTarget = _obj_blocking_at(NULL, attack->tile, attack->defender->elevation);
}
if (v25 != NULL && (v25->flags & OBJECT_SHOOT_THRU) == 0) {
if (accidentalTarget != NULL && (accidentalTarget->flags & OBJECT_SHOOT_THRU) == 0) {
attack->attackerFlags |= DAM_HIT;
attack->defender = v25;
attack->defender = accidentalTarget;
attackComputeDamage(attack, 1, 2);
}
}
@ -3969,75 +3974,75 @@ static int attackCompute(Attack* attack)
// compute_explosion_on_extras
// 0x423C10
void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4)
void _compute_explosion_on_extras(Attack* attack, bool isFromAttacker, bool isGrenade, bool noDamage)
{
Object* attacker;
Object* targetObj;
if (a2) {
attacker = attack->attacker;
if (isFromAttacker) {
targetObj = attack->attacker;
} else {
if ((attack->attackerFlags & DAM_HIT) != 0) {
attacker = attack->defender;
targetObj = attack->defender;
} else {
attacker = NULL;
targetObj = NULL;
}
}
int tile;
if (attacker != NULL) {
tile = attacker->tile;
int explosionTile;
if (targetObj != NULL) {
explosionTile = targetObj->tile;
} else {
tile = attack->tile;
explosionTile = attack->tile;
}
if (tile == -1) {
if (explosionTile == -1) {
debugPrint("\nError: compute_explosion_on_extras: Called with bad target/tileNum");
return;
}
// TODO: The math in this loop is rather complex and hard to understand.
int v20;
int v22 = 0;
int ringTileIdx;
int radius = 0;
int rotation = 0;
int v5 = -1;
int v19 = tile;
int tile = -1;
int ringFirstTile = explosionTile;
// SFALL
int maxTargets = explosionGetMaxTargets();
// Check adjacent tiles for possible targets, going ring-by-ring
while (attack->extrasLength < maxTargets) {
if (v22 != 0 && (v5 == -1 || (v5 = tileGetTileInDirection(v5, rotation, 1)) != v19)) {
v20++;
if (v20 % v22 == 0) {
if (radius != 0 && (tile == -1 || (tile = tileGetTileInDirection(tile, rotation, 1)) != ringFirstTile)) {
ringTileIdx++;
if (ringTileIdx % radius == 0) { // the larger the radius, the slower we rotate
rotation += 1;
if (rotation == ROTATION_COUNT) {
rotation = ROTATION_NE;
}
}
} else {
v22++;
if (isGrenade && weaponGetGrenadeExplosionRadius(attack->weapon) < v22) {
v5 = -1;
} else if (isGrenade || weaponGetRocketExplosionRadius(attack->weapon) >= v22) {
v5 = tileGetTileInDirection(v19, ROTATION_NE, 1);
radius++; // go to the next ring
if (isGrenade && weaponGetGrenadeExplosionRadius(attack->weapon) < radius) {
tile = -1;
} else if (isGrenade || weaponGetRocketExplosionRadius(attack->weapon) >= radius) {
tile = tileGetTileInDirection(ringFirstTile, ROTATION_NE, 1);
} else {
v5 = -1;
tile = -1;
}
v19 = v5;
ringFirstTile = tile;
rotation = ROTATION_SE;
v20 = 0;
ringTileIdx = 0;
}
if (v5 == -1) {
if (tile == -1) {
break;
}
Object* obstacle = _obj_blocking_at(attacker, v5, attack->attacker->elevation);
Object* obstacle = _obj_blocking_at(targetObj, tile, attack->attacker->elevation);
if (obstacle != NULL
&& FID_TYPE(obstacle->fid) == OBJ_TYPE_CRITTER
&& (obstacle->data.critter.combat.results & DAM_DEAD) == 0
&& (obstacle->flags & OBJECT_SHOOT_THRU) == 0
&& !_combat_is_shot_blocked(obstacle, obstacle->tile, tile, NULL, NULL)) {
&& !_combat_is_shot_blocked(obstacle, obstacle->tile, explosionTile, NULL, NULL)) {
if (obstacle == attack->attacker) {
attack->attackerFlags &= ~DAM_HIT;
attackComputeDamage(attack, 1, 2);
@ -4055,7 +4060,7 @@ void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4
attack->extrasHitLocation[index] = HIT_LOCATION_TORSO;
attack->extras[index] = obstacle;
attackInit(&_explosion_ctd, attack->attacker, obstacle, attack->hitMode, HIT_LOCATION_TORSO);
if (!a4) {
if (!noDamage) {
_explosion_ctd.attackerFlags |= DAM_HIT;
attackComputeDamage(&_explosion_ctd, 1, 2);
}
@ -4277,26 +4282,26 @@ static void _do_random_cripple(int* flagsPtr)
}
// 0x42436C
int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode)
int _determine_to_hit(Object* attacker, Object* defender, int hitLocation, int hitMode)
{
return attackDetermineToHit(a1, a1->tile, a2, hitLocation, hitMode, true);
return attackDetermineToHit(attacker, attacker->tile, defender, hitLocation, hitMode, true);
}
// 0x424380
int _determine_to_hit_no_range(Object* a1, Object* a2, int hitLocation, int hitMode, unsigned char* a5)
int _determine_to_hit_no_range(Object* attacker, Object* defender, int hitLocation, int hitMode, unsigned char* a5)
{
return attackDetermineToHit(a1, a1->tile, a2, hitLocation, hitMode, false);
return attackDetermineToHit(attacker, attacker->tile, defender, hitLocation, hitMode, false);
}
// 0x424394
int _determine_to_hit_from_tile(Object* a1, int tile, Object* a3, int hitLocation, int hitMode)
int _determine_to_hit_from_tile(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode)
{
return attackDetermineToHit(a1, tile, a3, hitLocation, hitMode, true);
return attackDetermineToHit(attacker, tile, defender, hitLocation, hitMode, true);
}
// determine_to_hit
// 0x4243A8
static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool a6)
static int attackDetermineToHit(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, bool useDistance)
{
Object* weapon = critterGetWeaponForHitMode(attacker, hitMode);
@ -4306,32 +4311,30 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in
bool isRangedWeapon = false;
int accuracy;
int toHit;
if (weapon == NULL || isUnarmedHitMode(hitMode)) {
accuracy = skillGetValue(attacker, SKILL_UNARMED);
toHit = skillGetValue(attacker, SKILL_UNARMED);
} else {
accuracy = weaponGetSkillValue(attacker, hitMode);
int modifier = 0;
toHit = weaponGetSkillValue(attacker, hitMode);
int attackType = weaponGetAttackTypeForHitMode(weapon, hitMode);
if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) {
isRangedWeapon = true;
int v29 = 0;
int v25 = 0;
int perceptionBonusMult = 0;
int minEffectiveDist = 0;
int weaponPerk = weaponGetPerk(weapon);
switch (weaponPerk) {
case PERK_WEAPON_LONG_RANGE:
v29 = 4;
perceptionBonusMult = 4;
break;
case PERK_WEAPON_SCOPE_RANGE:
v29 = 5;
v25 = 8;
perceptionBonusMult = 5;
minEffectiveDist = 8;
break;
default:
v29 = 2;
perceptionBonusMult = 2;
break;
}
@ -4342,71 +4345,72 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in
perception += 2 * perkGetRank(gDude, PERK_SHARPSHOOTER);
}
int distanceMod = 0;
// SFALL: Fix for `determine_to_hit_func` function taking distance
// into account when called from `determine_to_hit_no_range`.
if (defender != NULL && a6) {
modifier = objectGetDistanceBetweenTiles(attacker, tile, defender, defender->tile);
if (defender != NULL && useDistance) {
distanceMod = objectGetDistanceBetweenTiles(attacker, tile, defender, defender->tile);
} else {
modifier = 0;
distanceMod = 0;
}
if (modifier >= v25) {
int penalty = attacker == gDude
? v29 * (perception - 2)
: v29 * perception;
if (distanceMod >= minEffectiveDist) {
int perceptionBonus = attacker == gDude
? perceptionBonusMult * (perception - 2)
: perceptionBonusMult * perception;
modifier -= penalty;
distanceMod -= perceptionBonus;
} else {
modifier += v25;
distanceMod += minEffectiveDist;
}
if (-2 * perception > modifier) {
modifier = -2 * perception;
if (distanceMod < -2 * perception) {
distanceMod = -2 * perception;
}
if (modifier >= 0) {
if (distanceMod >= 0) {
if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) {
modifier *= -12;
distanceMod *= -12;
} else {
modifier *= -4;
distanceMod *= -4;
}
} else {
modifier *= -4;
distanceMod *= -4;
}
if (a6 || modifier > 0) {
accuracy += modifier;
if (useDistance || distanceMod > 0) {
toHit += distanceMod;
}
modifier = 0;
int numCrittersInLof = 0;
if (defender != NULL && a6) {
_combat_is_shot_blocked(attacker, tile, defender->tile, defender, &modifier);
if (defender != NULL && useDistance) {
_combat_is_shot_blocked(attacker, tile, defender->tile, defender, &numCrittersInLof);
}
accuracy -= 10 * modifier;
toHit -= 10 * numCrittersInLof;
}
if (attacker == gDude && traitIsSelected(TRAIT_ONE_HANDER)) {
if (weaponIsTwoHanded(weapon)) {
accuracy -= 40;
toHit -= 40;
} else {
accuracy += 20;
toHit += 20;
}
}
int minStrength = weaponGetMinStrengthRequired(weapon);
modifier = minStrength - critterGetStat(attacker, STAT_STRENGTH);
int minStrengthMod = minStrength - critterGetStat(attacker, STAT_STRENGTH);
if (attacker == gDude && perkGetRank(gDude, PERK_WEAPON_HANDLING) != 0) {
modifier -= 3;
minStrengthMod -= 3;
}
if (modifier > 0) {
accuracy -= 20 * modifier;
if (minStrengthMod > 0) {
toHit -= 20 * minStrengthMod;
}
if (weaponGetPerk(weapon) == PERK_WEAPON_ACCURATE) {
accuracy += 20;
toHit += 20;
}
}
@ -4417,17 +4421,17 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in
armorClass = 0;
}
accuracy -= armorClass;
toHit -= armorClass;
}
if (isRangedWeapon) {
accuracy += hit_location_penalty[hitLocation];
toHit += hit_location_penalty[hitLocation];
} else {
accuracy += hit_location_penalty[hitLocation] / 2;
toHit += hit_location_penalty[hitLocation] / 2;
}
if (defender != NULL && (defender->flags & OBJECT_MULTIHEX) != 0) {
accuracy += 15;
toHit += 15;
}
if (attacker == gDude) {
@ -4442,45 +4446,45 @@ static int attackDetermineToHit(Object* attacker, int tile, Object* defender, in
}
if (lightIntensity <= 26214)
accuracy -= 40;
toHit -= 40;
else if (lightIntensity <= 39321)
accuracy -= 25;
toHit -= 25;
else if (lightIntensity <= 52428)
accuracy -= 10;
toHit -= 10;
}
if (_gcsd != NULL) {
accuracy += _gcsd->accuracyBonus;
toHit += _gcsd->accuracyBonus;
}
if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) {
accuracy -= 25;
toHit -= 25;
}
if (targetIsCritter && defender != NULL && (defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) {
accuracy += 40;
toHit += 40;
}
if (attacker->data.critter.combat.team != gDude->data.critter.combat.team) {
switch (settings.preferences.combat_difficulty) {
case 0:
accuracy -= 20;
toHit -= 20;
break;
case 2:
accuracy += 20;
toHit += 20;
break;
}
}
if (accuracy > 95) {
accuracy = 95;
if (toHit > 95) {
toHit = 95;
}
if (accuracy < -100) {
if (toHit < -100) {
debugPrint("Whoa! Bad skill value in determine_to_hit!\n");
}
return accuracy;
return toHit;
}
// 0x4247B8
@ -5875,39 +5879,35 @@ void _combat_highlight_change()
_combat_highlight = targetHighlight;
}
// Probably calculates line of sight or determines if object can see other object.
// Checks if line of fire to the target object is blocked or not. Optionally calculate number of critters on the line of fire.
//
// 0x426CC4
bool _combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5)
bool _combat_is_shot_blocked(Object* sourceObj, int from, int to, Object* targetObj, int* numCrittersOnLof)
{
if (a5 != NULL) {
*a5 = 0;
if (numCrittersOnLof != NULL) {
*numCrittersOnLof = 0;
}
Object* obstacle = a1;
Object* obstacle = sourceObj;
int current = from;
while (obstacle != NULL && current != to) {
_make_straight_path_func(a1, current, to, 0, &obstacle, 32, _obj_shoot_blocking_at);
_make_straight_path_func(sourceObj, current, to, 0, &obstacle, 32, _obj_shoot_blocking_at);
if (obstacle != NULL) {
if (FID_TYPE(obstacle->fid) != OBJ_TYPE_CRITTER && obstacle != a4) {
if (FID_TYPE(obstacle->fid) != OBJ_TYPE_CRITTER && obstacle != targetObj) {
return true;
}
if (a5 != NULL) {
if (obstacle != a4) {
if (a4 != NULL) {
// SFALL: Fix for combat_is_shot_blocked_ engine
// function not taking the flags of critters in the
// line of fire into account when calculating the hit
// chance penalty of ranged attacks in
// determine_to_hit_func_ engine function.
if ((obstacle->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_DOWN | DAM_KNOCKED_OUT)) == 0) {
*a5 += 1;
if (numCrittersOnLof != NULL && obstacle != targetObj && targetObj != NULL) {
// SFALL: Fix for combat_is_shot_blocked_ engine
// function not taking the flags of critters in the
// line of fire into account when calculating the hit
// chance penalty of ranged attacks in
// determine_to_hit_func_ engine function.
if ((obstacle->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_DOWN | DAM_KNOCKED_OUT)) == 0) {
*numCrittersOnLof += 1;
if ((obstacle->flags & OBJECT_MULTIHEX) != 0) {
*a5 += 1;
}
}
if ((obstacle->flags & OBJECT_MULTIHEX) != 0) {
*numCrittersOnLof += 1;
}
}
}
@ -6825,4 +6825,9 @@ void combat_reset_hit_location_penalty()
}
}
Attack* combat_get_data()
{
return &_main_ctd;
}
} // namespace fallout

View File

@ -34,13 +34,13 @@ void _combat_over_from_load();
void _combat_give_exps(int exp_points);
void _combat_turn_run();
void _combat(STRUCT_664980* attack);
void attackInit(Attack* attack, Object* a2, Object* a3, int a4, int a5);
int _combat_attack(Object* a1, Object* a2, int a3, int a4);
int _combat_bullet_start(const Object* a1, const Object* a2);
void _compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4);
void attackInit(Attack* attack, Object* attacker, Object* defender, int hitMode, int hitLocation);
int _combat_attack(Object* attacker, Object* defender, int hitMode, int hitLocation);
int _combat_bullet_start(const Object* attacker, const Object* target);
void _compute_explosion_on_extras(Attack* attack, bool isFromAttacker, bool isGrenade, bool noDamage);
int _determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode);
int _determine_to_hit_no_range(Object* a1, Object* a2, int a3, int a4, unsigned char* a5);
int _determine_to_hit_from_tile(Object* a1, int a2, Object* a3, int a4, int a5);
int _determine_to_hit_no_range(Object* attacker, Object* defender, int hitLocation, int hitMode, unsigned char* a5);
int _determine_to_hit_from_tile(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode);
void attackComputeDeathFlags(Attack* attack);
void _apply_damage(Attack* attack, bool animated);
void _combat_display(Attack* attack);
@ -52,7 +52,7 @@ void _combat_attack_this(Object* a1);
void _combat_outline_on();
void _combat_outline_off();
void _combat_highlight_change();
bool _combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5);
bool _combat_is_shot_blocked(Object* sourceObj, int from, int to, Object* targetObj, int* numCrittersOnLof);
int _combat_player_knocked_out_by();
int _combat_explode_scenery(Object* a1, Object* a2);
void _combat_delete_critter(Object* obj);
@ -74,6 +74,7 @@ bool damageModGetDisplayBonusDamage();
int combat_get_hit_location_penalty(int hit_location);
void combat_set_hit_location_penalty(int hit_location, int penalty);
void combat_reset_hit_location_penalty();
Attack* combat_get_data();
static inline bool isInCombat()
{

View File

@ -701,6 +701,30 @@ static int aiPacketWrite(File* stream, AiPacket* ai)
return 0;
}
// 0x428058
int combat_ai_num()
{
return gAiPacketsLength;
}
// 0x428060
char* combat_ai_name(int packet_num)
{
int index;
if (packet_num < 0 || packet_num >= gAiPacketsLength) {
return NULL;
}
for (index = 0; index < gAiPacketsLength; index++) {
if (gAiPackets[index].packet_num == packet_num) {
return gAiPackets[index].name;
}
}
return NULL;
}
// Get ai from object
//
// 0x4280B4
@ -1440,7 +1464,7 @@ static int aiFindAttackers(Object* critter, Object** whoHitMePtr, Object** whoHi
*whoHitFriendPtr = NULL;
}
if (*whoHitByFriendPtr != NULL) {
if (whoHitByFriendPtr != NULL) {
*whoHitByFriendPtr = NULL;
}
@ -2327,61 +2351,61 @@ static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender)
}
// 0x429FC8
static int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, bool taunt)
static int _ai_move_steps_closer(Object* critter, Object* target, int actionPoints, bool taunt)
{
if (actionPoints <= 0) {
return -1;
}
int distance = aiGetDistance(a1);
int distance = aiGetDistance(critter);
if (distance == DISTANCE_STAY) {
return -1;
}
if (distance == DISTANCE_STAY_CLOSE) {
if (a2 != gDude) {
int currentDistance = objectGetDistanceBetween(a1, gDude);
if (target != gDude) {
int currentDistance = objectGetDistanceBetween(critter, gDude);
if (currentDistance > 5
&& objectGetDistanceBetween(a2, gDude) > 5
&& objectGetDistanceBetween(target, gDude) > 5
&& currentDistance + actionPoints > 5) {
return -1;
}
}
}
if (objectGetDistanceBetween(a1, a2) <= 1) {
if (objectGetDistanceBetween(critter, target) <= 1) {
return -1;
}
reg_anim_begin(ANIMATION_REQUEST_RESERVED);
if (taunt) {
_combatai_msg(a1, NULL, AI_MESSAGE_TYPE_MOVE, 0);
_combatai_msg(critter, NULL, AI_MESSAGE_TYPE_MOVE, 0);
}
Object* v18 = a2;
Object* initialTarget = target;
bool shouldUnhide;
if ((a2->flags & OBJECT_MULTIHEX) != 0) {
if ((target->flags & OBJECT_MULTIHEX) != 0) {
shouldUnhide = true;
a2->flags |= OBJECT_HIDDEN;
target->flags |= OBJECT_HIDDEN;
} else {
shouldUnhide = false;
}
if (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_blocking_at) == 0) {
if (pathfinderFindPath(critter, critter->tile, target->tile, NULL, 0, _obj_blocking_at) == 0) {
_moveBlockObj = NULL;
if (pathfinderFindPath(a1, a1->tile, a2->tile, NULL, 0, _obj_ai_blocking_at) == 0
if (pathfinderFindPath(critter, critter->tile, target->tile, NULL, 0, _obj_ai_blocking_at) == 0
&& _moveBlockObj != NULL
&& PID_TYPE(_moveBlockObj->pid) == OBJ_TYPE_CRITTER) {
if (shouldUnhide) {
a2->flags &= ~OBJECT_HIDDEN;
target->flags &= ~OBJECT_HIDDEN;
}
a2 = _moveBlockObj;
if ((a2->flags & OBJECT_MULTIHEX) != 0) {
target = _moveBlockObj;
if ((target->flags & OBJECT_MULTIHEX) != 0) {
shouldUnhide = true;
a2->flags |= OBJECT_HIDDEN;
target->flags |= OBJECT_HIDDEN;
} else {
shouldUnhide = false;
}
@ -2389,25 +2413,25 @@ static int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, bool
}
if (shouldUnhide) {
a2->flags &= ~OBJECT_HIDDEN;
target->flags &= ~OBJECT_HIDDEN;
}
int tile = a2->tile;
if (a2 == v18) {
_cai_retargetTileFromFriendlyFire(a1, a2, &tile);
int tile = target->tile;
if (target == initialTarget) {
_cai_retargetTileFromFriendlyFire(critter, target, &tile);
}
if (actionPoints >= critterGetStat(a1, STAT_MAXIMUM_ACTION_POINTS) / 2 && artCritterFidShouldRun(a1->fid)) {
if ((a2->flags & OBJECT_MULTIHEX) != 0) {
animationRegisterRunToObject(a1, a2, actionPoints, 0);
if (actionPoints >= critterGetStat(critter, STAT_MAXIMUM_ACTION_POINTS) / 2 && artCritterFidShouldRun(critter->fid)) {
if ((target->flags & OBJECT_MULTIHEX) != 0) {
animationRegisterRunToObject(critter, target, actionPoints, 0);
} else {
animationRegisterRunToTile(a1, tile, a1->elevation, actionPoints, 0);
animationRegisterRunToTile(critter, tile, critter->elevation, actionPoints, 0);
}
} else {
if ((a2->flags & OBJECT_MULTIHEX) != 0) {
animationRegisterMoveToObject(a1, a2, actionPoints, 0);
if ((target->flags & OBJECT_MULTIHEX) != 0) {
animationRegisterMoveToObject(critter, target, actionPoints, 0);
} else {
animationRegisterMoveToTile(a1, tile, a1->elevation, actionPoints, 0);
animationRegisterMoveToTile(critter, tile, critter->elevation, actionPoints, 0);
}
}
@ -2665,35 +2689,35 @@ static int _ai_attack(Object* attacker, Object* defender, int hitMode)
}
// 0x42A7D8
static int _ai_try_attack(Object* a1, Object* a2)
static int _ai_try_attack(Object* attacker, Object* defender)
{
_critter_set_who_hit_me(a1, a2);
_critter_set_who_hit_me(attacker, defender);
CritterCombatData* combatData = &(a1->data.critter.combat);
CritterCombatData* combatData = &(attacker->data.critter.combat);
bool taunt = true;
Object* weapon = critterGetItem2(a1);
Object* weapon = critterGetItem2(attacker);
if (weapon != NULL && itemGetType(weapon) != ITEM_TYPE_WEAPON) {
weapon = NULL;
}
int hitMode = _ai_pick_hit_mode(a1, weapon, a2);
int minToHit = aiGetPacket(a1)->min_to_hit;
int hitMode = _ai_pick_hit_mode(attacker, weapon, defender);
int minToHit = aiGetPacket(attacker)->min_to_hit;
int actionPoints = a1->data.critter.combat.ap;
int actionPoints = attacker->data.critter.combat.ap;
int safeDistance = 0;
int v42 = 0;
int actionPointsToUse = 0;
if (weapon != NULL
|| (critterGetBodyType(a2) == BODY_TYPE_BIPED
&& ((a2->fid & 0xF000) >> 12 == 0)
&& artExists(buildFid(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_THROW_PUNCH, 0, a1->rotation + 1)))) {
|| (critterGetBodyType(defender) == BODY_TYPE_BIPED
&& ((defender->fid & 0xF000) >> 12 == 0)
&& artExists(buildFid(OBJ_TYPE_CRITTER, attacker->fid & 0xFFF, ANIM_THROW_PUNCH, 0, attacker->rotation + 1)))) {
// SFALL: Check the safety of weapons based on the selected attack mode
// instead of always the primary weapon hit mode.
if (_combat_safety_invalidate_weapon(a1, weapon, hitMode, a2, &safeDistance)) {
_ai_switch_weapons(a1, &hitMode, &weapon, a2);
if (_combat_safety_invalidate_weapon(attacker, weapon, hitMode, defender, &safeDistance)) {
_ai_switch_weapons(attacker, &hitMode, &weapon, defender);
}
} else {
_ai_switch_weapons(a1, &hitMode, &weapon, a2);
_ai_switch_weapons(attacker, &hitMode, &weapon, defender);
}
unsigned char rotations[800];
@ -2704,37 +2728,37 @@ static int _ai_try_attack(Object* a1, Object* a2)
break;
}
int reason = _combat_check_bad_shot(a1, a2, hitMode, false);
int reason = _combat_check_bad_shot(attacker, defender, hitMode, false);
if (reason == COMBAT_BAD_SHOT_NO_AMMO) {
// out of ammo
if (aiHaveAmmo(a1, weapon, &ammo)) {
if (aiHaveAmmo(attacker, weapon, &ammo)) {
int remainingAmmoQuantity = weaponReload(weapon, ammo);
if (remainingAmmoQuantity == 0 && ammo != NULL) {
_obj_destroy(ammo);
}
if (remainingAmmoQuantity != -1) {
int volume = _gsound_compute_relative_volume(a1);
int volume = _gsound_compute_relative_volume(attacker);
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL);
_gsound_play_sfx_file_volume(sfx, volume);
_ai_magic_hands(a1, weapon, 5002);
_ai_magic_hands(attacker, weapon, 5002);
// SFALL: Fix incorrect AP cost when AI reloads a weapon.
// CE: There is a commented out code which checks
// available action points before performing reload. Not
// sure why it was commented, probably needs additional
// testing.
int actionPointsRequired = weaponGetActionPointCost(a1, HIT_MODE_RIGHT_WEAPON_RELOAD, false);
if (a1->data.critter.combat.ap >= actionPointsRequired) {
a1->data.critter.combat.ap -= actionPointsRequired;
int actionPointsRequired = weaponGetActionPointCost(attacker, HIT_MODE_RIGHT_WEAPON_RELOAD, false);
if (attacker->data.critter.combat.ap >= actionPointsRequired) {
attacker->data.critter.combat.ap -= actionPointsRequired;
} else {
a1->data.critter.combat.ap = 0;
attacker->data.critter.combat.ap = 0;
}
}
} else {
ammo = _ai_search_environ(a1, ITEM_TYPE_AMMO);
ammo = _ai_search_environ(attacker, ITEM_TYPE_AMMO);
if (ammo != NULL) {
ammo = _ai_retrieve_object(a1, ammo);
ammo = _ai_retrieve_object(attacker, ammo);
if (ammo != NULL) {
int remainingAmmoQuantity = weaponReload(weapon, ammo);
if (remainingAmmoQuantity == 0) {
@ -2742,62 +2766,63 @@ static int _ai_try_attack(Object* a1, Object* a2)
}
if (remainingAmmoQuantity != -1) {
int volume = _gsound_compute_relative_volume(a1);
int volume = _gsound_compute_relative_volume(attacker);
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL);
_gsound_play_sfx_file_volume(sfx, volume);
_ai_magic_hands(a1, weapon, 5002);
_ai_magic_hands(attacker, weapon, 5002);
// SFALL: Fix incorrect AP cost when AI reloads a
// weapon.
// CE: See note above, probably need to check
// available action points before performing
// reload.
int actionPointsRequired = weaponGetActionPointCost(a1, HIT_MODE_RIGHT_WEAPON_RELOAD, false);
if (a1->data.critter.combat.ap >= actionPointsRequired) {
a1->data.critter.combat.ap -= actionPointsRequired;
int actionPointsRequired = weaponGetActionPointCost(attacker, HIT_MODE_RIGHT_WEAPON_RELOAD, false);
if (attacker->data.critter.combat.ap >= actionPointsRequired) {
attacker->data.critter.combat.ap -= actionPointsRequired;
} else {
a1->data.critter.combat.ap = 0;
attacker->data.critter.combat.ap = 0;
}
}
}
} else {
int volume = _gsound_compute_relative_volume(a1);
int volume = _gsound_compute_relative_volume(attacker);
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, weapon, hitMode, NULL);
_gsound_play_sfx_file_volume(sfx, volume);
_ai_magic_hands(a1, weapon, 5001);
_ai_magic_hands(attacker, weapon, 5001);
if (_inven_unwield(a1, 1) == 0) {
if (_inven_unwield(attacker, 1) == 0) {
_combat_turn_run();
}
_ai_switch_weapons(a1, &hitMode, &weapon, a2);
_ai_switch_weapons(attacker, &hitMode, &weapon, defender);
}
}
} else if (reason == COMBAT_BAD_SHOT_NOT_ENOUGH_AP || reason == COMBAT_BAD_SHOT_ARM_CRIPPLED || reason == COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED) {
// 3 - not enough action points
// 6 - crippled one arm for two-handed weapon
// 7 - both hands crippled
if (_ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1) {
if (_ai_switch_weapons(attacker, &hitMode, &weapon, defender) == -1) {
return -1;
}
} else if (reason == COMBAT_BAD_SHOT_OUT_OF_RANGE) {
// target out of range
int accuracy = _determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, rotations);
if (accuracy < minToHit) {
debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(a1));
_ai_run_away(a1, a2);
int toHitNoRange = _determine_to_hit_no_range(attacker, defender, HIT_LOCATION_UNCALLED, hitMode, rotations);
if (toHitNoRange < minToHit) {
// hit chance is too low even at point blank range (not taking range into account)
debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(attacker));
_ai_run_away(attacker, defender);
return 0;
}
if (weapon != NULL) {
if (_ai_move_steps_closer(a1, a2, actionPoints, taunt) == -1) {
if (_ai_move_steps_closer(attacker, defender, actionPoints, taunt) == -1) {
return -1;
}
taunt = false;
} else {
if (_ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1 || weapon == NULL) {
if (_ai_switch_weapons(attacker, &hitMode, &weapon, defender) == -1 || weapon == NULL) {
// NOTE: Uninline.
if (_ai_move_closer(a1, a2, taunt) == -1) {
if (_ai_move_closer(attacker, defender, taunt) == -1) {
return -1;
}
}
@ -2805,66 +2830,66 @@ static int _ai_try_attack(Object* a1, Object* a2)
}
} else if (reason == COMBAT_BAD_SHOT_AIM_BLOCKED) {
// aim is blocked
if (_ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, taunt) == -1) {
if (_ai_move_steps_closer(attacker, defender, attacker->data.critter.combat.ap, taunt) == -1) {
return -1;
}
taunt = false;
} else if (reason == COMBAT_BAD_SHOT_OK) {
int accuracy = _determine_to_hit(a1, a2, HIT_LOCATION_UNCALLED, hitMode);
int accuracy = _determine_to_hit(attacker, defender, HIT_LOCATION_UNCALLED, hitMode);
if (safeDistance != 0) {
if (_ai_move_away(a1, a2, safeDistance) == -1) {
if (_ai_move_away(attacker, defender, safeDistance) == -1) {
return -1;
}
}
if (accuracy < minToHit) {
int accuracyNoRange = _determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, rotations);
if (accuracyNoRange < minToHit) {
debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(a1));
_ai_run_away(a1, a2);
int toHitNoRange = _determine_to_hit_no_range(attacker, defender, HIT_LOCATION_UNCALLED, hitMode, rotations);
if (toHitNoRange < minToHit) {
debugPrint("%s: FLEEING: Can't possibly Hit Target!", critterGetName(attacker));
_ai_run_away(attacker, defender);
return 0;
}
if (actionPoints > 0) {
int v24 = pathfinderFindPath(a1, a1->tile, a2->tile, rotations, 0, _obj_blocking_at);
if (v24 == 0) {
v42 = actionPoints;
int pathLength = pathfinderFindPath(attacker, attacker->tile, defender->tile, rotations, 0, _obj_blocking_at);
if (pathLength == 0) {
actionPointsToUse = actionPoints;
} else {
if (v24 < actionPoints) {
actionPoints = v24;
if (pathLength < actionPoints) {
actionPoints = pathLength;
}
int tile = a1->tile;
int tile = attacker->tile;
int index;
for (index = 0; index < actionPoints; index++) {
tile = tileGetTileInDirection(tile, rotations[index], 1);
v42++;
actionPointsToUse++;
int v27 = _determine_to_hit_from_tile(a1, tile, a2, HIT_LOCATION_UNCALLED, hitMode);
if (v27 >= minToHit) {
int toHit = _determine_to_hit_from_tile(attacker, tile, defender, HIT_LOCATION_UNCALLED, hitMode);
if (toHit >= minToHit) {
break;
}
}
if (index == actionPoints) {
v42 = actionPoints;
actionPointsToUse = actionPoints;
}
}
}
if (_ai_move_steps_closer(a1, a2, v42, taunt) == -1) {
debugPrint("%s: FLEEING: Can't possibly get closer to Target!", critterGetName(a1));
_ai_run_away(a1, a2);
if (_ai_move_steps_closer(attacker, defender, actionPointsToUse, taunt) == -1) {
debugPrint("%s: FLEEING: Can't possibly get closer to Target!", critterGetName(attacker));
_ai_run_away(attacker, defender);
return 0;
}
taunt = false;
if (_ai_attack(a1, a2, hitMode) == -1 || weaponGetActionPointCost(a1, hitMode, 0) > a1->data.critter.combat.ap) {
if (_ai_attack(attacker, defender, hitMode) == -1 || weaponGetActionPointCost(attacker, hitMode, 0) > attacker->data.critter.combat.ap) {
return -1;
}
} else {
if (_ai_attack(a1, a2, hitMode) == -1 || weaponGetActionPointCost(a1, hitMode, 0) > a1->data.critter.combat.ap) {
if (_ai_attack(attacker, defender, hitMode) == -1 || weaponGetActionPointCost(attacker, hitMode, 0) > attacker->data.critter.combat.ap) {
return -1;
}
}

View File

@ -30,6 +30,8 @@ void aiReset();
int aiExit();
int aiLoad(File* stream);
int aiSave(File* stream);
int combat_ai_num();
char* combat_ai_name(int packet_num);
int aiGetAreaAttackMode(Object* obj);
int aiGetRunAwayMode(Object* obj);
int aiGetBestWeapon(Object* obj);

View File

@ -280,24 +280,28 @@ bool configRead(Config* config, const char* filePath, bool isDb)
if (isDb) {
File* stream = fileOpen(filePath, "rb");
if (stream != NULL) {
while (fileReadString(string, sizeof(string), stream) != NULL) {
configParseLine(config, string);
}
fileClose(stream);
// CE: Return `false` if file does not exists in database.
if (stream == NULL) {
return false;
}
while (fileReadString(string, sizeof(string), stream) != NULL) {
configParseLine(config, string);
}
fileClose(stream);
} else {
FILE* stream = compat_fopen(filePath, "rt");
if (stream != NULL) {
while (compat_fgets(string, sizeof(string), stream) != NULL) {
configParseLine(config, string);
}
fclose(stream);
// CE: Return `false` if file does not exists on the file system.
if (stream == NULL) {
return false;
}
// FIXME: This function returns `true` even if the file was not actually
// read. I'm pretty sure it's bug.
while (compat_fgets(string, sizeof(string), stream) != NULL) {
configParseLine(config, string);
}
fclose(stream);
}
return true;
@ -393,7 +397,7 @@ static bool configParseLine(Config* config, char* string)
// keys there.
// Skip leading whitespace.
while (isspace(*string)) {
while (isspace(static_cast<unsigned char>(*string))) {
string++;
}
@ -496,7 +500,7 @@ static bool configTrimString(char* string)
// Starting from the end of the string, loop while it's a whitespace and
// decrement string length.
char* pch = string + length - 1;
while (length != 0 && isspace(*pch)) {
while (length != 0 && isspace(static_cast<unsigned char>(*pch))) {
length--;
pch--;
}
@ -507,7 +511,7 @@ static bool configTrimString(char* string)
// Starting from the beginning of the string loop while it's a whitespace
// and decrement string length.
pch = string;
while (isspace(*pch)) {
while (isspace(static_cast<unsigned char>(*pch))) {
pch++;
length--;
}

View File

@ -9,6 +9,7 @@
#include "cycle.h"
#include "db.h"
#include "debug.h"
#include "delay.h"
#include "draw.h"
#include "game_mouse.h"
#include "input.h"
@ -172,8 +173,7 @@ void creditsOpen(const char* filePath, int backgroundFid, bool useReversedStyle)
windowBuffer,
windowWidth);
while (getTicksSince(tick) < CREDITS_WINDOW_SCROLLING_DELAY) {
}
delay_ms(CREDITS_WINDOW_SCROLLING_DELAY - (getTicks() - tick));
tick = getTicks();
@ -215,8 +215,7 @@ void creditsOpen(const char* filePath, int backgroundFid, bool useReversedStyle)
windowBuffer,
windowWidth);
while (getTicksSince(tick) < CREDITS_WINDOW_SCROLLING_DELAY) {
}
delay_ms(CREDITS_WINDOW_SCROLLING_DELAY - (getTicks() - tick));
tick = getTicks();

View File

@ -1396,4 +1396,40 @@ bool _critter_flag_check(int pid, int flag)
return (proto->critter.data.flags & flag) != 0;
}
// 0x42E6F0
void critter_flag_set(int pid, int flag)
{
Proto* proto;
if (pid == -1) {
return;
}
if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {
return;
}
protoGetProto(pid, &proto);
proto->critter.data.flags |= flag;
}
// 0x42E71C
void critter_flag_unset(int pid, int flag)
{
Proto* proto;
if (pid == -1) {
return;
}
if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {
return;
}
protoGetProto(pid, &proto);
proto->critter.data.flags &= ~flag;
}
} // namespace fallout

View File

@ -70,6 +70,8 @@ int critterGetMovementPointCostAdjustedForCrippledLegs(Object* critter, int a2);
bool critterIsEncumbered(Object* critter);
bool critterIsFleeing(Object* a1);
bool _critter_flag_check(int pid, int flag);
void critter_flag_set(int pid, int flag);
void critter_flag_unset(int pid, int flag);
} // namespace fallout

View File

@ -9,6 +9,7 @@
#include "character_editor.h"
#include "color.h"
#include "debug.h"
#include "delay.h"
#include "draw.h"
#include "game.h"
#include "game_sound.h"
@ -885,8 +886,8 @@ int showLoadFileDialog(char* title, char** fileList, char* dest, int fileListLen
}
unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24;
while (getTicksSince(scrollTick) < delay) {
}
delay_ms(delay - (getTicks() - scrollTick));
if (_game_user_wants_to_quit != 0) {
rc = 1;
@ -909,8 +910,7 @@ int showLoadFileDialog(char* title, char** fileList, char* dest, int fileListLen
doubleClickSelectedFileIndex = -2;
}
while (getTicksSince(tick) < (1000 / 24)) {
}
delay_ms(1000 / 24 - (getTicks() - tick));
}
if (_game_user_wants_to_quit) {
@ -1335,8 +1335,7 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
// FIXME: Missing windowRefresh makes blinking useless.
unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24;
while (getTicksSince(scrollTick) < delay) {
}
delay_ms(delay - (getTicks() - scrollTick));
if (_game_user_wants_to_quit != 0) {
rc = 1;
@ -1369,8 +1368,7 @@ int showSaveFileDialog(char* title, char** fileList, char* dest, int fileListLen
doubleClickSelectedFileIndex = -2;
}
while (getTicksSince(tick) < (1000 / 24)) {
}
delay_ms(1000 / 24 - (getTicks() - tick));
}
if (_game_user_wants_to_quit != 0) {

View File

@ -56,7 +56,7 @@ void _debug_register_mono()
// 0x4C6D18
void _debug_register_log(const char* fileName, const char* mode)
{
if ((mode[0] == 'w' && mode[1] == 'a') && mode[1] == 't') {
if ((mode[0] == 'w' || mode[0] == 'a') && mode[1] == 't') {
if (_fd != NULL) {
fclose(_fd);
}
@ -104,14 +104,8 @@ void _debug_register_env()
// NOTE: Uninline.
_debug_register_screen();
} else if (strcmp(copy, "gnw") == 0) {
if (gDebugPrintProc != _win_debug) {
if (_fd != NULL) {
fclose(_fd);
_fd = NULL;
}
gDebugPrintProc = _win_debug;
}
// NOTE: Uninline.
_debug_register_func(_win_debug);
}
internal_free(copy);

11
src/delay.cc Normal file
View File

@ -0,0 +1,11 @@
#include "delay.h"
#include <SDL.h>
void delay_ms(int ms)
{
if (ms <= 0) {
return;
}
SDL_Delay(ms);
}

6
src/delay.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef DELAY_H
#define DELAY_H
void delay_ms(int ms);
#endif

View File

@ -27,7 +27,7 @@ void bufferDrawLine(unsigned char* buf, int pitch, int x1, int y1, int x2, int y
p1 = buf + pitch * y1 + x1;
p2 = buf + pitch * y2 + x2;
while (p1 < p2) {
while (p1 <= p2) {
*p1 = color;
*p2 = color;
p1 += pitch;

View File

@ -8,6 +8,7 @@
#include "art.h"
#include "cycle.h"
#include "debug.h"
#include "delay.h"
#include "draw.h"
#include "game_mouse.h"
#include "game_sound.h"
@ -453,8 +454,7 @@ int elevatorSelectLevel(int elevator, int* mapPtr, int* elevationPtr, int* tileP
windowRefresh(gElevatorWindow);
while (getTicksSince(tick) < delay) {
}
delay_ms(delay - (getTicks() - tick));
renderPresent();
sharedFpsLimiter.throttle();

View File

@ -150,16 +150,8 @@ int _gzdecompress_file(const char* existingFilePath, const char* newFilePath)
static void fileCopy(const char* existingFilePath, const char* newFilePath)
{
char nativeExistingFilePath[COMPAT_MAX_PATH];
strcpy(nativeExistingFilePath, existingFilePath);
compat_windows_path_to_native(nativeExistingFilePath);
char nativeNewFilePath[COMPAT_MAX_PATH];
strcpy(nativeNewFilePath, newFilePath);
compat_windows_path_to_native(nativeNewFilePath);
FILE* in = fopen(nativeExistingFilePath, "rb");
FILE* out = fopen(nativeNewFilePath, "wb");
FILE* in = compat_fopen(existingFilePath, "rb");
FILE* out = compat_fopen(newFilePath, "wb");
if (in != NULL && out != NULL) {
std::vector<unsigned char> buffer(0xFFFF);

View File

@ -50,8 +50,11 @@
#include "random.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_arrays.h"
#include "sfall_config.h"
#include "sfall_global_scripts.h"
#include "sfall_global_vars.h"
#include "sfall_ini.h"
#include "sfall_lists.h"
#include "skill.h"
#include "skilldex.h"
@ -62,6 +65,7 @@
#include "trait.h"
#include "version.h"
#include "window_manager.h"
#include "window_manager_private.h"
#include "worldmap.h"
namespace fallout {
@ -165,6 +169,20 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
showSplash();
}
// CE: Handle debug mode (exactly as seen in `mapper2.exe`).
const char* debugMode = settings.debug.mode.c_str();
if (compat_stricmp(debugMode, "environment") == 0) {
_debug_register_env();
} else if (compat_stricmp(debugMode, "screen") == 0) {
_debug_register_screen();
} else if (compat_stricmp(debugMode, "log") == 0) {
_debug_register_log("debug.log", "wt");
} else if (compat_stricmp(debugMode, "mono") == 0) {
_debug_register_mono();
} else if (compat_stricmp(debugMode, "gnw") == 0) {
_debug_register_func(_win_debug);
}
interfaceFontsInit();
fontManagerAdd(&gModernFontManager);
fontSetCurrent(font);
@ -338,8 +356,8 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
// SFALL
premadeCharactersInit();
if (!sfallGlobalVarsInit()) {
debugPrint("Failed on sfallGlobalVarsInit");
if (!sfall_gl_vars_init()) {
debugPrint("Failed on sfall_gl_vars_init");
return -1;
}
@ -348,6 +366,20 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4
return -1;
}
if (!sfallArraysInit()) {
debugPrint("Failed on sfallArraysInit");
return -1;
}
if (!sfall_gl_scr_init()) {
debugPrint("Failed on sfall_gl_scr_init");
return -1;
}
char* customConfigBasePath;
configGetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, &customConfigBasePath);
sfall_ini_set_base_path(customConfigBasePath);
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MISC, &gMiscMessageList);
return 0;
@ -392,9 +424,11 @@ void gameReset()
_init_options_menu();
// SFALL
sfallGlobalVarsReset();
sfall_gl_vars_reset();
sfallListsReset();
messageListRepositoryReset();
sfallArraysReset();
sfall_gl_scr_reset();
}
// 0x442C34
@ -403,8 +437,10 @@ void gameExit()
debugPrint("\nGame Exit\n");
// SFALL
sfall_gl_scr_exit();
sfallArraysExit();
sfallListsExit();
sfallGlobalVarsExit();
sfall_gl_vars_exit();
premadeCharactersExit();
tileDisable();

View File

@ -125,10 +125,26 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
char* ch = strrchr(executable, '\\');
if (ch != NULL) {
*ch = '\0';
snprintf(gGameConfigFilePath, sizeof(gGameConfigFilePath), "%s\\%s", executable, GAME_CONFIG_FILE_NAME);
if (isMapper) {
snprintf(gGameConfigFilePath,
sizeof(gGameConfigFilePath),
"%s\\%s",
executable,
MAPPER_CONFIG_FILE_NAME);
} else {
snprintf(gGameConfigFilePath,
sizeof(gGameConfigFilePath),
"%s\\%s",
executable,
GAME_CONFIG_FILE_NAME);
}
*ch = '\\';
} else {
strcpy(gGameConfigFilePath, GAME_CONFIG_FILE_NAME);
if (isMapper) {
strcpy(gGameConfigFilePath, MAPPER_CONFIG_FILE_NAME);
} else {
strcpy(gGameConfigFilePath, GAME_CONFIG_FILE_NAME);
}
}
// Read contents of `fallout2.cfg` into config. The values from the file

View File

@ -5,8 +5,8 @@
namespace fallout {
// The file name of the main config file.
#define GAME_CONFIG_FILE_NAME "fallout2.cfg"
#define MAPPER_CONFIG_FILE_NAME "mapper2.cfg"
#define GAME_CONFIG_SYSTEM_KEY "system"
#define GAME_CONFIG_PREFERENCES_KEY "preferences"

View File

@ -13,6 +13,7 @@
#include "critter.h"
#include "cycle.h"
#include "debug.h"
#include "delay.h"
#include "dialog.h"
#include "display_monitor.h"
#include "draw.h"
@ -2936,7 +2937,6 @@ void _gdialog_scroll_subwin(int win, int a2, unsigned char* a3, unsigned char* a
int v7;
unsigned char* v9;
Rect rect;
unsigned int tick;
v7 = a6;
v9 = a4;
@ -2971,9 +2971,7 @@ void _gdialog_scroll_subwin(int win, int a2, unsigned char* a3, unsigned char* a
v7 += 10;
v9 -= 10 * (GAME_DIALOG_WINDOW_WIDTH);
tick = getTicks();
while (getTicksSince(tick) < 33) {
}
delay_ms(33);
renderPresent();
sharedFpsLimiter.throttle();
@ -3011,9 +3009,7 @@ void _gdialog_scroll_subwin(int win, int a2, unsigned char* a3, unsigned char* a
rect.top += 10;
tick = getTicks();
while (getTicksSince(tick) < 33) {
}
delay_ms(33);
renderPresent();
sharedFpsLimiter.throttle();

View File

@ -1328,6 +1328,12 @@ int gameMouseGetCursor()
return gGameMouseCursor;
}
// 0x44C9F0
void gmouse_set_mapper_mode(int mode)
{
_gmouse_mapper_mode = mode;
}
// 0x44C9F8
void _gmouse_3d_enable_modes()
{
@ -2458,4 +2464,9 @@ void gameMouseRefreshImmediately()
renderPresent();
}
Object* gmouse_get_outlined_object()
{
return gGameMouseHighlightedItem;
}
} // namespace fallout

View File

@ -86,6 +86,7 @@ void gameMouseRefresh();
void _gmouse_handle_event(int mouseX, int mouseY, int mouseState);
int gameMouseSetCursor(int cursor);
int gameMouseGetCursor();
void gmouse_set_mapper_mode(int mode);
void gameMouseSetMode(int a1);
int gameMouseGetMode();
void gameMouseCycleMode();
@ -102,6 +103,7 @@ void gameMouseLoadItemHighlight();
void _gmouse_remove_item_outline(Object* object);
void gameMouseRefreshImmediately();
Object* gmouse_get_outlined_object();
} // namespace fallout

View File

@ -1370,7 +1370,7 @@ char* gameSoundBuildInterfaceName(const char* a1)
// 0x451760
char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* target)
{
int v6;
int soundVariant;
char weaponSoundCode;
char effectTypeCode;
char materialCode;
@ -1384,12 +1384,12 @@ char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* ta
if (hitMode != HIT_MODE_LEFT_WEAPON_PRIMARY
&& hitMode != HIT_MODE_RIGHT_WEAPON_PRIMARY
&& hitMode != HIT_MODE_PUNCH) {
v6 = 2;
soundVariant = 2;
} else {
v6 = 1;
soundVariant = 1;
}
} else {
v6 = 1;
soundVariant = 1;
}
int damageType = weaponGetDamageType(NULL, weapon);
@ -1438,7 +1438,7 @@ char* sfxBuildWeaponName(int effectType, Object* weapon, int hitMode, Object* ta
}
}
snprintf(_sfx_file_name, sizeof(_sfx_file_name), "W%c%c%1d%cXX%1d", effectTypeCode, weaponSoundCode, v6, materialCode, 1);
snprintf(_sfx_file_name, sizeof(_sfx_file_name), "W%c%c%1d%cXX%1d", effectTypeCode, weaponSoundCode, soundVariant, materialCode, 1);
compat_strupr(_sfx_file_name);
return _sfx_file_name;
}

View File

@ -107,6 +107,62 @@ void _rect_clip_list(RectListNode** rectListNodePtr, Rect* rect)
}
}
// 0x4C6AAC
RectListNode* rect_clip(Rect* b, Rect* t)
{
RectListNode* list = NULL;
Rect clipped_t;
if (rectIntersection(t, b, &clipped_t) == 0) {
RectListNode** next = &list;
Rect clipped_b[4];
int k;
clipped_b[0].left = b->left;
clipped_b[0].top = b->top;
clipped_b[0].right = b->right;
clipped_b[0].bottom = clipped_t.top - 1;
clipped_b[1].left = b->left;
clipped_b[1].top = clipped_t.top;
clipped_b[1].right = clipped_t.left - 1;
clipped_b[1].bottom = clipped_t.bottom;
clipped_b[2].left = clipped_t.right + 1;
clipped_b[2].top = clipped_t.top;
clipped_b[2].right = b->right;
clipped_b[2].bottom = clipped_t.bottom;
clipped_b[3].left = b->left;
clipped_b[3].top = clipped_t.bottom + 1;
clipped_b[3].right = b->right;
clipped_b[3].bottom = b->bottom;
for (k = 0; k < 4; k++) {
if (clipped_b[k].left <= clipped_b[k].right && clipped_b[k].top <= clipped_b[k].bottom) {
*next = _rect_malloc();
if (*next == NULL) {
return NULL;
}
(*next)->rect = clipped_b[k];
(*next)->next = NULL;
next = &((*next)->next);
}
}
} else {
list = _rect_malloc();
if (list == NULL) {
return NULL;
}
list->rect = *b;
list->next = NULL;
}
return list;
}
// 0x4C6BB8
RectListNode* _rect_malloc()
{

View File

@ -27,6 +27,7 @@ typedef struct RectListNode {
void _GNW_rect_exit();
void _rect_clip_list(RectListNode** rectListNodePtr, Rect* rect);
RectListNode* rect_clip(Rect* b, Rect* t);
RectListNode* _rect_malloc();
void _rect_free(RectListNode* entry);
void rectUnion(const Rect* s1, const Rect* s2, Rect* r);

View File

@ -52,6 +52,14 @@ unsigned char HighRGB(unsigned char color)
return std::max(std::max(r, g), b);
}
// 0x44ED98
int load_lbm_to_buf(const char* path, unsigned char* buffer, int a3, int a4, int a5, int a6, int a7)
{
// TODO: Incomplete.
return -1;
}
// 0x44F250
int graphCompress(unsigned char* a1, unsigned char* a2, int a3)
{

View File

@ -4,6 +4,7 @@
namespace fallout {
unsigned char HighRGB(unsigned char color);
int load_lbm_to_buf(const char* path, unsigned char* buffer, int a3, int a4, int a5, int a6, int a7);
int graphCompress(unsigned char* a1, unsigned char* a2, int a3);
int graphDecompress(unsigned char* a1, unsigned char* a2, int a3);
void grayscalePaletteUpdate(int a1, int a2);

View File

@ -4,6 +4,7 @@
#include "audio_engine.h"
#include "color.h"
#include "delay.h"
#include "dinput.h"
#include "draw.h"
#include "kb.h"
@ -199,6 +200,13 @@ int inputGetInput()
return -1;
}
// 0x4C8BC8
void get_input_position(int* x, int* y)
{
*x = _input_mx;
*y = _input_my;
}
// 0x4C8BDC
void _process_bk()
{
@ -634,12 +642,7 @@ void inputPauseForTocks(unsigned int delay)
// 0x4C93B8
void inputBlockForTocks(unsigned int ms)
{
unsigned int start = SDL_GetTicks();
unsigned int diff;
do {
// NOTE: Uninline
diff = getTicksSince(start);
} while (diff < ms);
delay_ms(ms);
}
// 0x4C93E0

View File

@ -13,6 +13,7 @@ typedef int(ScreenshotHandler)(int width, int height, unsigned char* buffer, uns
int inputInit(int a1);
void inputExit();
int inputGetInput();
void get_input_position(int* x, int* y);
void _process_bk();
void enqueueInputEvent(int a1);
void inputEventQueueReset();

View File

@ -14,6 +14,7 @@
#include "interpreter_lib.h"
#include "memory_manager.h"
#include "platform_compat.h"
#include "sfall_global_scripts.h"
#include "svga.h"
namespace fallout {
@ -30,20 +31,19 @@ static int _outputStr(char* a1);
static int _checkWait(Program* program);
static char* programGetCurrentProcedureName(Program* s);
static opcode_t stackReadInt16(unsigned char* data, int pos);
static int stackReadInt32(unsigned char* a1, int a2);
static void stackWriteInt16(int value, unsigned char* a2, int a3);
static void stackWriteInt32(int value, unsigned char* stack, int pos);
static void stackPushInt16(unsigned char* a1, int* a2, int value);
static void stackPushInt32(unsigned char* a1, int* a2, int value);
static int stackPopInt32(unsigned char* a1, int* a2);
static opcode_t stackPopInt16(unsigned char* a1, int* a2);
static int stackReadInt32(unsigned char* data, int pos);
static void stackWriteInt16(int value, unsigned char* data, int pos);
static void stackWriteInt32(int value, unsigned char* data, int pos);
static void stackPushInt16(unsigned char* data, int* pointer, int value);
static void stackPushInt32(unsigned char* data, int* pointer, int value);
static int stackPopInt32(unsigned char* data, int* pointer);
static opcode_t stackPopInt16(unsigned char* data, int* pointer);
static void _interpretIncStringRef(Program* program, opcode_t opcode, int value);
static void programReturnStackPushInt16(Program* program, int value);
static opcode_t programReturnStackPopInt16(Program* program);
static int programReturnStackPopInt32(Program* program);
static void _detachProgram(Program* program);
static void _purgeProgram(Program* program);
static void programFree(Program* program);
static opcode_t _getOp(Program* program);
static void programMarkHeap(Program* program);
static void opNoop(Program* program);
@ -51,7 +51,7 @@ static void opPush(Program* program);
static void opPushBase(Program* program);
static void opPopBase(Program* program);
static void opPopToBase(Program* program);
static void op802C(Program* program);
static void opSetGlobal(Program* program);
static void opDump(Program* program);
static void opDelayedCall(Program* program);
static void opConditionalCall(Program* program);
@ -230,17 +230,17 @@ static char* programGetCurrentProcedureName(Program* program)
int procedureCount = stackReadInt32(program->procedures, 0);
unsigned char* ptr = program->procedures + 4;
int procedureOffset = stackReadInt32(ptr, 16);
int identifierOffset = stackReadInt32(ptr, 0);
int procedureOffset = stackReadInt32(ptr, offsetof(Procedure, bodyOffset));
int identifierOffset = stackReadInt32(ptr, offsetof(Procedure, nameOffset));
for (int index = 0; index < procedureCount; index++) {
int nextProcedureOffset = stackReadInt32(ptr + 24, 16);
int nextProcedureOffset = stackReadInt32(ptr + 24, offsetof(Procedure, bodyOffset));
if (program->instructionPointer >= procedureOffset && program->instructionPointer < nextProcedureOffset) {
return (char*)(program->identifiers + identifierOffset);
}
ptr += 24;
identifierOffset = stackReadInt32(ptr, 0);
identifierOffset = stackReadInt32(ptr, offsetof(Procedure, nameOffset));
}
return _aCouldnTFindPro;
@ -421,7 +421,7 @@ static void _purgeProgram(Program* program)
}
// 0x467614
static void programFree(Program* program)
void programFree(Program* program)
{
// NOTE: Uninline.
_detachProgram(program);
@ -712,7 +712,7 @@ static void opPopToBase(Program* program)
}
// 0x467DE0
static void op802C(Program* program)
static void opSetGlobal(Program* program)
{
program->basePointer = program->stackValues->size();
}
@ -745,10 +745,10 @@ static void opDelayedCall(Program* program)
delay += 1000 * _timerFunc() / _timerTick;
}
int flags = stackReadInt32(procedure_ptr, 4);
int flags = stackReadInt32(procedure_ptr, offsetof(Procedure, flags));
stackWriteInt32(delay, procedure_ptr, 8);
stackWriteInt32(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, 4);
stackWriteInt32(delay, procedure_ptr, offsetof(Procedure, time));
stackWriteInt32(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, offsetof(Procedure, flags));
}
// 0x468034
@ -761,10 +761,10 @@ static void opConditionalCall(Program* program)
}
unsigned char* procedure_ptr = program->procedures + 4 + 24 * data[0];
int flags = stackReadInt32(procedure_ptr, 4);
int flags = stackReadInt32(procedure_ptr, offsetof(Procedure, flags));
stackWriteInt32(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, 4);
stackWriteInt32(data[1], procedure_ptr, 12);
stackWriteInt32(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, offsetof(Procedure, flags));
stackWriteInt32(data[1], procedure_ptr, offsetof(Procedure, conditionOffset));
}
// 0x46817C
@ -788,9 +788,9 @@ static void opCancel(Program* program)
}
Procedure* proc = (Procedure*)(program->procedures + 4 + data * sizeof(*proc));
proc->field_4 = 0;
proc->field_8 = 0;
proc->field_C = 0;
proc->flags = 0;
proc->time = 0;
proc->conditionOffset = 0;
}
// 0x468330
@ -802,9 +802,9 @@ static void opCancelAll(Program* program)
// TODO: Original code uses different approach, check.
Procedure* proc = (Procedure*)(program->procedures + 4 + index * sizeof(*proc));
proc->field_4 = 0;
proc->field_8 = 0;
proc->field_C = 0;
proc->flags = 0;
proc->time = 0;
proc->conditionOffset = 0;
}
}
@ -2040,12 +2040,12 @@ static void opCall(Program* program)
unsigned char* ptr = program->procedures + 4 + 24 * value;
int flags = stackReadInt32(ptr, 4);
if ((flags & 4) != 0) {
int flags = stackReadInt32(ptr, offsetof(Procedure, flags));
if ((flags & PROCEDURE_FLAG_IMPORTED) != 0) {
// TODO: Incomplete.
} else {
program->instructionPointer = stackReadInt32(ptr, 16);
if ((flags & 0x10) != 0) {
program->instructionPointer = stackReadInt32(ptr, offsetof(Procedure, bodyOffset));
if ((flags & PROCEDURE_FLAG_CRITICAL) != 0) {
program->flags |= PROGRAM_FLAG_CRITICAL_SECTION;
}
}
@ -2248,7 +2248,7 @@ static void opFetchProcedureAddress(Program* program)
{
int procedureIndex = programStackPopInteger(program);
int address = stackReadInt32(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 16);
int address = stackReadInt32(program->procedures + 4 + sizeof(Procedure) * procedureIndex, offsetof(Procedure, bodyOffset));
programStackPushInteger(program, address);
}
@ -2308,8 +2308,8 @@ static void opExportProcedure(Program* program)
unsigned char* proc_ptr = program->procedures + 4 + sizeof(Procedure) * procedureIndex;
char* procedureName = programGetIdentifier(program, stackReadInt32(proc_ptr, 0));
int procedureAddress = stackReadInt32(proc_ptr, 16);
char* procedureName = programGetIdentifier(program, stackReadInt32(proc_ptr, offsetof(Procedure, nameOffset)));
int procedureAddress = stackReadInt32(proc_ptr, offsetof(Procedure, bodyOffset));
if (externalProcedureCreate(program, procedureName, procedureAddress, argumentCount) != 0) {
char err[256];
@ -2478,9 +2478,9 @@ static void opCheckProcedureArgumentCount(Program* program)
int expectedArgumentCount = programStackPopInteger(program);
int procedureIndex = programStackPopInteger(program);
int actualArgumentCount = stackReadInt32(program->procedures + 4 + 24 * procedureIndex, 20);
int actualArgumentCount = stackReadInt32(program->procedures + 4 + 24 * procedureIndex, offsetof(Procedure, argCount));
if (actualArgumentCount != expectedArgumentCount) {
const char* identifier = programGetIdentifier(program, stackReadInt32(program->procedures + 4 + 24 * procedureIndex, 0));
const char* identifier = programGetIdentifier(program, stackReadInt32(program->procedures + 4 + 24 * procedureIndex, offsetof(Procedure, nameOffset)));
char err[260];
snprintf(err, sizeof(err), "Wrong number of args to procedure %s\n", identifier);
programFatalError(err);
@ -2501,7 +2501,7 @@ static void opLookupStringProc(Program* program)
// Start with 1 since we've skipped main procedure, which is always at
// index 0.
for (int index = 1; index < procedureCount; index++) {
int offset = stackReadInt32(procedurePtr, 0);
int offset = stackReadInt32(procedurePtr, offsetof(Procedure, nameOffset));
const char* procedureName = programGetIdentifier(program, offset);
if (compat_stricmp(procedureName, procedureNameToLookup) == 0) {
programStackPushInteger(program, index);
@ -2566,7 +2566,7 @@ void interpreterRegisterOpcodeHandlers()
interpreterRegisterOpcode(OPCODE_POP_BASE, opPopBase);
interpreterRegisterOpcode(OPCODE_POP_TO_BASE, opPopToBase);
interpreterRegisterOpcode(OPCODE_PUSH_BASE, opPushBase);
interpreterRegisterOpcode(OPCODE_SET_GLOBAL, op802C);
interpreterRegisterOpcode(OPCODE_SET_GLOBAL, opSetGlobal);
interpreterRegisterOpcode(OPCODE_FETCH_PROCEDURE_ADDRESS, opFetchProcedureAddress);
interpreterRegisterOpcode(OPCODE_DUMP, opDump);
interpreterRegisterOpcode(OPCODE_IF, opIf);
@ -2786,9 +2786,9 @@ void _executeProc(Program* program, int procedureIndex)
char err[256];
procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex;
procedureFlags = stackReadInt32(procedurePtr, 4);
procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags));
if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) {
procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, 0));
procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, offsetof(Procedure, nameOffset)));
externalProgram = externalProcedureGetProgram(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount);
if (externalProgram != NULL) {
if (externalProcedureArgumentCount == 0) {
@ -2805,7 +2805,7 @@ void _executeProc(Program* program, int procedureIndex)
_setupExternalCall(program, externalProgram, externalProcedureAddress, 28);
procedurePtr = externalProgram->procedures + 4 + sizeof(Procedure) * procedureIndex;
procedureFlags = stackReadInt32(procedurePtr, 4);
procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags));
if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) {
// NOTE: Uninline.
@ -2813,7 +2813,7 @@ void _executeProc(Program* program, int procedureIndex)
_interpret(externalProgram, 0);
}
} else {
procedureAddress = stackReadInt32(procedurePtr, 16);
procedureAddress = stackReadInt32(procedurePtr, offsetof(Procedure, bodyOffset));
// NOTE: Uninline.
_setupCall(program, procedureAddress, 20);
@ -2836,7 +2836,7 @@ int programFindProcedure(Program* program, const char* name)
unsigned char* ptr = program->procedures + 4;
for (int index = 0; index < procedureCount; index++) {
int identifierOffset = stackReadInt32(ptr, offsetof(Procedure, field_0));
int identifierOffset = stackReadInt32(ptr, offsetof(Procedure, nameOffset));
if (compat_stricmp((char*)(program->identifiers + identifierOffset), name) == 0) {
return index;
}
@ -2861,10 +2861,10 @@ void _executeProcedure(Program* program, int procedureIndex)
jmp_buf env;
procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex;
procedureFlags = stackReadInt32(procedurePtr, 4);
procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags));
if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) {
procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, 0));
procedureIdentifier = programGetIdentifier(program, stackReadInt32(procedurePtr, offsetof(Procedure, nameOffset)));
externalProgram = externalProcedureGetProgram(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount);
if (externalProgram != NULL) {
if (externalProcedureArgumentCount == 0) {
@ -2882,7 +2882,7 @@ void _executeProcedure(Program* program, int procedureIndex)
_interpretOutput(err);
}
} else {
procedureAddress = stackReadInt32(procedurePtr, 16);
procedureAddress = stackReadInt32(procedurePtr, offsetof(Procedure, bodyOffset));
// NOTE: Uninline.
_setupCall(program, procedureAddress, 24);
@ -2918,14 +2918,14 @@ static void _doEvents()
procedurePtr = programListNode->program->procedures + 4;
for (procedureIndex = 0; procedureIndex < procedureCount; procedureIndex++) {
procedureFlags = stackReadInt32(procedurePtr, 4);
procedureFlags = stackReadInt32(procedurePtr, offsetof(Procedure, flags));
if ((procedureFlags & PROCEDURE_FLAG_CONDITIONAL) != 0) {
memcpy(env, programListNode->program, sizeof(env));
oldProgramFlags = programListNode->program->flags;
oldInstructionPointer = programListNode->program->instructionPointer;
programListNode->program->flags = 0;
programListNode->program->instructionPointer = stackReadInt32(procedurePtr, 12);
programListNode->program->instructionPointer = stackReadInt32(procedurePtr, offsetof(Procedure, conditionOffset));
_interpret(programListNode->program, -1);
if ((programListNode->program->flags & PROGRAM_FLAG_0x04) == 0) {
@ -2936,16 +2936,16 @@ static void _doEvents()
if (data != 0) {
// NOTE: Uninline.
stackWriteInt32(0, procedurePtr, 4);
stackWriteInt32(0, procedurePtr, offsetof(Procedure, flags));
_executeProc(programListNode->program, procedureIndex);
}
}
memcpy(programListNode->program, env, sizeof(env));
} else if ((procedureFlags & PROCEDURE_FLAG_TIMED) != 0) {
if ((unsigned int)stackReadInt32(procedurePtr, 8) < time) {
if ((unsigned int)stackReadInt32(procedurePtr, offsetof(Procedure, time)) < time) {
// NOTE: Uninline.
stackWriteInt32(0, procedurePtr, 4);
stackWriteInt32(0, procedurePtr, offsetof(Procedure, flags));
_executeProc(programListNode->program, procedureIndex);
}
}
@ -3022,6 +3022,15 @@ Program* runScript(char* name)
// 0x46E1EC
void _updatePrograms()
{
// CE: Implementation is different. Sfall inserts global scripts into
// program list upon creation, so engine does not diffirentiate between
// global and normal scripts. Global scripts in CE are not part of program
// list, so we need a separate call to continue execution (usually
// non-critical calls scheduled from managed windows). One more thing to
// note is that global scripts in CE cannot handle conditional/timed procs
// (which are not used anyway).
sfall_gl_scr_update(_cpuBurstSize);
ProgramListNode* curr = gInterpreterProgramListHead;
while (curr != NULL) {
ProgramListNode* next = curr->next;
@ -3262,7 +3271,7 @@ void* programReturnStackPopPointer(Program* program)
return programValue.pointerValue;
}
bool ProgramValue::isEmpty()
bool ProgramValue::isEmpty() const
{
switch (opcode) {
case VALUE_TYPE_INT:
@ -3279,19 +3288,19 @@ bool ProgramValue::isEmpty()
}
// Matches Sfall implementation.
bool ProgramValue::isInt()
bool ProgramValue::isInt() const
{
return opcode == VALUE_TYPE_INT;
}
// Matches Sfall implementation.
bool ProgramValue::isFloat()
bool ProgramValue::isFloat() const
{
return opcode == VALUE_TYPE_FLOAT;
}
// Matches Sfall implementation.
float ProgramValue::asFloat()
float ProgramValue::asFloat() const
{
switch (opcode) {
case VALUE_TYPE_INT:
@ -3303,4 +3312,42 @@ float ProgramValue::asFloat()
}
}
bool ProgramValue::isString() const
{
return opcode == VALUE_TYPE_STRING || opcode == VALUE_TYPE_DYNAMIC_STRING;
}
ProgramValue::ProgramValue()
{
opcode = VALUE_TYPE_INT;
integerValue = 0;
}
ProgramValue::ProgramValue(int value)
{
opcode = VALUE_TYPE_INT;
integerValue = value;
};
ProgramValue::ProgramValue(Object* value)
{
opcode = VALUE_TYPE_PTR;
pointerValue = value;
};
bool ProgramValue::isPointer() const
{
return opcode == VALUE_TYPE_PTR;
}
int ProgramValue::asInt() const
{
switch (opcode) {
case VALUE_TYPE_INT:
return integerValue;
case VALUE_TYPE_FLOAT:
return static_cast<int>(floatValue);
default:
return 0;
}
}
} // namespace fallout

View File

@ -1,6 +1,7 @@
#ifndef INTERPRETER_H
#define INTERPRETER_H
#include "object.h"
#include <setjmp.h>
#include <vector>
@ -132,15 +133,20 @@ enum RawValueType {
typedef unsigned short opcode_t;
typedef struct Procedure {
int field_0;
int field_4;
int field_8;
int field_C;
int field_10;
int field_14;
int nameOffset;
int flags;
int time;
int conditionOffset;
int bodyOffset;
int argCount;
} Procedure;
typedef struct ProgramValue {
class ProgramValue {
public:
ProgramValue();
ProgramValue(int value);
ProgramValue(Object* value);
opcode_t opcode;
union {
int integerValue;
@ -148,12 +154,14 @@ typedef struct ProgramValue {
void* pointerValue;
};
ProgramValue() : opcode(0), pointerValue(nullptr) {}
bool isEmpty();
bool isInt();
bool isFloat();
float asFloat();
} ProgramValue;
bool isEmpty() const;
bool isInt() const;
bool isFloat() const;
bool isString() const;
float asFloat() const;
bool isPointer() const;
int asInt() const;
};
typedef std::vector<ProgramValue> ProgramStack;
@ -196,6 +204,7 @@ void _interpretOutputFunc(int (*func)(char*));
int _interpretOutput(const char* format, ...);
[[noreturn]] void programFatalError(const char* str, ...);
void _interpretDecStringRef(Program* program, opcode_t a2, int a3);
void programFree(Program* program);
Program* programCreateByPath(const char* path);
char* programGetString(Program* program, opcode_t opcode, int offset);
char* programGetIdentifier(Program* program, int offset);

View File

@ -1580,12 +1580,22 @@ static void opPickup(Program* program)
return;
}
if (script->target == NULL) {
Object* self = script->target;
// SFALL: Override `self` via `op_set_self`.
// CE: Implementation is different. Sfall integrates via `scriptGetSid` by
// returning fake script with overridden `self` (and `target` in this case).
if (script->overriddenSelf != nullptr) {
self = script->overriddenSelf;
script->overriddenSelf = nullptr;
}
if (self == NULL) {
scriptPredefinedError(program, "pickup_obj", SCRIPT_ERROR_OBJECT_IS_NULL);
return;
}
actionPickUp(script->target, object);
actionPickUp(self, object);
}
// drop_obj
@ -4548,6 +4558,15 @@ static void opUseObjectOnObject(Program* program)
}
Object* self = scriptGetSelf(program);
// SFALL: Override `self` via `op_set_self`.
// CE: Implementation is different. Sfall integrates via `scriptGetSid` by
// returning fake script with overridden `self`.
if (script->overriddenSelf != nullptr) {
self = script->overriddenSelf;
script->overriddenSelf = nullptr;
}
if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) {
_action_use_an_item_on_object(self, target, item);
} else {

View File

@ -5981,4 +5981,9 @@ int _inven_set_timer(Object* a1)
return seconds;
}
Object* inven_get_current_target_obj()
{
return _target_stack[_target_curr_stack];
}
} // namespace fallout

View File

@ -27,6 +27,7 @@ int inventoryOpenLooting(Object* a1, Object* a2);
int inventoryOpenStealing(Object* a1, Object* a2);
void inventoryOpenTrade(int win, Object* a2, Object* a3, Object* a4, int a5);
int _inven_set_timer(Object* a1);
Object* inven_get_current_target_obj();
} // namespace fallout

View File

@ -18,6 +18,7 @@
#include "db.h"
#include "dbox.h"
#include "debug.h"
#include "delay.h"
#include "display_monitor.h"
#include "draw.h"
#include "file_utils.h"
@ -45,6 +46,9 @@
#include "random.h"
#include "scripts.h"
#include "settings.h"
#include "sfall_config.h"
#include "sfall_global_scripts.h"
#include "sfall_global_vars.h"
#include "skill.h"
#include "stat.h"
#include "svga.h"
@ -58,6 +62,12 @@
namespace fallout {
#define LOAD_SAVE_SIGNATURE "FALLOUT SAVE FILE"
#define LOAD_SAVE_DESCRIPTION_LENGTH 30
#define LOAD_SAVE_HANDLER_COUNT 27
#define LSGAME_MSG_NAME "LSGAME.MSG"
#define LS_WINDOW_WIDTH 640
#define LS_WINDOW_HEIGHT 480
@ -80,8 +90,8 @@ namespace fallout {
#define ITEMS_DIR_NAME "items"
#define PROTO_FILE_EXT "pro"
#define LOAD_SAVE_DESCRIPTION_LENGTH (30)
#define LOAD_SAVE_HANDLER_COUNT (27)
typedef int LoadGameHandler(File* stream);
typedef int SaveGameHandler(File* stream);
typedef enum LoadSaveWindowType {
LOAD_SAVE_WINDOW_TYPE_SAVE_GAME,
@ -104,33 +114,28 @@ typedef enum LoadSaveScrollDirection {
LOAD_SAVE_SCROLL_DIRECTION_DOWN,
} LoadSaveScrollDirection;
typedef int LoadGameHandler(File* stream);
typedef int SaveGameHandler(File* stream);
#define LSGAME_MSG_NAME ("LSGAME.MSG")
typedef struct STRUCT_613D30 {
char field_0[24];
short field_18;
short field_1A;
typedef struct LoadSaveSlotData {
char signature[24];
short versionMinor;
short versionMajor;
// TODO: The type is probably char, but it's read with the same function as
// reading unsigned chars, which in turn probably result of collapsing
// reading functions.
unsigned char field_1C;
char character_name[32];
unsigned char versionRelease;
char characterName[32];
char description[LOAD_SAVE_DESCRIPTION_LENGTH];
short field_5C;
short field_5E;
short field_60;
int field_64;
short field_68;
short field_6A;
short field_6C;
int field_70;
short field_74;
short field_76;
char file_name[16];
} STRUCT_613D30;
short fileMonth;
short fileDay;
short fileYear;
int fileTime;
short gameMonth;
short gameDay;
short gameYear;
unsigned int gameTime;
short elevation;
short map;
char fileName[16];
} LoadSaveSlotData;
typedef enum LoadSaveFrm {
LOAD_SAVE_FRM_BACKGROUND,
@ -153,10 +158,10 @@ static int lsgLoadGameInSlot(int slot);
static int lsgSaveHeaderInSlot(int slot);
static int lsgLoadHeaderInSlot(int slot);
static int _GetSlotList();
static void _ShowSlotList(int a1);
static void _DrawInfoBox(int a1);
static int _LoadTumbSlot(int a1);
static int _GetComment(int a1);
static void _ShowSlotList(int windowType);
static void _DrawInfoBox(int slot);
static int _LoadTumbSlot(int slot);
static int _GetComment(int slot);
static int _get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* description, int maxLength, int x, int y, int textColor, int backgroundColor, int flags);
static int _DummyFunc(File* stream);
static int _PrepLoad(File* stream);
@ -164,8 +169,7 @@ static int _EndLoad(File* stream);
static int _GameMap2Slot(File* stream);
static int _SlotMap2Game(File* stream);
static int _mygets(char* dest, File* stream);
static int _copy_file(const char* a1, const char* a2);
static int _MapDirErase(const char* path, const char* a2);
static int _copy_file(const char* existingFileName, const char* newFileName);
static int _SaveBackup();
static int _RestoreSave();
static int _LoadObjDudeCid(File* stream);
@ -198,7 +202,7 @@ static bool gLoadSaveWindowIsoWasEnabled = false;
static int _map_backup_count = -1;
// 0x5193C8
static int _automap_db_flag = 0;
static bool _automap_db_flag = false;
// 0x5193CC
static const char* _patches = NULL;
@ -266,7 +270,7 @@ static LoadGameHandler* _master_load_list[LOAD_SAVE_HANDLER_COUNT] = {
};
// 0x5194C4
static int _loadingGame = 0;
static bool _loadingGame = false;
// lsgame.msg
//
@ -274,7 +278,7 @@ static int _loadingGame = 0;
static MessageList gLoadSaveMessageList;
// 0x613D30
static STRUCT_613D30 _LSData[10];
static LoadSaveSlotData _LSData[10];
// 0x614280
static int _LSstatus[10];
@ -326,6 +330,9 @@ static int gLoadSaveWindowOldFont;
static FrmImage _loadsaveFrmImages[LOAD_SAVE_FRM_COUNT];
static int quickSaveSlots = 0;
static bool autoQuickSaveSlots = false;
// 0x47B7E4
void _InitLoadSave()
{
@ -333,17 +340,22 @@ void _InitLoadSave()
_slot_cursor = 0;
_patches = settings.system.master_patches_path.c_str();
_MapDirErase("MAPS\\", "SAV");
_MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT);
_MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT);
MapDirErase("MAPS\\", "SAV");
MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT);
MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT);
configGetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_QUICK_SAVE, &quickSaveSlots);
if (quickSaveSlots > 0 && quickSaveSlots <= 10) {
autoQuickSaveSlots = true;
}
}
// 0x47B85C
void _ResetLoadSave()
{
_MapDirErase("MAPS\\", "SAV");
_MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT);
_MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT);
MapDirErase("MAPS\\", "SAV");
MapDirErase(PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME "\\", PROTO_FILE_EXT);
MapDirErase(PROTO_DIR_NAME "\\" ITEMS_DIR_NAME "\\", PROTO_FILE_EXT);
}
// SaveGame
@ -357,7 +369,18 @@ int lsgSaveGame(int mode)
_ls_error_code = 0;
_patches = settings.system.master_patches_path.c_str();
// SFALL: skip slot selection if auto quicksave is enabled
if (autoQuickSaveSlots) {
_quick_done = true;
}
if (mode == LOAD_SAVE_MODE_QUICK && _quick_done) {
// SFALL: cycle through first N slots for quicksaving
if (autoQuickSaveSlots) {
if (++_slot_cursor >= quickSaveSlots) {
_slot_cursor = 0;
}
}
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
strcat(_gmpath, "SAVE.DAT");
@ -471,7 +494,7 @@ int lsgSaveGame(int mode)
break;
}
_ShowSlotList(0);
_ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME);
_DrawInfoBox(_slot_cursor);
windowRefresh(gLoadSaveWindow);
@ -650,9 +673,9 @@ int lsgSaveGame(int mode)
}
if (scrollCounter > 14.4) {
while (getTicksSince(start) < 1000 / scrollVelocity) { }
delay_ms(1000 / scrollVelocity - (getTicks() - start));
} else {
while (getTicksSince(start) < 1000 / 24) { }
delay_ms(1000 / 24 - (getTicks() - start));
}
keyCode = inputGetInput();
@ -696,8 +719,7 @@ int lsgSaveGame(int mode)
doubleClickSlot = -1;
}
while (getTicksSince(tick) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - tick));
}
if (rc == 1) {
@ -976,7 +998,7 @@ int lsgLoadGame(int mode)
break;
}
_ShowSlotList(2);
_ShowSlotList(LOAD_SAVE_WINDOW_TYPE_LOAD_GAME);
_DrawInfoBox(_slot_cursor);
windowRefresh(gLoadSaveWindow);
renderPresent();
@ -1147,15 +1169,15 @@ int lsgLoadGame(int mode)
break;
}
_ShowSlotList(2);
_ShowSlotList(LOAD_SAVE_WINDOW_TYPE_LOAD_GAME);
_DrawInfoBox(_slot_cursor);
windowRefresh(gLoadSaveWindow);
}
if (scrollCounter > 14.4) {
while (getTicksSince(start) < 1000 / scrollVelocity) { }
delay_ms(1000 / scrollVelocity - (getTicks() - start));
} else {
while (getTicksSince(start) < 1000 / 24) { }
delay_ms(1000 / 24 - (getTicks() - start));
}
keyCode = inputGetInput();
@ -1194,7 +1216,7 @@ int lsgLoadGame(int mode)
}
_DrawInfoBox(_slot_cursor);
_ShowSlotList(2);
_ShowSlotList(LOAD_SAVE_WINDOW_TYPE_LOAD_GAME);
}
windowRefresh(gLoadSaveWindow);
@ -1205,7 +1227,7 @@ int lsgLoadGame(int mode)
doubleClickSlot = -1;
}
while (getTicksSince(time) < 1000 / 24) { }
delay_ms(1000 / 24 - (getTicks() - time));
}
if (rc == 1) {
@ -1547,7 +1569,7 @@ static int lsgPerformSaveGame()
debugPrint("\nLOADSAVE: ** Error opening save game for writing! **\n");
_RestoreSave();
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
_MapDirErase(_gmpath, "BAK");
MapDirErase(_gmpath, "BAK");
_partyMemberUnPrepSave();
backgroundSoundResume();
return -1;
@ -1560,7 +1582,7 @@ static int lsgPerformSaveGame()
fileClose(_flptr);
_RestoreSave();
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
_MapDirErase(_gmpath, "BAK");
MapDirErase(_gmpath, "BAK");
_partyMemberUnPrepSave();
backgroundSoundResume();
return -1;
@ -1574,7 +1596,7 @@ static int lsgPerformSaveGame()
fileClose(_flptr);
_RestoreSave();
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
_MapDirErase(_gmpath, "BAK");
MapDirErase(_gmpath, "BAK");
_partyMemberUnPrepSave();
backgroundSoundResume();
return -1;
@ -1587,8 +1609,75 @@ static int lsgPerformSaveGame()
fileClose(_flptr);
// SFALL: Save sfallgv.sav.
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
_MapDirErase(_gmpath, "BAK");
strcat(_gmpath, "sfallgv.sav");
_flptr = fileOpen(_gmpath, "wb");
if (_flptr != NULL) {
do {
if (!sfall_gl_vars_save(_flptr)) {
debugPrint("LOADSAVE (SFALL): ** Error saving global vars **\n");
break;
}
// TODO: For now fill remaining sections with zeros to that Sfall
// can successfully read our global vars and skip the rest.
int nextObjectId = 0;
if (fileWrite(&nextObjectId, sizeof(nextObjectId), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving next object id **\n");
break;
}
int addedYears = 0;
if (fileWrite(&addedYears, sizeof(addedYears), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving added years **\n");
break;
}
int fakeTraitsCount = 0;
if (fileWrite(&fakeTraitsCount, sizeof(fakeTraitsCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving fake traits **\n");
break;
}
int fakePerksCount = 0;
if (fileWrite(&fakePerksCount, sizeof(fakePerksCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving fake perks **\n");
break;
}
int fakeSelectablePerksCount = 0;
if (fileWrite(&fakeSelectablePerksCount, sizeof(fakeSelectablePerksCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving fake selectable perks **\n");
break;
}
int arraysCountOld = 0;
if (fileWrite(&arraysCountOld, sizeof(arraysCountOld), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving arrays (old fmt) **\n");
break;
}
int arraysCountNew = 0;
if (fileWrite(&arraysCountNew, sizeof(arraysCountNew), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving arrays (new fmt) **\n");
break;
}
int drugPidsCount = 0;
if (fileWrite(&drugPidsCount, sizeof(drugPidsCount), 1, _flptr) != 1) {
debugPrint("LOADSAVE (SFALL): ** Error saving drug pids **\n");
break;
}
} while (0);
fileClose(_flptr);
}
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
MapDirErase(_gmpath, "BAK");
gLoadSaveMessageListItem.num = 140;
if (messageListGetItem(&gLoadSaveMessageList, &gLoadSaveMessageListItem)) {
@ -1603,7 +1692,7 @@ static int lsgPerformSaveGame()
}
// 0x47DC60
int _isLoadingGame()
bool _isLoadingGame()
{
return _loadingGame;
}
@ -1611,7 +1700,7 @@ int _isLoadingGame()
// 0x47DC68
static int lsgLoadGameInSlot(int slot)
{
_loadingGame = 1;
_loadingGame = true;
if (isInCombat()) {
interfaceBarEndButtonsHide(false);
@ -1622,13 +1711,13 @@ static int lsgLoadGameInSlot(int slot)
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
strcat(_gmpath, "SAVE.DAT");
STRUCT_613D30* ptr = &(_LSData[slot]);
LoadSaveSlotData* ptr = &(_LSData[slot]);
debugPrint("\nLOADSAVE: Load name: %s\n", ptr->description);
_flptr = fileOpen(_gmpath, "rb");
if (_flptr == NULL) {
debugPrint("\nLOADSAVE: ** Error opening load game file for reading! **\n");
_loadingGame = 0;
_loadingGame = false;
return -1;
}
@ -1637,7 +1726,7 @@ static int lsgLoadGameInSlot(int slot)
debugPrint("\nLOADSAVE: ** Error reading save game header! **\n");
fileClose(_flptr);
gameReset();
_loadingGame = 0;
_loadingGame = false;
return -1;
}
@ -1652,7 +1741,7 @@ static int lsgLoadGameInSlot(int slot)
debugPrint("LOADSAVE: Load function #%d data size read: %d bytes.\n", index, fileTell(_flptr) - pos);
fileClose(_flptr);
gameReset();
_loadingGame = 0;
_loadingGame = false;
return -1;
}
@ -1662,8 +1751,26 @@ static int lsgLoadGameInSlot(int slot)
debugPrint("LOADSAVE: Total load data read: %ld bytes.\n", fileTell(_flptr));
fileClose(_flptr);
// SFALL: Load sfallgv.sav.
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
strcat(_gmpath, "sfallgv.sav");
_flptr = fileOpen(_gmpath, "rb");
if (_flptr != NULL) {
do {
if (!sfall_gl_vars_load(_flptr)) {
debugPrint("LOADSAVE (SFALL): ** Error loading global vars **\n");
break;
}
// TODO: For now silently ignore remaining sections.
} while (0);
fileClose(_flptr);
}
snprintf(_str, sizeof(_str), "%s\\", "MAPS");
_MapDirErase(_str, "BAK");
MapDirErase(_str, "BAK");
_proto_dude_update_gender();
// Game Loaded.
@ -1674,7 +1781,10 @@ static int lsgLoadGameInSlot(int slot)
debugPrint("\nError: Couldn't find LoadSave Message!");
}
_loadingGame = 0;
_loadingGame = false;
// SFALL: Start global scripts.
sfall_gl_scr_exec_start_proc();
return 0;
}
@ -1684,10 +1794,10 @@ static int lsgSaveHeaderInSlot(int slot)
{
_ls_error_code = 4;
STRUCT_613D30* ptr = &(_LSData[slot]);
strncpy(ptr->field_0, "FALLOUT SAVE FILE", 24);
LoadSaveSlotData* ptr = &(_LSData[slot]);
strncpy(ptr->signature, LOAD_SAVE_SIGNATURE, 24);
if (fileWrite(ptr->field_0, 1, 24, _flptr) == -1) {
if (fileWrite(ptr->signature, 1, 24, _flptr) == -1) {
return -1;
}
@ -1695,22 +1805,22 @@ static int lsgSaveHeaderInSlot(int slot)
temp[0] = VERSION_MAJOR;
temp[1] = VERSION_MINOR;
ptr->field_18 = temp[0];
ptr->field_1A = temp[1];
ptr->versionMinor = temp[0];
ptr->versionMajor = temp[1];
if (fileWriteInt16List(_flptr, temp, 2) == -1) {
return -1;
}
ptr->field_1C = VERSION_RELEASE;
ptr->versionRelease = VERSION_RELEASE;
if (fileWriteUInt8(_flptr, VERSION_RELEASE) == -1) {
return -1;
}
char* characterName = critterGetName(gDude);
strncpy(ptr->character_name, characterName, 32);
strncpy(ptr->characterName, characterName, 32);
if (fileWrite(ptr->character_name, 32, 1, _flptr) != 1) {
if (fileWrite(ptr->characterName, 32, 1, _flptr) != 1) {
return -1;
}
@ -1725,16 +1835,16 @@ static int lsgSaveHeaderInSlot(int slot)
temp[1] = local->tm_mon + 1;
temp[2] = local->tm_year + 1900;
ptr->field_5E = temp[0];
ptr->field_5C = temp[1];
ptr->field_60 = temp[2];
ptr->field_64 = local->tm_hour + local->tm_min;
ptr->fileDay = temp[0];
ptr->fileMonth = temp[1];
ptr->fileYear = temp[2];
ptr->fileTime = local->tm_hour + local->tm_min;
if (fileWriteInt16List(_flptr, temp, 3) == -1) {
return -1;
}
if (_db_fwriteLong(_flptr, ptr->field_64) == -1) {
if (_db_fwriteLong(_flptr, ptr->fileTime) == -1) {
return -1;
}
@ -1746,23 +1856,23 @@ static int lsgSaveHeaderInSlot(int slot)
temp[0] = month;
temp[1] = day;
temp[2] = year;
ptr->field_70 = gameTimeGetTime();
ptr->gameTime = gameTimeGetTime();
if (fileWriteInt16List(_flptr, temp, 3) == -1) {
return -1;
}
if (_db_fwriteLong(_flptr, ptr->field_70) == -1) {
if (fileWriteUInt32(_flptr, ptr->gameTime) == -1) {
return -1;
}
ptr->field_74 = gElevation;
if (fileWriteInt16(_flptr, ptr->field_74) == -1) {
ptr->elevation = gElevation;
if (fileWriteInt16(_flptr, ptr->elevation) == -1) {
return -1;
}
ptr->field_76 = mapGetCurrentMap();
if (fileWriteInt16(_flptr, ptr->field_76) == -1) {
ptr->map = mapGetCurrentMap();
if (fileWriteInt16(_flptr, ptr->map) == -1) {
return -1;
}
@ -1771,8 +1881,8 @@ static int lsgSaveHeaderInSlot(int slot)
// NOTE: Uppercased from "sav".
char* v1 = _strmfe(_str, mapName, "SAV");
strncpy(ptr->file_name, v1, 16);
if (fileWrite(ptr->file_name, 16, 1, _flptr) != 1) {
strncpy(ptr->fileName, v1, 16);
if (fileWrite(ptr->fileName, 16, 1, _flptr) != 1) {
return -1;
}
@ -1795,13 +1905,13 @@ static int lsgLoadHeaderInSlot(int slot)
{
_ls_error_code = 3;
STRUCT_613D30* ptr = &(_LSData[slot]);
LoadSaveSlotData* ptr = &(_LSData[slot]);
if (fileRead(ptr->field_0, 1, 24, _flptr) != 24) {
if (fileRead(ptr->signature, 1, 24, _flptr) != 24) {
return -1;
}
if (strncmp(ptr->field_0, "FALLOUT SAVE FILE", 18) != 0) {
if (strncmp(ptr->signature, LOAD_SAVE_SIGNATURE, 18) != 0) {
debugPrint("\nLOADSAVE: ** Invalid save file on load! **\n");
_ls_error_code = 2;
return -1;
@ -1812,20 +1922,20 @@ static int lsgLoadHeaderInSlot(int slot)
return -1;
}
ptr->field_18 = v8[0];
ptr->field_1A = v8[1];
ptr->versionMinor = v8[0];
ptr->versionMajor = v8[1];
if (fileReadUInt8(_flptr, &(ptr->field_1C)) == -1) {
if (fileReadUInt8(_flptr, &(ptr->versionRelease)) == -1) {
return -1;
}
if (ptr->field_18 != 1 || ptr->field_1A != 2 || ptr->field_1C != 'R') {
debugPrint("\nLOADSAVE: Load slot #%d Version: %d.%d%c\n", slot, ptr->field_18, ptr->field_1A, ptr->field_1C);
if (ptr->versionMinor != 1 || ptr->versionMajor != 2 || ptr->versionRelease != 'R') {
debugPrint("\nLOADSAVE: Load slot #%d Version: %d.%d%c\n", slot, ptr->versionMinor, ptr->versionMajor, ptr->versionRelease);
_ls_error_code = 1;
return -1;
}
if (fileRead(ptr->character_name, 32, 1, _flptr) != 1) {
if (fileRead(ptr->characterName, 32, 1, _flptr) != 1) {
return -1;
}
@ -1837,11 +1947,11 @@ static int lsgLoadHeaderInSlot(int slot)
return -1;
}
ptr->field_5C = v8[0];
ptr->field_5E = v8[1];
ptr->field_60 = v8[2];
ptr->fileMonth = v8[0];
ptr->fileDay = v8[1];
ptr->fileYear = v8[2];
if (_db_freadInt(_flptr, &(ptr->field_64)) == -1) {
if (_db_freadInt(_flptr, &(ptr->fileTime)) == -1) {
return -1;
}
@ -1849,23 +1959,23 @@ static int lsgLoadHeaderInSlot(int slot)
return -1;
}
ptr->field_68 = v8[0];
ptr->field_6A = v8[1];
ptr->field_6C = v8[2];
ptr->gameMonth = v8[0];
ptr->gameDay = v8[1];
ptr->gameYear = v8[2];
if (_db_freadInt(_flptr, &(ptr->field_70)) == -1) {
if (fileReadUInt32(_flptr, &(ptr->gameTime)) == -1) {
return -1;
}
if (fileReadInt16(_flptr, &(ptr->field_74)) == -1) {
if (fileReadInt16(_flptr, &(ptr->elevation)) == -1) {
return -1;
}
if (fileReadInt16(_flptr, &(ptr->field_76)) == -1) {
if (fileReadInt16(_flptr, &(ptr->map)) == -1) {
return -1;
}
if (fileRead(ptr->file_name, 1, 16, _flptr) != 16) {
if (fileRead(ptr->fileName, 1, 16, _flptr) != 16) {
return -1;
}
@ -1919,7 +2029,7 @@ static int _GetSlotList()
}
// 0x47E6D8
static void _ShowSlotList(int a1)
static void _ShowSlotList(int windowType)
{
bufferFill(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 87 + 55, 230, 353, LS_WINDOW_WIDTH, gLoadSaveWindowBuffer[LS_WINDOW_WIDTH * 86 + 55] & 0xFF);
@ -1927,7 +2037,7 @@ static void _ShowSlotList(int a1)
for (int index = 0; index < 10; index += 1) {
int color = index == _slot_cursor ? _colorTable[32747] : _colorTable[992];
const char* text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, a1 != 0 ? 110 : 109);
const char* text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, windowType != 0 ? 110 : 109);
snprintf(_str, sizeof(_str), "[ %s %.2d: ]", text, index + 1);
fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * y + 55, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);
@ -1961,7 +2071,7 @@ static void _ShowSlotList(int a1)
}
// 0x47E8E0
static void _DrawInfoBox(int a1)
static void _DrawInfoBox(int slot)
{
blitBufferToBuffer(_loadsaveFrmImages[LOAD_SAVE_FRM_BACKGROUND].getData() + LS_WINDOW_WIDTH * 254 + 396, 164, 60, LS_WINDOW_WIDTH, gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, 640);
@ -1969,26 +2079,28 @@ static void _DrawInfoBox(int a1)
const char* text;
int color = _colorTable[992];
switch (_LSstatus[a1]) {
switch (_LSstatus[slot]) {
case SLOT_STATE_OCCUPIED:
do {
STRUCT_613D30* ptr = &(_LSData[a1]);
fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, ptr->character_name, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);
if (1) {
LoadSaveSlotData* ptr = &(_LSData[slot]);
fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * 254 + 396, ptr->characterName, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);
int v4 = ptr->field_70 / 600;
int v5 = v4 % 60;
int v6 = 25 * (v4 / 60 % 24);
int v21 = 4 * v6 + v5;
text = getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 116 + ptr->field_68);
snprintf(_str, sizeof(_str), "%.2d %s %.4d %.4d", ptr->field_6A, text, ptr->field_6C, v21);
snprintf(_str,
sizeof(_str),
"%.2d %s %.4d %.4d",
ptr->gameDay,
getmsg(&gLoadSaveMessageList, &gLoadSaveMessageListItem, 116 + ptr->gameMonth),
ptr->gameYear,
100 * ((ptr->gameTime / 600) / 60 % 24) + (ptr->gameTime / 600) % 60);
int v2 = fontGetLineHeight();
fontDrawText(gLoadSaveWindowBuffer + LS_WINDOW_WIDTH * (256 + v2) + 397, _str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);
const char* v22 = mapGetName(ptr->field_76, ptr->field_74);
const char* v9 = mapGetCityName(ptr->field_76);
snprintf(_str, sizeof(_str), "%s %s", v9, v22);
snprintf(_str,
sizeof(_str),
"%s %s",
mapGetCityName(ptr->map),
mapGetName(ptr->map, ptr->elevation));
int y = v2 + 3 + v2 + 256;
short beginnings[WORD_WRAP_MAX_COUNT];
@ -2003,7 +2115,7 @@ static void _DrawInfoBox(int a1)
y += v2 + 2;
}
}
} while (0);
}
return;
case SLOT_STATE_EMPTY:
// Empty.
@ -2030,30 +2142,28 @@ static void _DrawInfoBox(int a1)
}
// 0x47EC48
static int _LoadTumbSlot(int a1)
static int _LoadTumbSlot(int slot)
{
File* stream;
int v2;
v2 = _LSstatus[_slot_cursor];
if (v2 != 0 && v2 != 2 && v2 != 3) {
if (_LSstatus[_slot_cursor] != SLOT_STATE_EMPTY
&& _LSstatus[_slot_cursor] != SLOT_STATE_ERROR
&& _LSstatus[_slot_cursor] != SLOT_STATE_UNSUPPORTED_VERSION) {
snprintf(_str, sizeof(_str), "%s\\%s%.2d\\%s", "SAVEGAME", "SLOT", _slot_cursor + 1, "SAVE.DAT");
debugPrint(" Filename %s\n", _str);
stream = fileOpen(_str, "rb");
File* stream = fileOpen(_str, "rb");
if (stream == NULL) {
debugPrint("\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\n", a1);
debugPrint("\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\n", slot);
return -1;
}
if (fileSeek(stream, 131, SEEK_SET) != 0) {
debugPrint("\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\n", a1);
debugPrint("\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\n", slot);
fileClose(stream);
return -1;
}
if (fileRead(_thumbnail_image, LS_PREVIEW_SIZE, 1, stream) != 1) {
debugPrint("\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\n", a1);
debugPrint("\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\n", slot);
fileClose(stream);
return -1;
}
@ -2065,7 +2175,7 @@ static int _LoadTumbSlot(int a1)
}
// 0x47ED5C
static int _GetComment(int a1)
static int _GetComment(int slot)
{
// Maintain original position in original resolution, otherwise center it.
int commentWindowX = screenGetWidth() != 640
@ -2166,7 +2276,7 @@ static int _GetComment(int a1)
char description[LOAD_SAVE_DESCRIPTION_LENGTH];
if (_LSstatus[_slot_cursor] == SLOT_STATE_OCCUPIED) {
strncpy(description, _LSData[a1].description, LOAD_SAVE_DESCRIPTION_LENGTH);
strncpy(description, _LSData[slot].description, LOAD_SAVE_DESCRIPTION_LENGTH);
} else {
memset(description, '\0', LOAD_SAVE_DESCRIPTION_LENGTH);
}
@ -2175,8 +2285,8 @@ static int _GetComment(int a1)
int backgroundColor = *(_loadsaveFrmImages[LOAD_SAVE_FRM_BOX].getData() + _loadsaveFrmImages[LOAD_SAVE_FRM_BOX].getWidth() * 35 + 24);
if (_get_input_str2(window, 507, 508, description, LOAD_SAVE_DESCRIPTION_LENGTH - 1, 24, 35, _colorTable[992], backgroundColor, 0) == 0) {
strncpy(_LSData[a1].description, description, LOAD_SAVE_DESCRIPTION_LENGTH);
_LSData[a1].description[LOAD_SAVE_DESCRIPTION_LENGTH - 1] = '\0';
strncpy(_LSData[slot].description, description, LOAD_SAVE_DESCRIPTION_LENGTH);
_LSData[slot].description[LOAD_SAVE_DESCRIPTION_LENGTH - 1] = '\0';
rc = 1;
} else {
rc = 0;
@ -2277,8 +2387,7 @@ static int _get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* de
windowRefresh(win);
}
while (getTicksSince(tick) < 1000 / 24) {
}
delay_ms(1000 / 24 - (getTicks() - tick));
renderPresent();
sharedFpsLimiter.throttle();
@ -2306,7 +2415,7 @@ static int _PrepLoad(File* stream)
gameReset();
gameMouseSetCursor(MOUSE_CURSOR_WAIT_PLANET);
gMapHeader.name[0] = '\0';
gameTimeSetTime(_LSData[_slot_cursor].field_70);
gameTimeSetTime(_LSData[_slot_cursor].gameTime);
return 0;
}
@ -2314,7 +2423,7 @@ static int _PrepLoad(File* stream)
static int _EndLoad(File* stream)
{
wmMapMusicStart();
dudeSetName(_LSData[_slot_cursor].character_name);
dudeSetName(_LSData[_slot_cursor].characterName);
interfaceBarRefresh();
indicatorBarRefresh();
tileWindowRefresh();
@ -2376,7 +2485,7 @@ static int _GameMap2Slot(File* stream)
snprintf(_gmpath, sizeof(_gmpath), "%s\\%s%.2d\\", "SAVEGAME", "SLOT", _slot_cursor + 1);
if (_MapDirErase(_gmpath, "SAV") == -1) {
if (MapDirErase(_gmpath, "SAV") == -1) {
fileNameListFree(&fileNameList, 0);
return -1;
}
@ -2455,19 +2564,19 @@ static int _SlotMap2Game(File* stream)
snprintf(_str0, sizeof(_str0), "%s\\", PROTO_DIR_NAME "\\" CRITTERS_DIR_NAME);
if (_MapDirErase(_str0, PROTO_FILE_EXT) == -1) {
if (MapDirErase(_str0, PROTO_FILE_EXT) == -1) {
debugPrint("LOADSAVE: returning 3\n");
return -1;
}
snprintf(_str0, sizeof(_str0), "%s\\", PROTO_DIR_NAME "\\" ITEMS_DIR_NAME);
if (_MapDirErase(_str0, PROTO_FILE_EXT) == -1) {
if (MapDirErase(_str0, PROTO_FILE_EXT) == -1) {
debugPrint("LOADSAVE: returning 4\n");
return -1;
}
snprintf(_str0, sizeof(_str0), "%s\\", "MAPS");
if (_MapDirErase(_str0, "SAV") == -1) {
if (MapDirErase(_str0, "SAV") == -1) {
debugPrint("LOADSAVE: returning 5\n");
return -1;
}
@ -2525,7 +2634,7 @@ static int _SlotMap2Game(File* stream)
return -1;
}
if (mapLoadSaved(_LSData[_slot_cursor].file_name) == -1) {
if (mapLoadSaved(_LSData[_slot_cursor].fileName) == -1) {
debugPrint("LOADSAVE: returning 13\n");
return -1;
}
@ -2561,7 +2670,7 @@ static int _mygets(char* dest, File* stream)
}
// 0x47FE58
static int _copy_file(const char* a1, const char* a2)
static int _copy_file(const char* existingFileName, const char* newFileName)
{
File* stream1;
File* stream2;
@ -2575,7 +2684,7 @@ static int _copy_file(const char* a1, const char* a2)
buf = NULL;
result = -1;
stream1 = fileOpen(a1, "rb");
stream1 = fileOpen(existingFileName, "rb");
if (stream1 == NULL) {
goto out;
}
@ -2585,7 +2694,7 @@ static int _copy_file(const char* a1, const char* a2)
goto out;
}
stream2 = fileOpen(a2, "wb");
stream2 = fileOpen(newFileName, "wb");
if (stream2 == NULL) {
goto out;
}
@ -2622,7 +2731,7 @@ out:
}
if (stream2 != NULL) {
fileClose(stream1);
fileClose(stream2);
}
if (buf != NULL) {
@ -2638,11 +2747,11 @@ void lsgInit()
{
char path[COMPAT_MAX_PATH];
snprintf(path, sizeof(path), "%s\\", "MAPS");
_MapDirErase(path, "SAV");
MapDirErase(path, "SAV");
}
// 0x480040
static int _MapDirErase(const char* relativePath, const char* extension)
int MapDirErase(const char* relativePath, const char* extension)
{
char path[COMPAT_MAX_PATH];
snprintf(path, sizeof(path), "%s*.%s", relativePath, extension);
@ -2726,7 +2835,7 @@ static int _SaveBackup()
char* v2 = _strmfe(_str2, "AUTOMAP.DB", "BAK");
snprintf(_str1, sizeof(_str1), "%s\\%s", _gmpath, v2);
_automap_db_flag = 0;
_automap_db_flag = false;
File* stream2 = fileOpen(_str0, "rb");
if (stream2 != NULL) {
@ -2736,7 +2845,7 @@ static int _SaveBackup()
return -1;
}
_automap_db_flag = 1;
_automap_db_flag = true;
}
return 0;

View File

@ -18,8 +18,9 @@ void _InitLoadSave();
void _ResetLoadSave();
int lsgSaveGame(int mode);
int lsgLoadGame(int mode);
int _isLoadingGame();
bool _isLoadingGame();
void lsgInit();
int MapDirErase(const char* path, const char* extension);
int _MapDirEraseFile_(const char* a1, const char* a2);
} // namespace fallout

View File

@ -33,6 +33,7 @@
#include "selfrun.h"
#include "settings.h"
#include "sfall_config.h"
#include "sfall_global_scripts.h"
#include "svga.h"
#include "text_font.h"
#include "window.h"
@ -148,6 +149,9 @@ int falloutMain(int argc, char** argv)
_main_load_new(mapNameCopy);
free(mapNameCopy);
// SFALL: AfterNewGameStartHook.
sfall_gl_scr_exec_start_proc();
mainLoop();
paletteFadeTo(gPaletteWhite);
@ -357,6 +361,10 @@ static void mainLoop()
sharedFpsLimiter.mark();
int keyCode = inputGetInput();
// SFALL: MainLoopHook.
sfall_gl_scr_process_main();
gameHandleKey(keyCode, false);
scriptsHandleRequests();

View File

@ -108,7 +108,7 @@ int* gMapLocalVars = NULL;
// map_vars
// 0x51956C
static int* gMapGlobalVars = NULL;
int* gMapGlobalVars = NULL;
// local_vars_num
// 0x519570
@ -116,7 +116,7 @@ int gMapLocalVarsLength = 0;
// map_vars_num
// 0x519574
static int gMapGlobalVarsLength = 0;
int gMapGlobalVarsLength = 0;
// Current elevation.
//
@ -1746,7 +1746,7 @@ static int mapHeaderWrite(MapHeader* ptr, File* stream)
if (fileWriteInt32(stream, ptr->darkness) == -1) return -1;
if (fileWriteInt32(stream, ptr->globalVariablesCount) == -1) return -1;
if (fileWriteInt32(stream, ptr->field_34) == -1) return -1;
if (fileWriteInt32(stream, ptr->lastVisitTime) == -1) return -1;
if (fileWriteUInt32(stream, ptr->lastVisitTime) == -1) return -1;
if (fileWriteInt32List(stream, ptr->field_3C, 44) == -1) return -1;
return 0;
@ -1766,7 +1766,7 @@ static int mapHeaderRead(MapHeader* ptr, File* stream)
if (fileReadInt32(stream, &(ptr->darkness)) == -1) return -1;
if (fileReadInt32(stream, &(ptr->globalVariablesCount)) == -1) return -1;
if (fileReadInt32(stream, &(ptr->field_34)) == -1) return -1;
if (fileReadInt32(stream, &(ptr->lastVisitTime)) == -1) return -1;
if (fileReadUInt32(stream, &(ptr->lastVisitTime)) == -1) return -1;
if (fileReadInt32List(stream, ptr->field_3C, 44) == -1) return -1;
return 0;

View File

@ -54,7 +54,7 @@ typedef struct MapHeader {
int field_34;
// Time in game ticks when PC last visited this map.
int lastVisitTime;
unsigned int lastVisitTime;
int field_3C[44];
} MapHeader;
@ -69,7 +69,9 @@ typedef void IsoWindowRefreshProc(Rect* rect);
extern int gMapSid;
extern int* gMapLocalVars;
extern int* gMapGlobalVars;
extern int gMapLocalVarsLength;
extern int gMapGlobalVarsLength;
extern int gElevation;
extern MessageList gMapMessageList;

180
src/mapper/map_func.cc Normal file
View File

@ -0,0 +1,180 @@
#include "mapper/map_func.h"
#include "actions.h"
#include "color.h"
#include "game_mouse.h"
#include "input.h"
#include "map.h"
#include "memory.h"
#include "mouse.h"
#include "proto.h"
#include "svga.h"
#include "tile.h"
#include "window_manager.h"
#include "window_manager_private.h"
namespace fallout {
// 0x5595CC
static bool block_obj_view_on = false;
// 0x4825B0
void setup_map_dirs()
{
// TODO: Incomplete.
}
// 0x4826B4
void copy_proto_lists()
{
// TODO: Incomplete.
}
// 0x482708
void place_entrance_hex()
{
int x;
int y;
int tile;
while (inputGetInput() != -2) {
}
if ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {
if (_mouse_click_in(0, 0, _scr_size.right - _scr_size.left, _scr_size.bottom - _scr_size.top - 100)) {
mouseGetPosition(&x, &y);
tile = tileFromScreenXY(x, y, gElevation);
if (tile != -1) {
if (tileSetCenter(tile, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == 0) {
mapSetEnteringLocation(tile, gElevation, rotation);
} else {
win_timed_msg("ERROR: Entrance out of range!", _colorTable[31744]);
}
}
}
}
}
// 0x4841C4
void pick_region(Rect* rect)
{
Rect temp;
int x;
int y;
gameMouseSetCursor(MOUSE_CURSOR_PLUS);
gameMouseObjectsHide();
while (1) {
if (inputGetInput() == -2
&& (mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {
break;
}
}
get_input_position(&x, &y);
temp.left = x;
temp.top = y;
temp.right = x;
temp.bottom = y;
while ((mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) {
inputGetInput();
get_input_position(&x, &y);
if (x != temp.right || y != temp.bottom) {
erase_rect(rect);
sort_rect(rect, &temp);
draw_rect(rect, _colorTable[32747]);
}
}
erase_rect(rect);
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
gameMouseObjectsShow();
}
// 0x484294
void sort_rect(Rect* a, Rect* b)
{
if (b->right > b->left) {
a->left = b->left;
a->right = b->right;
} else {
a->left = b->right;
a->right = b->left;
}
if (b->bottom > b->top) {
a->top = b->top;
a->bottom = b->bottom;
} else {
a->top = b->bottom;
a->bottom = b->top;
}
}
// 0x4842D4
void draw_rect(Rect* rect, unsigned char color)
{
int width = rect->right - rect->left;
int height = rect->bottom - rect->top;
int max_dimension;
if (height < width) {
max_dimension = width;
} else {
max_dimension = height;
}
unsigned char* buffer = (unsigned char*)internal_malloc(max_dimension);
if (buffer != NULL) {
memset(buffer, color, max_dimension);
_scr_blit(buffer, width, 1, 0, 0, width, 1, rect->left, rect->top);
_scr_blit(buffer, 1, height, 0, 0, 1, height, rect->left, rect->top);
_scr_blit(buffer, width, 1, 0, 0, width, 1, rect->left, rect->bottom);
_scr_blit(buffer, 1, height, 0, 0, 1, height, rect->right, rect->top);
internal_free(buffer);
}
}
// 0x4843A0
void erase_rect(Rect* rect)
{
Rect r = *rect;
r.bottom = rect->top;
windowRefreshAll(&r);
r.bottom = rect->bottom;
r.left = rect->right;
windowRefreshAll(&r);
r.left = rect->left;
r.top = rect->bottom;
windowRefreshAll(&r);
r.top = rect->top;
r.right = rect->left;
windowRefreshAll(&r);
}
// 0x484400
int toolbar_proto(int type, int id)
{
if (id < proto_max_id(type)) {
return (type << 24) | id;
} else {
return -1;
}
}
// 0x485D44
bool map_toggle_block_obj_viewing_on()
{
return block_obj_view_on;
}
} // namespace fallout

20
src/mapper/map_func.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef FALLOUT_MAPPER_MAP_FUNC_H_
#define FALLOUT_MAPPER_MAP_FUNC_H_
#include "geometry.h"
namespace fallout {
void setup_map_dirs();
void copy_proto_lists();
void place_entrance_hex();
void pick_region(Rect* rect);
void sort_rect(Rect* a, Rect* b);
void draw_rect(Rect* rect, unsigned char color);
void erase_rect(Rect* rect);
int toolbar_proto(int type, int id);
bool map_toggle_block_obj_viewing_on();
} // namespace fallout
#endif /* FALLOUT_MAPPER_MAP_FUNC_H_ */

1743
src/mapper/mapper.cc Normal file

File diff suppressed because it is too large Load Diff

23
src/mapper/mapper.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef FALLOUT_MAPPER_MAPPER_H_
#define FALLOUT_MAPPER_MAPPER_H_
#include "map.h"
#include "obj_types.h"
namespace fallout {
extern MapTransition mapInfo;
extern int menu_val_0[8];
extern int menu_val_2[8];
extern int menu_val_1[21];
extern unsigned char* tool;
extern int tool_win;
int mapper_main(int argc, char** argv);
void print_toolbar_name(int object_type);
int mapper_inven_unwield(Object* obj, int right_hand);
} // namespace fallout
#endif /* FALLOUT_MAPPER_MAPPER_H_ */

607
src/mapper/mp_proto.cc Normal file
View File

@ -0,0 +1,607 @@
#include "mapper/mp_proto.h"
#include <string.h>
#include "art.h"
#include "color.h"
#include "combat_ai.h"
#include "critter.h"
#include "input.h"
#include "kb.h"
#include "mapper/mp_targt.h"
#include "memory.h"
#include "proto.h"
#include "svga.h"
#include "window_manager.h"
#include "window_manager_private.h"
namespace fallout {
#define CRITTER_FLAG_COUNT 10
#define YES 0
#define NO 1
static int proto_choose_container_flags(Proto* proto);
static int proto_subdata_setup_int_button(const char* title, int key, int value, int min_value, int max_value, int* y, int a7);
static int proto_subdata_setup_fid_button(const char* title, int key, int fid, int* y, int a5);
static int proto_subdata_setup_pid_button(const char* title, int key, int pid, int* y, int a5);
static void proto_critter_flags_redraw(int win, int pid);
static int proto_critter_flags_modify(int pid);
static int mp_pick_kill_type();
static char kYes[] = "YES";
static char kNo[] = "NO";
// 0x53DAFC
static char default_proto_builder_name[36] = "EVERTS SCOTTY";
// 0x559924
char* proto_builder_name = default_proto_builder_name;
// 0x559B94
static const char* wall_light_strs[] = {
"North/South",
"East/West",
"North Corner",
"South Corner",
"East Corner",
"West Corner",
};
// 0x559C50
static char* yesno[] = {
kYes,
kNo,
};
// 0x559C58
int edit_window_color = 1;
// 0x559C60
bool can_modify_protos = false;
// 0x559C68
static int subwin = -1;
// 0x559C6C
static int critFlagList[CRITTER_FLAG_COUNT] = {
CRITTER_NO_STEAL,
CRITTER_NO_DROP,
CRITTER_NO_LIMBS,
CRITTER_NO_AGE,
CRITTER_NO_HEAL,
CRITTER_INVULNERABLE,
CRITTER_FLAT,
CRITTER_SPECIAL_DEATH,
CRITTER_LONG_LIMBS,
CRITTER_NO_KNOCKBACK,
};
// 0x559C94
static const char* critFlagStrs[CRITTER_FLAG_COUNT] = {
"_Steal",
"_Drop",
"_Limbs",
"_Ages",
"_Heal",
"Invuln.,",
"_Flattens",
"Special",
"Rng",
"_Knock",
};
// 0x4922F8
void init_mapper_protos()
{
edit_window_color = _colorTable[10570];
can_modify_protos = target_overriden();
}
// 0x492840
int proto_choose_container_flags(Proto* proto)
{
int win = windowCreate(320,
185,
220,
205,
edit_window_color,
WINDOW_MOVE_ON_TOP);
if (win == -1) {
return -1;
}
_win_register_text_button(win,
10,
11,
-1,
-1,
-1,
'1',
"Magic Hands Grnd",
0);
if ((proto->item.data.container.openFlags & 0x1) != 0) {
windowDrawText(win,
yesno[YES],
50,
125,
15,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(win,
yesno[NO],
50,
125,
15,
_colorTable[32747] | 0x10000);
}
_win_register_text_button(win,
10,
32,
-1,
-1,
-1,
'2',
"Cannot Pick Up",
0);
if (_proto_action_can_pickup(proto->pid)) {
windowDrawText(win,
yesno[YES],
50,
125,
36,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(win,
yesno[NO],
50,
125,
36,
_colorTable[32747] | 0x10000);
}
windowDrawBorder(win);
windowRefresh(win);
while (1) {
sharedFpsLimiter.mark();
int input = inputGetInput();
if (input == KEY_ESCAPE
|| input == KEY_BAR
|| input == KEY_RETURN) {
break;
}
if (input == '1') {
proto->item.data.container.openFlags ^= 0x1;
if ((proto->item.data.container.openFlags & 0x1) != 0) {
windowDrawText(win,
yesno[YES],
50,
125,
15,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(win,
yesno[NO],
50,
125,
15,
_colorTable[32747] | 0x10000);
}
windowRefresh(win);
} else if (input == '2') {
proto->item.extendedFlags ^= 0x8000;
if (_proto_action_can_pickup(proto->pid)) {
windowDrawText(win,
yesno[YES],
50,
125,
36,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(win,
yesno[NO],
50,
125,
36,
_colorTable[32747] | 0x10000);
}
windowRefresh(win);
}
renderPresent();
sharedFpsLimiter.throttle();
}
windowDestroy(win);
return 0;
}
// 0x492A3C
int proto_subdata_setup_int_button(const char* title, int key, int value, int min_value, int max_value, int* y, int a7)
{
char text[36];
int button_x;
int value_offset_x;
button_x = 10;
value_offset_x = 90;
if (a7 == 9) {
*y -= 189;
}
if (a7 > 8) {
button_x = 165;
value_offset_x -= 16;
}
_win_register_text_button(subwin,
button_x,
*y,
-1,
-1,
-1,
key,
title,
0);
if (value >= min_value && value < max_value) {
sprintf(text, "%d", value);
windowDrawText(subwin,
text,
38,
button_x + value_offset_x,
*y + 4,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(subwin,
"<ERROR>",
38,
button_x + value_offset_x,
*y + 4,
_colorTable[31744] | 0x10000);
}
*y += 21;
return 0;
}
// 0x492B28
int proto_subdata_setup_fid_button(const char* title, int key, int fid, int* y, int a5)
{
char text[36];
char* pch;
int button_x;
int value_offset_x;
button_x = 10;
value_offset_x = 90;
if (a5 == 9) {
*y -= 189;
}
if (a5 > 8) {
button_x = 165;
value_offset_x -= 16;
}
_win_register_text_button(subwin,
button_x,
*y,
-1,
-1,
-1,
key,
title,
0);
if (art_list_str(fid, text) != -1) {
pch = strchr(text, '.');
if (pch != NULL) {
*pch = '\0';
}
windowDrawText(subwin,
text,
80,
button_x + value_offset_x,
*y + 4,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(subwin,
"None",
80,
button_x + value_offset_x,
*y + 4,
_colorTable[992] | 0x10000);
}
*y += 21;
return 0;
}
// 0x492C20
int proto_subdata_setup_pid_button(const char* title, int key, int pid, int* y, int a5)
{
int button_x;
int value_offset_x;
button_x = 10;
value_offset_x = 90;
if (a5 == 9) {
*y -= 189;
}
if (a5 > 8) {
button_x = 165;
value_offset_x = 74;
}
_win_register_text_button(subwin,
button_x,
*y,
-1,
-1,
-1,
key,
title,
0);
if (pid != -1) {
windowDrawText(subwin,
protoGetName(pid),
49,
button_x + value_offset_x,
*y + 4,
_colorTable[32747] | 0x10000);
} else {
windowDrawText(subwin,
"None",
49,
button_x + value_offset_x,
*y + 4,
_colorTable[992] | 0x10000);
}
*y += 21;
return 0;
}
// 0x495438
const char* proto_wall_light_str(int flags)
{
if ((flags & 0x8000000) != 0) {
return wall_light_strs[1];
}
if ((flags & 0x10000000) != 0) {
return wall_light_strs[2];
}
if ((flags & 0x20000000) != 0) {
return wall_light_strs[3];
}
if ((flags & 0x40000000) != 0) {
return wall_light_strs[4];
}
if ((flags & 0x80000000) != 0) {
return wall_light_strs[5];
}
return wall_light_strs[0];
}
// 0x4960B8
void proto_critter_flags_redraw(int win, int pid)
{
int index;
int color;
int x = 110;
for (index = 0; index < CRITTER_FLAG_COUNT; index++) {
if (_critter_flag_check(pid, critFlagList[index])) {
color = _colorTable[992];
} else {
color = _colorTable[10570];
}
windowDrawText(win, critFlagStrs[index], 44, x, 195, color | 0x10000);
x += 48;
}
}
// 0x496120
int proto_critter_flags_modify(int pid)
{
Proto* proto;
int rc;
int flags = 0;
int index;
if (protoGetProto(pid, &proto) == -1) {
return -1;
}
rc = win_yes_no("Can't be stolen from?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_NO_STEAL;
}
rc = win_yes_no("Can't Drop items?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_NO_DROP;
}
rc = win_yes_no("Can't lose limbs?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_NO_LIMBS;
}
rc = win_yes_no("Dead Bodies Can't Age?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_NO_AGE;
}
rc = win_yes_no("Can't Heal by Aging?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_NO_HEAL;
}
rc = win_yes_no("Is Invlunerable????", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_INVULNERABLE;
}
rc = win_yes_no("Can't Flatten on Death?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_FLAT;
}
rc = win_yes_no("Has Special Death?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_SPECIAL_DEATH;
}
rc = win_yes_no("Has Extra Hand-To-Hand Range?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_LONG_LIMBS;
}
rc = win_yes_no("Can't be knocked back?", 340, 200, _colorTable[15855]);
if (rc == -1) {
return -1;
}
if (rc == 1) {
flags |= CRITTER_NO_KNOCKBACK;
}
if (!can_modify_protos) {
win_timed_msg("Can't modify protos!", _colorTable[31744] | 0x10000);
return -1;
}
for (index = 0; index < CRITTER_FLAG_COUNT; index++) {
if ((critFlagList[index] & flags) != 0) {
critter_flag_set(pid, critFlagList[index]);
} else {
critter_flag_unset(pid, critFlagList[index]);
}
}
return 0;
}
// 0x497520
int mp_pick_kill_type()
{
char* names[KILL_TYPE_COUNT];
int index;
for (index = 0; index < KILL_TYPE_COUNT; index++) {
names[index] = killTypeGetName(index);
}
return _win_list_select("Kill Type",
names,
KILL_TYPE_COUNT,
NULL,
50,
100,
_colorTable[15855]);
}
// 0x497568
int proto_pick_ai_packet(int* value)
{
int count;
char** names;
int index;
int rc;
count = combat_ai_num();
if (count <= 0) {
return -1;
}
names = (char**)internal_malloc(sizeof(char*) * count);
for (index = 0; index < count; index++) {
names[index] = (char*)internal_malloc(strlen(combat_ai_name(index)) + 1);
strcpy(names[index], combat_ai_name(index));
}
rc = _win_list_select("AI Packet",
names,
count,
NULL,
50,
100,
_colorTable[15855]);
if (rc != -1) {
*value = rc;
}
for (index = 0; index < count; index++) {
internal_free(names[index]);
}
internal_free(names);
return 0;
}
} // namespace fallout

15
src/mapper/mp_proto.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef FALLOUT_MAPPER_MP_PROTO_H_
#define FALLOUT_MAPPER_MP_PROTO_H_
namespace fallout {
extern char* proto_builder_name;
extern bool can_modify_protos;
void init_mapper_protos();
const char* proto_wall_light_str(int flags);
int proto_pick_ai_packet(int* value);
} // namespace fallout
#endif /* FALLOUT_MAPPER_MP_PROTO_H_ */

90
src/mapper/mp_scrpt.cc Normal file
View File

@ -0,0 +1,90 @@
#include "mapper/mp_scrpt.h"
#include "art.h"
#include "object.h"
#include "scripts.h"
#include "tile.h"
namespace fallout {
// 0x49B170
int map_scr_remove_spatial(int tile, int elevation)
{
Script* scr;
Object* obj;
Rect rect;
scr = scriptGetFirstSpatialScript(elevation);
while (scr != NULL) {
if (builtTileGetTile(scr->sp.built_tile) == tile) {
scriptRemove(scr->sid);
scr = scriptGetFirstSpatialScript(elevation);
continue;
}
scr = scriptGetNextSpatialScript();
}
obj = objectFindFirstAtElevation(elevation);
while (obj != NULL) {
if (obj->tile == tile && buildFid(OBJ_TYPE_INTERFACE, 3, 0, 0, 0) == obj->fid) {
objectDestroy(obj, &rect);
tileWindowRefreshRect(&rect, elevation);
obj = objectFindFirstAtElevation(elevation);
continue;
}
obj = objectFindNextAtElevation();
}
return 0;
}
// 0x49B214
int map_scr_remove_all_spatials()
{
int elevation;
Script* scr;
Object* obj;
int sid;
for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) {
scr = scriptGetFirstSpatialScript(elevation);
while (scr != NULL) {
scriptRemove(scr->sid);
scr = scriptGetFirstSpatialScript(elevation);
}
obj = objectFindFirstAtElevation(elevation);
while (obj != NULL) {
if (buildFid(OBJ_TYPE_INTERFACE, 3, 0, 0, 0) == obj->fid) {
objectDestroy(obj, NULL);
obj = objectFindFirstAtElevation(elevation);
continue;
}
obj = objectFindNextAtElevation();
}
}
tileWindowRefresh();
for (sid = 0; sid < 15000; sid++) {
if (scriptGetScript(sid, &scr) != -1) {
if (scr->owner != NULL) {
if (scr->owner->pid == 0x500000C) {
scr->owner->sid = -1;
scriptRemove(sid);
}
}
}
}
return 0;
}
} // namespace fallout

11
src/mapper/mp_scrpt.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef FALLOUT_MAPPER_MP_SCRPTR_H_
#define FALLOUT_MAPPER_MP_SCRPTR_H_
namespace fallout {
int map_scr_remove_spatial(int tile, int elevation);
int map_scr_remove_all_spatials();
} // namespace fallout
#endif /* FALLOUT_MAPPER_MP_SCRPTR_H_ */

531
src/mapper/mp_targt.cc Normal file
View File

@ -0,0 +1,531 @@
#include "mapper/mp_targt.h"
#include <string.h>
#include "art.h"
#include "game.h"
#include "map.h"
#include "mapper/mp_proto.h"
#include "memory.h"
#include "proto.h"
#include "window_manager_private.h"
namespace fallout {
#define TARGET_DAT "target.dat"
typedef struct TargetNode {
TargetSubNode subnode;
struct TargetNode* next;
} TargetNode;
typedef struct TargetList {
TargetNode* tail;
int count;
int next_tid;
} TargetList;
// 0x53F354
static char default_target_path_base[] = "\\fallout2\\dev\\proto\\";
// 0x559CC4
static TargetList targetlist = { NULL, 0, 0 };
// 0x559CD0
static char* target_path_base = default_target_path_base;
// 0x559DBC
static bool tgt_overriden = false;
// 0x49B2F0
void target_override_protection()
{
char* name;
tgt_overriden = true;
name = getenv("MAIL_NAME");
if (name != NULL) {
// NOTE: Unsafe, backing buffer is 32 byte max.
strcpy(proto_builder_name, strupr(name));
} else {
strcpy(proto_builder_name, "UNKNOWN");
}
}
// 0x49B2F0
bool target_overriden()
{
return tgt_overriden;
}
// 0x49B34C
void target_make_path(char* path, int pid)
{
if (_cd_path_base[0] != '\0' && _cd_path_base[1] == ':') {
strncpy(path, _cd_path_base, 2);
strcat(path, target_path_base);
} else {
strcpy(path, target_path_base);
}
if (pid != -1) {
strcat(path, artGetObjectTypeName(PID_TYPE(pid)));
}
}
// 0x49B424
int target_init()
{
target_remove_all();
target_header_load();
return 0;
}
// 0x49B434
int target_exit()
{
if (can_modify_protos) {
target_header_save();
target_remove_all();
} else {
target_remove_all();
}
return 0;
}
// 0x49B454
int target_header_save()
{
char path[COMPAT_MAX_PATH];
FILE* stream;
target_make_path(path, -1);
strcat(path, TARGET_DAT);
stream = fopen(path, "wb");
if (stream == NULL) {
return -1;
}
if (fwrite(&targetlist, sizeof(targetlist), 1, stream) != 1) {
// FIXME: Leaking `stream`.
return -1;
}
fclose(stream);
return 0;
}
// 0x49B4E8
int target_header_load()
{
char path[COMPAT_MAX_PATH];
FILE* stream;
target_make_path(path, -1);
strcat(path, TARGET_DAT);
stream = fopen(path, "rb");
if (stream == NULL) {
return -1;
}
if (fread(&targetlist, sizeof(targetlist), 1, stream) != 1) {
// FIXME: Leaking `stream`.
return -1;
}
targetlist.tail = NULL;
targetlist.count = 0;
fclose(stream);
return 0;
}
// 0x49B58C
int target_save(int pid)
{
char path[COMPAT_MAX_PATH];
size_t len;
char* extension;
FILE* stream;
TargetSubNode* subnode;
if (target_ptr(pid, &subnode) == -1) {
return -1;
}
target_make_path(path, pid);
len = strlen(path);
path[len] = '\\';
_proto_list_str(pid, path + len + 1);
extension = strchr(path + len + 1, '.');
if (extension != NULL) {
strcpy(extension + 1, "tgt");
} else {
strcat(path, ".tgt");
}
stream = fopen(path, "wb");
if (stream == NULL) {
return -1;
}
while (subnode != NULL) {
fwrite(subnode, sizeof(TargetSubNode), 1, stream);
subnode = subnode->next;
}
fclose(stream);
return 0;
}
// 0x49B6BC
int target_load(int pid, TargetSubNode** subnode_ptr)
{
char path[COMPAT_MAX_PATH];
size_t len;
char* extension;
FILE* stream;
TargetSubNode* subnode;
target_make_path(path, pid);
len = strlen(path);
path[len] = '\\';
_proto_list_str(pid, path + len + 1);
extension = strchr(path + len + 1, '.');
if (extension != NULL) {
strcpy(extension + 1, "tgt");
} else {
strcat(path, ".tgt");
}
stream = fopen(path, "rb");
if (stream == NULL) {
*subnode_ptr = NULL;
return -1;
}
if (target_find_free_subnode(&subnode) == -1) {
*subnode_ptr = NULL;
// FIXME: Leaks `stream`.
return -1;
}
fread(subnode, sizeof(TargetSubNode), 1, stream);
*subnode_ptr = subnode;
while (subnode->next != NULL) {
subnode->next = (TargetSubNode*)internal_malloc(sizeof(TargetSubNode));
if (subnode->next == NULL) {
// FIXME: Leaks `stream`.
return -1;
}
subnode = subnode->next;
fread(subnode, sizeof(TargetSubNode), 1, stream);
}
fclose(stream);
return 0;
}
// 0x49B9C0
int target_find_free_subnode(TargetSubNode** subnode_ptr)
{
TargetNode* node = (TargetNode*)internal_malloc(sizeof(TargetNode));
if (node == NULL) {
*subnode_ptr = NULL;
return -1;
}
*subnode_ptr = &(node->subnode);
node->subnode.pid = -1;
node->subnode.next = NULL;
node->next = targetlist.tail;
targetlist.tail = node;
targetlist.count++;
return 0;
}
// 0x49BA10
int target_new(int pid, int* tid_ptr)
{
TargetSubNode* subnode;
TargetSubNode* new_subnode;
if (target_ptr(pid, &subnode) == -1) {
if (target_find_free_subnode(&subnode) == -1) {
return -1;
}
}
new_subnode = (TargetSubNode*)internal_malloc(sizeof(TargetSubNode));
if (new_subnode == NULL) {
return -1;
}
new_subnode->next = NULL;
while (subnode->next != NULL) {
subnode = subnode->next;
}
subnode->next = new_subnode;
new_subnode->pid = pid;
new_subnode->tid = targetlist.next_tid;
*tid_ptr = targetlist.next_tid;
targetlist.count++;
targetlist.next_tid++;
return 0;
}
// 0x49BBD4
int target_remove(int pid)
{
TargetNode* node;
TargetSubNode* subnode;
TargetSubNode* subnode_next;
node = targetlist.tail;
while (node != NULL) {
if (node->subnode.pid == pid) {
break;
}
node = node->next;
}
if (node == NULL) {
return -1;
}
subnode = node->subnode.next;
if (node == targetlist.tail) {
targetlist.tail = targetlist.tail->next;
}
internal_free(node);
while (subnode != NULL) {
subnode_next = subnode->next;
internal_free(subnode);
subnode = subnode_next;
}
return 0;
}
// 0x49BC3C
int target_remove_tid(int pid, int tid)
{
TargetNode* node;
TargetSubNode* subnode;
TargetSubNode* subnode_next;
node = targetlist.tail;
while (node != NULL) {
if (node->subnode.pid == pid) {
break;
}
node = node->next;
}
if (node == NULL) {
return -1;
}
if (node->subnode.tid == tid) {
subnode_next = node->subnode.next;
if (subnode_next != NULL) {
memcpy(&(node->subnode), subnode_next, sizeof(TargetSubNode));
internal_free(subnode_next);
} else {
target_remove(pid);
}
// FIXME: Should probably return 0 here.
} else {
subnode = &(node->subnode);
while (subnode != NULL) {
subnode_next = subnode->next;
if (subnode_next->tid == tid) {
subnode->next = subnode_next->next;
internal_free(subnode_next);
return 0;
}
subnode = subnode_next;
}
}
return -1;
}
// 0x49BCBC
int target_remove_all()
{
TargetNode* node;
TargetNode* node_next;
TargetSubNode* subnode;
TargetSubNode* subnode_next;
node = targetlist.tail;
targetlist.tail = NULL;
while (node != NULL) {
node_next = node->next;
subnode = node->subnode.next;
while (subnode != NULL) {
subnode_next = subnode->next;
internal_free(subnode);
subnode = subnode_next;
}
internal_free(node);
node = node_next;
}
return 0;
}
// 0x49BD00
int target_ptr(int pid, TargetSubNode** subnode_ptr)
{
TargetNode* node = targetlist.tail;
while (node != NULL) {
if (node->subnode.pid == pid) {
*subnode_ptr = &(node->subnode);
return 0;
}
}
return target_load(pid, subnode_ptr);
}
// 0x49BD38
int target_tid_ptr(int pid, int tid, TargetSubNode** subnode_ptr)
{
TargetSubNode* subnode;
if (target_ptr(pid, &subnode) == -1) {
return -1;
}
while (subnode != NULL) {
if (subnode->tid == tid) {
*subnode_ptr = subnode;
return 0;
}
}
return -1;
}
// 0x49BD98
int pick_rot()
{
int value;
win_get_num_i(&value,
-1,
5,
false,
"Rotation",
100,
100);
return value;
}
// 0x49BDD0
int target_pick_global_var(int* value_ptr)
{
int value;
int rc;
if (gGameGlobalVarsLength == 0) {
return -1;
}
rc = win_get_num_i(&value,
0,
gGameGlobalVarsLength - 1,
false,
"Global Variable Index #:",
100,
100);
if (rc == -1) {
return -1;
}
*value_ptr = value;
return 0;
}
// 0x49BE20
int target_pick_map_var(int* value_ptr)
{
int value;
int rc;
if (gMapGlobalVarsLength == 0) {
return -1;
}
rc = win_get_num_i(&value,
0,
gMapGlobalVarsLength - 1,
false,
"Map Variable Index #:",
100,
100);
if (rc == -1) {
return -1;
}
*value_ptr = value;
return 0;
}
// 0x49BE70
int target_pick_local_var(int* value_ptr)
{
int value;
int rc;
if (gMapLocalVarsLength == 0) {
return -1;
}
rc = win_get_num_i(&value,
0,
gMapLocalVarsLength - 1,
false,
"Local Variable Index #:",
100,
100);
if (rc == -1) {
return -1;
}
*value_ptr = value;
return 0;
}
} // namespace fallout

72
src/mapper/mp_targt.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef FALLOUT_MAPPER_MP_TARGT_H_
#define FALLOUT_MAPPER_MP_TARGT_H_
namespace fallout {
typedef struct TargetSubNode {
int pid;
int tid;
int field_8;
int field_C;
int field_10;
int field_14;
int field_18;
int field_1C;
int field_20;
int field_24;
struct TargetSubNode* next;
int field_2C;
int field_30;
int field_34;
int field_38;
int field_3C;
int field_40;
int field_44;
int field_48;
int field_4C;
int field_50;
int field_54;
int field_58;
int field_5C;
int field_60;
int field_64;
int field_68;
int field_6C;
int field_70;
int field_74;
int field_78;
int field_7C;
int field_80;
int field_84;
int field_88;
int field_8C;
int field_90;
int field_94;
int field_98;
int field_9C;
} TargetSubNode;
void target_override_protection();
bool target_overriden();
void target_make_path(char* path, int pid);
int target_init();
int target_exit();
int target_header_save();
int target_header_load();
int target_save(int pid);
int target_load(int pid, TargetSubNode** subnode_ptr);
int target_find_free_subnode(TargetSubNode** subnode_ptr);
int target_new(int pid, int* tid_ptr);
int target_remove(int pid);
int target_remove_tid(int pid, int tid);
int target_remove_all();
int target_ptr(int pid, TargetSubNode** subnode_ptr);
int target_tid_ptr(int pid, int tid, TargetSubNode** subnode_ptr);
int pick_rot();
int target_pick_global_var(int* value_ptr);
int target_pick_map_var(int* value_ptr);
int target_pick_local_var(int* value_ptr);
} // namespace fallout
#endif /* FALLOUT_MAPPER_MP_TARGT_H_ */

13
src/mapper/mp_text.cc Normal file
View File

@ -0,0 +1,13 @@
#include "mapper/mp_text.h"
namespace fallout {
// 0x49DAC4
int proto_build_all_texts()
{
// TODO: Incomplete.
return 0;
}
} // namespace fallout

10
src/mapper/mp_text.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef FALLOUT_MAPPER_MP_TEXT_H_
#define FALLOUT_MAPPER_MP_TEXT_H_
namespace fallout {
int proto_build_all_texts();
} // namespace fallout
#endif /* FALLOUT_MAPPER_MP_TEXT_H_ */

View File

@ -32,7 +32,6 @@ typedef struct MemoryBlockFooter {
static void* memoryBlockMallocImpl(size_t size);
static void* memoryBlockReallocImpl(void* ptr, size_t size);
static void memoryBlockFreeImpl(void* ptr);
static void memoryBlockPrintStats();
static void* mem_prep_block(void* block, size_t size);
static void memoryBlockValidate(void* block);
@ -176,10 +175,8 @@ static void memoryBlockFreeImpl(void* ptr)
}
}
// NOTE: Not used.
//
// 0x4C5C5C
static void memoryBlockPrintStats()
void mem_check()
{
if (gMallocProc == memoryBlockMallocImpl) {
debugPrint("Current memory allocated: %6d blocks, %9u bytes total\n", gMemoryBlocksCurrentCount, gMemoryBlocksCurrentSize);

View File

@ -9,6 +9,7 @@ char* internal_strdup(const char* string);
void* internal_malloc(size_t size);
void* internal_realloc(void* ptr, size_t size);
void internal_free(void* ptr);
void mem_check();
} // namespace fallout

View File

@ -9,6 +9,7 @@
#include <string.h>
#include "audio_engine.h"
#include "delay.h"
#include "platform_compat.h"
namespace fallout {
@ -794,6 +795,8 @@ static int _syncWait()
if (_sync_active) {
if (((_sync_time + 1000 * compat_timeGetTime()) & 0x80000000) != 0) {
result = 1;
delay_ms(-(_sync_time + 1000 * compat_timeGetTime()) / 1000 - 3);
while (((_sync_time + 1000 * compat_timeGetTime()) & 0x80000000) != 0)
;
}
@ -1148,6 +1151,7 @@ static int _MVE_sndConfigure(int a1, int a2, int a3, int a4, int a5, int a6)
}
// 0x4F56C0
// Looks like this function is not used
static void _MVE_syncSync()
{
if (_sync_active) {
@ -1294,6 +1298,10 @@ static void _MVE_sndSync()
break;
}
v0 = true;
#ifdef EMSCRIPTEN
delay_ms(1);
#endif
}
if (dword_6B3660 != dword_6B3AE4) {
@ -1318,6 +1326,10 @@ static int _syncWaitLevel(int a1)
v2 = _sync_time + a1;
do {
result = v2 + 1000 * compat_timeGetTime();
if (result < 0) {
delay_ms(-result / 1000 - 3);
}
result = v2 + 1000 * compat_timeGetTime();
} while (result < 0);
_sync_time += _sync_wait_quanta;

View File

@ -2375,10 +2375,10 @@ bool _obj_occupied(int tile, int elevation)
}
// 0x48B848
Object* _obj_blocking_at(Object* a1, int tile, int elev)
Object* _obj_blocking_at(Object* excludeObj, int tile, int elev)
{
ObjectListNode* objectListNode;
Object* v7;
Object* obj;
int type;
if (!hexGridTileIsValid(tile)) {
@ -2387,14 +2387,14 @@ Object* _obj_blocking_at(Object* a1, int tile, int elev)
objectListNode = gObjectListHeadByTile[tile];
while (objectListNode != NULL) {
v7 = objectListNode->obj;
if (v7->elevation == elev) {
if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) {
type = FID_TYPE(v7->fid);
obj = objectListNode->obj;
if (obj->elevation == elev) {
if ((obj->flags & OBJECT_HIDDEN) == 0 && (obj->flags & OBJECT_NO_BLOCK) == 0 && obj != excludeObj) {
type = FID_TYPE(obj->fid);
if (type == OBJ_TYPE_CRITTER
|| type == OBJ_TYPE_SCENERY
|| type == OBJ_TYPE_WALL) {
return v7;
return obj;
}
}
}
@ -2406,15 +2406,15 @@ Object* _obj_blocking_at(Object* a1, int tile, int elev)
if (hexGridTileIsValid(neighboor)) {
objectListNode = gObjectListHeadByTile[neighboor];
while (objectListNode != NULL) {
v7 = objectListNode->obj;
if ((v7->flags & OBJECT_MULTIHEX) != 0) {
if (v7->elevation == elev) {
if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) {
type = FID_TYPE(v7->fid);
obj = objectListNode->obj;
if ((obj->flags & OBJECT_MULTIHEX) != 0) {
if (obj->elevation == elev) {
if ((obj->flags & OBJECT_HIDDEN) == 0 && (obj->flags & OBJECT_NO_BLOCK) == 0 && obj != excludeObj) {
type = FID_TYPE(obj->fid);
if (type == OBJ_TYPE_CRITTER
|| type == OBJ_TYPE_SCENERY
|| type == OBJ_TYPE_WALL) {
return v7;
return obj;
}
}
}
@ -2428,7 +2428,7 @@ Object* _obj_blocking_at(Object* a1, int tile, int elev)
}
// 0x48B930
Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev)
Object* _obj_shoot_blocking_at(Object* excludeObj, int tile, int elev)
{
if (!hexGridTileIsValid(tile)) {
return NULL;
@ -2439,7 +2439,7 @@ Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev)
Object* candidate = objectListItem->obj;
if (candidate->elevation == elev) {
unsigned int flags = candidate->flags;
if ((flags & OBJECT_HIDDEN) == 0 && ((flags & OBJECT_NO_BLOCK) == 0 || (flags & OBJECT_SHOOT_THRU) == 0) && candidate != obj) {
if ((flags & OBJECT_HIDDEN) == 0 && ((flags & OBJECT_NO_BLOCK) == 0 || (flags & OBJECT_SHOOT_THRU) == 0) && candidate != excludeObj) {
int type = FID_TYPE(candidate->fid);
// SFALL: Fix to prevent corpses from blocking line of fire.
if ((type == OBJ_TYPE_CRITTER && !critterIsDead(candidate))
@ -2464,7 +2464,7 @@ Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev)
unsigned int flags = candidate->flags;
if ((flags & OBJECT_MULTIHEX) != 0) {
if (candidate->elevation == elev) {
if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != obj) {
if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != excludeObj) {
int type = FID_TYPE(candidate->fid);
// SFALL: Fix to prevent corpses from blocking line of
// fire.
@ -2484,7 +2484,7 @@ Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev)
}
// 0x48BA20
Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation)
Object* _obj_ai_blocking_at(Object* excludeObj, int tile, int elevation)
{
if (!hexGridTileIsValid(tile)) {
return NULL;
@ -2496,7 +2496,7 @@ Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation)
if (object->elevation == elevation) {
if ((object->flags & OBJECT_HIDDEN) == 0
&& (object->flags & OBJECT_NO_BLOCK) == 0
&& object != a1) {
&& object != excludeObj) {
int objectType = FID_TYPE(object->fid);
if (objectType == OBJ_TYPE_CRITTER
|| objectType == OBJ_TYPE_SCENERY
@ -2525,7 +2525,7 @@ Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation)
if (object->elevation == elevation) {
if ((object->flags & OBJECT_HIDDEN) == 0
&& (object->flags & OBJECT_NO_BLOCK) == 0
&& object != a1) {
&& object != excludeObj) {
int objectType = FID_TYPE(object->fid);
if (objectType == OBJ_TYPE_CRITTER
|| objectType == OBJ_TYPE_SCENERY
@ -2571,7 +2571,7 @@ int _obj_scroll_blocking_at(int tile, int elev)
}
// 0x48BB88
Object* _obj_sight_blocking_at(Object* a1, int tile, int elevation)
Object* _obj_sight_blocking_at(Object* excludeObj, int tile, int elevation)
{
ObjectListNode* objectListNode = gObjectListHeadByTile[tile];
while (objectListNode != NULL) {
@ -2579,7 +2579,7 @@ Object* _obj_sight_blocking_at(Object* a1, int tile, int elevation)
if (object->elevation == elevation
&& (object->flags & OBJECT_HIDDEN) == 0
&& (object->flags & OBJECT_LIGHT_THRU) == 0
&& object != a1) {
&& object != excludeObj) {
int objectType = FID_TYPE(object->fid);
if (objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) {
return object;

View File

@ -71,11 +71,11 @@ Object* objectFindFirstAtLocation(int elevation, int tile);
Object* objectFindNextAtLocation();
void objectGetRect(Object* obj, Rect* rect);
bool _obj_occupied(int tile_num, int elev);
Object* _obj_blocking_at(Object* a1, int tile_num, int elev);
Object* _obj_shoot_blocking_at(Object* obj, int tile, int elev);
Object* _obj_ai_blocking_at(Object* a1, int tile, int elevation);
Object* _obj_blocking_at(Object* excludeObj, int tile_num, int elev);
Object* _obj_shoot_blocking_at(Object* excludeObj, int tile, int elev);
Object* _obj_ai_blocking_at(Object* excludeObj, int tile, int elevation);
int _obj_scroll_blocking_at(int tile_num, int elev);
Object* _obj_sight_blocking_at(Object* a1, int tile_num, int elev);
Object* _obj_sight_blocking_at(Object* excludeObj, int tile_num, int elev);
int objectGetDistanceBetween(Object* object1, Object* object2);
int objectGetDistanceBetweenTiles(Object* object1, int tile1, Object* object2, int tile2);
int objectListCreate(int tile, int elevation, int objectType, Object*** objectsPtr);

View File

@ -64,22 +64,22 @@ typedef struct STRU_519DBC {
int field_8; // early what?
} STRU_519DBC;
typedef struct STRUCT_519DA8 {
typedef struct PartyMemberListItem {
Object* object;
Script* script;
int* vars;
struct STRUCT_519DA8* next;
} STRUCT_519DA8;
struct PartyMemberListItem* next;
} PartyMemberListItem;
static int partyMemberGetDescription(Object* object, PartyMemberDescription** partyMemberDescriptionPtr);
static void partyMemberDescriptionInit(PartyMemberDescription* partyMemberDescription);
static int _partyMemberPrepLoadInstance(STRUCT_519DA8* a1);
static int _partyMemberRecoverLoadInstance(STRUCT_519DA8* a1);
static int _partyMemberPrepLoadInstance(PartyMemberListItem* a1);
static int _partyMemberRecoverLoadInstance(PartyMemberListItem* a1);
static int _partyMemberNewObjID();
static int _partyMemberNewObjIDRecurseFind(Object* object, int objectId);
static int _partyMemberPrepItemSave(Object* object);
static int _partyMemberItemSave(Object* object);
static int _partyMemberItemRecover(STRUCT_519DA8* a1);
static int _partyMemberItemRecover(PartyMemberListItem* a1);
static int _partyMemberClearItemList();
static int partyFixMultipleMembers();
static int _partyMemberCopyLevelInfo(Object* object, int a2);
@ -91,12 +91,12 @@ int gPartyMemberDescriptionsLength = 0;
int* gPartyMemberPids = NULL;
//
static STRUCT_519DA8* _itemSaveListHead = NULL;
static PartyMemberListItem* _itemSaveListHead = NULL;
// List of party members, it's length is [gPartyMemberDescriptionsLength] + 20.
//
// 0x519DA8
static STRUCT_519DA8* gPartyMembers = NULL;
PartyMemberListItem* gPartyMembers = NULL;
// Number of critters added to party.
//
@ -150,7 +150,7 @@ int partyMembersInit()
memset(gPartyMemberPids, 0, sizeof(*gPartyMemberPids) * gPartyMemberDescriptionsLength);
gPartyMembers = (STRUCT_519DA8*)internal_malloc(sizeof(*gPartyMembers) * (gPartyMemberDescriptionsLength + 20));
gPartyMembers = (PartyMemberListItem*)internal_malloc(sizeof(*gPartyMembers) * (gPartyMemberDescriptionsLength + 20));
if (gPartyMembers == NULL) {
goto err;
}
@ -379,7 +379,7 @@ int partyMemberAdd(Object* object)
}
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
if (partyMember->object == object || partyMember->object->pid == object->pid) {
return 0;
}
@ -390,7 +390,7 @@ int partyMemberAdd(Object* object)
return -1;
}
STRUCT_519DA8* partyMember = &(gPartyMembers[gPartyMembersLength]);
PartyMemberListItem* partyMember = &(gPartyMembers[gPartyMembersLength]);
partyMember->object = object;
partyMember->script = NULL;
partyMember->vars = NULL;
@ -435,7 +435,7 @@ int partyMemberRemove(Object* object)
int index;
for (index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
if (partyMember->object == object) {
break;
}
@ -480,7 +480,7 @@ int _partyMemberPrepSave()
_partyStatePrepped = 1;
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
PartyMemberListItem* ptr = &(gPartyMembers[index]);
if (index > 0) {
ptr->object->flags &= ~(OBJECT_NO_REMOVE | OBJECT_NO_SAVE);
@ -499,7 +499,7 @@ int _partyMemberPrepSave()
int _partyMemberUnPrepSave()
{
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
PartyMemberListItem* ptr = &(gPartyMembers[index]);
if (index > 0) {
ptr->object->flags |= (OBJECT_NO_REMOVE | OBJECT_NO_SAVE);
@ -523,7 +523,7 @@ int partyMembersSave(File* stream)
if (fileWriteInt32(stream, _partyMemberItemCount) == -1) return -1;
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
if (fileWriteInt32(stream, partyMember->object->id) == -1) return -1;
}
@ -547,7 +547,7 @@ int _partyMemberPrepLoad()
_partyStatePrepped = 1;
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr_519DA8 = &(gPartyMembers[index]);
PartyMemberListItem* ptr_519DA8 = &(gPartyMembers[index]);
if (_partyMemberPrepLoadInstance(ptr_519DA8) != 0) {
return -1;
}
@ -558,7 +558,7 @@ int _partyMemberPrepLoad()
// partyMemberPrepLoadInstance
// 0x49480C
static int _partyMemberPrepLoadInstance(STRUCT_519DA8* a1)
static int _partyMemberPrepLoadInstance(PartyMemberListItem* a1)
{
Object* obj = a1->object;
@ -643,7 +643,7 @@ int _partyMemberRecoverLoad()
debugPrint("[Party Member %d]: %s\n", index, critterGetName(gPartyMembers[index].object));
}
STRUCT_519DA8* v6 = _itemSaveListHead;
PartyMemberListItem* v6 = _itemSaveListHead;
while (v6 != NULL) {
_itemSaveListHead = v6->next;
@ -664,7 +664,7 @@ int _partyMemberRecoverLoad()
// partyMemberRecoverLoadInstance
// 0x494A88
static int _partyMemberRecoverLoadInstance(STRUCT_519DA8* a1)
static int _partyMemberRecoverLoadInstance(PartyMemberListItem* a1)
{
if (a1->script == NULL) {
showMesageBox("\n Error!: partyMemberRecoverLoadInstance: No script!");
@ -801,7 +801,7 @@ int _partyMemberSyncPosition()
int n = 0;
int distance = 2;
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
Object* partyMemberObj = partyMember->object;
if ((partyMemberObj->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(partyMemberObj->pid) == OBJ_TYPE_CRITTER) {
int rotation;
@ -833,7 +833,7 @@ int _partyMemberRestingHeal(int a1)
}
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
if (PID_TYPE(partyMember->object->pid) == OBJ_TYPE_CRITTER) {
int healingRate = critterGetStat(partyMember->object, STAT_HEALING_RATE);
critterAdjustHitPoints(partyMember->object, v1 * healingRate);
@ -860,7 +860,7 @@ Object* partyMemberFindByPid(int pid)
bool _isPotentialPartyMember(Object* object)
{
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
if (partyMember->object->pid == gPartyMemberPids[index]) {
return true;
}
@ -976,7 +976,7 @@ static int _partyMemberNewObjIDRecurseFind(Object* obj, int objectId)
int _partyMemberPrepItemSaveAll()
{
for (int partyMemberIndex = 0; partyMemberIndex < gPartyMembersLength; partyMemberIndex++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[partyMemberIndex]);
PartyMemberListItem* partyMember = &(gPartyMembers[partyMemberIndex]);
Inventory* inventory = &(partyMember->object->data.inventory);
for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) {
@ -1025,7 +1025,7 @@ static int _partyMemberItemSave(Object* object)
object->id = script->field_1C;
}
STRUCT_519DA8* node = (STRUCT_519DA8*)internal_malloc(sizeof(*node));
PartyMemberListItem* node = (PartyMemberListItem*)internal_malloc(sizeof(*node));
if (node == NULL) {
showMesageBox("\n Error!: partyMemberItemSave: Out of memory!");
exit(1);
@ -1053,7 +1053,7 @@ static int _partyMemberItemSave(Object* object)
node->vars = NULL;
}
STRUCT_519DA8* temp = _itemSaveListHead;
PartyMemberListItem* temp = _itemSaveListHead;
_itemSaveListHead = node;
node->next = temp;
}
@ -1069,7 +1069,7 @@ static int _partyMemberItemSave(Object* object)
// partyMemberItemRecover
// 0x495388
static int _partyMemberItemRecover(STRUCT_519DA8* a1)
static int _partyMemberItemRecover(PartyMemberListItem* a1)
{
int sid = -1;
if (scriptAdd(&sid, SCRIPT_TYPE_ITEM) == -1) {
@ -1108,7 +1108,7 @@ static int _partyMemberItemRecover(STRUCT_519DA8* a1)
static int _partyMemberClearItemList()
{
while (_itemSaveListHead != NULL) {
STRUCT_519DA8* node = _itemSaveListHead;
PartyMemberListItem* node = _itemSaveListHead;
_itemSaveListHead = _itemSaveListHead->next;
if (node->script != NULL) {
@ -1262,7 +1262,7 @@ static int partyFixMultipleMembers()
}
for (int index = 0; index < gPartyMembersLength; index++) {
STRUCT_519DA8* partyMember = &(gPartyMembers[index]);
PartyMemberListItem* partyMember = &(gPartyMembers[index]);
Script* script;
if (scriptGetScript(partyMember->object->sid, &script) != -1) {
@ -1454,7 +1454,7 @@ bool partyMemberSupportsChemUse(Object* object, int chemUse)
int _partyMemberIncLevels()
{
int i;
STRUCT_519DA8* ptr;
PartyMemberListItem* ptr;
Object* obj;
PartyMemberDescription* party_member;
const char* name;
@ -1622,7 +1622,7 @@ static int _partyMemberCopyLevelInfo(Object* critter, int a2)
bool partyIsAnyoneCanBeHealedByRest()
{
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
PartyMemberListItem* ptr = &(gPartyMembers[index]);
Object* object = ptr->object;
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue;
@ -1649,7 +1649,7 @@ int partyGetMaxWoundToHealByRest()
int maxWound = 0;
for (int index = 1; index < gPartyMembersLength; index++) {
STRUCT_519DA8* ptr = &(gPartyMembers[index]);
PartyMemberListItem* ptr = &(gPartyMembers[index]);
Object* object = ptr->object;
if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue;
@ -1670,4 +1670,20 @@ int partyGetMaxWoundToHealByRest()
return maxWound;
}
std::vector<Object*> get_all_party_members_objects(bool include_hidden)
{
std::vector<Object*> value;
value.reserve(gPartyMembersLength);
for (int index = 0; index < gPartyMembersLength; index++) {
auto object = gPartyMembers[index].object;
if (include_hidden
|| (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER
&& !critterIsDead(object)
&& (object->flags & OBJECT_HIDDEN) == 0)) {
value.push_back(object);
}
}
return value;
}
} // namespace fallout

View File

@ -1,8 +1,11 @@
#ifndef PARTY_MEMBER_H
#define PARTY_MEMBER_H
#include <vector>
#include "db.h"
#include "obj_types.h"
#include "scripts.h"
namespace fallout {
@ -42,6 +45,7 @@ bool partyMemberSupportsChemUse(Object* object, int chemUse);
int _partyMemberIncLevels();
bool partyIsAnyoneCanBeHealedByRest();
int partyGetMaxWoundToHealByRest();
std::vector<Object*> get_all_party_members_objects(bool include_hidden);
} // namespace fallout

View File

@ -13,6 +13,7 @@
#include "cycle.h"
#include "dbox.h"
#include "debug.h"
#include "delay.h"
#include "draw.h"
#include "game.h"
#include "game_mouse.h"
@ -1962,7 +1963,7 @@ static bool pipboyRest(int hours, int minutes, int duration)
double v2 = v1 * (1.0 / 1440.0) * 3.5 + 0.25;
double v3 = (double)minutes / v1 * v2;
if (minutes != 0) {
int gameTime = gameTimeGetTime();
unsigned int gameTime = gameTimeGetTime();
double v4 = v3 * 20.0;
int v5 = 0;
@ -2001,8 +2002,7 @@ static bool pipboyRest(int hours, int minutes, int duration)
pipboyDrawDate();
windowRefresh(gPipboyWindow);
while (getTicksSince(start) < 50) {
}
delay_ms(50 - (getTicks() - start));
}
renderPresent();
@ -2025,7 +2025,7 @@ static bool pipboyRest(int hours, int minutes, int duration)
}
if (hours != 0 && !rc) {
int gameTime = gameTimeGetTime();
unsigned int gameTime = gameTimeGetTime();
double v7 = (v2 - v3) * 20.0;
for (int hour = 0; hour < (int)v7; hour++) {
@ -2072,8 +2072,7 @@ static bool pipboyRest(int hours, int minutes, int duration)
pipboyDrawHitPoints();
windowRefresh(gPipboyWindow);
while (getTicksSince(start) < 50) {
}
delay_ms(50 - (getTicks() - start));
}
renderPresent();
@ -2143,9 +2142,7 @@ static bool pipboyRest(int hours, int minutes, int duration)
}
}
int gameTime = gameTimeGetTime();
int nextEventGameTime = queueGetNextEventTime();
if (gameTime > nextEventGameTime) {
if (gameTimeGetTime() > queueGetNextEventTime()) {
if (queueProcessEvents()) {
debugPrint("PIPBOY: Returning from Queue trigger...\n");
_proc_bail_flag = 1;
@ -2368,8 +2365,7 @@ static int pipboyRenderScreensaver()
v31 -= 1;
} else {
windowRefreshRect(gPipboyWindow, &gPipboyWindowContentRect);
while (getTicksSince(time) < 50) {
}
delay_ms(50 - (getTicks() - time));
}
renderPresent();

View File

@ -7,6 +7,7 @@
#include "combat.h"
#include "combat_ai.h"
#include "debug.h"
#include "delay.h"
#include "draw.h"
#include "game.h"
#include "game_mouse.h"
@ -1570,8 +1571,7 @@ static void _DoThing(int eventCode)
blitBufferToBufferTrans(_preferencesFrmImages[PREFERENCES_WINDOW_FRM_KNOB_ON].getData(), 21, 12, 21, gPreferencesWindowBuffer + PREFERENCES_WINDOW_WIDTH * meta->knobY + v31, PREFERENCES_WINDOW_WIDTH);
windowRefresh(gPreferencesWindow);
while (getTicksSince(tick) < 35)
;
delay_ms(35 - (getTicks() - tick));
renderPresent();
sharedFpsLimiter.throttle();

View File

@ -24,7 +24,6 @@
namespace fallout {
static int _proto_critter_init(Proto* a1, int a2);
static int objectCritterCombatDataRead(CritterCombatData* data, File* stream);
static int objectCritterCombatDataWrite(CritterCombatData* data, File* stream);
static int _proto_update_gen(Object* obj);
@ -39,8 +38,7 @@ static int _proto_load_pid(int pid, Proto** out_proto);
static int _proto_find_free_subnode(int type, Proto** out_ptr);
static void _proto_remove_some_list(int type);
static void _proto_remove_list(int type);
static int _proto_new_id(int a1);
static int _proto_max_id(int a1);
static int _proto_new_id(int type);
// 0x50CF3C
static char _aProto_0[] = "proto\\";
@ -169,7 +167,7 @@ char* _proto_none_str;
static char* gBodyTypeNames[BODY_TYPE_COUNT];
// 0x664834
static char* gItemTypeNames[ITEM_TYPE_COUNT];
char* gItemTypeNames[ITEM_TYPE_COUNT];
// 0x66484C
static char* gDamageTypeNames[DAMAGE_TYPE_COUNT];
@ -187,8 +185,8 @@ static char** _perk_code_strs;
// 0x6648BC
static char** _critter_stats_list;
// NOTE: Inlined.
void _proto_make_path(char* path, int pid)
// 0x49E270
void proto_make_path(char* path, int pid)
{
strcpy(path, _cd_path_base);
strcat(path, _proto_path_base);
@ -211,7 +209,7 @@ int _proto_list_str(int pid, char* proto_path)
}
char path[COMPAT_MAX_PATH];
_proto_make_path(path, pid);
proto_make_path(path, pid);
strcat(path, "\\");
strcat(path, artGetObjectTypeName(PID_TYPE(pid)));
strcat(path, ".lst");
@ -370,32 +368,143 @@ char* protoGetDescription(int pid)
return protoGetMessage(pid, PROTOTYPE_MESSAGE_DESCRIPTION);
}
// 0x49EB2C
int proto_item_init(Proto* proto, int a2)
{
int v1 = a2 & 0xFFFFFF;
proto->item.pid = -1;
proto->item.messageId = 100 * v1;
proto->item.fid = buildFid(OBJ_TYPE_ITEM, v1 - 1, 0, 0, 0);
if (!artExists(proto->item.fid)) {
proto->item.fid = buildFid(OBJ_TYPE_ITEM, 0, 0, 0, 0);
}
proto->item.lightDistance = 0;
proto->item.lightIntensity = 0;
proto->item.flags = 0xA0000008;
proto->item.extendedFlags = 0xA000;
proto->item.sid = -1;
proto->item.type = ITEM_TYPE_MISC;
proto_item_subdata_init(proto, proto->item.type);
proto->item.material = 1;
proto->item.size = 1;
proto->item.weight = 10;
proto->item.cost = 0;
proto->item.inventoryFid = -1;
proto->item.field_80 = '0';
return 0;
}
// 0x49EBFC
int proto_item_subdata_init(Proto* proto, int type)
{
int index;
switch (type) {
case ITEM_TYPE_ARMOR:
proto->item.data.armor.armorClass = 0;
for (index = 0; index < DAMAGE_TYPE_COUNT; index++) {
proto->item.data.armor.damageResistance[index] = 0;
proto->item.data.armor.damageThreshold[index] = 0;
}
proto->item.data.armor.perk = -1;
proto->item.data.armor.maleFid = -1;
proto->item.data.armor.femaleFid = -1;
break;
case ITEM_TYPE_CONTAINER:
proto->item.data.container.openFlags = 0;
proto->item.data.container.maxSize = 250;
proto->item.extendedFlags |= 0x800;
break;
case ITEM_TYPE_DRUG:
proto->item.data.drug.stat[0] = STAT_STRENGTH;
proto->item.data.drug.stat[1] = -1;
proto->item.data.drug.stat[2] = -1;
proto->item.data.drug.amount[0] = 0;
proto->item.data.drug.amount[1] = 0;
proto->item.data.drug.amount[2] = 0;
proto->item.data.drug.duration1 = 0;
proto->item.data.drug.amount1[0] = 0;
proto->item.data.drug.amount1[1] = 0;
proto->item.data.drug.amount1[2] = 0;
proto->item.data.drug.duration2 = 0;
proto->item.data.drug.amount2[0] = 0;
proto->item.data.drug.amount2[1] = 0;
proto->item.data.drug.amount2[2] = 0;
proto->item.data.drug.addictionChance = 0;
proto->item.data.drug.withdrawalEffect = 0;
proto->item.data.drug.withdrawalOnset = 0;
proto->item.extendedFlags |= 0x1000;
break;
case ITEM_TYPE_WEAPON:
proto->item.data.weapon.animationCode = 0;
proto->item.data.weapon.minDamage = 0;
proto->item.data.weapon.maxDamage = 0;
proto->item.data.weapon.damageType = 0;
proto->item.data.weapon.maxRange1 = 0;
proto->item.data.weapon.maxRange2 = 0;
proto->item.data.weapon.projectilePid = -1;
proto->item.data.weapon.minStrength = 0;
proto->item.data.weapon.actionPointCost1 = 0;
proto->item.data.weapon.actionPointCost2 = 0;
proto->item.data.weapon.criticalFailureType = 0;
proto->item.data.weapon.perk = -1;
proto->item.data.weapon.rounds = 0;
proto->item.data.weapon.caliber = 0;
proto->item.data.weapon.ammoTypePid = -1;
proto->item.data.weapon.ammoCapacity = 0;
proto->item.data.weapon.soundCode = 0;
break;
case ITEM_TYPE_AMMO:
proto->item.data.ammo.caliber = 0;
proto->item.data.ammo.quantity = 20;
proto->item.data.ammo.armorClassModifier = 0;
proto->item.data.ammo.damageResistanceModifier = 0;
proto->item.data.ammo.damageMultiplier = 1;
proto->item.data.ammo.damageDivisor = 1;
break;
case ITEM_TYPE_MISC:
proto->item.data.misc.powerTypePid = -1;
proto->item.data.misc.powerType = 20;
break;
case ITEM_TYPE_KEY:
proto->item.data.key.keyCode = -1;
proto->item.extendedFlags |= 0x1000;
break;
}
return 0;
}
// 0x49EDB4
static int _proto_critter_init(Proto* a1, int a2)
int proto_critter_init(Proto* proto, int pid)
{
if (!_protos_been_initialized) {
return -1;
}
int v1 = a2 & 0xFFFFFF;
int num = pid & 0xFFFFFF;
a1->pid = -1;
a1->messageId = 100 * v1;
a1->fid = buildFid(OBJ_TYPE_CRITTER, v1 - 1, 0, 0, 0);
a1->critter.lightDistance = 0;
a1->critter.lightIntensity = 0;
a1->critter.flags = 0x20000000;
a1->critter.extendedFlags = 0x6000;
a1->critter.sid = -1;
a1->critter.data.flags = 0;
a1->critter.data.bodyType = 0;
a1->critter.headFid = -1;
a1->critter.aiPacket = 1;
if (!artExists(a1->fid)) {
a1->fid = buildFid(OBJ_TYPE_CRITTER, 0, 0, 0, 0);
proto->pid = -1;
proto->messageId = 100 * num;
proto->fid = buildFid(OBJ_TYPE_CRITTER, num - 1, 0, 0, 0);
proto->critter.lightDistance = 0;
proto->critter.lightIntensity = 0;
proto->critter.flags = 0x20000000;
proto->critter.extendedFlags = 0x6000;
proto->critter.sid = -1;
proto->critter.data.flags = 0;
proto->critter.data.bodyType = 0;
proto->critter.headFid = -1;
proto->critter.aiPacket = 1;
if (!artExists(proto->fid)) {
proto->fid = buildFid(OBJ_TYPE_CRITTER, 0, 0, 0, 0);
}
CritterProtoData* data = &(a1->critter.data);
CritterProtoData* data = &(proto->critter.data);
data->experience = 60;
data->killType = 0;
data->damageType = 0;
@ -828,6 +937,165 @@ int _proto_dude_init(const char* path)
return 0;
}
// 0x49FBBC
int proto_scenery_init(Proto* proto, int pid)
{
int num = pid & 0xFFFFFF;
proto->scenery.pid = -1;
proto->scenery.messageId = 100 * num;
proto->scenery.fid = buildFid(OBJ_TYPE_SCENERY, num - 1, 0, 0, 0);
if (!artExists(proto->scenery.fid)) {
proto->scenery.fid = buildFid(OBJ_TYPE_SCENERY, 0, 0, 0, 0);
}
proto->scenery.lightDistance = 0;
proto->scenery.lightIntensity = 0;
proto->scenery.flags = 0;
proto->scenery.extendedFlags = 0x2000;
proto->scenery.sid = -1;
proto->scenery.type = SCENERY_TYPE_GENERIC;
proto_scenery_subdata_init(proto, proto->scenery.type);
proto->scenery.field_2C = -1;
proto->scenery.field_34 = '0';
return 0;
}
// 0x49FC74
int proto_scenery_subdata_init(Proto* proto, int type)
{
switch (type) {
case SCENERY_TYPE_DOOR:
proto->scenery.data.door.openFlags = 0;
proto->scenery.extendedFlags |= 0x800;
break;
case SCENERY_TYPE_STAIRS:
proto->scenery.data.stairs.field_0 = -1;
proto->scenery.data.stairs.field_4 = -1;
proto->scenery.extendedFlags |= 0x800;
break;
case SCENERY_TYPE_ELEVATOR:
proto->scenery.data.elevator.type = -1;
proto->scenery.data.elevator.level = -1;
proto->scenery.extendedFlags |= 0x800;
break;
case SCENERY_TYPE_LADDER_UP:
proto->scenery.data.ladder.field_0 = -1;
proto->scenery.extendedFlags |= 0x800;
break;
case SCENERY_TYPE_LADDER_DOWN:
proto->scenery.data.ladder.field_0 = -1;
proto->scenery.extendedFlags |= 0x800;
break;
}
return 0;
}
// 0x49FCFC
int proto_wall_init(Proto* proto, int pid)
{
int num = pid & 0xFFFFFF;
proto->wall.pid = -1;
proto->wall.messageId = 100 * num;
proto->wall.fid = buildFid(OBJ_TYPE_WALL, num - 1, 0, 0, 0);
if (!artExists(proto->wall.fid)) {
proto->wall.fid = buildFid(OBJ_TYPE_WALL, 0, 0, 0, 0);
}
proto->wall.lightDistance = 0;
proto->wall.lightIntensity = 0;
proto->wall.flags = 0;
proto->wall.extendedFlags = 0x2000;
proto->wall.sid = -1;
proto->wall.material = 1;
return 0;
}
// 0x49FD84
int proto_tile_init(Proto* proto, int pid)
{
int num = pid & 0xFFFFFF;
proto->tile.pid = -1;
proto->tile.messageId = 100 * num;
proto->tile.fid = buildFid(OBJ_TYPE_TILE, num - 1, 0, 0, 0);
if (!artExists(proto->tile.fid)) {
proto->tile.fid = buildFid(OBJ_TYPE_TILE, 0, 0, 0, 0);
}
proto->tile.flags = 0;
proto->tile.extendedFlags = 0x2000;
proto->tile.sid = -1;
proto->tile.material = 1;
return 0;
}
// 0x49FDFC
int proto_misc_init(Proto* proto, int pid)
{
int num = pid & 0xFFFFFF;
proto->misc.pid = -1;
proto->misc.messageId = 100 * num;
proto->misc.fid = buildFid(OBJ_TYPE_MISC, num - 1, 0, 0, 0);
if (!artExists(proto->misc.fid)) {
proto->misc.fid = buildFid(OBJ_TYPE_MISC, 0, 0, 0, 0);
}
proto->misc.lightDistance = 0;
proto->misc.lightIntensity = 0;
proto->misc.flags = 0;
proto->misc.extendedFlags = 0;
return 0;
}
// 0x49FE74
int proto_copy_proto(int srcPid, int dstPid)
{
int srcType;
int dstType;
Proto* src;
Proto* dst;
srcType = PID_TYPE(srcPid);
dstType = PID_TYPE(dstPid);
if (srcType != dstType) {
return -1;
}
if (protoGetProto(srcPid, &src) == -1) {
return -1;
}
if (protoGetProto(dstPid, &dst) == -1) {
return -1;
}
memcpy(dst, src, _proto_sizes[srcType]);
dst->pid = dstPid;
return 0;
}
// 0x49FEDC
bool proto_is_subtype(Proto* proto, int subtype)
{
if (subtype == -1) {
return true;
}
switch (PID_TYPE(proto->pid)) {
case OBJ_TYPE_ITEM:
return proto->item.type == subtype;
case OBJ_TYPE_SCENERY:
return proto->scenery.type == subtype;
}
return false;
}
// proto_data_member
// 0x49FFD8
int protoGetDataMember(int pid, int member, ProtoDataMemberValue* value)
@ -1083,7 +1351,7 @@ int protoInit()
compat_mkdir(path);
// TODO: Get rid of cast.
_proto_critter_init((Proto*)&gDudeProto, 0x1000000);
proto_critter_init((Proto*)&gDudeProto, 0x1000000);
gDudeProto.pid = 0x1000000;
gDudeProto.fid = buildFid(OBJ_TYPE_CRITTER, 1, 0, 0, 0);
@ -1202,7 +1470,7 @@ void protoReset()
int i;
// TODO: Get rid of cast.
_proto_critter_init((Proto*)&gDudeProto, 0x1000000);
proto_critter_init((Proto*)&gDudeProto, 0x1000000);
gDudeProto.pid = 0x1000000;
gDudeProto.fid = buildFid(OBJ_TYPE_CRITTER, 1, 0, 0, 0);
@ -1251,7 +1519,7 @@ static int _proto_header_load()
ptr->max_entries_num = 1;
char path[COMPAT_MAX_PATH];
_proto_make_path(path, index << 24);
proto_make_path(path, index << 24);
strcat(path, "\\");
strcat(path, artGetObjectTypeName(index));
strcat(path, ".lst");
@ -1661,7 +1929,7 @@ int _proto_save_pid(int pid)
}
char path[260];
_proto_make_path(path, pid);
proto_make_path(path, pid);
strcat(path, "\\");
_proto_list_str(pid, path + strlen(path));
@ -1682,7 +1950,7 @@ int _proto_save_pid(int pid)
static int _proto_load_pid(int pid, Proto** protoPtr)
{
char path[COMPAT_MAX_PATH];
_proto_make_path(path, pid);
proto_make_path(path, pid);
strcat(path, "\\");
if (_proto_list_str(pid, path + strlen(path)) == -1) {
@ -1761,6 +2029,48 @@ static int _proto_find_free_subnode(int type, Proto** protoPtr)
return 0;
}
// 0x4A1E90
int proto_new(int* pid, int type)
{
Proto* proto;
if (_proto_find_free_subnode(type, &proto) == -1) {
return -1;
}
*pid = _proto_new_id(type) | (type << 24);
switch (type) {
case OBJ_TYPE_ITEM:
proto_item_init(proto, *pid);
proto->item.pid = *pid;
break;
case OBJ_TYPE_CRITTER:
proto_critter_init(proto, *pid);
proto->critter.pid = *pid;
break;
case OBJ_TYPE_SCENERY:
proto_scenery_init(proto, *pid);
proto->scenery.pid = *pid;
break;
case OBJ_TYPE_WALL:
proto_wall_init(proto, *pid);
proto->wall.pid = *pid;
break;
case OBJ_TYPE_TILE:
proto_tile_init(proto, *pid);
proto->tile.pid = *pid;
break;
case OBJ_TYPE_MISC:
proto_misc_init(proto, *pid);
proto->misc.pid = *pid;
break;
default:
return -1;
}
return 0;
}
// Evict top most proto cache block.
//
// 0x4A2040
@ -1850,18 +2160,18 @@ int protoGetProto(int pid, Proto** protoPtr)
}
// 0x4A21DC
static int _proto_new_id(int a1)
static int _proto_new_id(int type)
{
int result = _protoLists[a1].max_entries_num;
_protoLists[a1].max_entries_num = result + 1;
int result = _protoLists[type].max_entries_num;
_protoLists[type].max_entries_num = result + 1;
return result;
}
// 0x4A2214
static int _proto_max_id(int a1)
int proto_max_id(int type)
{
return _protoLists[a1].max_entries_num;
return _protoLists[type].max_entries_num;
}
// 0x4A22C0

View File

@ -101,8 +101,9 @@ extern char _cd_path_base[COMPAT_MAX_PATH];
extern MessageList gProtoMessageList;
extern char* _proto_none_str;
extern char* gItemTypeNames[ITEM_TYPE_COUNT];
void _proto_make_path(char* path, int pid);
void proto_make_path(char* path, int pid);
int _proto_list_str(int pid, char* proto_path);
size_t proto_size(int type);
bool _proto_action_can_use(int pid);
@ -112,20 +113,32 @@ int _proto_action_can_pickup(int pid);
char* protoGetMessage(int pid, int message);
char* protoGetName(int pid);
char* protoGetDescription(int pid);
int proto_item_init(Proto* proto, int pid);
int proto_item_subdata_init(Proto* proto, int type);
int proto_critter_init(Proto* proto, int pid);
void objectDataReset(Object* obj);
int objectDataRead(Object* obj, File* stream);
int objectDataWrite(Object* obj, File* stream);
int _proto_update_init(Object* obj);
int _proto_dude_update_gender();
int _proto_dude_init(const char* path);
int proto_scenery_init(Proto* proto, int pid);
int proto_scenery_subdata_init(Proto* proto, int type);
int proto_wall_init(Proto* proto, int pid);
int proto_tile_init(Proto* proto, int pid);
int proto_misc_init(Proto* proto, int pid);
int proto_copy_proto(int srcPid, int dstPid);
bool proto_is_subtype(Proto* proto, int subtype);
int protoGetDataMember(int pid, int member, ProtoDataMemberValue* value);
int protoInit();
void protoReset();
void protoExit();
int _proto_save_pid(int pid);
int proto_new(int* pid, int type);
void _proto_remove_all();
int protoGetProto(int pid, Proto** out_proto);
int protoGetProto(int pid, Proto** protoPtr);
int _ResetPlayer();
int proto_max_id(int type);
static bool isExitGridPid(int pid)
{

View File

@ -18,8 +18,7 @@
namespace fallout {
typedef struct QueueListNode {
// TODO: Make unsigned.
int time;
unsigned int time;
int type;
Object* owner;
void* data;
@ -102,7 +101,7 @@ int queueLoad(File* stream)
break;
}
if (fileReadInt32(stream, &(queueListNode->time)) == -1) {
if (fileReadUInt32(stream, &(queueListNode->time)) == -1) {
internal_free(queueListNode);
rc = -1;
break;
@ -169,27 +168,20 @@ int queueLoad(File* stream)
}
}
if (oldListHead != NULL) {
QueueListNode** v13 = &gQueueListHead;
QueueListNode* v15;
do {
while (true) {
QueueListNode* v14 = *v13;
if (v14 == NULL) {
break;
}
if (v14->time > oldListHead->time) {
break;
}
v13 = &(v14->next);
while (oldListHead != NULL) {
QueueListNode** queueListNodePtr = &gQueueListHead;
while (*queueListNodePtr != NULL) {
if ((*queueListNodePtr)->time > oldListHead->time) {
break;
}
v15 = oldListHead->next;
oldListHead->next = *v13;
*v13 = oldListHead;
oldListHead = v15;
} while (v15 != NULL);
queueListNodePtr = &((*queueListNodePtr)->next);
}
QueueListNode* next = oldListHead->next;
oldListHead->next = *queueListNodePtr;
*queueListNodePtr = oldListHead;
oldListHead = next;
}
return rc;
@ -217,7 +209,7 @@ int queueSave(File* stream)
Object* object = queueListNode->owner;
int objectId = object != NULL ? object->id : -2;
if (fileWriteInt32(stream, queueListNode->time) == -1) {
if (fileWriteUInt32(stream, queueListNode->time) == -1) {
return -1;
}
@ -250,9 +242,7 @@ int queueAddEvent(int delay, Object* obj, void* data, int eventType)
return -1;
}
int v1 = gameTimeGetTime();
int v2 = v1 + delay;
newQueueListNode->time = v2;
newQueueListNode->time = gameTimeGetTime() + delay;
newQueueListNode->type = eventType;
newQueueListNode->owner = obj;
newQueueListNode->data = data;
@ -261,22 +251,18 @@ int queueAddEvent(int delay, Object* obj, void* data, int eventType)
obj->flags |= OBJECT_QUEUED;
}
QueueListNode** v3 = &gQueueListHead;
QueueListNode** queueListNodePtr = &gQueueListHead;
if (gQueueListHead != NULL) {
QueueListNode* v4;
while (*queueListNodePtr != NULL) {
if (newQueueListNode->time < (*queueListNodePtr)->time) {
break;
}
do {
v4 = *v3;
if (v2 < v4->time) {
break;
}
v3 = &(v4->next);
} while (v4->next != NULL);
queueListNodePtr = &((*queueListNodePtr)->next);
}
newQueueListNode->next = *v3;
*v3 = newQueueListNode;
newQueueListNode->next = *queueListNodePtr;
*queueListNodePtr = newQueueListNode;
return 0;
}
@ -357,19 +343,20 @@ bool queueHasEvent(Object* owner, int eventType)
// 0x4A26D0
int queueProcessEvents()
{
int time = gameTimeGetTime();
int v1 = 0;
unsigned int time = gameTimeGetTime();
// TODO: this is 0 or 1, but in some cases -1. Probably needs to be bool.
int stopProcess = 0;
while (gQueueListHead != NULL) {
QueueListNode* queueListNode = gQueueListHead;
if (time < queueListNode->time || v1 != 0) {
if (time < queueListNode->time || stopProcess != 0) {
break;
}
gQueueListHead = queueListNode->next;
EventTypeDescription* eventTypeDescription = &(gEventTypeDescriptions[queueListNode->type]);
v1 = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data);
stopProcess = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data);
if (eventTypeDescription->freeProc != NULL) {
eventTypeDescription->freeProc(queueListNode->data);
@ -378,7 +365,7 @@ int queueProcessEvents()
internal_free(queueListNode);
}
return v1;
return stopProcess;
}
// 0x4A2748
@ -437,10 +424,8 @@ void _queue_clear_type(int eventType, QueueEventHandler* fn)
}
}
// TODO: Make unsigned.
//
// 0x4A2808
int queueGetNextEventTime()
unsigned int queueGetNextEventTime()
{
if (gQueueListHead == NULL) {
return 0;

View File

@ -66,7 +66,7 @@ bool queueHasEvent(Object* owner, int eventType);
int queueProcessEvents();
void queueClear();
void _queue_clear_type(int eventType, QueueEventHandler* fn);
int queueGetNextEventTime();
unsigned int queueGetNextEventTime();
void _queue_leaving_map();
bool queueIsEmpty();
void* queueFindFirstEvent(Object* owner, int eventType);

View File

@ -100,7 +100,7 @@ int randomRoll(int difficulty, int criticalSuccessModifier, int* howMuchPtr)
// 0x4A3030
static int randomTranslateRoll(int delta, int criticalSuccessModifier)
{
int gameTime = gameTimeGetTime();
unsigned int gameTime = gameTimeGetTime();
// SFALL: Remove criticals time limits.
bool criticalsTimeLimitsRemoved = false;

View File

@ -29,7 +29,9 @@
#include "proto.h"
#include "proto_instance.h"
#include "queue.h"
#include "sfall_arrays.h"
#include "sfall_config.h"
#include "sfall_global_scripts.h"
#include "stat.h"
#include "svga.h"
#include "tile.h"
@ -125,7 +127,7 @@ static int _script_engine_game_mode = 0;
// Game time in ticks (1/10 second).
//
// 0x51C720
static int gGameTime = 302400;
static unsigned int gGameTime = 302400;
// 0x51C724
static const int gGameTimeDaysPerMonth[12] = {
@ -144,7 +146,7 @@ static const int gGameTimeDaysPerMonth[12] = {
};
// 0x51C758
static const char* gScriptProcNames[28] = {
const char* gScriptProcNames[SCRIPT_PROC_COUNT] = {
"no_p_proc",
"start",
"spatial_p_proc",
@ -275,12 +277,10 @@ static int gMovieTimerArtimer2;
static int gMovieTimerArtimer3;
static int gMovieTimerArtimer4;
// TODO: Make unsigned.
//
// Returns game time in ticks (1/10 second).
//
// 0x4A3330
int gameTimeGetTime()
unsigned int gameTimeGetTime()
{
return gGameTime;
}
@ -349,7 +349,7 @@ char* gameTimeGetTimeString()
// TODO: Make unsigned.
//
// 0x4A347C
void gameTimeSetTime(int time)
void gameTimeSetTime(unsigned int time)
{
if (time == 0) {
time = 1;
@ -405,7 +405,7 @@ int gameTimeScheduleUpdateEvent()
int gameTimeEventProcess(Object* obj, void* data)
{
int movie_index;
int v4;
int stopProcess;
movie_index = -1;
@ -421,17 +421,17 @@ int gameTimeEventProcess(Object* obj, void* data)
_scriptsCheckGameEvents(&movie_index, -1);
}
v4 = _critter_check_rads(gDude);
stopProcess = _critter_check_rads(gDude);
_queue_clear_type(4, 0);
gameTimeScheduleUpdateEvent();
if (movie_index != -1) {
v4 = 1;
stopProcess = 1;
}
return v4;
return stopProcess;
}
// 0x4A3690
@ -773,9 +773,7 @@ static void _script_chk_timed_events()
if (v1) {
while (!queueIsEmpty()) {
int time = gameTimeGetTime();
int v2 = queueGetNextEventTime();
if (time < v2) {
if (gameTimeGetTime() < queueGetNextEventTime()) {
break;
}
@ -915,8 +913,8 @@ int scriptsHandleRequests()
}
}
if ((gScriptsRequests & SCRIPT_REQUEST_0x02) != 0) {
gScriptsRequests &= ~SCRIPT_REQUEST_0x02;
if ((gScriptsRequests & SCRIPT_REQUEST_TOWN_MAP) != 0) {
gScriptsRequests &= ~SCRIPT_REQUEST_TOWN_MAP;
wmTownMap();
}
@ -1027,6 +1025,8 @@ int scriptsHandleRequests()
inventoryOpenStealing(gScriptsRequestedStealingBy, gScriptsRequestedStealingFrom);
}
DeleteAllTempArrays();
return 0;
}
@ -1128,6 +1128,16 @@ void _scripts_request_combat_locked(STRUCT_664980* a1)
gScriptsRequests |= (SCRIPT_REQUEST_0x0400 | SCRIPT_REQUEST_COMBAT);
}
// 0x4A461C
void scripts_request_townmap()
{
if (isInCombat()) {
_game_user_wants_to_quit = 1;
}
gScriptsRequests |= SCRIPT_REQUEST_TOWN_MAP;
}
// request_world_map()
// 0x4A4644
void scriptsRequestWorldMap()
@ -1988,6 +1998,8 @@ static int scriptRead(Script* scr, File* stream)
scr->localVarsCount = 0;
}
scr->overriddenSelf = nullptr;
return 0;
}
@ -2204,6 +2216,8 @@ int scriptAdd(int* sidPtr, int scriptType)
scr->procs[index] = SCRIPT_PROC_NO_PROC;
}
scr->overriddenSelf = nullptr;
scriptListExtent->length++;
return 0;
@ -2586,6 +2600,9 @@ void scriptsExecMapUpdateProc()
// 0x4A67EC
void scriptsExecMapUpdateScripts(int proc)
{
// SFALL: Run global scripts.
sfall_gl_scr_exec_map_update_scripts(proc);
_scr_SpatialsEnabled = false;
int fixedParam = 0;

View File

@ -25,7 +25,7 @@ namespace fallout {
typedef enum ScriptRequests {
SCRIPT_REQUEST_COMBAT = 0x01,
SCRIPT_REQUEST_0x02 = 0x02,
SCRIPT_REQUEST_TOWN_MAP = 0x02,
SCRIPT_REQUEST_WORLD_MAP = 0x04,
SCRIPT_REQUEST_ELEVATOR = 0x08,
SCRIPT_REQUEST_EXPLOSION = 0x10,
@ -143,15 +143,19 @@ typedef struct Script {
int field_D4;
int field_D8;
int field_DC;
Object* overriddenSelf;
} Script;
int gameTimeGetTime();
extern const char* gScriptProcNames[SCRIPT_PROC_COUNT];
unsigned int gameTimeGetTime();
void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr);
int gameTimeGetHour();
char* gameTimeGetTimeString();
void gameTimeAddTicks(int a1);
void gameTimeAddSeconds(int a1);
void gameTimeSetTime(int time);
void gameTimeSetTime(unsigned int time);
int gameTimeScheduleUpdateEvent();
int gameTimeEventProcess(Object* obj, void* data);
int _scriptsCheckGameEvents(int* moviePtr, int window);
@ -173,6 +177,7 @@ int scriptsHandleRequests();
int _scripts_check_state_in_combat();
int scriptsRequestCombat(STRUCT_664980* a1);
void _scripts_request_combat_locked(STRUCT_664980* ptr);
void scripts_request_townmap();
void scriptsRequestWorldMap();
int scriptsRequestElevator(Object* a1, int a2);
int scriptsRequestExplosion(int tile, int elevation, int minDamage, int maxDamage);

717
src/sfall_arrays.cc Normal file
View File

@ -0,0 +1,717 @@
#include "sfall_arrays.h"
#include <assert.h>
#include <string.h>
#include <algorithm>
#include <memory>
#include <random>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "interpreter.h"
#include "sfall_lists.h"
namespace fallout {
static constexpr ArrayId kInitialArrayId = 1;
#define ARRAY_MAX_STRING (255) // maximum length of string to be stored as array key or value
#define ARRAY_MAX_SIZE (100000) // maximum number of array elements,
// special actions for arrays using array_resize operator
#define ARRAY_ACTION_SORT (-2)
#define ARRAY_ACTION_RSORT (-3)
#define ARRAY_ACTION_REVERSE (-4)
#define ARRAY_ACTION_SHUFFLE (-5)
template <class T, typename Compare>
static void ListSort(std::vector<T>& arr, int type, Compare cmp)
{
switch (type) {
case ARRAY_ACTION_SORT: // sort ascending
std::sort(arr.begin(), arr.end(), cmp);
break;
case ARRAY_ACTION_RSORT: // sort descending
std::sort(arr.rbegin(), arr.rend(), cmp);
break;
case ARRAY_ACTION_REVERSE: // reverse elements
std::reverse(arr.rbegin(), arr.rend());
break;
case ARRAY_ACTION_SHUFFLE: // shuffle elements
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(arr.begin(), arr.end(), g);
break;
}
}
enum class ArrayElementType {
INT,
FLOAT,
STRING,
POINTER
};
/**
* This is mostly the same as ProgramValue but it owns strings.
*
* This is done because when we pop dynamic string element from
* the stack we decrease ref count for this string and it memory
* can be freed.
*
* In theory arrays can be shared between programs so we also
* have to copy static strings.
*
*/
class ArrayElement {
public:
ArrayElement()
: type(ArrayElementType::INT)
, value({ 0 })
{
}
ArrayElement(const ArrayElement& other) = delete;
ArrayElement& operator=(const ArrayElement& rhs) = delete;
ArrayElement(ArrayElement&& other) noexcept
: type(ArrayElementType::INT)
, value({ 0 })
{
std::swap(type, other.type);
std::swap(value, other.value);
}
ArrayElement& operator=(ArrayElement&& other) noexcept
{
std::swap(type, other.type);
std::swap(value, other.value);
return *this;
}
ArrayElement(ProgramValue programValue, Program* program)
{
switch (programValue.opcode) {
case VALUE_TYPE_INT:
type = ArrayElementType::INT;
value.integerValue = programValue.integerValue;
break;
case VALUE_TYPE_FLOAT:
type = ArrayElementType::FLOAT;
value.floatValue = programValue.floatValue;
break;
case VALUE_TYPE_PTR:
type = ArrayElementType::POINTER;
value.pointerValue = programValue.pointerValue;
break;
case VALUE_TYPE_STRING:
case VALUE_TYPE_DYNAMIC_STRING:
setString(programGetString(program, programValue.opcode, programValue.integerValue), -1);
break;
default:
type = ArrayElementType::INT;
value.integerValue = 0;
break;
}
}
ArrayElement(const char* str)
{
setString(str, -1);
}
ArrayElement(const char* str, size_t sLen)
{
setString(str, sLen);
}
ProgramValue toValue(Program* program) const
{
ProgramValue out;
switch (type) {
case ArrayElementType::INT:
out.opcode = VALUE_TYPE_INT;
out.integerValue = value.integerValue;
break;
case ArrayElementType::FLOAT:
out.opcode = VALUE_TYPE_FLOAT;
out.floatValue = value.floatValue;
break;
case ArrayElementType::POINTER:
out.opcode = VALUE_TYPE_PTR;
out.pointerValue = value.pointerValue;
break;
case ArrayElementType::STRING:
out.opcode = VALUE_TYPE_DYNAMIC_STRING;
out.integerValue = programPushString(program, value.stringValue);
break;
}
return out;
}
bool operator<(ArrayElement const& other) const
{
if (type != other.type) {
return type < other.type;
}
switch (type) {
case ArrayElementType::INT:
return value.integerValue < other.value.integerValue;
case ArrayElementType::FLOAT:
return value.floatValue < other.value.floatValue;
case ArrayElementType::POINTER:
return value.pointerValue < other.value.pointerValue;
case ArrayElementType::STRING:
return strcmp(value.stringValue, other.value.stringValue) < 0;
default:
return false;
}
}
bool operator==(ArrayElement const& other) const
{
if (type != other.type) {
return false;
}
switch (type) {
case ArrayElementType::INT:
return value.integerValue == other.value.integerValue;
case ArrayElementType::FLOAT:
return value.floatValue == other.value.floatValue;
case ArrayElementType::POINTER:
return value.pointerValue == other.value.pointerValue;
case ArrayElementType::STRING:
return strcmp(value.stringValue, other.value.stringValue) == 0;
default:
return false;
}
}
~ArrayElement()
{
if (type == ArrayElementType::STRING) {
free(value.stringValue);
}
}
private:
void setString(const char* str, size_t sLen)
{
type = ArrayElementType::STRING;
if (sLen == -1) {
sLen = strlen(str);
}
if (sLen >= ARRAY_MAX_STRING) {
sLen = ARRAY_MAX_STRING - 1; // memory safety
}
value.stringValue = (char*)malloc(sLen + 1);
memcpy(value.stringValue, str, sLen);
value.stringValue[sLen] = '\0';
}
ArrayElementType type;
union {
int integerValue;
float floatValue;
char* stringValue;
void* pointerValue;
} value;
};
class SFallArray {
public:
SFallArray(unsigned int flags)
: flags(flags)
{
}
virtual ~SFallArray() = default;
virtual ProgramValue GetArrayKey(int index, Program* program) = 0;
virtual ProgramValue GetArray(const ProgramValue& key, Program* program) = 0;
virtual void SetArray(const ProgramValue& key, const ProgramValue& val, bool allowUnset, Program* program) = 0;
virtual void SetArray(const ProgramValue& key, ArrayElement&& val, bool allowUnset) = 0;
virtual void ResizeArray(int newLen) = 0;
virtual ProgramValue ScanArray(const ProgramValue& value, Program* program) = 0;
virtual int size() = 0;
bool isReadOnly() const
{
return (flags & SFALL_ARRAYFLAG_CONSTVAL) != 0;
}
protected:
unsigned int flags;
};
class SFallArrayList : public SFallArray {
public:
SFallArrayList() = delete;
SFallArrayList(unsigned int len, unsigned int flags)
: SFallArray(flags)
{
values.resize(len);
}
int size()
{
return static_cast<int>(values.size());
}
ProgramValue GetArrayKey(int index, Program* program)
{
if (index < -1 || index > size()) {
return ProgramValue(0);
}
if (index == -1) { // special index to indicate if array is associative
return ProgramValue(0);
}
return ProgramValue(index);
}
ProgramValue GetArray(const ProgramValue& key, Program* program)
{
auto index = key.asInt();
if (index < 0 || index >= size()) {
return ProgramValue(0);
}
return values[index].toValue(program);
}
void SetArray(const ProgramValue& key, const ProgramValue& val, bool allowUnset, Program* program)
{
SetArray(key, ArrayElement { val, program }, allowUnset);
}
void SetArray(const ProgramValue& key, ArrayElement&& val, bool allowUnset)
{
if (key.isInt()) {
auto index = key.asInt();
if (index >= 0 && index < size()) {
std::swap(values[index], val);
}
}
}
void ResizeArray(int newLen)
{
if (newLen == -1 || size() == newLen) {
return;
}
if (newLen == 0) {
values.clear();
} else if (newLen > 0) {
if (newLen > ARRAY_MAX_SIZE) {
newLen = ARRAY_MAX_SIZE; // safety
}
values.resize(newLen);
} else if (newLen >= ARRAY_ACTION_SHUFFLE) {
ListSort(values, newLen, std::less<ArrayElement>());
}
}
ProgramValue ScanArray(const ProgramValue& value, Program* program)
{
auto element = ArrayElement { value, program };
for (int i = 0; i < size(); i++) {
if (element == values[i]) {
return ProgramValue(i);
}
}
return ProgramValue(-1);
}
private:
std::vector<ArrayElement> values;
};
class SFallArrayAssoc : public SFallArray {
public:
SFallArrayAssoc() = delete;
SFallArrayAssoc(unsigned int flags)
: SFallArray(flags)
{
}
int size()
{
return static_cast<int>(pairs.size());
}
ProgramValue GetArrayKey(int index, Program* program)
{
if (index < -1 || index > size()) {
return ProgramValue(0);
}
if (index == -1) { // special index to indicate if array is associative
return ProgramValue(1);
}
return pairs[index].key.toValue(program);
}
ProgramValue GetArray(const ProgramValue& key, Program* program)
{
auto keyEl = ArrayElement { key, program };
auto it = std::find_if(pairs.begin(), pairs.end(), [&keyEl](const KeyValuePair& pair) -> bool {
return pair.key == keyEl;
});
if (it == pairs.end()) {
return ProgramValue(0);
}
auto index = it - pairs.begin();
return pairs[index].value.toValue(program);
}
void SetArray(const ProgramValue& key, const ProgramValue& val, bool allowUnset, Program* program)
{
auto keyEl = ArrayElement { key, program };
auto it = std::find_if(pairs.begin(), pairs.end(), [&keyEl](const KeyValuePair& pair) -> bool {
return pair.key == keyEl;
});
if (it != pairs.end() && isReadOnly()) {
// don't update value of key
return;
}
if (allowUnset && !isReadOnly() && val.isInt() && val.asInt() == 0) {
// after assigning zero to a key, no need to store it, because "get_array" returns 0 for non-existent keys: try unset
if (it != pairs.end()) {
pairs.erase(it);
}
} else {
if (it == pairs.end()) {
// size check
if (size() >= ARRAY_MAX_SIZE) {
return;
}
pairs.push_back(KeyValuePair { std::move(keyEl), ArrayElement { val, program } });
} else {
auto index = it - pairs.begin();
pairs[index].value = ArrayElement { val, program };
}
}
}
void SetArray(const ProgramValue& key, ArrayElement&& val, bool allowUnset)
{
assert(false && "This method is not used for associative arrays thus it is not implemented");
}
void ResizeArray(int newLen)
{
if (newLen == -1 || size() == newLen) {
return;
}
// only allow to reduce number of elements (adding range of elements is meaningless for maps)
if (newLen >= 0 && newLen < size()) {
pairs.resize(newLen);
} else if (newLen < 0) {
if (newLen < (ARRAY_ACTION_SHUFFLE - 2)) return;
MapSort(newLen);
}
}
ProgramValue ScanArray(const ProgramValue& value, Program* program)
{
auto valueEl = ArrayElement { value, program };
auto it = std::find_if(pairs.begin(), pairs.end(), [&valueEl](const KeyValuePair& pair) {
return pair.value == valueEl;
});
if (it == pairs.end()) {
return ProgramValue(-1);
}
return it->key.toValue(program);
}
private:
struct KeyValuePair {
ArrayElement key;
ArrayElement value;
};
void MapSort(int type)
{
bool sortByValue = false;
if (type < ARRAY_ACTION_SHUFFLE) {
type += 4;
sortByValue = true;
}
if (sortByValue) {
ListSort(pairs, type, [](const KeyValuePair& a, const KeyValuePair& b) -> bool {
return a.value < b.value;
});
} else {
ListSort(pairs, type, [](const KeyValuePair& a, const KeyValuePair& b) -> bool {
return a.key < b.key;
});
}
}
std::vector<KeyValuePair> pairs;
};
struct SfallArraysState {
std::unordered_map<ArrayId, std::unique_ptr<SFallArray>> arrays;
std::unordered_set<ArrayId> temporaryArrayIds;
int nextArrayId = kInitialArrayId;
};
static SfallArraysState* _state = nullptr;
bool sfallArraysInit()
{
_state = new (std::nothrow) SfallArraysState();
if (_state == nullptr) {
return false;
}
return true;
}
void sfallArraysReset()
{
if (_state != nullptr) {
_state->arrays.clear();
_state->temporaryArrayIds.clear();
_state->nextArrayId = kInitialArrayId;
}
}
void sfallArraysExit()
{
if (_state != nullptr) {
delete _state;
}
}
ArrayId CreateArray(int len, unsigned int flags)
{
flags = (flags & ~1); // reset 1 bit
if (len < 0) {
flags |= SFALL_ARRAYFLAG_ASSOC;
} else if (len > ARRAY_MAX_SIZE) {
len = ARRAY_MAX_SIZE; // safecheck
}
ArrayId arrayId = _state->nextArrayId++;
if (flags & SFALL_ARRAYFLAG_ASSOC) {
_state->arrays.emplace(std::make_pair(arrayId, std::make_unique<SFallArrayAssoc>(flags)));
} else {
_state->arrays.emplace(std::make_pair(arrayId, std::make_unique<SFallArrayList>(len, flags)));
}
return arrayId;
}
ArrayId CreateTempArray(int len, unsigned int flags)
{
ArrayId arrayId = CreateArray(len, flags);
_state->temporaryArrayIds.insert(arrayId);
return arrayId;
}
SFallArray* get_array_by_id(ArrayId arrayId)
{
auto it = _state->arrays.find(arrayId);
if (it == _state->arrays.end()) {
return nullptr;
}
return it->second.get();
}
ProgramValue GetArrayKey(ArrayId arrayId, int index, Program* program)
{
auto arr = get_array_by_id(arrayId);
if (arr == nullptr) {
return ProgramValue(0);
}
return arr->GetArrayKey(index, program);
}
int LenArray(ArrayId arrayId)
{
auto arr = get_array_by_id(arrayId);
if (arr == nullptr) {
return -1;
}
return arr->size();
}
ProgramValue GetArray(ArrayId arrayId, const ProgramValue& key, Program* program)
{
auto arr = get_array_by_id(arrayId);
if (arr == nullptr) {
return ProgramValue(0);
}
return arr->GetArray(key, program);
}
void SetArray(ArrayId arrayId, const ProgramValue& key, const ProgramValue& val, bool allowUnset, Program* program)
{
auto arr = get_array_by_id(arrayId);
if (arr == nullptr) {
return;
}
arr->SetArray(key, val, allowUnset, program);
}
void FreeArray(ArrayId arrayId)
{
// TODO: remove from saved_arrays
_state->arrays.erase(arrayId);
}
void FixArray(ArrayId arrayId)
{
_state->temporaryArrayIds.erase(arrayId);
}
void DeleteAllTempArrays()
{
for (auto it = _state->temporaryArrayIds.begin(); it != _state->temporaryArrayIds.end(); ++it) {
FreeArray(*it);
}
_state->temporaryArrayIds.clear();
}
void ResizeArray(ArrayId arrayId, int newLen)
{
auto arr = get_array_by_id(arrayId);
if (arr == nullptr) {
return;
}
arr->ResizeArray(newLen);
}
int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program)
{
// CE: Sfall uses eponymous global variable which is always the id of the
// last created array.
ArrayId stackArrayId = _state->nextArrayId - 1;
auto arr = get_array_by_id(stackArrayId);
if (arr == nullptr) {
return 0;
}
auto size = arr->size();
if (size >= ARRAY_MAX_SIZE) {
return 0;
}
if (key.asInt() >= size) {
arr->ResizeArray(size + 1);
}
SetArray(stackArrayId, key, val, false, program);
return 0;
}
ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program)
{
auto arr = get_array_by_id(arrayId);
if (arr == nullptr) {
return ProgramValue(-1);
}
return arr->ScanArray(val, program);
}
ArrayId ListAsArray(int type)
{
std::vector<Object*> objects;
sfall_lists_fill(type, objects);
int count = static_cast<int>(objects.size());
ArrayId arrayId = CreateTempArray(count, 0);
auto arr = get_array_by_id(arrayId);
// A little bit ugly and likely inefficient.
for (int index = 0; index < count; index++) {
arr->SetArray(ProgramValue { index },
ArrayElement { ProgramValue { objects[index] }, nullptr },
false);
}
return arrayId;
}
ArrayId StringSplit(const char* str, const char* split)
{
size_t splitLen = strlen(split);
ArrayId arrayId = CreateTempArray(0, 0);
auto arr = get_array_by_id(arrayId);
if (splitLen == 0) {
int count = static_cast<int>(strlen(str));
arr->ResizeArray(count);
for (int i = 0; i < count; i++) {
arr->SetArray(ProgramValue { i }, ArrayElement { &str[i], 1 }, false);
}
} else {
int count = 1;
const char* ptr = str;
while (true) {
const char* newptr = strstr(ptr, split);
if (newptr == nullptr) {
break;
}
count++;
ptr = newptr + splitLen;
}
arr->ResizeArray(count);
count = 0;
ptr = str;
while (true) {
const char* newptr = strstr(ptr, split);
size_t len = (newptr != nullptr) ? newptr - ptr : strlen(ptr);
arr->SetArray(ProgramValue { count }, ArrayElement { ptr, len }, false);
if (newptr == nullptr) {
break;
}
count++;
ptr = newptr + splitLen;
}
}
return arrayId;
}
} // namespace fallout

35
src/sfall_arrays.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef FALLOUT_SFALL_ARRAYS_H_
#define FALLOUT_SFALL_ARRAYS_H_
#include "interpreter.h"
namespace fallout {
#define SFALL_ARRAYFLAG_ASSOC (1) // is map
#define SFALL_ARRAYFLAG_CONSTVAL (2) // don't update value of key if the key exists in map
#define SFALL_ARRAYFLAG_RESERVED (4)
using ArrayId = unsigned int;
bool sfallArraysInit();
void sfallArraysReset();
void sfallArraysExit();
ArrayId CreateArray(int len, unsigned int flags);
ArrayId CreateTempArray(int len, unsigned int flags);
ProgramValue GetArrayKey(ArrayId arrayId, int index, Program* program);
int LenArray(ArrayId arrayId);
ProgramValue GetArray(ArrayId arrayId, const ProgramValue& key, Program* program);
void SetArray(ArrayId arrayId, const ProgramValue& key, const ProgramValue& val, bool allowUnset, Program* program);
void FreeArray(ArrayId arrayId);
void FixArray(ArrayId id);
void ResizeArray(ArrayId arrayId, int newLen);
void DeleteAllTempArrays();
int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program);
ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program);
ArrayId ListAsArray(int type);
ArrayId StringSplit(const char* str, const char* split);
} // namespace fallout
#endif /* FALLOUT_SFALL_ARRAYS_H_ */

View File

@ -57,6 +57,10 @@ bool sfallConfigInit(int argc, char** argv)
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER2, 180);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER3, 270);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER4, 360);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_QUICK_SAVE, 0);
configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, "");
configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, "");
char path[COMPAT_MAX_PATH];
char* executable = argv[0];

View File

@ -8,6 +8,7 @@ namespace fallout {
#define SFALL_CONFIG_FILE_NAME "ddraw.ini"
#define SFALL_CONFIG_MISC_KEY "Misc"
#define SFALL_CONFIG_SCRIPTS_KEY "Scripts"
#define SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_MALE_KEY "MaleDefaultModel"
#define SFALL_CONFIG_DUDE_NATIVE_LOOK_JUMPSUIT_FEMALE_KEY "FemaleDefaultModel"
@ -68,6 +69,9 @@ namespace fallout {
#define SFALL_CONFIG_TOWN_MAP_HOTKEYS_FIX_KEY "TownMapHotkeysFix"
#define SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY "ExtraGameMsgFileList"
#define SFALL_CONFIG_NUMBERS_IS_DIALOG_KEY "NumbersInDialogue"
#define SFALL_CONFIG_INI_CONFIG_FOLDER "IniConfigFolder"
#define SFALL_CONFIG_GLOBAL_SCRIPT_PATHS "GlobalScriptPaths"
#define SFALL_CONFIG_AUTO_QUICK_SAVE "AutoQuickSave"
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1
#define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3

220
src/sfall_global_scripts.cc Normal file
View File

@ -0,0 +1,220 @@
#include "sfall_global_scripts.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include "db.h"
#include "input.h"
#include "platform_compat.h"
#include "scripts.h"
#include "sfall_config.h"
namespace fallout {
struct GlobalScript {
Program* program = nullptr;
int procs[SCRIPT_PROC_COUNT] = { 0 };
int repeat = 0;
int count = 0;
int mode = 0;
bool once = true;
};
struct GlobalScriptsState {
std::vector<std::string> paths;
std::vector<GlobalScript> globalScripts;
};
static GlobalScriptsState* state = nullptr;
bool sfall_gl_scr_init()
{
state = new (std::nothrow) GlobalScriptsState();
if (state == nullptr) {
return false;
}
char* paths;
configGetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, &paths);
char* curr = paths;
while (curr != nullptr && *curr != '\0') {
char* end = strchr(curr, ',');
if (end != nullptr) {
*end = '\0';
}
char drive[COMPAT_MAX_DRIVE];
char dir[COMPAT_MAX_DIR];
compat_splitpath(curr, drive, dir, nullptr, nullptr);
char** files;
int filesLength = fileNameListInit(curr, &files, 0, 0);
if (filesLength != 0) {
for (int index = 0; index < filesLength; index++) {
char path[COMPAT_MAX_PATH];
compat_makepath(path, drive, dir, files[index], nullptr);
state->paths.push_back(std::string { path });
}
fileNameListFree(&files, 0);
}
if (end != nullptr) {
*end = ',';
curr = end + 1;
} else {
curr = nullptr;
}
}
std::sort(state->paths.begin(), state->paths.end());
return true;
}
void sfall_gl_scr_reset()
{
if (state != nullptr) {
sfall_gl_scr_remove_all();
}
}
void sfall_gl_scr_exit()
{
if (state != nullptr) {
sfall_gl_scr_remove_all();
delete state;
state = nullptr;
}
}
void sfall_gl_scr_exec_start_proc()
{
for (auto& path : state->paths) {
Program* program = programCreateByPath(path.c_str());
if (program != nullptr) {
GlobalScript scr;
scr.program = program;
for (int action = 0; action < SCRIPT_PROC_COUNT; action++) {
scr.procs[action] = programFindProcedure(program, gScriptProcNames[action]);
}
state->globalScripts.push_back(std::move(scr));
_interpret(program, -1);
}
}
tickersAdd(sfall_gl_scr_process_input);
}
void sfall_gl_scr_remove_all()
{
tickersRemove(sfall_gl_scr_process_input);
for (auto& scr : state->globalScripts) {
programFree(scr.program);
}
state->globalScripts.clear();
}
void sfall_gl_scr_exec_map_update_scripts(int action)
{
for (auto& scr : state->globalScripts) {
if (scr.mode == 0 || scr.mode == 3) {
if (scr.procs[action] != -1) {
_executeProcedure(scr.program, scr.procs[action]);
}
}
}
}
static void sfall_gl_scr_process_simple(int mode1, int mode2)
{
for (auto& scr : state->globalScripts) {
if (scr.repeat != 0 && (scr.mode == mode1 || scr.mode == mode2)) {
scr.count++;
if (scr.count >= scr.repeat) {
_executeProcedure(scr.program, scr.procs[SCRIPT_PROC_START]);
scr.count = 0;
}
}
}
}
void sfall_gl_scr_process_main()
{
sfall_gl_scr_process_simple(0, 3);
}
void sfall_gl_scr_process_input()
{
sfall_gl_scr_process_simple(1, 1);
}
void sfall_gl_scr_process_worldmap()
{
sfall_gl_scr_process_simple(2, 3);
}
static GlobalScript* sfall_gl_scr_map_program_to_scr(Program* program)
{
auto it = std::find_if(state->globalScripts.begin(),
state->globalScripts.end(),
[&program](const GlobalScript& scr) {
return scr.program == program;
});
return it != state->globalScripts.end() ? &(*it) : nullptr;
}
void sfall_gl_scr_set_repeat(Program* program, int frames)
{
GlobalScript* scr = sfall_gl_scr_map_program_to_scr(program);
if (scr != nullptr) {
scr->repeat = frames;
}
}
void sfall_gl_scr_set_type(Program* program, int type)
{
if (type < 0 || type > 3) {
return;
}
GlobalScript* scr = sfall_gl_scr_map_program_to_scr(program);
if (scr != nullptr) {
scr->mode = type;
}
}
bool sfall_gl_scr_is_loaded(Program* program)
{
GlobalScript* scr = sfall_gl_scr_map_program_to_scr(program);
if (scr != nullptr) {
if (scr->once) {
scr->once = false;
return true;
}
return false;
}
// Not a global script.
return true;
}
void sfall_gl_scr_update(int burstSize)
{
for (auto& scr : state->globalScripts) {
_interpret(scr.program, burstSize);
}
}
} // namespace fallout

View File

@ -0,0 +1,24 @@
#ifndef FALLOUT_SFALL_GLOBAL_SCRIPTS_H_
#define FALLOUT_SFALL_GLOBAL_SCRIPTS_H_
#include "interpreter.h"
namespace fallout {
bool sfall_gl_scr_init();
void sfall_gl_scr_reset();
void sfall_gl_scr_exit();
void sfall_gl_scr_exec_start_proc();
void sfall_gl_scr_remove_all();
void sfall_gl_scr_exec_map_update_scripts(int action);
void sfall_gl_scr_process_main();
void sfall_gl_scr_process_input();
void sfall_gl_scr_process_worldmap();
void sfall_gl_scr_set_repeat(Program* program, int frames);
void sfall_gl_scr_set_type(Program* program, int type);
bool sfall_gl_scr_is_loaded(Program* program);
void sfall_gl_scr_update(int burstSize);
} // namespace fallout
#endif /* FALLOUT_SFALL_GLOBAL_SCRIPTS_H_ */

View File

@ -10,72 +10,127 @@ struct SfallGlobalVarsState {
std::unordered_map<uint64_t, int> vars;
};
static bool sfallGlobalVarsStore(uint64_t key, int value);
static bool sfallGlobalVarsFetch(uint64_t key, int& value);
#pragma pack(push)
#pragma pack(8)
static SfallGlobalVarsState* _state;
/// Matches Sfall's `GlobalVar` to maintain binary compatibility.
struct GlobalVarEntry {
uint64_t key;
int32_t value;
int32_t unused;
};
bool sfallGlobalVarsInit()
#pragma pack(pop)
static bool sfall_gl_vars_store(uint64_t key, int value);
static bool sfall_gl_vars_fetch(uint64_t key, int& value);
static SfallGlobalVarsState* sfall_gl_vars_state = nullptr;
bool sfall_gl_vars_init()
{
_state = new (std::nothrow) SfallGlobalVarsState();
if (_state == nullptr) {
sfall_gl_vars_state = new (std::nothrow) SfallGlobalVarsState();
if (sfall_gl_vars_state == nullptr) {
return false;
}
return true;
}
void sfallGlobalVarsReset()
void sfall_gl_vars_reset()
{
_state->vars.clear();
sfall_gl_vars_state->vars.clear();
}
void sfallGlobalVarsExit()
void sfall_gl_vars_exit()
{
if (_state != nullptr) {
delete _state;
_state = nullptr;
if (sfall_gl_vars_state != nullptr) {
delete sfall_gl_vars_state;
sfall_gl_vars_state = nullptr;
}
}
bool sfallGlobalVarsStore(const char* key, int value)
bool sfall_gl_vars_save(File* stream)
{
int count = static_cast<int>(sfall_gl_vars_state->vars.size());
if (fileWrite(&count, sizeof(count), 1, stream) != 1) {
return false;
}
GlobalVarEntry entry = { 0 };
for (auto& pair : sfall_gl_vars_state->vars) {
entry.key = pair.first;
entry.value = pair.second;
if (fileWrite(&entry, sizeof(entry), 1, stream) != 1) {
return false;
}
}
return true;
}
bool sfall_gl_vars_load(File* stream)
{
int count;
if (fileRead(&count, sizeof(count), 1, stream) != 1) {
return false;
}
sfall_gl_vars_state->vars.reserve(count);
GlobalVarEntry entry;
while (count > 0) {
if (fileRead(&entry, sizeof(entry), 1, stream) != 1) {
return false;
}
sfall_gl_vars_state->vars[entry.key] = static_cast<int>(entry.value);
count--;
}
return true;
}
bool sfall_gl_vars_store(const char* key, int value)
{
if (strlen(key) != 8) {
return false;
}
uint64_t numericKey = *(reinterpret_cast<const uint64_t*>(key));
return sfallGlobalVarsStore(numericKey, value);
return sfall_gl_vars_store(numericKey, value);
}
bool sfallGlobalVarsStore(int key, int value)
bool sfall_gl_vars_store(int key, int value)
{
return sfallGlobalVarsStore(static_cast<uint64_t>(key), value);
return sfall_gl_vars_store(static_cast<uint64_t>(key), value);
}
bool sfallGlobalVarsFetch(const char* key, int& value)
bool sfall_gl_vars_fetch(const char* key, int& value)
{
if (strlen(key) != 8) {
return false;
}
uint64_t numericKey = *(reinterpret_cast<const uint64_t*>(key));
return sfallGlobalVarsFetch(numericKey, value);
return sfall_gl_vars_fetch(numericKey, value);
}
bool sfallGlobalVarsFetch(int key, int& value)
bool sfall_gl_vars_fetch(int key, int& value)
{
return sfallGlobalVarsFetch(static_cast<uint64_t>(key), value);
return sfall_gl_vars_fetch(static_cast<uint64_t>(key), value);
}
static bool sfallGlobalVarsStore(uint64_t key, int value)
static bool sfall_gl_vars_store(uint64_t key, int value)
{
auto it = _state->vars.find(key);
if (it == _state->vars.end()) {
_state->vars.emplace(key, value);
auto it = sfall_gl_vars_state->vars.find(key);
if (it == sfall_gl_vars_state->vars.end()) {
sfall_gl_vars_state->vars.emplace(key, value);
} else {
if (value == 0) {
_state->vars.erase(it);
sfall_gl_vars_state->vars.erase(it);
} else {
it->second = value;
}
@ -84,10 +139,10 @@ static bool sfallGlobalVarsStore(uint64_t key, int value)
return true;
}
static bool sfallGlobalVarsFetch(uint64_t key, int& value)
static bool sfall_gl_vars_fetch(uint64_t key, int& value)
{
auto it = _state->vars.find(key);
if (it == _state->vars.end()) {
auto it = sfall_gl_vars_state->vars.find(key);
if (it == sfall_gl_vars_state->vars.end()) {
return false;
}

View File

@ -1,15 +1,19 @@
#ifndef FALLOUT_SFALL_GLOBAL_VARS_H_
#define FALLOUT_SFALL_GLOBAL_VARS_H_
#include "db.h"
namespace fallout {
bool sfallGlobalVarsInit();
void sfallGlobalVarsReset();
void sfallGlobalVarsExit();
bool sfallGlobalVarsStore(const char* key, int value);
bool sfallGlobalVarsStore(int key, int value);
bool sfallGlobalVarsFetch(const char* key, int& value);
bool sfallGlobalVarsFetch(int key, int& value);
bool sfall_gl_vars_init();
void sfall_gl_vars_reset();
void sfall_gl_vars_exit();
bool sfall_gl_vars_save(File* stream);
bool sfall_gl_vars_load(File* stream);
bool sfall_gl_vars_store(const char* key, int value);
bool sfall_gl_vars_store(int key, int value);
bool sfall_gl_vars_fetch(const char* key, int& value);
bool sfall_gl_vars_fetch(int key, int& value);
} // namespace fallout

197
src/sfall_ini.cc Normal file
View File

@ -0,0 +1,197 @@
#include "sfall_ini.h"
#include <algorithm>
#include <cstring>
#include "config.h"
#include "platform_compat.h"
namespace fallout {
/// The max length of `fileName` chunk in the triplet.
static constexpr size_t kFileNameMaxSize = 63;
/// The max length of `section` chunk in the triplet.
static constexpr size_t kSectionMaxSize = 32;
/// Special .ini file names which are accessed without adding base path.
static constexpr const char* kSystemConfigFileNames[] = {
"ddraw.ini",
"f2_res.ini",
};
static char basePath[COMPAT_MAX_PATH];
/// Parses "fileName|section|key" triplet into parts. `fileName` and `section`
/// chunks are copied into appropriate variables. Returns the pointer to `key`,
/// or `nullptr` on any error.
static const char* parse_ini_triplet(const char* triplet, char* fileName, char* section)
{
const char* fileNameSectionSep = strchr(triplet, '|');
if (fileNameSectionSep == nullptr) {
return nullptr;
}
size_t fileNameLength = fileNameSectionSep - triplet;
if (fileNameLength > kFileNameMaxSize) {
return nullptr;
}
const char* sectionKeySep = strchr(fileNameSectionSep + 1, '|');
if (sectionKeySep == nullptr) {
return nullptr;
}
size_t sectionLength = sectionKeySep - fileNameSectionSep - 1;
if (sectionLength > kSectionMaxSize) {
return nullptr;
}
strncpy(fileName, triplet, fileNameLength);
fileName[fileNameLength] = '\0';
strncpy(section, fileNameSectionSep + 1, sectionLength);
section[sectionLength] = '\0';
return sectionKeySep + 1;
}
/// Returns `true` if given `fileName` is a special system .ini file name.
static bool is_system_file_name(const char* fileName)
{
for (auto& systemFileName : kSystemConfigFileNames) {
if (compat_stricmp(systemFileName, fileName) == 0) {
return true;
}
}
return false;
}
void sfall_ini_set_base_path(const char* path)
{
if (path != nullptr) {
strcpy(basePath, path);
size_t length = strlen(basePath);
if (length > 0) {
if (basePath[length - 1] == '\\' || basePath[length - 1] == '/') {
basePath[length - 1] = '\0';
}
}
} else {
basePath[0] = '\0';
}
}
bool sfall_ini_get_int(const char* triplet, int* value)
{
char string[20];
if (!sfall_ini_get_string(triplet, string, sizeof(string))) {
return false;
}
*value = atol(string);
return true;
}
bool sfall_ini_get_string(const char* triplet, char* value, size_t size)
{
char fileName[kFileNameMaxSize];
char section[kSectionMaxSize];
const char* key = parse_ini_triplet(triplet, fileName, section);
if (key == nullptr) {
return false;
}
Config config;
if (!configInit(&config)) {
return false;
}
char path[COMPAT_MAX_PATH];
bool loaded = false;
if (basePath[0] != '\0' && !is_system_file_name(fileName)) {
// Attempt to load requested file in base directory.
snprintf(path, sizeof(path), "%s\\%s", basePath, fileName);
loaded = configRead(&config, path, false);
}
if (!loaded) {
// There was no base path set, requested file is a system config, or
// non-system config file was not found the base path - attempt to load
// from current working directory.
strcpy(path, fileName);
loaded = configRead(&config, path, false);
}
// NOTE: Sfall's `GetIniSetting` returns error code (-1) only when it cannot
// parse triplet. Otherwise the default for string settings is empty string.
value[0] = '\0';
if (loaded) {
char* stringValue;
if (configGetString(&config, section, key, &stringValue)) {
strncpy(value, stringValue, size - 1);
value[size - 1] = '\0';
}
}
configFree(&config);
return true;
}
bool sfall_ini_set_int(const char* triplet, int value)
{
char stringValue[20];
compat_itoa(value, stringValue, 10);
return sfall_ini_set_string(triplet, stringValue);
}
bool sfall_ini_set_string(const char* triplet, const char* value)
{
char fileName[kFileNameMaxSize];
char section[kSectionMaxSize];
const char* key = parse_ini_triplet(triplet, fileName, section);
if (key == nullptr) {
return false;
}
Config config;
if (!configInit(&config)) {
return false;
}
char path[COMPAT_MAX_PATH];
bool loaded = false;
if (basePath[0] != '\0' && !is_system_file_name(fileName)) {
// Attempt to load requested file in base directory.
snprintf(path, sizeof(path), "%s\\%s", basePath, fileName);
loaded = configRead(&config, path, false);
}
if (!loaded) {
// There was no base path set, requested file is a system config, or
// non-system config file was not found the base path - attempt to load
// from current working directory.
strcpy(path, fileName);
loaded = configRead(&config, path, false);
}
configSetString(&config, section, key, value);
bool saved = configWrite(&config, path, false);
configFree(&config);
return saved;
}
} // namespace fallout

25
src/sfall_ini.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef FALLOUT_SFALL_INI_H_
#define FALLOUT_SFALL_INI_H_
#include <cstddef>
namespace fallout {
/// Sets base directory to lookup .ini files.
void sfall_ini_set_base_path(const char* path);
/// Reads integer key identified by "fileName|section|key" triplet into `value`.
bool sfall_ini_get_int(const char* triplet, int* value);
/// Reads string key identified by "fileName|section|key" triplet into `value`.
bool sfall_ini_get_string(const char* triplet, char* value, size_t size);
/// Writes integer key identified by "fileName|section|key" triplet.
bool sfall_ini_set_int(const char* triplet, int value);
/// Writes string key identified by "fileName|section|key" triplet.
bool sfall_ini_set_string(const char* triplet, const char* value);
} // namespace fallout
#endif /* FALLOUT_SFALL_INI_H_ */

297
src/sfall_kb_helpers.cc Normal file
View File

@ -0,0 +1,297 @@
#include "sfall_kb_helpers.h"
#include <SDL.h>
/// Maps DirectInput DIK constants to SDL scancodes.
static constexpr SDL_Scancode kDiks[256] = {
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_ESCAPE, // DIK_ESCAPE
SDL_SCANCODE_1, // DIK_1
SDL_SCANCODE_2, // DIK_2
SDL_SCANCODE_3, // DIK_3
SDL_SCANCODE_4, // DIK_4
SDL_SCANCODE_5, // DIK_5
SDL_SCANCODE_6, // DIK_6
SDL_SCANCODE_7, // DIK_7
SDL_SCANCODE_8, // DIK_8
SDL_SCANCODE_9, // DIK_9
SDL_SCANCODE_0, // DIK_0
SDL_SCANCODE_MINUS, // DIK_MINUS
SDL_SCANCODE_EQUALS, // DIK_EQUALS
SDL_SCANCODE_BACKSPACE, // DIK_BACK
SDL_SCANCODE_TAB, // DIK_TAB
SDL_SCANCODE_Q, // DIK_Q
SDL_SCANCODE_W, // DIK_W
SDL_SCANCODE_E, // DIK_E
SDL_SCANCODE_R, // DIK_R
SDL_SCANCODE_T, // DIK_T
SDL_SCANCODE_Y, // DIK_Y
SDL_SCANCODE_U, // DIK_U
SDL_SCANCODE_I, // DIK_I
SDL_SCANCODE_O, // DIK_O
SDL_SCANCODE_P, // DIK_P
SDL_SCANCODE_LEFTBRACKET, // DIK_LBRACKET
SDL_SCANCODE_RIGHTBRACKET, // DIK_RBRACKET
SDL_SCANCODE_RETURN, // DIK_RETURN
SDL_SCANCODE_LCTRL, // DIK_LCONTROL
SDL_SCANCODE_A, // DIK_A
SDL_SCANCODE_S, // DIK_S
SDL_SCANCODE_D, // DIK_D
SDL_SCANCODE_F, // DIK_F
SDL_SCANCODE_G, // DIK_G
SDL_SCANCODE_H, // DIK_H
SDL_SCANCODE_J, // DIK_J
SDL_SCANCODE_K, // DIK_K
SDL_SCANCODE_L, // DIK_L
SDL_SCANCODE_SEMICOLON, // DIK_SEMICOLON
SDL_SCANCODE_APOSTROPHE, // DIK_APOSTROPHE
SDL_SCANCODE_GRAVE, // DIK_GRAVE
SDL_SCANCODE_LSHIFT, // DIK_LSHIFT
SDL_SCANCODE_BACKSLASH, // DIK_BACKSLASH
SDL_SCANCODE_Z, // DIK_Z
SDL_SCANCODE_X, // DIK_X
SDL_SCANCODE_C, // DIK_C
SDL_SCANCODE_V, // DIK_V
SDL_SCANCODE_B, // DIK_B
SDL_SCANCODE_N, // DIK_N
SDL_SCANCODE_M, // DIK_M
SDL_SCANCODE_COMMA, // DIK_COMMA
SDL_SCANCODE_PERIOD, // DIK_PERIOD
SDL_SCANCODE_SLASH, // DIK_SLASH
SDL_SCANCODE_RSHIFT, // DIK_RSHIFT
SDL_SCANCODE_KP_MULTIPLY, // DIK_MULTIPLY
SDL_SCANCODE_LALT, // DIK_LMENU
SDL_SCANCODE_SPACE, // DIK_SPACE
SDL_SCANCODE_CAPSLOCK, // DIK_CAPITAL
SDL_SCANCODE_F1, // DIK_F1
SDL_SCANCODE_F2, // DIK_F2
SDL_SCANCODE_F3, // DIK_F3
SDL_SCANCODE_F4, // DIK_F4
SDL_SCANCODE_F5, // DIK_F5
SDL_SCANCODE_F6, // DIK_F6
SDL_SCANCODE_F7, // DIK_F7
SDL_SCANCODE_F8, // DIK_F8
SDL_SCANCODE_F9, // DIK_F9
SDL_SCANCODE_F10, // DIK_F10
SDL_SCANCODE_NUMLOCKCLEAR, // DIK_NUMLOCK
SDL_SCANCODE_SCROLLLOCK, // DIK_SCROLL
SDL_SCANCODE_KP_7, // DIK_NUMPAD7
SDL_SCANCODE_KP_8, // DIK_NUMPAD8
SDL_SCANCODE_KP_9, // DIK_NUMPAD9
SDL_SCANCODE_KP_MINUS, // DIK_SUBTRACT
SDL_SCANCODE_KP_4, // DIK_NUMPAD4
SDL_SCANCODE_KP_5, // DIK_NUMPAD5
SDL_SCANCODE_KP_6, // DIK_NUMPAD6
SDL_SCANCODE_KP_PLUS, // DIK_ADD
SDL_SCANCODE_KP_1, // DIK_NUMPAD1
SDL_SCANCODE_KP_2, // DIK_NUMPAD2
SDL_SCANCODE_KP_3, // DIK_NUMPAD3
SDL_SCANCODE_KP_0, // DIK_NUMPAD0
SDL_SCANCODE_KP_PERIOD, // DIK_DECIMAL
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_F11, // DIK_F11
SDL_SCANCODE_F12, // DIK_F12
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_KP_EQUALS, // DIK_NUMPADEQUALS
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN, // DIK_AT
SDL_SCANCODE_UNKNOWN, // DIK_COLON
SDL_SCANCODE_UNKNOWN, // DIK_UNDERLINE
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN, // DIK_STOP
SDL_SCANCODE_UNKNOWN, // DIK_AX
SDL_SCANCODE_UNKNOWN, // DIK_UNLABELED
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_KP_ENTER, // DIK_NUMPADENTER
SDL_SCANCODE_RCTRL, // DIK_RCONTROL
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_KP_COMMA, // DIK_NUMPADCOMMA
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_SLASH, // DIK_DIVIDE
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_SYSREQ, // DIK_SYSRQ
SDL_SCANCODE_RALT, // DIK_RMENU
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_HOME, // DIK_HOME
SDL_SCANCODE_UP, // DIK_UP
SDL_SCANCODE_PAGEUP, // DIK_PRIOR
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_LEFT, // DIK_LEFT
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_RIGHT, // DIK_RIGHT
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_END, // DIK_END
SDL_SCANCODE_DOWN, // DIK_DOWN
SDL_SCANCODE_PAGEDOWN, // DIK_NEXT
SDL_SCANCODE_INSERT, // DIK_INSERT
SDL_SCANCODE_DELETE, // DIK_DELETE
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_LGUI, // DIK_LWIN
SDL_SCANCODE_RGUI, // DIK_RWIN
SDL_SCANCODE_APPLICATION, // DIK_APPS
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
SDL_SCANCODE_UNKNOWN,
};
/// Translates Sfall key code (DIK or VK constant) to SDL scancode.
static SDL_Scancode get_scancode_from_key(int key)
{
return kDiks[key & 0xFF];
}
bool sfall_kb_is_key_pressed(int key)
{
SDL_Scancode scancode = get_scancode_from_key(key);
if (scancode == SDL_SCANCODE_UNKNOWN) {
return false;
}
const Uint8* state = SDL_GetKeyboardState(nullptr);
return state[scancode] != 0;
}
void sfall_kb_press_key(int key)
{
SDL_Scancode scancode = get_scancode_from_key(key);
if (scancode == SDL_SCANCODE_UNKNOWN) {
return;
}
SDL_Event event;
event.key.keysym.scancode = scancode;
event.type = SDL_KEYDOWN;
SDL_PushEvent(&event);
event.type = SDL_KEYUP;
SDL_PushEvent(&event);
}

10
src/sfall_kb_helpers.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef FALLOUT_SFALL_KB_HELPERS_H_
#define FALLOUT_SFALL_KB_HELPERS_H_
/// Returns `true` if given key is pressed.
bool sfall_kb_is_key_pressed(int key);
/// Simulates pressing `key`.
void sfall_kb_press_key(int key);
#endif /* FALLOUT_SFALL_KB_HELPERS_H_ */

View File

@ -1,7 +1,6 @@
#include "sfall_lists.h"
#include <unordered_map>
#include <vector>
#include "object.h"
#include "scripts.h"
@ -66,46 +65,7 @@ int sfallListsCreate(int listType)
int listId = _state->nextListId++;
List& list = _state->lists[listId];
if (listType == LIST_TILES) {
// For unknown reason this list type is not implemented in Sfall.
} else if (listType == LIST_SPATIAL) {
for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {
Script* script = scriptGetFirstSpatialScript(elevation);
while (script != nullptr) {
Object* obj = script->owner;
if (obj == nullptr) {
obj = scriptGetSelf(script->program);
}
list.objects.push_back(obj);
script = scriptGetNextSpatialScript();
}
}
} else {
// CE: Implementation is slightly different. Sfall manually loops thru
// elevations (3) and hexes (40000) and use |objectFindFirstAtLocation|
// (originally |obj_find_first_at_tile|) to obtain next object. This
// functionality is already implemented in |objectFindFirst| and
// |objectFindNext|.
//
// As a small optimization |LIST_ALL| is handled separately since there
// is no need to check object type.
if (listType == LIST_ALL) {
Object* obj = objectFindFirst();
while (obj != nullptr) {
list.objects.push_back(obj);
obj = objectFindNext();
}
} else {
Object* obj = objectFindFirst();
while (obj != nullptr) {
int objectType = PID_TYPE(obj->pid);
if (objectType < kObjectTypeToListTypeSize && kObjectTypeToListType[objectType] == listType) {
list.objects.push_back(obj);
}
obj = objectFindNext();
}
}
}
sfall_lists_fill(listType, list.objects);
return listId;
}
@ -131,4 +91,54 @@ void sfallListsDestroy(int listId)
}
}
void sfall_lists_fill(int type, std::vector<Object*>& objects)
{
if (type == LIST_TILES) {
// For unknown reason this list type is not implemented in Sfall.
return;
}
objects.reserve(100);
if (type == LIST_SPATIAL) {
for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {
Script* script = scriptGetFirstSpatialScript(elevation);
while (script != nullptr) {
Object* obj = script->owner;
if (obj == nullptr) {
obj = scriptGetSelf(script->program);
}
objects.push_back(obj);
script = scriptGetNextSpatialScript();
}
}
} else {
// CE: Implementation is slightly different. Sfall manually loops thru
// elevations (3) and hexes (40000) and use |objectFindFirstAtLocation|
// (originally |obj_find_first_at_tile|) to obtain next object. This
// functionality is already implemented in |objectFindFirst| and
// |objectFindNext|.
//
// As a small optimization |LIST_ALL| is handled separately since there
// is no need to check object type.
if (type == LIST_ALL) {
Object* obj = objectFindFirst();
while (obj != nullptr) {
objects.push_back(obj);
obj = objectFindNext();
}
} else {
Object* obj = objectFindFirst();
while (obj != nullptr) {
int objectType = PID_TYPE(obj->pid);
if (objectType < kObjectTypeToListTypeSize
&& kObjectTypeToListType[objectType] == type) {
objects.push_back(obj);
}
obj = objectFindNext();
}
}
}
}
} // namespace fallout

View File

@ -1,6 +1,8 @@
#ifndef FALLOUT_SFALL_LISTS_H_
#define FALLOUT_SFALL_LISTS_H_
#include <vector>
#include "obj_types.h"
namespace fallout {
@ -22,6 +24,7 @@ void sfallListsExit();
int sfallListsCreate(int listType);
Object* sfallListsGetNext(int listId);
void sfallListsDestroy(int listId);
void sfall_lists_fill(int type, std::vector<Object*>& objects);
} // namespace fallout

291
src/sfall_metarules.cc Normal file
View File

@ -0,0 +1,291 @@
#include "sfall_metarules.h"
#include <string.h>
#include "combat.h"
#include "debug.h"
#include "game.h"
#include "game_dialog.h"
#include "game_mouse.h"
#include "interface.h"
#include "inventory.h"
#include "object.h"
#include "sfall_ini.h"
#include "text_font.h"
#include "tile.h"
#include "window.h"
#include "worldmap.h"
namespace fallout {
typedef void(MetaruleHandler)(Program* program, int args);
// Simplified cousin of `SfallMetarule` from Sfall.
typedef struct MetaruleInfo {
const char* name;
MetaruleHandler* handler;
int minArgs;
int maxArgs;
} MetaruleInfo;
static void mf_car_gas_amount(Program* program, int args);
static void mf_combat_data(Program* program, int args);
static void mf_critter_inven_obj2(Program* program, int args);
static void mf_dialog_obj(Program* program, int args);
static void mf_get_cursor_mode(Program* program, int args);
static void mf_get_flags(Program* program, int args);
static void mf_get_object_data(Program* program, int args);
static void mf_get_text_width(Program* program, int args);
static void mf_intface_redraw(Program* program, int args);
static void mf_loot_obj(Program* program, int args);
static void mf_metarule_exist(Program* program, int args);
static void mf_outlined_object(Program* program, int args);
static void mf_set_cursor_mode(Program* program, int args);
static void mf_set_flags(Program* program, int args);
static void mf_set_ini_setting(Program* program, int args);
static void mf_set_outline(Program* program, int args);
static void mf_show_window(Program* program, int args);
static void mf_tile_refresh_display(Program* program, int args);
constexpr MetaruleInfo kMetarules[] = {
{ "car_gas_amount", mf_car_gas_amount, 0, 0 },
{ "combat_data", mf_combat_data, 0, 0 },
{ "critter_inven_obj2", mf_critter_inven_obj2, 2, 2 },
{ "dialog_obj", mf_dialog_obj, 0, 0 },
{ "get_cursor_mode", mf_get_cursor_mode, 0, 0 },
{ "get_flags", mf_get_flags, 1, 1 },
{ "get_object_data", mf_get_object_data, 2, 2 },
{ "get_text_width", mf_get_text_width, 1, 1 },
{ "intface_redraw", mf_intface_redraw, 0, 1 },
{ "loot_obj", mf_loot_obj, 0, 0 },
{ "metarule_exist", mf_metarule_exist, 1, 1 },
{ "outlined_object", mf_outlined_object, 0, 0 },
{ "set_cursor_mode", mf_set_cursor_mode, 1, 1 },
{ "set_flags", mf_set_flags, 2, 2 },
{ "set_ini_setting", mf_set_ini_setting, 2, 2 },
{ "set_outline", mf_set_outline, 2, 2 },
{ "show_window", mf_show_window, 0, 1 },
{ "tile_refresh_display", mf_tile_refresh_display, 0, 0 },
};
constexpr int kMetarulesMax = sizeof(kMetarules) / sizeof(kMetarules[0]);
void mf_car_gas_amount(Program* program, int args)
{
programStackPushInteger(program, wmCarGasAmount());
}
void mf_combat_data(Program* program, int args)
{
if (isInCombat()) {
programStackPushPointer(program, combat_get_data());
} else {
programStackPushPointer(program, nullptr);
}
}
void mf_critter_inven_obj2(Program* program, int args)
{
int slot = programStackPopInteger(program);
Object* obj = static_cast<Object*>(programStackPopPointer(program));
switch (slot) {
case 0:
programStackPushPointer(program, critterGetArmor(obj));
break;
case 1:
programStackPushPointer(program, critterGetItem2(obj));
break;
case 2:
programStackPushPointer(program, critterGetItem1(obj));
break;
case -2:
programStackPushInteger(program, obj->data.inventory.length);
break;
default:
programFatalError("mf_critter_inven_obj2: invalid type");
}
}
void mf_dialog_obj(Program* program, int args)
{
if (GameMode::isInGameMode(GameMode::kDialog)) {
programStackPushPointer(program, gGameDialogSpeaker);
} else {
programStackPushPointer(program, nullptr);
}
}
void mf_get_cursor_mode(Program* program, int args)
{
programStackPushInteger(program, gameMouseGetMode());
}
void mf_get_flags(Program* program, int args)
{
Object* object = static_cast<Object*>(programStackPopPointer(program));
programStackPushInteger(program, object->flags);
}
void mf_get_object_data(Program* program, int args)
{
size_t offset = static_cast<size_t>(programStackPopInteger(program));
void* ptr = programStackPopPointer(program);
if (offset % 4 != 0) {
programFatalError("mf_get_object_data: bad offset %d", offset);
}
int value = *reinterpret_cast<int*>(reinterpret_cast<unsigned char*>(ptr) + offset);
programStackPushInteger(program, value);
}
void mf_get_text_width(Program* program, int args)
{
const char* string = programStackPopString(program);
programStackPushInteger(program, fontGetStringWidth(string));
}
void mf_intface_redraw(Program* program, int args)
{
if (args == 0) {
interfaceBarRefresh();
} else {
// TODO: Incomplete.
programFatalError("mf_intface_redraw: not implemented");
}
programStackPushInteger(program, -1);
}
void mf_loot_obj(Program* program, int args)
{
if (GameMode::isInGameMode(GameMode::kInventory)) {
programStackPushPointer(program, inven_get_current_target_obj());
} else {
programStackPushPointer(program, nullptr);
}
}
void mf_metarule_exist(Program* program, int args)
{
const char* metarule = programStackPopString(program);
for (int index = 0; index < kMetarulesMax; index++) {
if (strcmp(kMetarules[index].name, metarule) == 0) {
programStackPushInteger(program, 1);
return;
}
}
programStackPushInteger(program, 0);
}
void mf_outlined_object(Program* program, int args)
{
programStackPushPointer(program, gmouse_get_outlined_object());
}
void mf_set_cursor_mode(Program* program, int args)
{
int mode = programStackPopInteger(program);
gameMouseSetMode(mode);
programStackPushInteger(program, -1);
}
void mf_set_flags(Program* program, int args)
{
int flags = programStackPopInteger(program);
Object* object = static_cast<Object*>(programStackPopPointer(program));
object->flags = flags;
programStackPushInteger(program, -1);
}
void mf_set_ini_setting(Program* program, int args)
{
ProgramValue value = programStackPopValue(program);
const char* triplet = programStackPopString(program);
if (value.isString()) {
const char* stringValue = programGetString(program, value.opcode, value.integerValue);
if (!sfall_ini_set_string(triplet, stringValue)) {
debugPrint("set_ini_setting: unable to write '%s' to '%s'",
stringValue,
triplet);
}
} else {
int integerValue = value.asInt();
if (!sfall_ini_set_int(triplet, integerValue)) {
debugPrint("set_ini_setting: unable to write '%d' to '%s'",
integerValue,
triplet);
}
}
programStackPushInteger(program, -1);
}
void mf_set_outline(Program* program, int args)
{
int outline = programStackPopInteger(program);
Object* object = static_cast<Object*>(programStackPopPointer(program));
object->outline = outline;
programStackPushInteger(program, -1);
}
void mf_show_window(Program* program, int args)
{
if (args == 0) {
_windowShow();
} else if (args == 1) {
const char* windowName = programStackPopString(program);
if (!_windowShowNamed(windowName)) {
debugPrint("show_window: window '%s' is not found", windowName);
}
}
programStackPushInteger(program, -1);
}
void mf_tile_refresh_display(Program* program, int args)
{
tileWindowRefresh();
programStackPushInteger(program, -1);
}
void sfall_metarule(Program* program, int args)
{
static ProgramValue values[6];
for (int index = 0; index < args; index++) {
values[index] = programStackPopValue(program);
}
const char* metarule = programStackPopString(program);
for (int index = 0; index < args; index++) {
programStackPushValue(program, values[index]);
}
int metaruleIndex = -1;
for (int index = 0; index < kMetarulesMax; index++) {
if (strcmp(kMetarules[index].name, metarule) == 0) {
metaruleIndex = index;
break;
}
}
if (metaruleIndex == -1) {
programFatalError("op_sfall_func: '%s' is not implemented", metarule);
}
if (args < kMetarules[metaruleIndex].minArgs || args > kMetarules[metaruleIndex].maxArgs) {
programFatalError("op_sfall_func: '%s': invalid number of args", metarule);
}
kMetarules[metaruleIndex].handler(program, args);
}
} // namespace fallout

12
src/sfall_metarules.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef FALLOUT_SFALL_METARULES_H_
#define FALLOUT_SFALL_METARULES_H_
#include "interpreter.h"
namespace fallout {
void sfall_metarule(Program* program, int args);
} // namespace fallout
#endif /* FALLOUT_SFALL_METARULES_H_ */

View File

@ -1,21 +1,32 @@
#include "sfall_opcodes.h"
#include <string.h>
#include "animation.h"
#include "art.h"
#include "color.h"
#include "combat.h"
#include "dbox.h"
#include "debug.h"
#include "game.h"
#include "input.h"
#include "interface.h"
#include "interpreter.h"
#include "item.h"
#include "memory.h"
#include "message.h"
#include "mouse.h"
#include "object.h"
#include "party_member.h"
#include "proto.h"
#include "scripts.h"
#include "sfall_arrays.h"
#include "sfall_global_scripts.h"
#include "sfall_global_vars.h"
#include "sfall_ini.h"
#include "sfall_kb_helpers.h"
#include "sfall_lists.h"
#include "sfall_metarules.h"
#include "stat.h"
#include "svga.h"
#include "tile.h"
@ -23,6 +34,18 @@
namespace fallout {
typedef enum ExplosionMetarule {
EXPL_FORCE_EXPLOSION_PATTERN = 1,
EXPL_FORCE_EXPLOSION_ART = 2,
EXPL_FORCE_EXPLOSION_RADIUS = 3,
EXPL_FORCE_EXPLOSION_DMGTYPE = 4,
EXPL_STATIC_EXPLOSION_RADIUS = 5,
EXPL_GET_EXPLOSION_DAMAGE = 6,
EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE = 7,
EXPL_SET_PLASTIC_EXPLOSION_DAMAGE = 8,
EXPL_SET_EXPLOSION_MAX_TARGET = 9,
} ExplosionMetarule;
static constexpr int kVersionMajor = 4;
static constexpr int kVersionMinor = 3;
static constexpr int kVersionPatch = 4;
@ -85,6 +108,13 @@ static void opGetPcBonusStat(Program* program)
programStackPushInteger(program, value);
}
// tap_key
static void op_tap_key(Program* program)
{
int key = programStackPopInteger(program);
sfall_kb_press_key(key);
}
// get_year
static void op_get_year(Program* program)
{
@ -93,12 +123,41 @@ static void op_get_year(Program* program)
programStackPushInteger(program, year);
}
// game_loaded
static void op_game_loaded(Program* program)
{
bool loaded = sfall_gl_scr_is_loaded(program);
programStackPushInteger(program, loaded ? 1 : 0);
}
// set_global_script_repeat
static void op_set_global_script_repeat(Program* program)
{
int frames = programStackPopInteger(program);
sfall_gl_scr_set_repeat(program, frames);
}
// key_pressed
static void op_key_pressed(Program* program)
{
int key = programStackPopInteger(program);
bool pressed = sfall_kb_is_key_pressed(key);
programStackPushInteger(program, pressed ? 1 : 0);
}
// in_world_map
static void op_in_world_map(Program* program)
{
programStackPushInteger(program, GameMode::isInGameMode(GameMode::kWorldmap) ? 1 : 0);
}
// force_encounter
static void op_force_encounter(Program* program)
{
int map = programStackPopInteger(program);
wmForceEncounter(map, 0);
}
// set_world_map_pos
static void op_set_world_map_pos(Program* program)
{
@ -113,6 +172,13 @@ static void opGetCurrentHand(Program* program)
programStackPushInteger(program, interfaceGetCurrentHand());
}
// set_global_script_type
static void op_set_global_script_type(Program* program)
{
int type = programStackPopInteger(program);
sfall_gl_scr_set_type(program, type);
}
// set_sfall_global
static void opSetGlobalVar(Program* program)
{
@ -121,9 +187,9 @@ static void opSetGlobalVar(Program* program)
if ((variable.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {
const char* key = programGetString(program, variable.opcode, variable.integerValue);
sfallGlobalVarsStore(key, value.integerValue);
sfall_gl_vars_store(key, value.integerValue);
} else if (variable.opcode == VALUE_TYPE_INT) {
sfallGlobalVarsStore(variable.integerValue, value.integerValue);
sfall_gl_vars_store(variable.integerValue, value.integerValue);
}
}
@ -135,14 +201,27 @@ static void opGetGlobalInt(Program* program)
int value = 0;
if ((variable.opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {
const char* key = programGetString(program, variable.opcode, variable.integerValue);
sfallGlobalVarsFetch(key, value);
sfall_gl_vars_fetch(key, value);
} else if (variable.opcode == VALUE_TYPE_INT) {
sfallGlobalVarsFetch(variable.integerValue, value);
sfall_gl_vars_fetch(variable.integerValue, value);
}
programStackPushInteger(program, value);
}
// get_ini_setting
static void op_get_ini_setting(Program* program)
{
const char* string = programStackPopString(program);
int value;
if (sfall_ini_get_int(string, &value)) {
programStackPushInteger(program, value);
} else {
programStackPushInteger(program, -1);
}
}
// get_game_mode
static void opGetGameMode(Program* program)
{
@ -177,6 +256,19 @@ static void op_set_bodypart_hit_modifier(Program* program)
combat_set_hit_location_penalty(hit_location, penalty);
}
// get_ini_string
static void op_get_ini_string(Program* program)
{
const char* string = programStackPopString(program);
char value[256];
if (sfall_ini_get_string(string, value, sizeof(value))) {
programStackPushString(program, value);
} else {
programStackPushInteger(program, -1);
}
}
// sqrt
static void op_sqrt(Program* program)
{
@ -196,6 +288,13 @@ static void op_abs(Program* program)
}
}
// get_script
static void op_get_script(Program* program)
{
Object* obj = static_cast<Object*>(programStackPopPointer(program));
programStackPushInteger(program, obj->field_80 + 1);
}
// get_proto_data
static void op_get_proto_data(Program* program)
{
@ -246,6 +345,19 @@ static void op_set_proto_data(Program* program)
*reinterpret_cast<int*>(reinterpret_cast<unsigned char*>(proto) + offset) = value;
}
// set_self
static void op_set_self(Program* program)
{
Object* obj = static_cast<Object*>(programStackPopPointer(program));
int sid = scriptGetSid(program);
Script* scr;
if (scriptGetScript(sid, &scr) == 0) {
scr->overriddenSelf = obj;
}
}
// list_begin
static void opListBegin(Program* program)
{
@ -417,6 +529,47 @@ static void opGetScreenHeight(Program* program)
programStackPushInteger(program, screenGetHeight());
}
// create_message_window
static void op_create_message_window(Program* program)
{
static bool showing = false;
if (showing) {
return;
}
const char* string = programStackPopString(program);
if (string == nullptr || string[0] == '\0') {
return;
}
char* copy = internal_strdup(string);
const char* body[4];
int count = 0;
char* pch = strchr(copy, '\n');
while (pch != nullptr && count < 4) {
*pch = '\0';
body[count++] = pch + 1;
pch = strchr(pch + 1, '\n');
}
showing = true;
showDialogBox(copy,
body,
count,
192,
116,
_colorTable[32328],
nullptr,
_colorTable[32328],
DIALOG_BOX_LARGE);
showing = false;
internal_free(copy);
}
// get_attack_type
static void op_get_attack_type(Program* program)
{
@ -428,6 +581,22 @@ static void op_get_attack_type(Program* program)
}
}
// force_encounter_with_flags
static void op_force_encounter_with_flags(Program* program)
{
unsigned int flags = programStackPopInteger(program);
int map = programStackPopInteger(program);
wmForceEncounter(map, flags);
}
// list_as_array
static void op_list_as_array(Program* program)
{
int type = programStackPopInteger(program);
int arrayId = ListAsArray(type);
programStackPushInteger(program, arrayId);
}
// atoi
static void opParseInt(Program* program)
{
@ -453,6 +622,53 @@ static void op_tile_under_cursor(Program* program)
programStackPushInteger(program, tile);
}
// substr
static void opSubstr(Program* program)
{
auto length = programStackPopInteger(program);
auto startPos = programStackPopInteger(program);
const char* str = programStackPopString(program);
char buf[5120] = { 0 };
int len = strlen(str);
if (startPos < 0) {
startPos += len; // start from end
if (startPos < 0) {
startPos = 0;
}
}
if (length < 0) {
length += len - startPos; // cutoff at end
if (length == 0) {
programStackPushString(program, buf);
return;
}
length = abs(length); // length can't be negative
}
// check position
if (startPos >= len) {
// start position is out of string length, return empty string
programStackPushString(program, buf);
return;
}
if (length == 0 || length + startPos > len) {
length = len - startPos; // set the correct length, the length of characters goes beyond the end of the string
}
if (length > sizeof(buf) - 1) {
length = sizeof(buf) - 1;
}
memcpy(buf, &str[startPos], length);
buf[length] = '\0';
programStackPushString(program, buf);
}
// strlen
static void opGetStringLength(Program* program)
{
@ -460,6 +676,64 @@ static void opGetStringLength(Program* program)
programStackPushInteger(program, static_cast<int>(strlen(string)));
}
// metarule2_explosions
static void op_explosions_metarule(Program* program)
{
int param2 = programStackPopInteger(program);
int param1 = programStackPopInteger(program);
int metarule = programStackPopInteger(program);
switch (metarule) {
case EXPL_FORCE_EXPLOSION_PATTERN:
if (param1 != 0) {
explosionSetPattern(2, 4);
} else {
explosionSetPattern(0, 6);
}
programStackPushInteger(program, 0);
break;
case EXPL_FORCE_EXPLOSION_ART:
explosionSetFrm(param1);
programStackPushInteger(program, 0);
break;
case EXPL_FORCE_EXPLOSION_RADIUS:
explosionSetRadius(param1);
programStackPushInteger(program, 0);
break;
case EXPL_FORCE_EXPLOSION_DMGTYPE:
explosionSetDamageType(param1);
programStackPushInteger(program, 0);
break;
case EXPL_STATIC_EXPLOSION_RADIUS:
weaponSetGrenadeExplosionRadius(param1);
weaponSetRocketExplosionRadius(param2);
programStackPushInteger(program, 0);
break;
case EXPL_GET_EXPLOSION_DAMAGE:
if (1) {
int minDamage;
int maxDamage;
explosiveGetDamage(param1, &minDamage, &maxDamage);
ArrayId arrayId = CreateTempArray(2, 0);
SetArray(arrayId, ProgramValue { 0 }, ProgramValue { minDamage }, false, program);
SetArray(arrayId, ProgramValue { 1 }, ProgramValue { maxDamage }, false, program);
programStackPushInteger(program, arrayId);
}
break;
case EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE:
explosiveSetDamage(PROTO_ID_DYNAMITE_I, param1, param2);
break;
case EXPL_SET_PLASTIC_EXPLOSION_DAMAGE:
explosiveSetDamage(PROTO_ID_PLASTIC_EXPLOSIVES_I, param1, param2);
break;
case EXPL_SET_EXPLOSION_MAX_TARGET:
explosionSetMaxTargets(param1);
break;
}
}
// pow (^)
static void op_power(Program* program)
{
@ -485,6 +759,146 @@ static void opGetMessage(Program* program)
programStackPushString(program, text);
}
// array_key
static void opGetArrayKey(Program* program)
{
auto index = programStackPopInteger(program);
auto arrayId = programStackPopInteger(program);
auto value = GetArrayKey(arrayId, index, program);
programStackPushValue(program, value);
}
// create_array
static void opCreateArray(Program* program)
{
auto flags = programStackPopInteger(program);
auto len = programStackPopInteger(program);
auto arrayId = CreateArray(len, flags);
programStackPushInteger(program, arrayId);
}
// temp_array
static void opTempArray(Program* program)
{
auto flags = programStackPopInteger(program);
auto len = programStackPopInteger(program);
auto arrayId = CreateTempArray(len, flags);
programStackPushInteger(program, arrayId);
}
// fix_array
static void opFixArray(Program* program)
{
auto arrayId = programStackPopInteger(program);
FixArray(arrayId);
}
// string_split
static void opStringSplit(Program* program)
{
auto split = programStackPopString(program);
auto str = programStackPopString(program);
auto arrayId = StringSplit(str, split);
programStackPushInteger(program, arrayId);
}
// set_array
static void opSetArray(Program* program)
{
auto value = programStackPopValue(program);
auto key = programStackPopValue(program);
auto arrayId = programStackPopInteger(program);
SetArray(arrayId, key, value, true, program);
}
// arrayexpr
static void opStackArray(Program* program)
{
auto value = programStackPopValue(program);
auto key = programStackPopValue(program);
auto returnValue = StackArray(key, value, program);
programStackPushInteger(program, returnValue);
}
// scan_array
static void opScanArray(Program* program)
{
auto value = programStackPopValue(program);
auto arrayId = programStackPopInteger(program);
auto returnValue = ScanArray(arrayId, value, program);
programStackPushValue(program, returnValue);
}
// get_array
static void opGetArray(Program* program)
{
auto key = programStackPopValue(program);
auto arrayId = programStackPopValue(program);
if (arrayId.isInt()) {
auto value = GetArray(arrayId.integerValue, key, program);
programStackPushValue(program, value);
} else if (arrayId.isString() && key.isInt()) {
auto pos = key.asInt();
auto str = programGetString(program, arrayId.opcode, arrayId.integerValue);
char buf[2] = { 0 };
if (pos < strlen(str)) {
buf[0] = str[pos];
programStackPushString(program, buf);
} else {
programStackPushString(program, buf);
}
}
}
// free_array
static void opFreeArray(Program* program)
{
auto arrayId = programStackPopInteger(program);
FreeArray(arrayId);
}
// len_array
static void opLenArray(Program* program)
{
auto arrayId = programStackPopInteger(program);
programStackPushInteger(program, LenArray(arrayId));
}
// resize_array
static void opResizeArray(Program* program)
{
auto newLen = programStackPopInteger(program);
auto arrayId = programStackPopInteger(program);
ResizeArray(arrayId, newLen);
}
// party_member_list
static void opPartyMemberList(Program* program)
{
auto includeHidden = programStackPopInteger(program);
auto objects = get_all_party_members_objects(includeHidden);
auto arrayId = CreateTempArray(objects.size(), SFALL_ARRAYFLAG_RESERVED);
for (int i = 0; i < LenArray(arrayId); i++) {
SetArray(arrayId, ProgramValue { i }, ProgramValue { objects[i] }, false, program);
}
programStackPushInteger(program, arrayId);
}
// type_of
static void opTypeOf(Program* program)
{
auto value = programStackPopValue(program);
if (value.isInt()) {
programStackPushInteger(program, 1);
} else if (value.isFloat()) {
programStackPushInteger(program, 2);
} else {
programStackPushInteger(program, 3);
};
}
// round
static void opRound(Program* program)
{
@ -559,6 +973,48 @@ static void opArtExists(Program* program)
programStackPushInteger(program, artExists(fid));
}
// sfall_func0
static void op_sfall_func0(Program* program)
{
sfall_metarule(program, 0);
}
// sfall_func1
static void op_sfall_func1(Program* program)
{
sfall_metarule(program, 1);
}
// sfall_func2
static void op_sfall_func2(Program* program)
{
sfall_metarule(program, 2);
}
// sfall_func3
static void op_sfall_func3(Program* program)
{
sfall_metarule(program, 3);
}
// sfall_func4
static void op_sfall_func4(Program* program)
{
sfall_metarule(program, 4);
}
// sfall_func5
static void op_sfall_func5(Program* program)
{
sfall_metarule(program, 5);
}
// sfall_func6
static void op_sfall_func6(Program* program)
{
sfall_metarule(program, 6);
}
// div (/)
static void op_div(Program* program)
{
@ -588,21 +1044,31 @@ void sfallOpcodesInit()
interpreterRegisterOpcode(0x815B, opSetPcBonusStat);
interpreterRegisterOpcode(0x815C, op_get_pc_base_stat);
interpreterRegisterOpcode(0x815D, opGetPcBonusStat);
interpreterRegisterOpcode(0x8162, op_tap_key);
interpreterRegisterOpcode(0x8163, op_get_year);
interpreterRegisterOpcode(0x8164, op_game_loaded);
interpreterRegisterOpcode(0x816A, op_set_global_script_repeat);
interpreterRegisterOpcode(0x816C, op_key_pressed);
interpreterRegisterOpcode(0x8170, op_in_world_map);
interpreterRegisterOpcode(0x8171, op_force_encounter);
interpreterRegisterOpcode(0x8172, op_set_world_map_pos);
interpreterRegisterOpcode(0x8193, opGetCurrentHand);
interpreterRegisterOpcode(0x819B, op_set_global_script_type);
interpreterRegisterOpcode(0x819D, opSetGlobalVar);
interpreterRegisterOpcode(0x819E, opGetGlobalInt);
interpreterRegisterOpcode(0x81AC, op_get_ini_setting);
interpreterRegisterOpcode(0x81AF, opGetGameMode);
interpreterRegisterOpcode(0x81B3, op_get_uptime);
interpreterRegisterOpcode(0x81B6, op_set_car_current_town);
interpreterRegisterOpcode(0x81DF, op_get_bodypart_hit_modifier);
interpreterRegisterOpcode(0x81E0, op_set_bodypart_hit_modifier);
interpreterRegisterOpcode(0x81EB, op_get_ini_string);
interpreterRegisterOpcode(0x81EC, op_sqrt);
interpreterRegisterOpcode(0x81ED, op_abs);
interpreterRegisterOpcode(0x81F5, op_get_script);
interpreterRegisterOpcode(0x8204, op_get_proto_data);
interpreterRegisterOpcode(0x8205, op_set_proto_data);
interpreterRegisterOpcode(0x8206, op_set_self);
interpreterRegisterOpcode(0x820D, opListBegin);
interpreterRegisterOpcode(0x820E, opListNext);
interpreterRegisterOpcode(0x820F, opListEnd);
@ -618,17 +1084,43 @@ void sfallOpcodesInit()
interpreterRegisterOpcode(0x821E, op_get_mouse_buttons);
interpreterRegisterOpcode(0x8220, opGetScreenWidth);
interpreterRegisterOpcode(0x8221, opGetScreenHeight);
interpreterRegisterOpcode(0x8224, op_create_message_window);
interpreterRegisterOpcode(0x8228, op_get_attack_type);
interpreterRegisterOpcode(0x8229, op_force_encounter_with_flags);
interpreterRegisterOpcode(0x822D, opCreateArray);
interpreterRegisterOpcode(0x822E, opSetArray);
interpreterRegisterOpcode(0x822F, opGetArray);
interpreterRegisterOpcode(0x8230, opFreeArray);
interpreterRegisterOpcode(0x8231, opLenArray);
interpreterRegisterOpcode(0x8232, opResizeArray);
interpreterRegisterOpcode(0x8233, opTempArray);
interpreterRegisterOpcode(0x8234, opFixArray);
interpreterRegisterOpcode(0x8235, opStringSplit);
interpreterRegisterOpcode(0x8236, op_list_as_array);
interpreterRegisterOpcode(0x8237, opParseInt);
interpreterRegisterOpcode(0x8238, op_atof);
interpreterRegisterOpcode(0x8239, opScanArray);
interpreterRegisterOpcode(0x824B, op_tile_under_cursor);
interpreterRegisterOpcode(0x824E, opSubstr);
interpreterRegisterOpcode(0x824F, opGetStringLength);
interpreterRegisterOpcode(0x8253, opTypeOf);
interpreterRegisterOpcode(0x8256, opGetArrayKey);
interpreterRegisterOpcode(0x8257, opStackArray);
interpreterRegisterOpcode(0x8261, op_explosions_metarule);
interpreterRegisterOpcode(0x8263, op_power);
interpreterRegisterOpcode(0x826B, opGetMessage);
interpreterRegisterOpcode(0x8267, opRound);
interpreterRegisterOpcode(0x826B, opGetMessage);
interpreterRegisterOpcode(0x826E, op_make_straight_path);
interpreterRegisterOpcode(0x826F, op_obj_blocking_at);
interpreterRegisterOpcode(0x8271, opPartyMemberList);
interpreterRegisterOpcode(0x8274, opArtExists);
interpreterRegisterOpcode(0x8276, op_sfall_func0);
interpreterRegisterOpcode(0x8277, op_sfall_func1);
interpreterRegisterOpcode(0x8278, op_sfall_func2);
interpreterRegisterOpcode(0x8279, op_sfall_func3);
interpreterRegisterOpcode(0x827A, op_sfall_func4);
interpreterRegisterOpcode(0x827B, op_sfall_func5);
interpreterRegisterOpcode(0x827C, op_sfall_func6);
interpreterRegisterOpcode(0x827F, op_div);
}

View File

@ -1151,7 +1151,7 @@ static int skillGetFreeUsageSlot(int skill)
}
}
int time = gameTimeGetTime();
unsigned int time = gameTimeGetTime();
int hoursSinceLastUsage = (time - _timesSkillUsed[skill][0]) / GAME_TIME_TICKS_PER_HOUR;
if (hoursSinceLastUsage <= 24) {
return -1;

View File

@ -102,6 +102,7 @@ void _zero_vid_mem()
int _GNW95_init_mode_ex(int width, int height, int bpp)
{
bool fullscreen = true;
int scale = 1;
Config resolutionConfig;
if (configInit(&resolutionConfig)) {
@ -121,6 +122,18 @@ int _GNW95_init_mode_ex(int width, int height, int bpp)
fullscreen = !windowed;
}
int scaleValue;
if (configGetInt(&resolutionConfig, "MAIN", "SCALE_2X", &scaleValue)) {
scale = scaleValue + 1; // 0 = 1x, 1 = 2x
// Only allow scaling if resulting game resolution is >= 640x480
if ((width / scale) < 640 || (height / scale) < 480) {
scale = 1;
} else {
width /= scale;
height /= scale;
}
}
configGetBool(&resolutionConfig, "IFACE", "IFACE_BAR_MODE", &gInterfaceBarMode);
configGetInt(&resolutionConfig, "IFACE", "IFACE_BAR_WIDTH", &gInterfaceBarWidth);
configGetInt(&resolutionConfig, "IFACE", "IFACE_BAR_SIDE_ART", &gInterfaceSidePanelsImageId);
@ -129,7 +142,7 @@ int _GNW95_init_mode_ex(int width, int height, int bpp)
configFree(&resolutionConfig);
}
if (_GNW95_init_window(width, height, fullscreen) == -1) {
if (_GNW95_init_window(width, height, fullscreen, scale) == -1) {
return -1;
}
@ -157,7 +170,7 @@ int _init_vesa_mode(int width, int height)
}
// 0x4CAEDC
int _GNW95_init_window(int width, int height, bool fullscreen)
int _GNW95_init_window(int width, int height, bool fullscreen, int scale)
{
if (gSdlWindow == NULL) {
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
@ -172,7 +185,7 @@ int _GNW95_init_window(int width, int height, bool fullscreen)
windowFlags |= SDL_WINDOW_FULLSCREEN;
}
gSdlWindow = SDL_CreateWindow(gProgramWindowTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, windowFlags);
gSdlWindow = SDL_CreateWindow(gProgramWindowTitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width * scale, height * scale, windowFlags);
if (gSdlWindow == NULL) {
return -1;
}

View File

@ -31,7 +31,7 @@ void _get_start_mode_();
void _zero_vid_mem();
int _GNW95_init_mode_ex(int width, int height, int bpp);
int _init_vesa_mode(int width, int height);
int _GNW95_init_window(int width, int height, bool fullscreen);
int _GNW95_init_window(int width, int height, bool fullscreen, int scale);
int directDrawInit(int width, int height, int bpp);
void directDrawFree();
void directDrawSetPaletteInRange(unsigned char* a1, int a2, int a3);

View File

@ -654,6 +654,17 @@ static void tileRefreshGame(Rect* rect, int elevation)
gTileWindowRefreshProc(&rectToUpdate);
}
// 0x4B1634
void tile_toggle_roof(bool refresh)
{
gTileRoofIsVisible = !gTileRoofIsVisible;
if (refresh) {
// NOTE: Uninline.
tileWindowRefresh();
}
}
// 0x4B166C
int tileRoofIsVisible()
{

View File

@ -26,6 +26,7 @@ void tileEnable();
void tileWindowRefreshRect(Rect* rect, int elevation);
void tileWindowRefresh();
int tileSetCenter(int tile, int flags);
void tile_toggle_roof(bool refresh);
int tileRoofIsVisible();
int tileToScreenXY(int tile, int* x, int* y, int elevation);
int tileFromScreenXY(int x, int y, int elevation, bool ignoreBounds = false);

View File

@ -2,6 +2,7 @@
#include <stdlib.h>
#include "delay.h"
#include "input.h"
#include "kb.h"
#include "memory.h"
@ -228,8 +229,7 @@ int vcrUpdate()
* (vcrEntry->time - stru_6AD940.time)
/ (vcrEntry->counter - stru_6AD940.counter);
while (getTicksSince(_vcr_start_time) < delay) {
}
delay_ms(delay - (getTicks() - _vcr_start_time));
}
}
@ -438,4 +438,4 @@ bool vcrReadEntry(VcrEntry* vcrEntry, File* stream)
return false;
}
} // fallout
} // namespace fallout

View File

@ -70,6 +70,8 @@ typedef struct ManagedWindow {
typedef int (*INITVIDEOFN)();
static void redrawButton(ManagedButton* managedButton);
// 0x51DCAC
static int _holdTime = 250;
@ -663,6 +665,38 @@ void _setButtonGFX(int width, int height, unsigned char* normal, unsigned char*
}
}
// 0x4B75F4
static void redrawButton(ManagedButton* managedButton)
{
_win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, false);
}
// 0x4B7610
bool _windowHide()
{
ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]);
if (managedWindow->window == -1) {
return false;
}
windowHide(managedWindow->window);
return true;
}
// 0x4B7648
bool _windowShow()
{
ManagedWindow* managedWindow = &(gManagedWindows[gCurrentManagedWindowIndex]);
if (managedWindow->window == -1) {
return false;
}
windowShow(managedWindow->window);
return true;
}
// 0x4B7734
int _windowWidth()
{
@ -1714,7 +1748,8 @@ bool _windowAddButtonGfx(const char* buttonName, char* pressedFileName, char* no
buttonSetMask(managedButton->btn, managedButton->normal);
}
_win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0);
// NOTE: Uninline.
redrawButton(managedButton);
return true;
}
@ -1952,7 +1987,8 @@ bool _windowAddButtonTextWithOffsets(const char* buttonName, const char* text, i
buttonSetMask(managedButton->btn, managedButton->normal);
}
_win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0);
// NOTE: Uninline.
redrawButton(managedButton);
return true;
}
@ -2646,4 +2682,19 @@ void _fillBuf3x3(unsigned char* src, int srcWidth, int srcHeight, unsigned char*
destWidth);
}
bool _windowShowNamed(const char* windowName)
{
for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) {
ManagedWindow* managedWindow = &(gManagedWindows[index]);
if (managedWindow->window != -1) {
if (compat_stricmp(managedWindow->name, windowName) == 0) {
windowShow(managedWindow->window);
return true;
}
}
}
return false;
}
} // namespace fallout

View File

@ -63,6 +63,8 @@ void _doRightButtonPress(int btn, int keyCode);
void sub_4B704C(int btn, int mouseEvent);
void _doRightButtonRelease(int btn, int keyCode);
void _setButtonGFX(int width, int height, unsigned char* normal, unsigned char* pressed, unsigned char* a5);
bool _windowHide();
bool _windowShow();
int _windowWidth();
int _windowHeight();
bool _windowDraw();
@ -127,6 +129,8 @@ void _drawScaledBuf(unsigned char* dest, int destWidth, int destHeight, unsigned
void _alphaBltBuf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* alphaWindowBuffer, unsigned char* alphaBuffer, unsigned char* dest, int destPitch);
void _fillBuf3x3(unsigned char* src, int srcWidth, int srcHeight, unsigned char* dest, int destWidth, int destHeight);
bool _windowShowNamed(const char* name);
} // namespace fallout
#endif /* WINDOW_H */

Some files were not shown because too many files have changed in this diff Show More