mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa69e343dc | ||
|
|
69cc853475 | ||
|
|
331bf79ec0 | ||
|
|
456f44b2a3 | ||
|
|
8d8b19e979 | ||
|
|
80bd1b2c8b | ||
|
|
0804ccac87 | ||
|
|
4ec9f88986 | ||
|
|
491384522f | ||
|
|
49f4376e89 | ||
|
|
f4688409fb | ||
|
|
ab2a09610d | ||
|
|
cef24823f4 | ||
|
|
63d79e4bfd | ||
|
|
e42d8021c1 | ||
|
|
8254c758fe |
+79
-4
@@ -38,7 +38,9 @@ endif()
|
||||
# Git Info
|
||||
include("cmake/gitver.cmake")
|
||||
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
# Shared engine sources — used by both game and mapper targets.
|
||||
# Excludes main.cc/main.h (game-only entry logic).
|
||||
set(FALLOUT_ENGINE_SOURCES
|
||||
"src/actions.cc"
|
||||
"src/actions.h"
|
||||
"src/animation.cc"
|
||||
@@ -154,8 +156,6 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"src/lips.h"
|
||||
"src/loadsave.cc"
|
||||
"src/loadsave.h"
|
||||
"src/main.cc"
|
||||
"src/main.h"
|
||||
"src/mainmenu.cc"
|
||||
"src/mainmenu.h"
|
||||
"src/map_defs.h"
|
||||
@@ -268,7 +268,7 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"src/xfile.h"
|
||||
)
|
||||
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
set(FALLOUT_PLATFORM_SOURCES
|
||||
"src/audio_engine.cc"
|
||||
"src/audio_engine.h"
|
||||
"src/delay.cc"
|
||||
@@ -316,6 +316,14 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"third_party/lodepng/lodepng.cpp"
|
||||
)
|
||||
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
${FALLOUT_ENGINE_SOURCES}
|
||||
"src/main.cc"
|
||||
"src/main.h"
|
||||
)
|
||||
|
||||
target_sources(${EXECUTABLE_NAME} PUBLIC ${FALLOUT_PLATFORM_SOURCES})
|
||||
|
||||
target_include_directories(${EXECUTABLE_NAME} PUBLIC "third_party/lodepng")
|
||||
|
||||
set(FALLOUT_HAVE_STB_VORBIS OFF)
|
||||
@@ -561,6 +569,73 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
|
||||
if(WIN32)
|
||||
add_executable(mapper-ce WIN32)
|
||||
else()
|
||||
add_executable(mapper-ce)
|
||||
endif()
|
||||
|
||||
target_sources(mapper-ce PUBLIC
|
||||
${FALLOUT_ENGINE_SOURCES}
|
||||
${FALLOUT_PLATFORM_SOURCES}
|
||||
"src/mapper/map_func.cc"
|
||||
"src/mapper/map_func.h"
|
||||
"src/mapper/mapper.cc"
|
||||
"src/mapper/mapper.h"
|
||||
"src/mapper/mp_instance.cc"
|
||||
"src/mapper/mp_instance.h"
|
||||
"src/mapper/mp_proto.cc"
|
||||
"src/mapper/mp_proto.h"
|
||||
"src/mapper/mp_scrpt.cc"
|
||||
"src/mapper/mp_scrpt.h"
|
||||
"src/mapper/mp_targt.cc"
|
||||
"src/mapper/mp_targt.h"
|
||||
"src/mapper/mp_text.cc"
|
||||
"src/mapper/mp_text.h"
|
||||
)
|
||||
|
||||
target_compile_definitions(mapper-ce PUBLIC FALLOUT_MAPPER)
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(mapper-ce PUBLIC
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_CRT_NONSTDC_NO_WARNINGS
|
||||
NOMINMAX
|
||||
WIN32_LEAN_AND_MEAN
|
||||
_STATIC_CPPLIB
|
||||
)
|
||||
target_link_libraries(mapper-ce
|
||||
winmm
|
||||
debug libcpmtd
|
||||
optimized libcpmt
|
||||
)
|
||||
target_sources(mapper-ce PUBLIC
|
||||
"os/windows/fallout2-ce.rc"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(mapper-ce "-framework CoreFoundation")
|
||||
set_target_properties(mapper-ce PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(mapper-ce PUBLIC "third_party/lodepng")
|
||||
target_include_directories(mapper-ce PRIVATE "src")
|
||||
target_include_directories(mapper-ce PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_include_directories(mapper-ce PRIVATE ${SDL2_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(mapper-ce
|
||||
fpattern::fpattern
|
||||
fpattern_windows::fpattern_windows
|
||||
${ZLIB_LIBRARIES}
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
install(TARGETS ${EXECUTABLE_NAME} DESTINATION "Payload")
|
||||
|
||||
@@ -101,8 +101,8 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| Steal | `HOOK_STEAL` | 🚫 | Et tu |
|
||||
| WithinPerception | `HOOK_WITHINPERCEPTION` | ✅ | - |
|
||||
| InventoryMove | `HOOK_INVENTORYMOVE` | ✅ | - |
|
||||
| InvenWield | `HOOK_INVENWIELD` | 🚫 | - |
|
||||
| AdjustFID | `HOOK_ADJUSTFID` | 🚫 | - |
|
||||
| InvenWield | `HOOK_INVENWIELD` | ✅ | - |
|
||||
| AdjustFID | `HOOK_ADJUSTFID` | ✅ | Second hook arg currently matches the first because CE has no internal FID modifiers like Hero Appearance. |
|
||||
| CombatTurn | `HOOK_COMBATTURN` | ✅ | - |
|
||||
| StdProcedure | `HOOK_STDPROCEDURE` | ✅ | - |
|
||||
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | ✅ | - |
|
||||
@@ -124,5 +124,5 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| AdjustRads | `HOOK_ADJUSTRADS` | 🚫 | (maybe) |
|
||||
| RollCheck | `HOOK_ROLLCHECK` | 🚫 | - |
|
||||
| BestWeapon | `HOOK_BESTWEAPON` | 🚫 | - |
|
||||
| CanUseWeapon | `HOOK_CANUSEWEAPON` | 🚫 | - |
|
||||
| CanUseWeapon | `HOOK_CANUSEWEAPON` | ✅ | - |
|
||||
| BuildSfxWeapon | `HOOK_BUILDSFXWEAPON` | 🚫 | - |
|
||||
|
||||
@@ -22,6 +22,10 @@ save_text_maps=0
|
||||
show_pid_numbers=0
|
||||
sort_script_list=0
|
||||
use_art_not_protos=0
|
||||
; Allows to load a certain map right away after loading the mapper.
|
||||
map=denbus2
|
||||
; Allows to disable grid item picker and use a simpler list-based picker.
|
||||
use_grid_item_picker=1
|
||||
|
||||
[preferences]
|
||||
brightness=1.000000
|
||||
|
||||
+89
-1
@@ -355,6 +355,14 @@ int artIsObjectTypeHidden(int objectType)
|
||||
return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].flags & 1 : 0;
|
||||
}
|
||||
|
||||
// 0x409DF0
|
||||
void artToggleObjectTypeHidden(int objectType)
|
||||
{
|
||||
if (objectType >= 0 && objectType < OBJ_TYPE_COUNT) {
|
||||
gArtListDescriptions[objectType].flags ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x418F7C
|
||||
int artGetFidgetCount(int headFid)
|
||||
{
|
||||
@@ -439,7 +447,87 @@ void artRender(int fid, unsigned char* dest, int width, int height, int pitch)
|
||||
// mapper2.exe: 0x40A03C
|
||||
int art_list_str(int fid, char* name)
|
||||
{
|
||||
// TODO: Incomplete.
|
||||
if (fid == -1 || name == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int objectType = (fid & 0xF000000) >> 24;
|
||||
int index = fid & 0xFFF;
|
||||
|
||||
const char* typeName = artGetObjectTypeName(objectType);
|
||||
if (typeName == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char path[260];
|
||||
snprintf(path, sizeof(path), "%s%s%s\\%s.lst", _cd_path_base, "art\\", typeName, typeName);
|
||||
|
||||
File* stream = fileOpen(path, "rt");
|
||||
if (stream == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buffer[260];
|
||||
int line = 0;
|
||||
bool found = false;
|
||||
while (fileReadString(buffer, sizeof(buffer), stream) != nullptr) {
|
||||
if (line == index) {
|
||||
char* p = buffer;
|
||||
while (*p) {
|
||||
if (*p == ' ' || *p == '\n') {
|
||||
*p = '\0';
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
char* dst = name;
|
||||
const char* src = buffer;
|
||||
do {
|
||||
*dst++ = *src++;
|
||||
*dst++ = *src++;
|
||||
} while (dst[-2] != '\0' || dst[-1] != '\0');
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
line++;
|
||||
}
|
||||
|
||||
fileClose(stream);
|
||||
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
// art_list_index
|
||||
int artListIndex(int objectType, const char* name)
|
||||
{
|
||||
if (objectType < 0 || objectType >= OBJ_TYPE_COUNT) return -1;
|
||||
if (gArtListDescriptions[objectType].fileNames == nullptr) return -1;
|
||||
|
||||
char upperName[13] = { 0 };
|
||||
strncpy(upperName, name, 12);
|
||||
upperName[12] = '\0';
|
||||
compat_strupr(upperName);
|
||||
|
||||
int length = gArtListDescriptions[objectType].fileNamesLength;
|
||||
const char* fileNames = gArtListDescriptions[objectType].fileNames;
|
||||
|
||||
for (int index = 0; index < length; index++) {
|
||||
const char* entry = fileNames + index * 13;
|
||||
|
||||
char upperEntry[13];
|
||||
strncpy(upperEntry, entry, 12);
|
||||
upperEntry[12] = '\0';
|
||||
compat_strupr(upperEntry);
|
||||
|
||||
char* p = upperEntry;
|
||||
while (*p && ((*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_'))
|
||||
p++;
|
||||
*p = '\0';
|
||||
|
||||
if (strcmp(upperEntry, upperName) == 0) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ void artReset();
|
||||
void artExit();
|
||||
char* artGetObjectTypeName(int objectType);
|
||||
int artIsObjectTypeHidden(int objectType);
|
||||
void artToggleObjectTypeHidden(int objectType);
|
||||
int artGetFidgetCount(int headFid);
|
||||
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
|
||||
int art_list_str(int fid, char* name);
|
||||
@@ -150,6 +151,7 @@ int _art_alias_num(int index);
|
||||
int artCritterFidShouldRun(int fid);
|
||||
int artAliasFid(int fid);
|
||||
int buildFid(int objectType, int frmId, int animType, int weaponCode, int rotation);
|
||||
int artListIndex(int objectType, const char* name);
|
||||
Art* artLoad(const char* path);
|
||||
int artRead(const char* path, unsigned char* data);
|
||||
int artWrite(const char* path, unsigned char* data);
|
||||
|
||||
@@ -264,7 +264,6 @@ int audioOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPtr)
|
||||
|
||||
if (info != nullptr) {
|
||||
info->sampleRate = audioFile->sampleRate;
|
||||
info->channels = audioFile->channels;
|
||||
info->bitsPerSample = audioFile->bitsPerSample;
|
||||
}
|
||||
if (isMemoryBackedPtr != nullptr) {
|
||||
|
||||
@@ -100,7 +100,6 @@ int audioFileOpen(const char* fname, AudioFileInfo* info, bool* isMemoryBackedPt
|
||||
audioFile->fileSize *= 2;
|
||||
if (info != nullptr) {
|
||||
info->sampleRate = audioFile->sampleRate;
|
||||
info->channels = audioFile->channels;
|
||||
info->bitsPerSample = 16;
|
||||
}
|
||||
if (isMemoryBackedPtr != nullptr) {
|
||||
|
||||
+24
-14
@@ -1972,31 +1972,41 @@ static Object* _ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon
|
||||
// 0x4298EC
|
||||
static bool _ai_can_use_weapon(Object* critter, Object* weapon, int hitMode)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
int damageFlags = critter->data.critter.combat.results;
|
||||
if ((damageFlags & DAM_CRIP_ARM_LEFT) != 0 && (damageFlags & DAM_CRIP_ARM_RIGHT) != 0) {
|
||||
return false;
|
||||
result = false;
|
||||
}
|
||||
|
||||
if ((damageFlags & DAM_CRIP_ARM_ANY) != 0 && weaponIsTwoHanded(weapon)) {
|
||||
return false;
|
||||
if (result && (damageFlags & DAM_CRIP_ARM_ANY) != 0 && weaponIsTwoHanded(weapon)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
int rotation = critter->rotation + 1;
|
||||
int animationCode = weaponGetAnimationCode(weapon);
|
||||
int weaponAnimationCode = weaponGetAnimationForHitMode(weapon, hitMode);
|
||||
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, weaponAnimationCode, animationCode, rotation);
|
||||
if (!artExists(fid)) {
|
||||
return false;
|
||||
if (result) {
|
||||
int rotation = critter->rotation + 1;
|
||||
int animationCode = weaponGetAnimationCode(weapon);
|
||||
int weaponAnimationCode = weaponGetAnimationForHitMode(weapon, hitMode);
|
||||
int fid = buildFid(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, weaponAnimationCode, animationCode, rotation);
|
||||
if (!artExists(fid)) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
int skill = weaponGetSkillForHitMode(weapon, hitMode);
|
||||
AiPacket* ai = aiGetPacket(critter);
|
||||
if (skillGetValue(critter, skill) < ai->min_to_hit) {
|
||||
return false;
|
||||
if (result) {
|
||||
int skill = weaponGetSkillForHitMode(weapon, hitMode);
|
||||
if (skillGetValue(critter, skill) < ai->min_to_hit) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
int attackType = weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);
|
||||
return _caiHasWeapPrefType(ai, attackType) != 0;
|
||||
if (result) {
|
||||
int attackType = weaponGetAttackTypeForHitMode(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);
|
||||
result = _caiHasWeapPrefType(ai, attackType) != 0;
|
||||
}
|
||||
|
||||
return scriptHooks_CanUseWeapon(result, critter, weapon, hitMode);
|
||||
}
|
||||
|
||||
// 0x4299A0
|
||||
|
||||
@@ -29,34 +29,82 @@ long fileTell(File* stream);
|
||||
void fileRewind(File* stream);
|
||||
int fileEof(File* stream);
|
||||
int fileReadUInt8(File* stream, unsigned char* valuePtr);
|
||||
|
||||
// Reads a 16-bit big-endian integer from stream and stores it in host byte order.
|
||||
int fileReadInt16(File* stream, short* valuePtr);
|
||||
|
||||
// Reads a 16-bit big-endian unsigned integer from stream and stores it in host byte order.
|
||||
int fileReadUInt16(File* stream, unsigned short* valuePtr);
|
||||
|
||||
// Reads a 32-bit big-endian integer from stream and stores it in host byte order.
|
||||
int fileReadInt32(File* stream, int* valuePtr);
|
||||
|
||||
// Reads a 32-bit big-endian unsigned integer from stream and stores it in host byte order.
|
||||
int fileReadUInt32(File* stream, unsigned int* valuePtr);
|
||||
|
||||
// Reads a 32-bit big-endian integer from stream and stores it in host byte order (alias).
|
||||
int _db_freadInt(File* stream, int* valuePtr);
|
||||
|
||||
// Reads a 32-bit big-endian floating-point value from stream and stores it in host byte order.
|
||||
int fileReadFloat(File* stream, float* valuePtr);
|
||||
|
||||
// Reads a 32-bit big-endian integer from stream and stores the boolean equivalent in host byte order.
|
||||
int fileReadBool(File* stream, bool* valuePtr);
|
||||
int fileWriteUInt8(File* stream, unsigned char value);
|
||||
|
||||
// Writes a 16-bit integer to stream in big-endian byte order.
|
||||
int fileWriteInt16(File* stream, short value);
|
||||
|
||||
// Writes a 16-bit unsigned integer to stream in big-endian byte order.
|
||||
int fileWriteUInt16(File* stream, unsigned short value);
|
||||
|
||||
// Writes a 32-bit integer to stream in big-endian byte order.
|
||||
int fileWriteInt32(File* stream, int value);
|
||||
|
||||
// Writes a 32-bit integer to stream in big-endian byte order (alias).
|
||||
int _db_fwriteLong(File* stream, int value);
|
||||
|
||||
// Writes a 32-bit unsigned integer to stream in big-endian byte order.
|
||||
int fileWriteUInt32(File* stream, unsigned int value);
|
||||
|
||||
// Writes a 32-bit floating-point value to stream in big-endian byte order.
|
||||
int fileWriteFloat(File* stream, float value);
|
||||
|
||||
// Writes a boolean value to stream as a 32-bit big-endian integer (1 or 0).
|
||||
int fileWriteBool(File* stream, bool value);
|
||||
int fileReadUInt8List(File* stream, unsigned char* arr, int count);
|
||||
int fileReadFixedLengthString(File* stream, char* string, int length);
|
||||
|
||||
// Reads a list of 16-bit big-endian integers from stream into arr (in host byte order).
|
||||
int fileReadInt16List(File* stream, short* arr, int count);
|
||||
|
||||
// Reads a list of 16-bit big-endian unsigned integers from stream into arr (in host byte order).
|
||||
int fileReadUInt16List(File* stream, unsigned short* arr, int count);
|
||||
|
||||
// Reads a list of 32-bit big-endian integers from stream into arr (in host byte order).
|
||||
int fileReadInt32List(File* stream, int* arr, int count);
|
||||
|
||||
// Reads a list of 32-bit big-endian integers from stream into arr (in host byte order, alias).
|
||||
int _db_freadIntCount(File* stream, int* arr, int count);
|
||||
|
||||
// Reads a list of 32-bit big-endian unsigned integers from stream into arr (in host byte order).
|
||||
int fileReadUInt32List(File* stream, unsigned int* arr, int count);
|
||||
int fileWriteUInt8List(File* stream, unsigned char* arr, int count);
|
||||
int fileWriteFixedLengthString(File* stream, char* string, int length);
|
||||
|
||||
// Writes a list of 16-bit integers to stream in big-endian byte order.
|
||||
int fileWriteInt16List(File* stream, short* arr, int count);
|
||||
|
||||
// Writes a list of 16-bit unsigned integers to stream in big-endian byte order.
|
||||
int fileWriteUInt16List(File* stream, unsigned short* arr, int count);
|
||||
|
||||
// Writes a list of 32-bit integers to stream in big-endian byte order.
|
||||
int fileWriteInt32List(File* stream, int* arr, int count);
|
||||
|
||||
// Writes a list of 32-bit integers to stream in big-endian byte order (alias).
|
||||
int _db_fwriteLongCount(File* stream, int* arr, int count);
|
||||
|
||||
// Writes a list of 32-bit unsigned integers to stream in big-endian byte order.
|
||||
int fileWriteUInt32List(File* stream, unsigned int* arr, int count);
|
||||
int fileNameListInit(const char* pattern, char*** fileNames);
|
||||
void fileNameListFree(char*** fileNames, int unused);
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
namespace fallout {
|
||||
|
||||
// The size of decompression buffer for reading compressed [DFile]s.
|
||||
#define DFILE_DECOMPRESSION_BUFFER_SIZE (0x400)
|
||||
#define DFILE_DECOMPRESSION_BUFFER_SIZE (0x1000)
|
||||
|
||||
// Specifies that [DFile] has unget character.
|
||||
//
|
||||
|
||||
+1
-2
@@ -86,7 +86,6 @@ namespace fallout {
|
||||
#define SPLASH_HEIGHT (480)
|
||||
#define SPLASH_COUNT (10)
|
||||
|
||||
static int gameLoadGlobalVars();
|
||||
static int gameTakeScreenshot(int width, int height, unsigned char* buffer, unsigned char* palette);
|
||||
static void gameFreeGlobalVars();
|
||||
static bool tryLoadBaseCEModAtPath(const char* path, bool* found, bool* openFailed);
|
||||
@@ -1016,7 +1015,7 @@ int gameSetGlobalVar(int var, int value)
|
||||
|
||||
// game_load_info
|
||||
// 0x443CC8
|
||||
static int gameLoadGlobalVars()
|
||||
int gameLoadGlobalVars()
|
||||
{
|
||||
if (globalVarsRead("data\\vault13.gam", "GAME_GLOBAL_VARS:", &gGameGlobalVarsLength, &gGameGlobalVars) != 0) {
|
||||
return -1;
|
||||
|
||||
@@ -47,6 +47,7 @@ int gameRequestState(int newGameState);
|
||||
void gameUpdateState();
|
||||
int showQuitConfirmationDialog();
|
||||
|
||||
int gameLoadGlobalVars();
|
||||
int gameShowDeathDialog(const char* message);
|
||||
void gameHandleSkilldexResult(int rc);
|
||||
void* gameGetGlobalPointer(int var);
|
||||
|
||||
+23
-2
@@ -313,7 +313,6 @@ Object* gGameMouseHexCursor;
|
||||
// 0x596C74
|
||||
static Object* gGameMousePointedObject;
|
||||
|
||||
static int _gmouse_get_click_to_scroll();
|
||||
static void _gmouse_3d_enable_modes();
|
||||
static int gameMouseSetBouncingCursorFid(int fid);
|
||||
static int gameMouseRenderAccuracy(const char* string, int color);
|
||||
@@ -455,6 +454,15 @@ int _gmouse_get_click_to_scroll()
|
||||
return _gmouse_click_to_scroll;
|
||||
}
|
||||
|
||||
// gmouse_set_click_to_scroll
|
||||
void _gmouse_set_click_to_scroll(int value)
|
||||
{
|
||||
if (value != _gmouse_click_to_scroll) {
|
||||
_gmouse_click_to_scroll = value;
|
||||
_gmouse_clicked_on_edge = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x44B54C
|
||||
int _gmouse_is_scrolling()
|
||||
{
|
||||
@@ -2279,7 +2287,7 @@ int _gmouse_3d_move_to(int x, int y, int elevation, Rect* rect)
|
||||
int fid = gGameMouseBouncingCursor->fid;
|
||||
if (FID_TYPE(fid) == OBJ_TYPE_TILE) {
|
||||
int squareTile = squareTileFromScreenXY(x, y, elevation);
|
||||
if (squareTile == -1) {
|
||||
if (squareTile != -1) {
|
||||
tile = HEX_GRID_WIDTH * (2 * (squareTile / SQUARE_GRID_WIDTH) + 1) + 2 * (squareTile % SQUARE_GRID_WIDTH) + 1;
|
||||
x1 = -8;
|
||||
y1 = 13;
|
||||
@@ -2383,6 +2391,19 @@ int gameMouseHandleScrolling(int x, int y, int cursor)
|
||||
flags |= SCROLLABLE_S;
|
||||
}
|
||||
|
||||
// Click-to-scroll mode (mapper Alt-Z): only scroll while the left button is held at an
|
||||
// edge. Track this in _gmouse_clicked_on_edge so callers can react (e.g. suppress the
|
||||
// regular 3D cursor update while an edge-click scroll is active).
|
||||
// TODO: reconcile with other site where _gmouse_clicked_on_edge is set to true
|
||||
if (_gmouse_click_to_scroll) {
|
||||
bool atEdge = flags != 0;
|
||||
bool leftHeld = (mouseGetEvent() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0;
|
||||
_gmouse_clicked_on_edge = atEdge && leftHeld;
|
||||
if (!_gmouse_clicked_on_edge) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int dx = 0;
|
||||
int dy = 0;
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ void _gmouse_enable_scrolling();
|
||||
void _gmouse_disable_scrolling();
|
||||
bool gmouse_scrolling_is_enabled();
|
||||
int _gmouse_is_scrolling();
|
||||
int _gmouse_get_click_to_scroll();
|
||||
void _gmouse_set_click_to_scroll(int value);
|
||||
void gameMouseRefresh();
|
||||
void _gmouse_handle_event(int mouseX, int mouseY, int mouseState);
|
||||
int gameMouseSetCursor(int cursor);
|
||||
|
||||
+151
-4
@@ -1,12 +1,13 @@
|
||||
#include "graph_lib.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "color.h"
|
||||
#include "db.h"
|
||||
#include "debug.h"
|
||||
#include "memory.h"
|
||||
#include "palette.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
@@ -53,10 +54,156 @@ unsigned char HighRGB(unsigned char color)
|
||||
}
|
||||
|
||||
// 0x44ED98
|
||||
int load_lbm_to_buf(const char* path, unsigned char* buffer, int a3, int a4, int a5, int a6, int a7)
|
||||
// Loads LBM image file into a dstBuffer.
|
||||
// Returns image width on success, -1 on error.
|
||||
int load_lbm_to_buf(const char* path, unsigned char* dstBuffer, int xMin, int yMin, int xMax, int yMax)
|
||||
{
|
||||
// TODO: Incomplete.
|
||||
File* stream = fileOpen(path, "rb");
|
||||
if (stream == nullptr) {
|
||||
debugPrint("load_lbm_to_buf(%s): fileOpen failed\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned char form[4];
|
||||
unsigned int formSize;
|
||||
unsigned char ilbm[4];
|
||||
if (fileRead(form, 1, 4, stream) != 4
|
||||
|| memcmp(form, "FORM", 4) != 0
|
||||
|| fileReadUInt32(stream, &formSize) == -1
|
||||
|| fileRead(ilbm, 1, 4, stream) != 4) {
|
||||
debugPrint("load_lbm_to_buf(%s): incorrect LBM header\n", path);
|
||||
fileClose(stream);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int imgWidth = 0, imgHeight = 0;
|
||||
|
||||
// NOTE: The original ASM's sliding-window scanner consumes the BMHD
|
||||
// chunk size as a 4-byte skip, reads only the 2-byte width, and then
|
||||
// lets the scanner drift through the remaining BMHD fields (height,
|
||||
// nPlanes, masking, comp, etc.) while searching for the next tag.
|
||||
// Height and compression are therefore never stored by the original.
|
||||
// The BODY decoder always runs as PackBits unconditionally.
|
||||
|
||||
// paletteRemap[i] = system-palette index that best matches LBM colour i.
|
||||
// Built from CMAP via colorTable. Index 0 is populated but never used
|
||||
// (pixel value 0 is always output as 0 — the transparent colour).
|
||||
unsigned char paletteRemap[256] = {};
|
||||
|
||||
while (true) {
|
||||
unsigned char chunkType[4];
|
||||
unsigned int chunkSize;
|
||||
if (fileRead(chunkType, 1, 4, stream) != 4
|
||||
|| fileReadUInt32(stream, &chunkSize) == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned int aligned = (chunkSize + 1) & ~1u;
|
||||
|
||||
if (memcmp(chunkType, "BMHD", 4) == 0) {
|
||||
// Original reads only w (2 bytes); h is read here to advance the
|
||||
// file position but its value is intentionally discarded.
|
||||
unsigned short w, h;
|
||||
if (fileReadUInt16(stream, &w) == -1
|
||||
|| fileReadUInt16(stream, &h) == -1) break;
|
||||
imgWidth = w;
|
||||
imgHeight = h;
|
||||
|
||||
// Skip every remaining BMHD field — the original never reads them.
|
||||
unsigned int bmhdRemaining = aligned - 4; // already consumed w + h
|
||||
if (bmhdRemaining > 0) fileSeek(stream, bmhdRemaining, SEEK_CUR);
|
||||
|
||||
} else if (memcmp(chunkType, "CMAP", 4) == 0) {
|
||||
// The game DOES use its own palette, but it reconciles the two
|
||||
// by mapping each LBM color through colorTable (RGB555 → nearest
|
||||
// system index), storing the result in paletteRemap[].
|
||||
int entries = static_cast<int>(std::min(chunkSize / 3u, 256u));
|
||||
for (int i = 0; i < entries; i++) {
|
||||
unsigned char r, g, b;
|
||||
if (fileReadUInt8(stream, &r) == -1
|
||||
|| fileReadUInt8(stream, &g) == -1
|
||||
|| fileReadUInt8(stream, &b) == -1) {
|
||||
fileClose(stream);
|
||||
return -1;
|
||||
}
|
||||
int rgb555 = ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3);
|
||||
paletteRemap[i] = _colorTable[rgb555];
|
||||
}
|
||||
unsigned int cmapRemaining = aligned - static_cast<unsigned int>(entries * 3);
|
||||
if (cmapRemaining > 0) fileSeek(stream, cmapRemaining, SEEK_CUR);
|
||||
|
||||
} else if (memcmp(chunkType, "BODY", 4) == 0) {
|
||||
// Decompression, palette remapping, and clipped output in a single pass with no intermediate pixel buffer.
|
||||
// Output rules:
|
||||
// pixel == 0 -> write 0 unconditionally (transparent)
|
||||
// pixel != 0 -> write paletteRemap[pixel]
|
||||
//
|
||||
// The run-length case pre-remaps the pixel value once before the repeat loop.
|
||||
|
||||
unsigned char* dst = dstBuffer;
|
||||
int col = 0; // current column - wraps at imgWidth
|
||||
int row = 0; // current row - increments on wrap
|
||||
|
||||
// Advance the pixel cursor by one position.
|
||||
auto advance = [&]() {
|
||||
if (++col == imgWidth) {
|
||||
col = 0;
|
||||
++row;
|
||||
}
|
||||
};
|
||||
|
||||
// Emit one pixel, respecting the clip rectangle.
|
||||
// Used for literal-run pixels where the remap must occur per byte.
|
||||
auto emit = [&](unsigned char raw) {
|
||||
if (col >= xMin && col <= xMax && row >= yMin && row <= yMax)
|
||||
*dst++ = (raw == 0) ? 0 : paletteRemap[raw];
|
||||
advance();
|
||||
};
|
||||
|
||||
// PackBits RLE — always, regardless of BMHD compression field.
|
||||
bool eof = false;
|
||||
unsigned char ctrl;
|
||||
while (!eof && fileReadUInt8(stream, &ctrl) != -1) {
|
||||
if (ctrl < 0x80) {
|
||||
// Literal run: the next (ctrl + 1) bytes are distinct pixels.
|
||||
int count = ctrl + 1;
|
||||
for (int i = 0; i < count && !eof; i++) {
|
||||
unsigned char px;
|
||||
if (fileReadUInt8(stream, &px) == -1)
|
||||
eof = true;
|
||||
else
|
||||
emit(px);
|
||||
}
|
||||
} else {
|
||||
// Run: repeat the next single pixel (257 − ctrl) times.
|
||||
int count = 257 - static_cast<int>(ctrl);
|
||||
unsigned char px;
|
||||
if (fileReadUInt8(stream, &px) == -1) {
|
||||
eof = true;
|
||||
} else {
|
||||
// Remap once for the whole run (mirrors var_10 in the ASM).
|
||||
unsigned char mapped = (px == 0) ? 0 : paletteRemap[px];
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (col >= xMin && col <= xMax && row >= yMin && row <= yMax)
|
||||
*dst++ = mapped;
|
||||
advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching the end of BODY data (normally or via EOF) is the
|
||||
// success path.
|
||||
fileClose(stream);
|
||||
debugPrint("load_lbm_to_buf: loaded %dx%d OK\n", imgWidth, imgHeight);
|
||||
return imgWidth;
|
||||
|
||||
} else {
|
||||
if (aligned > 0) fileSeek(stream, aligned, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
fileClose(stream);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
namespace fallout {
|
||||
|
||||
unsigned char HighRGB(unsigned char color);
|
||||
int load_lbm_to_buf(const char* path, unsigned char* buffer, int a3, int a4, int a5, int a6, int a7);
|
||||
int load_lbm_to_buf(const char* path, unsigned char* dstBuffer, int xMin, int yMin, int xMax, int yMax);
|
||||
int graphCompress(unsigned char* a1, unsigned char* a2, int a3);
|
||||
int graphDecompress(unsigned char* a1, unsigned char* a2, int a3);
|
||||
void grayscalePaletteUpdate(int a1, int a2);
|
||||
|
||||
@@ -969,6 +969,7 @@ static void buildNormalizedQwertyKeys()
|
||||
keys[SDL_SCANCODE_RIGHT] = SDL_SCANCODE_RIGHT;
|
||||
keys[SDL_SCANCODE_END] = SDL_SCANCODE_END;
|
||||
keys[SDL_SCANCODE_DOWN] = SDL_SCANCODE_DOWN;
|
||||
keys[SDL_SCANCODE_PAGEUP] = SDL_SCANCODE_PAGEUP;
|
||||
keys[SDL_SCANCODE_PAGEDOWN] = SDL_SCANCODE_PAGEDOWN;
|
||||
keys[SDL_SCANCODE_INSERT] = SDL_SCANCODE_INSERT;
|
||||
keys[SDL_SCANCODE_DELETE] = SDL_SCANCODE_DELETE;
|
||||
|
||||
@@ -3298,6 +3298,9 @@ bool ProgramValue::isEmpty() const
|
||||
switch (opcode) {
|
||||
case VALUE_TYPE_INT:
|
||||
case VALUE_TYPE_STRING:
|
||||
case VALUE_TYPE_DYNAMIC_STRING:
|
||||
// XXX: this matches the engine, but for VALUE_TYPE_STRING, 0 is valid string index, so this
|
||||
// would be wrong in that case.
|
||||
return integerValue == 0;
|
||||
case VALUE_TYPE_FLOAT:
|
||||
return floatValue == 0.0;
|
||||
|
||||
+45
-20
@@ -36,7 +36,9 @@
|
||||
#include "reaction.h"
|
||||
#include "scripts.h"
|
||||
#include "settings.h"
|
||||
#include "sfall_config.h"
|
||||
#include "sfall_opcodes.h"
|
||||
#include "sfall_script_hooks.h"
|
||||
#include "skill.h"
|
||||
#include "stat.h"
|
||||
#include "svga.h"
|
||||
@@ -121,13 +123,6 @@ typedef enum CritterState {
|
||||
CRITTER_STATE_PRONE = 0x02,
|
||||
} CritterState;
|
||||
|
||||
enum {
|
||||
INVEN_TYPE_WORN = 0,
|
||||
INVEN_TYPE_RIGHT_HAND = 1,
|
||||
INVEN_TYPE_LEFT_HAND = 2,
|
||||
INVEN_TYPE_INV_COUNT = -2,
|
||||
};
|
||||
|
||||
typedef enum FloatingMessageType {
|
||||
FLOATING_MESSAGE_TYPE_WARNING = -2,
|
||||
FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE = -1,
|
||||
@@ -414,6 +409,15 @@ static int tileIsVisible(int tile)
|
||||
// 0x45409C
|
||||
static int _correctFidForRemovedItem(Object* critter, Object* item, int flags)
|
||||
{
|
||||
InvenSlot invenSlot = InvenSlot::Armor;
|
||||
if ((flags & OBJECT_IN_RIGHT_HAND) != 0) {
|
||||
invenSlot = InvenSlot::RightHand;
|
||||
} else if ((flags & OBJECT_IN_LEFT_HAND) != 0) {
|
||||
invenSlot = InvenSlot::LeftHand;
|
||||
}
|
||||
|
||||
scriptHooks_InvenWield(critter, item, invenSlot, 0, 1);
|
||||
|
||||
if (critter == gDude) {
|
||||
bool animated = !gameUiIsDisabled();
|
||||
interfaceUpdateItems(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);
|
||||
@@ -3015,11 +3019,16 @@ static void opCritterGetInventoryObject(Program* program)
|
||||
Object* critter = static_cast<Object*>(programStackPopPointer(program));
|
||||
|
||||
if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) {
|
||||
switch (type) {
|
||||
case INVEN_TYPE_WORN:
|
||||
if (type == kInvenSlotInvCount) {
|
||||
programStackPushInteger(program, critter->data.inventory.length);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (static_cast<InvenSlot>(type)) {
|
||||
case InvenSlot::Armor:
|
||||
programStackPushPointer(program, critterGetArmor(critter));
|
||||
break;
|
||||
case INVEN_TYPE_RIGHT_HAND:
|
||||
case InvenSlot::RightHand:
|
||||
if (critter == gDude) {
|
||||
if (interfaceGetCurrentHand() != HAND_LEFT) {
|
||||
programStackPushPointer(program, critterGetItem2(critter));
|
||||
@@ -3030,7 +3039,7 @@ static void opCritterGetInventoryObject(Program* program)
|
||||
programStackPushPointer(program, critterGetItem2(critter));
|
||||
}
|
||||
break;
|
||||
case INVEN_TYPE_LEFT_HAND:
|
||||
case InvenSlot::LeftHand:
|
||||
if (critter == gDude) {
|
||||
if (interfaceGetCurrentHand() == HAND_LEFT) {
|
||||
programStackPushPointer(program, critterGetItem1(critter));
|
||||
@@ -3041,9 +3050,6 @@ static void opCritterGetInventoryObject(Program* program)
|
||||
programStackPushPointer(program, critterGetItem1(critter));
|
||||
}
|
||||
break;
|
||||
case INVEN_TYPE_INV_COUNT:
|
||||
programStackPushInteger(program, critter->data.inventory.length);
|
||||
break;
|
||||
default:
|
||||
scriptError("script error: %s: Error in critter_inven_obj -- wrong type!", program->name);
|
||||
programStackPushInteger(program, 0);
|
||||
@@ -4599,24 +4605,43 @@ static void opMoveObjectInventoryToObject(Program* program)
|
||||
}
|
||||
|
||||
Object* oldArmor = nullptr;
|
||||
Object* item2 = nullptr;
|
||||
Object* oldWeapon = nullptr;
|
||||
if (object1 == gDude) {
|
||||
oldArmor = critterGetArmor(object1);
|
||||
if (interfaceGetCurrentHand() == HAND_RIGHT) {
|
||||
oldWeapon = critterGetItem2(object1);
|
||||
} else {
|
||||
oldWeapon = critterGetItem1(object1);
|
||||
}
|
||||
} else {
|
||||
item2 = critterGetItem2(object1);
|
||||
oldWeapon = critterGetItem2(object1);
|
||||
}
|
||||
|
||||
if (object1 != gDude && item2 != nullptr) {
|
||||
if (object1 == gDude) {
|
||||
if (oldWeapon != nullptr) {
|
||||
InvenSlot invenSlot = interfaceGetCurrentHand() == HAND_RIGHT
|
||||
? InvenSlot::RightHand
|
||||
: InvenSlot::LeftHand;
|
||||
scriptHooks_InvenWield(object1, oldWeapon, invenSlot, 0, 1);
|
||||
}
|
||||
|
||||
if (oldArmor != nullptr) {
|
||||
scriptHooks_InvenWield(object1, oldArmor, InvenSlot::Armor, 0, 1);
|
||||
}
|
||||
} else if (oldWeapon != nullptr) {
|
||||
// CE intentionally reports the NPC weapon's actual hand slot here.
|
||||
// Sfall's op_move_obj_inven_to_obj HOOK_INVENWIELD path passes
|
||||
// InvenSlot::Armor unconditionally for NPC weapons in this case.
|
||||
int flags = 0;
|
||||
if ((item2->flags & OBJECT_IN_LEFT_HAND) != 0) {
|
||||
if ((oldWeapon->flags & OBJECT_IN_LEFT_HAND) != 0) {
|
||||
flags |= OBJECT_IN_LEFT_HAND;
|
||||
}
|
||||
|
||||
if ((item2->flags & OBJECT_IN_RIGHT_HAND) != 0) {
|
||||
if ((oldWeapon->flags & OBJECT_IN_RIGHT_HAND) != 0) {
|
||||
flags |= OBJECT_IN_RIGHT_HAND;
|
||||
}
|
||||
|
||||
_correctFidForRemovedItem(object1, item2, flags);
|
||||
_correctFidForRemovedItem(object1, oldWeapon, flags);
|
||||
}
|
||||
|
||||
itemMoveAll(object1, object2);
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
|
||||
namespace fallout {
|
||||
|
||||
enum class InvenSlot {
|
||||
Armor = 0, // INVEN_TYPE_WORN
|
||||
RightHand = 1, // INVEN_TYPE_RIGHT_HAND
|
||||
LeftHand = 2, // INVEN_TYPE_LEFT_HAND
|
||||
};
|
||||
|
||||
constexpr int kInvenSlotInvCount = -2;
|
||||
|
||||
void _intExtraClose_();
|
||||
void _initIntExtra();
|
||||
void intExtraUpdate();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user