# Script to validate a level config file. This checks that the names used within # the file are consistent. It also checks that the panel and door IDs mentioned # all exist in the map file. # # Usage: validate_config.rb [config file] [map file] require 'set' require 'yaml' configpath = ARGV[0] idspath = ARGV[1] mappath = ARGV[2] panels = Set["Countdown Panels/Panel_1234567890_wanderlust"] doors = Set["Naps Room Doors/Door_hider_new1", "Tower Room Area Doors/Door_wanderer_entrance"] paintings = Set[] File.readlines(mappath).each do |line| line.match(/node name=\"(.*)\" parent=\"Panels\/(.*)\" instance/) do |m| panels.add(m[2] + "/" + m[1]) end line.match(/node name=\"(.*)\" parent=\"Doors\/(.*)\" instance/) do |m| doors.add(m[2] + "/" + m[1]) end line.match(/node name=\"(.*)\" parent=\"Decorations\/Paintings\" instance/) do |m| paintings.add(m[1]) end line.match(/node name=\"(.*)\" parent=\"Decorations\/EndPanel\" instance/) do |m| panels.add("EndPanel/" + m[1]) end end configured_rooms = Set["Menu"] configured_doors = Set[] configured_panels = Set[] mentioned_rooms = Set[] mentioned_doors = Set[] mentioned_panels = Set[] door_groups = {} directives = Set["entrances", "panels", "doors", "paintings", "progression"] panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt"] door_directives = Set["id", "painting_id", "panels", "item_name", "location_name", "skip_location", "skip_item", "group", "include_reduce", "junk_item", "event"] painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] non_counting = 0 ids = YAML.load_file(idspath) config = YAML.load_file(configpath) config.each do |room_name, room| configured_rooms.add(room_name) used_directives = Set[] room.each_key do |key| used_directives.add(key) end diff_directives = used_directives - directives unless diff_directives.empty? then puts("#{room_name} has the following invalid top-level directives: #{diff_directives.to_s}") end (room["entrances"] || {}).each do |source_room, entrance| mentioned_rooms.add(source_room) entrances = [] if entrance.kind_of? Hash if entrance.keys() != ["painting"] then entrances = [entrance] end elsif entrance.kind_of? Array entrances = entrance end entrances.each do |e| entrance_room = e.include?("room") ? e["room"] : room_name mentioned_rooms.add(entrance_room) mentioned_doors.add(entrance_room + " - " + e["door"]) end end (room["panels"] || {}).each do |panel_name, panel| unless panel_name.kind_of? String then puts "#{room_name} has an invalid panel name" end configured_panels.add(room_name + " - " + panel_name) if panel.include?("id") panel_ids = [] if panel["id"].kind_of? Array panel_ids = panel["id"] else panel_ids = [panel["id"]] end panel_ids.each do |panel_id| unless panels.include? panel_id then puts "#{room_name} - #{panel_name} :::: Invalid Panel ID #{panel_id}" end end else puts "#{room_name} - #{panel_name} :::: Panel is missing an ID" end if panel.include?("required_room") required_rooms = [] if panel["required_room"].kind_of? Array required_rooms = panel["required_room"] else required_rooms = [panel["required_room"]] end required_rooms.each do |required_room| mentioned_rooms.add(required_room) end end if panel.include?("required_door") required_doors = [] if panel["required_door"].kind_of? Array required_doors = panel["required_door"] else required_doors = [panel["required_door"]] end required_doors.each do |required_door| other_room = required_door.include?("room") ? required_door["room"] : room_name mentioned_rooms.add(other_room) mentioned_doors.add("#{other_room} - #{required_door["door"]}") end end if panel.include?("required_panel") required_panels = [] if panel["required_panel"].kind_of? Array required_panels = panel["required_panel"] else required_panels = [panel["required_panel"]] end required_panels.each do |required_panel| other_room = required_panel.include?("room") ? required_panel["room"] : room_name mentioned_rooms.add(other_room) mentioned_panels.add("#{other_room} - #{required_panel["panel"]}") end end unless panel.include?("tag") then puts "#{room_name} - #{panel_name} :::: Panel is missing a tag" end if panel.include?("non_counting") then non_counting += 1 end bad_subdirectives = [] panel.keys.each do |key| unless panel_directives.include?(key) then bad_subdirectives << key end end unless bad_subdirectives.empty? then puts "#{room_name} - #{panel_name} :::: Panel has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end unless ids.include?("panels") and ids["panels"].include?(room_name) and ids["panels"][room_name].include?(panel_name) puts "#{room_name} - #{panel_name} :::: Panel is missing a location ID" end end (room["doors"] || {}).each do |door_name, door| configured_doors.add("#{room_name} - #{door_name}") if door.include?("id") door_ids = [] if door["id"].kind_of? Array door_ids = door["id"] else door_ids = [door["id"]] end door_ids.each do |door_id| unless doors.include? door_id then puts "#{room_name} - #{door_name} :::: Invalid Door ID #{door_id}" end end end if door.include?("painting_id") painting_ids = [] if door["painting_id"].kind_of? Array painting_ids = door["painting_id"] else painting_ids = [door["painting_id"]] end painting_ids.each do |painting_id| unless paintings.include? painting_id then puts "#{room_name} - #{door_name} :::: Invalid Painting ID #{painting_id}" end end end if not door.include?("id") and not door.include?("painting_id") and not door["skip_item"] and not door["event"] then puts "#{room_name} - #{door_name} :::: Should be marked skip_item or event if there are no doors or paintings" end if door.include?("panels") door["panels"].each do |panel| if panel.kind_of? Hash then other_room = panel.include?("room") ? panel["room"] : room_name mentioned_panels.add("#{other_room} - #{panel["panel"]}") else other_room = panel.include?("room") ? panel["room"] : room_name mentioned_panels.add("#{room_name} - #{panel}") end end elsif not door["skip_location"] puts "#{room_name} - #{door_name} :::: Should be marked skip_location if there are no panels" end if door.include?("group") door_groups[door["group"]] ||= 0 door_groups[door["group"]] += 1 end bad_subdirectives = [] door.keys.each do |key| unless door_directives.include?(key) then bad_subdirectives << key end end unless bad_subdirectives.empty? then puts "#{room_name} - #{door_name} :::: Door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end unless door["skip_item"] or door["event"] unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("item") puts "#{room_name} - #{door_name} :::: Door is missing an item ID" end end unless door["skip_location"] or door["event"] unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("location") puts "#{room_name} - #{door_name} :::: Door is missing a location ID" end end end (room["paintings"] || []).each do |painting| if painting.include?("id") and painting["id"].kind_of? String then unless paintings.include? painting["id"] then puts "#{room_name} :::: Invalid Painting ID #{painting["id"]}" end else puts "#{room_name} :::: Painting is missing an ID" end if painting["disable"] then # We're good. next end if painting.include?("orientation") then unless ["north", "south", "east", "west"].include? painting["orientation"] then puts "#{room_name} - #{painting["id"] || "painting"} :::: Invalid orientation #{painting["orientation"]}" end else puts "#{room_name} :::: Painting is missing an orientation" end if painting.include?("required_door") other_room = painting["required_door"].include?("room") ? painting["required_door"]["room"] : room_name mentioned_doors.add("#{other_room} - #{painting["required_door"]["door"]}") unless painting["enter_only"] then puts "#{room_name} - #{painting["id"] || "painting"} :::: Should be marked enter_only if there is a required_door" end end bad_subdirectives = [] painting.keys.each do |key| unless painting_directives.include?(key) then bad_subdirectives << key end end unless bad_subdirectives.empty? then puts "#{room_name} - #{painting["id"] || "painting"} :::: Painting has the following invalid subdirectives: #{bad_subdirectives.join(", ")}" end end (room["progression"] || {}).each do |progression_name, door_list| door_list.each do |door| if door.kind_of? Hash then mentioned_doors.add("#{door["room"]} - #{door["door"]}") else mentioned_doors.add("#{room_name} - #{door}") end end unless ids.include?("progression") and ids["progression"].include?(progression_name) puts "#{room_name} - #{progression_name} :::: Progression is missing an item ID" end end end errored_rooms = mentioned_rooms - configured_rooms unless errored_rooms.empty? then puts "The folloring rooms are mentioned but do not exist: " + errored_rooms.to_s end errored_panels = mentioned_panels - configured_panels unless errored_panels.empty? then puts "The folloring panels are mentioned but do not exist: " + errored_panels.to_s end errored_doors = mentioned_doors - configured_doors unless errored_doors.empty? then puts "The folloring doors are mentioned but do not exist: " + errored_doors.to_s end door_groups.each do |group,num| if num == 1 then puts "Door group \"#{group}\" only has one door in it" end unless ids.include?("door_groups") and ids["door_groups"].include?(group) puts "#{group} :::: Door group is missing an item ID" end end slashed_rooms = configured_rooms.select do |room| room.include? "/" end unless slashed_rooms.empty? then puts "The following rooms have slashes in their names: " + slashed_rooms.to_s end slashed_panels = configured_panels.select do |panel| panel.include? "/" end unless slashed_panels.empty? then puts "The following panels have slashes in their names: " + slashed_panels.to_s end slashed_doors = configured_doors.select do |door| door.include? "/" end unless slashed_doors.empty? then puts "The following doors have slashes in their names: " + slashed_doors.to_s end puts "#{configured_panels.size} panels (#{non_counting} non counting)"