Stopping large monsters at the stairs with a door rule
A door in a dungeon plan can now name a rule that admits monsters by size class first and height second, because the classes overlap.
A door in a plan can now carry a named rule that decides which monsters may follow the party through it. There are two rules. The ground door of a building lets every size class through, and its 3.3 m opening stops the tallest bodies. The short door of an upper storey lets through only classes up to medium, however tall the monster is. So a large monster chases you to the stairs and stops there. Nothing changes on screen yet, because the building assembler doesn’t put these rules on a building’s doors, so there’s no clip.
Why can’t the door’s height do it?
I wanted upper storeys to have a short door, about 2.4 m by 1.5 m, that medium monsters can pass and large ones can’t. Then I kept the clear height at 2.60 m, because the tallest medium body is 2.5 m and the game keeps 0.1 m to spare on every opening.
The trouble is that the size classes overlap. Medium bodies run from 1.50 to 2.50 m and large ones from 1.65 to 2.75 m. A door tall enough for every medium body also lets the shorter large ones in, and a door that stops every large body would stop some medium ones. No height does both, so the door has to ask for the class first.
How does a door know its rule?
The rules are a small table in a new script, DoorFit. Each row has a clear width and height in meters and the largest class it admits. The numbers are plain const lines so the art kit can read them as text.
const GROUND_WIDTH := 2.5
const GROUND_HEIGHT := 3.3
const GROUND_LARGEST := MonsterSize.Class.HUGE
const UPPER_WIDTH := 1.48
const UPPER_HEIGHT := 2.6
const UPPER_LARGEST := MonsterSize.Class.MEDIUM
static func lets(rule: String, size: int, width: float, height: float) -> bool:
if not RULES.has(rule):
return false
var row: Dictionary = RULES[rule]
if size != ANY_SIZE and size > int(row["largest"]):
return false
return width + DungeonPlan.PASS_MARGIN <= float(row["width"]) + MonsterSize.EPSILON \
and height + DungeonPlan.PASS_MARGIN <= float(row["height"]) + MonsterSize.EPSILON
The class is checked first, then the opening. The small epsilon is there so a body of exactly 3.2 m passes the 3.3 m ground door while a 3.3 m body stays outside, which is the rule that bodies over 3.2 m stay out of houses. A rule name that isn’t in the table lets nothing through.
A door thing in a sheet can name its rule. When the plan is built, DoorFit.plan() checks the name (an unknown one is an error on the sheet) and writes it into a dictionary on the plan, from square to rule, for every square the door spans. A wide door covers several squares, so it walks along the passage to get them all.
What happens when a monster walks at one?
Monsters here walk in straight lines with no pathfinding. PassageFit follows the line from the monster to its goal and stops it just short of the first square it doesn’t fit. I gave it the monster’s size class, and on a square with a rule it now asks in this order:
- The class and the rule’s opening.
- The fixtures on the square, like glass, bars or a shut cover.
- For a walker, the square’s own opening, standing.
That third step means nothing crouches through a door with a rule. Small monsters can crouch through a crawl space, but a door is a door. Flyers get the same class rule and then their ceiling rule. A square with no rule is tested exactly as before, and the 31 older dungeon, passage, crawl, vent and door tests didn’t change.
What’s left
The assembler still has to lay these rules on a building’s doors. The plan’s fit report and two other walkers don’t pass a class yet, so they’d still judge a ruled door by height alone. The new test has 30 checks and all of them pass.