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-15 16:10:47 -04:00
committed by Roxy
co-authored by Jeremiah
parent 2fd0b2e05b
commit 021fa615b6
17 changed files with 127 additions and 127 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
@@ -978,6 +978,13 @@
"aqm" = (
/turf/closed/mineral/random/stationside/asteroid/porus,
/area/space/nearstation)
"aqo" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/structure/cable,
/obj/structure/disposalpipe/segment,
/obj/machinery/holopad,
/turf/open/floor/carpet,
/area/station/command/heads_quarters/hop)
"aqD" = (
/turf/open/floor/iron/white/smooth_large,
/area/station/security/prison)
@@ -6850,6 +6857,14 @@
"cbT" = (
/turf/closed/wall/r_wall,
/area/station/command/heads_quarters/ce)
"cbV" = (
/obj/machinery/conveyor{
dir = 8;
id = "HoPsToy"
},
/obj/structure/window/reinforced/spawner/directional/south,
/turf/open/floor/carpet,
/area/station/command/heads_quarters/hop)
"ccc" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -15609,13 +15624,6 @@
/obj/machinery/door/firedoor,
/turf/open/floor/iron/dark,
/area/station/security/checkpoint/medical)
"eJo" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/structure/cable,
/obj/structure/disposalpipe/segment,
/obj/machinery/holopad,
/turf/open/floor/carpet,
/area/station/command/heads_quarters/hop)
"eJX" = (
/obj/effect/turf_decal/trimline/yellow/filled/line{
dir = 4
@@ -27722,6 +27730,17 @@
},
/turf/open/floor/iron,
/area/station/cargo/storage)
"inT" = (
/obj/machinery/door/airlock/command{
name = "Head of Personnel"
},
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/structure/cable,
/obj/effect/mapping_helpers/airlock/access/all/command/hop,
/obj/structure/disposalpipe/segment,
/turf/open/floor/wood,
/area/station/command/heads_quarters/hop)
"inU" = (
/obj/structure/railing{
dir = 8
@@ -38452,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,
@@ -62769,17 +62777,6 @@
},
/turf/open/floor/engine/hull,
/area/space/nearstation)
"sOR" = (
/obj/machinery/door/airlock/command{
name = "Head of Personnel"
},
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/structure/cable,
/obj/effect/mapping_helpers/airlock/access/all/command/hop,
/obj/structure/disposalpipe/segment,
/turf/open/floor/wood,
/area/station/command/heads_quarters/hop)
"sOT" = (
/obj/structure/cable,
/turf/open/floor/wood,
@@ -63159,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
@@ -64676,14 +64669,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/plating,
/area/station/maintenance/hallway/abandoned_recreation)
"ttP" = (
/obj/machinery/conveyor{
dir = 8;
id = "HoPsToy"
},
/obj/structure/window/reinforced/spawner/directional/south,
/turf/open/floor/carpet,
/area/station/command/heads_quarters/hop)
"ttQ" = (
/obj/item/weldingtool,
/turf/open/floor/plating,
@@ -112443,10 +112428,10 @@ rui
byY
hRd
bUO
eJo
aqo
fVx
lgo
sOR
inT
whZ
bqR
ecw
@@ -116291,7 +116276,7 @@ dTL
dCm
dCm
dCm
sUp
voV
rnp
oUO
toM
@@ -177465,7 +177450,7 @@ dUb
fDE
vuO
hNa
ttP
cbV
pML
miZ
vzB
@@ -207525,7 +207510,7 @@ ibI
kNf
kNf
mfi
lyK
mfi
kFi
qSf
mfi
+22 -22
View File
@@ -14700,17 +14700,6 @@
},
/turf/open/floor/plating/snowed/smoothed/icemoon,
/area/icemoon/surface/outdoors/nospawn)
"ecc" = (
/obj/effect/spawner/random/structure/table_fancy,
/obj/structure/sign/painting/library_secure{
pixel_x = 32
},
/obj/machinery/door/window/right/directional/west{
name = "Secure Art Exhibition";
req_access = list("library")
},
/turf/open/floor/wood,
/area/station/service/library)
"ecs" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -16251,10 +16240,6 @@
/obj/item/toy/sword,
/turf/open/floor/iron/checker,
/area/station/maintenance/port/fore)
"eAg" = (
/obj/effect/spawner/structure/window/reinforced/tinted,
/turf/closed/wall,
/area/station/security/prison/toilet)
"eAh" = (
/obj/machinery/power/smes,
/obj/structure/cable,
@@ -48126,6 +48111,17 @@
"nKa" = (
/turf/closed/wall,
/area/station/security/checkpoint/medical)
"nKb" = (
/obj/effect/spawner/random/structure/table_fancy,
/obj/structure/sign/painting/library_secure{
pixel_x = 32
},
/obj/machinery/door/window/right/directional/west{
name = "Secure Art Exhibition";
req_access = list("library")
},
/turf/open/floor/wood,
/area/station/service/library)
"nKk" = (
/obj/effect/turf_decal/siding/thinplating_new{
dir = 8
@@ -73029,6 +73025,10 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/purple/visible,
/turf/open/floor/plating,
/area/station/maintenance/port/aft)
"uIB" = (
/obj/effect/spawner/structure/window/reinforced/tinted,
/turf/open/floor/plating,
/area/station/security/prison/toilet)
"uIC" = (
/obj/machinery/door/airlock/security{
aiControlDisabled = 1;
@@ -109117,8 +109117,8 @@ thA
thA
thA
uOb
eAg
eAg
uIB
uIB
yiz
lBR
fZq
@@ -109631,8 +109631,8 @@ thA
thA
thA
uOb
eAg
eAg
uIB
uIB
nHs
lBR
oWe
@@ -110145,8 +110145,8 @@ thA
thA
thA
uOb
eAg
eAg
uIB
uIB
kXO
lBR
jbb
@@ -262336,7 +262336,7 @@ pzV
uZn
kKL
jbz
ecc
nKb
god
xse
iQQ
+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