Files
Grinch-AP/worlds/lingo/utils/validate_config.rb
Star Rauchenberger d3b09bde12 Lingo: Fix entrance checking being broken on default settings (#2506)
The most serious issue this PR addresses is that entrances that use doors without items (a small subset of doors when door shuffle is on, but *every* door when door shuffle is off, which is the default) underestimate the requirements needed to use that entrance. The logic would calculate the panels needed to open the door, but would neglect to keep track of the rooms those panels were in, meaning that doors would be considered openable if you had the colors needed to solve a panel that's in a room you have no access to.

Another issue is that, previously, logic would always consider the "ANOTHER TRY" panel accessible for the purposes of the LEVEL 2 panel hunt. This could result in seeds where the player is expected to have exactly the correct number of solves to reach LEVEL 2, but in reality is short by one because ANOTHER TRY itself is not revealed until the panel hunt is complete. This change marks ANOTHER TRY as non-counting, because even though it is technically a counting panel in-game, it can never contribute to the LEVEL 2 panel hunt. This issue could also apply to THE MASTER, since it is the only other counting panel with special access rules, although it is much less likely. This change adds special handling for counting THE MASTER. These issues were possible to manifest whenever the LEVEL 2 panel hunt was enabled, which it is by default.

Smaller logic issues also fixed in this PR:

* The Orange Tower Basement MASTERY panel was marked as requiring the mastery doors to be opened, when it was actually possible to get it without them by using a painting to get into the room.
* The Pilgrim Room painting item was incorrectly being marked as a filler item, despite it being progression.
* There has been another update to the game that adds connections between areas that were previously not connected. These changes were additive, which is why they are not critical.
* The panel stacks in the rhyme room now require both colours on each panel.
2023-12-10 19:15:42 +01:00

330 lines
10 KiB
Ruby

# 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]
mappath = ARGV[1]
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
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
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
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
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
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)"