mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd34a745c | ||
|
|
a98d9d6c26 | ||
|
|
0a64cf4f6b | ||
|
|
b97c9864aa | ||
|
|
8d0009aa45 |
@@ -434,6 +434,7 @@ 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})
|
||||||
|
|||||||
@@ -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` | âś… | CE passes the raw `Attack*` as the final mixed argument, matching sfall's shape. |
|
| CombatDamage | `HOOK_COMBATDAMAGE` | âś… | - |
|
||||||
| 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 notes that sfall used VK codes there. |
|
| KeyPress | `HOOK_KEYPRESS` | âś… | Third hook arg is currently `0`; CE doesn't use VK codes. |
|
||||||
| 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` | đźš« | Et tu |
|
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
|
||||||
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
|
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
|
||||||
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
|
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
|
||||||
| CombatTurn | `HOOK_COMBATTURN` | đźš« | - |
|
| CombatTurn | `HOOK_COMBATTURN` | đźš« | - |
|
||||||
|
|||||||
@@ -197,11 +197,6 @@ 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,18 +13,23 @@ 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, documentFile.getName()))) {
|
if (!copyFile(contentResolver, documentFile, new File(dest, name))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (documentFile.isDirectory()) {
|
} else if (documentFile.isDirectory()) {
|
||||||
final File subdirectory = new File(dest, documentFile.getName());
|
final File subdirectory = new File(dest, name);
|
||||||
if (!subdirectory.exists()) {
|
|
||||||
subdirectory.mkdir();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!copyRecursively(contentResolver, documentFile, subdirectory)) {
|
if (!copyRecursively(contentResolver, documentFile, subdirectory)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -34,18 +39,22 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) {
|
private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) {
|
||||||
try {
|
final File parent = dest.getParentFile();
|
||||||
final InputStream inputStream = contentResolver.openInputStream(src.getUri());
|
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||||
final OutputStream outputStream = new FileOutputStream(dest);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,10 +2,12 @@ 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;
|
||||||
|
|
||||||
@@ -17,18 +19,18 @@ public class ImportActivity extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
launchImportPicker();
|
||||||
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.getData();
|
final Uri treeUri = resultData != null ? resultData.getData() : null;
|
||||||
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) {
|
if (treeDocument != null && treeDocument.isDirectory()) {
|
||||||
copyFiles(treeDocument);
|
copyFiles(treeDocument);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -42,20 +44,59 @@ public class ImportActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void copyFiles(DocumentFile treeDocument) {
|
private void copyFiles(DocumentFile treeDocument) {
|
||||||
ProgressDialog dialog = createProgressDialog();
|
final ProgressDialog dialog = createProgressDialog();
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
ContentResolver contentResolver = getContentResolver();
|
final ContentResolver contentResolver = getContentResolver();
|
||||||
File externalFilesDir = getExternalFilesDir(null);
|
final File externalFilesDir = getExternalFilesDir(null);
|
||||||
FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
|
final boolean success = externalFilesDir != null
|
||||||
|
&& FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
|
||||||
|
|
||||||
startMainActivity();
|
runOnUiThread(() -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
finish();
|
|
||||||
|
if (success) {
|
||||||
|
startMainActivity();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, R.string.import_failed, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish();
|
||||||
|
});
|
||||||
}).start();
|
}).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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
|||||||
@@ -2,4 +2,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
+2
-14
@@ -30,22 +30,10 @@ 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 `data/scripts/gl_<script_name>.int`
|
1. Move compiled `.int` file into game folder as `scripts/gl_<script_name>.int`
|
||||||
|
|
||||||
2. Change `ddraw.ini` and add this section:
|
2. Run game, check that game displays message about tests
|
||||||
```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
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,12 @@ 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");
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#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
|
||||||
+4
-3
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <fpattern/fpattern.h>
|
#include "fpattern_windows.h"
|
||||||
|
|
||||||
#include "platform_compat.h"
|
#include "platform_compat.h"
|
||||||
|
|
||||||
@@ -201,9 +201,10 @@ 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
|
||||||
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_match(pattern, entry->path)) {
|
if (fpattern_windows_match(pattern, entry->path)) {
|
||||||
strcpy(findFileData->fileName, entry->path);
|
strcpy(findFileData->fileName, entry->path);
|
||||||
strcpy(findFileData->pattern, pattern);
|
strcpy(findFileData->pattern, pattern);
|
||||||
findFileData->index = index;
|
findFileData->index = index;
|
||||||
@@ -219,7 +220,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_match(findFileData->pattern, entry->path)) {
|
if (fpattern_windows_match(findFileData->pattern, entry->path)) {
|
||||||
strcpy(findFileData->fileName, entry->path);
|
strcpy(findFileData->fileName, entry->path);
|
||||||
findFileData->index = index;
|
findFileData->index = index;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#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
|
||||||
+85
-25
@@ -247,6 +247,12 @@ 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);
|
||||||
@@ -280,7 +286,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 int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode);
|
static InventoryAmmoMoveResult _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);
|
||||||
@@ -2440,14 +2446,18 @@ static void _inven_pickup(int buttonCode, int indexOffset)
|
|||||||
itemIndex = 0;
|
itemIndex = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_drop_ammo_into_weapon(targetItem, item, itemSlot, count, buttonCode) == 0) {
|
if (_drop_ammo_into_weapon(targetItem, item, itemSlot, count, buttonCode) == INVENTORY_AMMO_MOVE_RESULT_SUCCESS) {
|
||||||
itemIndex = 0;
|
itemIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediate || pickUpFromSlot) {
|
if (immediate || itemIndex == -1) {
|
||||||
|
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)) {
|
||||||
@@ -2465,30 +2475,38 @@ 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, buttonCode);
|
_switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex);
|
||||||
} else {
|
} else {
|
||||||
_switch_hand(item, &gInventoryRightHandItem, itemSlot, buttonCode);
|
_switch_hand(item, &gInventoryRightHandItem, itemSlot, itemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 || _drop_ammo_into_weapon(gInventoryLeftHandItem, item, itemSlot, count, buttonCode)) {
|
} else if (gInventoryLeftHandItem == nullptr) {
|
||||||
_switch_hand(item, &gInventoryLeftHandItem, itemSlot, buttonCode);
|
_switch_hand(item, &gInventoryLeftHandItem, itemSlot, itemIndex);
|
||||||
|
} 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 || _drop_ammo_into_weapon(gInventoryRightHandItem, item, itemSlot, count, buttonCode)) {
|
} else if (gInventoryRightHandItem == nullptr) {
|
||||||
|
_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);
|
||||||
@@ -2518,13 +2536,19 @@ 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);
|
||||||
@@ -2550,7 +2574,16 @@ 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);
|
||||||
@@ -3933,8 +3966,14 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -3952,14 +3991,20 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
|
|||||||
|
|
||||||
if (quantity > 0) {
|
if (quantity > 0) {
|
||||||
if (quantity == 1) {
|
if (quantity == 1) {
|
||||||
itemSetMoney(item, 1);
|
if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) {
|
||||||
objectDrop(owner, item);
|
itemSetMoney(item, 1);
|
||||||
|
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) {
|
||||||
itemSetMoney(item2, quantity);
|
if (scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item2, nullptr)) {
|
||||||
objectDrop(owner, item2);
|
itemSetMoney(item2, quantity);
|
||||||
|
objectDrop(owner, item2);
|
||||||
|
} else {
|
||||||
|
itemAdd(owner, item, quantity - 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
itemAdd(owner, item, quantity - 1);
|
itemAdd(owner, item, quantity - 1);
|
||||||
}
|
}
|
||||||
@@ -3967,22 +4012,29 @@ static void inventoryWindowOpenContextMenu(int keyCode, int inventoryWindowType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (explosiveIsActiveExplosive(item->pid)) {
|
} else if (explosiveIsActiveExplosive(item->pid)) {
|
||||||
_dropped_explosive = 1;
|
if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) {
|
||||||
objectDrop(owner, item);
|
_dropped_explosive = 1;
|
||||||
|
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) {
|
||||||
objectDrop(owner, item);
|
if (scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) {
|
||||||
|
objectDrop(owner, item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
objectDrop(owner, item);
|
if (inventoryMoveAlreadyChecked || scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_GROUND, item, nullptr)) {
|
||||||
|
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);
|
||||||
@@ -5487,6 +5539,10 @@ 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);
|
||||||
@@ -5525,18 +5581,22 @@ static int _drop_into_container(Object* container, Object* item, int sourceIndex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 0x47650C
|
// 0x47650C
|
||||||
static int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoItemSlot, int quantity, int keyCode)
|
static InventoryAmmoMoveResult _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 -1;
|
return INVENTORY_AMMO_MOVE_RESULT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemGetType(ammo) != ITEM_TYPE_AMMO) {
|
if (itemGetType(ammo) != ITEM_TYPE_AMMO) {
|
||||||
return -1;
|
return INVENTORY_AMMO_MOVE_RESULT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!weaponCanBeReloadedWith(weapon, ammo)) {
|
if (!weaponCanBeReloadedWith(weapon, ammo)) {
|
||||||
return -1;
|
return INVENTORY_AMMO_MOVE_RESULT_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scriptHooks_InventoryMove(HOOK_INVENTORYMOVE_WEAPON_RELOAD, ammo, weapon)) {
|
||||||
|
return INVENTORY_AMMO_MOVE_RESULT_BLOCKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int quantityToMove;
|
int quantityToMove;
|
||||||
@@ -5547,7 +5607,7 @@ static int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (quantityToMove == -1) {
|
if (quantityToMove == -1) {
|
||||||
return -1;
|
return INVENTORY_AMMO_MOVE_RESULT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object* sourceItem = ammo;
|
Object* sourceItem = ammo;
|
||||||
@@ -5580,13 +5640,13 @@ static int _drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** ammoIte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isReloaded) {
|
if (!isReloaded) {
|
||||||
return -1;
|
return INVENTORY_AMMO_MOVE_RESULT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 0;
|
return INVENTORY_AMMO_MOVE_RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x47664C
|
// 0x47664C
|
||||||
|
|||||||
@@ -570,6 +570,10 @@ 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);
|
||||||
|
|||||||
+44
-9
@@ -476,7 +476,14 @@ 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;
|
||||||
@@ -525,6 +532,21 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,28 +636,41 @@ void ResizeArray(ArrayId arrayId, int newLen)
|
|||||||
arr->ResizeArray(newLen);
|
arr->ResizeArray(newLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program)
|
void SetArrayFromExpression(const ProgramValue& key, const ProgramValue& val, Program* program)
|
||||||
{
|
{
|
||||||
// CE: Sfall uses eponymous global variable which is always the id of the
|
ArrayId arrayId = !_state->arrayExpressionStack.empty()
|
||||||
// last created array.
|
? _state->arrayExpressionStack.back()
|
||||||
ArrayId stackArrayId = _state->nextArrayId - 1;
|
: _state->expressionArrayId;
|
||||||
|
|
||||||
auto arr = get_array_by_id(stackArrayId);
|
auto arr = get_array_by_id(arrayId);
|
||||||
if (arr == nullptr) {
|
if (arr == nullptr) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto size = arr->size();
|
auto size = arr->size();
|
||||||
if (size >= ARRAY_MAX_SIZE) {
|
if (size >= ARRAY_MAX_SIZE) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.asInt() >= size) {
|
if (key.asInt() >= size) {
|
||||||
arr->ResizeArray(size + 1);
|
arr->ResizeArray(size + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetArray(stackArrayId, key, val, false, program);
|
SetArray(arrayId, 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)
|
||||||
|
|||||||
+4
-1
@@ -8,6 +8,8 @@ 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;
|
||||||
|
|
||||||
@@ -24,7 +26,8 @@ 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();
|
||||||
int StackArray(const ProgramValue& key, const ProgramValue& val, Program* program);
|
void SetArrayFromExpression(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);
|
||||||
|
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ 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, "");
|
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ namespace fallout {
|
|||||||
#define SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY "ExtraGameMsgFileList"
|
#define SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY "ExtraGameMsgFileList"
|
||||||
#define SFALL_CONFIG_NUMBERS_IS_DIALOG_KEY "NumbersInDialogue"
|
#define SFALL_CONFIG_NUMBERS_IS_DIALOG_KEY "NumbersInDialogue"
|
||||||
#define SFALL_CONFIG_INI_CONFIG_FOLDER "IniConfigFolder"
|
#define SFALL_CONFIG_INI_CONFIG_FOLDER "IniConfigFolder"
|
||||||
#define SFALL_CONFIG_GLOBAL_SCRIPT_PATHS "GlobalScriptPaths"
|
|
||||||
#define SFALL_CONFIG_AUTO_QUICK_SAVE "AutoQuickSave"
|
#define SFALL_CONFIG_AUTO_QUICK_SAVE "AutoQuickSave"
|
||||||
#define SFALL_CONFIG_VERSION_STRING "VersionString"
|
#define SFALL_CONFIG_VERSION_STRING "VersionString"
|
||||||
#define SFALL_CONFIG_CONFIG_FILE "ConfigFile"
|
#define SFALL_CONFIG_CONFIG_FILE "ConfigFile"
|
||||||
|
|||||||
+11
-31
@@ -37,39 +37,19 @@ bool sfall_gl_scr_init()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* paths;
|
// CE: always use "scripts\gl*.int" as global script path
|
||||||
configGetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, &paths);
|
const char* scriptPath = "scripts\\gl*.int";
|
||||||
|
const char* dir = "scripts";
|
||||||
char* curr = paths;
|
char** files;
|
||||||
while (curr != nullptr && *curr != '\0') {
|
int filesLength = fileNameListInit(scriptPath, &files);
|
||||||
char* end = strchr(curr, ',');
|
if (filesLength != 0) {
|
||||||
if (end != nullptr) {
|
for (int index = 0; index < filesLength; index++) {
|
||||||
*end = '\0';
|
char path[COMPAT_MAX_PATH];
|
||||||
|
snprintf(path, sizeof(path), "%s\\%s", dir, files[index]);
|
||||||
|
state->paths.push_back(std::string { path });
|
||||||
}
|
}
|
||||||
|
|
||||||
char drive[COMPAT_MAX_DRIVE];
|
fileNameListFree(&files, 0);
|
||||||
char dir[COMPAT_MAX_DIR];
|
|
||||||
compat_splitpath(curr, drive, dir, nullptr, nullptr);
|
|
||||||
|
|
||||||
char** files;
|
|
||||||
int filesLength = fileNameListInit(curr, &files);
|
|
||||||
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());
|
std::sort(state->paths.begin(), state->paths.end());
|
||||||
|
|||||||
+14
-4
@@ -957,6 +957,14 @@ static void op_temp_array(Program* program)
|
|||||||
{
|
{
|
||||||
auto flags = programStackPopInteger(program);
|
auto flags = programStackPopInteger(program);
|
||||||
auto len = programStackPopInteger(program);
|
auto len = programStackPopInteger(program);
|
||||||
|
|
||||||
|
// Special case for array sub-expressions.
|
||||||
|
if ((flags & SFALL_ARRAYFLAG_EXPR_POP) != 0) {
|
||||||
|
PopExpressionArray();
|
||||||
|
programStackPushInteger(program, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto arrayId = CreateTempArray(len, flags);
|
auto arrayId = CreateTempArray(len, flags);
|
||||||
programStackPushInteger(program, arrayId);
|
programStackPushInteger(program, arrayId);
|
||||||
}
|
}
|
||||||
@@ -986,13 +994,15 @@ static void op_set_array(Program* program)
|
|||||||
SetArray(arrayId, key, value, true, program);
|
SetArray(arrayId, key, value, true, program);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This special opcode is used to implement array expressions.
|
||||||
|
// It should always push 0 on the stack.
|
||||||
// arrayexpr
|
// arrayexpr
|
||||||
static void op_stack_array(Program* program)
|
static void op_arrayexpr(Program* program)
|
||||||
{
|
{
|
||||||
auto value = programStackPopValue(program);
|
auto value = programStackPopValue(program);
|
||||||
auto key = programStackPopValue(program);
|
auto key = programStackPopValue(program);
|
||||||
auto returnValue = StackArray(key, value, program);
|
SetArrayFromExpression(key, value, program);
|
||||||
programStackPushInteger(program, returnValue);
|
programStackPushInteger(program, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan_array
|
// scan_array
|
||||||
@@ -1654,7 +1664,7 @@ void sfallOpcodesInit()
|
|||||||
// 0x8256 - int array_key(int array, int index)
|
// 0x8256 - int array_key(int array, int index)
|
||||||
interpreterRegisterOpcode(0x8256, op_get_array_key);
|
interpreterRegisterOpcode(0x8256, op_get_array_key);
|
||||||
// 0x8257 - int arrayexpr(any key, any value)
|
// 0x8257 - int arrayexpr(any key, any value)
|
||||||
interpreterRegisterOpcode(0x8257, op_stack_array);
|
interpreterRegisterOpcode(0x8257, op_arrayexpr);
|
||||||
|
|
||||||
// 0x81a0 - void set_pickpocket_max(int percentage)
|
// 0x81a0 - void set_pickpocket_max(int percentage)
|
||||||
// 0x81a1 - void set_hit_chance_max(int percentage)
|
// 0x81a1 - void set_hit_chance_max(int percentage)
|
||||||
|
|||||||
@@ -164,6 +164,36 @@ void scriptHooks_GameModeChange(int exit, int previousGameMode)
|
|||||||
ScriptHookCall(HOOK_GAMEMODECHANGE, 0, { exit, previousGameMode }).call();
|
ScriptHookCall(HOOK_GAMEMODECHANGE, 0, { exit, previousGameMode }).call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Runs before moving items between inventory slots in dude interface. You can override the action.
|
||||||
|
|
||||||
|
int arg0 - Target slot:
|
||||||
|
0 - main backpack
|
||||||
|
1 - left hand
|
||||||
|
2 - right hand
|
||||||
|
3 - armor slot
|
||||||
|
4 - weapon, when reloading it by dropping ammo
|
||||||
|
5 - container, like bag/backpack
|
||||||
|
6 - dropping on the ground
|
||||||
|
7 - picking up item
|
||||||
|
8 - dropping item on the character portrait
|
||||||
|
Item arg1 - Item being moved
|
||||||
|
Item arg2 - Item being replaced, weapon being reloaded, or container being filled (can be 0)
|
||||||
|
|
||||||
|
int ret0 - Override setting (-1 - use engine handler, any other value - prevent relocation)
|
||||||
|
*/
|
||||||
|
bool scriptHooks_InventoryMove(HookInventoryMoveType actionType, Object* item, Object* targetItem)
|
||||||
|
{
|
||||||
|
ScriptHookCall hook(HOOK_INVENTORYMOVE, 1, { actionType, item, targetItem });
|
||||||
|
hook.call();
|
||||||
|
|
||||||
|
if (hook.numReturnValues() <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hook.getReturnValueAt(0).asInt() == -1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Runs when Fallout is calculating the chances of an attack striking a target.
|
Runs when Fallout is calculating the chances of an attack striking a target.
|
||||||
Runs after the hit chance is fully calculated normally, including applying the 95% cap.
|
Runs after the hit chance is fully calculated normally, including applying the 95% cap.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user