[TOOL] Custom Dungeon Analyzer

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
User avatar
Edsploration
Posts: 104
Joined: Wed Sep 19, 2012 4:32 pm

[TOOL] Custom Dungeon Analyzer

Post 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!
Last edited by Edsploration on Wed Oct 17, 2012 11:24 pm, edited 2 times in total.
Open Project -> Community FrankenDungeon: viewtopic.php?f=14&t=4276
User avatar
Neikun
Posts: 2457
Joined: Thu Sep 13, 2012 1:06 pm
Location: New Brunswick, Canada
Contact:

Re: [TOOL] Custom Dungeon Analyzer

Post by Neikun »

This looks very cool, but I need to go to bed. I will look at it in the morning.
Thanks in advance.
"I'm okay with being referred to as a goddess."
Community Model Request Thread
See what I'm working on right now: Neikun's Workshop
Lead Coordinator for Legends of the Northern Realms Project
  • Message me to join in!
User avatar
crisman
Posts: 305
Joined: Sat Sep 22, 2012 9:23 pm
Location: Italy

Re: [TOOL] Custom Dungeon Analyzer

Post by crisman »

This is awesome, seems my dungeon is a bit tough at the beginning, but overall is balanced :D
Great tool!
User avatar
Kuningas
Posts: 268
Joined: Wed Apr 11, 2012 10:29 pm
Location: Northern Finland

Re: [TOOL] Custom Dungeon Analyzer

Post by Kuningas »

This seems really cool -- I'll test it once I get back home (I gotta run in a few minutes).
BASILEUS
GoldCoast
Posts: 4
Joined: Tue Oct 09, 2012 6:19 pm
Location: Germany

Re: [TOOL] Custom Dungeon Analyzer

Post by GoldCoast »

Great tool, THANKS :)!
Greetings... GoldCoast
User avatar
JohnWordsworth
Posts: 1397
Joined: Fri Sep 14, 2012 4:19 pm
Location: Devon, United Kingdom
Contact:

Re: [TOOL] Custom Dungeon Analyzer

Post 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.
My Grimrock Projects Page with links to the Grimrock Model Toolkit, GrimFBX, Atlas Toolkit, QuickBar, NoteBook and the Oriental Weapons Pack.
User avatar
Komag
Posts: 3658
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: [TOOL] Custom Dungeon Analyzer

Post by Komag »

holy cow, this is impressive work! Thanks!
Finished Dungeons - complete mods to play
User avatar
SpiderFighter
Posts: 789
Joined: Thu Apr 12, 2012 4:15 pm

Re: [TOOL] Custom Dungeon Analyzer

Post 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. :)
Last edited by SpiderFighter on Fri Oct 12, 2012 9:06 pm, edited 1 time in total.
User avatar
pulpum
Posts: 155
Joined: Wed Apr 18, 2012 1:23 am
Location: bordeaux, france

Re: [TOOL] Custom Dungeon Analyzer

Post by pulpum »

excellent idea! thank you!


ps: I don't know for you, but grimrock's community ROCKS! :D
ad&d / ff / d&d
User avatar
Edsploration
Posts: 104
Joined: Wed Sep 19, 2012 4:32 pm

Re: [TOOL] Custom Dungeon Analyzer

Post 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.
Open Project -> Community FrankenDungeon: viewtopic.php?f=14&t=4276
Post Reply