diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm
index daa5d4285f9..6dc90ea4d64 100644
--- a/code/__DEFINES/ai/ai_blackboard.dm
+++ b/code/__DEFINES/ai/ai_blackboard.dm
@@ -141,5 +141,8 @@
/// The next time at which this mob can call for reinforcements
#define BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN "BB_basic_mob_reinforcements_cooldown"
+/// the direction we started when executing stare at things
+#define BB_STARTING_DIRECTION "BB_startdir"
+
///Text we display when we befriend someone
#define BB_FRIENDLY_MESSAGE "friendly_message"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index ac48989a59f..d3dce4b065a 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -603,6 +603,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_LAVA_STOPPED "lava_stopped"
///Chasms will be safe to cross while they've this trait.
#define TRAIT_CHASM_STOPPED "chasm_stopped"
+///Chasms will be safe to cross if there is something with this trait on it
+#define TRAIT_CHASM_STOPPER "chasm_stopper"
///The effects of the immerse element will be halted while this trait is present.
#define TRAIT_IMMERSE_STOPPED "immerse_stopped"
/// The effects of hyperspace drift are blocked when the tile has this trait
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 64ddcfe4b4b..1b93d58dae3 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -66,6 +66,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_WAS_RENAMED" = TRAIT_WAS_RENAMED,
"TRAIT_WADDLING" = TRAIT_WADDLING,
"TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE,
+ "TRAIT_CHASM_STOPPER" = TRAIT_CHASM_STOPPER,
),
/datum/controller/subsystem/economy = list(
"TRAIT_MARKET_CRASHING" = TRAIT_MARKET_CRASHING,
diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm
index 9829dfc3f52..b4406857ac1 100644
--- a/code/datums/components/chasm.dm
+++ b/code/datums/components/chasm.dm
@@ -109,6 +109,9 @@
return CHASM_NOT_DROPPING
if(dropped_thing.throwing || (dropped_thing.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
return CHASM_REGISTER_SIGNALS
+ for(var/atom/thing_to_check as anything in parent)
+ if(HAS_TRAIT(thing_to_check, TRAIT_CHASM_STOPPER))
+ return CHASM_NOT_DROPPING
//Flies right over the chasm
if(ismob(dropped_thing))
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index e7f56393a66..db4900a1edd 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -23,6 +23,18 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
has_gravity = STANDARD_GRAVITY
ambientsounds = list('sound/ambience/shore.ogg', 'sound/ambience/seag1.ogg','sound/ambience/seag2.ogg','sound/ambience/seag2.ogg','sound/ambience/ambiodd.ogg','sound/ambience/ambinice.ogg')
+/area/awaymission/museum
+ name = "Nanotrasen Museum"
+ icon_state = "awaycontent28"
+ sound_environment = SOUND_ENVIRONMENT_CONCERT_HALL
+
+/area/awaymission/museum/mothroachvoid
+ static_lighting = FALSE
+ base_lighting_alpha = 200
+ base_lighting_color = "#FFF4AA"
+ sound_environment = SOUND_ENVIRONMENT_PLAIN
+ ambientsounds = list('sound/ambience/shore.ogg', 'sound/ambience/ambiodd.ogg','sound/ambience/ambinice.ogg')
+
/area/awaymission/errorroom
name = "Super Secret Room"
static_lighting = FALSE
diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm
index 5d390019f9b..eca8d88da4b 100644
--- a/code/game/machinery/doors/shutters.dm
+++ b/code/game/machinery/doors/shutters.dm
@@ -66,3 +66,12 @@
/obj/machinery/door/poddoor/shutters/window/preopen
icon_state = "open"
density = FALSE
+
+/obj/machinery/door/poddoor/shutters/window/indestructible
+ name = "hardened windowed shutters"
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+/obj/machinery/door/poddoor/shutters/window/indestructible/preopen
+ icon_state = "open"
+ density = FALSE
+ opacity = FALSE
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index 24c0ac8bda5..fbc27e3dac1 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -549,6 +549,19 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/ai, 32)
connected_display.receive_signal(status_signal)
+/obj/machinery/status_display/random_message
+ current_mode = SD_MESSAGE
+ /// list to pick the first line from
+ var/list/firstline_to_secondline = list()
+
+/obj/machinery/status_display/random_message/Initialize(mapload, ndir, building)
+ if(firstline_to_secondline?.len)
+ message1 = pick(firstline_to_secondline)
+ message2 = firstline_to_secondline[message1]
+ return ..() // status displays call update appearance on init so i suppose we should set the messages before calling parent as to not call it twice
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/random_message, 32)
+
#undef MAX_STATIC_WIDTH
#undef FONT_STYLE
#undef SCROLL_RATE
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index 1a1dc80fe25..7fe6c59075f 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -486,6 +486,9 @@
ignite()
return ..()
+/obj/effect/decal/cleanable/fuel_pool/hivis
+ icon_state = "fuel_pool_hivis"
+
/obj/effect/decal/cleanable/rubble
name = "rubble"
desc = "A pile of rubble."
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index e63e7e39c4a..cd8a859f86f 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -117,8 +117,7 @@
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(3, 1, src)
s.start()
- if(ismob(triggerer))
- mineEffect(triggerer)
+ mineEffect(triggerer)
triggered = TRUE
SEND_SIGNAL(src, COMSIG_MINE_TRIGGERED, triggerer)
qdel(src)
diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm
index a014857994a..ecfa560bfe6 100644
--- a/code/game/objects/effects/step_triggers.dm
+++ b/code/game/objects/effects/step_triggers.dm
@@ -19,7 +19,7 @@
/obj/effect/step_trigger/proc/on_entered(datum/source, H as mob|obj)
SIGNAL_HANDLER
- if(!H)
+ if(!H || H == src)
return
if(isobserver(H) && !affect_ghosts)
return
@@ -138,6 +138,26 @@
if (T)
A.forceMove(T)
+/* Teleports atoms directly to an offset, no randomness, looping hallways! */
+
+/obj/effect/step_trigger/teleporter/offset
+ var/teleport_x_offset = 0
+ var/teleport_y_offset = 0
+
+/obj/effect/step_trigger/teleporter/offset/on_entered(datum/source, H as mob|obj, atom/old_loc)
+ if(!old_loc?.Adjacent(loc)) // prevents looping, if we were teleported into this then the old loc is usually not adjacent
+ return
+ return ..()
+
+/obj/effect/step_trigger/teleporter/offset/Trigger(atom/movable/poor_soul)
+ var/turf/destination = locate(x + teleport_x_offset, y + teleport_y_offset, z)
+ if(!destination)
+ return
+ poor_soul.forceMove(destination)
+ var/mob/living/living_soul = poor_soul
+ if(istype(living_soul) && living_soul.client)
+ living_soul.client.move_delay = 0
+
/* Fancy teleporter, creates sparks and smokes when used */
/obj/effect/step_trigger/teleport_fancy
diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm
index 8eba5081d22..7ac22d00897 100644
--- a/code/game/objects/items/puzzle_pieces.dm
+++ b/code/game/objects/items/puzzle_pieces.dm
@@ -403,3 +403,61 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/puzzle_keycardpad, 32)
playsound(src, SFX_SPARKS, 100, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
do_sparks(3, cardinal_only = FALSE, source = src)
qdel(src)
+
+/obj/structure/puzzle_blockade/oneway
+ name = "one-way gate"
+ desc = "A wall of solid light, likely defending something important. Virtually indestructible."
+ icon = 'icons/obj/structures.dmi'
+ icon_state = "oneway"
+ base_icon_state = "oneway"
+ light_color = COLOR_BIOLUMINESCENCE_BLUE
+ light_range = 1
+ density = FALSE
+
+/obj/structure/puzzle_blockade/oneway/update_icon_state()
+ icon_state = "[base_icon_state][density ? "" : "-off"]"
+ return ..()
+
+/obj/structure/puzzle_blockade/oneway/CanAllowThrough(atom/movable/mover, border_dir)
+ return ..() && (REVERSE_DIR(border_dir) == dir || get_turf(mover) == get_turf(src))
+
+/obj/structure/puzzle_blockade/oneway/CanAStarPass(border_dir, datum/can_pass_info/pass_info)
+ return REVERSE_DIR(border_dir) == dir
+
+/obj/structure/puzzle_blockade/oneway/try_signal(datum/source)
+ density = FALSE
+ update_appearance(UPDATE_ICON)
+
+/obj/effect/puzzle_poddoor_open
+ name = "puzzle-poddoor relay"
+ desc = "activates poddoors if activated with a puzzle signal."
+ icon = 'icons/effects/mapping_helpers.dmi'
+ icon_state = ""
+ anchored = TRUE
+ invisibility = INVISIBILITY_MAXIMUM
+ /// if we receive a puzzle signal with this we do our thing
+ var/queue_id
+ /// door id
+ var/id
+
+/obj/effect/puzzle_poddoor_open/Initialize(mapload)
+ . = ..()
+ if(isnull(id) || isnull(queue_id))
+ log_mapping("[src] id:[id] has no id or door id and has been deleted")
+ return INITIALIZE_HINT_QDEL
+
+ SSqueuelinks.add_to_queue(src, queue_id)
+
+/obj/effect/puzzle_poddoor_open/MatchedLinks(id, list/partners)
+ for(var/partner in partners)
+ RegisterSignal(partner, COMSIG_PUZZLE_COMPLETED, PROC_REF(try_signal))
+
+/obj/effect/puzzle_poddoor_open/proc/try_signal(datum/source)
+ SIGNAL_HANDLER
+ var/openclose
+ for(var/obj/machinery/door/poddoor/door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor))
+ if(door.id != id)
+ continue
+ if(isnull(openclose))
+ openclose = door.density
+ INVOKE_ASYNC(door, openclose ? TYPE_PROC_REF(/obj/machinery/door/poddoor, open) : TYPE_PROC_REF(/obj/machinery/door/poddoor, close))
diff --git a/code/game/objects/structures/fluff.dm b/code/game/objects/structures/fluff.dm
index a38659aaefe..82e89314d68 100644
--- a/code/game/objects/structures/fluff.dm
+++ b/code/game/objects/structures/fluff.dm
@@ -305,3 +305,12 @@
anchored = FALSE
density = TRUE
deconstructible = TRUE
+
+/obj/structure/fluff/wallsign
+ name = "direction sign"
+ desc = "Now, where to go?"
+ density = FALSE
+ icon = 'icons/obj/fluff/general.dmi'
+ icon_state = "wallsign"
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/fluff/wallsign, 32)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index dc1c7548d49..0e50c983d88 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -932,6 +932,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/tinted/frosted/spaw
icon_state = "clockwork_window-single"
glass_type = /obj/item/stack/sheet/bronze
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/bronze/spawner, 0)
+
/obj/structure/window/bronze/unanchored
anchored = FALSE
diff --git a/code/game/turfs/closed/indestructible.dm b/code/game/turfs/closed/indestructible.dm
index b8c386ecf10..f1243b68a84 100644
--- a/code/game/turfs/closed/indestructible.dm
+++ b/code/game/turfs/closed/indestructible.dm
@@ -121,6 +121,16 @@ SKYRAT EDIT REMOVAL END */
smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
canSmoothWith = SMOOTH_GROUP_WALLS
+/turf/closed/indestructible/reinforced/titanium
+ name = "reinforced titanium imitation wall"
+ desc = "A huge chunk of reinforced metal used to separate rooms. Naturally, to cut down on costs, this is just a really good paint job to resemble titanium. Effectively impervious to conventional methods of destruction."
+ icon = 'icons/turf/walls/shuttle_wall.dmi'
+ icon_state = "shuttle_wall-0"
+ base_icon_state = "shuttle_wall"
+
+/turf/closed/indestructible/reinforced/titanium/nodiagonal
+ icon_state = "shuttle_wall-15"
+ smoothing_flags = SMOOTH_BITMASK
/turf/closed/indestructible/riveted
icon = 'icons/turf/walls/riveted.dmi'
diff --git a/code/game/turfs/open/chasm.dm b/code/game/turfs/open/chasm.dm
index 48ab1ceca38..142d966172b 100644
--- a/code/game/turfs/open/chasm.dm
+++ b/code/game/turfs/open/chasm.dm
@@ -119,3 +119,19 @@
/turf/open/chasm/true/apply_components(mapload)
AddComponent(/datum/component/chasm, null, mapload) //Don't pass anything for below_turf.
+
+/turf/open/chasm/true/no_smooth
+ smoothing_flags = NONE
+
+/turf/open/chasm/true/no_smooth/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ return FALSE
+
+/turf/open/chasm/true/no_smooth/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data)
+ return FALSE
+
+/turf/open/chasm/true/no_smooth/attackby(obj/item/item, mob/user, params, area/area_restriction)
+ if(istype(item, /obj/item/stack/rods))
+ return
+ else if(istype(item, /obj/item/stack/tile/iron))
+ return
+ return ..()
diff --git a/code/modules/awaymissions/away_props.dm b/code/modules/awaymissions/away_props.dm
index f6d1a830a91..1ed0a1ec0ff 100644
--- a/code/modules/awaymissions/away_props.dm
+++ b/code/modules/awaymissions/away_props.dm
@@ -118,3 +118,19 @@
icon = 'icons/turf/floors.dmi'
icon_state = "floor"
hidden = TRUE
+
+/// only player mobs (has ckey) may pass, reverse for the opposite
+/obj/effect/playeronly_barrier
+ name = "player-only barrier"
+ desc = "You shall pass."
+ icon = 'icons/effects/mapping_helpers.dmi'
+ icon_state = "blocker"
+ anchored = TRUE
+ invisibility = INVISIBILITY_MAXIMUM
+ var/reverse = FALSE //Block if has ckey
+
+/obj/effect/playeronly_barrier/CanAllowThrough(mob/living/mover, border_dir)
+ . = ..()
+ if(!istype(mover))
+ return
+ return isnull(mover.ckey) == reverse
diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm b/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm
new file mode 100644
index 00000000000..20210d56cc9
--- /dev/null
+++ b/code/modules/mapfluff/ruins/objects_and_mobs/museum.dm
@@ -0,0 +1,131 @@
+/obj/machinery/computer/terminal/museum
+ name = "exhibit info terminal"
+ desc = "A relatively low-tech info board. Not as low-tech as an actual sign though. Appears to be quite old."
+ upperinfo = "Nanotrasen Museum Exhibit Info"
+ icon_state = "plaque"
+ icon_screen = "plaque_screen"
+ icon_keyboard = null
+
+/obj/effect/replica_spawner //description and name are intact, better to make a new fluff object for stuff that is not actually ingame as an object
+ name = "replica creator"
+ desc = "This creates a fluff object that looks exactly like the input, but like obviously a replica. Do not for the love of god use with stuff that has Initialize side effects."
+ icon = 'icons/hud/screen_gen.dmi'
+ icon_state = "x2"
+ invisibility = INVISIBILITY_ABSTRACT //nope, can't see this
+ anchored = TRUE
+ density = TRUE
+ opacity = FALSE
+ var/replica_path = /obj/structure/fluff
+ var/target_path
+ var/obvious_replica = TRUE
+
+/obj/effect/replica_spawner/Initialize(mapload)
+ . = ..()
+ if(isnull(target_path))
+ return INITIALIZE_HINT_QDEL //no use to make a replica of null
+ var/atom/appearance_object = new target_path
+ var/atom/new_replica = new replica_path(loc)
+
+ new_replica.icon = appearance_object.icon
+ new_replica.icon_state = appearance_object.icon_state
+ new_replica.copy_overlays(appearance_object.appearance, cut_old = TRUE)
+ new_replica.density = appearance_object.density //for like nondense showers and stuff
+
+ new_replica.name = "[appearance_object.name][obvious_replica ? " replica" : ""]"
+ new_replica.desc = "[appearance_object.desc][obvious_replica ? " ..except this one is a replica.": ""]"
+ qdel(appearance_object)
+ qdel(src)
+ return INITIALIZE_HINT_QDEL
+
+/obj/structure/fluff/dnamod
+ name = "DNA Modifier"
+ desc = "DNA Manipulator replica. Essentially just a box of cool lights."
+ icon = 'icons/obj/service/hydroponics/equipment.dmi'
+ icon_state = "dnamod"
+ density = TRUE
+
+/obj/structure/fluff/preserved_borer
+ name = "preserved borer exhibit"
+ desc = "A preserved cortical borer. Probably been there long enough to not last long outside the exhibit."
+ icon = 'icons/obj/structures.dmi'
+ icon_state = "preservedborer"
+ density = TRUE
+
+/obj/structure/fluff/balloon_nuke
+ name = "nuclear balloon explosive"
+ desc = "You probably shouldn't stick around to see if this is inflated."
+ icon = /obj/machinery/nuclearbomb::icon
+ icon_state = /obj/machinery/nuclearbomb::icon_state
+ density = TRUE
+ max_integrity = 5 //one tap
+
+/obj/structure/fluff/balloon_nuke/atom_destruction()
+ playsound(loc, 'sound/effects/cartoon_pop.ogg', 75, vary = TRUE)
+ ..()
+
+/obj/structure/fluff/fake_camera
+ name = /obj/machinery/camera::name
+ desc = /obj/machinery/camera::desc
+ icon = /obj/machinery/camera::icon
+ icon_state = /obj/machinery/camera::icon_state
+
+/obj/structure/fluff/fake_scrubber
+ name = /obj/machinery/atmospherics/components/unary/vent_scrubber::name
+ desc = /obj/machinery/atmospherics/components/unary/vent_scrubber::desc
+ icon = /obj/machinery/atmospherics/components/unary/vent_scrubber::icon
+ layer = /obj/machinery/atmospherics/components/unary/vent_scrubber::layer
+ plane = FLOOR_PLANE
+ icon_state = "scrub_on"
+
+/obj/structure/fluff/fake_vent
+ name = /obj/machinery/atmospherics/components/unary/vent_pump::name
+ desc = /obj/machinery/atmospherics/components/unary/vent_pump::desc
+ icon = /obj/machinery/atmospherics/components/unary/vent_pump::icon
+ layer = /obj/machinery/atmospherics/components/unary/vent_scrubber::layer
+ plane = FLOOR_PLANE
+ icon_state = "vent_out"
+
+/turf/open/mirage
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "mirage"
+ invisibility = INVISIBILITY_ABSTRACT
+ /// target turf x and y are offsets from our location instead of a direct coordinate
+ var/offset = TRUE
+ /// tile range that we show, 2 means that the target tile and two tiles ahead of it in our direction will show
+ var/range
+ var/target_turf_x = 0
+ var/target_turf_y = 0
+ /// if not specified, uses our Z
+ var/target_turf_z
+
+/turf/open/mirage/Initialize(mapload)
+ . = ..()
+ if(isnull(range))
+ range = world.view
+ var/used_z = target_turf_z || z //if target z is not defined, use ours
+ var/turf/target = locate(offset ? target_turf_x + x : target_turf_x, offset ? target_turf_y + y : target_turf_y, used_z)
+ AddElement(/datum/element/mirage_border, target, dir, range)
+
+/obj/effect/mapping_helpers/ztrait_injector/museum
+ traits_to_add = list(ZTRAIT_NOPARALLAX = TRUE, ZTRAIT_NOXRAY = TRUE, ZTRAIT_NOPHASE = TRUE, ZTRAIT_BASETURF = /turf/open/indestructible/plating, ZTRAIT_SECRET = TRUE)
+
+/obj/effect/smooths_with_walls
+ name = "effect that smooths with walls"
+ desc = "to supplement /turf/open/mirage."
+ icon = 'icons/hud/screen_gen.dmi'
+ icon_state = "x2"
+ invisibility = INVISIBILITY_ABSTRACT
+ anchored = TRUE
+ density = TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+
+/obj/item/paper/fluff/museum/noend
+ name = "scrambled note"
+ default_raw_text = {"this place,
+
god whose idea was to build a museum in the void in the middle of god knows where there is no reason we should have done this
+
and those mannequins why do they stare back where the fuck did you get these from
+
how would we even get visitors here
+
sometimes i can catch them moving
+
+
we should have never come here"}
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index 7676801a0a9..faaaf9e4206 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -1395,3 +1395,32 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
var/turf/our_turf = get_turf(src) // In case a locker ate us or something
our_turf.AddElement(/datum/element/bombable_turf)
return INITIALIZE_HINT_QDEL
+
+/// this helper buckles all mobs on the tile to the first buckleable object
+/obj/effect/mapping_helpers/mob_buckler
+ name = "Buckle Mob"
+ icon_state = "buckle"
+ late = TRUE
+ ///whether we force a buckle
+ var/force_buckle = FALSE
+
+/obj/effect/mapping_helpers/mob_buckler/Initialize(mapload)
+ . = ..()
+ var/atom/movable/buckle_to
+ var/list/mobs = list()
+ for(var/atom/movable/possible_buckle as anything in loc)
+ if(isnull(buckle_to) && possible_buckle.can_buckle)
+ buckle_to = possible_buckle
+ continue
+
+ if(isliving(possible_buckle))
+ mobs += possible_buckle
+
+ if(isnull(buckle_to))
+ log_mapping("[type] at [x] [y] [z] did not find anything to buckle to")
+ return INITIALIZE_HINT_QDEL
+
+ for(var/mob/living/mob as anything in mobs)
+ buckle_to.buckle_mob(mob, force = force_buckle)
+
+ return INITIALIZE_HINT_QDEL
diff --git a/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm b/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm
new file mode 100644
index 00000000000..7b6bf6c8399
--- /dev/null
+++ b/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm
@@ -0,0 +1,89 @@
+/mob/living/basic/statue/mannequin
+ name = "mannequin"
+ desc = "Oh, so this is a dress-up game now."
+ icon = 'icons/mob/human/mannequin.dmi'
+ icon_state = "mannequin_wood_male"
+ icon_living = "mannequin_wood_male"
+ icon_dead = "mannequin_wood_male"
+ health = 300
+ maxHealth = 300
+ melee_damage_lower = 15
+ melee_damage_upper = 30
+ sentience_type = SENTIENCE_ARTIFICIAL
+ ai_controller = /datum/ai_controller/basic_controller/stares_at_people
+ /// the path to a fake item we will hold in our right hand
+ var/obj/item/held_item
+ /// the path to a fake hat we will wear
+ var/obj/item/hat
+
+/mob/living/basic/statue/mannequin/Initialize(mapload)
+ . = ..()
+ update_appearance()
+
+/mob/living/basic/statue/mannequin/update_overlays()
+ . = ..()
+ if(held_item)
+ . += mutable_appearance(initial(held_item.righthand_file), initial(held_item.inhand_icon_state))
+ if(hat)
+ . += mutable_appearance(initial(hat.worn_icon), initial(hat.worn_icon_state) || initial(hat.icon_state))
+
+/datum/ai_controller/basic_controller/stares_at_people
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_AGGRO_RANGE = 6,
+ )
+
+ ai_movement = /datum/ai_movement/dumb
+ idle_behavior = null
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/face_target_or_face_initial, // we be creepy and all
+ )
+
+/datum/ai_planning_subtree/face_target_or_face_initial
+
+/datum/ai_planning_subtree/face_target_or_face_initial/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
+ if(isnull(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]))
+ return
+ var/mob/living/we = controller.pawn
+ controller.blackboard[BB_STARTING_DIRECTION] = we.dir
+ controller.queue_behavior(/datum/ai_behavior/face_target_or_face_initial, BB_BASIC_MOB_CURRENT_TARGET)
+
+/datum/ai_behavior/face_target_or_face_initial
+
+/datum/ai_behavior/face_target_or_face_initial/setup(datum/ai_controller/controller, target_key)
+ . = ..()
+ var/atom/movable/target = controller.blackboard[target_key]
+ return ismovable(target) && isturf(target.loc) && ismob(controller.pawn)
+
+/datum/ai_behavior/face_target_or_face_initial/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
+ . = ..()
+ var/atom/movable/target = controller.blackboard[target_key]
+ var/mob/living/we = controller.pawn
+ if(isnull(target) || get_dist(we, target) > 8)
+ we.dir = controller.blackboard[BB_STARTING_DIRECTION]
+ finish_action(controller, TRUE)
+ else
+ we.face_atom(target)
+
+/mob/living/basic/statue/mannequin/suspicious
+ name = "mannequin?"
+ desc = "Their eyes follow you."
+ health = 1500 //yeah uhh avoid these
+ maxHealth = 1500
+ ai_controller = /datum/ai_controller/basic_controller/suspicious_mannequin
+
+/datum/ai_controller/basic_controller/suspicious_mannequin
+ blackboard = list(
+ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
+ BB_AGGRO_RANGE = 14,
+ BB_EMOTE_KEY = "scream", //spooky
+ )
+
+ ai_movement = /datum/ai_movement/jps //threat
+ idle_behavior = null
+ planning_subtrees = list(
+ /datum/ai_planning_subtree/simple_find_target,
+ /datum/ai_planning_subtree/run_emote,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
+ )
diff --git a/code/modules/mob/living/basic/vermin/mothroach.dm b/code/modules/mob/living/basic/vermin/mothroach.dm
index 15f82d38f85..4c06665a14a 100644
--- a/code/modules/mob/living/basic/vermin/mothroach.dm
+++ b/code/modules/mob/living/basic/vermin/mothroach.dm
@@ -73,3 +73,10 @@
planning_subtrees = list(
/datum/ai_planning_subtree/random_speech/mothroach,
)
+
+/mob/living/basic/mothroach/bar
+ name = "mothroach bartender"
+ desc = "A mothroach serving drinks. Look at him go."
+ icon_state = "barroach"
+ icon_living = "barroach"
+ icon_dead = "barroach_dead"
diff --git a/code/modules/transport/tram/tram_controller.dm b/code/modules/transport/tram/tram_controller.dm
index 5665755520e..22504b6118b 100644
--- a/code/modules/transport/tram/tram_controller.dm
+++ b/code/modules/transport/tram/tram_controller.dm
@@ -672,6 +672,11 @@
dispatch_transport(destination_platform = push_destination)
return push_destination
+
+/datum/transport_controller/linear/tram/slow //for some reason speed is set to initial() in the code but if i touched it it would probably break so
+ speed_limiter = 3
+ base_speed_limiter = 3
+
/**
* The physical cabinet on the tram. Acts as the interface between players and the controller datum.
*/
diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm
index 7a57529b8ff..89a7333865e 100644
--- a/code/modules/transport/transport_module.dm
+++ b/code/modules/transport/transport_module.dm
@@ -86,6 +86,7 @@
if(radial_travel)
AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Transport")
+ ADD_TRAIT(src, TRAIT_CHASM_STOPPER, INNATE_TRAIT)
set_movement_registrations()
//since transport_controller datums find all connected platforms when a transport structure first creates it and then
@@ -936,3 +937,7 @@
var/throw_target = get_edge_target_turf(src, throw_direction)
var/datum/callback/land_slam = new(passenger, TYPE_PROC_REF(/mob/living/, tram_slam_land))
passenger.throw_at(throw_target, 400, 4, force = MOVE_FORCE_OVERPOWERING, callback = land_slam)
+
+/obj/structure/transport/linear/tram/slow
+ transport_controller_type = /datum/transport_controller/linear/tram/slow
+ speed_limiter = /datum/transport_controller/linear/tram/slow::speed_limiter
diff --git a/config/awaymissionconfig.txt b/config/awaymissionconfig.txt
index 7278655a39b..0a96d93984a 100644
--- a/config/awaymissionconfig.txt
+++ b/config/awaymissionconfig.txt
@@ -7,9 +7,7 @@
#Do NOT tick the maps during compile -- the game uses this list to decide which map to load. Ticking the maps will result in them ALL being loaded at once.
#DO tick the associated code file for the away mission you are enabling. Otherwise, the map will be trying to reference objects which do not exist, which will cause runtime errors!
-#_maps/RandomZLevels/blackmarketpackers.dmm
#_maps/RandomZLevels/TheBeach.dmm
-#_maps/RandomZLevels/centcomAway.dmm
#_maps/RandomZLevels/moonoutpost19.dmm
#_maps/RandomZLevels/undergroundoutpost45.dmm
#_maps/RandomZLevels/caves.dmm
@@ -17,3 +15,4 @@
#_maps/RandomZLevels/research.dmm
#_maps/RandomZLevels/SnowCabin.dmm
#_maps/RandomZLevels/blackmesa.dmm
+#_maps/RandomZLevels/museum.dmm
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 1b2f617972a..fcb4e262d7c 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi
index d28d2770ec4..351bb5f00c4 100644
Binary files a/icons/effects/mapping_helpers.dmi and b/icons/effects/mapping_helpers.dmi differ
diff --git a/icons/mob/simple/animal.dmi b/icons/mob/simple/animal.dmi
index 3bd3438bc62..7fcf0e9d65e 100644
Binary files a/icons/mob/simple/animal.dmi and b/icons/mob/simple/animal.dmi differ
diff --git a/icons/obj/fluff/general.dmi b/icons/obj/fluff/general.dmi
index 2628eea8746..1aa7ae5c898 100644
Binary files a/icons/obj/fluff/general.dmi and b/icons/obj/fluff/general.dmi differ
diff --git a/icons/obj/machines/computer.dmi b/icons/obj/machines/computer.dmi
index cba0069cf6f..aae3a83a455 100644
Binary files a/icons/obj/machines/computer.dmi and b/icons/obj/machines/computer.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index 40420db3705..1e6a2ba6872 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index 6fc1178a6b4..89b4876c0ce 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index fb1e85f2942..ee20e3d2a98 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -4461,6 +4461,7 @@
#include "code\modules\mapfluff\ruins\lavalandruin_code\watcher_grave.dm"
#include "code\modules\mapfluff\ruins\objects_and_mobs\ash_walker_den.dm"
#include "code\modules\mapfluff\ruins\objects_and_mobs\cursed_slot_machine.dm"
+#include "code\modules\mapfluff\ruins\objects_and_mobs\museum.dm"
#include "code\modules\mapfluff\ruins\objects_and_mobs\necropolis_gate.dm"
#include "code\modules\mapfluff\ruins\objects_and_mobs\sin_ruins.dm"
#include "code\modules\mapfluff\ruins\spaceruin_code\allamericandiner.dm"
@@ -4865,6 +4866,7 @@
#include "code\modules\mob\living\basic\space_fauna\spider\spiderlings\spiderling_subtypes.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\young_spider\young_spider.dm"
#include "code\modules\mob\living\basic\space_fauna\spider\young_spider\young_spider_subtypes.dm"
+#include "code\modules\mob\living\basic\space_fauna\statue\mannequin.dm"
#include "code\modules\mob\living\basic\space_fauna\statue\statue.dm"
#include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\fugu_gland.dm"
#include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\inflation.dm"