diff --git a/__odlint.dm b/__odlint.dm
new file mode 100644
index 0000000000000..ecf2c0bba6f35
--- /dev/null
+++ b/__odlint.dm
@@ -0,0 +1,10 @@
+// This file is included right at the start of the DME.
+// Its purpose is to enable multiple lints (pragmas) that are supported by OpenDream to better validate the codebase
+// These are essentially nitpicks the DM compiler should pick up on but doesnt
+
+#if defined(CIBUILDING) && defined(OPENDREAM)
+// This is in a separate file as a hack to avoid SpacemanDMM
+// evaluating the #pragma lines, even if its outside a block it cares about
+// (Also so people can code-own it. Shoutout to AA)
+#include "tools/ci/od_lints.dm"
+#endif
diff --git a/beestation.dme b/beestation.dme
index 7922be1d72385..c4887eb1faa26 100644
--- a/beestation.dme
+++ b/beestation.dme
@@ -13,6 +13,7 @@
// END_PREFERENCES
// BEGIN_INCLUDE
+#include "__odlint.dm"
#include "_maps\_basemap.dm"
#include "code\__byond_version_compat.dm"
#include "code\_compile_options.dm"
diff --git a/code/datums/elements/mechanical_repair.dm b/code/datums/elements/mechanical_repair.dm
index 5f8071c37fd50..ecfd1e7d78a19 100644
--- a/code/datums/elements/mechanical_repair.dm
+++ b/code/datums/elements/mechanical_repair.dm
@@ -32,7 +32,7 @@
return COMPONENT_NO_AFTERATTACK
/datum/element/mechanical_repair/proc/complete_repairs(mob/living/carbon/human/target, obj/item/I, mob/user, selected_zone)
- if(target in user.do_afters || !user.can_interact_with(target, TRUE) || !user.can_interact_with(I, TRUE))
+ if((target in user.do_afters) || !user.can_interact_with(target, TRUE) || !user.can_interact_with(I, TRUE))
return COMPONENT_NO_AFTERATTACK
var/obj/item/bodypart/affecting = target.get_bodypart(check_zone(selected_zone))
diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm
index 031b5df731bc0..8144aa115344c 100644
--- a/code/game/machinery/computer/card.dm
+++ b/code/game/machinery/computer/card.dm
@@ -698,7 +698,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
update_modify_manifest()
if ("demote")
- if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant")
+ if((inserted_modify_id.assignment in head_subordinates) || inserted_modify_id.assignment == "Assistant")
inserted_modify_id.assignment = "Demoted"
log_id("[key_name(usr)] demoted [inserted_modify_id], unassigning the card without affecting access, using [inserted_scan_id] at [AREACOORD(usr)].")
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index d54ac8c5c523d..9b06ed7837563 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -227,7 +227,7 @@
. = TRUE
if("select_stencil")
var/stencil = params["item"]
- if(stencil in all_drawables + randoms)
+ if(stencil in (all_drawables + randoms))
drawtype = stencil
. = TRUE
text_buffer = ""
@@ -316,7 +316,7 @@
temp = "symbol"
else if(drawing in drawings)
temp = "drawing"
- else if(drawing in graffiti|oriented)
+ else if(drawing in (graffiti | oriented))
temp = "graffiti"
var/gang_check = hippie_gang_check(user,target) // hippie start -- gang check and temp setting
if(!gang_check) return // hippie end
diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm
index 8389506970502..6fa312c6cf393 100644
--- a/code/game/objects/items/pet_carrier.dm
+++ b/code/game/objects/items/pet_carrier.dm
@@ -39,7 +39,7 @@
occupant_weight -= L.mob_size
/obj/item/pet_carrier/handle_atom_del(atom/A)
- if(A in occupants && isliving(A))
+ if((A in occupants) && isliving(A))
var/mob/living/L = A
occupants -= L
occupant_weight -= L.mob_size
@@ -184,7 +184,7 @@
add_occupant(target)
/obj/item/pet_carrier/proc/add_occupant(mob/living/occupant)
- if(occupant in occupants || !istype(occupant))
+ if((occupant in occupants) || !istype(occupant))
return
occupant.forceMove(src)
occupants += occupant
diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm
index 4123e0882f1b1..cf544c293bd4a 100644
--- a/code/modules/admin/permissionedit.dm
+++ b/code/modules/admin/permissionedit.dm
@@ -218,7 +218,7 @@
. = ckey(admin_key)
if(!.)
return FALSE
- if(!admin_ckey && (. in GLOB.admin_datums+GLOB.deadmins))
+ if(!admin_ckey && (. in (GLOB.admin_datums+GLOB.deadmins)))
to_chat(usr, "[admin_key] is already an admin.")
return FALSE
if(use_db)
diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm
index 025d085e789fd..5113bb18ca5f5 100644
--- a/code/modules/antagonists/blob/overmind.dm
+++ b/code/modules/antagonists/blob/overmind.dm
@@ -140,7 +140,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/blob)
if(!T || !is_station_level(T.z))
continue
- if(L in GLOB.overminds || (L.pass_flags & PASSBLOB))
+ if((L in GLOB.overminds) || (L.pass_flags & PASSBLOB))
continue
var/area/Ablob = get_area(T)
diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
index 43ee73e2b6ca2..ada6562b60a4c 100644
--- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
@@ -402,7 +402,7 @@
max_dist = max(max_dist, get_dist(found_turf, centre) + 1)
for(var/turf/nearby_turf as anything in spiral_range_turfs(max_dist, centre, FALSE))
- if(nearby_turf in rusted_turfs || is_type_in_typecache(nearby_turf, blacklisted_turfs))
+ if((nearby_turf in rusted_turfs) || is_type_in_typecache(nearby_turf, blacklisted_turfs))
continue
for(var/turf/line_turf as anything in getline(nearby_turf, centre))
diff --git a/code/modules/antagonists/heretic/magic/ash_ascension.dm b/code/modules/antagonists/heretic/magic/ash_ascension.dm
index 4b4d2ef267ed8..af77434b6872f 100644
--- a/code/modules/antagonists/heretic/magic/ash_ascension.dm
+++ b/code/modules/antagonists/heretic/magic/ash_ascension.dm
@@ -127,7 +127,7 @@
if(L.anti_magic_check())
L.visible_message("The spell bounces off of [L]!","The spell bounces off of you!")
continue
- if(L in hit_list || L == source)
+ if((L in hit_list) || L == source)
continue
hit_list += L
L.adjustFireLoss(20)
diff --git a/code/modules/mob/living/simple_animal/bot/atmosbot.dm b/code/modules/mob/living/simple_animal/bot/atmosbot.dm
index 90403ff440119..3aaf937247caa 100644
--- a/code/modules/mob/living/simple_animal/bot/atmosbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/atmosbot.dm
@@ -303,7 +303,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/simple_animal/bot/atmosbot)
//Add adjacent turfs
for(var/direction in list(NORTH, SOUTH, EAST, WEST))
var/turf/adjacent_turf = get_step(checking_turf, direction)
- if(adjacent_turf in checked_turfs || !adjacent_turf.CanAtmosPass(adjacent_turf))
+ if((adjacent_turf in checked_turfs) || !adjacent_turf.CanAtmosPass(adjacent_turf))
continue
var/datum/gas_mixture/checking_air = checking_turf.return_air()
if (!checking_air)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index d654d848378d3..d3ca35b2c40bb 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -271,7 +271,7 @@ Difficulty: Medium
new /obj/effect/hotspot(T)
T.hotspot_expose(700,50,1)
for(var/mob/living/L in T.contents)
- if(L in hit_list || L == source)
+ if((L in hit_list) || L == source)
continue
hit_list += L
L.adjustFireLoss(20)
diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
index 56f572ee823d8..310412685da39 100644
--- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm
@@ -154,7 +154,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/light_prism)
user.remove_movespeed_modifier(/datum/movespeed_modifier/admantine_armor)
/obj/item/clothing/suit/armor/heavy/adamantine/IsReflect(def_zone)
- if(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) && prob(hit_reflect_chance))
+ if((def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)) && prob(hit_reflect_chance))
return TRUE
else
return FALSE
diff --git a/tools/ci/od_lints.dm b/tools/ci/od_lints.dm
new file mode 100644
index 0000000000000..d7fb03822759d
--- /dev/null
+++ b/tools/ci/od_lints.dm
@@ -0,0 +1,58 @@
+/**
+ * Beestation OpenDream linting config:
+ * See https://github.com/OpenDreamProject/OpenDream/blob/master/DMCompiler/DMStandard/DefaultPragmaConfig.dm
+ */
+
+// This is the default error/warning/notice/disable setup when the user does not mandate a different file or configuration.
+// If you add a new named error with a code greater than 999, please mark it here.
+
+//1000-1999
+#pragma FileAlreadyIncluded error
+#pragma MissingIncludedFile error
+#pragma InvalidWarningCode error
+#pragma MisplacedDirective error
+#pragma UndefineMissingDirective error
+#pragma DefinedMissingParen error
+#pragma ErrorDirective error
+// Beestation: Explicitly kept at warning as this is the #warn define.
+#pragma WarningDirective warning
+#pragma MiscapitalizedDirective error
+
+//2000-2999
+#pragma SoftReservedKeyword error
+#pragma DuplicateVariable error
+#pragma DuplicateProcDefinition error
+#pragma PointlessParentCall error
+#pragma PointlessBuiltinCall error
+#pragma SuspiciousMatrixCall error
+#pragma FallbackBuiltinArgument error
+#pragma PointlessScopeOperator error
+#pragma MalformedRange error
+#pragma InvalidRange error
+#pragma InvalidSetStatement error
+#pragma InvalidOverride error
+#pragma InvalidIndexOperation error
+#pragma DanglingVarType error
+#pragma MissingInterpolatedExpression error
+#pragma AmbiguousResourcePath error
+#pragma SuspiciousSwitchCase error
+#pragma PointlessPositionalArgument error
+// NOTE: The next few pragmas are for OpenDream's experimental type checker
+// This feature is still in development, elevating these pragmas outside of local testing is discouraged
+// An RFC to finalize this feature is coming soon(TM)
+// BEGIN TYPEMAKER
+#pragma UnsupportedTypeCheck notice
+#pragma InvalidReturnType notice
+#pragma InvalidVarType notice
+#pragma ImplicitNullType notice
+#pragma LostTypeInfo notice
+// END TYPEMAKER
+#pragma UnimplementedAccess error
+
+//3000-3999
+#pragma EmptyBlock notice
+#pragma EmptyProc disabled // NOTE: If you enable this in OD's default pragma config file, it will emit for OD's DMStandard. Put it in your codebase's pragma config file.
+#pragma UnsafeClientAccess disabled // NOTE: Only checks for unsafe accesses like "client.foobar" and doesn't consider if the client was already null-checked earlier in the proc
+#pragma AssignmentInConditional error
+#pragma PickWeightedSyntax disabled
+#pragma AmbiguousInOrder error