These utilties are run from inside the game, but they require use of your operating system's terminal because they print huge volumes of text. Generally, you want to pipe the game's terminal output to a file. Then after running the script you can open that file in a text editor (such as Notepad or Notepad++ on Windows) to look at it.
Windows terminal tutorial: http://www.cs.princeton.edu/courses/arc ... rompt.html
On Windows, just navigate to the game's directory in the Command Prompt and run:
grimrock2.exe > output_file.txt
Mac OS tutorial: http://blog.teamtreehouse.com/introduct ... mmand-line
Like on Windows, you'll just append " > output_file.txt" to the command you
use to run the game.
Linux tutorial: wine grimrock2.exe > output_file.txt
Then you can open output_file.txt to retrieve the output.
Object and Component Counter
This script will count all of the objects and components in your dungeon and sort them by whether they have minimalSaveState or not as well as how often they appear. This can be a useful diagnostic tool to see if you have excessive numbers of an object in your dungeon, especially problematic when that object lacks minimalSaveState. It will also warn you if you have minimalSaveState objects with components that require (or are at least likely to require) state information.
Unfortunately, you cannot poll minimalSaveState from the scripting interface. As a result, it has to use a lookup table. This table is already filled with all standard and Nex assets. However, if your dungeon has custom objects (and what's the point of a dungeon without custom objects?) you will have to add them to this table. Also, if you have changed any of the standard assets' save states in your mod's asset definitions, you need to remember to change them in this table too.
Some standard assets that you likely want to consider adding minimalSaveState to:
- invisible_wall
- invisible_rocky_wall
- dig_hole
You can copy and paste this code into a script_entity, or paste it in a file and load it in a script_entity as an external source file. Just run the dungeonReport function (e.g. "opt_utils.script.dungeonReport()" in the console if you named the script entity "opt_utils") and it will produce the report. Basic object counts and the total length of object names/ids will appear in a note item in-game, and the full object/component lists will be printed to the terminal and console. (The in-game console text may start to overlap after running this function because it wasn't built to handle this volume of output. Simply run "clear" in the console to fix this.)
Code: Select all
-- False means the object doesn't have minimalSaveState.
-- True means that it does.
-- If you have added any new objects, or changed the save state of any standard
-- objects, remember to add/change the values in this table too!
-- NOTE: As a shortcut, objects with components named "item" or "monster" are
-- assumed to be items or monsters, which should never, ever have minimalSaveState.
-- This way we don't have to add hundreds of item/monster entries to this table.
OBJECT_HAS_MINIMALSAVESTATE = {
-- base.lua
base_floor = false,
base_wall = false,
base_ceiling = false,
base_pillar = false,
base_alcove = false,
base_altar = false,
base_pressure_plate = false,
base_wall_decoration = false,
base_floor_decoration = false,
base_pillar_decoration = false,
base_wall_text = false,
base_stairs_down = false,
base_stairs_up = false,
base_pit = false,
base_pit_trapdoor = false,
base_ceiling_shaft = false,
base_obstacle = false,
base_door = false,
base_door_sparse = false,
base_double_door = false,
base_double_door_sparse = false,
base_secret_door = false,
base_wall_grating = false,
base_item = false,
base_monster = false,
base_monster_group = false,
base_spell = false,
-- beach.lua
beach_ground_01 = true,
beach_rock_wall_01 = true,
beach_rock_wall_02 = true,
beach_rock_wall_04 = true,
beach_rock_pillar_01 = true,
beach_rock_wall_button_01 = false,
beach_rock_blocker_01 = true,
beach_rock_blocker_02 = true,
beach_cattail_wall = true,
grass_planes_01 = true,
beach_statue_01 = true,
beach_statue_02 = true,
beach_statue_03 = true,
beach_door_portcullis = false,
beach_door_wood = false,
beach_wall_text = false,
beach_secret_door = false,
beach_rock_arch = true,
beach_rock_spire = true,
beach_rock_spire_pillar = true,
beach_rock_2x1 = true,
beach_rock_3x1 = true,
beach_rock_3x2 = true,
beach_rock_outside_wall_low = true,
beach_rock_outside_wall_high = true,
beach_rock_1x1 = true,
beach_rock_1x1_low = true,
beach_boulder = true,
beach_rock_arch_small = true,
beach_rock_overhang_1x1 = true,
turtle_nest = true,
beach_puzzle_statue = false,
beach_lock_gold = false,
beach_lock_round = false,
beach_lock_ornate = false,
beach_wall_button = false,
beach_lever = false,
beach_pressure_plate = false,
beach_grass_pressure_plate = false,
pressure_plate_grass = true,
beach_crab = false,
beach_master_gate = false,
beach_shipwreck = true,
beach_shipwreck_land_hull = false,
beach_shipwreck_land_mast = false,
beach_shipwreck_land_parts = true,
beach_rock_shore = true,
beach_rock_border = true,
beach_seaweed_01 = true,
beach_stone_ring = true,
beach_wall_decoration_01 = true,
beach_wall_decoration_02 = true,
beach_wall_decoration_03 = true,
beach_wall_decoration_grass_01 = true,
beach_wall_decoration_grass_02 = true,
beach_sandpile = true,
prison_cage = false,
prison_cage_door = false,
prison_cage_door_breakable = false,
-- beacon_tower.lua
beacon_tower = true,
beacon_furnace = false,
beacon_furnace_rune_balance = true,
beacon_furnace_rune_earth = true,
beacon_furnace_rune_ice = true,
beacon_furnace_rune_air = true,
beacon_furnace_rune_fire = true,
furnace_explosion = false,
-- breakable.lua
barrel_crate_block = false,
barrel_crate_block_damaged = false,
barrel_crate_block_broken = false,
spider_eggs = false,
spider_eggs_broken = false,
beach_cattail_blocker = false,
beach_cattail_damaged = false,
beach_cattail_broken = false,
beach_thicket_01 = false,
beach_thicket_damaged = false,
beach_thicket_broken = false,
forest_thorn_blocker = false,
forest_thorn_damaged = false,
forest_thorn_broken = false,
terracotta_jars_block = false,
terracotta_jars_block_damaged = false,
terracotta_jars_block_broken = false,
castle_wall_cloth = false,
castle_wall_cloth_torn = false,
-- castle.lua
castle_floor_01 = true,
castle_wall_01 = true,
castle_wall_02 = true,
castle_wall_03 = true,
castle_wall_tall_01 = true,
castle_wall_bookshelf_01 = true,
castle_wall_bookshelf_02 = true,
castle_ceiling_light = false,
castle_pillar_01 = true,
castle_pillar_tall_01 = true,
castle_ceiling_01 = true,
castle_ceiling = true,
castle_ceiling_0000 = true,
castle_ceiling_0001 = true,
castle_ceiling_0010 = true,
castle_ceiling_0011 = true,
castle_ceiling_0100 = true,
castle_ceiling_0101 = true,
castle_ceiling_0110 = true,
castle_ceiling_0111 = true,
castle_ceiling_1000 = true,
castle_ceiling_1001 = true,
castle_ceiling_1010 = true,
castle_ceiling_1011 = true,
castle_ceiling_1100 = true,
castle_ceiling_1101 = true,
castle_ceiling_1110 = true,
castle_ceiling_1111 = true,
castle_ceiling_tall_01 = true,
castle_ceiling_strut = true,
castle_wall_arch = true,
castle_door_wood = false,
castle_door_portcullis = false,
castle_wall_grating = false, -- really!
castle_secret_door = false,
castle_tall_wall_corridor_end = true,
castle_pillar_candle_holder = false,
castle_torch_holder = false,
castle_pit = false,
castle_pit_trapdoor = false,
castle_pressure_plate = false,
castle_wall_lever = false,
castle_ceiling_shaft = true,
castle_wall_grating_ornament = false, -- again, really!
beacon_fire = false,
beacon_air = false,
beacon_earth = false,
beacon_water = false,
beacon_balance = false,
castle_ceiling_lantern = false,
castle_bridge = false,
castle_bridge_grating = false,
castle_pillar_end = true,
castle_pillar_light = false,
castle_stairs_up = false,
castle_stairs_down = false,
castle_alcove = false,
castle_wall_text = false,
castle_wall_text_long = false,
castle_wall_button = false,
castle_door_button = false,
castle_arena_floor = true,
castle_arena_tower = true,
castle_arena_merlon = true,
castle_arena_merlon_outer_corner = true,
castle_arena_merlon_inner_corner = true,
castle_arena_stairs_down = false,
castle_wall_outside_01 = true,
castle_gate_outside = true,
castle_wall_outside_corner_01 = true,
castle_wall_outside_tall_01 = true,
castle_tower_01 = true,
castle_tower_tall_01 = true,
castle_facade = true,
castle_sidewings_01 = true,
castle_bridge_outside = true,
castle_bridge_outside_middle_01 = true,
castle_bridge_outside_middle_02 = true,
castle_entrance_door = false,
castle_outside_stairs_up = false,
-- cemetery.lua
tombstone = false,
cemetery_wall_01 = true,
cemetery_fence_01 = true,
cemetery_arch_01 = true,
cemetery_pillar_01 = true,
mausoleum_stairs_down = false,
-- dungeon.lua
dungeon_floor_01 = true,
dungeon_floor_dirt_01 = true,
dungeon_wall_01 = true,
dungeon_wall_02 = true,
dungeon_pillar = true,
dungeon_pillar_high = true,
dungeon_pillar_top = true,
dungeon_ceiling = true,
dungeon_ceiling_0000 = true,
dungeon_ceiling_0001 = true,
dungeon_ceiling_0010 = true,
dungeon_ceiling_0011 = true,
dungeon_ceiling_0100 = true,
dungeon_ceiling_0101 = true,
dungeon_ceiling_0110 = true,
dungeon_ceiling_0111 = true,
dungeon_ceiling_1000 = true,
dungeon_ceiling_1001 = true,
dungeon_ceiling_1010 = true,
dungeon_ceiling_1011 = true,
dungeon_ceiling_1100 = true,
dungeon_ceiling_1101 = true,
dungeon_ceiling_1110 = true,
dungeon_ceiling_1111 = true,
dungeon_ceiling_wall = true,
dungeon_alcove = false,
dungeon_pressure_plate = false,
dungeon_pressure_plate_dirt_floor = false,
dungeon_secret_button_small = false,
dungeon_secret_button_large = false,
dungeon_secret_door = false,
dungeon_pit = false,
dungeon_pit_trapdoor = false,
dungeon_ceiling_shaft = true,
dungeon_ceiling_shaft_0000 = true,
dungeon_ceiling_shaft_0001 = true,
dungeon_ceiling_shaft_0010 = true,
dungeon_ceiling_shaft_0011 = true,
dungeon_ceiling_shaft_0100 = true,
dungeon_ceiling_shaft_0101 = true,
dungeon_ceiling_shaft_0110 = true,
dungeon_ceiling_shaft_0111 = true,
dungeon_ceiling_shaft_1000 = true,
dungeon_ceiling_shaft_1001 = true,
dungeon_ceiling_shaft_1010 = true,
dungeon_ceiling_shaft_1011 = true,
dungeon_ceiling_shaft_1100 = true,
dungeon_ceiling_shaft_1101 = true,
dungeon_ceiling_shaft_1110 = true,
dungeon_ceiling_shaft_1111 = true,
dungeon_door_portcullis = false,
dungeon_door_iron = false,
dungeon_door_iron_barred = false,
dungeon_door_wooden = false,
dungeon_door_wooden_double = false,
dungeon_wall_grating = true,
dungeon_stairs_down = false,
dungeon_stairs_up = false,
dungeon_wall_text = false,
dungeon_wall_text_long = false,
dungeon_door_stone = false,
dungeon_iron_gate = false,
dungeon_wall_ivy_01 = true,
dungeon_wall_ivy_02 = true,
dungeon_support_ceiling_01 = true,
dungeon_pillar_lantern_01 = false,
dungeon_wall_lantern = false,
dungeon_wall_open = true,
dungeon_wall_drain = true,
tiny_spider = false,
tiny_rat = false,
catacomb_alcove_01 = true,
catacomb_alcove_02 = true,
catacomb_alcove_03 = true,
catacomb_alcove_candle_01 = true,
catacomb_alcove_candle_02 = true,
catacomb_altar_01 = false,
catacomb_altar_02 = false,
catacomb_altar_candle_01 = false,
catacomb_ceiling = true,
catacomb_pillar_lantern_01 = false,
catacomb_ceiling_shaft = true,
dungeon_wall_chains_01 = true,
dungeon_wall_chains_02 = true,
dungeon_wall_chains_hooks_01 = true,
dungeon_wall_overlay_grating_01 = true,
dungeon_pillar_chains_01 = true,
dungeon_pillar_chains_02 = true,
dungeon_wall_broken_01 = true,
dungeon_wall_broken_02 = true,
dungeon_wall_height_difference = true,
dungeon_fire_pit = false,
ceiling_roots_01 = true,
dungeon_cave_in = true,
-- forest.lua
forest_ground_01 = true,
forest_heightmap = false,
forest_wall_01 = true,
forest_wall_02 = true,
forest_hedge_01 = true,
forest_pillar_01 = true,
forest_grass_01 = true,
forest_heather = true,
swamp_heather = true,
swamp_heather_low = true,
swamp_grass_01 = true,
forest_oak = true,
forest_oak_cluster = true,
forest_oak_stump = true,
forest_oak_trunk = true,
forest_spruce_01 = true,
forest_spruce_small_01 = true,
forest_spruce_smallest_01 = true,
forest_spruce_sapling_01 = true,
forest_spruce_sapling_02 = true,
forest_spruce_sapling_pillar = true,
forest_plant_cluster_01 = true,
forest_blocker_stone = true,
forest_cage_01 = false,
forest_cage_02 = false,
forest_exit = false,
forest_exit_rock_tunnel = false,
forest_exit_underwater = false,
forest_ruins_ceiling = true,
forest_ruins_ceiling_flat = true,
forest_ruins_wall_01 = true,
forest_ruins_wall_02 = true,
forest_ruins_secret_door = false,
forest_ruins_wall_ivy_01 = true,
forest_ruins_wall_ivy_02 = true,
forest_ruins_wall_end_right = true,
forest_ruins_wall_end_left = true,
forest_ruins_pillar_01 = true,
forest_ruins_pillar_02 = true,
forest_ruins_pillar_03 = true,
forest_ruins_arch = true,
daemon_head_ruins = true,
receptor_ruins = false,
lever_ruins = false,
forest_statue_pillar_01 = true,
forest_statue_pillar_02 = true,
forest_statue_pillar_03 = true,
forest_ruins_pillar_fallen = true,
forest_ruins_dome = true,
forest_ruins_fallen_bricks = true,
forest_ruins_ground_tile_01 = true,
forest_ruins_ground_tile_02 = true,
forest_ruins_ground_tile_03 = true,
forest_ruins_ground_tile_balance = true,
forest_bridge = false,
forest_bridge_end = false,
forest_bridge_middle_pillar = true,
forest_bridge_pillar = true,
forest_old_oak = true,
forest_stairs_down = false,
forest_statue_1 = true,
forest_statue_2 = true,
forest_statue_3 = true,
forest_fountain = true,
forest_altar = false,
forest_fireflies = false,
forest_chasm_edge = true,
forest_chasm_corner = true,
forest_chasm = false,
forest_elevation_edge = true,
forest_elevation_ledge = true,
forest_pit_fog = false,
forest_wall_text_short = false,
forest_wall_text_long = false,
forest_lantern = false,
forest_pit = false,
forest_pit_trapdoor = false,
forest_pressure_plate = false,
forest_underwater_pressure_plate = false,
forest_ruins_secret_button_small = false,
forest_ruins_secret_button_big = false,
forest_border_rocks_01 = true,
forest_willow = true,
background_hill_01 = true,
forest_seaweed_pillar = true,
forest_seaweed_wall_decoration_01 = true,
forest_seaweed_floor_01 = true,
forest_seaweed_floor_02 = true,
forest_seaweed_wall_decoration_02 = true,
forest_underwater_bubbles = false,
forest_underwater_plankton = false,
forest_elevation_edge_overhang = true,
crow = false,
small_fish = false,
crows_flying = false,
-- generic.lua
starting_location = false,
party = false,
teleporter = false,
invisible_teleporter = false,
teleportation_effect = false,
wall_trigger = false,
receptor = false,
timer = false,
counter = false,
spawner = false,
blocker = false,
invisible_wall = false, -- This is a great example of an object you may want to change!
invisible_rocky_wall = false, -- as is this one!
invisible_platform = false,
particle_system = false,
sound = false,
secret = false,
script_entity = false,
boss_fight = false,
wall_button = false,
lever = false,
floor_trigger = false,
torch_holder = false,
altar = false,
lock = false,
lock_round = false,
lock_ornate = false,
lock_gold = false,
lock_gear = false,
lock_prison = false,
daemon_head = true,
floor_corpse = true,
floor_dirt = true,
healing_crystal = false,
chest = false,
door_pullchain = false,
door_lever = false,
floor_spike_trap = false,
dig_hole = false, -- another one you may want to change
ladder = false,
ladder_metal = false,
ladder_metal_4m = false,
pedestal = false,
pullchain = false,
ceiling_witch_lantern = false,
k_decal = true,
nexus_lock = false,
mine_lock = false,
skull_lock = false,
dummy_light = false,
-- magic_bridge.lua
magic_bridge = false,
-- mainmenu.lua
menu_camera = false,
menu_sky = false,
menu_heightmap = false,
menu_island = false,
menu_fog = false,
menu_ocean = false,
-- mine.lua
mine_floor_01 = true,
mine_wall_01 = true,
mine_pillar_01 = true,
mine_pillar_hatless_01 = true,
mine_pillar_02 = true,
mine_pillar_03 = true,
mine_ceiling_01 = true,
mine_ceiling_02 = true,
mine_ceiling_03 = true,
mine_wooden_support = true,
mine_pit = false,
mine_pit_trapdoor = false,
mine_chasm_edge = true,
mine_chasm = false,
mine_bridge_01 = false,
mine_ceiling_shaft_edge = true,
mine_ceiling_shaft = false,
mine_door_spear = false,
mine_secret_door = false,
mine_lever = false,
mine_lever_corner = false,
mine_secret_lever = false,
mine_pillar_crystal = true,
mine_stairs_up = false,
mine_stairs_down = false,
mine_moss_stairs_up = false,
mine_pressure_plate = false,
mine_wooden_support_wall = true,
mine_support_pillar_01 = true,
mine_support_wall_01 = true,
mine_support_wall_02 = true,
mine_support_beam_01 = true,
mine_support_ceiling_01 = true,
mine_alcove = false,
mine_floor_sandpile = true,
mine_wall_text = false,
mine_wall_text_long = false,
mine_rockpile_01 = true,
mine_door_heavy = false,
mine_door_support = false,
mine_door_camo = false,
mine_alcove_support = false,
mine_spell_receptor = false,
mine_spell_receptor_support = false,
mine_spell_launcher = true,
mine_spell_launcher_support = false, -- for some reason
mine_support_pillar_lantern_01 = false,
mine_ceiling_pit_light = false,
mine_ceiling_pit_light_bright = false,
mine_floor_pit_light = false,
mine_ceiling_lantern = false,
mine_support_wall_button = false,
mine_support_secret_button = false,
mine_cave_in = true,
mine_counterweight_chains = true,
mine_counterweight_platform = true,
mine_elevation_edge = true,
mine_ceiling_roots_01 = true,
mine_ceiling_shaft_roots_01 = true,
mine_moss_wall_01 = true,
mine_moss_pillar_01 = true,
mine_moss_pillar_hatless_01 = true,
mine_moss_pillar_02 = true,
mine_moss_pillar_03 = true,
mine_moss_ceiling_01 = true,
mine_moss_ceiling_02 = true,
mine_moss_ceiling_03 = true,
mine_moss_rockpile_01 = true,
mine_moss_cave_in = true,
mine_moss_wooden_support = true,
mine_moss_door_support = false,
-- portal.lua
portal = false,
portal_locked = false,
portal_lock = false,
-- pushable_block.lua
pushable_block = false,
pushable_block_floor = false,
pushable_block_floor_trigger = false,
-- sky.lua
forest_day_sky = false,
castle_arena_sky = false,
swamp_sky = false,
cemetery_sky = false,
-- stone_philosophers.lua
forest_statue_wall_1 = false,
mine_statue_wall = false,
beacon_furnace_head = false,
-- swamp.lua
swamp_oak = true,
swamp_oak_cluster = true,
swamp_oak_cluster_polypore = true,
swamp_dead_tree = true,
swamp_mushrooms_01 = true,
swamp_mushroom_cluster = true,
swamp_dungeon_fog = false,
swamp_wall_ivy_01 = true,
swamp_wall_ivy_02 = true,
-- tomb.lua
tomb_floor_01 = true,
tomb_floor_02 = true,
tomb_wall_01 = true,
tomb_wall_02 = true,
tomb_wall_03 = true,
tomb_wall_04 = true,
tomb_wall_ornament = true,
tomb_pillar = true,
tomb_ceiling_01 = true,
tomb_alcove = false,
tomb_pressure_plate = false,
tomb_secret_button_small = false,
tomb_secret_door = false,
tomb_pit = false,
tomb_pit_trapdoor = false,
tomb_ceiling_shaft = true,
tomb_door_portcullis = false,
tomb_stairs_down = false,
tomb_stairs_up = false,
tomb_wall_text = false,
tomb_wall_text_long = false,
tomb_door_stone = false,
tomb_iron_gate = false,
tomb_door_serpent = false,
sarcophagus = false,
tomb_wall_sarcophagus = true,
tomb_wall_sarcophagus_empty = true,
tomb_wall_sarcophagus_mummy = true,
tomb_wall_face = true,
snake_statue = true,
snake_pillar = true,
gold_mask_statue_wall = true,
gold_mask_statue_floor = true,
tomb_torch_holder = false,
tomb_wall_lever = false,
tomb_floor_dirt = true,
tomb_wall_grating = false, -- hmmm...
tomb_ceiling_lamp = false,
tomb_wall_lantern = false,
tomb_wall_painting_1 = true,
tomb_wall_painting_2 = true,
tomb_wall_painting_3 = true,
tomb_wall_painting_4 = true,
tomb_wall_spiketrap = false,
lock_gold_mask_statue = false,
tomb_pyramid_top = true,
tomb_pyramid_side = true,
tomb_inside_top_ceiling = true,
lock_tomb = false,
-- water.lua
beach_ocean = false,
water_surface = false,
water_surface_underground = false,
swamp_water = false,
-- misc other objects
power_gem = false,
vector_marker = true,
blob = false,
blob_blast = false,
blob_hit_receptor = false,
dark_bolt = false,
dark_bolt_blast = false,
darkness_cloud = false,
dispel_projectile = false,
dispel_blast = false,
fireball_small = false,
fireball_medium = false,
fireball_large = false,
fireball_blast_small = false,
fireball_blast_medium = false,
fireball_blast_large = false,
fireburst = false,
force_field = false,
frostbolt = false,
frostbolt_blast = false,
frostbolt_1 = false,
frostbolt_2 = false,
frostbolt_3 = false,
frostbolt_4 = false,
frostbolt_5 = false,
frostbolt_blast_1 = false,
frostbolt_blast_2 = false,
frostbolt_blast_3 = false,
frostbolt_blast_4 = false,
frostbolt_blast_5 = false,
frostburst = false,
ice_shards = false,
lightning_bolt = false,
lightning_bolt_blast = false,
lightning_bolt_greater = false,
lightning_bolt_greater_blast = false,
open_door = false,
open_door_blast = false,
poison_bolt = false,
poison_bolt_blast = false,
poison_bolt_greater = false,
poison_bolt_greater_blast = false,
poison_cloud_small = false,
poison_cloud_medium = false,
poison_cloud_large = false,
fire_trap_rune = false,
shockburst = false,
wall_fire = false,
fear_cloud = false,
summon_stone_pile = false,
lindworm_cinematic_tower = false,
-- Now you just have to add your custom objects here! (And update any
-- standard objects that you changed, obviously)
}
-- In order to prevent spam, item action and monster action/brain components are
-- not included in this list because they will only appear on items or monsters,
-- and ItemComponent and MonsterComponent are already unsafe.
UNSAFE_MINIMAL_COMPONENTS = {
BeaconFurnaceControllerComponent = true,
BlastComponent = true,
-- BlindedMonsterComponent = true,
BossFightComponent = true,
-- BrainComponent = true,
-- BurningMonsterComponent = true,
ButtonComponent = true,
CameraComponent = true,
CameraShakeComponent = true,
-- CastSpellComponent = true,
ChestComponent = true,
ClickableComponent = true,
CloudSpellComponent = true,
-- ContainerItemComponent = true,
ControllerComponent = true, -- does not really have state information,
-- but a controller on a minimalSaveState object
-- is almost certainly a mistake
CounterComponent = true,
-- CrabBrainComponent = true,
-- CraftPotionComponent = true,
CrowControllerComponent = true,
-- CrowernAttackComponent = true,
CrystalComponent = true,
-- CrystalShardItemComponent = true,
-- DiggingToolComponent = true,
-- DoorComponent is NOT included although it has state information, because it
-- is often used for wall gratings, forest ruins walls, etc. that will never
-- actually be opened.
DynamicObstacleComponent = true, -- like ControllerComponent I am not
-- sure this technically has meaningful
-- state information, but is only useful
-- for objects that require full saving
EarthquakeComponent = true,
-- EntangledMonsterComponent = true,
-- EquipmentItemComponent = true,
-- EyctopusBrainComponent = true,
-- FireElementalBrainComponent = true,
-- FirearmAttackComponent = true,
FloorTriggerComponent = true,
ForceFieldComponent = true,
-- FrozenMonsterComponent = true,
-- GoromorgBrainComponent = true,
-- GoromorgShieldComponent = true,
GravityComponent = true,
HealthComponent = true,
-- HerderBigBrainComponent = true,
-- HerderSmallBrainComponent = true,
-- IceGuardianBrainComponent = true,
-- IceLizardBrainComponent = true,
IceShardsComponent = true,
-- ItemActionComponent = true,
ItemComponent = true,
LeverComponent = true,
-- LindwormBrainComponent = true,
-- LindwormChargeComponent = true,
-- LindwormFlyComponent = true,
LockComponent = true,
-- MagmaGolemBrainComponent = true,
MapMarkerComponent = true,
-- MeleeAttackComponent = true,
-- MeleeBrainComponent = true,
-- MimicCameraAnimationComponent = true,
-- MonsterActionComponent = true,
-- MonsterAttackComponent = true,
-- MonsterChangeAltitudeComponent = true,
-- MonsterChargeComponent = true,
MonsterComponent = true,
-- MonsterDropItemComponent = true,
-- MonsterGroupComponent = true,
-- MonsterJumpComponent = true,
-- MonsterKnockbackComponent = true,
-- MonsterLightCullerComponent = true,
-- MonsterMoveAttackComponent = true,
-- MonsterMoveComponent = true,
-- MonsterOperateDeviceComponent = true,
-- MonsterPickUpItemComponent = true,
-- MonsterStealWeaponComponent = true,
-- MonsterTurnComponent = true,
-- MonsterWarpComponent = true,
-- MosquitoSwarmBrainComponent = true,
-- OgreBrainComponent = true,
PartyComponent = true,
-- For the same reason as DoorComponent, we don't include PitComponent.
PoisonCloudAttackComponent = true,
-- PoisonedMonsterComponent = true,
PortalComponent = true, -- connectors at the very least
ProjectileComponent = true,
ProjectileImpactComponent = true,
PullChainComponent = true,
PushableBlockComponent = true,
PushableBlockFloorComponent = true,
-- RangedAttackComponent = true,
-- RangedBrainComponent = true,
-- RatlingBossBrainComponent = true,
-- ReloadFirearmComponent = true,
-- RopeToolComponent = true,
-- RunePanelComponent = true,
ScriptComponent = true,
ScriptControllerComponent = true,
-- ScrollItemComponent = true,
SecretComponent = true,
-- SkeletonArcherBrainComponent = true,
-- SkeletonCommanderBrainComponent = true,
-- SleepingMonsterComponent = true,
-- SlimeBrainComponent = true,
SmallFishControllerComponent = true,
SocketComponent = true,
SoundComponent = true,
SpawnerComponent = true,
-- SpellScrollItemComponent = true,
StairsComponent = true, -- custom targets; ExitComponent doesn't support them so it's fine I think
StatisticsComponent = true,
StonePhilosopherControllerComponent = true,
-- StunnedMonsterComponent = true,
SurfaceComponent = true,
-- SwarmBrainComponent = true,
TeleporterComponent = true,
-- TentacleBrainComponent = true,
-- TentacleHideComponent = true,
ThornWallComponent = true,
-- ThrowAttackComponent = true,
TileDamagerComponent = true,
TimerComponent = true,
TinyCritterControllerComponent = true,
-- ToadBrainComponent = true,
TorchHolderControllerComponent = true,
-- TorchItemComponent = true,
-- TricksterBrainComponent = true,
-- TurtleBrainComponent = true,
-- TwigrootBrainComponent = true,
-- UggardianBrainComponent = true,
-- UggardianFlamesComponent = true,
-- UsableItemComponent = true,
-- ViperRootBrainComponent = true,
WallTextComponent = true,
WallTriggerComponent = true,
-- WargBrainComponent = true,
-- WizardBrainComponent = true,
-- XeloroidBrainComponent = true,
-- ZarchtonBrainComponent = true,
}
function dungeonReport()
-- Count objects and components.
---------------------------------
local minimalSaveTotal = 0
local fullSaveTotal = 0
local unknownSaveTotal = 0
local fullComponentTotal = 0
local minimalComponentTotal = 0
local unknownComponentTotal = 0
local fullObjectCounts = {}
local minimalObjectCounts = {}
local unknownObjectCounts = {}
local fullComponentCounts = {}
local minimalComponentCounts = {}
local unknownComponentCounts = {}
local objectNameIdLength = 0
local saveWarnings = {}
local function processObject(e)
local name = e.name
objectNameIdLength = objectNameIdLength+e.id:len()+name:len()
local saveState = OBJECT_HAS_MINIMALSAVESTATE[name]
if saveState or name:find("merged_level_") then
minimalSaveTotal = minimalSaveTotal+1
minimalObjectCounts[name] = minimalObjectCounts[name] and minimalObjectCounts[name]+1 or 1
-- nobody is enough of an idiot to give minimalSaveState to a proper item or monster right?
elseif (saveState == false) or e.item or e.monster or e.monstergroup then
fullSaveTotal = fullSaveTotal+1
fullObjectCounts[name] = fullObjectCounts[name] and fullObjectCounts[name]+1 or 1
else
print("unknown object: "..name)
unknownSaveTotal = unknownSaveTotal+1
unknownObjectCounts[name] = unknownObjectCounts[name] and unknownObjectCounts[name]+1 or 1
end
for _,c in e:componentIterator() do
local cls = c:getClass()
if saveState then
minimalComponentTotal = minimalComponentTotal+1
minimalComponentCounts[cls] = minimalComponentCounts[cls] and minimalComponentCounts[cls]+1 or 1
if UNSAFE_MINIMAL_COMPONENTS[cls] then
if saveWarnings[name] then
saveWarnings[name][cls] = true
else
saveWarnings[name] = {[cls] = true}
end
end
elseif saveState == false or e.item or e.monster or e.monstergroup then
fullComponentTotal = fullComponentTotal+1
fullComponentCounts[cls] = fullComponentCounts[cls] and fullComponentCounts[cls]+1 or 1
else
print("unknown component on object: "..name)
unknownComponentTotal = unknownComponentTotal+1
unknownComponentCounts[cls] = unknownComponentCounts[cls] and unknownComponentCounts[cls]+1 or 1
end
if cls == "ContainerItemComponent" then
-- XXX: This will cause infinite recursion if a container contains itself, but the Grimrock engine
-- already causes infinite recursion in that situation anyway.
for slot=1,c:getCapacity() do
local itm = c:getItem(slot)
if itm then processObject(itm.go) end
end
elseif cls == "MonsterComponent" then
-- I suppose theoretically you could make a monster contain itself too...
-- I'm not sure how, but this iterator is goofy and this check is required.
local _,_,count = c:contents()
if count ~= 0 then
for _,itm in c:contents() do
processObject(itm.go)
end
end
end
end
end
for l=1,Dungeon.getMaxLevels() do -- get all objects on the map and monster/container inventories...
for e in Dungeon.getMap(l):allEntities() do
processObject(e)
end
end
for c=1,4 do -- ...plus all objects in champions' inventories.
local champ = party.party:getChampion(c)
for slot=1,ItemSlot.MaxSlots do
local itm = champ:getItem(slot)
if itm then processObject(itm.go) end
end
end
if getMouseItem() then processObject(getMouseItem().go) end
-- Objects and components have been counted, now sort them.
-- We used the object names and component classes as keys when counting,
-- because if we used a straight array we would have to traverse it to
-- find the object's entry, which is of course O(log(n)) compared to the
-- O(1) of a table lookup. This does, however, mean that we must build
-- a straight array now so we can sort it. Another option is to build
-- the array while counting, but that isn't actually faster.
-----------------------------------------------------------
local cmpfunc = function(a,b) return a.c > b.c end
local fullObjectsSorted = {}
local minimalObjectsSorted = {}
local unknownObjectsSorted = {}
local fullComponentsSorted = {}
local minimalComponentsSorted = {}
local unknownComponentsSorted = {}
for name,count in pairs(fullObjectCounts) do
table.insert(fullObjectsSorted,{n=name,c=count})
end
for name,count in pairs(minimalObjectCounts) do
table.insert(minimalObjectsSorted,{n=name,c=count})
end
for name,count in pairs(unknownObjectCounts) do
table.insert(unknownObjectsSorted,{n=name,c=count})
end
for name,count in pairs(fullComponentCounts) do
table.insert(fullComponentsSorted,{n=name,c=count})
end
for name,count in pairs(minimalComponentCounts) do
table.insert(minimalComponentsSorted,{n=name,c=count})
end
for name,count in pairs(unknownComponentCounts) do
table.insert(unknownComponentsSorted,{n=name,c=count})
end
table.sort(fullObjectsSorted,cmpfunc)
table.sort(minimalObjectsSorted,cmpfunc)
table.sort(unknownObjectsSorted,cmpfunc)
table.sort(fullComponentsSorted,cmpfunc)
table.sort(minimalComponentsSorted,cmpfunc)
table.sort(unknownComponentsSorted,cmpfunc)
local report = string.format([[
Total Objects: %d Total Components: %d
Full Save State Objects: %d Full Save State Components: %d
These objects and components will have their entire state saved.
Minimal Save State Objects: %d Minimal Save State Components: %d
These objects will only have their name, id, level, x, y, facing, and elevation
saved. The components are not saved at all.]],
fullSaveTotal+minimalSaveTotal+unknownSaveTotal,
fullComponentTotal+minimalComponentTotal+unknownComponentTotal,
fullSaveTotal,fullComponentTotal,
minimalSaveTotal,minimalComponentTotal)
if unknownSaveTotal ~= 0 then
report = report..string.format([[
Unknown Save State Objects: %d Unknown Save State Components: %d
These objects are absent from the save state tables in this script. Please add
them to the OBJECT_HAS_MINIMALSAVESTATE table.
A list of these objects is available at the end of the terminal output.]],
unknownSaveTotal,unknownComponentTotal)
end
report = report..string.format([[
Total length of object names and ids: %d]],
objectNameIdLength)
local note = spawn("note")
note.scrollitem:setScrollText(report..[[
For the full report including counts of each object and component, and warnings
about unsafe minimalSaveState usage, please check the game's terminal output.]])
note.item:setUiName("Dungeon Report")
if not getMouseItem() then setMouseItem(note.item) end
print([[
================================================================================
Full save state objects:
================================================================================]])
for _,t in ipairs(fullObjectsSorted) do
print(t.c,t.n)
end
print([[
================================================================================
Minimal save state objects:
================================================================================]])
for _,t in ipairs(minimalObjectsSorted) do
print(t.c,t.n)
end
if #unknownObjectsSorted ~= 0 then
print([[
================================================================================
Unknown save state objects:
================================================================================]])
for _,t in ipairs(unknownObjectsSorted) do
print(t.c,t.n)
end
end
print([[
================================================================================
Full save state components:
================================================================================]])
for _,t in ipairs(fullComponentsSorted) do
print(t.c,t.n)
end
print([[
================================================================================
Minimal save state components:
================================================================================]])
for _,t in ipairs(minimalComponentsSorted) do
print(t.c,t.n)
end
if #unknownComponentsSorted ~= 0 then
print([[
================================================================================
Unknown save state components:
================================================================================]])
for _,t in ipairs(unknownComponentsSorted) do
print(t.c,t.n)
end
end
-- check if warnings are empty
local warnings = false
for _,i in pairs(saveWarnings) do
warnings = true
break
end
if warnings then
print([[
================================================================================
WARNING: The following objects have components with extra state information, yet
you have defined them with minimalSaveState. Are you sure you know what you're
doing here?
================================================================================]])
for name,t in pairs(saveWarnings) do
local str = name..": "
for component,_ in pairs(t) do
str = str..component.." "
end
print(str)
end
end
end
Code: Select all
================================================================================
Full save state objects:
================================================================================
429 floor_trigger
283 spawner
248 mine_ceiling_shaft
210 teleporter
189 blocker
179 mine_chasm
165 floor_spike_trap
133 magic_bridge
130 torch
118 ladder
117 script_entity
103 forest_bridge
102 castle_bridge_grating
[Truncated to fit forum's 60,000 character limit]
================================================================================
Minimal save state objects:
================================================================================
6898 dungeon_pillar
3797 dungeon_wall_01
3226 dungeon_floor_dirt_01
2219 castle_pillar_01
1879 dungeon_wall_02
1545 tomb_pillar
986 tomb_ceiling_01
956 mine_floor_01
940 mine_floor_sandpile
933 mine_wall_01
845 catacomb_ceiling
813 castle_floor_01
778 beach_rock_pillar_01
661 swamp_grass_01
627 mine_support_pillar_01
618 dungeon_ceiling_0000
581 forest_elevation_edge
543 forest_pillar_01
514 mine_support_wall_01
508 castle_wall_01
505 tomb_floor_01
502 castle_wall_02
501 forest_oak_cluster
500 forest_heather
[Truncated to fit forum's 60,000 character limit]
================================================================================
Full save state components:
================================================================================
5763 ModelComponent
2216 ControllerComponent
1696 AnimationComponent
1584 LightComponent
1447 ParticleComponent
1254 ItemComponent
1251 ClickableComponent
994 ItemConstrainBoxComponent
909 MonsterAttackComponent
812 SoundComponent
788 MonsterMoveComponent
723 GravityComponent
718 DynamicObstacleComponent
710 MonsterComponent
704 MonsterTurnComponent
610 FloorTriggerComponent
608 ObstacleComponent
586 DoorComponent
[Truncated to fit fourm's 60,000 character limit]
================================================================================
Minimal save state components:
================================================================================
51555 ModelComponent
26753 OccluderComponent
3609 AnimationComponent
2941 ObstacleComponent
2941 ProjectileColliderComponent
1982 LightComponent
1195 DoorComponent
364 ParticleComponent
230 ItemConstrainBoxComponent
2 MapGraphicsComponent
The most-used minimalSaveState objects are the dungeon walls, floors, ceilings, and pillars, due to the many large levels with tiles that create them. Intelligent use of minimalSaveState was also very helpful for wall gratings, forest ruin walls, and other "doors" that will never be opened (and therefore don't need full save state): it prevented 1195 DoorComponents and even more ModelComponents from taking up save space.
Object Merger
This is a utility that merges some objects that don't require state data into one single object per level. This can improve saving performance. Note that this is a terrible, terrible hack, and should only be used as a last resort, for dungeons where saving causes crashes (because of running out of memory) and where you refuse to just reduce your object count/memory usage.
Download
The download contains a version of Isle of Nex with this script run on it (and one of the boss fights removed because it has special handling in the original game, which I was too lazy to recreate). The important script is grimrock2_merged/mod_assets/scripts/opt_utils.lua, which contains the object merger and the complete documentation for it.
Long description directly from that file:
that leaves your dungeon essentially un-editable once you use it. Only use it if
you absolutely need it and other ways of optimizing your dungeon have failed.
If you do decide to do it:
- Only do it if you're about to release your dungeon or test it in the game
(not in the editor). It won't help you with testing in the editor at all.
If testing in the editor is making you run out of memory, this script won't
help, you need to make changes to the dungeon yourself.
- Before running it, make a backup of your dungeon.lua. You need this whether it
works or not, because after applying this "optimization", the dungeon is no
longer safe to edit at all.
- If you're just testing the dungeon in the game, consider trying the partial
transformation first, it will fix the saving performance without requiring you
to edit tiles or dungeon.lua. If the dungeon crashes upon startup with the
partial transformation, you will have to do the full transformation.
- If you are doing an actual release of your dungeon, only the full
transformation is appropriate.
- If you want to make ANY changes to your dungeon after this, you HAVE to
reverse the transformation by deleting the script you added (if you did the
reversible transformation), or reverting to your backed-up dungeon.lua if you
did the full transformation. You should also remove the merged object
definitions, since any changes make them useless anyway and it'll save you
some editor loading time.
This utility can be used to collect many objects on a level and merge them all
into a small number (1-8) of minimalSaveState objects with many components. This
removes the need to serialize each individual object, improving the speed and
memory usage of saving the game.
Again, I strongly recommend that you avoid doing this *at all* if you can
possibly avoid it. However, if you insist on making dungeons that use every texture
and model available along with 100,000 objects, then this may be your only
option to reduce the object count enough to make the dungeon playable.
In order to use this utility, you must know how to use the command line, because
it requires printing a HUGE block of text to standard output. You cannot
retrieve the text from the in-game console, it is orders of magnitude too
long. Instead, you must run grimrock2.exe from the command line and pipe the
output to a file, which you can then open in your text editor to retrive the
text. For example, you can do this in Windows Command Prompt with:
grimrock2.exe > output.txt
When mergeObjectsOnLevel is run successfully, it will print
a series of variables and object definitions to standard output (wrapped in two
layers of functions to get around Lua limitations). You can then copy and paste
this block into your mod's asset definitions, just like any other object
definition. These define a series of objects that have all the components of the
objects that were merged; one merged object may have hundreds or even thousands
of components. This block will probably be extremely long and is NOT designed to
be human-readable, so you probably shouldn't try.
After this block, unless the dontListObjects parameter is true, an array with
the ids of all the merged objects will be printed, followed by a short code
snippet that iterates through this array and deletes all of those objects. The
array will probably be extremely long, as it has one line for every merged
object. If you wish to do the reversible transformation, you need to copy and
paste this array and snippet into a script entity (or an external script loaded
by a script entity). This snippet will run at the start of the dungeon and
delete all of the objects that were merged, leaving only the newly defined
merged objects. The end result is that the dungeon has the same components in
it, but the object count is (in most cases) greatly reduced. Since the merged
objects have minimalSaveState, none of the components need to be saved, and the
reduction in object count will make saving faster and less RAM intensive.
After that block (or the object definition block if dontListObjects was true),
a simple code snippet will be printed: this
snippet can be copied and pasted into any script entity (or an external script
loaded by a script entity).
This snippet will run at the start of the dungeon and spawn all of the merged
objects that were just defined.
After that, if doPillars is true, yet another array and snippet will be printed.
Paste it into a script entity just like the last one. The purpose of this
array and snippet is to spawn dummy_pillar objects on the former level,x,y
positions of all the objects that were merged away and had automap icon 92
(pillar). In cases where multiple pillars were on top of each other (very common
in any dungeon that uses elevation), only one dummy_pillar needs to be spawned,
therefore the number of pillar objects is still reduced overall.
If you REALLY need to reduce your object count as much as possible, and don't
mind losing the automap icons on your pillars, then you can skip this step. But
the automap wall icon looks quite ugly without pillars bordering it. Perhaps
you could draw a new wall icon that looks good without adjacent pillar icons?
If doPillars is not true, then no objects with the pillar icon will be merged
in the first place, so this block won't be printed.
The definition for dummy_pillar is:
defineObject{
name = "dummy_pillar",
baseObject = "base_pillar",
minimalSaveState = true,
}
You need to add this to your initialization files.
Finally, the number of objects merged is printed. This is also printed to the
in-game HUD, since the in-game console tends to overflow rather badly when this
function is run. I guess the Grimrock 2 developers didn't think it was important
for the console to be able to print 120,000 lines at once.
HOW I DID THE FULL TRANSFORMATION ON ISLE OF NEX:
0. I removed script_entity_43 and the lindworm fight, since script_entity_43
modifies a minimalSaveState object (turtle nest) that will be merged
(this was a mistake by the devs; the change is destroyed as soon as
the player saves and reloads the game anyway), and I didn't want to
reimplement the lindworm fight (it has special internal handling).
This was all the setup necessary for this dungeon. For your dungeon,
you will need to add any custom objects you want merged to
MERGEABLE_OBJECTS with the appropriate information, and you will need
to add void-ified versions of any relevant custom tiles to
voidTiles.lua. You will also need to make sure your dungeon does not
have any cases like the turtle nest one I mentioned, where code
relies on a mergeable object.
1. I copied this file, opt_utils.lua, as well as voidTiles.lua, into
the dungeon's folder.
2. I made a new file, optimizer.lua, that contains all the reflection maps from
dungeon.lua, and a few lines that call mergeObjectsOnLevel to merge the
objects on all levels with the dontListObjects parameter as true.
Remember to include the reflection maps.
3. I launched grimrock2.exe, piping the output to a new text file.
4. I loaded both opt_utils.lua and optimizer.lua from script entities in the
dungeon.
5. At this point, I backed up the dungeon.lua; this will be the version I revert
to if I want to make changes and re-merge.
6. I ran optimizer.script.merge() from the console and waited for it to finish.
7. I retrieved the merged object definitions block from the text file I piped
output to, and saved it to merged_objects.lua.
8. I retrieved the other blocks (spawn merged objects and spawn dummy pillars)
from the text file, and saved them to spawnstuff.lua.
9. I printed a regular expression from getMergeableRegex(true,true), retrieved
it from the text file, and replaced all instances of it in dungeon.lua
with nothing. You can use sed, Notepad++, or whatever you want for this.
10. I added an import line for merged_objects.lua, as well as the dummy_pillar
definition, to init.lua.
10.5. I also added an import line for voidTiles.lua to init.lua (voidTiles.lua
must come AFTER the standard tile definitions so that it replaces them).
11. I loaded the dungeon again, added a script entity that loads spawnstuff.lua,
and deleted the script entities that loaded opt_utils.lua and
optimizer.lua (since they are no longer needed). Then I saved the
dungeon. This also has the effect of removing the blank lines in
dungeon.lua that were introduced in step 9, so we don't have to worry
about those.
At this point, the merging is complete and the dungeon is ready to
export and release, but should not be edited further.
If you want to edit the dungeon after merging, you must reverse the process:
1. Revert to the backup of dungeon.lua that you make in step 5.
2. Remove the import lines for voidTiles.lua and merged_objects.lua from
init.lua. merged_objects.lua can be deleted, since it will be useless
now anyway.
That's it! The process has been totally reversed and you can edit your dungeon
again.
ALTERNATE METHOD:
You can do a partial transformation that leaves the objects in dungeon.lua,
and removes the need to replace tiles, but still gives about the same
improvement to savegame performance. However it introduces a heavy burden to
startup performance. To do this transformation, follow the steps for the full
transformation above, but in step 2, pass false or nil for dontListObjects
instead of true. This will add a new block to step 8 that you can paste along
with the others, which deletes all the objects that were merged. Additionally,
skip step 10.5.
If running mergeObjectsOnLevel makes you run out of memory, try setting texture
resolution to "Low", and doing a clean load of the dungeon (start the game,
start the editor, load the dungeon ONCE in the editor, without reloading it or
loading another dungeon), then running it again. If you STILL run out of memory,
your dungeon is probably beyond this sort of help in the first place - but you
can try merging fewer levels at once, e.g. merge half the levels then the other
half and copy both sets of object definitions etc.
Note that the object
definitions file will be smallest if you merge all the levels with a single
mergeObjectsOnLevels call, because it tracks duplicate strings. However, there
will be no difference in in-game performance whether you merge all the levels at
once or not.
If you want to edit the dungeon after this transformation, all you have to do
is delete the script entity that loads spawnstuff.lua (or whatever you called
it). This transformation is not suitable for a final release of your dungeon
due to the heavy performance burden on startup, but it may be useful for testing.
If you have a specific instance of a mergeable object that you don't want
merged, include the string "nomerge" in its id. For example, a dungeon_wall_01
with the id "dungeon_wall_01_500_nomerge" will never be merged; neither will a
dungeon_wall_01 with the id "nomerge1wall" or any other id containing "nomerge".
NOTE: Peculiarly, when a DiggingToolComponent (shovel) is used to dig a hole on
a square containing a grass_planes_01 object, the grass is destroyed. This
does not happen to any of the other grasses or other objects in the game. If
the grass planes are merged, this behaviour will, of course, go away.
Therefore, the grass_planes_01 entry in MERGEABLE_OBJECTS is commented out
by default, but you can uncomment it if you want to merge grass planes and
don't mind losing this special shovel behaviour.
Finally, I know a lot of you don't like to, but TEST YOUR MODS after doing
this. You probably didn't get everything right the first time, *especially* if
you made a dungeon badly optimized enough to need this godawful hack in the
first place (that's what this script is, I want to be 100% clear, it is a
godawful hack and a last resort).
FAQ:
Q: How do I copy text from the console?
A: You don't copy text from the in-game console. You run the game from the
terminal and pipe the output to a file.
Windows tutorial: http://www.cs.princeton.edu/courses/arc ... rompt.html
On Windows, just navigate to the game's directory in the Command Prompt and run:
grimrock2.exe > output_file.txt
Mac OS tutorial: http://blog.teamtreehouse.com/introduct ... mmand-line
Like on Windows, you'll just append " > output_file.txt" to the command you
use to run the game.
Linux tutorial: wine grimrock2.exe > output_file.txt
Q: Will this improve my dungeon's framerate?
A: Not significantly. Separate ModelComponents are not easier to render just
because they are on the same object. If you have too many models on one
level, you can try merging nearby models into a single mesh in your
favored 3D modeling program, which does improve performance especially
if they use the same material, but at the expense of file size,
flexibility, and occlusion. Generally, this is a bad idea.
In fact, if you run with doAnimations on, your dungeon's framerate will
get worse on levels with lots of objects like forest_heather, since they
now have to be animated at all distances. This is why doAnimations is an
option at all.
Q: Will this improve my dungeon's memory usage even when not saving the game or
starting a new game?
A: Probably, but not by very much. This is very unlikely to make a difference.
Q: What WILL this improve the performance of?
A: Saving the game, always: saving will be faster and require less memory.
Starting a new game, if you do the full transformation rather than the
partial one.
Iterations through allEntities() on any level that had objects merged
will also be sped up, since there are fewer objects to iterate through.
(But if you're iterating through all *components* then it won't help much,
since the merged object has all the original objects' components.)
Q: Will this improve the performance of loading the game?
A: Maybe a little, but not enough to care about. Unserializing the save file is
faster since the uncompressed save file is a lot smaller, but most of the
time taken by loading comes from creating and initializing tens of
thousands of components.
Q: Won't these huge lookup tables also use up memory?
A: Yes, but you don't actually need to include this script in your mod after
you've finished using it. For your final release, you can just remove this
script from the dungeon.
Even if you leave this script in, the tables don't use THAT much memory.
Q: Well, won't the huge object definitions still use up memory?
A: Yes, but since it replaces objects with components, the resulting memory usage
is still lower overall. And the large number of components on the object
doesn't affect saving performance at all because it has minimalSaveState -
the entire point of this script is to improve saving performance. Any other
performance gain is secondary.
Q: Is there an equivalent of this for Grimrock 1?
A: No, and there cannot be.
Q: Why can't [ObstacleComponent/DoorComponent/some other component] be merged?
A: Most components have state information that makes minimalSaveState unsuitable
for their parent objects. ObstacleComponent, DoorComponent, and a
handful of other components are special: they are often used with
minimalSaveState when their state will not change (such as on wall gratings
and almost all obstacles), but they cannot be merged because their effects
depend on the location of the parent object, not the component itself. If you
want an ObstacleComponent on four squares, you have to use four objects.
Q: Can you make a version that I can use without learning how to use the terminal?
Q: Can you make a version that I can use without knowing what a function is?
Q: [any other variation of the question "Can you make a version that a hamster
could use"]
A: If you can't take the 3 minutes it takes to learn to use the terminal, then
you should have no trouble at all understanding why I can't take the 3 weeks
it would take to write a program "user-friendly" enough for you.
Q: Could this be optimized further?
A: Definitely. For example, the object definition text could be shortened a lot
by doing some actual analysis on similar components etc. But I've already
spent more time than I want to on this tool that should never need to be
used in the first place...