Add CI map linter for window and electrified grille spawners (#91090)

## About The Pull Request
Remake of:
- #79867

This adds a new map linter that checks that there are no walls spawning
inside of windows and vice versa. This also checks to make sure there
are no grilles spawning twice due to a mapper using the electrified
grille spawner with windows. I had to modify the python files to handle
new linter rules for an `ignore` section that has been included in the
README.

There were about two dozen mapping issues where these lints fail that
have now been fixed.

## Why It's Good For The Game
Better mapping and consistent mapping.

## Changelog
🆑
qol: Add CI map linter for window and electrified grille spawners
map: Fix many duplicate windows, walls, and grilles spawning together or
inside each other on a bunch of maps.
/🆑

---------

Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
This commit is contained in:
Tim
2025-05-12 16:32:55 -07:00
committed by GitHub
co-authored by Jeremiah
parent 86bc009f7c
commit 6b3a31e060
17 changed files with 77 additions and 77 deletions
@@ -286,7 +286,6 @@
/area/ruin/space/has_grav/allamericandiner)
"ob" = (
/obj/effect/spawner/structure/window,
/obj/structure/grille,
/turf/open/floor/plating,
/area/ruin/space/has_grav/allamericandiner)
"og" = (
@@ -455,7 +455,6 @@
/area/ruin/space/has_grav/dangerous_research/maint)
"gl" = (
/obj/effect/spawner/structure/window/reinforced/plasma,
/obj/effect/spawner/structure/window/reinforced/plasma,
/obj/machinery/atmospherics/pipe/layer_manifold/scrubbers/visible,
/turf/open/floor/plating,
/area/ruin/space/has_grav/dangerous_research/maint)
+1 -5
View File
@@ -114,10 +114,6 @@
/obj/machinery/light/dim/directional/east,
/turf/open/floor/wood,
/area/ruin/space/has_grav/film_studio/stage)
"cx" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/wall,
/area/ruin/space/has_grav/film_studio/solars)
"cC" = (
/obj/machinery/door/window/right/directional/west,
/turf/open/floor/pod/light,
@@ -5250,7 +5246,7 @@ dd
dd
BK
BK
cx
BK
fu
uc
Ei
+1 -5
View File
@@ -1691,10 +1691,6 @@
},
/turf/open/floor/iron/white,
/area/ruin/space/ancientstation/delta/rnd)
"hF" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/wall,
/area/ruin/space/ancientstation/delta/hall)
"hI" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/dirt,
@@ -13881,7 +13877,7 @@ bE
Po
bE
bE
hF
LK
LK
bE
bE
@@ -10,10 +10,6 @@
/obj/structure/girder/reinforced,
/turf/open/misc/asteroid/airless,
/area/ruin/space/prison_shuttle)
"d" = (
/obj/effect/spawner/structure/window/reinforced/shuttle,
/turf/closed/mineral/random/stationside/asteroid,
/area/ruin/space/prison_shuttle)
"e" = (
/obj/structure/table/reinforced,
/turf/open/floor/carpet/red/airless,
@@ -261,7 +257,7 @@ F
E
Y
Y
d
Y
E
j
q
@@ -280,7 +276,7 @@ a
E
Y
Y
d
Y
K
m
r
+3 -3
View File
@@ -4839,7 +4839,7 @@
"rx" = (
/obj/machinery/door/firedoor,
/obj/structure/window/reinforced/fulltile/ice,
/obj/effect/spawner/structure/electrified_grille,
/obj/structure/cable,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/cavern2)
"ry" = (
@@ -5549,7 +5549,7 @@
"uO" = (
/obj/machinery/door/firedoor,
/obj/structure/window/reinforced/fulltile/ice,
/obj/effect/spawner/structure/electrified_grille,
/obj/structure/cable,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/cavern1)
"uQ" = (
@@ -6703,7 +6703,7 @@
"zR" = (
/obj/machinery/door/firedoor,
/obj/structure/window/reinforced/fulltile/ice,
/obj/effect/spawner/structure/electrified_grille,
/obj/structure/cable,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/mining_dock)
"zS" = (
+3 -11
View File
@@ -6450,10 +6450,6 @@
/obj/machinery/light/cold/directional/north,
/turf/open/floor/iron,
/area/station/security/prison/rec)
"cuw" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/wall,
/area/station/command/corporate_showroom)
"cuZ" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -12306,10 +12302,6 @@
/obj/structure/cable,
/turf/open/floor/iron,
/area/station/security)
"euz" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/wall,
/area/station/hallway/primary/central/aft)
"euO" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/stripes/white/line{
@@ -96591,7 +96583,7 @@ trB
saz
lcD
gCl
euz
tYX
dDB
dDB
dDB
@@ -97058,7 +97050,7 @@ jdJ
xMk
jYQ
hcc
cuw
xGJ
bBN
jwi
qtd
@@ -98086,7 +98078,7 @@ hoN
cKV
dHL
giU
cuw
xGJ
mID
jwi
jwi
@@ -38471,17 +38471,6 @@
},
/turf/open/floor/iron/dark/smooth_large,
/area/station/ai_monitored/turret_protected/ai_upload_foyer)
"lyK" = (
/obj/effect/spawner/structure/window/reinforced,
/obj/machinery/door/poddoor/shutters/preopen{
id = "aicorewindow";
name = "AI Core Shutters"
},
/obj/structure/railing/corner/end{
dir = 4
},
/turf/open/floor/plating,
/area/station/ai_monitored/turret_protected/ai)
"lyP" = (
/obj/structure/railing/corner,
/obj/structure/cable,
@@ -63167,10 +63156,6 @@
dir = 8
},
/area/station/science/ordnance/storage)
"sUp" = (
/obj/effect/spawner/structure/window,
/turf/closed/wall,
/area/station/maintenance/starboard/fore)
"sUu" = (
/obj/effect/turf_decal/weather/dirt{
dir = 1
@@ -116291,7 +116276,7 @@ dTL
dCm
dCm
dCm
sUp
voV
rnp
oUO
toM
@@ -207525,7 +207510,7 @@ ibI
kNf
kNf
mfi
lyK
mfi
kFi
qSf
mfi
@@ -16242,7 +16242,7 @@
/area/station/maintenance/port/fore)
"eAg" = (
/obj/effect/spawner/structure/window/reinforced/tinted,
/turf/closed/wall,
/turf/open/floor/plating,
/area/station/security/prison/toilet)
"eAh" = (
/obj/machinery/power/smes,
+1 -5
View File
@@ -1114,10 +1114,6 @@
/obj/item/paper/guides/jobs/security/labor_camp,
/turf/open/floor/iron,
/area/station/security/brig)
"gW" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/wall/r_wall,
/area/station/engineering/gravity_generator)
"hm" = (
/obj/machinery/airalarm/directional/west,
/obj/effect/mapping_helpers/airalarm/unlocked,
@@ -7945,7 +7941,7 @@ aa
aa
aa
ak
gW
ak
Lb
aX
Qi
+1 -6
View File
@@ -56,11 +56,6 @@
/obj/item/ctf_flag/green,
/turf/open/floor/circuit/green/anim,
/area/centcom/ctf)
"dq" = (
/obj/effect/spawner/structure/window/reinforced/shuttle/indestructible,
/obj/effect/spawner/structure/window/reinforced/shuttle/indestructible,
/turf/open/floor/plating,
/area/centcom/ctf)
"ds" = (
/obj/effect/turf_decal/tile/red{
dir = 8
@@ -3062,7 +3057,7 @@ hc
fU
cK
cK
dq
cK
cK
cK
cK
+1 -5
View File
@@ -9,10 +9,6 @@
/obj/effect/landmark/mafia,
/turf/open/floor/plating/abductor,
/area/centcom/mafia)
"f" = (
/obj/effect/spawner/structure/window/reinforced/plasma/plastitanium,
/turf/closed/indestructible/alien,
/area/centcom/mafia)
"g" = (
/obj/mafia_game_board,
/obj/effect/mapping_helpers/broken_floor,
@@ -573,7 +569,7 @@ q
b
b
r
f
b
q
b
b
+3 -11
View File
@@ -46,10 +46,6 @@
},
/turf/open/floor/iron/dark,
/area/shuttle/escape)
"aR" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/wall,
/area/shuttle/escape)
"bb" = (
/obj/machinery/power/apc/auto_name/directional/east,
/obj/structure/cable,
@@ -449,10 +445,6 @@
},
/turf/open/floor/iron/dark,
/area/shuttle/escape)
"ib" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/closed/mineral,
/area/shuttle/escape)
"ig" = (
/obj/machinery/light/small/directional/west,
/obj/effect/turf_decal/tile/red/half/contrasted,
@@ -4594,8 +4586,8 @@ JG
qA
zE
Ho
ib
ib
Ho
Ho
Ho
Ho
nr
@@ -5480,7 +5472,7 @@ JG
(22,1,1) = {"
zE
zE
aR
zE
LK
LK
LK
+13
View File
@@ -122,6 +122,19 @@ Similar to [banned_neighbors](#banned_neighbors), you can specify a regular expr
allow: { pattern: '^[A-Z].*$' }
```
### `ignore`
If you want to exclude certain objects and subtypes from `banned_neighbors`, you can specify objects in `ignore` that will get skipped from triggering `banned_neighbors`.
```yml
/turf/wall:
banned_neighbors:
- /obj/structure
ignore:
- /obj/structure/sign
```
This lint stops structures from being put inside solid walls but we make an exception to ignore any signs and all their subtypes like `/obj/structure/sign/poster`.
### `help`
If you want a custom message to go with your lint, you can specify "help" in the root.
@@ -0,0 +1,4 @@
/obj/effect/spawner/structure/electrified_grille:
banned_neighbors:
- /obj/effect/spawner/structure/window
+21
View File
@@ -0,0 +1,21 @@
/obj/effect/spawner/structure/window:
banned_neighbors:
/turf/closed:
STRUCTURE:
pattern: ^/obj/structure/(?!.*/directional).*$
STRUCTURE_SPAWNER:
pattern: ^/obj/effect/spawner/structure/(?!.*/hollow)(?!.*/directional).*$
ignore:
- /obj/structure/cable
- /obj/structure/transit_tube
- /obj/structure/disposalpipe
- /obj/structure/flora
- /obj/structure/sign
- /obj/structure/barricade
- /obj/structure/curtain
- /obj/effect/spawner/structure/electrified_grille
- /obj/structure/railing
- /obj/structure/lattice
- /obj/structure/fans
- /obj/structure/spacevine
- /obj/structure/girder
+20
View File
@@ -45,6 +45,7 @@ class AtomNeighbor:
identical: bool = False
typepath: Optional[TypepathExtra] = None
pattern: Optional[re.Pattern] = None
ignore: list[TypepathExtra] = []
def __init__(self, typepath, data = {}):
if typepath.upper() != typepath:
@@ -62,6 +63,11 @@ class AtomNeighbor:
if "pattern" in data:
self.pattern = re.compile(data.pop("pattern"))
if "ignore" in data:
ignore_data = data.pop("ignore")
expect(isinstance(ignore_data, list), "ignore must be a list of typepaths.")
self.ignore = [TypepathExtra(tp) for tp in ignore_data]
expect(len(data) == 0, f"Unknown key in banned neighbor: {', '.join(data.keys())}.")
def matches(self, identified: Content, neighbor: Content):
@@ -303,11 +309,17 @@ class Rules:
banned_neighbors: list[AtomNeighbor] = []
banned_variables: bool | list[BannedVariable] = []
required_neighbors: list[AtomNeighbor] = []
ignored_neighbors: list[AtomNeighbor] = []
when: Optional[When] = None
def __init__(self, data):
expect(isinstance(data, dict), "Lint rules must be a dictionary.")
if "ignore" in data:
ignored_neighbors_data = data.pop("ignore")
expect(isinstance(ignored_neighbors_data, list), "'ignore' must be a list of typepaths.")
self.ignored_neighbors = [TypepathExtra(tp) for tp in ignored_neighbors_data]
if "banned" in data:
self.banned = data.pop("banned")
expect(isinstance(self.banned, bool), "banned must be a boolean.")
@@ -361,6 +373,14 @@ class Rules:
failures.append(fail_content(identified, f"Typepath {identified.path} is banned{when_text}."))
for banned_neighbor in self.banned_neighbors:
ignored = False
for neighbor in contents[:identified_index] + contents[identified_index + 1:]:
if any(ignore.matches_path(neighbor.path) for ignore in self.ignored_neighbors):
ignored = True
break
if ignored:
continue
for neighbor in contents[:identified_index] + contents[identified_index + 1:]:
if not banned_neighbor.matches(identified, neighbor):
continue