Dungeon optimization tools

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Dungeon optimization tools

Post by minmay »

Some modders have chosen to ignore the limitations of 32-bit Windows and paid the price for it. I am making this thread to try to stop people from going down that path, and help people backtrack on it if they already have. First of all, read this, because a lot of this optimization has to do with saving the game.
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
Example output from Isle of Nex:
Image

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
Here you can see that the most used non-minimalSaveState object is floor_trigger with 429 occurrences. None of the full save state objects appear in such great numbers as to be really problematic, although a few (such as mine_ceiling_shaft) could be redesigned a bit to use minimalSaveState safely.
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:
SpoilerShow
WARNING: This script is a last-resort, nuclear bomb option. It's a horrible hack
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...
If you can't figure out how to use the object merger, you can PM me with the complete source files for your dungeon if you want me to do it for you instead, but I don't really want to do that, so it's possible I will refuse. In particular, I won't do this for unfinished dungeons and I won't fix your dungeon's bugs or other bad things.
Last edited by minmay on Fri Aug 21, 2015 10:17 pm, edited 2 times in total.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
Jhaelen
Posts: 74
Joined: Fri Oct 19, 2012 10:49 am
Location: Paris, France

Re: Dungeon optimization tools

Post by Jhaelen »

This sounds very interesting ! I'm now officialy addicted to this thread ;) !
Last edited by Jhaelen on Mon Aug 10, 2015 9:56 am, edited 1 time in total.
User avatar
Drakkan
Posts: 1318
Joined: Mon Dec 31, 2012 12:25 am

Re: Dungeon optimization tools

Post by Drakkan »

Some modders have chosen to ignore the limitations of 32-bit Windows and paid the price for it.
thats not entirely true, because I believe modders are not aware of such a limitation... at least I was not.

Anyway if this will help somehow optimize my dungeon you will have my eternal gratitude and pedestal with your name in my dungeon ;)
Breath from the unpromising waters.
Eye of the Atlantis
User avatar
Jhaelen
Posts: 74
Joined: Fri Oct 19, 2012 10:49 am
Location: Paris, France

Re: Dungeon optimization tools

Post by Jhaelen »

Same as Drakkan, I wasn't aware of such limitations. Maybe it's my fault, I should have red more before starting my mod... If all my areas aren't so linked, I will have separated my mod in two or three ones, but right now it's just impossible.

But I'm not blocked as Drakkan because I can play & test my mod using medium textures.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Dungeon optimization tools

Post by minmay »

Drakkan wrote:thats not entirely true, because I believe modders are not aware of such a limitation... at least I was not.
It should be your responsibility as a developer to know the bare minimum of what the hell you are doing...it's like you tried to change the engine in your car without knowing what oil is.
Drakkan wrote:Anyway if this will help somehow optimize my dungeon you will have my eternal gratitude and pedestal with your name in my dungeon ;)
I would prefer not to have my name associated with it, but okay.

Just a warning, the object merger requires some manual work. You will definitely want to know the basics of regular expressions. And it doesn't save you any memory during editing, it's only for making your release versions more efficient.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Dungeon optimization tools

Post by Isaac »

minmay wrote:Just a warning, the object merger requires some manual work.
Does this involve making multi-tile objects? Making room objects out of discrete floor and wall pieces?
Or ... Is this more like making a single chest that appears in different rooms, with contents that depend on the party's location on the map?
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Dungeon optimization tools

Post by minmay »

No. There are two ways you can use it. For a reversible transformation that hurts file size and startup time/memory usage, but still improves saving performance after startup, it requires:
- copying and pasting reflection maps from your dungeon.lua
- after running my script, copying and pasting a series of object definitions printed from the terminal
- copying and pasting a long passage of code into a script entity (again printed from the terminal)
- if you decide you want to make any changes to the dungeon after this, you need to remove the code in the new script entity (effectively reversing the transformation), and repeat the previous two steps when you want to run the object merger again

For the full optimization, which is NOT reversible but improves both startup and saving, it requires:
- copying and pasting reflection maps from your dungeon.lua
- after running my script, copying and pasting a series of object definitions printed from the terminal
- copying and pasting a couple of lines of code into a script entity
- removing all the merged objects from your dungeon by swapping out tile definitions for tile-spawned objects (definitions for the standard tiles are already provided) and deleting their spawn lines from dungeon.lua (should be easy with an awk program, and even if you don't do that, it would only takes a few minutes in any text editor that supports regexes)

The intended usage is to back up your dungeon.lua before doing the full optimization. Then, when you want to edit the dungeon again, you switch back to the backed up dungeon.lua, and run the full optimization again when you are ready to release the dungeon again. The reversible "optimization" is convenient for preventing save game crashes during testing, but you would not generally want to use it for release.
In addition, the script needs to know not only which objects can be merged, but several pieces of information that it can't get from the scripting interface, mainly OccluderComponent models and AnimationComponent animation tables. So if you have changed any standard objects or added new ones, you need to make sure your changes are reflected in a table with all this information.

Also, if your dungeon does a Bad Thing that transforms or otherwise relies on one of the objects of the types in the table, despite it being in the table and having minimalSaveState in the first place (an item without minimalSaveState is never appropriate for merging), you will have to either change that, or add "nomerge" to the specific object instance's id. But since this only runs on objects you shouldn't be making state changes to in the first place, that shouldn't be a deal breaker. This is also something where you could catch 99% of cases with your awk program.

For example to run it on Isle of Nex the script doesn't need any information added to the table (it comes with the necessary information for standard assets). That table looks like this:

Code: Select all

MERGEABLE_OBJECTS = {
	beach_ground_01 = true,
	beach_rock_wall_01 = {occluder={model = "assets/models/env/beach_rock_wall_01_occluder.fbx"}},
	beach_rock_wall_02 = {occluder={model = "assets/models/env/beach_rock_wall_02_occluder.fbx"}},
	beach_rock_wall_04 = {occluder={model = "assets/models/env/beach_rock_wall_04_occluder.fbx"}},

--	beach_rock_pillar_01 = {pillar=true},
	beach_cattail_wall = {
		animation = {
			animations = {
				sway = "assets/animations/env/beach_cattail_wall_idle.fbx",
			}
		},
	},
--[[ 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.]]
--[[	grass_planes_01 = {
		animation = {
			animations = {
				sway = "assets/animations/env/grass_planes_01_idle.fbx",
			}
		},
		reflectionMode = "never",
	},]]
--	beach_rock_spire_pillar = {pillar=true},
	turtle_nest = 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,

	beacon_tower = true,
	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,

	castle_floor_01 = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	castle_wall_01 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
	castle_wall_02 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
	castle_wall_03 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
	castle_wall_tall_01 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
	castle_wall_bookshelf_01 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
	castle_wall_bookshelf_02 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
--	castle_pillar_01 = {pillar=true},
--	castle_pillar_tall_01 = {pillar=true},
	castle_ceiling_01 = {occluder={model = "assets/models/env/dungeon_ceiling_1111_occluder.fbx"}},
	castle_ceiling = {occluder={model = "assets/models/env/dungeon_ceiling_1111_occluder.fbx"}},
	castle_ceiling_0000 = {occluder={model = "assets/models/env/castle_ceiling_0000_occluder.fbx"}},
	castle_ceiling_0001 = {occluder={model = "assets/models/env/castle_ceiling_0001_occluder.fbx"}},
	castle_ceiling_0010 = {occluder={model = "assets/models/env/castle_ceiling_0010_occluder.fbx"}},
	castle_ceiling_0011 = {occluder={model = "assets/models/env/castle_ceiling_0011_occluder.fbx"}},
	castle_ceiling_0100 = {occluder={model = "assets/models/env/castle_ceiling_0100_occluder.fbx"}},
	castle_ceiling_0101 = {occluder={model = "assets/models/env/castle_ceiling_0101_occluder.fbx"}},
	castle_ceiling_0110 = {occluder={model = "assets/models/env/castle_ceiling_0110_occluder.fbx"}},
	castle_ceiling_0111 = {occluder={model = "assets/models/env/castle_ceiling_0111_occluder.fbx"}},
	castle_ceiling_1000 = {occluder={model = "assets/models/env/castle_ceiling_1000_occluder.fbx"}},
	castle_ceiling_1001 = {occluder={model = "assets/models/env/castle_ceiling_1001_occluder.fbx"}},
	castle_ceiling_1010 = {occluder={model = "assets/models/env/castle_ceiling_1010_occluder.fbx"}},
	castle_ceiling_1011 = {occluder={model = "assets/models/env/castle_ceiling_1011_occluder.fbx"}},
	castle_ceiling_1100 = {occluder={model = "assets/models/env/castle_ceiling_1100_occluder.fbx"}},
	castle_ceiling_1101 = {occluder={model = "assets/models/env/castle_ceiling_1101_occluder.fbx"}},
	castle_ceiling_1110 = {occluder={model = "assets/models/env/castle_ceiling_1110_occluder.fbx"}},
	castle_ceiling_1111 = {occluder={model = "assets/models/env/castle_ceiling_1111_occluder.fbx"}},
	castle_ceiling_strut = true,
	castle_wall_arch = true,
	castle_tall_wall_corridor_end = true,
	castle_ceiling_shaft = true,
	castle_pillar_end = true,
	castle_arena_floor = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	castle_arena_tower = true,
--	castle_arena_merlon = {pillar=true},
--	castle_arena_merlon_outer_corner = {pillar=true},
--	castle_arena_merlon_inner_corner = {pillar=true},
	castle_wall_outside_01 = {occluder={model = "assets/models/env/castle_wall_01_occluder.fbx"}},
	castle_wall_outside_corner_01 = true,
	castle_wall_outside_tall_01 = true,
	castle_gate_outside = true,
	castle_tower_01 = true,
	castle_tower_tall_01 = true,
	castle_sidewings_01 = true,

--	cemetery_pillar_01 = {pillar=true},

	dungeon_floor_01 = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	dungeon_floor_dirt_01 = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	dungeon_wall_01 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	dungeon_wall_02 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
--	dungeon_pillar = {pillar=true},
--	dungeon_pillar_high = {pillar=true},
--	dungeon_pillar_top = {pillar=true},
	dungeon_ceiling = {occluder={model = "assets/models/env/dungeon_ceiling_1111_occluder.fbx"}},
	dungeon_ceiling_0000 = {occluder={model = "assets/models/env/dungeon_ceiling_0000_occluder.fbx"}},
	dungeon_ceiling_0001 = {occluder={model = "assets/models/env/dungeon_ceiling_0001_occluder.fbx"}},
	dungeon_ceiling_0010 = {occluder={model = "assets/models/env/dungeon_ceiling_0010_occluder.fbx"}},
	dungeon_ceiling_0011 = {occluder={model = "assets/models/env/dungeon_ceiling_0011_occluder.fbx"}},
	dungeon_ceiling_0100 = {occluder={model = "assets/models/env/dungeon_ceiling_0100_occluder.fbx"}},
	dungeon_ceiling_0101 = {occluder={model = "assets/models/env/dungeon_ceiling_0101_occluder.fbx"}},
	dungeon_ceiling_0110 = {occluder={model = "assets/models/env/dungeon_ceiling_0110_occluder.fbx"}},
	dungeon_ceiling_0111 = {occluder={model = "assets/models/env/dungeon_ceiling_0111_occluder.fbx"}},
	dungeon_ceiling_1000 = {occluder={model = "assets/models/env/dungeon_ceiling_1000_occluder.fbx"}},
	dungeon_ceiling_1001 = {occluder={model = "assets/models/env/dungeon_ceiling_1001_occluder.fbx"}},
	dungeon_ceiling_1010 = {occluder={model = "assets/models/env/dungeon_ceiling_1010_occluder.fbx"}},
	dungeon_ceiling_1011 = {occluder={model = "assets/models/env/dungeon_ceiling_1011_occluder.fbx"}},
	dungeon_ceiling_1100 = {occluder={model = "assets/models/env/dungeon_ceiling_1100_occluder.fbx"}},
	dungeon_ceiling_1101 = {occluder={model = "assets/models/env/dungeon_ceiling_1101_occluder.fbx"}},
	dungeon_ceiling_1110 = {occluder={model = "assets/models/env/dungeon_ceiling_1110_occluder.fbx"}},
	dungeon_ceiling_1111 = {occluder={model = "assets/models/env/dungeon_ceiling_1111_occluder.fbx"}},
	dungeon_ceiling_wall = true,
	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_wall_ivy_01 = true,
	dungeon_wall_ivy_02 = true,
	dungeon_support_ceiling_01 = true,
	dungeon_wall_open = true,
	dungeon_wall_drain = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	catacomb_ceiling = {occluder={model = "assets/models/env/catacomb_ceiling_occluder.fbx"}},
	catacomb_ceiling_shaft = true,
	catacomb_alcove_01 = {occluder={model = "assets/models/env/catacomb_alcove_01_occluder.fbx"}},
	catacomb_alcove_02 = {occluder={model = "assets/models/env/catacomb_alcove_01_occluder.fbx"}},
	catacomb_alcove_03 = {occluder={model = "assets/models/env/catacomb_alcove_01_occluder.fbx"}},
	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 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	dungeon_wall_broken_02 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	dungeon_wall_height_difference = {occluder={model = "assets/models/env/dungeon_wall_height_difference_occluder.fbx"}},
	ceiling_roots_01 = true,

	forest_ground_01 = true,
	forest_wall_01 = {occluder={model = "assets/models/env/beach_rock_wall_01_occluder.fbx"}},
	forest_wall_02 = {occluder={model = "assets/models/env/beach_rock_wall_02_occluder.fbx"}},
	forest_hedge_01 = true,
	forest_pillar_01 = true,
	forest_grass_01 = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_grass_01_idle.fbx",
				}
			},
			reflectionMode = "never",
	},
	forest_heather = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_heather_idle.fbx",
				}
			},
			reflectionMode = "never",
	},
	swamp_heather = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_heather_idle.fbx",
				}
			},
			reflectionMode = "never",
	},
	swamp_heather_low = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_heather_idle.fbx",
				}
			},
			reflectionMode = "never",
	},
	swamp_grass_01 = {
			animation = {
				animations = {
					sway = "assets/animations/env/grass_planes_01_idle.fbx",
				}
			},
			reflectionMode = "never",
	},
	forest_oak = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_oak_idle.fbx",
				}
			},
	},
	forest_oak_stump = true,
	forest_oak_trunk = true,
	forest_spruce_sapling_pillar = true,
	forest_ruins_ceiling = true,
	forest_ruins_ceiling_flat = true,
	forest_ruins_wall_ivy_01 = true,
	forest_ruins_wall_ivy_02 = true,
--	forest_ruins_pillar_01 = {pillar=true},
--	forest_ruins_pillar_02 = {pillar=true},
--	forest_ruins_pillar_03 = {pillar=true},
	forest_statue_pillar_01 = true,
	forest_statue_pillar_02 = true,
	forest_statue_pillar_03 = 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_middle_pillar = true,
	forest_bridge_pillar = true,
	forest_elevation_edge = true,
	forest_elevation_ledge = true,
	--forest_chasm_edge = true, -- forest_chasm would create duplicates
	--forest_chasm_corner = true,
	forest_border_rocks_01 = true,
	forest_seaweed_pillar = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_seaweed_pillar_idle.fbx",
				}
			},
	},
	forest_seaweed_wall_decoration_01 = true,
	forest_seaweed_floor_01 = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_seaweed_floor_01_idle.fbx",
				}
			},
	},
	forest_seaweed_floor_02 = {
			animation = {
				animations = {
					sway = "assets/animations/env/forest_seaweed_floor_02_idle.fbx",
				}
			},
	},
	forest_seaweed_wall_decoration_02 = true,
	forest_elevation_edge_overhang = true,

	mine_floor_01 = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	mine_wall_01 = {occluder={model = "assets/models/env/mine_wall_01_occluder.fbx"}},
	mine_pillar_01 = {occluder={model = "assets/models/env/mine_pillar_01_occluder.fbx"}},
	mine_pillar_hatless_01 = {occluder={model = "assets/models/env/mine_wall_01_occluder.fbx"}},
	mine_pillar_02 = {occluder={model = "assets/models/env/mine_wall_01_occluder.fbx"}},
	mine_pillar_03 = {occluder={model = "assets/models/env/mine_wall_01_occluder.fbx"}},
	mine_pillar_crystal = {
		occluder={model = "assets/models/env/mine_wall_01_occluder.fbx"},
		crystal_light_01={fillLight=true},
		crystal_light_02={fillLight=true},
		crystal_light_03={fillLight=true},
		crystal_light_04={fillLight=true},
		crystal_light_05={fillLight=true},
		crystal_light_06={fillLight=true},
		crystal_light_07={fillLight=true},
		particle
		},
	mine_ceiling_01 = {occluder={model = "assets/models/env/mine_ceiling_01_occluder.fbx"}},
	mine_ceiling_02 = {occluder={model = "assets/models/env/mine_ceiling_01_occluder.fbx"}},
	--mine_chasm_edge = true, -- mine_chasm would create duplicates
	mine_support_beam_01 = true,
	mine_support_ceiling_01 = true,
	mine_floor_sandpile = true,
	mine_elevation_edge = {occluder={model = "assets/models/env/mine_elevation_edge_occluder.fbx"}},
	mine_ceiling_roots_01 = true,
	--mine_ceiling_shaft_edge = true, -- mine_ceiling_shaft would create duplicates
	mine_ceiling_shaft_roots_01 = true,
	mine_moss_wall_01 = {occluder={model = "assets/models/env/mine_wall_01_occluder.fbx"}},
	mine_moss_pillar_01 = {occluder={model = "assets/models/env/mine_pillar_01_occluder.fbx"}},
	mine_moss_pillar_hatless_01 = {occluder={model = "assets/models/env/mine_pillar_01_occluder.fbx"}},
	mine_moss_pillar_02 = {occluder={model = "assets/models/env/mine_pillar_01_occluder.fbx"}},
	mine_moss_pillar_03 = {occluder={model = "assets/models/env/mine_pillar_01_occluder.fbx"}},
	mine_moss_ceiling_01 = {occluder={model = "assets/models/env/mine_ceiling_01_occluder.fbx"}},
	mine_moss_ceiling_02 = {occluder={model = "assets/models/env/mine_ceiling_01_occluder.fbx"}},
	mine_moss_ceiling_03 = {occluder={model = "assets/models/env/mine_ceiling_01_occluder.fbx"}},

	swamp_oak = true,
	swamp_mushrooms_01 = true,
	swamp_wall_ivy_01 = true,
	swamp_wall_ivy_02 = true,

	tomb_floor_01 = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	tomb_floor_02 = {occluder={model = "assets/models/env/dungeon_floor_01_occluder.fbx"}},
	tomb_wall_01 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_02 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_03 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_04 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_ornament = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
--	tomb_pillar = {pillar=true},
	tomb_ceiling_01 = {occluder={model = "assets/models/env/catacomb_ceiling_occluder.fbx"}},
	tomb_ceiling_shaft = true,
	tomb_wall_sarcophagus = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_sarcophagus_empty = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_sarcophagus_mummy = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_face = true,
--	snake_pillar = {pillar=true},
	gold_mask_statue_wall = true,
	tomb_floor_dirt = true,
	tomb_wall_painting_1 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_painting_2 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_painting_3 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_wall_painting_4 = {occluder={model = "assets/models/env/dungeon_wall_01_occluder.fbx"}},
	tomb_pyramid_top = true,
	tomb_pyramid_side = true,
	tomb_inside_top_ceiling = true,
}
However it does require either removing script_entity_43 or adding "nomerge" to turtle_nest_7's id, because that script entity changes the world position of said turtle nest - even though this change vanishes as soon as the player saves and reloads! This is the only really Bad Thing that Isle of Nex tries, AFAIK, and it's obviously a mistake in the first place so it's no problem to remove it. However, I haven't finished testing for lingering issues yet.
Basically, the transformation is uniquely dangerous if your mod has dumb code in it, and you absolutely need to fully test your dungeon AFTER applying it.

(ignore the pillars being commented out, they won't be like that for long)
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
Komag
Posts: 3658
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: Dungeon optimization tools

Post by Komag »

minmay wrote:
Drakkan wrote:thats not entirely true, because I believe modders are not aware of such a limitation... at least I was not.
It should be your responsibility as a developer to know the bare minimum of what the hell you are doing...it's like you tried to change the engine in your car without knowing what oil is.
Dude, we know you're smart, and your stuff is helpful, but you're being a real jerk when you say things like that. I don't care how right you think you are, you could use some tact and just be nicer about it. I suggest you consider cutting people some slack, and maybe stop being holier-than-thou when it comes to the rest of us editing as a fun hobby for a game we're fans of. Why hurt people's feelings when there's no need to?

You're so knowledgeable and helpful overall that you've proven very valuable, and creating and releasing tools like these really do a lot of good for others. If you just combined that with being a bit kinder, it would go a long way toward earning the respect of us all.
Finished Dungeons - complete mods to play
Azel
Posts: 808
Joined: Thu Nov 06, 2014 10:40 pm

Re: Dungeon optimization tools

Post by Azel »

minmay wrote:It should be your responsibility as a developer to know the bare minimum of what the hell you are doing...it's like you tried to change the engine in your car without knowing what oil is.
The "bare minimum" is on this website:
http://www.grimrock.net/modding

Unless I missed something, the MinimalSave state property is nowhere to be found. It's not even on the page dedicated to Save Games and Variables:
http://www.grimrock.net/modding/save-ga ... variables/

So this is not a case of "developers not knowing the bare minimum."

The real reason this bothers you so much is because you posted about it here:
http://grimrock.net/forum/viewtopic.php?f=22&t=9366

... and most people ignored you. But the reason people ignore you so often is because you pollute your good intentions with terrible elitist comments like the one I just quoted. I actually feel bad for Drakkan because he is a very talented Modder with wonderful ideas (far more impressive than anything presented in "Lost Halls") but due to the fact that the Grimrock Editor doesn't provide invisible 'thin' walls with MinimalSaveState='true' ... he has to constantly endure verbal abuse from The Minmay.

Well, I just hope that Almost Human gives the Grimrock editor a much needed update soon so that the rest of us who are here for fun and encouragement can get back to doing exactly that.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Dungeon optimization tools

Post by minmay »

Geez, it has nothing to do with "smart", I just don't like it when people release broken mods or assets. It wastes players' and modders' time. But if it makes you all feel better this was going to be my last substantial post for a long time anyway (probably until my next mod release), and definitely my most bitter one.

Anyway, the final count for merged-away objects on Isle of Nex is at 45565, or 88.5% of the minimalSaveState objects. It could be squeezed further by adding a few more special cases but I don't think that's worth it and will leave it up to users if they really want. Even on large dungeons the script should be able to merge all of the levels within a minute on most computers, assuming the script itself doesn't run out of memory (if it does, you can just process one level at a time instead of all of them at once). Script should actually be posted here within the week.

I'm also really curious what reasonable features Azel thinks could possibly be added to the editor itself that would help with this, lol. I guess when you place the 60,000th object the editor could have a pop-up box saying "damn bro you have a crap-ton of crap in this here dungeon, i dunno if im gonna be able to save all that shizz when you play it, are you sure you dont wanna delete some of it?"
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Post Reply