#ifndef H_FORMAT #define H_FORMAT #include "utils.h" #include "gameflow.h" #include "savegame.h" #define MAX_RESERVED_ENTITIES 128 #define MAX_TRIGGER_COMMANDS 32 #define MAX_MESHES 1024 #define MAX_WEAPONS 4 #define MAX_JOINTS 32 // Lara's height in units / height in meters #define ONE_METER (768.0f / 1.8f) #define TR1_TYPES_START 0 #define TR2_TYPES_START 1000 #define TR3_TYPES_START 2000 #define TR4_TYPES_START 3000 #define TR_TYPES(E) \ E( LARA = TR1_TYPES_START) \ E( LARA_PISTOLS ) \ E( LARA_SHOTGUN ) \ E( LARA_MAGNUMS ) \ E( LARA_UZIS ) \ E( LARA_SPEC ) \ E( ENEMY_DOPPELGANGER ) \ E( ENEMY_WOLF ) \ E( ENEMY_BEAR ) \ E( ENEMY_BAT ) \ E( ENEMY_CROCODILE_LAND ) \ E( ENEMY_CROCODILE_WATER ) \ E( ENEMY_LION_MALE ) \ E( ENEMY_LION_FEMALE ) \ E( ENEMY_PUMA ) \ E( ENEMY_GORILLA ) \ E( ENEMY_RAT_LAND ) \ E( ENEMY_RAT_WATER ) \ E( ENEMY_REX ) \ E( ENEMY_RAPTOR ) \ E( ENEMY_MUTANT_1 ) \ E( ENEMY_MUTANT_2 ) \ E( ENEMY_MUTANT_3 ) \ E( ENEMY_CENTAUR ) \ E( ENEMY_MUMMY ) \ E( UNUSED_1 ) \ E( UNUSED_2 ) \ E( ENEMY_LARSON ) \ E( ENEMY_PIERRE ) \ E( ENEMY_SKATEBOARD ) \ E( ENEMY_SKATEBOY ) \ E( ENEMY_COWBOY ) \ E( ENEMY_MR_T ) \ E( ENEMY_NATLA ) \ E( ENEMY_GIANT_MUTANT ) \ E( TRAP_FLOOR ) \ E( TRAP_SWING_BLADE ) \ E( TRAP_SPIKES ) \ E( TRAP_BOULDER ) \ E( DART ) \ E( TRAP_DART_EMITTER ) \ E( DRAWBRIDGE ) \ E( TRAP_SLAM ) \ E( TRAP_SWORD ) \ E( HAMMER_HANDLE ) \ E( HAMMER_BLOCK ) \ E( LIGHTNING ) \ E( MOVING_OBJECT ) \ E( BLOCK_1 ) \ E( BLOCK_2 ) \ E( BLOCK_3 ) \ E( BLOCK_4 ) \ E( MOVING_BLOCK ) \ E( TRAP_CEILING_1 ) \ E( TRAP_CEILING_2 ) \ E( SWITCH ) \ E( SWITCH_WATER ) \ E( DOOR_1 ) \ E( DOOR_2 ) \ E( DOOR_3 ) \ E( DOOR_4 ) \ E( DOOR_5 ) \ E( DOOR_6 ) \ E( DOOR_7 ) \ E( DOOR_8 ) \ E( TRAP_DOOR_1 ) \ E( TRAP_DOOR_2 ) \ E( UNUSED_3 ) \ E( BRIDGE_1 ) \ E( BRIDGE_2 ) \ E( BRIDGE_3 ) \ E( INV_PASSPORT ) \ E( INV_COMPASS ) \ E( INV_HOME ) \ E( GEARS_1 ) \ E( GEARS_2 ) \ E( GEARS_3 ) \ E( CUT_1 ) \ E( CUT_2 ) \ E( CUT_3 ) \ E( CUT_4 ) \ E( INV_PASSPORT_CLOSED ) \ E( INV_MAP ) \ E( CRYSTAL ) \ E( PISTOLS ) \ E( SHOTGUN ) \ E( MAGNUMS ) \ E( UZIS ) \ E( AMMO_PISTOLS ) \ E( AMMO_SHOTGUN ) \ E( AMMO_MAGNUMS ) \ E( AMMO_UZIS ) \ E( EXPLOSIVE ) \ E( MEDIKIT_SMALL ) \ E( MEDIKIT_BIG ) \ E( INV_DETAIL ) \ E( INV_SOUND ) \ E( INV_CONTROLS ) \ E( INV_GAMMA ) \ E( INV_PISTOLS ) \ E( INV_SHOTGUN ) \ E( INV_MAGNUMS ) \ E( INV_UZIS ) \ E( INV_AMMO_PISTOLS ) \ E( INV_AMMO_SHOTGUN ) \ E( INV_AMMO_MAGNUMS ) \ E( INV_AMMO_UZIS ) \ E( INV_EXPLOSIVE ) \ E( INV_MEDIKIT_SMALL ) \ E( INV_MEDIKIT_BIG ) \ E( PUZZLE_1 ) \ E( PUZZLE_2 ) \ E( PUZZLE_3 ) \ E( PUZZLE_4 ) \ E( INV_PUZZLE_1 ) \ E( INV_PUZZLE_2 ) \ E( INV_PUZZLE_3 ) \ E( INV_PUZZLE_4 ) \ E( PUZZLE_HOLE_1 ) \ E( PUZZLE_HOLE_2 ) \ E( PUZZLE_HOLE_3 ) \ E( PUZZLE_HOLE_4 ) \ E( PUZZLE_DONE_1 ) \ E( PUZZLE_DONE_2 ) \ E( PUZZLE_DONE_3 ) \ E( PUZZLE_DONE_4 ) \ E( LEADBAR ) \ E( INV_LEADBAR ) \ E( MIDAS_HAND ) \ E( KEY_ITEM_1 ) \ E( KEY_ITEM_2 ) \ E( KEY_ITEM_3 ) \ E( KEY_ITEM_4 ) \ E( INV_KEY_ITEM_1 ) \ E( INV_KEY_ITEM_2 ) \ E( INV_KEY_ITEM_3 ) \ E( INV_KEY_ITEM_4 ) \ E( KEY_HOLE_1 ) \ E( KEY_HOLE_2 ) \ E( KEY_HOLE_3 ) \ E( KEY_HOLE_4 ) \ E( UNUSED_4 ) \ E( UNUSED_5 ) \ E( SCION_PICKUP_QUALOPEC ) \ E( SCION_PICKUP_DROP ) \ E( SCION_TARGET ) \ E( SCION_PICKUP_HOLDER ) \ E( SCION_HOLDER ) \ E( UNUSED_6 ) \ E( UNUSED_7 ) \ E( INV_SCION ) \ E( EXPLOSION ) \ E( UNUSED_8 ) \ E( WATER_SPLASH ) \ E( UNUSED_9 ) \ E( BUBBLE ) \ E( UNUSED_10 ) \ E( UNUSED_11 ) \ E( BLOOD ) \ E( UNUSED_12 ) \ E( SMOKE ) \ E( CENTAUR_STATUE ) \ E( CABIN ) \ E( MUTANT_EGG_SMALL ) \ E( RICOCHET ) \ E( SPARKLES ) \ E( MUZZLE_FLASH ) \ E( UNUSED_13 ) \ E( UNUSED_14 ) \ E( VIEW_TARGET ) \ E( WATERFALL ) \ E( NATLA_BULLET ) \ E( MUTANT_BULLET ) \ E( CENTAUR_BULLET ) \ E( UNUSED_16 ) \ E( UNUSED_17 ) \ E( LAVA_PARTICLE ) \ E( TRAP_LAVA_EMITTER ) \ E( FLAME ) \ E( TRAP_FLAME_EMITTER ) \ E( TRAP_LAVA ) \ E( MUTANT_EGG_BIG ) \ E( BOAT ) \ E( EARTHQUAKE ) \ E( UNUSED_18 ) \ E( UNUSED_19 ) \ E( UNUSED_20 ) \ E( UNUSED_21 ) \ E( UNUSED_22 ) \ E( LARA_BRAID ) \ E( GLYPHS ) \ E( TR1_TYPE_MAX ) \ E( _LARA = TR2_TYPES_START ) \ E( _LARA_PISTOLS ) \ E( _LARA_BRAID ) \ E( _LARA_SHOTGUN ) \ E( LARA_AUTOPISTOLS ) \ E( _LARA_UZIS ) \ E( LARA_M16 ) \ E( LARA_GRENADE ) \ E( LARA_HARPOON ) \ E( LARA_FLARE ) \ E( LARA_SNOWMOBILE ) \ E( LARA_BOAT ) \ E( _LARA_SPEC ) \ E( VEHICLE_SNOWMOBILE_RED ) \ E( VEHICLE_BOAT ) \ E( ENEMY_DOG ) \ E( ENEMY_GOON_MASK_1 ) \ E( ENEMY_GOON_MASK_2 ) \ E( ENEMY_GOON_MASK_3 ) \ E( ENEMY_GOON_KNIFE ) \ E( ENEMY_GOON_SHOTGUN ) \ E( ENEMY_RAT ) \ E( ENEMY_DRAGON_FRONT ) \ E( ENEMY_DRAGON_BACK ) \ E( GONDOLA ) \ E( ENEMY_SHARK ) \ E( ENEMY_MORAY_1 ) \ E( ENEMY_MORAY_2 ) \ E( ENEMY_BARACUDA ) \ E( ENEMY_DIVER ) \ E( ENEMY_GUNMAN_1 ) \ E( ENEMY_GUNMAN_2 ) \ E( ENEMY_GOON_STICK_1 ) \ E( ENEMY_GOON_STICK_2 ) \ E( ENEMY_GOON_FLAME ) \ E( UNUSED_23 ) \ E( ENEMY_SPIDER ) \ E( ENEMY_SPIDER_GIANT ) \ E( ENEMY_CROW ) \ E( ENEMY_TIGER ) \ E( ENEMY_MARCO ) \ E( ENEMY_GUARD_SPEAR ) \ E( ENEMY_GUARD_SPEAR_STATUE )\ E( ENEMY_GUARD_SWORD ) \ E( ENEMY_GUARD_SWORD_STATUE ) \ E( ENEMY_YETI ) \ E( ENEMY_BIRD_MONSTER ) \ E( ENEMY_EAGLE ) \ E( ENEMY_MERCENARY_1 ) \ E( ENEMY_MERCENARY_2 ) \ E( ENEMY_MERCENARY_3 ) \ E( VEHICLE_SNOWMOBILE_BLACK ) \ E( ENEMY_MERCENARY_SNOWMOBILE ) \ E( ENEMY_MONK_1 ) \ E( ENEMY_MONK_2 ) \ E( _TRAP_FLOOR ) \ E( UNUSED_24 ) \ E( TRAP_BOARDS ) \ E( TRAP_SWING_BAG ) \ E( _TRAP_SPIKES ) \ E( _TRAP_BOULDER ) \ E( _DART ) \ E( _TRAP_DART_EMITTER ) \ E( _DRAWBRIDGE ) \ E( _TRAP_SLAM ) \ E( ELEVATOR ) \ E( MINISUB ) \ E( _BLOCK_1 ) \ E( _BLOCK_2 ) \ E( _BLOCK_3 ) \ E( _BLOCK_4 ) \ E( LAVA_BOWL ) \ E( WINDOW_1 ) \ E( WINDOW_2 ) \ E( UNUSED_25 ) \ E( UNUSED_26 ) \ E( PROPELLER_PLANE ) \ E( SAW ) \ E( HOOK ) \ E( _TRAP_CEILING_1 ) \ E( TRAP_SPINDLE ) \ E( TRAP_BLADE_WALL ) \ E( TRAP_BLADE_STATUE ) \ E( TRAP_BOULDERS ) \ E( TRAP_ICICLES ) \ E( TRAP_SPIKES_WALL ) \ E( JUMPPAD ) \ E( TRAP_SPIKES_CEILING ) \ E( BELL ) \ E( WAKE_BOAT ) \ E( WAKE_SNOWMOBILE ) \ E( SNOWMOBILE_BELT ) \ E( WHEEL_KNOB ) \ E( SWITCH_BIG ) \ E( PROPELLER_WATER ) \ E( PROPELLER_AIR ) \ E( TRAP_SWING_BOX ) \ E( _CUT_1 ) \ E( _CUT_2 ) \ E( _CUT_3 ) \ E( UI_FRAME ) \ E( ROLLING_DRUMS ) \ E( ZIPLINE_HANDLE ) \ E( SWITCH_BUTTON ) \ E( _SWITCH ) \ E( _SWITCH_WATER ) \ E( _DOOR_1 ) \ E( _DOOR_2 ) \ E( _DOOR_3 ) \ E( _DOOR_4 ) \ E( _DOOR_5 ) \ E( _DOOR_6 ) \ E( _DOOR_7 ) \ E( _DOOR_8 ) \ E( _TRAP_DOOR_1 ) \ E( _TRAP_DOOR_2 ) \ E( TRAP_DOOR_3 ) \ E( _BRIDGE_1 ) \ E( _BRIDGE_2 ) \ E( _BRIDGE_3 ) \ E( _INV_PASSPORT ) \ E( INV_STOPWATCH ) \ E( _INV_HOME ) \ E( _CUT_4 ) \ E( CUT_5 ) \ E( CUT_6 ) \ E( CUT_7 ) \ E( CUT_8 ) \ E( CUT_9 ) \ E( CUT_10 ) \ E( CUT_11 ) \ E( UNUSED_27 ) \ E( UNUSED_28 ) \ E( _INV_PASSPORT_CLOSED ) \ E( _INV_MAP ) \ E( _PISTOLS ) \ E( _SHOTGUN ) \ E( AUTOPISTOLS ) \ E( _UZIS ) \ E( HARPOON ) \ E( M16 ) \ E( GRENADE ) \ E( _AMMO_PISTOLS ) \ E( _AMMO_SHOTGUN ) \ E( AMMO_AUTOPISTOLS ) \ E( _AMMO_UZIS ) \ E( AMMO_HARPOON ) \ E( AMMO_M16 ) \ E( AMMO_GRENADE ) \ E( _MEDIKIT_SMALL ) \ E( _MEDIKIT_BIG ) \ E( FLARES ) \ E( FLARE ) \ E( _INV_DETAIL ) \ E( _INV_SOUND ) \ E( _INV_CONTROLS ) \ E( UNUSED_29 ) \ E( _INV_PISTOLS ) \ E( _INV_SHOTGUN ) \ E( INV_AUTOPISTOLS ) \ E( _INV_UZIS ) \ E( INV_HARPOON ) \ E( INV_M16 ) \ E( INV_GRENADE ) \ E( _INV_AMMO_PISTOLS ) \ E( _INV_AMMO_SHOTGUN ) \ E( INV_AMMO_AUTOPISTOLS ) \ E( _INV_AMMO_UZIS ) \ E( INV_AMMO_HARPOON ) \ E( INV_AMMO_M16 ) \ E( INV_AMMO_GRENADE ) \ E( _INV_MEDIKIT_SMALL ) \ E( _INV_MEDIKIT_BIG ) \ E( INV_FLARES ) \ E( _PUZZLE_1 ) \ E( _PUZZLE_2 ) \ E( _PUZZLE_3 ) \ E( _PUZZLE_4 ) \ E( _INV_PUZZLE_1 ) \ E( _INV_PUZZLE_2 ) \ E( _INV_PUZZLE_3 ) \ E( _INV_PUZZLE_4 ) \ E( _PUZZLE_HOLE_1 ) \ E( _PUZZLE_HOLE_2 ) \ E( _PUZZLE_HOLE_3 ) \ E( _PUZZLE_HOLE_4 ) \ E( _PUZZLE_DONE_1 ) \ E( _PUZZLE_DONE_2 ) \ E( _PUZZLE_DONE_3 ) \ E( _PUZZLE_DONE_4 ) \ E( SECRET_1 ) \ E( SECRET_2 ) \ E( SECRET_3 ) \ E( _KEY_ITEM_1 ) \ E( _KEY_ITEM_2 ) \ E( _KEY_ITEM_3 ) \ E( _KEY_ITEM_4 ) \ E( _INV_KEY_ITEM_1 ) \ E( _INV_KEY_ITEM_2 ) \ E( _INV_KEY_ITEM_3 ) \ E( _INV_KEY_ITEM_4 ) \ E( _KEY_HOLE_1 ) \ E( _KEY_HOLE_2 ) \ E( _KEY_HOLE_3 ) \ E( _KEY_HOLE_4 ) \ E( QUEST_ITEM_1 ) \ E( QUEST_ITEM_2 ) \ E( INV_QUEST_ITEM_1 ) \ E( INV_QUEST_ITEM_2 ) \ E( DRAGON_EXPLOSION_1 ) \ E( DRAGON_EXPLOSION_2 ) \ E( DRAGON_EXPLOSION_3 ) \ E( SOUND_ALARM ) \ E( SOUND_WATER ) \ E( _ENEMY_REX ) \ E( SOUND_BIRD ) \ E( SOUND_CLOCK ) \ E( SOUND_PLACEHOLDER ) \ E( DRAGON_BONES_FRONT ) \ E( DRAGON_BONES_BACK ) \ E( TRAP_EXTRA_FIRE ) \ E( TRAP_MINE ) \ E( UNUSED_30 ) \ E( INV_BACKGROUND ) \ E( GRAY_DISK ) \ E( GONG_STICK ) \ E( GONG ) \ E( DETONATOR ) \ E( HELICOPTER ) \ E( _EXPLOSION ) \ E( _WATER_SPLASH ) \ E( _BUBBLE ) \ E( UNUSED_31 ) \ E( _BLOOD ) \ E( UNUSED_32 ) \ E( FLARE_SPARKLES ) \ E( MUZZLE_GLOW ) \ E( UNUSED_33 ) \ E( _RICOCHET ) \ E( UNUSED_34 ) \ E( _MUZZLE_FLASH ) \ E( MUZZLE_FLASH_M16 ) \ E( UNUSED_35 ) \ E( _VIEW_TARGET ) \ E( _WATERFALL ) \ E( HARPOON_WTF ) \ E( UNUSED_36 ) \ E( UNUSED_37 ) \ E( GRENADE_BULLET ) \ E( HARPOON_BULLET ) \ E( _LAVA_PARTICLE ) \ E( _TRAP_LAVA_EMITTER ) \ E( _FLAME ) \ E( _TRAP_FLAME_EMITTER ) \ E( SKY ) \ E( _GLYPHS ) \ E( ENEMY_MONK_3 ) \ E( SOUND_DOOR_BELL ) \ E( SOUND_ALARM_BELL ) \ E( HELICOPTER_FLYING ) \ E( ENEMY_WINSTON ) \ E( UNUSED_38 ) \ E( LARA_END_PLACE ) \ E( LARA_END_SHOTGUN ) \ E( DRAGON_EXPLSION_EMITTER ) \ E( TR2_TYPE_MAX ) \ E( __LARA = TR3_TYPES_START ) \ E( __LARA_PISTOLS ) \ E( __LARA_BRAID ) \ E( __LARA_SHOTGUN ) \ E( LARA_DESERT_EAGLE ) \ E( __LARA_UZIS ) \ E( LARA_MP5 ) \ E( LARA_ROCKET ) \ E( __LARA_GRENADE ) \ E( __LARA_HARPOON ) \ E( __LARA_FLARE ) \ E( LARA_UPV ) \ E( VEHICLE_UPV ) \ E( UNUSED_TR3_13 ) \ E( VEHICLE_KAYAK ) \ E( __VEHICLE_BOAT ) \ E( VEHICLE_QUADBIKE ) \ E( VEHICLE_MINECART ) \ E( BIG_GUN ) \ E( HYDRO_PROPELLER ) \ E( ENEMY_TRIBESMAN_AXE ) \ E( ENEMY_TRIBESMAN_DART ) \ E( __ENEMY_DOG ) \ E( __ENEMY_RAT ) \ E( KILL_ALL_TRIGGERS ) \ E( ENEMY_WHALE ) \ E( __ENEMY_DIVER ) \ E( __ENEMY_CROW ) \ E( __ENEMY_TIGER ) \ E( ENEMY_VULTURE ) \ E( ASSAULT_TARGET ) \ E( ENEMY_CRAWLER_MUTANT_1 ) \ E( ENEMY_ALLIGATOR ) \ E( UNUSED_TR3_33 ) \ E( ENEMY_COMPSOGNATHUS ) \ E( ENEMY_LIZARD_MAN ) \ E( ENEMY_PUNA ) \ E( ENEMY_MERCENARY ) \ E( ENEMY_RAPTOR_HUNG ) \ E( ENEMY_RX_TECH_GUY_1 ) \ E( ENEMY_RX_TECH_GUY_2 ) \ E( ENEMY_ANTARC_DOG ) \ E( ENEMY_CRAWLER_MUTANT_2 ) \ E( UNUSED_TR3_43 ) \ E( ENEMY_TINNOS_WASP ) \ E( ENEMY_TINNOS_MONSTER ) \ E( ENEMY_BRUTE_MUTANT ) \ E( RESPAWN_TINNOS_WASP ) \ E( RESPAWN_RAPTOR ) \ E( ENEMY_WILLARD_SPIDER ) \ E( ENEMY_RX_TECH_FLAME_GUY ) \ E( ENEMY_LONDON_MERCENARY ) \ E( UNUSED_TR3_52 ) \ E( ENEMY_PUNK ) \ E( UNUSED_TR3_54 ) \ E( UNUSED_TR3_55 ) \ E( ENEMY_LONDON_GUARD ) \ E( ENEMY_SOPHIA ) \ E( CLEANER_ROBOT ) \ E( UNUSED_TR3_59 ) \ E( ENEMY_MILITARY_1 ) \ E( ENEMY_MILITARY_2 ) \ E( PRISONER ) \ E( ENEMY_MILITARY_3 ) \ E( GUN_TURRET ) \ E( ENEMY_DAM_GUARD ) \ E( TRIPWIRE ) \ E( ELECTRIC_WIRE ) \ E( KILLER_TRIPWIRE ) \ E( ENEMY_COBRA ) \ E( ENEMY_SHIVA ) \ E( ENEMY_MONKEY ) \ E( UNUSED_TR3_72 ) \ E( ENEMY_TONY ) \ E( AI_GUARD ) \ E( AI_AMBUSH ) \ E( AI_PATROL_1 ) \ E( AI_MODIFY ) \ E( AI_FOLLOW ) \ E( AI_PATROL_2 ) \ E( AI_PATH ) \ E( AI_CHECK ) \ E( UNUSED_TR3_82 ) \ E( __TRAP_FLOOR ) \ E( UNUSED_TR3_84 ) \ E( UNUSED_TR3_85 ) \ E( TRAP_SWING_THING ) \ E( __TRAP_SPIKES ) \ E( __TRAP_BOULDER ) \ E( TRAP_BOULDER_GIANT ) \ E( __DART ) \ E( __TRAP_DART_EMITTER ) \ E( UNUSED_TR3_92 ) \ E( UNUSED_TR3_93 ) \ E( TRAP_SKELETON ) \ E( __BLOCK_1 ) \ E( __BLOCK_2 ) \ E( __BLOCK_3 ) \ E( __BLOCK_4 ) \ E( UNUSED_TR3_99 ) \ E( UNUSED_TR3_100 ) \ E( __WINDOW_1 ) \ E( __WINDOW_2 ) \ E( UNUSED_TR3_103 ) \ E( UNUSED_TR3_104 ) \ E( UNUSED_TR3_105 ) \ E( AREA51_SWINGER ) \ E( __TRAP_CEILING_1 ) \ E( __TRAP_SPINDLE ) \ E( UNUSED_TR3_109 ) \ E( SUBWAY_TRAIN ) \ E( TRAP_WALL_KNIFE_BLADE ) \ E( UNUSED_TR3_112 ) \ E( __TRAP_ICICLES ) \ E( __TRAP_SPIKES_WALL ) \ E( UNUSED_TR3_115 ) \ E( TRAP_SPIKES_VERT ) \ E( __WHEEL_KNOB ) \ E( __SWITCH_BIG ) \ E( __CUT_1 ) \ E( __CUT_2 ) \ E( __CUT_3 ) \ E( SHIVA_STATUE ) \ E( MONKEY_MEDIPACK ) \ E( MONKEY_KEY ) \ E( __UI_FRAME ) \ E( UNUSED_TR3_126 ) \ E( __ZIPLINE_HANDLE ) \ E( __SWITCH_BUTTON ) \ E( __SWITCH ) \ E( __SWITCH_WATER ) \ E( __DOOR_1 ) \ E( __DOOR_2 ) \ E( __DOOR_3 ) \ E( __DOOR_4 ) \ E( __DOOR_5 ) \ E( __DOOR_6 ) \ E( __DOOR_7 ) \ E( __DOOR_8 ) \ E( __TRAP_DOOR_1 ) \ E( __TRAP_DOOR_2 ) \ E( __TRAP_DOOR_3 ) \ E( __BRIDGE_1 ) \ E( __BRIDGE_2 ) \ E( __BRIDGE_3 ) \ E( __INV_PASSPORT ) \ E( __INV_STOPWATCH ) \ E( __INV_HOME ) \ E( __CUT_4 ) \ E( __CUT_5 ) \ E( __CUT_6 ) \ E( __CUT_7 ) \ E( __CUT_8 ) \ E( __CUT_9 ) \ E( __CUT_10 ) \ E( __CUT_11 ) \ E( CUT_12 ) \ E( UNUSED_TR3_157 ) \ E( __INV_PASSPORT_CLOSED ) \ E( __INV_MAP ) \ E( __PISTOLS ) \ E( __SHOTGUN ) \ E( DESERT_EAGLE ) \ E( __UZIS ) \ E( __HARPOON ) \ E( MP5 ) \ E( ROCKET ) \ E( __GRENADE ) \ E( __AMMO_PISTOLS ) \ E( __AMMO_SHOTGUN ) \ E( AMMO_DESERT_EAGLE ) \ E( __AMMO_UZIS ) \ E( __AMMO_HARPOON ) \ E( AMMO_MP5 ) \ E( AMMO_ROCKET ) \ E( __AMMO_GRENADE ) \ E( __MEDIKIT_SMALL ) \ E( __MEDIKIT_BIG ) \ E( __FLARES ) \ E( __FLARE ) \ E( CRYSTAL_PICKUP ) \ E( __INV_DETAIL ) \ E( __INV_SOUND ) \ E( __INV_CONTROLS ) \ E( INV_GLOBE ) \ E( __INV_PISTOLS ) \ E( __INV_SHOTGUN ) \ E( INV_DESERT_EAGLE ) \ E( __INV_UZIS ) \ E( __INV_HARPOON ) \ E( INV_MP5 ) \ E( INV_ROCKET ) \ E( __INV_GRENADE ) \ E( __INV_AMMO_PISTOLS ) \ E( __INV_AMMO_SHOTGUN ) \ E( INV_AMMO_DESERT_EAGLE ) \ E( __INV_AMMO_UZIS ) \ E( __INV_AMMO_HARPOON ) \ E( INV_AMMO_MP5 ) \ E( INV_AMMO_ROCKET ) \ E( __INV_AMMO_GRENADE ) \ E( __INV_MEDIKIT_SMALL ) \ E( __INV_MEDIKIT_BIG ) \ E( __INV_FLARES ) \ E( INV_CRYSTAL ) \ E( __PUZZLE_1 ) \ E( __PUZZLE_2 ) \ E( __PUZZLE_3 ) \ E( __PUZZLE_4 ) \ E( __INV_PUZZLE_1 ) \ E( __INV_PUZZLE_2 ) \ E( __INV_PUZZLE_3 ) \ E( __INV_PUZZLE_4 ) \ E( __PUZZLE_HOLE_1 ) \ E( __PUZZLE_HOLE_2 ) \ E( __PUZZLE_HOLE_3 ) \ E( __PUZZLE_HOLE_4 ) \ E( __PUZZLE_DONE_1 ) \ E( __PUZZLE_DONE_2 ) \ E( __PUZZLE_DONE_3 ) \ E( __PUZZLE_DONE_4 ) \ E( UNUSER_TR3_121 ) \ E( UNUSER_TR3_122 ) \ E( UNUSER_TR3_123 ) \ E( __KEY_ITEM_1 ) \ E( __KEY_ITEM_2 ) \ E( __KEY_ITEM_3 ) \ E( __KEY_ITEM_4 ) \ E( __INV_KEY_ITEM_1 ) \ E( __INV_KEY_ITEM_2 ) \ E( __INV_KEY_ITEM_3 ) \ E( __INV_KEY_ITEM_4 ) \ E( __KEY_HOLE_1 ) \ E( __KEY_HOLE_2 ) \ E( __KEY_HOLE_3 ) \ E( __KEY_HOLE_4 ) \ E( __QUEST_ITEM_1 ) \ E( __QUEST_ITEM_2 ) \ E( __INV_QUEST_ITEM_1 ) \ E( __INV_QUEST_ITEM_2 ) \ E( STONE_ITEM_1 ) \ E( STONE_ITEM_2 ) \ E( STONE_ITEM_3 ) \ E( STONE_ITEM_4 ) \ E( INV_STONE_ITEM_1 ) \ E( INV_STONE_ITEM_2 ) \ E( INV_STONE_ITEM_3 ) \ E( INV_STONE_ITEM_4 ) \ E( UNUSED_TR3_248 ) \ E( UNUSED_TR3_249 ) \ E( UNUSED_TR3_250 ) \ E( UNUSED_TR3_251 ) \ E( UNUSED_TR3_252 ) \ E( UNUSED_TR3_253 ) \ E( UNUSED_TR3_254 ) \ E( UNUSED_TR3_255 ) \ E( UNUSED_TR3_256 ) \ E( UNUSED_TR3_257 ) \ E( UNUSED_TR3_258 ) \ E( UNUSED_TR3_259 ) \ E( UNUSED_TR3_260 ) \ E( UNUSED_TR3_261 ) \ E( UNUSED_TR3_262 ) \ E( UNUSED_TR3_263 ) \ E( UNUSED_TR3_264 ) \ E( UNUSED_TR3_265 ) \ E( UNUSED_TR3_266 ) \ E( UNUSED_TR3_267 ) \ E( UNUSED_TR3_268 ) \ E( UNUSED_TR3_269 ) \ E( UNUSED_TR3_270 ) \ E( UNUSED_TR3_271 ) \ E( KEYS_SPRITE_1 ) \ E( KEYS_SPRITE_2 ) \ E( UNUSED_TR3_274 ) \ E( UNUSED_TR3_275 ) \ E( STONE_ITEM_PLACE_1 ) \ E( STONE_ITEM_PLACE_2 ) \ E( STONE_ITEM_PLACE_3 ) \ E( STONE_ITEM_PLACE_4 ) \ E( UNUSED_TR3_280 ) \ E( UNUSED_TR3_281 ) \ E( DRAGON_STATUE ) \ E( UNUSED_TR3_283 ) \ E( UNUSED_TR3_284 ) \ E( UNKNOWN_VISIBLE ) \ E( UNUSED_TR3_286 ) \ E( __ENEMY_REX ) \ E( __ENEMY_RAPTOR ) \ E( UNUSED_TR3_289 ) \ E( UNUSED_TR3_290 ) \ E( LASER_SWEEPER ) \ E( ELECTRIC_FILER ) \ E( UNUSED_TR3_293 ) \ E( SHADOW_SPRITE ) \ E( __DETONATOR ) \ E( MISC_SPRITES ) \ E( __BUBBLE ) \ E( UNUSED_TR3_298 ) \ E( __MUZZLE_GLOW ) \ E( __MUZZLE_FLASH ) \ E( MUZZLE_FLASH_MP5 ) \ E( UNUSED_TR3_302 ) \ E( UNUSED_TR3_303 ) \ E( __VIEW_TARGET ) \ E( __WATERFALL ) \ E( __HARPOON_WTF ) \ E( UNUSED_TR3_307 ) \ E( UNUSED_TR3_308 ) \ E( BULLET_ROCKET ) \ E( BULLET_HARPOON ) \ E( BULLET_GRENADE ) \ E( BIG_MISSILE ) \ E( __SMOKE ) \ E( MOVABLE_BOOM ) \ E( LARA_SKIN ) \ E( GLOW_2 ) \ E( UNKNONW_TR3_VISIBLE ) \ E( LIGHT_1 ) \ E( LIGHT_2 ) \ E( LIGHT_3 ) \ E( LIGHT_4 ) \ E( LIGHT_5 ) \ E( LIGHT_6 ) \ E( LIGHT_7 ) \ E( LIGHT_8 ) \ E( LIGHT_9 ) \ E( LIGHT_10 ) \ E( LIGHT_11 ) \ E( UNUSED_TR3_329 ) \ E( PARTICLE_FIRE_1 ) \ E( PARTICLE_FIRE_2 ) \ E( PARTICLE_FIRE_3 ) \ E( PARTICLE_FIRE_4 ) \ E( PARTICLE_SMOKE_1 ) \ E( PARTICLE_SMOKE_2 ) \ E( PARTICLE_SMOKE_3 ) \ E( PARTICLE_SMOKE_4 ) \ E( ENEMY_PIRANHAS ) \ E( ENEMY_FISH ) \ E( UNUSED_TR3_340 ) \ E( UNUSED_TR3_341 ) \ E( UNUSED_TR3_342 ) \ E( UNUSED_TR3_343 ) \ E( UNUSED_TR3_344 ) \ E( UNUSED_TR3_345 ) \ E( UNUSED_TR3_346 ) \ E( ENEMY_BAT_SWARM ) \ E( UNUSED_TR3_348 ) \ E( ANIMATING_1 ) \ E( ANIMATING_2 ) \ E( ANIMATING_3 ) \ E( ANIMATING_4 ) \ E( ANIMATING_5 ) \ E( ANIMATING_6 ) \ E( __SKY ) \ E( __GLYPHS ) \ E( __SOUND_DOOR_BELL ) \ E( UNUSED_TR3_358 ) \ E( UNUSED_TR3_359 ) \ E( __ENEMY_WINSTON ) \ E( ENEMY_WINSTON_CAMO ) \ E( GLYPHS_TIMER ) \ E( UNUSED_TR3_363 ) \ E( UNUSED_TR3_364 ) \ E( __EARTHQUAKE ) \ E( GUN_SHELL_1 ) \ E( GUN_SHELL_2 ) \ E( UNUSED_TR3_368 ) \ E( UNUSED_TR3_369 ) \ E( TINNOS_LIGHT_SHAFT ) \ E( UNUSED_TR3_371 ) \ E( UNUSED_TR3_372 ) \ E( ELECTRIC_SWITCH ) \ E( TR3_TYPE_MAX ) \ E( ___LARA = TR4_TYPES_START ) \ E( ___LARA_PISTOLS ) \ E( ___LARA_UZIS ) \ E( ___LARA_SHOTGUN ) \ E( LARA_CROSSBOW ) \ E( ___LARA_GRENADE ) \ E( LARA_REVOLVER ) \ E( ___LARA_FLARE ) \ E( ___LARA_SKIN ) \ E( LARA_SKIN_JOINTS ) \ E( LARA_SCREAM ) \ E( LARA_CROSSBOW_LASER ) \ E( LARA_REVOLVER_LASER ) \ E( LARA_HOLSTERS ) \ E( LARA_HOLSTERS_PISTOLS ) \ E( LARA_HOLSTERS_UZIS ) \ E( LARA_HOLSTERS_REVOLVER ) \ E( LARA_HEAD1 ) \ E( LARA_HEAD2 ) \ E( LARA_HEAD3 ) \ E( LARA_HEAD4 ) \ E( ACTOR1_HEAD1 ) \ E( ACTOR1_HEAD2 ) \ E( ACTOR2_HEAD1 ) \ E( ACTOR2_HEAD2 ) \ E( LARA_WATER_MESH ) \ E( LARA_PETROL_MESH ) \ E( LARA_DIRT_MESH ) \ E( CROWBAR_ANIM ) \ E( TORCH_ANIM ) \ E( ___LARA_BRAID ) \ E( MOTORBIKE ) \ E( JEEP ) \ E( VEHICLE_EXTRA ) \ E( ENEMY_JEEP ) \ E( SKELETON ) \ E( SKELETON_MIP ) \ E( GUIDE ) \ E( GUIDE_MIP ) \ E( VON_CROY ) \ E( VON_CROY_MIP ) \ E( BADDY_1 ) \ E( BADDY_1_MIP ) \ E( BADDY_2 ) \ E( BADDY_2_MIP ) \ E( SETHA ) \ E( SETHA_MIP ) \ E( MUMMY ) \ E( MUMMY_MIP ) \ E( SPHINX ) \ E( SPHINX_MIP ) \ E( CROCODILE ) \ E( CROCODILE_MIP ) \ E( HORSEMAN ) \ E( HORSEMAN_MIP ) \ E( SCORPION ) \ E( SCORPION_MIP ) \ E( JEAN_YVES ) \ E( JEAN_YVES_MIP ) \ E( TROOPS ) \ E( TROOPS_MIP ) \ E( KNIGHTS_TEMPLAR ) \ E( KNIGHTS_TEMPLAR_MIP ) \ E( MUTANT ) \ E( MUTANT_MIP ) \ E( HORSE ) \ E( HORSE_MIP ) \ E( BABOON_NORMAL ) \ E( BABOON_NORMAL_MIP ) \ E( BABOON_INV ) \ E( BABOON_INV_MIP ) \ E( BABOON_SILENT ) \ E( BABOON_SILENT_MIP ) \ E( WILD_BOAR ) \ E( WILD_BOAR_MIP ) \ E( HARPY ) \ E( HARPY_MIP ) \ E( DEMIGOD1 ) \ E( DEMIGOD1_MIP ) \ E( DEMIGOD2 ) \ E( DEMIGOD2_MIP ) \ E( DEMIGOD3 ) \ E( DEMIGOD3_MIP ) \ E( LITTLE_BEETLE ) \ E( BIG_BEETLE ) \ E( BIG_BEETLE_MIP ) \ E( WRAITH1 ) \ E( WRAITH2 ) \ E( WRAITH3 ) \ E( WRAITH4 ) \ E( BAT ) \ E( DOG ) \ E( DOG_MIP ) \ E( HAMMERHEAD ) \ E( HAMMERHEAD_MIP ) \ E( SAS ) \ E( SAS_MIP ) \ E( SAS_DYING ) \ E( SAS_DYING_MIP ) \ E( SAS_CAPTAIN ) \ E( SAS_CAPTAIN_MIP ) \ E( SAS_DRAG_BLOKE ) \ E( AHMET ) \ E( AHMET_MIP ) \ E( LARA_DOUBLE ) \ E( LARA_DOUBLE_MIP ) \ E( SMALL_SCORPION ) \ E( LOCUST_EMITTER ) \ E( GAME_PIECE1 ) \ E( GAME_PIECE2 ) \ E( GAME_PIECE3 ) \ E( ENEMY_PIECE ) \ E( WHEEL_OF_FORTUNE ) \ E( SCALES ) \ E( DARTS ) \ E( DART_EMITTER ) \ E( HOMING_DART_EMITTER ) \ E( FALLING_CEILING ) \ E( FALLING_BLOCK ) \ E( FALLING_BLOCK2 ) \ E( SMASHABLE_BIKE_WALL ) \ E( SMASHABLE_BIKE_FLOOR ) \ E( TRAPDOOR1 ) \ E( TRAPDOOR2 ) \ E( TRAPDOOR3 ) \ E( FLOOR_TRAPDOOR1 ) \ E( FLOOR_TRAPDOOR2 ) \ E( CEILING_TRAPDOOR1 ) \ E( CEILING_TRAPDOOR2 ) \ E( SCALING_TRAPDOOR ) \ E( ROLLINGBALL ) \ E( SPIKEY_FLOOR ) \ E( TEETH_SPIKES ) \ E( JOBY_SPIKES ) \ E( SLICER_DICER ) \ E( CHAIN ) \ E( PLOUGH ) \ E( STARGATE ) \ E( HAMMER ) \ E( BURNING_FLOOR ) \ E( COG ) \ E( SPIKEBALL ) \ E( ___FLAME ) \ E( ___TRAP_FLAME_EMITTER ) \ E( TRAP_FLAME_EMITTER2 ) \ E( TRAP_FLAME_EMITTER3 ) \ E( ROPE ) \ E( FIREROPE ) \ E( POLEROPE ) \ E( ONEBLOCK_PLATFORM ) \ E( TWOBLOCK_PLATFORM ) \ E( RAISING_BLOCK1 ) \ E( RAISING_BLOCK2 ) \ E( EXPANDING_PLATFORM ) \ E( SQUISHY_BLOCK1 ) \ E( SQUISHY_BLOCK2 ) \ E( ___BLOCK_1 ) \ E( ___BLOCK_2 ) \ E( ___BLOCK_3 ) \ E( ___BLOCK_4 ) \ E( BLOCK_5 ) \ E( ___TRIPWIRE ) \ E( SENTRY_GUN ) \ E( MINE ) \ E( MAPPER ) \ E( OBELISK ) \ E( FLOOR_4BLADE ) \ E( ROOF_4BLADE ) \ E( BIRD_BLADE ) \ E( CATWALK_BLADE ) \ E( MOVING_BLADE ) \ E( PLINTH_BLADE ) \ E( SETH_BLADE ) \ E( LIGHTNING_CONDUCTOR ) \ E( ELEMENT_PUZZLE ) \ E( PUZZLE_ITEM1 ) \ E( PUZZLE_ITEM2 ) \ E( PUZZLE_ITEM3 ) \ E( PUZZLE_ITEM4 ) \ E( PUZZLE_ITEM5 ) \ E( PUZZLE_ITEM6 ) \ E( PUZZLE_ITEM7 ) \ E( PUZZLE_ITEM8 ) \ E( PUZZLE_ITEM9 ) \ E( PUZZLE_ITEM10 ) \ E( PUZZLE_ITEM11 ) \ E( PUZZLE_ITEM12 ) \ E( PUZZLE_ITEM1_COMBO1 ) \ E( PUZZLE_ITEM1_COMBO2 ) \ E( PUZZLE_ITEM2_COMBO1 ) \ E( PUZZLE_ITEM2_COMBO2 ) \ E( PUZZLE_ITEM3_COMBO1 ) \ E( PUZZLE_ITEM3_COMBO2 ) \ E( PUZZLE_ITEM4_COMBO1 ) \ E( PUZZLE_ITEM4_COMBO2 ) \ E( PUZZLE_ITEM5_COMBO1 ) \ E( PUZZLE_ITEM5_COMBO2 ) \ E( PUZZLE_ITEM6_COMBO1 ) \ E( PUZZLE_ITEM6_COMBO2 ) \ E( PUZZLE_ITEM7_COMBO1 ) \ E( PUZZLE_ITEM7_COMBO2 ) \ E( PUZZLE_ITEM8_COMBO1 ) \ E( PUZZLE_ITEM8_COMBO2 ) \ E( KEY_ITEM1 ) \ E( KEY_ITEM2 ) \ E( KEY_ITEM3 ) \ E( KEY_ITEM4 ) \ E( KEY_ITEM5 ) \ E( KEY_ITEM6 ) \ E( KEY_ITEM7 ) \ E( KEY_ITEM8 ) \ E( KEY_ITEM9 ) \ E( KEY_ITEM10 ) \ E( KEY_ITEM11 ) \ E( KEY_ITEM12 ) \ E( KEY_ITEM1_COMBO1 ) \ E( KEY_ITEM1_COMBO2 ) \ E( KEY_ITEM2_COMBO1 ) \ E( KEY_ITEM2_COMBO2 ) \ E( KEY_ITEM3_COMBO1 ) \ E( KEY_ITEM3_COMBO2 ) \ E( KEY_ITEM4_COMBO1 ) \ E( KEY_ITEM4_COMBO2 ) \ E( KEY_ITEM5_COMBO1 ) \ E( KEY_ITEM5_COMBO2 ) \ E( KEY_ITEM6_COMBO1 ) \ E( KEY_ITEM6_COMBO2 ) \ E( KEY_ITEM7_COMBO1 ) \ E( KEY_ITEM7_COMBO2 ) \ E( KEY_ITEM8_COMBO1 ) \ E( KEY_ITEM8_COMBO2 ) \ E( PICKUP_ITEM1 ) \ E( PICKUP_ITEM2 ) \ E( PICKUP_ITEM3 ) \ E( PICKUP_ITEM4 ) \ E( PICKUP_ITEM1_COMBO1 ) \ E( PICKUP_ITEM1_COMBO2 ) \ E( PICKUP_ITEM2_COMBO1 ) \ E( PICKUP_ITEM2_COMBO2 ) \ E( PICKUP_ITEM3_COMBO1 ) \ E( PICKUP_ITEM3_COMBO2 ) \ E( PICKUP_ITEM4_COMBO1 ) \ E( PICKUP_ITEM4_COMBO2 ) \ E( EXAMINE1 ) \ E( EXAMINE2 ) \ E( EXAMINE3 ) \ E( CROWBAR_ITEM ) \ E( BURNING_TORCH_ITEM ) \ E( CLOCKWORK_BEETLE ) \ E( CLOCKWORK_BEETLE_COMBO1 ) \ E( CLOCKWORK_BEETLE_COMBO2 ) \ E( MINE_DETECTOR ) \ E( QUEST_ITEM1 ) \ E( QUEST_ITEM2 ) \ E( QUEST_ITEM3 ) \ E( QUEST_ITEM4 ) \ E( QUEST_ITEM5 ) \ E( QUEST_ITEM6 ) \ E( MAP ) \ E( SECRET_MAP ) \ E( PUZZLE_HOLE1 ) \ E( PUZZLE_HOLE2 ) \ E( PUZZLE_HOLE3 ) \ E( PUZZLE_HOLE4 ) \ E( PUZZLE_HOLE5 ) \ E( PUZZLE_HOLE6 ) \ E( PUZZLE_HOLE7 ) \ E( PUZZLE_HOLE8 ) \ E( PUZZLE_HOLE9 ) \ E( PUZZLE_HOLE10 ) \ E( PUZZLE_HOLE11 ) \ E( PUZZLE_HOLE12 ) \ E( PUZZLE_DONE1 ) \ E( PUZZLE_DONE2 ) \ E( PUZZLE_DONE3 ) \ E( PUZZLE_DONE4 ) \ E( PUZZLE_DONE5 ) \ E( PUZZLE_DONE6 ) \ E( PUZZLE_DONE7 ) \ E( PUZZLE_DONE8 ) \ E( PUZZLE_DONE9 ) \ E( PUZZLE_DONE10 ) \ E( PUZZLE_DONE11 ) \ E( PUZZLE_DONE12 ) \ E( KEY_HOLE1 ) \ E( KEY_HOLE2 ) \ E( KEY_HOLE3 ) \ E( KEY_HOLE4 ) \ E( KEY_HOLE5 ) \ E( KEY_HOLE6 ) \ E( KEY_HOLE7 ) \ E( KEY_HOLE8 ) \ E( KEY_HOLE9 ) \ E( KEY_HOLE10 ) \ E( KEY_HOLE11 ) \ E( KEY_HOLE12 ) \ E( WATERSKIN1_EMPTY ) \ E( WATERSKIN1_1 ) \ E( WATERSKIN1_2 ) \ E( WATERSKIN1_3 ) \ E( WATERSKIN2_EMPTY ) \ E( WATERSKIN2_1 ) \ E( WATERSKIN2_2 ) \ E( WATERSKIN2_3 ) \ E( WATERSKIN2_4 ) \ E( WATERSKIN2_5 ) \ E( SWITCH_TYPE1 ) \ E( SWITCH_TYPE2 ) \ E( SWITCH_TYPE3 ) \ E( SWITCH_TYPE4 ) \ E( SWITCH_TYPE5 ) \ E( SWITCH_TYPE6 ) \ E( SWITCH_TYPE7 ) \ E( SWITCH_TYPE8 ) \ E( UNDERWATER_SWITCH1 ) \ E( UNDERWATER_SWITCH2 ) \ E( TURN_SWITCH ) \ E( COG_SWITCH ) \ E( LEVER_SWITCH ) \ E( JUMP_SWITCH ) \ E( CROWBAR_SWITCH ) \ E( PULLEY ) \ E( DOOR_TYPE1 ) \ E( DOOR_TYPE2 ) \ E( DOOR_TYPE3 ) \ E( DOOR_TYPE4 ) \ E( DOOR_TYPE5 ) \ E( DOOR_TYPE6 ) \ E( DOOR_TYPE7 ) \ E( DOOR_TYPE8 ) \ E( PUSHPULL_DOOR1 ) \ E( PUSHPULL_DOOR2 ) \ E( KICK_DOOR1 ) \ E( KICK_DOOR2 ) \ E( UNDERWATER_DOOR ) \ E( DOUBLE_DOORS ) \ E( BRIDGE_FLAT ) \ E( BRIDGE_TILT1 ) \ E( BRIDGE_TILT2 ) \ E( SARCOPHAGUS ) \ E( SEQUENCE_DOOR1 ) \ E( SEQUENCE_SWITCH1 ) \ E( SEQUENCE_SWITCH2 ) \ E( SEQUENCE_SWITCH3 ) \ E( SARCOPHAGUS_CUT ) \ E( HORUS_STATUE ) \ E( GOD_HEAD ) \ E( SETH_DOOR ) \ E( STATUE_PLINTH ) \ E( PISTOLS_ITEM ) \ E( PISTOLS_AMMO_ITEM ) \ E( UZI_ITEM ) \ E( UZI_AMMO_ITEM ) \ E( SHOTGUN_ITEM ) \ E( SHOTGUN_AMMO1_ITEM ) \ E( SHOTGUN_AMMO2_ITEM ) \ E( CROSSBOW_ITEM ) \ E( CROSSBOW_AMMO1_ITEM ) \ E( CROSSBOW_AMMO2_ITEM ) \ E( CROSSBOW_AMMO3_ITEM ) \ E( CROSSBOW_BOLT ) \ E( GRENADE_GUN_ITEM ) \ E( GRENADE_GUN_AMMO1_ITEM ) \ E( GRENADE_GUN_AMMO2_ITEM ) \ E( GRENADE_GUN_AMMO3_ITEM ) \ E( ___GRENADE ) \ E( REVOLVER_ITEM ) \ E( REVOLVER_AMMO_ITEM ) \ E( ___INV_MEDIKIT_BIG ) \ E( ___INV_MEDIKIT_SMALL ) \ E( LASERSIGHT_ITEM ) \ E( BINOCULARS_ITEM ) \ E( ___FLARES ) \ E( ___INV_FLARES ) \ E( ___INV_PASSPORT ) \ E( ___INV_COMPASS ) \ E( MEMCARD_LOAD_INV_ITEM ) \ E( MEMCARD_SAVE_INV_ITEM ) \ E( PC_LOAD_INV_ITEM ) \ E( PC_SAVE_INV_ITEM ) \ E( SMOKE_EMITTER_WHITE ) \ E( SMOKE_EMITTER_BLACK ) \ E( STEAM_EMITTER ) \ E( ___EARTHQUAKE ) \ E( BUBBLES ) \ E( WATERFALLMIST ) \ E( GUNSHELL ) \ E( SHOTGUNSHELL ) \ E( ___MUZZLE_FLASH ) \ E( BUTTERFLY ) \ E( SPRINKLER ) \ E( RED_LIGHT ) \ E( GREEN_LIGHT ) \ E( BLUE_LIGHT ) \ E( AMBER_LIGHT ) \ E( WHITE_LIGHT ) \ E( BLINKING_LIGHT ) \ E( LENS_FLARE ) \ E( ___AI_GUARD ) \ E( ___AI_AMBUSH ) \ E( AI_PATROL1 ) \ E( ___AI_MODIFY ) \ E( ___AI_FOLLOW ) \ E( AI_PATROL2 ) \ E( AI_X1 ) \ E( AI_X2 ) \ E( LARA_START_POS ) \ E( ___KILL_ALL_TRIGGERS ) \ E( TRIGGER_TRIGGERER ) \ E( SMASH_OBJECT1 ) \ E( SMASH_OBJECT2 ) \ E( SMASH_OBJECT3 ) \ E( SMASH_OBJECT4 ) \ E( SMASH_OBJECT5 ) \ E( SMASH_OBJECT6 ) \ E( SMASH_OBJECT7 ) \ E( SMASH_OBJECT8 ) \ E( MESHSWAP1 ) \ E( MESHSWAP2 ) \ E( MESHSWAP3 ) \ E( DEATH_SLIDE ) \ E( BODY_PART ) \ E( CAMERA_TARGET ) \ E( WATERFALL1 ) \ E( WATERFALL2 ) \ E( WATERFALL3 ) \ E( PLANET_EFFECT ) \ E( ANIMATING1 ) \ E( ANIMATING1_MIP ) \ E( ANIMATING2 ) \ E( ANIMATING2_MIP ) \ E( ANIMATING3 ) \ E( ANIMATING3_MIP ) \ E( ANIMATING4 ) \ E( ANIMATING4_MIP ) \ E( ANIMATING5 ) \ E( ANIMATING5_MIP ) \ E( ANIMATING6 ) \ E( ANIMATING6_MIP ) \ E( ANIMATING7 ) \ E( ANIMATING7_MIP ) \ E( ANIMATING8 ) \ E( ANIMATING8_MIP ) \ E( ANIMATING9 ) \ E( ANIMATING9_MIP ) \ E( ANIMATING10 ) \ E( ANIMATING10_MIP ) \ E( ANIMATING11 ) \ E( ANIMATING11_MIP ) \ E( ANIMATING12 ) \ E( ANIMATING12_MIP ) \ E( ANIMATING13 ) \ E( ANIMATING13_MIP ) \ E( ANIMATING14 ) \ E( ANIMATING14_MIP ) \ E( ANIMATING15 ) \ E( ANIMATING15_MIP ) \ E( ANIMATING16 ) \ E( ANIMATING16_MIP ) \ E( ___SKY ) \ E( SKY_GRAPHICS ) \ E( BINOCULAR_GRAPHICS ) \ E( TARGET_GRAPHICS ) \ E( ___GLYPHS ) \ E( ___MISC_SPRITES ) \ E( MOTOR_BOAT ) \ E( MOTOR_BOAT_LARA ) \ E( RUBBER_BOAT ) \ E( RUBBER_BOAT_LARA ) \ E( MOTORBIKE_LARA ) \ E( FONT_GRAPHICS ) \ E( PARALLEL_BARS ) \ E( PANEL_BORDER ) \ E( PANEL_MIDDLE ) \ E( PANEL_CORNER ) \ E( PANEL_DIAGONAL ) \ E( PANEL_STRIP ) \ E( PANEL_HALF_BORDER1 ) \ E( PANEL_HALF_BORDER2 ) \ E( PANEL_MIDDLE_CORNER ) \ E( TIGHT_ROPE ) \ E( LASER_HEAD ) \ E( LASER_HEAD_BASE ) \ E( LASER_HEAD_TENTACLE ) \ E( HYDRA ) \ E( HYDRA_MISSILE ) \ E( ENEMY_SUB_MARINE ) \ E( ENEMY_SUB_MARINE_MIP ) \ E( SUB_MARINE_MISSILE ) \ E( FROG_MAN ) \ E( FROG_MAN_HARPOON ) \ E( FISH_EMITTER ) \ E( KAYAK ) \ E( KAYAK_LARA ) \ E( CUSTOM_SPRITES ) \ E( BRIDGE_TILT3 ) \ E( BRIDGE_TILT4 ) \ E( BRIDGE_CUSTOM ) \ E( ROBOT_CLEANER ) \ E( ROBOT_STAR_WARS ) \ E( MECH_WARRIOR ) \ E( MECH_WARRIOR_LARA ) \ E( UW_PROPULSOR ) \ E( UW_PROPULSOR_LARA ) \ E( MINE_CART ) \ E( MINE_CART_LARA ) \ E( NEW_SLOT5 ) \ E( NEW_SLOT6 ) \ E( NEW_SLOT7 ) \ E( NEW_SLOT8 ) \ E( NEW_SLOT9 ) \ E( NEW_SLOT10 ) \ E( NEW_SLOT11 ) \ E( NEW_SLOT12 ) \ E( NEW_SLOT13 ) \ E( NEW_SLOT14 ) \ E( NEW_SLOT15 ) \ E( NEW_SLOT16 ) \ E( NEW_SLOT17 ) \ E( NEW_SLOT18 ) \ E( TR4_TYPE_MAX ) namespace TR { struct Level; enum { NO_FLOOR = -127, NO_ROOM = 0xFF, NO_BOX = 0xFFFF, NO_WATER = 0x7FFFFFFF, ACTIVE = 0x1F, }; enum { ANIM_CMD_NONE , ANIM_CMD_OFFSET , ANIM_CMD_JUMP , ANIM_CMD_EMPTY , ANIM_CMD_KILL , ANIM_CMD_SOUND , ANIM_CMD_EFFECT , }; struct Effect { enum Type { NONE = -1, ROTATE_180 , FLOOR_SHAKE , LARA_NORMAL , LARA_BUBBLES , FINISH_LEVEL , EARTHQUAKE , FLOOD , UNK1 , STAIRS2SLOPE , UNK3 , UNK4 , EXPLOSION , LARA_HANDSFREE , FLIP_MAP , DRAW_RIGHTGUN , DRAW_LEFTGUN , SHOT_RIGHTGUN , SHOT_LEFTGUN , MESH_SWAP_1 , MESH_SWAP_2 , MESH_SWAP_3 , INV_ON , INV_OFF , DYN_ON , DYN_OFF , STATUE_FX , RESET_HAIR , BOILER_FX , ASSAULT_RESET , ASSAULT_STOP , ASSAULT_START , ASSAULT_FINISH , FOOTPRINT , // specific TR1_FLICKER = 16, }; }; enum { SND_NO = 2, SND_LANDING = 4, SND_UNHOLSTER = 6, SND_HOLSTER = 7, SND_PISTOLS_SHOT = 8, SND_SHOTGUN_RELOAD = 9, SND_RICOCHET = 10, SND_HIT_BEAR = 16, SND_HIT_WOLF = 20, SND_SCREAM = 30, SND_HIT = 31, SND_WATER_SPLASH = 33, SND_BUBBLE = 37, SND_UZIS_SHOT = 43, SND_MAGNUMS_SHOT = 44, SND_SHOTGUN_SHOT = 45, SND_EMPTY = 48, SND_HIT_UNDERWATER = 50, SND_UNDERWATER = 60, SND_BOULDER = 70, SND_FLOOD = 81, SND_HIT_LION = 85, SND_HIT_RAT = 95, SND_LIGHTNING = 98, SND_ROCK = 99, SND_SWORD = 103, SND_EXPLOSION = 104, SND_INV_SPIN = 108, SND_INV_HOME = 109, SND_INV_CONTROLS = 110, SND_INV_SHOW = 111, SND_INV_HIDE = 112, SND_INV_COMPASS = 113, SND_INV_WEAPON = 114, SND_INV_PAGE = 115, SND_HEALTH = 116, SND_STAIRS2SLOPE = 119, SND_NATLA_SHOT = 123, SND_HIT_SKATEBOY = 132, SND_HIT_MUTANT = 142, SND_STOMP = 147, SND_LAVA = 149, SND_FLAME = 150, SND_DART = 151, SND_TNT = 170, SND_MUTANT_DEATH = 171, SND_SECRET = 173, SND_HELICOPTER = 297, SND_WINSTON_SCARED = 344, SND_WINSTON_WALK = 345, SND_WINSTON_PUSH = 346, SND_WINSTON_TRAY = 347, }; enum { MODEL_LARA = 0, MODEL_PISTOLS = 1, MODEL_SHOTGUN = 2, MODEL_MAGNUMS = 3, MODEL_UZIS = 4, MODEL_LARA_SPEC = 5, }; enum HitType { HIT_DEFAULT, HIT_FALL, HIT_DART, HIT_BLADE, HIT_BOULDER, HIT_SPIKES, HIT_SWORD, HIT_LAVA, HIT_SLAM, HIT_REX, HIT_LIGHTNING, HIT_MIDAS, HIT_GIANT_MUTANT, }; struct Action { enum Type { ACTIVATE , // activate item CAMERA_SWITCH , // switch to camera FLOW , // underwater flow FLIP , // flip map FLIP_ON , // flip on FLIP_OFF , // flip off CAMERA_TARGET , // look at item END , // end level SOUNDTRACK , // play soundtrack EFFECT , // special effect trigger SECRET , // secret found CLEAR_BODIES , // clear all dead bodies FLYBY , // play flyby camera sequence CUTSCENE , // play cutscene }; }; namespace Limits { struct Limit { float dy, dz, ay; ::Box box; bool alignAngle; bool alignHoriz; }; Limit SWITCH = { 0, 376, 30, ::Box(vec3(-200, 0, 312), vec3(200, 0, 512)), true, false }; Limit SWITCH_UNDERWATER = { 0, 100, 80, ::Box(vec3(-1024, -1024, -1024), vec3(1024, 1024, 512)), true, true }; Limit SWITCH_BUTTON = { 0, 292, 30, ::Box(vec3(-200, 0, 250), vec3(200, 0, 512)), true, true }; Limit PICKUP = { 0, -100, 180, ::Box(vec3(-256, -100, -256), vec3(256, 100, 100)), false, true }; Limit PICKUP_UNDERWATER = { -200, -350, 45, ::Box(vec3(-512, -512, -512), vec3(512, 512, 512)), false, true }; Limit KEY_HOLE = { 0, 362, 30, ::Box(vec3(-200, 0, 312), vec3(200, 0, 512)), true, true }; Limit PUZZLE_HOLE = { 0, 327, 30, ::Box(vec3(-200, 0, 312), vec3(200, 0, 512)), true, true }; Limit BLOCK = { 0, -612, 30, ::Box(vec3(-300, 0, -692), vec3(300, 0, -512)), true, false }; Limit MIDAS = { 512, -612, 30, ::Box(vec3(-700, 284, -700), vec3(700, 996, 700)), true, false }; Limit SCION = { 640, -202, 30, ::Box(vec3(-256, 540, -350), vec3(256, 740, -200)), false, false }; Limit SCION_HOLDER = { 640, -202, 10, ::Box(vec3(-256, 206, -862), vec3(256, 306, -200)), true, false }; } union fixed { uint32 value; struct { uint16 L; int16 H; }; operator float() const { return H + L / 65535.0f; } }; struct angle { uint16 value; angle() {} angle(uint16 value) : value(value) {} angle(float value) : value(uint16(value / (PI * 0.5f) * 16384.0f)) {} operator float() const { return value / 16384.0f * PI * 0.5f; }; }; enum TextureType { TEX_TYPE_ROOM, TEX_TYPE_ITEM, TEX_TYPE_OBJECT, TEX_TYPE_SPRITE, }; struct TextureInfo { TextureType type; // determines in which atlas this texture will be stored TextureType dataType; // original texture type from file uint16 index; uint16 clut; uint16 tile; uint32 attribute:15, animated:1; // 0 - opaque, 1 - transparent, 2 - blend additive, animated, triangle short2 texCoord[4]; short2 texCoordAtlas[4]; int16 l, t, r, b; uint16 sub[4], i5; TextureInfo() {} TextureInfo(TextureType type, int16 l, int16 t, int16 r, int16 b, uint8 tx, uint8 ty, uint8 tw, uint8 th) : type(type), dataType(type), attribute(1), l(l), t(t), r(r), b(b) { texCoord[0] = texCoordAtlas[0] = short2( tx, ty ); texCoord[1] = texCoordAtlas[1] = short2( tx + tw - 1, ty + th - 1 ); } short4 getMinMax() const { if (dataType == TEX_TYPE_SPRITE) return short4( texCoord[0].x, texCoord[0].y, texCoord[1].x, texCoord[1].y ); return short4( min(min(texCoord[0].x, texCoord[1].x), texCoord[2].x), min(min(texCoord[0].y, texCoord[1].y), texCoord[2].y), max(max(texCoord[0].x, texCoord[1].x), texCoord[2].x), max(max(texCoord[0].y, texCoord[1].y), texCoord[2].y) ); } short4 getMinMaxAtlas() const { if (dataType == TEX_TYPE_SPRITE) return short4( texCoordAtlas[0].x, texCoordAtlas[0].y, texCoordAtlas[1].x, texCoordAtlas[1].y ); return short4( min(min(texCoordAtlas[0].x, texCoordAtlas[1].x), texCoordAtlas[2].x), min(min(texCoordAtlas[0].y, texCoordAtlas[1].y), texCoordAtlas[2].y), max(max(texCoordAtlas[0].x, texCoordAtlas[1].x), texCoordAtlas[2].x), max(max(texCoordAtlas[0].y, texCoordAtlas[1].y), texCoordAtlas[2].y) ); } }; // used for access from ::cmp func static TextureInfo *gObjectTextures = NULL; static TextureInfo *gSpriteTextures = NULL; static int gObjectTexturesCount; static int gSpriteTexturesCount; struct Face { union { struct { uint16 texture:15, doubleSided:1; }; uint16 value; } flags; union { struct { uint16 additive:1, env:1, shininess:6; }; uint16 value; } effects; short3 normal; uint16 vertices[4]; uint8 triangle:1, colored:1, water:1, flip:5; Face() : triangle(0), colored(0), water(0), flip(0) { flags.value = 0; effects.value = 0; } static int cmp(const Face &a, const Face &b) { int aIndex = a.flags.texture; int bIndex = b.flags.texture; if (aIndex >= gObjectTexturesCount) return 1; if (bIndex >= gObjectTexturesCount) return -1; ASSERT(aIndex < gObjectTexturesCount); ASSERT(bIndex < gObjectTexturesCount); TextureInfo &ta = gObjectTextures[aIndex]; TextureInfo &tb = gObjectTextures[bIndex]; if (ta.tile < tb.tile) return -1; if (ta.tile > tb.tile) return 1; #ifdef SPLIT_BY_CLUT if (ta.clut < tb.clut) return -1; if (ta.clut > tb.clut) return 1; #endif if (aIndex < bIndex) return -1; if (aIndex > bIndex) return 1; return 0; } }; #define FACE4_SIZE (sizeof(uint16) + sizeof(uint16) * 4) // flags + vertices[4] #define FACE3_SIZE (FACE4_SIZE - sizeof(uint16)) struct Room { struct Info { int32 x, z; int32 yBottom, yTop; } info; struct Data { uint32 size; // Number of data words (uint16_t's) int16 vCount; int16 tCount; int16 rCount; int16 fCount; int16 sCount; Face *faces; struct Vertex { short3 pos; int16 unused_lighting; // 0 (bright) .. 0x1FFF (dark) uint16 attributes; Color32 color; } *vertices; struct Sprite { int16 vertexIndex; int16 texture; static int cmp(const Sprite &a, const Sprite &b) { ASSERT(a.texture < gSpriteTexturesCount); ASSERT(b.texture < gSpriteTexturesCount); TextureInfo &ta = gSpriteTextures[a.texture]; TextureInfo &tb = gSpriteTextures[b.texture]; if (ta.tile < tb.tile) return -1; if (ta.tile > tb.tile) return 1; #ifdef SPLIT_BY_CLUT if (ta.clut < tb.clut) return -1; if (ta.clut > tb.clut) return 1; #endif if (a.texture < b.texture) return -1; if (a.texture > b.texture) return 1; return 0; } } *sprites; } data; uint16 portalsCount; uint16 zSectors; uint16 xSectors; uint16 ambient; // 0 (bright) .. 0x1FFF (dark) uint16 ambient2; uint16 lightMode; uint16 lightsCount; uint16 meshesCount; int16 alternateRoom; union { struct { uint16 water:1, :2, sky:1, :1, wind:1, unused:9, visible:1; }; uint16 value; } flags; uint8 waterScheme; uint8 reverbType; uint8 filter; uint8 align; int32 waterLevel[2]; // water level for normal and flipped level state int32 waterLevelSurface; struct DynLight { int32 id; vec4 pos; vec4 color; } dynLights[2]; // 1 is reserved for main light int32 dynLightsCount; struct Portal { uint16 roomIndex; short3 normal; short3 vertices[4]; vec3 getCenter() const { return vec3(float( (int(vertices[0].x) + int(vertices[1].x) + int(vertices[2].x) + int(vertices[3].x)) / 4 ), float( (int(vertices[0].y) + int(vertices[1].y) + int(vertices[2].y) + int(vertices[3].y)) / 4 ), float( (int(vertices[0].z) + int(vertices[1].z) + int(vertices[2].z) + int(vertices[3].z)) / 4 )); } vec3 getSize() const { return vec3(float( abs(int(vertices[0].x) - int(vertices[2].x)) / 2 ), float( abs(int(vertices[0].y) - int(vertices[2].y)) / 2 ), float( abs(int(vertices[0].z) - int(vertices[2].z)) / 2 )); } } *portals; struct Sector { uint32 material; uint16 floorIndex; // Index into FloorData[] uint16 boxIndex; // Index into Boxes[] (-1 if none) uint8 roomBelow; // 255 is none int8 floor; // Absolute height of floor * 256 uint8 roomAbove; // 255 if none int8 ceiling; // Absolute height of ceiling * 256 } *sectors; struct Light { enum Type { DIRECT, POINT, SPOT, SHADOW, FOG }; int32 x, y, z; uint32 radius; int32 intensity; Color24 color; float in, out; float length, cutoff; vec3 dir; uint8 type; } *lights; struct Mesh { int32 x, y, z; angle rotation; uint16 meshID; Color32 color; uint32 meshIndex; // index into static meshes array } *meshes; bool contains(const vec3 &p) const { return p.x >= info.x && p.x <= info.x + xSectors * 1024 && p.z >= info.z && p.z <= info.z + zSectors * 1024 && p.y >= info.yTop && p.y <= info.yBottom; } vec3 getOffset() const { return vec3(float(info.x), 0.0f, float(info.z)); } vec3 getCenter() const { return vec3(info.x + xSectors * 512.0f, (info.yBottom + info.yTop) * 0.5f, info.z + zSectors * 512.0f); } Sector* getSector(int sx, int sz) { if (sz <= 0 || sz >= zSectors - 1) { sz = clamp(sz, 0, zSectors - 1); sx = clamp(sx, 1, xSectors - 2); } else sx = clamp(sx, 0, xSectors - 1); ASSERT(sx >= 0 && sx < xSectors && sz >= 0 && sz < zSectors); return sectors + sx * zSectors + sz; } void addDynLight(int32 id, const vec4 &pos, const vec4 &color, bool priority = false) { DynLight *light = NULL; for (int i = 0; i < dynLightsCount; i++) if (dynLights[i].id == id) { float maxRadius = min(dynLights[i].color.w, color.w); // radius is invSqrt light = &dynLights[i]; light->color.w = maxRadius; break; } // 1 is additional second light, can be overridden if (!light) { if (dynLightsCount < COUNT(dynLights)) light = &dynLights[dynLightsCount++]; else light = &dynLights[1]; } light->id = id; light->pos = pos; light->color = color; if (priority && dynLights[0].id != id) swap(dynLights[0], dynLights[1]); } void removeDynLight(int32 id) { for (int i = 0; i < dynLightsCount; i++) if (dynLights[i].id == id) { if (i == 0) dynLights[0] = dynLights[1]; dynLightsCount--; break; } } int getAmbient(int x, int y, int z, Light **nearLight = NULL) const { if (!lightsCount) { return ambient; } int ambientInv = 0x1FFF - ambient; int maxValue = 0; for (int i = 0; i < lightsCount; i++) { Light &light = lights[i]; if (light.intensity > 8192) continue; int dx = x - light.x; int dy = y - light.y; int dz = z - light.z; int D = (SQR(dx) + SQR(dy) + SQR(dz)) >> 12; int R = SQR(light.radius >> 1) >> 12; int M = max(1, D + R); // TODO TR4 int value = min(0x1FFF, (light.intensity * R) / M + ambientInv); if (maxValue < value) { if (nearLight) { *nearLight = &light; } maxValue = value; } } return 0x1FFF - (maxValue + ambientInv) / 2; } }; union FloorData { uint16 value; union Command { struct { uint16 func:5, tri:3, sub:7, end:1; }; struct { int16 :5, a:5, b:5, :1; } triangle; } cmd; struct { int16 slantX:8, slantZ:8; }; struct { uint16 a:4, b:4, c:4, d:4; }; struct TriggerInfo { uint16 timer:8, once:1, mask:5, :2; } triggerInfo; union TriggerCommand { struct { uint16 args:10, action:5, end:1; }; struct { uint16 timer:8, once:1, speed:5, :2; }; } triggerCmd; enum { NONE , PORTAL , FLOOR , CEILING , TRIGGER , LAVA , CLIMB , FLOOR_NW_SE_SOLID , FLOOR_NE_SW_SOLID , CEILING_NW_SE_SOLID , CEILING_NE_SW_SOLID , FLOOR_NW_SE_PORTAL_SE , FLOOR_NW_SE_PORTAL_NW , FLOOR_NE_SW_PORTAL_SW , FLOOR_NE_SW_PORTAL_NE , CEILING_NW_SE_PORTAL_SE , CEILING_NW_SE_PORTAL_NW , CEILING_NE_SW_PORTAL_SW , CEILING_NE_SW_PORTAL_NE , MONKEY , MINECART_LEFT , MINECART_RIGHT }; }; union Overlap { struct { uint16 boxIndex:15, end:1; }; uint16 value; }; struct Flags { uint16 :8, once:1, active:5, :2; }; // internal mesh structure struct Mesh { struct Vertex { short4 coord; short4 normal; }; short3 center; int16 radius; union { struct { uint16 isStatic:1, reserved:15; }; uint16 value; } flags; int16 vCount; int16 tCount; int16 rCount; int16 fCount; int32 offset; Vertex *vertices; Face *faces; Mesh() : vertices(0), faces(0) {} ~Mesh() { delete[] vertices; delete[] faces; } }; struct Entity { enum ActiveState { asNone, asActive, asInactive }; enum Type { NONE = 0, TR_TYPES(DECL_ENUM) TYPE_MAX = 0xFFFF }; Type type; int16 room; int32 x, y, z; angle rotation; int16 intensity; union { int16 intensity2; int16 OCB; }; union Flags { struct { uint16 state:2, unused:3, smooth:1, :1, invisible:1, once:1, active:5, reverse:1, rendered:1; }; uint16 value; } flags; // not exists in file int32 modelIndex; // index of representation in models (index + 1) or spriteSequences (-(index + 1)) arrays void *controller; // Controller implementation or NULL static Type remap(Version version, Type type) { if (version & VER_TR1) return type; if (version & VER_TR2) type = Type(type + TR2_TYPES_START); if (version & VER_TR3) type = Type(type + TR3_TYPES_START); if (version & VER_TR4) type = Type(type + TR4_TYPES_START); #define REMAP_2(TYPE) case _##TYPE : return TYPE #define REMAP_3(TYPE) case __##TYPE : return TYPE #define REMAP_4(TYPE) case ___##TYPE : return TYPE switch (type) { // TR2 REMAP_2( LARA ); REMAP_2( LARA_PISTOLS ); REMAP_2( LARA_BRAID ); REMAP_2( LARA_SHOTGUN ); REMAP_2( LARA_UZIS ); REMAP_2( LARA_SPEC ); REMAP_2( TRAP_FLOOR ); REMAP_2( TRAP_SPIKES ); REMAP_2( TRAP_BOULDER ); REMAP_2( DART ); REMAP_2( TRAP_DART_EMITTER ); REMAP_2( DRAWBRIDGE ); REMAP_2( TRAP_SLAM ); REMAP_2( BLOCK_1 ); REMAP_2( BLOCK_2 ); REMAP_2( BLOCK_3 ); REMAP_2( BLOCK_4 ); REMAP_2( TRAP_CEILING_1 ); REMAP_2( CUT_1 ); REMAP_2( CUT_2 ); REMAP_2( CUT_3 ); REMAP_2( SWITCH ); REMAP_2( SWITCH_WATER ); REMAP_2( DOOR_1 ); REMAP_2( DOOR_2 ); REMAP_2( DOOR_3 ); REMAP_2( DOOR_4 ); REMAP_2( DOOR_5 ); REMAP_2( DOOR_6 ); REMAP_2( DOOR_7 ); REMAP_2( DOOR_8 ); REMAP_2( TRAP_DOOR_1 ); REMAP_2( TRAP_DOOR_2 ); REMAP_2( BRIDGE_1 ); REMAP_2( BRIDGE_2 ); REMAP_2( BRIDGE_3 ); REMAP_2( INV_PASSPORT ); REMAP_2( INV_HOME ); REMAP_2( CUT_4 ); REMAP_2( INV_PASSPORT_CLOSED ); REMAP_2( INV_MAP ); REMAP_2( PISTOLS ); REMAP_2( SHOTGUN ); REMAP_2( UZIS ); REMAP_2( AMMO_PISTOLS ); REMAP_2( AMMO_SHOTGUN ); REMAP_2( AMMO_UZIS ); REMAP_2( MEDIKIT_SMALL ); REMAP_2( MEDIKIT_BIG ); REMAP_2( INV_DETAIL ); REMAP_2( INV_SOUND ); REMAP_2( INV_CONTROLS ); REMAP_2( INV_PISTOLS ); REMAP_2( INV_SHOTGUN ); REMAP_2( INV_UZIS ); REMAP_2( INV_AMMO_PISTOLS ); REMAP_2( INV_AMMO_SHOTGUN ); REMAP_2( INV_AMMO_UZIS ); REMAP_2( INV_MEDIKIT_SMALL ); REMAP_2( INV_MEDIKIT_BIG ); REMAP_2( PUZZLE_1 ); REMAP_2( PUZZLE_2 ); REMAP_2( PUZZLE_3 ); REMAP_2( PUZZLE_4 ); REMAP_2( INV_PUZZLE_1 ); REMAP_2( INV_PUZZLE_2 ); REMAP_2( INV_PUZZLE_3 ); REMAP_2( INV_PUZZLE_4 ); REMAP_2( PUZZLE_HOLE_1 ); REMAP_2( PUZZLE_HOLE_2 ); REMAP_2( PUZZLE_HOLE_3 ); REMAP_2( PUZZLE_HOLE_4 ); REMAP_2( PUZZLE_DONE_1 ); REMAP_2( PUZZLE_DONE_2 ); REMAP_2( PUZZLE_DONE_3 ); REMAP_2( PUZZLE_DONE_4 ); REMAP_2( KEY_ITEM_1 ); REMAP_2( KEY_ITEM_2 ); REMAP_2( KEY_ITEM_3 ); REMAP_2( KEY_ITEM_4 ); REMAP_2( INV_KEY_ITEM_1 ); REMAP_2( INV_KEY_ITEM_2 ); REMAP_2( INV_KEY_ITEM_3 ); REMAP_2( INV_KEY_ITEM_4 ); REMAP_2( KEY_HOLE_1 ); REMAP_2( KEY_HOLE_2 ); REMAP_2( KEY_HOLE_3 ); REMAP_2( KEY_HOLE_4 ); REMAP_2( ENEMY_REX ); REMAP_2( EXPLOSION ); REMAP_2( WATER_SPLASH ); REMAP_2( BUBBLE ); REMAP_2( BLOOD ); REMAP_2( RICOCHET ); REMAP_2( MUZZLE_FLASH ); REMAP_2( VIEW_TARGET ); REMAP_2( WATERFALL ); REMAP_2( LAVA_PARTICLE ); REMAP_2( TRAP_LAVA_EMITTER ); REMAP_2( FLAME ); REMAP_2( TRAP_FLAME_EMITTER ); REMAP_2( GLYPHS ); // TR3 REMAP_3( LARA ); REMAP_3( LARA_PISTOLS ); REMAP_3( LARA_BRAID ); REMAP_3( LARA_SHOTGUN ); REMAP_3( LARA_UZIS ); REMAP_3( LARA_GRENADE ); REMAP_3( LARA_HARPOON ); REMAP_3( LARA_FLARE ); REMAP_3( VEHICLE_BOAT ); REMAP_3( ENEMY_DOG ); REMAP_3( ENEMY_RAT ); REMAP_3( ENEMY_DIVER ); REMAP_3( ENEMY_CROW ); REMAP_3( ENEMY_TIGER ); REMAP_3( TRAP_FLOOR ); REMAP_3( TRAP_SPIKES ); REMAP_3( TRAP_BOULDER ); REMAP_3( DART ); REMAP_3( TRAP_DART_EMITTER ); REMAP_3( BLOCK_1 ); REMAP_3( BLOCK_2 ); REMAP_3( BLOCK_3 ); REMAP_3( BLOCK_4 ); REMAP_3( WINDOW_1 ); REMAP_3( WINDOW_2 ); REMAP_3( TRAP_CEILING_1 ); REMAP_3( TRAP_SPINDLE ); REMAP_3( TRAP_ICICLES ); REMAP_3( TRAP_SPIKES_WALL ); REMAP_3( WHEEL_KNOB ); REMAP_3( SWITCH_BIG ); REMAP_3( CUT_1 ); REMAP_3( CUT_2 ); REMAP_3( CUT_3 ); REMAP_3( UI_FRAME ); REMAP_3( ZIPLINE_HANDLE ); REMAP_3( SWITCH_BUTTON ); REMAP_3( SWITCH ); REMAP_3( SWITCH_WATER ); REMAP_3( DOOR_1 ); REMAP_3( DOOR_2 ); REMAP_3( DOOR_3 ); REMAP_3( DOOR_4 ); REMAP_3( DOOR_5 ); REMAP_3( DOOR_6 ); REMAP_3( DOOR_7 ); REMAP_3( DOOR_8 ); REMAP_3( TRAP_DOOR_1 ); REMAP_3( TRAP_DOOR_2 ); REMAP_3( TRAP_DOOR_3 ); REMAP_3( BRIDGE_1 ); REMAP_3( BRIDGE_2 ); REMAP_3( BRIDGE_3 ); REMAP_3( INV_PASSPORT ); REMAP_3( INV_STOPWATCH ); REMAP_3( INV_HOME ); REMAP_3( CUT_4 ); REMAP_3( CUT_5 ); REMAP_3( CUT_6 ); REMAP_3( CUT_7 ); REMAP_3( CUT_8 ); REMAP_3( CUT_9 ); REMAP_3( CUT_10 ); REMAP_3( CUT_11 ); REMAP_3( INV_PASSPORT_CLOSED ); REMAP_3( INV_MAP ); REMAP_3( PISTOLS ); REMAP_3( SHOTGUN ); REMAP_3( UZIS ); REMAP_3( HARPOON ); REMAP_3( GRENADE ); REMAP_3( AMMO_PISTOLS ); REMAP_3( AMMO_SHOTGUN ); REMAP_3( AMMO_UZIS ); REMAP_3( AMMO_HARPOON ); REMAP_3( AMMO_GRENADE ); REMAP_3( MEDIKIT_SMALL ); REMAP_3( MEDIKIT_BIG ); REMAP_3( FLARES ); REMAP_3( FLARE ); REMAP_3( INV_DETAIL ); REMAP_3( INV_SOUND ); REMAP_3( INV_CONTROLS ); REMAP_3( INV_PISTOLS ); REMAP_3( INV_SHOTGUN ); REMAP_3( INV_UZIS ); REMAP_3( INV_HARPOON ); REMAP_3( INV_GRENADE ); REMAP_3( INV_AMMO_PISTOLS ); REMAP_3( INV_AMMO_SHOTGUN ); REMAP_3( INV_AMMO_UZIS ); REMAP_3( INV_AMMO_HARPOON ); REMAP_3( INV_AMMO_GRENADE ); REMAP_3( INV_MEDIKIT_SMALL ); REMAP_3( INV_MEDIKIT_BIG ); REMAP_3( INV_FLARES ); REMAP_3( PUZZLE_1 ); REMAP_3( PUZZLE_2 ); REMAP_3( PUZZLE_3 ); REMAP_3( PUZZLE_4 ); REMAP_3( INV_PUZZLE_1 ); REMAP_3( INV_PUZZLE_2 ); REMAP_3( INV_PUZZLE_3 ); REMAP_3( INV_PUZZLE_4 ); REMAP_3( PUZZLE_HOLE_1 ); REMAP_3( PUZZLE_HOLE_2 ); REMAP_3( PUZZLE_HOLE_3 ); REMAP_3( PUZZLE_HOLE_4 ); REMAP_3( PUZZLE_DONE_1 ); REMAP_3( PUZZLE_DONE_2 ); REMAP_3( PUZZLE_DONE_3 ); REMAP_3( PUZZLE_DONE_4 ); REMAP_3( KEY_ITEM_1 ); REMAP_3( KEY_ITEM_2 ); REMAP_3( KEY_ITEM_3 ); REMAP_3( KEY_ITEM_4 ); REMAP_3( INV_KEY_ITEM_1 ); REMAP_3( INV_KEY_ITEM_2 ); REMAP_3( INV_KEY_ITEM_3 ); REMAP_3( INV_KEY_ITEM_4 ); REMAP_3( KEY_HOLE_1 ); REMAP_3( KEY_HOLE_2 ); REMAP_3( KEY_HOLE_3 ); REMAP_3( KEY_HOLE_4 ); REMAP_3( QUEST_ITEM_1 ); REMAP_3( QUEST_ITEM_2 ); REMAP_3( INV_QUEST_ITEM_1 ); REMAP_3( INV_QUEST_ITEM_2 ); REMAP_3( ENEMY_REX ); REMAP_3( ENEMY_RAPTOR ); REMAP_3( DETONATOR ); REMAP_3( BUBBLE ); REMAP_3( MUZZLE_GLOW ); REMAP_3( MUZZLE_FLASH ); REMAP_3( VIEW_TARGET ); REMAP_3( WATERFALL ); REMAP_3( HARPOON_WTF ); REMAP_3( SMOKE ); REMAP_3( SKY ); REMAP_3( GLYPHS ); REMAP_3( SOUND_DOOR_BELL ); REMAP_3( ENEMY_WINSTON ); REMAP_3( EARTHQUAKE ); // TR4 REMAP_4( LARA ); REMAP_4( LARA_PISTOLS ); REMAP_4( LARA_UZIS ); REMAP_4( LARA_SHOTGUN ); REMAP_4( LARA_GRENADE ); REMAP_4( LARA_FLARE ); REMAP_4( LARA_SKIN ); REMAP_4( LARA_BRAID ); REMAP_4( FLAME ); REMAP_4( TRAP_FLAME_EMITTER ); REMAP_4( TRIPWIRE ); REMAP_4( BLOCK_1 ); REMAP_4( BLOCK_2 ); REMAP_4( BLOCK_3 ); REMAP_4( BLOCK_4 ); REMAP_4( GRENADE ); REMAP_4( INV_MEDIKIT_BIG ); REMAP_4( INV_MEDIKIT_SMALL ); REMAP_4( FLARES ); REMAP_4( INV_FLARES ); REMAP_4( INV_PASSPORT ); REMAP_4( INV_COMPASS ); REMAP_4( EARTHQUAKE ); REMAP_4( MUZZLE_FLASH ); REMAP_4( AI_GUARD ); REMAP_4( AI_AMBUSH ); REMAP_4( AI_MODIFY ); REMAP_4( AI_FOLLOW ); REMAP_4( KILL_ALL_TRIGGERS ); REMAP_4( SKY ); //REMAP_4( GLYPHS ); REMAP_4( MISC_SPRITES ); default : return type; } #undef REMAP } bool isEnemy() const { return (type >= ENEMY_DOPPELGANGER && type <= ENEMY_GIANT_MUTANT) || type == SCION_TARGET || (type >= ENEMY_DOG && type <= ENEMY_DRAGON_BACK) || (type >= ENEMY_SHARK && type <= ENEMY_MONK_2); } bool isBigEnemy() const { return type == ENEMY_REX || type == ENEMY_MUTANT_1 || type == ENEMY_CENTAUR; } bool isVehicle() const { return type == VEHICLE_BOAT || type == VEHICLE_SNOWMOBILE_RED || type == VEHICLE_SNOWMOBILE_BLACK; } bool isDoor() const { return type >= DOOR_1 && type <= DOOR_8; } static bool isPuzzleItem(Type type) { return (type >= PUZZLE_1 && type <= PUZZLE_4) || (type >= INV_PUZZLE_1 && type <= INV_PUZZLE_4); } static bool isKeyItem(Type type) { return (type >= KEY_ITEM_1 && type <= KEY_ITEM_4) || (type >= INV_KEY_ITEM_1 && type <= INV_KEY_ITEM_4); } static bool isWeapon(Type type) { return (type >= PISTOLS && type <= AMMO_UZIS) || (type == M16 || type == AMMO_M16) || (type == MP5 || type == AMMO_MP5) || (type == AUTOPISTOLS || type == AMMO_AUTOPISTOLS) || (type == DESERT_EAGLE || type == AMMO_DESERT_EAGLE) || (type == GRENADE || type == AMMO_GRENADE) || (type == ROCKET || type == AMMO_ROCKET) || (type == HARPOON || type == AMMO_HARPOON); } static bool isPickup(Type type) { return isPuzzleItem(type) || isKeyItem(type) || isWeapon(type) || (type == MEDIKIT_SMALL || type == MEDIKIT_BIG) || (type == SCION_PICKUP_QUALOPEC || type == SCION_PICKUP_DROP || type == SCION_PICKUP_HOLDER || type == LEADBAR) || (type == CRYSTAL) || (type >= SECRET_1 && type <= SECRET_3) || (type == FLARES || type == FLARE) || (type >= STONE_ITEM_1 && type <= STONE_ITEM_4); } static bool isInventoryItem(Type type) { return type == INV_PASSPORT || type == INV_PASSPORT_CLOSED || type == INV_MAP || type == INV_COMPASS || type == INV_STOPWATCH || type == INV_HOME || type == INV_DETAIL || type == INV_SOUND || type == INV_CONTROLS || type == INV_GAMMA || type == INV_PISTOLS || type == INV_SHOTGUN || type == INV_MAGNUMS || type == INV_UZIS || type == INV_AMMO_PISTOLS || type == INV_AMMO_SHOTGUN || type == INV_AMMO_MAGNUMS || type == INV_AMMO_UZIS || type == INV_EXPLOSIVE || type == INV_MEDIKIT_SMALL || type == INV_MEDIKIT_BIG || type == INV_PUZZLE_1 || type == INV_PUZZLE_2 || type == INV_PUZZLE_3 || type == INV_PUZZLE_4 || type == INV_KEY_ITEM_1 || type == INV_KEY_ITEM_2 || type == INV_KEY_ITEM_3 || type == INV_KEY_ITEM_4 || type == INV_LEADBAR || type == INV_SCION; } static bool isCrossLevelItem(Type type) { return isPickup(type) && !isPuzzleItem(type) && !isKeyItem(type) && (type != LEADBAR) && !(type >= SECRET_1 && type <= SECRET_3); } bool isPickup() const { return isPickup(type); } bool isActor() const { return (type >= CUT_1 && type <= CUT_4) || (type >= CUT_5 && type <= CUT_11) || (type == CUT_12); } bool isPuzzleHole() const { return type >= PUZZLE_HOLE_1 && type <= PUZZLE_HOLE_4; } bool isBlock() const { return type == BLOCK_1 || type == BLOCK_2 || type == BLOCK_3 || type == BLOCK_4 || type == BLOCK_5; } bool isLara() const { return type == LARA; } bool isSprite() const { return type == EXPLOSION || type == WATER_SPLASH || type == BUBBLE || type == BLOOD || type == SMOKE || type == FLAME || type == RICOCHET || type == SPARKLES || type == LAVA_PARTICLE; } bool castShadow() const { return isLara() || isEnemy() || isVehicle() || isActor() || type == DART || type == TRAP_SWORD || type == ENEMY_WINSTON || type == ENEMY_WINSTON_CAMO; } void getAxis(int &dx, int &dz) { switch (rotation.value / 0x4000) { case 0 : dx = 0; dz = 1; break; case 1 : dx = 1; dz = 0; break; case 2 : dx = 0; dz = -1; break; case 3 : dx = -1; dz = 0; break; default : dx = 0; dz = 0; break; } } static Type getItemForHole(Type hole) { switch (hole) { case PUZZLE_HOLE_1 : return PUZZLE_1; case PUZZLE_HOLE_2 : return PUZZLE_2; case PUZZLE_HOLE_3 : return PUZZLE_3; case PUZZLE_HOLE_4 : return PUZZLE_4; case KEY_HOLE_1 : return KEY_ITEM_1; case KEY_HOLE_2 : return KEY_ITEM_2; case KEY_HOLE_3 : return KEY_ITEM_3; case KEY_HOLE_4 : return KEY_ITEM_4; case MIDAS_HAND : return LEADBAR; default : return LARA; } } static void fixOpaque(Type type, bool &opaque) { // to boost performance on mobile devices if (type >= LARA && type <= ENEMY_GIANT_MUTANT && type != ENEMY_REX && type != ENEMY_RAPTOR && type != ENEMY_MUTANT_1 && type != ENEMY_MUTANT_2 && type != ENEMY_MUTANT_3 && type != ENEMY_CENTAUR && type != ENEMY_GIANT_MUTANT && type != ENEMY_MUMMY && type != ENEMY_NATLA) opaque = true; if (type == SWITCH || type == SWITCH_WATER || type == CUT_1) opaque = true; if ((type >= PUZZLE_HOLE_1 && type <= PUZZLE_HOLE_4) || type == LIGHTNING) opaque = false; } }; #define INV_ITEM_PAIR(TYPE) { Entity::TYPE, Entity::INV_##TYPE } static struct { Entity::Type src, dst; } ITEM_TO_INV[] = { // weapon INV_ITEM_PAIR(PISTOLS), INV_ITEM_PAIR(SHOTGUN), INV_ITEM_PAIR(MAGNUMS), INV_ITEM_PAIR(UZIS), INV_ITEM_PAIR(AUTOPISTOLS), INV_ITEM_PAIR(HARPOON), INV_ITEM_PAIR(M16), INV_ITEM_PAIR(GRENADE), // ammo INV_ITEM_PAIR(AMMO_PISTOLS), INV_ITEM_PAIR(AMMO_SHOTGUN), INV_ITEM_PAIR(AMMO_MAGNUMS), INV_ITEM_PAIR(AMMO_UZIS), INV_ITEM_PAIR(AMMO_AUTOPISTOLS), INV_ITEM_PAIR(AMMO_HARPOON), INV_ITEM_PAIR(AMMO_M16), INV_ITEM_PAIR(AMMO_GRENADE), // items INV_ITEM_PAIR(MEDIKIT_BIG), INV_ITEM_PAIR(MEDIKIT_SMALL), INV_ITEM_PAIR(FLARES), // key items INV_ITEM_PAIR(KEY_ITEM_1), INV_ITEM_PAIR(KEY_ITEM_2), INV_ITEM_PAIR(KEY_ITEM_3), INV_ITEM_PAIR(KEY_ITEM_4), // puzzle items INV_ITEM_PAIR(PUZZLE_1), INV_ITEM_PAIR(PUZZLE_2), INV_ITEM_PAIR(PUZZLE_3), INV_ITEM_PAIR(PUZZLE_4), // other items INV_ITEM_PAIR(LEADBAR), INV_ITEM_PAIR(QUEST_ITEM_1), INV_ITEM_PAIR(QUEST_ITEM_2), { Entity::SCION_PICKUP_DROP, Entity::INV_SCION }, { Entity::SCION_PICKUP_QUALOPEC, Entity::INV_SCION }, }; #undef INV_ITEM_PAIR struct Animation { uint32 frameOffset; uint8 frameRate; uint8 frameSize; uint16 state; fixed speed; fixed accel; fixed speedLateral; fixed accelLateral; uint16 frameStart; uint16 frameEnd; uint16 nextAnimation; uint16 nextFrame; uint16 scCount; uint16 scOffset; uint16 acCount; uint16 animCommand; }; struct AnimState { uint16 state; uint16 rangesCount; uint16 rangesOffset; }; struct AnimRange { int16 low; // Lowest frame that uses this range int16 high; // Highest frame that uses this range int16 nextAnimation; // Animation to dispatch to int16 nextFrame; // Frame offset to dispatch to }; struct MinMax { int16 minX, maxX, minY, maxY, minZ, maxZ; vec3 min() const { return vec3((float)minX, (float)minY, (float)minZ); } vec3 max() const { return vec3((float)maxX, (float)maxY, (float)maxZ); } }; struct AnimFrame { MinMax box; short3 pos; uint16 angles[1]; // angle frames in YXZ order, first angle must be skipped in TR1 #define ANGLE_SCALE (PI2 / 1024.0f) vec3 unpack(uint16 a, uint16 b) { return vec3(float((a & 0x3FF0) >> 4), float( ((a & 0x000F) << 6) | ((b & 0xFC00) >> 10)), float(b & 0x03FF)) * ANGLE_SCALE; } vec3 getAngle(Version version, int joint) { int index = 0; if (version & VER_TR1) { index = joint * 2 + 1; uint16 b = angles[index++]; uint16 a = angles[index++]; if (version & VER_SAT) { swap(a, b); } return unpack(a, b); } else { // TODO: optimize this !!! for (int i = 0; i < joint; i++) { if (!(angles[index++] & 0xC000)) { index++; } } uint16 a = angles[index++]; float rot; if (((version & VER_VERSION) >= VER_TR4)) { rot = float(a & 0x0FFF) * (PI2 / 4096.0f); } else { rot = float(a & 0x03FF) * (PI2 / 1024.0f); } switch (a & 0xC000) { case 0x4000 : return vec3(rot, 0, 0); case 0x8000 : return vec3(0, rot, 0); case 0xC000 : return vec3(0, 0, rot); default : return unpack(a, angles[index++]); } } return vec3(0); } #undef ANGLE_SCALE }; struct AnimTexture { uint16 count; uint16 *textures; }; struct Node { uint32 flags; int32 x, y, z; }; struct Model { Entity::Type type; uint16 index; uint16 mCount; uint16 mStart; uint32 node; uint32 frame; uint16 animation; uint16 align; }; struct StaticMesh { uint32 id; uint16 mesh; MinMax vbox; MinMax cbox; uint16 flags; void getBox(bool collision, angle rotation, ::Box &box) { int k = rotation.value / 0x4000; MinMax &m = collision ? cbox : vbox; ASSERT(m.minX <= m.maxX && m.minY <= m.maxY && m.minZ <= m.maxZ); box = ::Box(m.min(), m.max()); box.rotate90(k); ASSERT(box.min.x <= box.max.x && box.min.y <= box.max.y && box.min.z <= box.max.z); } }; struct SpriteSequence { Entity::Type type; uint16 unused; int16 sCount; int16 sStart; uint8 transp; }; struct Camera { int32 x, y, z; union { int16 room; // for camera int16 speed; // for sink (underwater current) }; union { struct { uint16 :8, once:1, :5, :2; }; uint16 boxIndex; } flags; }; struct FlybyCamera { int32 x, y, z; int32 dx, dy, dz; uint8 sequence; uint8 index; uint16 fov; int16 roll; uint16 timer; uint16 speed; uint16 flags; uint32 room; }; struct CameraFrame { short3 target; short3 pos; int16 fov; int16 roll; }; struct AIObject { uint16 type; uint16 room; int32 x, y, z; uint16 OCB; uint16 flags; int32 angle; }; struct SoundSource { int32 x, y, z; // absolute position of sound source (world coordinates) uint16 id; // internal sample index uint16 flags; // 0x40, 0x80, or 0xC0 }; struct Box { union { struct { int32 minZ, maxZ; // Horizontal dimensions in global units int32 minX, maxX; }; int32 sides[4]; }; int16 floor; // Height value in global units union { struct { uint16 index:14, block:1, blockable:1; // Index into Overlaps[]. }; uint16 value; } overlap; bool contains(int32 x, int32 z) const { return z >= minZ && z <= maxZ && x >= minX && x <= maxX; } void expand(int32 value) { minZ -= value; minX -= value; maxZ += value; maxX += value; } void clip(const Box &box) { minZ = max(minZ, box.minZ); minX = max(minX, box.minX); maxZ = min(maxZ, box.maxZ); maxX = min(maxX, box.maxX); } }; struct Zone { uint16 *ground1; uint16 *ground2; uint16 *ground3; uint16 *ground4; uint16 *fly; }; struct SoundInfo { float volume; float chance; float range; float pitch; uint16 index; union { struct { uint16 mode:2, count:4, unused:6, camera:1, pitch:1, gain:1, :1; }; uint16 value; } flags; }; struct Location { int16 room; uint16 box; vec3 pos; }; struct Level { Version version; LevelID id; int32 tilesCount; uint16 roomsCount; Room *rooms; int32 floorsCount; FloorData *floors; int16 meshesCount; Mesh meshes[MAX_MESHES]; int32 meshDataSize; uint16 *meshData; int32 meshOffsetsCount; int32 *meshOffsets; int32 animsCount; Animation *anims; int32 statesCount; AnimState *states; int32 rangesCount; AnimRange *ranges; int32 commandsCount; int16 *commands; int32 nodesDataSize; uint32 *nodesData; int32 frameDataSize; uint16 *frameData; int32 modelsCount; Model *models; int32 staticMeshesCount; StaticMesh *staticMeshes; int32 objectTexturesBaseCount; int32 objectTexturesCount; TextureInfo *objectTextures; int32 objectTexturesDataSize; uint8 *objectTexturesData; int32 roomTexturesCount; TextureInfo *roomTextures; int32 roomTexturesDataSize; uint8 *roomTexturesData; int32 itemTexturesCount; TextureInfo *itemTextures; int32 itemTexturesBaseCount; int32 itemTexturesDataSize; uint8 *itemTexturesData; int32 spriteTexturesCount; TextureInfo *spriteTextures; int32 spriteSequencesCount; SpriteSequence *spriteSequences; int32 spriteTexturesDataSize; uint8 *spriteTexturesData; int32 camerasCount; Camera *cameras; int32 flybyCamerasCount; FlybyCamera *flybyCameras; int32 soundSourcesCount; SoundSource *soundSources; int32 boxesCount; Box *boxes; int32 overlapsCount; Overlap *overlaps; Zone zones[2]; // default and alternative int16 animTexturesCount; AnimTexture *animTextures; int32 entitiesBaseCount; int32 entitiesCount; Entity *entities; uint8 lightmap[32 * 256]; // color indices table for 32 shades Color24 *palette; Color32 *palette32; uint16 kanjiSprite; int32 clutsCount; CLUT *cluts; Tile4 *tiles4; Tile8 *tiles8; Tile16 *tiles16; Tile32 *tiles32; Tile32 *tilesMisc; uint16 cameraFramesCount; CameraFrame *cameraFrames; AIObject *AIObjects; uint32 AIObjectsCount; uint16 demoDataSize; uint8 *demoData; int16 *soundsMap; int32 soundsInfoCount; SoundInfo *soundsInfo; int32 soundsCount; int32 soundDataSize; uint8 *soundData; int32 soundOffsetsCount; uint32 *soundOffsets; uint32 *soundSize; Color32 skyColor; // common struct Trigger { enum Type { ACTIVATE , PAD , SWITCH , KEY , PICKUP , HEAVY , ANTIPAD , COMBAT , DUMMY , }; }; struct FloorInfo { // TODO reduse type sizes float roomFloor, roomCeiling; int roomNext, roomBelow, roomAbove; float floor, ceiling; int slantX, slantZ; int floorIndex; int boxIndex; int lava; int climb; int trigCmdCount; Trigger::Type trigger; FloorData::TriggerInfo trigInfo; FloorData::TriggerCommand trigCmd[MAX_TRIGGER_COMMANDS]; vec3 getNormal() { return vec3((float)-slantX, -4.0f, (float)-slantZ).normal(); } vec3 getSlant(const vec3 &dir) { // project floor normal into plane(dir, up) vec3 r = vec3(dir.z, 0.0f, -dir.x); // up(0, 1, 0).cross(dir) vec3 n = getNormal(); n = n - r * r.dot(n); // project dir into plane(dir, n) return n.cross(dir.cross(n)).normal(); } }; SaveState state; int cutEntity; mat4 cutMatrix; bool isDemoLevel; bool simpleItems; struct Extra { static int getWeaponIndex(Entity::Type type) { #if MAX_WEAPONS != 4 #error update this func first #endif switch (type) { // pistols case Entity::LARA_PISTOLS : case Entity::INV_PISTOLS : case Entity::INV_AMMO_PISTOLS : case Entity::AMMO_PISTOLS : case Entity::PISTOLS : return 0; // shotgun case Entity::LARA_SHOTGUN : case Entity::INV_SHOTGUN : case Entity::INV_AMMO_SHOTGUN : case Entity::AMMO_SHOTGUN : case Entity::SHOTGUN : return 1; // magnums case Entity::LARA_MAGNUMS : case Entity::INV_MAGNUMS : case Entity::INV_AMMO_MAGNUMS : case Entity::AMMO_MAGNUMS : case Entity::MAGNUMS : return 2; // uzis case Entity::LARA_UZIS : case Entity::INV_UZIS : case Entity::INV_AMMO_UZIS : case Entity::AMMO_UZIS : case Entity::UZIS : return 3; default : ASSERT(false); } return 0; } int16 muzzleFlash; int16 puzzleDone[4]; int16 braid; int16 laraSpec; int16 laraSkin; int16 laraJoints; int16 meshSwap[3]; int16 sky; int16 smoke; int16 waterSplash; int16 glyphs; struct Weapon { int16 items[MAX_WEAPONS]; int16& operator[] (Entity::Type type) { return items[getWeaponIndex(type)]; }; } weapons; struct Inventory { int16 passport; int16 passport_closed; int16 map; int16 compass; int16 stopwatch; int16 home; int16 detail; int16 sound; int16 controls; int16 gamma; int16 explosive; struct Weapon { int16 items[MAX_WEAPONS]; int16& operator[] (Entity::Type type) { return items[getWeaponIndex(type)]; }; } weapons; struct Ammo { int16 items[MAX_WEAPONS]; int16& operator[] (Entity::Type type) { return items[getWeaponIndex(type)]; }; } ammo; int16 medikit[2]; int16 puzzle[4]; int16 key[4]; int16 leadbar; int16 scion; } inv; } extra; uint32 tsubCount; uint8 *tsub; Level(Stream &stream) { memset(this, 0, sizeof(*this)); version = VER_UNKNOWN; cutEntity = -1; meshesCount = 0; uint32 magic; #define MAGIC_TR1_PC 0x00000020 #define MAGIC_TR1_PSX 0x56414270 #define MAGIC_TR1_SAT 0x4D4F4F52 #define MAGIC_TR2_PC 0x0000002D #define MAGIC_TR3_PC1 0xFF080038 #define MAGIC_TR3_PC2 0xFF180038 #define MAGIC_TR3_PC3 0xFF180034 #define MAGIC_TR3_PSX 0xFFFFFFC8 #define MAGIC_TR4_PC 0x00345254 id = TR::getLevelID(stream.size, stream.name, version, isDemoLevel); if (version == VER_UNKNOWN || version == VER_TR1_PC || version == VER_TR1_PSX || version == VER_TR1_SAT || version == VER_TR3_PSX) { stream.read(magic); if (magic != MAGIC_TR1_PC && magic != MAGIC_TR1_SAT && magic != MAGIC_TR2_PC && magic != MAGIC_TR3_PC1 && magic != MAGIC_TR3_PC2 && magic != MAGIC_TR3_PC3 && magic != MAGIC_TR3_PSX && magic != MAGIC_TR4_PC) { stream.read(magic); } switch (magic) { case MAGIC_TR1_PC : version = VER_TR1_PC; break; case MAGIC_TR1_PSX : version = VER_TR1_PSX; break; case MAGIC_TR1_SAT : version = VER_TR1_SAT; break; case MAGIC_TR2_PC : version = VER_TR2_PC; break; case MAGIC_TR3_PC1 : case MAGIC_TR3_PC2 : case MAGIC_TR3_PC3 : version = VER_TR3_PC; break; case MAGIC_TR3_PSX : version = VER_TR3_PSX; break; case MAGIC_TR4_PC : version = VER_TR4_PC; break; default : ; } } if (version == VER_UNKNOWN) { LOG("unsupported level format\n"); ASSERT(false); return; } #ifdef _GAPI_SW ASSERT((version & VER_TR1_PC) == VER_TR1_PC); if ((version & VER_TR1_PC) != VER_TR1_PC) { return; } #endif #ifdef _GAPI_GU ASSERT((version & VER_TR1_PSX) == VER_TR1_PSX); if ((version & VER_TR1_PSX) != VER_TR1_PSX) { return; } #endif switch (version) { case VER_TR1_PC : loadTR1_PC (stream); break; case VER_TR1_PSX : loadTR1_PSX (stream); break; case VER_TR1_SAT : loadTR1_SAT (stream); break; case VER_TR2_PC : loadTR2_PC (stream); break; case VER_TR2_PSX : loadTR2_PSX (stream); break; case VER_TR3_PC : loadTR3_PC (stream); break; case VER_TR3_PSX : loadTR3_PSX (stream); break; #ifdef USE_INFLATE case VER_TR4_PC : loadTR4_PC (stream); break; #endif case VER_TR4_PSX : loadTR4_PSX (stream); break; case VER_TR4_SDC : loadTR4_SDC (stream); break; case VER_TR5_PC : loadTR5_PC (stream); break; case VER_TR5_PSX : loadTR5_PSX (stream); break; case VER_TR5_SDC : loadTR5_SDC (stream); break; default : ASSERT(false); } prepare(); } ~Level() { // rooms for (int i = 0; i < roomsCount; i++) { Room &r = rooms[i]; delete[] r.data.vertices; delete[] r.data.faces; delete[] r.data.sprites; delete[] r.portals; delete[] r.sectors; delete[] r.lights; delete[] r.meshes; } delete[] rooms; delete[] floors; delete[] meshOffsets; delete[] anims; delete[] states; delete[] ranges; delete[] commands; delete[] nodesData; delete[] frameData; delete[] models; delete[] staticMeshes; delete[] objectTextures; delete[] objectTexturesData; delete[] roomTextures; delete[] roomTexturesData; delete[] itemTextures; delete[] itemTexturesData; delete[] spriteTextures; delete[] spriteSequences; delete[] spriteTexturesData; delete[] cameras; delete[] flybyCameras; delete[] soundSources; delete[] boxes; delete[] overlaps; for (int i = 0; i < 2; i++) { delete[] zones[i].ground1; delete[] zones[i].ground2; delete[] zones[i].ground3; delete[] zones[i].ground4; delete[] zones[i].fly; } for (int i = 0; i < animTexturesCount; i++) delete[] animTextures[i].textures; delete[] animTextures; delete[] entities; delete[] palette; delete[] palette32; delete[] cluts; delete[] tiles4; delete[] tiles8; delete[] tiles16; delete[] tiles32; delete[] tilesMisc; delete[] cameraFrames; delete[] AIObjects; delete[] demoData; delete[] soundsMap; delete[] soundsInfo; delete[] soundData; delete[] soundOffsets; delete[] soundSize; delete[] tsub; } void loadTR1_PC (Stream &stream) { stream.read(tiles8, stream.read(tilesCount)); readDataArrays(stream); readObjectTex(stream); readSpriteTex(stream); if (isDemoLevel) { stream.read(palette, 256); } readCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); readEntities(stream); readLightMap(stream); if (!isDemoLevel) { stream.read(palette, 256); } readCameraFrames(stream); readDemoData(stream); readSoundMap(stream); readSoundData(stream); readSoundOffsets(stream); } void loadTR1_PSX (Stream &stream) { if (!isCutsceneLevel()) { uint32 offsetTexTiles; stream.seek(8); stream.read(offsetTexTiles); // sound offsets uint16 numSounds; stream.setPos(22); stream.read(numSounds); stream.setPos(2086 + numSounds * 512); soundOffsetsCount = numSounds; soundOffsets = new uint32[soundOffsetsCount]; soundSize = new uint32[soundOffsetsCount]; soundDataSize = 0; for (int i = 0; i < soundOffsetsCount; i++) { soundOffsets[i] = soundDataSize; uint16 size; stream.read(size); soundDataSize += soundSize[i] = size * 8; } // sound data stream.setPos(2600 + numSounds * 512); stream.read(soundData, soundDataSize); stream.setPos(offsetTexTiles + 8); } stream.read(tiles4, tilesCount = 13); stream.read(cluts, clutsCount = 1024); readDataArrays(stream); readObjectTex(stream); readSpriteTex(stream); readCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); readEntities(stream); readSoundMap(stream); stream.seek(4); readCameraFrames(stream); } void loadTR1_SAT (Stream &stream) { stream.seek(-4); // no magic header // get file name without extension size_t len = strlen(stream.name); char *name = new char[len + 1]; memcpy(name, stream.name, len); for (int i = int(len) - 1; i >= 0; i--) { if (name[i] == '/' || name[i] == '\\') break; if (name[i] == '.') { len = i; break; } } name[len] = 0; LOG("load Sega Saturn level: %s\n", name); strcat(name, ".SAD"); Stream sad(name); name[len] = '\0'; strcat(name, ".SPR"); Stream spr(name); name[len] = '\0'; strcat(name, ".SND"); Stream snd(name); name[len] = '\0'; strcat(name, ".CIN"); Stream *cin = NULL; if (Stream::existsContent(name)) { cin = new Stream(name); } else { len = strlen(name); for (int i = int(len) - 1; i >= 0; i--) if (name[i] == '/' || name[i] == '\\') { char *newName = new char[len + 11 + 1]; name[i] = 0; strcpy(newName, name); strcat(newName, "/../CINDATA/"); strcat(newName, name + i + 1); delete[] name; name = newName; break; } if (Stream::existsContent(name)) cin = new Stream(name); } delete[] name; readSAT(sad); readSAT(spr); readSAT(snd); if (cin) { readCIN(*cin); delete cin; } readSAT(stream); // sat } void loadTR2_PC (Stream &stream) { stream.read(palette, 256); stream.read(palette32, 256); stream.read(tiles8, stream.read(tilesCount)); stream.read(tiles16, tilesCount); readDataArrays(stream); readObjectTex(stream); readSpriteTex(stream); readCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); readEntities(stream); readLightMap(stream); readCameraFrames(stream); readDemoData(stream); readSoundMap(stream); readSoundOffsets(stream); new Stream(getGameSoundsFile(version), sfxLoadAsync, this); } void loadTR2_PSX (Stream &stream) { stream.read(soundOffsets, stream.read(soundOffsetsCount) + 1); soundSize = new uint32[soundOffsetsCount]; soundDataSize = 0; for (int i = 0; i < soundOffsetsCount; i++) { ASSERT(soundOffsets[i] < soundOffsets[i + 1]); soundSize[i] = soundOffsets[i + 1] - soundOffsets[i]; soundOffsets[i] = soundDataSize; soundDataSize += soundSize[i]; } stream.read(soundData, soundDataSize); readDataArrays(stream); stream.read(tiles4, stream.read(tilesCount)); stream.read(clutsCount); if (clutsCount > 1024) { // check for japanese version (read kanji CLUT index) kanjiSprite = clutsCount & 0xFFFF; stream.seek(-2); stream.read(clutsCount); } stream.read(cluts, clutsCount); stream.seek(4); readObjectTex(stream); readSpriteTex(stream); readCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); readEntities(stream); stream.seek(4); readSoundMap(stream); stream.seek(4); readCameraFrames(stream); } void loadTR3_PC (Stream &stream) { stream.read(palette, 256); stream.read(palette32, 256); stream.read(tiles8, stream.read(tilesCount)); stream.read(tiles16, tilesCount); readDataArrays(stream); readSpriteTex(stream); readCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); readObjectTex(stream); readEntities(stream); readLightMap(stream); readCameraFrames(stream); readDemoData(stream); readSoundMap(stream); readSoundOffsets(stream); new Stream(getGameSoundsFile(version), sfxLoadAsync, this); } void loadTR3_PSX (Stream &stream) { readSoundOffsets(stream); if (soundOffsetsCount) { readSoundData(stream); soundSize = new uint32[soundOffsetsCount]; int size = 0; for (int i = 0; i < soundOffsetsCount - 1; i++) { ASSERT(soundOffsets[i] < soundOffsets[i + 1]); size += soundSize[i] = soundOffsets[i + 1] - soundOffsets[i]; } if (soundOffsetsCount) { soundSize[soundOffsetsCount - 1] = soundDataSize; if (soundOffsetsCount > 1) { soundSize[soundOffsetsCount - 1] -= soundSize[soundOffsetsCount - 2]; } } } // skip code modules int size; for (int i = 0; i < 13; i++) { stream.read(size); if (size) { stream.seek(size); stream.read(size); stream.seek(size); } } readDataArrays(stream); stream.read(tiles4, stream.read(tilesCount)); stream.read(clutsCount); if (clutsCount > 1024) { // check for japanese version (read kanji CLUT index) kanjiSprite = clutsCount & 0xFFFF; stream.seek(-2); stream.read(clutsCount); } clutsCount *= 2; // read underwater cluts too stream.read(cluts, clutsCount); readObjectTex(stream); readSpriteTex(stream); readCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); readEntities(stream); stream.read(skyColor); roomTexturesCount = stream.readLE32(); if (roomTexturesCount) { roomTextures = new TextureInfo[roomTexturesCount]; // load room textures for (int i = 0; i < roomTexturesCount; i++) { readObjectTex(stream, roomTextures[i], TEX_TYPE_ROOM); stream.seek(2 * 16); // skip 2 mipmap levels } } readSoundMap(stream); stream.seek(4); readCameraFrames(stream); } #ifdef USE_INFLATE void loadTR4_PC (Stream &stream) { uint16 roomTilesCount, objTilesCount, bumpTilesCount; uint32 sizeD, sizeC, sizeR; uint8 *dataC, *dataD; stream.read(roomTilesCount); stream.read(objTilesCount); stream.read(bumpTilesCount); // tiles32 stream.read(sizeD); stream.read(dataC, stream.read(sizeC)); ASSERT(sizeD == sizeof(Tile32) * (roomTilesCount + objTilesCount + bumpTilesCount)); tiles32 = new Tile32[roomTilesCount + objTilesCount + bumpTilesCount]; tinf_uncompress(tiles32, &sizeR, dataC + 2, 0); ASSERT(sizeD == sizeR); delete[] dataC; // tiles16 stream.read(sizeD); stream.read(sizeC); stream.seek(sizeC); // skip compressed 16-bit tiles // tiles16 stream.read(sizeD); stream.read(dataC, stream.read(sizeC)); ASSERT(sizeD == sizeof(Tile32) * 2); tilesMisc = new Tile32[2]; tinf_uncompress(tilesMisc, &sizeR, dataC + 2, 0); ASSERT(sizeD == sizeR); delete[] dataC; stream.read(sizeD); stream.read(dataC, stream.read(sizeC)); dataD = new uint8[sizeD]; tinf_uncompress(dataD, &sizeR, dataC + 2, 0); ASSERT(sizeD == sizeR); delete[] dataC; { Stream stream(NULL, dataD, sizeD); readDataArrays(stream); stream.seek(3); // SPR readSpriteTex(stream); readCameras(stream); readFlybyCameras(stream); readSoundSources(stream); readBoxes(stream); readOverlaps(stream); readZones(stream); readAnimTex(stream); stream.seek(3); // TEX readObjectTex(stream); readEntities(stream); readAIObjects(stream); readDemoData(stream); readSoundMap(stream); readSoundOffsets(stream); } delete[] dataD; stream.read(soundsCount); if (soundOffsetsCount <= 0 && soundsCount > 0) { soundOffsetsCount = soundsCount; soundOffsets = new uint32[soundOffsetsCount]; } soundDataSize = stream.size - stream.pos; soundData = new uint8[soundDataSize]; soundDataSize = 0; for (int i = 0; i < soundsCount; i++) { stream.read(sizeD); stream.read(sizeC); soundOffsets[i] = soundDataSize; stream.raw(soundData + soundDataSize, sizeC); soundDataSize += sizeC; } } #endif void loadTR4_PSX (Stream &stream) { } void loadTR4_SDC (Stream &stream) { } void loadTR5_PC (Stream &stream) { } void loadTR5_PSX (Stream &stream) { } void loadTR5_SDC (Stream &stream) { } void readDataArrays(Stream &stream) { if (version != VER_TR3_PSX) { stream.seek(4); } rooms = stream.read(roomsCount) ? new Room[roomsCount] : NULL; for (int i = 0; i < roomsCount; i++) { readRoom(stream, i); } stream.read(floors, stream.read(floorsCount)); if (version == VER_TR3_PSX) { // outside room offsets stream.seek(27 * 27 * 2); // outside rooms table int size; stream.read(size); stream.seek(size); // room mesh bbox stream.read(size); stream.seek(8 * size); } stream.read(meshData, stream.read(meshDataSize)); stream.read(meshOffsets, stream.read(meshOffsetsCount)); readAnims(stream); stream.read(states, stream.read(statesCount)); stream.read(ranges, stream.read(rangesCount)); stream.read(commands, stream.read(commandsCount)); stream.read(nodesData, stream.read(nodesDataSize)); stream.read(frameData, stream.read(frameDataSize)); readModels(stream); stream.read(staticMeshes, stream.read(staticMeshesCount)); } void readAnims(Stream &stream) { stream.read(animsCount); anims = animsCount ? new Animation[animsCount] : NULL; for (int i = 0; i < animsCount; i++) { Animation &anim = anims[i]; stream.read(anim.frameOffset); stream.read(anim.frameRate); stream.read(anim.frameSize); stream.read(anim.state); stream.read(anim.speed); stream.read(anim.accel); if (version & (VER_TR4 | VER_TR5)) { stream.read(anim.speedLateral); stream.read(anim.accelLateral); } else { anim.speedLateral.value = 0; anim.accelLateral.value = 0; } stream.read(anim.frameStart); stream.read(anim.frameEnd); stream.read(anim.nextAnimation); stream.read(anim.nextFrame); stream.read(anim.scCount); stream.read(anim.scOffset); stream.read(anim.acCount); stream.read(anim.animCommand); } } void readModels(Stream &stream) { models = stream.read(modelsCount) ? new Model[modelsCount] : NULL; for (int i = 0; i < modelsCount; i++) { Model &m = models[i]; uint16 type; m.type = Entity::Type(stream.read(type)); stream.seek(sizeof(m.index)); m.index = i; stream.read(m.mCount); stream.read(m.mStart); stream.read(m.node); stream.read(m.frame); stream.read(m.animation); if (version & VER_PSX) { stream.seek(2); } } } void readCameras(Stream &stream) { stream.read(cameras, stream.read(camerasCount)); } void readFlybyCameras(Stream &stream) { stream.read(flybyCameras, stream.read(flybyCamerasCount)); } void readSoundSources(Stream &stream) { stream.read(soundSources, stream.read(soundSourcesCount)); } void readBoxes(Stream &stream) { boxes = stream.read(boxesCount) ? new Box[boxesCount] : NULL; for (int i = 0; i < boxesCount; i++) { Box &b = boxes[i]; if (version & VER_TR1) { stream.read(b.minZ); stream.read(b.maxZ); stream.read(b.minX); stream.read(b.maxX); } if (version & (VER_TR2 | VER_TR3 | VER_TR4 | VER_TR5)) { uint8 value; b.minZ = stream.read(value) * 1024; b.maxZ = stream.read(value) * 1024; b.minX = stream.read(value) * 1024; b.maxX = stream.read(value) * 1024; } stream.read(b.floor); stream.read(b.overlap.value); } } void readOverlaps(Stream &stream) { stream.read(overlaps, stream.read(overlapsCount)); } void readZones(Stream &stream) { for (int i = 0; i < 2; i++) { stream.read(zones[i].ground1, boxesCount); stream.read(zones[i].ground2, boxesCount); if (!(version & VER_TR1)) { stream.read(zones[i].ground3, boxesCount); stream.read(zones[i].ground4, boxesCount); } else { zones[i].ground3 = NULL; zones[i].ground4 = NULL; } stream.read(zones[i].fly, boxesCount); } } void readLightMap(Stream &stream) { stream.raw(lightmap, 32 * 256); for (int i = 0; i < 32; i++) { lightmap[i * 256] = 0; } } void readCameraFrames(Stream &stream) { stream.read(cameraFrames, stream.read(cameraFramesCount)); } void readAIObjects(Stream &stream) { stream.read(AIObjects, stream.read(AIObjectsCount)); } void readDemoData(Stream &stream) { stream.read(demoData, stream.read(demoDataSize)); } void readSoundMap(Stream &stream) { soundsCount = (version & VER_TR1) ? 256 : 370; stream.read(soundsMap, soundsCount); soundsInfo = (stream.read(soundsInfoCount) > 0) ? new SoundInfo[soundsInfoCount] : NULL; for (int i = 0; i < soundsInfoCount; i++) { SoundInfo &s = soundsInfo[i]; stream.read(s.index); if (version & (VER_TR1 | VER_TR2)) { uint16 v; stream.read(v); s.volume = float(v) / 0x7FFF; stream.read(v); s.chance = float(v) / 0xFFFF; s.range = 8 * 1024; s.pitch = 0.2f; } else { uint8 v; stream.read(v); s.volume = float(v) / 0xFF; stream.read(v); s.range = float(v) * 1024; stream.read(v); s.chance = float(v) / 0xFF; stream.read(v); s.pitch = float(v) / 0xFF; } stream.read(s.flags.value); ASSERT(s.volume <= 1.0f); } } void readSoundData(Stream &stream) { stream.read(soundDataSize) > 0 ? stream.read(soundData, soundDataSize) : NULL; } void readSoundOffsets(Stream &stream) { stream.read(soundOffsetsCount) > 0 ? stream.read(soundOffsets, soundOffsetsCount) : NULL; } #define CHUNK(str) ((uint64)((const char*)(str))[0] | ((uint64)((const char*)(str))[1] << 8) | ((uint64)((const char*)(str))[2] << 16) | ((uint64)((const char*)(str))[3] << 24) | \ ((uint64)((const char*)(str))[4] << 32) | ((uint64)((const char*)(str))[5] << 40) | ((uint64)((const char*)(str))[6] << 48) | ((uint64)((const char*)(str))[7] << 56)) #define SAT_ROOMFILE 0x454C49464D4F4F52ULL /* CHUNK("ROOMFILE") */ #define SAT_ROOMTINF 0x464E49544D4F4F52ULL /* CHUNK("ROOMTINF") */ #define SAT_ROOMTQTR 0x525451544D4F4F52ULL /* CHUNK("ROOMTQTR") */ #define SAT_ROOMTSUB 0x425553544D4F4F52ULL /* CHUNK("ROOMTSUB") */ #define SAT_ROOMTPAL 0x4C4150544D4F4F52ULL /* CHUNK("ROOMTPAL") */ #define SAT_ROOMSPAL 0x4C4150534D4F4F52ULL /* CHUNK("ROOMSPAL") */ #define SAT_ROOMDATA 0x415441444D4F4F52ULL /* CHUNK("ROOMDATA") */ #define SAT_ROOMNUMB 0x424D554E4D4F4F52ULL /* CHUNK("ROOMNUMB") */ #define SAT_MESHPOS_ 0x20534F504853454DULL /* CHUNK("MESHPOS ") */ #define SAT_MESHSIZE 0x455A49534853454DULL /* CHUNK("MESHSIZE") */ #define SAT_DOORDATA 0x41544144524F4F44ULL /* CHUNK("DOORDATA") */ #define SAT_FLOORDAT 0x544144524F4F4C46ULL /* CHUNK("FLOORDAT") */ #define SAT_FLOORSIZ 0x5A4953524F4F4C46ULL /* CHUNK("FLOORSIZ") */ #define SAT_FLORDATA 0x41544144524F4C46ULL /* CHUNK("FLORDATA") */ #define SAT_LIGHTAMB 0x424D41544847494CULL /* CHUNK("LIGHTAMB") */ #define SAT_RM_FLIP_ 0x2050494C465F4D52ULL /* CHUNK("RM_FLIP ") */ #define SAT_RM_FLAGS 0x5347414C465F4D52ULL /* CHUNK("RM_FLAGS") */ #define SAT_LIGHTSIZ 0x5A4953544847494CULL /* CHUNK("LIGHTSIZ") */ #define SAT_CAMERAS_ 0x20534152454D4143ULL /* CHUNK("CAMERAS ") */ #define SAT_SOUNDFX_ 0x205846444E554F53ULL /* CHUNK("SOUNDFX ") */ #define SAT_BOXES___ 0x2020205345584F42ULL /* CHUNK("BOXES ") */ #define SAT_OVERLAPS 0x5350414C5245564FULL /* CHUNK("OVERLAPS") */ #define SAT_GND_ZONE 0x454E4F5A5F444E47ULL /* CHUNK("GND_ZONE") */ #define SAT_GND_ZON2 0x324E4F5A5F444E47ULL /* CHUNK("GND_ZON2") */ #define SAT_FLY_ZONE 0x454E4F5A5F594C46ULL /* CHUNK("FLY_ZONE") */ #define SAT_ARANGES_ 0x205345474E415241ULL /* CHUNK("ARANGES ") */ #define SAT_ITEMDATA 0x415441444D455449ULL /* CHUNK("ITEMDATA") */ #define SAT_ROOMEND_ 0x20444E454D4F4F52ULL /* CHUNK("ROOMEND ") */ #define SAD_OBJFILE_ 0x20454C49464A424FULL /* CHUNK("OBJFILE ") */ #define SAD_ANIMS___ 0x202020534D494E41ULL /* CHUNK("ANIMS ") */ #define SAD_CHANGES_ 0x205345474E414843ULL /* CHUNK("CHANGES ") */ #define SAD_RANGES_z 0x00205345474E4152ULL /* CHUNK("RANGES \0") */ #define SAD_COMMANDS 0x53444E414D4D4F43ULL /* CHUNK("COMMANDS") */ #define SAD_ANIBONES 0x53454E4F42494E41ULL /* CHUNK("ANIBONES") */ #define SAD_ANIMOBJ_ 0x204A424F4D494E41ULL /* CHUNK("ANIMOBJ ") */ #define SAD_STATOBJ_ 0x204A424F54415453ULL /* CHUNK("STATOBJ ") */ #define SAD_FRAMES__ 0x202053454D415246ULL /* CHUNK("FRAMES ") */ #define SAD_MESHPTRS 0x535254504853454DULL /* CHUNK("MESHPTRS") */ #define SAD_MESHDATA 0x415441444853454DULL /* CHUNK("MESHDATA") */ #define SAD_OTEXTINF 0x464E49545845544FULL /* CHUNK("OTEXTINF") */ #define SAD_OTEXTDAT 0x544144545845544FULL /* CHUNK("OTEXTDAT") */ #define SAD_ITEXTINF 0x464E495458455449ULL /* CHUNK("ITEXTINF") */ #define SAD_ITEXTDAT 0x5441445458455449ULL /* CHUNK("ITEXTDAT") */ #define SAD_OBJEND__ 0x2020444E454A424FULL /* CHUNK("OBJEND ") */ #define SPR_SPRFILE_ 0x20454C4946525053ULL /* CHUNK("SPRFILE ") */ #define SPR_SPRITINF 0x464E495449525053ULL /* CHUNK("SPRITINF") */ #define SPR_SPRITDAT 0x5441445449525053ULL /* CHUNK("SPRITDAT") */ #define SPR_OBJECTS_ 0x20535443454A424FULL /* CHUNK("OBJECTS ") */ #define SPR_SPRITEND 0x444E455449525053ULL /* CHUNK("SPRITEND") */ #define SND_SAMPLUT_ 0x2054554C504D4153ULL /* CHUNK("SAMPLUT ") */ #define SND_SAMPINFS 0x53464E49504D4153ULL /* CHUNK("SAMPINFS") */ #define SND_SAMPLE__ 0x2020454C504D4153ULL /* CHUNK("SAMPLE ") */ #define SND_ENDFILEz 0x00454C4946444E45ULL /* CHUNK("ENDFILE\0") */ void readSAT(Stream &stream) { #if !defined(_OS_PSP) Room *room = NULL; while (stream.pos < stream.size) { uint64 chunkType = stream.read64(); /* { char buf[9]; memcpy(buf, &chunkType, 8); buf[8] = 0; LOG("chunk: \"%s\"\n", buf); } */ switch (chunkType) { // SAT case SAT_ROOMFILE : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000020); break; case SAT_ROOMTINF : ASSERTV(stream.readBE32() == 0x00000010); roomTexturesCount = stream.readBE32(); roomTextures = roomTexturesCount ? new TextureInfo[roomTexturesCount] : NULL; for (int i = 0; i < roomTexturesCount; i++) readObjectTex(stream, roomTextures[i], TEX_TYPE_ROOM); break; case SAT_ROOMTQTR : { ASSERTV(stream.readBE32() == 0x00000001); roomTexturesDataSize = stream.readBE32(); roomTexturesData = roomTexturesDataSize ? new uint8[roomTexturesDataSize] : NULL; stream.raw(roomTexturesData, roomTexturesDataSize); /* int32 count = stream.readBE32(); stream.seek(count); */ break; } case SAT_ROOMTSUB : ASSERTV(stream.readBE32() == 0x00000001); /* roomTexturesDataSize = stream.readBE32(); roomTexturesData = roomTexturesDataSize ? new uint8[roomTexturesDataSize] : NULL; stream.raw(roomTexturesData, roomTexturesDataSize); */ tsubCount = stream.readBE32(); tsub = new uint8[tsubCount]; stream.raw(tsub, sizeof(uint8) * tsubCount); break; case SAT_ROOMTPAL : { ASSERTV(stream.readBE32() == 0x00000003); stream.seek(stream.readBE32() * 3); break; } case SAT_ROOMSPAL : { ASSERTV(stream.readLE32() == 0x02000000); stream.seek(stream.readBE32() * 2); break; } case SAT_ROOMDATA : ASSERTV(stream.readBE32() == 0x00000044); roomsCount = stream.readBE32(); rooms = new Room[roomsCount]; memset(rooms, 0, sizeof(Room) * roomsCount); break; case SAT_ROOMNUMB : ASSERTV(stream.readBE32() == 0x00000000); room = &rooms[stream.readBE32()]; break; case SAT_MESHPOS_ : ASSERT(room); room->info.x = stream.readBE32(); room->info.z = stream.readBE32(); room->info.yBottom = stream.readBE32(); room->info.yTop = stream.readBE32(); break; case SAT_MESHSIZE : { ASSERT(room); uint32 flag = stream.readBE32(); if (flag == 0x00000014) { room->meshesCount = stream.readBE32(); room->meshes = room->meshesCount ? new Room::Mesh[room->meshesCount] : NULL; for (int i = 0; i < room->meshesCount; i++) { Room::Mesh &m = room->meshes[i]; m.x = stream.readBE32(); m.y = stream.readBE32(); m.z = stream.readBE32(); m.rotation.value = stream.readBE16(); uint16 intensity = stream.readBE16(); stream.readBE16(); int value = clamp((intensity > 0x1FFF) ? 255 : (255 - (intensity >> 5)), 0, 255); m.color.r = m.color.g = m.color.b = value; m.color.a = 0; m.meshID = stream.readBE16(); } //stream.seek(20 * count); // unknown break; } ASSERT(flag == 0x00000002); Room::Data &data = room->data; data.size = stream.readBE32(); data.vCount = stream.readBE16(); data.vertices = new Room::Data::Vertex[data.vCount]; for (int j = 0; j < data.vCount; j++) { Room::Data::Vertex &v = data.vertices[j]; v.pos.x = stream.readBE16(); v.pos.y = stream.readBE16(); v.pos.z = stream.readBE16(); v.color = Color16(stream.readBE16()); v.attributes = 0; } data.fCount = stream.readBE16(); data.faces = new Face[data.fCount]; data.sprites = new Room::Data::Sprite[data.fCount]; enum { TYPE_R_TRANSP = 33, TYPE_T_INVISIBLE = 34, TYPE_R_INVISIBLE = 35, TYPE_T_SOLID = 36, TYPE_R_SOLID = 37, TYPE_SPRITE = 39, }; int fIndex = 0; uint16 typesCount = stream.readBE16(); for (int j = 0; j < typesCount; j++) { uint16 type = stream.readBE16(); uint16 count = stream.readBE16(); //LOG(" type:%d count:%d\n", (int)type, (int)count); for (int k = 0; k < count; k++) { ASSERT(fIndex < data.fCount); Face &f = data.faces[fIndex++]; switch (type) { case TYPE_SPRITE : { Room::Data::Sprite &sprite = data.sprites[data.sCount++]; sprite.vertexIndex = (stream.readBE16() >> 4); sprite.texture = (stream.readBE16() >> 4); fIndex--; break; } case TYPE_T_INVISIBLE : case TYPE_T_SOLID : f.triangle = true; f.vertices[0] = (stream.readBE16() >> 4); f.vertices[1] = (stream.readBE16() >> 4); f.vertices[2] = (stream.readBE16() >> 4); f.vertices[3] = 0; ASSERT(f.vertices[0] < data.vCount && f.vertices[1] < data.vCount && f.vertices[2] < data.vCount); f.flags.value = stream.readBE16(); if (type == TYPE_T_INVISIBLE) { fIndex--; continue; } data.tCount++; break; case TYPE_R_INVISIBLE : case TYPE_R_TRANSP : case TYPE_R_SOLID : f.vertices[0] = (stream.readBE16() >> 4); f.vertices[1] = (stream.readBE16() >> 4); f.vertices[2] = (stream.readBE16() >> 4); f.vertices[3] = (stream.readBE16() >> 4); ASSERT(f.vertices[0] < data.vCount && f.vertices[1] < data.vCount && f.vertices[2] < data.vCount && f.vertices[3] < data.vCount); f.flags.value = stream.readBE16(); if (type == TYPE_R_INVISIBLE) { fIndex--; continue; } data.rCount++; break; default : LOG("! unknown face type: %d\n", type); ASSERT(false); } if (type != TYPE_SPRITE) { ASSERT(f.flags.value % 16 == 0); ASSERT(f.flags.value / 16 < roomTexturesCount); f.flags.value /= 16; } if (type == TYPE_R_TRANSP) { roomTextures[f.flags.texture].attribute = 1; } } } data.fCount = fIndex; break; } case SAT_DOORDATA : { int32 roomIndex = stream.readBE32(); ASSERT(roomIndex < roomsCount); Room *room = &rooms[roomIndex]; ASSERT(room && room->sectors == NULL); room->portalsCount = stream.readBE32(); room->portals = new Room::Portal[room->portalsCount]; for (int j = 0; j < room->portalsCount; j++) { Room::Portal &p = room->portals[j]; p.roomIndex = stream.readBE16(); p.normal.x = stream.readBE16(); p.normal.y = stream.readBE16(); p.normal.z = stream.readBE16(); for (int k = 0; k < 4; k++) { p.vertices[k].x = stream.readBE16(); p.vertices[k].y = stream.readBE16(); p.vertices[k].z = stream.readBE16(); } } break; } case SAT_FLOORDAT : ASSERT(room); room->zSectors = stream.readBE32(); room->xSectors = stream.readBE32(); break; case SAT_FLOORSIZ : { ASSERTV(stream.readBE32() == 0x00000008); ASSERT(room && room->sectors == NULL); int32 count = stream.readBE32(); ASSERT(count == room->xSectors * room->zSectors); room->sectors = count ? new Room::Sector[count] : NULL; for (int i = 0; i < count; i++) { Room::Sector &s = room->sectors[i]; s.material = 0; s.floorIndex = stream.readBE16(); s.boxIndex = stream.readBE16(); stream.read(s.roomBelow); stream.read(s.floor); stream.read(s.roomAbove); stream.read(s.ceiling); } break; } case SAT_FLORDATA : ASSERTV(stream.readBE32() == 0x00000002); ASSERT(floors == NULL); floorsCount = stream.readBE32(); floors = new FloorData[floorsCount]; for (int i = 0; i < floorsCount; i++) floors[i].value = stream.readBE16(); break; case SAT_LIGHTAMB : ASSERT(room); room->ambient = stream.readBE32(); room->ambient2 = stream.readBE32(); break; case SAT_RM_FLIP_ : { ASSERTV(stream.readBE32() == 0x00000002); uint32 value = stream.readBE32(); room->alternateRoom = value == 0xFFFFFFFF ? -1 : value; break; } case SAT_RM_FLAGS : { ASSERT(room); ASSERTV(stream.readBE32() == 0x00000002); uint32 value = stream.readBE32(); room->flags.water = (value & 0x01) != 0; break; } case SAT_LIGHTSIZ : { ASSERTV(stream.readBE32() == 0x00000014); ASSERT(room && room->lights == NULL); room->lightsCount = stream.readBE32(); room->lights = room->lightsCount ? new Room::Light[room->lightsCount] : NULL; for (int i = 0; i < room->lightsCount; i++) { Room::Light &light = room->lights[i]; light.x = stream.readBE32(); light.y = stream.readBE32(); light.z = stream.readBE32(); int16 intensity = stream.readBE16(); int16 intensity2 = stream.readBE16(); ASSERTV(intensity == intensity2); int value = clamp((intensity > 0x1FFF) ? 0 : (intensity >> 5), 0, 255); light.color.r = light.color.g = light.color.b = value; light.intensity = intensity; light.radius = stream.readBE32() * 2; } break; } case SAT_CAMERAS_ : ASSERTV(stream.readBE32() == 0x00000010); ASSERT(cameras == NULL); camerasCount = stream.readBE32(); cameras = camerasCount ? new Camera[camerasCount] : NULL; for (int i = 0; i < camerasCount; i++) { Camera &cam = cameras[i]; cam.x = stream.readBE32(); cam.y = stream.readBE32(); cam.z = stream.readBE32(); cam.room = stream.readBE16(); cam.flags.boxIndex = stream.readBE16(); } break; case SAT_SOUNDFX_ : { uint32 flag = stream.readBE32(); if (flag == 0x00000000) { // SND ASSERTV(stream.readBE32() == 0x00000000); int pos = stream.pos; stream.setPos(0); soundDataSize = stream.size; soundData = new uint8[soundDataSize]; stream.raw(soundData, soundDataSize); soundOffsetsCount = 256; soundOffsets = new uint32[soundOffsetsCount]; soundSize = new uint32[soundOffsetsCount]; memset(soundOffsets, 0, soundOffsetsCount * sizeof(uint32)); memset(soundSize, 0, soundOffsetsCount * sizeof(uint32)); stream.setPos(pos); break; } ASSERT(flag == 0x00000010); // SAT ASSERT(soundSources == NULL); soundSourcesCount = stream.readBE32(); soundSources = soundSourcesCount ? new SoundSource[soundSourcesCount] : NULL; for (int i = 0; i < soundSourcesCount; i++) { SoundSource &s = soundSources[i]; s.x = stream.readBE32(); s.y = stream.readBE32(); s.z = stream.readBE32(); s.id = stream.readBE16(); s.flags = stream.readBE16(); } break; } case SAT_BOXES___ : ASSERTV(stream.readBE32() == 0x00000014); ASSERT(boxes == NULL); boxesCount = stream.readBE32(); boxes = boxesCount ? new Box[boxesCount] : NULL; for (int i = 0; i < boxesCount; i++) { Box &b = boxes[i]; b.minZ = stream.readBE32(); b.maxZ = stream.readBE32(); b.minX = stream.readBE32(); b.maxX = stream.readBE32(); b.floor = stream.readBE16(); b.overlap.value = stream.readBE16(); } break; case SAT_OVERLAPS : ASSERTV(stream.readBE32() == 0x00000002); ASSERT(overlaps == NULL); overlapsCount = stream.readBE32(); overlaps = overlapsCount ? new Overlap[overlapsCount] : NULL; for (int i = 0; i < overlapsCount; i++) overlaps[i].value = stream.readBE16(); break; case SAT_GND_ZONE : case SAT_GND_ZON2 : case SAT_FLY_ZONE : { ASSERTV(stream.readBE32() == 0x00000002); uint16 **ptr; switch (chunkType) { case SAT_GND_ZONE : ptr = zones[0].ground1 ? &zones[1].ground1 : &zones[0].ground1; break; case SAT_GND_ZON2 : ptr = zones[0].ground2 ? &zones[1].ground2 : &zones[0].ground2; break; case SAT_FLY_ZONE : ptr = zones[0].fly ? &zones[1].fly : &zones[0].fly; break; default : ptr = NULL; } ASSERT(ptr != NULL); ASSERT(*ptr == NULL); int32 count = stream.readBE32(); *ptr = count ? new uint16[count] : NULL; for (int i = 0; i < count; i++) (*ptr)[i] = stream.readBE16(); break; } case SAT_ARANGES_ : { ASSERTV(stream.readBE32() == 0x00000008); animTexturesCount = stream.readBE32(); animTextures = new AnimTexture[animTexturesCount]; for (int i = 0; i < animTexturesCount; i++) { AnimTexture &animTex = animTextures[i]; uint32 first = stream.readBE32(); uint32 last = stream.readBE32(); animTex.count = last - first + 1; animTex.textures = new uint16[animTex.count]; for (int j = 0; j < animTex.count; j++) animTex.textures[j] = first + j; } break; } case SAT_ITEMDATA : { ASSERTV(stream.readBE32() == 0x00000014); entitiesBaseCount = stream.readBE32(); entitiesCount = entitiesBaseCount + MAX_RESERVED_ENTITIES; entities = new Entity[entitiesCount]; for (int i = 0; i < entitiesBaseCount; i++) { Entity &e = entities[i]; e.type = Entity::Type(stream.readBE16()); e.room = stream.readBE16(); e.x = stream.readBE32(); e.y = stream.readBE32(); e.z = stream.readBE32(); e.rotation.value = stream.readBE16(); e.intensity = stream.readBE16(); e.intensity2 = stream.readBE16(); e.flags.value = stream.readBE16(); } break; } case SAT_ROOMEND_ : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000000); break; // SAD case SAD_OBJFILE_ : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000020); break; case SAD_ANIMS___ : ASSERTV(stream.readBE32() == 0x00000022); ASSERT(anims == NULL); animsCount = stream.readBE32(); anims = animsCount ? new Animation[animsCount] : NULL; for (int i = 0; i < animsCount; i++) { Animation &anim = anims[i]; anim.frameOffset = stream.readBE32(); anim.frameSize = stream.read(); anim.frameRate = stream.read(); anim.state = stream.readBE16(); anim.speed.H = stream.readBE16(); anim.speed.L = stream.readBE16(); anim.accel.H = stream.readBE16(); anim.accel.L = stream.readBE16(); anim.frameStart = stream.readBE16(); anim.frameEnd = stream.readBE16(); anim.nextAnimation = stream.readBE16(); anim.nextFrame = stream.readBE16(); anim.scCount = stream.readBE16(); anim.scOffset = stream.readBE16(); anim.acCount = stream.readBE16(); anim.animCommand = stream.readBE16(); } break; case SAD_CHANGES_ : ASSERTV(stream.readBE32() == 0x00000008); ASSERT(states == NULL); statesCount = stream.readBE32(); states = statesCount ? new AnimState[statesCount] : NULL; for (int i = 0; i < statesCount; i++) { AnimState &state = states[i]; state.state = stream.readBE16(); state.rangesCount = stream.readBE16(); state.rangesOffset = stream.readBE16(); ASSERTV(stream.readBE16() == state.rangesOffset); // dummy } break; case SAD_RANGES_z : ASSERTV(stream.readBE32() == 0x00000008); ASSERT(ranges == NULL); rangesCount = stream.readBE32(); ranges = rangesCount ? new AnimRange[rangesCount] : NULL; for (int i = 0; i < rangesCount; i++) { AnimRange &range = ranges[i]; range.low = stream.readBE16(); range.high = stream.readBE16(); range.nextAnimation = stream.readBE16(); range.nextFrame = stream.readBE16(); } break; case SAD_COMMANDS : ASSERTV(stream.readBE32() == 0x00000002); ASSERT(commands == NULL); commandsCount = stream.readBE32(); commands = commandsCount ? new int16[commandsCount] : NULL; for (int i = 0; i < commandsCount; i++) commands[i] = stream.readBE16(); break; case SAD_ANIBONES : ASSERTV(stream.readBE32() == 0x00000004); ASSERT(nodesData == NULL); nodesDataSize = stream.readBE32(); nodesData = nodesDataSize ? new uint32[nodesDataSize] : NULL; for (int i = 0; i < nodesDataSize; i++) nodesData[i] = stream.readBE32(); break; case SAD_ANIMOBJ_ : ASSERTV(stream.readBE32() == 0x00000038); ASSERT(models == NULL); modelsCount = stream.readBE32(); models = modelsCount ? new Model[modelsCount] : NULL; for (int i = 0; i < modelsCount; i++) { Model &model = models[i]; ASSERTV(stream.readBE16() == 0x0000); model.index = i; model.type = Entity::Type(stream.readBE16()); model.mCount = stream.readBE16(); model.mStart = stream.readBE16(); model.node = stream.readBE32(); model.frame = stream.readBE32(); model.animation = stream.readBE16(); ASSERTV(stream.readBE16() == model.animation); } break; case SAD_STATOBJ_ : ASSERTV(stream.readBE32() == 0x00000020); ASSERT(staticMeshes == NULL); staticMeshesCount = stream.readBE32(); staticMeshes = staticMeshesCount ? new StaticMesh[staticMeshesCount] : NULL; for (int i = 0; i < staticMeshesCount; i++) { StaticMesh &mesh = staticMeshes[i]; mesh.id = stream.readBE32(); mesh.mesh = stream.readBE16(); mesh.vbox.minX = stream.readBE16(); mesh.vbox.maxX = stream.readBE16(); mesh.vbox.minY = stream.readBE16(); mesh.vbox.maxY = stream.readBE16(); mesh.vbox.minZ = stream.readBE16(); mesh.vbox.maxZ = stream.readBE16(); mesh.cbox.minX = stream.readBE16(); mesh.cbox.maxX = stream.readBE16(); mesh.cbox.minY = stream.readBE16(); mesh.cbox.maxY = stream.readBE16(); mesh.cbox.minZ = stream.readBE16(); mesh.cbox.maxZ = stream.readBE16(); mesh.flags = stream.readBE16(); } break; case SAD_FRAMES__ : ASSERTV(stream.readBE32() == 0x00000002); ASSERT(frameData == NULL); frameDataSize = stream.readBE32(); frameData = frameDataSize ? new uint16[frameDataSize] : NULL; for (int i = 0; i < frameDataSize; i++) frameData[i] = stream.readBE16(); break; case SAD_MESHPTRS : ASSERTV(stream.readBE32() == 0x00000004); ASSERT(meshOffsets == NULL); meshOffsetsCount = stream.readBE32(); meshOffsets = meshOffsetsCount ? new int32[meshOffsetsCount] : NULL; for (int i = 0; i < meshOffsetsCount; i++) meshOffsets[i] = stream.readBE32(); break; case SAD_MESHDATA : ASSERTV(stream.readBE32() == 0x00000002); ASSERT(meshData == NULL); meshDataSize = stream.readBE32(); meshData = meshDataSize ? new uint16[meshDataSize] : NULL; stream.raw(meshData, sizeof(uint16) * meshDataSize); break; case SAD_OTEXTINF : ASSERTV(stream.readBE32() == 0x00000010); ASSERT(objectTextures == NULL); objectTexturesCount = stream.readBE32(); objectTextures = objectTexturesCount ? new TextureInfo[objectTexturesCount * 5] : NULL; for (int i = 0; i < objectTexturesCount; i++) readObjectTex(stream, objectTextures[i], TEX_TYPE_OBJECT); objectTexturesBaseCount = objectTexturesCount; expandObjectTex(objectTextures, objectTexturesCount); break; case SAD_OTEXTDAT : { ASSERTV(stream.readBE32() == 0x00000001); objectTexturesDataSize = stream.readBE32(); objectTexturesData = objectTexturesDataSize ? new uint8[objectTexturesDataSize] : NULL; stream.raw(objectTexturesData, objectTexturesDataSize); break; } case SAD_ITEXTINF : { ASSERTV(stream.readBE32() == 0x00000014); itemTexturesCount = stream.readBE32(); itemTextures = itemTexturesCount ? new TextureInfo[itemTexturesCount * 5] : NULL; for (int i = 0; i < itemTexturesCount; i++) readObjectTex(stream, itemTextures[i], TEX_TYPE_ITEM); itemTexturesBaseCount = itemTexturesCount; expandObjectTex(itemTextures, itemTexturesCount); break; } case SAD_ITEXTDAT : { ASSERTV(stream.readBE32() == 0x00000001); itemTexturesDataSize = stream.readBE32(); itemTexturesData = itemTexturesDataSize ? new uint8[itemTexturesDataSize] : NULL; stream.raw(itemTexturesData, itemTexturesDataSize); break; } case SAD_OBJEND__ : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000000); break; // SPR case SPR_SPRFILE_ : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000020); break; case SPR_SPRITINF : { ASSERTV(stream.readBE32() == 0x00000010); spriteTexturesCount = stream.readBE32(); spriteTextures = spriteTexturesCount ? new TextureInfo[spriteTexturesCount] : NULL; for (int i = 0; i < spriteTexturesCount; i++) readSpriteTex(stream, spriteTextures[i]); break; } case SPR_SPRITDAT : { ASSERTV(stream.readBE32() == 0x00000001); spriteTexturesDataSize = stream.readBE32(); spriteTexturesData = spriteTexturesDataSize ? new uint8[spriteTexturesDataSize] : NULL; stream.raw(spriteTexturesData, spriteTexturesDataSize); break; } case SPR_OBJECTS_ : { ASSERTV(stream.readBE32() == 0x00000000); spriteSequencesCount = stream.readBE32(); spriteSequences = spriteSequencesCount ? new SpriteSequence[spriteSequencesCount] : NULL; for (int i = 0; i < spriteSequencesCount; i++) { SpriteSequence &s = spriteSequences[i]; ASSERTV(stream.readBE16() == 0); s.type = Entity::remap(version, Entity::Type(stream.readBE16())); s.sCount = (s.type >= Entity::TR1_TYPE_MAX) ? 1 : -stream.readBE16(); s.sStart = stream.readBE16(); s.transp = 1; } break; } case SPR_SPRITEND : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000000); break; // SND case SND_SAMPLUT_ : { ASSERTV(stream.readBE32() == 0x00000002); int count = stream.readBE32(); soundsMap = new int16[count]; for (int i = 0; i < count; i++) soundsMap[i] = stream.readBE16(); break; } case SND_SAMPINFS : { ASSERTV(stream.readBE32() == 0x00000008); soundsInfoCount = stream.readBE32(); soundsInfo = soundsInfoCount ? new SoundInfo[soundsInfoCount] : NULL; for (int i = 0; i < soundsInfoCount; i++) { SoundInfo &s = soundsInfo[i]; s.index = stream.readBE16(); s.volume = float(stream.readBE16()) / 0x7FFF; s.chance = float(stream.readBE16()) / 0xFFFF; s.range = 8 * 1024; s.pitch = 0.2f; s.flags.value = stream.readBE16(); } break; } case SND_SAMPLE__ : { int32 index = stream.readBE32(); int32 size = stream.readBE32(); ASSERT(index < soundOffsetsCount); soundOffsets[index] = stream.pos - 4; soundSize[index] = size - 4; // trim last samples (clicks) *((uint32*)(&soundData[soundOffsets[index]])) = FOURCC("SEGA"); // mark sample as SEGA PCM stream.seek(size); break; } case SND_ENDFILEz : ASSERTV(stream.readBE32() == 0x00000000); ASSERTV(stream.readBE32() == 0x00000000); break; default : { char buf[9]; memcpy(buf, &chunkType, 8); buf[8] = 0; LOG("! unknown chunk type: \"%s\"\n", buf); ASSERT(false); break; } } } #endif } void readCIN(Stream &stream) { stream.seek(2); // skip unknown word cameraFramesCount = (stream.size - 2) / 16; cameraFrames = cameraFramesCount ? new CameraFrame[cameraFramesCount] : NULL; stream.raw(cameraFrames, cameraFramesCount * 16); } void appendObjectTex(TextureInfo *&objTex, int32 &count) { if (!objTex) return; TextureInfo *newObjectTextures = new TextureInfo[objectTexturesCount + count]; memcpy(newObjectTextures, objectTextures, sizeof(TextureInfo) * objectTexturesCount); memcpy(newObjectTextures + objectTexturesCount, objTex, sizeof(TextureInfo) * count); delete[] objectTextures; objectTextures = newObjectTextures; objectTexturesCount += count; delete[] objTex; objTex = NULL; count = 0; } void expandObjectTex(TextureInfo *textures, int32 &count) { for (int i = 0; i < count; i++) for (int j = 0; j < 4; j++) { int idx = count * (1 + j) + i; textures[idx] = textures[i]; textures[idx].tile = textures[idx].sub[j]; } count *= 5; } int getItemTexureByIndex(uint16 index) { for (int i = 0; i < itemTexturesCount; i++) if (itemTextures[i].index == index) return i; ASSERT(false); return 0; } void prepare() { if (version == VER_TR1_PC) { // Amiga -> PC color palette for TR1 PC ASSERT(palette); Color24 *c = palette; for (int i = 0; i < 256; i++) { c->r <<= 2; c->g <<= 2; c->b <<= 2; c++; } } for (int i = 0; i < modelsCount; i++) { Model &model = models[i]; model.type = Entity::remap(version, model.type); for (int j = 0; j < model.mCount; j++) { initMesh(model.mStart + j, model.type); } } for (int i = 0; i < staticMeshesCount; i++) { initMesh(staticMeshes[i].mesh); } remapMeshOffsetsToIndices(); delete[] meshData; meshData = NULL; LOG("meshes: %d\n", meshesCount); for (int i = 0; i < entitiesBaseCount; i++) { Entity &e = entities[i]; e.type = Entity::remap(version, e.type); e.controller = NULL; e.modelIndex = 0; // turn off interpolation for some entities e.flags.smooth = !((id == LVL_TR2_CUT_1 && (e.type == Entity::CUT_6 || e.type == Entity::CUT_8 || e.type == Entity::CUT_9)) || e.type == Entity::SWITCH_BUTTON); } for (int i = entitiesBaseCount; i < entitiesCount; i++) entities[i].controller = NULL; if (isCutsceneLevel()) { for (int i = 0; i < entitiesBaseCount; i++) { Entity &e = entities[i]; if ((((version & VER_TR1)) && e.type == Entity::CUT_1) || (((version & (VER_TR2 | VER_TR3))) && e.isLara())) { cutEntity = i; break; } } } if (roomTextures) { // remap texture indices for room faces for (int i = 0; i < roomsCount; i++) { Room::Data &data = rooms[i].data; for (int j = 0; j < data.fCount; j++) data.faces[j].flags.texture += objectTexturesCount; } for (int i = 0; i < animTexturesCount; i++) for (int j = 0; j < animTextures[i].count; j++) animTextures[i].textures[j] += objectTexturesCount; appendObjectTex(roomTextures, roomTexturesCount); } if (itemTextures) { // remap texture indices for inventory items for (int i = 0; i < modelsCount; i++) { Model &model = models[i]; if (Entity::isInventoryItem(model.type)) { for (int j = model.mStart; j < model.mStart + model.mCount; j++) { Mesh &mesh = meshes[meshOffsets[j]]; for (int k = 0; k < mesh.fCount; k++) if (!mesh.faces[k].colored) { mesh.faces[k].flags.texture += objectTexturesCount; } } } } appendObjectTex(itemTextures, itemTexturesCount); } if (version == VER_TR1_SAT) { for (int i = 0; i < objectTexturesCount; i++) { if (objectTextures[i].tile == 1) objectTextures[i].tile = 0xFFFF; // mark as unused } } initRoomMeshes(); initAnimTex(); initExtra(); initCutscene(); initTextureTypes(); gObjectTextures = objectTextures; gSpriteTextures = spriteTextures; gObjectTexturesCount = objectTexturesCount; gSpriteTexturesCount = spriteTexturesCount; } void readSamples(Stream &stream) { stream.read(soundData, soundDataSize = stream.size); int32 dataOffsets[512]; int32 dataOffsetsCount = 0; int32 dataOffset = 0; while (dataOffset < soundDataSize) { ASSERT(FOURCC(soundData + dataOffset) == FOURCC("RIFF")); dataOffsets[dataOffsetsCount++] = dataOffset; dataOffset += FOURCC(soundData + dataOffset + 4) + 8; // add chunk size } for (int i = 0; i < soundOffsetsCount; i++) { soundOffsets[i] = dataOffsets[soundOffsets[i]]; } } static void sfxLoadAsync(Stream *stream, void *userData) { if (!stream) { LOG("! can't load MAIN.SFX\n"); return; } ((Level*)userData)->readSamples(*stream); delete stream; } void initExtra() { // get special models indices memset(&extra, 0xFF, sizeof(extra)); for (int i = 0; i < modelsCount; i++) { Entity::Type type = models[i].type; switch (type) { case Entity::MUZZLE_FLASH : extra.muzzleFlash = i; break; case Entity::PUZZLE_DONE_1 : extra.puzzleDone[0] = i; break; case Entity::PUZZLE_DONE_2 : extra.puzzleDone[1] = i; break; case Entity::PUZZLE_DONE_3 : extra.puzzleDone[2] = i; break; case Entity::PUZZLE_DONE_4 : extra.puzzleDone[3] = i; break; case Entity::LARA_BRAID : extra.braid = i; break; case Entity::LARA_SPEC : extra.laraSpec = i; break; case Entity::LARA_SKIN : extra.laraSkin = i; break; case Entity::LARA_SKIN_JOINTS : extra.laraJoints = i; break; case Entity::CUT_1 : extra.meshSwap[0] = i; break; case Entity::CUT_2 : extra.meshSwap[1] = i; break; case Entity::CUT_3 : extra.meshSwap[2] = i; break; case Entity::INV_PASSPORT : extra.inv.passport = i; break; case Entity::INV_PASSPORT_CLOSED : extra.inv.passport_closed = i; break; case Entity::INV_MAP : extra.inv.map = i; break; case Entity::INV_COMPASS : extra.inv.compass = i; break; case Entity::INV_STOPWATCH : extra.inv.stopwatch = i; break; case Entity::INV_HOME : extra.inv.home = i; break; case Entity::INV_DETAIL : extra.inv.detail = i; break; case Entity::INV_SOUND : extra.inv.sound = i; break; case Entity::INV_CONTROLS : extra.inv.controls = i; break; case Entity::INV_GAMMA : extra.inv.gamma = i; break; case Entity::INV_EXPLOSIVE : extra.inv.explosive = i; break; case Entity::LARA_PISTOLS : case Entity::LARA_SHOTGUN : case Entity::LARA_MAGNUMS : case Entity::LARA_UZIS : extra.weapons[type] = i; break; case Entity::INV_PISTOLS : case Entity::INV_SHOTGUN : case Entity::INV_MAGNUMS : case Entity::INV_UZIS : extra.inv.weapons[type] = i; break; case Entity::INV_AMMO_PISTOLS : case Entity::INV_AMMO_SHOTGUN : case Entity::INV_AMMO_MAGNUMS : case Entity::INV_AMMO_UZIS : extra.inv.ammo[type] = i; break; case Entity::INV_MEDIKIT_SMALL : extra.inv.medikit[0] = i; break; case Entity::INV_MEDIKIT_BIG : extra.inv.medikit[1] = i; break; case Entity::INV_PUZZLE_1 : extra.inv.puzzle[0] = i; break; case Entity::INV_PUZZLE_2 : extra.inv.puzzle[1] = i; break; case Entity::INV_PUZZLE_3 : extra.inv.puzzle[2] = i; break; case Entity::INV_PUZZLE_4 : extra.inv.puzzle[3] = i; break; case Entity::INV_KEY_ITEM_1 : extra.inv.key[0] = i; break; case Entity::INV_KEY_ITEM_2 : extra.inv.key[1] = i; break; case Entity::INV_KEY_ITEM_3 : extra.inv.key[2] = i; break; case Entity::INV_KEY_ITEM_4 : extra.inv.key[3] = i; break; case Entity::INV_LEADBAR : extra.inv.leadbar = i; break; case Entity::INV_SCION : extra.inv.scion = i; break; case Entity::SKY : extra.sky = i; break; default : ; } } for (int i = 0; i < spriteSequencesCount; i++) switch (spriteSequences[i].type) { case Entity::SMOKE : extra.smoke = i; break; case Entity::WATER_SPLASH : extra.waterSplash = i; break; case Entity::GLYPHS : extra.glyphs = i; break; default : ; } //ASSERT(extra.glyphs != -1); } void initCutscene() { if (id == LVL_TR3_CUT_2 || id == LVL_TR3_CUT_6) cutEntity = 1; // TODO TR3 // init cutscene transform cutMatrix.identity(); if (cutEntity > -1) { Entity &e = entities[cutEntity]; switch (id) { // HACK to detect cutscene level number case LVL_TR1_CUT_1 : cutMatrix.translate(vec3(36668, float(e.y), 63180)); cutMatrix.rotateY(-23312.0f / float(0x4000) * PI * 0.5f); break; case LVL_TR1_CUT_2 : cutMatrix.translate(vec3(51962, float(e.y), 53760)); cutMatrix.rotateY(16380.0f / float(0x4000) * PI * 0.5f); break; case LVL_TR1_CUT_3 : flipMap(); case LVL_TR1_CUT_4 : cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z))); cutMatrix.rotateY(PI * 0.5f); break; default : cutMatrix.translate(vec3(float(e.x), float(e.y), float(e.z))); cutMatrix.rotateY(e.rotation); } } } void initTextureTypes() { // rooms geometry for (int roomIndex = 0; roomIndex < roomsCount; roomIndex++) { Room &room = rooms[roomIndex]; Room::Data &data = room.data; for (int i = 0; i < data.fCount; i++) { Face &f = data.faces[i]; ASSERT(!f.colored); ASSERT(f.flags.texture < objectTexturesCount); TextureInfo &t = objectTextures[f.flags.texture]; t.type = TEX_TYPE_ROOM; } } // rooms static meshes for (int staticMeshIndex = 0; staticMeshIndex < staticMeshesCount; staticMeshIndex++) { StaticMesh *staticMesh = &staticMeshes[staticMeshIndex]; if (!meshOffsets[staticMesh->mesh]) continue; Mesh &mesh = meshes[meshOffsets[staticMesh->mesh]]; for (int i = 0; i < mesh.fCount; i++) { Face &f = mesh.faces[i]; ASSERT(f.colored || f.flags.texture < objectTexturesCount); if (f.colored) continue; TextureInfo &t = objectTextures[f.flags.texture]; t.type = TEX_TYPE_ROOM; } } // animated textures for (int animTextureIndex = 0; animTextureIndex < animTexturesCount; animTextureIndex++) { AnimTexture &animTex = animTextures[animTextureIndex]; for (int j = 0; j < animTex.count; j++) { TextureInfo &t = objectTextures[animTex.textures[j]]; t.type = TEX_TYPE_ROOM; } } // objects (duplicate all textures that are simultaneously used for static meshes) TextureInfo *dupObjTex = NULL; int dupObjTexCount = 0; for (int modelIndex = 0; modelIndex < modelsCount; modelIndex++) { Model &model = models[modelIndex]; for (int meshIndex = model.mStart; meshIndex < model.mStart + model.mCount; meshIndex++) { Mesh &mesh = meshes[meshOffsets[meshIndex]]; for (int i = 0; i < mesh.fCount; i++) { Face &f = mesh.faces[i]; // ASSERT(f.colored || f.flags.texture < objectTexturesCount); if (f.flags.texture >= objectTexturesCount) { f.colored = true; } if (f.colored) continue; TextureInfo &t = objectTextures[f.flags.texture]; if (t.type != TEX_TYPE_OBJECT) { if (!dupObjTex) { dupObjTex = new TextureInfo[256]; } int index = 0; while (index < dupObjTexCount) { TextureInfo &ot = dupObjTex[index]; if (ot.tile == t.tile && ot.clut == t.clut && ot.texCoord[0] == t.texCoord[0] && ot.texCoord[1] == t.texCoord[1]) break; index++; } if (index == dupObjTexCount) { ASSERT(index <= 256); dupObjTex[dupObjTexCount] = t; dupObjTex[dupObjTexCount].type = TEX_TYPE_OBJECT; dupObjTexCount++; } f.flags.texture = objectTexturesCount + index; } } } } appendObjectTex(dupObjTex, dupObjTexCount); } static Entity::Type convToInv(Entity::Type type) { for (int j = 0; j < COUNT(ITEM_TO_INV); j++) if (type == ITEM_TO_INV[j].src) { return ITEM_TO_INV[j].dst; } return type; } static Entity::Type convFromInv(Entity::Type type) { for (int j = 0; j < COUNT(ITEM_TO_INV); j++) if (type == ITEM_TO_INV[j].dst) { return ITEM_TO_INV[j].src; } return type; } void initModelIndices() { for (int i = 0; i < entitiesCount; i++) { TR::Entity &e = entities[i]; e.modelIndex = getModelIndex(e.type); } } LevelID getTitleId() const { return TR::getTitleId(version); } LevelID getHomeId() const { return TR::getHomeId(version); } LevelID getStartId() const { return TR::getStartId(version); } LevelID getEndId() const { return TR::getEndId(version); } bool isTitle() const { return id == getTitleId(); } bool isHome() const { return id == getHomeId(); } bool isEnd() const { return id == getEndId(); } bool isCutsceneLevel() const { return TR::isCutsceneLevel(id); } void readFace(Stream &stream, Face &f, bool colored, bool triangle, bool isRoomMesh) { f.triangle = triangle; for (int i = 0; i < (triangle ? 3 : 4); i++) { stream.read(f.vertices[i]); } if (triangle) { f.vertices[3] = 0; } stream.read(f.flags.value); if (!isRoomMesh && (version & (VER_TR4 | VER_TR5))) { stream.read(f.effects.value); } f.colored = colored; } void readRoom(Stream &stream, int roomIndex) { Room &r = rooms[roomIndex]; Room::Data &d = r.data; // room info stream.read(r.info); // room data stream.read(d.size); int startOffset = stream.pos; if (version == VER_TR1_PSX) { stream.seek(2); } // only for TR3 PSX int32 partsCount; int32 parts[16]; if (version == VER_TR3_PSX) { d.vCount = d.tCount = d.rCount = d.fCount = 0; // pre-calculate number of vertices, triangles and rectangles for TR3 PSX format stream.read(partsCount); stream.raw(parts, sizeof(parts)); for (int pIndex = 0; pIndex < partsCount; pIndex++) { stream.setPos(startOffset + parts[pIndex] + 6); uint8 tmpV, tmpT; d.vCount += stream.read(tmpV); d.tCount += stream.read(tmpT); stream.seek(tmpV * 4 + tmpT * 4); // skip vertices and triangles int q = 0; uint32 info; while (1) { if (!q) { stream.read(info); q = 3; } uint32 texture = info & 0x03FF; if (texture == 0x03FF) // ending flag break; info >>= 10; stream.seek(4); // skip rectangle indices d.rCount++; q--; } } ASSERT(int(d.size * 2) >= stream.pos - startOffset); stream.setPos(startOffset + d.size * 2); d.fCount = d.rCount + d.tCount; d.faces = d.fCount ? new Face[d.fCount] : NULL; d.vertices = d.vCount ? new Room::Data::Vertex[d.vCount] : NULL; d.vCount = d.fCount = 0; } else { d.vertices = stream.read(d.vCount) ? new Room::Data::Vertex[d.vCount] : NULL; } if (version == VER_TR3_PSX) { for (int pIndex = 0; pIndex < partsCount; pIndex++) { stream.setPos(startOffset + parts[pIndex] + 4); uint8 rIndex, rFlags; stream.read(rIndex); // room index stream.read(rFlags); // room flags ASSERT(rIndex == roomIndex); r.flags.value = rFlags; uint8 vCount, tCount; stream.read(vCount); stream.read(tCount); int32 vStart = d.vCount; // read vertices for (uint8 i = 0; i < vCount; i++) { Room::Data::Vertex &v = d.vertices[d.vCount++]; struct { uint32 z:5, y:5, x:5, color:15, flags:2; } cv; stream.raw(&cv, sizeof(cv)); //ASSERT(cv.attribute == 0); v.pos.x = (cv.x << 10); v.pos.y = (cv.y << 8) + r.info.yTop; v.pos.z = (cv.z << 10); v.attributes = 0; v.color = Color16(cv.color); } // read triangles for (uint8 i = 0; i < tCount; i++) { struct { uint32 i0:7, i1:7, i2:7, texture:10, unknown:1; } t; stream.raw(&t, sizeof(t)); ASSERT(t.unknown == 0); Face &f = d.faces[d.fCount++]; f.flags.texture = t.texture; f.triangle = true; f.vertices[0] = vStart + t.i0; f.vertices[1] = vStart + t.i1; f.vertices[2] = vStart + t.i2; f.vertices[3] = 0; ASSERT(f.vertices[0] < d.vCount); ASSERT(f.vertices[1] < d.vCount); ASSERT(f.vertices[2] < d.vCount); } // read rectangles int q = 0; uint32 info; while (1) { if (!q) { stream.read(info); q = 3; } uint32 texture = info & 0x03FF; if (texture == 0x3FF) break; info >>= 10; struct { uint32 i0:7, i1:7, i2:7, i3:7, unknown:4; } r; stream.raw(&r, sizeof(r)); ASSERT(r.unknown == 0); Face &f = d.faces[d.fCount++]; f.flags.texture = texture; f.vertices[0] = vStart + r.i0; f.vertices[1] = vStart + r.i1; f.vertices[2] = vStart + r.i2; f.vertices[3] = vStart + r.i3; ASSERT(f.vertices[0] < d.vCount); ASSERT(f.vertices[1] < d.vCount); ASSERT(f.vertices[2] < d.vCount); ASSERT(f.vertices[3] < d.vCount); q--; } } } else { for (int i = 0; i < d.vCount; i++) { Room::Data::Vertex &v = d.vertices[i]; uint16 lighting; if (version == VER_TR2_PSX) { struct { uint32 lighting:8, attributes:8, z:5, y:5, x:5, w:1; } cv; stream.raw(&cv, sizeof(cv)); v.pos.x = (cv.x << 10); v.pos.y = (cv.y << 8) + r.info.yTop; v.pos.z = (cv.z << 10); lighting = cv.lighting; v.attributes = cv.attributes; lighting = 0x1FFF - (lighting << 5); // TODO: calc lighting by lighting = [0..255] and mode = [0..31] values } else { stream.read(v.pos.x); stream.read(v.pos.y); stream.read(v.pos.z); stream.read(lighting); if (version == VER_TR2_PC || version == VER_TR3_PC || version == VER_TR4_PC) { stream.read(v.attributes); } if (version == VER_TR2_PC) { stream.read(lighting); // real lighting value } if (version == VER_TR3_PC || version == VER_TR4_PC) { Color16 color; stream.read(color.value); v.color = color; } } if (version == VER_TR1_PSX || version == VER_TR3_PSX) { lighting = 0x1FFF - (lighting << 5); // convert vertex luminance from PSX to PC format } if ((version & VER_VERSION) < VER_TR3) { // lighting to color conversion int value = clamp((lighting > 0x1FFF) ? 255 : (255 - (lighting >> 5)), 0, 255); v.color.r = v.color.g = v.color.b = value; v.color.a = 255; } } if (version == VER_TR2_PSX) { stream.seek(2); } int tmp = stream.pos; if (version == VER_TR2_PSX) { stream.read(d.rCount); stream.seek(sizeof(uint16) * d.rCount); if ((stream.pos - startOffset) % 4) stream.seek(2); stream.seek(sizeof(uint16) * 4 * d.rCount); } else { stream.seek(stream.read(d.rCount) * FACE4_SIZE); // uint32 colored (not existing in file) } stream.read(d.tCount); stream.setPos(tmp); d.fCount = d.rCount + d.tCount; d.faces = d.fCount ? new Face[d.fCount] : NULL; int idx = 0; int16 tmpCount; stream.read(tmpCount); ASSERT(tmpCount == d.rCount); if (version == VER_TR2_PSX) { for (int i = 0; i < d.rCount; i++) stream.raw(&d.faces[i].flags.value, sizeof(uint16)); if ((stream.pos - startOffset) % 4) { stream.seek(2); } for (int i = 0; i < d.rCount; i++) { Face &f = d.faces[i]; stream.raw(f.vertices, sizeof(uint16) * 4); f.vertices[0] >>= 2; f.vertices[1] >>= 2; f.vertices[2] >>= 2; f.vertices[3] >>= 2; } } else { for (int i = 0; i < d.rCount; i++) { readFace(stream, d.faces[idx++], false, false, true); } } stream.read(tmpCount); ASSERT(tmpCount == d.tCount); if (version == VER_TR2_PSX) { stream.seek(2); for (int i = 0; i < d.tCount; i++) { Face &f = d.faces[d.rCount + i]; stream.raw(&f.flags.value, sizeof(uint16)); stream.raw(f.vertices, sizeof(uint16) * 3); f.vertices[0] >>= 2; f.vertices[1] >>= 2; f.vertices[2] >>= 2; f.vertices[3] = 0; f.triangle = true; } } else { for (int i = 0; i < d.tCount; i++) { readFace(stream, d.faces[idx++], false, true, true); } } } if (version & VER_PSX) { // swap indices (quad strip -> quad list) only for PSX version for (int j = 0; j < d.fCount; j++) { if (!d.faces[j].triangle) { swap(d.faces[j].vertices[2], d.faces[j].vertices[3]); } } } // room sprites if (version == VER_TR2_PSX || version == VER_TR3_PSX) { // there is no room sprites d.sprites = NULL; d.sCount = 0; } else { stream.read(d.sprites, stream.read(d.sCount)); } if (version == VER_TR3_PSX && partsCount != 0) { stream.seek(4); // skip unknown shit } ASSERT(int(d.size * 2) >= stream.pos - startOffset); stream.setPos(startOffset + d.size * 2); // portals stream.read(r.portals, stream.read(r.portalsCount)); if (version == VER_TR2_PSX || version == VER_TR3_PSX) { for (int i = 0; i < r.portalsCount; i++) { r.portals[i].vertices[0].y += r.info.yTop; r.portals[i].vertices[1].y += r.info.yTop; r.portals[i].vertices[2].y += r.info.yTop; r.portals[i].vertices[3].y += r.info.yTop; } } // sectors stream.read(r.zSectors); stream.read(r.xSectors); r.sectors = (r.zSectors * r.xSectors > 0) ? new Room::Sector[r.zSectors * r.xSectors] : NULL; for (int i = 0; i < r.zSectors * r.xSectors; i++) { Room::Sector &s = r.sectors[i]; stream.read(s.floorIndex); stream.read(s.boxIndex); stream.read(s.roomBelow); stream.read(s.floor); stream.read(s.roomAbove); stream.read(s.ceiling); if (version & (VER_TR1 | VER_TR2)) { s.material = 0; } else { s.material = s.boxIndex & 0x0F; s.boxIndex = s.boxIndex >> 4; if (s.boxIndex == 2047) { s.boxIndex = 0; // TODO TR3 slide box indices } } } // ambient light luminance stream.read(r.ambient); if (version != VER_TR3_PSX) { if (version & (VER_TR2 | VER_TR3 | VER_TR4)) stream.read(r.ambient2); if (version & VER_TR2) stream.read(r.lightMode); } else { r.ambient = 0x1FFF - r.ambient; stream.read(r.ambient2); } // lights r.lights = stream.read(r.lightsCount) ? new Room::Light[r.lightsCount] : NULL; for (int i = 0; i < r.lightsCount; i++) { Room::Light &light = r.lights[i]; stream.read(light.x); stream.read(light.y); stream.read(light.z); uint16 intensity; if (version & (VER_TR3 | VER_TR4)) { stream.read(light.color); stream.read(light.type); } if (version & VER_TR4) { uint8 unknown; stream.read(unknown); //ASSERT(unknown == 0x00 || unknown == 0xFF); uint8 byteIntensity; intensity = stream.read(byteIntensity); stream.read(light.in); stream.read(light.out); stream.read(light.length); stream.read(light.cutoff); stream.read(light.dir); light.radius = uint32(light.length); } else { stream.read(intensity); } if (version == VER_TR1_PSX) { stream.seek(2); } if (version & (VER_TR2 | VER_TR3)) { stream.seek(2); // intensity2 } if (version != VER_TR4_PC) { stream.read(light.radius); } if (version & VER_TR2) { stream.seek(4); // radius2 } if ((version & VER_VERSION) < VER_TR3) { int value = clamp((intensity > 0x1FFF) ? 0 : (intensity >> 5), 0, 255); light.color.r = light.color.g = light.color.b = value; } light.intensity = intensity; if (version == VER_TR3_PSX) { light.radius >>= 2; } light.radius *= 2; } // meshes stream.read(r.meshesCount); r.meshes = r.meshesCount ? new Room::Mesh[r.meshesCount] : NULL; for (int i = 0; i < r.meshesCount; i++) { Room::Mesh &m = r.meshes[i]; stream.read(m.x); stream.read(m.y); stream.read(m.z); stream.read(m.rotation.value); if (version & (VER_TR3 | VER_TR4)) { Color16 color; stream.read(color.value); m.color = color; stream.seek(2); } else { if (version & VER_TR2) { stream.seek(2); } uint16 intensity; stream.read(intensity); if ((version & VER_VERSION) < VER_TR3) { int value = clamp((intensity > 0x1FFF) ? 255 : (255 - (intensity >> 5)), 0, 255); m.color.r = m.color.g = m.color.b = value; m.color.a = 0; } } stream.read(m.meshID); if (version == VER_TR1_PSX) { stream.seek(2); // skip padding } } // misc flags stream.read(r.alternateRoom); stream.read(r.flags.value); if (version & (VER_TR3 | VER_TR4)) { stream.read(r.waterScheme); stream.read(r.reverbType); stream.read(r.filter); } r.dynLightsCount = 0; } void initMesh(int mIndex, Entity::Type type = Entity::NONE) { int offset = meshOffsets[mIndex]; for (int i = 0; i < meshesCount; i++) { if (meshes[i].offset == offset) { return; } } Stream stream(NULL, &meshData[offset / 2], 1024 * 1024); Mesh &mesh = meshes[meshesCount++]; mesh.offset = offset; uint32 fOffset = 0xFFFFFFFF; if (type == Entity::SKY && version == VER_TR3_PSX) { mesh.center.x = mesh.center.y = mesh.center.z = mesh.radius = mesh.tCount = 0; mesh.flags.value = 0x80; stream.read(mesh.vCount); stream.read(mesh.rCount); } else { stream.read(mesh.center); stream.read(mesh.radius); if (version == VER_TR3_PSX) { uint8 tmp; uint16 tmpOffset; mesh.vCount = stream.read(tmp); mesh.flags.value = stream.read(tmp); stream.read(tmpOffset); fOffset = stream.pos + tmpOffset; } else { stream.read(mesh.flags.value); stream.read(mesh.vCount); } if (version == VER_TR1_SAT) { mesh.center.x = swap16(mesh.center.x); mesh.center.y = swap16(mesh.center.y); mesh.center.z = swap16(mesh.center.z); mesh.radius = swap16(mesh.radius); mesh.flags.value = swap16(mesh.flags.value); mesh.vCount = swap16(mesh.vCount); } } switch (version) { case VER_TR1_SAT : { mesh.vertices = new Mesh::Vertex[mesh.vCount]; for (int i = 0; i < mesh.vCount; i++) { short4 &c = mesh.vertices[i].coord; c.x = stream.readBE16(); c.y = stream.readBE16(); c.z = stream.readBE16(); } int16 nCount = stream.readBE16(); ASSERT(mesh.vCount == abs(nCount)); for (int i = 0; i < mesh.vCount; i++) { short4 &c = mesh.vertices[i].coord; short4 &n = mesh.vertices[i].normal; if (nCount > 0) { // normal n.x = stream.readBE16(); n.y = stream.readBE16(); n.z = stream.readBE16(); n.w = 1; c.w = 0x1FFF; } else { // intensity c.w = stream.readBE16(); n = short4( 0, 0, 0, 0 ); } } mesh.fCount = stream.readBE16(); mesh.faces = new Face[mesh.fCount]; mesh.tCount = mesh.rCount = 0; enum { TYPE_T_TEX_UNK = 2, TYPE_T_COLOR = 4, TYPE_T_TEX = 8, TYPE_T_TEX_TRANSP = 16, TYPE_R_COLOR = 5, TYPE_R_TEX = 9, TYPE_R_TRANSP = 17, TYPE_R_FLIP_TEX = 49, TYPE_R_FLIP_TRANSP = 57, }; TextureInfo *textures; int texturesCount; int texturesBaseCount; if (itemTexturesCount && Entity::isInventoryItem(type)) { textures = itemTextures; texturesCount = itemTexturesCount; texturesBaseCount = itemTexturesBaseCount; } else { textures = objectTextures; texturesCount = objectTexturesCount; texturesBaseCount = objectTexturesBaseCount; } int fIndex = 0; uint16 typesCount = stream.readBE16(); for (int j = 0; j < typesCount; j++) { uint16 type = stream.readBE16(); uint16 count = stream.readBE16(); for (int k = 0; k < count; k++) { ASSERT(fIndex < mesh.fCount); Face &f = mesh.faces[fIndex++]; switch (type) { case TYPE_T_COLOR : f.colored = true; case TYPE_T_TEX_UNK : case TYPE_T_TEX : case TYPE_T_TEX_TRANSP : f.triangle = true; f.vertices[0] = (stream.readBE16() >> 5); f.vertices[1] = (stream.readBE16() >> 5); f.vertices[2] = (stream.readBE16() >> 5); f.vertices[3] = 0; f.flags.value = stream.readBE16(); ASSERT(f.vertices[0] < mesh.vCount && f.vertices[1] < mesh.vCount && f.vertices[2] < mesh.vCount); mesh.tCount++; if (!f.colored) { union { struct { uint16 texture:12, flip:4; }; uint16 value; } tri; tri.value = f.flags.value; f.flags.value = tri.texture; f.flip = tri.flip; if (textures == itemTextures) f.flags.value = getItemTexureByIndex(f.flags.value); f.flags.value += texturesBaseCount; if (f.flip != 0 && f.flip != 2 && f.flip != 4 && f.flip != 6 && f.flip != 8 && f.flip != 10 && f.flip != 12 && f.flip != 14) { // TODO puma flip 14 bug ASSERT(false); f.colored = true; f.flags.value = 0x00FF; } if (f.flip == 6 || f.flip == 12) { // 3 bit f.flags.value += texturesBaseCount + texturesBaseCount + texturesBaseCount; } if (f.flip == 4 || f.flip == 14) { f.flags.value += texturesBaseCount + texturesBaseCount; } if (f.flip == 2 || f.flip == 8) { f.flags.value += texturesBaseCount; } } break; case TYPE_R_COLOR : f.colored = true; case TYPE_R_FLIP_TEX : case TYPE_R_FLIP_TRANSP : f.flip = 1; case TYPE_R_TEX : case TYPE_R_TRANSP : f.vertices[0] = (stream.readBE16() >> 5); f.vertices[1] = (stream.readBE16() >> 5); f.vertices[2] = (stream.readBE16() >> 5); f.vertices[3] = (stream.readBE16() >> 5); f.flags.value = stream.readBE16(); ASSERT(f.vertices[0] < mesh.vCount && f.vertices[1] < mesh.vCount && f.vertices[2] < mesh.vCount && f.vertices[3] < mesh.vCount); mesh.rCount++; f.flip = (type == TYPE_R_FLIP_TEX || type == TYPE_R_FLIP_TRANSP); if (!f.colored) { ASSERT(f.flags.value % 16 == 0); f.flags.value /= 16; if (textures == itemTextures) f.flags.value = getItemTexureByIndex(f.flags.value); } break; default : LOG("! unknown face type: %d\n", type); ASSERT(false); } ASSERT(f.colored || f.flags.value < texturesCount); if (type == TYPE_R_TRANSP || type == TYPE_R_FLIP_TRANSP || type == TYPE_T_TEX_TRANSP) textures[f.flags.texture].attribute = 1; } } ASSERT(fIndex == mesh.fCount); break; } case VER_TR1_PC : case VER_TR2_PC : case VER_TR3_PC : case VER_TR4_PC : case VER_TR5_PC : { mesh.vertices = new Mesh::Vertex[mesh.vCount]; for (int i = 0; i < mesh.vCount; i++) { short4 &c = mesh.vertices[i].coord; stream.read(c.x); stream.read(c.y); stream.read(c.z); } int16 nCount; stream.read(nCount); ASSERT(mesh.vCount == abs(nCount)); for (int i = 0; i < mesh.vCount; i++) { short4 &c = mesh.vertices[i].coord; short4 &n = mesh.vertices[i].normal; if (nCount > 0) { // normal stream.read(n.x); stream.read(n.y); stream.read(n.z); n.w = 1; c.w = 0x1FFF; } else { // intensity stream.read(c.w); n = short4( 0, 0, 0, 0 ); } } uint16 rCount, tCount, crCount = 0, ctCount = 0; int faceSize4 = FACE4_SIZE; int faceSize3 = FACE3_SIZE; if (version & (VER_TR4 | VER_TR5)) { faceSize4 += 2; faceSize3 += 2; } int tmp = stream.pos; stream.seek(stream.read(rCount) * faceSize4); // uint32 colored (not existing in file) stream.seek(stream.read(tCount) * faceSize3); if (!(version & (VER_TR4 | VER_TR5))) { stream.seek(stream.read(crCount) * faceSize4); stream.seek(stream.read(ctCount) * faceSize3); } stream.setPos(tmp); mesh.rCount = rCount + crCount; mesh.tCount = tCount + ctCount; mesh.fCount = mesh.rCount + mesh.tCount; mesh.faces = mesh.fCount ? new Face[mesh.fCount] : NULL; int idx = 0; stream.seek(sizeof(rCount)); for (int i = 0; i < rCount; i++) readFace(stream, mesh.faces[idx++], false, false, false); stream.seek(sizeof(tCount)); for (int i = 0; i < tCount; i++) readFace(stream, mesh.faces[idx++], false, true, false); if (!(version & (VER_TR4 | VER_TR5))) { stream.seek(sizeof(crCount)); for (int i = 0; i < crCount; i++) readFace(stream, mesh.faces[idx++], true, false, false); stream.seek(sizeof(ctCount)); for (int i = 0; i < ctCount; i++) readFace(stream, mesh.faces[idx++], true, true, false); } #ifdef _DEBUG for (int i = 0; i < mesh.fCount; i++) { Face &f = mesh.faces[i]; for (int j = 0; j < (f.triangle ? 3 : 4); j++) { ASSERT(f.vertices[j] < mesh.vCount); } } #endif break; } case VER_TR1_PSX : case VER_TR2_PSX : { int nCount = mesh.vCount; mesh.vCount = abs(mesh.vCount); mesh.vertices = new Mesh::Vertex[mesh.vCount]; for (int i = 0; i < mesh.vCount; i++) stream.read(mesh.vertices[i].coord); for (int i = 0; i < mesh.vCount; i++) { short4 &c = mesh.vertices[i].coord; short4 &n = mesh.vertices[i].normal; if (nCount > 0) { // normal stream.read(n); n.w = 1; c.w = 0x1FFF; } else { // intensity stream.read(c.w); n = short4( 0, 0, 0, 0 ); } } if ((version & VER_TR2) && nCount > 0) { // TODO probably for unused meshes only but need to check uint16 crCount = 0, ctCount = 0; stream.read(crCount); stream.seek((FACE4_SIZE + 2) * crCount); stream.read(ctCount); stream.seek((FACE3_SIZE + 2) * ctCount); } int tmp = stream.pos; stream.seek(stream.read(mesh.rCount) * FACE4_SIZE); // uint32 colored (not existing in file) stream.seek(stream.read(mesh.tCount) * FACE3_SIZE); stream.setPos(tmp); mesh.fCount = mesh.rCount + mesh.tCount; mesh.faces = mesh.fCount ? new Face[mesh.fCount] : NULL; int idx = 0; stream.seek(sizeof(mesh.rCount)); for (int i = 0; i < mesh.rCount; i++) readFace(stream, mesh.faces[idx++], false, false, false); stream.seek(sizeof(mesh.tCount)); for (int i = 0; i < mesh.tCount; i++) readFace(stream, mesh.faces[idx++], false, true, false); if (!mesh.fCount) LOG("! warning: mesh %d has no geometry with %d vertices\n", meshesCount - 1, mesh.vCount); //ASSERT(mesh.rCount != 0 || mesh.tCount != 0); for (int i = 0; i < mesh.fCount; i++) { Face &f = mesh.faces[i]; #ifndef SPLIT_BY_TILE f.colored = (f.flags.texture < 256) ? true : false; #endif if (version == VER_TR2_PSX) { f.vertices[0] >>= 3; f.vertices[1] >>= 3; f.vertices[2] >>= 3; f.vertices[3] >>= 3; } } break; } case VER_TR3_PSX : { mesh.tCount = 0; mesh.rCount = 0; mesh.fCount = 0; if (!mesh.vCount) { mesh.vertices = NULL; mesh.faces = NULL; break; } mesh.vertices = new Mesh::Vertex[mesh.vCount]; for (int i = 0; i < mesh.vCount; i++) stream.read(mesh.vertices[i].coord); if (mesh.flags.value & 0x80) { // static mesh without normals for (int i = 0; i < mesh.vCount; i++) mesh.vertices[i].normal = short4( 0, 0, 0, 0 ); } else { for (int i = 0; i < mesh.vCount; i++) stream.read(mesh.vertices[i].normal); } if (type != Entity::SKY) { ASSERT(stream.pos == fOffset); stream.setPos(fOffset); stream.read(mesh.tCount); stream.read(mesh.rCount); } mesh.fCount = mesh.rCount + mesh.tCount; mesh.faces = mesh.fCount ? new Face[mesh.fCount] : NULL; // read triangles int idx = 0; uint32 info; for (int i = 0; i < mesh.tCount; i++) { if (!(i % 4)) stream.read(info); struct { uint32 i0:8, i1:8, i2:8, tex:8; } r; stream.raw(&r, sizeof(r)); Face &f = mesh.faces[idx++]; f.flags.texture = (info & 0xFF) | (r.tex << 8); f.triangle = true; f.vertices[0] = r.i0; f.vertices[1] = r.i1; f.vertices[2] = r.i2; f.vertices[3] = 0; info >>= 8; } // read rectangles for (int i = 0; i < mesh.rCount; i++) { if (!(i % 2)) stream.read(info); Face &f = mesh.faces[idx++]; f.flags.texture = info & 0xFFFF; struct { uint32 i0:8, i1:8, i2:8, i3:8; } r; stream.raw(&r, sizeof(r)); f.vertices[0] = r.i0; f.vertices[1] = r.i1; f.vertices[2] = r.i3; f.vertices[3] = r.i2; info >>= 16; } break; } default : ASSERT(false); } #define RECALC_ZERO_NORMALS(mesh, face)\ int fn = -1;\ for (int j = 0; j < (face.triangle ? 3 : 4); j++) {\ Mesh::Vertex &v = mesh.vertices[face.vertices[j]];\ short4 &n = v.normal;\ if (!(n.x | n.y | n.z)) {\ if (fn > -1) {\ n = mesh.vertices[face.vertices[fn]].normal;\ continue;\ }\ vec3 o = mesh.vertices[face.vertices[0]].coord;\ vec3 a = o - mesh.vertices[face.vertices[1]].coord;\ vec3 b = o - mesh.vertices[face.vertices[2]].coord;\ o = b.cross(a).normal() * 16300.0f;\ n.x = (int)o.x;\ n.y = (int)o.y;\ n.z = (int)o.z;\ fn = j;\ }\ } // recalc zero normals for (int i = 0; i < mesh.fCount; i++) { Face &f = mesh.faces[i]; RECALC_ZERO_NORMALS(mesh, f); } #undef RECALC_ZERO_NORMALS } void remapMeshOffsetsToIndices() { for (int i = 0; i < meshOffsetsCount; i++) { int index = -1; for (int j = 0; j < meshesCount; j++) if (meshes[j].offset == meshOffsets[i]) { index = j; break; } meshOffsets[i] = index; } } void readObjectTex(Stream &stream, TextureInfo &t, TextureType type = TEX_TYPE_OBJECT) { #define SET_PARAMS(t, d, c) {\ t.clut = c;\ t.tile = d.tile;\ t.attribute = d.attribute;\ t.animated = false;\ t.texCoord[0] = t.texCoordAtlas[0] = short2( d.x0, d.y0 );\ t.texCoord[1] = t.texCoordAtlas[1] = short2( d.x1, d.y1 );\ t.texCoord[2] = t.texCoordAtlas[2] = short2( d.x2, d.y2 );\ t.texCoord[3] = t.texCoordAtlas[3] = short2( d.x3, d.y3 );\ } t.type = t.dataType = type; switch (version) { case VER_TR1_SAT : { struct { uint16 attribute; uint16 tile; uint16 clut; uint8 w, h; uint8 x0, y0; uint8 x1, y1; uint8 x2, y2; uint8 x3, y3; } d; t.index = 0; if (type == TEX_TYPE_ITEM) t.index = stream.readBE16(); d.attribute = 0; d.tile = stream.readBE16(); // offset to 4-bit indices t.sub[0] = stream.readBE16(); t.sub[1] = stream.readBE16(); t.sub[2] = stream.readBE16(); t.sub[3] = stream.readBE16(); d.clut = stream.readBE16(); // offset to color palette d.w = stream.read() * 8; d.h = stream.read(); t.i5 = stream.readBE16(); if (type == TEX_TYPE_ROOM) { // for room tiles we will use fullres TSUB textures instead of mips TQTR d.w *= 2; d.h *= 2; } if (type == TEX_TYPE_ITEM) ASSERTV(stream.readBE16() == t.i5); d.x0 = d.y0 = d.x3 = d.y1 = 0; d.x1 = d.x2 = max(0, d.w - 1); d.y2 = d.y3 = max(0, d.h - 1); SET_PARAMS(t, d, d.clut); //if (type == TEX_TYPE_ITEM) { // LOG("%d %d | %d %d %d %d | %d %d %dx%d | %d\n", t.index, t.tile, t.sub[0], t.sub[1], t.sub[2], t.sub[3], t.clut, t.i5, d.w, d.h, bi); //} break; } case VER_TR1_PC : case VER_TR2_PC : case VER_TR3_PC : { struct { uint16 attribute; uint16 tile:14, :2; uint8 xh0, x0, yh0, y0; uint8 xh1, x1, yh1, y1; uint8 xh2, x2, yh2, y2; uint8 xh3, x3, yh3, y3; } d; stream.raw(&d, sizeof(d)); SET_PARAMS(t, d, 0); break; } case VER_TR4_PC : case VER_TR5_PC : { struct { uint16 attribute; uint16 tile:14, :2; uint16 flags; uint8 xh0, x0, yh0, y0; uint8 xh1, x1, yh1, y1; uint8 xh2, x2, yh2, y2; uint8 xh3, x3, yh3, y3; } d; struct { uint32 U, V, W, H; } duv; stream.raw(&d, sizeof(d)); stream.raw(&duv, sizeof(duv)); SET_PARAMS(t, d, 0); break; } case VER_TR1_PSX : case VER_TR2_PSX : case VER_TR3_PSX : { struct { uint8 x0, y0; uint16 clut; uint8 x1, y1; uint16 tile:14, :2; uint8 x2, y2; uint16 unknown2; uint8 x3, y3; uint16 attribute; } d; stream.raw(&d, sizeof(d)); if (version == VER_TR3_PSX) { if (d.attribute == 0) d.attribute = 0; else if (d.attribute == 2) d.attribute = 2; else if (d.attribute == 0xFFFF) d.attribute = 1; else { //ASSERT(false); d.attribute = 0; } } SET_PARAMS(t, d, d.clut); break; } default : ASSERT(false); } #undef SET_PARAMS } void readObjectTex(Stream &stream) { objectTextures = stream.read(objectTexturesCount) ? new TextureInfo[objectTexturesCount] : NULL; for (int i = 0; i < objectTexturesCount; i++) { readObjectTex(stream, objectTextures[i]); } } void readSpriteTex(Stream &stream, TextureInfo &t) { #define SET_PARAMS(t, d, c) {\ t.clut = c;\ t.tile = d.tile;\ t.l = d.l;\ t.t = d.t;\ t.r = d.r;\ t.b = d.b;\ } t.type = TEX_TYPE_SPRITE; t.dataType = TEX_TYPE_SPRITE; t.attribute = 1; switch (version) { case VER_TR1_SAT : { struct { uint16 tile; uint16 clut; uint8 w, h; int16 l, t, r, b; } d; d.tile = stream.readBE16(); // offset to 4-bit indices d.clut = stream.readBE16() - d.tile; // offset to 16-bit color table (16 colors) d.w = stream.read(); // texture width div 8 d.h = stream.read(); // texture height d.l = stream.readBE16(); d.t = stream.readBE16(); d.r = stream.readBE16(); d.b = stream.readBE16(); ASSERTV((int16)stream.readBE16() == d.b); SET_PARAMS(t, d, d.clut); t.texCoord[0] = t.texCoordAtlas[0] = short2( 0, 0 ); t.texCoord[1] = t.texCoordAtlas[1] = short2( ((int16)d.w << 3) - 1, ((int16)d.h) - 1 ); break; } case VER_TR1_PC : case VER_TR2_PC : case VER_TR3_PC : case VER_TR4_PC : case VER_TR5_PC : { struct { uint16 tile; uint8 u, v; uint16 w, h; int16 l, t, r, b; } d; stream.raw(&d, sizeof(d)); SET_PARAMS(t, d, 0); t.texCoord[0] = t.texCoordAtlas[0] = short2( d.u, d.v ); t.texCoord[1] = t.texCoordAtlas[1] = short2( (uint8)(d.u + (d.w >> 8)), (uint8)(d.v + (d.h >> 8)) ); break; } case VER_TR1_PSX : case VER_TR2_PSX : case VER_TR3_PSX : { struct { int16 l, t, r, b; uint16 clut; uint16 tile; uint8 u0, v0; uint8 u1, v1; } d; stream.raw(&d, sizeof(d)); SET_PARAMS(t, d, d.clut); t.texCoord[0] = t.texCoordAtlas[0] = short2( d.u0, d.v0 ); t.texCoord[1] = t.texCoordAtlas[1] = short2( d.u1, d.v1 ); break; } default : ASSERT(false); } #undef SET_PARAMS } void readSpriteTex(Stream &stream) { spriteTextures = stream.read(spriteTexturesCount) ? new TextureInfo[spriteTexturesCount] : NULL; for (int i = 0; i < spriteTexturesCount; i++) readSpriteTex(stream, spriteTextures[i]); spriteSequences = stream.read(spriteSequencesCount) ? new SpriteSequence[spriteSequencesCount] : NULL; for (int i = 0; i < spriteSequencesCount; i++) { SpriteSequence &s = spriteSequences[i]; uint16 type; stream.read(type); s.type = Entity::remap(version, Entity::Type(type)); stream.read(s.unused); stream.read(s.sCount); stream.read(s.sStart); s.sCount = -s.sCount; s.transp = 1; } // remove unavailable sprites (check EGYPT.PHD) for (int roomIndex = 0; roomIndex < roomsCount; roomIndex++) { Room::Data &data = rooms[roomIndex].data; int i = 0; while (i < data.sCount) if (data.sprites[i].vertexIndex >= data.vCount || data.sprites[i].texture >= spriteTexturesCount) { LOG("! room %d has wrong sprite %d (v:%d/%d t:%d/%d)\n", roomIndex, i, data.sprites[i].vertexIndex, data.vCount, data.sprites[i].texture, spriteTexturesCount); ASSERT(false); data.sprites[i] = data.sprites[--data.sCount]; } else i++; if (!data.sCount && data.sprites) { delete[] data.sprites; data.sprites = NULL; } } } void readAnimTex(Stream &stream) { uint32 animTexBlockSize; stream.read(animTexBlockSize); if (animTexBlockSize) { uint16 *animTexBlock = new uint16[animTexBlockSize]; for (uint32 i = 0; i < animTexBlockSize; i++) { animTexBlock[i] = stream.readLE16(); } uint16 *ptr = animTexBlock; animTexturesCount = *(ptr++); animTextures = animTexturesCount ? new AnimTexture[animTexturesCount] : NULL; for (int i = 0; i < animTexturesCount; i++) { AnimTexture &animTex = animTextures[i]; animTex.count = *(ptr++) + 1; animTex.textures = new uint16[animTex.count]; for (int j = 0; j < animTex.count; j++) animTex.textures[j] = *(ptr++); } delete[] animTexBlock; } if (version & (VER_TR4 | VER_TR5)) { uint8 animTexUVs; stream.read(animTexUVs); } } void readEntities(Stream &stream) { entitiesCount = stream.read(entitiesBaseCount) + MAX_RESERVED_ENTITIES; entities = new Entity[entitiesCount]; for (int i = 0; i < entitiesBaseCount; i++) { Entity &e = entities[i]; uint16 type; e.type = Entity::Type(stream.read(type)); stream.read(e.room); stream.read(e.x); stream.read(e.y); stream.read(e.z); stream.read(e.rotation.value); stream.read(e.intensity); if (version & (VER_TR2 | VER_TR3)) { stream.read(e.intensity2); } if (version & (VER_TR4 | VER_TR5)) { stream.read(e.OCB); } stream.read(e.flags.value); } } void initRoomMeshes() { for (int i = 0; i < roomsCount; i++) { Room &room = rooms[i]; for (int j = 0; j < room.meshesCount; j++) room.meshes[j].meshIndex = getMeshByID(room.meshes[j].meshID); } } void initAnimTex() { for (int i = 0; i < animTexturesCount; i++) { uint8 transp = 0; for (int j = 0; j < animTextures[i].count; j++) { TextureInfo &t = objectTextures[animTextures[i].textures[j]]; t.animated = true; if (t.attribute != 0) transp = t.attribute; } if (transp) { for (int j = 0; j < animTextures[i].count; j++) { TextureInfo &t = objectTextures[animTextures[i].textures[j]]; t.attribute = transp; } } } } void shiftAnimTex() { for (int i = 0; i < animTexturesCount; i++) { AnimTexture &animTex = animTextures[i]; TextureInfo tmp = objectTextures[animTex.textures[0]]; for (int j = 0; j < animTex.count - 1; j++) objectTextures[animTex.textures[j]] = objectTextures[animTex.textures[j + 1]]; objectTextures[animTex.textures[animTex.count - 1]] = tmp; } } void fillObjectTexture32(AtlasTile *dst, const Color32 *data, const short4 &uv, TextureInfo *t) { AtlasColor *ptr = &dst->color[uv.y * 256]; for (int y = uv.y; y < uv.w; y++) { for (int x = uv.x; x < uv.z; x++) { ptr[x] = data[y * 256 + x]; } ptr += 256; } premultiplyAlpha(dst->color, uv); } void fillObjectTexture(AtlasTile *dst, const short4 &uv, TextureInfo *t) { // convert to RGBA switch (version) { case VER_TR1_SAT : { uint32 iOffset = uint32(uint16(t->tile)) << 3; uint32 cOffset = uint32(uint16(t->clut + t->tile)) << 3; uint8 *data = NULL; switch (t->dataType) { case TEX_TYPE_ROOM : data = roomTexturesData; break; case TEX_TYPE_ITEM : data = itemTexturesData; break; case TEX_TYPE_OBJECT : data = objectTexturesData; break; case TEX_TYPE_SPRITE : data = spriteTexturesData; break; } ASSERT(data != NULL); ColorIndex4 *indices = (ColorIndex4*) (data + iOffset); CLUT *clut = (CLUT*) (data + cOffset); int h = uv.w - uv.y; int w = uv.z - uv.x; ASSERT(w <= 256 && h <= 256); for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) { ColorIndex4 *index; if (t->dataType == TEX_TYPE_ROOM) { int iw = w / 2; int ih = h / 2; int ix = x % iw; int iy = y % ih; int offset = uint32(uint16(t->sub[y >= ih ? (x >= iw ? 2 : 3) : (x >= iw ? 1 : 0)])) << 3; offset += (iy * iw + ix) / 2; index = (ColorIndex4*) (tsub + offset); } else index = indices + (y * w + x) / 2; int idx = (x % 2) ? index->a : index->b; ColorCLUT &c = clut->color[idx]; if (t->attribute == 1 && idx == 0) dst->color[y * 256 + x].value = 0; else dst->color[y * 256 + x] = ColorCLUT(swap16(c.value)); } break; } case VER_TR1_PC : { ASSERT(tiles8); ASSERT(palette); AtlasColor *ptr = &dst->color[uv.y * 256]; for (int y = uv.y; y < uv.w; y++) { for (int x = uv.x; x < uv.z; x++) { ASSERT(x >= 0 && y >= 0 && x < 256 && y < 256); uint8 index = tiles8[t->tile].index[y * 256 + x]; if (index != 0) { ptr[x] = palette[index]; } else ptr[x].value = 0; } ptr += 256; } break; } case VER_TR2_PC : case VER_TR3_PC : { ASSERT(tiles16); AtlasColor *ptr = &dst->color[uv.y * 256]; for (int y = uv.y; y < uv.w; y++) { for (int x = uv.x; x < uv.z; x++) { ptr[x] = tiles16[t->tile].color[y * 256 + x]; } ptr += 256; } break; } case VER_TR4_PC : case VER_TR5_PC : { ASSERT(tiles32); AtlasColor *ptr = &dst->color[uv.y * 256]; for (int y = uv.y; y < uv.w; y++) { for (int x = uv.x; x < uv.z; x++) { Color32 c = tiles32[t->tile].color[y * 256 + x]; swap(c.r, c.b); ptr[x] = c; } ptr += 256; } break; } case VER_TR1_PSX : case VER_TR2_PSX : case VER_TR3_PSX : { ASSERT(tiles4); ASSERT(cluts); CLUT &clut = cluts[t->clut]; Tile4 &src = tiles4[t->tile]; for (int y = uv.y; y < uv.w; y++) { for (int x = uv.x; x < uv.z; x++) { dst->color[y * 256 + x] = clut.color[(x % 2) ? src.index[(y * 256 + x) / 2].b : src.index[(y * 256 + x) / 2].a]; } } break; } default : ASSERT(false); } premultiplyAlpha(dst->color, uv); } void premultiplyAlpha(AtlasColor *data, const short4 &uv) { // pre-multiple alpha for (int y = uv.y; y < uv.w; y++) for (int x = uv.x; x < uv.z; x++) { AtlasColor &c = data[y * 256 + x]; #ifdef USE_ATLAS_RGBA16 if (c.a == 0) { c.value = 0; } #else c.r = uint8((uint16(c.r) * c.a) / 255); c.g = uint8((uint16(c.g) * c.a) / 255); c.b = uint8((uint16(c.b) * c.a) / 255); #endif } } // common methods Color32 getColor(int texture) const { switch (version) { case VER_TR1_SAT : return ColorCLUT((uint16)texture); case VER_TR1_PC : #ifdef _GAPI_SW return Color32(texture & 0xFF, 0, 0, 142); #else return palette[texture & 0xFF]; #endif case VER_TR2_PC : case VER_TR3_PC : return palette32[(texture >> 8) & 0xFF]; case VER_TR1_PSX : case VER_TR2_PSX : case VER_TR3_PSX : { ASSERT((texture & 0x7FFF) < 256); TextureInfo &t = objectTextures[texture & 0x7FFF]; int idx = (t.texCoord[0].y * 256 + t.texCoord[0].x) / 2; int part = t.texCoord[0].x % 2; Tile4 &tile = tiles4[t.tile]; CLUT &clut = cluts[t.clut]; return clut.color[part ? tile.index[idx].b : tile.index[idx].a]; } case VER_TR4_PC : break; default : ASSERT(false); } return Color32(255, 0, 255, 255); } Stream* getSampleStream(int index) const { if (!soundOffsets || !soundData) return NULL; uint8 *data = soundData + soundOffsets[index]; uint32 size = 0; switch (version) { case VER_TR1_SAT : size = soundSize[index]; break; case VER_TR1_PC : case VER_TR2_PC : case VER_TR3_PC : case VER_TR4_PC : size = FOURCC(data + 4) + 8; break; // read size from wave header case VER_TR1_PSX : case VER_TR2_PSX : case VER_TR3_PSX : size = soundSize[index]; break; default : ASSERT(false); } return new Stream(NULL, data, size); } int getMeshByID(uint32 id) const { for (int i = 0; i < staticMeshesCount; i++) if (staticMeshes[i].id == id) return i; ASSERT(false); return 0; } int16 getModelIndex(Entity::Type type) const { if (!simpleItems) type = convToInv(type); //#ifndef _DEBUG if ((type >= Entity::AI_GUARD && type <= Entity::AI_CHECK) || (type >= Entity::GLOW_2 && type <= Entity::ENEMY_BAT_SWARM) || type == Entity::WATERFALL || type == Entity::KILL_ALL_TRIGGERS || type == Entity::VIEW_TARGET || type == Entity::SOUND_DOOR_BELL || type == Entity::SOUND_ALARM_BELL || type == Entity::TRIPWIRE) return 0; //#endif if (type == Entity::ENEMY_MUTANT_2 || type == Entity::ENEMY_MUTANT_3) type = Entity::ENEMY_MUTANT_1; // hardcoded mutant models remapping if (((version & VER_TR3)) && (type == Entity::RICOCHET || type == Entity::WATER_SPLASH || type == Entity::BLOOD || type == Entity::FLAME)) { type = Entity::MISC_SPRITES; // TODO TR3 } for (int i = 0; i < modelsCount; i++) if (type == models[i].type) return i + 1; for (int i = 0; i < spriteSequencesCount; i++) if (type == spriteSequences[i].type) return -(i + 1); return 0; } void flipMap() { for (int i = 0; i < roomsCount; i++) if (rooms[i].alternateRoom > -1) { Room &src = rooms[i]; Room &dst = rooms[src.alternateRoom]; swap(src, dst); swap(src.alternateRoom, dst.alternateRoom); } state.flags.flipped = !state.flags.flipped; } void floorSkipCommand(FloorData* &fd, int func) { switch (func) { case FloorData::PORTAL : case FloorData::FLOOR : case FloorData::CEILING : fd++; break; case FloorData::FLOOR_NW_SE_SOLID : case FloorData::FLOOR_NE_SW_SOLID : case FloorData::CEILING_NW_SE_SOLID : case FloorData::CEILING_NE_SW_SOLID : case FloorData::FLOOR_NW_SE_PORTAL_NW : case FloorData::FLOOR_NW_SE_PORTAL_SE : case FloorData::FLOOR_NE_SW_PORTAL_NE : case FloorData::FLOOR_NE_SW_PORTAL_SW : case FloorData::CEILING_NW_SE_PORTAL_NW : case FloorData::CEILING_NW_SE_PORTAL_SE : case FloorData::CEILING_NE_SW_PORTAL_NE : case FloorData::CEILING_NE_SW_PORTAL_SW : fd++; break; case FloorData::TRIGGER : fd++; do {} while (!(*fd++).triggerCmd.end); break; case FloorData::LAVA : case FloorData::CLIMB : case FloorData::MONKEY : case FloorData::MINECART_LEFT : case FloorData::MINECART_RIGHT : break; default : LOG("unknown func to skip: %d\n", func); } } int getNextRoom(const Room::Sector *sector) const { ASSERT(sector); if (!sector->floorIndex) return NO_ROOM; FloorData *fd = &floors[sector->floorIndex]; // floor data always in this order if ( fd->cmd.func == FloorData::FLOOR || fd->cmd.func == FloorData::FLOOR_NW_SE_SOLID || fd->cmd.func == FloorData::FLOOR_NE_SW_SOLID || fd->cmd.func == FloorData::FLOOR_NW_SE_PORTAL_SE || fd->cmd.func == FloorData::FLOOR_NW_SE_PORTAL_NW || fd->cmd.func == FloorData::FLOOR_NE_SW_PORTAL_SW || fd->cmd.func == FloorData::FLOOR_NE_SW_PORTAL_NE) { if (fd->cmd.end) return NO_ROOM; fd += 2; } if ( fd->cmd.func == FloorData::CEILING || fd->cmd.func == FloorData::CEILING_NE_SW_SOLID || fd->cmd.func == FloorData::CEILING_NW_SE_SOLID || fd->cmd.func == FloorData::CEILING_NE_SW_PORTAL_SW || fd->cmd.func == FloorData::CEILING_NE_SW_PORTAL_NE || fd->cmd.func == FloorData::CEILING_NW_SE_PORTAL_SE || fd->cmd.func == FloorData::CEILING_NW_SE_PORTAL_NW) { if (fd->cmd.end) return NO_ROOM; fd += 2; } if (fd->cmd.func == FloorData::PORTAL) return (++fd)->value; return NO_ROOM; } Room::Sector& getSector(int roomIndex, int x, int z, int &dx, int &dz) const { ASSERT(roomIndex >= 0 && roomIndex < roomsCount); Room &room = rooms[roomIndex]; int sx = x - room.info.x; int sz = z - room.info.z; sx = clamp(sx, 0, room.xSectors * 1024 - 1); sz = clamp(sz, 0, room.zSectors * 1024 - 1); dx = sx % 1024; dz = sz % 1024; sx /= 1024; sz /= 1024; return room.sectors[sx * room.zSectors + sz]; } Room::Sector& getSector(int roomIndex, int x, int z, int §orIndex) { ASSERT(roomIndex >= 0 && roomIndex < roomsCount); Room &room = rooms[roomIndex]; x -= room.info.x; z -= room.info.z; x /= 1024; z /= 1024; return room.sectors[sectorIndex = (x * room.zSectors + z)]; } Room::Sector* getSector(int16 &roomIndex, const vec3 &pos) { ASSERT(roomIndex >= 0 && roomIndex <= roomsCount); Room::Sector *sector = NULL; int x = int(pos.x); int y = int(pos.y); int z = int(pos.z); // check horizontal int16 prevRoom = roomIndex; while (1) { // Let's Rock! Room &room = rooms[roomIndex]; int sx = (x - room.info.x) / 1024; int sz = (z - room.info.z) / 1024; sector = room.getSector(sx, sz); int nextRoom = getNextRoom(sector); if (nextRoom == NO_ROOM || nextRoom == prevRoom) break; prevRoom = roomIndex; roomIndex = nextRoom; }; // check vertical while (sector->roomAbove != NO_ROOM && y < sector->ceiling * 256) { Room &room = rooms[roomIndex = sector->roomAbove]; sector = room.getSector((x - room.info.x) / 1024, (z - room.info.z) / 1024); } while (sector->roomBelow != NO_ROOM && y >= sector->floor * 256) { Room &room = rooms[roomIndex = sector->roomBelow]; sector = room.getSector((x - room.info.x) / 1024, (z - room.info.z) / 1024); } return sector; } float getFloor(const Room::Sector *sector, const vec3 &pos, int16 *roomIndex = NULL) { int x = int(pos.x); int z = int(pos.z); int dx = x & 1023; int dz = z & 1023; while (sector->roomBelow != NO_ROOM) { Room &room = rooms[sector->roomBelow]; if (roomIndex) *roomIndex = sector->roomBelow; sector = room.getSector((x - room.info.x) / 1024, (z - room.info.z) / 1024); } int floor = sector->floor * 256; if (!sector->floorIndex) return float(floor); FloorData *fd = &floors[sector->floorIndex]; FloorData::Command cmd; do { cmd = (*fd++).cmd; switch (cmd.func) { case TR::FloorData::FLOOR : case TR::FloorData::FLOOR_NW_SE_SOLID : case TR::FloorData::FLOOR_NE_SW_SOLID : case TR::FloorData::FLOOR_NW_SE_PORTAL_SE : case TR::FloorData::FLOOR_NW_SE_PORTAL_NW : case TR::FloorData::FLOOR_NE_SW_PORTAL_SW : case TR::FloorData::FLOOR_NE_SW_PORTAL_NE : { int sx, sz; if (cmd.func == TR::FloorData::FLOOR) { sx = fd->slantX; sz = fd->slantZ; } else { if (cmd.func == TR::FloorData::FLOOR_NW_SE_SOLID || cmd.func == TR::FloorData::FLOOR_NW_SE_PORTAL_SE || cmd.func == TR::FloorData::FLOOR_NW_SE_PORTAL_NW) { if (dx <= 1024 - dz) { floor += cmd.triangle.b * 256; sx = fd->a - fd->b; sz = fd->c - fd->b; } else { floor += cmd.triangle.a * 256; sx = fd->d - fd->c; sz = fd->d - fd->a; } } else { if (dx <= dz) { floor += cmd.triangle.b * 256; sx = fd->d - fd->c; sz = fd->c - fd->b; } else { floor += cmd.triangle.a * 256; sx = fd->a - fd->b; sz = fd->d - fd->a; } } } fd++; floor -= sx * (sx > 0 ? (dx - 1023) : dx) >> 2; floor -= sz * (sz > 0 ? (dz - 1023) : dz) >> 2; break; } case FloorData::TRIGGER : { fd++; FloorData::TriggerCommand trigCmd; do { trigCmd = (*fd++).triggerCmd; if (trigCmd.action != Action::ACTIVATE) continue; // TODO controller[trigCmd.args]->getFloor(&floor, x, y, z); } while (!trigCmd.end); break; } default : floorSkipCommand(fd, cmd.func); } } while (!cmd.end); return float(floor); } float getCeiling(const Room::Sector *sector, const vec3 &pos) { int x = int(pos.x); int z = int(pos.z); int dx = x & 1023; int dz = z & 1023; ASSERT(sector); while (sector->roomAbove != NO_ROOM) { Room &room = rooms[sector->roomAbove]; sector = room.getSector((x - room.info.x) / 1024, (z - room.info.z) / 1024); } int ceiling = sector->ceiling * 256; if (!sector->floorIndex) return float(ceiling); FloorData *fd = &floors[sector->floorIndex]; FloorData::Command cmd; do { cmd = (*fd++).cmd; switch (cmd.func) { case TR::FloorData::CEILING : case TR::FloorData::CEILING_NE_SW_SOLID : case TR::FloorData::CEILING_NW_SE_SOLID : case TR::FloorData::CEILING_NE_SW_PORTAL_SW : case TR::FloorData::CEILING_NE_SW_PORTAL_NE : case TR::FloorData::CEILING_NW_SE_PORTAL_SE : case TR::FloorData::CEILING_NW_SE_PORTAL_NW : { int sx, sz; if (cmd.func == TR::FloorData::CEILING) { sx = fd->slantX; sz = fd->slantZ; } else { if (cmd.func == TR::FloorData::CEILING_NW_SE_SOLID || cmd.func == TR::FloorData::CEILING_NW_SE_PORTAL_SE || cmd.func == TR::FloorData::CEILING_NW_SE_PORTAL_NW) { if (dx <= 1024 - dz) { ceiling += cmd.triangle.b * 256; sx = fd->c - fd->d; sz = fd->b - fd->c; } else { ceiling += cmd.triangle.a * 256; sx = fd->b - fd->a; sz = fd->a - fd->d; } } else { if (dx <= dz) { ceiling += cmd.triangle.b * 256; sx = fd->b - fd->a; sz = fd->b - fd->c; } else { ceiling += cmd.triangle.a * 256; sx = fd->c - fd->d; sz = fd->a - fd->d; } } } fd++; ceiling -= sx * (sx < 0 ? (dx - 1023) : dx) >> 2; ceiling += sz * (sz > 0 ? (dz - 1023) : dz) >> 2; break; } case FloorData::TRIGGER : { fd++; FloorData::TriggerCommand trigCmd; do { trigCmd = (*fd++).triggerCmd; if (trigCmd.action != Action::ACTIVATE) continue; // TODO controller[trigCmd.args]->getCeiling(&ceiling, x, y, z); } while (!trigCmd.end); break; } default : floorSkipCommand(fd, cmd.func); } } while (!cmd.end); return float(ceiling); } Room::Sector* getWaterLevelSector(int16 &roomIndex, const vec3 &pos) { int x = int(pos.x); int z = int(pos.z); Room *room = &rooms[roomIndex]; Room::Sector *sector = room->getSector((x - room->info.x) / 1024, (z - room->info.z) / 1024); if (room->flags.water) { // go up to the air while (sector->roomAbove != NO_ROOM) { room = &rooms[sector->roomAbove]; if (!room->flags.water) return sector; roomIndex = sector->roomAbove; sector = room->getSector((x - room->info.x) / 1024, (z - room->info.z) / 1024); } } else { // go down to the water while (sector->roomBelow != NO_ROOM) { room = &rooms[roomIndex = sector->roomBelow]; sector = room->getSector((x - room->info.x) / 1024, (z - room->info.z) / 1024); if (room->flags.water) return sector; } } return NULL; } void getWaterInfo(int16 roomIndex, const vec3 &pos, float &level, float &depth) { depth = 0.0f; level = 0.0f; Room::Sector *sector = getWaterLevelSector(roomIndex, pos); if (!sector) return; level = sector->ceiling * 256.0f; if (pos.y < level) depth = pos.y - level; else depth = getFloor(sector, pos) - level; } bool isBlocked(int16 &roomIndex, const vec3 &pos) { Room::Sector *sector = getSector(roomIndex, pos); return pos.y >= getFloor(sector, pos) || pos.y <= getCeiling(sector, pos); } }; // struct Level } #endif