/** * BONES: The Game of the Haunted Mansion * Annotated Decompilation * * Original: Bruce N. Baker, Science Factor Systems (1987-1991) * Decompiled: Ghidra 12.0.1 * Annotated: January 2025 * * This file contains the key game logic functions with meaningful * variable names and explanatory comments based on reverse engineering. */ /*============================================================================ * GLOBAL VARIABLE MEMORY MAP *============================================================================ * These are the key memory addresses used throughout the game. * In the original code, these are accessed via *(int *)ADDRESS *============================================================================*/ // Player Position #define PLAYER_COLUMN 0x5330 // Current column (1-7) #define PLAYER_ROW 0xa03a // Current row (1-7) #define CURRENT_ROOM 0x50d8 // Room number (1-49) #define ROOM_INDEX 0xd282 // Room index (0-48), used for array access #define CURRENT_LEVEL 0xd214 // Mansion level (0-3) // Player Stats #define LIFE_FORCE 0xd1c6 // Player health #define ARMOR_VALUE 0x57e8 // Defense #define ATTACK_FORCE 0xd42a // Attack power #define EXPERIENCE 0xd63c // XP points #define USER_LEVEL 0xd1c4 // Player level #define SCORE 0xd446 // Current score (long, 2 words) #define GOLD_COUNT 0x5332 // Gold collected #define JEWEL_COUNT 0x50c0 // Jewels collected // Combat State #define SKELETONS_SEEN 0xd284 // Number of skeletons in room #define SKELETON_WOUND 0xa016 // Skeleton damage state #define SKELETON_ARMOR 0x4f36 // Skeleton defense #define SKELETON_POWER 0xd200 // Skeleton attack #define SKELETON_FORCE 0x5334 // Skeleton force #define SKELETON_TYPE 0xd200 // SDEX - skeleton type index // Inventory Flags #define SWORD_FLAG 0x50c4 // Has sword #define MACE_FLAG 0xd27e // Has mace #define SHIELD_FLAG 0xd41a // Has shield #define LASER_FLAG 0xd20e // Has laser #define UZI_FLAG 0x50c2 // Has UZI #define GLOBE_FLAG 0xd428 // Has transportal globe (WIN!) #define SPELL_FLAG 0xa02a // Has spell book #define EXPLOSIVES_COUNT 0xd418 // Plastic explosives // Game State #define GAME_TIME 0xd436 // Time counter #define WINS_COUNT 0x4f30 // Games won #define IN_COMBAT 0x5334 // Combat active flag // Room Data Arrays (base addresses, indexed by level * 0x62 + room * 2) #define ROOM_VISITED_BASE 0x54d0 // Visited rooms #define GOLD_AMOUNT_BASE 0x5348 // Gold in each room #define JEWEL_AMOUNT_BASE 0x57fa // Jewels in each room #define SKELETON_BASE 0x4f38 // Skeletons in each room #define ONE_WAY_BASE (-0x2d70) // One-way rooms #define MIST_ROOM_BASE 0x565e // Mist rooms #define SPECIAL_ROOM_BASE (-0x5fc2) // Special rooms #define EW_WALL_BLOCKED 0x50da // East/West walls blocked #define NS_WALL_BLOCKED (-0x2bac) // North/South walls blocked // Item Room Locations (per level, indexed by level * 2) #define STAIRWAY_ROOM (-0x2bd2) // Stairway location #define GLOBE_ROOM (-0x5fe6) // Globe location #define SWORD_ROOM (-0x29ce) // Sword location #define MACE_ROOM (-0x2e34) // Mace location #define SHIELD_ROOM 0x50c8 // Shield location #define POTION_ROOM 0x57f2 // Potion location #define UZI_ROOM (-0x2be4) // UZI location #define LASER_ROOM (-0x2d8c) // Laser location #define BOOST_ROOM (-0x2bc2) // Life boost location #define SPELL_ROOM (-0x2d7a) // Spell book location #define CLIP_ROOM 0x533a // UZI clips location #define EXPLOSIVE_ROOM (-0x5fd0) // Explosives location /*============================================================================ * CONSTANTS *============================================================================*/ #define GRID_SIZE 7 // 7x7 room grid per level #define ROOMS_PER_LEVEL 49 // 7 * 7 = 49 rooms #define NUM_LEVELS 4 // 4 mansion levels #define TOTAL_ROOMS 196 // 49 * 4 = 196 total rooms // Movement directions #define DIR_WEST 1 #define DIR_EAST 2 #define DIR_NORTH 3 #define DIR_SOUTH 4 /*============================================================================ * UTILITY FUNCTIONS *============================================================================*/ /** * rand(max) - Generate random number from 0 to max-1 * Original: FUN_190c_23b0 * * This is the core random number generator used throughout the game. * When called with a mansion seed, it produces reproducible results. */ int rand(int max); /** * getRandomRoom() - Get a valid random room number (1-49) * Original: FUN_1534_10ce * * Keeps generating random numbers until we get a valid room. * Used for placing items to ensure they're in valid locations. */ int getRandomRoom(void) { int room; do { do { room = rand(ROOMS_PER_LEVEL); // rand(49) } while (room < 1); // Must be >= 1 } while (room > ROOMS_PER_LEVEL); // Must be <= 49 return room; } /*============================================================================ * MANSION GENERATION * Original: FUN_1534_0ac0 *============================================================================ * This is the main function that generates a new mansion layout. * Called at the start of each game with a mansion seed number. *============================================================================*/ void generateMansion(void) { int level, room; int goldAmount, jewelAmount, skeletonCount; int randVal; /*------------------------------------------------------------------------ * STEP 1: Generate starting position *------------------------------------------------------------------------ * Player starts at a random position in the 7x7 grid. * Row and column are each 1-6 (avoiding edges initially). *------------------------------------------------------------------------*/ int startRow = rand(6) + 1; // Random 1-6 int startCol = rand(6) + 1; // Random 1-6 // Calculate room number: (row-1) * 7 + column // This gives us room 1-49 from coordinates int startRoom = (startRow - 1) * GRID_SIZE + startCol; // Store starting position playerRow = startRow; playerColumn = startCol; currentRoom = startRoom; roomIndex = startRoom - 1; // 0-indexed for arrays /*------------------------------------------------------------------------ * STEP 2: Clear all room visited flags *------------------------------------------------------------------------ * Initialize the visited array for all 4 levels x 49 rooms *------------------------------------------------------------------------*/ for (level = 0; level < NUM_LEVELS; level++) { for (room = 0; room < ROOMS_PER_LEVEL; room++) { roomVisited[level][room] = 0; } } /*------------------------------------------------------------------------ * STEP 3: Place special items on each level *------------------------------------------------------------------------ * Each level gets one of each special item in a unique room. * The while loops ensure no two items share the same room. *------------------------------------------------------------------------*/ for (level = 0; level < NUM_LEVELS; level++) { // Place stairway (connects to other levels) stairwayRoom[level] = getRandomRoom(); // Initialize other item rooms to stairway (will be changed) globeRoom[level] = stairwayRoom[level]; swordRoom[level] = stairwayRoom[level]; maceRoom[level] = stairwayRoom[level]; shieldRoom[level] = stairwayRoom[level]; potionRoom[level] = stairwayRoom[level]; // Place shield - must not be in stairway while (shieldRoom[level] == stairwayRoom[level]) { shieldRoom[level] = getRandomRoom(); } // Place potion - must not be in stairway or shield room while (potionRoom[level] == stairwayRoom[level] || potionRoom[level] == shieldRoom[level]) { potionRoom[level] = getRandomRoom(); } // Place mace - must not overlap previous items while (maceRoom[level] == stairwayRoom[level] || maceRoom[level] == shieldRoom[level] || maceRoom[level] == potionRoom[level]) { maceRoom[level] = getRandomRoom(); } // Place sword - must not overlap previous items while (swordRoom[level] == stairwayRoom[level] || swordRoom[level] == shieldRoom[level] || swordRoom[level] == potionRoom[level] || swordRoom[level] == maceRoom[level]) { swordRoom[level] = getRandomRoom(); } // Place globe (WIN ITEM!) - must not overlap previous items while (globeRoom[level] == stairwayRoom[level] || globeRoom[level] == shieldRoom[level] || globeRoom[level] == potionRoom[level] || globeRoom[level] == maceRoom[level] || globeRoom[level] == swordRoom[level]) { globeRoom[level] = getRandomRoom(); } // Place UZI - avoid overlap with key items while (uziRoom[level] == stairwayRoom[level] || uziRoom[level] == swordRoom[level] || uziRoom[level] == potionRoom[level] || uziRoom[level] == maceRoom[level]) { uziRoom[level] = getRandomRoom(); } // Place laser - avoid overlap with key items while (laserRoom[level] == stairwayRoom[level] || laserRoom[level] == swordRoom[level] || laserRoom[level] == potionRoom[level] || laserRoom[level] == maceRoom[level] || laserRoom[level] == uziRoom[level]) { laserRoom[level] = getRandomRoom(); } // Place boost room and spell book boostRoom[level] = getRandomRoom(); spellRoom[level] = getRandomRoom(); // Place clips - different from spell room while (clipRoom[level] == spellRoom[level]) { clipRoom[level] = getRandomRoom(); } // Place explosives - different from clips and spell while (explosiveRoom[level] == clipRoom[level] || explosiveRoom[level] == spellRoom[level]) { explosiveRoom[level] = getRandomRoom(); } } /*------------------------------------------------------------------------ * STEP 4: Generate room contents (gold, jewels, skeletons, hazards) *------------------------------------------------------------------------ * This is where the level-scaling difficulty comes from! * Higher levels have more gold/jewels but also more hazards. *------------------------------------------------------------------------*/ for (level = 0; level < NUM_LEVELS; level++) { for (room = 0; room < ROOMS_PER_LEVEL; room++) { /*---------------------------------------------------------------- * GOLD CALCULATION * Formula: rand(level^5 + level^2 + 5) * rand(2) * rand(2) * * Level 0: rand(5) * rand(2) * rand(2) = 0-4 gold * Level 1: rand(8) * rand(2) * rand(2) = 0-7 gold * Level 2: rand(41) * rand(2) * rand(2) = 0-40 gold * Level 3: rand(257) * rand(2) * rand(2) = 0-256 gold! *----------------------------------------------------------------*/ int goldMax = (level * level * level * level * level) + (level * level) + 5; goldAmount = rand(goldMax) * rand(2) * rand(2); roomGold[level][room] = goldAmount; /*---------------------------------------------------------------- * JEWEL CALCULATION * Formula: rand(2) * rand(level^3 + level^2 + 3) * rand(2) * rand(2) * * Level 0: rand(2) * rand(3) * rand(2) * rand(2) = 0-2 jewels * Level 1: rand(2) * rand(5) * rand(2) * rand(2) = 0-4 jewels * Level 2: rand(2) * rand(15) * rand(2) * rand(2) = 0-14 jewels * Level 3: rand(2) * rand(39) * rand(2) * rand(2) = 0-38 jewels! *----------------------------------------------------------------*/ int jewelMax = (level * level * level) + (level * level) + 3; jewelAmount = rand(2) * rand(jewelMax) * rand(2) * rand(2); roomJewels[level][room] = jewelAmount; /*---------------------------------------------------------------- * SKELETON COUNT * Formula: rand(2) * rand(2) * * Results: 0 (75%), 1 (25%) * About 1 in 4 rooms has a skeleton initially *----------------------------------------------------------------*/ skeletonCount = rand(2) * rand(2); roomSkeletons[level][room] = skeletonCount; /*---------------------------------------------------------------- * ONE-WAY ROOM (trap - can enter but exit blocked) * Formula: rand(10) == 1 * * 10% chance of being a one-way room *----------------------------------------------------------------*/ if (rand(10) == 1) { roomOneWay[level][room] = 1; } else { roomOneWay[level][room] = 0; } /*---------------------------------------------------------------- * MIST ROOM (cumulative damage hazard) * Formula: rand(15 - level*2) == 1 * * Level 0: 1/15 chance (6.7%) * Level 1: 1/13 chance (7.7%) * Level 2: 1/11 chance (9.1%) * Level 3: 1/9 chance (11.1%) * * Higher levels = more dangerous mist rooms! *----------------------------------------------------------------*/ int mistChance = 15 - (level * 2); if (rand(mistChance) == 1) { roomMist[level][room] = 1; } else { roomMist[level][room] = 0; } /*---------------------------------------------------------------- * SPECIAL ROOM * Formula: rand(4) == 1 * * 25% chance of being a special room *----------------------------------------------------------------*/ if (rand(4) == 1) { roomSpecial[level][room] = 1; } else { roomSpecial[level][room] = 0; } } } /*------------------------------------------------------------------------ * STEP 5: Generate wall/door configuration *------------------------------------------------------------------------ * Each room can have blocked walls preventing movement. * About 25% of walls are blocked. *------------------------------------------------------------------------*/ for (level = 0; level < NUM_LEVELS; level++) { for (room = 0; room < ROOMS_PER_LEVEL; room++) { // North/South wall blocking - 25% chance if (rand(4) == 0) { wallBlockedNS[level][room] = 1; // Blocked! } else { wallBlockedNS[level][room] = 0; // Open } // East/West wall - just generate random, always store 0 // (Original code calls rand(4) but ignores result) rand(4); wallBlockedEW[level][room] = 0; } } } /*============================================================================ * PLAYER MOVEMENT * Original: FUN_1000_0c26 *============================================================================ * Handles moving the player in one of four directions. * Checks for walls, edges, and triggers room entry events. *============================================================================*/ void movePlayer(int direction) { int canMove = 0; /*------------------------------------------------------------------------ * Check if movement is valid *------------------------------------------------------------------------ * Must satisfy: * 1. Not at grid edge in that direction * 2. Wall not blocked in that direction *------------------------------------------------------------------------*/ // WEST: column > 1, and east/west wall not blocked if (direction == DIR_WEST && playerColumn > 1 && wallBlockedEW[currentLevel][roomIndex] == 0) { canMove = 1; } // EAST: column < 7, and east/west wall not blocked if (direction == DIR_EAST && playerColumn < GRID_SIZE && wallBlockedEW[currentLevel][roomIndex] == 0) { canMove = 1; } // NORTH: row > 1, and north/south wall not blocked if (direction == DIR_NORTH && playerRow > 1 && wallBlockedNS[currentLevel][roomIndex] == 0) { canMove = 1; } // SOUTH: row < 7, and north/south wall not blocked if (direction == DIR_SOUTH && playerRow < GRID_SIZE && wallBlockedNS[currentLevel][roomIndex] == 0) { canMove = 1; } /*------------------------------------------------------------------------ * Execute movement *------------------------------------------------------------------------*/ if (canMove) { // Clear combat flag inCombat = 0; // Check if skeletons block exit (if in combat) if (skeletonsInRoom == 1) { handleCombatExit(); } else { // Random chance of flavor text if (rand(6) == 0) { displayExitMessage(); // "You leave the room", etc. } else { displayQuickExit(); } } // Flag that we moved roomChanged = 1; // Update position based on direction switch (direction) { case DIR_WEST: playerColumn = playerColumn - 1; currentRoom = currentRoom - 1; break; case DIR_EAST: playerColumn = playerColumn + 1; currentRoom = currentRoom + 1; break; case DIR_NORTH: playerRow = playerRow - 1; currentRoom = currentRoom - GRID_SIZE; // -7 break; case DIR_SOUTH: playerRow = playerRow + 1; currentRoom = currentRoom + GRID_SIZE; // +7 break; } // Trigger room entry enterRoom(DIR_NONE, 1); // Update room index for array access roomIndex = currentRoom - 1; } else { // Can't move - show blocked message displayBlockedMessage(); // "The door is shut", etc. } } /*============================================================================ * COORDINATE CONVERSION UTILITIES *============================================================================*/ /** * Convert room number (1-49) to row/column coordinates */ void roomToCoordinates(int room, int *row, int *col) { // Room 1 is at row 1, col 1 // Room 7 is at row 1, col 7 // Room 8 is at row 2, col 1 // etc. *col = 0; *row = 1; int temp = room; for (int i = 1; i < 8; i++) { if (temp == GRID_SIZE || temp - GRID_SIZE < 0) { *col = temp; i = 8; // Exit loop } else { *row = *row + 1; temp = temp - GRID_SIZE; } } } /** * Convert row/column (1-7, 1-7) to room number (1-49) */ int coordinatesToRoom(int row, int col) { return (row - 1) * GRID_SIZE + col; } /*============================================================================ * ROOM DESCRIPTION SELECTION *============================================================================ * Room descriptions are loaded from BONE.D1/D2/D3 files. * Each level has 49 descriptions (one per room). * The description index is stored per room. *============================================================================*/ /** * Get room description string index * Level determines which section of .D file (49 strings per level) * Room index selects which description */ int getRoomDescriptionIndex(int level, int room) { // Each level has its own set of 49 room descriptions // Description is randomly assigned during generation return roomDescription[level][room]; } /*============================================================================ * COMBAT SYSTEM OVERVIEW *============================================================================ * Combat is turn-based: * 1. Player selects attack type (hand, bones, sword, mace, laser, spell, UZI) * 2. Hit/miss calculated based on player level vs skeleton type * 3. Damage applied if hit * 4. Skeleton counterattacks * 5. Repeat until skeleton dead or player flees * * Damage formulas (simplified): * - Hand attack: low damage, always available * - Bones: slightly better than hand * - Sword: good damage * - Mace: excellent damage * - Laser: very good, but depletes over time * - Spell: powerful, but limited casts * - UZI: devastating, uses ammo, can hit groups *============================================================================*/ /*============================================================================ * SKELETON TYPES *============================================================================ * Different skeleton types have special abilities: * * - Warrior: Standard combat skeleton * - Thief: Can steal items and gold, tries to flee * - Electric: MULTIPLIES when hit by laser! * - Mystic: Easily blocks spells * - Warlock: Casts spells, steals Warlock's Shield back * - Phase: Immune to physical attacks * - Vapor: Immune to certain attacks *============================================================================*/ /*============================================================================ * WIN CONDITION *============================================================================ * To win the game: * 1. Find the Transportal Globe (located in globeRoom on one of the levels) * 2. Use the globe to escape the mansion * * When the globe is used: * - "THERES A BLINDING FLASH OF LIGHT" * - "THE GLOBE TRANSPORTS YOU OUT OF THE MANSION" * - Final score is calculated * - High score check *============================================================================*/ /*============================================================================ * SCORING *============================================================================ * Points are earned for: * - Killing skeletons * - Collecting gold * - Collecting jewels * - Finding special items * - Communicating successfully with skeletons * * Points are lost for: * - Drinking poison potions * - Getting gold stolen * - Various hazards * * Achievement titles (from highest to lowest): * 1. Mansion Master * 2. Bone Tycoon * 3. Bone Master * 4. Mansion Maniac * 5. Bone Buster * 6. Bone Hunter * 7. Apprentice * 8. Common Tramp *============================================================================*/ /*============================================================================ * STRING DATABASE FORMAT (.D FILES) *============================================================================ * The game loads strings from BONE.D1, BONE.D2, or BONE.D3 files. * Format: * * * String 1| * String 2| * ... * * * String 1| * ... * end * * Sections: * 1-4: Room descriptions (49 each, one per level) * 5: Blocked exit messages (12) * 6-7: Hit skeleton messages (single/group) * 8-9: Skeleton hits player messages (single/group) * 10: Successful exit messages * 11: Exit blocked by skeleton * 12: Escape past skeleton * 13: Gold found messages * 14: Skeleton activity messages * 15: Jewel found messages * 16-17: Sword hit messages (single/group) * 18-19: Mace hit messages (single/group) * 20-21: Communication success (single/group) * 22: Thief skeleton warnings * 23-24: Communication failed messages * 25-26: Skeleton killed messages (single/all) * 27: Player missed attack * 28: Skeleton missed attack *============================================================================*/ /* End of annotated decompilation */