mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
139fc3c440 | ||
|
|
90ca28a13c | ||
|
|
fbae40e9a8 | ||
|
|
4070d29fcb | ||
|
|
dcba8882bf | ||
|
|
62c2621fef |
@@ -10,6 +10,7 @@
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.DS_Store
|
||||
.local-tools/
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
## Further Reading
|
||||
- Refer to the project README for overall architecture.
|
||||
|
||||
## Working With Code
|
||||
- Only search for code and symbols inside the `src/` directory.
|
||||
- NEVER search entire code base for certain file or symbol when it's location isn't known, ask first.
|
||||
- NEVER read entire files unless absolutely necessary for task. Use MCP search instead.
|
||||
- Prefer CLion MCP for searching code and basic refactoring over raw text tools such as grep.
|
||||
- Test changes by compiling (separate cc file for small local changes, or via CLion build_project MCP for big changes).
|
||||
- If game run test is request by prompt, start the game from C:\Games\Fallout2\@CE folder. It always contains last built executable, config file and game assets for full testing.
|
||||
|
||||
## Language Standard
|
||||
- The project targets C++17 (`CMAKE_CXX_STANDARD 17` in `CMakeLists.txt`).
|
||||
- Prefer C++17 library APIs and semantics when they simplify code. For example, `std::string::data()` is mutable in C++17.
|
||||
- static global variable need no prefix. exported globals get g*.
|
||||
- generally prefer camelCase
|
||||
|
||||
## Project Structure
|
||||
- `src/`: All core game logic and engine implementation.
|
||||
- `third_party/`: Vendored dependencies (zlib, SDL2, etc.).
|
||||
- `os/`: Platform-specific assets and configuration (icons, plists, etc.).
|
||||
- `cmake/`: Custom CMake scripts.
|
||||
- `files/`: Default configuration files and data assets.
|
||||
- `sfall_testing/`: SSL scripts for testing Sfall-related functionality.
|
||||
|
||||
@@ -448,6 +448,28 @@ target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(${EXECUTABLE_NAME} ${SDL2_LIBRARIES})
|
||||
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
|
||||
|
||||
add_executable(fallout2-dat
|
||||
"tools/dat_tool.cc"
|
||||
"src/dfile.cc"
|
||||
"src/platform_compat.cc"
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(fallout2-dat PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(fallout2-dat
|
||||
fpattern_windows::fpattern_windows
|
||||
${ZLIB_LIBRARIES}
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
target_include_directories(fallout2-dat PRIVATE "src")
|
||||
target_include_directories(fallout2-dat PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_include_directories(fallout2-dat PRIVATE ${SDL2_INCLUDE_DIRS})
|
||||
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
install(TARGETS ${EXECUTABLE_NAME} DESTINATION "Payload")
|
||||
|
||||
@@ -119,9 +119,6 @@
|
||||
"base"
|
||||
],
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY": ""
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
|
||||
This file is currently a shell to be filled in over time. For now, it only captures project-specific maintenance notes that are easy to miss.
|
||||
|
||||
## `.dat` CLI Tool
|
||||
|
||||
There is a small command-line archive tool in this repo for inspecting Fallout `.dat` files using the same reader implementation as the game.
|
||||
|
||||
Build it with `make TARGET=fallout2-dat`.
|
||||
|
||||
The executable is written to your selected `BUILD_DIR`, so the default path is `out/build/local-debug-arm64/fallout2-dat` on this macOS setup, but other platforms or custom build directories will differ.
|
||||
|
||||
The current tool is read-only. Available commands:
|
||||
|
||||
1. `./<BUILD_DIR>/fallout2-dat <archive.dat> list [pattern]`
|
||||
2. `./<BUILD_DIR>/fallout2-dat <archive.dat> info [pattern]`
|
||||
3. `./<BUILD_DIR>/fallout2-dat <archive.dat> extract [--lower] <output-dir> [pattern]`
|
||||
4. `./<BUILD_DIR>/fallout2-dat <archive.dat> cat <entry>`
|
||||
|
||||
Use `--lower` with `extract` when you want every extracted file and directory name forced to lowercase.
|
||||
For example, from `/Applications/Fallout2Codex` you can run:
|
||||
|
||||
`/Users/klaas/game/fallout2-ce/out/build/local-debug-arm64/fallout2-dat master.dat extract --lower /tmp/fallout2-dat-lower data\\*`
|
||||
|
||||
## Updating SDL
|
||||
|
||||
SDL is pinned for native builds in `third_party/sdl2/CMakeLists.txt`. Right now, Android also relies on checked-in Java bindings in `os/android/app/src/main/java/org/libsdl/app`, and those bindings must match the SDL version fetched by CMake.
|
||||
|
||||
@@ -8,7 +8,7 @@ For now, this covers opcodes/metarules, and hooks. In the future, it will inclu
|
||||
|
||||
Settings previously read from `ddraw.ini` have been moved into standard CE config files.
|
||||
|
||||
Most settings that control game behavior (premade characters, extra message files, combat tweaks, worldmap, etc.) have been moved into [`<DAT>/config/game.cfg`](files/foce_base.dat/config/game.cfg), which is a content-mod config file intended to be overridden by mods. See that file for the full list with descriptions.
|
||||
Most settings that control game behavior (premade characters, extra message files, combat tweaks, worldmap, etc.) have been moved into [`<DAT>/config/game.cfg`](files/ce.dat/config/game.cfg), which is a content-mod config file intended to be overridden by mods. See that file for the full list with descriptions.
|
||||
|
||||
The following settings were moved into [`fallout2.cfg`](files/fallout2.cfg) instead:
|
||||
|
||||
@@ -70,7 +70,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| Interface / Cursor | get/set_cursor_mode | âś… | - |
|
||||
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
|
||||
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… except get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
|
||||
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, obj_is_openable, get_proto_data, set_proto_data | - |
|
||||
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get/set/remove_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, obj_is_openable, get_proto_data, set_proto_data | - |
|
||||
| Other / Game management | set_movie_path<br>stop/resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | implemented: game_loaded, get_game_mode, get_uptime, signal_close_game | - |
|
||||
| Gameplay tweaks | set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>set_hp_per_level_mod<br>get_unspent_ap_bonus<br>gdialog_get_barter_mod<br>set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_inven_ap_cost<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | implemented: gdialog_get_barter_mod | - |
|
||||
| NPCs | inc_npc_level<br>get_npc_level<br>npc_engine_level_up | not implemented | - |
|
||||
@@ -86,7 +86,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| DeathAnim1 | `HOOK_DEATHANIM1` | đźš« | Use DEATHANIM2 instead |
|
||||
| DeathAnim2 | `HOOK_DEATHANIM2` | âś… | - |
|
||||
| CombatDamage | `HOOK_COMBATDAMAGE` | âś… | - |
|
||||
| OnDeath | `HOOK_ONDEATH` | đźš« | - |
|
||||
| OnDeath | `HOOK_ONDEATH` | âś… | - |
|
||||
| FindTarget | `HOOK_FINDTARGET` | đźš« | (maybe) |
|
||||
| UseObjOn | `HOOK_USEOBJON` | âś… | - |
|
||||
| UseObj | `HOOK_USEOBJ` | âś… | CE notes an sfall-matching inconsistency around return code `2` behavior between interface contexts. |
|
||||
@@ -104,8 +104,8 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
|
||||
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
|
||||
| CombatTurn | `HOOK_COMBATTURN` | âś… | - |
|
||||
| StdProcedure | `HOOK_STDPROCEDURE` | đźš« | Et tu |
|
||||
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | đźš« | - |
|
||||
| StdProcedure | `HOOK_STDPROCEDURE` | âś… | - |
|
||||
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | âś… | - |
|
||||
| CarTravel | `HOOK_CARTRAVEL` | đźš« | - |
|
||||
| SetGlobalVar | `HOOK_SETGLOBALVAR` | đźš« | - |
|
||||
| RestTimer | `HOOK_RESTTIMER` | đźš« | Et tu |
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "sfall.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#define TEST_SCRIPT_INDEX (2)
|
||||
// Depends on Test0.int, which provides a controllable object script helper.
|
||||
// Put the compiled helper at mods/test_script_override/scripts/Test0.int,
|
||||
// and add "test_script_override" to mod_order.txt. The helper must live there
|
||||
// (and not scripts/) because patch000.dat already contains a stock Test0.int and
|
||||
// scripts/ load before it.
|
||||
// Without Test0, these 2 tests will fail:
|
||||
// - "set_script normal hp"
|
||||
// - "set_script no map_enter hp"
|
||||
|
||||
variable testScriptObj := 0;
|
||||
|
||||
procedure create_test_object begin
|
||||
if (testScriptObj != 0) then return;
|
||||
testScriptObj := create_object_sid(obj_pid(dude_obj), tile_num_in_direction(tile_num(dude_obj), has_trait(TRAIT_OBJECT, dude_obj, OBJECT_CUR_ROT), 3), elevation(dude_obj), -1);
|
||||
end
|
||||
|
||||
procedure stdprocedure_hook begin
|
||||
if (get_sfall_arg_at(0) != 15) then return;
|
||||
|
||||
if (get_sfall_arg_at(3)) then begin
|
||||
set_sfall_global("T0HAFTR1", 1);
|
||||
end else begin
|
||||
set_sfall_global("T0HBEFO1", 1);
|
||||
end
|
||||
end
|
||||
|
||||
procedure exercise_set_script(variable scriptId, variable expectedHp, variable expectedBeforeHook, variable expectedAfterHook, variable desc) begin
|
||||
set_sfall_global("T0MAPEN1", 0);
|
||||
set_sfall_global("T0HBEFO1", 0);
|
||||
set_sfall_global("T0HAFTR1", 0);
|
||||
set_script(testScriptObj, scriptId);
|
||||
call assertEquals(desc + " get_script", get_script(testScriptObj), TEST_SCRIPT_INDEX);
|
||||
call assertEquals(desc + " map_enter global", get_sfall_global_int("T0MAPEN1"), expectedHp);
|
||||
call assertEquals(desc + " stdprocedure hook before", get_sfall_global_int("T0HBEFO1"), expectedBeforeHook);
|
||||
call assertEquals(desc + " stdprocedure hook after", get_sfall_global_int("T0HAFTR1"), expectedAfterHook);
|
||||
remove_script(testScriptObj);
|
||||
call assertEquals(desc + " remove_script clears get_script", get_script(testScriptObj), 0);
|
||||
end
|
||||
|
||||
procedure start begin
|
||||
if (not game_loaded) then return;
|
||||
|
||||
display_msg("Testing set_script/remove_script/get_script...");
|
||||
register_hook_proc(HOOK_STDPROCEDURE, stdprocedure_hook);
|
||||
register_hook_proc(HOOK_STDPROCEDURE_END, stdprocedure_hook);
|
||||
call create_test_object;
|
||||
|
||||
call exercise_set_script(TEST_SCRIPT_INDEX, 22, 1, 1, "set_script normal");
|
||||
call exercise_set_script(TEST_SCRIPT_INDEX bwor 0x80000000, 11, 0, 0, "set_script no map_enter");
|
||||
|
||||
destroy_object(testScriptObj);
|
||||
testScriptObj := 0;
|
||||
|
||||
call report_test_results("script_manipulation");
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "sfall.h"
|
||||
#include "dik.h"
|
||||
#include "define_lite.h"
|
||||
|
||||
#define TEST_KEY (DIK_J)
|
||||
|
||||
procedure ondeath_handler begin
|
||||
variable
|
||||
dead_critter := get_sfall_arg_at(0),
|
||||
critter_name := "<null>",
|
||||
critter_pid := -1;
|
||||
|
||||
if (dead_critter) then begin
|
||||
critter_name := obj_name(dead_critter);
|
||||
critter_pid := obj_pid(dead_critter);
|
||||
end
|
||||
|
||||
display_msg(string_format2("ondeath critter=%s pid=%d", critter_name, critter_pid));
|
||||
end
|
||||
|
||||
procedure keypress_handler begin
|
||||
variable
|
||||
pressed := get_sfall_arg_at(0),
|
||||
key := get_sfall_arg_at(1),
|
||||
target;
|
||||
|
||||
if (not pressed) then return;
|
||||
if (key != TEST_KEY) then return;
|
||||
|
||||
target := obj_under_cursor(true, false);
|
||||
if (target == 0) then begin
|
||||
display_msg("ondeath test: no critter under cursor");
|
||||
return;
|
||||
end
|
||||
|
||||
if (target == dude_obj) then begin
|
||||
display_msg("ondeath test: refusing to kill dude");
|
||||
return;
|
||||
end
|
||||
|
||||
display_msg(string_format1("ondeath test: killing %s", obj_name(target)));
|
||||
critter_dmg(target, 9999, DMG_BYPASS_ARMOR);
|
||||
end
|
||||
|
||||
procedure start begin
|
||||
if (not game_loaded) then return;
|
||||
|
||||
display_msg("ondeath manual test ready: hover a critter and press J");
|
||||
|
||||
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
|
||||
register_hook_proc(HOOK_ONDEATH, ondeath_handler);
|
||||
end
|
||||
@@ -4936,6 +4936,7 @@ static void _damage_object(Object* a1, int damage, bool animated, int a4, Object
|
||||
}
|
||||
|
||||
partyMemberRemove(a1);
|
||||
scriptHooks_OnDeath(a1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,6 @@ static bool aiCanUseItem(Object* obj, Object* a2);
|
||||
static Object* _ai_search_environ(Object* critter, int itemType);
|
||||
static Object* _ai_retrieve_object(Object* critter, Object* item);
|
||||
static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender);
|
||||
static bool _aiIsMeleeThreat(Object* attacker, Object* defender);
|
||||
static int _ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, bool taunt);
|
||||
static int _ai_move_closer(Object* a1, Object* a2, bool taunt);
|
||||
static int _cai_retargetTileFromFriendlyFire(Object* source, Object* target, int* tilePtr);
|
||||
@@ -2352,28 +2351,6 @@ static int _ai_pick_hit_mode(Object* attacker, Object* weapon, Object* defender)
|
||||
return HIT_MODE_RIGHT_WEAPON_PRIMARY;
|
||||
}
|
||||
|
||||
// Returns true if the defender is primarily a melee threat.
|
||||
static bool _aiIsMeleeThreat(Object* attacker, Object* defender)
|
||||
{
|
||||
if (defender == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object* weapon = critterGetItem2(defender);
|
||||
|
||||
if (weapon != nullptr && itemGetType(weapon) == ITEM_TYPE_WEAPON) {
|
||||
int attackType = weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);
|
||||
if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) {
|
||||
return false;
|
||||
}
|
||||
if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x429FC8
|
||||
static int _ai_move_steps_closer(Object* critter, Object* target, int actionPoints, bool taunt)
|
||||
{
|
||||
@@ -2744,29 +2721,6 @@ static int _ai_try_attack(Object* attacker, Object* defender)
|
||||
_ai_switch_weapons(attacker, &hitMode, &weapon, defender);
|
||||
}
|
||||
|
||||
AiPacket* ai = aiGetPacket(attacker);
|
||||
if (weapon != nullptr && itemGetType(weapon) == ITEM_TYPE_WEAPON) {
|
||||
int attackType = weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);
|
||||
if (attackType == ATTACK_TYPE_RANGED
|
||||
&& ai->best_weapon != BEST_WEAPON_MELEE
|
||||
&& ai->best_weapon != BEST_WEAPON_MELEE_OVER_RANGED
|
||||
&& _aiIsMeleeThreat(attacker, defender)
|
||||
&& objectGetDistanceBetween(attacker, defender) <= 2) {
|
||||
int attackCost = weaponGetActionPointCost(attacker, hitMode, false);
|
||||
int moveCost = 2;
|
||||
if (attacker->data.critter.combat.ap > attackCost + moveCost) {
|
||||
if (_ai_move_away(attacker, defender, 2) == -1) {
|
||||
}
|
||||
|
||||
weapon = critterGetItem2(attacker);
|
||||
if (weapon != nullptr && itemGetType(weapon) != ITEM_TYPE_WEAPON) {
|
||||
weapon = nullptr;
|
||||
}
|
||||
hitMode = _ai_pick_hit_mode(attacker, weapon, defender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char rotations[800];
|
||||
|
||||
Object* ammo = nullptr;
|
||||
|
||||
@@ -331,6 +331,26 @@ bool configRead(Config* config, const char* filePath, bool isDb)
|
||||
configParseLine(config, string);
|
||||
}
|
||||
fileClose(stream);
|
||||
|
||||
// Build patch file path by inserting "#patch" before the extension.
|
||||
char patchPath[COMPAT_MAX_PATH];
|
||||
const char* dot = strrchr(filePath, '.');
|
||||
if (dot != nullptr) {
|
||||
snprintf(patchPath, sizeof(patchPath), "%.*s#patch%s", (int)(dot - filePath), filePath, dot);
|
||||
} else {
|
||||
snprintf(patchPath, sizeof(patchPath), "%s#patch", filePath);
|
||||
}
|
||||
|
||||
struct PatchContext {
|
||||
Config* config;
|
||||
char string[CONFIG_FILE_MAX_LINE_LENGTH];
|
||||
} patchCtx = { config };
|
||||
|
||||
xfileOpenEachReverse(patchPath, "rb", [](XFile* file, void* ctx) {
|
||||
auto* pc = static_cast<PatchContext*>(ctx);
|
||||
while (fileReadString(pc->string, sizeof(pc->string), file) != nullptr) {
|
||||
configParseLine(pc->config, pc->string);
|
||||
} }, &patchCtx);
|
||||
} else {
|
||||
FILE* stream = compat_fopen(filePath, "rt");
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ void contentConfigInit()
|
||||
}
|
||||
|
||||
configRead(&gContentConfig, kConfigPath, true);
|
||||
// Patch config allows to override only certain fields, without replacing the whole file.
|
||||
// TODO: remove this after config patching by mods is implemented inside configRead
|
||||
configRead(&gContentConfig, kConfigPatchPath, true);
|
||||
}
|
||||
|
||||
void contentConfigExit()
|
||||
|
||||
+5
-3
@@ -25,6 +25,7 @@
|
||||
#include "random.h"
|
||||
#include "reaction.h"
|
||||
#include "scripts.h"
|
||||
#include "sfall_script_hooks.h"
|
||||
#include "skill.h"
|
||||
#include "stat.h"
|
||||
#include "tile.h"
|
||||
@@ -825,7 +826,11 @@ void critterKill(Object* critter, int anim, bool refreshRect)
|
||||
|
||||
int elevation = critter->elevation;
|
||||
|
||||
critter->data.critter.hp = 0;
|
||||
critter->data.critter.combat.results |= DAM_DEAD;
|
||||
|
||||
partyMemberRemove(critter);
|
||||
scriptHooks_OnDeath(critter);
|
||||
|
||||
// NOTE: Original code uses goto to jump out from nested conditions below.
|
||||
bool shouldChangeFid = false;
|
||||
@@ -893,9 +898,6 @@ void critterKill(Object* critter, int anim, bool refreshRect)
|
||||
_obj_turn_off_light(critter, &tempRect);
|
||||
rectUnion(&updatedRect, &tempRect, &updatedRect);
|
||||
|
||||
critter->data.critter.hp = 0;
|
||||
critter->data.critter.combat.results |= DAM_DEAD;
|
||||
|
||||
if (critter->sid != -1) {
|
||||
scriptRemove(critter->sid);
|
||||
critter->sid = -1;
|
||||
|
||||
+5
-1
@@ -88,7 +88,7 @@ static void showHelp();
|
||||
static int gameDbInit();
|
||||
static void showSplash();
|
||||
|
||||
inline constexpr char kBaseModPath[] = "foce_base.dat";
|
||||
inline constexpr char kBaseModPath[] = "ce.dat";
|
||||
|
||||
// 0x501C9C
|
||||
static char _aGame_0[] = "game\\";
|
||||
@@ -1381,6 +1381,10 @@ static int gameDbInit()
|
||||
if (*patch_file_name == '\0') {
|
||||
patch_file_name = nullptr;
|
||||
}
|
||||
// Try to ensure that patches dir exists early. This is needed for auto-generated game.cfg later.
|
||||
if (patch_file_name != nullptr) {
|
||||
compat_mkdir_recursive(patch_file_name);
|
||||
}
|
||||
|
||||
int master_db_handle = dbOpen(main_file_name, patch_file_name);
|
||||
if (master_db_handle == -1) {
|
||||
|
||||
@@ -218,19 +218,17 @@ static bool contentConfigMigrateFromSfall(Config* sfallConfig, const char* conte
|
||||
assert(sfallConfig != nullptr && contentConfigFilePath != nullptr);
|
||||
|
||||
// Skip if a local game.cfg already exists (already migrated or user-managed).
|
||||
FILE* existing = compat_fopen(contentConfigFilePath, "rt");
|
||||
if (existing != nullptr) {
|
||||
fclose(existing);
|
||||
if (compat_file_exists(contentConfigFilePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Migrate start year/month/day only when explicitly set (not the sfall -1 sentinel).
|
||||
bool migrated = false;
|
||||
Config migratedConfig;
|
||||
if (!configInit(&migratedConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool migrated = false;
|
||||
// Migrate start year/month/day only when explicitly set (not the sfall -1 sentinel).
|
||||
auto migrateStartInt = [&](const char* sfallKey, const char* targetKey, int defaultValue) {
|
||||
int value;
|
||||
if (configGetInt(sfallConfig, "Misc", sfallKey, &value) && value >= 0 && value != defaultValue) {
|
||||
@@ -257,7 +255,12 @@ static bool contentConfigMigrateFromSfall(Config* sfallConfig, const char* conte
|
||||
|
||||
if (migrated) {
|
||||
// Ensure all directory components exist before writing.
|
||||
compat_mkdir_recursive(contentConfigFilePath);
|
||||
char drive[COMPAT_MAX_DRIVE];
|
||||
char dirPart[COMPAT_MAX_DIR];
|
||||
char pathWithoutFile[COMPAT_MAX_PATH];
|
||||
compat_splitpath(contentConfigFilePath, drive, dirPart, nullptr, nullptr);
|
||||
compat_makepath(pathWithoutFile, drive, dirPart, nullptr, nullptr);
|
||||
compat_mkdir_recursive(pathWithoutFile);
|
||||
|
||||
if (!configWrite(&migratedConfig, contentConfigFilePath, false)) {
|
||||
debugPrint("Failed to write migrated settings to %s!\n", contentConfigFilePath);
|
||||
@@ -270,15 +273,17 @@ static bool contentConfigMigrateFromSfall(Config* sfallConfig, const char* conte
|
||||
|
||||
void contentConfigTryMigrateFromSfall(const char* contentConfigPath)
|
||||
{
|
||||
if (!gSfallConfig.isInitialized() || gSfallConfig.entriesLength == 0) {
|
||||
// Nothing to migrate.
|
||||
return;
|
||||
}
|
||||
const auto& masterPatches = settings.system.master_patches_path;
|
||||
if (masterPatches.empty()) {
|
||||
debugPrint("Failed to migrate from ddraw.ini: no master_patches is set.\n");
|
||||
return;
|
||||
}
|
||||
FILE* baseAsDat = compat_fopen(masterPatches.c_str(), "rb");
|
||||
if (baseAsDat != nullptr) {
|
||||
// Master patches is pointing to a dat file. This shouldn't normally happen, so don't migrate in this case.
|
||||
fclose(baseAsDat);
|
||||
if (compat_file_exists(masterPatches.c_str())) {
|
||||
// Master patches is pointing to a file instead of a folder. This shouldn't normally happen, so don't migrate in this case.
|
||||
return;
|
||||
}
|
||||
char contentCfgPath[COMPAT_MAX_PATH];
|
||||
|
||||
+1
-1
@@ -287,7 +287,7 @@ typedef struct Object {
|
||||
int outline; // obj_outline
|
||||
int sid; // obj_sid
|
||||
Object* owner;
|
||||
int scriptIndex;
|
||||
int scriptIndex; // TODO: remove
|
||||
} Object;
|
||||
|
||||
typedef struct ObjectListNode {
|
||||
|
||||
+17
-7
@@ -218,24 +218,34 @@ int compat_mkdir(const char* path)
|
||||
int compat_mkdir_recursive(const char* path)
|
||||
{
|
||||
char drive[COMPAT_MAX_DRIVE];
|
||||
char dirPart[COMPAT_MAX_DIR];
|
||||
compat_splitpath(path, drive, dirPart, nullptr, nullptr);
|
||||
compat_splitpath(path, drive, nullptr, nullptr, nullptr);
|
||||
|
||||
char dir[COMPAT_MAX_PATH];
|
||||
compat_makepath(dir, drive, dirPart, nullptr, nullptr);
|
||||
char pathCopy[COMPAT_MAX_PATH];
|
||||
strcpy(pathCopy, path);
|
||||
|
||||
// Skip drive root (e.g. "C:\\" or leading "/") to avoid mkdir("") or mkdir("C:").
|
||||
char* sep = dir + strlen(drive);
|
||||
char* sep = pathCopy + strlen(drive);
|
||||
if (*sep == '\\' || *sep == '/') sep++;
|
||||
for (; *sep != '\0'; sep++) {
|
||||
if (*sep == '\\' || *sep == '/') {
|
||||
char saved = *sep;
|
||||
*sep = '\0';
|
||||
compat_mkdir(dir);
|
||||
if (compat_mkdir(pathCopy) < 0) {
|
||||
break;
|
||||
}
|
||||
*sep = saved;
|
||||
}
|
||||
}
|
||||
return compat_mkdir(dir);
|
||||
return compat_mkdir(path);
|
||||
}
|
||||
|
||||
bool compat_file_exists(const char* filePath)
|
||||
{
|
||||
FILE* file = compat_fopen(filePath, "rb");
|
||||
if (file == nullptr) return false;
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int compat_timeGetTime()
|
||||
|
||||
@@ -33,6 +33,7 @@ long compat_tell(int fileHandle);
|
||||
long compat_filelength(int fd);
|
||||
int compat_mkdir(const char* path);
|
||||
int compat_mkdir_recursive(const char* path);
|
||||
bool compat_file_exists(const char* filePath);
|
||||
unsigned int compat_timeGetTime();
|
||||
FILE* compat_fopen(const char* path, const char* mode);
|
||||
gzFile compat_gzopen(const char* path, const char* mode);
|
||||
|
||||
@@ -130,7 +130,7 @@ int objectSetScriptFromProto(Object* object, int* sidPtr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 0x49AAC0
|
||||
// 0x49AAC0 obj_new_sid_inst
|
||||
int objectSetScript(Object* obj, int scriptType, int scriptIndex)
|
||||
{
|
||||
if (scriptIndex == -1) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user