diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm
index 5380268e979c..c67d9e1a8327 100644
--- a/code/__DEFINES/shuttles.dm
+++ b/code/__DEFINES/shuttles.dm
@@ -94,3 +94,13 @@
#define SHUTTLE_UNLOCK_BUBBLEGUM "bubblegum"
#define SHUTTLE_UNLOCK_MEDISIM "holodeck"
#define SHUTTLE_UNLOCK_NARNAR "narnar"
+
+//Shuttle Events
+
+///Self destruct if this is returned in process
+#define SHUTTLE_EVENT_CLEAR 2
+
+///spawned stuff should float by the window and not hit the shuttle
+#define SHUTTLE_EVENT_MISS_SHUTTLE 1 << 0
+///spawned stuff should hit the shuttle
+#define SHUTTLE_EVENT_HIT_SHUTTLE 1 << 1
diff --git a/code/__DEFINES/spatial_gridmap.dm b/code/__DEFINES/spatial_gridmap.dm
index 85462c11bb9f..f43fcbc3c6d3 100644
--- a/code/__DEFINES/spatial_gridmap.dm
+++ b/code/__DEFINES/spatial_gridmap.dm
@@ -2,6 +2,7 @@
#define SPATIAL_GRID_CELLSIZE 17
///Takes a coordinate, and spits out the spatial grid index (x or y) it's inside
#define GET_SPATIAL_INDEX(coord) ROUND_UP((coord) / SPATIAL_GRID_CELLSIZE)
+#define GRID_INDEX_TO_COORDS(index) (index * SPATIAL_GRID_CELLSIZE)
#define SPATIAL_GRID_CELLS_PER_SIDE(world_bounds) GET_SPATIAL_INDEX(world_bounds)
#define SPATIAL_GRID_CHANNELS 2
@@ -15,6 +16,8 @@
///all atmos machines are stored in this channel (I'm sorry kyler)
#define SPATIAL_GRID_CONTENTS_TYPE_ATMOS "spatial_grid_contents_type_atmos"
+#define ALL_CONTENTS_OF_CELL(cell) (cell.hearing_contents | cell.client_contents | cell.atmos_contents)
+
///whether movable is itself or containing something which should be in one of the spatial grid channels.
#define HAS_SPATIAL_GRID_CONTENTS(movable) (movable.spatial_grid_key)
@@ -43,3 +46,9 @@
if(!length(cell_contents_list)) {\
cell_contents_list = dummy_list; \
};
+
+///remove from every list
+#define GRID_CELL_REMOVE_ALL(cell, movable) \
+ GRID_CELL_REMOVE(cell.hearing_contents, movable) \
+ GRID_CELL_REMOVE(cell.client_contents, movable) \
+ GRID_CELL_REMOVE(cell.atmos_contents, movable)
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 5d7ebb88be40..7ee56e1c372d 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -644,6 +644,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"
+/// The effects of hyperspace drift are blocked when the tile has this trait
+#define TRAIT_HYPERSPACE_STOPPED "hyperspace_stopped"
///Turf slowdown will be ignored when this trait is added to a turf.
#define TRAIT_TURF_IGNORE_SLOWDOWN "turf_ignore_slowdown"
///Mobs won't slip on a wet turf while it has this trait
diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm
index 18b50c16faec..bb688b4655fb 100644
--- a/code/__HELPERS/areas.dm
+++ b/code/__HELPERS/areas.dm
@@ -160,7 +160,7 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(
//inform atoms on the turf that their area has changed
for(var/atom/stuff as anything in the_turf)
//unregister the stuff from its old area
- SEND_SIGNAL(stuff, COMSIG_EXIT_AREA, oldA)
+ SEND_SIGNAL(stuff, COMSIG_EXIT_AREA, old_area)
//register the stuff to its new area. special exception for apc as its not registered to this signal
if(istype(stuff, /obj/machinery/power/apc))
diff --git a/code/__HELPERS/lighting.dm b/code/__HELPERS/lighting.dm
index 98ed7e7d66c3..674dc1d71134 100644
--- a/code/__HELPERS/lighting.dm
+++ b/code/__HELPERS/lighting.dm
@@ -59,13 +59,13 @@
// First, we cut away a constant amount
var/cut_away = (alpha_to_leave - 1) / 255
- var/atom/movable/render_step/color/alpha_threshold_down = new(make_blocker, make_blocker.render_target, list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,-cut_away))
+ var/atom/movable/render_step/color/alpha_threshold_down = new(null, make_blocker, list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,-cut_away))
alpha_threshold_down.render_target = "*emissive_block_alpha_down_[uid]"
// Then we multiply what remains by the amount we took away
- var/atom/movable/render_step/color/alpha_threshold_up = new(make_blocker, alpha_threshold_down.render_target, list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,alpha_to_leave, 0,0,0,0))
+ var/atom/movable/render_step/color/alpha_threshold_up = new(null, alpha_threshold_down, list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,alpha_to_leave, 0,0,0,0))
alpha_threshold_up.render_target = "*emissive_block_alpha_up_[uid]"
// Now we just feed that into an emissive blocker
- var/atom/movable/render_step/emissive_blocker/em_block = new(make_blocker, alpha_threshold_up.render_target)
+ var/atom/movable/render_step/emissive_blocker/em_block = new(null, alpha_threshold_up)
var/list/hand_back = list()
hand_back += alpha_threshold_down
hand_back += alpha_threshold_up
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index c64d99251d92..69519a66b735 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -98,7 +98,7 @@
#endif
#ifndef PRELOAD_RSC //set to:
-#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
+#define PRELOAD_RSC 1 // 0 to allow using external resources or on-demand behaviour;
#endif // 1 to use the default behaviour;
// 2 for preloading absolutely everything;
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 3092722c79df..dd90e9c77fd9 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -94,7 +94,7 @@
old_object.MouseExited(over_location, over_control, params)
last_hovored_ref = WEAKREF(over_object)
- over_object.MouseEntered(over_location, over_control, params)
+ over_object?.MouseEntered(over_location, over_control, params)
/atom/movable/screen/movable/action_button/MouseEntered(location, control, params)
. = ..()
@@ -230,7 +230,7 @@
return
for(var/datum/action/action as anything in take_from.actions)
- if(!action.show_to_observers)
+ if(!action.show_to_observers || !action.owner_has_control)
continue
action.GiveAction(src)
RegisterSignal(take_from, COMSIG_MOB_GRANTED_ACTION, PROC_REF(on_observing_action_granted), TRUE)
@@ -251,7 +251,7 @@
/mob/proc/on_observing_action_granted(mob/living/source, datum/action/action)
SIGNAL_HANDLER
- if(!action.show_to_observers)
+ if(!action.show_to_observers || !action.owner_has_control)
return
action.GiveAction(src)
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 3a8ccad63adb..5f7dc3f52b89 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -198,8 +198,8 @@ SUBSYSTEM_DEF(shuttle)
if(!thing)
mobile_docking_ports.Remove(thing)
continue
- var/obj/docking_port/mobile/P = thing
- P.check()
+ var/obj/docking_port/mobile/port = thing
+ port.check()
for(var/thing in transit_docking_ports)
var/obj/docking_port/stationary/transit/T = thing
if(!T.owner)
diff --git a/code/controllers/subsystem/spatial_gridmap.dm b/code/controllers/subsystem/spatial_gridmap.dm
index c6c90b612c7b..1b097994a956 100644
--- a/code/controllers/subsystem/spatial_gridmap.dm
+++ b/code/controllers/subsystem/spatial_gridmap.dm
@@ -482,21 +482,106 @@ SUBSYSTEM_DEF(spatial_grid)
return TRUE
-///find the cell this movable is associated with and removes it from all lists
-/datum/controller/subsystem/spatial_grid/proc/force_remove_from_cell(atom/movable/to_remove, datum/spatial_grid_cell/input_cell)
+/// if for whatever reason this movable is "untracked" e.g. it breaks the assumption that a movable is only inside the contents of any grid cell associated with its loc,
+/// this will error. this checks every grid cell in the world so dont call this on live unless you have to.
+/// returns TRUE if this movable is untracked, FALSE otherwise
+/datum/controller/subsystem/spatial_grid/proc/untracked_movable_error(atom/movable/movable_to_check)
+ if(!movable_to_check?.spatial_grid_key)
+ return FALSE
+
+ if(!initialized)
+ return FALSE
+
+ var/datum/spatial_grid_cell/loc_cell = get_cell_of(movable_to_check)
+ var/list/containing_cells = find_hanging_cell_refs_for_movable(movable_to_check, remove_from_cells=FALSE)
+ //if we're in multiple cells, throw an error.
+ //if we're in 1 cell but it cant be deduced by our location, throw an error.
+ if(length(containing_cells) > 1 || (length(containing_cells) == 1 && loc_cell && containing_cells[1] != loc_cell && containing_cells[1] != null))
+ var/error_data = ""
+
+ var/location_string = "which is in nullspace, and thus not be within the contents of any spatial grid cell"
+ if(loc_cell)
+ location_string = "which is supposed to only be in the contents of a spatial grid cell at coords: ([GRID_INDEX_TO_COORDS(loc_cell.cell_x)], [GRID_INDEX_TO_COORDS(loc_cell.cell_y)], [loc_cell.cell_z])"
+
+ var/error_explanation = "was in the contents of [length(containing_cells)] spatial grid cells when it was only supposed to be in one!"
+ if(length(containing_cells) == 1)
+ error_explanation = "was in the contents of 1 spatial grid cell but it was inside the area handled by another grid cell!"
+ var/datum/spatial_grid_cell/bad_cell = containing_cells[1]
+
+ error_data = "within the contents of a cell at coords: ([GRID_INDEX_TO_COORDS(bad_cell.cell_x)], [GRID_INDEX_TO_COORDS(bad_cell.cell_y)], [bad_cell.cell_z])"
+
+ if(!error_data)
+ for(var/datum/spatial_grid_cell/cell in containing_cells)
+ var/coords = "([GRID_INDEX_TO_COORDS(cell.cell_x)], [GRID_INDEX_TO_COORDS(cell.cell_y)], [cell.cell_z])"
+ var/contents = ""
+
+ if(movable_to_check in cell.hearing_contents)
+ contents = "hearing"
+
+ if(movable_to_check in cell.client_contents)
+ if(length(contents) > 0)
+ contents = "[contents], client"
+ else
+ contents = "client"
+
+ if(movable_to_check in cell.atmos_contents)
+ if(length(contents) > 0)
+ contents = "[contents], atmos"
+ else
+ contents = "atmos"
+
+ if(length(error_data) > 0)
+ error_data = "[error_data], {coords: [coords], within channels: [contents]}"
+ else
+ error_data = "within the contents of the following cells: {coords: [coords], within channels: [contents]}"
+
+ /**
+ * example:
+ *
+ * /mob/living/trolls_the_maintainer instance, which is supposed to only be in the contents of a spatial grid cell at coords: (136, 136, 14),
+ * was in the contents of 3 spatial grid cells when it was only supposed to be in one! within the contents of the following cells:
+ * {(68, 153, 2), within channels: hearing},
+ * {coords: (221, 170, 3), within channels: hearing},
+ * {coords: (255, 153, 11), within channels: hearing},
+ * {coords: (136, 136, 14), within channels: hearing}.
+ */
+ stack_trace("[movable_to_check.type] instance, [location_string], [error_explanation] [error_data].")
+
+ return TRUE
+
+ return FALSE
+
+/**
+ * remove this movable from the grid by finding the grid cell its in and removing it from that.
+ * if it cant infer a grid cell its located in (e.g. if its in nullspace but it can happen if the grid isnt expanded to a z level), search every grid cell.
+ */
+/datum/controller/subsystem/spatial_grid/proc/force_remove_from_grid(atom/movable/to_remove)
+ if(!to_remove?.spatial_grid_key)
+ return
+
if(!initialized)
remove_from_pre_init_queue(to_remove)//the spatial grid doesnt exist yet, so just take it out of the queue
return
+#ifdef UNIT_TESTS
+ if(untracked_movable_error(to_remove))
+ find_hanging_cell_refs_for_movable(to_remove, remove_from_cells=TRUE)
+ return
+#endif
+
+ var/datum/spatial_grid_cell/loc_cell = get_cell_of(to_remove)
+
+ if(loc_cell)
+ GRID_CELL_REMOVE_ALL(loc_cell, to_remove)
+ else
+ find_hanging_cell_refs_for_movable(to_remove, remove_from_cells=TRUE)
+
+///remove this movable from the given spatial_grid_cell
+/datum/controller/subsystem/spatial_grid/proc/force_remove_from_cell(atom/movable/to_remove, datum/spatial_grid_cell/input_cell)
if(!input_cell)
- input_cell = get_cell_of(to_remove)
- if(!input_cell)
- find_hanging_cell_refs_for_movable(to_remove, TRUE)
- return
+ return
- GRID_CELL_REMOVE(input_cell.client_contents, to_remove)
- GRID_CELL_REMOVE(input_cell.hearing_contents, to_remove)
- GRID_CELL_REMOVE(input_cell.atmos_contents, to_remove)
+ GRID_CELL_REMOVE_ALL(input_cell, to_remove)
///if shit goes south, this will find hanging references for qdeleting movables inside the spatial grid
/datum/controller/subsystem/spatial_grid/proc/find_hanging_cell_refs_for_movable(atom/movable/to_remove, remove_from_cells = TRUE)
@@ -526,7 +611,7 @@ SUBSYSTEM_DEF(spatial_grid)
///debug proc for checking if a movable is in multiple cells when it shouldnt be (ie always unless multitile entering is implemented)
/atom/proc/find_all_cells_containing(remove_from_cells = FALSE)
var/datum/spatial_grid_cell/real_cell = SSspatial_grid.get_cell_of(src)
- var/list/containing_cells = SSspatial_grid.find_hanging_cell_refs_for_movable(src, FALSE, remove_from_cells)
+ var/list/containing_cells = SSspatial_grid.find_hanging_cell_refs_for_movable(src, remove_from_cells)
message_admins("[src] is located in the contents of [length(containing_cells)] spatial grid cells")
@@ -758,7 +843,5 @@ SUBSYSTEM_DEF(spatial_grid)
#undef BOUNDING_BOX_MAX
#undef BOUNDING_BOX_MIN
-#undef GRID_CELL_ADD
-#undef GRID_CELL_REMOVE
-#undef GRID_CELL_SET
+
#undef NUMBER_OF_PREGENERATED_ORANGES_EARS
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index e19cde3daa97..e517742bd217 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -33,8 +33,10 @@
..()
/datum/brain_trauma/severe/aphasia/on_lose()
- owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_APHASIA)
- owner.remove_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA)
+ if(!QDELING(owner))
+ owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_APHASIA)
+ owner.remove_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA)
+
..()
/datum/brain_trauma/severe/blindness
diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm
index 23b13a652469..ab1b06501069 100644
--- a/code/datums/components/mind_linker.dm
+++ b/code/datums/components/mind_linker.dm
@@ -190,11 +190,10 @@
return ..() && (owner.stat != DEAD)
/datum/action/innate/linked_speech/Activate()
-
var/datum/component/mind_linker/linker = target
var/mob/living/linker_parent = linker.parent
- var/message = sanitize(tgui_input_text(owner, "Enter a message to transmit.", "[linker.network_name] Telepathy"))
+ var/message = tgui_input_text(owner, "Enter a message to transmit.", "[linker.network_name] Telepathy")
if(!message || QDELETED(src) || QDELETED(owner) || owner.stat == DEAD)
return
@@ -208,7 +207,8 @@
var/list/all_who_can_hear = assoc_to_keys(linker.linked_mobs) + linker_parent
for(var/mob/living/recipient as anything in all_who_can_hear)
- to_chat(recipient, formatted_message)
+ var/avoid_highlighting = (recipient == owner) || (recipient == linker_parent)
+ to_chat(recipient, formatted_message, type = MESSAGE_TYPE_RADIO, avoid_highlighting = avoid_highlighting)
for(var/mob/recipient as anything in GLOB.dead_mob_list)
- to_chat(recipient, "[FOLLOW_LINK(recipient, owner)] [formatted_message]")
+ to_chat(recipient, "[FOLLOW_LINK(recipient, owner)] [formatted_message]", type = MESSAGE_TYPE_RADIO)
diff --git a/code/datums/components/shuttle_cling.dm b/code/datums/components/shuttle_cling.dm
index 57c79e15193a..f686d7103bc8 100644
--- a/code/datums/components/shuttle_cling.dm
+++ b/code/datums/components/shuttle_cling.dm
@@ -38,15 +38,25 @@
ADD_TRAIT(parent, TRAIT_HYPERSPACED, src)
RegisterSignals(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UNBUCKLE, COMSIG_ATOM_NO_LONGER_PULLED), PROC_REF(update_state))
+ RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_FREE_HYPERSPACE_MOVEMENT), PROC_REF(initialize_loop))
+ RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_FREE_HYPERSPACE_MOVEMENT), PROC_REF(clear_loop))
//Items have this cool thing where they're first put on the floor if you grab them from storage, and then into your hand, which isn't caught by movement signals that well
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_PICKUP, PROC_REF(do_remove))
- hyperloop = SSmove_manager.move(moving = parent, direction = direction, delay = not_clinging_move_delay, subsystem = SShyperspace_drift, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_NO_DIR_UPDATE|MOVEMENT_LOOP_DRAGGING)
+ if(!HAS_TRAIT(parent, TRAIT_FREE_HYPERSPACE_MOVEMENT))
+ initialize_loop()
update_state(parent) //otherwise we'll get moved 1 tile before we can correct ourselves, which isnt super bad but just looks jank
+/datum/component/shuttle_cling/proc/initialize_loop()
+ hyperloop = SSmove_manager.move(moving = parent, direction = direction, delay = not_clinging_move_delay, subsystem = SShyperspace_drift, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_NO_DIR_UPDATE|MOVEMENT_LOOP_OUTSIDE_CONTROL)
+ update_state()
+
+/datum/component/shuttle_cling/proc/clear_loop()
+ QDEL_NULL(hyperloop)
+
///Check if we're in hyperspace and our state in hyperspace
/datum/component/shuttle_cling/proc/update_state()
SIGNAL_HANDLER
@@ -55,6 +65,9 @@
qdel(src)
return
+ if(!hyperloop)
+ return
+
var/should_loop = FALSE
switch(is_holding_on(parent))
@@ -72,6 +85,10 @@
if(ALL_GOOD)
should_loop = FALSE
+ // the hyperloop can get reset to null from the above procs
+ if(!hyperloop)
+ return
+
//Do pause/unpause/nothing for the hyperloop
if(should_loop && hyperloop.paused)
hyperloop.resume_loop()
@@ -80,12 +97,16 @@
///Check if we're "holding on" to the shuttle
/datum/component/shuttle_cling/proc/is_holding_on(atom/movable/movee)
- if(movee.pulledby || !isturf(movee.loc))
+ if(movee.pulledby || !isturf(movee.loc) || HAS_TRAIT(movee, TRAIT_FREE_HYPERSPACE_MOVEMENT))
return ALL_GOOD
if(!isliving(movee))
+ if(HAS_TRAIT(movee, TRAIT_FORCED_GRAVITY)) // nothing can block the singularity
+ return SUPER_NOT_HOLDING_ON
+
if(is_tile_solid(get_step(movee, direction))) //something is blocking us so do the cool drift
return CLINGING
+
return SUPER_NOT_HOLDING_ON
var/mob/living/living = movee
@@ -108,7 +129,7 @@
///Are we on a hyperspace tile? There's some special bullshit with lattices so we just wrap this check
/datum/component/shuttle_cling/proc/is_on_hyperspace(atom/movable/clinger)
- if(istype(clinger.loc, hyperspace_type) && !(locate(/obj/structure/lattice) in clinger.loc))
+ if(istype(clinger.loc, hyperspace_type) && !HAS_TRAIT(clinger.loc, TRAIT_HYPERSPACE_STOPPED))
return TRUE
return FALSE
diff --git a/code/datums/elements/forced_gravity.dm b/code/datums/elements/forced_gravity.dm
index bedda891fb5f..5be4ffe63946 100644
--- a/code/datums/elements/forced_gravity.dm
+++ b/code/datums/elements/forced_gravity.dm
@@ -6,7 +6,7 @@
///whether we will override the turf if it forces no gravity
var/ignore_turf_gravity
-/datum/element/forced_gravity/Attach(datum/target, gravity = 1, ignore_turf_gravity = FALSE)
+/datum/element/forced_gravity/Attach(datum/target, gravity = 1, ignore_turf_gravity = FALSE, can_override = FALSE)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
@@ -14,9 +14,9 @@
src.gravity = gravity
src.ignore_turf_gravity = ignore_turf_gravity
- RegisterSignal(target, COMSIG_ATOM_HAS_GRAVITY, PROC_REF(gravity_check))
+ RegisterSignal(target, COMSIG_ATOM_HAS_GRAVITY, PROC_REF(gravity_check), override = can_override)
if(isturf(target))
- RegisterSignal(target, COMSIG_TURF_HAS_GRAVITY, PROC_REF(turf_gravity_check))
+ RegisterSignal(target, COMSIG_TURF_HAS_GRAVITY, PROC_REF(turf_gravity_check), override = can_override)
ADD_TRAIT(target, TRAIT_FORCED_GRAVITY, REF(src))
diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm
index f8a0f4a062c6..bbcb44f91754 100644
--- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm
+++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm
@@ -7,6 +7,11 @@
icon_file = 'icons/mob/simple/carp.dmi'
json_config = 'code/datums/greyscale/json_configs/carp.json'
+/datum/greyscale_config/carp_friend
+ name = "Friend Carp"
+ icon_file = 'icons/mob/simple/carp.dmi'
+ json_config = 'code/datums/greyscale/json_configs/carp_friend.json'
+
/datum/greyscale_config/carp_magic
name = "Magicarp"
icon_file = 'icons/mob/simple/carp.dmi'
diff --git a/code/datums/greyscale/json_configs/carp_friend.json b/code/datums/greyscale/json_configs/carp_friend.json
new file mode 100644
index 000000000000..a5b3ffdf7a47
--- /dev/null
+++ b/code/datums/greyscale/json_configs/carp_friend.json
@@ -0,0 +1,23 @@
+{
+ "base_friend": [
+ {
+ "type": "icon_state",
+ "icon_state": "base_friend",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ },
+ {
+ "type": "icon_state",
+ "icon_state": "base_friend_mouth",
+ "blend_mode": "overlay"
+ }
+ ],
+ "base_friend_dead": [
+ {
+ "type": "icon_state",
+ "icon_state": "base_friend_dead",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ]
+}
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 5d352ad0f4d1..ac943f525e4f 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -37,7 +37,9 @@
/datum/progressbar/New(mob/User, goal_number, atom/target, border_look = "border", border_look_accessory, bar_look = "prog_bar", old_format = FALSE, active_color = "#6699FF", finish_color = "#FFEE8C", fail_color = "#FF0033" , mutable_appearance/additional_image)
. = ..()
if (!istype(target))
- EXCEPTION("Invalid target given")
+ stack_trace("Invalid target [target] passed in")
+ qdel(src)
+ return
if(QDELETED(User) || !istype(User))
stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] user")
qdel(src)
diff --git a/code/datums/proximity_monitor/fields/gravity.dm b/code/datums/proximity_monitor/fields/gravity.dm
index e341c36c9b18..ac9b143c2083 100644
--- a/code/datums/proximity_monitor/fields/gravity.dm
+++ b/code/datums/proximity_monitor/fields/gravity.dm
@@ -12,8 +12,7 @@
. = ..()
if (isnull(modified_turfs[target]))
return
-
- target.AddElement(/datum/element/forced_gravity, gravity_value)
+ target.AddElement(/datum/element/forced_gravity, gravity_value, can_override = TRUE)
modified_turfs[target] = gravity_value
/datum/proximity_monitor/advanced/gravity/cleanup_field_turf(turf/target)
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index dd6f53c6442d..47a9f019df31 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -138,7 +138,7 @@
if (blocks_emissive)
if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
render_target = ref(src)
- em_block = new(src, render_target)
+ em_block = new(null, src)
overlays += em_block
if(managed_overlays)
if(islist(managed_overlays))
@@ -211,7 +211,7 @@
move_packet = null
if(spatial_grid_key)
- SSspatial_grid.force_remove_from_cell(src)
+ SSspatial_grid.force_remove_from_grid(src)
LAZYCLEARLIST(client_mobs_in_contents)
@@ -243,9 +243,11 @@
// This saves several hundred milliseconds of init time.
if (blocks_emissive)
if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
- if(!em_block && !QDELETED(src))
+ if(em_block)
+ SET_PLANE(em_block, EMISSIVE_PLANE, src)
+ else if(!QDELETED(src))
render_target = ref(src)
- em_block = new(src, render_target)
+ em_block = new(null, src)
return em_block
// Implied else if (blocks_emissive == EMISSIVE_BLOCK_NONE) -> return
// EMISSIVE_BLOCK_GENERIC == 0
@@ -261,7 +263,7 @@
SET_PLANE(underlay_appearance, PLANE_SPACE, generate_for)
if(!generate_for.render_target)
generate_for.render_target = ref(generate_for)
- var/atom/movable/render_step/emissive_blocker/em_block = new(null, generate_for.render_target)
+ var/atom/movable/render_step/emissive_blocker/em_block = new(null, generate_for)
underlay_appearance.overlays += em_block
// We used it because it's convienient and easy, but it's gotta go now or it'll hang refs
QDEL_NULL(em_block)
@@ -1471,6 +1473,10 @@
/// Gets or creates the relevant language holder. For mindless atoms, gets the local one. For atom with mind, gets the mind one.
/atom/movable/proc/get_language_holder(get_minds = TRUE)
+ if(QDELING(src))
+ CRASH("get_language_holder() called on a QDELing atom, \
+ this will try to re-instantiate the language holder that's about to be deleted, which is bad.")
+
if(!language_holder)
language_holder = new initial_language_holder(src)
return language_holder
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index ff5bad76dc2b..ae87b00aea69 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -879,7 +879,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
render_target = "holoray#[uid]"
uid++
// Let's GLOW BROTHER! (Doing it like this is the most robust option compared to duped overlays)
- glow = new(src, render_target)
+ glow = new(null, src)
// We need to counteract the pixel offset to ensure we don't double offset (I hate byond)
glow.pixel_x = 32
glow.pixel_y = 32
diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm
index f067f0c17bf8..269f3d6edafc 100644
--- a/code/game/objects/effects/spiderwebs.dm
+++ b/code/game/objects/effects/spiderwebs.dm
@@ -43,9 +43,9 @@
return
if(!HAS_TRAIT(user,TRAIT_WEB_WEAVER))
return
- user.balloon_alert_to_viewers("weaving...")
+ loc.balloon_alert_to_viewers("weaving...")
if(!do_after(user, 2 SECONDS))
- user.balloon_alert(user, "interrupted!")
+ loc.balloon_alert(user, "interrupted!")
return
qdel(src)
var/obj/item/stack/sheet/cloth/woven_cloth = new /obj/item/stack/sheet/cloth
@@ -68,7 +68,7 @@
if(mover.pulledby && HAS_TRAIT(mover.pulledby, TRAIT_WEB_SURFER))
return TRUE
if(prob(50))
- balloon_alert(mover, "stuck in web!")
+ loc.balloon_alert(mover, "stuck in web!")
return FALSE
else if(isprojectile(mover))
return prob(30)
@@ -100,7 +100,7 @@
if(mover.pulledby == allowed_mob)
return TRUE
if(prob(50))
- balloon_alert(mover, "stuck in web!")
+ loc.balloon_alert(mover, "stuck in web!")
return FALSE
else if(isprojectile(mover))
return prob(30)
@@ -180,7 +180,7 @@
return
if(!isnull(mover.pulledby) && isspider(mover.pulledby))
return TRUE
- balloon_alert(mover, "stuck in web!")
+ loc.balloon_alert(mover, "stuck in web!")
return FALSE
/obj/structure/spider/spikes
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index 7dee7378e6f1..feeedc5a823b 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -292,3 +292,11 @@
*/
/obj/item/transfer_valve/proc/ready()
return tank_one && tank_two
+
+/obj/item/transfer_valve/fake/Initialize(mapload)
+ . = ..()
+
+ tank_one = new /obj/item/tank/internals/plasma (src)
+ tank_two = new /obj/item/tank/internals/oxygen (src)
+
+ update_appearance()
diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm
index 6e7bef5883e3..d06b0067de24 100644
--- a/code/game/objects/items/melee/baton.dm
+++ b/code/game/objects/items/melee/baton.dm
@@ -787,9 +787,10 @@
if(!.)
return
var/obj/item/stuff_in_hand = target.get_active_held_item()
- if(stuff_in_hand && target.temporarilyRemoveItemFromInventory(stuff_in_hand))
- if(user.put_in_inactive_hand(stuff_in_hand))
- stuff_in_hand.loc.visible_message(span_warning("[stuff_in_hand] suddenly appears in [user]'s hand!"))
- else
- stuff_in_hand.forceMove(user.drop_location())
- stuff_in_hand.loc.visible_message(span_warning("[stuff_in_hand] suddenly appears!"))
+ if(!user || !stuff_in_hand || !target.temporarilyRemoveItemFromInventory(stuff_in_hand))
+ return
+ if(user.put_in_inactive_hand(stuff_in_hand))
+ stuff_in_hand.loc.visible_message(span_warning("[stuff_in_hand] suddenly appears in [user]'s hand!"))
+ else
+ stuff_in_hand.forceMove(user.drop_location())
+ stuff_in_hand.loc.visible_message(span_warning("[stuff_in_hand] suddenly appears!"))
diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm
index 83207ff7a070..7e8ba630cf47 100644
--- a/code/game/objects/items/rcd/RCD.dm
+++ b/code/game/objects/items/rcd/RCD.dm
@@ -434,6 +434,7 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window())
airlock_electronics.name = "Access Control"
airlock_electronics.holder = src
GLOB.rcd_list += src
+ AddElement(/datum/element/openspace_item_click_handler)
/obj/item/construction/rcd/Destroy()
QDEL_NULL(airlock_electronics)
@@ -611,6 +612,11 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window())
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+/obj/item/construction/rcd/handle_openspace_click(turf/target, mob/user, proximity_flag, click_parameters)
+ if((!proximity_flag && !ranged) || (ranged && !range_check(target, user)))
+ return
+ afterattack(target, user, TRUE, click_parameters)
+
/obj/item/construction/rcd/proc/detonate_pulse()
audible_message("[src] begins to vibrate and \
buzz loudly!","[src] begins \
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index f029858f4ef3..e47cbd0ebdda 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -33,6 +33,7 @@
. = ..()
pixel_x = rand(-3, 3)
pixel_y = rand(-3, 3) //randomize a little
+ AddElement(/datum/element/openspace_item_click_handler)
if(tile_reskin_types)
tile_reskin_types = tile_reskin_list(tile_reskin_types)
if(tile_rotate_dirs)
@@ -100,6 +101,10 @@
playsound(target_plating, 'sound/weapons/genhit.ogg', 50, TRUE)
return target_plating
+/obj/item/stack/tile/handle_openspace_click(turf/target, mob/user, proximity_flag, click_parameters)
+ if(proximity_flag)
+ target.attackby(src, user, click_parameters)
+
//Grass
/obj/item/stack/tile/grass
name = "grass tile"
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index 50f31d76346f..ff71f06497e9 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -16,7 +16,7 @@
canSmoothWith = SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_OPEN_FLOOR
var/number_of_mats = 1
var/build_material = /obj/item/stack/rods
- var/list/give_turf_traits = list(TRAIT_CHASM_STOPPED)
+ var/list/give_turf_traits = list(TRAIT_CHASM_STOPPED, TRAIT_HYPERSPACE_STOPPED)
/obj/structure/lattice/Initialize(mapload)
. = ..()
@@ -24,7 +24,6 @@
give_turf_traits = string_list(give_turf_traits)
AddElement(/datum/element/give_turf_traits, give_turf_traits)
-
/datum/armor/structure_lattice
melee = 50
fire = 80
@@ -100,7 +99,7 @@
smoothing_groups = SMOOTH_GROUP_CATWALK + SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_OPEN_FLOOR
canSmoothWith = SMOOTH_GROUP_CATWALK
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP
- give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN, TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED)
+ give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN, TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED, TRAIT_HYPERSPACE_STOPPED)
/obj/structure/lattice/catwalk/Initialize(mapload)
. = ..()
@@ -155,7 +154,7 @@
canSmoothWith = SMOOTH_GROUP_LATTICE
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP
resistance_flags = FIRE_PROOF | LAVA_PROOF
- give_turf_traits = list(TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED)
+ give_turf_traits = list(TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED, TRAIT_HYPERSPACE_STOPPED)
/obj/structure/lattice/lava/deconstruction_hints(mob/user)
return span_notice("The rods look like they could be cut, but the heat treatment will shatter off. There's space for a tile.")
diff --git a/code/game/turfs/open/space/transit.dm b/code/game/turfs/open/space/transit.dm
index 6c58d4872947..8876080830bf 100644
--- a/code/game/turfs/open/space/transit.dm
+++ b/code/game/turfs/open/space/transit.dm
@@ -11,10 +11,13 @@
. = ..()
update_appearance()
RegisterSignal(src, COMSIG_TURF_RESERVATION_RELEASED, PROC_REF(launch_contents))
+ RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(initialize_drifting))
+ RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialize_drifting_but_from_initialize))
/turf/open/space/transit/Destroy()
//Signals are NOT removed from turfs upon replacement, and we get replaced ALOT, so unregister our signal
- UnregisterSignal(src, COMSIG_TURF_RESERVATION_RELEASED)
+ UnregisterSignal(src, list(COMSIG_TURF_RESERVATION_RELEASED, COMSIG_ATOM_ENTERED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON))
+
return ..()
/turf/open/space/transit/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
@@ -30,11 +33,17 @@
icon_state = "speedspace_ns_[get_transit_state(src)]"
return ..()
-/turf/open/space/transit/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
- . = ..()
+/turf/open/space/transit/proc/initialize_drifting(atom/entered, atom/movable/enterer)
+ SIGNAL_HANDLER
- if(!HAS_TRAIT(arrived, TRAIT_HYPERSPACED) && !HAS_TRAIT(arrived, TRAIT_FREE_HYPERSPACE_MOVEMENT))
- arrived.AddComponent(/datum/component/shuttle_cling, turn(dir, 180), old_loc)
+ if(enterer && !HAS_TRAIT(enterer, TRAIT_HYPERSPACED) && !HAS_TRAIT(src, TRAIT_HYPERSPACE_STOPPED))
+ enterer.AddComponent(/datum/component/shuttle_cling, turn(dir, 180))
+
+/turf/open/space/transit/proc/initialize_drifting_but_from_initialize(atom/movable/location, atom/movable/enterer, mapload)
+ SIGNAL_HANDLER
+
+ if(!mapload && !enterer.anchored)
+ INVOKE_ASYNC(src, PROC_REF(initialize_drifting), src, enterer)
/turf/open/space/transit/Exited(atom/movable/gone, direction)
. = ..()
@@ -52,6 +61,10 @@
///Dump a movable in a random valid spacetile
/proc/dump_in_space(atom/movable/dumpee)
+ if(HAS_TRAIT(dumpee, TRAIT_DEL_ON_SPACE_DUMP))
+ qdel(dumpee)
+ return
+
var/max = world.maxx-TRANSITIONEDGE
var/min = 1+TRANSITIONEDGE
diff --git a/code/game/world.dm b/code/game/world.dm
index 43f9aad06e9c..6a071e02ef82 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -330,6 +330,7 @@ GLOBAL_VAR(restart_counter)
shutdown_logging() // See comment below.
auxcleanup()
TgsEndProcess()
+ return ..()
log_world("World rebooted at [time_stamp()]")
diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm
index 6df75daf2425..fa1b832ecc31 100644
--- a/code/modules/admin/IsBanned.dm
+++ b/code/modules/admin/IsBanned.dm
@@ -43,13 +43,15 @@
var/client_is_in_db = query_client_in_db.NextRow()
if(!client_is_in_db)
-
var/reject_message = "Failed Login: [ckey] [address]-[computer_id] - New Account attempting to connect during panic bunker, but was rejected due to no prior connections to game servers (no database entry)"
log_access(reject_message)
if (message)
message_admins(span_adminnotice("[reject_message]"))
+ qdel(query_client_in_db)
return list("reason"="panicbunker", "desc" = "Sorry but the server is currently not accepting connections from never before seen players")
+ qdel(query_client_in_db)
+
//Whitelist
if(!real_bans_only && !C && CONFIG_GET(flag/usewhitelist))
if(!check_whitelist(ckey))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index a6c087f2b074..5f61e2bc7035 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -48,6 +48,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/admins/proc/open_artifactpanel,
/datum/verbs/menu/Admin/verb/playerpanel, /* It isn't /datum/admin but it fits no less */
/datum/admins/proc/kick_player_by_ckey, //MONKESTATION ADDITION - kick a player by their ckey
+ /datum/admins/proc/change_shuttle_events, //allows us to change the shuttle events
// Client procs
/client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/
/client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcom*/
diff --git a/code/modules/admin/verbs/change_shuttle_events.dm b/code/modules/admin/verbs/change_shuttle_events.dm
new file mode 100644
index 000000000000..a24aa456f237
--- /dev/null
+++ b/code/modules/admin/verbs/change_shuttle_events.dm
@@ -0,0 +1,42 @@
+///Manipulate the events that are gonna run/are running on the escape shuttle
+/datum/admins/proc/change_shuttle_events()
+ set category = "Admin.Events"
+ set name = "Change Shuttle Events"
+ set desc = "Allows you to change the events on a shuttle."
+
+ if (!istype(src, /datum/admins))
+ src = usr.client.holder
+ if (!istype(src, /datum/admins))
+ to_chat(usr, "Error: you are not an admin!", confidential = TRUE)
+ return
+
+ //At least for now, just letting admins modify the emergency shuttle is fine
+ var/obj/docking_port/mobile/port = SSshuttle.emergency
+
+ if(!port)
+ to_chat(usr, span_admin("Uh oh, couldn't find the escape shuttle!"))
+
+ var/list/options = list("Clear"="Clear")
+
+ //Grab the active events so we know which ones we can Add or Remove
+ var/list/active = list()
+ for(var/datum/shuttle_event/event in port.event_list)
+ active[event.type] = event
+
+ for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
+ options[((event in active) ? "(Remove)" : "(Add)") + initial(event.name)] = event
+
+ //Throw up an ugly menu with the shuttle events and the options to add or remove them, or clear them all
+ var/result = input(usr, "Choose an event to add/remove", "Shuttle Events") as null|anything in sort_list(options)
+
+ if(result == "Clear")
+ port.event_list.Cut()
+ log_admin("[key_name_admin(usr)] has cleared the shuttle events on: [port]")
+ else if(options[result])
+ var/typepath = options[result]
+ if(typepath in active)
+ port.event_list.Remove(active[options[result]])
+ log_admin("[key_name_admin(usr)] has removed '[active[result]]' from [port].")
+ else
+ port.event_list.Add(new typepath (port))
+ log_admin("[key_name_admin(usr)] has added '[typepath]' to [port].")
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index 3eca4f7eda6c..ee973a03e061 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -215,7 +215,8 @@
/datum/heretic_knowledge/spell/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
var/datum/action/cooldown/spell/created_spell = created_spell_ref?.resolve()
- created_spell?.Remove(user)
+ if(created_spell?.owner == user)
+ created_spell.Remove(user)
/**
* A knowledge subtype for knowledge that can only
diff --git a/code/modules/asset_cache/assets/plumbing.dm b/code/modules/asset_cache/assets/plumbing.dm
index a0d256243494..275e1861abe8 100644
--- a/code/modules/asset_cache/assets/plumbing.dm
+++ b/code/modules/asset_cache/assets/plumbing.dm
@@ -36,6 +36,15 @@
"synthesizer_booze",
"tap_output",
),
+ /* monkestation start: xenobiology rework */
+ 'monkestation/code/modules/slimecore/icons/machinery.dmi' = list(
+ "cross_compressor",
+ "ooze_sucker",
+ ),
+ 'monkestation/code/modules/slimecore/icons/slime_grinder.dmi' = list(
+ "slime_grinder_backdrop",
+ ),
+ /* monkestation end */
)
for(var/icon_file as anything in essentials)
diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm
index 99aa175529f2..22223cb0e784 100644
--- a/code/modules/atmospherics/machinery/other/meter.dm
+++ b/code/modules/atmospherics/machinery/other/meter.dm
@@ -62,8 +62,8 @@
return target?.return_air() || ..()
/obj/machinery/meter/process_atmos()
- var/datum/gas_mixture/pipe_air = target.return_air()
- if(!pipe_air)
+ var/datum/gas_mixture/pipe_air = target?.return_air()
+ if(isnull(pipe_air))
icon_state = "meter0"
return FALSE
diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm
index 177fc85ba37d..285d0d49e105 100644
--- a/code/modules/awaymissions/cordon.dm
+++ b/code/modules/awaymissions/cordon.dm
@@ -46,6 +46,12 @@
/turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover)
return FALSE
+/turf/cordon/Bumped(atom/movable/bumped_atom)
+ . = ..()
+
+ if(HAS_TRAIT(bumped_atom, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT)) //we could feasibly reach the border, so just dont
+ dump_in_space(bumped_atom)
+
/// Area used in conjuction with the cordon turf to create a fully functioning world border.
/area/misc/cordon
name = "CORDON"
diff --git a/code/modules/logging/log_holder.dm b/code/modules/logging/log_holder.dm
index ecfdf5d7851c..aaf6af47cbfe 100644
--- a/code/modules/logging/log_holder.dm
+++ b/code/modules/logging/log_holder.dm
@@ -284,7 +284,13 @@ GENERAL_PROTECT_DATUM(/datum/log_holder)
/datum/log_holder/proc/human_readable_timestamp(precision = 3)
var/start = time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")
// now we grab the millis from the rustg timestamp
- var/list/timestamp = splittext(unix_timestamp_string(), ".")
+ var/rustg_stamp = unix_timestamp_string()
+ var/list/timestamp = splittext(rustg_stamp, ".")
+#ifdef UNIT_TESTS
+ if(length(timestamp) != 2)
+ stack_trace("rustg returned illegally formatted string '[rustg_stamp]'")
+ return start
+#endif
var/millis = timestamp[2]
if(length(millis) > precision)
millis = copytext(millis, 1, precision + 1)
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index ca91c58847fb..b03177acfe13 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -347,6 +347,7 @@
// And we are done lads, call it off
loading = FALSE
SSatoms.map_loader_stop(REF(src))
+ loading = FALSE
if(new_z)
for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ])
diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm
index 8c246699b4eb..b4e933d87d3c 100644
--- a/code/modules/mapping/space_management/space_reservation.dm
+++ b/code/modules/mapping/space_management/space_reservation.dm
@@ -115,7 +115,7 @@
/datum/turf_reservation/transit/make_repel(turf/pre_cordon_turf)
..()
- RegisterSignal(pre_cordon_turf, COMSIG_ATOM_ENTERED, PROC_REF(space_dump))
+ RegisterSignal(pre_cordon_turf, COMSIG_ATOM_ENTERED, PROC_REF(space_dump_soft))
/datum/turf_reservation/transit/stop_repel(turf/pre_cordon_turf)
..()
@@ -127,6 +127,11 @@
dump_in_space(enterer)
+/datum/turf_reservation/transit/proc/space_dump_soft(atom/source, atom/movable/enterer)
+ SIGNAL_HANDLER
+
+ if(!HAS_TRAIT(enterer, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT))
+ space_dump(source, enterer)
/// Internal proc which handles reserving the area for the reservation.
/datum/turf_reservation/proc/_reserve_area(width, height, zlevel)
diff --git a/code/modules/mob/living/basic/guardian/guardian_types/protector.dm b/code/modules/mob/living/basic/guardian/guardian_types/protector.dm
index a0aa34ad17f1..c923cad60c4a 100644
--- a/code/modules/mob/living/basic/guardian/guardian_types/protector.dm
+++ b/code/modules/mob/living/basic/guardian/guardian_types/protector.dm
@@ -16,7 +16,6 @@
/mob/living/basic/guardian/protector/Initialize(mapload, datum/guardian_fluff/theme)
. = ..()
shield = new(src)
- shield.owner_has_control = FALSE // Hide it from the user, it's integrated with guardian UI
shield.Grant(src)
/mob/living/basic/guardian/protector/Destroy()
@@ -49,6 +48,7 @@
background_icon_state = "base"
cooldown_time = 1 SECONDS
click_to_activate = FALSE
+ owner_has_control = FALSE // Hide it from the user, it's integrated with guardian UI
/datum/action/cooldown/mob_cooldown/protector_shield/Activate(mob/living/target)
if (!isliving(target))
diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
index b42000f27701..24fb0b582642 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
@@ -100,9 +100,10 @@
)
/mob/living/basic/carp/Initialize(mapload, mob/tamer)
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) //Need to set before init cause if we init in hyperspace we get dragged before the trait can be added
. = ..()
apply_colour()
- add_traits(list(TRAIT_HEALS_FROM_CARP_RIFTS, TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT)
+ add_traits(list(TRAIT_HEALS_FROM_CARP_RIFTS, TRAIT_SPACEWALK), INNATE_TRAIT)
if (cell_line)
AddElement(/datum/element/swabable, cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
@@ -165,6 +166,16 @@
ai_controller.set_blackboard_key(BB_CARP_MIGRATION_PATH, actual_points)
+/mob/living/basic/carp/death(gibbed)
+ . = ..()
+
+ REMOVE_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
+
+/mob/living/basic/carp/revive(full_heal_flags, excess_healing, force_grab_ghost)
+ . = ..()
+
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
+
/**
* Holographic carp from the holodeck
*/
@@ -259,3 +270,25 @@
new_overlays += disk_overlay
#undef RARE_CAYENNE_CHANCE
+
+///Wild carp that just vibe ya know
+/mob/living/basic/carp/passive
+ name = "passive carp"
+ desc = "A timid, sucker-bearing creature that resembles a fish. "
+
+ icon_state = "base_friend"
+ icon_living = "base_friend"
+ icon_dead = "base_friend_dead"
+ greyscale_config = /datum/greyscale_config/carp_friend
+
+ attack_verb_continuous = "suckers"
+ attack_verb_simple = "suck"
+
+ melee_damage_lower = 4
+ melee_damage_upper = 4
+ ai_controller = /datum/ai_controller/basic_controller/carp/passive
+
+/mob/living/basic/carp/passive/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/ai_retaliate)
+ AddElement(/datum/element/pet_bonus, "bloops happily!")
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index ad10167adec1..ae8a2a99b2a5 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -555,7 +555,7 @@
set_invis_see(min(glasses.invis_view, see_invisible))
if(!isnull(glasses.lighting_cutoff))
lighting_cutoff = max(lighting_cutoff, glasses.lighting_cutoff)
- if(!isnull(glasses.color_cutoffs))
+ if(length(glasses.color_cutoffs))
lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, glasses.color_cutoffs)
diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm
index f2edcf670561..d75ed5450ae1 100644
--- a/code/modules/mob/living/carbon/death.dm
+++ b/code/modules/mob/living/carbon/death.dm
@@ -11,7 +11,7 @@
. = ..()
- if(!gibbed)
+ if(!gibbed && !QDELING(src)) //double check they didn't start getting deleted in ..()
attach_rot()
for(var/T in get_traumas())
@@ -35,7 +35,7 @@
for(var/mob/M in src)
M.forceMove(Tsec)
visible_message(span_danger("[M] bursts out of [src]!"))
- . = ..()
+ return ..()
/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts)
var/atom/Tsec = drop_location()
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index fa67853e091f..4adb0b2160a9 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -422,9 +422,12 @@ GLOBAL_LIST_EMPTY(features_by_species)
qdel(existing_organ)
continue
- if(!isnull(old_species) && !isnull(existing_organ))
- if(existing_organ.type != old_species.get_mutant_organ_type_for_slot(slot))
- continue // we don't want to remove organs that are not the default for this species
+ // we don't want to remove organs that are not the default for this species
+ if(!isnull(existing_organ))
+ if(!isnull(old_species) && existing_organ.type != old_species.get_mutant_organ_type_for_slot(slot))
+ continue
+ else if(!replace_current && existing_organ.type != get_mutant_organ_type_for_slot(slot))
+ continue
// at this point we already know new_organ is not null
if(existing_organ?.type == new_organ)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index c4a7aa290892..c67ff29b4e13 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -281,7 +281,7 @@
var/datum/crime/citation/new_citation = new(name = citation_name, author = allowed_access, fine = fine)
target_record.citations += new_citation
- new_citation.alert_owner(target_record.name, "You have been fined [fine] credits for '[citation_name]'. Fines may be paid at security.")
+ new_citation.alert_owner(usr, src, target_record.name, "You have been fined [fine] credits for '[citation_name]'. Fines may be paid at security.")
investigate_log("New Citation: [citation_name] Fine: [fine] | Added to [target_record.name] by [key_name(human_user)]", INVESTIGATE_RECORDS)
SSblackbox.ReportCitation(REF(new_citation), human_user.ckey, human_user.real_name, target_record.name, citation_name, fine)
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index eeb9bceb5d5c..0da524effa61 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -12,7 +12,8 @@
// If we're moving to/from nullspace, refresh
// Easier then adding nullchecks to all this shit, and technically right since a null turf means nograv
if(isnull(old_turf) || isnull(new_turf))
- refresh_gravity()
+ if(!QDELING(src))
+ refresh_gravity()
return
// If the turf gravity has changed, then it's possible that our state has changed, so update
if(HAS_TRAIT(old_turf, TRAIT_FORCED_GRAVITY) != HAS_TRAIT(new_turf, TRAIT_FORCED_GRAVITY) || new_turf.force_no_gravity != old_turf.force_no_gravity)
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 603dc1ffe091..d61573491c51 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -48,6 +48,7 @@
/// What the game tells ghosts when you make one
var/ghost_notification_message = "IT'S LOOSE"
+ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
flags_1 = SUPERMATTER_IGNORES_1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF | SHUTTLE_CRUSH_PROOF
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
@@ -470,3 +471,7 @@
/obj/singularity/deadchat_controlled/Initialize(mapload, starting_energy)
. = ..()
deadchat_plays(mode = DEMOCRACY_MODE)
+
+/// Special singularity that spawns for shuttle events only
+/obj/singularity/shuttle_event
+ anchored = FALSE
diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm
index 8527041e8600..6b715fdb74f5 100644
--- a/code/modules/projectiles/projectile/energy/_energy.dm
+++ b/code/modules/projectiles/projectile/energy/_energy.dm
@@ -6,3 +6,9 @@
armor_flag = ENERGY
reflectable = REFLECT_NORMAL
impact_effect_type = /obj/effect/temp_visual/impact_effect/energy
+
+/obj/projectile/energy/Initialize(mapload)
+ . = ..()
+
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT)
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 3ef25b22e219..97e1a1b0791e 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -313,6 +313,11 @@
var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
var/hijack_status = NOT_BEGUN
+/obj/docking_port/mobile/emergency/Initialize(mapload)
+ . = ..()
+
+ setup_shuttle_events()
+
/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S)
return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process
@@ -480,6 +485,7 @@
color_override = "orange",
)
ShuttleDBStuff()
+ addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS)
if(SHUTTLE_DOCKED)
@@ -536,6 +542,10 @@
color_override = "orange",
)
INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts))
+ //Tell the events we're starting, so they can time their spawns or do some other stuff
+ for(var/datum/shuttle_event/event as anything in event_list)
+ event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff)
+
SSmapping.mapvote() //If no map vote has been run yet, start one.
if(SHUTTLE_STRANDED, SHUTTLE_DISABLED)
@@ -563,6 +573,8 @@
if(istype(M, /obj/docking_port/mobile/pod))
M.parallax_slowdown()
+ process_events()
+
if(time_left <= 0)
//move each escape pod to its corresponding escape dock
for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports)
@@ -596,6 +608,16 @@
color_override = "orange",
)
+///Generate a list of events to run during the departure
+/obj/docking_port/mobile/emergency/proc/setup_shuttle_events()
+ var/list/names = list()
+ for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
+ if(prob(initial(event.event_probability)))
+ event_list.Add(new event(src))
+ names += initial(event.name)
+ if(LAZYLEN(names))
+ log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].")
+
/obj/docking_port/mobile/monastery
name = "monastery pod"
shuttle_id = "mining_common" //set so mining can call it down
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 769de2efb336..fdf008c26344 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -488,6 +488,8 @@
///if this shuttle can move docking ports other than the one it is docked at
var/can_move_docking_ports = FALSE
var/list/hidden_turfs = list()
+ ///List of shuttle events that can run or are running
+ var/list/datum/shuttle_event/event_list = list()
#define WORLDMAXX_CUTOFF (world.maxx + 1)
#define WORLDMAXY_CUTOFF (world.maxx + 1)
@@ -843,6 +845,7 @@
//used by shuttle subsystem to check timers
/obj/docking_port/mobile/proc/check()
check_effects()
+ //process_events() if you were to add events to non-escape shuttles, uncomment this
if(mode == SHUTTLE_IGNITING)
check_transit_zone()
@@ -1148,13 +1151,17 @@
return FALSE
return ..()
-
//Called when emergency shuttle leaves the station
/obj/docking_port/mobile/proc/on_emergency_launch()
if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
launch_status = ENDGAME_LAUNCHED
enterTransit()
+///Let people know shits about to go down
+/obj/docking_port/mobile/proc/announce_shuttle_events()
+ for(var/datum/shuttle_event/event as anything in event_list)
+ notify_ghosts("The [name] has selected: [event.name]")
+
/obj/docking_port/mobile/emergency/on_emergency_launch()
return
@@ -1173,6 +1180,15 @@
/obj/docking_port/mobile/emergency/on_emergency_dock()
return
+///Process all the shuttle events for every shuttle tick we get
+/obj/docking_port/mobile/proc/process_events()
+ var/list/removees
+ for(var/datum/shuttle_event/event as anything in event_list)
+ if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up
+ LAZYADD(removees, event)
+ for(var/item in removees)
+ event_list.Remove(item)
+
#ifdef TESTING
#undef DOCKING_PORT_HIGHLIGHT
#endif
diff --git a/code/modules/shuttle/shuttle_events/_shuttle_events.dm b/code/modules/shuttle/shuttle_events/_shuttle_events.dm
new file mode 100644
index 000000000000..6736428c02aa
--- /dev/null
+++ b/code/modules/shuttle/shuttle_events/_shuttle_events.dm
@@ -0,0 +1,140 @@
+///An event that can run during shuttle flight, and will run for the duration of it (configurable)
+/datum/shuttle_event
+ ///How we're announced to ghosts and stuff
+ var/name = "The concept of a shuttle event"
+ ///probability of this event to run from 0 to 100
+ var/event_probability = 0
+ ///Track if we're allowed to run, gets turned to TRUE when the activation timer hits
+ VAR_PRIVATE/active = FALSE
+ ///fraction of the escape timer at which we activate, 0 means we start running immediately
+ ///(so if activation timer is 0.2 and shuttle takes 3 minutes to get going, it will activate in 36 seconds)
+ ///We only care about the timer from the moment of launch, any speed changed afterwards are not worth dealing with
+ var/activation_fraction = 0
+ ///when do we activate?
+ VAR_PRIVATE/activate_at
+ ///Our reference to the docking port and thus the shuttle
+ var/obj/docking_port/mobile/port
+
+/datum/shuttle_event/New(obj/docking_port/mobile/port)
+ . = ..()
+
+ src.port = port
+
+/datum/shuttle_event/proc/start_up_event(evacuation_duration)
+ activate_at = world.time + evacuation_duration * activation_fraction
+
+///We got activated
+/datum/shuttle_event/proc/activate()
+ return
+
+///Process with the SShutle subsystem. Return SHUTTLE_EVENT_CLEAR to self-destruct
+/datum/shuttle_event/proc/event_process()
+ . = TRUE
+
+ if(!active)
+ if(world.time < activate_at)
+ return FALSE
+ active = TRUE
+ . = activate()
+
+///Spawns objects, mobs, whatever with all the necessary code to make it hit and/or miss the shuttle
+/datum/shuttle_event/simple_spawner
+ ///behaviour of spawning objects, if we spawn
+ var/spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE | SHUTTLE_EVENT_HIT_SHUTTLE
+ ///List of valid spawning turfs, generated from generate_spawning_turfs(), that will HIT the shuttle
+ var/list/turf/spawning_turfs_hit
+ ///List of valid spawning turfs, generated from generate_spawning_turfs(), that will MISS the shuttle
+ var/list/turf/spawning_turfs_miss
+ ///Chance, from 0 to 100, for something to spawn
+ var/spawn_probability_per_process = 0
+ ///Increment if you want more stuff to spawn at once
+ var/spawns_per_spawn = 1
+ ///weighted list with spawnable movables
+ var/list/spawning_list = list()
+ ///If set to TRUE, every time an object is spawned their weight is decreased untill they are removed
+ var/remove_from_list_when_spawned = FALSE
+ ///If set to true, we'll delete ourselves if we cant spawn anything anymore. Useful in conjunction with remove_from_list_when_spawned
+ var/self_destruct_when_empty = FALSE
+
+/datum/shuttle_event/simple_spawner/start_up_event(evacuation_duration)
+ ..()
+
+ generate_spawning_turfs(port.return_coords(), spawning_flags, port.preferred_direction)
+
+///Bounding coords are list(x0, y0, x1, y1) where x0 and y0 are top-left
+/datum/shuttle_event/simple_spawner/proc/generate_spawning_turfs(list/bounding_coords, spawning_behaviour, direction)
+ spawning_turfs_hit = list() //turfs that will drift its contents to miss the shuttle
+ spawning_turfs_miss = list() //turfs that will drift its contents to hit the shuttle
+ var/list/step_dir //vector, either -1, 0 or 1. once we get a corner (lets say top right), in which direction do we 'walk' to get the full side? (this case to the right, so (1, 0)
+ var/list/target_corner //Top left or bottom right corner
+ var/list/spawn_offset //bounding_coords is ONLY the shuttle, not the space around it, so offset spawn_tiles or stuff spawns on the walls of the shuttle
+
+ switch(direction)
+ if(NORTH) //we're travelling north (so people get pushed south)
+ step_dir = list(1, 0)
+ target_corner = list(bounding_coords[1], bounding_coords[2])
+ spawn_offset = list(0, SHUTTLE_TRANSIT_BORDER)
+ if(SOUTH)
+ step_dir = list(-1, 0)
+ target_corner = list(bounding_coords[3], bounding_coords[4])
+ spawn_offset = list(0, -SHUTTLE_TRANSIT_BORDER)
+ if(EAST)
+ step_dir = list(0, 1)
+ target_corner = list(bounding_coords[3], bounding_coords[4])
+ spawn_offset = list(SHUTTLE_TRANSIT_BORDER, 0)
+ if(WEST)
+ step_dir = list(0, -1)
+ target_corner = list(bounding_coords[1], bounding_coords[2])
+ spawn_offset = list(-SHUTTLE_TRANSIT_BORDER, 0)
+
+ if(spawning_behaviour & SHUTTLE_EVENT_HIT_SHUTTLE)
+ ///so we get either the horizontal width or vertical width, which would both equal the amount of spawn tiles
+ var/tile_amount = abs((direction == NORTH || SOUTH) ? bounding_coords[1] - bounding_coords[3] : bounding_coords[2] - bounding_coords[4])
+ for(var/i in 0 to tile_amount)
+ var/list/target_coords = list(target_corner[1] + step_dir[1] * i + spawn_offset[1], target_corner[2] + step_dir[2] * i + spawn_offset[2])
+ spawning_turfs_hit.Add(locate(target_coords[1], target_coords[2], port.z))
+ if(spawning_behaviour & SHUTTLE_EVENT_MISS_SHUTTLE)
+ for(var/i in 1 to SHUTTLE_TRANSIT_BORDER)
+ //Get the corner tile, and move away from the shuttle and towards the cordon
+ spawning_turfs_miss.Add(locate(target_corner[1] - step_dir[1] * i + spawn_offset[1], target_corner[2] - step_dir[2] * i + spawn_offset[2], port.z))
+ var/corner_delta = list(bounding_coords[3] - bounding_coords[1], bounding_coords[2] - bounding_coords[4])
+ //Get the corner tile, but jump over the shuttle and then continue unto the cordon
+ spawning_turfs_miss.Add(locate(target_corner[1] + corner_delta[1] * step_dir[1] + step_dir[1] * i + spawn_offset[1], target_corner[2] + corner_delta[2] * step_dir[2] + step_dir[2] * i + spawn_offset[2], port.z))
+
+
+/datum/shuttle_event/simple_spawner/event_process()
+ . = ..()
+
+ if(!.)
+ return
+
+ if(!LAZYLEN(spawning_list))
+ if(self_destruct_when_empty)
+ return SHUTTLE_EVENT_CLEAR
+ return
+
+ if(prob(spawn_probability_per_process))
+ for(var/i in 1 to spawns_per_spawn)
+ spawn_movable(get_type_to_spawn())
+
+///Pick a random turf from the valid turfs we got. Overwrite if you need some custom picking
+/datum/shuttle_event/simple_spawner/proc/get_spawn_turf()
+ RETURN_TYPE(/turf)
+ return pick(spawning_turfs_hit + spawning_turfs_miss)
+
+///Spawn stuff! if you're not using this, don't use the simple_spawner subtype
+/datum/shuttle_event/simple_spawner/proc/spawn_movable(spawn_type)
+ post_spawn(new spawn_type (get_spawn_turf()))
+
+///Not technically a getter if remove_from_list_when_spawned=TRUE. Otherwise, this returns the type we're going to spawn and throw at the shuttle
+/datum/shuttle_event/simple_spawner/proc/get_type_to_spawn()
+ . = pick_weight(spawning_list)
+ if(remove_from_list_when_spawned) //if we have this enabled, we decrease the pickweight by 1 till it runs out
+ spawning_list[.] -= 1
+ if(spawning_list[.] < 1)
+ spawning_list.Remove(.)
+
+///Do any post-spawn edits you need to do
+/datum/shuttle_event/simple_spawner/proc/post_spawn(atom/movable/spawnee)
+ ADD_TRAIT(spawnee, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT, REF(src)) //Lets us spawn and move further away from the shuttle without being teleported into space
+ ADD_TRAIT(spawnee, TRAIT_DEL_ON_SPACE_DUMP, REF(src)) //if we hit the cordon, we get deleted. If the shuttle can make you, it can qdel you
diff --git a/code/modules/shuttle/shuttle_events/carp.dm b/code/modules/shuttle/shuttle_events/carp.dm
new file mode 100644
index 000000000000..18529f1c0288
--- /dev/null
+++ b/code/modules/shuttle/shuttle_events/carp.dm
@@ -0,0 +1,62 @@
+///CARPTIDE! CARPTIDE! CARPTIDE! A swarm of carp will pass by and through the shuttle, including consequences of carp going through the shuttle
+/datum/shuttle_event/simple_spawner/carp
+ name = "Carp Nest! (Very Dangerous!)"
+ event_probability = 0.4
+ activation_fraction = 0.2
+
+ spawning_list = list(/mob/living/basic/carp = 12, /mob/living/basic/carp/mega = 3)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE
+ spawn_probability_per_process = 20
+
+ remove_from_list_when_spawned = TRUE
+ self_destruct_when_empty = TRUE
+
+/datum/shuttle_event/simple_spawner/carp/post_spawn(mob/living/basic/carp/carpee)
+ . = ..()
+ //Give the carp the goal to migrate in a straight line so they dont just idle in hyperspace
+ carpee.migrate_to(list(WEAKREF(get_edge_target_turf(carpee.loc, angle2dir(dir2angle(port.preferred_direction) - 180)))))
+
+///CARPTIDE! CARPTIDE! CARPTIDE! Magical carp will attack the shuttle!
+/datum/shuttle_event/simple_spawner/carp/magic
+ name = "Magical Carp Nest! (Very Dangerous!)"
+ event_probability = 0
+ activation_fraction = 0.2
+
+ spawning_list = list(/mob/living/basic/carp/magic = 12, /mob/living/basic/carp/magic/chaos = 1)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE
+ spawn_probability_per_process = 20
+
+ remove_from_list_when_spawned = TRUE
+ self_destruct_when_empty = TRUE
+
+///Spawn a bunch of friendly carp to view from inside the shuttle! May occassionally pass through and nibble some windows, but are otherwise pretty harmless
+/datum/shuttle_event/simple_spawner/carp/friendly
+ name = "Passive Carp Nest! (Mostly Harmless!)"
+ event_probability = 3
+ activation_fraction = 0.1
+
+ spawning_list = list(/mob/living/basic/carp/passive = 1)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE
+ spawns_per_spawn = 2
+ spawn_probability_per_process = 100
+
+ remove_from_list_when_spawned = FALSE
+
+ ///Chance we hit the shuttle, instead of flying past it (most carp will go through anyway, and we dont want this to be too annoying to take away from the majesty)
+ var/hit_the_shuttle_chance = 1
+
+/datum/shuttle_event/simple_spawner/carp/friendly/get_spawn_turf()
+ return prob(hit_the_shuttle_chance) ? pick(spawning_turfs_hit) : pick(spawning_turfs_miss)
+
+///Same as /friendly, but we only go through the shuttle, MUHAHAHAHAHAHA!! They dont actually harm anyone, but itll be a clusterfuck of confusion
+/datum/shuttle_event/simple_spawner/carp/friendly_but_no_personal_space
+ name = "Comfortable Carp Nest going through the shuttle! (Extremely annoying and confusing!)"
+ event_probability = 0
+ activation_fraction = 0.5
+
+ spawning_list = list(/mob/living/basic/carp/passive = 1)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE
+ spawns_per_spawn = 2
+ spawn_probability_per_process = 100
+
+ remove_from_list_when_spawned = FALSE
diff --git a/code/modules/shuttle/shuttle_events/meteors.dm b/code/modules/shuttle/shuttle_events/meteors.dm
new file mode 100644
index 000000000000..d1ded58cbada
--- /dev/null
+++ b/code/modules/shuttle/shuttle_events/meteors.dm
@@ -0,0 +1,42 @@
+/datum/shuttle_event/simple_spawner/meteor
+ spawning_list = list(/obj/effect/meteor)
+
+/datum/shuttle_event/simple_spawner/meteor/post_spawn(atom/movable/spawnee)
+ ADD_TRAIT(spawnee, TRAIT_FREE_HYPERSPACE_MOVEMENT, src)
+ ..()
+
+/datum/shuttle_event/simple_spawner/meteor/spawn_movable(spawn_type)
+ var/turf/spawn_turf = get_spawn_turf()
+ //invert the dir cause we shoot in the opposite direction we're flying
+ if(ispath(spawn_type, /obj/effect/meteor))
+ post_spawn(new spawn_type (spawn_turf, get_edge_target_turf(spawn_turf, angle2dir(dir2angle(port.preferred_direction) - 180))))
+ else //if you want to spawn some random garbage inbetween, go wild
+ post_spawn(new spawn_type (get_spawn_turf()))
+
+///Very weak meteors, but may very rarely actually hit the shuttle!
+/datum/shuttle_event/simple_spawner/meteor/dust
+ name = "Dust Meteors! (Mostly Safe)"
+ event_probability = 2
+ activation_fraction = 0.1
+
+ spawn_probability_per_process = 100
+ spawns_per_spawn = 5
+ spawning_list = list(/obj/effect/meteor/dust = 1, /obj/effect/meteor/sand = 1)
+ spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE | SHUTTLE_EVENT_HIT_SHUTTLE
+ ///We can, occassionally, hit the shuttle, but we dont do a lot of damage and should only do so pretty rarely
+ var/hit_the_shuttle_chance = 1
+
+/datum/shuttle_event/simple_spawner/meteor/dust/get_spawn_turf()
+ return prob(hit_the_shuttle_chance) ? pick(spawning_turfs_hit) : pick(spawning_turfs_miss)
+
+///Okay this spawns a lot of really bad meteors, but they never hit the shuttle so it's perfectly safe (unless you go outside lol)
+/datum/shuttle_event/simple_spawner/meteor/safe
+ name = "Various Meteors! (Safe)"
+ event_probability = 5
+ activation_fraction = 0.1
+
+ spawn_probability_per_process = 100
+ spawns_per_spawn = 6
+ spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE
+ spawning_list = list(/obj/effect/meteor/medium = 10, /obj/effect/meteor/big = 5, /obj/effect/meteor/flaming = 3, /obj/effect/meteor/cluster = 1,
+ /obj/effect/meteor/irradiated = 3, /obj/effect/meteor/bluespace = 2)
diff --git a/code/modules/shuttle/shuttle_events/misc.dm b/code/modules/shuttle/shuttle_events/misc.dm
new file mode 100644
index 000000000000..f65eddadf7cb
--- /dev/null
+++ b/code/modules/shuttle/shuttle_events/misc.dm
@@ -0,0 +1,61 @@
+///thats amoreeeeee
+/datum/shuttle_event/simple_spawner/italian
+ name = "Italian Storm! (Mama Mia!)"
+ event_probability = 0.05
+
+ spawns_per_spawn = 5
+ spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE | SHUTTLE_EVENT_HIT_SHUTTLE
+ spawn_probability_per_process = 100
+ spawning_list = list(/obj/item/food/spaghetti/boiledspaghetti = 5, /obj/item/food/meatball = 1, /obj/item/food/spaghetti/pastatomato = 2,
+ /obj/item/food/spaghetti/meatballspaghetti = 2, /obj/item/food/pizza/margherita = 1)
+
+///We do a little bit of tomfoolery
+/datum/shuttle_event/simple_spawner/fake_ttv
+ name = "Fake TTV (Harmless!)"
+ event_probability = 0.5
+ activation_fraction = 0.1
+
+ spawning_list = list(/obj/item/transfer_valve/fake = 1)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE
+ spawn_probability_per_process = 5
+
+ remove_from_list_when_spawned = TRUE
+ self_destruct_when_empty = TRUE
+
+///Just spawn random maint garbage
+/datum/shuttle_event/simple_spawner/maintenance
+ name = "Maintenance Debris (Harmless!)"
+ event_probability = 3
+ activation_fraction = 0.1
+
+ spawning_list = list()
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE
+ spawn_probability_per_process = 100
+ spawns_per_spawn = 2
+
+/datum/shuttle_event/simple_spawner/maintenance/get_type_to_spawn()
+ var/list/spawn_list = GLOB.maintenance_loot
+ while(islist(spawn_list))
+ spawn_list = pick_weight(spawn_list)
+ return spawn_list
+
+///Sensors indicate that a black hole's gravitational field is affecting the region of space we were headed through
+/datum/shuttle_event/simple_spawner/black_hole
+ name = "Black Hole (Oh no!)"
+ event_probability = 0 // only admin spawnable
+ spawn_probability_per_process = 10
+ activation_fraction = 0.35
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE
+ spawning_list = list(/obj/singularity/shuttle_event = 1)
+ // only spawn it once
+ remove_from_list_when_spawned = TRUE
+ self_destruct_when_empty = TRUE
+
+///Kobayashi Maru version
+/datum/shuttle_event/simple_spawner/black_hole/adminbus
+ name = "Black Holes (OH GOD!)"
+ event_probability = 0
+ spawn_probability_per_process = 50
+ activation_fraction = 0.2
+ spawning_list = list(/obj/singularity/shuttle_event = 10)
+ remove_from_list_when_spawned = TRUE
diff --git a/code/modules/shuttle/shuttle_events/player_controlled.dm b/code/modules/shuttle/shuttle_events/player_controlled.dm
new file mode 100644
index 000000000000..fb4d6f6945c5
--- /dev/null
+++ b/code/modules/shuttle/shuttle_events/player_controlled.dm
@@ -0,0 +1,77 @@
+///Mobs spawned with this one are automatically player controlled, if possible
+/datum/shuttle_event/simple_spawner/player_controlled
+ spawning_list = list(/mob/living/basic/carp)
+
+ ///If we cant find a ghost, do we spawn them anyway? Otherwise they go in the garbage bin
+ var/spawn_anyway_if_no_player = FALSE
+
+ var/ghost_alert_string = "Would you like to be shot at the shuttle?"
+
+ var/role_type = ROLE_SENTIENCE
+
+/datum/shuttle_event/simple_spawner/player_controlled/spawn_movable(spawn_type)
+ if(ispath(spawn_type, /mob/living))
+ INVOKE_ASYNC(src, PROC_REF(try_grant_ghost_control), spawn_type)
+ else
+ ..()
+
+/// Attempt to grant control of a mob to ghosts before spawning it in. if spawn_anyway_if_no_player = TRUE, we spawn the mob even if there's no ghosts
+/datum/shuttle_event/simple_spawner/player_controlled/proc/try_grant_ghost_control(spawn_type)
+ var/list/candidates = SSpolling.poll_ghost_candidates(
+ ghost_alert_string + " (Warning: you will not be able to return to your body!)",
+ check_jobban = role_type,
+ poll_time = 10 SECONDS,
+ pic_source = spawn_type,
+ role_name_text = "shot at shuttle"
+ )
+ var/mob/dead/observer/candidate = length(candidates) ? pick(candidates) : null
+ if(candidate || spawn_anyway_if_no_player)
+ var/mob/living/new_mob = new spawn_type (get_turf(get_spawn_turf()))
+ if(candidate)
+ new_mob.ckey = candidate.ckey
+ post_spawn(new_mob)
+
+///BACK FOR REVENGE!!!
+/datum/shuttle_event/simple_spawner/player_controlled/alien_queen
+ name = "ALIEN QUEEN! (Kinda dangerous!)"
+ spawning_list = list(/mob/living/carbon/alien/adult/royal/queen = 1, /obj/vehicle/sealed/mecha/working/ripley = 1)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE
+
+ event_probability = 0.2
+ spawn_probability_per_process = 10
+ activation_fraction = 0.5
+
+ spawn_anyway_if_no_player = FALSE
+ ghost_alert_string = "Would you like to be an alien queen shot at the shuttle?"
+ remove_from_list_when_spawned = TRUE
+ self_destruct_when_empty = TRUE
+
+ role_type = ROLE_ALIEN
+
+///Spawns three player controlled carp!! Deadchats final chance to wreak havoc, probably really not that dangerous if even one person has a laser gun
+/datum/shuttle_event/simple_spawner/player_controlled/carp
+ name = "Three player controlled carp! (Little dangerous!)"
+ spawning_list = list(/mob/living/basic/carp = 10, /mob/living/basic/carp/mega = 2, /mob/living/basic/carp/magic = 2, /mob/living/basic/carp/magic/chaos = 1)
+ spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE
+
+ event_probability = 1
+ spawn_probability_per_process = 10
+ activation_fraction = 0.4
+
+ spawn_anyway_if_no_player = TRUE
+ ghost_alert_string = "Would you like to be a space carp to pester the emergency shuttle?"
+ remove_from_list_when_spawned = TRUE
+ self_destruct_when_empty = TRUE
+
+ role_type = ROLE_SENTIENCE
+
+ ///how many carp can we spawn max?
+ var/max_carp_spawns = 3
+
+/datum/shuttle_event/simple_spawner/player_controlled/carp/New(obj/docking_port/mobile/port)
+ . = ..()
+
+ var/list/spawning_list_copy = spawning_list.Copy()
+ spawning_list.Cut()
+ for(var/i in 1 to max_carp_spawns)
+ spawning_list[pick_weight(spawning_list_copy)] += 1
diff --git a/code/modules/shuttle/shuttle_events/turbulence.dm b/code/modules/shuttle/shuttle_events/turbulence.dm
new file mode 100644
index 000000000000..bbc136397c2a
--- /dev/null
+++ b/code/modules/shuttle/shuttle_events/turbulence.dm
@@ -0,0 +1,48 @@
+/// Repeat the "buckle in or fall over" event a couple times
+/datum/shuttle_event/turbulence
+ name = "Turbulence"
+ event_probability = 5
+ activation_fraction = 0.1
+ /// Minimum time to wait between periods of turbulence
+ var/minimum_interval = 20 SECONDS
+ /// Maximum time to wait between periods of turbulence
+ var/maximum_interval = 50 SECONDS
+ /// Time until we should shake again
+ COOLDOWN_DECLARE(turbulence_cooldown)
+ /// How long do we give people to get buckled?
+ var/warning_interval = 2 SECONDS
+
+/datum/shuttle_event/turbulence/activate()
+ . = ..()
+ minor_announce("Please note, we are entering an area of subspace turbulence. For your own safety, \
+ please fasten your belts and remain seated until the vehicle comes to a complete stop.",
+ title = "Emergency Shuttle", alert = TRUE)
+ COOLDOWN_START(src, turbulence_cooldown, rand(5 SECONDS, 20 SECONDS)) // Reduced interval after the announcement
+
+/datum/shuttle_event/turbulence/event_process()
+ . = ..()
+ if (!.)
+ return
+ if (!COOLDOWN_FINISHED(src, turbulence_cooldown))
+ return
+ COOLDOWN_START(src, turbulence_cooldown, rand(minimum_interval, maximum_interval))
+ shake()
+ addtimer(CALLBACK(src, PROC_REF(knock_down)), warning_interval, TIMER_DELETE_ME)
+
+/// Warn players to get buckled
+/datum/shuttle_event/turbulence/proc/shake()
+ var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape))
+ for(var/mob/living/mob as anything in mobs)
+ var/shake_intensity = mob.buckled ? 0.25 : 1
+ if(mob.client)
+ shake_camera(mob, 3 SECONDS, shake_intensity)
+
+/// Knock them down
+/datum/shuttle_event/turbulence/proc/knock_down()
+ if (SSshuttle.emergency.mode != SHUTTLE_ESCAPE)
+ return // They docked
+ var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape)) // Not very efficient but check again in case someone was outdoors
+ for(var/mob/living/mob as anything in mobs)
+ if(mob.buckled)
+ continue
+ mob.Paralyze(3 SECONDS, ignore_canstun = TRUE)
diff --git a/code/modules/visuals/render_steps.dm b/code/modules/visuals/render_steps.dm
index 74cc7f80d7f7..e29436596c6e 100644
--- a/code/modules/visuals/render_steps.dm
+++ b/code/modules/visuals/render_steps.dm
@@ -12,10 +12,16 @@
//we'll display using that, so we gotta reset
appearance_flags = KEEP_APART|KEEP_TOGETHER|RESET_TRANSFORM
-/atom/movable/render_step/Initialize(mapload, source)
+/atom/movable/render_step/Initialize(mapload, atom/source)
. = ..()
verbs.Cut() //Cargo cultttttt
- render_source = source
+
+ if(!source)
+ return
+
+ render_source = source.render_target
+ SET_PLANE_EXPLICIT(src, initial(plane), source)
+ RegisterSignal(source, COMSIG_QDELETING, PROC_REF(on_source_deleting))
/atom/movable/render_step/ex_act(severity)
return FALSE
@@ -34,6 +40,12 @@
if(harderforce)
return ..()
+/atom/movable/render_step/proc/on_source_deleting(atom/source)
+ SIGNAL_HANDLER
+
+ if(!QDELING(src))
+ qdel(src)
+
/**
* Render step that modfies an atom's color
* Useful for creating coherent emissive blockers out of things like glass floors by lowering alpha statically using matrixes
@@ -44,7 +56,7 @@
//RESET_COLOR is obvious I hope
appearance_flags = KEEP_APART|KEEP_TOGETHER|RESET_COLOR|RESET_TRANSFORM
-/atom/movable/render_step/color/Initialize(mapload, source, color)
+/atom/movable/render_step/color/Initialize(mapload, atom/source, color)
. = ..()
src.color = color
@@ -61,11 +73,10 @@
plane = EMISSIVE_PLANE
appearance_flags = EMISSIVE_APPEARANCE_FLAGS|RESET_TRANSFORM
-/atom/movable/render_step/emissive_blocker/Initialize(mapload, source)
+/atom/movable/render_step/emissive_blocker/Initialize(mapload, atom/source)
. = ..()
src.color = GLOB.em_block_color
-
/**
* Render step that makes the passed in render source GLOW
*
diff --git a/html/changelogs/AutoChangeLog-pr-1709.yml b/html/changelogs/AutoChangeLog-pr-1709.yml
deleted file mode 100644
index 75d61f5e0646..000000000000
--- a/html/changelogs/AutoChangeLog-pr-1709.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-author: "Jacquerel, IndieanaJones"
-delete-after: True
-changes:
- - rscadd: "Adds a box of liveleak stickers for 1 TC to the badass category"
- - balance: "The kindle scripture now has reduced effects on people with mindshields"
- - balance: "Observation consoles can now only warp to a random list of areas, vitality can be spent to add more"
- - balance: "Vitality sigils now reduce max health instead of dealing clone damage"
- - bugfix: "Anchoring crystals are now actually needed to open the ark"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-2456.yml b/html/changelogs/AutoChangeLog-pr-2456.yml
new file mode 100644
index 000000000000..bc3c45e4720a
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-2456.yml
@@ -0,0 +1,6 @@
+author: "00-Steven"
+delete-after: True
+changes:
+ - bugfix: "Ethereal heart revive doesn't delete organs alien to your species, like prosthetics, cybernetics, and possibly itself."
+ - bugfix: "Nightmare heart revive doesn't delete organs alien to your species, like itself, upon which it would stop the conversion to shadowperson."
+ - bugfix: "Bloodsucker torpor, doesn't delete organs alien to your species, which would result in things such as wings being lost (therefore preventing ultrakill jokes from lasting more than 10 minutes)"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-2503.yml b/html/changelogs/AutoChangeLog-pr-2503.yml
new file mode 100644
index 000000000000..7a301b3ea905
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-2503.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+ - bugfix: "Fixed some errors with some objectives, i.e cult sacrifice, trying to find a target."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-2512.yml b/html/changelogs/AutoChangeLog-pr-2512.yml
new file mode 100644
index 000000000000..8b523a62a8f7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-2512.yml
@@ -0,0 +1,4 @@
+author: "Absolucy"
+delete-after: True
+changes:
+ - bugfix: "RPLD UI now actually shows the sprites for new xenobio stuff, rather than a generic sprite."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-2523.yml b/html/changelogs/AutoChangeLog-pr-2523.yml
new file mode 100644
index 000000000000..de50fc6a5591
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-2523.yml
@@ -0,0 +1,6 @@
+author: "cnleth, Absolucy"
+delete-after: True
+changes:
+ - bugfix: "Symbols display correctly when sending messages via mind link"
+ - qol: "Linked speech now prevents messages from highlighting if its a message you sent, or if you are the owner of said link.."
+ - qol: "Linked speech messages are now marked as radio messages, for chat tab purposes."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-2536.yml b/html/changelogs/AutoChangeLog-pr-2536.yml
new file mode 100644
index 000000000000..928f2a37a005
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-2536.yml
@@ -0,0 +1,4 @@
+author: "FlufflesTheDog"
+delete-after: True
+changes:
+ - bugfix: "Multi-z hole repair works better, especially when the turf below is blocked by items"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-07.yml b/html/changelogs/archive/2024-07.yml
index c4c2d72ddd70..12c35164939f 100644
--- a/html/changelogs/archive/2024-07.yml
+++ b/html/changelogs/archive/2024-07.yml
@@ -26,3 +26,13 @@
- balance: Changed light step cost to 2 points to be on pair with hard soles
- sound: Changed lizard footsteps to claws
- balance: Increased prices of firing pins
+2024-07-05:
+ Jacquerel, IndieanaJones:
+ - rscadd: Adds a box of liveleak stickers for 1 TC to the badass category
+ - balance: The kindle scripture now has reduced effects on people with mindshields
+ - balance: Observation consoles can now only warp to a random list of areas, vitality
+ can be spent to add more
+ - balance: Vitality sigils now reduce max health instead of dealing clone damage
+ - bugfix: Anchoring crystals are now actually needed to open the ark
+ ViktorKoL:
+ - bugfix: heretics no longer lose their spells when returning from a shapeshift.
diff --git a/icons/mob/simple/carp.dmi b/icons/mob/simple/carp.dmi
index 42baa703400f..d58245094799 100644
Binary files a/icons/mob/simple/carp.dmi and b/icons/mob/simple/carp.dmi differ
diff --git a/monkestation/code/modules/blueshift/optin/objectives.dm b/monkestation/code/modules/blueshift/optin/objectives.dm
index 1c806b049574..8a846dfc65f2 100644
--- a/monkestation/code/modules/blueshift/optin/objectives.dm
+++ b/monkestation/code/modules/blueshift/optin/objectives.dm
@@ -8,6 +8,9 @@
/// Returns whether or not our opt in levels/variables are correct for the target. If true, they can be picked as a target.
/datum/objective/proc/opt_in_valid(datum/mind/target_mind)
+ if(ismob(target_mind))
+ var/mob/target_mob = target_mind
+ target_mind = target_mob.mind
return (get_opt_in_level(target_mind) <= target_mind.get_effective_opt_in_level())
// ROUND REMOVE
diff --git a/tgstation.dme b/tgstation.dme
index 90339d930913..bed61f119cb1 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2609,6 +2609,7 @@
#include "code\modules\admin\verbs\atmosdebug.dm"
#include "code\modules\admin\verbs\beakerpanel.dm"
#include "code\modules\admin\verbs\borgpanel.dm"
+#include "code\modules\admin\verbs\change_shuttle_events.dm"
#include "code\modules\admin\verbs\cinematic.dm"
#include "code\modules\admin\verbs\color_blind_test.dm"
#include "code\modules\admin\verbs\commandreport.dm"
@@ -5264,6 +5265,12 @@
#include "code\modules\shuttle\supply.dm"
#include "code\modules\shuttle\syndicate.dm"
#include "code\modules\shuttle\white_ship.dm"
+#include "code\modules\shuttle\shuttle_events\_shuttle_events.dm"
+#include "code\modules\shuttle\shuttle_events\carp.dm"
+#include "code\modules\shuttle\shuttle_events\meteors.dm"
+#include "code\modules\shuttle\shuttle_events\misc.dm"
+#include "code\modules\shuttle\shuttle_events\player_controlled.dm"
+#include "code\modules\shuttle\shuttle_events\turbulence.dm"
#include "code\modules\spatial_grid\cell_tracker.dm"
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\madness_curse.dm"