#include "worldmap.h" #include #include #include #include #include #include #include "animation.h" #include "art.h" #include "automap.h" #include "color.h" #include "combat.h" #include "combat_ai.h" #include "critter.h" #include "cycle.h" #include "db.h" #include "dbox.h" #include "debug.h" #include "display_monitor.h" #include "draw.h" #include "game.h" #include "game_mouse.h" #include "game_movie.h" #include "game_sound.h" #include "input.h" #include "interface.h" #include "item.h" #include "kb.h" #include "memory.h" #include "mouse.h" #include "object.h" #include "palette.h" #include "party_member.h" #include "perk.h" #include "proto_instance.h" #include "queue.h" #include "random.h" #include "scripts.h" #include "settings.h" #include "sfall_config.h" #include "sfall_global_scripts.h" #include "skill.h" #include "stat.h" #include "string_parsers.h" #include "svga.h" #include "text_font.h" #include "tile.h" #include "window_manager.h" namespace fallout { #define CITY_NAME_SIZE (40) #define TILE_WALK_MASK_NAME_SIZE (40) #define ENTRANCE_LIST_CAPACITY (10) // Up from 6 to handle `Tartar 3rd Floor 2` and `Livos Living Rooms` sfx // configuration in Olympus. #define MAP_AMBIENT_SOUND_EFFECTS_CAPACITY (7) #define MAP_STARTING_POINTS_CAPACITY (15) #define SUBTILE_GRID_WIDTH (7) #define SUBTILE_GRID_HEIGHT (6) #define ENCOUNTER_ENTRY_SPECIAL (0x01) #define ENCOUNTER_SUBINFO_DEAD (0x01) #define WM_WINDOW_DIAL_X (532) #define WM_WINDOW_DIAL_Y (48) #define WM_TOWN_LIST_SCROLL_UP_X (480) #define WM_TOWN_LIST_SCROLL_UP_Y (137) #define WM_TOWN_LIST_SCROLL_DOWN_X (WM_TOWN_LIST_SCROLL_UP_X) #define WM_TOWN_LIST_SCROLL_DOWN_Y (152) #define WM_WINDOW_GLOBE_OVERLAY_X (495) #define WM_WINDOW_GLOBE_OVERLAY_Y (330) #define WM_WINDOW_CAR_X (514) #define WM_WINDOW_CAR_Y (336) #define WM_WINDOW_CAR_OVERLAY_X (499) #define WM_WINDOW_CAR_OVERLAY_Y (330) #define WM_WINDOW_CAR_FUEL_BAR_X (500) #define WM_WINDOW_CAR_FUEL_BAR_Y (339) #define WM_WINDOW_CAR_FUEL_BAR_HEIGHT (70) #define WM_TOWN_WORLD_SWITCH_X (519) #define WM_TOWN_WORLD_SWITCH_Y (439) #define WM_TILE_WIDTH (350) #define WM_TILE_HEIGHT (300) #define WM_SUBTILE_SIZE (50) #define WM_WINDOW_WIDTH (640) #define WM_WINDOW_HEIGHT (480) #define WM_VIEW_X (22) #define WM_VIEW_Y (21) #define WM_VIEW_WIDTH (450) #define WM_VIEW_HEIGHT (443) typedef enum EncounterFormationType { ENCOUNTER_FORMATION_TYPE_SURROUNDING, ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE, ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE, ENCOUNTER_FORMATION_TYPE_WEDGE, ENCOUNTER_FORMATION_TYPE_CONE, ENCOUNTER_FORMATION_TYPE_HUDDLE, ENCOUNTER_FORMATION_TYPE_COUNT, } EncounterFormationType; typedef enum EncounterFrequencyType { ENCOUNTER_FREQUENCY_TYPE_NONE, ENCOUNTER_FREQUENCY_TYPE_RARE, ENCOUNTER_FREQUENCY_TYPE_UNCOMMON, ENCOUNTER_FREQUENCY_TYPE_COMMON, ENCOUNTER_FREQUENCY_TYPE_FREQUENT, ENCOUNTER_FREQUENCY_TYPE_FORCED, ENCOUNTER_FREQUENCY_TYPE_COUNT, } EncounterFrequencyType; typedef enum EncounterSceneryType { ENCOUNTER_SCENERY_TYPE_NONE, ENCOUNTER_SCENERY_TYPE_LIGHT, ENCOUNTER_SCENERY_TYPE_NORMAL, ENCOUNTER_SCENERY_TYPE_HEAVY, ENCOUNTER_SCENERY_TYPE_COUNT, } EncounterSceneryType; typedef enum EncounterSituation { ENCOUNTER_SITUATION_NOTHING, ENCOUNTER_SITUATION_AMBUSH, ENCOUNTER_SITUATION_FIGHTING, ENCOUNTER_SITUATION_AND, ENCOUNTER_SITUATION_COUNT, } EncounterSituation; typedef enum EncounterLogicalOperator { ENCOUNTER_LOGICAL_OPERATOR_NONE, ENCOUNTER_LOGICAL_OPERATOR_AND, ENCOUNTER_LOGICAL_OPERATOR_OR, } EncounterLogicalOperator; typedef enum EncounterConditionType { ENCOUNTER_CONDITION_TYPE_NONE = 0, ENCOUNTER_CONDITION_TYPE_GLOBAL = 1, ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS = 2, ENCOUNTER_CONDITION_TYPE_RANDOM = 3, ENCOUNTER_CONDITION_TYPE_PLAYER = 4, ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED = 5, ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY = 6, } EncounterConditionType; typedef enum EncounterConditionalOperator { ENCOUNTER_CONDITIONAL_OPERATOR_NONE, ENCOUNTER_CONDITIONAL_OPERATOR_EQUAL, ENCOUNTER_CONDITIONAL_OPERATOR_NOT_EQUAL, ENCOUNTER_CONDITIONAL_OPERATOR_LESS_THAN, ENCOUNTER_CONDITIONAL_OPERATOR_GREATER_THAN, ENCOUNTER_CONDITIONAL_OPERATOR_COUNT, } EncounterConditionalOperator; typedef enum Daytime { DAY_PART_MORNING, DAY_PART_AFTERNOON, DAY_PART_NIGHT, DAY_PART_COUNT, } Daytime; typedef enum LockState { LOCK_STATE_UNLOCKED, LOCK_STATE_LOCKED, } LockState; typedef enum SubtileState { SUBTILE_STATE_UNKNOWN, SUBTILE_STATE_KNOWN, SUBTILE_STATE_VISITED, } SubtileState; typedef enum SubtileFill { SUBTILE_FILL_NONE, SUBTILE_FILL_N, SUBTILE_FILL_S, SUBTILE_FILL_E, SUBTILE_FILL_W, SUBTILE_FILL_NW, SUBTILE_FILL_NE, SUBTILE_FILL_SW, SUBTILE_FILL_SE, SUBTILE_FILL_COUNT, } SubtileFill; typedef enum WorldMapEncounterFrm { WORLD_MAP_ENCOUNTER_FRM_RANDOM_BRIGHT, WORLD_MAP_ENCOUNTER_FRM_RANDOM_DARK, WORLD_MAP_ENCOUNTER_FRM_SPECIAL_BRIGHT, WORLD_MAP_ENCOUNTER_FRM_SPECIAL_DARK, WORLD_MAP_ENCOUNTER_FRM_COUNT, } WorldMapEncounterFrm; typedef enum WorldmapArrowFrm { WORLDMAP_ARROW_FRM_NORMAL, WORLDMAP_ARROW_FRM_PRESSED, WORLDMAP_ARROW_FRM_COUNT, } WorldmapArrowFrm; typedef enum CitySize { CITY_SIZE_SMALL, CITY_SIZE_MEDIUM, CITY_SIZE_LARGE, CITY_SIZE_COUNT, } CitySize; typedef struct EntranceInfo { int state; int x; int y; int map; int elevation; int tile; int rotation; } EntranceInfo; typedef struct CityInfo { char name[CITY_NAME_SIZE]; int areaId; int x; int y; int size; int state; int lockState; int visitedState; int mapFid; int labelFid; int entrancesLength; EntranceInfo entrances[ENTRANCE_LIST_CAPACITY]; } CityInfo; typedef struct MapAmbientSoundEffectInfo { char name[40]; int chance; } MapAmbientSoundEffectInfo; typedef struct MapStartPointInfo { int elevation; int tile; int rotation; } MapStartPointInfo; typedef struct MapInfo { char lookupName[40]; int field_28; int field_2C; char mapFileName[40]; char music[40]; int flags; int ambientSoundEffectsLength; MapAmbientSoundEffectInfo ambientSoundEffects[MAP_AMBIENT_SOUND_EFFECTS_CAPACITY]; int startPointsLength; MapStartPointInfo startPoints[MAP_STARTING_POINTS_CAPACITY]; } MapInfo; typedef struct Terrain { char lookupName[40]; int difficulty; int mapsLength; int maps[20]; } Terrain; typedef struct EncounterConditionEntry { int type; int conditionalOperator; int param; int value; } EncounterConditionEntry; typedef struct EncounterCondition { int entriesLength; EncounterConditionEntry entries[3]; int logicalOperators[2]; } EncounterCondition; typedef struct EncounterTableSubEntry { int minimumCount; int maximumCount; int encounterIndex; int situation; } EncounterTableSubEntry; typedef struct EncounterTableEntry { int flags; int map; int scenery; int chance; int counter; EncounterCondition condition; int subEntiesLength; EncounterTableSubEntry subEntries[6]; } EncounterTableEntry; typedef struct EncounterTable { char lookupName[40]; int index; int mapsLength; int maps[6]; int field_48; int entriesLength; EncounterTableEntry entries[41]; } EncounterTable; typedef struct EncounterItem { int pid; int minimumQuantity; int maximumQuantity; bool isEquipped; } EncounterItem; typedef struct EncounterEntry { char field_0[40]; int field_28; int field_2C; int ratio; int pid; int flags; int distance; int tile; int itemsLength; EncounterItem items[10]; int team; int scriptIdx; EncounterCondition condition; } EncounterEntry; typedef struct Encounter { char name[40]; int position; int spacing; int distance; int entriesLength; EncounterEntry entries[10]; } Encounter; typedef struct SubtileInfo { int terrain; int fill; int encounterChance[DAY_PART_COUNT]; int encounterType; int state; } SubtileInfo; // A worldmap tile is 7x6 area, thus consisting of 42 individual subtiles. typedef struct TileInfo { int fid; CacheEntry* handle; unsigned char* data; char walkMaskName[TILE_WALK_MASK_NAME_SIZE]; unsigned char* walkMaskData; int encounterDifficultyModifier; SubtileInfo subtiles[SUBTILE_GRID_HEIGHT][SUBTILE_GRID_WIDTH]; } TileInfo; typedef struct CitySizeDescription { int fid; FrmImage frmImage; } CitySizeDescription; typedef struct WmGenData { bool mousePressed; bool didMeetFrankHorrigan; int currentAreaId; int worldPosX; int worldPosY; SubtileInfo* currentSubtile; int dword_672E18; bool isWalking; int walkDestinationX; int walkDestinationY; int walkDistance; int walkLineDelta; int walkLineDeltaMainAxisStep; int walkLineDeltaCrossAxisStep; int walkWorldPosMainAxisStepX; int walkWorldPosCrossAxisStepX; int walkWorldPosMainAxisStepY; int walkWorldPosCrossAxisStepY; bool encounterIconIsVisible; int encounterMapId; int encounterTableId; int encounterEntryId; int encounterCursorId; int oldWorldPosX; int oldWorldPosY; bool isInCar; int currentCarAreaId; int carFuel; CacheEntry* carImageFrmHandle; Art* carImageFrm; int carImageFrmWidth; int carImageFrmHeight; int carImageCurrentFrameIndex; FrmImage hotspotNormalFrmImage; FrmImage hotspotPressedFrmImage; FrmImage destinationMarkerFrmImage; FrmImage locationMarkerFrmImage; FrmImage encounterCursorFrmImages[WORLD_MAP_ENCOUNTER_FRM_COUNT]; int viewportMaxX; int viewportMaxY; FrmImage tabsBackgroundFrmImage; int tabsOffsetY; FrmImage tabsBorderFrmImage; CacheEntry* dialFrmHandle; int dialFrmWidth; int dialFrmHeight; int dialFrmCurrentFrameIndex; Art* dialFrm; FrmImage carOverlayFrmImage; FrmImage globeOverlayFrmImage; int oldTabsOffsetY; int tabsScrollingDelta; FrmImage redButtonNormalFrmImage; FrmImage redButtonPressedFrmImage; FrmImage scrollUpButtonFrmImages[WORLDMAP_ARROW_FRM_COUNT]; FrmImage scrollDownButtonFrmImages[WORLDMAP_ARROW_FRM_COUNT]; FrmImage monthsFrmImage; FrmImage numbersFrmImage; int oldFont; } WmGenData; static void wmSetFlags(int* flagsPtr, int flag, int value); static int wmGenDataInit(); static int wmGenDataReset(); static int wmWorldMapSaveTempData(); static int wmWorldMapLoadTempData(); static int wmConfigInit(); static int wmReadEncounterType(Config* config, char* lookupName, char* sectionKey); static int wmParseEncounterTableIndex(EncounterTableEntry* encounterTableEntry, char* string); static int wmParseEncounterSubEncStr(EncounterTableEntry* encounterTableEntry, char** stringPtr); static int wmParseFindSubEncTypeMatch(char* str, int* valuePtr); static int wmFindEncBaseTypeMatch(char* str, int* valuePtr); static int wmReadEncBaseType(char* name, int* valuePtr); static int wmParseEncBaseSubTypeStr(EncounterEntry* encounterEntry, char** stringPtr); static int wmEncBaseTypeSlotInit(Encounter* encounter); static int wmEncBaseSubTypeSlotInit(EncounterEntry* encounterEntry); static int wmEncounterSubEncSlotInit(EncounterTableSubEntry* encounterTableSubEntry); static int wmEncounterTypeSlotInit(EncounterTableEntry* encounterTableEntry); static int wmEncounterTableSlotInit(EncounterTable* encounterTable); static int wmTileSlotInit(TileInfo* tile); static int wmTerrainTypeSlotInit(Terrain* terrain); static int wmConditionalDataInit(EncounterCondition* condition); static int wmParseTerrainTypes(Config* config, char* string); static int wmParseTerrainRndMaps(Config* config, Terrain* terrain); static int wmParseSubTileInfo(TileInfo* tile, int row, int column, char* string); static int wmParseFindEncounterTypeMatch(char* string, int* valuePtr); static int wmParseFindTerrainTypeMatch(char* string, int* valuePtr); static int wmParseEncounterItemType(char** stringPtr, EncounterItem* encounterItem, int* itemCountPtr, const char* delim); static int wmParseItemType(char* string, EncounterItem* encounterItem); static int wmParseConditional(char** stringPtr, const char* a2, EncounterCondition* condition); static int wmParseSubConditional(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr); static int wmParseConditionalEval(char** stringPtr, int* conditionalOperatorPtr); static int wmAreaSlotInit(CityInfo* area); static int wmAreaInit(); static int wmParseFindMapIdxMatch(char* string, int* valuePtr); static int wmEntranceSlotInit(EntranceInfo* entrance); static int wmMapSlotInit(MapInfo* map); static int wmMapInit(); static int wmRStartSlotInit(MapStartPointInfo* rsp); static int wmMatchEntranceFromMap(int areaIdx, int mapIdx, int* entranceIdxPtr); static int wmMatchEntranceElevFromMap(int areaIdx, int mapIdx, int elevation, int* entranceIdxPtr); static int wmMatchAreaFromMap(int mapIdx, int* areaIdxPtr); static int wmWorldMapFunc(int a1); static int wmInterfaceCenterOnParty(); static void wmCheckGameEvents(); static int wmRndEncounterOccurred(); static int wmPartyFindCurSubTile(); static int wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtilePtr); static int wmFindCurTileFromPos(int x, int y, TileInfo** tilePtr); static int wmRndEncounterPick(); static int wmSetupCritterObjs(int encounterIndex, Object** critterPtr, int critterCount); static int wmSetupRndNextTileNumInit(Encounter* encounter); static int wmSetupRndNextTileNum(Encounter* encounter, EncounterEntry* encounterEntry, int* tilePtr); static bool wmEvalConditional(EncounterCondition* encounterCondition, int* critterCountPtr); static bool wmEvalSubConditional(int operand1, int condionalOperator, int operand2); static bool wmGameTimeIncrement(int ticksToAdd); static int wmGrabTileWalkMask(int tileIdx); static bool wmWorldPosInvalid(int x, int y); static void wmPartyInitWalking(int x, int y); static void wmPartyWalkingStep(); static void wmInterfaceScrollTabsStart(int delta); static void wmInterfaceScrollTabsStop(); static void wmInterfaceScrollTabsUpdate(); static int wmInterfaceInit(); static int wmInterfaceExit(); static int wmInterfaceScroll(int dx, int dy, bool* successPtr); static int wmInterfaceScrollPixel(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh); static void wmMouseBkProc(); static int wmMarkSubTileOffsetVisited(int tile, int subtileX, int subtileY, int offsetX, int offsetY); static int wmMarkSubTileOffsetKnown(int tile, int subtileX, int subtileY, int offsetX, int offsetY); static int wmMarkSubTileOffsetVisitedFunc(int tile, int subtileX, int subtileY, int offsetX, int offsetY, int subtileState); static void wmMarkSubTileRadiusVisited(int x, int y); static int wmTileGrabArt(int tileIdx); static int wmInterfaceRefresh(); static void wmInterfaceRefreshDate(bool shouldRefreshWindow); static int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr); static int wmInterfaceDrawCircleOverlay(CityInfo* cityInfo, CitySizeDescription* citySizeInfo, unsigned char* buffer, int x, int y); static void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch); static int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6); static int wmDrawCursorStopped(); static bool wmCursorIsVisible(); static int wmGetAreaName(CityInfo* city, char* name); static void wmMarkAllSubTiles(int state); static int wmTownMapFunc(int* mapIdxPtr); static int wmTownMapInit(); static int wmTownMapRefresh(); static int wmTownMapExit(); static int wmRefreshInterfaceOverlay(bool shouldRefreshWindow); static void wmInterfaceRefreshCarFuel(); static int wmRefreshTabs(); static int wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr); static int wmTabsCompareNames(const void* a1, const void* a2); static int wmFreeTabsLabelList(int** quickDestinationsListPtr, int* quickDestinationsLengthPtr); static void wmRefreshInterfaceDial(bool shouldRefreshWindow); static void wmInterfaceDialSyncTime(bool shouldRefreshWindow); static int wmAreaFindFirstValidMap(int* mapIdxPtr); static void wmFadeOut(); static void wmFadeIn(); static void wmFadeReset(); static void wmBlinkRndEncounterIcon(bool special); // 0x4BC860 static const int _can_rest_here[ELEVATION_COUNT] = { MAP_CAN_REST_ELEVATION_0, MAP_CAN_REST_ELEVATION_1, MAP_CAN_REST_ELEVATION_2, }; // 0x4BC86C static const int gDayPartEncounterFrequencyModifiers[DAY_PART_COUNT] = { 40, 30, 0, }; // 0x4BC878 static const char* gWorldmapEncDefaultMsg[2] = { "You detect something up ahead.", "Do you wish to encounter it?", }; // 0x4BC880 static MessageListItem gWorldmapMessageListItem; // 0x50EE44 static char _aCricket[] = "cricket"; // 0x50EE4C static char _aCricket1[] = "cricket1"; // 0x51DD88 static const char* wmStateStrs[2] = { "off", "on" }; // 0x51DD90 static const char* wmYesNoStrs[2] = { "no", "yes", }; // 0x51DD98 static const char* wmFreqStrs[ENCOUNTER_FREQUENCY_TYPE_COUNT] = { "none", "rare", "uncommon", "common", "frequent", "forced", }; // 0x51DDB0 static const char* wmFillStrs[SUBTILE_FILL_COUNT] = { "no_fill", "fill_n", "fill_s", "fill_e", "fill_w", "fill_nw", "fill_ne", "fill_sw", "fill_se", }; // 0x51DDD4 static const char* wmSceneryStrs[ENCOUNTER_SCENERY_TYPE_COUNT] = { "none", "light", "normal", "heavy", }; // 0x51DDE4 static Terrain* wmTerrainTypeList = NULL; // 0x51DDE8 static int wmMaxTerrainTypes = 0; // 0x51DDEC static TileInfo* wmTileInfoList = NULL; // 0x51DDF0 static int wmMaxTileNum = 0; // The width of worldmap grid in tiles. // // There is no separate variable for grid height, instead its calculated as // [wmMaxTileNum] / [gWorldmapTilesGridWidth]. // // num_horizontal_tiles // 0x51DDF4 static int wmNumHorizontalTiles = 0; // 0x51DDF8 static CityInfo* wmAreaInfoList = NULL; // 0x51DDFC static int wmMaxAreaNum = 0; // 0x51DE00 static const char* wmAreaSizeStrs[CITY_SIZE_COUNT] = { "small", "medium", "large", }; // 0x51DE0C static MapInfo* wmMapInfoList = NULL; // 0x51DE10 static int wmMaxMapNum = 0; // 0x51DE14 static int wmBkWin = -1; // 0x51DE24 static unsigned char* wmBkWinBuf = NULL; // 0x51DE2C static int wmWorldOffsetX = 0; // 0x51DE30 static int wmWorldOffsetY = 0; // 0x51DE34 unsigned char* circleBlendTable = NULL; // 0x51DE38 static int wmInterfaceWasInitialized = 0; // 0x51DE3C static const char* wmEncOpStrs[ENCOUNTER_SITUATION_COUNT] = { "nothing", "ambush", "fighting", "and", }; // 0x51DE4C static const char* wmConditionalOpStrs[ENCOUNTER_CONDITIONAL_OPERATOR_COUNT] = { "_", "==", "!=", "<", ">", }; // 0x51DE64 static const char* wmConditionalQualifierStrs[2] = { "and", "or", }; // 0x51DE6C static const char* wmFormationStrs[ENCOUNTER_FORMATION_TYPE_COUNT] = { "surrounding", "straight_line", "double_line", "wedge", "cone", "huddle", }; // 0x51DE84 static const int wmRndCursorFids[WORLD_MAP_ENCOUNTER_FRM_COUNT] = { 154, 155, 438, 439, }; // 0x51DE94 static int* wmLabelList = NULL; // 0x51DE98 static int wmLabelCount = 0; // 0x51DE9C static int wmTownMapCurArea = -1; // 0x51DEA0 static unsigned int wmLastRndTime = 0; // 0x51DEA4 static int wmRndIndex = 0; // 0x51DEA8 static int wmRndCallCount = 0; // 0x51DEAC static int _terrainCounter = 1; // 0x51DEC8 static char* wmRemapSfxList[2] = { _aCricket, _aCricket1, }; // 0x672DB8 static int wmRndTileDirs[2]; // 0x672DC0 static int wmRndCenterTiles[2]; // 0x672DC8 static int wmRndCenterRotations[2]; // 0x672DD0 static int wmRndRotOffsets[2]; // Buttons for city entrances. // // 0x672DD8 static int wmTownMapButtonId[ENTRANCE_LIST_CAPACITY]; // NOTE: There are no symbols in |mapper2.exe| for the range between |wmGenData| // and |wmMsgFile| implying everything in between are fields of the large // struct. // // 0x672E00 static WmGenData wmGenData; // worldmap.msg // // 0x672FB0 static MessageList wmMsgFile; // 0x672FB8 static int wmFreqValues[6]; // 0x672FD0 static int wmRndOriginalCenterTile; // worldmap.txt // // 0x672FD4 static Config* pConfigCfg; // 0x672FD8 static int wmTownMapSubButtonIds[7]; // 0x672FF4 static Encounter* wmEncBaseTypeList; // 0x672FF8 static CitySizeDescription wmSphereData[CITY_SIZE_COUNT]; // 0x673034 static EncounterTable* wmEncounterTableList; // Number of enc_base_types. // // 0x673038 static int wmMaxEncBaseTypes; // 0x67303C static int wmMaxEncounterInfoTables; static bool gTownMapHotkeysFix; static double gGameTimeIncRemainder = 0.0; static FrmImage _backgroundFrmImage; static FrmImage _townFrmImage; static bool wmFaded = false; static inline bool cityIsValid(int city) { return city >= 0 && city < wmMaxAreaNum; } // 0x4BC890 static void wmSetFlags(int* flagsPtr, int flag, int value) { if (value) { *flagsPtr |= flag; } else { *flagsPtr &= ~flag; } } // 0x4BC89C int wmWorldMap_init() { char path[COMPAT_MAX_PATH]; if (wmGenDataInit() == -1) { return -1; } if (!messageListInit(&wmMsgFile)) { return -1; } snprintf(path, sizeof(path), "%s%s", asc_5186C8, "worldmap.msg"); if (!messageListLoad(&wmMsgFile, path)) { return -1; } if (wmConfigInit() == -1) { return -1; } wmGenData.viewportMaxX = WM_TILE_WIDTH * wmNumHorizontalTiles - WM_VIEW_WIDTH; wmGenData.viewportMaxY = WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles) - WM_VIEW_HEIGHT; circleBlendTable = _getColorBlendTable(_colorTable[992]); wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); wmWorldMapSaveTempData(); // SFALL gTownMapHotkeysFix = true; configGetBool(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_TOWN_MAP_HOTKEYS_FIX_KEY, &gTownMapHotkeysFix); // CE: City size fids should be initialized during startup. They are used // during |wmTeleportToArea| to calculate worldmap position when jumping // from Temple to Arroyo - before giving a chance to |wmInterfaceInit| to // initialize it. for (int citySize = 0; citySize < CITY_SIZE_COUNT; citySize++) { CitySizeDescription* citySizeDescription = &(wmSphereData[citySize]); citySizeDescription->fid = buildFid(OBJ_TYPE_INTERFACE, 336 + citySize, 0, 0, 0); } messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_WORLDMAP, &wmMsgFile); return 0; } // 0x4BC984 static int wmGenDataInit() { wmGenData.didMeetFrankHorrigan = false; wmGenData.currentAreaId = -1; wmGenData.worldPosX = 173; wmGenData.worldPosY = 122; wmGenData.currentSubtile = NULL; wmGenData.dword_672E18 = 0; wmGenData.isWalking = false; wmGenData.walkDestinationX = -1; wmGenData.walkDestinationY = -1; wmGenData.walkDistance = 0; wmGenData.walkLineDelta = 0; wmGenData.walkLineDeltaMainAxisStep = 0; wmGenData.walkLineDeltaCrossAxisStep = 0; wmGenData.walkWorldPosMainAxisStepX = 0; wmGenData.walkWorldPosMainAxisStepY = 0; wmGenData.walkWorldPosCrossAxisStepY = 0; wmGenData.encounterIconIsVisible = false; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; wmGenData.encounterCursorId = -1; wmGenData.oldWorldPosX = 0; wmGenData.oldWorldPosY = 0; wmGenData.isInCar = false; wmGenData.currentCarAreaId = -1; wmGenData.carFuel = CAR_FUEL_MAX; wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageFrmWidth = 0; wmGenData.carImageFrmHeight = 0; wmGenData.carImageCurrentFrameIndex = 0; wmGenData.mousePressed = false; wmGenData.walkWorldPosCrossAxisStepX = 0; wmGenData.carImageFrm = NULL; wmGenData.viewportMaxY = 0; wmGenData.tabsOffsetY = 0; wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY; wmGenData.dialFrm = NULL; wmGenData.dialFrmWidth = 0; wmGenData.dialFrmHeight = 0; wmGenData.dialFrmCurrentFrameIndex = 0; wmGenData.oldTabsOffsetY = 0; wmGenData.tabsScrollingDelta = 0; wmGenData.viewportMaxX = 0; return 0; } // 0x4BCBFC static int wmGenDataReset() { wmGenData.didMeetFrankHorrigan = false; wmGenData.currentSubtile = NULL; wmGenData.dword_672E18 = 0; wmGenData.isWalking = false; wmGenData.walkDistance = 0; wmGenData.walkLineDelta = 0; wmGenData.walkLineDeltaMainAxisStep = 0; wmGenData.walkLineDeltaCrossAxisStep = 0; wmGenData.walkWorldPosMainAxisStepX = 0; wmGenData.walkWorldPosMainAxisStepY = 0; wmGenData.walkWorldPosCrossAxisStepY = 0; wmGenData.encounterIconIsVisible = false; wmGenData.mousePressed = false; wmGenData.currentAreaId = -1; wmGenData.worldPosX = 173; wmGenData.worldPosY = 122; wmGenData.walkDestinationX = -1; wmGenData.walkDestinationY = -1; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; wmGenData.encounterCursorId = -1; wmGenData.currentCarAreaId = -1; wmGenData.carFuel = CAR_FUEL_MAX; wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY; wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY; wmGenData.walkWorldPosCrossAxisStepX = 0; wmGenData.oldWorldPosX = 0; wmGenData.oldWorldPosY = 0; wmGenData.isInCar = false; wmGenData.carImageFrmWidth = 0; wmGenData.carImageFrmHeight = 0; wmGenData.carImageCurrentFrameIndex = 0; wmGenData.tabsOffsetY = 0; wmGenData.dialFrm = NULL; wmGenData.dialFrmWidth = 0; wmGenData.dialFrmHeight = 0; wmGenData.dialFrmCurrentFrameIndex = 0; wmGenData.oldTabsOffsetY = 0; wmGenData.tabsScrollingDelta = 0; wmGenData.carImageFrm = NULL; wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); return 0; } // 0x4BCE00 void wmWorldMap_exit() { if (wmTerrainTypeList != NULL) { internal_free(wmTerrainTypeList); wmTerrainTypeList = NULL; } if (wmTileInfoList) { internal_free(wmTileInfoList); wmTileInfoList = NULL; } wmNumHorizontalTiles = 0; wmMaxTileNum = 0; if (wmEncounterTableList != NULL) { internal_free(wmEncounterTableList); wmEncounterTableList = NULL; } wmMaxEncounterInfoTables = 0; if (wmEncBaseTypeList != NULL) { internal_free(wmEncBaseTypeList); wmEncBaseTypeList = NULL; } wmMaxEncBaseTypes = 0; if (wmAreaInfoList != NULL) { internal_free(wmAreaInfoList); wmAreaInfoList = NULL; } wmMaxAreaNum = 0; if (wmMapInfoList != NULL) { internal_free(wmMapInfoList); } wmMaxMapNum = 0; if (circleBlendTable != NULL) { _freeColorBlendTable(_colorTable[992]); circleBlendTable = NULL; } messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_WORLDMAP, nullptr); messageListFree(&wmMsgFile); } // 0x4BCEF8 int wmWorldMap_reset() { wmWorldOffsetX = 0; wmWorldOffsetY = 0; // CE: Fix Pathfinder perk. gGameTimeIncRemainder = 0.0; wmWorldMapLoadTempData(); wmMarkAllSubTiles(0); return wmGenDataReset(); } // 0x4BCF28 int wmWorldMap_save(File* stream) { int i; int j; int k; EncounterTable* encounter_table; EncounterTableEntry* encounter_entry; if (fileWriteBool(stream, wmGenData.didMeetFrankHorrigan) == -1) return -1; if (fileWriteInt32(stream, wmGenData.currentAreaId) == -1) return -1; if (fileWriteInt32(stream, wmGenData.worldPosX) == -1) return -1; if (fileWriteInt32(stream, wmGenData.worldPosY) == -1) return -1; if (fileWriteBool(stream, wmGenData.encounterIconIsVisible) == -1) return -1; if (fileWriteInt32(stream, wmGenData.encounterMapId) == -1) return -1; if (fileWriteInt32(stream, wmGenData.encounterTableId) == -1) return -1; if (fileWriteInt32(stream, wmGenData.encounterEntryId) == -1) return -1; if (fileWriteBool(stream, wmGenData.isInCar) == -1) return -1; if (fileWriteInt32(stream, wmGenData.currentCarAreaId) == -1) return -1; if (fileWriteInt32(stream, wmGenData.carFuel) == -1) return -1; if (fileWriteInt32(stream, wmMaxAreaNum) == -1) return -1; for (int areaIdx = 0; areaIdx < wmMaxAreaNum; areaIdx++) { CityInfo* cityInfo = &(wmAreaInfoList[areaIdx]); if (fileWriteInt32(stream, cityInfo->x) == -1) return -1; if (fileWriteInt32(stream, cityInfo->y) == -1) return -1; if (fileWriteInt32(stream, cityInfo->state) == -1) return -1; if (fileWriteInt32(stream, cityInfo->visitedState) == -1) return -1; if (fileWriteInt32(stream, cityInfo->entrancesLength) == -1) return -1; for (int entranceIdx = 0; entranceIdx < cityInfo->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(cityInfo->entrances[entranceIdx]); if (fileWriteInt32(stream, entrance->state) == -1) return -1; } } if (fileWriteInt32(stream, wmMaxTileNum) == -1) return -1; if (fileWriteInt32(stream, wmNumHorizontalTiles) == -1) return -1; for (int tileIndex = 0; tileIndex < wmMaxTileNum; tileIndex++) { TileInfo* tileInfo = &(wmTileInfoList[tileIndex]); for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { SubtileInfo* subtile = &(tileInfo->subtiles[column][row]); if (fileWriteInt32(stream, subtile->state) == -1) return -1; } } } k = 0; for (i = 0; i < wmMaxEncounterInfoTables; i++) { encounter_table = &(wmEncounterTableList[i]); for (j = 0; j < encounter_table->entriesLength; j++) { encounter_entry = &(encounter_table->entries[j]); if (encounter_entry->counter != -1) { k++; } } } if (fileWriteInt32(stream, k) == -1) return -1; for (i = 0; i < wmMaxEncounterInfoTables; i++) { encounter_table = &(wmEncounterTableList[i]); for (j = 0; j < encounter_table->entriesLength; j++) { encounter_entry = &(encounter_table->entries[j]); if (encounter_entry->counter != -1) { if (fileWriteInt32(stream, i) == -1) return -1; if (fileWriteInt32(stream, j) == -1) return -1; if (fileWriteInt32(stream, encounter_entry->counter) == -1) return -1; } } } return 0; } // 0x4BD28C int wmWorldMap_load(File* stream) { if (fileReadBool(stream, &(wmGenData.didMeetFrankHorrigan)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.currentAreaId)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.worldPosX)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.worldPosY)) == -1) return -1; if (fileReadBool(stream, &(wmGenData.encounterIconIsVisible)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.encounterMapId)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.encounterTableId)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.encounterEntryId)) == -1) return -1; if (fileReadBool(stream, &(wmGenData.isInCar)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.currentCarAreaId)) == -1) return -1; if (fileReadInt32(stream, &(wmGenData.carFuel)) == -1) return -1; int numCities; if (fileReadInt32(stream, &numCities) == -1) return -1; for (int areaIdx = 0; areaIdx < numCities; areaIdx++) { CityInfo* city = &(wmAreaInfoList[areaIdx]); if (fileReadInt32(stream, &(city->x)) == -1) return -1; if (fileReadInt32(stream, &(city->y)) == -1) return -1; if (fileReadInt32(stream, &(city->state)) == -1) return -1; if (fileReadInt32(stream, &(city->visitedState)) == -1) return -1; int entranceCount; if (fileReadInt32(stream, &(entranceCount)) == -1) { return -1; } for (int entranceIdx = 0; entranceIdx < entranceCount; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (fileReadInt32(stream, &(entrance->state)) == -1) { return -1; } } } int numTiles; if (fileReadInt32(stream, &numTiles) == -1) return -1; int numHorizontalTiles; if (fileReadInt32(stream, &numHorizontalTiles) == -1) return -1; for (int tileIndex = 0; tileIndex < numTiles; tileIndex++) { TileInfo* tile = &(wmTileInfoList[tileIndex]); for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { SubtileInfo* subtile = &(tile->subtiles[column][row]); if (fileReadInt32(stream, &(subtile->state)) == -1) return -1; } } } int numCounters; if (fileReadInt32(stream, &numCounters) == -1) return -1; for (int counterIdx = 0; counterIdx < numCounters; counterIdx++) { int encounterTableIdx; int encounterTableEntryIdx; if (fileReadInt32(stream, &encounterTableIdx) == -1) return -1; EncounterTable* encounterTable = &(wmEncounterTableList[encounterTableIdx]); if (fileReadInt32(stream, &encounterTableEntryIdx) == -1) return -1; EncounterTableEntry* encounterTableEntry = &(encounterTable->entries[encounterTableEntryIdx]); if (fileReadInt32(stream, &(encounterTableEntry->counter)) == -1) return -1; } wmInterfaceCenterOnParty(); return 0; } // 0x4BD678 static int wmWorldMapSaveTempData() { File* stream = fileOpen("worldmap.dat", "wb"); if (stream == NULL) { return -1; } int rc = 0; if (wmWorldMap_save(stream) == -1) { rc = -1; } fileClose(stream); return rc; } // 0x4BD6B4 static int wmWorldMapLoadTempData() { File* stream = fileOpen("worldmap.dat", "rb"); if (stream == NULL) { return -1; } int rc = 0; if (wmWorldMap_load(stream) == -1) { rc = -1; } fileClose(stream); return rc; } // 0x4BD6F0 static int wmConfigInit() { if (wmAreaInit() == -1) { return -1; } Config config; if (!configInit(&config)) { return -1; } if (configRead(&config, "data\\worldmap.txt", true)) { for (int index = 0; index < ENCOUNTER_FREQUENCY_TYPE_COUNT; index++) { if (!configGetInt(&config, "data", wmFreqStrs[index], &(wmFreqValues[index]))) { break; } } char* terrainTypes; configGetString(&config, "data", "terrain_types", &terrainTypes); wmParseTerrainTypes(&config, terrainTypes); for (int index = 0;; index++) { char section[40]; snprintf(section, sizeof(section), "Encounter Table %d", index); char* lookupName; if (!configGetString(&config, section, "lookup_name", &lookupName)) { break; } if (wmReadEncounterType(&config, lookupName, section) == -1) { return -1; } } if (!configGetInt(&config, "Tile Data", "num_horizontal_tiles", &wmNumHorizontalTiles)) { showMesageBox("\nwmConfigInit::Error loading tile data!"); return -1; } for (int tileIndex = 0; tileIndex < 9999; tileIndex++) { char section[40]; snprintf(section, sizeof(section), "Tile %d", tileIndex); int artIndex; if (!configGetInt(&config, section, "art_idx", &artIndex)) { break; } wmMaxTileNum++; TileInfo* worldmapTiles = (TileInfo*)internal_realloc(wmTileInfoList, sizeof(*wmTileInfoList) * wmMaxTileNum); if (worldmapTiles == NULL) { showMesageBox("\nwmConfigInit::Error loading tiles!"); exit(1); } wmTileInfoList = worldmapTiles; TileInfo* tile = &(worldmapTiles[wmMaxTileNum - 1]); // NOTE: Uninline. wmTileSlotInit(tile); tile->fid = buildFid(OBJ_TYPE_INTERFACE, artIndex, 0, 0, 0); int encounterDifficulty; if (configGetInt(&config, section, "encounter_difficulty", &encounterDifficulty)) { tile->encounterDifficultyModifier = encounterDifficulty; } char* walkMaskName; if (configGetString(&config, section, "walk_mask_name", &walkMaskName)) { strncpy(tile->walkMaskName, walkMaskName, TILE_WALK_MASK_NAME_SIZE); } for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { char key[40]; snprintf(key, sizeof(key), "%d_%d", row, column); char* subtileProps; if (!configGetString(&config, section, key, &subtileProps)) { showMesageBox("\nwmConfigInit::Error loading tiles!"); exit(1); } if (wmParseSubTileInfo(tile, row, column, subtileProps) == -1) { showMesageBox("\nwmConfigInit::Error loading tiles!"); exit(1); } } } } } configFree(&config); return 0; } // 0x4BD9F0 static int wmReadEncounterType(Config* config, char* lookupName, char* sectionKey) { wmMaxEncounterInfoTables++; EncounterTable* encounterTables = (EncounterTable*)internal_realloc(wmEncounterTableList, sizeof(EncounterTable) * wmMaxEncounterInfoTables); if (encounterTables == NULL) { showMesageBox("\nwmConfigInit::Error loading Encounter Table!"); exit(1); } wmEncounterTableList = encounterTables; EncounterTable* encounterTable = &(encounterTables[wmMaxEncounterInfoTables - 1]); // NOTE: Uninline. wmEncounterTableSlotInit(encounterTable); encounterTable->index = wmMaxEncounterInfoTables - 1; strncpy(encounterTable->lookupName, lookupName, 40); char* str; if (configGetString(config, sectionKey, "maps", &str)) { while (*str != '\0') { if (encounterTable->mapsLength >= 6) { break; } if (strParseStrFromFunc(&str, &(encounterTable->maps[encounterTable->mapsLength]), wmParseFindMapIdxMatch) == -1) { break; } encounterTable->mapsLength++; } } for (;;) { char key[40]; snprintf(key, sizeof(key), "enc_%02d", encounterTable->entriesLength); char* str; if (!configGetString(config, sectionKey, key, &str)) { break; } if (encounterTable->entriesLength >= 40) { showMesageBox("\nwmConfigInit::Error: Encounter Table: Too many table indexes!!"); exit(1); } pConfigCfg = config; if (wmParseEncounterTableIndex(&(encounterTable->entries[encounterTable->entriesLength]), str) == -1) { return -1; } encounterTable->entriesLength++; } return 0; } // 0x4BDB64 static int wmParseEncounterTableIndex(EncounterTableEntry* encounterTableEntry, char* string) { // NOTE: Uninline. if (wmEncounterTypeSlotInit(encounterTableEntry) == -1) { return -1; } while (string != NULL && *string != '\0') { strParseIntWithKey(&string, "chance", &(encounterTableEntry->chance), ":"); strParseIntWithKey(&string, "counter", &(encounterTableEntry->counter), ":"); if (strstr(string, "special")) { encounterTableEntry->flags |= ENCOUNTER_ENTRY_SPECIAL; // CE: Original code unconditionally consumes 8 characters, which is // right when "special" is followed by conditions (separated with // comma). However when "special" is the last keyword (which I guess // is wrong, but present in worldmap.txt), consuming 8 characters // sets pointer past NULL terminator, which can lead to many bad // things (UB). string += 7; if (*string != '\0') { string++; } } if (string != NULL) { char* pch = strstr(string, "map:"); if (pch != NULL) { string = pch + 4; strParseStrFromFunc(&string, &(encounterTableEntry->map), wmParseFindMapIdxMatch); } } if (wmParseEncounterSubEncStr(encounterTableEntry, &string) == -1) { break; } if (string != NULL) { char* pch = strstr(string, "scenery:"); if (pch != NULL) { string = pch + 8; strParseStrFromList(&string, &(encounterTableEntry->scenery), wmSceneryStrs, ENCOUNTER_SCENERY_TYPE_COUNT); } } wmParseConditional(&string, "if", &(encounterTableEntry->condition)); } return 0; } // 0x4BDCA8 static int wmParseEncounterSubEncStr(EncounterTableEntry* encounterTableEntry, char** stringPtr) { char* string = *stringPtr; if (compat_strnicmp(string, "enc:", 4) != 0) { return -1; } // Consume "enc:". string += 4; char* comma = strstr(string, ","); if (comma != NULL) { // Comma is present, position string pointer to the next chunk. *stringPtr = comma + 1; *comma = '\0'; } else { // No comma, this chunk is the last one. *stringPtr = NULL; } while (string != NULL) { EncounterTableSubEntry* encounterTableSubEntry = &(encounterTableEntry->subEntries[encounterTableEntry->subEntiesLength]); // NOTE: Uninline. wmEncounterSubEncSlotInit(encounterTableSubEntry); if (*string == '(') { string++; encounterTableSubEntry->minimumCount = atoi(string); while (*string != '\0' && *string != '-') { string++; } if (*string == '-') { string++; } encounterTableSubEntry->maximumCount = atoi(string); while (*string != '\0' && *string != ')') { string++; } if (*string == ')') { string++; } } while (*string == ' ') { string++; } char* end = string; while (*end != '\0' && *end != ' ') { end++; } char ch = *end; *end = '\0'; if (strParseStrFromFunc(&string, &(encounterTableSubEntry->encounterIndex), wmParseFindSubEncTypeMatch) == -1) { return -1; } *end = ch; if (ch == ' ') { string++; } end = string; while (*end != '\0' && *end != ' ') { end++; } ch = *end; *end = '\0'; if (*string != '\0') { strParseStrFromList(&string, &(encounterTableSubEntry->situation), wmEncOpStrs, ENCOUNTER_SITUATION_COUNT); } *end = ch; encounterTableEntry->subEntiesLength++; while (*string == ' ') { string++; } if (*string == '\0') { string = NULL; } } if (comma != NULL) { *comma = ','; } return 0; } // 0x4BDE94 static int wmParseFindSubEncTypeMatch(char* str, int* valuePtr) { *valuePtr = 0; if (compat_stricmp(str, "player") == 0) { *valuePtr = -1; return 0; } if (wmFindEncBaseTypeMatch(str, valuePtr) == 0) { return 0; } if (wmReadEncBaseType(str, valuePtr) == 0) { return 0; } return -1; } // 0x4BDED8 static int wmFindEncBaseTypeMatch(char* str, int* valuePtr) { for (int index = 0; index < wmMaxEncBaseTypes; index++) { if (compat_stricmp(wmEncBaseTypeList[index].name, str) == 0) { *valuePtr = index; return 0; } } *valuePtr = -1; return -1; } // 0x4BDF34 static int wmReadEncBaseType(char* name, int* valuePtr) { char section[40]; snprintf(section, sizeof(section), "Encounter: %s", name); char key[40]; snprintf(key, sizeof(key), "type_00"); char* string; if (!configGetString(pConfigCfg, section, key, &string)) { return -1; } wmMaxEncBaseTypes++; Encounter* encounters = (Encounter*)internal_realloc(wmEncBaseTypeList, sizeof(*wmEncBaseTypeList) * wmMaxEncBaseTypes); if (encounters == NULL) { showMesageBox("\nwmConfigInit::Error Reading EncBaseType!"); exit(1); } wmEncBaseTypeList = encounters; Encounter* encounter = &(encounters[wmMaxEncBaseTypes - 1]); // NOTE: Uninline. wmEncBaseTypeSlotInit(encounter); strncpy(encounter->name, name, 40); while (1) { if (wmParseEncBaseSubTypeStr(&(encounter->entries[encounter->entriesLength]), &string) == -1) { return -1; } encounter->entriesLength++; snprintf(key, sizeof(key), "type_%02d", encounter->entriesLength); if (!configGetString(pConfigCfg, section, key, &string)) { int team; configGetInt(pConfigCfg, section, "team_num", &team); for (int index = 0; index < encounter->entriesLength; index++) { EncounterEntry* encounterEntry = &(encounter->entries[index]); if (PID_TYPE(encounterEntry->pid) == OBJ_TYPE_CRITTER) { encounterEntry->team = team; } } if (configGetString(pConfigCfg, section, "position", &string)) { strParseStrFromList(&string, &(encounter->position), wmFormationStrs, ENCOUNTER_FORMATION_TYPE_COUNT); strParseIntWithKey(&string, "spacing", &(encounter->spacing), ":"); strParseIntWithKey(&string, "distance", &(encounter->distance), ":"); } *valuePtr = wmMaxEncBaseTypes - 1; return 0; } } return -1; } // 0x4BE140 static int wmParseEncBaseSubTypeStr(EncounterEntry* encounterEntry, char** stringPtr) { char* string = *stringPtr; // NOTE: Uninline. if (wmEncBaseSubTypeSlotInit(encounterEntry) == -1) { return -1; } if (strParseIntWithKey(&string, "ratio", &(encounterEntry->ratio), ":") == 0) { encounterEntry->field_2C = 0; } if (strstr(string, "dead,") == string) { encounterEntry->flags |= ENCOUNTER_SUBINFO_DEAD; string += 5; } strParseIntWithKey(&string, "pid", &(encounterEntry->pid), ":"); if (encounterEntry->pid == 0) { encounterEntry->pid = -1; } strParseIntWithKey(&string, "distance", &(encounterEntry->distance), ":"); strParseIntWithKey(&string, "tilenum", &(encounterEntry->tile), ":"); for (int index = 0; index < 10; index++) { if (strstr(string, "item:") == NULL) { break; } wmParseEncounterItemType(&string, &(encounterEntry->items[encounterEntry->itemsLength]), &(encounterEntry->itemsLength), ":"); } strParseIntWithKey(&string, "script", &(encounterEntry->scriptIdx), ":"); wmParseConditional(&string, "if", &(encounterEntry->condition)); return 0; } // NOTE: Inlined. // // 0x4BE2A0 static int wmEncBaseTypeSlotInit(Encounter* encounter) { encounter->name[0] = '\0'; encounter->position = ENCOUNTER_FORMATION_TYPE_SURROUNDING; encounter->spacing = 1; encounter->distance = -1; encounter->entriesLength = 0; return 0; } // NOTE: Inlined. // // 0x4BE2C4 static int wmEncBaseSubTypeSlotInit(EncounterEntry* encounterEntry) { encounterEntry->field_28 = -1; encounterEntry->field_2C = 1; encounterEntry->ratio = 100; encounterEntry->pid = -1; encounterEntry->flags = 0; encounterEntry->distance = 0; encounterEntry->tile = -1; encounterEntry->itemsLength = 0; encounterEntry->scriptIdx = -1; encounterEntry->team = -1; return wmConditionalDataInit(&(encounterEntry->condition)); } // NOTE: Inlined. // // 0x4BE32C static int wmEncounterSubEncSlotInit(EncounterTableSubEntry* encounterTableSubEntry) { encounterTableSubEntry->minimumCount = 1; encounterTableSubEntry->maximumCount = 1; encounterTableSubEntry->encounterIndex = -1; encounterTableSubEntry->situation = ENCOUNTER_SITUATION_NOTHING; return 0; } // NOTE: Inlined. // // 0x4BE34C static int wmEncounterTypeSlotInit(EncounterTableEntry* encounterTableEntry) { encounterTableEntry->flags = 0; encounterTableEntry->map = -1; encounterTableEntry->scenery = ENCOUNTER_SCENERY_TYPE_NORMAL; encounterTableEntry->chance = 0; encounterTableEntry->counter = -1; encounterTableEntry->subEntiesLength = 0; return wmConditionalDataInit(&(encounterTableEntry->condition)); } // NOTE: Inlined. // // 0x4BE3B8 static int wmEncounterTableSlotInit(EncounterTable* encounterTable) { encounterTable->lookupName[0] = '\0'; encounterTable->mapsLength = 0; encounterTable->field_48 = 0; encounterTable->entriesLength = 0; return 0; } // NOTE: Inlined. // // 0x4BE3D4 static int wmTileSlotInit(TileInfo* tile) { tile->fid = -1; tile->handle = INVALID_CACHE_ENTRY; tile->data = NULL; tile->walkMaskName[0] = '\0'; tile->walkMaskData = NULL; tile->encounterDifficultyModifier = 0; return 0; } // NOTE: Inlined. // // 0x4BE400 static int wmTerrainTypeSlotInit(Terrain* terrain) { terrain->lookupName[0] = '\0'; terrain->difficulty = 0; terrain->mapsLength = 0; return 0; } // 0x4BE378 static int wmConditionalDataInit(EncounterCondition* condition) { condition->entriesLength = 0; for (int index = 0; index < 3; index++) { EncounterConditionEntry* conditionEntry = &(condition->entries[index]); conditionEntry->type = ENCOUNTER_CONDITION_TYPE_NONE; conditionEntry->conditionalOperator = ENCOUNTER_CONDITIONAL_OPERATOR_NONE; conditionEntry->param = 0; conditionEntry->value = 0; } for (int index = 0; index < 2; index++) { condition->logicalOperators[index] = ENCOUNTER_LOGICAL_OPERATOR_NONE; } return 0; } // 0x4BE414 static int wmParseTerrainTypes(Config* config, char* string) { if (*string == '\0') { return -1; } int terrainCount = 1; char* pch = string; while (*pch != '\0') { if (*pch == ',') { terrainCount++; } pch++; } wmMaxTerrainTypes = terrainCount; wmTerrainTypeList = (Terrain*)internal_malloc(sizeof(*wmTerrainTypeList) * terrainCount); if (wmTerrainTypeList == NULL) { return -1; } for (int index = 0; index < wmMaxTerrainTypes; index++) { Terrain* terrain = &(wmTerrainTypeList[index]); // NOTE: Uninline. wmTerrainTypeSlotInit(terrain); } compat_strlwr(string); pch = string; for (int index = 0; index < wmMaxTerrainTypes; index++) { Terrain* terrain = &(wmTerrainTypeList[index]); pch += strspn(pch, " "); size_t endPos = strcspn(pch, ","); char end = pch[endPos]; pch[endPos] = '\0'; size_t delimeterPos = strcspn(pch, ":"); char delimeter = pch[delimeterPos]; pch[delimeterPos] = '\0'; strncpy(terrain->lookupName, pch, 40); terrain->difficulty = atoi(pch + delimeterPos + 1); pch[delimeterPos] = delimeter; pch[endPos] = end; if (end == ',') { pch += endPos + 1; } } for (int index = 0; index < wmMaxTerrainTypes; index++) { wmParseTerrainRndMaps(config, &(wmTerrainTypeList[index])); } return 0; } // 0x4BE598 static int wmParseTerrainRndMaps(Config* config, Terrain* terrain) { char section[40]; snprintf(section, sizeof(section), "Random Maps: %s", terrain->lookupName); for (;;) { char key[40]; snprintf(key, sizeof(key), "map_%02d", terrain->mapsLength); char* string; if (!configGetString(config, section, key, &string)) { break; } if (strParseStrFromFunc(&string, &(terrain->maps[terrain->mapsLength]), wmParseFindMapIdxMatch) == -1) { return -1; } terrain->mapsLength++; if (terrain->mapsLength >= 20) { return -1; } } return 0; } // 0x4BE61C static int wmParseSubTileInfo(TileInfo* tile, int row, int column, char* string) { SubtileInfo* subtile = &(tile->subtiles[column][row]); subtile->state = SUBTILE_STATE_UNKNOWN; if (strParseStrFromFunc(&string, &(subtile->terrain), wmParseFindTerrainTypeMatch) == -1) { return -1; } if (strParseStrFromList(&string, &(subtile->fill), wmFillStrs, SUBTILE_FILL_COUNT) == -1) { return -1; } for (int index = 0; index < DAY_PART_COUNT; index++) { if (strParseStrFromList(&string, &(subtile->encounterChance[index]), wmFreqStrs, ENCOUNTER_FREQUENCY_TYPE_COUNT) == -1) { return -1; } } if (strParseStrFromFunc(&string, &(subtile->encounterType), wmParseFindEncounterTypeMatch) == -1) { return -1; } return 0; } // 0x4BE6D4 static int wmParseFindEncounterTypeMatch(char* string, int* valuePtr) { for (int index = 0; index < wmMaxEncounterInfoTables; index++) { if (compat_stricmp(string, wmEncounterTableList[index].lookupName) == 0) { *valuePtr = index; return 0; } } debugPrint("WorldMap Error: Couldn't find match for Encounter Type!"); *valuePtr = -1; return -1; } // 0x4BE73C static int wmParseFindTerrainTypeMatch(char* string, int* valuePtr) { for (int index = 0; index < wmMaxTerrainTypes; index++) { Terrain* terrain = &(wmTerrainTypeList[index]); if (compat_stricmp(string, terrain->lookupName) == 0) { *valuePtr = index; return 0; } } debugPrint("WorldMap Error: Couldn't find match for Terrain Type!"); *valuePtr = -1; return -1; } // 0x4BE7A4 static int wmParseEncounterItemType(char** stringPtr, EncounterItem* encounterItem, int* itemCountPtr, const char* delimeters) { char* string = *stringPtr; if (*string == '\0') { return -1; } compat_strlwr(string); if (*string == ',') { string++; *stringPtr += 1; } string += strspn(string, " "); size_t commaPos = strcspn(string, ","); char comma = string[commaPos]; string[commaPos] = '\0'; size_t delimPos = strcspn(string, delimeters); char delim = string[delimPos]; string[delimPos] = '\0'; bool found = false; if (strcmp(string, "item") == 0) { *stringPtr += commaPos + 1; found = true; wmParseItemType(string + delimPos + 1, encounterItem); *itemCountPtr += 1; } string[delimPos] = delim; string[commaPos] = comma; return found ? 0 : -1; } // 0x4BE888 static int wmParseItemType(char* string, EncounterItem* encounterItem) { while (*string == ' ') { string++; } encounterItem->minimumQuantity = 1; encounterItem->maximumQuantity = 1; encounterItem->isEquipped = false; if (*string == '(') { string++; encounterItem->minimumQuantity = atoi(string); while (isdigit(*string)) { string++; } if (*string == '-') { string++; encounterItem->maximumQuantity = atoi(string); while (isdigit(*string)) { string++; } } else { encounterItem->maximumQuantity = encounterItem->minimumQuantity; } if (*string == ')') { string++; } } while (*string == ' ') { string++; } encounterItem->pid = atoi(string); while (isdigit(*string)) { string++; } while (*string == ' ') { string++; } if (strstr(string, "{wielded}") != NULL || strstr(string, "(wielded)") != NULL || strstr(string, "{worn}") != NULL || strstr(string, "(worn)") != NULL) { encounterItem->isEquipped = true; } return 0; } // 0x4BE988 static int wmParseConditional(char** stringPtr, const char* a2, EncounterCondition* condition) { while (condition->entriesLength < 3) { EncounterConditionEntry* conditionEntry = &(condition->entries[condition->entriesLength]); if (wmParseSubConditional(stringPtr, a2, &(conditionEntry->type), &(conditionEntry->conditionalOperator), &(conditionEntry->param), &(conditionEntry->value)) == -1) { return -1; } condition->entriesLength++; char* andStatement = strstr(*stringPtr, "and"); if (andStatement != NULL) { *stringPtr = andStatement + 3; condition->logicalOperators[condition->entriesLength - 1] = ENCOUNTER_LOGICAL_OPERATOR_AND; continue; } char* orStatement = strstr(*stringPtr, "or"); if (orStatement != NULL) { *stringPtr = orStatement + 2; condition->logicalOperators[condition->entriesLength - 1] = ENCOUNTER_LOGICAL_OPERATOR_OR; continue; } break; } return 0; } // 0x4BEA24 static int wmParseSubConditional(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr) { char* string = *stringPtr; if (string == NULL) { return -1; } if (*string == '\0') { return -1; } compat_strlwr(string); if (*string == ',') { string++; *stringPtr = string; } string += strspn(string, " "); size_t commaPos = strcspn(string, ","); char comma = string[commaPos]; string[commaPos] = '\0'; size_t parenPos = strcspn(string, "("); char paren = string[parenPos]; string[parenPos] = '\0'; bool found = false; if (strstr(string, a2) == string) { found = true; } string[parenPos] = paren; string[commaPos] = comma; if (!found) { return -1; } string += parenPos + 1; char* pch; if (strstr(string, "rand(") == string) { string += 5; *typePtr = ENCOUNTER_CONDITION_TYPE_RANDOM; *operatorPtr = ENCOUNTER_CONDITIONAL_OPERATOR_NONE; *paramPtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } else if (strstr(string, "global(") == string) { string += 7; *typePtr = ENCOUNTER_CONDITION_TYPE_GLOBAL; *paramPtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "player(level)") == string) { string += 13; *typePtr = ENCOUNTER_CONDITION_TYPE_PLAYER; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "days_played") == string) { string += 11; *typePtr = ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "time_of_day") == string) { string += 11; *typePtr = ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "enctr(num_critters)") == string) { string += 19; *typePtr = ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else { *stringPtr = string; return 0; } return -1; } // 0x4BEEBC static int wmParseConditionalEval(char** stringPtr, int* conditionalOperatorPtr) { char* string = *stringPtr; *conditionalOperatorPtr = ENCOUNTER_CONDITIONAL_OPERATOR_NONE; int index; for (index = 0; index < ENCOUNTER_CONDITIONAL_OPERATOR_COUNT; index++) { if (strstr(string, wmConditionalOpStrs[index]) == string) { break; } } if (index == ENCOUNTER_CONDITIONAL_OPERATOR_COUNT) { return -1; } *conditionalOperatorPtr = index; string += strlen(wmConditionalOpStrs[index]); while (*string == ' ') { string++; } *stringPtr = string; return 0; } // NOTE: Inlined. // // 0x4BEF1C static int wmAreaSlotInit(CityInfo* area) { area->name[0] = '\0'; area->areaId = -1; area->x = 0; area->y = 0; area->size = CITY_SIZE_LARGE; area->state = CITY_STATE_UNKNOWN; area->lockState = LOCK_STATE_UNLOCKED; area->visitedState = 0; area->mapFid = -1; area->labelFid = -1; area->entrancesLength = 0; return 0; } // 0x4BEF68 static int wmAreaInit() { Config cfg; char section[40]; char key[40]; int area_idx; int num; char* str; CityInfo* cities; CityInfo* city; EntranceInfo* entrance; if (wmMapInit() == -1) { return -1; } if (!configInit(&cfg)) { return -1; } if (configRead(&cfg, "data\\city.txt", true)) { area_idx = 0; do { snprintf(section, sizeof(section), "Area %02d", area_idx); if (!configGetInt(&cfg, section, "townmap_art_idx", &num)) { break; } wmMaxAreaNum++; cities = (CityInfo*)internal_realloc(wmAreaInfoList, sizeof(CityInfo) * wmMaxAreaNum); if (cities == NULL) { showMesageBox("\nwmConfigInit::Error loading areas!"); exit(1); } wmAreaInfoList = cities; city = &(cities[wmMaxAreaNum - 1]); // NOTE: Uninline. wmAreaSlotInit(city); city->areaId = area_idx; if (num != -1) { num = buildFid(OBJ_TYPE_INTERFACE, num, 0, 0, 0); } city->mapFid = num; if (configGetInt(&cfg, section, "townmap_label_art_idx", &num)) { if (num != -1) { num = buildFid(OBJ_TYPE_INTERFACE, num, 0, 0, 0); } city->labelFid = num; } if (!configGetString(&cfg, section, "area_name", &str)) { showMesageBox("\nwmConfigInit::Error loading areas!"); exit(1); } strncpy(city->name, str, 40); if (!configGetString(&cfg, section, "world_pos", &str)) { showMesageBox("\nwmConfigInit::Error loading areas!"); exit(1); } if (strParseInt(&str, &(city->x)) == -1) { return -1; } if (strParseInt(&str, &(city->y)) == -1) { return -1; } if (!configGetString(&cfg, section, "start_state", &str)) { showMesageBox("\nwmConfigInit::Error loading areas!"); exit(1); } if (strParseStrFromList(&str, &(city->state), wmStateStrs, 2) == -1) { return -1; } if (configGetString(&cfg, section, "lock_state", &str)) { if (strParseStrFromList(&str, &(city->lockState), wmStateStrs, 2) == -1) { return -1; } } if (!configGetString(&cfg, section, "size", &str)) { showMesageBox("\nwmConfigInit::Error loading areas!"); exit(1); } if (strParseStrFromList(&str, &(city->size), wmAreaSizeStrs, 3) == -1) { return -1; } while (city->entrancesLength < ENTRANCE_LIST_CAPACITY) { snprintf(key, sizeof(key), "entrance_%d", city->entrancesLength); if (!configGetString(&cfg, section, key, &str)) { break; } entrance = &(city->entrances[city->entrancesLength]); // NOTE: Uninline. wmEntranceSlotInit(entrance); if (strParseStrFromList(&str, &(entrance->state), wmStateStrs, 2) == -1) { return -1; } if (strParseInt(&str, &(entrance->x)) == -1) { return -1; } if (strParseInt(&str, &(entrance->y)) == -1) { return -1; } if (strParseStrFromFunc(&str, &(entrance->map), &wmParseFindMapIdxMatch) == -1) { return -1; } if (strParseInt(&str, &(entrance->elevation)) == -1) { return -1; } if (strParseInt(&str, &(entrance->tile)) == -1) { return -1; } if (strParseInt(&str, &(entrance->rotation)) == -1) { return -1; } city->entrancesLength++; } area_idx++; } while (area_idx < 5000); } configFree(&cfg); if (wmMaxAreaNum != CITY_COUNT) { showMesageBox("\nwmAreaInit::Error loading Cities!"); exit(1); } return 0; } // 0x4BF3E0 static int wmParseFindMapIdxMatch(char* string, int* valuePtr) { for (int index = 0; index < wmMaxMapNum; index++) { MapInfo* map = &(wmMapInfoList[index]); if (compat_stricmp(string, map->lookupName) == 0) { *valuePtr = index; return 0; } } debugPrint("\nWorldMap Error: Couldn't find match for Map Index!"); *valuePtr = -1; return -1; } // NOTE: Inlined. // // 0x4BF448 static int wmEntranceSlotInit(EntranceInfo* entrance) { entrance->state = 0; entrance->x = 0; entrance->y = 0; entrance->map = -1; entrance->elevation = 0; entrance->tile = 0; entrance->rotation = 0; return 0; } // 0x4BF47C static int wmMapSlotInit(MapInfo* map) { map->lookupName[0] = '\0'; map->field_28 = -1; map->field_2C = -1; map->mapFileName[0] = '\0'; map->music[0] = '\0'; map->flags = 0x3F; map->ambientSoundEffectsLength = 0; map->startPointsLength = 0; return 0; } // 0x4BF4BC static int wmMapInit() { char* str; int num; MapInfo* maps; MapInfo* map; Config config; if (!configInit(&config)) { return -1; } if (configRead(&config, "data\\maps.txt", true)) { for (int mapIdx = 0;; mapIdx++) { char section[40]; snprintf(section, sizeof(section), "Map %03d", mapIdx); if (!configGetString(&config, section, "lookup_name", &str)) { break; } wmMaxMapNum++; maps = (MapInfo*)internal_realloc(wmMapInfoList, sizeof(*wmMapInfoList) * wmMaxMapNum); if (maps == NULL) { showMesageBox("\nwmConfigInit::Error loading maps!"); exit(1); } wmMapInfoList = maps; map = &(maps[wmMaxMapNum - 1]); wmMapSlotInit(map); strncpy(map->lookupName, str, 40); if (!configGetString(&config, section, "map_name", &str)) { showMesageBox("\nwmConfigInit::Error loading maps!"); exit(1); } compat_strlwr(str); strncpy(map->mapFileName, str, 40); if (configGetString(&config, section, "music", &str)) { strncpy(map->music, str, 40); } if (configGetString(&config, section, "ambient_sfx", &str)) { while (str) { MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[map->ambientSoundEffectsLength]); if (strParseKeyValue(&str, sfx->name, &(sfx->chance), ":") == -1) { return -1; } map->ambientSoundEffectsLength++; if (*str == '\0') { str = NULL; } if (map->ambientSoundEffectsLength >= MAP_AMBIENT_SOUND_EFFECTS_CAPACITY) { if (str != NULL) { debugPrint("\nwmMapInit::Error reading ambient sfx. Too many! Str: %s, MapIdx: %d", map->lookupName, mapIdx); str = NULL; } } } } if (configGetString(&config, section, "saved", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_SAVED, num); } if (configGetString(&config, section, "dead_bodies_age", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_DEAD_BODIES_AGE, num); } if (configGetString(&config, section, "can_rest_here", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_0, num); if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_1, num); if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_2, num); } if (configGetString(&config, section, "pipboy_active", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_PIPBOY_ACTIVE, num); } // SFALL: Pip-boy automaps patch. if (configGetString(&config, section, "automap", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } automapSetDisplayMap(mapIdx, num); } if (configGetString(&config, section, "random_start_point_0", &str)) { int rspIndex = 0; while (str != NULL) { while (*str != '\0') { if (map->startPointsLength >= MAP_STARTING_POINTS_CAPACITY) { break; } MapStartPointInfo* rsp = &(map->startPoints[map->startPointsLength]); // NOTE: Uninline. wmRStartSlotInit(rsp); strParseIntWithKey(&str, "elev", &(rsp->elevation), ":"); strParseIntWithKey(&str, "tile_num", &(rsp->tile), ":"); map->startPointsLength++; } char key[40]; snprintf(key, sizeof(key), "random_start_point_%1d", ++rspIndex); if (!configGetString(&config, section, key, &str)) { str = NULL; } } } } } configFree(&config); return 0; } // NOTE: Inlined. // // 0x4BF954 static int wmRStartSlotInit(MapStartPointInfo* rsp) { rsp->elevation = 0; rsp->tile = -1; rsp->rotation = -1; return 0; } // 0x4BF96C int wmMapMaxCount() { return wmMaxMapNum; } // 0x4BF974 int wmMapIdxToName(int mapIdx, char* dest, size_t size) { if (mapIdx == -1 || mapIdx > wmMaxMapNum) { dest[0] = '\0'; return -1; } snprintf(dest, size, "%s.MAP", wmMapInfoList[mapIdx].mapFileName); return 0; } // 0x4BF9BC int wmMapMatchNameToIdx(char* name) { compat_strlwr(name); char* pch = name; while (*pch != '\0' && *pch != '.') { pch++; } bool truncated = false; if (*pch != '\0') { *pch = '\0'; truncated = true; } int map = -1; for (int index = 0; index < wmMaxMapNum; index++) { if (strcmp(wmMapInfoList[index].mapFileName, name) == 0) { map = index; break; } } if (truncated) { *pch = '.'; } return map; } // 0x4BFA44 bool wmMapIdxIsSaveable(int mapIdx) { return (wmMapInfoList[mapIdx].flags & MAP_SAVED) != 0; } // 0x4BFA64 bool wmMapIsSaveable() { return (wmMapInfoList[gMapHeader.field_34].flags & MAP_SAVED) != 0; } // 0x4BFA90 bool wmMapDeadBodiesAge() { return (wmMapInfoList[gMapHeader.field_34].flags & MAP_DEAD_BODIES_AGE) != 0; } // 0x4BFABC bool wmMapCanRestHere(int elevation) { int flags[3]; // NOTE: I'm not sure why they're copied. memcpy(flags, _can_rest_here, sizeof(flags)); MapInfo* map = &(wmMapInfoList[gMapHeader.field_34]); return (map->flags & flags[elevation]) != 0; } // 0x4BFAFC bool wmMapPipboyActive() { return gameMovieIsSeen(MOVIE_VSUIT); } // 0x4BFB08 int wmMapMarkVisited(int mapIdx) { if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); if ((map->flags & MAP_SAVED) == 0) { return 0; } int areaIdx; if (wmMatchAreaContainingMapIdx(mapIdx, &areaIdx) == -1) { return -1; } // NOTE: Uninline. wmAreaMarkVisited(areaIdx); return 0; } // 0x4BFB64 static int wmMatchEntranceFromMap(int areaIdx, int mapIdx, int* entranceIdxPtr) { CityInfo* city = &(wmAreaInfoList[areaIdx]); for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (mapIdx == entrance->map) { *entranceIdxPtr = entranceIdx; return 0; } } *entranceIdxPtr = -1; return -1; } // 0x4BFBE8 static int wmMatchEntranceElevFromMap(int areaIdx, int mapIdx, int elevation, int* entranceIdxPtr) { CityInfo* city = &(wmAreaInfoList[areaIdx]); for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (entrance->map == mapIdx) { if (elevation == -1 || entrance->elevation == -1 || elevation == entrance->elevation) { *entranceIdxPtr = entranceIdx; return 0; } } } *entranceIdxPtr = -1; return -1; } // 0x4BFC7C static int wmMatchAreaFromMap(int mapIdx, int* areaIdxPtr) { for (int areaIdx = 0; areaIdx < wmMaxAreaNum; areaIdx++) { CityInfo* city = &(wmAreaInfoList[areaIdx]); for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (mapIdx == entrance->map) { *areaIdxPtr = areaIdx; return 0; } } } *areaIdxPtr = -1; return -1; } // Mark map entrance. // // 0x4BFD50 int wmMapMarkMapEntranceState(int mapIdx, int elevation, int state) { if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); if ((map->flags & MAP_SAVED) == 0) { return -1; } int areaIdx; if (wmMatchAreaContainingMapIdx(mapIdx, &areaIdx) == -1) { return -1; } int entranceIdx; if (wmMatchEntranceElevFromMap(areaIdx, mapIdx, elevation, &entranceIdx) == -1) { return -1; } CityInfo* city = &(wmAreaInfoList[areaIdx]); EntranceInfo* entrance = &(city->entrances[entranceIdx]); entrance->state = state; return 0; } // 0x4BFE0C void wmWorldMap() { wmWorldMapFunc(0); } // 0x4BFE10 static int wmWorldMapFunc(int a1) { ScopedGameMode gm(GameMode::kWorldmap); wmFadeOut(); if (wmInterfaceInit() == -1) { wmInterfaceExit(); wmFadeReset(); return -1; } wmFadeIn(); wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentAreaId)); unsigned int partyHealTime = 0; int map = -1; int rc = 0; while (true) { sharedFpsLimiter.mark(); int keyCode = inputGetInput(); // SFALL: WorldmapLoopHook. sfall_gl_scr_process_worldmap(); unsigned int now = getTicks(); int mouseX; int mouseY; mouseGetPositionInWindow(wmBkWin, &mouseX, &mouseY); int worldX = wmWorldOffsetX + mouseX - WM_VIEW_X; int worldY = wmWorldOffsetY + mouseY - WM_VIEW_Y; if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { showQuitConfirmationDialog(); } // NOTE: Uninline. wmCheckGameEvents(); if (_game_user_wants_to_quit != 0) { break; } int mouseEvent = mouseGetEvent(); if (wmGenData.isWalking) { wmPartyWalkingStep(); if (wmGenData.isInCar) { wmPartyWalkingStep(); wmPartyWalkingStep(); wmPartyWalkingStep(); if (gameGetGlobalVar(GVAR_CAR_BLOWER)) { wmPartyWalkingStep(); } if (gameGetGlobalVar(GVAR_NEW_RENO_CAR_UPGRADE)) { wmPartyWalkingStep(); } if (gameGetGlobalVar(GVAR_NEW_RENO_SUPER_CAR)) { wmPartyWalkingStep(); wmPartyWalkingStep(); wmPartyWalkingStep(); } wmGenData.carImageCurrentFrameIndex++; if (wmGenData.carImageCurrentFrameIndex >= artGetFrameCount(wmGenData.carImageFrm)) { wmGenData.carImageCurrentFrameIndex = 0; } wmCarUseGas(100); if (wmGenData.carFuel <= 0) { wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmMatchWorldPosToArea(worldX, worldY, &(wmGenData.currentAreaId)); wmGenData.isInCar = false; if (wmGenData.currentAreaId == -1) { wmGenData.currentCarAreaId = CITY_CAR_OUT_OF_GAS; CityInfo* city = &(wmAreaInfoList[CITY_CAR_OUT_OF_GAS]); CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); int worldmapX = wmGenData.worldPosX + wmGenData.hotspotNormalFrmImage.getWidth() / 2 + citySizeDescription->frmImage.getWidth() / 2; int worldmapY = wmGenData.worldPosY + wmGenData.hotspotNormalFrmImage.getHeight() / 2 + citySizeDescription->frmImage.getHeight() / 2; wmAreaSetWorldPos(CITY_CAR_OUT_OF_GAS, worldmapX, worldmapY); city->state = CITY_STATE_KNOWN; city->visitedState = 1; wmGenData.currentAreaId = CITY_CAR_OUT_OF_GAS; } else { wmGenData.currentCarAreaId = wmGenData.currentAreaId; } debugPrint("\nRan outta gas!"); } } wmInterfaceRefresh(); if (getTicksBetween(now, partyHealTime) > 1000) { if (_partyMemberRestingHeal(3)) { interfaceRenderHitPoints(false); partyHealTime = now; } } wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); if (wmGenData.walkDistance <= 0) { wmGenData.isWalking = false; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentAreaId)); } wmInterfaceRefresh(); if (wmGameTimeIncrement(18000)) { if (_game_user_wants_to_quit != 0) { break; } } if (wmGenData.isWalking) { if (wmRndEncounterOccurred()) { if (wmGenData.encounterMapId != -1) { if (wmGenData.isInCar) { wmMatchAreaContainingMapIdx(wmGenData.encounterMapId, &(wmGenData.currentCarAreaId)); } wmFadeOut(); mapLoadById(wmGenData.encounterMapId); } break; } } } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) { if (mouseHitTestInWindow(wmBkWin, WM_VIEW_X, WM_VIEW_Y, 472, 465)) { if (!wmGenData.isWalking && !wmGenData.mousePressed && abs(wmGenData.worldPosX - worldX) < 5 && abs(wmGenData.worldPosY - worldY) < 5) { wmGenData.mousePressed = true; wmInterfaceRefresh(); renderPresent(); } } else { continue; } } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { if (wmGenData.mousePressed) { wmGenData.mousePressed = false; wmInterfaceRefresh(); if (abs(wmGenData.worldPosX - worldX) < 5 && abs(wmGenData.worldPosY - worldY) < 5) { if (wmGenData.currentAreaId != -1) { CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (city->visitedState == 2 && city->mapFid != -1) { if (wmTownMapFunc(&map) == -1) { rc = -1; break; } } else { if (wmAreaFindFirstValidMap(&map) == -1) { rc = -1; break; } city->visitedState = 2; } } else { map = 0; } if (map != -1) { if (wmGenData.isInCar) { wmGenData.isInCar = false; if (wmGenData.currentAreaId == -1) { wmMatchAreaContainingMapIdx(map, &(wmGenData.currentCarAreaId)); } else { wmGenData.currentCarAreaId = wmGenData.currentAreaId; } } wmFadeOut(); mapLoadById(map); break; } } } else { if (mouseHitTestInWindow(wmBkWin, WM_VIEW_X, WM_VIEW_Y, 472, 465)) { wmPartyInitWalking(worldX, worldY); } wmGenData.mousePressed = false; } } // NOTE: Uninline. wmInterfaceScrollTabsUpdate(); if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T) { if (!wmGenData.isWalking && wmGenData.currentAreaId != -1) { CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (city->visitedState == 2 && city->mapFid != -1) { if (wmTownMapFunc(&map) == -1) { rc = -1; } if (map != -1) { if (wmGenData.isInCar) { // SFALL: Fix for the car being lost when entering a // location via the Town/World button and then // leaving on foot. // // CE: Fix is very different, but looks right - // matches the code above (processing mouse events). wmGenData.isInCar = false; wmMatchAreaContainingMapIdx(map, &(wmGenData.currentCarAreaId)); } wmFadeOut(); mapLoadById(map); } } } } else if (keyCode == KEY_HOME) { wmInterfaceCenterOnParty(); } else if (keyCode == KEY_ARROW_UP) { // NOTE: Uninline. wmInterfaceScroll(0, -1, NULL); } else if (keyCode == KEY_ARROW_LEFT) { // NOTE: Uninline. wmInterfaceScroll(-1, 0, NULL); } else if (keyCode == KEY_ARROW_DOWN) { // NOTE: Uninline. wmInterfaceScroll(0, 1, NULL); } else if (keyCode == KEY_ARROW_RIGHT) { // NOTE: Uninline. wmInterfaceScroll(1, 0, NULL); } else if (keyCode == KEY_CTRL_ARROW_UP) { wmInterfaceScrollTabsStart(-27); } else if (keyCode == KEY_CTRL_ARROW_DOWN) { wmInterfaceScrollTabsStart(27); } else if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) { int quickDestinationIndex = wmGenData.tabsOffsetY / 27 + (keyCode - KEY_CTRL_F1); if (quickDestinationIndex < wmLabelCount) { int areaIdx = wmLabelList[quickDestinationIndex]; CityInfo* city = &(wmAreaInfoList[areaIdx]); if (wmAreaIsKnown(city->areaId)) { if (wmGenData.currentAreaId != areaIdx) { // SFALL: Fix the position of the destination marker for // small/medium location circles. // CE: Fix is slightly different. `wmPartyInitWalking` // assumes x/y are compensated for worldmap viewport // offset (as can be seen earlier in this function). CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); int destX = city->x + citySizeDescription->frmImage.getWidth() / 2 - WM_VIEW_X; int destY = city->y + citySizeDescription->frmImage.getHeight() / 2 - WM_VIEW_Y; wmPartyInitWalking(destX, destY); wmGenData.mousePressed = 0; } } } } if ((mouseEvent & MOUSE_EVENT_WHEEL) != 0) { int wheelX; int wheelY; mouseGetWheel(&wheelX, &wheelY); if (mouseHitTestInWindow(wmBkWin, WM_VIEW_X, WM_VIEW_Y, 472, 465)) { wmInterfaceScrollPixel(20, 20, wheelX, -wheelY, NULL, true); } else if (mouseHitTestInWindow(wmBkWin, 501, 135, 501 + 119, 135 + 178)) { if (wheelY != 0) { wmInterfaceScrollTabsStart(wheelY > 0 ? 27 : -27); } } } if (map != -1 || rc == -1) { break; } renderPresent(); sharedFpsLimiter.throttle(); } if (wmInterfaceExit() == -1) { wmFadeReset(); return -1; } wmFadeIn(); return rc; } // 0x4C056C int wmCheckGameAreaEvents() { if (wmGenData.currentAreaId == CITY_FAKE_VAULT_13_A) { // NOTE: Uninline. wmAreaSetVisibleState(CITY_FAKE_VAULT_13_A, CITY_STATE_UNKNOWN, true); // NOTE: Uninline. wmAreaSetVisibleState(CITY_FAKE_VAULT_13_B, CITY_STATE_KNOWN, true); wmAreaMarkVisitedState(CITY_FAKE_VAULT_13_B, 2); } return 0; } // 0x4C05C4 static int wmInterfaceCenterOnParty() { wmWorldOffsetX = std::clamp(wmGenData.worldPosX - 203, 0, wmGenData.viewportMaxX); wmWorldOffsetY = std::clamp(wmGenData.worldPosY - 200, 0, wmGenData.viewportMaxY); wmInterfaceRefresh(); return 0; } // NOTE: Inlined. // // 0x4C0624 static void wmCheckGameEvents() { _scriptsCheckGameEvents(NULL, wmBkWin); } // 0x4C0634 static int wmRndEncounterOccurred() { unsigned int now = getTicks(); if (getTicksBetween(now, wmLastRndTime) < 1500) { return 0; } wmLastRndTime = now; if (abs(wmGenData.oldWorldPosX - wmGenData.worldPosX) < 3) { return 0; } if (abs(wmGenData.oldWorldPosY - wmGenData.worldPosY) < 3) { return 0; } int areaIdx; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &areaIdx); if (areaIdx != -1) { return 0; } if (!wmGenData.didMeetFrankHorrigan) { unsigned int gameTime = gameTimeGetTime(); if (gameTime / GAME_TIME_TICKS_PER_DAY > 35) { // SFALL: Add a flashing icon to the Horrigan encounter. wmBlinkRndEncounterIcon(true); wmGenData.encounterMapId = -1; wmGenData.didMeetFrankHorrigan = true; if (wmGenData.isInCar) { wmMatchAreaContainingMapIdx(MAP_IN_GAME_MOVIE1, &(wmGenData.currentCarAreaId)); } wmFadeOut(); mapLoadById(MAP_IN_GAME_MOVIE1); return 1; } } // NOTE: Uninline. wmPartyFindCurSubTile(); int dayPart; int gameTimeHour = gameTimeGetHour(); if (gameTimeHour >= 1800 || gameTimeHour < 600) { dayPart = DAY_PART_NIGHT; } else if (gameTimeHour >= 1200) { dayPart = DAY_PART_AFTERNOON; } else { dayPart = DAY_PART_MORNING; } int frequency = wmFreqValues[wmGenData.currentSubtile->encounterChance[dayPart]]; if (frequency > 0 && frequency < 100) { int modifier = frequency / 15; switch (settings.preferences.game_difficulty) { case GAME_DIFFICULTY_EASY: frequency -= modifier; break; case GAME_DIFFICULTY_HARD: frequency += modifier; break; } } int chance = randomBetween(0, 100); if (chance >= frequency) { return 0; } wmRndEncounterPick(); EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]); EncounterTableEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]); if ((encounterTableEntry->flags & ENCOUNTER_ENTRY_SPECIAL) != 0) { wmMatchAreaContainingMapIdx(wmGenData.encounterMapId, &areaIdx); CityInfo* city = &(wmAreaInfoList[areaIdx]); CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); int worldmapX = wmGenData.worldPosX + wmGenData.hotspotNormalFrmImage.getWidth() / 2 + citySizeDescription->frmImage.getWidth() / 2; int worldmapY = wmGenData.worldPosY + wmGenData.hotspotNormalFrmImage.getHeight() / 2 + citySizeDescription->frmImage.getHeight() / 2; wmAreaSetWorldPos(areaIdx, worldmapX, worldmapY); if (areaIdx >= 0 && areaIdx < wmMaxAreaNum) { CityInfo* city = &(wmAreaInfoList[areaIdx]); if (city->lockState != LOCK_STATE_LOCKED) { city->state = CITY_STATE_KNOWN; } } } // Blinking. wmBlinkRndEncounterIcon((encounterTableEntry->flags & ENCOUNTER_ENTRY_SPECIAL) != 0); if (wmGenData.isInCar) { int modifiers[DAY_PART_COUNT]; // NOTE: I'm not sure why they're copied. memcpy(modifiers, gDayPartEncounterFrequencyModifiers, sizeof(gDayPartEncounterFrequencyModifiers)); frequency -= modifiers[dayPart]; } bool randomEncounterIsDetected = false; if (frequency > chance) { int outdoorsman = partyGetBestSkillValue(SKILL_OUTDOORSMAN); Object* scanner = objectGetCarriedObjectByPid(gDude, PROTO_ID_MOTION_SENSOR); if (scanner != NULL) { if (gDude == scanner->owner) { outdoorsman += 20; } } if (outdoorsman > 95) { outdoorsman = 95; } TileInfo* tile; // NOTE: Uninline. wmFindCurTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, &tile); debugPrint("\nEncounter Difficulty Mod: %d", tile->encounterDifficultyModifier); outdoorsman += tile->encounterDifficultyModifier; if (randomBetween(1, 100) < outdoorsman) { randomEncounterIsDetected = true; int xp = 100 - outdoorsman; if (xp > 0) { // SFALL: Display actual xp received. debugPrint("WorldMap: Giving Player [%d] Experience For Catching Rnd Encounter!", xp); int xpGained; pcAddExperience(xp, &xpGained); MessageListItem messageListItem; char* text = getmsg(&gMiscMessageList, &messageListItem, 8500); if (strlen(text) < 110) { char formattedText[120]; snprintf(formattedText, sizeof(formattedText), text, xpGained); displayMonitorAddMessage(formattedText); } else { debugPrint("WorldMap: Error: Rnd Encounter string too long!"); } } } } else { randomEncounterIsDetected = true; } wmGenData.oldWorldPosX = wmGenData.worldPosX; wmGenData.oldWorldPosY = wmGenData.worldPosY; if (randomEncounterIsDetected) { MessageListItem messageListItem; const char* title = gWorldmapEncDefaultMsg[0]; const char* body = gWorldmapEncDefaultMsg[1]; title = getmsg(&wmMsgFile, &messageListItem, 2999); body = getmsg(&wmMsgFile, &messageListItem, 3000 + 50 * wmGenData.encounterTableId + wmGenData.encounterEntryId); if (showDialogBox(title, &body, 1, 169, 116, _colorTable[32328], NULL, _colorTable[32328], DIALOG_BOX_LARGE | DIALOG_BOX_YES_NO) == 0) { wmGenData.encounterIconIsVisible = false; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; return 0; } } return 1; } // NOTE: Inlined. // // 0x4C0BE4 static int wmPartyFindCurSubTile() { return wmFindCurSubTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentSubtile)); } // 0x4C0C00 static int wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtilePtr) { int tileIndex = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles; TileInfo* tile = &(wmTileInfoList[tileIndex]); int column = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE; int row = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE; *subtilePtr = &(tile->subtiles[column][row]); return 0; } // NOTE: Inlined. // // 0x4C0CA8 static int wmFindCurTileFromPos(int x, int y, TileInfo** tilePtr) { int tileIndex = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles; *tilePtr = &(wmTileInfoList[tileIndex]); return 0; } // 0x4C0CF4 static int wmRndEncounterPick() { if (wmGenData.currentSubtile == NULL) { // NOTE: Uninline. wmPartyFindCurSubTile(); } wmGenData.encounterTableId = wmGenData.currentSubtile->encounterType; EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]); int candidates[41]; int candidatesLength = 0; int totalChance = 0; for (int index = 0; index < encounterTable->entriesLength; index++) { EncounterTableEntry* encounterTableEntry = &(encounterTable->entries[index]); bool selected = true; if (wmEvalConditional(&(encounterTableEntry->condition), NULL) == 0) { selected = false; } if (encounterTableEntry->counter == 0) { selected = false; } if (selected) { candidates[candidatesLength++] = index; totalChance += encounterTableEntry->chance; } } int effectiveLuck = critterGetStat(gDude, STAT_LUCK) - 5; int chance = randomBetween(0, totalChance) + effectiveLuck; if (perkHasRank(gDude, PERK_EXPLORER)) { chance += 2; } if (perkHasRank(gDude, PERK_RANGER)) { chance += 1; } if (perkHasRank(gDude, PERK_SCOUT)) { chance += 1; } switch (settings.preferences.game_difficulty) { case GAME_DIFFICULTY_EASY: chance += 5; if (chance > totalChance) { chance = totalChance; } break; case GAME_DIFFICULTY_HARD: chance -= 5; if (chance < 0) { chance = 0; } break; } int index; for (index = 0; index < candidatesLength; index++) { EncounterTableEntry* encounterTableEntry = &(encounterTable->entries[candidates[index]]); if (chance < encounterTableEntry->chance) { break; } chance -= encounterTableEntry->chance; } if (index == candidatesLength) { index = candidatesLength - 1; } wmGenData.encounterEntryId = candidates[index]; EncounterTableEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]); if (encounterTableEntry->counter > 0) { encounterTableEntry->counter--; } if (encounterTableEntry->map == -1) { if (encounterTable->mapsLength <= 0) { Terrain* terrain = &(wmTerrainTypeList[wmGenData.currentSubtile->terrain]); int randommapIdx = randomBetween(0, terrain->mapsLength - 1); wmGenData.encounterMapId = terrain->maps[randommapIdx]; } else { int randommapIdx = randomBetween(0, encounterTable->mapsLength - 1); wmGenData.encounterMapId = encounterTable->maps[randommapIdx]; } } else { wmGenData.encounterMapId = encounterTableEntry->map; } return 0; } // 0x4C0FA4 int wmSetupRandomEncounter() { MessageListItem messageListItem; if (wmGenData.encounterMapId == -1) { return 0; } EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]); EncounterTableEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]); // SFALL: Display encounter description in one line. char formattedText[512]; snprintf(formattedText, sizeof(formattedText), "%s %s", getmsg(&wmMsgFile, &messageListItem, 2998), getmsg(&wmMsgFile, &messageListItem, 3000 + 50 * wmGenData.encounterTableId + wmGenData.encounterEntryId)); displayMonitorAddMessage(formattedText); int gameDifficulty = settings.preferences.game_difficulty; switch (encounterTableEntry->scenery) { case ENCOUNTER_SCENERY_TYPE_NONE: case ENCOUNTER_SCENERY_TYPE_LIGHT: case ENCOUNTER_SCENERY_TYPE_NORMAL: case ENCOUNTER_SCENERY_TYPE_HEAVY: debugPrint("\nwmSetupRandomEncounter: Scenery Type: %s", wmSceneryStrs[encounterTableEntry->scenery]); break; default: debugPrint("\nERROR: wmSetupRandomEncounter: invalid Scenery Type!"); return -1; } Object* prevCritter = NULL; for (int index = 0; index < encounterTableEntry->subEntiesLength; index++) { EncounterTableSubEntry* encounterTableSubEntry = &(encounterTableEntry->subEntries[index]); int critterCount = randomBetween(encounterTableSubEntry->minimumCount, encounterTableSubEntry->maximumCount); switch (gameDifficulty) { case GAME_DIFFICULTY_EASY: critterCount -= 2; if (critterCount < encounterTableSubEntry->minimumCount) { critterCount = encounterTableSubEntry->minimumCount; } break; case GAME_DIFFICULTY_HARD: critterCount += 2; break; } int partyMemberCount = _getPartyMemberCount(); if (partyMemberCount > 2) { critterCount += 2; } if (critterCount != 0) { Object* critter; if (wmSetupCritterObjs(encounterTableSubEntry->encounterIndex, &critter, critterCount) == -1) { scriptsRequestWorldMap(); return -1; } if (index > 0) { if (prevCritter != NULL) { if (prevCritter != critter) { if (encounterTableEntry->subEntiesLength != 1) { if (encounterTableEntry->subEntiesLength == 2 && !isInCombat()) { prevCritter->data.critter.combat.whoHitMe = critter; critter->data.critter.combat.whoHitMe = prevCritter; STRUCT_664980 combat; combat.attacker = prevCritter; combat.defender = critter; combat.actionPointsBonus = 0; combat.accuracyBonus = 0; combat.damageBonus = 0; combat.minDamage = 0; combat.maxDamage = 500; combat.field_1C = 0; _caiSetupTeamCombat(critter, prevCritter); _scripts_request_combat_locked(&combat); } } else { if (!isInCombat()) { prevCritter->data.critter.combat.whoHitMe = gDude; STRUCT_664980 combat; combat.attacker = prevCritter; combat.defender = gDude; combat.actionPointsBonus = 0; combat.accuracyBonus = 0; combat.damageBonus = 0; combat.minDamage = 0; combat.maxDamage = 500; combat.field_1C = 0; _caiSetupTeamCombat(gDude, prevCritter); _scripts_request_combat_locked(&combat); } } } } } prevCritter = critter; } } return 0; } // wmSetupCritterObjs // 0x4C11FC static int wmSetupCritterObjs(int encounterIndex, Object** critterPtr, int critterCount) { if (encounterIndex == -1) { return 0; } *critterPtr = NULL; Encounter* encounter = &(wmEncBaseTypeList[encounterIndex]); debugPrint("\nwmSetupCritterObjs: typeIdx: %d, Formation: %s", encounterIndex, wmFormationStrs[encounter->position]); if (wmSetupRndNextTileNumInit(encounter) == -1) { return -1; } for (int index = 0; index < encounter->entriesLength; index++) { EncounterEntry* encounterEntry = &(encounter->entries[index]); if (encounterEntry->pid == -1) { continue; } if (!wmEvalConditional(&(encounterEntry->condition), &critterCount)) { continue; } int encounterEntryCritterCount; switch (encounterEntry->field_2C) { case 0: encounterEntryCritterCount = encounterEntry->ratio * critterCount / 100; break; case 1: encounterEntryCritterCount = 1; break; default: assert(false && "Should be unreachable"); } if (encounterEntryCritterCount < 1) { encounterEntryCritterCount = 1; } for (int critterIndex = 0; critterIndex < encounterEntryCritterCount; critterIndex++) { int tile; if (wmSetupRndNextTileNum(encounter, encounterEntry, &tile) == -1) { debugPrint("\nERROR: wmSetupCritterObjs: wmSetupRndNextTileNum:"); continue; } if (encounterEntry->pid == -1) { continue; } Object* object; if (objectCreateWithPid(&object, encounterEntry->pid) == -1) { return -1; } if (*critterPtr == NULL) { if (PID_TYPE(encounterEntry->pid) == OBJ_TYPE_CRITTER) { *critterPtr = object; } } if (encounterEntry->team != -1) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { object->data.critter.combat.team = encounterEntry->team; } } if (encounterEntry->scriptIdx != -1) { if (object->sid != -1) { scriptRemove(object->sid); object->sid = -1; } _obj_new_sid_inst(object, SCRIPT_TYPE_CRITTER, encounterEntry->scriptIdx - 1); } if (encounter->position != ENCOUNTER_FORMATION_TYPE_SURROUNDING) { objectSetLocation(object, tile, gElevation, NULL); } else { _obj_attempt_placement(object, tile, 0, 0); } int direction = tileGetRotationTo(tile, gDude->tile); objectSetRotation(object, direction, NULL); for (int itemIndex = 0; itemIndex < encounterEntry->itemsLength; itemIndex++) { EncounterItem* encounterItem = &(encounterEntry->items[itemIndex]); int quantity; if (encounterItem->maximumQuantity == encounterItem->minimumQuantity) { quantity = encounterItem->maximumQuantity; } else { quantity = randomBetween(encounterItem->minimumQuantity, encounterItem->maximumQuantity); } if (quantity == 0) { continue; } Object* item; if (objectCreateWithPid(&item, encounterItem->pid) == -1) { return -1; } if (encounterItem->pid == PROTO_ID_MONEY) { if (perkHasRank(gDude, PERK_FORTUNE_FINDER)) { quantity *= 2; } } if (itemAdd(object, item, quantity) == -1) { return -1; } _obj_disconnect(item, NULL); if (encounterItem->isEquipped) { if (_inven_wield(object, item, 1) == -1) { debugPrint("\nERROR: wmSetupCritterObjs: Inven Wield Failed: %d on %s: Critter Fid: %d", item->pid, critterGetName(object), object->fid); } } } } } return 0; } // 0x4C155C static int wmSetupRndNextTileNumInit(Encounter* encounter) { for (int index = 0; index < 2; index++) { wmRndCenterRotations[index] = 0; wmRndTileDirs[index] = 0; wmRndCenterTiles[index] = -1; if (index & 1) { wmRndRotOffsets[index] = 5; } else { wmRndRotOffsets[index] = 1; } } wmRndCallCount = 0; switch (encounter->position) { case ENCOUNTER_FORMATION_TYPE_SURROUNDING: wmRndCenterTiles[0] = gDude->tile; wmRndTileDirs[0] = randomBetween(0, ROTATION_COUNT - 1); wmRndOriginalCenterTile = wmRndCenterTiles[0]; return 0; case ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE: case ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE: case ENCOUNTER_FORMATION_TYPE_WEDGE: case ENCOUNTER_FORMATION_TYPE_CONE: case ENCOUNTER_FORMATION_TYPE_HUDDLE: if (1) { MapInfo* map = &(wmMapInfoList[gMapHeader.field_34]); if (map->startPointsLength != 0) { int rspIndex = randomBetween(0, map->startPointsLength - 1); MapStartPointInfo* rsp = &(map->startPoints[rspIndex]); wmRndCenterTiles[0] = rsp->tile; wmRndCenterTiles[1] = wmRndCenterTiles[0]; wmRndCenterRotations[0] = rsp->rotation; wmRndCenterRotations[1] = wmRndCenterRotations[0]; } else { wmRndCenterRotations[0] = 0; wmRndCenterRotations[1] = 0; wmRndCenterTiles[0] = gDude->tile; wmRndCenterTiles[1] = gDude->tile; } wmRndTileDirs[0] = tileGetRotationTo(wmRndCenterTiles[0], gDude->tile); wmRndTileDirs[1] = tileGetRotationTo(wmRndCenterTiles[1], gDude->tile); wmRndOriginalCenterTile = wmRndCenterTiles[0]; return 0; } default: debugPrint("\nERROR: wmSetupCritterObjs: invalid Formation Type!"); return -1; } } // wmSetupRndNextTileNum // 0x4C16F0 static int wmSetupRndNextTileNum(Encounter* encounter, EncounterEntry* encounterEntry, int* tilePtr) { int tile; int attempt = 0; while (1) { switch (encounter->position) { case ENCOUNTER_FORMATION_TYPE_SURROUNDING: if (1) { int distance; if (encounterEntry->distance != 0) { distance = encounterEntry->distance; } else { distance = randomBetween(-2, 2); distance += critterGetStat(gDude, STAT_PERCEPTION); if (perkHasRank(gDude, PERK_CAUTIOUS_NATURE)) { distance += 3; } } if (distance < 0) { distance = 0; } int origin = encounterEntry->tile; if (origin == -1) { origin = tileGetTileInDirection(gDude->tile, wmRndTileDirs[0], distance); } if (++wmRndTileDirs[0] >= ROTATION_COUNT) { wmRndTileDirs[0] = 0; } int randomizedDistance = randomBetween(0, distance / 2); int randomizedRotation = randomBetween(0, ROTATION_COUNT - 1); tile = tileGetTileInDirection(origin, (randomizedRotation + wmRndTileDirs[0]) % ROTATION_COUNT, randomizedDistance); } break; case ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE: tile = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { int rotation = (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT; int origin = tileGetTileInDirection(wmRndCenterTiles[wmRndIndex], rotation, encounter->spacing); tile = tileGetTileInDirection(origin, (rotation + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, encounter->spacing); wmRndCenterTiles[wmRndIndex] = tile; wmRndIndex = 1 - wmRndIndex; } break; case ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE: tile = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { int rotation = (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT; int origin = tileGetTileInDirection(wmRndCenterTiles[wmRndIndex], rotation, encounter->spacing); tile = tileGetTileInDirection(origin, (rotation + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, encounter->spacing); wmRndCenterTiles[wmRndIndex] = tile; wmRndIndex = 1 - wmRndIndex; } break; case ENCOUNTER_FORMATION_TYPE_WEDGE: tile = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { tile = tileGetTileInDirection(wmRndCenterTiles[wmRndIndex], (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT, encounter->spacing); wmRndCenterTiles[wmRndIndex] = tile; wmRndIndex = 1 - wmRndIndex; } break; case ENCOUNTER_FORMATION_TYPE_CONE: tile = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { tile = tileGetTileInDirection(wmRndCenterTiles[wmRndIndex], (wmRndTileDirs[wmRndIndex] + 3 + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, encounter->spacing); wmRndCenterTiles[wmRndIndex] = tile; wmRndIndex = 1 - wmRndIndex; } break; case ENCOUNTER_FORMATION_TYPE_HUDDLE: tile = wmRndCenterTiles[0]; if (wmRndCallCount != 0) { wmRndTileDirs[0] = (wmRndTileDirs[0] + 1) % ROTATION_COUNT; tile = tileGetTileInDirection(wmRndCenterTiles[0], wmRndTileDirs[0], encounter->spacing); wmRndCenterTiles[0] = tile; } break; default: assert(false && "Should be unreachable"); } ++attempt; ++wmRndCallCount; if (wmEvalTileNumForPlacement(tile)) { break; } debugPrint("\nWARNING: EVAL-TILE-NUM FAILED!"); if (tileDistanceBetween(wmRndOriginalCenterTile, wmRndCenterTiles[wmRndIndex]) > 25) { return -1; } if (attempt > 25) { return -1; } } debugPrint("\nwmSetupRndNextTileNum:TileNum: %d", tile); *tilePtr = tile; return 0; } // 0x4C1A64 bool wmEvalTileNumForPlacement(int tile) { if (_obj_blocking_at(gDude, tile, gElevation) != NULL) { return false; } if (pathfinderFindPath(gDude, gDude->tile, tile, NULL, 0, _obj_shoot_blocking_at) == 0) { return false; } return true; } // 0x4C1AC8 static bool wmEvalConditional(EncounterCondition* condition, int* critterCountPtr) { int value; bool matches = true; for (int index = 0; index < condition->entriesLength; index++) { EncounterConditionEntry* conditionEntry = &(condition->entries[index]); matches = true; switch (conditionEntry->type) { case ENCOUNTER_CONDITION_TYPE_GLOBAL: value = gameGetGlobalVar(conditionEntry->param); if (!wmEvalSubConditional(value, conditionEntry->conditionalOperator, conditionEntry->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS: if (!wmEvalSubConditional(*critterCountPtr, conditionEntry->conditionalOperator, conditionEntry->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_RANDOM: value = randomBetween(0, 100); if (value > conditionEntry->param) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_PLAYER: value = pcGetStat(PC_STAT_LEVEL); if (!wmEvalSubConditional(value, conditionEntry->conditionalOperator, conditionEntry->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED: value = gameTimeGetTime(); if (!wmEvalSubConditional(value / GAME_TIME_TICKS_PER_DAY, conditionEntry->conditionalOperator, conditionEntry->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY: value = gameTimeGetHour(); if (!wmEvalSubConditional(value / 100, conditionEntry->conditionalOperator, conditionEntry->value)) { matches = false; } break; } if (!matches) { // FIXME: Can overflow with all 3 conditions specified. if (condition->logicalOperators[index] == ENCOUNTER_LOGICAL_OPERATOR_AND) { break; } } } return matches; } // 0x4C1C0C static bool wmEvalSubConditional(int operand1, int condionalOperator, int operand2) { switch (condionalOperator) { case ENCOUNTER_CONDITIONAL_OPERATOR_EQUAL: return operand1 == operand2; case ENCOUNTER_CONDITIONAL_OPERATOR_NOT_EQUAL: return operand1 != operand2; case ENCOUNTER_CONDITIONAL_OPERATOR_LESS_THAN: return operand1 < operand2; case ENCOUNTER_CONDITIONAL_OPERATOR_GREATER_THAN: return operand1 > operand2; } return false; } // 0x4C1C50 static bool wmGameTimeIncrement(int ticksToAdd) { if (ticksToAdd == 0) { return false; } // SFALL: Fix Pathfinder perk. int pathfinderRank = perkGetRank(gDude, PERK_PATHFINDER); double bonus = static_cast(ticksToAdd) * static_cast(pathfinderRank) * 0.25 + gGameTimeIncRemainder; gGameTimeIncRemainder = modf(bonus, &bonus); ticksToAdd -= static_cast(bonus); while (ticksToAdd != 0) { unsigned int gameTime = gameTimeGetTime(); unsigned int nextEventTime = queueGetNextEventTime(); int ticksToNextEvent = nextEventTime >= gameTime ? ticksToAdd : nextEventTime - gameTime; ticksToAdd -= ticksToNextEvent; gameTimeAddTicks(ticksToNextEvent); // NOTE: Uninline. wmInterfaceDialSyncTime(true); wmInterfaceRefreshDate(true); if (queueProcessEvents()) { break; } } return true; } // Reads .msk file if needed. // // 0x4C1CE8 static int wmGrabTileWalkMask(int tileIdx) { TileInfo* tileInfo = &(wmTileInfoList[tileIdx]); if (tileInfo->walkMaskData != NULL) { return 0; } if (*tileInfo->walkMaskName == '\0') { return 0; } tileInfo->walkMaskData = (unsigned char*)internal_malloc(13200); if (tileInfo->walkMaskData == NULL) { return -1; } char path[COMPAT_MAX_PATH]; snprintf(path, sizeof(path), "data\\%s.msk", tileInfo->walkMaskName); File* stream = fileOpen(path, "rb"); if (stream == NULL) { return -1; } int rc = 0; if (fileReadUInt8List(stream, tileInfo->walkMaskData, 13200) == -1) { rc = -1; } fileClose(stream); return rc; } // 0x4C1D9C static bool wmWorldPosInvalid(int x, int y) { int tileIdx = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles; if (wmGrabTileWalkMask(tileIdx) == -1) { return false; } TileInfo* tileDescription = &(wmTileInfoList[tileIdx]); unsigned char* mask = tileDescription->walkMaskData; if (mask == NULL) { return false; } // Mask length is 13200, which is 300 * 44 // 44 * 8 is 352, which is probably left 2 bytes intact // TODO: Check math. int pos = (y % WM_TILE_HEIGHT) * 44 + (x % WM_TILE_WIDTH) / 8; int bit = 1 << (((x % WM_TILE_WIDTH) / 8) & 3); return (mask[pos] & bit) != 0; } // 0x4C1E54 static void wmPartyInitWalking(int x, int y) { wmGenData.walkDestinationX = x; wmGenData.walkDestinationY = y; wmGenData.currentAreaId = -1; wmGenData.isWalking = true; int dx = abs(x - wmGenData.worldPosX); int dy = abs(y - wmGenData.worldPosY); if (dx < dy) { wmGenData.walkDistance = dy; wmGenData.walkLineDeltaMainAxisStep = 2 * dx; wmGenData.walkWorldPosMainAxisStepX = 0; wmGenData.walkLineDelta = 2 * dx - dy; wmGenData.walkLineDeltaCrossAxisStep = 2 * (dx - dy); wmGenData.walkWorldPosCrossAxisStepX = 1; wmGenData.walkWorldPosMainAxisStepY = 1; wmGenData.walkWorldPosCrossAxisStepY = 1; } else { wmGenData.walkDistance = dx; wmGenData.walkLineDeltaMainAxisStep = 2 * dy; wmGenData.walkWorldPosMainAxisStepY = 0; wmGenData.walkLineDelta = 2 * dy - dx; wmGenData.walkLineDeltaCrossAxisStep = 2 * (dy - dx); wmGenData.walkWorldPosMainAxisStepX = 1; wmGenData.walkWorldPosCrossAxisStepX = 1; wmGenData.walkWorldPosCrossAxisStepY = 1; } if (wmGenData.walkDestinationX < wmGenData.worldPosX) { wmGenData.walkWorldPosCrossAxisStepX = -wmGenData.walkWorldPosCrossAxisStepX; wmGenData.walkWorldPosMainAxisStepX = -wmGenData.walkWorldPosMainAxisStepX; } if (wmGenData.walkDestinationY < wmGenData.worldPosY) { wmGenData.walkWorldPosCrossAxisStepY = -wmGenData.walkWorldPosCrossAxisStepY; wmGenData.walkWorldPosMainAxisStepY = -wmGenData.walkWorldPosMainAxisStepY; } if (!wmCursorIsVisible()) { wmInterfaceCenterOnParty(); } } // 0x4C1F90 static void wmPartyWalkingStep() { if (wmGenData.walkDistance <= 0) { return; } _terrainCounter++; if (_terrainCounter > 4) { _terrainCounter = 1; } // NOTE: Uninline. wmPartyFindCurSubTile(); Terrain* terrain = &(wmTerrainTypeList[wmGenData.currentSubtile->terrain]); // SFALL: Fix Pathfinder perk. int terrainDifficulty = terrain->difficulty; if (terrainDifficulty < 1) { terrainDifficulty = 1; } if (_terrainCounter / terrainDifficulty >= 1) { if (wmGenData.walkLineDelta >= 0) { if (wmWorldPosInvalid(wmGenData.walkWorldPosCrossAxisStepX + wmGenData.worldPosX, wmGenData.walkWorldPosCrossAxisStepY + wmGenData.worldPosY)) { wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosX, &(wmGenData.currentAreaId)); wmGenData.walkDistance = 0; return; } wmGenData.walkLineDelta += wmGenData.walkLineDeltaCrossAxisStep; wmGenData.worldPosX += wmGenData.walkWorldPosCrossAxisStepX; wmGenData.worldPosY += wmGenData.walkWorldPosCrossAxisStepY; wmInterfaceScrollPixel(1, 1, wmGenData.walkWorldPosCrossAxisStepX, wmGenData.walkWorldPosCrossAxisStepY, NULL, false); } else { if (wmWorldPosInvalid(wmGenData.walkWorldPosMainAxisStepX + wmGenData.worldPosX, wmGenData.walkWorldPosMainAxisStepY + wmGenData.worldPosY) == 1) { wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosX, &(wmGenData.currentAreaId)); wmGenData.walkDistance = 0; return; } wmGenData.walkLineDelta += wmGenData.walkLineDeltaMainAxisStep; wmGenData.worldPosY += wmGenData.walkWorldPosMainAxisStepY; wmGenData.worldPosX += wmGenData.walkWorldPosMainAxisStepX; wmInterfaceScrollPixel(1, 1, wmGenData.walkWorldPosMainAxisStepX, wmGenData.walkWorldPosMainAxisStepY, NULL, false); } wmGenData.walkDistance -= 1; if (wmGenData.walkDistance == 0) { wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmGenData.walkDestinationX = 0; } } } // 0x4C219C static void wmInterfaceScrollTabsStart(int delta) { for (int index = 0; index < 7; index++) { buttonDisable(wmTownMapSubButtonIds[index]); } wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY; if (delta >= 0) { if (wmGenData.oldTabsOffsetY < wmGenData.tabsBackgroundFrmImage.getHeight() - 230) { wmGenData.oldTabsOffsetY = std::min(wmGenData.tabsOffsetY + 7 * delta, wmGenData.tabsBackgroundFrmImage.getHeight() - 230); wmGenData.tabsScrollingDelta = delta; } } else { if (wmGenData.tabsOffsetY > 0) { wmGenData.oldTabsOffsetY = std::max(wmGenData.tabsOffsetY + 7 * delta, 0); wmGenData.tabsScrollingDelta = delta; } } // NOTE: Uninline. wmInterfaceScrollTabsUpdate(); } // 0x4C2270 static void wmInterfaceScrollTabsStop() { wmGenData.tabsScrollingDelta = 0; for (int index = 0; index < 7; index++) { buttonEnable(wmTownMapSubButtonIds[index]); } } // NOTE: Inlined. // // 0x4C2290 static void wmInterfaceScrollTabsUpdate() { if (wmGenData.tabsScrollingDelta != 0) { wmGenData.tabsOffsetY += wmGenData.tabsScrollingDelta; wmRefreshInterfaceOverlay(true); if (wmGenData.tabsScrollingDelta >= 0) { if (wmGenData.oldTabsOffsetY <= wmGenData.tabsOffsetY) { // NOTE: Uninline. wmInterfaceScrollTabsStop(); } } else { if (wmGenData.oldTabsOffsetY >= wmGenData.tabsOffsetY) { // NOTE: Uninline. wmInterfaceScrollTabsStop(); } } } } // 0x4C2324 static int wmInterfaceInit() { int fid; wmLastRndTime = getTicks(); // SFALL: Fix default worldmap font. // CE: This setting affects only city names. In Sfall it's configurable via // WorldMapFontPatch and is turned off by default. wmGenData.oldFont = fontGetCurrent(); fontSetCurrent(101); _map_save_in_game(true); const char* backgroundSoundFileName = wmGenData.isInCar ? "20car" : "23world"; _gsound_background_play_level_music(backgroundSoundFileName, 12); // CE: Hide entire interface, not just indicator bar, and disable tile // engine. interfaceBarHide(); tileDisable(); isoDisable(); colorCycleDisable(); gameMouseSetCursor(MOUSE_CURSOR_ARROW); // CE: Clear map window. windowFill(gIsoWindow, 0, 0, windowGetWidth(gIsoWindow), windowGetHeight(gIsoWindow), _colorTable[0]); windowRefresh(gIsoWindow); // CE: Stop all animations. animationStop(); int worldmapWindowX = (screenGetWidth() - WM_WINDOW_WIDTH) / 2; int worldmapWindowY = (screenGetHeight() - WM_WINDOW_HEIGHT) / 2; wmBkWin = windowCreate(worldmapWindowX, worldmapWindowY, WM_WINDOW_WIDTH, WM_WINDOW_HEIGHT, _colorTable[0], WINDOW_MOVE_ON_TOP); if (wmBkWin == -1) { return -1; } fid = buildFid(OBJ_TYPE_INTERFACE, 136, 0, 0, 0); if (!_backgroundFrmImage.lock(fid)) { return -1; } wmBkWinBuf = windowGetBuffer(wmBkWin); if (wmBkWinBuf == NULL) { return -1; } blitBufferToBuffer(_backgroundFrmImage.getData(), _backgroundFrmImage.getWidth(), _backgroundFrmImage.getHeight(), _backgroundFrmImage.getWidth(), wmBkWinBuf, WM_WINDOW_WIDTH); for (int citySize = 0; citySize < CITY_SIZE_COUNT; citySize++) { CitySizeDescription* citySizeDescription = &(wmSphereData[citySize]); if (!citySizeDescription->frmImage.lock(citySizeDescription->fid)) { return -1; } } // hotspot1.frm - town map selector shape #1 fid = buildFid(OBJ_TYPE_INTERFACE, 168, 0, 0, 0); if (!wmGenData.hotspotNormalFrmImage.lock(fid)) { return -1; } // hotspot2.frm - town map selector shape #2 fid = buildFid(OBJ_TYPE_INTERFACE, 223, 0, 0, 0); if (!wmGenData.hotspotPressedFrmImage.lock(fid)) { return -1; } // wmaptarg.frm - world map move target maker #1 fid = buildFid(OBJ_TYPE_INTERFACE, 139, 0, 0, 0); if (!wmGenData.destinationMarkerFrmImage.lock(fid)) { return -1; } // wmaploc.frm - world map location marker fid = buildFid(OBJ_TYPE_INTERFACE, 138, 0, 0, 0); if (!wmGenData.locationMarkerFrmImage.lock(fid)) { return -1; } for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) { fid = buildFid(OBJ_TYPE_INTERFACE, wmRndCursorFids[index], 0, 0, 0); if (!wmGenData.encounterCursorFrmImages[index].lock(fid)) { return -1; } } for (int index = 0; index < wmMaxTileNum; index++) { wmTileInfoList[index].handle = INVALID_CACHE_ENTRY; } // wmtabs.frm - worldmap town tabs underlay fid = buildFid(OBJ_TYPE_INTERFACE, 364, 0, 0, 0); if (!wmGenData.tabsBackgroundFrmImage.lock(fid)) { return -1; } // wmtbedge.frm - worldmap town tabs edging overlay fid = buildFid(OBJ_TYPE_INTERFACE, 367, 0, 0, 0); if (!wmGenData.tabsBorderFrmImage.lock(fid)) { return -1; } // wmdial.frm - worldmap night/day dial fid = buildFid(OBJ_TYPE_INTERFACE, 365, 0, 0, 0); wmGenData.dialFrm = artLock(fid, &(wmGenData.dialFrmHandle)); if (wmGenData.dialFrm == NULL) { return -1; } wmGenData.dialFrmWidth = artGetWidth(wmGenData.dialFrm, 0, 0); wmGenData.dialFrmHeight = artGetHeight(wmGenData.dialFrm, 0, 0); // wmscreen - worldmap overlay screen fid = buildFid(OBJ_TYPE_INTERFACE, 363, 0, 0, 0); if (!wmGenData.carOverlayFrmImage.lock(fid)) { return -1; } // wmglobe.frm - worldmap globe stamp overlay fid = buildFid(OBJ_TYPE_INTERFACE, 366, 0, 0, 0); if (!wmGenData.globeOverlayFrmImage.lock(fid)) { return -1; } // lilredup.frm - little red button up fid = buildFid(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); wmGenData.redButtonNormalFrmImage.lock(fid); // lilreddn.frm - little red button down fid = buildFid(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); wmGenData.redButtonPressedFrmImage.lock(fid); // months.frm - month strings for pip boy fid = buildFid(OBJ_TYPE_INTERFACE, 129, 0, 0, 0); if (!wmGenData.monthsFrmImage.lock(fid)) { return -1; } // numbers.frm - numbers for the hit points and fatigue counters fid = buildFid(OBJ_TYPE_INTERFACE, 82, 0, 0, 0); if (!wmGenData.numbersFrmImage.lock(fid)) { return -1; } // create town/world switch button int switchBtn = buttonCreate(wmBkWin, WM_TOWN_WORLD_SWITCH_X, WM_TOWN_WORLD_SWITCH_Y, wmGenData.redButtonNormalFrmImage.getWidth(), wmGenData.redButtonNormalFrmImage.getHeight(), -1, -1, -1, KEY_UPPERCASE_T, wmGenData.redButtonNormalFrmImage.getData(), wmGenData.redButtonPressedFrmImage.getData(), NULL, BUTTON_FLAG_TRANSPARENT); // SFALL: Add missing button sounds. if (switchBtn != -1) { buttonSetCallbacks(switchBtn, _gsound_red_butt_press, _gsound_red_butt_release); } for (int index = 0; index < 7; index++) { wmTownMapSubButtonIds[index] = buttonCreate(wmBkWin, 508, 138 + 27 * index, wmGenData.redButtonNormalFrmImage.getWidth(), wmGenData.redButtonNormalFrmImage.getHeight(), -1, -1, -1, KEY_CTRL_F1 + index, wmGenData.redButtonNormalFrmImage.getData(), wmGenData.redButtonPressedFrmImage.getData(), NULL, BUTTON_FLAG_TRANSPARENT); // SFALL: Add missing button sounds. if (wmTownMapSubButtonIds[index] != -1) { buttonSetCallbacks(wmTownMapSubButtonIds[index], _gsound_red_butt_press, _gsound_red_butt_release); } } for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { // 200 - uparwon.frm - character editor // 199 - uparwoff.frm - character editor // SFALL: Fix images for scroll buttons. fid = buildFid(OBJ_TYPE_INTERFACE, 199 + index, 0, 0, 0); if (!wmGenData.scrollUpButtonFrmImages[index].lock(fid)) { return -1; } } for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { // 182 - dnarwon.frm - character editor // 181 - dnarwoff.frm - character editor // SFALL: Fix images for scroll buttons. fid = buildFid(OBJ_TYPE_INTERFACE, 181 + index, 0, 0, 0); if (!wmGenData.scrollDownButtonFrmImages[index].lock(fid)) { return -1; } } // Scroll up button. int scrollUpBtn = buttonCreate(wmBkWin, WM_TOWN_LIST_SCROLL_UP_X, WM_TOWN_LIST_SCROLL_UP_Y, wmGenData.scrollUpButtonFrmImages[WORLDMAP_ARROW_FRM_NORMAL].getWidth(), wmGenData.scrollUpButtonFrmImages[WORLDMAP_ARROW_FRM_NORMAL].getHeight(), -1, -1, -1, KEY_CTRL_ARROW_UP, wmGenData.scrollUpButtonFrmImages[WORLDMAP_ARROW_FRM_NORMAL].getData(), wmGenData.scrollUpButtonFrmImages[WORLDMAP_ARROW_FRM_PRESSED].getData(), NULL, BUTTON_FLAG_TRANSPARENT); // SFALL: Add missing button sounds. if (scrollUpBtn != -1) { buttonSetCallbacks(scrollUpBtn, _gsound_red_butt_press, _gsound_red_butt_release); } // Scroll down button. int scrollDownBtn = buttonCreate(wmBkWin, WM_TOWN_LIST_SCROLL_DOWN_X, WM_TOWN_LIST_SCROLL_DOWN_Y, wmGenData.scrollDownButtonFrmImages[WORLDMAP_ARROW_FRM_NORMAL].getWidth(), wmGenData.scrollDownButtonFrmImages[WORLDMAP_ARROW_FRM_NORMAL].getHeight(), -1, -1, -1, KEY_CTRL_ARROW_DOWN, wmGenData.scrollDownButtonFrmImages[WORLDMAP_ARROW_FRM_NORMAL].getData(), wmGenData.scrollDownButtonFrmImages[WORLDMAP_ARROW_FRM_PRESSED].getData(), NULL, BUTTON_FLAG_TRANSPARENT); // SFALL: Add missing button sounds. if (scrollDownBtn != -1) { buttonSetCallbacks(scrollDownBtn, _gsound_red_butt_press, _gsound_red_butt_release); } if (wmGenData.isInCar) { // wmcarmve.frm - worldmap car movie fid = buildFid(OBJ_TYPE_INTERFACE, 433, 0, 0, 0); wmGenData.carImageFrm = artLock(fid, &(wmGenData.carImageFrmHandle)); if (wmGenData.carImageFrm == NULL) { return -1; } wmGenData.carImageFrmWidth = artGetWidth(wmGenData.carImageFrm, 0, 0); wmGenData.carImageFrmHeight = artGetHeight(wmGenData.carImageFrm, 0, 0); } tickersAdd(wmMouseBkProc); if (wmMakeTabsLabelList(&wmLabelList, &wmLabelCount) == -1) { return -1; } wmInterfaceWasInitialized = 1; if (wmInterfaceRefresh() == -1) { return -1; } windowRefresh(wmBkWin); scriptsDisable(); _scr_remove_all(); return 0; } // 0x4C2E44 static int wmInterfaceExit() { int i; TileInfo* tile; tickersRemove(wmMouseBkProc); _backgroundFrmImage.unlock(); if (wmBkWin != -1) { windowDestroy(wmBkWin); wmBkWin = -1; } wmGenData.hotspotNormalFrmImage.unlock(); wmGenData.hotspotPressedFrmImage.unlock(); wmGenData.destinationMarkerFrmImage.unlock(); wmGenData.locationMarkerFrmImage.unlock(); for (i = 0; i < 4; i++) { wmGenData.encounterCursorFrmImages[i].unlock(); } for (i = 0; i < CITY_SIZE_COUNT; i++) { CitySizeDescription* citySizeDescription = &(wmSphereData[i]); citySizeDescription->frmImage.unlock(); } for (i = 0; i < wmMaxTileNum; i++) { tile = &(wmTileInfoList[i]); if (tile->handle != INVALID_CACHE_ENTRY) { artUnlock(tile->handle); tile->handle = INVALID_CACHE_ENTRY; tile->data = NULL; if (tile->walkMaskData != NULL) { internal_free(tile->walkMaskData); tile->walkMaskData = NULL; } } } wmGenData.tabsBackgroundFrmImage.unlock(); wmGenData.tabsBorderFrmImage.unlock(); if (wmGenData.dialFrm != NULL) { artUnlock(wmGenData.dialFrmHandle); wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY; wmGenData.dialFrm = NULL; } wmGenData.carOverlayFrmImage.unlock(); wmGenData.globeOverlayFrmImage.unlock(); wmGenData.redButtonNormalFrmImage.unlock(); wmGenData.redButtonPressedFrmImage.unlock(); for (i = 0; i < 2; i++) { wmGenData.scrollUpButtonFrmImages[i].unlock(); wmGenData.scrollDownButtonFrmImages[i].unlock(); } wmGenData.monthsFrmImage.unlock(); wmGenData.numbersFrmImage.unlock(); if (wmGenData.carImageFrm != NULL) { artUnlock(wmGenData.carImageFrmHandle); wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageFrm = NULL; wmGenData.carImageFrmWidth = 0; wmGenData.carImageFrmHeight = 0; } wmGenData.encounterIconIsVisible = false; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; // CE: Enable tile engine and interface. interfaceBarShow(); tileEnable(); isoEnable(); colorCycleEnable(); fontSetCurrent(wmGenData.oldFont); // NOTE: Uninline. wmFreeTabsLabelList(&wmLabelList, &wmLabelCount); wmInterfaceWasInitialized = 0; scriptsEnable(); return 0; } // NOTE: Inlined. // // 0x4C31E8 static int wmInterfaceScroll(int dx, int dy, bool* successPtr) { return wmInterfaceScrollPixel(20, 20, dx, dy, successPtr, 1); } // FIXME: There is small bug in this function. There is [success] flag returned // by reference so that calling code can update scrolling mouse cursor to invalid // range. It works OK on straight directions. But in diagonals when scrolling in // one direction is possible (and in fact occured), it will still be reported as // error. // // 0x4C3200 static int wmInterfaceScrollPixel(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh) { if (success != NULL) { *success = true; } if (dy < 0) { if (wmWorldOffsetY > 0) { wmWorldOffsetY -= stepY; if (wmWorldOffsetY < 0) { wmWorldOffsetY = 0; } } else { if (success != NULL) { *success = false; } } } else if (dy > 0) { if (wmWorldOffsetY < wmGenData.viewportMaxY) { wmWorldOffsetY += stepY; if (wmWorldOffsetY > wmGenData.viewportMaxY) { wmWorldOffsetY = wmGenData.viewportMaxY; } } else { if (success != NULL) { *success = false; } } } if (dx < 0) { if (wmWorldOffsetX > 0) { wmWorldOffsetX -= stepX; if (wmWorldOffsetX < 0) { wmWorldOffsetX = 0; } } else { if (success != NULL) { *success = false; } } } else if (dx > 0) { if (wmWorldOffsetX < wmGenData.viewportMaxX) { wmWorldOffsetX += stepX; if (wmWorldOffsetX > wmGenData.viewportMaxX) { wmWorldOffsetX = wmGenData.viewportMaxX; } } else { if (success != NULL) { *success = false; } } } if (shouldRefresh) { if (wmInterfaceRefresh() == -1) { return -1; } } return 0; } // 0x4C32EC static void wmMouseBkProc() { // 0x51DEB0 static unsigned int lastTime = 0; // 0x51DEB4 static bool couldScroll = true; int x; int y; mouseGetPosition(&x, &y); int dx = 0; if (x == screenGetWidth() - 1) { dx = 1; } else if (x == 0) { dx = -1; } int dy = 0; if (y == screenGetHeight() - 1) { dy = 1; } else if (y == 0) { dy = -1; } int oldMouseCursor = gameMouseGetCursor(); int newMouseCursor = oldMouseCursor; if (dx != 0 || dy != 0) { if (dx > 0) { if (dy > 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_SE; } else if (dy < 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_NE; } else { newMouseCursor = MOUSE_CURSOR_SCROLL_E; } } else if (dx < 0) { if (dy > 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_SW; } else if (dy < 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_NW; } else { newMouseCursor = MOUSE_CURSOR_SCROLL_W; } } else { if (dy < 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_N; } else if (dy > 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_S; } } unsigned int tick = _get_bk_time(); if (getTicksBetween(tick, lastTime) > 50) { lastTime = _get_bk_time(); // NOTE: Uninline. wmInterfaceScroll(dx, dy, &couldScroll); } if (!couldScroll) { newMouseCursor += 8; } } else { if (oldMouseCursor != MOUSE_CURSOR_ARROW) { newMouseCursor = MOUSE_CURSOR_ARROW; } } if (oldMouseCursor != newMouseCursor) { gameMouseSetCursor(newMouseCursor); } } // NOTE: Inlined. // // 0x4C340C static int wmMarkSubTileOffsetVisited(int tile, int subtileX, int subtileY, int offsetX, int offsetY) { return wmMarkSubTileOffsetVisitedFunc(tile, subtileX, subtileY, offsetX, offsetY, SUBTILE_STATE_VISITED); } // NOTE: Inlined. // // 0x4C3420 static int wmMarkSubTileOffsetKnown(int tile, int subtileX, int subtileY, int offsetX, int offsetY) { return wmMarkSubTileOffsetVisitedFunc(tile, subtileX, subtileY, offsetX, offsetY, SUBTILE_STATE_KNOWN); } // 0x4C3434 static int wmMarkSubTileOffsetVisitedFunc(int tile, int subtileX, int subtileY, int offsetX, int offsetY, int subtileState) { int actualTile; int actualSubtileX; int actualSubtileY; TileInfo* tileInfo; SubtileInfo* subtileInfo; actualSubtileX = subtileX + offsetX; actualTile = tile; actualSubtileY = subtileY + offsetY; if (actualSubtileX >= 0) { if (actualSubtileX >= SUBTILE_GRID_WIDTH) { if (tile % wmNumHorizontalTiles == wmNumHorizontalTiles - 1) { return -1; } actualTile = tile + 1; actualSubtileX %= SUBTILE_GRID_WIDTH; } } else { if (!(tile % wmNumHorizontalTiles)) { return -1; } actualSubtileX += SUBTILE_GRID_WIDTH; actualTile = tile - 1; } if (actualSubtileY >= 0) { if (actualSubtileY >= SUBTILE_GRID_HEIGHT) { if (actualTile > wmMaxTileNum - wmNumHorizontalTiles - 1) { return -1; } actualTile += wmNumHorizontalTiles; actualSubtileY %= SUBTILE_GRID_HEIGHT; } } else { if (actualTile < wmNumHorizontalTiles) { return -1; } actualSubtileY += SUBTILE_GRID_HEIGHT; actualTile -= wmNumHorizontalTiles; } tileInfo = &(wmTileInfoList[actualTile]); subtileInfo = &(tileInfo->subtiles[actualSubtileY][actualSubtileX]); if (subtileState != SUBTILE_STATE_KNOWN || subtileInfo->state == SUBTILE_STATE_UNKNOWN) { subtileInfo->state = subtileState; } return 0; } // 0x4C3550 static void wmMarkSubTileRadiusVisited(int x, int y) { int radius = 1; if (perkHasRank(gDude, PERK_SCOUT)) { radius = 2; } wmSubTileMarkRadiusVisited(x, y, radius); } // 0x4C35A8 int wmSubTileMarkRadiusVisited(int x, int y, int radius) { int tile; int subtileX; int subtileY; int offsetX; int offsetY; SubtileInfo* subtile; tile = x / WM_TILE_WIDTH % wmNumHorizontalTiles + y / WM_TILE_HEIGHT * wmNumHorizontalTiles; subtileX = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE; subtileY = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE; for (offsetY = -radius; offsetY <= radius; offsetY++) { for (offsetX = -radius; offsetX <= radius; offsetX++) { // NOTE: Uninline. wmMarkSubTileOffsetKnown(tile, subtileX, subtileY, offsetX, offsetY); } } subtile = &(wmTileInfoList[tile].subtiles[subtileY][subtileX]); subtile->state = SUBTILE_STATE_VISITED; switch (subtile->fill) { case SUBTILE_FILL_S: while (subtileY-- > 0) { // NOTE: Uninline. wmMarkSubTileOffsetVisited(tile, subtileX, subtileY, 0, 0); } break; case SUBTILE_FILL_W: while (subtileX-- >= 0) { // NOTE: Uninline. wmMarkSubTileOffsetVisited(tile, subtileX, subtileY, 0, 0); } if (tile % wmNumHorizontalTiles > 0) { for (subtileX = 0; subtileX < SUBTILE_GRID_WIDTH; subtileX++) { // NOTE: Uninline. wmMarkSubTileOffsetVisited(tile - 1, subtileX, subtileY, 0, 0); } } break; } return 0; } // 0x4C3740 int wmSubTileGetVisitedState(int x, int y, int* statePtr) { TileInfo* tile; SubtileInfo* subtile; tile = &(wmTileInfoList[y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles]); subtile = &(tile->subtiles[y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE][x % WM_TILE_WIDTH / WM_SUBTILE_SIZE]); *statePtr = subtile->state; return 0; } // Load tile art if needed. // // 0x4C37EC static int wmTileGrabArt(int tileIdx) { TileInfo* tile = &(wmTileInfoList[tileIdx]); if (tile->data != NULL) { return 0; } tile->data = artLockFrameData(tile->fid, 0, 0, &(tile->handle)); if (tile->data != NULL) { return 0; } wmInterfaceExit(); return -1; } // 0x4C3830 static int wmInterfaceRefresh() { if (wmInterfaceWasInitialized != 1) { return 0; } int v17 = wmWorldOffsetX % WM_TILE_WIDTH; int v18 = wmWorldOffsetY % WM_TILE_HEIGHT; int v20 = WM_TILE_HEIGHT - v18; int v21 = WM_TILE_WIDTH * v18; int v19 = WM_TILE_WIDTH - v17; // Render tiles. int y = 0; int x = 0; int v0 = wmWorldOffsetY / WM_TILE_HEIGHT * wmNumHorizontalTiles + wmWorldOffsetX / WM_TILE_WIDTH % wmNumHorizontalTiles; while (y < WM_VIEW_HEIGHT) { x = 0; int v23 = 0; int height; while (x < WM_VIEW_WIDTH) { if (wmTileGrabArt(v0) == -1) { return -1; } int width = WM_TILE_WIDTH; int srcX = 0; if (x == 0) { srcX = v17; width = v19; } if (width + x > WM_VIEW_WIDTH) { width = WM_VIEW_WIDTH - x; } height = WM_TILE_HEIGHT; if (y == 0) { height = v20; srcX += v21; } if (height + y > WM_VIEW_HEIGHT) { height = WM_VIEW_HEIGHT - y; } TileInfo* tileInfo = &(wmTileInfoList[v0]); blitBufferToBuffer(tileInfo->data + srcX, width, height, WM_TILE_WIDTH, wmBkWinBuf + WM_WINDOW_WIDTH * (y + WM_VIEW_Y) + WM_VIEW_X + x, WM_WINDOW_WIDTH); v0++; x += width; v23++; } v0 += wmNumHorizontalTiles - v23; y += height; } // Render cities. for (int index = 0; index < wmMaxAreaNum; index++) { CityInfo* cityInfo = &(wmAreaInfoList[index]); if (cityInfo->state != CITY_STATE_UNKNOWN) { CitySizeDescription* citySizeDescription = &(wmSphereData[cityInfo->size]); int cityX = cityInfo->x - wmWorldOffsetX; int cityY = cityInfo->y - wmWorldOffsetY; if (cityX >= 0 && cityX <= 472 - citySizeDescription->frmImage.getWidth() && cityY >= 0 && cityY <= 465 - citySizeDescription->frmImage.getHeight()) { wmInterfaceDrawCircleOverlay(cityInfo, citySizeDescription, wmBkWinBuf, cityX, cityY); } } } // Hide unknown subtiles, dim unvisited. int v25 = wmWorldOffsetX / WM_TILE_WIDTH % wmNumHorizontalTiles + wmWorldOffsetY / WM_TILE_HEIGHT * wmNumHorizontalTiles; int v30 = 0; while (v30 < WM_VIEW_HEIGHT) { int v24 = 0; int v33 = 0; int v29; while (v33 < WM_VIEW_WIDTH) { int v31 = WM_TILE_WIDTH; if (v33 == 0) { v31 = WM_TILE_WIDTH - v17; } if (v33 + v31 > WM_VIEW_WIDTH) { v31 = WM_VIEW_WIDTH - v33; } v29 = WM_TILE_HEIGHT; if (v30 == 0) { v29 -= v18; } if (v30 + v29 > WM_VIEW_HEIGHT) { v29 = WM_VIEW_HEIGHT - v30; } int v32; if (v30 != 0) { v32 = WM_VIEW_Y; } else { v32 = WM_VIEW_Y - v18; } int v13 = 0; int v34 = v30 + v32; for (int row = 0; row < SUBTILE_GRID_HEIGHT; row++) { int v35; if (v33 != 0) { v35 = WM_VIEW_X; } else { v35 = WM_VIEW_X - v17; } int v15 = v33 + v35; for (int column = 0; column < SUBTILE_GRID_WIDTH; column++) { TileInfo* tileInfo = &(wmTileInfoList[v25]); wmInterfaceDrawSubTileList(tileInfo, column, row, v15, v34, 1); v15 += WM_SUBTILE_SIZE; v35 += WM_SUBTILE_SIZE; } v32 += WM_SUBTILE_SIZE; v34 += WM_SUBTILE_SIZE; } v25++; v24++; v33 += v31; } v25 += wmNumHorizontalTiles - v24; v30 += v29; } wmDrawCursorStopped(); wmRefreshInterfaceOverlay(true); return 0; } // 0x4C3C9C static void wmInterfaceRefreshDate(bool shouldRefreshWindow) { int month; int day; int year; gameTimeGetDate(&month, &day, &year); month--; unsigned char* dest = wmBkWinBuf; int numbersFrmWidth = wmGenData.numbersFrmImage.getWidth(); int numbersFrmHeight = wmGenData.numbersFrmImage.getHeight(); unsigned char* numbersFrmData = wmGenData.numbersFrmImage.getData(); dest += WM_WINDOW_WIDTH * 12 + 487; blitBufferToBuffer(numbersFrmData + 9 * (day / 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); blitBufferToBuffer(numbersFrmData + 9 * (day % 10), 9, numbersFrmHeight, numbersFrmWidth, dest + 9, WM_WINDOW_WIDTH); int monthsFrmWidth = wmGenData.monthsFrmImage.getWidth(); unsigned char* monthsFrmData = wmGenData.monthsFrmImage.getData(); blitBufferToBuffer(monthsFrmData + monthsFrmWidth * 15 * month, 29, 14, 29, dest + WM_WINDOW_WIDTH + 26, WM_WINDOW_WIDTH); dest += 98; for (int index = 0; index < 4; index++) { dest -= 9; blitBufferToBuffer(numbersFrmData + 9 * (year % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); year /= 10; } int gameTimeHour = gameTimeGetHour(); dest += 72; for (int index = 0; index < 4; index++) { blitBufferToBuffer(numbersFrmData + 9 * (gameTimeHour % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); dest -= 9; gameTimeHour /= 10; } if (shouldRefreshWindow) { Rect rect; rect.left = 487; rect.top = 12; rect.bottom = numbersFrmHeight + 12; rect.right = 630; windowRefreshRect(wmBkWin, &rect); } } // 0x4C3F00 static int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr) { int v3 = y + WM_VIEW_Y; int v4 = x + WM_VIEW_X; int index; for (index = 0; index < wmMaxAreaNum; index++) { CityInfo* city = &(wmAreaInfoList[index]); if (city->state) { if (v4 >= city->x && v3 >= city->y) { CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); if (v4 <= city->x + citySizeDescription->frmImage.getWidth() && v3 <= city->y + citySizeDescription->frmImage.getHeight()) { break; } } } } if (index == wmMaxAreaNum) { *areaIdxPtr = -1; } else { *areaIdxPtr = index; } return 0; } // 0x4C3FA8 static int wmInterfaceDrawCircleOverlay(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y) { _dark_translucent_trans_buf_to_buf(citySizeDescription->frmImage.getData(), citySizeDescription->frmImage.getWidth(), citySizeDescription->frmImage.getHeight(), citySizeDescription->frmImage.getWidth(), dest, x, y, WM_WINDOW_WIDTH, 0x10000, circleBlendTable, _commonGrayTable); // CE: Slightly increase whitespace between cirle and city name. int nameY = y + citySizeDescription->frmImage.getHeight() + 3; int maxY = 464 - fontGetLineHeight(); if (nameY < maxY) { MessageListItem messageListItem; char name[40]; if (wmAreaIsKnown(city->areaId)) { // NOTE: Uninline. wmGetAreaName(city, name); } else { strncpy(name, getmsg(&wmMsgFile, &messageListItem, 1004), 40); } int width = fontGetStringWidth(name); fontDrawText(dest + WM_WINDOW_WIDTH * nameY + x + citySizeDescription->frmImage.getWidth() / 2 - width / 2, name, width, WM_WINDOW_WIDTH, _colorTable[992] | FONT_SHADOW); } return 0; } // Helper function that dims specified rectangle in given buffer. It's used to // slightly darken subtile which is known, but not visited. // // 0x4C40A8 static void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch) { int skipY = pitch - width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned char color = *dest; *dest++ = intensityColorTable[color][75]; } dest += skipY; } } // 0x4C40E4 static int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6) { SubtileInfo* subtileInfo = &(tileInfo->subtiles[row][column]); int destY = y; int destX = x; int height = WM_SUBTILE_SIZE; if (y < WM_VIEW_Y) { if (y < 0) { height = y + 29; } else { height = WM_SUBTILE_SIZE - (WM_VIEW_Y - y); } destY = WM_VIEW_Y; } if (height + y > 464) { height -= height + y - 464; } int width = WM_SUBTILE_SIZE * a6; if (x < WM_VIEW_X) { destX = WM_VIEW_X; width -= WM_VIEW_X - x; } if (width + x > 472) { width -= width + x - 472; } if (width > 0 && height > 0) { unsigned char* dest = wmBkWinBuf + WM_WINDOW_WIDTH * destY + destX; switch (subtileInfo->state) { case SUBTILE_STATE_UNKNOWN: bufferFill(dest, width, height, WM_WINDOW_WIDTH, _colorTable[0]); break; case SUBTILE_STATE_KNOWN: wmInterfaceDrawSubTileRectFogged(dest, width, height, WM_WINDOW_WIDTH); break; } } return 0; } // 0x4C41EC static int wmDrawCursorStopped() { unsigned char* src; int width; int height; if (wmGenData.walkDestinationX >= 1 || wmGenData.walkDestinationY >= 1) { if (wmGenData.encounterIconIsVisible) { src = wmGenData.encounterCursorFrmImages[wmGenData.encounterCursorId].getData(); width = wmGenData.encounterCursorFrmImages[wmGenData.encounterCursorId].getWidth(); height = wmGenData.encounterCursorFrmImages[wmGenData.encounterCursorId].getHeight(); } else { src = wmGenData.locationMarkerFrmImage.getData(); width = wmGenData.locationMarkerFrmImage.getWidth(); height = wmGenData.locationMarkerFrmImage.getHeight(); } if (wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT) { blitBufferToBufferTrans(src, width, height, width, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.worldPosY - height / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.worldPosX - width / 2, WM_WINDOW_WIDTH); } if (wmGenData.walkDestinationX >= wmWorldOffsetX && wmGenData.walkDestinationX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.walkDestinationY >= wmWorldOffsetY && wmGenData.walkDestinationY < wmWorldOffsetY + WM_VIEW_HEIGHT) { blitBufferToBufferTrans(wmGenData.destinationMarkerFrmImage.getData(), wmGenData.destinationMarkerFrmImage.getWidth(), wmGenData.destinationMarkerFrmImage.getHeight(), wmGenData.destinationMarkerFrmImage.getWidth(), wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.walkDestinationY - wmGenData.destinationMarkerFrmImage.getHeight() / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.walkDestinationX - wmGenData.destinationMarkerFrmImage.getWidth() / 2, WM_WINDOW_WIDTH); } } else { if (wmGenData.encounterIconIsVisible) { src = wmGenData.encounterCursorFrmImages[wmGenData.encounterCursorId].getData(); width = wmGenData.encounterCursorFrmImages[wmGenData.encounterCursorId].getWidth(); height = wmGenData.encounterCursorFrmImages[wmGenData.encounterCursorId].getHeight(); } else { src = wmGenData.mousePressed ? wmGenData.hotspotPressedFrmImage.getData() : wmGenData.hotspotNormalFrmImage.getData(); width = wmGenData.hotspotNormalFrmImage.getWidth(); height = wmGenData.hotspotNormalFrmImage.getHeight(); } if (wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT) { blitBufferToBufferTrans(src, width, height, width, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.worldPosY - height / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.worldPosX - width / 2, WM_WINDOW_WIDTH); } } return 0; } // 0x4C4490 static bool wmCursorIsVisible() { return wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT; } // NOTE: Inlined. // // 0x4C44D8 static int wmGetAreaName(CityInfo* city, char* name) { MessageListItem messageListItem; getmsg(&gMapMessageList, &messageListItem, city->areaId + 1500); strncpy(name, messageListItem.text, 40); return 0; } // Copy city short name. // // 0x4C450C int wmGetAreaIdxName(int areaIdx, char* name) { MessageListItem messageListItem; getmsg(&gMapMessageList, &messageListItem, 1500 + areaIdx); strncpy(name, messageListItem.text, 40); return 0; } // Returns true if world area is known. // // 0x4C453C bool wmAreaIsKnown(int areaIdx) { if (!cityIsValid(areaIdx)) { return false; } CityInfo* city = &(wmAreaInfoList[areaIdx]); if (city->visitedState) { if (city->state == CITY_STATE_KNOWN) { return true; } } return false; } // 0x4C457C int wmAreaVisitedState(int areaIdx) { if (!cityIsValid(areaIdx)) { return 0; } CityInfo* city = &(wmAreaInfoList[areaIdx]); if (city->visitedState && city->state == CITY_STATE_KNOWN) { return city->visitedState; } return 0; } // 0x4C45BC bool wmMapIsKnown(int mapIdx) { int areaIdx; if (wmMatchAreaFromMap(mapIdx, &areaIdx) != 0) { return false; } int entranceIdx; if (wmMatchEntranceFromMap(areaIdx, mapIdx, &entranceIdx) != 0) { return false; } CityInfo* city = &(wmAreaInfoList[areaIdx]); EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (entrance->state != 1) { return false; } return true; } // 0x4C4624 int wmAreaMarkVisited(int areaIdx) { return wmAreaMarkVisitedState(areaIdx, CITY_STATE_VISITED); } // 0x4C4634 bool wmAreaMarkVisitedState(int areaIdx, int state) { if (!cityIsValid(areaIdx)) { return false; } CityInfo* city = &(wmAreaInfoList[areaIdx]); int oldVisitedState = city->visitedState; if (city->state == CITY_STATE_KNOWN && state != 0) { wmMarkSubTileRadiusVisited(city->x, city->y); } city->visitedState = state; SubtileInfo* subtile; if (wmFindCurSubTileFromPos(city->x, city->y, &subtile) == -1) { return false; } if (state == 1) { subtile->state = SUBTILE_STATE_KNOWN; } else if (state == 2 && oldVisitedState == 0) { city->visitedState = 1; } return true; } // 0x4C46CC bool wmAreaSetVisibleState(int areaIdx, int state, bool force) { if (!cityIsValid(areaIdx)) { return false; } CityInfo* city = &(wmAreaInfoList[areaIdx]); if (city->lockState != LOCK_STATE_LOCKED || force) { city->state = state; return true; } return false; } // 0x4C4710 int wmAreaSetWorldPos(int areaIdx, int x, int y) { if (!cityIsValid(areaIdx)) { return -1; } if (x < 0 || x >= WM_TILE_WIDTH * wmNumHorizontalTiles) { return -1; } if (y < 0 || y >= WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles)) { return -1; } CityInfo* city = &(wmAreaInfoList[areaIdx]); city->x = x; city->y = y; return 0; } // Returns current town x/y. // // 0x4C47A4 int wmGetPartyWorldPos(int* xPtr, int* yPtr) { if (xPtr != NULL) { *xPtr = wmGenData.worldPosX; } if (yPtr != NULL) { *yPtr = wmGenData.worldPosY; } return 0; } // Returns current town. // // 0x4C47C0 int wmGetPartyCurArea(int* areaIdxPtr) { if (areaIdxPtr != NULL) { *areaIdxPtr = wmGenData.currentAreaId; return 0; } return -1; } // 0x4C47D8 static void wmMarkAllSubTiles(int state) { for (int tileIndex = 0; tileIndex < wmMaxTileNum; tileIndex++) { TileInfo* tile = &(wmTileInfoList[tileIndex]); for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { SubtileInfo* subtile = &(tile->subtiles[column][row]); subtile->state = state; } } } } // 0x4C4850 void wmTownMap() { wmWorldMapFunc(1); } // 0x4C485C static int wmTownMapFunc(int* mapIdxPtr) { *mapIdxPtr = -1; if (wmTownMapInit() == -1) { wmTownMapExit(); return -1; } if (wmGenData.currentAreaId == -1) { return -1; } CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); for (;;) { sharedFpsLimiter.mark(); int keyCode = inputGetInput(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { showQuitConfirmationDialog(); } if (_game_user_wants_to_quit) { break; } if (keyCode != -1) { if (keyCode == KEY_ESCAPE) { break; } if (keyCode >= KEY_1 && keyCode < KEY_1 + city->entrancesLength) { EntranceInfo* entrance = &(city->entrances[keyCode - KEY_1]); // SFALL: Prevent using number keys to enter unvisited areas on // a town map. if (gTownMapHotkeysFix) { if (entrance->state == 0 || entrance->x == -1 || entrance->y == -1) { continue; } } *mapIdxPtr = entrance->map; mapSetEnteringLocation(entrance->elevation, entrance->tile, entrance->rotation); break; } if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) { int quickDestinationIndex = wmGenData.tabsOffsetY / 27 + keyCode - KEY_CTRL_F1; if (quickDestinationIndex < wmLabelCount) { int areaIdx = wmLabelList[quickDestinationIndex]; CityInfo* city = &(wmAreaInfoList[areaIdx]); if (!wmAreaIsKnown(city->areaId)) { break; } if (areaIdx != wmGenData.currentAreaId) { // CE: Fix incorrect destination positioning. See // `wmWorldMapFunc` for explanation. CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); int destX = city->x + citySizeDescription->frmImage.getWidth() / 2 - WM_VIEW_X; int destY = city->y + citySizeDescription->frmImage.getHeight() / 2 - WM_VIEW_Y; wmPartyInitWalking(destX, destY); wmGenData.mousePressed = false; break; } } } else { if (keyCode == KEY_CTRL_ARROW_UP) { wmInterfaceScrollTabsStart(-27); } else if (keyCode == KEY_CTRL_ARROW_DOWN) { wmInterfaceScrollTabsStart(27); } else if (keyCode == 2069) { if (wmTownMapRefresh() == -1) { return -1; } } if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T || keyCode == KEY_UPPERCASE_W || keyCode == KEY_LOWERCASE_W) { keyCode = KEY_ESCAPE; } if (keyCode == KEY_ESCAPE) { break; } } } renderPresent(); sharedFpsLimiter.throttle(); } if (wmTownMapExit() == -1) { return -1; } return 0; } // 0x4C4A6C static int wmTownMapInit() { wmTownMapCurArea = wmGenData.currentAreaId; CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (!_townFrmImage.lock(city->mapFid)) { return -1; } for (int index = 0; index < city->entrancesLength; index++) { wmTownMapButtonId[index] = -1; } for (int index = 0; index < city->entrancesLength; index++) { EntranceInfo* entrance = &(city->entrances[index]); if (entrance->state == 0) { continue; } if (entrance->x == -1 || entrance->y == -1) { continue; } wmTownMapButtonId[index] = buttonCreate(wmBkWin, entrance->x, entrance->y, wmGenData.hotspotNormalFrmImage.getWidth(), wmGenData.hotspotNormalFrmImage.getHeight(), -1, 2069, -1, KEY_1 + index, wmGenData.hotspotNormalFrmImage.getData(), wmGenData.hotspotPressedFrmImage.getData(), NULL, BUTTON_FLAG_TRANSPARENT); if (wmTownMapButtonId[index] == -1) { return -1; } } tickersRemove(wmMouseBkProc); if (wmTownMapRefresh() == -1) { return -1; } return 0; } // 0x4C4BD0 static int wmTownMapRefresh() { blitBufferToBuffer(_townFrmImage.getData(), _townFrmImage.getWidth(), _townFrmImage.getHeight(), _townFrmImage.getWidth(), wmBkWinBuf + WM_WINDOW_WIDTH * WM_VIEW_Y + WM_VIEW_X, WM_WINDOW_WIDTH); wmRefreshInterfaceOverlay(false); CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); for (int index = 0; index < city->entrancesLength; index++) { EntranceInfo* entrance = &(city->entrances[index]); if (entrance->state == 0) { continue; } if (entrance->x == -1 || entrance->y == -1) { continue; } MessageListItem messageListItem; messageListItem.num = 200 + 10 * wmTownMapCurArea + index; if (messageListGetItem(&wmMsgFile, &messageListItem)) { if (messageListItem.text != NULL) { int width = fontGetStringWidth(messageListItem.text); // CE: Slightly increase whitespace between marker and entrance name. windowDrawText(wmBkWin, messageListItem.text, width, wmGenData.hotspotNormalFrmImage.getWidth() / 2 + entrance->x - width / 2, wmGenData.hotspotNormalFrmImage.getHeight() + entrance->y + 4, _colorTable[992] | 0x2000000 | FONT_SHADOW); } } } windowRefresh(wmBkWin); return 0; } // 0x4C4D00 static int wmTownMapExit() { _townFrmImage.unlock(); if (wmTownMapCurArea != -1) { CityInfo* city = &(wmAreaInfoList[wmTownMapCurArea]); for (int index = 0; index < city->entrancesLength; index++) { if (wmTownMapButtonId[index] != -1) { buttonDestroy(wmTownMapButtonId[index]); wmTownMapButtonId[index] = -1; } } } if (wmInterfaceRefresh() == -1) { return -1; } tickersAdd(wmMouseBkProc); return 0; } // 0x4C4DA4 int wmCarUseGas(int amount) { if (gameGetGlobalVar(GVAR_NEW_RENO_SUPER_CAR) != 0) { amount -= amount * 90 / 100; } if (gameGetGlobalVar(GVAR_NEW_RENO_CAR_UPGRADE) != 0) { amount -= amount * 10 / 100; } if (gameGetGlobalVar(GVAR_CAR_UPGRADE_FUEL_CELL_REGULATOR) != 0) { amount /= 2; } wmGenData.carFuel -= amount; if (wmGenData.carFuel < 0) { wmGenData.carFuel = 0; } return 0; } // Returns amount of fuel that does not fit into tank. // // 0x4C4E34 int wmCarFillGas(int amount) { if ((amount + wmGenData.carFuel) <= CAR_FUEL_MAX) { wmGenData.carFuel += amount; return 0; } int remaining = CAR_FUEL_MAX - wmGenData.carFuel; wmGenData.carFuel = CAR_FUEL_MAX; return remaining; } // 0x4C4E74 int wmCarGasAmount() { return wmGenData.carFuel; } // 0x4C4E7C bool wmCarIsOutOfGas() { return wmGenData.carFuel <= 0; } // 0x4C4E8C int wmCarCurrentArea() { return wmGenData.currentCarAreaId; } // 0x4C4E94 int wmCarGiveToParty() { MessageListItem messageListItem; memcpy(&messageListItem, &gWorldmapMessageListItem, sizeof(MessageListItem)); if (wmGenData.carFuel <= 0) { // The car is out of power. char* msg = getmsg(&wmMsgFile, &messageListItem, 1502); displayMonitorAddMessage(msg); return -1; } wmGenData.isInCar = true; MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = -2; mapSetTransition(&transition); CityInfo* city = &(wmAreaInfoList[CITY_CAR_OUT_OF_GAS]); city->state = CITY_STATE_UNKNOWN; city->visitedState = 0; return 0; } // 0x4C4F28 int wmSfxMaxCount() { int mapIdx = mapGetCurrentMap(); if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); return map->ambientSoundEffectsLength; } // 0x4C4F5C int wmSfxRollNextIdx() { int mapIdx = mapGetCurrentMap(); if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); int totalChances = 0; for (int index = 0; index < map->ambientSoundEffectsLength; index++) { MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]); totalChances += sfx->chance; } int chance = randomBetween(0, totalChances); for (int index = 0; index < map->ambientSoundEffectsLength; index++) { MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]); if (chance >= sfx->chance) { chance -= sfx->chance; continue; } return index; } return -1; } // 0x4C5004 int wmSfxIdxName(int sfxIdx, char** namePtr) { if (namePtr == NULL) { return -1; } *namePtr = NULL; int mapIdx = mapGetCurrentMap(); if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); if (sfxIdx < 0 || sfxIdx >= map->ambientSoundEffectsLength) { return -1; } MapAmbientSoundEffectInfo* ambientSoundEffectInfo = &(map->ambientSoundEffects[sfxIdx]); *namePtr = ambientSoundEffectInfo->name; // Remap bird sounds for night. int remapped = 0; if (strcmp(ambientSoundEffectInfo->name, "brdchir1") == 0) { remapped = 1; } else if (strcmp(ambientSoundEffectInfo->name, "brdchirp") == 0) { remapped = 2; } if (remapped != 0) { int dayPart; int gameTimeHour = gameTimeGetHour(); if (gameTimeHour <= 600 || gameTimeHour >= 1800) { dayPart = DAY_PART_NIGHT; } else if (gameTimeHour >= 1200) { dayPart = DAY_PART_AFTERNOON; } else { dayPart = DAY_PART_MORNING; } if (dayPart == DAY_PART_NIGHT) { *namePtr = wmRemapSfxList[remapped - 1]; } } return 0; } // 0x4C50F4 static int wmRefreshInterfaceOverlay(bool shouldRefreshWindow) { blitBufferToBufferTrans(_backgroundFrmImage.getData(), _backgroundFrmImage.getWidth(), _backgroundFrmImage.getHeight(), _backgroundFrmImage.getWidth(), wmBkWinBuf, WM_WINDOW_WIDTH); wmRefreshTabs(); // NOTE: Uninline. wmInterfaceDialSyncTime(false); wmRefreshInterfaceDial(false); if (wmGenData.isInCar) { unsigned char* data = artGetFrameData(wmGenData.carImageFrm, wmGenData.carImageCurrentFrameIndex, 0); if (data == NULL) { return -1; } blitBufferToBuffer(data, wmGenData.carImageFrmWidth, wmGenData.carImageFrmHeight, wmGenData.carImageFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_Y + WM_WINDOW_CAR_X, WM_WINDOW_WIDTH); blitBufferToBufferTrans(wmGenData.carOverlayFrmImage.getData(), wmGenData.carOverlayFrmImage.getWidth(), wmGenData.carOverlayFrmImage.getHeight(), wmGenData.carOverlayFrmImage.getWidth(), wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_OVERLAY_Y + WM_WINDOW_CAR_OVERLAY_X, WM_WINDOW_WIDTH); wmInterfaceRefreshCarFuel(); } else { blitBufferToBufferTrans(wmGenData.globeOverlayFrmImage.getData(), wmGenData.globeOverlayFrmImage.getWidth(), wmGenData.globeOverlayFrmImage.getHeight(), wmGenData.globeOverlayFrmImage.getWidth(), wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_GLOBE_OVERLAY_Y + WM_WINDOW_GLOBE_OVERLAY_X, WM_WINDOW_WIDTH); } wmInterfaceRefreshDate(false); if (shouldRefreshWindow) { windowRefresh(wmBkWin); } return 0; } // 0x4C5244 static void wmInterfaceRefreshCarFuel() { int ratio = (WM_WINDOW_CAR_FUEL_BAR_HEIGHT * wmGenData.carFuel) / CAR_FUEL_MAX; if ((ratio & 1) != 0) { ratio -= 1; } unsigned char* dest = wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_FUEL_BAR_Y + WM_WINDOW_CAR_FUEL_BAR_X; for (int index = WM_WINDOW_CAR_FUEL_BAR_HEIGHT; index > ratio; index--) { *dest = 14; dest += 640; } while (ratio > 0) { *dest = 196; dest += WM_WINDOW_WIDTH; *dest = 14; dest += WM_WINDOW_WIDTH; ratio -= 2; } } // 0x4C52B0 static int wmRefreshTabs() { unsigned char* v30; unsigned char* v0; int v31; CityInfo* city; int v10; unsigned char* v11; unsigned char* v12; int v32; unsigned char* v13; FrmImage labelFrm; blitBufferToBufferTrans(wmGenData.tabsBackgroundFrmImage.getData() + wmGenData.tabsBackgroundFrmImage.getWidth() * wmGenData.tabsOffsetY + 9, 119, 178, wmGenData.tabsBackgroundFrmImage.getWidth(), wmBkWinBuf + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH); v30 = wmBkWinBuf + WM_WINDOW_WIDTH * 138 + 530; v0 = wmBkWinBuf + WM_WINDOW_WIDTH * 138 + 530 - WM_WINDOW_WIDTH * (wmGenData.tabsOffsetY % 27); v31 = wmGenData.tabsOffsetY / 27; if (v31 < wmLabelCount) { city = &(wmAreaInfoList[wmLabelList[v31]]); if (city->labelFid != -1) { if (!labelFrm.lock(city->labelFid)) { return -1; } v10 = labelFrm.getHeight() - wmGenData.tabsOffsetY % 27; v11 = labelFrm.getData() + labelFrm.getWidth() * (wmGenData.tabsOffsetY % 27); v12 = v0; if (v0 < v30 - WM_WINDOW_WIDTH) { v12 = v30 - WM_WINDOW_WIDTH; } blitBufferToBuffer(v11, labelFrm.getWidth(), v10, labelFrm.getWidth(), v12, WM_WINDOW_WIDTH); labelFrm.unlock(); } } v13 = v0 + WM_WINDOW_WIDTH * 27; v32 = v31 + 6; for (int v14 = v31 + 1; v14 < v32; v14++) { if (v14 < wmLabelCount) { city = &(wmAreaInfoList[wmLabelList[v14]]); if (city->labelFid != -1) { if (!labelFrm.lock(city->labelFid)) { return -1; } blitBufferToBuffer(labelFrm.getData(), labelFrm.getWidth(), labelFrm.getHeight(), labelFrm.getWidth(), v13, WM_WINDOW_WIDTH); labelFrm.unlock(); } } v13 += WM_WINDOW_WIDTH * 27; } if (v31 + 6 < wmLabelCount) { city = &(wmAreaInfoList[wmLabelList[v31 + 6]]); if (city->labelFid != -1) { if (!labelFrm.lock(city->labelFid)) { return -1; } blitBufferToBuffer(labelFrm.getData(), labelFrm.getWidth(), labelFrm.getHeight() - 5, labelFrm.getWidth(), v13, WM_WINDOW_WIDTH); labelFrm.unlock(); } } blitBufferToBufferTrans(wmGenData.tabsBorderFrmImage.getData(), 119, 178, 119, wmBkWinBuf + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH); return 0; } // Creates array of cities available as quick destinations. // // 0x4C55D4 static int wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr) { int* quickDestinations = *quickDestinationsPtr; // NOTE: Uninline. wmFreeTabsLabelList(quickDestinationsPtr, quickDestinationsLengthPtr); int capacity = 10; quickDestinations = (int*)internal_malloc(sizeof(*quickDestinations) * capacity); *quickDestinationsPtr = quickDestinations; if (quickDestinations == NULL) { return -1; } int quickDestinationsLength = *quickDestinationsLengthPtr; for (int index = 0; index < wmMaxAreaNum; index++) { if (wmAreaIsKnown(index) && wmAreaInfoList[index].labelFid != -1) { quickDestinationsLength++; *quickDestinationsLengthPtr = quickDestinationsLength; if (capacity <= quickDestinationsLength) { capacity += 10; quickDestinations = (int*)internal_realloc(quickDestinations, sizeof(*quickDestinations) * capacity); if (quickDestinations == NULL) { return -1; } *quickDestinationsPtr = quickDestinations; } quickDestinations[quickDestinationsLength - 1] = index; } } qsort(quickDestinations, quickDestinationsLength, sizeof(*quickDestinations), wmTabsCompareNames); return 0; } // 0x4C56C8 static int wmTabsCompareNames(const void* a1, const void* a2) { int index1 = *(int*)a1; int index2 = *(int*)a2; CityInfo* city1 = &(wmAreaInfoList[index1]); CityInfo* city2 = &(wmAreaInfoList[index2]); return compat_stricmp(city1->name, city2->name); } // NOTE: Inlined. // // 0x4C5710 static int wmFreeTabsLabelList(int** quickDestinationsListPtr, int* quickDestinationsLengthPtr) { if (*quickDestinationsListPtr != NULL) { internal_free(*quickDestinationsListPtr); *quickDestinationsListPtr = NULL; } *quickDestinationsLengthPtr = 0; return 0; } // 0x4C5734 static void wmRefreshInterfaceDial(bool shouldRefreshWindow) { unsigned char* data = artGetFrameData(wmGenData.dialFrm, wmGenData.dialFrmCurrentFrameIndex, 0); blitBufferToBufferTrans(data, wmGenData.dialFrmWidth, wmGenData.dialFrmHeight, wmGenData.dialFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_DIAL_Y + WM_WINDOW_DIAL_X, WM_WINDOW_WIDTH); if (shouldRefreshWindow) { Rect rect; rect.left = WM_WINDOW_DIAL_X; rect.top = WM_WINDOW_DIAL_Y - 1; rect.right = rect.left + wmGenData.dialFrmWidth; rect.bottom = rect.top + wmGenData.dialFrmHeight; windowRefreshRect(wmBkWin, &rect); } } // NOTE: Inlined. // // 0x4C57BC static void wmInterfaceDialSyncTime(bool shouldRefreshWindow) { int gameHour; int frame; gameHour = gameTimeGetHour(); frame = (gameHour / 100 + 12) % artGetFrameCount(wmGenData.dialFrm); if (frame != wmGenData.dialFrmCurrentFrameIndex) { wmGenData.dialFrmCurrentFrameIndex = frame; wmRefreshInterfaceDial(shouldRefreshWindow); } } // 0x4C5804 static int wmAreaFindFirstValidMap(int* mapIdxPtr) { *mapIdxPtr = -1; if (wmGenData.currentAreaId == -1) { return -1; } CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (city->entrancesLength == 0) { return -1; } for (int index = 0; index < city->entrancesLength; index++) { EntranceInfo* entrance = &(city->entrances[index]); if (entrance->state != 0) { *mapIdxPtr = entrance->map; return 0; } } EntranceInfo* entrance = &(city->entrances[0]); entrance->state = 1; *mapIdxPtr = entrance->map; return 0; } // 0x4C58C0 int wmMapMusicStart() { do { int mapIdx = mapGetCurrentMap(); if (mapIdx == -1 || mapIdx >= wmMaxMapNum) { break; } MapInfo* map = &(wmMapInfoList[mapIdx]); if (strlen(map->music) == 0) { break; } if (_gsound_background_play_level_music(map->music, 12) == -1) { break; } return 0; } while (0); debugPrint("\nWorldMap Error: Couldn't start map Music!"); return -1; } // 0x4C5928 int wmSetMapMusic(int mapIdx, const char* name) { if (mapIdx == -1 || mapIdx >= wmMaxMapNum) { return -1; } if (name == NULL) { return -1; } debugPrint("\nwmSetMapMusic: %d, %s", mapIdx, name); MapInfo* map = &(wmMapInfoList[mapIdx]); strncpy(map->music, name, 40); map->music[39] = '\0'; if (mapGetCurrentMap() == mapIdx) { backgroundSoundDelete(); wmMapMusicStart(); } return 0; } // 0x4C59A4 int wmMatchAreaContainingMapIdx(int mapIdx, int* areaIdxPtr) { *areaIdxPtr = 0; for (int areaIdx = 0; areaIdx < wmMaxAreaNum; areaIdx++) { CityInfo* cityInfo = &(wmAreaInfoList[areaIdx]); for (int entranceIdx = 0; entranceIdx < cityInfo->entrancesLength; entranceIdx++) { EntranceInfo* entranceInfo = &(cityInfo->entrances[entranceIdx]); if (entranceInfo->map == mapIdx) { *areaIdxPtr = areaIdx; return 0; } } } return -1; } // 0x4C5A1C int wmTeleportToArea(int areaIdx) { if (!cityIsValid(areaIdx)) { return -1; } wmGenData.currentAreaId = areaIdx; wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; CityInfo* city = &(wmAreaInfoList[areaIdx]); // SFALL: Fix for incorrect positioning after exiting small/medium // locations. // CE: See `wmWorldMapFunc` for explanation. CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); // CE: This function might be called outside |wmWorldmapFunc|, so it's // image might not be locked. bool wasLocked = citySizeDescription->frmImage.isLocked(); if (!wasLocked) { citySizeDescription->frmImage.lock(citySizeDescription->fid); } wmGenData.worldPosX = city->x + citySizeDescription->frmImage.getWidth() / 2 - WM_VIEW_X; wmGenData.worldPosY = city->y + citySizeDescription->frmImage.getHeight() / 2 - WM_VIEW_Y; if (!wasLocked) { citySizeDescription->frmImage.unlock(); } return 0; } void wmFadeOut() { if (!wmFaded) { paletteFadeTo(gPaletteBlack); wmFaded = true; } } void wmFadeIn() { if (wmFaded) { paletteFadeTo(_cmap); wmFaded = false; } } void wmFadeReset() { wmFaded = false; paletteSetEntries(_cmap); } void wmBlinkRndEncounterIcon(bool special) { wmGenData.encounterIconIsVisible = true; // CE: Original code cycles circled bright and non-circled dark icons. int dark; int bright; if (special) { dark = WORLD_MAP_ENCOUNTER_FRM_SPECIAL_DARK; bright = WORLD_MAP_ENCOUNTER_FRM_SPECIAL_BRIGHT; } else { dark = WORLD_MAP_ENCOUNTER_FRM_RANDOM_DARK; bright = WORLD_MAP_ENCOUNTER_FRM_RANDOM_BRIGHT; } for (int index = 0; index < 7; index++) { wmGenData.encounterCursorId = index % 2 == 0 ? dark : bright; if (wmInterfaceRefresh() == -1) { return; } renderPresent(); inputBlockForTocks(200); } wmGenData.encounterIconIsVisible = false; } void wmSetPartyWorldPos(int x, int y) { wmGenData.worldPosX = x; wmGenData.worldPosY = y; } void wmCarSetCurrentArea(int area) { wmGenData.currentCarAreaId = area; } } // namespace fallout