mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
344f4e375e | ||
|
|
c02c5efcf6 |
@@ -142,7 +142,7 @@ jobs:
|
||||
ios:
|
||||
name: iOS
|
||||
|
||||
runs-on: macos-15
|
||||
runs-on: macos-14
|
||||
needs: [ce-dat]
|
||||
|
||||
steps:
|
||||
@@ -328,7 +328,7 @@ jobs:
|
||||
macos:
|
||||
name: macOS
|
||||
|
||||
runs-on: macos-15
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
|
||||
+2
-2
@@ -6,10 +6,10 @@ set(EXECUTABLE_NAME fallout2-ce)
|
||||
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "13" CACHE STRING "")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "12" CACHE STRING "")
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "")
|
||||
else()
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "")
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -43,6 +43,7 @@ The following settings were moved into [`<DAT>/config/game.cfg`](files/ce.dat/co
|
||||
| ddraw.ini section | ddraw.ini key | game.cfg section | game.cfg key |
|
||||
| --- | --- | --- | --- |
|
||||
| `Misc` | `StartGDialogFix` | `dialog` | `start_gdialog_fix` |
|
||||
| `Misc` | `Movie1` - `Movie32` | `movies` | `movie1` - `movie32` |
|
||||
|
||||
## Opcodes / Metarules
|
||||
|
||||
@@ -89,7 +90,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
|
||||
| INI settings | get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… | `modified_ini` is intentionally omitted as deprecated. |
|
||||
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>get/set_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get/set_object_data<br>get/set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | âś… except set_dude_obj, set_scr_name | `get/set_object_data` maps supported object and `C_ATTACK_*` offsets instead of exposing arbitrary raw memory offsets. |
|
||||
| Other / Game management | set_movie_path<br>stop/resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | implemented: game_loaded, get_game_mode, get_uptime, signal_close_game | - |
|
||||
| Other / Game management | set_movie_path<br>stop/resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | âś… except stop/resume_game | - |
|
||||
| Gameplay tweaks | set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>set_hp_per_level_mod<br>gdialog_get_barter_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>get/set_inven_ap_cost<br>set_drugs_data<br>get_kill_counter<br>mod_kill_counter<br>set_pipboy_available | implemented: gdialog_get_barter_mod, get/set_unspent_ap{_perk}_bonus, get/set_inven_ap_cost | - |
|
||||
| NPCs | inc_npc_level<br>get_npc_level<br>npc_engine_level_up | not implemented | - |
|
||||
| Hero Appearance | set_dm/df_model<br>hero_select_win<br>set_hero_race<br>set_hero_style | not implemented | - |
|
||||
|
||||
@@ -60,6 +60,41 @@ credits_offset_x=0
|
||||
credits_offset_y=0
|
||||
|
||||
[movies]
|
||||
; Overrides for game movie file names in art\cuts\. These correspond to sfall's Movie1 through Movie32 settings.
|
||||
; Movie IDs used by scripts are 0-indexed, so movie1 is script movie ID 0.
|
||||
movie1=iplogo.mve
|
||||
movie2=intro.mve
|
||||
movie3=elder.mve
|
||||
movie4=vsuit.mve
|
||||
movie5=afailed.mve
|
||||
movie6=adestroy.mve
|
||||
movie7=car.mve
|
||||
movie8=cartucci.mve
|
||||
movie9=timeout.mve
|
||||
movie10=tanker.mve
|
||||
movie11=enclave.mve
|
||||
movie12=derrick.mve
|
||||
movie13=artimer1.mve
|
||||
movie14=artimer2.mve
|
||||
movie15=artimer3.mve
|
||||
movie16=artimer4.mve
|
||||
movie17=credits.mve
|
||||
; Additional script-playable movies.
|
||||
movie18=
|
||||
movie19=
|
||||
movie20=
|
||||
movie21=
|
||||
movie22=
|
||||
movie23=
|
||||
movie24=
|
||||
movie25=
|
||||
movie26=
|
||||
movie27=
|
||||
movie28=
|
||||
movie29=
|
||||
movie30=
|
||||
movie31=
|
||||
movie32=
|
||||
; Number of days after game start when each Hakunin dream sequence becomes available.
|
||||
artimer1=90
|
||||
artimer2=180
|
||||
|
||||
@@ -89,7 +89,6 @@ def assertSdlJavaBindingsMatch = { String variantName ->
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
ndkVersion "27.3.13750724"
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.alexbatalov.fallout2ce'
|
||||
|
||||
+32
-15
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace fallout {
|
||||
|
||||
#define CONFIG_FILE_MAX_LINE_LENGTH (1024)
|
||||
#define CONFIG_FILE_MAX_LINE_LENGTH (2048)
|
||||
|
||||
// The initial number of sections (or key-value) pairs in the config.
|
||||
#define CONFIG_INITIAL_CAPACITY (10)
|
||||
@@ -32,9 +32,10 @@ struct CaseInsensitiveLess {
|
||||
typedef std::set<std::string, CaseInsensitiveLess> StringSet;
|
||||
|
||||
static bool configParseLine(Config* config, char* string);
|
||||
static bool configParseKeyValue(char* string, char* key, char* value);
|
||||
static bool configParseKeyValue(char* string, std::string& key, std::string& value);
|
||||
static bool configEnsureSectionExists(Config* config, const char* sectionKey);
|
||||
static bool configTrimString(char* string);
|
||||
static std::string configGetTrimmedString(const char* string);
|
||||
|
||||
static bool configWriteDb(Config* config, const char* filePath);
|
||||
static bool configWriteStandard(Config* config, const char* filePath);
|
||||
@@ -120,10 +121,10 @@ bool configParseCommandLineArguments(Config* config, int argc, char** argv)
|
||||
|
||||
*pch = '\0';
|
||||
|
||||
char key[260];
|
||||
char value[260];
|
||||
std::string key;
|
||||
std::string value;
|
||||
if (configParseKeyValue(pch + 1, key, value)) {
|
||||
if (!configSetString(config, sectionKey, key, value)) {
|
||||
if (!configSetString(config, sectionKey, key.c_str(), value.c_str())) {
|
||||
*pch = ']';
|
||||
return false;
|
||||
}
|
||||
@@ -683,13 +684,13 @@ static bool configParseLine(Config* config, char* string)
|
||||
}
|
||||
}
|
||||
|
||||
char key[260];
|
||||
char value[260];
|
||||
std::string key;
|
||||
std::string value;
|
||||
if (!configParseKeyValue(string, key, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return configSetString(config, gConfigLastSectionKey, key, value);
|
||||
return configSetString(config, gConfigLastSectionKey, key.c_str(), value.c_str());
|
||||
}
|
||||
|
||||
// Splits "key=value" pair from [string] and copy appropriate parts into [key]
|
||||
@@ -698,9 +699,9 @@ static bool configParseLine(Config* config, char* string)
|
||||
// Both key and value are trimmed.
|
||||
//
|
||||
// 0x42C594
|
||||
static bool configParseKeyValue(char* string, char* key, char* value)
|
||||
static bool configParseKeyValue(char* string, std::string& key, std::string& value)
|
||||
{
|
||||
if (string == nullptr || key == nullptr || value == nullptr) {
|
||||
if (string == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -712,14 +713,11 @@ static bool configParseKeyValue(char* string, char* key, char* value)
|
||||
|
||||
*pch = '\0';
|
||||
|
||||
strcpy(key, string);
|
||||
strcpy(value, pch + 1);
|
||||
key = configGetTrimmedString(string);
|
||||
value = configGetTrimmedString(pch + 1);
|
||||
|
||||
*pch = '=';
|
||||
|
||||
configTrimString(key);
|
||||
configTrimString(value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -791,6 +789,25 @@ static bool configTrimString(char* string)
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string configGetTrimmedString(const char* string)
|
||||
{
|
||||
if (string == nullptr) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const char* start = string;
|
||||
while (isspace(static_cast<unsigned char>(*start))) {
|
||||
start++;
|
||||
}
|
||||
|
||||
const char* end = start + strlen(start);
|
||||
while (end > start && isspace(static_cast<unsigned char>(*(end - 1)))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return std::string(start, end);
|
||||
}
|
||||
|
||||
// 0x42C718
|
||||
bool configGetDouble(Config* config, const char* sectionKey, const char* key, double* valuePtr)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "content_config.h"
|
||||
#include "debug.h"
|
||||
#include "game_config.h"
|
||||
#include "game_movie.h"
|
||||
#include "platform_compat.h"
|
||||
#include "settings.h"
|
||||
#include "sfall_config.h"
|
||||
@@ -237,6 +238,58 @@ namespace {
|
||||
{ kSfallMisc, "ExtraGameMsgFileList", CONTENT_CONFIG_TEXT_SECTION, "extra_msg_file_list" },
|
||||
};
|
||||
|
||||
constexpr const char* kSfallDefaultMovies[MOVIE_COUNT] = {
|
||||
"iplogo.mve",
|
||||
"intro.mve",
|
||||
"elder.mve",
|
||||
"vsuit.mve",
|
||||
"afailed.mve",
|
||||
"adestroy.mve",
|
||||
"car.mve",
|
||||
"cartucci.mve",
|
||||
"timeout.mve",
|
||||
"tanker.mve",
|
||||
"enclave.mve",
|
||||
"derrick.mve",
|
||||
"artimer1.mve",
|
||||
"artimer2.mve",
|
||||
"artimer3.mve",
|
||||
"artimer4.mve",
|
||||
"credits.mve",
|
||||
};
|
||||
|
||||
static bool contentConfigMigrateSfallMovieOverrides(Config* sfallConfig, Config* migratedConfig)
|
||||
{
|
||||
assert(sfallConfig != nullptr && migratedConfig != nullptr);
|
||||
|
||||
bool migrated = false;
|
||||
for (int index = 0; index < GAME_MOVIE_MAX_COUNT; index++) {
|
||||
char sfallKey[16];
|
||||
snprintf(sfallKey, sizeof(sfallKey), "Movie%d", index + 1);
|
||||
|
||||
char* value;
|
||||
if (!configGetString(sfallConfig, kSfallMisc, sfallKey, &value) || value[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index < MOVIE_COUNT && strcmp(value, kSfallDefaultMovies[index]) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char targetKey[16];
|
||||
snprintf(targetKey, sizeof(targetKey), "movie%d", index + 1);
|
||||
|
||||
if (gameConfigHasKey(migratedConfig, CONTENT_CONFIG_MOVIES_SECTION, targetKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
configSetString(migratedConfig, CONTENT_CONFIG_MOVIES_SECTION, targetKey, value);
|
||||
migrated = true;
|
||||
}
|
||||
|
||||
return migrated;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Migrate sfall settings from ddraw.ini to game.cfg.
|
||||
@@ -283,6 +336,10 @@ static bool contentConfigMigrateFromSfall(Config* sfallConfig, const char* conte
|
||||
}
|
||||
}
|
||||
|
||||
if (contentConfigMigrateSfallMovieOverrides(sfallConfig, &migratedConfig)) {
|
||||
migrated = true;
|
||||
}
|
||||
|
||||
if (migrated) {
|
||||
// Ensure all directory components exist before writing.
|
||||
char drive[COMPAT_MAX_DRIVE];
|
||||
|
||||
+101
-15
@@ -1,9 +1,13 @@
|
||||
#include "game_movie.h"
|
||||
|
||||
#include <array>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "color.h"
|
||||
#include "content_config.h"
|
||||
#include "cycle.h"
|
||||
#include "debug.h"
|
||||
#include "game.h"
|
||||
@@ -24,12 +28,16 @@
|
||||
namespace fallout {
|
||||
|
||||
static char* gameMovieBuildSubtitlesFilePath(char* movieFilePath);
|
||||
static bool gameMovieFindFilePath(char* movieFilePath, size_t movieFilePathSize, const char* movieFileName);
|
||||
static bool gameMovieFindFilePathInDir(char* movieFilePath, size_t movieFilePathSize, const char* dir, const char* movieFileName);
|
||||
static void gameMovieInitFileNames();
|
||||
static void gameMovieLoadConfigFileNames();
|
||||
|
||||
// 0x50352A
|
||||
static const float flt_50352A = 0.032258064f;
|
||||
|
||||
// 0x518DA0 movie_list
|
||||
static const char* gMovieFileNames[MOVIE_COUNT] = {
|
||||
static const char* gMovieDefaultFileNames[MOVIE_COUNT] = {
|
||||
"iplogo.mve",
|
||||
"intro.mve",
|
||||
"elder.mve",
|
||||
@@ -49,6 +57,8 @@ static const char* gMovieFileNames[MOVIE_COUNT] = {
|
||||
"credits.mve",
|
||||
};
|
||||
|
||||
static std::array<std::string, GAME_MOVIE_MAX_COUNT> gMovieFileNames;
|
||||
|
||||
// 0x518DE4 subtitlePalList
|
||||
static const char* gMoviePaletteFilePaths[MOVIE_COUNT] = {
|
||||
nullptr,
|
||||
@@ -95,6 +105,9 @@ int gameMoviesInit()
|
||||
|
||||
movieSetBuildSubtitleFilePathProc(gameMovieBuildSubtitlesFilePath);
|
||||
|
||||
gameMovieInitFileNames();
|
||||
gameMovieLoadConfigFileNames();
|
||||
|
||||
memset(gGameMoviesSeen, 0, sizeof(gGameMoviesSeen));
|
||||
|
||||
gGameMovieIsPlaying = false;
|
||||
@@ -136,28 +149,24 @@ int gameMoviesSave(File* stream)
|
||||
// 0x44E690 gmovie_play
|
||||
int gameMoviePlay(int movie, int flags)
|
||||
{
|
||||
if (movie < 0 || movie >= GAME_MOVIE_MAX_COUNT || gMovieFileNames[movie].empty()) {
|
||||
debugPrint("\ngmovie_play() - Error: Invalid movie %d\n", movie);
|
||||
return -1;
|
||||
}
|
||||
|
||||
gGameMovieIsPlaying = true;
|
||||
|
||||
const char* movieFileName = gMovieFileNames[movie];
|
||||
const char* movieFileName = gMovieFileNames[movie].c_str();
|
||||
debugPrint("\nPlaying movie: %s\n", movieFileName);
|
||||
|
||||
const char* language = settings.system.language.c_str();
|
||||
char movieFilePath[COMPAT_MAX_PATH];
|
||||
int movieFileSize;
|
||||
bool movieFound = false;
|
||||
|
||||
if (compat_stricmp(language, ENGLISH) != 0) {
|
||||
snprintf(movieFilePath, sizeof(movieFilePath), "art\\%s\\cuts\\%s", language, gMovieFileNames[movie]);
|
||||
movieFound = dbGetFileSize(movieFilePath, &movieFileSize) == 0;
|
||||
}
|
||||
movieFound = gameMovieFindFilePath(movieFilePath, sizeof(movieFilePath), movieFileName);
|
||||
|
||||
if (!movieFound) {
|
||||
snprintf(movieFilePath, sizeof(movieFilePath), "art\\cuts\\%s", gMovieFileNames[movie]);
|
||||
movieFound = dbGetFileSize(movieFilePath, &movieFileSize) == 0;
|
||||
}
|
||||
|
||||
if (!movieFound) {
|
||||
debugPrint("\ngmovie_play() - Error: Unable to open %s\n", gMovieFileNames[movie]);
|
||||
debugPrint("\ngmovie_play() - Error: Unable to open %s\n", movieFileName);
|
||||
gGameMovieIsPlaying = false;
|
||||
return -1;
|
||||
}
|
||||
@@ -205,7 +214,7 @@ int gameMoviePlay(int movie, int flags)
|
||||
int oldFont;
|
||||
if (subtitlesEnabled) {
|
||||
const char* subtitlesPaletteFilePath;
|
||||
if (gMoviePaletteFilePaths[movie] != nullptr) {
|
||||
if (movie < MOVIE_COUNT && gMoviePaletteFilePaths[movie] != nullptr) {
|
||||
subtitlesPaletteFilePath = gMoviePaletteFilePaths[movie];
|
||||
} else {
|
||||
subtitlesPaletteFilePath = "art\\cuts\\subtitle.pal";
|
||||
@@ -264,7 +273,7 @@ int gameMoviePlay(int movie, int flags)
|
||||
_movieUpdate();
|
||||
paletteSetEntries(gPaletteBlack);
|
||||
|
||||
gGameMoviesSeen[movie] = 1;
|
||||
gameMovieMarkSeen(movie);
|
||||
|
||||
colorCycleEnable();
|
||||
|
||||
@@ -308,6 +317,23 @@ int gameMoviePlay(int movie, int flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool gameMovieSetPath(int movie, const char* fileName)
|
||||
{
|
||||
if (movie < 0 || movie >= GAME_MOVIE_MAX_COUNT || fileName == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gMovieFileNames[movie] = fileName;
|
||||
return true;
|
||||
}
|
||||
|
||||
void gameMovieMarkSeen(int movie)
|
||||
{
|
||||
if (movie >= 0 && movie < MOVIE_COUNT) {
|
||||
gGameMoviesSeen[movie] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 0x44EAE4 gmPaletteFinish
|
||||
void gameMovieFadeOut()
|
||||
{
|
||||
@@ -320,6 +346,10 @@ void gameMovieFadeOut()
|
||||
// 0x44EB04 gmovie_has_been_played
|
||||
bool gameMovieIsSeen(int movie)
|
||||
{
|
||||
if (movie < 0 || movie >= MOVIE_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return gGameMoviesSeen[movie] == 1;
|
||||
}
|
||||
|
||||
@@ -351,4 +381,60 @@ static char* gameMovieBuildSubtitlesFilePath(char* movieFilePath)
|
||||
return gGameMovieSubtitlesFilePath;
|
||||
}
|
||||
|
||||
static bool gameMovieFindFilePath(char* movieFilePath, size_t movieFilePathSize, const char* movieFileName)
|
||||
{
|
||||
assert(movieFilePath != nullptr);
|
||||
assert(movieFileName != nullptr);
|
||||
|
||||
const char* language = settings.system.language.c_str();
|
||||
if (compat_stricmp(language, ENGLISH) != 0) {
|
||||
char localizedDir[COMPAT_MAX_PATH];
|
||||
snprintf(localizedDir, sizeof(localizedDir), "art\\%s\\cuts", language);
|
||||
if (gameMovieFindFilePathInDir(movieFilePath, movieFilePathSize, localizedDir, movieFileName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return gameMovieFindFilePathInDir(movieFilePath, movieFilePathSize, "art\\cuts", movieFileName);
|
||||
}
|
||||
|
||||
static bool gameMovieFindFilePathInDir(char* movieFilePath, size_t movieFilePathSize, const char* dir, const char* movieFileName)
|
||||
{
|
||||
char localMovieFilePath[COMPAT_MAX_PATH];
|
||||
snprintf(localMovieFilePath, sizeof(localMovieFilePath), ".\\%s\\%s", dir, movieFileName);
|
||||
if (compat_file_exists(localMovieFilePath)) {
|
||||
snprintf(movieFilePath, movieFilePathSize, "%s\\%s", dir, movieFileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
snprintf(movieFilePath, movieFilePathSize, "%s\\%s", dir, movieFileName);
|
||||
|
||||
int movieFileSize;
|
||||
return dbGetFileSize(movieFilePath, &movieFileSize) == 0;
|
||||
}
|
||||
|
||||
static void gameMovieInitFileNames()
|
||||
{
|
||||
for (auto& fileName : gMovieFileNames) {
|
||||
fileName.clear();
|
||||
}
|
||||
|
||||
for (int index = 0; index < MOVIE_COUNT; index++) {
|
||||
gMovieFileNames[index] = gMovieDefaultFileNames[index];
|
||||
}
|
||||
}
|
||||
|
||||
static void gameMovieLoadConfigFileNames()
|
||||
{
|
||||
char key[16];
|
||||
for (int index = 0; index < GAME_MOVIE_MAX_COUNT; index++) {
|
||||
snprintf(key, sizeof(key), "movie%d", index + 1);
|
||||
|
||||
char* fileName;
|
||||
if (configGetString(&gContentConfig, CONTENT_CONFIG_MOVIES_SECTION, key, &fileName) && fileName[0] != '\0') {
|
||||
gameMovieSetPath(index, fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
@@ -33,11 +33,15 @@ typedef enum GameMovie {
|
||||
MOVIE_COUNT,
|
||||
} GameMovie;
|
||||
|
||||
constexpr int GAME_MOVIE_MAX_COUNT = 32;
|
||||
|
||||
int gameMoviesInit();
|
||||
void gameMoviesReset();
|
||||
int gameMoviesLoad(File* stream);
|
||||
int gameMoviesSave(File* stream);
|
||||
int gameMoviePlay(int movie, int flags);
|
||||
bool gameMovieSetPath(int movie, const char* fileName);
|
||||
void gameMovieMarkSeen(int movie);
|
||||
void gameMovieFadeOut();
|
||||
bool gameMovieIsSeen(int movie);
|
||||
bool gameMovieIsPlaying();
|
||||
|
||||
@@ -3613,6 +3613,10 @@ static void opPlayGameMovie(Program* program)
|
||||
program->flags |= PROGRAM_FLAG_CHILD_CALL;
|
||||
|
||||
int movie = programStackPopInteger(program);
|
||||
int movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC;
|
||||
if (movie >= 0 && movie < MOVIE_COUNT) {
|
||||
movieFlags = flags[movie];
|
||||
}
|
||||
|
||||
// CE: Disable map updates. Needed to stop animation of objects (dude in
|
||||
// particular) when playing movies (the problem can be seen as visual
|
||||
@@ -3621,7 +3625,7 @@ static void opPlayGameMovie(Program* program)
|
||||
|
||||
gameDialogDisable();
|
||||
|
||||
if (gameMoviePlay(movie, flags[movie]) == -1) {
|
||||
if (gameMoviePlay(movie, movieFlags) == -1) {
|
||||
debugPrint("\nError playing movie %d!", movie);
|
||||
}
|
||||
|
||||
|
||||
@@ -536,11 +536,23 @@ void movieSetPaletteProc(MovieSetPaletteProc* proc)
|
||||
// 0x48731C openFile
|
||||
static File* movieOpen(char* filePath)
|
||||
{
|
||||
char looseFilePath[COMPAT_MAX_PATH];
|
||||
snprintf(looseFilePath, sizeof(looseFilePath), ".\\%s", filePath);
|
||||
|
||||
if (compat_file_exists(looseFilePath)) {
|
||||
gMovieFileStream = fileOpen(looseFilePath, "rb");
|
||||
if (gMovieFileStream != nullptr) {
|
||||
debugPrint("Movie file path: %s\n", looseFilePath);
|
||||
return gMovieFileStream;
|
||||
}
|
||||
}
|
||||
|
||||
gMovieFileStream = fileOpen(filePath, "rb");
|
||||
if (gMovieFileStream == nullptr) {
|
||||
debugPrint("Couldn't find movie file %s\n", filePath);
|
||||
return nullptr;
|
||||
}
|
||||
debugPrint("Movie file path: %s\n", filePath);
|
||||
return gMovieFileStream;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1947,7 +1947,7 @@ static void pipboyHandleVideoArchive(int a1)
|
||||
}
|
||||
}
|
||||
|
||||
if (movie <= MOVIE_COUNT) {
|
||||
if (movie < MOVIE_COUNT) {
|
||||
gameMoviePlay(movie, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC);
|
||||
} else {
|
||||
debugPrint("\n ** Selected movie not found in list! **\n");
|
||||
|
||||
+189
-40
@@ -1,5 +1,6 @@
|
||||
#include "platform_compat.h"
|
||||
#include <filesystem>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
@@ -13,6 +14,17 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#ifdef _WIN64
|
||||
#include <timeapi.h>
|
||||
#else
|
||||
#include <mmsystem.h>
|
||||
#endif
|
||||
#else
|
||||
#include <chrono>
|
||||
#endif
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace fallout {
|
||||
@@ -57,46 +69,138 @@ char* compat_itoa(int value, char* buffer, int radix)
|
||||
|
||||
void compat_splitpath(const char* path, char* drive, char* dir, char* fname, char* ext)
|
||||
{
|
||||
std::filesystem::path fsPath(path);
|
||||
#ifdef _WIN32
|
||||
_splitpath(path, drive, dir, fname, ext);
|
||||
#else
|
||||
const char* driveStart = path;
|
||||
const char* pathAfterDrive = path;
|
||||
if (pathAfterDrive[0] != '\0'
|
||||
&& pathAfterDrive[1] != '\0'
|
||||
&& compatIsPathSeparator(pathAfterDrive[0])
|
||||
&& compatIsPathSeparator(pathAfterDrive[1])) {
|
||||
pathAfterDrive += 2;
|
||||
while (*pathAfterDrive != '\0' && !compatIsPathSeparator(*pathAfterDrive) && *pathAfterDrive != '.') {
|
||||
pathAfterDrive++;
|
||||
}
|
||||
}
|
||||
|
||||
if (drive != nullptr) {
|
||||
strncpy(drive, fsPath.root_name().string().c_str(), COMPAT_MAX_DRIVE - 1);
|
||||
size_t driveSize = pathAfterDrive - driveStart;
|
||||
if (driveSize > COMPAT_MAX_DRIVE - 1) {
|
||||
driveSize = COMPAT_MAX_DRIVE - 1;
|
||||
}
|
||||
strncpy(drive, driveStart, driveSize);
|
||||
drive[driveSize] = '\0';
|
||||
}
|
||||
|
||||
const char* fnameStart = pathAfterDrive;
|
||||
for (const char* pch = pathAfterDrive; *pch != '\0'; pch++) {
|
||||
if (compatIsPathSeparator(*pch)) {
|
||||
fnameStart = pch + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const char* end = pathAfterDrive + strlen(pathAfterDrive);
|
||||
const char* extStart = end;
|
||||
for (const char* pch = end; pch != fnameStart; pch--) {
|
||||
if (pch[-1] == '.') {
|
||||
extStart = pch - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dir != nullptr) {
|
||||
strncpy(dir, fsPath.parent_path().string().c_str(), COMPAT_MAX_DIR - 1);
|
||||
size_t dirSize = fnameStart - pathAfterDrive;
|
||||
if (dirSize > COMPAT_MAX_DIR - 1) {
|
||||
dirSize = COMPAT_MAX_DIR - 1;
|
||||
}
|
||||
strncpy(dir, pathAfterDrive, dirSize);
|
||||
dir[dirSize] = '\0';
|
||||
}
|
||||
|
||||
if (fname != nullptr) {
|
||||
strncpy(fname, fsPath.stem().string().c_str(), COMPAT_MAX_FNAME - 1);
|
||||
size_t fileNameSize = extStart - fnameStart;
|
||||
if (fileNameSize > COMPAT_MAX_FNAME - 1) {
|
||||
fileNameSize = COMPAT_MAX_FNAME - 1;
|
||||
}
|
||||
strncpy(fname, fnameStart, fileNameSize);
|
||||
fname[fileNameSize] = '\0';
|
||||
}
|
||||
|
||||
if (ext != nullptr) {
|
||||
strncpy(ext, fsPath.extension().string().c_str(), COMPAT_MAX_EXT - 1);
|
||||
size_t extSize = end - extStart;
|
||||
if (extSize > COMPAT_MAX_EXT - 1) {
|
||||
extSize = COMPAT_MAX_EXT - 1;
|
||||
}
|
||||
strncpy(ext, extStart, extSize);
|
||||
ext[extSize] = '\0';
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void compat_makepath(char* path, const char* drive, const char* dir, const char* fname, const char* ext)
|
||||
{
|
||||
std::filesystem::path fsPath;
|
||||
#ifdef _WIN32
|
||||
_makepath(path, drive, dir, fname, ext);
|
||||
#else
|
||||
path[0] = '\0';
|
||||
|
||||
if (drive != nullptr && *drive != '\0') {
|
||||
fsPath /= drive;
|
||||
if (drive != nullptr) {
|
||||
if (*drive != '\0') {
|
||||
strcpy(path, drive);
|
||||
path = strchr(path, '\0');
|
||||
|
||||
if (compatIsPathSeparator(path[-1])) {
|
||||
path--;
|
||||
} else {
|
||||
*path = '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dir != nullptr && *dir != '\0') {
|
||||
fsPath /= dir;
|
||||
if (dir != nullptr) {
|
||||
if (*dir != '\0') {
|
||||
if (!compatIsPathSeparator(*dir) && compatIsPathSeparator(*path)) {
|
||||
path++;
|
||||
}
|
||||
|
||||
strcpy(path, dir);
|
||||
path = strchr(path, '\0');
|
||||
|
||||
if (compatIsPathSeparator(path[-1])) {
|
||||
path--;
|
||||
} else {
|
||||
*path = '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fname != nullptr && *fname != '\0') {
|
||||
fsPath /= fname;
|
||||
if (!compatIsPathSeparator(*fname) && compatIsPathSeparator(*path)) {
|
||||
path++;
|
||||
}
|
||||
|
||||
strcpy(path, fname);
|
||||
path = strchr(path, '\0');
|
||||
} else {
|
||||
if (compatIsPathSeparator(*path)) {
|
||||
path++;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext != nullptr && *ext != '\0') {
|
||||
fsPath += ext;
|
||||
if (ext != nullptr) {
|
||||
if (*ext != '\0') {
|
||||
if (*ext != '.') {
|
||||
*path++ = '.';
|
||||
}
|
||||
|
||||
strcpy(path, ext);
|
||||
path = strchr(path, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(path, fsPath.string().c_str(), COMPAT_MAX_PATH - 1);
|
||||
*path = '\0';
|
||||
#endif
|
||||
}
|
||||
|
||||
long compat_tell(int fd)
|
||||
@@ -104,48 +208,100 @@ long compat_tell(int fd)
|
||||
return lseek(fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
long compat_filelength(const char* path)
|
||||
long compat_filelength(int fd)
|
||||
{
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, path);
|
||||
return std::filesystem::file_size(nativePath);
|
||||
long originalOffset = lseek(fd, 0, SEEK_CUR);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
long filesize = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, originalOffset, SEEK_SET);
|
||||
return filesize;
|
||||
}
|
||||
|
||||
int compat_mkdir(const char* path)
|
||||
{
|
||||
std::error_code ec;
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, path);
|
||||
std::filesystem::create_directory(nativePath, ec);
|
||||
return ec.value();
|
||||
|
||||
#ifdef _WIN32
|
||||
return mkdir(nativePath);
|
||||
#else
|
||||
return mkdir(nativePath, 0755);
|
||||
#endif
|
||||
}
|
||||
|
||||
int compat_mkdir_recursive(const char* path)
|
||||
{
|
||||
std::error_code ec;
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, path);
|
||||
std::filesystem::create_directories(nativePath, ec);
|
||||
return ec.value();
|
||||
char drive[COMPAT_MAX_DRIVE];
|
||||
compat_splitpath(path, drive, nullptr, nullptr, nullptr);
|
||||
|
||||
char pathCopy[COMPAT_MAX_PATH];
|
||||
strcpy(pathCopy, path);
|
||||
|
||||
// Skip drive root (e.g. "C:\\" or leading "/") to avoid mkdir("") or mkdir("C:").
|
||||
char* sep = pathCopy + strlen(drive);
|
||||
if (*sep == '\\' || *sep == '/') sep++;
|
||||
for (; *sep != '\0'; sep++) {
|
||||
if (*sep == '\\' || *sep == '/') {
|
||||
char saved = *sep;
|
||||
*sep = '\0';
|
||||
if (compat_mkdir(pathCopy) < 0) {
|
||||
break;
|
||||
}
|
||||
*sep = saved;
|
||||
}
|
||||
}
|
||||
return compat_mkdir(path);
|
||||
}
|
||||
|
||||
bool compat_is_dir(const char* path)
|
||||
{
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, path);
|
||||
return std::filesystem::is_directory(nativePath);
|
||||
|
||||
#ifdef _WIN32
|
||||
struct _stat info;
|
||||
if (_stat(nativePath, &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
return (info.st_mode & _S_IFDIR) != 0;
|
||||
#else
|
||||
struct stat info;
|
||||
if (stat(nativePath, &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
return S_ISDIR(info.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool compat_file_exists(const char* filePath)
|
||||
{
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, filePath);
|
||||
return std::filesystem::exists(nativePath);
|
||||
|
||||
#ifdef _WIN32
|
||||
struct _stat info;
|
||||
if (_stat(nativePath, &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
return (info.st_mode & _S_IFDIR) == 0;
|
||||
#else
|
||||
struct stat info;
|
||||
if (stat(nativePath, &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
return !S_ISDIR(info.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int compat_timeGetTime()
|
||||
{
|
||||
return SDL_GetTicks64();
|
||||
#ifdef _WIN32
|
||||
return timeGetTime();
|
||||
#else
|
||||
static auto start = std::chrono::steady_clock::now();
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
return static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count());
|
||||
#endif
|
||||
}
|
||||
|
||||
FILE* compat_fopen(const char* path, const char* mode)
|
||||
@@ -194,25 +350,20 @@ char* compat_gzgets(gzFile stream, char* buffer, int maxCount)
|
||||
|
||||
int compat_remove(const char* path)
|
||||
{
|
||||
std::error_code err;
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, path);
|
||||
std::filesystem::remove(nativePath, err);
|
||||
return err.value();
|
||||
return remove(nativePath);
|
||||
}
|
||||
|
||||
int compat_rename(const char* oldFileName, const char* newFileName)
|
||||
{
|
||||
std::error_code err;
|
||||
|
||||
char nativeOldFileName[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativeOldFileName, oldFileName);
|
||||
|
||||
char nativeNewFileName[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativeNewFileName, newFileName);
|
||||
|
||||
std::filesystem::rename(nativeOldFileName, nativeNewFileName, err);
|
||||
return err.value();
|
||||
return rename(nativeOldFileName, nativeNewFileName);
|
||||
}
|
||||
|
||||
void compat_windows_path_to_native(char* path)
|
||||
@@ -295,9 +446,7 @@ int compat_access(const char* path, int mode)
|
||||
{
|
||||
char nativePath[COMPAT_MAX_PATH];
|
||||
compat_prepare_native_path(nativePath, path);
|
||||
std::filesystem::perms targetPrms = static_cast<std::filesystem::perms>(mode);
|
||||
std::filesystem::perms fsPrms = std::filesystem::status(nativePath).permissions();
|
||||
return targetPrms == fsPrms;
|
||||
return access(nativePath, mode);
|
||||
}
|
||||
|
||||
char* compat_strdup(const char* string)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
#ifndef PLATFORM_COMPAT_H
|
||||
#define PLATFORM_COMPAT_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <filesystem>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -33,7 +30,7 @@ char* compat_itoa(int value, char* buffer, int radix);
|
||||
void compat_splitpath(const char* path, char* drive, char* dir, char* fname, char* ext);
|
||||
void compat_makepath(char* path, const char* drive, const char* dir, const char* fname, const char* ext);
|
||||
long compat_tell(int fileHandle);
|
||||
long compat_filelength(const char* path);
|
||||
long compat_filelength(int fd);
|
||||
int compat_mkdir(const char* path);
|
||||
int compat_mkdir_recursive(const char* path);
|
||||
bool compat_is_dir(const char* path);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "debug.h"
|
||||
#include "game.h"
|
||||
#include "game_dialog.h"
|
||||
#include "game_movie.h"
|
||||
#include "input.h"
|
||||
#include "interface.h"
|
||||
#include "interpreter.h"
|
||||
@@ -211,6 +212,28 @@ static void op_get_year(Program* program)
|
||||
programStackPushInteger(program, year);
|
||||
}
|
||||
|
||||
static void op_set_movie_path(Program* program)
|
||||
{
|
||||
int movie = programStackPopInteger(program);
|
||||
const char* fileName = programStackPopString(program);
|
||||
if (movie < 0 || movie >= GAME_MOVIE_MAX_COUNT || fileName == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(fileName) > 64) {
|
||||
programPrintError("set_movie_path: filename exceeds 64 characters");
|
||||
return;
|
||||
}
|
||||
|
||||
gameMovieSetPath(movie, fileName);
|
||||
}
|
||||
|
||||
static void op_mark_movie_played(Program* program)
|
||||
{
|
||||
int movie = programStackPopInteger(program);
|
||||
gameMovieMarkSeen(movie);
|
||||
}
|
||||
|
||||
// game_loaded
|
||||
static void op_game_loaded(Program* program)
|
||||
{
|
||||
@@ -1962,6 +1985,7 @@ void sfallOpcodesInit()
|
||||
// 0x8175 - void set_dm_model(string name)
|
||||
// 0x8176 - void set_df_model(string name)
|
||||
// 0x8177 - void set_movie_path(string filename, int movieid)
|
||||
interpreterRegisterOpcode(0x8177, op_set_movie_path);
|
||||
|
||||
// 0x8178 - void set_perk_image(int perkID, int value)
|
||||
// 0x8179 - void set_perk_ranks(int perkID, int value)
|
||||
@@ -2292,6 +2316,7 @@ void sfallOpcodesInit()
|
||||
// 0x823f - void disable_aimed_shots(int pid)
|
||||
|
||||
// 0x8240 - void mark_movie_played(int id)
|
||||
interpreterRegisterOpcode(0x8240, op_mark_movie_played);
|
||||
|
||||
// 0x8248 - object get_last_target(object critter)
|
||||
// 0x8249 - object get_last_attacker(object critter)
|
||||
|
||||
Reference in New Issue
Block a user