From 1db054e47aa917d17e4ceebf2cc48df62714a271 Mon Sep 17 00:00:00 2001 From: Mike Klaas Date: Sun, 26 Jul 2026 14:07:55 -0700 Subject: [PATCH] [Et tu] Implement `Start{X|Y}Pos` (#585) * [Et tu] Implement `Start{X|Y}Pos`, `ViewX|Y` --- SFALL_COMPATIBILITY.md | 5 ++ files/ce.dat/config/game.cfg | 6 +++ src/game_config_migration.cc | 4 ++ src/main.cc | 13 +++++ src/worldmap.cc | 99 ++++++++++++++++++++++++++++++++++-- src/worldmap.h | 3 ++ 6 files changed, 126 insertions(+), 4 deletions(-) diff --git a/SFALL_COMPATIBILITY.md b/SFALL_COMPATIBILITY.md index 3afd17da..405f7313 100644 --- a/SFALL_COMPATIBILITY.md +++ b/SFALL_COMPATIBILITY.md @@ -42,6 +42,11 @@ The following settings were moved into [`/config/game.cfg`](files/ce.dat/co | ddraw.ini section | ddraw.ini key | game.cfg section | game.cfg key | | --- | --- | --- | --- | +| `Misc` | `StartingMap` | `start` | `map` | +| `Misc` | `StartXPos` | `start` | `worldmap_x` | +| `Misc` | `StartYPos` | `start` | `worldmap_y` | +| `Misc` | `ViewXPos` | `start` | `worldmap_view_x` | +| `Misc` | `ViewYPos` | `start` | `worldmap_view_y` | | `Misc` | `StartGDialogFix` | `dialog` | `start_gdialog_fix` | ## Opcodes / Metarules diff --git a/files/ce.dat/config/game.cfg b/files/ce.dat/config/game.cfg index 813e6bae..a2503f32 100644 --- a/files/ce.dat/config/game.cfg +++ b/files/ce.dat/config/game.cfg @@ -11,6 +11,12 @@ month=6 day=24 ; To start a new game somewhere other than artemple.map, set this to the map file name. map= +; Starting worldmap marker position. -1 uses the starting map's world area. +worldmap_x=-1 +worldmap_y=-1 +; Starting worldmap viewport position. -1 uses the default upper-left view. +worldmap_view_x=-1 +worldmap_view_y=-1 ; Starting (tribal) and default (jumpsuit) player models. Can also be changed in-game via script. model_male=hmwarr model_male_default=hmjmps diff --git a/src/game_config_migration.cc b/src/game_config_migration.cc index a363e4ef..65876830 100644 --- a/src/game_config_migration.cc +++ b/src/game_config_migration.cc @@ -175,6 +175,10 @@ namespace { constexpr SfallMigrationEntry kSfallMigrationEntries[] = { // [start] { kSfallMisc, "StartingMap", CONTENT_CONFIG_START_SECTION, "map", "" }, + { kSfallMisc, "StartXPos", CONTENT_CONFIG_START_SECTION, "worldmap_x", "-1" }, + { kSfallMisc, "StartYPos", CONTENT_CONFIG_START_SECTION, "worldmap_y", "-1" }, + { kSfallMisc, "ViewXPos", CONTENT_CONFIG_START_SECTION, "worldmap_view_x", "-1" }, + { kSfallMisc, "ViewYPos", CONTENT_CONFIG_START_SECTION, "worldmap_view_y", "-1" }, { kSfallMisc, "MaleStartModel", CONTENT_CONFIG_START_SECTION, "model_male", "hmwarr" }, { kSfallMisc, "MaleDefaultModel", CONTENT_CONFIG_START_SECTION, "model_male_default", "hmjmps" }, { kSfallMisc, "FemaleStartModel", CONTENT_CONFIG_START_SECTION, "model_female", "hfprim" }, diff --git a/src/main.cc b/src/main.cc index 1bc1fa59..d70b4a18 100644 --- a/src/main.cc +++ b/src/main.cc @@ -331,6 +331,19 @@ static int _main_load_new(char* mapFileName) gameMouseSetCursor(MOUSE_CURSOR_NONE); mouseShowCursor(); mapLoadByName(mapFileName); + + // SFALL: Fix the starting position of the player's marker on the world map + // when starting a new game with a custom starting map. + int areaIdx; + if (wmMatchAreaContainingMapIdx(gMapHeader.index, &areaIdx) == 0) { + if (wmStartWorldPosIsConfigured()) { + wmSetPartyCurArea(areaIdx); + wmClearPartyWalking(); + } else { + wmTeleportToArea(areaIdx); + } + } + wmMapMusicStart(); paletteFadeTo(gPaletteWhite); windowDestroy(win); diff --git a/src/worldmap.cc b/src/worldmap.cc index f455b422..da6d91af 100644 --- a/src/worldmap.cc +++ b/src/worldmap.cc @@ -480,6 +480,10 @@ static std::vector> wmTerrainNameOverrides; static void wmSetFlags(int* flagsPtr, int flag, int value); static int wmGenDataInit(); static int wmGenDataReset(); +static void wmGenDataSetStartWorldPos(); +static bool wmGetStartWorldMapConfigValue(const char* key, int* valuePtr); +static void wmGenDataClampWorldPosToBounds(); +static void wmSetStartWorldView(); static int wmWorldMapSaveTempData(); static int wmWorldMapLoadTempData(); static int wmConfigInit(); @@ -979,8 +983,11 @@ int wmWorldMap_init() return -1; } + wmGenDataClampWorldPosToBounds(); + wmGenData.viewportMaxX = WM_TILE_WIDTH * wmNumHorizontalTiles - WM_VIEW_WIDTH; wmGenData.viewportMaxY = WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles) - WM_VIEW_HEIGHT; + wmSetStartWorldView(); circleBlendTable = _getColorBlendTable(COLOR_GREEN); wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); @@ -1009,8 +1016,7 @@ static int wmGenDataInit() { gDidMeetFrankHorrigan = false; wmGenData.currentAreaId = -1; - wmGenData.worldPosX = 173; - wmGenData.worldPosY = 122; + wmGenDataSetStartWorldPos(); wmGenData.currentSubtile = nullptr; wmGenData.dword_672E18 = 0; wmGenData.isWalking = false; @@ -1076,8 +1082,8 @@ static int wmGenDataReset() wmGenData.encounterIconIsVisible = false; mousePressed = false; wmGenData.currentAreaId = -1; - wmGenData.worldPosX = 173; - wmGenData.worldPosY = 122; + wmGenDataSetStartWorldPos(); + wmGenDataClampWorldPosToBounds(); wmGenData.walkDestinationX = -1; wmGenData.walkDestinationY = -1; wmGenData.encounterMapId = -1; @@ -1113,6 +1119,70 @@ static int wmGenDataReset() return 0; } +static void wmGenDataSetStartWorldPos() +{ + wmGenData.worldPosX = 173; + wmGenData.worldPosY = 122; + + int value; + if (wmGetStartWorldMapConfigValue("worldmap_x", &value)) { + wmGenData.worldPosX = value; + } + + if (wmGetStartWorldMapConfigValue("worldmap_y", &value)) { + wmGenData.worldPosY = value; + } +} + +static bool wmGetStartWorldMapConfigValue(const char* key, int* valuePtr) +{ + assert(key != nullptr); + assert(valuePtr != nullptr); + + int value; + if (!configGetInt(&gContentConfig, CONTENT_CONFIG_START_SECTION, key, &value)) { + return false; + } + + if (value == -1) { + return false; + } + + *valuePtr = std::max(value, 0); + return true; +} + +static void wmGenDataClampWorldPosToBounds() +{ + if (wmNumHorizontalTiles <= 0 || wmMaxTileNum <= 0) { + return; + } + + int worldMaxX = WM_TILE_WIDTH * wmNumHorizontalTiles; + int worldMaxY = WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles); + if (worldMaxX <= 0 || worldMaxY <= 0) { + return; + } + + wmGenData.worldPosX = std::clamp(wmGenData.worldPosX, 0, worldMaxX - 1); + wmGenData.worldPosY = std::clamp(wmGenData.worldPosY, 0, worldMaxY - 1); +} + +static void wmSetStartWorldView() +{ + wmWorldOffsetX = 0; + wmWorldOffsetY = 0; + + int value; + if (wmGetStartWorldMapConfigValue("worldmap_view_x", &value)) { + wmWorldOffsetX = std::clamp(value, 0, std::max(wmGenData.viewportMaxX, 0)); + } + + if (wmGetStartWorldMapConfigValue("worldmap_view_y", &value)) { + wmWorldOffsetY = std::clamp(value, 0, std::max(wmGenData.viewportMaxY, 0)); + } +} + // 0x4BCE00 wmWorldMap_exit void wmWorldMap_exit() { @@ -1177,6 +1247,7 @@ int wmWorldMap_reset() gGameTimeIncRemainder = 0.0; wmWorldMapLoadTempData(); + wmSetStartWorldView(); wmMarkAllSubTiles(0); return wmGenDataReset(); @@ -6170,6 +6241,14 @@ int wmGetPartyCurArea(int* areaIdxPtr) return -1; } +bool wmStartWorldPosIsConfigured() +{ + int x; + int y; + return wmGetStartWorldMapConfigValue("worldmap_x", &x) + || wmGetStartWorldMapConfigValue("worldmap_y", &y); +} + // 0x4C47D8 wmMarkAllSubTiles static void wmMarkAllSubTiles(int state) { @@ -7087,6 +7166,18 @@ void wmSetPartyWorldPos(int x, int y) wmGenData.worldPosY = y; } +void wmSetPartyCurArea(int areaIdx) +{ + wmGenData.currentAreaId = cityIsValid(areaIdx) ? areaIdx : -1; +} + +void wmClearPartyWalking() +{ + wmGenData.walkDestinationX = 0; + wmGenData.walkDestinationY = 0; + wmGenData.isWalking = false; +} + void wmCarSetCurrentArea(int area) { wmGenData.currentCarAreaId = area; diff --git a/src/worldmap.h b/src/worldmap.h index bb00428e..87fb2bb2 100644 --- a/src/worldmap.h +++ b/src/worldmap.h @@ -285,6 +285,9 @@ int wmMatchAreaContainingMapIdx(int mapIdx, int* areaIdxPtr); int wmTeleportToArea(int areaIdx); // CE +bool wmStartWorldPosIsConfigured(); +void wmSetPartyCurArea(int areaIdx); +void wmClearPartyWalking(); void wmSetPartyWorldPos(int x, int y); void wmCarSetCurrentArea(int area); void wmForceEncounter(int map, unsigned int flags);