Page 1 of 4

[TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 4:30 am
by Edsploration
I made a webpage/javascript which will analyze your custom Legend of Grimrock dungeon and give you some tables of information about it! Distribution of XP, difficulty, party leveling curve, that sort of thing.

It's available here:
https://dl.dropbox.com/u/18420968/LoG/E ... lyzer.html

Feel free to download and save your own copy. The whole thing is in one html file so anyone can easily view/edit/expand the code if they like.
If you want to see it in action, here is a sample dungeon.lua file which can be pasted into the Analyzer:

Code: Select all

-- This file has been generated by Dungeon Editor 1.3.1

--- level 1 ---

mapName("The Aqueduct")
setWallSet("dungeon")
playStream("assets/samples/music/dungeon_ambient.ogg")
mapDesc([[
################################
################################
################################
################################
#############....###############
#############....###############
#############.......############
#############....##.############
###############.###.############
########....###.###.###..#######
########.##......##......#######
######....####...##.###..#######
######....#########.##########..
######....#########.#########..#
######....####......#######...##
######...........##########.####
######....####...##########.####
######....####.####.........####
##############.####.############
##############.####.############
######..............############
######.#######.#######.#########
######.######..........#########
######.######....###############
######.######....###############
###....#########################
###.############################
##..############################
##.#############################
#..#############################
..##############################
.###############################
]])
spawn("torch_holder", 15,14,0, "torch_holder_1")
	:addTorch()
spawn("dungeon_stairs_down", 22,21,0, "dungeon_stairs_down_1")
spawn("starting_location", 5,25,1, "starting_location")
spawn("snail", 20,17,3, "snail_1")
spawn("snail", 31,12,3, "snail_2")
	:setLevel(3)
spawn("snail", 12,20,3, "snail_3")
spawn("herder", 24,9,2, "herder_1")
spawn("herder", 16,11,0, "herder_2")
spawn("snail", 22,22,3, "snail_4")
	:setLevel(2)
spawn("snail", 6,15,1, "snail_5")
spawn("snail", 7,12,2, "snail_6")
spawn("herder", 16,10,3, "herder_3")
spawn("healing_crystal", 14,5,1, "healing_crystal_1")
spawn("tome_willpower", 31,12,1, "tome_willpower_1")
spawn("long_sword", 6,25,3, "long_sword_1")
spawn("long_sword", 6,25,3, "long_sword_2")
spawn("legionary_spear", 6,25,3, "legionary_spear_1")
spawn("spawner", 31,12,3, "spawner_3")
	:setSpawnedEntity("snail")
	:setCoolDown(0)

--- level 2 ---

mapName("Forgotten Ruins")
setWallSet("dungeon")
playStream("assets/samples/music/dungeon_ambient.ogg")
mapDesc([[
################################
################################
################################
################################
################################
######################.#########
######...###....#####...########
######..........####.....#######
######...###....#..###.#########
############.##.#..###.#########
############.##.##.###.#########
######...#...#...#.#.....#######
######.......#...#.#.....##...##
######...#...#...........##...##
############.#...###.....##...##
############.##.####..........##
######...##......###.....####.##
######...........###.....####.##
######...##......###.....##...##
#############.#######...###...##
#############.########.#########
###########.....######.#########
###########.#.#.################
###########.#.#.################
###########.#.#.####...#########
###########.#.#.####.......#####
##########.......###...###.#####
##########.............###.#####
##########.......#######....####
############.#.#########....####
##########...#...#######....####
################################
]])
spawn("dungeon_stairs_up", 22,21,2, "dungeon_stairs_up_1")
spawn("dungeon_stairs_down", 22,5,0, "dungeon_stairs_down_2")
spawn("crowern", 11,27,2, "crowern_1")
spawn("spider", 27,30,0, "spider_1")
spawn("herder_small", 27,19,1, "herder_small_1")
spawn("snail", 6,18,0, "snail_7")
spawn("snail", 6,16,2, "snail_8")
spawn("snail", 7,16,2, "snail_9")
spawn("herder", 7,11,3, "herder_4")
spawn("herder", 6,13,0, "herder_5")
spawn("crowern", 6,7,1, "crowern_2")
spawn("skeleton_warrior", 22,12,2, "skeleton_warrior_1")
spawn("snail", 22,13,2, "snail_10")
spawn("skeleton_warrior", 15,17,0, "skeleton_warrior_2")
spawn("skeleton_warrior", 13,7,1, "skeleton_warrior_3")
spawn("skeleton_warrior", 21,25,3, "skeleton_warrior_4")
spawn("snail", 20,7,1, "snail_11")
spawn("snail", 24,7,3, "snail_12")
spawn("healing_crystal", 13,27,3, "healing_crystal_2")
spawn("tome_armors", 16,30,1, "tome_armors_1")
spawn("tome_vitality", 6,11,3, "tome_vitality_1")
spawn("spawner", 10,30,1, "spawner_1")
	:setSpawnedEntity("snail")
	:setCoolDown(0)
spawn("pressure_plate_hidden", 12,29,2, "pressure_plate_hidden_1")
	:setTriggeredByParty(true)
	:setTriggeredByMonster(true)
	:setTriggeredByItem(true)
	:setSilent(true)
	:addConnector("activate", "spawner_1", "activate")

--- level 3 ---

mapName("Deep Ruins")
setWallSet("dungeon")
playStream("assets/samples/music/dungeon_ambient.ogg")
mapDesc([[
################################
####################.....#######
####################.###.#######
####################.....#######
###########....#####.#.#.#######
###########....#####.#.#.##....#
###########....#####.###.##....#
###########....................#
####################.....##....#
###########....................#
##########.....#####.....##....#
###########....#################
##...##...#...........##########
##........#.#.#####...##########
##...##...#####.......##########
###.#####.#......###############
###.#...#.....#......###########
##....#.#.#...####...###########
##....#...#...####.....#########
#######...############.#########
##...################..#########
##...#..#..#..#..#..#..#########
##.........................#####
##...#..#..#..#..#..#..###.#####
##...#####################.#####
##############....######.#..####
##############.##.#####.....####
##########..#...###.......#.####
##########......##........######
#############......##.##.#######
#############...####...#########
################################
]])
spawn("dungeon_stairs_up", 22,5,2, "dungeon_stairs_up_2")
spawn("dungeon_stairs_down", 17,26,2, "dungeon_stairs_down_3")
spawn("spider", 7,23,0, "spider_2")
spawn("spider", 10,21,2, "spider_3")
spawn("snail", 19,23,0, "snail_13")
spawn("scavenger", 3,20,2, "scavenger_1")
spawn("scavenger", 3,24,0, "scavenger_2")
spawn("herder", 15,21,2, "herder_6")
spawn("herder_small", 12,23,0, "herder_small_2")
spawn("skeleton_warrior", 7,19,0, "skeleton_warrior_5")
spawn("skeleton_warrior", 8,13,2, "skeleton_warrior_6")
spawn("skeleton_archer", 3,14,3, "skeleton_archer_1")
spawn("scavenger", 12,11,2, "scavenger_3")
spawn("scavenger", 22,30,3, "scavenger_4")
spawn("scavenger", 23,26,2, "scavenger_5")
spawn("spider", 11,28,1, "spider_4")
spawn("herder_small", 21,10,0, "herder_small_3")
spawn("skeleton_warrior", 12,6,1, "skeleton_warrior_7")
spawn("skeleton_warrior", 13,5,2, "skeleton_warrior_8")
spawn("snail", 29,5,2, "snail_14")
spawn("snail", 28,5,2, "snail_15")
spawn("skeleton_patrol", 16,15,2, "skeleton_patrol_1")
spawn("healing_crystal", 3,22,2, "healing_crystal_3")
spawn("tome_health", 4,18,2, "tome_health_2")
spawn("tome_dodge", 5,18,2, "tome_dodge_1")

--- level 4 ---

mapName("Halls of Paradise")
setWallSet("dungeon")
playStream("assets/samples/music/dungeon_ambient.ogg")
mapDesc([[
################################
##..#..#..#..#..#..#..#..#..#..#
##..#..#..#..#..#..#..#..#..#..#
##.##.##.##.##.##.##.##.##.##.##
...............................#
.#############################.#
.........#...................#.#
..#......#..#...#..#..#...#..#.#
.##......#.###.###.#.##...##.#.#
....#....#..#...#..#....#....#.#
...#.#...#.........#...#.#...#.#
.........#..#...#..#.........#.#
......##.#.###.###.#.##...##.#.#
......#..#..#...#..#..#...#..#.#
...................#...........#
##############.###############.#
##############.###############.#
##############.#########...#...#
######...####...#...####.#...###
###........##...#...####.#######
###.##...#.##...###.##....######
##...##.##......###.......######
##...#...####...######....######
##...#.#.#####.#########.#######
##.###.#...###.#########.#.#.#.#
##.#...#...###.#########........
##.#...#...###.##.########.#.#.#
##.#...#####...#...#############
#..##.##....................####
#..##....###...######.......####
#####################.......####
#####################.#.#.#.####
]])
spawn("dungeon_stairs_up", 17,26,0, "dungeon_stairs_up_3")
spawn("dungeon_stairs_down", 24,10,0, "dungeon_stairs_down_4")
spawn("skeleton_archer", 1,12,1, "skeleton_archer_2")
spawn("skeleton_archer", 8,7,3, "skeleton_archer_3")
spawn("skeleton_archer", 15,14,1, "skeleton_archer_4")
spawn("skeleton_archer", 11,6,1, "skeleton_archer_5")
spawn("skeleton_warrior", 3,9,3, "skeleton_warrior_9")
spawn("skeleton_warrior", 15,10,3, "skeleton_warrior_10")
spawn("skeleton_warrior", 10,11,2, "skeleton_warrior_11")
spawn("snail", 20,14,1, "snail_16")
spawn("snail", 20,13,1, "snail_17")
spawn("snail", 20,12,1, "snail_18")
spawn("snail", 21,13,1, "snail_19")
spawn("snail", 21,14,1, "snail_20")
spawn("snail", 14,1,2, "snail_21")
spawn("herder", 8,1,2, "herder_7")
spawn("herder_small", 27,1,2, "herder_small_4")
spawn("herder_swarm", 3,1,2, "herder_swarm_1")
spawn("spider", 31,25,3, "spider_5")
spawn("spider", 28,24,2, "spider_6")
spawn("skeleton_warrior", 1,29,0, "skeleton_warrior_12")
spawn("crowern", 27,30,3, "crowern_3")
spawn("crowern", 25,28,3, "crowern_4")
spawn("skeleton_archer", 27,28,2, "skeleton_archer_6")
spawn("crowern", 24,1,3, "crowern_5")
spawn("skeleton_warrior", 10,25,0, "skeleton_warrior_13")
spawn("skeleton_warrior", 5,26,3, "skeleton_warrior_14")
spawn("skeleton_warrior", 7,18,2, "skeleton_warrior_15")
spawn("skeleton_patrol", 14,19,2, "skeleton_patrol_2")
spawn("skeleton_archer", 13,18,2, "skeleton_archer_7")
spawn("healing_crystal", 4,10,2, "healing_crystal_4")
spawn("tome_experience", 14,10,0, "tome_experience_1")
spawn("tome_protection", 21,1,0, "tome_protection_1")
spawn("tome_wisdom", 6,1,0, "tome_wisdom_1")

--- level 5 ---

mapName("Shattered Temple")
setWallSet("temple")
playStream("assets/samples/music/temple_ambient.ogg")
mapDesc([[
#######################...######
#################..##.......##..
#########.#.#.###..##.#...#.##..
########.......##...............
########.......##..##.##.##.##..
#########.#.#.###..##.##.##.##..
###########.#########.##.##.####
##....####....#####...##.##...##
##....#..#....##..#.####.####.##
##....#..#..........####.####...
####.###.#....##..######.#######
####.##..#.#####.###############
####.......#####....############
####.##...#######...############
####.##...##....#...####....####
##....#...#####.###.......######
##....###.#####.....####....####
##....##...#....#..#####...#####
########.#.#######.#######.#####
######...#...#####.#######.#####
####...#####.......#####...#####
####.#...#...##....#####.#.#####
####.###.#.####....#####...#####
####...#...#####.###############
####...##.###....####.##########
####......###....####.##########
#####.#######....####.##########
#####.##############........####
#####.#.#######.####......#.#...
#####...........................
#########.##.#####.#......#.#...
####################........####
]])
spawn("temple_stairs_up", 24,10,2, "temple_stairs_up_1")
spawn("temple_stairs_down", 21,24,0, "temple_stairs_down_1")
spawn("skeleton_archer_patrol", 17,1,0, "skeleton_archer_patrol_1")
	:setLevel(2)
spawn("green_slime", 31,4,2, "green_slime_1")
spawn("green_slime", 31,1,3, "green_slime_2")
spawn("skeleton_archer_patrol", 17,5,1, "skeleton_archer_patrol_2")
	:setLevel(2)
spawn("tentacles", 13,14,1, "tentacles_1")
spawn("tentacles", 12,30,0, "tentacles_2")
spawn("tentacles", 18,30,0, "tentacles_3")
spawn("uggardian", 31,29,3, "uggardian_1")
spawn("green_slime", 3,8,0, "green_slime_3")
spawn("green_slime", 5,7,3, "green_slime_4")
spawn("green_slime", 2,9,1, "green_slime_5")
spawn("scavenger_swarm", 24,22,1, "scavenger_swarm_1")
spawn("scavenger_swarm", 25,20,1, "scavenger_swarm_2")
spawn("scavenger", 24,20,1, "scavenger_6")
spawn("scavenger", 25,22,0, "scavenger_7")
spawn("herder_big", 9,2,2, "herder_big_1")
spawn("herder_big", 13,2,2, "herder_big_2")
spawn("skeleton_patrol", 8,21,3, "skeleton_patrol_3")
	:setLevel(5)
spawn("skeleton_patrol", 10,19,1, "skeleton_patrol_4")
	:setLevel(2)
spawn("skeleton_patrol", 8,19,3, "skeleton_patrol_5")
	:setLevel(4)
spawn("skeleton_patrol", 10,21,1, "skeleton_patrol_6")
	:setLevel(3)
spawn("snail", 2,17,1, "snail_22")
spawn("spider", 17,10,0, "spider_7")
spawn("shrakk_torr", 7,8,2, "shrakk_torr_1")
spawn("herder_big", 13,26,1, "herder_big_3")
spawn("herder_small", 13,25,1, "herder_small_5")
	:setLevel(3)
spawn("herder_swarm", 13,24,1, "herder_swarm_2")
	:setLevel(2)
spawn("herder", 14,25,1, "herder_8")
	:setLevel(4)
spawn("healing_crystal", 18,13,2, "healing_crystal_5")
spawn("tome_ice", 24,21,1, "tome_ice_1")
spawn("tome_fire", 31,30,3, "tome_fire_1")
spawn("tome_resist", 31,28,1, "tome_resist_1")
spawn("spawner", 20,28,1, "spawner_2")
	:setSpawnedEntity("uggardian")
	:setCoolDown(0)
spawn("pressure_plate_hidden", 26,31,0, "pressure_plate_hidden_2")
	:setTriggeredByParty(true)
	:setTriggeredByMonster(true)
	:setTriggeredByItem(true)
	:setActivateOnce(true)
	:setSilent(true)
	:addConnector("activate", "spawner_4", "activate")
spawn("pressure_plate_hidden", 26,27,0, "pressure_plate_hidden_3")
	:setTriggeredByParty(true)
	:setTriggeredByMonster(true)
	:setTriggeredByItem(true)
	:setActivateOnce(true)
	:setSilent(true)
	:addConnector("activate", "spawner_2", "activate")
spawn("spawner", 20,30,1, "spawner_4")
	:setSpawnedEntity("uggardian")
	:setCoolDown(0)

--- level 6 ---

mapName("The Transient Vessel")
setWallSet("temple")
playStream("assets/samples/music/temple_ambient.ogg")
mapDesc([[
################################
################################
################################
################################
################################
################################
############.................###
############.................###
##########...................###
##########.#.................###
##########.#.................###
############.................###
############..#.....#...#....###
############..#.#...#...#.#..###
#############.#.#.#.#.#.#.#.####
#############.#.#.#.#.#.#.#.####
#############...#.#.#.#...#.####
#############.###.#.#.###.#.####
#############.....#...###...####
#################.###.###.######
#################.###.....######
#################.###.##########
#################.....##########
#####################.##########
#####################.##########
################################
################################
################################
################################
################################
################################
################################
]])
spawn("temple_stairs_up", 21,24,2, "temple_stairs_up_2")
spawn("temple_stairs_down", 10,10,2, "temple_stairs_down_2")
spawn("uggardian", 14,6,2, "uggardian_2")
spawn("uggardian", 17,6,3, "uggardian_3")
spawn("uggardian", 20,6,1, "uggardian_4")
	:setLevel(3)
spawn("uggardian", 23,6,0, "uggardian_5")
spawn("uggardian", 26,6,2, "uggardian_6")
spawn("wyvern", 17,7,2, "wyvern_1")
spawn("wyvern", 23,7,2, "wyvern_2")
spawn("shrakk_torr", 12,13,0, "shrakk_torr_2")
spawn("shrakk_torr", 28,13,0, "shrakk_torr_3")

--- level 7 ---

mapName("Temporal Lapse")
setWallSet("temple")
playStream("assets/samples/music/dream.ogg")
mapDesc([[
################################
################################
################################
################################
################################
################################
################################
#####################...########
###################.....########
###################.#...########
##########.########.############
########.....###.....###########
########.....###.....###########
########.....###.....###########
###########.#####.#.############
###########....##.#...##########
###########.......###.##########
###########....######.##########
##################......########
##################......########
##################......########
###################.##.#########
############.....##.##.#########
############.#.#.##.##.#########
############.....#......########
############.....#......########
#############...##......########
##############.#####.###########
##############.......###########
################################
################################
################################
]])
spawn("temple_stairs_up", 10,10,0, "temple_stairs_up_3")
spawn("ogre", 12,22,1, "ogre_1")
	:setLevel(2)
spawn("crab", 23,9,3, "crab_1")
spawn("crab", 23,7,3, "crab_2")
spawn("skeleton_archer_patrol", 19,25,0, "skeleton_archer_patrol_3")
	:setLevel(5)
spawn("skeleton_archer_patrol", 22,25,0, "skeleton_archer_patrol_4")
	:setLevel(5)
spawn("scavenger", 23,8,3, "scavenger_8")
spawn("spider", 14,16,3, "spider_8")
	:setLevel(5)
spawn("shrakk_torr", 21,16,0, "shrakk_torr_4")
spawn("healing_crystal", 9,12,2, "healing_crystal_6")
spawn("tome_strength", 14,22,0, "tome_strength_1")
Features:
:arrow: Counts monsters (lvl 1-5), monster spawners, and tomes (including Ixnatifual's Tome Library).
:arrow: Update v1.1: Counts secrets.
:arrow: Counts XP available on each dungeon level. Note that a champion only gets 50% XP if they do not land an attack on a monster, so it's possible to not get "100% XP Obtained" even if you kill everything. The 90% column may be a more realistic representation of the average player's party.
:arrow: Includes a Suggested Level (SL) rating to indicate the highest difficulty monster on the dungeon level.

Upcoming Features:
:arrow: Tracking of stats gained from tomes. (Experience Tome is already tracked.)
:arrow: Manually add/subtract XP per level and recalculate the projected leveling curves.

Potential Upcoming Features:
:arrow: Counts all equipment and keeps track of their Suggested Levels (SL's).
:arrow: Auto-Generation of LUA code which would level up your champions and equip them with the best obtainable items up to a specific level X in your dungeon. That is, they will have roughly the same stats/equipment as if they had reached that point in the dungeon in a normal playthrough.
:arrow: Other useful statistics?

What do you mean by Suggested Level?
SL is my own creation; a metric for measuring the relative strength of items and difficulty of monsters. A rule of thumb is a monster should probably not be placed where they would be fought more than one SL above the party level.

Note that this Dungeon Analyzer only tracks monster SL's at this point. The SL listed alongside dungeon level VS party level is simply the highest monster SL on that dungeon level.

SL's for for most monsters/items are listed in this stats spreadsheet: https://docs.google.com/spreadsheet/ccc ... FBTNU16SGc
Item SL's, especially armors, are much more versatile and should be spread widely. That is, don't actually put the whole valor set on the same dungeon level (unless you want to).

In closing...
Hopefully this can be/become a valuable tool for dungeon designers. I intend it to be used in the upcoming Community FrankenDungeon for keeping each designer's dungeon level more or less appropriate for their chosen difficulty tier.

Please let me know of any feedback, questions, or advice!

Re: [TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 4:35 am
by Neikun
This looks very cool, but I need to go to bed. I will look at it in the morning.
Thanks in advance.

Re: [TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 5:25 am
by crisman
This is awesome, seems my dungeon is a bit tough at the beginning, but overall is balanced :D
Great tool!

Re: [TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 8:38 am
by Kuningas
This seems really cool -- I'll test it once I get back home (I gotta run in a few minutes).

Re: [TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 10:20 am
by GoldCoast
Great tool, THANKS :)!

Re: [TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 10:53 am
by JohnWordsworth
That's a genius idea, great work. Will be very useful for getting a feel of dungeon difficulty and I think it'll really help dungeon-creators to quickly and easily get a feel for the difficulty curve of their dungeon.

Re: [TOOL] Custom Dungeon Analyzer

Posted: Wed Oct 10, 2012 12:46 pm
by Komag
holy cow, this is impressive work! Thanks!

Re: [TOOL] Custom Dungeon Analyzer

Posted: Fri Oct 12, 2012 6:02 pm
by SpiderFighter
Brialliant tool!!! Thanks for doing this, Eds! I've been looking for an easier way to figure out toal XP per level. Also, the output let me see a spawner I had forgotten about. :)

Re: [TOOL] Custom Dungeon Analyzer

Posted: Fri Oct 12, 2012 7:39 pm
by pulpum
excellent idea! thank you!


ps: I don't know for you, but grimrock's community ROCKS! :D

Re: [TOOL] Custom Dungeon Analyzer

Posted: Sat Oct 13, 2012 2:52 am
by Edsploration
I'm glad some are finding this useful, so I'll definitely expand on it! :mrgreen:
I realize many custom dungeons may supply XP in their own ways. I'll add in the capability to manually enter extra XP on any level and recalculate the charts.