Compare commits

..
Author SHA1 Message Date
Mike Klaas b9936e10ff PR 2026-03-31 11:51:16 -07:00
Mike Klaas 6bd1daae0b include 2026-03-31 11:30:47 -07:00
Mike Klaas d8336761cc fixes 2026-03-31 11:25:08 -07:00
Mike Klaas 5055e2b0d4 Path normalization fixex 2026-03-31 10:09:44 -07:00
Mike Klaas a0954c8df7 cleanup 2026-03-31 10:09:44 -07:00
Mike Klaas 7abab85e0a Normalize global script archive paths 2026-03-31 10:09:44 -07:00
31 changed files with 276 additions and 1580 deletions
-1
View File
@@ -434,7 +434,6 @@ endif()
add_subdirectory("third_party/fpattern") add_subdirectory("third_party/fpattern")
target_link_libraries(${EXECUTABLE_NAME} fpattern::fpattern) target_link_libraries(${EXECUTABLE_NAME} fpattern::fpattern)
target_link_libraries(${EXECUTABLE_NAME} fpattern_windows::fpattern_windows)
target_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES}) target_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES})
target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS}) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})
+4 -4
View File
@@ -38,7 +38,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… except init_hook | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. | | Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… except init_hook | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - | | Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - |
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - | | Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | implemented: all except available_global_script_types | - | | Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | implemented: all except available_global_script_types | `GlobalScriptPaths` masks now match archive-backed scripts regardless of `/` vs `\` separators, matching Windows-style `.dat` contents on non-Windows hosts. |
| Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: only get_attack_type, get_bodypart_hit_modifier, combat_data, set_bodypart_hit_modifier | - | | Combat | attack_is_aimed<br>block_combat<br>force_aimed_shots<br>disable_aimed_shots<br>get_attack_type<br>get/set_bodypart_hit_modifier<br>combat_data<br>get/set/reset_critical_table<br>get_last_target<br>get_last_attacker<br>set_critter_burst_disable<br>get/set_critter_current_ap<br>set_spray_settings<br>get/set_combat_free_move | implemented: only get_attack_type, get_bodypart_hit_modifier, combat_data, set_bodypart_hit_modifier | - |
| Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - | | Car | set_car_current_town<br>car_gas_amount<br>set_car_intface_art | implemented: all except set_car_intface_art | - |
| Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window | implemented: only message_box, get_text_width, show_window | - | | Interface / Windows and images | art_frame_data<br>interface_art_draw<br>interface_print<br>draw_image<br>draw_image_scaled<br>get_window_under_mouse<br>create_win<br>get_window_attribute<br>message_box<br>set_window_flag<br>win_fill_color<br>interface_overlay<br>dialog_message<br>get_text_width<br>hide_window<br>show_window | implemented: only message_box, get_text_width, show_window | - |
@@ -61,7 +61,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| CalcAPCost | `HOOK_CALCAPCOST` | đźš« | - | | CalcAPCost | `HOOK_CALCAPCOST` | đźš« | - |
| DeathAnim1 | `HOOK_DEATHANIM1` | đźš« | Use DEATHANIM2 instead | | DeathAnim1 | `HOOK_DEATHANIM1` | đźš« | Use DEATHANIM2 instead |
| DeathAnim2 | `HOOK_DEATHANIM2` | âś… | - | | DeathAnim2 | `HOOK_DEATHANIM2` | âś… | - |
| CombatDamage | `HOOK_COMBATDAMAGE` | âś… | - | | CombatDamage | `HOOK_COMBATDAMAGE` | âś… | CE passes the raw `Attack*` as the final mixed argument, matching sfall's shape. |
| OnDeath | `HOOK_ONDEATH` | đźš« | - | | OnDeath | `HOOK_ONDEATH` | đźš« | - |
| FindTarget | `HOOK_FINDTARGET` | đźš« | (maybe) | | FindTarget | `HOOK_FINDTARGET` | đźš« | (maybe) |
| UseObjOn | `HOOK_USEOBJON` | âś… | - | | UseObjOn | `HOOK_USEOBJON` | âś… | - |
@@ -71,12 +71,12 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
| MoveCost | `HOOK_MOVECOST` | đźš« | - | | MoveCost | `HOOK_MOVECOST` | đźš« | - |
| ItemDamage | `HOOK_ITEMDAMAGE` | đźš« | - | | ItemDamage | `HOOK_ITEMDAMAGE` | đźš« | - |
| AmmoCost | `HOOK_AMMOCOST` | đźš« | Et tu | | AmmoCost | `HOOK_AMMOCOST` | đźš« | Et tu |
| KeyPress | `HOOK_KEYPRESS` | âś… | Third hook arg is currently `0`; CE doesn't use VK codes. | | KeyPress | `HOOK_KEYPRESS` | âś… | Third hook arg is currently `0`; CE notes that sfall used VK codes there. |
| MouseClick | `HOOK_MOUSECLICK` | đźš« | - | | MouseClick | `HOOK_MOUSECLICK` | đźš« | - |
| UseSkill | `HOOK_USESKILL` | đźš« | - | | UseSkill | `HOOK_USESKILL` | đźš« | - |
| Steal | `HOOK_STEAL` | đźš« | Et tu | | Steal | `HOOK_STEAL` | đźš« | Et tu |
| WithinPerception | `HOOK_WITHINPERCEPTION` | đźš« | Et tu | | WithinPerception | `HOOK_WITHINPERCEPTION` | đźš« | Et tu |
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - | | InventoryMove | `HOOK_INVENTORYMOVE` | đźš« | Et tu |
| InvenWield | `HOOK_INVENWIELD` | đźš« | - | | InvenWield | `HOOK_INVENWIELD` | đźš« | - |
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - | | AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
| CombatTurn | `HOOK_COMBATTURN` | đźš« | - | | CombatTurn | `HOOK_COMBATTURN` | đźš« | - |
+5
View File
@@ -197,6 +197,11 @@ NumbersInDialogue=0
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Scripts] [Scripts]
;Comma-separated list of masked paths to load global scripts from
;Only use single backslash \ as the directory separator
;Paths outside of scripts folder are supported
;GlobalScriptPaths=scripts\gl_*.int,scripts\sfall\gl*.int
;Uncomment the option to specify an additional directory for ini files used by scripts ;Uncomment the option to specify an additional directory for ini files used by scripts
;The game will search for ini files first relative to this directory and then relative to the root directory if not found ;The game will search for ini files first relative to this directory and then relative to the root directory if not found
;The path length is limited to 61 characters ;The path length is limited to 61 characters
@@ -13,23 +13,18 @@ import java.io.OutputStream;
public class FileUtils { public class FileUtils {
static boolean copyRecursively(ContentResolver contentResolver, DocumentFile src, File dest) { static boolean copyRecursively(ContentResolver contentResolver, DocumentFile src, File dest) {
if (dest == null || (!dest.exists() && !dest.mkdirs())) {
return false;
}
final DocumentFile[] documentFiles = src.listFiles(); final DocumentFile[] documentFiles = src.listFiles();
for (final DocumentFile documentFile : documentFiles) { for (final DocumentFile documentFile : documentFiles) {
final String name = documentFile.getName();
if (name == null || name.isEmpty()) {
return false;
}
if (documentFile.isFile()) { if (documentFile.isFile()) {
if (!copyFile(contentResolver, documentFile, new File(dest, name))) { if (!copyFile(contentResolver, documentFile, new File(dest, documentFile.getName()))) {
return false; return false;
} }
} else if (documentFile.isDirectory()) { } else if (documentFile.isDirectory()) {
final File subdirectory = new File(dest, name); final File subdirectory = new File(dest, documentFile.getName());
if (!subdirectory.exists()) {
subdirectory.mkdir();
}
if (!copyRecursively(contentResolver, documentFile, subdirectory)) { if (!copyRecursively(contentResolver, documentFile, subdirectory)) {
return false; return false;
} }
@@ -39,22 +34,18 @@ public class FileUtils {
} }
private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) { private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) {
final File parent = dest.getParentFile(); try {
if (parent != null && !parent.exists() && !parent.mkdirs()) { final InputStream inputStream = contentResolver.openInputStream(src.getUri());
return false; final OutputStream outputStream = new FileOutputStream(dest);
}
try (final InputStream inputStream = contentResolver.openInputStream(src.getUri());
final OutputStream outputStream = new FileOutputStream(dest)) {
if (inputStream == null) {
return false;
}
final byte[] buffer = new byte[16384]; final byte[] buffer = new byte[16384];
int bytesRead; int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead); outputStream.write(buffer, 0, bytesRead);
} }
inputStream.close();
outputStream.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
@@ -2,12 +2,10 @@ package com.alexbatalov.fallout2ce;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Toast;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
@@ -19,18 +17,18 @@ public class ImportActivity extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
launchImportPicker(); Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, IMPORT_REQUEST_CODE);
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) { protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == IMPORT_REQUEST_CODE) { if (requestCode == IMPORT_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
final Uri treeUri = resultData != null ? resultData.getData() : null; final Uri treeUri = resultData.getData();
if (treeUri != null) { if (treeUri != null) {
grantImportPermissions(treeUri, resultData);
final DocumentFile treeDocument = DocumentFile.fromTreeUri(this, treeUri); final DocumentFile treeDocument = DocumentFile.fromTreeUri(this, treeUri);
if (treeDocument != null && treeDocument.isDirectory()) { if (treeDocument != null) {
copyFiles(treeDocument); copyFiles(treeDocument);
return; return;
} }
@@ -44,57 +42,18 @@ public class ImportActivity extends Activity {
} }
private void copyFiles(DocumentFile treeDocument) { private void copyFiles(DocumentFile treeDocument) {
final ProgressDialog dialog = createProgressDialog(); ProgressDialog dialog = createProgressDialog();
dialog.show(); dialog.show();
new Thread(() -> { new Thread(() -> {
final ContentResolver contentResolver = getContentResolver(); ContentResolver contentResolver = getContentResolver();
final File externalFilesDir = getExternalFilesDir(null); File externalFilesDir = getExternalFilesDir(null);
final boolean success = externalFilesDir != null FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
&& FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
runOnUiThread(() -> { startMainActivity();
dialog.dismiss(); dialog.dismiss();
if (success) {
startMainActivity();
} else {
Toast.makeText(this, R.string.import_failed, Toast.LENGTH_LONG).show();
}
finish();
});
}).start();
}
private void launchImportPicker() {
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
try {
startActivityForResult(intent, IMPORT_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.import_picker_unavailable, Toast.LENGTH_LONG).show();
finish(); finish();
} }).start();
}
private void grantImportPermissions(Uri treeUri, Intent resultData) {
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (resultData != null) {
flags = resultData.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
}
try {
getContentResolver().takePersistableUriPermission(treeUri, flags);
} catch (SecurityException ignored) {
// Some providers do not offer persistable grants. One-shot access is enough for import.
}
} }
private void startMainActivity() { private void startMainActivity() {
@@ -2,6 +2,4 @@
<string name="app_name">Fallout 2</string> <string name="app_name">Fallout 2</string>
<string name="loading_dialog_title">PLEASE STAND BY</string> <string name="loading_dialog_title">PLEASE STAND BY</string>
<string name="loading_dialog_message">Copying files…</string> <string name="loading_dialog_message">Copying files…</string>
<string name="import_failed">Failed to import game files. Please pick a valid Fallout 2 folder and try again.</string>
<string name="import_picker_unavailable">No compatible folder picker is available on this device.</string>
</resources> </resources>
+14 -2
View File
@@ -30,10 +30,22 @@ compile.exe -q -p -l -O2 -d -s -n -I<sfall_headers_id> <script_name.ssl>
- Install [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=BGforge.bgforge-mls) - Install [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=BGforge.bgforge-mls)
## Run test script ## Run test script
1. Move compiled `.int` file into game folder as `scripts/gl_<script_name>.int` 1. Move compiled `.int` file into game folder as `data/scripts/gl_<script_name>.int`
2. Run game, check that game displays message about tests 2. Change `ddraw.ini` and add this section:
```ini
[Scripts]
GlobalScriptPaths=data/scripts/gl*.int
```
(or add new path using comma as separator)
Note that on non-Windows it have to be `/` as folder separator
3. Run game, check that game displays message about tests
-6
View File
@@ -164,12 +164,6 @@ procedure array_test_suite begin
call assertEquals("saved arrays 3", len_array(list_saved_arrays), len_array(arr2) - 1); call assertEquals("saved arrays 3", len_array(list_saved_arrays), len_array(arr2) - 1);
*/ */
display_msg("Testing nested expressions...");
arr := [["one", "two"]];
call assertEquals("nested 1", arr[0][1], "two");
arr := [["one", "two"], {"three": ["four", -1]}];
call assertEquals("nested 2", arr[1].three[0], "four");
display_msg("All tests finished with "+test_suite_errors+" errors."); display_msg("All tests finished with "+test_suite_errors+" errors.");
call report_test_results("arrays"); call report_test_results("arrays");
-57
View File
@@ -1,57 +0,0 @@
#include "sfall.h"
#include "dik.h"
#include "lib.arrays.h"
variable inventorymove_blocking := false;
procedure inventorymove_handler begin
variable
args := get_sfall_args,
action := args[0],
item := args[1],
arg2 := args[2],
action_name := "unknown";
if (action == 0) then action_name := "main_backpack";
else if (action == 1) then action_name := "left_hand";
else if (action == 2) then action_name := "right_hand";
else if (action == 3) then action_name := "armor_slot";
else if (action == 4) then action_name := "weapon_reload";
else if (action == 5) then action_name := "container";
else if (action == 6) then action_name := "ground";
else if (action == 7) then action_name := "pickup";
else if (action == 8) then action_name := "character_portrait";
display_msg(string_format2("inventorymove %s args=%s", action_name, debug_array_str(args)));
if (item) then
display_msg(string_format2("inventorymove item=%s pid=%d", obj_name(item), obj_pid(item)));
if (arg2) then
display_msg(string_format2("inventorymove arg2=%s pid=%d", obj_name(arg2), obj_pid(arg2)));
display_msg(string_format1("inventorymove blocking=%d", inventorymove_blocking));
if (inventorymove_blocking) then begin
display_msg("inventorymove blocked");
set_sfall_return(0);
end
end
procedure keypress_handler begin
variable
pressed := get_sfall_arg_at(0),
key := get_sfall_arg_at(1);
if (not pressed) then return;
if (key != DIK_X) then return;
inventorymove_blocking := not inventorymove_blocking;
display_msg(string_format1("inventorymove blocking %d", inventorymove_blocking));
end
procedure start begin
if (not game_loaded) then return;
display_msg("inventorymove manual test ready: press X to toggle blocking");
register_hook_proc(HOOK_KEYPRESS, keypress_handler);
register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler);
end
+1 -1
View File
@@ -641,7 +641,7 @@ int fileNameListInit(const char* pattern, char*** fileNameListPtr)
char dir[COMPAT_MAX_DIR]; char dir[COMPAT_MAX_DIR];
char fileName[COMPAT_MAX_FNAME]; char fileName[COMPAT_MAX_FNAME];
char extension[COMPAT_MAX_EXT]; char extension[COMPAT_MAX_EXT];
compat_windows_path_to_native(name); compat_path_to_native(name);
compat_splitpath(name, nullptr, dir, fileName, extension); compat_splitpath(name, nullptr, dir, fileName, extension);
if (!isWildcard || *dir == '\0' || (strchr(dir, '\\') == nullptr && strchr(dir, '/') == nullptr)) { if (!isWildcard || *dir == '\0' || (strchr(dir, '\\') == nullptr && strchr(dir, '/') == nullptr)) {
+27 -6
View File
@@ -7,7 +7,7 @@
#include <algorithm> #include <algorithm>
#include "fpattern_windows.h" #include <fpattern/fpattern.h>
#include "platform_compat.h" #include "platform_compat.h"
@@ -42,6 +42,7 @@ static DFile* dfileOpenInternal(DBase* dbase, const char* filename, const char*
static int dfileReadCharInternal(DFile* stream); static int dfileReadCharInternal(DFile* stream);
static bool dfileReadCompressed(DFile* stream, void* ptr, size_t size); static bool dfileReadCompressed(DFile* stream, void* ptr, size_t size);
static void dfileUngetCompressed(DFile* stream, int ch); static void dfileUngetCompressed(DFile* stream, int ch);
static bool dfilePathMatchesPattern(const char* pattern, const char* path);
// Reads .DAT file contents. // Reads .DAT file contents.
// //
@@ -201,12 +202,15 @@ bool dbaseClose(DBase* dbase)
// 0x4E5308 // 0x4E5308
bool dbaseFindFirstEntry(DBase* dbase, DFileFindData* findFileData, const char* pattern) bool dbaseFindFirstEntry(DBase* dbase, DFileFindData* findFileData, const char* pattern)
{ {
// .dat files always have windows style paths // fpattern-matching requires native-separators
assert(pattern != nullptr);
compat_strlcpy(findFileData->pattern, pattern, sizeof(findFileData->pattern));
compat_path_to_native(findFileData->pattern);
for (int index = 0; index < dbase->entriesLength; index++) { for (int index = 0; index < dbase->entriesLength; index++) {
DBaseEntry* entry = &(dbase->entries[index]); DBaseEntry* entry = &(dbase->entries[index]);
if (fpattern_windows_match(pattern, entry->path)) { if (dfilePathMatchesPattern(findFileData->pattern, entry->path)) {
strcpy(findFileData->fileName, entry->path); strcpy(findFileData->fileName, entry->path);
strcpy(findFileData->pattern, pattern);
findFileData->index = index; findFileData->index = index;
return true; return true;
} }
@@ -220,7 +224,7 @@ bool dbaseFindNextEntry(DBase* dbase, DFileFindData* findFileData)
{ {
for (int index = findFileData->index + 1; index < dbase->entriesLength; index++) { for (int index = findFileData->index + 1; index < dbase->entriesLength; index++) {
DBaseEntry* entry = &(dbase->entries[index]); DBaseEntry* entry = &(dbase->entries[index]);
if (fpattern_windows_match(findFileData->pattern, entry->path)) { if (dfilePathMatchesPattern(findFileData->pattern, entry->path)) {
strcpy(findFileData->fileName, entry->path); strcpy(findFileData->fileName, entry->path);
findFileData->index = index; findFileData->index = index;
return true; return true;
@@ -639,7 +643,15 @@ static int dbaseFindEntryByFilePath(const void* file, const void* entryName)
// 0x4E5D9C // 0x4E5D9C
static DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char* mode, DFile* dfile) static DFile* dfileOpenInternal(DBase* dbase, const char* filePath, const char* mode, DFile* dfile)
{ {
DBaseEntry* entry = (DBaseEntry*)bsearch(filePath, dbase->entries, dbase->entriesLength, sizeof(*dbase->entries), dbaseFindEntryByFilePath); assert(filePath != nullptr);
// .dat files contain windows path separators
char normalizedFilePath[COMPAT_MAX_PATH];
compat_strlcpy(normalizedFilePath, filePath, sizeof(normalizedFilePath));
compat_path_to_windows(normalizedFilePath);
DBaseEntry* entry;
entry = (DBaseEntry*)bsearch(normalizedFilePath, dbase->entries, dbase->entriesLength, sizeof(*dbase->entries), dbaseFindEntryByFilePath);
if (entry == nullptr) { if (entry == nullptr) {
goto err; goto err;
} }
@@ -861,4 +873,13 @@ static void dfileUngetCompressed(DFile* stream, int ch)
stream->position--; stream->position--;
} }
// pattern must be normalized to native paths, since that's what fpattern requires
static bool dfilePathMatchesPattern(const char* pattern, const char* path)
{
char normalizedPath[COMPAT_MAX_PATH];
compat_strlcpy(normalizedPath, path, sizeof(normalizedPath));
compat_path_to_native(normalizedPath);
return fpattern_match(pattern, normalizedPath);
}
} // namespace fallout } // namespace fallout
-16
View File
@@ -1,16 +0,0 @@
#ifndef FALLOUT_FPATTERN_WINDOWS_H_
#define FALLOUT_FPATTERN_WINDOWS_H_
#ifdef __cplusplus
extern "C" {
#endif
int fpattern_windows_isvalid(const char* pat);
int fpattern_windows_match(const char* pat, const char* fname);
int fpattern_windows_matchn(const char* pat, const char* fname);
#ifdef __cplusplus
}
#endif
#endif
+2 -2
View File
@@ -132,7 +132,7 @@ bool gameConfigInit(bool isMapper, int argc, char** argv)
// CE: Detect alternative default music directory. // CE: Detect alternative default music directory.
char alternativeMusicPath[COMPAT_MAX_PATH]; char alternativeMusicPath[COMPAT_MAX_PATH];
strcpy(alternativeMusicPath, "data\\sound\\music\\*.acm"); strcpy(alternativeMusicPath, "data\\sound\\music\\*.acm");
compat_windows_path_to_native(alternativeMusicPath); compat_path_to_native(alternativeMusicPath);
compat_resolve_path(alternativeMusicPath); compat_resolve_path(alternativeMusicPath);
char** acms; char** acms;
@@ -260,7 +260,7 @@ static void gameConfigResolvePath(const char* section, const char* key)
{ {
char* path; char* path;
configGetString(&gGameConfig, section, key, &path); configGetString(&gGameConfig, section, key, &path);
compat_windows_path_to_native(path); compat_path_to_native(path);
compat_resolve_path(path); compat_resolve_path(path);
} }
+25 -85
View File
@@ -247,12 +247,6 @@ typedef enum InventoryMoveResult {
INVENTORY_MOVE_RESULT_SUCCESS, INVENTORY_MOVE_RESULT_SUCCESS,
} InventoryMoveResult; } InventoryMoveResult;
typedef enum InventoryAmmoMoveResult {
INVENTORY_AMMO_MOVE_RESULT_FAILED = -1,
INVENTORY_AMMO_MOVE_RESULT_SUCCESS = 0,
INVENTORY_AMMO_MOVE_RESULT_BLOCKED = 1,
} InventoryAmmoMoveResult;
static int inventoryMessageListInit(); static int inventoryMessageListInit();
static int inventoryMessageListFree(); static int inventoryMessageListFree();
static bool _setup_inventory(int inventoryWindowType); static bool _setup_inventory(int inventoryWindowType);
@@ -286,7 +280,7 @@ static void barterDisplayTables(int win, Object* leftTable, Object* rightTable,
static void _container_enter(int keyCode, int inventoryWindowType); static void _container_enter(int keyCode, int inventoryWindowType);
static void _container_exit(int keyCode, int inventoryWindowType); static void _container_exit(int keyCode, int inventoryWindowType);
static int _drop_into_container(Object* container, Object* item, int sourceIndex, Object** itemSlot, int quantity); static int _drop_into_container(Object* container, Object* item, int sourceIndex, Object** itemSlot, int quantity);
static InventoryAmmoMoveResult _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode); static int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode);
static void _draw_amount(int value, int inventoryWindowType); static void _draw_amount(int value, int inventoryWindowType);
static int inventoryQuantitySelect(int inventoryWindowType, Object* item, int maximum, int defaultValue = 1); static int inventoryQuantitySelect(int inventoryWindowType, Object* item, int maximum, int defaultValue = 1);
static int inventoryQuantityWindowInit(int inventoryWindowType, Object* item); static int inventoryQuantityWindowInit(int inventoryWindowType, Object* item);
@@ -2446,18 +2440,14 @@ static void _inven_pickup(int buttonCode, int indexOffset)
itemIndex = 0; itemIndex = 0;
} }
} else { } else {
if (_drop_ammo_into_weapon(targetItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_SUCCESS) { if (_drop_ammo_into_weapon(targetItem, item, itemSlot, count, buttonCode) == 0) {
itemIndex = 0; itemIndex = 0;
} }
} }
} }
} }
if (immediate || itemIndex == -1) { if (immediate || pickUpFromSlot) {
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_MAIN_BACKPACK, item, nullptr)) {
goto inventory_move_done;
}
// TODO: Holy shit, needs refactoring. // TODO: Holy shit, needs refactoring.
*itemSlot = nullptr; *itemSlot = nullptr;
if (itemAdd(_inven_dude, item, 1)) { if (itemAdd(_inven_dude, item, 1)) {
@@ -2475,38 +2465,30 @@ static void _inven_pickup(int buttonCode, int indexOffset)
// default to first empty hand, or left hand if both are full // default to first empty hand, or left hand if both are full
bool left = gInventoryLeftHandItem == nullptr || gInventoryRightHandItem != nullptr; bool left = gInventoryLeftHandItem == nullptr || gInventoryRightHandItem != nullptr;
if (left) { if (left) {
_switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex); _switch_hand(item, &gInventoryLeftHandItem, itemSlot, buttonCode);
} else { } else {
_switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex); _switch_hand(item, &gInventoryRightHandItem, itemSlot, buttonCode);
} }
// drop in left hand slot // drop in left hand slot
} else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LEFT_HAND_SLOT_Y, INVENTORY_LEFT_HAND_SLOT_MAX_X, INVENTORY_LEFT_HAND_SLOT_MAX_Y)) { } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LEFT_HAND_SLOT_Y, INVENTORY_LEFT_HAND_SLOT_MAX_X, INVENTORY_LEFT_HAND_SLOT_MAX_Y)) {
if (gInventoryLeftHandItem != nullptr && itemGetType(gInventoryLeftHandItem) == ITEM_TYPE_CONTAINER && gInventoryLeftHandItem != item) { if (gInventoryLeftHandItem != nullptr && itemGetType(gInventoryLeftHandItem) == ITEM_TYPE_CONTAINER && gInventoryLeftHandItem != item) {
_drop_into_container(gInventoryLeftHandItem, item, itemIndex, itemSlot, count); _drop_into_container(gInventoryLeftHandItem, item, itemIndex, itemSlot, count);
} else if (gInventoryLeftHandItem == nullptr) { } else if (gInventoryLeftHandItem == nullptr || _drop_ammo_into_weapon(gInventoryLeftHandItem, item, itemSlot, count, buttonCode)) {
_switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex); _switch_hand(item, &gInventoryLeftHandItem, itemSlot, buttonCode);
} else if (_drop_ammo_into_weapon(gInventoryLeftHandItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_FAILED) {
_switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex);
} }
// drop in right hand slot // drop in right hand slot
} else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_RIGHT_HAND_SLOT_X, INVENTORY_RIGHT_HAND_SLOT_Y, INVENTORY_RIGHT_HAND_SLOT_MAX_X, INVENTORY_RIGHT_HAND_SLOT_MAX_Y)) { } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_RIGHT_HAND_SLOT_X, INVENTORY_RIGHT_HAND_SLOT_Y, INVENTORY_RIGHT_HAND_SLOT_MAX_X, INVENTORY_RIGHT_HAND_SLOT_MAX_Y)) {
if (gInventoryRightHandItem != nullptr && itemGetType(gInventoryRightHandItem) == ITEM_TYPE_CONTAINER && gInventoryRightHandItem != item) { if (gInventoryRightHandItem != nullptr && itemGetType(gInventoryRightHandItem) == ITEM_TYPE_CONTAINER && gInventoryRightHandItem != item) {
_drop_into_container(gInventoryRightHandItem, item, itemIndex, itemSlot, count); _drop_into_container(gInventoryRightHandItem, item, itemIndex, itemSlot, count);
} else if (gInventoryRightHandItem == nullptr) { } else if (gInventoryRightHandItem == nullptr || _drop_ammo_into_weapon(gInventoryRightHandItem, item, itemSlot, count, buttonCode)) {
_switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex);
} else if (_drop_ammo_into_weapon(gInventoryRightHandItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_FAILED) {
_switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex); _switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex);
} }
} else if ((immediate && itemGetType(item) == ITEM_TYPE_ARMOR) || mouseHitTestInWindow(gInventoryWindow, INVENTORY_ARMOR_SLOT_X, INVENTORY_ARMOR_SLOT_Y, INVENTORY_ARMOR_SLOT_MAX_X, INVENTORY_ARMOR_SLOT_MAX_Y)) { } else if ((immediate && itemGetType(item) == ITEM_TYPE_ARMOR) || mouseHitTestInWindow(gInventoryWindow, INVENTORY_ARMOR_SLOT_X, INVENTORY_ARMOR_SLOT_Y, INVENTORY_ARMOR_SLOT_MAX_X, INVENTORY_ARMOR_SLOT_MAX_Y)) {
if (itemGetType(item) == ITEM_TYPE_ARMOR) { if (itemGetType(item) == ITEM_TYPE_ARMOR) {
Object* currentArmor = gInventoryArmor; Object* currentArmor = gInventoryArmor;
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_ARMOR_SLOT, item, currentArmor)) {
goto inventory_move_done;
}
int itemAddResult = 0; int itemAddResult = 0;
if (itemIndex != -1) { if (itemIndex != -1) {
itemRemove(_inven_dude, item, 1); itemRemove(_inven_dude, item, 1);
@@ -2536,19 +2518,13 @@ static void _inven_pickup(int buttonCode, int indexOffset)
} }
} }
} else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_PC_BODY_VIEW_X, INVENTORY_PC_BODY_VIEW_Y, INVENTORY_PC_BODY_VIEW_MAX_X, INVENTORY_PC_BODY_VIEW_MAX_Y)) { } else if (mouseHitTestInWindow(gInventoryWindow, INVENTORY_PC_BODY_VIEW_X, INVENTORY_PC_BODY_VIEW_Y, INVENTORY_PC_BODY_VIEW_MAX_X, INVENTORY_PC_BODY_VIEW_MAX_Y)) {
if (_curr_stack == 0) { if (_curr_stack != 0) {
// Call the hook when dropping item on the PC portrait when not in a container. Return value is irrelevant.
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_CHARACTER_PORTRAIT, item, nullptr)) {
goto inventory_move_done;
}
} else {
// If we are looking inside nested inventory (such as backpack item), we see this item in the PC Body View instead of the player. // If we are looking inside nested inventory (such as backpack item), we see this item in the PC Body View instead of the player.
// So we drop item into it. // So we drop item into it.
_drop_into_container(_stack[_curr_stack - 1], item, itemIndex, itemSlot, count); _drop_into_container(_stack[_curr_stack - 1], item, itemIndex, itemSlot, count);
} }
} }
inventory_move_done:
_adjust_fid(); _adjust_fid();
inventoryRenderSummary(); inventoryRenderSummary();
_display_inventory(indexOffset, -1, INVENTORY_WINDOW_TYPE_NORMAL); _display_inventory(indexOffset, -1, INVENTORY_WINDOW_TYPE_NORMAL);
@@ -2574,16 +2550,7 @@ static void _switch_hand(Object* sourceItem, Object** targetSlot, Object** sourc
if (itemGetType(*targetSlot) == ITEM_TYPE_WEAPON && itemGetType(sourceItem) == ITEM_TYPE_AMMO) { if (itemGetType(*targetSlot) == ITEM_TYPE_WEAPON && itemGetType(sourceItem) == ITEM_TYPE_AMMO) {
return; return;
} }
}
HookInventoryMoveType targetSlotType = targetSlot == &gInventoryLeftHandItem
? HOOK_INVENTORYMOVE_LEFT_HAND
: HOOK_INVENTORYMOVE_RIGHT_HAND;
if (!scriptHooks_InventoryMove(targetSlotType, sourceItem, *targetSlot)) {
return;
}
if (*targetSlot != nullptr) {
if (sourceSlot != nullptr && (sourceSlot != &gInventoryArmor || itemGetType(*targetSlot) == ITEM_TYPE_ARMOR)) { if (sourceSlot != nullptr && (sourceSlot != &gInventoryArmor || itemGetType(*targetSlot) == ITEM_TYPE_ARMOR)) {
if (sourceSlot == &gInventoryArmor) { if (sourceSlot == &gInventoryArmor) {
adjustCritterStatsOnArmorChange(_stack[0], gInventoryArmor, *targetSlot); adjustCritterStatsOnArmorChange(_stack[0], gInventoryArmor, *targetSlot);
@@ -3966,14 +3933,8 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
int actionMenuItem = actionMenuItems[menuItemIndex]; int actionMenuItem = actionMenuItems[menuItemIndex];
switch (actionMenuItem) { switch (actionMenuItem) {
case GAME_MOUSE_ACTION_MENU_ITEM_DROP: { case GAME_MOUSE_ACTION_MENU_ITEM_DROP:
bool inventoryMoveAlreadyChecked = false;
if (itemSlot != nullptr) { if (itemSlot != nullptr) {
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) {
break;
}
inventoryMoveAlreadyChecked = true;
if (itemSlot == &gInventoryArmor) { if (itemSlot == &gInventoryArmor) {
adjustCritterStatsOnArmorChange(_stack[0], item, nullptr); adjustCritterStatsOnArmorChange(_stack[0], item, nullptr);
} }
@@ -3991,20 +3952,14 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
if (quantity > 0) { if (quantity > 0) {
if (quantity == 1) { if (quantity == 1) {
if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { itemSetMoney(item, 1);
itemSetMoney(item, 1); objectDrop(owner, item);
objectDrop(owner, item);
}
} else { } else {
if (itemRemove(owner, item, quantity - 1) == 0) { if (itemRemove(owner, item, quantity - 1) == 0) {
Object* item2; Object* item2;
if (_inven_from_button(keyCode, &item2, &itemSlot, &owner) != 0) { if (_inven_from_button(keyCode, &item2, &itemSlot, &owner) != 0) {
if (scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item2, nullptr)) { itemSetMoney(item2, quantity);
itemSetMoney(item2, quantity); objectDrop(owner, item2);
objectDrop(owner, item2);
} else {
itemAdd(owner, item, quantity - 1);
}
} else { } else {
itemAdd(owner, item, quantity - 1); itemAdd(owner, item, quantity - 1);
} }
@@ -4012,29 +3967,22 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
} }
} }
} else if (explosiveIsActiveExplosive(item->pid)) { } else if (explosiveIsActiveExplosive(item->pid)) {
if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { _dropped_explosive = 1;
_dropped_explosive = 1; objectDrop(owner, item);
objectDrop(owner, item);
}
} else { } else {
if (quantity > 1) { if (quantity > 1) {
quantity = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); quantity = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity);
for (int index = 0; index < quantity; index++) { for (int index = 0; index < quantity; index++) {
if (_inven_from_button(keyCode, &item, &itemSlot, &owner) != 0) { if (_inven_from_button(keyCode, &item, &itemSlot, &owner) != 0) {
if (scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { objectDrop(owner, item);
objectDrop(owner, item);
}
} }
} }
} else { } else {
if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) { objectDrop(owner, item);
objectDrop(owner, item);
}
} }
} }
break; break;
}
case GAME_MOUSE_ACTION_MENU_ITEM_LOOK: case GAME_MOUSE_ACTION_MENU_ITEM_LOOK:
if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) {
objectExamineFunc(_stack[0], item, gInventoryPrintItemDescriptionHandler); objectExamineFunc(_stack[0], item, gInventoryPrintItemDescriptionHandler);
@@ -5539,10 +5487,6 @@ static void _container_exit(int keyCode, int inventoryWindowType)
// 0x476464 // 0x476464
static int _drop_into_container(Object* container, Object* item, int sourceIndex, Object** itemSlot, int quantity) static int _drop_into_container(Object* container, Object* item, int sourceIndex, Object** itemSlot, int quantity)
{ {
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_CONTAINER, item, container)) {
return -1;
}
int quantityToMove; int quantityToMove;
if (quantity > 1) { if (quantity > 1) {
quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity); quantityToMove = inventoryQuantitySelect(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, quantity);
@@ -5581,22 +5525,18 @@ static int _drop_into_container(Object* container, Object* item, int sourceIndex
} }
// 0x47650C // 0x47650C
static InventoryAmmoMoveResult _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode) static int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode)
{ {
if (itemGetType(weapon) != ITEM_TYPE_WEAPON) { if (itemGetType(weapon) != ITEM_TYPE_WEAPON) {
return INVENTORY_AMMO_MOVE_RESULT_FAILED; return -1;
} }
if (itemGetType(ammo) != ITEM_TYPE_AMMO) { if (itemGetType(ammo) != ITEM_TYPE_AMMO) {
return INVENTORY_AMMO_MOVE_RESULT_FAILED; return -1;
} }
if (!weaponCanBeReloadedWith(weapon, ammo)) { if (!weaponCanBeReloadedWith(weapon, ammo)) {
return INVENTORY_AMMO_MOVE_RESULT_FAILED; return -1;
}
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_WEAPON_RELOAD, ammo, weapon)) {
return INVENTORY_AMMO_MOVE_RESULT_BLOCKED;
} }
int quantityToMove; int quantityToMove;
@@ -5607,7 +5547,7 @@ static InventoryAmmoMoveResult _drop_ammo_into_weapon(Object* weapon, Object* am
} }
if (quantityToMove == -1) { if (quantityToMove == -1) {
return INVENTORY_AMMO_MOVE_RESULT_FAILED; return -1;
} }
Object* sourceItem = ammo; Object* sourceItem = ammo;
@@ -5640,13 +5580,13 @@ static InventoryAmmoMoveResult _drop_ammo_into_weapon(Object* weapon, Object* am
} }
if (!isReloaded) { if (!isReloaded) {
return INVENTORY_AMMO_MOVE_RESULT_FAILED; return -1;
} }
const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, nullptr); const char* sfx = sfxBuildWeaponName(WEAPON_SOUND_EFFECT_READY, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, nullptr);
soundPlayFile(sfx); soundPlayFile(sfx);
return INVENTORY_AMMO_MOVE_RESULT_SUCCESS; return 0;
} }
// 0x47664C // 0x47664C
+48 -11
View File
@@ -1,5 +1,6 @@
#include "platform_compat.h" #include "platform_compat.h"
#include <assert.h>
#include <string.h> #include <string.h>
#ifdef _WIN32 #ifdef _WIN32
@@ -38,6 +39,23 @@ int compat_strnicmp(const char* string1, const char* string2, size_t size)
return SDL_strncasecmp(string1, string2, size); return SDL_strncasecmp(string1, string2, size);
} }
size_t compat_strlcpy(char* dest, const char* src, size_t size)
{
size_t srcLength = strlen(src);
if (size != 0) {
size_t copyLength = srcLength;
if (copyLength >= size) {
copyLength = size - 1;
}
memcpy(dest, src, copyLength);
dest[copyLength] = '\0';
}
return srcLength;
}
char* compat_strupr(char* string) char* compat_strupr(char* string)
{ {
return SDL_strupr(string); return SDL_strupr(string);
@@ -205,7 +223,7 @@ int compat_mkdir(const char* path)
{ {
char nativePath[COMPAT_MAX_PATH]; char nativePath[COMPAT_MAX_PATH];
strcpy(nativePath, path); strcpy(nativePath, path);
compat_windows_path_to_native(nativePath); compat_path_to_native(nativePath);
compat_resolve_path(nativePath); compat_resolve_path(nativePath);
#ifdef _WIN32 #ifdef _WIN32
@@ -230,7 +248,7 @@ FILE* compat_fopen(const char* path, const char* mode)
{ {
char nativePath[COMPAT_MAX_PATH]; char nativePath[COMPAT_MAX_PATH];
strcpy(nativePath, path); strcpy(nativePath, path);
compat_windows_path_to_native(nativePath); compat_path_to_native(nativePath);
compat_resolve_path(nativePath); compat_resolve_path(nativePath);
return fopen(nativePath, mode); return fopen(nativePath, mode);
} }
@@ -239,7 +257,7 @@ gzFile compat_gzopen(const char* path, const char* mode)
{ {
char nativePath[COMPAT_MAX_PATH]; char nativePath[COMPAT_MAX_PATH];
strcpy(nativePath, path); strcpy(nativePath, path);
compat_windows_path_to_native(nativePath); compat_path_to_native(nativePath);
compat_resolve_path(nativePath); compat_resolve_path(nativePath);
return gzopen(nativePath, mode); return gzopen(nativePath, mode);
} }
@@ -278,7 +296,7 @@ int compat_remove(const char* path)
{ {
char nativePath[COMPAT_MAX_PATH]; char nativePath[COMPAT_MAX_PATH];
strcpy(nativePath, path); strcpy(nativePath, path);
compat_windows_path_to_native(nativePath); compat_path_to_native(nativePath);
compat_resolve_path(nativePath); compat_resolve_path(nativePath);
return remove(nativePath); return remove(nativePath);
} }
@@ -287,28 +305,47 @@ int compat_rename(const char* oldFileName, const char* newFileName)
{ {
char nativeOldFileName[COMPAT_MAX_PATH]; char nativeOldFileName[COMPAT_MAX_PATH];
strcpy(nativeOldFileName, oldFileName); strcpy(nativeOldFileName, oldFileName);
compat_windows_path_to_native(nativeOldFileName); compat_path_to_native(nativeOldFileName);
compat_resolve_path(nativeOldFileName); compat_resolve_path(nativeOldFileName);
char nativeNewFileName[COMPAT_MAX_PATH]; char nativeNewFileName[COMPAT_MAX_PATH];
strcpy(nativeNewFileName, newFileName); strcpy(nativeNewFileName, newFileName);
compat_windows_path_to_native(nativeNewFileName); compat_path_to_native(nativeNewFileName);
compat_resolve_path(nativeNewFileName); compat_resolve_path(nativeNewFileName);
return rename(nativeOldFileName, nativeNewFileName); return rename(nativeOldFileName, nativeNewFileName);
} }
void compat_windows_path_to_native(char* path) void compat_path_to_native(char* path)
{ {
#ifndef _WIN32
char* pch = path; char* pch = path;
while (*pch != '\0') { while (*pch != '\0') {
if (*pch == '\\') { if (
#ifdef _WIN32
*pch == '/'
#else
*pch == '\\'
#endif
) {
#ifdef _WIN32
*pch = '\\';
#else
*pch = '/'; *pch = '/';
#endif
}
pch++;
}
}
void compat_path_to_windows(char* path)
{
char* pch = path;
while (*pch != '\0') {
if (*pch == '/') {
*pch = '\\';
} }
pch++; pch++;
} }
#endif
} }
void compat_resolve_path(char* path) void compat_resolve_path(char* path)
@@ -369,7 +406,7 @@ int compat_access(const char* path, int mode)
{ {
char nativePath[COMPAT_MAX_PATH]; char nativePath[COMPAT_MAX_PATH];
strcpy(nativePath, path); strcpy(nativePath, path);
compat_windows_path_to_native(nativePath); compat_path_to_native(nativePath);
compat_resolve_path(nativePath); compat_resolve_path(nativePath);
return access(nativePath, mode); return access(nativePath, mode);
} }
+3 -1
View File
@@ -24,6 +24,7 @@ namespace fallout {
int compat_stricmp(const char* string1, const char* string2); int compat_stricmp(const char* string1, const char* string2);
int compat_strnicmp(const char* string1, const char* string2, size_t size); int compat_strnicmp(const char* string1, const char* string2, size_t size);
size_t compat_strlcpy(char* dest, const char* src, size_t size);
char* compat_strupr(char* string); char* compat_strupr(char* string);
char* compat_strlwr(char* string); char* compat_strlwr(char* string);
char* compat_itoa(int value, char* buffer, int radix); char* compat_itoa(int value, char* buffer, int radix);
@@ -39,7 +40,8 @@ char* compat_fgets(char* buffer, int maxCount, FILE* stream);
char* compat_gzgets(gzFile stream, char* buffer, int maxCount); char* compat_gzgets(gzFile stream, char* buffer, int maxCount);
int compat_remove(const char* path); int compat_remove(const char* path);
int compat_rename(const char* oldFileName, const char* newFileName); int compat_rename(const char* oldFileName, const char* newFileName);
void compat_windows_path_to_native(char* path); void compat_path_to_native(char* path);
void compat_path_to_windows(char* path);
void compat_resolve_path(char* path); void compat_resolve_path(char* path);
int compat_access(const char* path, int mode); int compat_access(const char* path, int mode);
char* compat_strdup(const char* string); char* compat_strdup(const char* string);
-4
View File
@@ -570,10 +570,6 @@ int objectPickup(Object* critter, Object* item)
{ {
bool overriden = false; bool overriden = false;
if (critter == gDude && !scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_PICKUP, item, nullptr)) {
return 0;
}
if (item->sid != -1) { if (item->sid != -1) {
scriptSetObjects(item->sid, critter, item); scriptSetObjects(item->sid, critter, item);
scriptExecProc(item->sid, SCRIPT_PROC_PICKUP); scriptExecProc(item->sid, SCRIPT_PROC_PICKUP);
+9 -44
View File
@@ -476,14 +476,7 @@ private:
struct SfallArraysState { struct SfallArraysState {
std::unordered_map<ArrayId, std::unique_ptr<SFallArray>> arrays; std::unordered_map<ArrayId, std::unique_ptr<SFallArray>> arrays;
std::unordered_set<ArrayId> temporaryArrayIds; std::unordered_set<ArrayId> temporaryArrayIds;
// auto-incremented ID
int nextArrayId = kInitialArrayId; int nextArrayId = kInitialArrayId;
// special array ID for array expressions, contains the ID number of the currently created array
ArrayId expressionArrayId = 0;
// special stack for array expressions, contains ID numbers of the currently created arrays
std::vector<ArrayId> arrayExpressionStack;
}; };
static SfallArraysState* _state = nullptr; static SfallArraysState* _state = nullptr;
@@ -532,21 +525,6 @@ ArrayId CreateArray(int len, unsigned int flags)
_state->arrays.emplace(std::make_pair(arrayId, std::make_unique<SFallArrayList>(len, flags))); _state->arrays.emplace(std::make_pair(arrayId, std::make_unique<SFallArrayList>(len, flags)));
} }
if ((flags & SFALL_ARRAYFLAG_EXPR_PUSH) != 0) {
// When creating array for sub-expression, make sure to add array for base expression to stack
// This is messy, but required to support older scripts:
// - We must always assign expressionArrayId for one-layer expressions from older scripts to work like they did before
// - We can't directly push first arrayID into stack b/c no way to distinguish between start of an expression and normal temp_array call
// - Compiler will only add this flag for temp_array call generated from a sub-expression
// - So only on this second call we know we are in expression and expressionArrayId definitely contains arrayId of the first layer
auto& expressionStack = _state->arrayExpressionStack;
if (expressionStack.empty() && _state->expressionArrayId != 0) {
expressionStack.push_back(_state->expressionArrayId);
}
expressionStack.push_back(arrayId);
}
_state->expressionArrayId = arrayId;
return arrayId; return arrayId;
} }
@@ -636,41 +614,28 @@ void ResizeArray(ArrayId arrayId, int newLen)
arr->ResizeArray(newLen); arr->ResizeArray(newLen);
} }
void SetArrayFromExpression(const ProgramValue& key, const ProgramValue& val, Program* program) int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program)
{ {
ArrayId arrayId = !_state->arrayExpressionStack.empty() // CE: Sfall uses eponymous global variable which is always the id of the
? _state->arrayExpressionStack.back() // last created array.
: _state->expressionArrayId; ArrayId stackArrayId = _state->nextArrayId - 1;
auto arr = get_array_by_id(arrayId); auto arr = get_array_by_id(stackArrayId);
if (arr == nullptr) { if (arr == nullptr) {
return; return 0;
} }
auto size = arr->size(); auto size = arr->size();
if (size >= ARRAY_MAX_SIZE) { if (size >= ARRAY_MAX_SIZE) {
return; return 0;
} }
if (key.asInt() >= size) { if (key.asInt() >= size) {
arr->ResizeArray(size + 1); arr->ResizeArray(size + 1);
} }
SetArray(arrayId, key, val, false, program); SetArray(stackArrayId, key, val, false, program);
} return 0;
void PopExpressionArray()
{
auto& expressionStack = _state->arrayExpressionStack;
if (expressionStack.empty()) return;
expressionStack.pop_back();
// Reversing the hack from CreateArray
if (expressionStack.size() == 1) {
_state->expressionArrayId = expressionStack.back();
expressionStack.pop_back();
}
} }
ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program) ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program)
+1 -4
View File
@@ -8,8 +8,6 @@ namespace fallout {
#define SFALL_ARRAYFLAG_ASSOC (1) // is map #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_CONSTVAL (2) // don't update value of key if the key exists in map
#define SFALL_ARRAYFLAG_RESERVED (4) // has no significance in sfall or CE #define SFALL_ARRAYFLAG_RESERVED (4) // has no significance in sfall or CE
#define SFALL_ARRAYFLAG_EXPR_PUSH (32) // is created as part of array sub-expression
#define SFALL_ARRAYFLAG_EXPR_POP (64) // is used to indicate end of array sub-expression, not used in actual array
using ArrayId = unsigned int; using ArrayId = unsigned int;
@@ -26,8 +24,7 @@ void FreeArray(ArrayId arrayId);
void FixArray(ArrayId id); void FixArray(ArrayId id);
void ResizeArray(ArrayId arrayId, int newLen); void ResizeArray(ArrayId arrayId, int newLen);
void DeleteAllTempArrays(); void DeleteAllTempArrays();
void SetArrayFromExpression(const ProgramValue& key, const ProgramValue& val, Program* program); int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program);
void PopExpressionArray();
ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program); ProgramValue ScanArray(ArrayId arrayId, const ProgramValue& val, Program* program);
ArrayId ListAsArray(int type); ArrayId ListAsArray(int type);
+2
View File
@@ -62,6 +62,8 @@ bool sfallConfigInit(int argc, char** argv)
configSetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_CITIES_LIMIT_FIX, true); configSetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_CITIES_LIMIT_FIX, true);
configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, ""); configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, "");
configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, "scripts\\gl_*.int"); // TODO: remove
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PIPBOY_AVAILABLE_AT_GAMESTART, 0); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_PIPBOY_AVAILABLE_AT_GAMESTART, 0);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, 5); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_USE_WALK_DISTANCE, 5);
configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_OPEN_DOORS, 0); configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_AUTO_OPEN_DOORS, 0);

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