")
diff --git a/code/__DEFINES/clockcult.dm b/code/__DEFINES/clockcult.dm
index 9fd1ca1db8b2..d2ba5c487343 100644
--- a/code/__DEFINES/clockcult.dm
+++ b/code/__DEFINES/clockcult.dm
@@ -100,7 +100,7 @@ GLOBAL_LIST_EMPTY(all_scripture)
#define GATEWAY_RATVAR_ARRIVAL 600
///Objective text define
-#define CLOCKCULT_OBJECTIVE "Construct the Ark of the Clockwork Justicar and free Ratvar."
+#define CLOCKCULT_OBJECTIVE "Construct the Ark of the Clockwork Justiciar and free Ratvar."
//Eminence defines
/// How many walls can be superheated at once
diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm
index b6fbca4f6dce..f69b8c13b4d2 100644
--- a/code/__DEFINES/colors.dm
+++ b/code/__DEFINES/colors.dm
@@ -33,6 +33,13 @@
#define COLOR_HALF_TRANSPARENT_BLACK "#0000007A"
#define COLOR_RED "#FF0000"
+#define COLOR_CHRISTMAS_RED "#D6001C"
+#define COLOR_OLD_GLORY_RED "#B22234"
+#define COLOR_FRENCH_RED "#EF4135"
+#define COLOR_ETHIOPIA_RED "#DA121A"
+#define COLOR_UNION_JACK_RED "#C8102E"
+#define COLOR_MEDIUM_DARK_RED "#CC0000"
+#define COLOR_PINK_RED "#EF3340"
#define COLOR_SYNDIE_RED "#F10303"
#define COLOR_MOSTLY_PURE_RED "#FF3300"
#define COLOR_DARK_RED "#A50824"
@@ -48,7 +55,10 @@
#define COLOR_YELLOW "#FFFF00"
#define COLOR_VIVID_YELLOW "#FBFF23"
+#define COLOR_TANGERINE_YELLOW "#FFCC00"
#define COLOR_VERY_SOFT_YELLOW "#FAE48E"
+#define COLOR_GOLD "#FFD700"
+#define COLOR_ETHIOPIA_YELLOW "#FCDD09"
#define COLOR_OLIVE "#808000"
#define COLOR_ASSISTANT_OLIVE "#828163"
@@ -59,6 +69,9 @@
#define COLOR_VERY_PALE_LIME_GREEN "#DDFFD3"
#define COLOR_VERY_DARK_LIME_GREEN "#003300"
#define COLOR_GREEN "#008000"
+#define COLOR_CHRISTMAS_GREEN "#00873E"
+#define COLOR_IRISH_GREEN "#169B62"
+#define COLOR_ETHIOPIA_GREEN "#078930"
#define COLOR_DARK_MODERATE_LIME_GREEN "#44964A"
#define COLOR_PAI_GREEN "#00FF88"
#define COLOR_PALE_GREEN "#20e28e"
@@ -68,6 +81,9 @@
#define COLOR_DARK_CYAN "#00A2FF"
#define COLOR_TEAL "#008080"
#define COLOR_BLUE "#0000FF"
+#define COLOR_OLD_GLORY_BLUE "#3C3B6E"
+#define COLOR_FRENCH_BLUE "#0055A4"
+#define COLOR_UNION_JACK_BLUE "#012169"
#define COLOR_STRONG_BLUE "#1919c8"
#define COLOR_CENTCOM_BLUE "#134975"
#define COLOR_BRIGHT_BLUE "#2CB2E8"
@@ -94,6 +110,7 @@
#define COLOR_DARK_PURPLE "#551A8B"
#define COLOR_ORANGE "#FF9900"
+#define COLOR_IRISH_ORANGE "#FF883E"
#define COLOR_ENGINEERING_ORANGE "#FFA62B"
#define COLOR_MOSTLY_PURE_ORANGE "#ff8000"
#define COLOR_TAN_ORANGE "#FF7B00"
@@ -186,11 +203,14 @@
*
* Important note: colors can end up significantly different from the basic html picture, especially when saturated
*/
-#define LIGHT_COLOR_WHITE "#FFFFFF"
+/// Bright light used by default in tubes and bulbs
+#define LIGHT_COLOR_DEFAULT "#F3FFFA"
/// Warm but extremely diluted red. rgb(250, 130, 130)
-#define LIGHT_COLOR_RED "#FA8282"
+#define LIGHT_COLOR_RED "#FA8282"
/// Bright but quickly dissipating neon green. rgb(100, 200, 100)
#define LIGHT_COLOR_GREEN "#64C864"
+/// Vivid, slightly blue green. rgb(60, 240, 70)
+#define LIGHT_COLOR_VIVID_GREEN "#3CF046"
/// Electric green. rgb(0, 255, 0)
#define LIGHT_COLOR_ELECTRIC_GREEN "#00FF00"
/// Cold, diluted blue. rgb(100, 150, 250)
@@ -231,6 +251,8 @@
#define LIGHT_COLOR_LAVA "#C48A18"
/// Bright, non-saturated red. Leaning slightly towards pink for visibility. rgb(250, 100, 75)
#define LIGHT_COLOR_FLARE "#FA644B"
+/// Vivid red. Leans a bit darker to accentuate red colors and leave other channels a bit dry. rgb(200, 25, 25)
+#define LIGHT_COLOR_INTENSE_RED "#C81919"
/// Weird color, between yellow and green, very slimy. rgb(175, 200, 75)
#define LIGHT_COLOR_SLIME_LAMP "#AFC84B"
/// Extremely diluted yellow, close to skin color (for some reason). rgb(250, 225, 175)
@@ -330,6 +352,9 @@
#define SOFA_BROWN "#a75400"
#define SOFA_MAROON "#830000"
+/// Color used for default blood
+#define COLOR_BLOOD "#CC0000"
+
GLOBAL_LIST_INIT(cable_colors, list(
CABLE_COLOR_BLUE = CABLE_HEX_COLOR_BLUE,
CABLE_COLOR_CYAN = CABLE_HEX_COLOR_CYAN,
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index f699cb55ca6c..b26ec10d8071 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -285,3 +285,6 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(
#define WEATHER_RAD "rad"
#define WEATHER_SNOW "snow"
#define WEATHER_ALL "all"
+
+#define BULLET_DISMEMBER_THRESHOLD 60 //dripstation edit. Blocks dismemberment in code\modules\mob\living\carbon\carbon_defense.dm if
+#define LASER_DISMEMBER_THRESHOLD 50 //dripstation edit, same as above
\ No newline at end of file
diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm
index 8658a984936d..0270a6120402 100644
--- a/code/__DEFINES/construction.dm
+++ b/code/__DEFINES/construction.dm
@@ -1,69 +1,3 @@
-/*ALL DEFINES RELATED TO CONSTRUCTION, CONSTRUCTING THINGS, OR CONSTRUCTED OBJECTS GO HERE*/
-
-//Defines for construction states
-
-//girder construction states
-#define GIRDER_NORMAL 0
-#define GIRDER_REINF_STRUTS 1
-#define GIRDER_REINF 2
-#define GIRDER_DISPLACED 3
-#define GIRDER_DISASSEMBLED 4
-
-//rwall construction states
-#define INTACT 0
-#define SUPPORT_LINES 1
-#define COVER 2
-#define CUT_COVER 3
-#define ANCHOR_BOLTS 4
-#define SUPPORT_RODS 5
-#define SHEATH 6
-
-//window construction states
-#define WINDOW_OUT_OF_FRAME 0
-#define WINDOW_IN_FRAME 1
-#define WINDOW_SCREWED_TO_FRAME 2
-
-//reinforced window construction states
-#define RWINDOW_FRAME_BOLTED 3
-#define RWINDOW_BARS_CUT 4
-#define RWINDOW_POPPED 5
-#define RWINDOW_BOLTS_OUT 6
-#define RWINDOW_BOLTS_HEATED 7
-#define RWINDOW_SECURE 8
-
-//mecha wreckage repair states
-#define MECHA_WRECK_CUT 0
-#define MECHA_WRECK_DENTED 1
-#define MECHA_WRECK_LOOSE 2
-#define MECHA_WRECK_UNWIRED 3
-
-//airlock assembly construction states
-#define AIRLOCK_ASSEMBLY_NEEDS_WIRES 0
-#define AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS 1
-#define AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER 2
-
-//default_unfasten_wrench() return defines
-#define CANT_UNFASTEN 0
-#define FAILED_UNFASTEN 1
-#define SUCCESSFUL_UNFASTEN 2
-
-//ai core defines
-#define EMPTY_CORE 0
-#define CIRCUIT_CORE 1
-#define SCREWED_CORE 2
-#define CABLED_CORE 3
-#define GLASS_CORE 4
-#define AI_READY_CORE 5
-
-//Construction defines for the pinion airlock
-#define GEAR_SECURE 1
-#define GEAR_LOOSE 2
-
-//floodlights because apparently we use defines now
-#define FLOODLIGHT_NEEDS_WIRES 0
-#define FLOODLIGHT_NEEDS_LIGHTS 1
-#define FLOODLIGHT_NEEDS_SECURING 2
-#define FLOODLIGHT_NEEDS_WRENCHING 3
//other construction-related things
@@ -75,10 +9,6 @@
//The amount of materials you get from a sheet of mineral like iron/diamond/glass etc
#define MINERAL_MATERIAL_AMOUNT 2000
-//The maximum size of a stack object.
-#define MAX_STACK_SIZE 50
-//maximum amount of cable in a coil
-#define MAXCOIL 40
// Crafting defines.
// When adding new defines, please make sure to also add them to the encompassing list.
@@ -152,21 +82,7 @@ GLOBAL_LIST_INIT(crafting_category_food, list(
CAT_DRINK,
))
-#define RCD_FLOORWALL (1<<0)
-#define RCD_AIRLOCK (1<<1)
-#define RCD_DECONSTRUCT (1<<2)
-#define RCD_WINDOWGRILLE (1<<3)
-#define RCD_MACHINE (1<<4)
-#define RCD_COMPUTER (1<<5)
-#define RCD_FURNISHING (1<<6)
-#define RCD_CONVEYOR (1<<7)
-#define RCD_SWITCH (1<<8)
-#define RCD_UPGRADE_FRAMES (1<<0)
-#define RCD_UPGRADE_SIMPLE_CIRCUITS (1<<1)
-#define RCD_UPGRADE_SILO_LINK (1<<2)
-#define RCD_UPGRADE_FURNISHING (1<<3)
-#define RCD_UPGRADE_CONVEYORS (1<<4)
#define RCD_WINDOW_FULLTILE "full tile"
#define RCD_WINDOW_DIRECTIONAL "directional"
diff --git a/code/__DEFINES/construction/actions.dm b/code/__DEFINES/construction/actions.dm
new file mode 100644
index 000000000000..9fe4fd157e31
--- /dev/null
+++ b/code/__DEFINES/construction/actions.dm
@@ -0,0 +1,11 @@
+//default_unfasten_wrench() return defines
+#define CANT_UNFASTEN 0
+#define FAILED_UNFASTEN 1
+#define SUCCESSFUL_UNFASTEN 2
+
+// Defines for the construction component
+#define FORWARD 1
+#define BACKWARD -1
+
+#define ITEM_DELETE "delete"
+#define ITEM_MOVE_INSIDE "move_inside"
diff --git a/code/__DEFINES/construction/material.dm b/code/__DEFINES/construction/material.dm
new file mode 100644
index 000000000000..10eeaf9ea6cf
--- /dev/null
+++ b/code/__DEFINES/construction/material.dm
@@ -0,0 +1,77 @@
+//Defines for amount of material retrived from sheets & other items
+/// The amount of materials you get from a sheet of mineral like iron/diamond/glass etc. 100 Units.
+#define SHEET_MATERIAL_AMOUNT 100
+/// The amount of materials you get from half a sheet. Used in standard object quantities. 50 units.
+#define HALF_SHEET_MATERIAL_AMOUNT (SHEET_MATERIAL_AMOUNT / 2)
+/// The amount of materials used in the smallest of objects, like pens and screwdrivers. 10 units.
+#define SMALL_MATERIAL_AMOUNT (HALF_SHEET_MATERIAL_AMOUNT / 5)
+/// The amount of material that goes into a coin, which determines the value of the coin.
+#define COIN_MATERIAL_AMOUNT (HALF_SHEET_MATERIAL_AMOUNT * 0.4)
+
+//Cable related values
+/// The maximum size of a stack object.
+#define MAX_STACK_SIZE 50
+/// Maximum amount of cable in a coil
+#define MAXCOIL 40
+
+//Category of materials
+/// Is the material from an ore? currently unused but exists atm for categorizations sake
+#define MAT_CATEGORY_ORE "ore capable"
+/// Hard materials, such as iron or silver
+#define MAT_CATEGORY_RIGID "rigid material"
+/// Materials that can be used to craft items
+#define MAT_CATEGORY_ITEM_MATERIAL "item material"
+/// Use this flag on TRUE if you want the basic recipes
+#define MAT_CATEGORY_BASE_RECIPES "basic recipes"
+
+///Flags for map loaded materials
+/// Used to make a material initialize at roundstart.
+#define MATERIAL_INIT_MAPLOAD (1<<0)
+/// Used to make a material type able to be instantiated on demand after roundstart.
+#define MATERIAL_INIT_BESPOKE (1<<1)
+
+//Material Container Flags.
+///If the container shows the amount of contained materials on examine.
+#define MATCONTAINER_EXAMINE (1<<0)
+///If the container cannot have materials inserted through attackby().
+#define MATCONTAINER_NO_INSERT (1<<1)
+///If the user can insert mats into the container despite the intent.
+#define MATCONTAINER_ANY_INTENT (1<<2)
+///If the user won't receive a warning when attacking the container with an unallowed item.
+#define MATCONTAINER_SILENT (1<<3)
+
+/// Whether a material's mechanical effects should apply to the atom. This is necessary for other flags to work.
+#define MATERIAL_EFFECTS (1<<0)
+/// Applies the material color to the atom's color. Deprecated, use MATERIAL_GREYSCALE instead
+#define MATERIAL_COLOR (1<<1)
+/// Whether a prefix describing the material should be added to the name
+#define MATERIAL_ADD_PREFIX (1<<2)
+/// Whether a material should affect the stats of the atom
+#define MATERIAL_AFFECT_STATISTICS (1<<3)
+/// Applies the material greyscale color to the atom's greyscale color.
+#define MATERIAL_GREYSCALE (1<<4)
+
+//Special return values of [/datum/component/material_container/insert_item]
+/// No material was found inside them item
+#define MATERIAL_INSERT_ITEM_NO_MATS -1
+/// The container does not have the space for the item
+#define MATERIAL_INSERT_ITEM_NO_SPACE -2
+/// The item material type was not accepted or other reasons
+#define MATERIAL_INSERT_ITEM_FAILURE 0
+
+
+// Slowdown values.
+/// The slowdown value of one [SHEET_MATERIAL_AMOUNT] of plasteel.
+#define MATERIAL_SLOWDOWN_PLASTEEL (0.05)
+/// The slowdown value of one [SHEET_MATERIAL_AMOUNT] of alien alloy.
+#define MATERIAL_SLOWDOWN_ALIEN_ALLOY (0.1)
+
+//Stock market stock values.
+/// How much quantity of a material stock exists for common materials like iron & glass.
+#define MATERIAL_QUANTITY_COMMON 25000
+/// How much quantity of a material stock exists for uncommon materials like silver & titanium.
+#define MATERIAL_QUANTITY_UNCOMMON 10000
+/// How much quantity of a material stock exists for rare materials like gold, uranium, & diamond.
+#define MATERIAL_QUANTITY_RARE 2500
+/// How much quantity of a material stock exists for exotic materials like diamond & bluespace crystals.
+#define MATERIAL_QUANTITY_EXOTIC 500
diff --git a/code/__DEFINES/construction/rcd.dm b/code/__DEFINES/construction/rcd.dm
new file mode 100644
index 000000000000..3f85052b9dd1
--- /dev/null
+++ b/code/__DEFINES/construction/rcd.dm
@@ -0,0 +1,61 @@
+//rcd constants for the design list
+/// The mode of operation to design an specific type of rcd design
+#define RCD_DESIGN_MODE "rcd_design_mode"
+ /// For changing turfs
+ #define RCD_FLOORWALL (1 << 0) //Should be RCD_TURF
+ /// Full tile windows
+ #define RCD_WINDOWGRILLE (1 << 1)
+ /// Windoors & Airlocks
+ #define RCD_AIRLOCK (1 << 2)
+ /// Literarly anything that is spawned on top of a turf such as tables, machines etc
+ #define RCD_STRUCTURE (1 << 3)
+ /// For wallmounts like air alarms, fire alarms & apc
+ #define RCD_WALLFRAME (1 << 4)
+ /// For deconstructing an structure
+ #define RCD_DECONSTRUCT (1 << 5)
+
+ //YOG RCD vals
+ #define RCD_MACHINE (1<<6)
+ #define RCD_COMPUTER (1<<7)
+ #define RCD_FURNISHING (1<<8)
+ #define RCD_CONVEYOR (1<<9)
+ #define RCD_SWITCH (1<<10)
+
+/// The typepath of the structure the rcd is trying to build
+#define RCD_DESIGN_PATH "rcd_design_path"
+
+
+/// Time taken for an rcd hologram to disappear
+#define RCD_HOLOGRAM_FADE_TIME (15 SECONDS)
+
+//All available upgrades
+/// Upgrade for building machines
+#define RCD_UPGRADE_FRAMES (1 << 0)
+/// Upgrade for installing circuitboards in air alarms, fire alarms, apc & cells in them
+#define RCD_UPGRADE_SIMPLE_CIRCUITS (1 << 1)
+/// Upgrade for drawing iron from ore silo
+#define RCD_UPGRADE_SILO_LINK (1 << 2)
+/// Upgrade for building furnishing items
+#define RCD_UPGRADE_FURNISHING (1 << 3)
+// Upgrade for building conveyer belts
+#define RCD_UPGRADE_CONVEYORS (1 << 4)
+/// Upgrade to stop construction effect from getting attacked
+#define RCD_UPGRADE_ANTI_INTERRUPT (1 << 5)
+/// Upgrade to disable delay multiplier when building multiple structures
+#define RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN (1 << 6)
+/// All upgrades packed in 1 flag
+#define RCD_ALL_UPGRADES (RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_SILO_LINK | RCD_UPGRADE_FURNISHING | RCD_UPGRADE_CONVEYORS | RCD_UPGRADE_ANTI_INTERRUPT | RCD_UPGRADE_NO_FREQUENT_USE_COOLDOWN)
+/// Upgrades for the Rapid Pipe Dispenser to unwrench pipes
+#define RPD_UPGRADE_UNWRENCH (1 << 0)
+
+//Memory constants for faster construction speeds
+/// The memory constant for a wall
+#define RCD_MEMORY_WALL 1
+/// The memory constant for full tile windows
+#define RCD_MEMORY_WINDOWGRILLE 2
+// How much faster to use the RCD when on a tile with memory
+#define RCD_MEMORY_SPEED_BUFF 5
+/// How much less resources the RCD uses when reconstructing
+#define RCD_MEMORY_COST_BUFF 8
+/// If set to TRUE in rcd_vals, will bypass the cooldown on slowing down frequent use
+#define RCD_RESULT_BYPASS_FREQUENT_USE_COOLDOWN "bypass_frequent_use_cooldown"
diff --git a/code/__DEFINES/construction/structures.dm b/code/__DEFINES/construction/structures.dm
new file mode 100644
index 000000000000..f67df93836f3
--- /dev/null
+++ b/code/__DEFINES/construction/structures.dm
@@ -0,0 +1,74 @@
+//Defines for construction states
+
+//ai core defines
+#define EMPTY_CORE 0
+#define CIRCUIT_CORE 1
+#define SCREWED_CORE 2
+#define CABLED_CORE 3
+#define GLASS_CORE 4
+#define AI_READY_CORE 5
+
+//girder construction states
+#define GIRDER_NORMAL 0
+#define GIRDER_REINF_STRUTS 1
+#define GIRDER_REINF 2
+#define GIRDER_DISPLACED 3
+#define GIRDER_DISASSEMBLED 4
+#define GIRDER_TRAM 5
+
+//rwall construction states
+#define INTACT 0
+#define SUPPORT_LINES 1
+#define COVER 2
+#define CUT_COVER 3
+#define ANCHOR_BOLTS 4
+#define SUPPORT_RODS 5
+#define SHEATH 6
+
+//window construction states
+#define WINDOW_OUT_OF_FRAME 0
+#define WINDOW_IN_FRAME 1
+#define WINDOW_SCREWED_TO_FRAME 2
+
+//reinforced window construction states
+#define RWINDOW_FRAME_BOLTED 3
+#define RWINDOW_BARS_CUT 4
+#define RWINDOW_POPPED 5
+#define RWINDOW_BOLTS_OUT 6
+#define RWINDOW_BOLTS_HEATED 7
+#define RWINDOW_SECURE 8
+
+//tram structure construction states
+#define TRAM_OUT_OF_FRAME 0
+#define TRAM_IN_FRAME 1
+#define TRAM_SCREWED_TO_FRAME 2
+
+//airlock assembly construction states
+#define AIRLOCK_ASSEMBLY_NEEDS_WIRES 0
+#define AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS 1
+#define AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER 2
+
+//blast door (de)construction states
+#define BLASTDOOR_NEEDS_WIRES 0
+#define BLASTDOOR_NEEDS_ELECTRONICS 1
+#define BLASTDOOR_FINISHED 2
+
+//floodlights because apparently we use defines now
+#define FLOODLIGHT_NEEDS_WIRES 0
+#define FLOODLIGHT_NEEDS_LIGHTS 1
+#define FLOODLIGHT_NEEDS_SECURING 2
+#define FLOODLIGHT_NEEDS_WRENCHING 3
+
+//Construction defines for the pinion airlock
+#define GEAR_SECURE 1
+#define GEAR_LOOSE 2
+
+// Stationary gas tanks
+#define TANK_FRAME 0
+#define TANK_PLATING_UNSECURED 1
+
+//mecha wreckage repair states
+#define MECHA_WRECK_CUT 0
+#define MECHA_WRECK_DENTED 1
+#define MECHA_WRECK_LOOSE 2
+#define MECHA_WRECK_UNWIRED 3
diff --git a/code/__DEFINES/dcs/flags.dm b/code/__DEFINES/dcs/flags.dm
index 916eb247b68b..6eefa69bb340 100644
--- a/code/__DEFINES/dcs/flags.dm
+++ b/code/__DEFINES/dcs/flags.dm
@@ -1,4 +1,4 @@
-/// Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type.
+/// Return this from `/datum/component/Initialize` or `/datum/component/OnTransfer` or `/datum/component/on_source_add` to have the component be deleted if it's applied to an incorrect type.
/// `parent` must not be modified if this is to be returned.
/// This will be noted in the runtime logs
#define COMPONENT_INCOMPATIBLE 1
@@ -14,6 +14,11 @@
/// You do not need this if you are only unregistering signals, for instance.
/// You would need it if you are doing something like removing the target from a processing list.
#define ELEMENT_DETACH_ON_HOST_DESTROY (1 << 0)
+
+
+// /datum/element flags
+/// Causes the detach proc to be called when the host object is being deleted
+#define ELEMENT_DETACH (1 << 0)
/**
* Only elements created with the same arguments given after `argument_hash_start_idx` share an element instance
* The arguments are the same when the text and number values are the same and all other values have the same ref
@@ -22,19 +27,36 @@
/// Causes all detach arguments to be passed to detach instead of only being used to identify the element
/// When this is used your Detach proc should have the same signature as your Attach proc
#define ELEMENT_COMPLEX_DETACH (1 << 2)
+/**
+ * Elements with this flag will have their datum lists arguments compared as is,
+ * without the contents being sorted alpha-numerically first.
+ * This is good for those elements where the position of the keys matter, like in the case of color matrices.
+ */
+#define ELEMENT_DONT_SORT_LIST_ARGS (1<<3)
+/**
+ * Elements with this flag will be ignored by the dcs_check_list_arguments test.
+ * A good example is connect_loc, for which it's pratically undoable unless we force every signal proc to have a different name.
+ */
+#define ELEMENT_NO_LIST_UNIT_TEST (1<<4)
// How multiple components of the exact same type are handled in the same datum
/// old component is deleted (default)
-#define COMPONENT_DUPE_HIGHLANDER 0
+#define COMPONENT_DUPE_HIGHLANDER 0
/// duplicates allowed
-#define COMPONENT_DUPE_ALLOWED 1
+#define COMPONENT_DUPE_ALLOWED 1
/// new component is deleted
-#define COMPONENT_DUPE_UNIQUE 2
+#define COMPONENT_DUPE_UNIQUE 2
+/**
+ * Component uses source tracking to manage adding and removal logic.
+ * Add a source/spawn to/the component by using AddComponentFrom(source, component_type, args...)
+ * Removing the last source will automatically remove the component from the parent.
+ * Arguments will be passed to on_source_add(source, args...); ensure that Initialize and on_source_add have the same signature.
+ */
+#define COMPONENT_DUPE_SOURCES 3
/// old component is given the initialization args of the new
-#define COMPONENT_DUPE_UNIQUE_PASSARGS 4
+#define COMPONENT_DUPE_UNIQUE_PASSARGS 4
/// each component of the same type is consulted as to whether the duplicate should be allowed
-#define COMPONENT_DUPE_SELECTIVE 5
-
+#define COMPONENT_DUPE_SELECTIVE 5
//Arch
#define ARCH_PROB "probability" //Probability for each item
diff --git a/code/__DEFINES/dcs/helpers.dm b/code/__DEFINES/dcs/helpers.dm
index 12eea34e1560..a9991a36686f 100644
--- a/code/__DEFINES/dcs/helpers.dm
+++ b/code/__DEFINES/dcs/helpers.dm
@@ -2,7 +2,7 @@
/// The datum hosting the signal is automaticaly added as the first argument
/// Returns a bitfield gathered from all registered procs
/// Arguments given here are packaged in a list and given to _SendSignal
-#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) )
+#define SEND_SIGNAL(target, sigtype, arguments...) ( !target._listen_lookup?[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) )
#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
@@ -10,14 +10,21 @@
/// Every proc you pass to RegisterSignal must have this.
#define SIGNAL_HANDLER SHOULD_NOT_SLEEP(TRUE)
+/// Signifies that this proc is used to handle signals, but also sleeps.
+/// Do not use this for new work.
+#define SIGNAL_HANDLER_DOES_SLEEP
+
/// A wrapper for _AddElement that allows us to pretend we're using normal named arguments
#define AddElement(arguments...) _AddElement(list(##arguments))
-
/// A wrapper for _RemoveElement that allows us to pretend we're using normal named arguments
#define RemoveElement(arguments...) _RemoveElement(list(##arguments))
/// A wrapper for _AddComponent that allows us to pretend we're using normal named arguments
#define AddComponent(arguments...) _AddComponent(list(##arguments))
+/// A wrapper for _AddComonent that passes in a source.
+/// Necessary if dupe_mode is set to COMPONENT_DUPE_SOURCES.
+#define AddComponentFrom(source, arguments...) _AddComponent(list(##arguments), source)
+
/// A wrapper for _LoadComponent that allows us to pretend we're using normal named arguments
#define LoadComponent(arguments...) _LoadComponent(list(##arguments))
diff --git a/code/__DEFINES/dcs/signals/mapping.dm b/code/__DEFINES/dcs/signals/mapping.dm
new file mode 100644
index 000000000000..d80d60365802
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/mapping.dm
@@ -0,0 +1,2 @@
+// Sent when the max plane offset changes : (old_max_offset, new_max_offset)
+#define COMSIG_PLANE_OFFSET_INCREASE "plane_offset_increase"
diff --git a/code/__DEFINES/dcs/signals/signals_area.dm b/code/__DEFINES/dcs/signals/signals_area.dm
index 5edba2346539..ae220d933753 100644
--- a/code/__DEFINES/dcs/signals/signals_area.dm
+++ b/code/__DEFINES/dcs/signals/signals_area.dm
@@ -31,3 +31,8 @@
// Area fire signals
/// Sent when an area's fire var changes: (fire_value)
#define COMSIG_AREA_FIRE_CHANGED "area_fire_set"
+
+/// Called when some weather starts in this area
+#define COMSIG_WEATHER_BEGAN_IN_AREA(event_type) "weather_began_in_area_[event_type]"
+/// Called when some weather ends in this area
+#define COMSIG_WEATHER_ENDED_IN_AREA(event_type) "weather_ended_in_area_[event_type]"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm
index 63a366ea1aea..391ed0112ac9 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm
@@ -31,7 +31,14 @@
#define COMSIG_ATOM_SET_LIGHT_ON "atom_set_light_on"
///Called right after the atom changes the value of light_on to a different one, from base of [/atom/proc/set_light_on]: (old_value)
#define COMSIG_ATOM_UPDATE_LIGHT_ON "atom_update_light_on"
+///Called right before the atom changes the value of light_height to a different one, from base [atom/proc/set_light_height]: (new_value)
+#define COMSIG_ATOM_SET_LIGHT_HEIGHT "atom_set_light_height"
+///Called right after the atom changes the value of light_height to a different one, from base of [/atom/proc/set_light_height]: (old_value)
+#define COMSIG_ATOM_UPDATE_LIGHT_HEIGHT "atom_update_light_height"
///Called right before the atom changes the value of light_flags to a different one, from base [atom/proc/set_light_flags]: (new_flags)
#define COMSIG_ATOM_SET_LIGHT_FLAGS "atom_set_light_flags"
///Called right after the atom changes the value of light_flags to a different one, from base of [/atom/proc/set_light_flags]: (old_flags)
#define COMSIG_ATOM_UPDATE_LIGHT_FLAGS "atom_update_light_flags"
+
+///Called when an atom has a light template applied to it. Frombase of [/datum/light_template/proc/mirror_onto]: ()
+#define COMSIG_ATOM_LIGHT_TEMPLATE_MIRRORED "atom_light_template_mirrored"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
index 43db37a73bd4..188297594d37 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
@@ -3,15 +3,15 @@
// All signals send the source datum of the signal as the first argument
// /atom signals
-///from base of atom/proc/Initialize(mapload): sent any time a new atom is created in this atom
-#define COMSIG_ATOM_INITIALIZED_ON "atom_initialized_on"
//from SSatoms InitAtom - Only if the atom was not deleted or failed initialization
#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE "atom_init_success"
+//from SSatoms InitAtom - Only if the atom was not deleted or failed initialization and has a loc
+#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON "atom_init_success_on"
///from base of atom/examine(): (/mob, list/examine_text)
-#define COMSIG_PARENT_EXAMINE "atom_examine"
+#define COMSIG_ATOM_EXAMINE "atom_examine"
///from base of atom/get_examine_name(): (/mob, list/overrides)
#define COMSIG_ATOM_GET_EXAMINE_NAME "atom_examine_name"
-#define COMSIG_PARENT_EXAMINE_MORE "atom_examine_more" ///from base of atom/examine_more(): (/mob)
+#define COMSIG_ATOM_EXAMINE_MORE "atom_examine_more" ///from base of atom/examine_more(): (/mob)
//Positions for overrides list
#define EXAMINE_POSITION_ARTICLE (1<<0)
#define EXAMINE_POSITION_BEFORE (1<<1)
@@ -43,6 +43,8 @@
#define COMSIG_ATOM_UPDATED_ICON "atom_updated_icon"
///from base of [/atom/proc/smooth_icon]: ()
#define COMSIG_ATOM_SMOOTHED_ICON "atom_smoothed_icon"
+///from [/datum/controller/subsystem/processing/dcs/proc/rotate_decals]: (list/datum/element/decal/rotating)
+#define COMSIG_ATOM_DECALS_ROTATING "atom_decals_rotating"
///from base of atom/Entered(): (atom/movable/arrived, atom/old_loc, list/atom/old_locs)
#define COMSIG_ATOM_ENTERED "atom_entered"
///from base of atom/movable/Moved(): (atom/movable/arrived, atom/old_loc, list/atom/old_locs)
@@ -69,8 +71,10 @@
#define COMSIG_ATOM_CREATEDBY_PROCESSING "atom_createdby_processing"
///when an atom is processed (mob/living/user, obj/item/I, list/atom/results)
#define COMSIG_ATOM_PROCESSED "atom_processed"
+///called when teleporting into a possibly protected turf: (channel, turf/origin, turf/destination)
+#define COMSIG_ATOM_INTERCEPT_TELEPORTING "intercept_teleporting"
///called when teleporting into a protected turf: (channel, turf/origin)
-#define COMSIG_ATOM_INTERCEPT_TELEPORT "intercept_teleport"
+#define COMSIG_ATOM_INTERCEPT_TELEPORTED "intercept_teleport"
#define COMPONENT_BLOCK_TELEPORT (1<<0)
///called when an atom is added to the hearers on get_hearers_in_view(): (list/processing_list, list/hearers)
#define COMSIG_ATOM_HEARER_IN_VIEW "atom_hearer_in_view"
@@ -96,6 +100,9 @@
/// from cosmetic items to restyle certain mobs, objects or organs: (atom/source, mob/living/trimmer, atom/movable/original_target, body_zone, restyle_type, style_speed)
#define COMSIG_ATOM_RESTYLE "atom_restyle"
+/// Called on [/atom/SpinAnimation()] : (speed, loops, segments, angle)
+#define COMSIG_ATOM_SPIN_ANIMATION "atom_spin_animation"
+
///! from proc/get_rad_contents(): ()
#define COMSIG_ATOM_RAD_PROBE "atom_rad_probe"
#define COMPONENT_BLOCK_RADIATION 1
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index 2ef0297bd972..44278be89e13 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -2,10 +2,10 @@
// When the signal is called: (signal arguments)
// All signals send the source datum of the signal as the first argument
-///from base of atom/movable/Moved(): (/atom)
+///from base of atom/movable/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change): (/atom)
#define COMSIG_MOVABLE_PRE_MOVE "movable_pre_move"
#define COMPONENT_MOVABLE_BLOCK_PRE_MOVE (1<<0)
-///from base of atom/movable/Moved(): (atom/old_loc, dir, forced, list/old_locs)
+///from base of atom/movable/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change): (atom/old_loc, dir, forced, list/old_locs)
#define COMSIG_MOVABLE_MOVED "movable_moved"
///from base of atom/movable/Cross(): (/atom/movable)
#define COMSIG_MOVABLE_CROSS "movable_cross"
@@ -94,6 +94,7 @@
/// from base of atom/movable/Process_Spacemove(): (movement_dir, continuous_move)
#define COMSIG_MOVABLE_SPACEMOVE "spacemove"
#define COMSIG_MOVABLE_STOP_SPACEMOVE (1<<0)
+ #define COMSIG_MOVABLE_ALLOW_SPACEMOVE (1<<1)
/// Sent from /obj/item/radio/talk_into(): (obj/item/radio/used_radio)
#define COMSIG_MOVABLE_USING_RADIO "movable_radio"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm
index 2aeb70768075..b1aa3a75bfc5 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm
@@ -13,6 +13,8 @@
#define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact"
///signal sent out by an atom upon onZImpact : (turf/impacted_turf, levels)
#define COMSIG_ATOM_ON_Z_IMPACT "movable_on_z_impact"
+///Signal sent after an atom movable moves on shuttle.
+#define COMSIG_ATOM_AFTER_SHUTTLE_MOVE "movable_after_shuttle_move"
///called on a movable (NOT living) when it starts pulling (atom/movable/pulled, state, force)
#define COMSIG_ATOM_START_PULL "movable_start_pull"
///called on /living when someone starts pulling (atom/movable/pulled, state, force)
@@ -34,8 +36,13 @@
#define COMSIG_ATOM_RELAYMOVE "atom_relaymove"
///prevents the "you cannot move while buckled! message"
#define COMSIG_BLOCK_RELAYMOVE (1<<0)
+/// From base of atom/setDir(): (old_dir, new_dir). Called before the direction changes
+#define COMSIG_ATOM_PRE_DIR_CHANGE "atom_pre_face_atom"
+ #define COMPONENT_ATOM_BLOCK_DIR_CHANGE (1<<0)
///from base of atom/setDir(): (old_dir, new_dir). Called before the direction changes.
#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change"
+///from base of atom/setDir(): (old_dir, new_dir). Called after the direction changes.
+#define COMSIG_ATOM_POST_DIR_CHANGE "atom_dir_change"
///from base of atom/setShift(): (dir). Called before the shift changes.
#define COMSIG_ATOM_SHIFT_CHANGE "atom_shift_change"
/// from /datum/component/singularity/proc/can_move(), as well as /obj/energy_ball/proc/can_move()
diff --git a/code/__DEFINES/dcs/signals/signals_beam.dm b/code/__DEFINES/dcs/signals/signals_beam.dm
new file mode 100644
index 000000000000..edfbc7c43713
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_beam.dm
@@ -0,0 +1,3 @@
+/// Called before beam is redrawn
+#define COMSIG_BEAM_BEFORE_DRAW "beam_before_draw"
+ #define BEAM_CANCEL_DRAW (1 << 0)
diff --git a/code/__DEFINES/dcs/signals/signals_camera.dm b/code/__DEFINES/dcs/signals/signals_camera.dm
new file mode 100644
index 000000000000..6ec142f54fab
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_camera.dm
@@ -0,0 +1,2 @@
+///Signal sent when a /datum/trackable found a target: (datum/trackable/source, mob/living/target)
+#define COMSIG_TRACKABLE_TRACKING_TARGET "comsig_trackable_tracking_target"
diff --git a/code/__DEFINES/dcs/signals/signals_client.dm b/code/__DEFINES/dcs/signals/signals_client.dm
new file mode 100644
index 000000000000..07e5251cad3b
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_client.dm
@@ -0,0 +1,14 @@
+// from /client/proc/change_view() : (new_size)
+#define COMSIG_VIEW_SET "view_set"
+
+// from /client/proc/handle_popup_close() : (window_id)
+#define COMSIG_POPUP_CLEARED "popup_cleared"
+
+/// Called after one or more verbs are added: (list of verbs added)
+#define COMSIG_CLIENT_VERB_ADDED "client_verb_added"
+
+/// Called after one or more verbs are added: (list of verbs added)
+#define COMSIG_CLIENT_VERB_REMOVED "client_verb_removed"
+
+/// Called after a client logs into a mob: (mob)
+#define COMSIG_CLIENT_MOB_LOGIN "client_mob_changed"
diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm
index 9b3067bc8a45..6b516a07da0d 100644
--- a/code/__DEFINES/dcs/signals/signals_datum.dm
+++ b/code/__DEFINES/dcs/signals/signals_datum.dm
@@ -1,22 +1,47 @@
-// Format:
+// Datum signals. Format:
// When the signal is called: (signal arguments)
// All signals send the source datum of the signal as the first argument
// /datum signals
-#define COMSIG_COMPONENT_ADDED "component_added" //! when a component is added to a datum: (/datum/component)
-#define COMSIG_COMPONENT_REMOVING "component_removing" //! before a component is removed from a datum because of RemoveComponent: (/datum/component)
-#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //! before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
-#define COMSIG_PARENT_QDELETING "parent_qdeleting" //! just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called
-#define COMSIG_TOPIC "handle_topic" //! generic topic handler (usr, href_list)
+/// when a component is added to a datum: (/datum/component)
+#define COMSIG_COMPONENT_ADDED "component_added"
+/// before a component is removed from a datum because of ClearFromParent: (/datum/component)
+#define COMSIG_COMPONENT_REMOVING "component_removing"
+
+/// before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation
+/// you should only be using this if you want to block deletion
+/// that's the only functional difference between it and COMSIG_QDELETING, outside setting QDELETING to detect
+#define COMSIG_PREQDELETED "parent_preqdeleted"
+//! just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called
+#define COMSIG_QDELETING "parent_qdeleting"
+/// generic topic handler (usr, href_list)
+#define COMSIG_TOPIC "handle_topic"
+/// handler for vv_do_topic (usr, href_list)
+#define COMSIG_VV_TOPIC "vv_topic"
+ #define COMPONENT_VV_HANDLED (1<<0)
+/// from datum ui_act (usr, action)
+#define COMSIG_UI_ACT "COMSIG_UI_ACT"
/// fires on the target datum when an element is attached to it (/datum/element)
#define COMSIG_ELEMENT_ATTACH "element_attach"
/// fires on the target datum when an element is attached to it (/datum/element)
#define COMSIG_ELEMENT_DETACH "element_detach"
-// Antagonist signals
-/// Called on the mind when an antagonist is being gained, after the antagonist list has updated (datum/antagonist/antagonist)
-#define COMSIG_ANTAGONIST_GAINED "antagonist_gained"
+// Merger datum signals
+/// Called on the object being added to a merger group: (datum/merger/new_merger)
+#define COMSIG_MERGER_ADDING "comsig_merger_adding"
+/// Called on the object being removed from a merger group: (datum/merger/old_merger)
+#define COMSIG_MERGER_REMOVING "comsig_merger_removing"
+/// Called on the merger after finishing a refresh: (list/leaving_members, list/joining_members)
+#define COMSIG_MERGER_REFRESH_COMPLETE "comsig_merger_refresh_complete"
+
+// Gas mixture signals
+/// From /datum/gas_mixture/proc/merge: ()
+#define COMSIG_GASMIX_MERGED "comsig_gasmix_merged"
+/// From /datum/gas_mixture/proc/remove: ()
+#define COMSIG_GASMIX_REMOVED "comsig_gasmix_removed"
+/// From /datum/gas_mixture/proc/react: ()
+#define COMSIG_GASMIX_REACTED "comsig_gasmix_reacted"
-/// Called on the mind when an antagonist is being removed, after the antagonist list has updated (datum/antagonist/antagonist)
-#define COMSIG_ANTAGONIST_REMOVED "antagonist_removed"
+///from /datum/bank_account/pay_debt(), after a portion or all the debt has been paid.
+#define COMSIG_BANK_ACCOUNT_DEBT_PAID "bank_account_debt_paid"
diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm
index 79e35ec3feed..b751f86bf679 100644
--- a/code/__DEFINES/dcs/signals/signals_global.dm
+++ b/code/__DEFINES/dcs/signals/signals_global.dm
@@ -51,6 +51,10 @@
#define LINKED_UP (1<<0)
/// an obj/item is created! (obj/item/created_item)
#define COMSIG_GLOB_NEW_ITEM "!new_item"
+/// called post /obj/item initialize (obj/item/created_item)
+#define COMSIG_GLOB_ATOM_AFTER_POST_INIT "!atom_after_post_init"
+/// an obj/machinery is created! (obj/machinery/created_machine)
+#define COMSIG_GLOB_NEW_MACHINE "!new_machine"
/// a client (re)connected, after all /client/New() checks have passed : (client/connected_client)
#define COMSIG_GLOB_CLIENT_CONNECT "!client_connect"
/// a weather event of some kind occured
@@ -74,3 +78,7 @@
#define COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED "!light_mechanism_completed"
/// Global Signal sent when the crew wins the revolution (No arguments).
#define COMSIG_GLOB_REVOLUTION_VICTORY "!revolution_victory"
+/// Global signal sent when narsie summon count is updated: (new count)
+#define COMSIG_NARSIE_SUMMON_UPDATE "!narsie_summon_update"
+/// Global signal when starlight color is changed (old_star, new_star)
+#define COMSIG_STARLIGHT_COLOR_CHANGED "!starlight_color_changed"
diff --git a/code/__DEFINES/dcs/signals/signals_huds.dm b/code/__DEFINES/dcs/signals/signals_huds.dm
new file mode 100644
index 000000000000..2d5d3eaa59cc
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_huds.dm
@@ -0,0 +1,10 @@
+/// Sent from /datum/hud/proc/on_eye_change(): (atom/old_eye, atom/new_eye)
+#define COMSIG_HUD_EYE_CHANGED "hud_eye_changed"
+/// Sent from /datum/hud/proc/eye_z_changed() : (old_offset, new_offset)
+#define COMSIG_HUD_OFFSET_CHANGED "hud_offset_changed"
+/// Sent from /atom/movable/screen/lobby/button/collapse/proc/collapse_buttons() : ()
+#define COMSIG_HUD_LOBBY_COLLAPSED "hud_lobby_collapsed"
+/// Sent from /atom/movable/screen/lobby/button/collapse/proc/expand_buttons() : ()
+#define COMSIG_HUD_LOBBY_EXPANDED "hud_lobby_expanded"
+/// Sent from /atom/movable/screen/lobby/button/ready/Click() : ()
+#define COMSIG_HUD_PLAYER_READY_TOGGLE "hud_player_ready_toggle"
diff --git a/code/__DEFINES/dcs/signals/signals_ladder.dm b/code/__DEFINES/dcs/signals/signals_ladder.dm
new file mode 100644
index 000000000000..ed398e9012e8
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_ladder.dm
@@ -0,0 +1,3 @@
+/// Called on a mob attempting to use a ladder to go in either direction. (entrance_ladder, exit_ladder, going_up)
+#define COMSIG_LADDER_TRAVEL "ladder-travel"
+ #define LADDER_TRAVEL_BLOCK (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_lazy_templates.dm b/code/__DEFINES/dcs/signals/signals_lazy_templates.dm
new file mode 100644
index 000000000000..1c6ce7926ea5
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_lazy_templates.dm
@@ -0,0 +1,2 @@
+/// Fired on the lazy template datum when the template is finished loading. (list/loaded_atom_movables, list/loaded_turfs, list/loaded_areas)
+#define COMSIG_LAZY_TEMPLATE_LOADED "lazy_template_loaded"
diff --git a/code/__DEFINES/dcs/signals/signals_mind.dm b/code/__DEFINES/dcs/signals/signals_mind.dm
new file mode 100644
index 000000000000..72f43f518ebc
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mind.dm
@@ -0,0 +1,8 @@
+///from mind/transfer_to. Sent after the mind has been transferred: (mob/previous_body)
+#define COMSIG_MIND_TRANSFERRED "mind_transferred"
+
+/// Called on the mind when an antagonist is being gained, after the antagonist list has updated (datum/antagonist/antagonist)
+#define COMSIG_ANTAGONIST_GAINED "antagonist_gained"
+
+/// Called on the mind when an antagonist is being removed, after the antagonist list has updated (datum/antagonist/antagonist)
+#define COMSIG_ANTAGONIST_REMOVED "antagonist_removed"
diff --git a/code/__DEFINES/dcs/signals/signals_mining.dm b/code/__DEFINES/dcs/signals/signals_mining.dm
new file mode 100644
index 000000000000..3ef84b1f7b63
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mining.dm
@@ -0,0 +1,9 @@
+/// Sent from /obj/structure/ore_vent, lets the spawner component know to qdel.
+#define COMSIG_VENT_WAVE_CONCLUDED "mining_waves_stop"
+
+/// Fired by a mob which has been grabbed by a goliath
+#define COMSIG_GOLIATH_TENTACLED_GRABBED "comsig_goliath_tentacle_grabbed"
+/// Fired by a goliath tentacle which is returning to the earth
+#define COMSIG_GOLIATH_TENTACLE_RETRACTING "comsig_goliath_tentacle_retracting"
+/// Fired by a mob which has triggered a brimdust explosion from itself (not the mobs that get hit)
+#define COMSIG_BRIMDUST_EXPLOSION "comsig_brimdust_explosion"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm
index 56d9754eb42f..d9d114e4c1d3 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm
@@ -39,8 +39,6 @@
#define COMSIG_LIVING_SET_BODY_POSITION "living_set_body_position"
///From post-can inject check of syringe after attack (mob/user)
#define COMSIG_LIVING_TRY_SYRINGE "living_try_syringe"
-///From living/Life(seconds_per_tick = SSMOBS_DT, times_fired). (deltatime, times_fired)
-#define COMSIG_LIVING_LIFE "living_life"
///From living/set_resting(): (new_resting, silent, instant)
#define COMSIG_LIVING_RESTING "living_resting"
@@ -96,7 +94,12 @@
#define COMSIG_LIVING_MOB_BUMP "living_mob_bump"
///From base of mob/living/ZImpactDamage() (mob/living, levels, turf/t)
#define COMSIG_LIVING_Z_IMPACT "living_z_impact"
- #define NO_Z_IMPACT_DAMAGE (1<<0)
+ /// Just for the signal return, does not run normal living handing of z fall damage for mobs
+ #define ZIMPACT_CANCEL_DAMAGE (1<<0)
+ /// Do not show default z-impact message
+ #define ZIMPACT_NO_MESSAGE (1<<1)
+ /// Do not do the spin animation when landing
+ #define ZIMPACT_NO_SPIN (1<<2)
/// From mob/living/try_speak(): (message, ignore_spam, forced)
#define COMSIG_LIVING_TRY_SPEECH "living_vocal_speech"
@@ -137,5 +140,12 @@
/// From /mob/living/unfriend() : (mob/living/old_friend)
#define COMSIG_LIVING_UNFRIENDED "living_unfriended"
-///from mind/transfer_to. Sent after the mind has been transferred: (mob/previous_body)
-#define COMSIG_MIND_TRANSFERRED "mind_transferred"
+///From living/Life(). (deltatime, times_fired)
+#define COMSIG_LIVING_LIFE "living_life"
+ /// Block the Life() proc from proceeding... this should really only be done in some really wacky situations.
+ #define COMPONENT_LIVING_CANCEL_LIFE_PROCESSING (1<<0)
+
+///from /mob/create_typing_indicator()
+#define COMSIG_MOB_CREATE_TYPING_INDICATOR "create_typing_indicator"
+ ///Icon used for the typing indicator
+ #define BUBBLE_ICON_STATE 1
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
index 14d26b0ac454..3e017b4c77af 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm
@@ -7,6 +7,8 @@
#define COMSIG_MOB_LOGIN "mob_login"
///from base of /mob/Logout(): ()
#define COMSIG_MOB_LOGOUT "mob_logout"
+///from base of /mob/mind_initialize
+#define COMSIG_MOB_MIND_INITIALIZED "mob_mind_inited"
///from base of mob/set_stat(): (new_stat, old_stat)
#define COMSIG_MOB_STATCHANGE "mob_statchange"
///from base of mob/clickon(): (atom/A, params)
@@ -80,9 +82,6 @@
#define COMSIG_MOB_SIGHT_CHANGE "mob_sight_changed"
///from base of mob/set_invis_see(): (new_invis, old_invis)
#define COMSIG_MOB_SEE_INVIS_CHANGE "mob_see_invis_change"
-///from base of mob/set_see_in_dark(): (new_range, old_range)
-#define COMSIG_MOB_SEE_IN_DARK_CHANGE "mob_see_in_dark_change"
-
///from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone, blocked, wound_bonus, bare_wound_bonus, sharpness, attack_direction)
#define COMSIG_MOB_APPLY_DAMAGE "mob_apply_damage"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_spawner.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_spawner.dm
new file mode 100644
index 000000000000..6ff8b1e8d61d
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_spawner.dm
@@ -0,0 +1,6 @@
+// signals for use by mob spawners
+/// called when a spawner spawns a mob
+#define COMSIG_SPAWNER_SPAWNED "spawner_spawned"
+
+/// called when a ghost clicks a spawner role: (mob/living)
+#define COMSIG_GHOSTROLE_SPAWNED "ghostrole_spawned"
diff --git a/code/__DEFINES/dcs/signals/signals_modular_computer.dm b/code/__DEFINES/dcs/signals/signals_modular_computer.dm
new file mode 100644
index 000000000000..dd325e41984d
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_modular_computer.dm
@@ -0,0 +1,49 @@
+// Various modular computer signals.
+
+/// From /obj/item/modular_computer/proc/turn_on: (user)
+#define COMSIG_MODULAR_COMPUTER_TURNED_ON "comsig_modular_computer_turned_on"
+/// From /obj/item/modular_computer/proc/shutdown_computer: (loud)
+#define COMSIG_MODULAR_COMPUTER_SHUT_DOWN "comsig_modular_computer_shut_down"
+
+/// From /obj/item/modular_computer/proc/store_file: (datum/computer_file/file_storing)
+#define COMSIG_MODULAR_COMPUTER_FILE_STORE "comsig_modular_computer_file_store"
+/// From /obj/item/modular_computer/proc/remove_file: (datum/computer_file/file_removing)
+#define COMSIG_MODULAR_COMPUTER_FILE_DELETE "comsig_modular_computer_file_delete"
+/// From /obj/item/modular_computer/proc/store_file: (datum/computer_file/file_source, obj/item/modular_computer/host)
+#define COMSIG_COMPUTER_FILE_STORE "comsig_computer_file_store"
+/// From /obj/item/modular_computer/proc/store_file: ()
+#define COMSIG_COMPUTER_FILE_DELETE "comsig_computer_file_delete"
+
+/// From /obj/item/modular_computer/proc/InsertID: (inserting_id, user)
+#define COMSIG_MODULAR_COMPUTER_INSERTED_ID "comsig_computer_inserted_id"
+
+/// From /datum/computer_file/program/on_start: (user)
+#define COMSIG_COMPUTER_PROGRAM_START "computer_program_start"
+
+/// From /datum/computer_file/program/kill_program: (user)
+#define COMSIG_COMPUTER_PROGRAM_KILL "computer_program_kill"
+
+/// From /datum/computer_file/program/nt_pay/make_payment: (payment_result)
+#define COMSIG_MODULAR_COMPUTER_NT_PAY_RESULT "comsig_modular_computer_nt_pay_result"
+
+/// From /datum/computer_file/program/nt_pay/make_payment: (spookiness, manual)
+#define COMSIG_MODULAR_COMPUTER_SPECTRE_SCAN "comsig_modular_computer_spectre_scan"
+
+/// From /datum/computer_file/program/radar/trackable: (atom/signal, turf/signal_turf, turf/computer_turf)
+#define COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE "comsig_modular_computer_radar_trackable"
+ #define COMPONENT_RADAR_TRACK_ANYWAY (1<<0)
+ #define COMPONENT_RADAR_DONT_TRACK (1<<1)
+/// From /datum/computer_file/program/radar/find_atom: (list/atom_container)
+#define COMSIG_MODULAR_COMPUTER_RADAR_FIND_ATOM "comsig_modular_computer_radar_find_atom"
+/// From /datum/computer_file/program/radar/ui_act, when action is "selecttarget": (selected_ref)
+#define COMSIG_MODULAR_COMPUTER_RADAR_SELECTED "comsig_modular_computer_radar_selected"
+
+/// from /obj/item/modular_computer/imprint_id(): (name, job)
+#define COMSIG_MODULAR_PDA_IMPRINT_UPDATED "comsig_modular_pda_imprint_updated"
+/// from /obj/item/modular_computer/reset_id(): ()
+#define COMSIG_MODULAR_PDA_IMPRINT_RESET "comsig_modular_pda_imprint_reset"
+
+/// From /datum/computer_file/program/messenger/receive_message, sent to the computer: (signal/subspace/messaging/tablet_message/signal, sender_job, sender_name)
+#define COMSIG_MODULAR_PDA_MESSAGE_RECEIVED "comsig_modular_pda_message_received"
+/// From /datum/computer_file/program/messenger/send_message_signal, sent to the computer: (atom/origin, datum/signal/subspace/messaging/tablet_message/signal)
+#define COMSIG_MODULAR_PDA_MESSAGE_SENT "comsig_modular_pda_message_sent"
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index c31218971d51..836b08a8071f 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -200,6 +200,10 @@
///from [/obj/structure/closet/supplypod/proc/preOpen]:
#define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom"
+/// from [/obj/item/stack/proc/can_merge]: (obj/item/stack/merge_with, in_hand)
+#define COMSIG_STACK_CAN_MERGE "stack_can_merge"
+ #define CANCEL_STACK_MERGE (1<<0)
+
///from /obj/item/storage/book/bible/afterattack(): (mob/user, proximity)
#define COMSIG_BIBLE_SMACKED "bible_smacked"
///stops the bible chain from continuing. When all of the effects of the bible smacking have been moved to a signal we can kill this
diff --git a/code/__DEFINES/dcs/signals/signals_turf.dm b/code/__DEFINES/dcs/signals/signals_turf.dm
index 08b1a59d7bc0..43b9fea167c7 100644
--- a/code/__DEFINES/dcs/signals/signals_turf.dm
+++ b/code/__DEFINES/dcs/signals/signals_turf.dm
@@ -2,15 +2,41 @@
// When the signal is called: (signal arguments)
// All signals send the source datum of the signal as the first argument
-// /turf signals
-#define COMSIG_TURF_CHANGE "turf_change" //! from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps)
-#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity" //! from base of atom/has_gravity(): (atom/asker, list/forced_gravities)
-#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new" //! from base of turf/New(): (turf/source, direction)
-#define COMSIG_TURF_AFTER_SHUTTLE_MOVE "turf_after_shuttle_move" //! from base of turf/proc/afterShuttleMove: (turf/new_turf)
+/// from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/post_change_callbacks).
+/// `post_change_callbacks` is a list that signal handlers can mutate to append `/datum/callback` objects.
+/// They will be called with the new turf after the turf has changed.
+#define COMSIG_TURF_CHANGE "turf_change"
+///from base of atom/has_gravity(): (atom/asker, list/forced_gravities)
+#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity"
+///from base of turf/multiz_turf_del(): (turf/source, direction)
+#define COMSIG_TURF_MULTIZ_DEL "turf_multiz_del"
+///from base of turf/multiz_turf_new: (turf/source, direction)
+#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new"
+///from base of turf/proc/onShuttleMove(): (turf/new_turf)
+#define COMSIG_TURF_ON_SHUTTLE_MOVE "turf_on_shuttle_move"
///from base of /datum/turf_reservation/proc/Release: (datum/turf_reservation/reservation)
#define COMSIG_TURF_RESERVATION_RELEASED "turf_reservation_released"
+///from /turf/open/temperature_expose(datum/gas_mixture/air, exposed_temperature)
+#define COMSIG_TURF_EXPOSE "turf_expose"
+///from /turf/proc/immediate_calculate_adjacent_turfs()
+#define COMSIG_TURF_CALCULATED_ADJACENT_ATMOS "turf_calculated_adjacent_atmos"
+///called when an elevator enters this turf
+#define COMSIG_TURF_INDUSTRIAL_LIFT_ENTER "turf_industrial_life_enter"
+
+///from /datum/element/decal/Detach(): (description, cleanable, directional, mutable_appearance/pic)
+#define COMSIG_TURF_DECAL_DETACHED "turf_decal_detached"
+
+///from /obj/item/pushbroom/sweep(): (broom, user, items_to_sweep)
+#define COMSIG_TURF_RECEIVE_SWEEPED_ITEMS "turf_receive_sweeped_items"
///from /datum/element/footstep/prepare_step(): (list/steps)
#define COMSIG_TURF_PREPARE_STEP_SOUND "turf_prepare_step_sound"
+ //stops element/footstep/proc/prepare_step() from returning null if the turf itself has no sound
+ #define FOOTSTEP_OVERRIDDEN (1<<0)
///from base of datum/thrownthing/finalize(): (turf/turf, atom/movable/thrownthing) when something is thrown and lands on us
#define COMSIG_TURF_MOVABLE_THROW_LANDED "turf_movable_throw_landed"
+
+///From element/elevation/reset_elevation(): (list/values)
+#define COMSIG_TURF_RESET_ELEVATION "turf_reset_elevation"
+ #define ELEVATION_CURRENT_PIXEL_SHIFT 1
+ #define ELEVATION_MAX_PIXEL_SHIFT 2
diff --git a/code/__DEFINES/directional.dm b/code/__DEFINES/directional.dm
index 85d746a43882..bfde544b4471 100644
--- a/code/__DEFINES/directional.dm
+++ b/code/__DEFINES/directional.dm
@@ -4,7 +4,32 @@
// #define EAST 4
// #define WEST 8
+/// North direction as a string "[1]"
#define TEXT_NORTH "[NORTH]"
+/// South direction as a string "[2]"
#define TEXT_SOUTH "[SOUTH]"
+/// East direction as a string "[4]"
#define TEXT_EAST "[EAST]"
+/// West direction as a string "[8]"
#define TEXT_WEST "[WEST]"
+
+/// Inverse direction, taking into account UP|DOWN if necessary.
+#define REVERSE_DIR(dir) ( ((dir & 85) << 1) | ((dir & 170) >> 1) )
+
+/// Create directional subtypes for a path to simplify mapping.
+#define MAPPING_DIRECTIONAL_HELPERS(path, offset) ##path/directional/north {\
+ dir = NORTH; \
+ pixel_y = offset; \
+} \
+##path/directional/south {\
+ dir = SOUTH; \
+ pixel_y = -offset; \
+} \
+##path/directional/east {\
+ dir = EAST; \
+ pixel_x = offset; \
+} \
+##path/directional/west {\
+ dir = WEST; \
+ pixel_x = -offset; \
+}
diff --git a/code/__DEFINES/do_afters.dm b/code/__DEFINES/do_afters.dm
new file mode 100644
index 000000000000..dcedbbe010ba
--- /dev/null
+++ b/code/__DEFINES/do_afters.dm
@@ -0,0 +1,9 @@
+#define DOAFTER_SOURCE_SURGERY "doafter_surgery"
+#define DOAFTER_SOURCE_MECHADRILL "doafter_mechadrill"
+#define DOAFTER_SOURCE_SURVIVALPEN "doafter_survivalpen"
+#define DOAFTER_SOURCE_GETTING_UP "doafter_gettingup"
+#define DOAFTER_SOURCE_CLIMBING_LADDER "doafter_climbingladder"
+#define DOAFTER_SOURCE_SPIDER "doafter_spider"
+#define DOAFTER_SOURCE_HEAL_TOUCH "doafter_heal_touch"
+#define DOAFTER_SOURCE_PLANTING_DEVICE "doafter_planting_device"
+#define DOAFTER_SOURCE_CHARGE_CRANKRECHARGE "doafter_charge_crank_recharge"
diff --git a/code/__DEFINES/economy.dm b/code/__DEFINES/economy.dm
index a2d799a72fb5..94c029ad6ac9 100644
--- a/code/__DEFINES/economy.dm
+++ b/code/__DEFINES/economy.dm
@@ -8,6 +8,9 @@
/// Probability of using letters of envelope sprites on all letters.
#define FULL_CRATE_LETTER_ODDS 70
+//Current Paycheck values. Altering these changes both the cost of items meant for each paygrade, as well as the passive/starting income of each job.
+///Default paygrade for the Unassigned Job/Unpaid job assignments.
+#define PAYCHECK_ZERO 0
#define PAYCHECK_ASSISTANT 5
#define PAYCHECK_MINIMAL 5
#define PAYCHECK_EASY 20
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 92c389063f8a..0586b90352b9 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -20,53 +20,69 @@
GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768))
// for /datum/var/datum_flags
-#define DF_USE_TAG (1<<0)
-#define DF_VAR_EDITED (1<<1)
-#define DF_ISPROCESSING (1<<2)
+#define DF_USE_TAG (1<<0)
+#define DF_VAR_EDITED (1<<1)
+#define DF_ISPROCESSING (1<<2)
//FLAGS BITMASK
+/// item has priority to check when entering or leaving
+#define ON_BORDER_1 (1<<1)
/// This flag is what recursive_hear_check() uses to determine wether to add an item to the hearer list or not.
-#define HEAR_1 (1<<3)
+#define HEAR_1 (1<<2)
/// Projectiels will check ricochet on things impacted that have this.
-#define CHECK_RICOCHET_1 (1<<4)
-/// conducts electricity (metal etc.)
-#define CONDUCT_1 (1<<5)
+#define CHECK_RICOCHET_1 (1<<3)
+///specifies that this atom is a hologram that isnt real
+#define HOLOGRAM_1 (1<<4)
+///Whether /atom/Initialize(mapload) has already run for the object
+#define INITIALIZED_1 (1<<5)
+/// was this spawned by an admin? used for stat tracking stuff.
+#define ADMIN_SPAWNED_1 (1<<6)
/// For machines and structures that should not break into parts, eg, holodeck stuff
#define NODECONSTRUCT_1 (1<<7)
-/// item has priority to check when entering or leaving
-#define ON_BORDER_1 (1<<8)
/// Prevent clicking things below it on the same turf eg. doors/ fulltile windows
#define PREVENT_CLICK_UNDER_1 (1<<9)
-#define HOLOGRAM_1 (1<<10)
/// TESLA_IGNORE grants immunity from being targeted by tesla-style electricity
#define TESLA_IGNORE_1 (1<<11)
-///Whether /atom/Initialize(mapload) has already run for the object
-#define INITIALIZED_1 (1<<12)
-/// was this spawned by an admin? used for stat tracking stuff.
-#define ADMIN_SPAWNED_1 (1<<13)
+/// If a turf can be made dirty at roundstart. This is also used in areas.
+#define CAN_BE_DIRTY_1 (1<<12)
+/// Should we use the initial icon for display? Mostly used by overlay only objects
+#define HTML_USE_INITAL_ICON_1 (1<<13)
+/// conducts electricity (metal etc.)
+#define CONDUCT_1 (1<<14)
/// should not get harmed if this gets caught by an explosion?
-#define PREVENT_CONTENTS_EXPLOSION_1 (1<<14)
+#define PREVENT_CONTENTS_EXPLOSION_1 (1<<15)
/// should the contents of this atom be acted upon
-#define RAD_PROTECT_CONTENTS_1 (1 << 15)
+#define RAD_PROTECT_CONTENTS_1 (1<<16)
/// should this object be allowed to be contaminated
-#define RAD_NO_CONTAMINATE_1 (1 << 16)
+#define RAD_NO_CONTAMINATE_1 (1<<17)
/// Prevents most radiation on this turf from leaving it
-#define RAD_CONTAIN_CONTENTS (1<<17)
+#define RAD_CONTAIN_CONTENTS (1<<18)
/// Is the thing currently spinning?
-#define IS_SPINNING_1 (1<<18)
-
-//turf-only flags
-#define NOJAUNT_1 (1<<0)
-#define UNUSED_RESERVATION_TURF_1 (1<<1)
-/// If a turf can be made dirty at roundstart. This is also used in areas.
-#define CAN_BE_DIRTY_1 (1<<2)
+#define IS_SPINNING_1 (1<<19)
+/// If this atom has experienced a decal element "init finished" sourced appearance update
+/// We use this to ensure stacked decals don't double up appearance updates for no rasin
+/// Flag as an optimization, don't make this a trait without profiling
+/// Yes I know this is a stupid flag, no you can't take him from me
+#define DECAL_INIT_UPDATE_EXPERIENCED_1 (1<<20)
+
+//TURF FLAGS
+/// If a turf cant be jaunted through.
+#define NOJAUNT (1<<0)
+/// If a turf is an usused reservation turf awaiting assignment
+#define UNUSED_RESERVATION_TURF (1<<1)
+/// If a turf is a reserved turf
+#define RESERVATION_TURF (1<<2)
/// Blocks lava rivers being generated on the turf
-#define NO_LAVA_GEN_1 (1<<6)
+#define NO_LAVA_GEN (1<<3)
/// Blocks ruins spawning on the turf
-#define NO_RUINS_1 (1<<18)
+#define NO_RUINS (1<<4)
/// Blocks this turf from being rusted
-#define NO_RUST (1<<19)
+#define NO_RUST (1<<5)
+/// Is this turf is "solid". Space and lava aren't for instance
+#define IS_SOLID (1<<6)
+/// This turf will never be cleared away by other objects on Initialize.
+#define NO_CLEARING (1<<7)
//AREA FLAGS
/// If blobs can spawn there and if it counts towards their score.
@@ -122,7 +138,12 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define VENTCRAWLING (1<<2)
#define FLOATING (1<<3)
/// When moving, will Bump()/Cross()/Uncross() everything, but won't be stopped.
-#define UNSTOPPABLE (1<<4)
+#define PHASING (1<<4)
+/// The mob is walking on the ceiling. Or is generally just, upside down.
+#define UPSIDE_DOWN (1<<5)
+
+/// Combination flag for movetypes which, for all intents and purposes, mean the mob is not touching the ground
+#define MOVETYPES_NOT_TOUCHING_GROUND (FLYING|FLOATING|UPSIDE_DOWN)
//Fire and Acid stuff, for resistance_flags
#define LAVA_PROOF (1<<0)
diff --git a/code/__DEFINES/gradient.dm b/code/__DEFINES/gradient.dm
new file mode 100644
index 000000000000..6a920e322b09
--- /dev/null
+++ b/code/__DEFINES/gradient.dm
@@ -0,0 +1,3 @@
+// spacemandmm doesn't really implement gradient() right, so let's just handle that here yeah?
+#define hsl_gradient(index, args...) UNLINT(gradient(args, space = COLORSPACE_HSL, index))
+#define hsv_gradient(index, args...) UNLINT(gradient(args, space = COLORSPACE_HSV, index))
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index fdcc04a03b8f..900b77f28848 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -233,3 +233,8 @@
#define SCRN_OBJ_INSERT_FIRST "first"
/// The filter name for the hover outline
#define HOVER_OUTLINE_FILTER "hover_outline"
+// Plane group keys, used to group swaths of plane masters that need to appear in subwindows
+/// The primary group, holds everything on the main window
+#define PLANE_GROUP_MAIN "main"
+/// A secondary group, used when a client views a generic window
+#define PLANE_GROUP_POPUP_WINDOW(screen) "popup-[REF(screen)]"
diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm
new file mode 100644
index 000000000000..1275dd4b7180
--- /dev/null
+++ b/code/__DEFINES/icon_smoothing.dm
@@ -0,0 +1,235 @@
+/* smoothing_flags */
+/// Smoothing system in where adjacencies are calculated and used to build an image by mounting each corner at runtime.
+#define SMOOTH_CORNERS (1<<0)
+/// Smoothing system in where adjacencies are calculated and used to select a pre-baked icon_state, encoded by bitmasking.
+#define SMOOTH_BITMASK (1<<1)
+/// Atom has diagonal corners, with underlays under them.
+#define SMOOTH_DIAGONAL_CORNERS (1<<2)
+/// Atom will smooth with the borders of the map.
+#define SMOOTH_BORDER (1<<3)
+/// Atom is currently queued to smooth.
+#define SMOOTH_QUEUED (1<<4)
+/// Smooths with objects, and will thus need to scan turfs for contents.
+#define SMOOTH_OBJ (1<<5)
+/// Uses directional object smoothing, so we care not only about something being on the right turf, but also its direction
+/// Changes the meaning of smoothing_junction, instead of representing the directions we are smoothing in
+/// it represents the sides of our directional border object that have a neighbor
+/// Is incompatible with SMOOTH_CORNERS because border objects don't have corners
+#define SMOOTH_BORDER_OBJECT (1<<6)
+
+DEFINE_BITFIELD(smoothing_flags, list(
+ "SMOOTH_CORNERS" = SMOOTH_CORNERS,
+ "SMOOTH_BITMASK" = SMOOTH_BITMASK,
+ "SMOOTH_DIAGONAL_CORNERS" = SMOOTH_DIAGONAL_CORNERS,
+ "SMOOTH_BORDER" = SMOOTH_BORDER,
+ "SMOOTH_QUEUED" = SMOOTH_QUEUED,
+ "SMOOTH_OBJ" = SMOOTH_OBJ,
+ "SMOOTH_BORDER_OBJECT" = SMOOTH_BORDER_OBJECT,
+))
+
+/// Components of a smoothing junction
+/// Redefinitions of the diagonal directions so they can be stored in one var without conflicts
+#define NORTH_JUNCTION NORTH //(1<<0)
+#define SOUTH_JUNCTION SOUTH //(1<<1)
+#define EAST_JUNCTION EAST //(1<<2)
+#define WEST_JUNCTION WEST //(1<<3)
+#define NORTHEAST_JUNCTION (1<<4)
+#define SOUTHEAST_JUNCTION (1<<5)
+#define SOUTHWEST_JUNCTION (1<<6)
+#define NORTHWEST_JUNCTION (1<<7)
+
+DEFINE_BITFIELD(smoothing_junction, list(
+ "NORTH_JUNCTION" = NORTH_JUNCTION,
+ "SOUTH_JUNCTION" = SOUTH_JUNCTION,
+ "EAST_JUNCTION" = EAST_JUNCTION,
+ "WEST_JUNCTION" = WEST_JUNCTION,
+ "NORTHEAST_JUNCTION" = NORTHEAST_JUNCTION,
+ "SOUTHEAST_JUNCTION" = SOUTHEAST_JUNCTION,
+ "SOUTHWEST_JUNCTION" = SOUTHWEST_JUNCTION,
+ "NORTHWEST_JUNCTION" = NORTHWEST_JUNCTION,
+))
+
+/*smoothing macros*/
+
+#define QUEUE_SMOOTH(thing_to_queue) if(thing_to_queue.smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)) {SSicon_smooth.add_to_queue(thing_to_queue)}
+
+#define QUEUE_SMOOTH_NEIGHBORS(thing_to_queue) for(var/atom/atom_neighbor as anything in orange(1, thing_to_queue)) {QUEUE_SMOOTH(atom_neighbor)}
+
+/**SMOOTHING GROUPS
+ * Groups of things to smooth with.
+ * * Contained in the `list/smoothing_groups` variable.
+ * * Matched with the `list/canSmoothWith` variable to check whether smoothing is possible or not.
+ */
+
+#define S_TURF(num) (#num + ",")
+
+/* /turf only */
+
+#define SMOOTH_GROUP_TURF_OPEN S_TURF(0) ///turf/open
+#define SMOOTH_GROUP_TURF_CHASM S_TURF(1) ///turf/open/chasm, /turf/open/floor/fakepit
+#define SMOOTH_GROUP_FLOOR_LAVA S_TURF(2) ///turf/open/lava/smooth
+#define SMOOTH_GROUP_FLOOR_TRANSPARENT_GLASS S_TURF(3) ///turf/open/floor/glass
+
+#define SMOOTH_GROUP_OPEN_FLOOR S_TURF(4) ///turf/open/floor
+
+#define SMOOTH_GROUP_FLOOR_GRASS S_TURF(5) ///turf/open/misc/grass
+#define SMOOTH_GROUP_FLOOR_ASH S_TURF(6) ///turf/open/misc/ashplanet/ash
+#define SMOOTH_GROUP_FLOOR_ASH_ROCKY S_TURF(7) ///turf/open/misc/ashplanet/rocky
+#define SMOOTH_GROUP_FLOOR_ICE S_TURF(8) ///turf/open/misc/ice
+#define SMOOTH_GROUP_FLOOR_SNOWED S_TURF(9) ///turf/open/floor/plating/snowed
+
+#define SMOOTH_GROUP_CARPET S_TURF(10) ///turf/open/floor/carpet
+#define SMOOTH_GROUP_CARPET_BLACK S_TURF(11) ///turf/open/floor/carpet/black
+#define SMOOTH_GROUP_CARPET_BLUE S_TURF(12) ///turf/open/floor/carpet/blue
+#define SMOOTH_GROUP_CARPET_CYAN S_TURF(13) ///turf/open/floor/carpet/cyan
+#define SMOOTH_GROUP_CARPET_GREEN S_TURF(14) ///turf/open/floor/carpet/green
+#define SMOOTH_GROUP_CARPET_ORANGE S_TURF(15) ///turf/open/floor/carpet/orange
+#define SMOOTH_GROUP_CARPET_PURPLE S_TURF(16) ///turf/open/floor/carpet/purple
+#define SMOOTH_GROUP_CARPET_RED S_TURF(17) ///turf/open/floor/carpet/red
+#define SMOOTH_GROUP_CARPET_ROYAL_BLACK S_TURF(18) ///turf/open/indestructible/carpet/royalblack
+///Unimplemented because we haven't cut the icons yet
+#define SMOOTH_GROUP_CARPET_ROYAL_GREEN S_TURF(19) ///turf/open/indestructible/carpet/royalgreen
+#define SMOOTH_GROUP_CARPET_ROYAL_BLUE S_TURF(20) ///turf/open/indestructible/carpet/royalblue
+///Unimplemented because we haven't cut the icons yet
+#define SMOOTH_GROUP_CARPET_ROYAL_PURPLE S_TURF(21) ///turf/open/indestructible/carpet/royalpurple
+#define SMOOTH_GROUP_CARPET_EXECUTIVE S_TURF(22) ///turf/open/floor/carpet/executive
+#define SMOOTH_GROUP_CARPET_STELLAR S_TURF(23) ///turf/open/floor/carpet/stellar
+#define SMOOTH_GROUP_CARPET_DONK S_TURF(24) ///turf/open/floor/carpet/donk
+#define SMOOTH_GROUP_CARPET_NEON S_TURF(25) //![turf/open/floor/carpet/neon]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON S_TURF(26) //![turf/open/floor/carpet/neon/simple]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_WHITE S_TURF(27) //![turf/open/floor/carpet/neon/simple/white]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_BLACK S_TURF(28) //![turf/open/floor/carpet/neon/simple/black]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_RED S_TURF(29) //![turf/open/floor/carpet/neon/simple/red]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_ORANGE S_TURF(30) //![turf/open/floor/carpet/neon/simple/orange]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_YELLOW S_TURF(31) //![turf/open/floor/carpet/neon/simple/yellow]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_LIME S_TURF(32) //![turf/open/floor/carpet/neon/simple/lime]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_GREEN S_TURF(33) //![turf/open/floor/carpet/neon/simple/green]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_TEAL S_TURF(34) //![turf/open/floor/carpet/neon/simple/teal]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_CYAN S_TURF(35) //![turf/open/floor/carpet/neon/simple/cyan]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_BLUE S_TURF(36) //![turf/open/floor/carpet/neon/simple/blue]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_PURPLE S_TURF(37) //![turf/open/floor/carpet/neon/simple/purple]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_VIOLET S_TURF(38) //![turf/open/floor/carpet/neon/simple/violet]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_PINK S_TURF(39) //![turf/open/floor/carpet/neon/simple/pink]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_NODOTS S_TURF(40) //![turf/open/floor/carpet/neon/simple/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_WHITE_NODOTS S_TURF(41) //![turf/open/floor/carpet/neon/simple/white/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_BLACK_NODOTS S_TURF(42) //![turf/open/floor/carpet/neon/simple/black/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_RED_NODOTS S_TURF(43) //![turf/open/floor/carpet/neon/simple/red/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_ORANGE_NODOTS S_TURF(44) //![turf/open/floor/carpet/neon/simple/orange/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_YELLOW_NODOTS S_TURF(45) //![turf/open/floor/carpet/neon/simple/yellow/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_LIME_NODOTS S_TURF(46) //![turf/open/floor/carpet/neon/simple/lime/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_GREEN_NODOTS S_TURF(47) //![turf/open/floor/carpet/neon/simple/green/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_TEAL_NODOTS S_TURF(48) //![turf/open/floor/carpet/neon/simple/teal/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_CYAN_NODOTS S_TURF(49) //![turf/open/floor/carpet/neon/simple/cyan/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_BLUE_NODOTS S_TURF(50) //![turf/open/floor/carpet/neon/simple/blue/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_PURPLE_NODOTS S_TURF(51) //![turf/open/floor/carpet/neon/simple/purple/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_VIOLET_NODOTS S_TURF(52) //![turf/open/floor/carpet/neon/simple/violet/nodots]
+#define SMOOTH_GROUP_CARPET_SIMPLE_NEON_PINK_NODOTS S_TURF(53) //![turf/open/floor/carpet/neon/simple/pink/nodots]
+#define SMOOTH_GROUP_BAMBOO_FLOOR S_TURF(54) //![/turf/open/floor/bamboo]
+#define SMOOTH_GROUP_BRAZIL S_TURF(55) ///turf/open/indestructible/brazil/lostit
+
+#define SMOOTH_GROUP_CLOSED_TURFS S_TURF(56) ///turf/closed
+#define SMOOTH_GROUP_MATERIAL_WALLS S_TURF(57) ///turf/closed/wall/material
+#define SMOOTH_GROUP_SYNDICATE_WALLS S_TURF(58) ///turf/closed/wall/r_wall/syndicate, /turf/closed/indestructible/syndicate
+#define SMOOTH_GROUP_HOTEL_WALLS S_TURF(59) ///turf/closed/indestructible/hotelwall
+#define SMOOTH_GROUP_MINERAL_WALLS S_TURF(60) ///turf/closed/mineral, /turf/closed/indestructible
+#define SMOOTH_GROUP_BOSS_WALLS S_TURF(61) ///turf/closed/indestructible/riveted/boss
+#define SMOOTH_GROUP_SURVIVAL_TITANIUM_WALLS S_TURF(62) ///turf/closed/wall/mineral/titanium/survival
+#define SMOOTH_GROUP_TURF_OPEN_CLIFF S_TURF(63) ///turf/open/cliff
+#define SMOOTH_GROUP_TURF_BALLPIT S_TURF(64) ///turf/open/floor/ballpit
+
+#define MAX_S_TURF 64 //Always match this value with the one above it.
+
+#define S_OBJ(num) ("-" + #num + ",")
+/* /obj included */
+
+#define SMOOTH_GROUP_WALLS S_OBJ(1) ///turf/closed/wall, /obj/structure/falsewall
+#define SMOOTH_GROUP_URANIUM_WALLS S_OBJ(2) ///turf/closed/wall/mineral/uranium, /obj/structure/falsewall/uranium
+#define SMOOTH_GROUP_GOLD_WALLS S_OBJ(3) ///turf/closed/wall/mineral/gold, /obj/structure/falsewall/gold
+#define SMOOTH_GROUP_SILVER_WALLS S_OBJ(4) ///turf/closed/wall/mineral/silver, /obj/structure/falsewall/silver
+#define SMOOTH_GROUP_DIAMOND_WALLS S_OBJ(5) ///turf/closed/wall/mineral/diamond, /obj/structure/falsewall/diamond
+#define SMOOTH_GROUP_PLASMA_WALLS S_OBJ(6) ///turf/closed/wall/mineral/plasma, /obj/structure/falsewall/plasma
+#define SMOOTH_GROUP_BANANIUM_WALLS S_OBJ(7) ///turf/closed/wall/mineral/bananium, /obj/structure/falsewall/bananium
+#define SMOOTH_GROUP_SANDSTONE_WALLS S_OBJ(8) ///turf/closed/wall/mineral/sandstone, /obj/structure/falsewall/sandstone
+#define SMOOTH_GROUP_WOOD_WALLS S_OBJ(9) ///turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood
+#define SMOOTH_GROUP_IRON_WALLS S_OBJ(10) ///turf/closed/wall/mineral/iron, /obj/structure/falsewall/iron
+#define SMOOTH_GROUP_ABDUCTOR_WALLS S_OBJ(11) ///turf/closed/wall/mineral/abductor, /obj/structure/falsewall/abductor
+#define SMOOTH_GROUP_TITANIUM_WALLS S_OBJ(12) ///turf/closed/wall/mineral/titanium, /obj/structure/falsewall/titanium
+#define SMOOTH_GROUP_PLASTITANIUM_WALLS S_OBJ(14) ///turf/closed/wall/mineral/plastitanium, /obj/structure/falsewall/plastitanium
+#define SMOOTH_GROUP_SURVIVAL_TITANIUM_POD S_OBJ(15) ///turf/closed/wall/mineral/titanium/survival/pod, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/reinforced/shuttle/survival_pod
+#define SMOOTH_GROUP_HIERO_WALL S_OBJ(16) ///obj/effect/temp_visual/elite_tumor_wall, /obj/effect/temp_visual/hierophant/wall
+#define SMOOTH_GROUP_BAMBOO_WALLS S_TURF(17) //![/turf/closed/wall/mineral/bamboo, /obj/structure/falsewall/bamboo]
+#define SMOOTH_GROUP_PLASTINUM_WALLS S_TURF(18) //![turf/closed/indestructible/riveted/plastinum]
+#define SMOOTH_GROUP_CLOCKWORK_WALLS S_TURF(19) //![/turf/closed/wall/clockwork, /obj/structure/falsewall/brass]
+
+#define SMOOTH_GROUP_PAPERFRAME S_OBJ(21) ///obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe
+
+#define SMOOTH_GROUP_WINDOW_FULLTILE S_OBJ(22) ///turf/closed/indestructible/fakeglass, /obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/reinforced/plasma/fulltile
+#define SMOOTH_GROUP_WINDOW_FULLTILE_BRONZE S_OBJ(23) ///obj/structure/window/bronze/fulltile
+#define SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM S_OBJ(24) ///turf/closed/indestructible/opsglass, /obj/structure/window/reinforced/plasma/plastitanium
+#define SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE S_OBJ(25) ///obj/structure/window/reinforced/shuttle
+
+#define SMOOTH_GROUP_WINDOW_DIRECTIONAL_TRAM S_OBJ(26) ///obj/structure/tram
+
+#define SMOOTH_GROUP_GRILLE S_OBJ(30) ///obj/structure/grille
+#define SMOOTH_GROUP_LATTICE S_OBJ(31) ///obj/structure/lattice
+#define SMOOTH_GROUP_CATWALK S_OBJ(32) ///obj/structure/lattice/catwalk
+
+#define SMOOTH_GROUP_AIRLOCK S_OBJ(41) ///obj/machinery/door/airlock
+
+#define SMOOTH_GROUP_INDUSTRIAL_LIFT S_OBJ(46) ///obj/structure/transport/linear
+#define SMOOTH_GROUP_TRAM_STRUCTURE S_OBJ(47) //obj/structure/tram
+
+#define SMOOTH_GROUP_TABLES S_OBJ(51) ///obj/structure/table
+#define SMOOTH_GROUP_WOOD_TABLES S_OBJ(52) ///obj/structure/table/wood
+#define SMOOTH_GROUP_FANCY_WOOD_TABLES S_OBJ(53) ///obj/structure/table/wood/fancy
+#define SMOOTH_GROUP_BRONZE_TABLES S_OBJ(54) ///obj/structure/table/bronze
+#define SMOOTH_GROUP_ABDUCTOR_TABLES S_OBJ(55) ///obj/structure/table/abductor
+#define SMOOTH_GROUP_GLASS_TABLES S_OBJ(56) ///obj/structure/table/glass
+#define SMOOTH_GROUP_BANANIUM_TABLES S_OBJ(57) ///obj/structure/table/glass
+
+#define SMOOTH_GROUP_ALIEN_NEST S_OBJ(60) ///obj/structure/bed/nest
+#define SMOOTH_GROUP_ALIEN_RESIN S_OBJ(61) ///obj/structure/alien/resin
+#define SMOOTH_GROUP_ALIEN_WALLS S_OBJ(62) ///obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane
+#define SMOOTH_GROUP_ALIEN_WEEDS S_OBJ(63) ///obj/structure/alien/weeds
+
+#define SMOOTH_GROUP_SECURITY_BARRICADE S_OBJ(64) ///obj/structure/barricade/security
+#define SMOOTH_GROUP_SANDBAGS S_OBJ(65) ///obj/structure/barricade/sandbags
+
+#define SMOOTH_GROUP_HEDGE_FLUFF S_OBJ(66) ///obj/structure/hedge
+
+#define SMOOTH_GROUP_SHUTTLE_PARTS S_OBJ(67) ///obj/structure/window/reinforced/shuttle, /obj/structure/window/reinforced/plasma/plastitanium, /turf/closed/indestructible/opsglass, /obj/machinery/power/shuttle_engine
+
+#define SMOOTH_GROUP_CLEANABLE_DIRT S_OBJ(68) ///obj/effect/decal/cleanable/dirt
+
+#define SMOOTH_GROUP_GAS_TANK S_OBJ(72)
+
+
+/// Performs the work to set smoothing_groups and canSmoothWith.
+/// An inlined function used in both turf/Initialize and atom/Initialize.
+#define SETUP_SMOOTHING(...) \
+ if (smoothing_groups) { \
+ if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) { \
+ ASSERT_SORTED_SMOOTHING_GROUPS(smoothing_groups); \
+ } \
+ SET_SMOOTHING_GROUPS(smoothing_groups); \
+ } \
+\
+ if (canSmoothWith) { \
+ if (PERFORM_ALL_TESTS(focus_only/sorted_smoothing_groups)) { \
+ ASSERT_SORTED_SMOOTHING_GROUPS(canSmoothWith); \
+ } \
+ /* S_OBJ is always negative, and we are guaranteed to be sorted. */ \
+ if (canSmoothWith[1] == "-") { \
+ smoothing_flags |= SMOOTH_OBJ; \
+ } \
+ SET_SMOOTHING_GROUPS(canSmoothWith); \
+ }
+
+/// Given a smoothing groups variable, will set out to the actual numbers inside it
+#define UNWRAP_SMOOTHING_GROUPS(smoothing_groups, out) \
+ json_decode("\[[##smoothing_groups]0\]"); \
+ ##out.len--;
+
+#define ASSERT_SORTED_SMOOTHING_GROUPS(smoothing_group_variable) \
+ var/list/unwrapped = UNWRAP_SMOOTHING_GROUPS(smoothing_group_variable, unwrapped); \
+ assert_sorted(unwrapped, "[#smoothing_group_variable] ([type])"); \
diff --git a/code/__DEFINES/interaction_flags.dm b/code/__DEFINES/interaction_flags.dm
index a85ffb8d7a09..25e31118fd17 100644
--- a/code/__DEFINES/interaction_flags.dm
+++ b/code/__DEFINES/interaction_flags.dm
@@ -36,3 +36,5 @@
/// This flag determines if a machine set_machine's the user when the user uses it, making updateUsrDialog make the user re-call interact() on it.
/// THIS FLAG IS ON ALL MACHINES BY DEFAULT, NEEDS TO BE RE-EVALUATED LATER!!
#define INTERACT_MACHINE_SET_MACHINE (1<<6)
+/// the user must have vision to interact (blind people need not apply)
+#define INTERACT_MACHINE_REQUIRES_SIGHT (1<<7)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 128866d2f223..f8f9e3ff4819 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -24,7 +24,9 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
/turf/open/space,
/turf/open/chasm,
/turf/open/lava,
- /turf/open/water
+ /turf/open/water,
+ /turf/open/openspace,
+ /turf/open/space/openspace
)))
#define isgroundlessturf(A) (is_type_in_typecache(A, GLOB.turfs_without_ground))
@@ -176,7 +178,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define iscameramob(A) (istype(A, /mob/camera))
-#define isaicamera(A) (istype(A, /mob/camera/aiEye))
+#define isaicamera(A) (istype(A, /mob/camera/ai_eye))
#define iseminence(A) (istype(A, /mob/camera/eminence))
@@ -195,7 +197,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define ismecha(A) (istype(A, /obj/mecha))
-#define ismopable(A) (A.loc.layer <= HIGH_SIGIL_LAYER) //If something can be cleaned by floor-cleaning devices such as mops or clean bots
+#define ismopable(A) (A && (A.layer <= FLOOR_CLEAN_LAYER)) //If something can be cleaned by floor-cleaning devices such as mops or clean bots
#define isorgan(A) (istype(A, /obj/item/organ))
@@ -234,9 +236,34 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define isblobmonster(O) (istype(O, /mob/living/simple_animal/hostile/blob))
-#define isshuttleturf(T) (length(T.baseturfs) && (/turf/baseturf_skipover/shuttle in T.baseturfs))
+#define isshuttleturf(T) (!isnull(T.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)))
//Fugitive
#define isfugitive(M) (istype(M) && M.mind?.has_antag_datum(/datum/antagonist/fugitive))
#define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20)
+
+GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list(
+ /turf/open/openspace,
+ /turf/open/space/openspace
+ )))
+
+#define istransparentturf(A) (HAS_TRAIT(A, TURF_Z_TRANSPARENT_TRAIT))
+
+#define isopenspaceturf(A) (is_type_in_typecache(A, GLOB.turfs_openspace))
+
+// Jobs
+#define is_job(job_type) (istype(job_type, /datum/job))
+#define is_assistant_job(job_type) (istype(job_type, /datum/job/assistant))
+#define is_bartender_job(job_type) (istype(job_type, /datum/job/bartender))
+#define is_captain_job(job_type) (istype(job_type, /datum/job/captain))
+#define is_chaplain_job(job_type) (istype(job_type, /datum/job/chaplain))
+#define is_clown_job(job_type) (istype(job_type, /datum/job/clown))
+#define is_detective_job(job_type) (istype(job_type, /datum/job/detective))
+#define is_scientist_job(job_type) (istype(job_type, /datum/job/scientist))
+#define is_security_officer_job(job_type) (istype(job_type, /datum/job/security_officer))
+#define is_research_director_job(job_type) (istype(job_type, /datum/job/research_director))
+#define is_unassigned_job(job_type) (istype(job_type, /datum/job/unassigned))
+
+#define isprojectilespell(thing) (istype(thing, /datum/action/cooldown/spell/pointed/projectile))
+#define is_multi_tile_object(atom) (atom.bound_width > world.icon_size || atom.bound_height > world.icon_size)
diff --git a/code/__DEFINES/lag_switch.dm b/code/__DEFINES/lag_switch.dm
new file mode 100644
index 000000000000..2115ce4a5dd4
--- /dev/null
+++ b/code/__DEFINES/lag_switch.dm
@@ -0,0 +1,20 @@
+// All of the possible Lag Switch lag mitigation measures
+// If you add more do not forget to update MEASURES_AMOUNT accordingly
+/// Stops ghosts flying around freely, they can still jump and orbit, staff exempted
+#define DISABLE_DEAD_KEYLOOP 1
+/// Stops ghosts using zoom/t-ray verbs and resets their view if zoomed out, staff exempted
+#define DISABLE_GHOST_ZOOM_TRAY 2
+/// Disable runechat and enable the bubbles, speaking mobs with TRAIT_BYPASS_MEASURES exempted
+#define DISABLE_RUNECHAT 3
+/// Disable icon2html procs from verbs like examine, mobs calling with TRAIT_BYPASS_MEASURES exempted
+#define DISABLE_USR_ICON2HTML 4
+/// Prevents anyone from joining the game as anything but observer
+#define DISABLE_NON_OBSJOBS 5
+/// Limit IC/dchat spam to one message every x seconds per client, TRAIT_BYPASS_MEASURES exempted
+#define SLOWMODE_SAY 6
+/// Disables parallax, as if everyone had disabled their preference, TRAIT_BYPASS_MEASURES exempted
+#define DISABLE_PARALLAX 7
+/// Disables footsteps, TRAIT_BYPASS_MEASURES exempted
+#define DISABLE_FOOTSTEPS 8
+
+#define MEASURES_AMOUNT 8 // The total number of switches defined above
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index 3ce945e5eecc..211dc52c42d3 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -1,166 +1,320 @@
//Defines for atom layers and planes
//KEEP THESE IN A NICE ACSCENDING ORDER, PLEASE
-#define CLICKCATCHER_PLANE -99
+//NEVER HAVE ANYTHING BELOW THIS PLANE ADJUST IF YOU NEED MORE SPACE
+#define LOWEST_EVER_PLANE -50
-#define PLANE_SPACE -95
-#define PLANE_SPACE_RENDER_TARGET "PLANE_SPACE"
-#define PLANE_SPACE_PARALLAX -90
-#define PLANE_SPACE_PARALLAX_RENDER_TARGET "PLANE_SPACE_PARALLAX"
+// Doesn't really layer, just throwing this in here cause it's the best place imo
+#define FIELD_OF_VISION_BLOCKER_PLANE -45
+#define FIELD_OF_VISION_BLOCKER_RENDER_TARGET "*FIELD_OF_VISION_BLOCKER_RENDER_TARGET"
-#define SINGULARITY_EFFECT_PLANE -80
-#define SINGULARITY_RENDER_TARGET "*SINGULARITY_EFFECTS_PLANE"
+#define CLICKCATCHER_PLANE -40
-#define FLOOR_PLANE -2
-#define FLOOR_PLANE_RENDER_TARGET "FLOOR_PLANE"
-#define GAME_PLANE -1
-#define GAME_PLANE_RENDER_TARGET "GAME_PLANE"
-#define BLACKNESS_PLANE 0 //To keep from conflicts with SEE_BLACKNESS internals
-#define BLACKNESS_PLANE_RENDER_TARGET "BLACKNESS_PLANE"
+#define PLANE_SPACE -21
+#define PLANE_SPACE_PARALLAX -20
+#define GRAVITY_PULSE_PLANE -12
+#define GRAVITY_PULSE_RENDER_TARGET "*GRAVPULSE_RENDER_TARGET"
+
+#define RENDER_PLANE_TRANSPARENT -11 //Transparent plane that shows openspace underneath the floor
+
+#define TRANSPARENT_FLOOR_PLANE -10
+
+#define FLOOR_PLANE -6
+
+#define WALL_PLANE -5
+#define GAME_PLANE -4
+#define ABOVE_GAME_PLANE -3
+///Slightly above the game plane but does not catch mouse clicks. Useful for certain visuals that should be clicked through, like seethrough trees
+#define SEETHROUGH_PLANE -2
+
+#define RENDER_PLANE_GAME_WORLD -1
+
+#define DEFAULT_PLANE 0 //Marks out the default plane, even if we don't use it
+
+#define AREA_PLANE 2
+#define MASSIVE_OBJ_PLANE 3
+#define GHOST_PLANE 4
+#define POINT_PLANE 5
+
+//---------- LIGHTING -------------
+///Normal 1 per turf dynamic lighting underlays
+#define LIGHTING_PLANE 10
+
+///Lighting objects that are "free floating"
+#define O_LIGHTING_VISUAL_PLANE 11
+#define O_LIGHTING_VISUAL_RENDER_TARGET "O_LIGHT_VISUAL_PLANE"
+
+#define EMISSIVE_PLANE 13
+/// This plane masks out lighting to create an "emissive" effect, ie for glowing lights in otherwise dark areas.
+#define EMISSIVE_RENDER_PLATE 14
+#define EMISSIVE_RENDER_TARGET "*EMISSIVE_PLANE"
+// Ensures all the render targets that point at the emissive plate layer correctly
+#define EMISSIVE_Z_BELOW_LAYER 1
+#define EMISSIVE_FLOOR_LAYER 2
+#define EMISSIVE_SPACE_LAYER 3
+#define EMISSIVE_WALL_LAYER 4
+
+#define RENDER_PLANE_LIGHTING 15
+
+/// Masks the lighting plane with turfs, so we never light up the void
+/// Failing that, masks emissives and the overlay lighting plane
+#define LIGHT_MASK_PLANE 16
+#define LIGHT_MASK_RENDER_TARGET "*LIGHT_MASK_PLANE"
+
+///Things that should render ignoring lighting
+#define ABOVE_LIGHTING_PLANE 17
+
+///---------------- MISC -----------------------
+
+///Pipecrawling images
+#define PIPECRAWL_IMAGES_PLANE 20
+
+///AI Camera Static
+#define CAMERA_STATIC_PLANE 21
+
+///Anything that wants to be part of the game plane, but also wants to draw above literally everything else
+#define HIGH_GAME_PLANE 22
+
+#define FULLSCREEN_PLANE 23
+
+///--------------- FULLSCREEN RUNECHAT BUBBLES ------------
+
+///Popup Chat Messages
+#define RUNECHAT_PLANE 30
+/// Plane for balloon text (text that fades up)
+#define BALLOON_CHAT_PLANE 31
+
+//-------------------- HUD ---------------------
+//HUD layer defines
+#define HUD_PLANE 35
+#define ABOVE_HUD_PLANE 36
+
+///Plane of the "splash" icon used that shows on the lobby screen
+#define SPLASHSCREEN_PLANE 37
+
+// The largest plane here must still be less than RENDER_PLANE_GAME
+
+//-------------------- Rendering ---------------------
+#define RENDER_PLANE_GAME 40
+/// If fov is enabled we'll draw game to this and do shit to it
+#define RENDER_PLANE_GAME_MASKED 41
+/// The bit of the game plane that is let alone is sent here
+#define RENDER_PLANE_GAME_UNMASKED 42
+#define RENDER_PLANE_NON_GAME 45
+
+// Only VERY special planes should be here, as they are above not just the game, but the UI planes as well.
+
+/// Plane related to the menu when pressing Escape.
+/// Needed so that we can apply a blur effect to EVERYTHING, and guarantee we are above all UI.
+#define ESCAPE_MENU_PLANE 46
+
+#define RENDER_PLANE_MASTER 50
+
+// Lummox I swear to god I will find you
+// NOTE! You can only ever have planes greater then -10000, if you add too many with large offsets you will brick multiz
+// Same can be said for large multiz maps. Tread carefully mappers
+#define HIGHEST_EVER_PLANE RENDER_PLANE_MASTER
+/// The range unique planes can be in
+/// Try and keep this to a nice whole number, so it's easy to look at a plane var and know what's going on
+#define PLANE_RANGE (HIGHEST_EVER_PLANE - LOWEST_EVER_PLANE)
+
+// PLANE_SPACE layer(s)
#define SPACE_LAYER 1.8
-//#define TURF_LAYER 2 //For easy recordkeeping; this is a byond define
+
+//#define TURF_LAYER 2 //For easy recordkeeping; this is a byond define. Most floors (FLOOR_PLANE) and walls (WALL_PLANE) use this.
+
+//FLOOR_PLANE layers
+#define TURF_PLATING_DECAL_LAYER 2.001
+#define TURF_DECAL_LAYER 2.009 //Makes turf decals appear in DM how they will look inworld.
+#define CULT_OVERLAY_LAYER 2.01
#define MID_TURF_LAYER 2.02
#define HIGH_TURF_LAYER 2.03
-#define TURF_PLATING_DECAL_LAYER 2.031
-#define TURF_DECAL_LAYER 2.039 //Makes turf decals appear in DM how they will look inworld.
-#define ABOVE_OPEN_TURF_LAYER 2.04
+#define LATTICE_LAYER 2.04
+#define DISPOSAL_PIPE_LAYER 2.042
+#define WIRE_LAYER 2.044
+#define GLASS_FLOOR_LAYER 2.046
+#define TRAM_RAIL_LAYER 2.047
+#define ABOVE_OPEN_TURF_LAYER 2.049
+
+//WALL_PLANE layers
#define CLOSED_TURF_LAYER 2.05
+
+// GAME_PLANE layers
#define BULLET_HOLE_LAYER 2.06
#define ABOVE_NORMAL_TURF_LAYER 2.08
-#define LATTICE_LAYER 2.2
-#define DISPOSAL_PIPE_LAYER 2.25
#define GAS_PIPE_HIDDEN_LAYER 2.35 //layer = initial(layer) + piping_layer / 1000 in atmospherics/update_icon() to determine order of pipe overlap
-#define WIRE_LAYER 2.4
+#define WIRE_BRIDGE_LAYER 2.44
#define WIRE_TERMINAL_LAYER 2.45
-#define UNDER_CATWALK 2.454
-#define CATWALK_LAYER 2.455
-#define GAS_SCRUBBER_LAYER 2.46
+#define GAS_SCRUBBER_LAYER 2.46
#define GAS_PIPE_VISIBLE_LAYER 2.47 //layer = initial(layer) + piping_layer / 1000 in atmospherics/update_icon() to determine order of pipe overlap
#define GAS_FILTER_LAYER 2.48
#define GAS_PUMP_LAYER 2.49
+#define PLUMBING_PIPE_VISIBILE_LAYER 2.495//layer = initial(layer) + ducting_layer / 3333 in atmospherics/handle_layer() to determine order of duct overlap
+#define BOT_PATH_LAYER 2.497
#define LOW_OBJ_LAYER 2.5
+///catwalk overlay of /turf/open/floor/plating/catwalk_floor
+#define CATWALK_LAYER 2.51
#define LOW_SIGIL_LAYER 2.52
-#define SIGIL_LAYER 2.54
-#define HIGH_SIGIL_LAYER 2.56
+#define SIGIL_LAYER 2.53
+#define HIGH_PIPE_LAYER 2.54
+// Anything above this layer is not "on" a turf for the purposes of washing
+// I hate this life of ours
+#define FLOOR_CLEAN_LAYER 2.55
+#define TRAM_STRUCTURE_LAYER 2.57
+#define TRAM_FLOOR_LAYER 2.58
+#define TRAM_WALL_LAYER 2.59
+
#define BELOW_OPEN_DOOR_LAYER 2.6
-#define GAS_METER_LAYER 2.61
+///Anything below this layer is to be considered completely (visually) under water by the immerse layer.
+#define WATER_LEVEL_LAYER 2.61
#define BLASTDOOR_LAYER 2.65
#define OPEN_DOOR_LAYER 2.7
-#define DOOR_HELPER_LAYER 2.71 //keep this above OPEN_DOOR_LAYER
+#define DOOR_ACCESS_HELPER_LAYER 2.71 //keep this above OPEN_DOOR_LAYER, special layer used for /obj/effect/mapping_helpers/airlock/access
+#define DOOR_HELPER_LAYER 2.72 //keep this above DOOR_ACCESS_HELPER_LAYER and OPEN_DOOR_LAYER since the others tend to have tiny sprites that tend to be covered up.
#define PROJECTILE_HIT_THRESHHOLD_LAYER 2.75 //projectiles won't hit objects at or below this layer if possible
#define TABLE_LAYER 2.8
+#define GATEWAY_UNDERLAY_LAYER 2.85
#define BELOW_OBJ_LAYER 2.9
#define LOW_ITEM_LAYER 2.95
//#define OBJ_LAYER 3 //For easy recordkeeping; this is a byond define
-#define CLOSED_BLASTDOOR_LAYER 3.05
#define CLOSED_DOOR_LAYER 3.1
#define CLOSED_FIREDOOR_LAYER 3.11
#define SHUTTER_LAYER 3.12 // HERE BE DRAGONS
+#define CLOSED_BLASTDOOR_LAYER 3.13 // ABOVE DOORS
#define ABOVE_OBJ_LAYER 3.2
#define ABOVE_WINDOW_LAYER 3.3
#define SIGN_LAYER 3.4
+#define CORGI_ASS_PIN_LAYER 3.41
#define NOT_HIGH_OBJ_LAYER 3.5
#define HIGH_OBJ_LAYER 3.6
-
#define BELOW_MOB_LAYER 3.7
+#define LOW_MOB_LAYER 3.75
#define LYING_MOB_LAYER 3.8
-#define SPACEPOD_LAYER 3.9 // yogs
+#define VEHICLE_LAYER 3.9
+#define MOB_BELOW_PIGGYBACK_LAYER 3.94
//#define MOB_LAYER 4 //For easy recordkeeping; this is a byond define
+#define MOB_SHIELD_LAYER 4.01
+#define MOB_ABOVE_PIGGYBACK_LAYER 4.06
+#define MOB_UPPER_LAYER 4.07
+#define HITSCAN_PROJECTILE_LAYER 4.09 //above all mob but still hidden by FoV
#define ABOVE_MOB_LAYER 4.1
#define WALL_OBJ_LAYER 4.25
+#define TRAM_SIGNAL_LAYER 4.26
#define EDGED_TURF_LAYER 4.3
#define ON_EDGED_TURF_LAYER 4.35
-#define LARGE_MOB_LAYER 4.4
-#define ABOVE_ALL_MOB_LAYER 4.5
+#define SPACEVINE_LAYER 4.4
+#define LARGE_MOB_LAYER 4.5
+#define SPACEVINE_MOB_LAYER 4.6
+
+// Intermediate layer used by both GAME_PLANE and ABOVE_GAME_PLANE
+#define ABOVE_ALL_MOB_LAYER 4.7
-#define SPACEVINE_LAYER 4.8
-#define SPACEVINE_MOB_LAYER 4.9
+// ABOVE_GAME_PLANE layers
+#define NAVIGATION_EYE_LAYER 4.9
//#define FLY_LAYER 5 //For easy recordkeeping; this is a byond define
#define GASFIRE_LAYER 5.05
#define RIPPLE_LAYER 5.1
-#define GHOST_LAYER 6
-#define LOW_LANDMARK_LAYER 9
-#define MID_LANDMARK_LAYER 9.1
-#define HIGH_LANDMARK_LAYER 9.2
-#define AREA_LAYER 10
-#define MASSIVE_OBJ_LAYER 11
-#define POINT_LAYER 12
+/**
+ * The layer of the visual overlay used in the submerge element.
+ * The vis overlay inherits the planes of the movables it's attached to (that also have KEEP_TOGETHER added)
+ * We just have to make sure the visual overlay is rendered above all the other overlays of those movables.
+ */
+#define WATER_VISUAL_OVERLAY_LAYER 1000
-#define EMISSIVE_BLOCKER_PLANE 12
-#define EMISSIVE_BLOCKER_LAYER 12
-#define EMISSIVE_BLOCKER_RENDER_TARGET "*EMISSIVE_BLOCKER_PLANE"
+// SEETHROUGH_PLANE layers here, tho it has no layer values
-#define CHAT_LAYER 12.0001 // Do not insert layers between these two values
-#define CHAT_LAYER_MAX 12.9999
+//---------- LIGHTING -------------
-#define EMISSIVE_PLANE 13
-#define EMISSIVE_LAYER 13
-#define EMISSIVE_RENDER_TARGET "*EMISSIVE_PLANE"
+// LIGHTING_PLANE layers
+// The layer of turf underlays starts at 0.01 and goes up by 0.01
+// Based off the z level. No I do not remember why, should check that
+/// Typically overlays, that "hide" portions of the turf underlay layer
+/// I'm allotting 100 z levels before this breaks. That'll never happen
+/// --Lemon
+#define LIGHTING_MASK_LAYER 10
+/// Misc things that draw on the turf lighting plane
+/// Space, solar beams, etc
+#define LIGHTING_PRIMARY_LAYER 15
+/// Stuff that needs to draw above everything else on this plane
+#define LIGHTING_ABOVE_ALL 20
-#define EMISSIVE_UNBLOCKABLE_PLANE 14
-#define EMISSIVE_UNBLOCKABLE_LAYER 14
-#define EMISSIVE_UNBLOCKABLE_RENDER_TARGET "*EMISSIVE_UNBLOCKABLE_PLANE"
-#define LIGHTING_PLANE 15
-#define LIGHTING_LAYER 15
-#define LIGHTING_RENDER_TARGET "LIGHT_PLANE"
+//---------- EMISSIVES -------------
+//Layering order of these is not particularly meaningful.
+//Important part is the seperation of the planes for control via plane_master
-#define RAD_TEXT_LAYER 15.1
+/// The layer you should use if you _really_ don't want an emissive overlay to be blocked.
+#define EMISSIVE_LAYER_UNBLOCKABLE 9999
+///--------------- FULLSCREEN IMAGES ------------
-#define O_LIGHTING_VISUAL_PLANE 16
-#define O_LIGHTING_VISUAL_LAYER 16
-#define O_LIGHTING_VISUAL_RENDER_TARGET "O_LIGHT_VISUAL_PLANE"
+#define FLASH_LAYER 1
+#define FULLSCREEN_LAYER 2
+#define UI_DAMAGE_LAYER 3
+#define BLIND_LAYER 4
+#define CRIT_LAYER 5
+#define CURSE_LAYER 6
+#define ECHO_LAYER 7
+#define PARRY_LAYER 8
-#define ABOVE_LIGHTING_PLANE 200 //things that should render ignoring lightning
-#define ABOVE_LIGHTING_LAYER 17
-#define ABOVE_LIGHTING_RENDER_TARGET "ABOVE_LIGHTING_PLANE"
+#define FOV_EFFECT_LAYER 100
-#define FLOOR_OPENSPACE_PLANE 17
-#define OPENSPACE_LAYER 17
+///--------------- FULLSCREEN RUNECHAT BUBBLES ------------
+/// Bubble for typing indicators
+#define TYPING_LAYER 500
-#define BYOND_LIGHTING_PLANE 18
-#define BYOND_LIGHTING_LAYER 18
-#define BYOND_LIGHTING_RENDER_TARGET "BYOND_LIGHTING_PLANE"
+#define RADIAL_BACKGROUND_LAYER 0
+///1000 is an unimportant number, it's just to normalize copied layers
+#define RADIAL_CONTENT_LAYER 1000
-#define CAMERA_STATIC_PLANE 19
-#define CAMERA_STATIC_LAYER 19
-#define CAMERA_STATIC_RENDER_TARGET "CAMERA_STATIC_PLANE"
+#define ADMIN_POPUP_LAYER 1
-#define RUNECHAT_PLANE 20
+///Layer for screentips
+#define SCREENTIP_LAYER 4
-#define BALLOON_CHAT_PLANE 20.9
+/// Layer for tutorial instructions
+#define TUTORIAL_INSTRUCTIONS_LAYER 5
-//HUD layer defines
+/// Layer for light overlays
+#define LIGHT_DEBUG_LAYER 6
-#define FULLSCREEN_PLANE 20
-#define FLASH_LAYER 20
-#define FULLSCREEN_LAYER 20.1
-#define UI_DAMAGE_LAYER 20.2
-#define BLIND_LAYER 20.3
-#define CRIT_LAYER 20.4
-#define CURSE_LAYER 20.5
-#define ECHO_LAYER 20.6
-#define FULLSCREEN_RENDER_TARGET "FULLSCREEN_PLANE"
+#define LOBBY_BACKGROUND_LAYER 3
+#define LOBBY_BUTTON_LAYER 4
-#define HUD_PLANE 21
-#define HUD_LAYER 21
-#define HUD_RENDER_TARGET "HUD_PLANE"
-#define ABOVE_HUD_PLANE 22
-#define ABOVE_HUD_LAYER 22
-#define ABOVE_HUD_RENDER_TARGET "ABOVE_HUD_PLANE"
+///Layer for lobby menu collapse button
+#define LOBBY_BELOW_MENU_LAYER 2
+///Layer for lobby menu background image and main buttons (Join/Ready, Observe, Charater Prefs)
+#define LOBBY_MENU_LAYER 3
+///Layer for lobby menu shutter, which covers up the menu to collapse/expand it
+#define LOBBY_SHUTTER_LAYER 4
+///Layer for lobby menu buttons that are hanging away from and lower than the main panel
+#define LOBBY_BOTTOM_BUTTON_LAYER 5
-#define SPLASHSCREEN_LAYER 23
-#define SPLASHSCREEN_PLANE 23
-#define SPLASHSCREEN_RENDER_TARGET "SPLASHSCREEN_PLANE"
+///cinematics are "below" the splash screen
+#define CINEMATIC_LAYER -1
+///Plane master controller keys
+#define PLANE_MASTERS_GAME "plane_masters_game"
+#define PLANE_MASTERS_NON_MASTER "plane_masters_non_master"
+#define PLANE_MASTERS_COLORBLIND "plane_masters_colorblind"
-//-------------------- Rendering ---------------------
-#define RENDER_PLANE_GAME 100
-#define RENDER_PLANE_NON_GAME 101
+//Plane master critical flags
+//Describes how different plane masters behave when they are being culled for performance reasons
+/// This plane master will not go away if its layer is culled. useful for preserving effects
+#define PLANE_CRITICAL_DISPLAY (1<<0)
+/// This plane master will temporarially remove relays to all other planes
+/// Allows us to retain the effects of a plane while cutting off the changes it makes
+#define PLANE_CRITICAL_NO_RELAY (1<<1)
+/// We assume this plane master has a render target starting with *, it'll be removed, forcing it to render in place
+#define PLANE_CRITICAL_CUT_RENDER (1<<2)
+#define PLANE_CRITICAL_FUCKO_PARALLAX (PLANE_CRITICAL_DISPLAY|PLANE_CRITICAL_NO_RELAY|PLANE_CRITICAL_CUT_RENDER)
-///1000 is an unimportant number, it's just to normalize copied layers
-#define RADIAL_CONTENT_LAYER 1000
+/// A value of /datum/preference/numeric/multiz_performance that disables the option
+#define MULTIZ_PERFORMANCE_DISABLE -1
+/// We expect at most 3 layers of multiz
+/// Increment this define if you make a huge map. We unit test for it too just to make it easy for you
+/// If you modify this, you'll need to modify the tsx file too
+#define MAX_EXPECTED_Z_DEPTH 3
diff --git a/code/__DEFINES/lazy_templates.dm b/code/__DEFINES/lazy_templates.dm
new file mode 100644
index 000000000000..8a469af4deed
--- /dev/null
+++ b/code/__DEFINES/lazy_templates.dm
@@ -0,0 +1,13 @@
+#define LAZY_TEMPLATE_KEY_NUKIEBASE "LT_NUKIEBASE"
+#define LAZY_TEMPLATE_KEY_WIZARDDEN "LT_WIZARDDEN"
+#define LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY "LT_NINJAHOLDING"
+#define LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS "LT_ABDUCTORSHIPS"
+#define LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE "LT_HERETICSACRIFICE"
+
+#define LAZY_TEMPLATE_KEY_LIST_ALL(...) list( \
+ "Nukie Base" = LAZY_TEMPLATE_KEY_NUKIEBASE, \
+ "Wizard Den" = LAZY_TEMPLATE_KEY_WIZARDDEN, \
+ "Ninja Holding" = LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY, \
+ "Abductor Ships" = LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS, \
+ "Heretic Sacrifice Level" = LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE, \
+)
diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm
index 57271b6bcdee..f5ae8aac9447 100644
--- a/code/__DEFINES/lighting.dm
+++ b/code/__DEFINES/lighting.dm
@@ -15,6 +15,8 @@
#define LIGHT_ATTACHED (1<<0)
/// Freezes a light in its current state, blocking any attempts at modification
#define LIGHT_FROZEN (1<<1)
+/// Does this light ignore inherent offsets? (Pixels, transforms, etc)
+#define LIGHT_IGNORE_OFFSET (1<<2)
//Bay lighting engine shit, not in /code/modules/lighting because BYOND is being shit about it
/// frequency, in 1/10ths of a second, of the lighting process
@@ -26,6 +28,10 @@
#define LIGHTING_FALLOFF 1
/// use lambertian shading for light sources
#define LIGHTING_LAMBERTIAN 0
+/// light UNDER the floor. primarily used for starlight, shouldn't fuck with this
+#define LIGHTING_HEIGHT_SPACE -0.5
+/// light ON the floor
+#define LIGHTING_HEIGHT_FLOOR 0
/// height off the ground of light sources on the pseudo-z-axis, you should probably leave this alone
#define LIGHTING_HEIGHT 1
/// Value used to round lumcounts, values smaller than 1/129 don't matter (if they do, thanks sinking points), greater values will make lighting less precise, but in turn increase performance, VERY SLIGHTLY.
@@ -56,25 +62,16 @@
///How many tiles standard fires glow.
#define LIGHT_RANGE_FIRE 3
-#define LIGHTING_PLANE_ALPHA_VISIBLE 255
-#define LIGHTING_PLANE_ALPHA_NV_TRAIT 245
-#define LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE 192
-/// For lighting alpha, small amounts lead to big changes. even at 128 its hard to figure out what is dark and what is light, at 64 you almost can't even tell.
-#define LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE 128
-#define LIGHTING_PLANE_ALPHA_INVISIBLE 0
-
-//lighting area defines
-/// dynamic lighting disabled (area stays at full brightness)
-#define DYNAMIC_LIGHTING_DISABLED 0
-/// dynamic lighting enabled
-#define DYNAMIC_LIGHTING_ENABLED 1
-/// dynamic lighting enabled even if the area doesn't require power
-#define DYNAMIC_LIGHTING_FORCED 2
-/// dynamic lighting enabled only if starlight is.
-#define DYNAMIC_LIGHTING_IFSTARLIGHT 3
-
-#define IS_DYNAMIC_LIGHTING(A) A.dynamic_lighting
+// Lighting cutoff defines
+// These are a percentage of how much darkness to cut off (in rgb)
+#define LIGHTING_CUTOFF_VISIBLE 0
+#define LIGHTING_CUTOFF_REAL_LOW 4.5
+#define LIGHTING_CUTOFF_MEDIUM 15
+#define LIGHTING_CUTOFF_HIGH 30
+#define LIGHTING_CUTOFF_FULLBRIGHT 100
+/// What counts as being able to see in the dark
+#define LIGHTING_NIGHTVISION_THRESHOLD 10
//code assumes higher numbers override lower numbers.
#define LIGHTING_NO_UPDATE 0
@@ -86,10 +83,13 @@
#define FLASH_LIGHT_POWER 3
#define FLASH_LIGHT_RANGE 3.8
+// Emissive blocking.
/// Uses vis_overlays to leverage caching so that very few new items need to be made for the overlay. For anything that doesn't change outline or opaque area much or at all.
-#define EMISSIVE_BLOCK_GENERIC 1
+#define EMISSIVE_BLOCK_GENERIC 0
/// Uses a dedicated render_target object to copy the entire appearance in real time to the blocking layer. For things that can change in appearance a lot from the base state, like humans.
-#define EMISSIVE_BLOCK_UNIQUE 2
+#define EMISSIVE_BLOCK_UNIQUE 1
+/// Don't block any emissives. Useful for things like, pieces of paper?
+#define EMISSIVE_BLOCK_NONE 2
/// Returns the red part of a #RRGGBB hex sequence as number
#define GETREDPART(hexa) hex2num(copytext(hexa, 2, 4))
@@ -114,3 +114,26 @@ do { \
source.lum_b = 1; \
}; \
} while (FALSE)
+
+
+
+
+#define _EMISSIVE_COLOR(val) list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, val,val,val,0)
+/// The color matrix applied to all emissive overlays. Should be solely dependent on alpha and not have RGB overlap with [EM_BLOCK_COLOR].
+#define EMISSIVE_COLOR _EMISSIVE_COLOR(1)
+/// A globaly cached version of [EMISSIVE_COLOR] for quick access.
+GLOBAL_LIST_INIT(emissive_color, EMISSIVE_COLOR)
+
+#define _EM_BLOCK_COLOR(val) list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,val, 0,0,0,0)
+/// The color matrix applied to all emissive blockers. Should be solely dependent on alpha and not have RGB overlap with [EMISSIVE_COLOR].
+#define EM_BLOCK_COLOR _EM_BLOCK_COLOR(1)
+/// A globaly cached version of [EM_BLOCK_COLOR] for quick access.
+GLOBAL_LIST_INIT(em_block_color, EM_BLOCK_COLOR)
+
+/// A set of appearance flags applied to all emissive and emissive blocker overlays.
+/// KEEP_APART to prevent parent hooking, KEEP_TOGETHER for children, and we reset the color and alpha of our parent so nothing gets overriden
+#define EMISSIVE_APPEARANCE_FLAGS (KEEP_APART|KEEP_TOGETHER|RESET_COLOR|RESET_ALPHA)
+/// The color matrix used to mask out emissive blockers on the emissive plane. Alpha should default to zero, be solely dependent on the RGB value of [EMISSIVE_COLOR], and be independant of the RGB value of [EM_BLOCK_COLOR].
+#define EM_MASK_MATRIX list(0,0,0,1/3, 0,0,0,1/3, 0,0,0,1/3, 0,0,0,0, 1,1,1,0)
+/// A globaly cached version of [EM_MASK_MATRIX] for quick access.
+GLOBAL_LIST_INIT(em_mask_matrix, EM_MASK_MATRIX)
diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm
index 408db118901a..d62b88203d66 100644
--- a/code/__DEFINES/logging.dm
+++ b/code/__DEFINES/logging.dm
@@ -24,25 +24,33 @@
#define INVESTIGATE_REACTOR "reactor"
// Logging types for log_message()
-#define LOG_ATTACK (1 << 0)
-#define LOG_SAY (1 << 1)
-#define LOG_WHISPER (1 << 2)
-#define LOG_EMOTE (1 << 3)
-#define LOG_DSAY (1 << 4)
-#define LOG_PDA (1 << 5)
-#define LOG_CHAT (1 << 6)
-#define LOG_COMMENT (1 << 7)
-#define LOG_TELECOMMS (1 << 8)
-#define LOG_NTSL (1 << 9)
-#define LOG_OOC (1 << 10)
-#define LOG_ADMIN (1 << 11)
-#define LOG_OWNERSHIP (1 << 12)
-#define LOG_GAME (1 << 13)
-#define LOG_ADMIN_PRIVATE (1 << 14)
-#define LOG_ASAY (1 << 15)
-#define LOG_MECHA (1 << 16)
-#define LOG_VIRUS (1 << 17)
-#define LOG_CLONING (1 << 18)
+#define LOG_ATTACK (1 << 0)
+#define LOG_SAY (1 << 1)
+#define LOG_WHISPER (1 << 2)
+#define LOG_EMOTE (1 << 3)
+#define LOG_DSAY (1 << 4)
+#define LOG_PDA (1 << 5)
+#define LOG_CHAT (1 << 6)
+#define LOG_COMMENT (1 << 7)
+#define LOG_TELECOMMS (1 << 8)
+#define LOG_OOC (1 << 9)
+#define LOG_ADMIN (1 << 10)
+#define LOG_OWNERSHIP (1 << 11)
+#define LOG_GAME (1 << 12)
+#define LOG_ADMIN_PRIVATE (1 << 13)
+#define LOG_ASAY (1 << 14)
+#define LOG_MECHA (1 << 15)
+#define LOG_VIRUS (1 << 16)
+#define LOG_SHUTTLE (1 << 17)
+#define LOG_ECON (1 << 18)
+#define LOG_VICTIM (1 << 19)
+#define LOG_RADIO_EMOTE (1 << 20)
+#define LOG_SPEECH_INDICATORS (1 << 21)
+#define LOG_TRANSPORT (1 << 22)
+
+//Yogger's Loggers
+#define LOG_NTSL (1 << 23)
+#define LOG_CLONING (1 << 24)
//Individual logging panel pages
#define INDIVIDUAL_ATTACK_LOG (LOG_ATTACK)
@@ -51,7 +59,137 @@
#define INDIVIDUAL_COMMS_LOG (LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS)
#define INDIVIDUAL_OOC_LOG (LOG_OOC | LOG_ADMIN)
#define INDIVIDUAL_OWNERSHIP_LOG (LOG_OWNERSHIP)
-#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_NTSL | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME)
+#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_RADIO_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME | LOG_ADMIN_PRIVATE | LOG_ASAY | LOG_MECHA | LOG_VIRUS | LOG_SHUTTLE | LOG_ECON | LOG_VICTIM | LOG_SPEECH_INDICATORS)
#define LOGSRC_CLIENT "Client"
+#define LOGSRC_CKEY "Ckey"
#define LOGSRC_MOB "Mob"
+
+// Log header keys
+#define LOG_HEADER_CATEGORY "cat"
+#define LOG_HEADER_CATEGORY_LIST "cat-list"
+#define LOG_HEADER_INIT_TIMESTAMP "ts"
+#define LOG_HEADER_ROUND_ID "round-id"
+#define LOG_HEADER_SECRET "secret"
+
+// Log json keys
+#define LOG_JSON_CATEGORY "cat"
+#define LOG_JSON_ENTRIES "entries"
+#define LOG_JSON_LOGGING_START "log-start"
+
+// Log entry keys
+#define LOG_ENTRY_KEY_TIMESTAMP "ts"
+#define LOG_ENTRY_KEY_CATEGORY "cat"
+#define LOG_ENTRY_KEY_MESSAGE "msg"
+#define LOG_ENTRY_KEY_DATA "data"
+#define LOG_ENTRY_KEY_WORLD_STATE "w-state"
+#define LOG_ENTRY_KEY_SEMVER_STORE "s-store"
+#define LOG_ENTRY_KEY_ID "id"
+#define LOG_ENTRY_KEY_SCHEMA_VERSION "s-ver"
+
+// Internal categories
+#define LOG_CATEGORY_INTERNAL_CATEGORY_NOT_FOUND "internal-category-not-found"
+#define LOG_CATEGORY_INTERNAL_ERROR "internal-error"
+
+// Misc categories
+#define LOG_CATEGORY_ATTACK "attack"
+#define LOG_CATEGORY_CONFIG "config"
+#define LOG_CATEGORY_DYNAMIC "dynamic"
+#define LOG_CATEGORY_ECONOMY "economy"
+#define LOG_CATEGORY_FILTER "filter"
+#define LOG_CATEGORY_MANIFEST "manifest"
+#define LOG_CATEGORY_MECHA "mecha"
+#define LOG_CATEGORY_PAPER "paper"
+#define LOG_CATEGORY_QDEL "qdel"
+#define LOG_CATEGORY_RUNTIME "runtime"
+#define LOG_CATEGORY_SHUTTLE "shuttle"
+#define LOG_CATEGORY_SILICON "silicon"
+#define LOG_CATEGORY_SILO "silo"
+#define LOG_CATEGORY_SIGNAL "signal"
+#define LOG_CATEGORY_SPEECH_INDICATOR "speech-indiciator"
+// Leave the underscore, it's there for backwards compatibility reasons
+#define LOG_CATEGORY_SUSPICIOUS_LOGIN "suspicious_logins"
+#define LOG_CATEGORY_TARGET_ZONE_SWITCH "target-zone-switch"
+#define LOG_CATEGORY_TELECOMMS "telecomms"
+#define LOG_CATEGORY_TOOL "tool"
+#define LOG_CATEGORY_TRANSPORT "transport"
+#define LOG_CATEGORY_VIRUS "virus"
+
+// Admin categories
+#define LOG_CATEGORY_ADMIN "admin"
+#define LOG_CATEGORY_ADMIN_CIRCUIT "admin-circuit"
+#define LOG_CATEGORY_ADMIN_DSAY "admin-dsay"
+
+// Admin private categories
+#define LOG_CATEGORY_ADMIN_PRIVATE "adminprivate"
+#define LOG_CATEGORY_ADMIN_PRIVATE_ASAY "adminprivate-asay"
+
+// Debug categories
+#define LOG_CATEGORY_DEBUG "debug"
+#define LOG_CATEGORY_DEBUG_ASSET "debug-asset"
+#define LOG_CATEGORY_DEBUG_JOB "debug-job"
+#define LOG_CATEGORY_DEBUG_LUA "debug-lua"
+#define LOG_CATEGORY_DEBUG_MAPPING "debug-mapping"
+#define LOG_CATEGORY_DEBUG_MOBTAG "debug-mobtag"
+#define LOG_CATEGORY_DEBUG_SQL "debug-sql"
+
+// Compatibility categories, for when stuff is changed and you need existing functionality to work
+#define LOG_CATEGORY_COMPAT_GAME "game-compat"
+
+// Game categories
+#define LOG_CATEGORY_GAME "game"
+#define LOG_CATEGORY_GAME_ACCESS "game-access"
+#define LOG_CATEGORY_GAME_EMOTE "game-emote"
+#define LOG_CATEGORY_GAME_INTERNET_REQUEST "game-internet-request"
+#define LOG_CATEGORY_GAME_OOC "game-ooc"
+#define LOG_CATEGORY_GAME_PRAYER "game-prayer"
+#define LOG_CATEGORY_GAME_RADIO_EMOTE "game-radio-emote"
+#define LOG_CATEGORY_GAME_SAY "game-say"
+#define LOG_CATEGORY_GAME_TOPIC "game-topic"
+#define LOG_CATEGORY_GAME_TRAITOR "game-traitor"
+#define LOG_CATEGORY_GAME_VOTE "game-vote"
+#define LOG_CATEGORY_GAME_WHISPER "game-whisper"
+
+// HREF categories
+#define LOG_CATEGORY_HREF "href"
+#define LOG_CATEGORY_HREF_TGUI "href-tgui"
+
+// Uplink categories
+#define LOG_CATEGORY_UPLINK "uplink"
+#define LOG_CATEGORY_UPLINK_CHANGELING "uplink-changeling"
+#define LOG_CATEGORY_UPLINK_HERETIC "uplink-heretic"
+#define LOG_CATEGORY_UPLINK_MALF "uplink-malf"
+#define LOG_CATEGORY_UPLINK_SPELL "uplink-spell"
+
+// PDA categories
+#define LOG_CATEGORY_PDA "pda"
+#define LOG_CATEGORY_PDA_CHAT "pda-chat"
+#define LOG_CATEGORY_PDA_COMMENT "pda-comment"
+
+// Flags that apply to the entry_flags var on logging categories
+// These effect how entry datums process the inputs passed into them
+/// Enables data list usage for readable log entries
+/// You'll likely want to disable internal formatting to make this work properly
+#define ENTRY_USE_DATA_W_READABLE (1<<0)
+
+#define SCHEMA_VERSION "schema-version"
+
+// Default log schema version
+#define LOG_CATEGORY_SCHEMA_VERSION_NOT_SET "0.0.1"
+
+//wrapper macros for easier grepping
+#define DIRECT_OUTPUT(A, B) A << B
+#define DIRECT_INPUT(A, B) A >> B
+#define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image)
+#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound)
+#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
+#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
+#define READ_FILE(file, text) DIRECT_INPUT(file, text)
+//This is an external call, "true" and "false" are how rust parses out booleans
+#ifdef EXTOOLS_LOGGING
+#define WRITE_LOG(log, text) extools_log_write(log, text, TRUE)
+#define WRITE_LOG_NO_FORMAT(log, text) extools_log_write(log, text, FALSE)
+#else
+#define WRITE_LOG(log, text) rustg_log_write(log, "\[[worldtime2text()]\] [text]", "true")
+#define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false")
+#endif
diff --git a/code/__DEFINES/map_switch.dm b/code/__DEFINES/map_switch.dm
new file mode 100644
index 000000000000..dbb0059786ee
--- /dev/null
+++ b/code/__DEFINES/map_switch.dm
@@ -0,0 +1,8 @@
+/// Uses the left operator when compiling, uses the right operator when not compiling.
+// Currently uses the CBT macro, but if http://www.byond.com/forum/post/2831057 is ever added,
+// or if map tools ever agree on a standard, this should switch to use that.
+#ifdef CBT
+#define MAP_SWITCH(compile_time, map_time) ##compile_time
+#else
+#define MAP_SWITCH(compile_time, map_time) ##map_time
+#endif
diff --git a/code/__DEFINES/mapping.dm b/code/__DEFINES/mapping.dm
new file mode 100644
index 000000000000..3c08679e2a4b
--- /dev/null
+++ b/code/__DEFINES/mapping.dm
@@ -0,0 +1,6 @@
+// Defines for SSmapping's multiz_levels
+/// TRUE if we're ok with going up
+#define Z_LEVEL_UP 1
+/// TRUE if we're ok with going down
+#define Z_LEVEL_DOWN 2
+#define LARGEST_Z_LEVEL_INDEX Z_LEVEL_DOWN
diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index 6bacc65b3ea1..af35816f8da3 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -1,32 +1,76 @@
/*
-The /tg/ codebase allows mixing of hardcoded and dynamically-loaded z-levels.
+The /tg/ codebase allows mixing of hardcoded and dynamically-loaded Z-levels.
Z-levels can be reordered as desired and their properties are set by "traits".
-See map_config.dm for how a particular station's traits may be chosen.
+See code/datums/map_config.dm for how a particular station's traits may be chosen.
The list DEFAULT_MAP_TRAITS at the bottom of this file should correspond to
the maps that are hardcoded, as set in _maps/_basemap.dm. SSmapping is
-responsible for loading every non-hardcoded z-level.
+responsible for loading every non-hardcoded Z-level.
-As of 2018-02-04, the typical z-levels for a single-level station are:
+As of April 26th, 2022, the typical Z-levels for a single-level station are:
1: CentCom
2: Station
-3-4: Randomized space
+3-4: Randomized Space (Ruins)
5: Mining
-6: City of Cogs
-7-11: Randomized space
-12: Empty space
-13: Transit space
-
-Multi-Z stations are supported and multi-Z mining and away missions would
-require only minor tweaks.
+6-11: Randomized Space (Ruins)
+12: Transit/Reserved Space
+
+However, if away missions are enabled:
+12: Away Mission
+13: Transit/Reserved Space
+
+Multi-Z stations are supported and Multi-Z mining and away missions would
+require only minor tweaks. They also handle their Z-Levels differently on their
+own case by case basis.
+
+This information will absolutely date quickly with how we handle Z-Levels, and will
+continue to handle them in the future. Currently, you can go into the Debug tab
+of your stat-panel (in game) and hit "Mapping verbs - Enable". You will then get a new tab
+called "Mapping", as well as access to the verb "Debug-Z-Levels". Although the information
+presented in this comment is factual for the time it was written for, it's ill-advised
+to trust the words presented within.
+
+We also provide this information to you so that you can have an at-a-glance look at how
+Z-Levels are arranged. It is extremely ill-advised to ever use the location of a Z-Level
+to assign traits to it or use it in coding. Use Z-Traits (ZTRAITs) for these.
+
+If you want to start toying around with Z-Levels, do not take these words for fact.
+Always compile, always use that verb, and always make sure that it works for what you want to do.
*/
// helpers for modifying jobs, used in various job_changes.dm files
-#define MAP_JOB_CHECK if(SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) { return; }
-#define MAP_JOB_CHECK_BASE if(SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) { return ..(); }
-#define MAP_REMOVE_JOB(jobpath) /datum/job/##jobpath/map_check() { return (SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) && ..() }
+
+#define MAP_CURRENT_VERSION 1
#define SPACERUIN_MAP_EDGE_PAD 15
+/// Distance from edge to move to another z-level
+#define TRANSITIONEDGE 7
+
+// Maploader bounds indices
+/// The maploader index for the maps minimum x
+#define MAP_MINX 1
+/// The maploader index for the maps minimum y
+#define MAP_MINY 2
+/// The maploader index for the maps minimum z
+#define MAP_MINZ 3
+/// The maploader index for the maps maximum x
+#define MAP_MAXX 4
+/// The maploader index for the maps maximum y
+#define MAP_MAXY 5
+/// The maploader index for the maps maximum z
+#define MAP_MAXZ 6
+
+/// Path for the next_map.json file, if someone, for some messed up reason, wants to change it.
+#define PATH_TO_NEXT_MAP_JSON "data/next_map.json"
+
+/// List of directories we can load map .json files from
+#define MAP_DIRECTORY_MAPS "_maps"
+#define MAP_DIRECTORY_DATA "data"
+#define MAP_DIRECTORY_WHITELIST list(MAP_DIRECTORY_MAPS,MAP_DIRECTORY_DATA)
+
+/// Special map path value for custom adminloaded stations.
+#define CUSTOM_MAP_PATH "custom"
+
// traits
// boolean - marks a level as having that property if present
#define ZTRAIT_CENTCOM "CentCom"
@@ -39,6 +83,7 @@ require only minor tweaks.
#define ZTRAIT_LAVA_RUINS "Lava Ruins"
#define ZTRAIT_ICE_RUINS "Ice Ruins"
#define ZTRAIT_ICE_RUINS_UNDERGROUND "Ice Ruins Underground"
+#define ZTRAIT_ISOLATED_RUINS "Isolated Ruins" //Placing ruins on z levels with this trait will use turf reservation instead of usual placement.
// boolean - weather types that occur on the level
#define ZTRAIT_SNOWSTORM "Weather_Snowstorm"
@@ -61,7 +106,7 @@ require only minor tweaks.
// number - default gravity if there's no gravity generators or area overrides present
#define ZTRAIT_GRAVITY "Gravity"
-// numeric offsets - e.g. {"Down": -1} means that chasms will fall to z - 1 rather than oblivion
+// Whether this z level is linked up/down. Bool.
#define ZTRAIT_UP "Up"
#define ZTRAIT_DOWN "Down"
@@ -77,6 +122,9 @@ require only minor tweaks.
// string - type path of the z-level's baseturf (defaults to space)
#define ZTRAIT_BASETURF "Baseturf"
+///boolean - does this z disable parallax?
+#define ZTRAIT_NOPARALLAX "No Parallax"
+
// default trait definitions, used by SSmapping
///Z level traits for CentCom
#define ZTRAITS_CENTCOM list(ZTRAIT_CENTCOM = TRUE, ZTRAIT_NOPHASE = TRUE)
@@ -87,23 +135,12 @@ require only minor tweaks.
///Z level traits for Lavaland
#define ZTRAITS_LAVALAND list(\
ZTRAIT_MINING = TRUE, \
+ ZTRAIT_NOPARALLAX = TRUE, \
ZTRAIT_ASHSTORM = TRUE, \
ZTRAIT_LAVA_RUINS = TRUE, \
ZTRAIT_BOMBCAP_MULTIPLIER = 2.5, \
ZTRAIT_BASETURF = /turf/open/lava/smooth/lava_land_surface)
-#define ZTRAITS_ICEMOON list(\
- ZTRAIT_MINING = TRUE, \
- ZTRAIT_ICE_RUINS = TRUE, \
- ZTRAIT_BOMBCAP_MULTIPLIER = 2.5, \
- ZTRAIT_UP = -1, \
- ZTRAIT_DOWN = 1, \
- ZTRAIT_BASETURF = /turf/open/floor/plating/asteroid/snow/icemoon)
-#define ZTRAITS_ICEMOON_UNDERGROUND list(\
- ZTRAIT_MINING = TRUE, \
- ZTRAIT_ICE_RUINS_UNDERGROUND = TRUE, \
- ZTRAIT_BOMBCAP_MULTIPLIER = 2.5, \
- ZTRAIT_UP = -1, \
- ZTRAIT_BASETURF = /turf/open/lava/plasma/ice_moon)
+
#define ZTRAITS_REEBE list(ZTRAIT_REEBE = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 0.60)
///Z level traits for Away Missions
@@ -134,9 +171,25 @@ require only minor tweaks.
#define PLACEMENT_TRIES 100 //How many times we try to fit the ruin somewhere until giving up (really should just swap to some packing algo)
#define PLACE_DEFAULT "random"
+///On same z level as original ruin
#define PLACE_SAME_Z "same"
+///On space ruin z level(s)
#define PLACE_SPACE_RUIN "space"
-#define PLACE_BELOW "below" //On z levl below - centered on same tile
+///On z levl below - centered on same tile
+#define PLACE_BELOW "below"
+///On lavaland ruin z levels(s)
#define PLACE_LAVA_RUIN "lavaland"
#define PLACE_ICE_RUIN "icesurface"
#define PLACE_ICE_UNDERGROUND_RUIN "iceunderground"
+
+///Map generation defines
+#define DEFAULT_SPACE_RUIN_LEVELS 7
+#define DEFAULT_SPACE_EMPTY_LEVELS 1
+
+/// A map key that corresponds to being one exclusively for Space.
+#define SPACE_KEY "space"
+
+// helpers for modifying jobs, used in various job_changes.dm files
+#define MAP_JOB_CHECK if(SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) { return; }
+#define MAP_JOB_CHECK_BASE if(SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) { return ..(); }
+#define MAP_REMOVE_JOB(jobpath) /datum/job/##jobpath/map_check() { return (SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) && ..() }
diff --git a/code/__DEFINES/materials.dm b/code/__DEFINES/materials.dm
index 2d48786b76bb..6b4f6ae436c0 100644
--- a/code/__DEFINES/materials.dm
+++ b/code/__DEFINES/materials.dm
@@ -1,12 +1,5 @@
-/// Is the material from an ore? currently unused but exists atm for categorizations sake
-#define MAT_CATEGORY_ORE "ore capable"
-
-/// Hard materials, such as iron or metal
-#define MAT_CATEGORY_RIGID "rigid material"
-
-
/// Gets the reference for the material type that was given
#define getmaterialref(A) (SSmaterials.materials[A])
/// Flag for atoms, this flag ensures it isn't re-colored by materials. Useful for snowflake icons such as default toolboxes.
-#define MATERIAL_NO_COLOR (1<<0)
\ No newline at end of file
+#define MATERIAL_NO_COLOR (1<<0)
diff --git a/code/__DEFINES/matrices.dm b/code/__DEFINES/matrices.dm
new file mode 100644
index 000000000000..26ff5a7232a2
--- /dev/null
+++ b/code/__DEFINES/matrices.dm
@@ -0,0 +1,27 @@
+/// Helper macro for creating a matrix at the given offsets.
+/// Works at compile time.
+#define TRANSLATE_MATRIX(offset_x, offset_y) matrix(1, 0, (offset_x), 0, 1, (offset_y))
+/// The color matrix of an image which colors haven't been altered. Does nothing.
+#define COLOR_MATRIX_IDENTITY list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0)
+/// Color inversion
+#define COLOR_MATRIX_INVERT list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0)
+///Sepiatone
+#define COLOR_MATRIX_SEPIATONE list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0)
+///Grayscale
+#define COLOR_MATRIX_GRAYSCALE list(0.33,0.33,0.33,0, 0.59,0.59,0.59,0, 0.11,0.11,0.11,0, 0,0,0,1, 0,0,0,0)
+///Polaroid colors
+#define COLOR_MATRIX_POLAROID list(1.438,-0.062,-0.062,0, -0.122,1.378,-0.122,0, -0.016,-0.016,1.483,0, 0,0,0,1, 0,0,0,0)
+/// Converts reds to blue, green to red and blue to green.
+#define COLOR_MATRIX_BRG list(0,0,1,0, 0,1,0,0, 1,0,0,0, 0,0,0,1, 0,0,0,0)
+/// Black & White
+#define COLOR_MATRIX_BLACK_WHITE list(1.5,1.5,1.5,0, 1.5,1.5,1.5,0, 1.5,1.5,1.5,0, 0,0,0,1, -1,-1,-1,0)
+/**
+ * Adds/subtracts overall lightness
+ * 0 is identity, 1 makes everything white, -1 makes everything black
+ */
+#define COLOR_MATRIX_LIGHTNESS(power) list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0)
+/**
+ * Changes distance colors have from rgb(127,127,127) grey
+ * 1 is identity. 0 makes everything grey >1 blows out colors and greys
+ */
+#define COLOR_MATRIX_CONTRAST(val) list(val,0,0,0, 0,val,0,0, 0,0,val,0, 0,0,0,1, (1-val)*0.5,(1-val)*0.5,(1-val)*0.5,0)
diff --git a/code/__DEFINES/melee.dm b/code/__DEFINES/melee.dm
index 3987040dc31d..cddfed1171e2 100644
--- a/code/__DEFINES/melee.dm
+++ b/code/__DEFINES/melee.dm
@@ -20,6 +20,7 @@
#define MARTIALART_WORLDBREAKER "worldbreaker"
#define MARTIALART_SPACIALLDOMINANCE "absolute spacial dominance"
#define MARTIALART_LIGHTNINGFLOW "lightning flow"
+#define MARTIALART_REVERBPALM "reverberating palm"
//Weapon stat defines
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 1f1efa711b5f..234078b2f3aa 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -57,8 +57,6 @@
#define MANIFEST_ERROR_CONTENTS 2
#define MANIFEST_ERROR_ITEM 4
-#define TRANSITIONEDGE 7 //Distance from edge to move to another z-level
-
#define BE_CLOSE TRUE //in the case of a silicon, to select if they need to be next to the atom
#define NO_DEXTERY TRUE //if other mobs (monkeys, aliens, etc) can use this
#define NO_TK TRUE
@@ -72,14 +70,6 @@
#define STAGE_FIVE 9
#define STAGE_SIX 11 //From supermatter shard
-//SSticker.current_state values
-#define GAME_STATE_STARTUP 0
-#define GAME_STATE_PREGAME 1
-#define GAME_STATE_SETTING_UP 2
-#define GAME_STATE_PLAYING 3
-#define GAME_STATE_FINISHED 4
-
-
//FONTS:
// Used by Paper and PhotoCopier (and PaperBin once a year).
// Used by PDA's Notekeeper.
@@ -115,12 +105,11 @@
GLOBAL_LIST_EMPTY(bloody_footprints_cache)
//Bloody shoes/footprints
-#define MAX_SHOE_BLOODINESS 100
-#define BLOODY_FOOTPRINT_BASE_ALPHA 150
-#define BLOOD_GAIN_PER_STEP 100
-#define BLOOD_LOSS_PER_STEP 5
-#define BLOOD_LOSS_IN_SPREAD 20
-#define BLOOD_AMOUNT_PER_DECAL 20
+#define BLOODY_FOOTPRINT_BASE_ALPHA 80 /// Minimum alpha of footprints
+#define BLOOD_AMOUNT_PER_DECAL 50 /// How much blood a regular blood splatter contains
+#define BLOOD_ITEM_MAX 200 /// How much blood an item can have stuck on it
+#define BLOOD_POOL_MAX 300 /// How much blood a blood decal can contain
+#define BLOOD_FOOTPRINTS_MIN 5 /// How much blood a footprint need to at least contain
//Bloody shoe blood states
#define BLOOD_STATE_HUMAN "blood"
@@ -142,18 +131,6 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
#define HAS_SENSORS 1
#define LOCKED_SENSORS 2
-//Wet floor type flags. Stronger ones should be higher in number.
-#define TURF_DRY (0)
-#define TURF_WET_WATER (1<<0)
-#define TURF_WET_PERMAFROST (1<<1)
-#define TURF_WET_ICE (1<<2)
-#define TURF_WET_LUBE (1<<3)
-#define TURF_WET_SUPERLUBE (1<<4)
-
-#define IS_WET_OPEN_TURF(O) O.GetComponent(/datum/component/wet_floor)
-
-//Maximum amount of time, (in deciseconds) a tile can be wet for.
-#define MAXIMUM_WET_TIME 5 MINUTES
//unmagic-strings for types of polls
#define POLLTYPE_OPTION "OPTION"
@@ -162,17 +139,9 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
#define POLLTYPE_MULTI "MULTICHOICE"
#define POLLTYPE_IRV "IRV"
-
-
//subtypesof(), typesof() without the parent path
#define subtypesof(typepath) ( typesof(typepath) - typepath )
-//Gets the turf this atom inhabits
-#define get_turf(A) (get_step(A, 0))
-
-//Same as above except gets the area instead
-#define get_area(A) (isarea(A) ? A : get_step(A, 0)?.loc)
-
//Ghost orbit types:
#define GHOST_ORBIT_CIRCLE "circle"
#define GHOST_ORBIT_TRIANGLE "triangle"
@@ -246,14 +215,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
//Just space
#define SPACE_ICON_STATE "[((x + y) ^ ~(x * y) + z) % 25]"
-// Maploader bounds indices
-#define MAP_MINX 1
-#define MAP_MINY 2
-#define MAP_MINZ 3
-#define MAP_MAXX 4
-#define MAP_MAXY 5
-#define MAP_MAXZ 6
-
// Defib stats
#define DEFIB_TIME_LIMIT 15 MINUTES
@@ -273,18 +234,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"
#define SHELTER_DEPLOY_OUTSIDE_MAP "outside map"
-//debug printing macros
-#define debug_world(msg) if (GLOB.Debug2) to_chat(world, \
- type = MESSAGE_TYPE_DEBUG, \
- text = "DEBUG: [msg]")
-#define debug_usr(msg) if (GLOB.Debug2&&usr) to_chat(usr, \
- type = MESSAGE_TYPE_DEBUG, \
- text = "DEBUG: [msg]")
-#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.permissions.admins, \
- type = MESSAGE_TYPE_DEBUG, \
- text = "DEBUG: [msg]")
-#define debug_world_log(msg) if (GLOB.Debug2) log_world("DEBUG: [msg]")
-
#define INCREMENT_TALLY(L, stat) if(L[stat]){L[stat]++}else{L[stat] = 1}
//TODO Move to a pref
@@ -375,26 +324,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define STACK_CHECK_CARDINALS "cardinals" //checks if there is an object of the result type in any of the cardinal directions
#define STACK_CHECK_ADJACENT "adjacent" //checks if there is an object of the result type within one tile
-//text files
-/// File location for brain damage traumas
-#define BRAIN_DAMAGE_FILE "traumas.json"
-/// File location for AI ion laws
-#define ION_FILE "ion_laws.json"
-/// File location for pirate names
-#define PIRATE_NAMES_FILE "pirates.json"
-/// File location for redpill questions
-#define REDPILL_FILE "redpill.json"
-/// File location for wanted posters messages
-#define WANTED_FILE "wanted_message.json"
-/// File location for really dumb suggestions memes
-#define VISTA_FILE "steve.json"
-/// File location for flesh wound descriptions
-#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
-/// File location for bone wound descriptions
-#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
-/// File location for scar wound descriptions
-#define SCAR_LOC_FILE "wounds/scar_loc.json"
-
//Fullscreen overlay resolution in tiles.
#define FULLSCREEN_OVERLAY_RESOLUTION_X 15
#define FULLSCREEN_OVERLAY_RESOLUTION_Y 15
@@ -411,10 +340,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
//Run the world with this parameter to enable a single run though of the game setup and tear down process with unit tests in between
#define TEST_RUN_PARAMETER "test-run"
-//Force the log directory to be something specific in the data/logs folder
-#define OVERRIDE_LOG_DIRECTORY_PARAMETER "log-directory"
-//Prevent the master controller from starting automatically, overrides TEST_RUN_PARAMETER
-#define NO_INIT_PARAMETER "no-init"
//Force the config directory to be something other than "config"
#define OVERRIDE_CONFIG_DIRECTORY_PARAMETER "config-directory"
@@ -423,8 +348,14 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
// Used by PDA and cartridge code to reduce repetitiveness of spritesheets
#define PDAIMG(what) {""}
+#define NEGATIVE_GRAVITY -1
+
#define STANDARD_GRAVITY 1 //Anything above this is high gravity, anything below no grav
#define GRAVITY_DAMAGE_TRESHOLD 3 //Starting with this value gravity will start to damage mobs
+/// The scaling factor for high gravity damage.
+#define GRAVITY_DAMAGE_SCALING 3.5
+/// The maximum [BRUTE] damage a mob can take from high gravity per second.
+#define GRAVITY_DAMAGE_MAXIMUM 4.5
#define CAMERA_NO_GHOSTS 0
#define CAMERA_SEE_GHOSTS_BASIC 1
@@ -432,9 +363,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define CLIENT_FROM_VAR(I) (ismob(I) ? I:client : (istype(I, /client) ? I : (istype(I, /datum/mind) ? I:current?:client : null)))
-/// Possible value of [/atom/movable/buckle_lying]. If set to a different (positive-or-zero) value than this, the buckling thing will force a lying angle on the buckled.
-#define NO_BUCKLE_LYING -1
-
#define AREASELECT_CORNERA "corner A"
#define AREASELECT_CORNERB "corner B"
@@ -455,10 +383,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define GRENADE_WIRED 2
#define GRENADE_READY 3
-//Misc text define. Does 4 spaces. Used as a makeshift tabulator.
-#define FOURSPACES " "
-
-
// camera shooting modes , originally was going to put this under tools since the camera is a tool in rl but wasn't 100% sure
#define CAMERA_STANDARD "standard"
#define CAMERA_DESCRIPTION "description"
@@ -481,3 +405,6 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C
#define ui_vamprank_display "WEST:6,CENTER-2:-5"
/// 6 pixels to the right, zero tiles & 5 pixels DOWN.
#define ui_sunlight_display "WEST:6,CENTER-0:0"
+
+
+
diff --git a/code/__DEFINES/mobfactions.dm b/code/__DEFINES/mobfactions.dm
new file mode 100644
index 000000000000..d503a499d0da
--- /dev/null
+++ b/code/__DEFINES/mobfactions.dm
@@ -0,0 +1,97 @@
+// Contains mob factions that are not handled by special role defines (for example, viscerators having ROLE_SYNDICATE)
+
+// Default factions
+
+/// Acts as a default faction for most violent creatures
+#define FACTION_HOSTILE "hostile"
+/// Acts as a default faction for most peaceful creatures
+#define FACTION_NEUTRAL "neutral"
+
+// Creature factions
+
+/// Ashwalker related creatures
+#define FACTION_ASHWALKER "ashwalker"
+/// Megafauna bosses of mining
+#define FACTION_BOSS "boss"
+/// CARPS
+#define FACTION_CARP "carp"
+/// Creatures summoned by chemical reactions
+#define FACTION_CHEMICAL_SUMMON "chemical_summon"
+/// Clown creatures and the Clown themselves
+#define FACTION_CLOWN "clowns"
+/// Headslugs
+#define FACTION_CREATURE "creature"
+/// Cats
+#define FACTION_CAT "cat"
+/// Faithless and shadowpeople
+#define FACTION_FAITHLESS "faithless"
+/// Gnomes
+#define FACTION_GNOME "gnomes"
+/// Gondolas
+#define FACTION_GONDOLA "gondola"
+/// Slaughterdemons
+#define FACTION_HELL "hell"
+/// Hivebots
+#define FACTION_HIVEBOT "hivebot"
+/// Illusionary creaturs
+#define FACTION_ILLUSION "illusion"
+/// Creatures of the never finished jungle planet, and gorillas
+#define FACTION_JUNGLE "jungle"
+/// Small lizards
+#define FACTION_LIZARD "lizard"
+/// Maint creatures have mutual respect for eachother.
+#define FACTION_MAINT_CREATURES "maint_creatures"
+/// Animated objects and statues
+#define FACTION_MIMIC "mimic"
+/// Beasts found on the various mining environments
+#define FACTION_MINING "mining"
+/// Watchers don't like any creatures other than each other
+#define FACTION_WATCHER "watcher"
+/// Monkeys and gorillas
+#define FACTION_MONKEY "monkey"
+/// Mushrooms and mushroompeople
+#define FACTION_MUSHROOM "mushroom"
+/// Nanotrasen private security
+#define FACTION_NANOTRASEN_PRIVATE "nanotrasen_private"
+/// Mobs from the Netherworld
+#define FACTION_NETHER "nether"
+/// Mobs spawned by the emagged orion arcade
+#define FACTION_ORION "orion"
+/// Penguins and their chicks
+#define FACTION_PENGUIN "penguin"
+/// Plants, lots of overlap with vines
+#define FACTION_PLANTS "plants"
+/// Rats and mice
+#define FACTION_RAT "rats"
+/// Creatures from Space Russia
+#define FACTION_RUSSIAN "russian"
+/// Creatures affiliated with the AI and Cyborgs
+#define FACTION_SILICON "silicon"
+/// Spooky scary skeletons
+#define FACTION_SKELETON "skeleton"
+/// Slimey creatures
+#define FACTION_SLIME "slime"
+/// Spiders and their webs
+#define FACTION_SPIDER "spiders"
+/// Currently used only by floating eyeballs
+#define FACTION_SPOOKY "spooky"
+/// Statues that move around when nobody is watching them
+#define FACTION_STATUE "statue"
+/// Stick creatures summoned by the Paperwizard, and the wizard themselves
+#define FACTION_STICKMAN "stickman"
+/// Creatures ignored by various turrets
+#define FACTION_TURRET "turret"
+/// Vines, lots of overlap with plants
+#define FACTION_VINES "vines"
+
+// Antagonist factions
+
+/// Cultists and their constructs
+#define FACTION_CULT "cult"
+/// Define for the heretic faction applied to heretics and heretic mobs.
+#define FACTION_HERETIC "heretics"
+/// Mainly used by pirate simplemobs. However I placed them here instead, as its also used by players
+#define FACTION_PIRATE "pirate"
+
+/// Generates a mob faction for the passed owner, used by stabilized pink extracts
+#define FACTION_PINK_EXTRACT(owner) "pink_[owner]"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index f38c46bdfa0e..e7f7f15cc4e2 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -249,11 +249,20 @@
#define ENVIRONMENT_SMASH_WALLS (1<<1) //walls
#define ENVIRONMENT_SMASH_RWALLS (1<<2) //rwalls
-#define NO_SLIP_WHEN_WALKING (1<<0)
-#define SLIDE (1<<1)
-#define GALOSHES_DONT_HELP (1<<2)
-#define SLIDE_ICE (1<<3)
-#define SLIP_WHEN_CRAWLING (1<<4) //clown planet ruin
+// Slip flags, also known as lube flags
+/// The mob will not slip if they're walking intent
+#define NO_SLIP_WHEN_WALKING (1<<0)
+/// Slipping on this will send them sliding a few tiles down
+#define SLIDE (1<<1)
+/// Ice slides only go one tile and don't knock you over, they're intended to cause a "slip chain"
+/// where you slip on ice until you reach a non-slippable tile (ice puzzles)
+#define SLIDE_ICE (1<<2)
+/// [TRAIT_NO_SLIP_WATER] does not work on this slip. ONLY [TRAIT_NO_SLIP_ALL] will
+#define GALOSHES_DONT_HELP (1<<3)
+/// Slip works even if you're already on the ground
+#define SLIP_WHEN_CRAWLING (1<<4)
+/// the mob won't slip if the turf has the TRAIT_TURF_IGNORE_SLIPPERY trait.
+#define SLIPPERY_TURF (1<<5)
#define MAX_CHICKENS 50
@@ -305,6 +314,13 @@
#define REAGENTS_METABOLISM 0.4 //How many units of reagent are consumed per tick, by default.
#define REAGENTS_EFFECT_MULTIPLIER (REAGENTS_METABOLISM / 0.4) // By defining the effect multiplier this way, it'll exactly adjust all effects according to how they originally were with the 0.4 metabolism
+// Eye protection
+#define FLASH_PROTECTION_HYPER_SENSITIVE -2
+#define FLASH_PROTECTION_SENSITIVE -1
+#define FLASH_PROTECTION_NONE 0
+#define FLASH_PROTECTION_FLASH 1
+#define FLASH_PROTECTION_WELDER 2
+
// Roundstart trait system
#define MAX_QUIRKS 6 //The maximum amount of quirks one character can have at roundstart
@@ -380,3 +396,28 @@
#define STANDING_UP 0
/// Mob is lying down, usually associated with lying_angle values of 90 or 270.
#define LYING_DOWN 1
+
+/// Possible value of [/atom/movable/buckle_lying]. If set to a different (positive-or-zero) value than this, the buckling thing will force a lying angle on the buckled.
+#define NO_BUCKLE_LYING -1
+
+/// Squashing will not occur if the mob is not lying down (bodyposition is LYING_DOWN)
+#define SQUASHED_SHOULD_BE_DOWN (1<<0)
+/// If present, outright gibs the squashed mob instead of just dealing damage
+#define SQUASHED_SHOULD_BE_GIBBED (1<<1)
+/// If squashing always passes if the mob is dead
+#define SQUASHED_ALWAYS_IF_DEAD (1<<2)
+/// Don't squash our mob if its not located in a turf
+#define SQUASHED_DONT_SQUASH_IN_CONTENTS (1<<3)
+
+// Bitflags for mob dismemberment and gibbing
+/// Mobs will drop a brain
+#define DROP_BRAIN (1<<0)
+/// Mobs will drop organs
+#define DROP_ORGANS (1<<1)
+/// Mobs will drop bodyparts (arms, legs, etc.)
+#define DROP_BODYPARTS (1<<2)
+/// Mobs will drop items
+#define DROP_ITEMS (1<<3)
+
+/// Mobs will drop everything
+#define DROP_ALL_REMAINS (DROP_BRAIN | DROP_ORGANS | DROP_BODYPARTS | DROP_ITEMS)
diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm
index c26cc24925b4..ecbd6e8fc6d6 100644
--- a/code/__DEFINES/movement.dm
+++ b/code/__DEFINES/movement.dm
@@ -14,3 +14,56 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
/// The whole result is then clamped to within the range above.
/// Not very readable but it works
#define DELAY_TO_GLIDE_SIZE(delay) (clamp(((32 / max((delay) / world.tick_lag, 1)) * GLOB.glide_size_multiplier), MIN_GLIDE_SIZE, MAX_GLIDE_SIZE))
+
+
+/**
+ * currently_z_moving defines. Higher numbers mean higher priority.
+ * This one is for falling down open space from stuff such as deleted tile, pit grate...
+ */
+#define CURRENTLY_Z_FALLING 1
+/// currently_z_moving is set to this in zMove() if 0.
+#define CURRENTLY_Z_MOVING_GENERIC 2
+/// This one is for falling down open space from movement.
+#define CURRENTLY_Z_FALLING_FROM_MOVE 3
+/// This one is for going upstairs.
+#define CURRENTLY_Z_ASCENDING 4
+
+/// possible bitflag return values of [atom/proc/intercept_zImpact] calls
+/// Stops the movable from falling further and crashing on the ground. Example: stairs.
+#define FALL_INTERCEPTED (1<<0)
+/// Suppresses the "[movable] falls through [old_turf]" message because it'd make little sense in certain contexts like climbing stairs.
+#define FALL_NO_MESSAGE (1<<1)
+/// Used when the whole intercept_zImpact forvar loop should be stopped. For example: when someone falls into the supermatter and becomes dust.
+#define FALL_STOP_INTERCEPTING (1<<2)
+/// Used when the grip on a pulled object shouldn't be broken.
+#define FALL_RETAIN_PULL (1<<3)
+
+/// Runs check_pulling() by the end of [/atom/movable/proc/zMove] for every movable that's pulling something. Should be kept enabled unless you know what you are doing.
+#define ZMOVE_CHECK_PULLING (1<<0)
+/// Checks if pulledby is nearby. if not, stop being pulled.
+#define ZMOVE_CHECK_PULLEDBY (1<<1)
+/// flags for different checks done in [/atom/movable/proc/can_z_move]. Should be self-explainatory.
+#define ZMOVE_FALL_CHECKS (1<<2)
+#define ZMOVE_CAN_FLY_CHECKS (1<<3)
+#define ZMOVE_INCAPACITATED_CHECKS (1<<4)
+/// Doesn't call zPassIn() and zPassOut()
+#define ZMOVE_IGNORE_OBSTACLES (1<<5)
+/// Gives players chat feedbacks if they're unable to move through z levels.
+#define ZMOVE_FEEDBACK (1<<6)
+/// Whether we check the movable (if it exists) the living mob is buckled on or not.
+#define ZMOVE_ALLOW_BUCKLED (1<<7)
+/// If the movable is actually ventcrawling vertically.
+#define ZMOVE_VENTCRAWLING (1<<8)
+/// Includes movables that're either pulled by the source or mobs buckled to it in the list of moving movables.
+#define ZMOVE_INCLUDE_PULLED (1<<9)
+/// Skips check for whether the moving atom is anchored or not.
+#define ZMOVE_ALLOW_ANCHORED (1<<10)
+
+#define ZMOVE_CHECK_PULLS (ZMOVE_CHECK_PULLING|ZMOVE_CHECK_PULLEDBY)
+
+/// Flags used in "Move Upwards" and "Move Downwards" verbs.
+#define ZMOVE_FLIGHT_FLAGS (ZMOVE_CAN_FLY_CHECKS|ZMOVE_INCAPACITATED_CHECKS|ZMOVE_CHECK_PULLS|ZMOVE_ALLOW_BUCKLED)
+/// Used when walking upstairs
+#define ZMOVE_STAIRS_FLAGS (ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED)
+/// Used for falling down open space.
+#define ZMOVE_FALL_FLAGS (ZMOVE_FALL_CHECKS|ZMOVE_ALLOW_BUCKLED)
diff --git a/code/__DEFINES/multiz.dm b/code/__DEFINES/multiz.dm
new file mode 100644
index 000000000000..370eaa8ba459
--- /dev/null
+++ b/code/__DEFINES/multiz.dm
@@ -0,0 +1,8 @@
+/// Attempt to get the turf below the provided one according to Z traits
+#define GET_TURF_BELOW(turf) ( \
+ (turf.turf_flags & RESERVATION_TURF) ? SSmapping.get_reservation_from_turf(turf)?.get_turf_below(turf) : \
+ (!(turf) || !length(SSmapping.multiz_levels) || !SSmapping.multiz_levels[(turf).z][Z_LEVEL_DOWN]) ? null : get_step((turf), DOWN))
+/// Attempt to get the turf above the provided one according to Z traits
+#define GET_TURF_ABOVE(turf) ( \
+ (turf.turf_flags & RESERVATION_TURF) ? SSmapping.get_reservation_from_turf(turf)?.get_turf_above(turf) : \
+ (!(turf) || !length(SSmapping.multiz_levels) || !SSmapping.multiz_levels[(turf).z][Z_LEVEL_UP]) ? null : get_step((turf), UP))
diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm
index 4c5d348b082c..d842fc919bc4 100644
--- a/code/__DEFINES/obj_flags.dm
+++ b/code/__DEFINES/obj_flags.dm
@@ -4,14 +4,21 @@
#define EMAGGED (1<<0)
#define IN_USE (1<<1) // If we have a user using us, this will be set on. We will check if the user has stopped using us, and thus stop updating and LAGGING EVERYTHING!
#define CAN_BE_HIT (1<<2) //can this be bludgeoned by items?
-#define BEING_SHOCKED (1<<3) // Whether this thing is currently (already) being shocked by a tesla
-#define DANGEROUS_POSSESSION (1<<4) //Admin possession yes/no
-#define ON_BLUEPRINTS (1<<5) //Are we visible on the station blueprints at roundstart?
-#define UNIQUE_RENAME (1<<6) // can you customize the name of the thing?
-#define USES_TGUI (1<<7) //put on things that use tgui on ui_interact instead of custom/old UI.
-#define FROZEN (1<<8)
-#define UNIQUE_REDESC (1<<9) // can you customize the description of the thing?
-#define CMAGGED (1<<10)
+#define DANGEROUS_POSSESSION (1<<3) //Admin possession yes/no
+#define BEING_SHOCKED (1<<4) // Whether this thing is currently (already) being shocked by a tesla
+#define BLOCK_Z_OUT_DOWN (1<<5) // Should this object block z falling from loc?
+#define BLOCK_Z_OUT_UP (1<<6) // Should this object block z uprise from loc?
+#define BLOCK_Z_IN_DOWN (1<<7) // Should this object block z falling from above?
+#define BLOCK_Z_IN_UP (1<<8) // Should this object block z uprise from below?
+#define BLOCKS_CONSTRUCTION (1<<9) //! Does this object prevent things from being built on it?
+#define BLOCKS_CONSTRUCTION_DIR (1<<10) //! Does this object prevent same-direction things from being built on it?
+#define IGNORE_DENSITY (1<<11) //! Can we ignore density when building on this object? (for example, directional windows and grilles)
+#define ON_BLUEPRINTS (1<<12) //Are we visible on the station blueprints at roundstart?
+#define UNIQUE_RENAME (1<<13) // can you customize the name of the thing?
+#define USES_TGUI (1<<14) //put on things that use tgui on ui_interact instead of custom/old UI.
+#define FROZEN (1<<15)
+#define UNIQUE_REDESC (1<<16) // can you customize the description of the thing?
+#define CMAGGED (1<<17)
// If you add new ones, be sure to add them to /obj/Initialize as well for complete mapping support
@@ -86,3 +93,4 @@
#define ADD_CLOTHING_TRAIT(mob, trait) ADD_TRAIT(mob, trait, "[CLOTHING_TRAIT]_[REF(src)]")
/// Wrapper for removing clothing based traits
#define REMOVE_CLOTHING_TRAIT(mob, trait) REMOVE_TRAIT(mob, trait, "[CLOTHING_TRAIT]_[REF(src)]")
+
diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm
new file mode 100644
index 000000000000..2dbc9b7c68b8
--- /dev/null
+++ b/code/__DEFINES/projectiles.dm
@@ -0,0 +1,10 @@
+///Not actually hitscan but close as we get without actual hitscan.
+#define MOVES_HITSCAN -1
+///How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers.
+#define MUZZLE_EFFECT_PIXEL_INCREMENT 17
+///Will always ricochet off of coins.
+#define ALWAYS_RICOSHOT 2
+
+// Penetration flags
+#define PENETRATE_OBJECTS (1<<0)
+#define PENETRATE_MOBS (1<<1)
diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm
index a5ed9eb4b5d1..cffa494d5c1a 100644
--- a/code/__DEFINES/robots.dm
+++ b/code/__DEFINES/robots.dm
@@ -58,3 +58,9 @@
#define BORG_MODULE_ALL_DISABLED (1<<0)
#define BORG_MODULE_TWO_DISABLED (1<<1)
#define BORG_MODULE_THREE_DISABLED (1<<2)
+
+//bot navigation beacon defines
+#define NAVBEACON_PATROL_MODE "patrol"
+#define NAVBEACON_PATROL_NEXT "next_patrol"
+#define NAVBEACON_DELIVERY_MODE "delivery"
+#define NAVBEACON_DELIVERY_DIRECTION "dir"
diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm
index 456467a56761..33ed99d829aa 100644
--- a/code/__DEFINES/say.dm
+++ b/code/__DEFINES/say.dm
@@ -112,3 +112,18 @@
//Used in visible_message_flags, audible_message_flags and runechat_flags
#define EMOTE_MESSAGE (1<<0)
+
+//Typing indicator defines, used in /mob/create_typing_indicator()
+#define BUBBLE_DEFAULT "default"
+#define BUBBLE_LAWYER "lawyer"
+#define BUBBLE_ROBOT "robot"
+#define BUBBLE_MACHINE "machine"
+#define BUBBLE_SYNDIBOT "syndibot"
+#define BUBBLE_SWARMER "swarmer"
+#define BUBBLE_SLIME "slime"
+#define BUBBLE_CLOCK "clock"
+#define BUBBLE_ALIEN "alien"
+#define BUBBLE_ALIENROYAL "alienroyal"
+#define BUBBLE_DARKSPAWN "darkspawn"
+#define BUBBLE_GUARDIAN "guardian"
+#define BUBBLE_BLOB "blob"
diff --git a/code/__DEFINES/screentips.dm b/code/__DEFINES/screentips.dm
new file mode 100644
index 000000000000..9d56e8cf36e5
--- /dev/null
+++ b/code/__DEFINES/screentips.dm
@@ -0,0 +1,32 @@
+/// Context applied to LMB actions
+#define SCREENTIP_CONTEXT_LMB "LMB"
+
+/// Context applied to RMB actions
+#define SCREENTIP_CONTEXT_RMB "RMB"
+
+/// Context applied to Shift-LMB actions
+#define SCREENTIP_CONTEXT_SHIFT_LMB "Shift-LMB"
+
+/// Context applied to Ctrl-LMB actions
+#define SCREENTIP_CONTEXT_CTRL_LMB "Ctrl-LMB"
+
+/// Context applied to Ctrl-RMB actions
+#define SCREENTIP_CONTEXT_CTRL_RMB "Ctrl-RMB"
+
+/// Context applied to Alt-LMB actions
+#define SCREENTIP_CONTEXT_ALT_LMB "Alt-LMB"
+
+/// Context applied to Alt-RMB actions
+#define SCREENTIP_CONTEXT_ALT_RMB "Alt-RMB"
+
+/// Context applied to Ctrl-Shift-LMB actions
+#define SCREENTIP_CONTEXT_CTRL_SHIFT_LMB "Ctrl-Shift-LMB"
+
+/// Screentips are always disabled
+#define SCREENTIP_PREFERENCE_DISABLED "Disabled"
+
+/// Screentips are always enabled
+#define SCREENTIP_PREFERENCE_ENABLED "Enabled"
+
+/// Screentips are only enabled when they have context
+#define SCREENTIP_PREFERENCE_CONTEXT_ONLY "Only with tips"
diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm
index f6a8b782498e..307af49de783 100644
--- a/code/__DEFINES/shuttles.dm
+++ b/code/__DEFINES/shuttles.dm
@@ -1,18 +1,20 @@
//shuttle mode defines
-#define SHUTTLE_IDLE "idle"
-#define SHUTTLE_IGNITING "igniting"
-#define SHUTTLE_RECALL "recall"
-#define SHUTTLE_CALL "call"
-#define SHUTTLE_DOCKED "docked"
-#define SHUTTLE_STRANDED "stranded"
-#define SHUTTLE_ESCAPE "escape"
-#define SHUTTLE_ENDGAME "endgame: game over"
-#define SHUTTLE_RECHARGING "recharging"
-#define SHUTTLE_PREARRIVAL "landing"
+#define SHUTTLE_IDLE "idle"
+#define SHUTTLE_IGNITING "igniting"
+#define SHUTTLE_RECALL "recalled"
+#define SHUTTLE_CALL "called"
+#define SHUTTLE_DOCKED "docked"
+#define SHUTTLE_STRANDED "stranded"
+#define SHUTTLE_DISABLED "disabled"
+#define SHUTTLE_ESCAPE "escape"
+#define SHUTTLE_ENDGAME "endgame: game over"
+#define SHUTTLE_RECHARGING "recharging"
+#define SHUTTLE_PREARRIVAL "landing"
#define EMERGENCY_IDLE_OR_RECALLED (SSshuttle.emergency && ((SSshuttle.emergency.mode == SHUTTLE_IDLE) || (SSshuttle.emergency.mode == SHUTTLE_RECALL)))
#define EMERGENCY_ESCAPED_OR_ENDGAMED (SSshuttle.emergency && ((SSshuttle.emergency.mode == SHUTTLE_ESCAPE) || (SSshuttle.emergency.mode == SHUTTLE_ENDGAME)))
#define EMERGENCY_AT_LEAST_DOCKED (SSshuttle.emergency && SSshuttle.emergency.mode != SHUTTLE_IDLE && SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_CALL)
+#define EMERGENCY_PAST_POINT_OF_NO_RETURN ((SSshuttle.emergency && SSshuttle.emergency.mode == SHUTTLE_CALL && !SSshuttle.canRecall()) || EMERGENCY_AT_LEAST_DOCKED)
// Shuttle return values
#define SHUTTLE_CAN_DOCK "can_dock"
@@ -27,8 +29,11 @@
//Launching Shuttles to CentCom
#define NOLAUNCH -1
#define UNLAUNCHED 0
+/// This shuttle launched to head to end game (or has arrived there), it will not update further
#define ENDGAME_LAUNCHED 1
#define EARLY_LAUNCHED 2
+/// "Endgame transit" denotes shuttles which "fly into the sunset" when the round ends, such as the whiteship.
+/// These shuttles leave when the main emergency shuttle does but don't dock anywhere (to save space), so this counts as "escaped".
#define ENDGAME_TRANSIT 3
//positive value = cannot purchase
@@ -42,7 +47,7 @@
#define TRANSIT_REQUEST 1
#define TRANSIT_READY 2
-#define SHUTTLE_TRANSIT_BORDER 8
+#define SHUTTLE_TRANSIT_BORDER 16
#define PARALLAX_LOOP_TIME 2.5 SECONDS
#define HYPERSPACE_END_TIME 0.5 SECONDS
@@ -84,6 +89,30 @@
#define SHUTTLE_DEFAULT_SHUTTLE_AREA_TYPE /area/shuttle
#define SHUTTLE_DEFAULT_UNDERLYING_AREA /area/space
+/// Shuttle unlocks
+// Needs Alien Technology researched.
+#define SHUTTLE_UNLOCK_ALIENTECH "abductor"
+// Needs bubblegum to die.
+#define SHUTTLE_UNLOCK_BUBBLEGUM "bubblegum"
+// Needs one to set the holodeck to Medieval Sim.
+#define SHUTTLE_UNLOCK_MEDISIM "holodeck"
+// Needs a rune to be cleared by a null rod.
+#define SHUTTLE_UNLOCK_NARNAR "narnar"
+// Needs someone to be polymorphed - Pride Mirror, Magic Mirror, Race Swap, Polymorph Staff/Wand. Badmin Mirror doesn't count, neither does xenobio.
+#define SHUTTLE_UNLOCK_WABBAJACK "wabbajack"
+// Needs cargo budget to be almost empty to be purchasable.
+#define SHUTTLE_UNLOCK_SCRAPHEAP "scrapheap"
+
+//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
+
///Check for arena shuttle, if the bubblegum has died this round
GLOBAL_VAR_INIT(bubblegum_dead, FALSE)
diff --git a/code/__DEFINES/sight.dm b/code/__DEFINES/sight.dm
index 862c851f187f..645e00941359 100644
--- a/code/__DEFINES/sight.dm
+++ b/code/__DEFINES/sight.dm
@@ -1,3 +1,5 @@
+#define INVISIBILITY_NONE 0
+
#define SEE_INVISIBLE_MINIMUM 5
#define INVISIBILITY_LIGHTING 20
@@ -10,6 +12,8 @@
//#define SEE_INVISIBLE_LEVEL_TWO 45 //currently unused
//#define INVISIBILITY_LEVEL_TWO 45 //currently unused
+#define INVISIBILITY_REVENANT 50
+
#define INVISIBILITY_OBSERVER 60
#define SEE_INVISIBLE_OBSERVER 60
@@ -17,16 +21,58 @@
#define INVISIBILITY_ABSTRACT 101 //only used for abstract objects (e.g. spacevine_controller), things that are not really there.
-#define BORGMESON (1<<0)
-#define BORGMESON_NIGHTVISION (1<<1)
-#define BORGTHERM (1<<2)
-#define BORGXRAY (1<<3)
-#define BORGMATERIAL (1<<4)
+#define BORGMESON (1<<0)
+#define BORGTHERM (1<<1)
+#define BORGXRAY (1<<2)
+#define BORGMATERIAL (1<<3)
//for clothing visor toggles, these determine which vars to toggle
-#define VISOR_FLASHPROTECT (1<<0)
-#define VISOR_TINT (1<<1)
-#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses
-#define VISOR_DARKNESSVIEW (1<<3)
-#define VISOR_INVISVIEW (1<<4)
+#define VISOR_FLASHPROTECT (1<<0)
+#define VISOR_TINT (1<<1)
+#define VISOR_VISIONFLAGS (1<<2) //all following flags only matter for glasses
+#define VISOR_INVISVIEW (1<<3)
+
+// BYOND internal values for the sight flags
+// See [https://www.byond.com/docs/ref/#/mob/var/sight]
+/// can't see anything
+//#define BLIND (1<<0)
+/// can see all mobs, no matter what
+//#define SEE_MOBS (1<<2)
+/// can see all objs, no matter what
+//#define SEE_OBJS (1<<3)
+// can see all turfs (and areas), no matter what
+//#define SEE_TURFS (1<<4)
+/// can see self, no matter what
+//#define SEE_SELF (1<<5)
+/// can see infra-red objects (different sort of luminosity, essentially a copy of it, one we do not use)
+//#define SEE_INFRA (1<<6)
+/// if an object is located on an unlit area, but some of its pixels are
+/// in a lit area (via pixel_x,y or smooth movement), can see those pixels
+//#define SEE_PIXELS (1<<8)
+/// can see through opaque objects
+//#define SEE_THRU (1<<9)
+/// render dark tiles as blackness (Note, this basically means we draw dark tiles to plane 0)
+/// we can then hijack that plane with a plane master, and start drawing it anywhere we want
+/// NOTE: this does not function with the SIDE_MAP map format. So we can't. :(
+//#define SEE_BLACKNESS (1<<10)
+
+/// Bitfield of sight flags that show THINGS but no lighting
+/// Since lighting is an underlay on turfs, this is everything but that
+#define SEE_AVOID_TURF_BLACKNESS (SEE_MOBS|SEE_OBJS)
+
+//------------------------
+// INVISIBILITY PRIORITIES
+
+#define INVISIBILITY_PRIORITY_ADMIN 100
+#define INVISIBILITY_PRIORITY_TURRET_COVER 20
+#define INVISIBILITY_PRIORITY_BASIC_ANTI_INVISIBILITY 10
+#define INVISIBILITY_PRIORITY_NONE 0
+
+//------------------------
+// INVISIBILITY SOURCE IDS
+// Though don't feel the need to add one here if you have a simple effect that
+// gets added and/or removed in only one place near eachother in the code.
+
+#define INVISIBILITY_SOURCE_INVISIMIN "invisimin"
+#define INVISIBILITY_SOURCE_STEALTHMODE "stealthmode"
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index 385b6108efad..0215cae1d8a9 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -3,7 +3,7 @@
#define CHANNEL_ADMIN 1023
#define CHANNEL_VOX 1022
#define CHANNEL_JUKEBOX 1021
-#define CHANNEL_JUSTICAR_ARK 1020
+#define CHANNEL_JUSTICIAR_ARK 1020
#define CHANNEL_HEARTBEAT 1019 //sound channel for heartbeats
#define CHANNEL_AMBIENT_EFFECTS 1018
#define CHANNEL_AMBIENT_MUSIC 1017
@@ -137,3 +137,37 @@ GLOBAL_LIST_INIT(announcer_keys, list(
ANNOUNCER_SHUTTLERECALLED,
ANNOUNCER_SPANOMALIES,
))
+
+/// List of all of our sound keys.
+#define SFX_BODYFALL "bodyfall"
+#define SFX_BULLET_MISS "bullet_miss"
+#define SFX_CAN_OPEN "can_open"
+#define SFX_CLOWN_STEP "clown_step"
+#define SFX_DESECRATION "desecration"
+#define SFX_EXPLOSION "explosion"
+#define SFX_EXPLOSION_CREAKING "explosion_creaking"
+#define SFX_HISS "hiss"
+#define SFX_HONKBOT_E "honkbot_e"
+#define SFX_GOOSE "goose"
+#define SFX_HULL_CREAKING "hull_creaking"
+#define SFX_HYPERTORUS_CALM "hypertorus_calm"
+#define SFX_HYPERTORUS_MELTING "hypertorus_melting"
+#define SFX_IM_HERE "im_here"
+#define SFX_LAW "law"
+#define SFX_PAGE_TURN "page_turn"
+#define SFX_PUNCH "punch"
+#define SFX_REVOLVER_SPIN "revolver_spin"
+#define SFX_RICOCHET "ricochet"
+#define SFX_RUSTLE "rustle"
+#define SFX_SHATTER "shatter"
+#define SFX_SM_CALM "sm_calm"
+#define SFX_SM_DELAM "sm_delam"
+#define SFX_SPARKS "sparks"
+#define SFX_SUIT_STEP "suit_step"
+#define SFX_SWING_HIT "swing_hit"
+#define SFX_TERMINAL_TYPE "terminal_type"
+#define SFX_WARPSPEED "warpspeed"
+#define SFX_CRUNCHY_BUSH_WHACK "crunchy_bush_whack"
+#define SFX_TREE_CHOP "tree_chop"
+#define SFX_ROCK_TAP "rock_tap"
+#define SFX_SEAR "sear"
diff --git a/code/__DEFINES/space.dm b/code/__DEFINES/space.dm
new file mode 100644
index 000000000000..eff2aebff3e7
--- /dev/null
+++ b/code/__DEFINES/space.dm
@@ -0,0 +1,5 @@
+#define SPACE_SIGNAL_GPSTAG "Distant Signal"
+
+/// Every mob in the game will have a screen object sitting inside them with a reference matching this
+/// So you can render_source against it and never need to update it again
+#define SPACE_OVERLAY_RENDER_TARGET(offset) "*space_overlay_target[offset]"
diff --git a/code/__DEFINES/station.dm b/code/__DEFINES/station.dm
index beaad1993599..5a04961429fd 100644
--- a/code/__DEFINES/station.dm
+++ b/code/__DEFINES/station.dm
@@ -2,4 +2,23 @@
#define STATION_TRAIT_NEUTRAL 2
#define STATION_TRAIT_NEGATIVE 3
-#define STATION_TRAIT_ABSTRACT (1<<0)
\ No newline at end of file
+///Defines for the cost of different station traits. This one is the default.
+#define STATION_TRAIT_COST_FULL 1
+///Cost for smaller traits that could fly under the radar, and are only minorly negative/positive if not neutral.
+#define STATION_TRAIT_COST_LOW 0.5
+///Cost for very little, and mainly neutral traits that hardly amount to anything really that interesting.
+#define STATION_TRAIT_COST_MINIMAL 0.3
+
+/// Only run on planet stations
+#define STATION_TRAIT_PLANETARY (1<<0)
+/// Only run on space stations
+#define STATION_TRAIT_SPACE_BOUND (1<<1)
+
+/// Not restricted by space or planet, can always just happen
+#define STATION_TRAIT_MAP_UNRESTRICTED STATION_TRAIT_PLANETARY | STATION_TRAIT_SPACE_BOUND
+
+/// The data file that future station traits forced by an admin are stored in
+#define FUTURE_STATION_TRAITS_FILE "data/future_station_traits.json"
+
+/// The amount of time until the station charter can no longer be used to rename the station
+#define STATION_RENAME_TIME_LIMIT 5 MINUTES
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index ab49290820fb..359711f46144 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -116,6 +116,8 @@
#define STATUS_EFFECT_EXPOSED /datum/status_effect/exposed //increases incoming damage
+#define STATUS_EFFECT_EXPOSED_HARPOONED /datum/status_effect/exposed/harpooned //increases incoming damage when hit by a gasharpoon
+
#define STATUS_EFFECT_TAMING /datum/status_effect/taming //tames the target after enough tame stacks
#define STATUS_EFFECT_NECROPOLIS_CURSE /datum/status_effect/necropolis_curse
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 944f6ef36c98..696563feadb6 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -69,9 +69,9 @@
///New should not call Initialize
#define INITIALIZATION_INSSATOMS 0
-///New should call Initialize(mapload, TRUE)
+///New should call Initialize(TRUE)
#define INITIALIZATION_INNEW_MAPLOAD 2
-///New should call Initialize(mapload, FALSE)
+///New should call Initialize(FALSE)
#define INITIALIZATION_INNEW_REGULAR 1
//! ### Initialization hints
@@ -79,12 +79,12 @@
///Nothing happens
#define INITIALIZE_HINT_NORMAL 0
/**
- * call LateInitialize at the end of all atom Initalization
- *
- * The item will be added to the late_loaders list, this is iterated over after
- * initalization of subsystems is complete and calls LateInitalize on the atom
- * see [this file for the LateIntialize proc](atom.html#proc/LateInitialize)
- */
+ * call LateInitialize at the end of all atom Initalization
+ *
+ * The item will be added to the late_loaders list, this is iterated over after
+ * initalization of subsystems is complete and calls LateInitalize on the atom
+ * see [this file for the LateIntialize proc](atom.html#proc/LateInitialize)
+ */
#define INITIALIZE_HINT_LATELOAD 1
///Call qdel on the atom after intialization
@@ -92,11 +92,14 @@
///type and all subtypes should always immediately call Initialize in New()
#define INITIALIZE_IMMEDIATE(X) ##X/New(loc, ...){\
- ..();\
- if(!(flags_1 & INITIALIZED_1)) {\
- args[1] = TRUE;\
- SSatoms.InitAtom(src, args);\
- }\
+ ..();\
+ if(!(flags_1 & INITIALIZED_1)) {\
+ var/previous_initialized_value = SSatoms.initialized;\
+ SSatoms.initialized = INITIALIZATION_INNEW_MAPLOAD;\
+ args[1] = TRUE;\
+ SSatoms.InitAtom(src, FALSE, args);\
+ SSatoms.initialized = previous_initialized_value;\
+ }\
}
//! ### SS initialization hints
@@ -117,6 +120,9 @@
/// Successful, but don't print anything. Useful if subsystem was disabled.
#define SS_INIT_NO_NEED 3
+/// Succesfully initialized, BUT do not announce it to players (generally to hide game mechanics it would otherwise spoil)
+#define SS_INIT_NO_MESSAGE 4
+
//! ### SS initialization load orders
// Subsystem init_order, from highest priority to lowest priority
// Subsystems shutdown in the reverse of the order they initialize in
@@ -138,8 +144,8 @@
#define INIT_ORDER_QUIRKS 73
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
-#define INIT_ORDER_MAPPING 60
-#define INIT_ORDER_TICKER 50
+#define INIT_ORDER_TICKER 55
+#define INIT_ORDER_MAPPING 50
#define INIT_ORDER_EARLY_ASSETS 48
#define INIT_ORDER_NETWORKS 45
#define INIT_ORDER_ECONOMY 40
@@ -151,7 +157,7 @@
#define INIT_ORDER_TIMER 1
#define INIT_ORDER_DEFAULT 0
#define INIT_ORDER_AIR_MACHINERY -0.5
-#define INIT_ORDER_AIR -2
+#define INIT_ORDER_AIR -1
#define INIT_ORDER_PERSISTENCE -2
#define INIT_ORDER_PERSISTENT_PAINTINGS -3 // Assets relies on this
#define INIT_ORDER_ASSETS -4
@@ -163,10 +169,12 @@
#define INIT_ORDER_LIGHTING -20
#define INIT_ORDER_SHUTTLE -21
#define INIT_ORDER_MINOR_MAPPING -40
+#define INIT_ORDER_BLUESPACE_LOCKER -45
#define INIT_ORDER_PATH -50
#define INIT_ORDER_DISCORD -60
#define INIT_ORDER_EXPLOSIONS -69
-#define INIT_ORDER_STATPANELS -98
+#define INIT_ORDER_STATPANELS -97
+#define INIT_ORDER_INIT_PROFILER -98 //Near the end, logs the costs of initialize
#define INIT_ORDER_DEMO -99 // To avoid a bunch of changes related to initialization being written, do this last
#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init.
@@ -188,11 +196,11 @@
#define FIRE_PRIORITY_THROWING 25
#define FIRE_PRIORITY_SPACEDRIFT 30
#define FIRE_PRIORITY_FIELDS 30
-#define FIRE_PRIOTITY_SMOOTHING 35
+#define FIRE_PRIORITY_SMOOTHING 35
#define FIRE_PRIORITY_NETWORKS 40
#define FIRE_PRIORITY_OBJ 40
#define FIRE_PRIORITY_ACID 40
-#define FIRE_PRIOTITY_BURNING 40
+#define FIRE_PRIORITY_BURNING 40
#define FIRE_PRIORITY_DEFAULT 50
#define FIRE_PRIORITY_PARALLAX 65
#define FIRE_PRIORITY_INSTRUMENTS 80
@@ -211,14 +219,33 @@
// SS runlevels
-#define RUNLEVEL_INIT 0
-#define RUNLEVEL_LOBBY 1
-#define RUNLEVEL_SETUP 2
-#define RUNLEVEL_GAME 4
-#define RUNLEVEL_POSTGAME 8
+#define RUNLEVEL_LOBBY (1<<0)
+#define RUNLEVEL_SETUP (1<<1)
+#define RUNLEVEL_GAME (1<<2)
+#define RUNLEVEL_POSTGAME (1<<3)
#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME)
+//SSticker.current_state values
+/// Game is loading
+#define GAME_STATE_STARTUP 0
+/// Game is loaded and in pregame lobby
+#define GAME_STATE_PREGAME 1
+/// Game is attempting to start the round
+#define GAME_STATE_SETTING_UP 2
+/// Game has round in progress
+#define GAME_STATE_PLAYING 3
+/// Game has round finished
+#define GAME_STATE_FINISHED 4
+
+// Used for SSticker.force_ending
+/// Default, round is not being forced to end.
+#define END_ROUND_AS_NORMAL 0
+/// End the round now as normal
+#define FORCE_END_ROUND 1
+/// For admin forcing roundend, can be used to distinguish the two
+#define ADMIN_FORCE_END_ROUND 2
+
// SSair run section
#define SSAIR_PIPENETS 1
#define SSAIR_ATMOSMACHINERY 2
diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm
index 823a88345de6..dc17aca279b5 100644
--- a/code/__DEFINES/text.dm
+++ b/code/__DEFINES/text.dm
@@ -1,10 +1,84 @@
+/// Does 4 spaces. Used as a makeshift tabulator.
+#define FOURSPACES " "
+
+/// Standard maptext
/// Prepares a text to be used for maptext. Use this so it doesn't look hideous.
#define MAPTEXT(text) {"[##text]"}
-/// Macro from Lummox used to get height from a MeasureText proc
-#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1))
+/**
+ * Pixel-perfect scaled fonts for use in the MAP element as defined in skin.dmf
+ *
+ * Four sizes to choose from, use the sizes as mentioned below.
+ * Between the variations and a step there should be an option that fits your use case.
+ * BYOND uses pt sizing, different than px used in TGUI. Using px will make it look blurry due to poor antialiasing.
+ *
+ * Default sizes are prefilled in the macro for ease of use and a consistent visual look.
+ * To use a step other than the default in the macro, specify it in a span style.
+ * For example: MAPTEXT_PIXELLARI("Some large maptext here")
+ */
+/// Large size (ie: context tooltips) - Size options: 12pt 24pt.
+#define MAPTEXT_PIXELLARI(text) {"[##text]"}
+
+/// Standard size (ie: normal runechat) - Size options: 6pt 12pt 18pt.
+#define MAPTEXT_GRAND9K(text) {"[##text]"}
+
+/// Small size. (ie: context subtooltips, spell delays) - Size options: 12pt 24pt.
+#define MAPTEXT_TINY_UNICODE(text) {"[##text]"}
+
+/// Smallest size. (ie: whisper runechat) - Size options: 6pt 12pt 18pt.
+#define MAPTEXT_SPESSFONT(text) {"[##text]"}
+
+/**
+ * Prepares a text to be used for maptext, using a variable size font.
+ *
+ * More flexible but doesn't scale pixel perfect to BYOND icon resolutions.
+ * (May be blurry.) Can use any size in pt or px.
+ *
+ * You MUST Specify the size when using the macro
+ * For example: MAPTEXT_VCR_OSD_MONO("Some large maptext here")
+ */
+/// Prepares a text to be used for maptext, using a variable size font.
+/// Variable size font. More flexible but doesn't scale pixel perfect to BYOND icon resolutions. (May be blurry.) Can use any size in pt or px.
+#define MAPTEXT_VCR_OSD_MONO(text) {"[##text]"}
+
+/// Macro from Lummox used to get height from a MeasureText proc.
+/// resolves the MeasureText() return value once, then resolves the height, then sets return_var to that.
+#define WXH_TO_HEIGHT(measurement, return_var) \
+ do { \
+ var/_measurement = measurement; \
+ return_var = text2num(copytext(_measurement, findtextEx(_measurement, "x") + 1)); \
+ } while(FALSE);
+
+/// Removes characters incompatible with file names.
+#define SANITIZE_FILENAME(text) (GLOB.filename_forbidden_chars.Replace(text, ""))
+
+/// Simply removes the < and > characters, and limits the length of the message.
+#define STRIP_HTML_SIMPLE(text, limit) (GLOB.angular_brackets.Replace(copytext(text, 1, limit), ""))
+
+/// Removes everything enclose in < and > inclusive of the bracket, and limits the length of the message.
+#define STRIP_HTML_FULL(text, limit) (GLOB.html_tags.Replace(copytext(text, 1, limit), ""))
/*
* Uses MAPTEXT to format antag points into a more appealing format
*/
#define ANTAG_MAPTEXT(value, color) MAPTEXT("
[round(value)]
")
+
+//text files
+/// File location for brain damage traumas
+#define BRAIN_DAMAGE_FILE "traumas.json"
+/// File location for AI ion laws
+#define ION_FILE "ion_laws.json"
+/// File location for pirate names
+#define PIRATE_NAMES_FILE "pirates.json"
+/// File location for redpill questions
+#define REDPILL_FILE "redpill.json"
+/// File location for wanted posters messages
+#define WANTED_FILE "wanted_message.json"
+/// File location for really dumb suggestions memes
+#define VISTA_FILE "steve.json"
+/// File location for flesh wound descriptions
+#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
+/// File location for bone wound descriptions
+#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
+/// File location for scar wound descriptions
+#define SCAR_LOC_FILE "wounds/scar_loc.json"
diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm
index f9e1c7278325..742b56c4f4c8 100644
--- a/code/__DEFINES/time.dm
+++ b/code/__DEFINES/time.dm
@@ -1,4 +1,17 @@
-#define MIDNIGHT_ROLLOVER 864000 //number of deciseconds in a day
+///number of deciseconds in a day
+#define MIDNIGHT_ROLLOVER 864000
+
+///displays the current time into the round, with a lot of extra code just there for ensuring it looks okay after an entire day passes
+#define ROUND_TIME(...) ( "[world.time - SSticker.round_start_time > MIDNIGHT_ROLLOVER ? "[round((world.time - SSticker.round_start_time)/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]" )
+
+///Returns the time that has passed since the game started
+#define STATION_TIME_PASSED(...) (world.time - SSticker.round_start_time)
+
+/// Define that just has the current in-universe year for use in whatever context you might want to display that in. (For example, 2022 -> 2562 given a 540 year offset)
+#define CURRENT_STATION_YEAR (GLOB.year_integer + STATION_YEAR_OFFSET)
+
+/// In-universe, SS13 is set 540 years in the future from the real-world day, hence this number for determining the year-offset for the in-game year.
+#define STATION_YEAR_OFFSET 540
#define JANUARY 1
#define FEBRUARY 2
@@ -21,6 +34,10 @@
#define HALLOWEEN "Halloween"
#define CHRISTMAS "Christmas"
#define FESTIVE_SEASON "Festive Season"
+#define GARBAGEDAY "Garbage Day"
+#define MONKEYDAY "Monkey Day"
+#define PRIDE_WEEK "Pride Week"
+#define MOTH_WEEK "Moth Week"
/*
@@ -31,13 +48,15 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
*/
#define MONDAY "Mon"
-#define TUESDAY "Tue"
+#define TUESDAY "Tue"
#define WEDNESDAY "Wed"
#define THURSDAY "Thu"
#define FRIDAY "Fri"
#define SATURDAY "Sat"
#define SUNDAY "Sun"
+#define INFINITE -1 // -1 is commonly used to indicate an infinite time duration
+
#define MILLISECONDS *0.01
#define DECISECONDS *1 //the base unit all of these defines are scaled by, because byond uses that as a unit of measurement for some fucking reason
@@ -57,3 +76,98 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
#define MS2DS(T) ((T) MILLISECONDS)
#define DS2MS(T) ((T) * 100)
+
+/*Timezones*/
+
+/// Line Islands Time
+#define TIMEZONE_LINT 14
+
+// Chatham Daylight Time
+#define TIMEZONE_CHADT 13.75
+
+/// Tokelau Time
+#define TIMEZONE_TKT 13
+
+/// Tonga Time
+#define TIMEZONE_TOT 13
+
+/// New Zealand Daylight Time
+#define TIMEZONE_NZDT 13
+
+/// New Zealand Standard Time
+#define TIMEZONE_NZST 12
+
+/// Norfolk Time
+#define TIMEZONE_NFT 11
+
+/// Lord Howe Standard Time
+#define TIMEZONE_LHST 10.5
+
+/// Australian Eastern Standard Time
+#define TIMEZONE_AEST 10
+
+/// Australian Central Standard Time
+#define TIMEZONE_ACST 9.5
+
+/// Australian Central Western Standard Time
+#define TIMEZONE_ACWST 8.75
+
+/// Australian Western Standard Time
+#define TIMEZONE_AWST 8
+
+/// Christmas Island Time
+#define TIMEZONE_CXT 7
+
+/// Cocos Islands Time
+#define TIMEZONE_CCT 6.5
+
+/// Central European Summer Time
+#define TIMEZONE_CEST 2
+
+/// Coordinated Universal Time
+#define TIMEZONE_UTC 0
+
+/// Eastern Daylight Time
+#define TIMEZONE_EDT -4
+
+/// Eastern Standard Time
+#define TIMEZONE_EST -5
+
+/// Central Daylight Time
+#define TIMEZONE_CDT -5
+
+/// Central Standard Time
+#define TIMEZONE_CST -6
+
+/// Mountain Daylight Time
+#define TIMEZONE_MDT -6
+
+/// Mountain Standard Time
+#define TIMEZONE_MST -7
+
+/// Pacific Daylight Time
+#define TIMEZONE_PDT -7
+
+/// Pacific Standard Time
+#define TIMEZONE_PST -8
+
+/// Alaska Daylight Time
+#define TIMEZONE_AKDT -8
+
+/// Alaska Standard Time
+#define TIMEZONE_AKST -9
+
+/// Hawaii-Aleutian Daylight Time
+#define TIMEZONE_HDT -9
+
+/// Hawaii Standard Time
+#define TIMEZONE_HST -10
+
+/// Cook Island Time
+#define TIMEZONE_CKT -10
+
+/// Niue Time
+#define TIMEZONE_NUT -11
+
+/// Anywhere on Earth
+#define TIMEZONE_ANYWHERE_ON_EARTH -12
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 95b71bec9749..e7e24b28c2ac 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -5,13 +5,13 @@
#define ADD_TRAIT(target, trait, source) \
do { \
var/list/_L; \
- if (!target.status_traits) { \
- target.status_traits = list(); \
- _L = target.status_traits; \
+ if (!target._status_traits) { \
+ target._status_traits = list(); \
+ _L = target._status_traits; \
_L[trait] = list(source); \
SEND_SIGNAL(target, SIGNAL_ADDTRAIT(trait), trait); \
} else { \
- _L = target.status_traits; \
+ _L = target._status_traits; \
if (_L[trait]) { \
_L[trait] |= list(source); \
} else { \
@@ -22,14 +22,14 @@
} while (0)
#define REMOVE_TRAIT(target, trait, sources) \
do { \
- var/list/_L = target.status_traits; \
+ var/list/_L = target._status_traits; \
var/list/_S; \
if (sources && !islist(sources)) { \
_S = list(sources); \
} else { \
_S = sources\
}; \
- if (_L && _L[trait]) { \
+ if (_L?[trait]) { \
for (var/_T in _L[trait]) { \
if ((!_S && (_T != ROUNDSTART_TRAIT)) || (_T in _S)) { \
_L[trait] -= _T \
@@ -40,20 +40,20 @@
SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(trait), trait); \
}; \
if (!length(_L)) { \
- target.status_traits = null \
+ target._status_traits = null \
}; \
} \
} while (0)
#define REMOVE_TRAIT_NOT_FROM(target, trait, sources) \
do { \
- var/list/_traits_list = target.status_traits; \
+ var/list/_traits_list = target._status_traits; \
var/list/_sources_list; \
if (sources && !islist(sources)) { \
_sources_list = list(sources); \
} else { \
_sources_list = sources\
}; \
- if (_traits_list && _traits_list[trait]) { \
+ if (_traits_list?[trait]) { \
for (var/_trait_source in _traits_list[trait]) { \
if (!(_trait_source in _sources_list)) { \
_traits_list[trait] -= _trait_source \
@@ -64,13 +64,13 @@
SEND_SIGNAL(target, SIGNAL_REMOVETRAIT(trait), trait); \
}; \
if (!length(_traits_list)) { \
- target.status_traits = null \
+ target._status_traits = null \
}; \
} \
} while (0)
#define REMOVE_TRAITS_NOT_IN(target, sources) \
do { \
- var/list/_L = target.status_traits; \
+ var/list/_L = target._status_traits; \
var/list/_S = sources; \
if (_L) { \
for (var/_T in _L) { \
@@ -81,14 +81,14 @@
}; \
};\
if (!length(_L)) { \
- target.status_traits = null\
+ target._status_traits = null\
};\
}\
} while (0)
#define REMOVE_TRAITS_IN(target, sources) \
do { \
- var/list/_L = target.status_traits; \
+ var/list/_L = target._status_traits; \
var/list/_S = sources; \
if (sources && !islist(sources)) { \
_S = list(sources); \
@@ -104,152 +104,66 @@
}; \
};\
if (!length(_L)) { \
- target.status_traits = null\
+ target._status_traits = null\
};\
}\
} while (0)
-#define HAS_TRAIT(target, trait) (target.status_traits ? (target.status_traits[trait] ? TRUE : FALSE) : FALSE)
-#define HAS_TRAIT_FROM(target, trait, source) (target.status_traits ? (target.status_traits[trait] ? (source in target.status_traits[trait]) : FALSE) : FALSE)
+
+#define HAS_TRAIT(target, trait) (target._status_traits?[trait] ? TRUE : FALSE)
+#define HAS_TRAIT_FROM(target, trait, source) (HAS_TRAIT(target, trait) && (source in target._status_traits[trait]))
+#define HAS_TRAIT_FROM_ONLY(target, trait, source) (HAS_TRAIT(target, trait) && (source in target._status_traits[trait]) && (length(target._status_traits[trait]) == 1))
+#define HAS_TRAIT_NOT_FROM(target, trait, source) (HAS_TRAIT(target, trait) && (length(target._status_traits[trait] - source) > 0))
+/// Returns a list of trait sources for this trait. Only useful for wacko cases and internal futzing
+/// You should not be using this
+#define GET_TRAIT_SOURCES(target, trait) (target._status_traits?[trait] || list())
+/// Returns the amount of sources for a trait. useful if you don't want to have a "thing counter" stuck around all the time
+#define COUNT_TRAIT_SOURCES(target, trait) length(GET_TRAIT_SOURCES(target, trait))
+/// A simple helper for checking traits in a mob's mind
+#define HAS_MIND_TRAIT(target, trait) (HAS_TRAIT(target, trait) || (target.mind ? HAS_TRAIT(target.mind, trait) : FALSE))
//Remember to update code/datums/traits/ folder if you're adding/removing/renaming traits.
//mob traits
-/// Forces the user to stay unconscious.
-#define TRAIT_KNOCKEDOUT "knockedout"
-/// Prevents voluntary movement.
-#define TRAIT_IMMOBILIZED "immobilized"
-/// Prevents voluntary standing or staying up on its own.
-#define TRAIT_FLOORED "floored"
-/// Forces user to stay standing
-#define TRAIT_FORCED_STANDING "forcedstanding"
-/// Prevents usage of manipulation appendages (picking, holding or using items, manipulating storage).
-#define TRAIT_HANDS_BLOCKED "handsblocked"
-/// Inability to access UI hud elements. Turned into a trait from [MOBILITY_UI] to be able to track sources.
-#define TRAIT_UI_BLOCKED "uiblocked"
-/// Inability to pull things. Turned into a trait from [MOBILITY_PULL] to be able to track sources.
-#define TRAIT_PULL_BLOCKED "pullblocked"
-/// Abstract condition that prevents movement if being pulled and might be resisted against. Handcuffs and straight jackets, basically.
-#define TRAIT_RESTRAINED "restrained"
-/// In some kind of critical condition. Is able to succumb.
-#define TRAIT_CRITICAL_CONDITION "critical-condition"
-/// Is frozen in place
-#define TRAIT_FROZEN "frozen"
-/// trait associated to a stat value or range of
-#define STAT_TRAIT "stat"
-#define TRAIT_INCAPACITATED "incapacitated"
-#define HANDCUFFED_TRAIT "handcuffed"
+
#define TRAIT_BLIND "blind"
-#define TRAIT_ECHOLOCATION_RECEIVER "echolocation_receiver"
-#define TRAIT_MUTE "mute"
-#define TRAIT_EMOTEMUTE "emotemute"
-#define TRAIT_DEAF "deaf"
#define TRAIT_NEARSIGHT "nearsighted"
-#define TRAIT_FAT "fat"
-#define TRAIT_HUSK "husk"
-#define TRAIT_BADDNA "baddna"
-#define TRAIT_CLUMSY "clumsy"
-#define TRAIT_DUMB "dumb"
+
#define TRAIT_MONKEYLIKE "monkeylike" //sets IsAdvancedToolUser to FALSE
-#define TRAIT_PACIFISM "pacifism"
-#define TRAIT_IGNORESLOWDOWN "ignoreslow"
-#define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown"
+
#define TRAIT_REDUCED_DAMAGE_SLOWDOWN "reduced_damage_slowdown"
#define TRAIT_RESISTDAMAGESLOWDOWN "resistdamageslowdown"
#define TRAIT_HIGHRESISTDAMAGESLOWDOWN "highresistdamageslowdown"
-#define TRAIT_DEATHCOMA "deathcoma" //Causes death-like unconsciousness
-#define TRAIT_FAKEDEATH "fakedeath" //Makes the owner appear as dead to most forms of medical examination
-#define TRAIT_DISFIGURED "disfigured"
-#define TRAIT_XENO_HOST "xeno_host" //Tracks whether we're gonna be a baby alien's mummy.
-#define TRAIT_STUNIMMUNE "stun_immunity"
-#define TRAIT_SLEEPIMMUNE "sleep_immunity"
+
#define TRAIT_IMPACTIMMUNE "impact_immunity" //protects from the damage of getting launched into a wall hard
-#define TRAIT_PUSHIMMUNE "push_immunity"
-#define TRAIT_SHOCKIMMUNE "shock_immunity"
-/// Prevents you from leaving your corpse
-#define TRAIT_CORPSELOCKED "corpselocked"
-#define TRAIT_STABLEHEART "stable_heart"
-#define TRAIT_STABLELIVER "stable_liver"
-#define TRAIT_RESISTHEAT "resist_heat"
-#define TRAIT_RESISTHEATHANDS "resist_heat_handsonly" //For when you want to be able to touch hot things, but still want fire to be an issue.
-#define TRAIT_RESISTCOLD "resist_cold"
-#define TRAIT_RESISTHIGHPRESSURE "resist_high_pressure"
-#define TRAIT_RESISTLOWPRESSURE "resist_low_pressure"
-#define TRAIT_BOMBIMMUNE "bomb_immunity"
-#define TRAIT_RADIMMUNE "rad_immunity"
+
#define TRAIT_EMPPROOF_SELF "emp_immunity_self" //for the specific "thing" itself
#define TRAIT_EMPPROOF_CONTENTS "emp_immunity_contents" //for anything the "thing" is carrying, but not itself
-#define TRAIT_GENELESS "geneless"
-#define TRAIT_VIRUSIMMUNE "virus_immunity"
-#define TRAIT_PIERCEIMMUNE "pierce_immunity"
-#define TRAIT_NODISMEMBER "dismember_immunity"
+
#define TRAIT_SAFEWELD "safe_welding" //prevents blinding from welding without giving actual flash immunity
-#define TRAIT_NOFIRE "nonflammable"
-#define TRAIT_NOGUNS "no_guns"
+
#define TRAIT_NO_STUN_WEAPONS "no_stun_weapons" //prevents use of commonly available instant or near instant stun weapons
#define TRAIT_NOINTERACT "no_interact" //Not allowed to touch anything (even with TK) or use things in hand
-#define TRAIT_NOHUNGER "no_hunger"
+
#define TRAIT_POWERHUNGRY "power_hungry" // uses electricity instead of food
-#define TRAIT_EASYDISMEMBER "easy_dismember"
-#define TRAIT_LIMBATTACHMENT "limb_attach"
-#define TRAIT_NOLIMBDISABLE "no_limb_disable"
-#define TRAIT_EASILY_WOUNDED "easy_limb_wound"
-#define TRAIT_HARDLY_WOUNDED "hard_limb_wound"
-#define TRAIT_TOXINLOVER "toxinlover"
-#define TRAIT_TOXIMMUNE "toxin_immune"
-#define TRAIT_NOBREATH "no_breath"
-/// This mob is antimagic, and immune to spells / cannot cast spells
-#define TRAIT_ANTIMAGIC "anti_magic"
-/// This allows a person who has antimagic to cast spells without getting blocked
-#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock"
-/// This mob recently blocked magic with some form of antimagic
-#define TRAIT_RECENTLY_BLOCKED_MAGIC "recently_blocked_magic"
-#define TRAIT_HOLY "holy"
-#define TRAIT_DEPRESSION "depression"
-#define TRAIT_JOLLY "jolly"
-#define TRAIT_NOCRITDAMAGE "no_crit"
+
#define TRAIT_NOSLIPICE "noslip_ice"
#define TRAIT_NOSLIPWATER "noslip_water"
#define TRAIT_NOSLIPALL "noslip_all"
-#define TRAIT_NODEATH "nodeath"
-#define TRAIT_NOHARDCRIT "nohardcrit"
-#define TRAIT_NOSOFTCRIT "nosoftcrit"
-#define TRAIT_MINDSHIELD "mindshield"
-#define TRAIT_DISSECTED "dissected"
-#define TRAIT_SIXTHSENSE "sixth_sense" //I can hear dead people
-#define TRAIT_FEARLESS "fearless"
-#define TRAIT_PARALYSIS_L_ARM "para-l-arm" //These are used for brain-based paralysis, where replacing the limb won't fix it
-#define TRAIT_PARALYSIS_R_ARM "para-r-arm"
-#define TRAIT_PARALYSIS_L_LEG "para-l-leg"
-#define TRAIT_PARALYSIS_R_LEG "para-r-leg"
-#define TRAIT_CANNOT_OPEN_PRESENTS "cannot-open-presents"
-#define TRAIT_PRESENT_VISION "present-vision"
-#define TRAIT_DISK_VERIFIER "disk-verifier"
-#define TRAIT_NOMOBSWAP "no-mob-swap"
-#define TRAIT_XRAY_VISION "xray_vision"
-#define TRAIT_THERMAL_VISION "thermal_vision"
+
#define TRAIT_INFRARED_VISION "infrared_vision"
-#define TRAIT_ABDUCTOR_TRAINING "abductor-training"
-#define TRAIT_ABDUCTOR_SCIENTIST_TRAINING "abductor-scientist-training"
-#define TRAIT_SURGEON "surgeon"
-#define TRAIT_STRONG_GRABBER "strong_grabber"
+
#define TRAIT_CALCIUM_HEALER "calcium_healer"
#define TRAIT_MAGIC_CHOKE "magic_choke"
-#define TRAIT_SOOTHED_THROAT "soothed-throat"
-#define TRAIT_LAW_ENFORCEMENT_METABOLISM "law-enforcement-metabolism"
+
#define TRAIT_PSYCH "psych-diagnosis"
#define TRAIT_ALWAYS_CLEAN "always-clean"
-#define TRAIT_BOOZE_SLIDER "booze-slider"
-#define TRAIT_QUICK_CARRY "quick-carry"
-#define TRAIT_QUICKER_CARRY "quicker-carry"
+
#define TRAIT_QUICKEST_CARRY "quickest-carry"
#define TRAIT_STRONG_GRIP "strong-grip"
-#define TRAIT_UNINTELLIGIBLE_SPEECH "unintelligible-speech"
-#define TRAIT_UNSTABLE "unstable"
-#define TRAIT_OIL_FRIED "oil_fried"
+
#define TRAIT_SHELTERED "sheltered"
#define TRAIT_RANDOM_ACCENT "random_accent"
#define TRAIT_MEDICALIGNORE "medical_ignore"
-#define TRAIT_PASSTABLE "passtable"
#define TRAIT_SLIME_EMPATHY "slime-empathy"
#define TRAIT_ACIDBLOOD "acid_blood"
#define TRAIT_PRESERVED_ORGANS "preserved_organs"
@@ -257,9 +171,8 @@
#define TRAIT_SURGERY_PREPARED "surgery_prepared"
#define TRAIT_NO_PASSIVE_COOLING "no-passive-cooling"
#define TRAIT_NO_PASSIVE_HEATING "no-passive-heating"
-#define TRAIT_BLOODY_MESS "bloody_mess" //from heparin, makes open bleeding wounds rapidly spill more blood
#define TRAIT_BLOODY_MESS_LITE "bloody_mess_lite" //weak heparin, otherwise the same
-#define TRAIT_COAGULATING "coagulating" //from coagulant reagents, this doesn't affect the bleeding itself but does affect the bleed warning messages
+#define TRAIT_NO_BLOOD_REGEN "no_blood_regen" //prevents regenerating blood
#define TRAIT_NOPULSE "nopulse" // Your heart doesn't beat
#define TRAIT_MASQUERADE "masquerade" // Falsifies Health analyzer blood levels
#define TRAIT_NOCLONE "noclone" // No cloning
@@ -274,150 +187,57 @@
#define TRAIT_LONG_TELOMERES "long_telomeres" //You get CLOONED faster!!!
///You become a Marine that can eat crayons!!!
#define TRAIT_MARINE "marine"
-/// makes your footsteps completely silent
-#define TRAIT_SILENT_FOOTSTEPS "silent_footsteps"
-/// Immune to being afflicted by time stop (spell)
-#define TRAIT_TIME_STOP_IMMUNE "time_stop_immune"
-/// This mob has no soul
-#define TRAIT_NO_SOUL "no_soul"
-/// Whether a spider's consumed this mob
-#define TRAIT_SPIDER_CONSUMED "spider_consumed"
+
/// Whether we're sneaking, from the alien sneak ability.
/// Maybe worth generalizing into a general "is sneaky" / "is stealth" trait in the future.
#define TRAIT_ALIEN_SNEAK "sneaking_alien"
-/// This mob is phased out of reality from magic, either a jaunt or rod form
-#define TRAIT_MAGICALLY_PHASED "magically_phased"
+
///This mob can't use vehicles
#define TRAIT_NOVEHICLE "no_vehicle"
-/// BALD!!!
-#define TRAIT_BALD "bald"
+
/// You can't see color!
#define TRAIT_COLORBLIND "color_blind"
/// This person is crying
#define TRAIT_CRYING "crying"
-/// This human wants to see the color of their glasses, for some reason
-#define TRAIT_SEE_GLASS_COLORS "see_glass_colors"
-
-//non-mob traits
-/// Used for limb-based paralysis, where replacing the limb will fix it.
-#define TRAIT_PARALYSIS "paralysis"
-/// Used for limbs.
-#define TRAIT_DISABLED_BY_WOUND "disabled-by-wound"
-
-// item traits
-#define TRAIT_NODROP "nodrop"
-#define TRAIT_T_RAY_VISIBLE "t-ray-visible" // Visible on t-ray scanners if the atom/var/level == 1
-/// Properly wielded two handed item
-#define TRAIT_WIELDED "wielded"
-/// The items needs two hands to be carried
-#define TRAIT_NEEDS_TWO_HANDS "needstwohands"
-#define TRAIT_NO_TELEPORT "no-teleport" //you just can't
+
#define TRAIT_NO_STORAGE "no-storage" //you cannot put this in any container, backpack, box etc
-#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
-#define TRAIT_AGEUSIA "ageusia"
-#define TRAIT_HEAVY_SLEEPER "heavy_sleeper"
-#define TRAIT_NIGHT_VISION "night_vision"
-#define TRAIT_LIGHT_STEP "light_step"
-#define TRAIT_SPIRITUAL "spiritual"
-#define TRAIT_VORACIOUS "voracious"
-#define TRAIT_SELF_AWARE "self_aware"
-#define TRAIT_FREERUNNING "freerunning"
-#define TRAIT_SKITTISH "skittish"
#define TRAIT_POOR_AIM "poor_aim"
-#define TRAIT_PROSOPAGNOSIA "prosopagnosia"
+
#define TRAIT_DRUNK_HEALING "drunk_healing"
-#define TRAIT_TAGGER "tagger"
-#define TRAIT_PHOTOGRAPHER "photographer"
-#define TRAIT_MUSICIAN "musician"
-#define TRAIT_LIGHT_DRINKER "light_drinker"
-#define TRAIT_EMPATH "empath"
-#define TRAIT_FRIENDLY "friendly"
+
#define TRAIT_ALLERGIC "allergic"
#define TRAIT_KLEPTOMANIAC "kleptomaniac"
#define TRAIT_EAT_LESS "eat_less"
#define TRAIT_CRAFTY "crafty"
#define TRAIT_ANOREXIC "anorexic"
-#define TRAIT_SHIFTY_EYES "shifty_eyes"
-#define TRAIT_ANXIOUS "anxious"
+
#define TRAIT_SEE_REAGENTS "see_reagents"
#define TRAIT_STARGAZED "stargazed"
-// common trait sources
-#define TRAIT_GENERIC "generic"
-#define UNCONSCIOUS_TRAIT "unconscious"
-#define EYE_DAMAGE "eye_damage"
-#define GENETIC_MUTATION "genetic"
-#define OBESITY "obesity"
-#define MAGIC_TRAIT "magic"
-#define TRAUMA_TRAIT "trauma"
-#define DISEASE_TRAIT "disease"
-#define SPECIES_TRAIT "species"
-#define ORGAN_TRAIT "organ"
-#define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention
-#define JOB_TRAIT "job"
-#define CYBORG_ITEM_TRAIT "cyborg-item"
-#define ADMIN_TRAIT "admin" // (B)admins only.
-#define CHANGELING_TRAIT "changeling"
-#define CULT_TRAIT "cult"
-#define LICH_TRAIT "lich"
/// The item is magically cursed
#define CURSED_ITEM_TRAIT(item_type) "cursed_item_[item_type]"
-#define ABSTRACT_ITEM_TRAIT "abstract-item"
+
#define PSEUDOCIDER_TRAIT "pseudocider_trait"
-#define STATUS_EFFECT_TRAIT "status-effect"
-#define CLOTHING_TRAIT "clothing"
-#define VEHICLE_TRAIT "vehicle" // inherited from riding vehicles
-#define INNATE_TRAIT "innate"
-#define STATION_TRAIT "station-trait"
+
#define ATTACHMENT_TRAIT "attachment-trait"
-#define GLASSES_TRAIT "glasses"
+
/// A trait given by a specific status effect (not sure why we need both but whatever!)
#define TRAIT_STATUS_EFFECT(effect_id) "[effect_id]-trait"
-/// trait associated to being held in a chokehold
-#define CHOKEHOLD_TRAIT "chokehold"
-#define CRYO_TRAIT "cryo_trait"
+
/// Trait applied by element
#define ELEMENT_TRAIT(source) "element_trait_[source]"
-/// Trait from [/datum/element/rust]. Its rusty and should be applying a special overlay to denote this.
-#define TRAIT_RUSTY "rust_trait"
+
// unique trait sources, still defines
#define CLONING_POD_TRAIT "cloning-pod"
-#define STATUE_MUTE "statue"
-#define CHANGELING_DRAIN "drain"
#define CHANGELING_HIVEMIND_MUTE "ling_mute"
#define ABYSSAL_GAZE_BLIND "abyssal_gaze"
#define HIGHLANDER "highlander"
-#define TRAIT_HULK "hulk"
-#define STASIS_MUTE "stasis"
-#define GENETICS_SPELL "genetics_spell"
-#define EYES_COVERED "eyes_covered"
#define CULT_EYES "cult_eyes"
-#define TRAIT_SANTA "santa"
-#define SCRYING_ORB "scrying-orb"
-#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
#define NUKEOP_TRAIT "nuke-op"
#define DEATHSQUAD_TRAIT "deathsquad"
-#define MEGAFAUNA_TRAIT "megafauna"
-#define CLOWN_NUKE_TRAIT "clown-nuke"
-#define STICKY_MOUSTACHE_TRAIT "sticky-moustache"
-#define CHAINSAW_FRENZY_TRAIT "chainsaw-frenzy"
-#define CHRONO_GUN_TRAIT "chrono-gun"
-#define REVERSE_BEAR_TRAP_TRAIT "reverse-bear-trap"
-#define CURSED_MASK_TRAIT "cursed-mask"
-#define HIS_GRACE_TRAIT "his-grace"
-#define HAND_REPLACEMENT_TRAIT "magic-hand"
-#define HOT_POTATO_TRAIT "hot-potato"
-#define SABRE_SUICIDE_TRAIT "sabre-suicide"
-#define ABDUCTOR_VEST_TRAIT "abductor-vest"
-#define CAPTURE_THE_FLAG_TRAIT "capture-the-flag"
-#define EYE_OF_GOD_TRAIT "eye-of-god"
-#define SHAMEBRERO_TRAIT "shamebrero"
-#define CHRONOSUIT_TRAIT "chronosuit"
-#define LOCKED_HELMET_TRAIT "locked-helmet"
-#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
#define HIVEMIND_ONE_MIND_TRAIT "one_mind"
#define VR_ZONE_TRAIT "vr_zone_trait"
@@ -425,7 +245,6 @@
#define STARGAZER_TRAIT "stargazer"
#define RANDOM_BLACKOUTS "random_blackouts"
#define MADE_UNCLONEABLE "made-uncloneable"
-#define PULLED_WHILE_SOFTCRIT_TRAIT "pulled-while-softcrit"
/// Source trait for Bloodsuckers-related traits
#define BLOODSUCKER_TRAIT "bloodsucker_trait"
/// Source trait for Monster Hunter-related traits
@@ -440,57 +259,10 @@
#define CHANGESTING_TRAIT "changesting"
#define POSIBRAIN_TRAIT "positrait"
#define WRIST_STRAP_TRAIT "wrist_strap"
-#define ECHOLOCATION_TRAIT "echolocation_trait"
#define GRIMOIRE_TRAIT "grimoire_trait"
///Traits given by station traits
-#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
-#define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation"
-#define STATION_TRAIT_PREMIUM_INTERNALS "station_trait_premium_internals"
-#define STATION_TRAIT_RANDOM_ARRIVALS "station_trait_random_arrivals"
-#define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint"
-#define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint"
-#define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched"
+
#define STATION_TRAIT_MOONSCORCH "station_trait_moonscorch"
-//important_recursive_contents traits
-/*
- * Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas.
- * Use [/atom/movable/proc/become_area_sensitive(trait_source)] to properly enable it. How you remove it isn't as important.
- */
-#define TRAIT_AREA_SENSITIVE "area-sensitive"
-///every hearing sensitive atom has this trait
-#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
-///every object that is currently the active storage of some client mob has this trait
-#define TRAIT_ACTIVE_STORAGE "active_storage"
-
-/// Climbable trait, given and taken by the climbable element when added or removed. Exists to be easily checked via HAS_TRAIT().
-#define TRAIT_CLIMBABLE "trait_climbable"
-
-///Organ traits
-#define TRAIT_BALLMER_SCIENTIST "ballmer_scientist"
-
-///storm immunity traits
-#define TRAIT_LAVA_IMMUNE "lava_immune" //Used by lava turfs and The Floor Is Lava.
-#define TRAIT_ASHSTORM_IMMUNE "ashstorm_immune"
-#define TRAIT_SNOWSTORM_IMMUNE "snowstorm_immune"
-#define TRAIT_RADSTORM_IMMUNE "radstorm_immune"
-#define TRAIT_VOIDSTORM_IMMUNE "voidstorm_immune"
-#define TRAIT_WEATHER_IMMUNE "weather_immune" //Immune to ALL weather effects.
-
-///Movement type traits for movables. See elements/movetype_handler.dm
-#define TRAIT_MOVE_GROUND "move_ground"
-#define TRAIT_MOVE_FLYING "move_flying"
-#define TRAIT_MOVE_VENTCRAWLING "move_ventcrawling"
-#define TRAIT_MOVE_FLOATING "move_floating"
-#define TRAIT_MOVE_PHASING "move_phasing"
-/// Disables the floating animation. See above.
-#define TRAIT_NO_FLOATING_ANIM "no-floating-animation"
-/// Used to prevent multiple floating blades from triggering over the same target
-#define TRAIT_BEING_BLADE_SHIELDED "being_blade_shielded"
-/// things with this trait are treated as having no access in /obj/proc/check_access(obj/item)
-#define TRAIT_ALWAYS_NO_ACCESS "alwaysnoaccess"
-///Trait for dryable items
-#define TRAIT_DRYABLE "trait_dryable"
-///Trait for dried items
-#define TRAIT_DRIED "trait_dried"
+
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
new file mode 100644
index 000000000000..4cb7051cb3ba
--- /dev/null
+++ b/code/__DEFINES/traits/declarations.dm
@@ -0,0 +1,1036 @@
+// This file contains all of the "static" define strings that tie to a trait.
+// WARNING: The sections here actually matter in this file as it's tested by CI. Please do not toy with the sections."
+
+// BEGIN TRAIT DEFINES
+
+/*
+Remember to update _globalvars/traits.dm if you're adding/removing/renaming traits.
+*/
+
+//mob traits
+/// Forces the user to stay unconscious.
+#define TRAIT_KNOCKEDOUT "knockedout"
+/// Prevents voluntary movement.
+#define TRAIT_IMMOBILIZED "immobilized"
+/// Prevents voluntary standing or staying up on its own.
+#define TRAIT_FLOORED "floored"
+/// Forces user to stay standing
+#define TRAIT_FORCED_STANDING "forcedstanding"
+/// Prevents usage of manipulation appendages (picking, holding or using items, manipulating storage).
+#define TRAIT_HANDS_BLOCKED "handsblocked"
+/// Inability to access UI hud elements. Turned into a trait from [MOBILITY_UI] to be able to track sources.
+#define TRAIT_UI_BLOCKED "uiblocked"
+/// Inability to pull things. Turned into a trait from [MOBILITY_PULL] to be able to track sources.
+#define TRAIT_PULL_BLOCKED "pullblocked"
+/// Abstract condition that prevents movement if being pulled and might be resisted against. Handcuffs and straight jackets, basically.
+#define TRAIT_RESTRAINED "restrained"
+/// Apply this to make a mob not dense, and remove it when you want it to no longer make them undense, other sorces of undesity will still apply. Always define a unique source when adding a new instance of this!
+#define TRAIT_UNDENSE "undense"
+/// Expands our FOV by 30 degrees if restricted
+#define TRAIT_EXPANDED_FOV "expanded_fov"
+/// Doesn't miss attacks
+#define TRAIT_PERFECT_ATTACKER "perfect_attacker"
+///Recolored by item/greentext
+#define TRAIT_GREENTEXT_CURSED "greentext_curse"
+#define TRAIT_INCAPACITATED "incapacitated"
+/// In some kind of critical condition. Is able to succumb.
+#define TRAIT_CRITICAL_CONDITION "critical-condition"
+/// Whitelist for mobs that can read or write
+#define TRAIT_LITERATE "literate"
+/// Blacklist for mobs that can't read or write
+#define TRAIT_ILLITERATE "illiterate"
+/// Mute. Can't talk.
+#define TRAIT_MUTE "mute"
+/// Softspoken. Always whisper.
+#define TRAIT_SOFTSPOKEN "softspoken"
+/// Gibs on death and slips like ice.
+#define TRAIT_CURSED "cursed"
+/// Emotemute. Can't... emote.
+#define TRAIT_EMOTEMUTE "emotemute"
+#define TRAIT_DEAF "deaf"
+#define TRAIT_FAT "fat"
+#define TRAIT_HUSK "husk"
+///Blacklisted from being revived via defibrilator
+#define TRAIT_DEFIB_BLACKLISTED "defib_blacklisted"
+#define TRAIT_BADDNA "baddna"
+#define TRAIT_CLUMSY "clumsy"
+/// Trait that means you are capable of holding items in some form
+#define TRAIT_CAN_HOLD_ITEMS "can_hold_items"
+/// Trait which lets you clamber over a barrier
+#define TRAIT_FENCE_CLIMBER "can_climb_fences"
+/// means that you can't use weapons with normal trigger guards.
+#define TRAIT_CHUNKYFINGERS "chunkyfingers"
+#define TRAIT_CHUNKYFINGERS_IGNORE_BATON "chunkyfingers_ignore_baton"
+/// Allows you to mine with your bare hands
+#define TRAIT_FIST_MINING "fist_mining"
+#define TRAIT_DUMB "dumb"
+/// Whether a mob is dexterous enough to use machines and certain items or not.
+#define TRAIT_ADVANCEDTOOLUSER "advancedtooluser"
+// Antagonizes the above.
+#define TRAIT_DISCOORDINATED_TOOL_USER "discoordinated_tool_user"
+#define TRAIT_PACIFISM "pacifism"
+#define TRAIT_IGNORESLOWDOWN "ignoreslow"
+#define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown"
+/// Makes it so the mob can use guns regardless of tool user status
+#define TRAIT_GUN_NATURAL "gunnatural"
+/// Causes death-like unconsciousness
+#define TRAIT_DEATHCOMA "deathcoma"
+/// The mob has the stasis effect.
+/// Does nothing on its own, applied via status effect.
+#define TRAIT_STASIS "in_stasis"
+/// Makes the owner appear as dead to most forms of medical examination
+#define TRAIT_FAKEDEATH "fakedeath"
+#define TRAIT_DISFIGURED "disfigured"
+/// "Magic" trait that blocks the mob from moving or interacting with anything. Used for transient stuff like mob transformations or incorporality in special cases.
+/// Will block movement, `Life()` (!!!), and other stuff based on the mob.
+#define TRAIT_NO_TRANSFORM "block_transformations"
+/// Tracks whether we're gonna be a baby alien's mummy.
+#define TRAIT_XENO_HOST "xeno_host"
+/// This parrot is currently perched
+#define TRAIT_PARROT_PERCHED "parrot_perched"
+/// This mob is immune to stun causing status effects and stamcrit.
+/// Prefer to use [/mob/living/proc/check_stun_immunity] over checking for this trait exactly.
+#define TRAIT_STUNIMMUNE "stun_immunity"
+#define TRAIT_BATON_RESISTANCE "baton_resistance"
+/// Anti Dual-baton cooldown bypass exploit.
+#define TRAIT_IWASBATONED "iwasbatoned"
+#define TRAIT_SLEEPIMMUNE "sleep_immunity"
+#define TRAIT_PUSHIMMUNE "push_immunity"
+/// Are we immune to shocks?
+#define TRAIT_SHOCKIMMUNE "shock_immunity"
+/// Are we immune to specifically tesla / SM shocks?
+#define TRAIT_TESLA_SHOCKIMMUNE "tesla_shock_immunity"
+#define TRAIT_AIRLOCK_SHOCKIMMUNE "airlock_shock_immunity"
+/// Is this atom being actively shocked? Used to prevent repeated shocks.
+#define TRAIT_BEING_SHOCKED "shocked"
+#define TRAIT_STABLEHEART "stable_heart"
+/// Prevents you from leaving your corpse
+#define TRAIT_CORPSELOCKED "corpselocked"
+#define TRAIT_STABLELIVER "stable_liver"
+#define TRAIT_VATGROWN "vatgrown"
+#define TRAIT_RESISTHEAT "resist_heat"
+///For when you've gotten a power from a dna vault
+#define TRAIT_USED_DNA_VAULT "used_dna_vault"
+/// For when you want to be able to touch hot things, but still want fire to be an issue.
+#define TRAIT_RESISTHEATHANDS "resist_heat_handsonly"
+#define TRAIT_RESISTCOLD "resist_cold"
+#define TRAIT_RESISTHIGHPRESSURE "resist_high_pressure"
+#define TRAIT_RESISTLOWPRESSURE "resist_low_pressure"
+/// This human is immune to the effects of being exploded. (ex_act)
+#define TRAIT_BOMBIMMUNE "bomb_immunity"
+/// Immune to being irradiated
+#define TRAIT_RADIMMUNE "rad_immunity"
+/// This mob won't get gibbed by nukes going off
+#define TRAIT_NUKEIMMUNE "nuke_immunity"
+/// Can't be given viruses
+#define TRAIT_VIRUSIMMUNE "virus_immunity"
+/// Won't become a husk under any circumstances
+#define TRAIT_UNHUSKABLE "trait_unhuskable"
+/// Reduces the chance viruses will spread to this mob, and if the mob has a virus, slows its advancement
+#define TRAIT_VIRUS_RESISTANCE "virus_resistance"
+#define TRAIT_GENELESS "geneless"
+#define TRAIT_PIERCEIMMUNE "pierce_immunity"
+#define TRAIT_NODISMEMBER "dismember_immunity"
+#define TRAIT_NOFIRE "nonflammable"
+#define TRAIT_NOFIRE_SPREAD "no_fire_spreading"
+/// Prevents plasmamen from self-igniting if only their helmet is missing
+#define TRAIT_NOSELFIGNITION_HEAD_ONLY "no_selfignition_head_only"
+#define TRAIT_NOGUNS "no_guns"
+/// Species with this trait are genderless
+#define TRAIT_AGENDER "agender"
+/// Species with this trait have a blood clan mechanic
+#define TRAIT_BLOOD_CLANS "blood_clans"
+/// Species with this trait have markings (this SUCKS, remove this later in favor of bodypart overlays)
+#define TRAIT_HAS_MARKINGS "has_markings"
+/// Species with this trait use skin tones for coloration
+#define TRAIT_USES_SKINTONES "uses_skintones"
+/// Species with this trait use mutant colors for coloration
+#define TRAIT_MUTANT_COLORS "mutcolors"
+/// Species with this trait have mutant colors that cannot be chosen by the player, nor altered ingame by external means
+#define TRAIT_FIXED_MUTANT_COLORS "fixed_mutcolors"
+/// Species with this trait have a haircolor that cannot be chosen by the player, nor altered ingame by external means
+#define TRAIT_FIXED_HAIRCOLOR "fixed_haircolor"
+/// Humans with this trait won't get bloody hands, nor bloody feet
+#define TRAIT_NO_BLOOD_OVERLAY "no_blood_overlay"
+/// Humans with this trait cannot have underwear
+#define TRAIT_NO_UNDERWEAR "no_underwear"
+/// This carbon doesn't show an overlay when they have no brain
+#define TRAIT_NO_DEBRAIN_OVERLAY "no_debrain_overlay"
+/// Humans with this trait cannot get augmentation surgery
+#define TRAIT_NO_AUGMENTS "no_augments"
+/// This carbon doesn't get hungry
+#define TRAIT_NOHUNGER "no_hunger"
+/// This carbon doesn't bleed
+#define TRAIT_NOBLOOD "noblood"
+/// This just means that the carbon will always have functional liverless metabolism
+#define TRAIT_LIVERLESS_METABOLISM "liverless_metabolism"
+/// This carbon can't be overdosed by chems
+#define TRAIT_OVERDOSEIMMUNE "overdose_immune"
+/// Humans with this trait cannot be turned into zombies
+#define TRAIT_NO_ZOMBIFY "no_zombify"
+/// Carbons with this trait can't have their DNA copied by diseases nor changelings
+#define TRAIT_NO_DNA_COPY "no_dna_copy"
+/// Carbons with this trait cant have their dna scrambled by genetics or a disease retrovirus.
+#define TRAIT_NO_DNA_SCRAMBLE "no_dna_scramble"
+/// Carbons with this trait can eat blood to regenerate their own blood volume, instead of injecting it
+#define TRAIT_DRINKS_BLOOD "drinks_blood"
+/// Mob is immune to toxin damage
+#define TRAIT_TOXIMMUNE "toxin_immune"
+/// Mob is immune to oxygen damage, does not need to breathe
+#define TRAIT_NOBREATH "no_breath"
+/// Mob is currently disguised as something else (like a morph being another mob or an object). Holds a reference to the thing that applied the trait.
+#define TRAIT_DISGUISED "disguised"
+/// Use when you want a mob to be able to metabolize plasma temporarily (e.g. plasma fixation disease symptom)
+#define TRAIT_PLASMA_LOVER_METABOLISM "plasma_lover_metabolism"
+#define TRAIT_EASYDISMEMBER "easy_dismember"
+#define TRAIT_LIMBATTACHMENT "limb_attach"
+#define TRAIT_NOLIMBDISABLE "no_limb_disable"
+#define TRAIT_EASILY_WOUNDED "easy_limb_wound"
+#define TRAIT_HARDLY_WOUNDED "hard_limb_wound"
+#define TRAIT_NEVER_WOUNDED "never_wounded"
+/// Species with this trait have 50% extra chance of bleeding from piercing and slashing wounds
+#define TRAIT_EASYBLEED "easybleed"
+#define TRAIT_TOXINLOVER "toxinlover"
+/// Doesn't get overlays from being in critical.
+#define TRAIT_NOCRITOVERLAY "no_crit_overlay"
+/// reduces the use time of syringes, pills, patches and medigels but only when using on someone
+#define TRAIT_FASTMED "fast_med_use"
+/// The mob is holy and resistance to cult magic
+#define TRAIT_HOLY "holy"
+/// This mob is antimagic, and immune to spells / cannot cast spells
+#define TRAIT_ANTIMAGIC "anti_magic"
+/// This allows a person who has antimagic to cast spells without getting blocked
+#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock"
+/// This mob recently blocked magic with some form of antimagic
+#define TRAIT_RECENTLY_BLOCKED_MAGIC "recently_blocked_magic"
+/// The user can do things like use magic staffs without penalty
+#define TRAIT_MAGICALLY_GIFTED "magically_gifted"
+/// This object innately spawns with fantasy variables already applied (the magical component is given to it on initialize), and thus we never want to give it the component again.
+#define TRAIT_INNATELY_FANTASTICAL_ITEM "innately_fantastical_item"
+#define TRAIT_DEPRESSION "depression"
+#define TRAIT_BLOOD_DEFICIENCY "blood_deficiency"
+#define TRAIT_JOLLY "jolly"
+#define TRAIT_NOCRITDAMAGE "no_crit"
+///Added to mob or mind, changes the icons of the fish shown in the minigame UI depending on the possible reward.
+#define TRAIT_REVEAL_FISH "reveal_fish"
+
+/// Added to a mob, allows that mob to experience flavour-based moodlets when examining food
+#define TRAIT_REMOTE_TASTING "remote_tasting"
+
+/// Stops the mob from slipping on water, or banana peels, or pretty much anything that doesn't have [GALOSHES_DONT_HELP] set
+#define TRAIT_NO_SLIP_WATER "noslip_water"
+/// Stops the mob from slipping on permafrost ice (not any other ice) (but anything with [SLIDE_ICE] set)
+#define TRAIT_NO_SLIP_ICE "noslip_ice"
+/// Stop the mob from sliding around from being slipped, but not the slip part.
+/// DOES NOT include ice slips.
+#define TRAIT_NO_SLIP_SLIDE "noslip_slide"
+/// Stops all slipping and sliding from ocurring
+#define TRAIT_NO_SLIP_ALL "noslip_all"
+
+/// Unlinks gliding from movement speed, meaning that there will be a delay between movements rather than a single move movement between tiles
+#define TRAIT_NO_GLIDE "no_glide"
+
+/// Applied into wounds when they're scanned with the wound analyzer, halves time to treat them manually.
+#define TRAIT_WOUND_SCANNED "wound_scanned"
+
+#define TRAIT_NODEATH "nodeath"
+#define TRAIT_NOHARDCRIT "nohardcrit"
+#define TRAIT_NOSOFTCRIT "nosoftcrit"
+#define TRAIT_MINDSHIELD "mindshield"
+#define TRAIT_DISSECTED "dissected"
+#define TRAIT_SURGICALLY_ANALYZED "surgically_analyzed"
+/// Lets the user succumb even if they got NODEATH
+#define TRAIT_SUCCUMB_OVERRIDE "succumb_override"
+/// Can hear observers
+#define TRAIT_SIXTHSENSE "sixth_sense"
+#define TRAIT_FEARLESS "fearless"
+/// Ignores darkness for hearing
+#define TRAIT_HEAR_THROUGH_DARKNESS "hear_through_darkness"
+/// These are used for brain-based paralysis, where replacing the limb won't fix it
+#define TRAIT_PARALYSIS_L_ARM "para-l-arm"
+#define TRAIT_PARALYSIS_R_ARM "para-r-arm"
+#define TRAIT_PARALYSIS_L_LEG "para-l-leg"
+#define TRAIT_PARALYSIS_R_LEG "para-r-leg"
+#define TRAIT_CANNOT_OPEN_PRESENTS "cannot-open-presents"
+#define TRAIT_PRESENT_VISION "present-vision"
+#define TRAIT_DISK_VERIFIER "disk-verifier"
+#define TRAIT_NOMOBSWAP "no-mob-swap"
+/// Can examine IDs to see if they are roundstart.
+#define TRAIT_ID_APPRAISER "id_appraiser"
+/// Gives us turf, mob and object vision through walls
+#define TRAIT_XRAY_VISION "xray_vision"
+/// Gives us mob vision through walls and slight night vision
+#define TRAIT_THERMAL_VISION "thermal_vision"
+/// Gives us turf vision through walls and slight night vision
+#define TRAIT_MESON_VISION "meson_vision"
+/// Gives us Night vision
+#define TRAIT_TRUE_NIGHT_VISION "true_night_vision"
+/// Negates our gravity, letting us move normally on floors in 0-g
+#define TRAIT_NEGATES_GRAVITY "negates_gravity"
+/// We are ignoring gravity
+#define TRAIT_IGNORING_GRAVITY "ignores_gravity"
+/// We have some form of forced gravity acting on us
+#define TRAIT_FORCED_GRAVITY "forced_gravity"
+/// Makes whispers clearly heard from seven tiles away, the full hearing range
+#define TRAIT_GOOD_HEARING "good_hearing"
+/// Allows you to hear speech through walls
+#define TRAIT_XRAY_HEARING "xray_hearing"
+
+/// Lets us scan reagents
+#define TRAIT_REAGENT_SCANNER "reagent_scanner"
+/// Lets us scan machine parts and tech unlocks
+#define TRAIT_RESEARCH_SCANNER "research_scanner"
+/// Can weave webs into cloth
+#define TRAIT_WEB_WEAVER "web_weaver"
+/// Can navigate the web without getting stuck
+#define TRAIT_WEB_SURFER "web_surfer"
+/// A web is being spun on this turf presently
+#define TRAIT_SPINNING_WEB_TURF "spinning_web_turf"
+#define TRAIT_ABDUCTOR_TRAINING "abductor-training"
+#define TRAIT_ABDUCTOR_SCIENTIST_TRAINING "abductor-scientist-training"
+#define TRAIT_SURGEON "surgeon"
+#define TRAIT_STRONG_GRABBER "strong_grabber"
+#define TRAIT_SOOTHED_THROAT "soothed-throat"
+#define TRAIT_BOOZE_SLIDER "booze-slider"
+/// We place people into a fireman carry quicker than standard
+#define TRAIT_QUICK_CARRY "quick-carry"
+/// We place people into a fireman carry especially quickly compared to quick_carry
+#define TRAIT_QUICKER_CARRY "quicker-carry"
+#define TRAIT_QUICK_BUILD "quick-build"
+/// We can handle 'dangerous' plants in botany safely
+#define TRAIT_PLANT_SAFE "plant_safe"
+/// Prevents the overlay from nearsighted
+#define TRAIT_NEARSIGHTED_CORRECTED "fixes_nearsighted"
+#define TRAIT_UNINTELLIGIBLE_SPEECH "unintelligible-speech"
+#define TRAIT_UNSTABLE "unstable"
+#define TRAIT_OIL_FRIED "oil_fried"
+#define TRAIT_MEDICAL_HUD "med_hud"
+#define TRAIT_SECURITY_HUD "sec_hud"
+/// for something granting you a diagnostic hud
+#define TRAIT_DIAGNOSTIC_HUD "diag_hud"
+/// Is a medbot healing you
+#define TRAIT_MEDIBOTCOMINGTHROUGH "medbot"
+#define TRAIT_PASSTABLE "passtable"
+/// Makes you immune to flashes
+#define TRAIT_NOFLASH "noflash"
+/// prevents xeno huggies implanting skeletons
+#define TRAIT_XENO_IMMUNE "xeno_immune"
+/// Allows the species to equip items that normally require a jumpsuit without having one equipped. Used by golems.
+#define TRAIT_NO_JUMPSUIT "no_jumpsuit"
+#define TRAIT_NAIVE "naive"
+/// always detect storms on icebox
+#define TRAIT_DETECT_STORM "detect_storm"
+#define TRAIT_PRIMITIVE "primitive"
+#define TRAIT_GUNFLIP "gunflip"
+/// Increases chance of getting special traumas, makes them harder to cure
+#define TRAIT_SPECIAL_TRAUMA_BOOST "special_trauma_boost"
+#define TRAIT_SPACEWALK "spacewalk"
+/// Sanity trait to keep track of when we're in hyperspace and add the appropriate element if we werent
+#define TRAIT_HYPERSPACED "hyperspaced"
+///Gives the movable free hyperspace movement without being pulled during shuttle transit
+#define TRAIT_FREE_HYPERSPACE_MOVEMENT "free_hyperspace_movement"
+///Lets the movable move freely in the soft-cordon area of transit space, which would otherwise teleport them away just before they got to see the true cordon
+#define TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT "free_hyperspace_softcordon_movement"
+///Deletes the object upon being dumped into space, usually from exiting hyperspace. Useful if you're spawning in a lot of stuff for hyperspace events that dont need to flood the entire game
+#define TRAIT_DEL_ON_SPACE_DUMP "del_on_hyperspace_leave"
+/// We can walk up or around cliffs, or at least we don't fall off of it
+#define TRAIT_CLIFF_WALKER "cliff_walker"
+/// Gets double arcade prizes
+#define TRAIT_GAMERGOD "gamer-god"
+#define TRAIT_GIANT "giant"
+#define TRAIT_DWARF "dwarf"
+/// makes your footsteps completely silent
+#define TRAIT_SILENT_FOOTSTEPS "silent_footsteps"
+/// hnnnnnnnggggg..... you're pretty good....
+#define TRAIT_NICE_SHOT "nice_shot"
+/// prevents the damage done by a brain tumor
+#define TRAIT_TUMOR_SUPPRESSED "brain_tumor_suppressed"
+/// Prevents hallucinations from the hallucination brain trauma (RDS)
+#define TRAIT_RDS_SUPPRESSED "rds_suppressed"
+/// mobs that have this trait cannot be extinguished
+#define TRAIT_PERMANENTLY_ONFIRE "permanently_onfire"
+/// Indicates if the mob is currently speaking with sign language
+#define TRAIT_SIGN_LANG "sign_language"
+/// This mob is able to use sign language over the radio.
+#define TRAIT_CAN_SIGN_ON_COMMS "can_sign_on_comms"
+/// nobody can use martial arts on this mob
+#define TRAIT_MARTIAL_ARTS_IMMUNE "martial_arts_immune"
+/// Immune to being afflicted by time stop (spell)
+#define TRAIT_TIME_STOP_IMMUNE "time_stop_immune"
+/// Revenants draining you only get a very small benefit.
+#define TRAIT_WEAK_SOUL "weak_soul"
+/// This mob has no soul
+#define TRAIT_NO_SOUL "no_soul"
+/// Prevents mob from riding mobs when buckled onto something
+#define TRAIT_CANT_RIDE "cant_ride"
+/// Prevents a mob from being unbuckled, currently only used to prevent people from falling over on the tram
+#define TRAIT_CANNOT_BE_UNBUCKLED "cannot_be_unbuckled"
+/// from heparin and nitrous oxide, makes open bleeding wounds rapidly spill more blood
+#define TRAIT_BLOODY_MESS "bloody_mess"
+/// from coagulant reagents, this doesn't affect the bleeding itself but does affect the bleed warning messages
+#define TRAIT_COAGULATING "coagulating"
+/// From anti-convulsant medication against seizures.
+#define TRAIT_ANTICONVULSANT "anticonvulsant"
+/// The holder of this trait has antennae or whatever that hurt a ton when noogied
+#define TRAIT_ANTENNAE "antennae"
+/// Blowing kisses actually does damage to the victim
+#define TRAIT_KISS_OF_DEATH "kiss_of_death"
+/// Used to activate french kissing
+#define TRAIT_GARLIC_BREATH "kiss_of_garlic_death"
+/// Addictions don't tick down, basically they're permanently addicted
+#define TRAIT_HOPELESSLY_ADDICTED "hopelessly_addicted"
+/// This mob has a cult halo.
+#define TRAIT_CULT_HALO "cult_halo"
+/// Their eyes glow an unnatural red colour. Currently used to set special examine text on humans. Does not guarantee the mob's eyes are coloured red, nor that there is any visible glow on their character sprite.
+#define TRAIT_UNNATURAL_RED_GLOWY_EYES "unnatural_red_glowy_eyes"
+/// Their eyes are bloodshot. Currently used to set special examine text on humans. Examine text is overridden by TRAIT_UNNATURAL_RED_GLOWY_EYES.
+#define TRAIT_BLOODSHOT_EYES "bloodshot_eyes"
+/// This mob should never close UI even if it doesn't have a client
+#define TRAIT_PRESERVE_UI_WITHOUT_CLIENT "preserve_ui_without_client"
+/// This mob overrides certian SSlag_switch measures with this special trait
+#define TRAIT_BYPASS_MEASURES "bypass_lagswitch_measures"
+/// Someone can safely be attacked with honorbound with ONLY a combat mode check, the trait is assuring holding a weapon and hitting won't hurt them..
+#define TRAIT_ALLOWED_HONORBOUND_ATTACK "allowed_honorbound_attack"
+/// The user is sparring
+#define TRAIT_SPARRING "sparring"
+/// The user is currently challenging an elite mining mob. Prevents him from challenging another until he's either lost or won.
+#define TRAIT_ELITE_CHALLENGER "elite_challenger"
+/// For living mobs. It signals that the mob shouldn't have their data written in an external json for persistence.
+#define TRAIT_DONT_WRITE_MEMORY "dont_write_memory"
+/// This mob can be painted with the spraycan
+#define TRAIT_SPRAY_PAINTABLE "spray_paintable"
+/// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met
+#define TRAIT_AI_BAGATTACK "bagattack"
+/// This mobs bodyparts are invisible but still clickable.
+#define TRAIT_INVISIBLE_MAN "invisible_man"
+/// Don't draw external organs/species features like wings, horns, frills and stuff
+#define TRAIT_HIDE_EXTERNAL_ORGANS "hide_external_organs"
+///When people are floating from zero-grav or something, we can move around freely!
+#define TRAIT_FREE_FLOAT_MOVEMENT "free_float_movement"
+// You can stare into the abyss, but it does not stare back.
+// You're immune to the hallucination effect of the supermatter, either
+// through force of will, or equipment. Present on /mob or /datum/mind
+#define TRAIT_MADNESS_IMMUNE "supermatter_madness_immune"
+// You can stare into the abyss, and it turns pink.
+// Being close enough to the supermatter makes it heal at higher temperatures
+// and emit less heat. Present on /mob or /datum/mind
+#define TRAIT_SUPERMATTER_SOOTHER "supermatter_soother"
+/// Mob has fov applied to it
+#define TRAIT_FOV_APPLIED "fov_applied"
+
+/// Trait added when a revenant is visible.
+#define TRAIT_REVENANT_REVEALED "revenant_revealed"
+/// Trait added when a revenant has been inhibited (typically by the bane of a holy weapon)
+#define TRAIT_REVENANT_INHIBITED "revenant_inhibited"
+
+/// Trait which prevents you from becoming overweight
+#define TRAIT_NOFAT "cant_get_fat"
+
+/// Trait which allows you to eat rocks
+#define TRAIT_ROCK_EATER "rock_eater"
+/// Trait which allows you to gain bonuses from consuming rocks
+#define TRAIT_ROCK_METAMORPHIC "rock_metamorphic"
+
+/// `do_teleport` will not allow this atom to teleport
+#define TRAIT_NO_TELEPORT "no-teleport"
+/// This atom is a secluded location, which is counted as out of bounds.
+/// Anything that enters this atom's contents should react if it wants to stay in bounds.
+#define TRAIT_SECLUDED_LOCATION "secluded_loc"
+
+/// Trait used by fugu glands to avoid double buffing
+#define TRAIT_FUGU_GLANDED "fugu_glanded"
+
+/// Trait that tracks if something has been renamed. Typically holds a REF() to the object itself (AKA src) for wide addition/removal.
+#define TRAIT_WAS_RENAMED "was_renamed"
+
+/// When someone with this trait fires a ranged weapon, their fire delays and click cooldowns are halved
+#define TRAIT_DOUBLE_TAP "double_tap"
+
+/// Trait applied to [/datum/mind] to stop someone from using the cursed hot springs to polymorph more than once.
+#define TRAIT_HOT_SPRING_CURSED "hot_spring_cursed"
+
+/// If something has been engraved/cannot be engraved
+#define TRAIT_NOT_ENGRAVABLE "not_engravable"
+
+/// Whether or not orbiting is blocked or not
+#define TRAIT_ORBITING_FORBIDDEN "orbiting_forbidden"
+/// Trait applied to mob/living to mark that spiders should not gain further enriched eggs from eating their corpse.
+#define TRAIT_SPIDER_CONSUMED "spider_consumed"
+/// Whether we're sneaking, from the creature sneak ability.
+#define TRAIT_SNEAK "sneaking_creatures"
+
+/// Item still allows you to examine items while blind and actively held.
+#define TRAIT_BLIND_TOOL "blind_tool"
+
+/// The person with this trait always appears as 'unknown'.
+#define TRAIT_UNKNOWN "unknown"
+
+/// If the mob has this trait and die, their bomb implant doesn't detonate automatically. It must be consciously activated.
+#define TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION "prevent_implant_auto_explosion"
+
+/// If applied to a mob, nearby dogs will have a small chance to nonharmfully harass said mob
+#define TRAIT_HATED_BY_DOGS "hated_by_dogs"
+/// Mobs with this trait will not be immobilized when held up
+#define TRAIT_NOFEAR_HOLDUPS "no_fear_holdup"
+/// Mob has gotten an armor buff from adamantine extract
+#define TRAIT_ADAMANTINE_EXTRACT_ARMOR "adamantine_extract_armor"
+/// Mobs with this trait won't be able to dual wield guns.
+#define TRAIT_NO_GUN_AKIMBO "no_gun_akimbo"
+
+/// Projectile with this trait will always hit the defined zone of a struck living mob.
+#define TRAIT_ALWAYS_HIT_ZONE "always_hit_zone"
+
+/// Mobs with this trait do care about a few grisly things, such as digging up graves. They also really do not like bringing people back to life or tending wounds, but love autopsies and amputations.
+#define TRAIT_MORBID "morbid"
+
+/// Whether or not the user is in a MODlink call, prevents making more calls
+#define TRAIT_IN_CALL "in_call"
+
+/// Is the mob standing on an elevated surface? This prevents them from dropping down if not elevated first.
+#define TRAIT_ON_ELEVATED_SURFACE "on_elevated_surface"
+
+/// Prevents you from twohanding weapons.
+#define TRAIT_NO_TWOHANDING "no_twohanding"
+
+/// Halves the time of tying a tie.
+#define TRAIT_FAST_TYING "fast_tying"
+
+/// Sells for more money on the pirate bounty pad.
+#define TRAIT_HIGH_VALUE_RANSOM "high_value_ransom"
+
+// METABOLISMS
+// Various jobs on the station have historically had better reactions
+// to various drinks and foodstuffs. Security liking donuts is a classic
+// example. Through years of training/abuse, their livers have taken
+// a liking to those substances. Steal a sec officer's liver, eat donuts good.
+
+// These traits are applied to /obj/item/organ/internal/liver
+#define TRAIT_LAW_ENFORCEMENT_METABOLISM "law_enforcement_metabolism"
+#define TRAIT_CULINARY_METABOLISM "culinary_metabolism"
+#define TRAIT_COMEDY_METABOLISM "comedy_metabolism"
+#define TRAIT_MEDICAL_METABOLISM "medical_metabolism"
+#define TRAIT_ENGINEER_METABOLISM "engineer_metabolism"
+#define TRAIT_ROYAL_METABOLISM "royal_metabolism"
+#define TRAIT_PRETENDER_ROYAL_METABOLISM "pretender_royal_metabolism"
+#define TRAIT_BALLMER_SCIENTIST "ballmer_scientist"
+#define TRAIT_MAINTENANCE_METABOLISM "maintenance_metabolism"
+#define TRAIT_CORONER_METABOLISM "coroner_metabolism"
+
+//LUNG TRAITS
+/// Lungs always breathe normally when in vacuum/space.
+#define TRAIT_SPACEBREATHING "spacebreathing"
+
+/// This mob can strip other mobs.
+#define TRAIT_CAN_STRIP "can_strip"
+/// Can use the nuclear device's UI, regardless of a lack of hands
+#define TRAIT_CAN_USE_NUKE "can_use_nuke"
+
+// If present on a mob or mobmind, allows them to "suplex" an immovable rod
+// turning it into a glorified potted plant, and giving them an
+// achievement. Can also be used on rod-form wizards.
+// Normally only present in the mind of a Research Director.
+#define TRAIT_ROD_SUPLEX "rod_suplex"
+/// The mob has an active mime vow of silence, and thus is unable to speak and has other mime things going on
+#define TRAIT_MIMING "miming"
+
+/// This mob is phased out of reality from magic, either a jaunt or rod form
+#define TRAIT_MAGICALLY_PHASED "magically_phased"
+
+//SKILLS
+#define TRAIT_UNDERWATER_BASKETWEAVING_KNOWLEDGE "underwater_basketweaving"
+#define TRAIT_WINE_TASTER "wine_taster"
+#define TRAIT_BONSAI "bonsai"
+#define TRAIT_LIGHTBULB_REMOVER "lightbulb_remover"
+#define TRAIT_KNOW_ROBO_WIRES "know_robo_wires"
+#define TRAIT_KNOW_ENGI_WIRES "know_engi_wires"
+#define TRAIT_ENTRAILS_READER "entrails_reader"
+#define TRAIT_SABRAGE_PRO "sabrage_pro"
+/// this skillchip trait lets you wash brains in washing machines to heal them
+#define TRAIT_BRAINWASHING "brainwashing"
+/// Allows chef's to chefs kiss their food, to make them with love
+#define TRAIT_CHEF_KISS "chefs_kiss"
+
+///Movement type traits for movables. See elements/movetype_handler.dm
+#define TRAIT_MOVE_GROUND "move_ground"
+#define TRAIT_MOVE_FLYING "move_flying"
+#define TRAIT_MOVE_VENTCRAWLING "move_ventcrawling"
+#define TRAIT_MOVE_FLOATING "move_floating"
+#define TRAIT_MOVE_PHASING "move_phasing"
+#define TRAIT_MOVE_UPSIDE_DOWN "move_upside_down"
+/// Disables the floating animation. See above.
+#define TRAIT_NO_FLOATING_ANIM "no-floating-animation"
+
+/// Cannot be turned into a funny skeleton by the plasma river
+#define TRAIT_NO_PLASMA_TRANSFORM "no_plasma_transform"
+
+/// Weather immunities, also protect mobs inside them.
+#define TRAIT_LAVA_IMMUNE "lava_immune" //Used by lava turfs and The Floor Is Lava.
+#define TRAIT_ASHSTORM_IMMUNE "ashstorm_immune"
+#define TRAIT_SNOWSTORM_IMMUNE "snowstorm_immune"
+#define TRAIT_RADSTORM_IMMUNE "radstorm_immune"
+#define TRAIT_VOIDSTORM_IMMUNE "voidstorm_immune"
+#define TRAIT_WEATHER_IMMUNE "weather_immune" //Immune to ALL weather effects.
+
+/// Cannot be grabbed by goliath tentacles
+#define TRAIT_TENTACLE_IMMUNE "tentacle_immune"
+/// Currently under the effect of overwatch
+#define TRAIT_OVERWATCHED "watcher_overwatched"
+/// Cannot be targeted by watcher overwatch
+#define TRAIT_OVERWATCH_IMMUNE "overwatch_immune"
+
+//non-mob traits
+/// Used for limb-based paralysis, where replacing the limb will fix it.
+#define TRAIT_PARALYSIS "paralysis"
+/// Used for limbs.
+#define TRAIT_DISABLED_BY_WOUND "disabled-by-wound"
+/// This movable atom has the explosive block element
+#define TRAIT_BLOCKING_EXPLOSIVES "blocking_explosives"
+
+///Lava will be safe to cross while it has this trait.
+#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 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
+#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
+#define TRAIT_TURF_IGNORE_SLIPPERY "turf_ignore_slippery"
+
+/// Mobs with this trait can't send the mining shuttle console when used outside the station itself
+#define TRAIT_FORBID_MINING_SHUTTLE_CONSOLE_OUTSIDE_STATION "forbid_mining_shuttle_console_outside_station"
+
+//important_recursive_contents traits
+/*
+ * Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas.
+ * Use [/atom/movable/proc/become_area_sensitive(trait_source)] to properly enable it. How you remove it isn't as important.
+ */
+#define TRAIT_AREA_SENSITIVE "area-sensitive"
+///every hearing sensitive atom has this trait
+#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
+///every object that is currently the active storage of some client mob has this trait
+#define TRAIT_ACTIVE_STORAGE "active_storage"
+
+/// Climbable trait, given and taken by the climbable element when added or removed. Exists to be easily checked via HAS_TRAIT().
+#define TRAIT_CLIMBABLE "trait_climbable"
+
+/// Used by the honkspam element to avoid spamming the sound. Amusing considering its name.
+#define TRAIT_HONKSPAMMING "trait_honkspamming"
+
+///Used for managing KEEP_TOGETHER in [/atom/var/appearance_flags]
+#define TRAIT_KEEP_TOGETHER "keep-together"
+
+// cargo traits
+///If the item will block the cargo shuttle from flying to centcom
+#define TRAIT_BANNED_FROM_CARGO_SHUTTLE "banned_from_cargo_shuttle"
+///If the crate's contents are immune to the missing item manifest error
+#define TRAIT_NO_MISSING_ITEM_ERROR "no_missing_item_error"
+///If the crate is immune to the wrong content in manifest error
+#define TRAIT_NO_MANIFEST_CONTENTS_ERROR "no_manifest_contents_error"
+
+///SSeconomy trait, if the market is crashing and people can't withdraw credits from ID cards.
+#define TRAIT_MARKET_CRASHING "market_crashing"
+
+// item traits
+#define TRAIT_NODROP "nodrop"
+/// cannot be inserted in a storage.
+#define TRAIT_NO_STORAGE_INSERT "no_storage_insert"
+/// Visible on t-ray scanners if the atom/var/level == 1
+#define TRAIT_T_RAY_VISIBLE "t-ray-visible"
+/// If this item's been grilled
+#define TRAIT_FOOD_GRILLED "food_grilled"
+/// If this item's been fried
+#define TRAIT_FOOD_FRIED "food_fried"
+/// This is a silver slime created item
+#define TRAIT_FOOD_SILVER "food_silver"
+/// If this item's been made by a chef instead of being map-spawned or admin-spawned or such
+#define TRAIT_FOOD_CHEF_MADE "food_made_by_chef"
+/// The items needs two hands to be carried
+#define TRAIT_NEEDS_TWO_HANDS "needstwohands"
+/// Can't be catched when thrown
+#define TRAIT_UNCATCHABLE "uncatchable"
+/// Fish in this won't die
+#define TRAIT_FISH_SAFE_STORAGE "fish_case"
+/// Stuff that can go inside fish cases
+#define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile"
+/// If the item can be used as a bit.
+#define TRAIT_FISHING_BAIT "fishing_bait"
+/// The quality of the bait. It influences odds of catching fish
+#define TRAIT_BASIC_QUALITY_BAIT "baic_quality_bait"
+#define TRAIT_GOOD_QUALITY_BAIT "good_quality_bait"
+#define TRAIT_GREAT_QUALITY_BAIT "great_quality_bait"
+/// Baits with this trait will ignore bait preferences and related fish traits.
+#define TRAIT_OMNI_BAIT "omni_bait"
+/// Plants that were mutated as a result of passive instability, not a mutation threshold.
+#define TRAIT_PLANT_WILDMUTATE "wildmutation"
+/// If you hit an APC with exposed internals with this item it will try to shock you
+#define TRAIT_APC_SHOCKING "apc_shocking"
+/// Properly wielded two handed item
+#define TRAIT_WIELDED "wielded"
+/// A transforming item that is actively extended / transformed
+#define TRAIT_TRANSFORM_ACTIVE "active_transform"
+/// Buckling yourself to objects with this trait won't immobilize you
+#define TRAIT_NO_IMMOBILIZE "no_immobilize"
+/// Prevents stripping this equipment
+#define TRAIT_NO_STRIP "no_strip"
+/// Disallows this item from being pricetagged with a barcode
+#define TRAIT_NO_BARCODES "no_barcode"
+/// Allows heretics to cast their spells.
+#define TRAIT_ALLOW_HERETIC_CASTING "allow_heretic_casting"
+/// Designates a heart as a living heart for a heretic.
+#define TRAIT_LIVING_HEART "living_heart"
+/// Prevents the same person from being chosen multiple times for kidnapping objective
+#define TRAIT_HAS_BEEN_KIDNAPPED "has_been_kidnapped"
+/// An item still plays its hitsound even if it has 0 force, instead of the tap
+#define TRAIT_CUSTOM_TAP_SOUND "no_tap_sound"
+/// Makes the feedback message when someone else is putting this item on you more noticeable
+#define TRAIT_DANGEROUS_OBJECT "dangerous_object"
+/// determines whether or not objects are haunted and teleport/attack randomly
+#define TRAIT_HAUNTED "haunted"
+
+//quirk traits
+#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
+#define TRAIT_HEAVY_DRINKER "heavy_drinker"
+#define TRAIT_AGEUSIA "ageusia"
+#define TRAIT_HEAVY_SLEEPER "heavy_sleeper"
+#define TRAIT_NIGHT_VISION "night_vision"
+#define TRAIT_LIGHT_STEP "light_step"
+#define TRAIT_SPIRITUAL "spiritual"
+#define TRAIT_CLOWN_ENJOYER "clown_enjoyer"
+#define TRAIT_MIME_FAN "mime_fan"
+#define TRAIT_VORACIOUS "voracious"
+#define TRAIT_SELF_AWARE "self_aware"
+#define TRAIT_FREERUNNING "freerunning"
+#define TRAIT_SKITTISH "skittish"
+#define TRAIT_PROSOPAGNOSIA "prosopagnosia"
+#define TRAIT_TAGGER "tagger"
+#define TRAIT_PHOTOGRAPHER "photographer"
+#define TRAIT_MUSICIAN "musician"
+#define TRAIT_LIGHT_DRINKER "light_drinker"
+#define TRAIT_EMPATH "empath"
+#define TRAIT_FRIENDLY "friendly"
+#define TRAIT_GRABWEAKNESS "grab_weakness"
+#define TRAIT_SNOB "snob"
+#define TRAIT_BALD "bald"
+#define TRAIT_SHAVED "shaved"
+#define TRAIT_BADTOUCH "bad_touch"
+#define TRAIT_EXTROVERT "extrovert"
+#define TRAIT_INTROVERT "introvert"
+#define TRAIT_ANXIOUS "anxious"
+#define TRAIT_SMOKER "smoker"
+#define TRAIT_POSTERBOY "poster_boy"
+#define TRAIT_THROWINGARM "throwing_arm"
+#define TRAIT_SETTLER "settler"
+
+/// This mob always lands on their feet when they fall, for better or for worse.
+#define TRAIT_CATLIKE_GRACE "catlike_grace"
+
+///if the atom has a sticker attached to it
+#define TRAIT_STICKERED "stickered"
+
+// Debug traits
+/// This object has light debugging tools attached to it
+#define TRAIT_LIGHTING_DEBUGGED "lighting_debugged"
+
+/// Gives you the Shifty Eyes quirk, rarely making people who examine you think you examined them back even when you didn't
+#define TRAIT_SHIFTY_EYES "shifty_eyes"
+
+///Trait for the gamer quirk.
+#define TRAIT_GAMER "gamer"
+
+///Trait for dryable items
+#define TRAIT_DRYABLE "trait_dryable"
+///Trait for dried items
+#define TRAIT_DRIED "trait_dried"
+/// Trait for customizable reagent holder
+#define TRAIT_CUSTOMIZABLE_REAGENT_HOLDER "customizable_reagent_holder"
+/// Trait for allowing an item that isn't food into the customizable reagent holder
+#define TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT "odd_customizable_food_ingredient"
+
+/// Used to prevent multiple floating blades from triggering over the same target
+#define TRAIT_BEING_BLADE_SHIELDED "being_blade_shielded"
+
+/// This mob doesn't count as looking at you if you can only act while unobserved
+#define TRAIT_UNOBSERVANT "trait_unobservant"
+
+/* Traits for ventcrawling.
+ * Both give access to ventcrawling, but *_NUDE requires the user to be
+ * wearing no clothes and holding no items. If both present, *_ALWAYS
+ * takes precedence.
+ */
+#define TRAIT_VENTCRAWLER_ALWAYS "ventcrawler_always"
+#define TRAIT_VENTCRAWLER_NUDE "ventcrawler_nude"
+
+/// Minor trait used for beakers, or beaker-ishes. [/obj/item/reagent_containers], to show that they've been used in a reagent grinder.
+#define TRAIT_MAY_CONTAIN_BLENDED_DUST "may_contain_blended_dust"
+
+/// Trait put on [/mob/living/carbon/human]. If that mob has a crystal core, also known as an ethereal heart, it will not try to revive them if the mob dies.
+#define TRAIT_CANNOT_CRYSTALIZE "cannot_crystalize"
+
+///Trait applied to turfs when an atmos holosign is placed on them. It will stop firedoors from closing.
+#define TRAIT_FIREDOOR_STOP "firedoor_stop"
+
+/// Trait applied when the MMI component is added to an [/obj/item/integrated_circuit]
+#define TRAIT_COMPONENT_MMI "component_mmi"
+
+/// Trait applied when an integrated circuit/module becomes undupable
+#define TRAIT_CIRCUIT_UNDUPABLE "circuit_undupable"
+
+/// Trait applied when an integrated circuit opens a UI on a player (see list pick component)
+#define TRAIT_CIRCUIT_UI_OPEN "circuit_ui_open"
+
+/// PDA/ModPC Traits. This one makes PDAs explode if the user opens the messages menu
+#define TRAIT_PDA_MESSAGE_MENU_RIGGED "pda_message_menu_rigged"
+/// This one denotes a PDA has received a rigged message and will explode when the user tries to reply to a rigged PDA message
+#define TRAIT_PDA_CAN_EXPLODE "pda_can_explode"
+///The download speeds of programs from the dowloader is halved.
+#define TRAIT_MODPC_HALVED_DOWNLOAD_SPEED "modpc_halved_download_speed"
+
+/// If present on a [/mob/living/carbon], will make them appear to have a medium level disease on health HUDs.
+#define TRAIT_DISEASELIKE_SEVERITY_MEDIUM "diseaselike_severity_medium"
+
+/// trait denoting someone will crawl faster in soft crit
+#define TRAIT_TENACIOUS "tenacious"
+
+/// trait denoting someone will sometimes recover out of crit
+#define TRAIT_UNBREAKABLE "unbreakable"
+
+/// trait that prevents AI controllers from planning detached from ai_status to prevent weird state stuff.
+#define TRAIT_AI_PAUSED "TRAIT_AI_PAUSED"
+
+/// this is used to bypass tongue language restrictions but not tongue disabilities
+#define TRAIT_TOWER_OF_BABEL "tower_of_babel"
+
+/// This target has recently been shot by a marksman coin and is very briefly immune to being hit by one again to prevent recursion
+#define TRAIT_RECENTLY_COINED "recently_coined"
+
+/// Receives echolocation images.
+#define TRAIT_ECHOLOCATION_RECEIVER "echolocation_receiver"
+/// Echolocation has a higher range.
+#define TRAIT_ECHOLOCATION_EXTRA_RANGE "echolocation_extra_range"
+
+/// Trait given to a living mob and any observer mobs that stem from them if they suicide.
+/// For clarity, this trait should always be associated/tied to a reference to the mob that suicided- not anything else.
+#define TRAIT_SUICIDED "committed_suicide"
+
+/// Trait given to a living mob to prevent wizards from making it immortal
+#define TRAIT_PERMANENTLY_MORTAL "permanently_mortal"
+
+///Trait given to a mob with a ckey currently in a temporary body, allowing people to know someone will re-enter the round later.
+#define TRAIT_MIND_TEMPORARILY_GONE "temporarily_gone"
+
+/// Similar trait given to temporary bodies inhabited by players
+#define TRAIT_TEMPORARY_BODY "temporary_body"
+
+/// Trait given to objects with the wallmounted component
+#define TRAIT_WALLMOUNTED "wallmounted"
+
+/// Trait given to mechs that can have orebox functionality on movement
+#define TRAIT_OREBOX_FUNCTIONAL "orebox_functional"
+
+///A trait for mechs that were created through the normal construction process, and not spawned by map or other effects.
+#define TRAIT_MECHA_CREATED_NORMALLY "trait_mecha_created_normally"
+
+///fish traits
+#define TRAIT_RESIST_EMULSIFY "resist_emulsify"
+#define TRAIT_FISH_SELF_REPRODUCE "fish_self_reproduce"
+#define TRAIT_FISH_NO_MATING "fish_no_mating"
+#define TRAIT_YUCKY_FISH "yucky_fish"
+#define TRAIT_FISH_TOXIN_IMMUNE "fish_toxin_immune"
+#define TRAIT_FISH_CROSSBREEDER "fish_crossbreeder"
+#define TRAIT_FISH_AMPHIBIOUS "fish_amphibious"
+///Trait needed for the lubefish evolution
+#define TRAIT_FISH_FED_LUBE "fish_fed_lube"
+#define TRAIT_FISH_NO_HUNGER "fish_no_hunger"
+///It comes from a fish case. Relevant for bounties so far.
+#define TRAIT_FISH_FROM_CASE "fish_from_case"
+
+/// Trait given to angelic constructs to let them purge cult runes
+#define TRAIT_ANGELIC "angelic"
+
+/// Trait given to a dreaming carbon when they are currently doing dreaming stuff
+#define TRAIT_DREAMING "currently_dreaming"
+
+///generic atom traits
+/// Trait from [/datum/element/rust]. Its rusty and should be applying a special overlay to denote this.
+#define TRAIT_RUSTY "rust_trait"
+/// Stops someone from splashing their reagent_container on an object with this trait
+#define TRAIT_DO_NOT_SPLASH "do_not_splash"
+/// Marks an atom when the cleaning of it is first started, so that the cleaning overlay doesn't get removed prematurely
+#define TRAIT_CURRENTLY_CLEANING "currently_cleaning"
+/// Objects with this trait are deleted if they fall into chasms, rather than entering abstract storage
+#define TRAIT_CHASM_DESTROYED "chasm_destroyed"
+/// Trait from being under the floor in some manner
+#define TRAIT_UNDERFLOOR "underfloor"
+/// If the movable shouldn't be reflected by mirrors.
+#define TRAIT_NO_MIRROR_REFLECTION "no_mirror_reflection"
+/// If this movable is currently treading in a turf with the immerse element.
+#define TRAIT_IMMERSED "immersed"
+/// From [/datum/element/elevation_core] for purpose of checking if the turf has the trait from an instance of the element
+#define TRAIT_ELEVATED_TURF "elevated_turf"
+/**
+ * With this, the immerse overlay will give the atom its own submersion visual overlay
+ * instead of one that's also shared with other movables, thus making editing its appearance possible.
+ */
+#define TRAIT_UNIQUE_IMMERSE "unique_immerse"
+
+/// This item is currently under the control of telekinesis
+#define TRAIT_TELEKINESIS_CONTROLLED "telekinesis_controlled"
+
+/// changelings with this trait can no longer talk over the hivemind
+#define TRAIT_CHANGELING_HIVEMIND_MUTE "ling_mute"
+#define TRAIT_HULK "hulk"
+/// Isn't attacked harmfully by blob structures
+#define TRAIT_BLOB_ALLY "blob_ally"
+
+///Traits given by station traits
+#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
+#define STATION_TRAIT_BIGGER_PODS "station_trait_bigger_pods"
+#define STATION_TRAIT_BIRTHDAY "station_trait_birthday"
+#define STATION_TRAIT_BOTS_GLITCHED "station_trait_bot_glitch"
+#define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation"
+#define STATION_TRAIT_CYBERNETIC_REVOLUTION "station_trait_cybernetic_revolution"
+#define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint"
+#define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint"
+#define STATION_TRAIT_FORESTED "station_trait_forested"
+#define STATION_TRAIT_HANGOVER "station_trait_hangover"
+#define STATION_TRAIT_LATE_ARRIVALS "station_trait_late_arrivals"
+#define STATION_TRAIT_LOANER_SHUTTLE "station_trait_loaner_shuttle"
+#define STATION_TRAIT_MEDBOT_MANIA "station_trait_medbot_mania"
+#define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched"
+#define STATION_TRAIT_PREMIUM_INTERNALS "station_trait_premium_internals"
+#define STATION_TRAIT_RADIOACTIVE_NEBULA "station_trait_radioactive_nebula"
+#define STATION_TRAIT_RANDOM_ARRIVALS "station_trait_random_arrivals"
+#define STATION_TRAIT_REVOLUTIONARY_TRASHING "station_trait_revolutionary_trashing"
+#define STATION_TRAIT_SHUTTLE_SALE "station_trait_shuttle_sale"
+#define STATION_TRAIT_SMALLER_PODS "station_trait_smaller_pods"
+#define STATION_TRAIT_SPIDER_INFESTATION "station_trait_spider_infestation"
+#define STATION_TRAIT_UNIQUE_AI "station_trait_unique_ai"
+#define STATION_TRAIT_UNNATURAL_ATMOSPHERE "station_trait_unnatural_atmosphere"
+#define STATION_TRAIT_VENDING_SHORTAGE "station_trait_vending_shortage"
+
+/// This atom is currently spinning.
+#define TRAIT_SPINNING "spinning"
+
+/// Denotes that this id card was given via the job outfit, aka the first ID this player got.
+#define TRAIT_JOB_FIRST_ID_CARD "job_first_id_card"
+/// ID cards with this trait will attempt to forcibly occupy the front-facing ID card slot in wallets.
+#define TRAIT_MAGNETIC_ID_CARD "magnetic_id_card"
+/// ID cards with this trait have special appraisal text.
+#define TRAIT_TASTEFULLY_THICK_ID_CARD "impressive_very_nice"
+/// things with this trait are treated as having no access in /obj/proc/check_access(obj/item)
+#define TRAIT_ALWAYS_NO_ACCESS "alwaysnoaccess"
+
+/// This human wants to see the color of their glasses, for some reason
+#define TRAIT_SEE_GLASS_COLORS "see_glass_colors"
+
+// Radiation defines
+
+/// Marks that this object is irradiated
+#define TRAIT_IRRADIATED "iraddiated"
+
+/// Harmful radiation effects, the toxin damage and the burns, will not occur while this trait is active
+#define TRAIT_HALT_RADIATION_EFFECTS "halt_radiation_effects"
+
+/// This clothing protects the user from radiation.
+/// This should not be used on clothing_traits, but should be applied to the clothing itself.
+#define TRAIT_RADIATION_PROTECTED_CLOTHING "radiation_protected_clothing"
+
+/// Whether or not this item will allow the radiation SS to go through standard
+/// radiation processing as if this wasn't already irradiated.
+/// Basically, without this, COMSIG_IN_RANGE_OF_IRRADIATION won't fire once the object is irradiated.
+#define TRAIT_BYPASS_EARLY_IRRADIATED_CHECK "radiation_bypass_early_irradiated_check"
+
+/// Simple trait that just holds if we came into growth from a specific mob type. Should hold a REF(src) to the type of mob that caused the growth, not anything else.
+#define TRAIT_WAS_EVOLVED "was_evolved_from_the_mob_we_hold_a_textref_to"
+
+// Traits to heal for
+
+/// This mob heals from carp rifts.
+#define TRAIT_HEALS_FROM_CARP_RIFTS "heals_from_carp_rifts"
+
+/// This mob heals from cult pylons.
+#define TRAIT_HEALS_FROM_CULT_PYLONS "heals_from_cult_pylons"
+
+/// Ignore Crew monitor Z levels
+#define TRAIT_MULTIZ_SUIT_SENSORS "multiz_suit_sensors"
+
+/// Ignores body_parts_covered during the add_fingerprint() proc. Works both on the person and the item in the glove slot.
+#define TRAIT_FINGERPRINT_PASSTHROUGH "fingerprint_passthrough"
+
+/// this object has been frozen
+#define TRAIT_FROZEN "frozen"
+
+/// Currently fishing
+#define TRAIT_GONE_FISHING "fishing"
+
+/// Makes a species be better/worse at tackling depending on their wing's status
+#define TRAIT_TACKLING_WINGED_ATTACKER "tacking_winged_attacker"
+
+/// Makes a species be frail and more likely to roll bad results if they hit a wall
+#define TRAIT_TACKLING_FRAIL_ATTACKER "tackling_frail_attacker"
+
+/// Makes a species be better/worse at defending against tackling depending on their tail's status
+#define TRAIT_TACKLING_TAILED_DEFENDER "tackling_tailed_defender"
+
+/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything?
+#define TRAIT_RUNECHAT_HIDDEN "runechat_hidden"
+
+/// the object has a label applied
+#define TRAIT_HAS_LABEL "labeled"
+
+/// Trait given to a mob that is currently thinking (giving off the "thinking" icon), used in an IC context
+#define TRAIT_THINKING_IN_CHARACTER "currently_thinking_IC"
+
+///without a human having this trait, they speak as if they have no tongue.
+#define TRAIT_SPEAKS_CLEARLY "speaks_clearly"
+
+// specific sources for TRAIT_SPEAKS_CLEARLY
+
+///Trait given by /datum/component/germ_sensitive
+#define TRAIT_GERM_SENSITIVE "germ_sensitive"
+
+/// This atom can have spells cast from it if a mob is within it
+/// This means the "caster" of the spell is changed to the mob's loc
+/// Note this doesn't mean all spells are guaranteed to work or the mob is guaranteed to cast
+#define TRAIT_CASTABLE_LOC "castable_loc"
+
+///Trait given by /datum/element/relay_attacker
+#define TRAIT_RELAYING_ATTACKER "relaying_attacker"
+
+///Trait given to limb by /mob/living/basic/living_limb_flesh
+#define TRAIT_IGNORED_BY_LIVING_FLESH "livingflesh_ignored"
+
+/// Trait given while using /datum/action/cooldown/mob_cooldown/wing_buffet
+#define TRAIT_WING_BUFFET "wing_buffet"
+/// Trait given while tired after using /datum/action/cooldown/mob_cooldown/wing_buffet
+#define TRAIT_WING_BUFFET_TIRED "wing_buffet_tired"
+/// Trait given to a dragon who fails to defend their rifts
+#define TRAIT_RIFT_FAILURE "fail_dragon_loser"
+
+///trait determines if this mob can breed given by /datum/component/breeding
+#define TRAIT_MOB_BREEDER "mob_breeder"
+/// Trait given to mobs that we do not want to mindswap
+#define TRAIT_NO_MINDSWAP "no_mindswap"
+///trait given to food that can be baked by /datum/component/bakeable
+#define TRAIT_BAKEABLE "bakeable"
+
+/// Trait given to foam darts that have an insert in them
+#define TRAIT_DART_HAS_INSERT "dart_has_insert"
+
+///Trait granted by janitor skillchip, allows communication with cleanbots
+#define TRAIT_CLEANBOT_WHISPERER "cleanbot_whisperer"
+
+/// Trait given when a mob is currently in invisimin mode
+#define TRAIT_INVISIMIN "invisimin"
+
+///Trait given when a mob has been tipped
+#define TRAIT_MOB_TIPPED "mob_tipped"
+
+/// Trait which self-identifies as an enemy of the law
+#define TRAIT_ALWAYS_WANTED "always_wanted"
+
+// END TRAIT DEFINES
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
new file mode 100644
index 000000000000..f9cdbe4326d1
--- /dev/null
+++ b/code/__DEFINES/traits/sources.dm
@@ -0,0 +1,281 @@
+// This file contains all of the trait sources, or all of the things that grant traits.
+// Several things such as `type` or `REF(src)` may be used in the ADD_TRAIT() macro as the "source", but this file contains all of the defines for immutable static strings.
+
+// common trait sources
+#define TRAIT_GENERIC "generic"
+#define UNCONSCIOUS_TRAIT "unconscious"
+#define EYE_DAMAGE "eye_damage"
+#define EAR_DAMAGE "ear_damage"
+#define GENETIC_MUTATION "genetic"
+#define OBESITY "obesity"
+#define MAGIC_TRAIT "magic"
+#define TRAUMA_TRAIT "trauma"
+#define FLIGHTPOTION_TRAIT "flightpotion"
+/// Trait inherited by experimental surgeries
+#define EXPERIMENTAL_SURGERY_TRAIT "experimental_surgery"
+#define DISEASE_TRAIT "disease"
+#define SPECIES_TRAIT "species"
+#define ORGAN_TRAIT "organ"
+/// Trait given by augmented limbs
+#define AUGMENTATION_TRAIT "augments"
+/// Trait given by organ gained via abductor surgery
+#define ABDUCTOR_GLAND_TRAIT "abductor_gland"
+/// cannot be removed without admin intervention
+#define ROUNDSTART_TRAIT "roundstart"
+#define JOB_TRAIT "job"
+#define CYBORG_ITEM_TRAIT "cyborg-item"
+/// Any traits granted by quirks.
+#define QUIRK_TRAIT "quirk_trait"
+/// (B)admins only.
+#define ADMIN_TRAIT "admin"
+/// Any traits given through a smite.
+#define SMITE_TRAIT "smite"
+#define CHANGELING_TRAIT "changeling"
+#define CULT_TRAIT "cult"
+#define LICH_TRAIT "lich"
+
+#define ABSTRACT_ITEM_TRAIT "abstract-item"
+/// A trait given by any status effect
+#define STATUS_EFFECT_TRAIT "status-effect"
+
+/// Trait from light debugging
+#define LIGHT_DEBUG_TRAIT "light-debug"
+
+#define CLOTHING_TRAIT "clothing"
+#define HELMET_TRAIT "helmet"
+/// inherited from the mask
+#define MASK_TRAIT "mask"
+/// inherited from your sweet kicks
+#define SHOES_TRAIT "shoes"
+/// Trait inherited by implants
+#define IMPLANT_TRAIT "implant"
+#define GLASSES_TRAIT "glasses"
+/// inherited from riding vehicles
+#define VEHICLE_TRAIT "vehicle"
+#define INNATE_TRAIT "innate"
+#define CRIT_HEALTH_TRAIT "crit_health"
+#define OXYLOSS_TRAIT "oxyloss"
+/// Trait sorce for "was recently shocked by something"
+#define WAS_SHOCKED "was_shocked"
+#define TURF_TRAIT "turf"
+/// trait associated to being buckled
+#define BUCKLED_TRAIT "buckled"
+/// trait associated to being held in a chokehold
+#define CHOKEHOLD_TRAIT "chokehold"
+/// trait associated to resting
+#define RESTING_TRAIT "resting"
+/// trait associated to a stat value or range of
+#define STAT_TRAIT "stat"
+#define STATION_TRAIT "station-trait"
+/// obtained from mapping helper
+#define MAPPING_HELPER_TRAIT "mapping-helper"
+/// Trait associated to wearing a suit
+#define SUIT_TRAIT "suit"
+/// Trait associated to lying down (having a [lying_angle] of a different value than zero).
+#define LYING_DOWN_TRAIT "lying-down"
+/// A trait gained by leaning against a wall
+#define LEANING_TRAIT "leaning"
+/// Trait associated to lacking electrical power.
+#define POWER_LACK_TRAIT "power-lack"
+/// Trait associated to lacking motor movement
+#define MOTOR_LACK_TRAIT "motor-lack"
+/// Trait associated with mafia
+#define MAFIA_TRAIT "mafia"
+/// Trait associated with ctf
+#define CTF_TRAIT "ctf"
+/// Trait associated with highlander
+#define HIGHLANDER_TRAIT "highlander"
+/// Trait given from playing pretend with baguettes
+#define SWORDPLAY_TRAIT "swordplay"
+/// Trait given by being recruited as a nuclear operative
+#define NUKE_OP_MINION_TRAIT "nuke-op-minion"
+
+/// Trait given to you by shapeshifting
+#define SHAPESHIFT_TRAIT "shapeshift_trait"
+
+// unique trait sources, still defines
+#define EMP_TRAIT "emp_trait"
+#define STATUE_MUTE "statue"
+#define CHANGELING_DRAIN "drain"
+
+#define STASIS_MUTE "stasis"
+#define GENETICS_SPELL "genetics_spell"
+#define EYES_COVERED "eyes_covered"
+#define NO_EYES "no_eyes"
+#define HYPNOCHAIR_TRAIT "hypnochair"
+#define FLASHLIGHT_EYES "flashlight_eyes"
+#define IMPURE_OCULINE "impure_oculine"
+#define HAUNTIUM_REAGENT_TRAIT "hauntium_reagent_trait"
+#define TRAIT_SANTA "santa"
+#define SCRYING_ORB "scrying-orb"
+#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
+#define JUNGLE_FEVER_TRAIT "jungle_fever"
+#define MEGAFAUNA_TRAIT "megafauna"
+#define CLOWN_NUKE_TRAIT "clown-nuke"
+#define STICKY_MOUSTACHE_TRAIT "sticky-moustache"
+#define CHAINSAW_FRENZY_TRAIT "chainsaw-frenzy"
+#define CHRONO_GUN_TRAIT "chrono-gun"
+#define REVERSE_BEAR_TRAP_TRAIT "reverse-bear-trap"
+#define CURSED_MASK_TRAIT "cursed-mask"
+#define HIS_GRACE_TRAIT "his-grace"
+#define HAND_REPLACEMENT_TRAIT "magic-hand"
+#define HOT_POTATO_TRAIT "hot-potato"
+#define SABRE_SUICIDE_TRAIT "sabre-suicide"
+#define ABDUCTOR_VEST_TRAIT "abductor-vest"
+#define CAPTURE_THE_FLAG_TRAIT "capture-the-flag"
+#define BASKETBALL_MINIGAME_TRAIT "basketball-minigame"
+#define EYE_OF_GOD_TRAIT "eye-of-god"
+#define SHAMEBRERO_TRAIT "shamebrero"
+#define CHRONOSUIT_TRAIT "chronosuit"
+#define LOCKED_HELMET_TRAIT "locked-helmet"
+#define NINJA_SUIT_TRAIT "ninja-suit"
+#define SLEEPING_CARP_TRAIT "sleeping_carp"
+#define TIMESTOP_TRAIT "timestop"
+#define LIFECANDLE_TRAIT "lifecandle"
+#define VENTCRAWLING_TRAIT "ventcrawling"
+#define SPECIES_FLIGHT_TRAIT "species-flight"
+#define FROSTMINER_ENRAGE_TRAIT "frostminer-enrage"
+#define NO_GRAVITY_TRAIT "no-gravity"
+#define NEGATIVE_GRAVITY_TRAIT "negative-gravity"
+
+/// A trait gained from a mob's leap action, like the leaper
+#define LEAPING_TRAIT "leaping"
+/// A trait gained from a mob's vanish action, like the herophant
+#define VANISHING_TRAIT "vanishing"
+/// A trait gained from a mob's swoop action, like the ash drake
+#define SWOOPING_TRAIT "swooping"
+/// A trait gained from a mob's mimic ability, like the mimic
+#define MIMIC_TRAIT "mimic"
+#define SHRUNKEN_TRAIT "shrunken"
+#define LEAPER_BUBBLE_TRAIT "leaper-bubble"
+#define DNA_VAULT_TRAIT "dna_vault"
+/// sticky nodrop sounds like a bad soundcloud rapper's name
+#define STICKY_NODROP "sticky-nodrop"
+#define SKILLCHIP_TRAIT "skillchip"
+#define SKILL_TRAIT "skill"
+#define BUSY_FLOORBOT_TRAIT "busy-floorbot"
+#define PULLED_WHILE_SOFTCRIT_TRAIT "pulled-while-softcrit"
+#define LOCKED_BORG_TRAIT "locked-borg"
+/// trait associated to not having locomotion appendages nor the ability to fly or float
+#define LACKING_LOCOMOTION_APPENDAGES_TRAIT "lacking-locomotion-appengades"
+#define CRYO_TRAIT "cryo"
+/// trait associated to not having fine manipulation appendages such as hands
+#define LACKING_MANIPULATION_APPENDAGES_TRAIT "lacking-manipulation-appengades"
+#define HANDCUFFED_TRAIT "handcuffed"
+/// Trait granted by [/obj/item/warp_whistle]
+#define WARPWHISTLE_TRAIT "warpwhistle"
+///Turf trait for when a turf is transparent
+#define TURF_Z_TRANSPARENT_TRAIT "turf_z_transparent"
+/// Trait applied by [/datum/component/soulstoned]
+#define SOULSTONE_TRAIT "soulstone"
+/// Trait applied to slimes by low temperature
+#define SLIME_COLD "slime-cold"
+/// Trait applied to mobs by being tipped over
+#define TIPPED_OVER "tipped-over"
+/// Trait applied to PAIs by being folded
+#define PAI_FOLDED "pai-folded"
+/// Trait applied to brain mobs when they lack external aid for locomotion, such as being inside a mech.
+#define BRAIN_UNAIDED "brain-unaided"
+/// Trait applied to a mob when it gets a required "operational datum" (components/elements). Sends out the source as the type of the element.
+#define TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM "element-required"
+/// Trait applied by MODsuits.
+#define MOD_TRAIT "mod"
+
+/// Trait granted by the berserker hood.
+#define BERSERK_TRAIT "berserk_trait"
+/// Trait granted by [/obj/item/rod_of_asclepius]
+#define HIPPOCRATIC_OATH_TRAIT "hippocratic_oath"
+/// Trait granted by [/datum/status_effect/blooddrunk]
+#define BLOODDRUNK_TRAIT "blooddrunk"
+/// Trait granted by lipstick
+#define LIPSTICK_TRAIT "lipstick_trait"
+/// Self-explainatory.
+#define BEAUTY_ELEMENT_TRAIT "beauty_element"
+#define MOOD_DATUM_TRAIT "mood_datum"
+#define DRONE_SHY_TRAIT "drone_shy"
+/// Trait given by stabilized light pink extracts
+#define STABILIZED_LIGHT_PINK_EXTRACT_TRAIT "stabilized_light_pink"
+/// Trait given by adamantine extracts
+#define ADAMANTINE_EXTRACT_TRAIT "adamantine_extract"
+/// Given by the multiple_lives component to the previous body of the mob upon death.
+#define EXPIRED_LIFE_TRAIT "expired_life"
+/// Trait given to an atom/movable when they orbit something.
+#define ORBITING_TRAIT "orbiting"
+/// From the item_scaling element
+#define ITEM_SCALING_TRAIT "item_scaling"
+/// Trait given by choking
+#define CHOKING_TRAIT "choking_trait"
+/// Trait given by hallucinations
+#define HALLUCINATION_TRAIT "hallucination_trait"
+/// Trait given by simple/basic mob death
+#define BASIC_MOB_DEATH_TRAIT "basic_mob_death"
+/// Trait given by your current speed
+#define SPEED_TRAIT "speed_trait"
+/// Trait given to mobs that have been autopsied
+#define AUTOPSY_TRAIT "autopsy_trait"
+/// Trait given by [/datum/status_effect/blessing_of_insanity]
+#define MAD_WIZARD_TRAIT "mad_wizard_trait"
+
+///From the market_crash event
+#define MARKET_CRASH_EVENT_TRAIT "crashed_market_event"
+
+/// Traits granted to items due to their chameleon properties.
+#define CHAMELEON_ITEM_TRAIT "chameleon_item_trait"
+
+// some trait sources dirived from bodyparts - BODYPART_TRAIT is generic.
+#define BODYPART_TRAIT "bodypart"
+#define HEAD_TRAIT "head"
+#define CHEST_TRAIT "chest"
+#define RIGHT_ARM_TRAIT "right_arm"
+#define LEFT_ARM_TRAIT "left_arm"
+#define RIGHT_LEG_TRAIT "right_leg"
+#define LEFT_LEG_TRAIT "left_leg"
+
+///coming from a fish trait datum.
+#define FISH_TRAIT_DATUM "fish_trait_datum"
+///coming from a fish evolution datum
+#define FISH_EVOLUTION "fish_evolution"
+
+/// Trait given by echolocation component.
+#define ECHOLOCATION_TRAIT "echolocation"
+
+///trait source that tongues should use
+#define SPEAKING_FROM_TONGUE "tongue"
+///trait source that sign language should use
+#define SPEAKING_FROM_HANDS "hands"
+
+/// Sources for TRAIT_IGNORING_GRAVITY
+#define IGNORING_GRAVITY_NEGATION "ignoring_gravity_negation"
+
+/// Hearing trait that is from the hearing component
+#define CIRCUIT_HEAR_TRAIT "circuit_hear"
+
+/// This trait comes from when a mob is currently typing.
+#define CURRENTLY_TYPING_TRAIT "currently_typing"
+
+/**
+* Trait granted by [/mob/living/carbon/Initialize] and
+* granted/removed by [/obj/item/organ/internal/tongue]
+* Used for ensuring that carbons without tongues cannot taste anything
+* so it is added in Initialize, and then removed when a tongue is inserted
+* and readded when a tongue is removed.
+*/
+#define NO_TONGUE_TRAIT "no_tongue_trait"
+
+/// Trait granted by [/mob/living/silicon/robot]
+/// Traits applied to a silicon mob by their model.
+#define MODEL_TRAIT "model_trait"
+
+/// Trait granted by [mob/living/silicon/ai]
+/// Applied when the ai anchors itself
+#define AI_ANCHOR_TRAIT "ai_anchor"
+
+/// Trait from [/datum/antagonist/nukeop/clownop]
+#define CLOWNOP_TRAIT "clownop"
+
+#define ANALYZER_TRAIT "analyzer_trait"
+
+/// Trait from an organ being inside a bodypart
+#define ORGAN_INSIDE_BODY_TRAIT "organ_inside_body"
+/// Trait when something was labelled by a pen.
+#define PEN_LABEL_TRAIT "pen_label"
diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm
index 549af806ee03..7b972abc20cb 100644
--- a/code/__DEFINES/turfs.dm
+++ b/code/__DEFINES/turfs.dm
@@ -6,7 +6,107 @@
#define CHANGETURF_RECALC_ADJACENT (1<<5) //Immediately recalc adjacent atmos turfs instead of queuing.
#define CHANGETURF_TRAPDOOR_INDUCED (1<<6) // Caused by a trapdoor, for trapdoor to know that this changeturf was caused by itself
+#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS)
+
+//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a
+///Returns a list of turf in a square
+#define RANGE_TURFS(RADIUS, CENTER) \
+ RECT_TURFS(RADIUS, RADIUS, CENTER)
+
+#define RECT_TURFS(H_RADIUS, V_RADIUS, CENTER) \
+ block( \
+ locate(max((CENTER).x-(H_RADIUS),1), max((CENTER).y-(V_RADIUS),1), (CENTER).z), \
+ locate(min((CENTER).x+(H_RADIUS),world.maxx), min((CENTER).y+(V_RADIUS),world.maxy), (CENTER).z) \
+ )
+
+///Returns all turfs in a zlevel
+#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL))
+
///Returns all currently loaded turfs
#define ALL_TURFS(...) block(locate(1, 1, 1), locate(world.maxx, world.maxy, world.maxz))
-#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS)
+#define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3]))
+
+/// Returns a list of turfs in the rectangle specified by BOTTOM LEFT corner and height/width, checks for being outside the world border for you
+#define CORNER_BLOCK(corner, width, height) CORNER_BLOCK_OFFSET(corner, width, height, 0, 0)
+
+/// Returns a list of turfs similar to CORNER_BLOCK but with offsets
+#define CORNER_BLOCK_OFFSET(corner, width, height, offset_x, offset_y) ((block(locate(corner.x + offset_x, corner.y + offset_y, corner.z), locate(min(corner.x + (width - 1) + offset_x, world.maxx), min(corner.y + (height - 1) + offset_y, world.maxy), corner.z))))
+
+/// Returns an outline (neighboring turfs) of the given block
+#define CORNER_OUTLINE(corner, width, height) ( \
+ CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, -1) + \
+ CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, height) + \
+ CORNER_BLOCK_OFFSET(corner, 1, height, -1, 0) + \
+ CORNER_BLOCK_OFFSET(corner, 1, height, width, 0))
+
+/// Returns a list of around us
+#define TURF_NEIGHBORS(turf) (CORNER_BLOCK_OFFSET(turf, 3, 3, -1, -1) - turf)
+
+/// The pipes, disposals, and wires are hidden
+#define UNDERFLOOR_HIDDEN 0
+/// The pipes, disposals, and wires are visible but cannot be interacted with
+#define UNDERFLOOR_VISIBLE 1
+/// The pipes, disposals, and wires are visible and can be interacted with
+#define UNDERFLOOR_INTERACTABLE 2
+
+//Wet floor type flags. Stronger ones should be higher in number.
+/// Turf is dry and mobs won't slip
+#define TURF_DRY (0)
+/// Turf has water on the floor and mobs will slip unless walking or using galoshes
+#define TURF_WET_WATER (1<<0)
+/// Turf has a thick layer of ice on the floor and mobs will slip in the direction until they bump into something
+#define TURF_WET_PERMAFROST (1<<1)
+/// Turf has a thin layer of ice on the floor and mobs will slip
+#define TURF_WET_ICE (1<<2)
+/// Turf has lube on the floor and mobs will slip
+#define TURF_WET_LUBE (1<<3)
+/// Turf has superlube on the floor and mobs will slip even if they are crawling
+#define TURF_WET_SUPERLUBE (1<<4)
+
+/// Checks if a turf is wet
+#define IS_WET_OPEN_TURF(O) O.GetComponent(/datum/component/wet_floor)
+
+/// Maximum amount of time, (in deciseconds) a tile can be wet for.
+#define MAXIMUM_WET_TIME (5 MINUTES)
+
+/**
+ * Get the turf that `A` resides in, regardless of any containers.
+ *
+ * Use in favor of `A.loc` or `src.loc` so that things work correctly when
+ * stored inside an inventory, locker, or other container.
+ */
+#define get_turf(A) (get_step(A, 0))
+
+/**
+ * Get the ultimate area of `A`, similarly to [get_turf].
+ *
+ * Use instead of `A.loc.loc`.
+ */
+#define get_area(A) (isarea(A) ? A : get_step(A, 0)?.loc)
+
+#define TEMPORARY_THERMAL_CONDUCTIVITY 1
+
+#define MAX_TEMPORARY_THERMAL_CONDUCTIVITY 1
+/// Turf will be passable if density is 0
+#define TURF_PATHING_PASS_DENSITY 0
+/// Turf will be passable depending on [CanAStarPass] return value
+#define TURF_PATHING_PASS_PROC 1
+/// Turf is never passable
+#define TURF_PATHING_PASS_NO 2
+
+/// Define the alpha for holiday/colored tile decals
+#define DECAL_ALPHA 60
+/// Generate horizontal striped color turf decals
+#define PATTERN_DEFAULT "default"
+/// Generate vertical striped color turf decals
+#define PATTERN_VERTICAL_STRIPE "vertical"
+/// Generate random color turf decals
+#define PATTERN_RANDOM "random"
+/// Generate rainbow color turf decals
+#define PATTERN_RAINBOW "rainbow"
+
+/**
+ * Finds the midpoint of two given turfs.
+ */
+#define TURF_MIDPOINT(a, b) (locate(((a.x + b.x) * 0.5), (a.y + b.y) * 0.5, (a.z + b.z) * 0.5))
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 7be173cddf12..7b3738de460c 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -65,6 +65,8 @@
#define VV_HK_CALLPROC "proc_call"
#define VV_HK_MARK "mark"
#define VV_HK_ADDCOMPONENT "addcomponent"
+#define VV_HK_REMOVECOMPONENT "removecomponent"
+#define VV_HK_MASS_REMOVECOMPONENT "massremovecomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
#define VV_HK_VIEW_REFERENCES "viewreferences"
#define VV_HK_WEAKREF_RESOLVE "weakref_resolve"
@@ -118,6 +120,7 @@
// misc
#define VV_HK_SPACEVINE_PURGE "spacevine_purge"
+#define VV_HK_SPAWN_ITEM_INSIDE "spawn_item_inside"
// /mob/living/carbon
#define VV_HK_MAKE_AI "aiify"
diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm
index 0b1b68c5d8d1..704207dfc915 100644
--- a/code/__DEFINES/wires.dm
+++ b/code/__DEFINES/wires.dm
@@ -49,3 +49,4 @@
#define WIRE_ZAP1 "High Voltage Circuit 1"
#define WIRE_ZAP2 "High Voltage Circuit 2"
#define WIRE_AGELIMIT "Age Limit"
+#define WIRE_LAUNCH "Launch"
diff --git a/code/__DEFINES/{dripstation_defines}/blackmarket.dm b/code/__DEFINES/{dripstation_defines}/blackmarket.dm
new file mode 100644
index 000000000000..c5e199546f71
--- /dev/null
+++ b/code/__DEFINES/{dripstation_defines}/blackmarket.dm
@@ -0,0 +1,9 @@
+
+// Shipping methods
+
+// The BEST way of shipping items: accurate, "undetectable"
+#define SHIPPING_METHOD_LTSRBT "LTSRBT"
+// Picks a random area to teleport the item to and gives you a minute to get there before it is sent.
+#define SHIPPING_METHOD_TELEPORT "Teleport"
+// Throws the item from somewhere at the station.
+#define SHIPPING_METHOD_LAUNCH "Launch"
\ No newline at end of file
diff --git a/code/__DEFINES/{dripstation_defines}/cargo.dm b/code/__DEFINES/{dripstation_defines}/cargo.dm
new file mode 100644
index 000000000000..b4c8b0c96256
--- /dev/null
+++ b/code/__DEFINES/{dripstation_defines}/cargo.dm
@@ -0,0 +1,2 @@
+/// Defining standart crate value for blackmarket, some events will change this shit some day
+#define CARGO_CRATE_VALUE 200
diff --git a/code/__DEFINES/{dripstation_defines}/dcs/signals/signals_transform.dm b/code/__DEFINES/{dripstation_defines}/dcs/signals/signals_transform.dm
new file mode 100644
index 000000000000..a520c050c73b
--- /dev/null
+++ b/code/__DEFINES/{dripstation_defines}/dcs/signals/signals_transform.dm
@@ -0,0 +1,10 @@
+// /datum/component/transforming signals
+
+/// From /datum/component/transforming/proc/on_attack_self(obj/item/source, mob/user): (obj/item/source, mob/user, active)
+#define COMSIG_TRANSFORMING_PRE_TRANSFORM "transforming_pre_transform"
+ /// Return COMPONENT_BLOCK_TRANSFORM to prevent the item from transforming.
+ #define COMPONENT_BLOCK_TRANSFORM (1<<0)
+/// From /datum/component/transforming/proc/do_transform(obj/item/source, mob/user): (obj/item/source, mob/user, active)
+#define COMSIG_TRANSFORMING_ON_TRANSFORM "transforming_on_transform"
+ /// Return COMPONENT_NO_DEFAULT_MESSAGE to prevent the transforming component from displaying the default transform message / sound.
+ #define COMPONENT_NO_DEFAULT_MESSAGE (1<<0)
\ No newline at end of file
diff --git a/code/__DEFINES/{dripstation_defines}/traits.dm b/code/__DEFINES/{dripstation_defines}/traits.dm
new file mode 100644
index 000000000000..d28d10afbaa4
--- /dev/null
+++ b/code/__DEFINES/{dripstation_defines}/traits.dm
@@ -0,0 +1,2 @@
+#define TRAIT_PSYCHOPATHIC "psychopathic"
+#define TRAIT_APATHETIC "apathetic"
\ No newline at end of file
diff --git a/code/__DEFINES/{yogs_defines}/atmospherics.dm b/code/__DEFINES/{yogs_defines}/atmospherics.dm
index 7e900fda3113..6f62fd706778 100644
--- a/code/__DEFINES/{yogs_defines}/atmospherics.dm
+++ b/code/__DEFINES/{yogs_defines}/atmospherics.dm
@@ -1,2 +1,4 @@
#define NITROGEN_NARCOSIS_PRESSURE_LOW 160 // Low-level Nitrogen Narcosis, laughter and tunnel vision
#define NITROGEN_NARCOSIS_PRESSURE_HIGH 480 // High-level nitrogen narcosis, with hallucinations
+
+#define JUNGLELAND_EQUIPMENT_EFFECT_PRESSURE 200
diff --git a/code/__DEFINES/{yogs_defines}/components.dm b/code/__DEFINES/{yogs_defines}/components.dm
index 90bf77194f02..9020f47ebef7 100644
--- a/code/__DEFINES/{yogs_defines}/components.dm
+++ b/code/__DEFINES/{yogs_defines}/components.dm
@@ -1,2 +1,15 @@
#define COMSIG_STORAGE_INSERTED "storage_inserted" //from base of /datum/component/storage/handle_item_insertion(): (obj/item/I, mob/M)
#define COMSIG_STORAGE_REMOVED "storage_removed" //from base of /datum/component/storage/remove_from_storage(): (atom/movable/AM, atom/new_location)
+
+
+#define COMSIG_JUNGLELAND_TAR_CURSE_PROC "jungleland_tar_curse_proc"
+#define COMSIG_REGEN_CORE_HEALED "regen_core_healed"
+
+
+#define COMSIG_MOB_CHECK_SHIELDS "check_shields" //from base of /mob/living/carbon/human/proc/check_shields():
+ //(atom/AM, var/damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0)
+
+#define COMSIG_KINETIC_CRUSHER_PROJECTILE_ON_RANGE "kinetic_crusher_projectile_on_range" // from base of /obj/projectile/destabilizer/on_range():
+ // (mob/user, /obj/item/twohanded/kinetic_crusher/hammer_synced)
+#define COMSIG_KINETIC_CRUSHER_PROJECTILE_FAILED_TO_MARK "kinetic_crusher_projectile_failed_to_mark" // from base of /obj/projectile/destabilizer/on_hit():
+ //(mob/user, /obj/item/twohanded/kinetic_crusher/hammer_synced)
diff --git a/code/__DEFINES/{yogs_defines}/is_helpers.dm b/code/__DEFINES/{yogs_defines}/is_helpers.dm
index 28755915f3c4..f405ac2380b3 100644
--- a/code/__DEFINES/{yogs_defines}/is_helpers.dm
+++ b/code/__DEFINES/{yogs_defines}/is_helpers.dm
@@ -19,3 +19,5 @@
#define is_battleroyale(M) (M.mind && M.mind.has_antag_datum(/datum/antagonist/battleroyale))
#define isspacepod(A) (istype(A, /obj/spacepod))
+
+#define ismineralturf_inclusive(A) (istype(A, /turf/closed/mineral) || istype(A,/turf/open/floor/plating/dirt/jungleland))
diff --git a/code/__DEFINES/{yogs_defines}/jungle.dm b/code/__DEFINES/{yogs_defines}/jungle.dm
new file mode 100644
index 000000000000..e02634a58c98
--- /dev/null
+++ b/code/__DEFINES/{yogs_defines}/jungle.dm
@@ -0,0 +1,39 @@
+#define ORE_TURF "ore_turf"
+#define ORE_PLASMA "plasma"
+#define ORE_IRON "iron"
+#define ORE_URANIUM "uranium"
+#define ORE_TITANIUM "titanium"
+#define ORE_BLUESPACE "bluespace"
+#define ORE_GOLD "gold"
+#define ORE_SILVER "silver"
+#define ORE_DIAMOND "diamond"
+#define ORE_DILITHIUM "dilithium"
+#define ORE_EMPTY "empty"
+
+
+#define MINETYPE_JUNGLE "jungle"
+#define MINETYPE_LAVALAND "lavaland"
+#define MINETYPE_ICEMOON "icemoon"
+GLOBAL_VAR(minetype)
+
+GLOBAL_LIST_INIT(jungle_ores, list( \
+ ORE_IRON = new /datum/ore_patch/iron(),
+ ORE_GOLD = new /datum/ore_patch/gold(),
+ ORE_SILVER = new /datum/ore_patch/silver(),
+ ORE_PLASMA = new /datum/ore_patch/plasma(),
+ ORE_DIAMOND = new /datum/ore_patch/diamond(),
+ ORE_TITANIUM = new /datum/ore_patch/titanium(),
+ ORE_URANIUM = new /datum/ore_patch/uranium(),
+ ORE_BLUESAPCE = new /datum/ore_patch/bluespace(),
+ ORE_DILITHIUM = new /datum/ore_patch/dilithium()
+))
+
+GLOBAL_LIST_INIT(quarry_ores, list( \
+ ORE_IRON = new /datum/ore_patch/iron(),
+ ORE_GOLD = new /datum/ore_patch/gold(),
+ ORE_SILVER = new /datum/ore_patch/silver(),
+ ORE_PLASMA = new /datum/ore_patch/plasma(),
+))
+
+
+GLOBAL_LIST_EMPTY(tar_pits)
diff --git a/code/__DEFINES/{yogs_defines}/layers.dm b/code/__DEFINES/{yogs_defines}/layers.dm
new file mode 100644
index 000000000000..11abdc4aeac3
--- /dev/null
+++ b/code/__DEFINES/{yogs_defines}/layers.dm
@@ -0,0 +1,3 @@
+#define TRIP_LAYER 20.6
+
+#define TRIP_PLANE 20.6
diff --git a/code/__DEFINES/{yogs_defines}/maps.dm b/code/__DEFINES/{yogs_defines}/maps.dm
new file mode 100644
index 000000000000..48477f779030
--- /dev/null
+++ b/code/__DEFINES/{yogs_defines}/maps.dm
@@ -0,0 +1,10 @@
+#define ZTRAIT_JUNGLE_RUINS "Jungle Ruins"
+
+
+#define ZTRAITS_JUNGLELAND list(\
+ ZTRAIT_MINING = TRUE, \
+ ZTRAIT_BOMBCAP_MULTIPLIER = 2.5, \
+ ZTRAIT_ACIDRAIN = TRUE, \
+ ZTRAIT_JUNGLE_RUINS = TRUE, \
+ ZTRAIT_BASETURF = /turf/open/water/toxic_pit)
+
diff --git a/code/__DEFINES/{yogs_defines}/misc.dm b/code/__DEFINES/{yogs_defines}/misc.dm
index 1c0463dc31fc..6594744e20af 100644
--- a/code/__DEFINES/{yogs_defines}/misc.dm
+++ b/code/__DEFINES/{yogs_defines}/misc.dm
@@ -1,6 +1,8 @@
//Endgame Results
#define GANG_LOSS 6
#define GANG_TAKEOVER 7
+#define YOGS_AMBIENT_OCCLUSION list("type"="drop_shadow","x"=0,"y"=2,"size"=4,"color"="#04080FAA" ) //filter(type="drop_shadow", x=0, y=-2, size=4, color="#04080FAA")
+#define YOGS_GAUSSIAN_BLUR(filter_size) list("type"="blur","size"=filter_size) //filter(type="blur", size=filter_size)
#define INFILTRATION_ALLCOMPLETE 25
#define INFILTRATION_MOSTCOMPLETE 26
#define INFILTRATION_SOMECOMPLETE 27
diff --git a/code/__DEFINES/{yogs_defines}/traits.dm b/code/__DEFINES/{yogs_defines}/traits.dm
new file mode 100644
index 000000000000..ec4900cad676
--- /dev/null
+++ b/code/__DEFINES/{yogs_defines}/traits.dm
@@ -0,0 +1,3 @@
+#define JUNGLELAND_TRAIT "jungleland" //trait that got aquired from some jungleland thing
+#define TRAIT_ENEMY_OF_THE_FOREST "enemy_of_the_forest"
+#define TRAIT_SULPH_PIT_IMMUNE "sulphuric_put_immune"
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index d5cd95b2db6b..0854d90f3e66 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -9,14 +9,56 @@
* Misc
*/
+/*
+ * ## Lazylists
+ *
+ * * What is a lazylist?
+ *
+ * True to its name a lazylist is a lazy instantiated list.
+ * It is a list that is only created when necessary (when it has elements) and is null when empty.
+ *
+ * * Why use a lazylist?
+ *
+ * Lazylists save memory - an empty list that is never used takes up more memory than just `null`.
+ *
+ * * When to use a lazylist?
+ *
+ * Lazylists are best used on hot types when making lists that are not always used.
+ *
+ * For example, if you were adding a list to all atoms that tracks the names of people who touched it,
+ * you would want to use a lazylist because most atoms will never be touched by anyone.
+ *
+ * * How do I use a lazylist?
+ *
+ * A lazylist is just a list you defined as `null` rather than `list()`.
+ * Then, you use the LAZY* macros to interact with it, which are essentially null-safe ways to interact with a list.
+ *
+ * Note that you probably should not be using these macros if your list is not a lazylist.
+ * This will obfuscate the code and make it a bit harder to read and debug.
+ *
+ * Generally speaking you shouldn't be checking if your lazylist is `null` yourself, the macros will do that for you.
+ * Remember that LAZYLEN (and by extension, length) will return 0 if the list is null.
+ */
+
+///Initialize the lazylist
#define LAZYINITLIST(L) if (!L) L = list()
+///If the provided list is empty, set it to null
#define UNSETEMPTY(L) if (L && !length(L)) L = null
+///Remove an item from the list, set the list to null if empty
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
+///Add an item to the list, if the list is null it will initialize it
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
+///Add an item to the list if not already present, if the list is null it will initialize it
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
-#define LAZYFIND(L, V) L ? L.Find(V) : 0
+///Returns the key of the submitted item in the list
+#define LAZYFIND(L, V) (L ? L.Find(V) : 0)
+///returns L[I] if L exists and I is a valid index of L, runtimes if L is not a list
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
+///Sets the item K to the value V, if the list is null it will initialize it
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
+///Sets the length of a lazylist
+#define LAZYSETLEN(L, V) if (!L) { L = list(); } L.len = V;
+///Returns the length of the list
#define LAZYLEN(L) length(L)
///Sets a list to null
#define LAZYNULL(L) L = null
@@ -39,6 +81,19 @@
///Removes the value V from the item K, if the item K is empty will remove it from the list, if the list is empty will set the list to null
#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
+///Ensures the length of a list is at least I, prefilling it with V if needed. if V is a proc call, it is repeated for each new index so that list() can just make a new list for each item.
+#define LISTASSERTLEN(L, I, V...) \
+ if (length(L) < I) { \
+ var/_OLD_LENGTH = length(L); \
+ L.len = I; \
+ /* Convert the optional argument to a if check */ \
+ for (var/_USELESS_VAR in list(V)) { \
+ for (var/_INDEX_TO_ASSIGN_TO in _OLD_LENGTH+1 to I) { \
+ L[_INDEX_TO_ASSIGN_TO] = V; \
+ } \
+ } \
+ }
+
/// Passed into BINARY_INSERT to compare keys
#define COMPARE_KEY __BIN_LIST[__BIN_MID]
/// Passed into BINARY_INSERT to compare values
@@ -389,7 +444,12 @@
/// Note: this implementation is expensive as heck for large numbers, I only use it because most of my usecase
/// Is < 10 ints
/proc/greatest_common_factor(list/values)
- var/smallest = min(arglist(values))
+ //Old implementation of this used var/smallest = min(argslist(values)), BUT this doesnt work for large lists! causing byond to spiral down into exception hell hole, THIS works!
+ var/smallest = INFINITY
+ for(var/entry in values)
+ if(entry < smallest)
+ smallest = entry
+
for(var/i in smallest to 1 step -1)
var/safe = TRUE
for(var/entry in values)
@@ -737,3 +797,85 @@
///sort any value in a list
/proc/sort_list(list/list_to_sort, cmp=/proc/cmp_text_asc)
return sortTim(list_to_sort.Copy(), cmp)
+
+///uses sort_list() but uses the var's name specifically. This should probably be using mergeAtom() instead
+/proc/sort_names(list/list_to_sort, order=1)
+ return sortTim(list_to_sort.Copy(), order >= 0 ? GLOBAL_PROC_REF(cmp_name_asc) : GLOBAL_PROC_REF(cmp_name_dsc))
+
+/// Runtimes if the passed in list is not sorted
+/proc/assert_sorted(list/list, name, cmp = GLOBAL_PROC_REF(cmp_numeric_asc))
+ var/last_value = list[1]
+
+ for (var/index in 2 to list.len)
+ var/value = list[index]
+
+ if (call(cmp)(value, last_value) < 0)
+ stack_trace("[name] is not sorted. value at [index] ([value]) is in the wrong place compared to the previous value of [last_value] (when compared to by [cmp])")
+
+ last_value = value
+
+/// Compares 2 lists, returns TRUE if they are the same
+/proc/deep_compare_list(list/list_1, list/list_2)
+ if(!islist(list_1) || !islist(list_2))
+ return FALSE
+
+ if(list_1 == list_2)
+ return TRUE
+
+ if(list_1.len != list_2.len)
+ return FALSE
+
+ for(var/i in 1 to list_1.len)
+ var/key_1 = list_1[i]
+ var/key_2 = list_2[i]
+ if (islist(key_1) && islist(key_2))
+ if(!deep_compare_list(key_1, key_2))
+ return FALSE
+ else if(key_1 != key_2)
+ return FALSE
+ if(istext(key_1) || islist(key_1) || ispath(key_1) || isdatum(key_1) || key_1 == world)
+ var/value_1 = list_1[key_1]
+ var/value_2 = list_2[key_1]
+ if (islist(value_1) && islist(value_2))
+ if(!deep_compare_list(value_1, value_2))
+ return FALSE
+ else if(value_1 != value_2)
+ return FALSE
+ return TRUE
+
+/**
+ * Picks a random element from a list based on a weighting system.
+ * For example, given the following list:
+ * A = 6, B = 3, C = 1, D = 0
+ * A would have a 60% chance of being picked,
+ * B would have a 30% chance of being picked,
+ * C would have a 10% chance of being picked,
+ * and D would have a 0% chance of being picked.
+ * You should only pass integers in.
+ */
+/proc/pick_weight(list/list_to_pick)
+ var/total = 0
+ var/item
+ for(item in list_to_pick)
+ if(!list_to_pick[item])
+ list_to_pick[item] = 0
+ total += list_to_pick[item]
+
+ total = rand(1, total)
+ for(item in list_to_pick)
+ total -= list_to_pick[item]
+ if(total <= 0 && list_to_pick[item])
+ return item
+
+ return null
+
+/// ORs two lazylists together without inserting errant nulls, returning a new list and not modifying the existing lists.
+#define LAZY_LISTS_OR(left_list, right_list)\
+ (length(left_list)\
+ ? length(right_list)\
+ ? (left_list | right_list)\
+ : left_list.Copy()\
+ : length(right_list)\
+ ? right_list.Copy()\
+ : null\
+ )
diff --git a/code/__HELPERS/_planes.dm b/code/__HELPERS/_planes.dm
new file mode 100644
index 000000000000..769c4283ea62
--- /dev/null
+++ b/code/__HELPERS/_planes.dm
@@ -0,0 +1,88 @@
+// This file contains helper macros for plane operations
+// See the planes section of Visuals.md for more detail, but essentially
+// When we render multiz, we do it by placing all atoms on lower levels on well, lower planes
+// This is done with stacks of plane masters (things we use to apply effects to planes)
+// These macros exist to facilitate working with this system, and other associated small bits
+
+/// Takes an atom to change the plane of, a new plane value, and something that can be used as a reference to a z level as input
+/// Modifies the new value to match the plane we actually want. Note, if you pass in an already offset plane the offsets will add up
+/// Use PLANE_TO_TRUE() to avoid this
+#define SET_PLANE(thing, new_value, z_reference) (thing.plane = MUTATE_PLANE(new_value, z_reference))
+
+/// Takes a plane and a z reference, and offsets the plane by the mutation
+/// The SSmapping.max_plane_offset bit here is technically redundant, but saves a bit of work in the base case
+/// And the base case is important to me. Non multiz shouldn't get hit too bad by this code
+#define MUTATE_PLANE(new_value, z_reference) ((SSmapping.max_plane_offset) ? GET_NEW_PLANE(new_value, GET_TURF_PLANE_OFFSET(z_reference)) : (new_value))
+
+/// Takes a z reference that we are unsure of, sanity checks it
+/// Returns either its offset, or 0 if it's not a valid ref
+/// Will return the reference's PLANE'S offset if we can't get anything out of the z level. We do our best
+#define GET_TURF_PLANE_OFFSET(z_reference) ((SSmapping.max_plane_offset && isatom(z_reference)) ? (z_reference.z ? GET_Z_PLANE_OFFSET(z_reference.z) : PLANE_TO_OFFSET(z_reference.plane)) : 0)
+/// Essentially just an unsafe version of GET_TURF_PLANE_OFFSET()
+/// Takes a z value we returns its offset with a list lookup
+/// Will runtime during parts of init. Be careful :)
+#define GET_Z_PLANE_OFFSET(z) (SSmapping.z_level_to_plane_offset[z])
+
+/// Takes a plane to offset, and the multiplier to use, and well, does the offsetting
+/// Respects a blacklist we use to remove redundant plane masters, such as hud objects
+#define GET_NEW_PLANE(new_value, multiplier) (SSmapping.plane_offset_blacklist?["[new_value]"] ? new_value : (new_value) - (PLANE_RANGE * (multiplier)))
+
+// Now for the more niche things
+
+/// Takes an object, new plane, and multipler, and offsets the plane
+/// This is for cases where you have a multipler precalculated, and just want to use it
+/// Often an optimization, sometimes a necessity
+#define SET_PLANE_W_SCALAR(thing, new_value, multiplier) (thing.plane = GET_NEW_PLANE(new_value, multiplier))
+
+
+/// Implicit plane set. We take the turf from the object we're changing the plane of, and use ITS z as a spokesperson for our plane value
+#define SET_PLANE_IMPLICIT(thing, new_value) SET_PLANE_EXPLICIT(thing, new_value, thing)
+
+// This is an unrolled and optimized version of SET_PLANE, for use anywhere where you are unsure of a source's "turfness"
+// We do also try and guess at what the thing's z level is, even if it's not a z
+// The plane is cached to allow for fancy stuff to be eval'd once, rather then often
+#define SET_PLANE_EXPLICIT(thing, new_value, source) \
+ do {\
+ if(SSmapping.max_plane_offset) {\
+ var/_cached_plane = new_value;\
+ var/turf/_our_turf = get_turf(source);\
+ if(_our_turf){\
+ thing.plane = GET_NEW_PLANE(_cached_plane, GET_Z_PLANE_OFFSET(_our_turf.z));\
+ }\
+ else if(source) {\
+ thing.plane = GET_NEW_PLANE(_cached_plane, PLANE_TO_OFFSET(source.plane));\
+ }\
+ else {\
+ thing.plane = _cached_plane;\
+ }\
+ }\
+ else {\
+ thing.plane = new_value;\
+ }\
+ }\
+ while (FALSE)
+
+// Now for macros that exist to get info from SSmapping
+// Mostly about details of planes, or z levels
+
+/// Takes a z level, gets the lowest plane offset in its "stack"
+#define GET_LOWEST_STACK_OFFSET(z) ((SSmapping.max_plane_offset) ? SSmapping.z_level_to_lowest_plane_offset[z] : 0)
+/// Takes a plane, returns the canonical, unoffset plane it represents
+#define PLANE_TO_TRUE(plane) ((SSmapping.plane_offset_to_true) ? SSmapping.plane_offset_to_true["[plane]"] : plane)
+/// Takes a plane, returns the offset it uses
+#define PLANE_TO_OFFSET(plane) ((SSmapping.plane_to_offset) ? SSmapping.plane_to_offset["[plane]"] : plane)
+/// Takes a plane, returns TRUE if it is of critical priority, FALSE otherwise
+#define PLANE_IS_CRITICAL(plane) ((SSmapping.plane_to_offset) ? !!SSmapping.critical_planes["[plane]"] : FALSE)
+/// Takes a true plane, returns the offset planes that would canonically represent it
+#define TRUE_PLANE_TO_OFFSETS(plane) ((SSmapping.true_to_offset_planes) ? SSmapping.true_to_offset_planes["[plane]"] : list(plane))
+/// Takes a render target and an offset, returns a canonical render target string for it
+#define OFFSET_RENDER_TARGET(render_target, offset) (_OFFSET_RENDER_TARGET(render_target, SSmapping.render_offset_blacklist?["[render_target]"] ? 0 : offset))
+/// Helper macro for the above
+/// Honestly just exists to make the pattern of render target strings more readable
+#define _OFFSET_RENDER_TARGET(render_target, offset) ("[(render_target)] #[(offset)]")
+
+// Known issues:
+// Potentially too much client load? Hard to tell due to not having a potato pc to hand.
+// This is solvable with lowspec preferences, which would not be hard to implement
+// Player popups will now render their effects, like overlay lights. this is fixable, but I've not gotten to it
+// I think overlay lights can render on the wrong z layer. s fucked
diff --git a/code/__HELPERS/_string_lists.dm b/code/__HELPERS/_string_lists.dm
index 3b8acba15d16..5ad2a6493cd9 100644
--- a/code/__HELPERS/_string_lists.dm
+++ b/code/__HELPERS/_string_lists.dm
@@ -13,7 +13,7 @@ GLOBAL_VAR(string_filename_current_key)
if((filename in GLOB.string_cache) && (key in GLOB.string_cache[filename]))
var/response = pick(GLOB.string_cache[filename][key])
var/regex/r = regex("@pick\\((\\D+?)\\)", "g")
- response = r.Replace(response, /proc/strings_subkey_lookup)
+ response = r.Replace(response, GLOBAL_PROC_REF(strings_subkey_lookup))
return response
else
CRASH("strings list not found: [directory]/[filename], index=[key]")
@@ -39,4 +39,4 @@ GLOBAL_VAR(string_filename_current_key)
if(fexists("[directory]/[filename]"))
GLOB.string_cache[filename] = json_load("[directory]/[filename]")
else
- CRASH("file not found: [directory]/[filename]")
\ No newline at end of file
+ CRASH("file not found: [directory]/[filename]")
diff --git a/code/__HELPERS/animations.dm b/code/__HELPERS/animations.dm
index cae8d3a8f52b..e80bfe319c0c 100644
--- a/code/__HELPERS/animations.dm
+++ b/code/__HELPERS/animations.dm
@@ -65,3 +65,53 @@
animate(transform = transforms[2], time = 0.1)
animate(transform = transforms[3], time = 0.2)
animate(transform = transforms[4], time = 0.3)
+
+
+/**
+ * Proc called when you want the atom to spin around the center of its icon (or where it would be if its transform var is translated)
+ * By default, it makes the atom spin forever and ever at a speed of 60 rpm.
+ *
+ * Arguments:
+ * * speed: how much it takes for the atom to complete one 360° rotation
+ * * loops: how many times do we want the atom to rotate
+ * * clockwise: whether the atom ought to spin clockwise or counter-clockwise
+ * * segments: in how many animate calls the rotation is split. Probably unnecessary, but you shouldn't set it lower than 3 anyway.
+ * * parallel: whether the animation calls have the ANIMATION_PARALLEL flag, necessary for it to run alongside concurrent animations.
+ */
+/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = TRUE, segments = 3, parallel = TRUE)
+ if(!segments)
+ return
+ var/segment = 360/segments
+ if(!clockwise)
+ segment = -segment
+ SEND_SIGNAL(src, COMSIG_ATOM_SPIN_ANIMATION, speed, loops, segments, segment)
+ do_spin_animation(speed, loops, segments, segment, parallel)
+
+/atom/proc/DabAnimation(speed = 1, loops = 1, direction = 1 , hold_seconds = 0 , angle = 1 , stay = FALSE) // Hopek 2019
+ // By making this in atom/proc everything in the game can potentially dab. You have been warned.
+ if(hold_seconds > 9999) // if you need to hold a dab for more than 2 hours intentionally let me know.
+ return
+ if(hold_seconds > 0)
+ hold_seconds = hold_seconds * 10 // Converts seconds to deciseconds
+ if(angle == 1) //if angle is 1: random angle. Else take angle
+ angle = rand(25,50)
+ if(direction == 1) // direciton:: 1 for random pick, 2 for clockwise , 3 for anti-clockwise
+ direction = pick(2,3)
+ if(direction == 3) // if 3 then counter clockwise
+ angle = angle * -1
+ if(speed == 1) // if speed is 1 choose random speed from list
+ speed = rand(3,5)
+
+ // dab matrix here
+ var/matrix/DAB_COMMENCE = matrix(transform)
+ var/matrix/DAB_RETURN = matrix(transform)
+ DAB_COMMENCE.Turn(angle) // dab angle to matrix
+
+ // Dab animation
+ animate(src, transform = DAB_COMMENCE, time = speed, loops ) // dab to hold angle
+ if(hold_seconds > 0)
+ sleep(hold_seconds) // time to hold the dab before going back
+ if(!stay) // if stay param is true dab doesn't return
+ animate(transform = DAB_RETURN, time = speed * 1.5, loops ) // reverse dab to starting position , slower
+ //doesn't have an object argument because this is "Stacking" with the animate call above
+ //3 billion% intentional
diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm
index 06537f8e7123..07996b978117 100644
--- a/code/__HELPERS/areas.dm
+++ b/code/__HELPERS/areas.dm
@@ -1,25 +1,23 @@
#define BP_MAX_ROOM_SIZE 300
-//Yogs start -- Fixes a very esoteric, mostly-hypothetical bug involving the fact that the /TG/ version doesn't use list() here
-GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(/area/engine/engineering,
- /area/engine/supermatter,
- /area/engine/atmos/engine,
- /area/ai_monitored/turret_protected/ai)))
-//Yogs end
+GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(/area/engine/engineering, \
+ /area/engine/supermatter, \
+ /area/engine/atmos/engine, \
+ /area/ai_monitored/turret_protected/ai))
// Gets an atmos isolated contained space
// Returns an associative list of turf|dirs pairs
// The dirs are connected turfs in the same space
// break_if_found is a typecache of turf/area types to return false if found
// Please keep this proc type agnostic. If you need to restrict it do it elsewhere or add an arg.
-/proc/detect_room(turf/origin, list/break_if_found, max_size=INFINITY)
+/proc/detect_room(turf/origin, list/break_if_found = list(), max_size=INFINITY)
if(origin.blocks_air)
return list(origin)
. = list()
var/list/checked_turfs = list()
var/list/found_turfs = list(origin)
- while(found_turfs.len)
+ while(length(found_turfs))
var/turf/sourceT = found_turfs[1]
found_turfs.Cut(1, 2)
var/dir_flags = checked_turfs[sourceT]
@@ -32,82 +30,128 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(/area/engin
if(!checkT)
continue
checked_turfs[sourceT] |= dir
- checked_turfs[checkT] |= turn(dir, 180)
+ checked_turfs[checkT] |= REVERSE_DIR(dir)
.[sourceT] |= dir
- .[checkT] |= turn(dir, 180)
+ .[checkT] |= REVERSE_DIR(dir)
if(break_if_found[checkT.type] || break_if_found[checkT.loc.type])
return FALSE
var/static/list/cardinal_cache = list("[NORTH]"=TRUE, "[EAST]"=TRUE, "[SOUTH]"=TRUE, "[WEST]"=TRUE)
- if(!cardinal_cache["[dir]"] || checkT.blocks_air || !CANATMOSPASS(sourceT, checkT))
+ if(!cardinal_cache["[dir]"] || checkT.blocks_air || !TURFS_CAN_SHARE(sourceT, checkT))
continue
found_turfs += checkT // Since checkT is connected, add it to the list to be processed
-/proc/create_area(mob/creator)
+/proc/create_area(mob/creator, new_area_type = /area)
// Passed into the above proc as list/break_if_found
- var/static/area_or_turf_fail_types = typecacheof(list(
+ var/static/list/area_or_turf_fail_types = typecacheof(list(
/turf/open/space,
/area/shuttle,
))
// Ignore these areas and dont let people expand them. They can expand into them though
- var/static/blacklisted_areas = typecacheof(list(
+ var/static/list/blacklisted_areas = typecacheof(list(
/area/space,
))
+
+ var/error = ""
var/list/turfs = detect_room(get_turf(creator), area_or_turf_fail_types, BP_MAX_ROOM_SIZE*2)
- if(!turfs)
- to_chat(creator, span_warning("The new area must be completely airtight and not a part of a shuttle."))
- return
- if(turfs.len > BP_MAX_ROOM_SIZE)
- to_chat(creator, span_warning("The room you're in is too big. It is [turfs.len >= BP_MAX_ROOM_SIZE *2 ? "more than 100" : ((turfs.len / BP_MAX_ROOM_SIZE)-1)*100]% larger than allowed."))
+ var/turf_count = length(turfs)
+ if(!turf_count)
+ error = "The new area must be completely airtight and not a part of a shuttle."
+ else if(turf_count > BP_MAX_ROOM_SIZE)
+ error = "The room you're in is too big. It is [turf_count >= BP_MAX_ROOM_SIZE *2 ? "more than 100" : ((turf_count / BP_MAX_ROOM_SIZE)-1)*100]% larger than allowed."
+ if(error)
+ to_chat(creator, span_warning(error))
return
- var/list/areas = list("New Area" = /area)
- for(var/i in 1 to turfs.len)
- var/area/place = get_area(turfs[i])
+
+ var/list/apc_map = list()
+ var/list/areas = list("New Area" = new_area_type)
+ for(var/i in 1 to turf_count)
+ var/turf/the_turf = turfs[i]
+ var/area/place = get_area(the_turf)
if(blacklisted_areas[place.type])
continue
if(!place.requires_power || place.noteleport || place.hidden)
continue // No expanding powerless rooms etc
+ if(!TURF_SHARES(the_turf)) // No expanding areas of walls/something blocking this turf because that defeats the whole point of them used to separate areas
+ continue
+ if(length(apc_map) > 1) // When merging 2 or more areas make sure we arent merging their apc into 1 area
+ to_chat(creator, span_warning("Multiple APC's detected in the vicinity. only 1 is allowed."))
+ return
areas[place.name] = place
- var/area_choice = input(creator, "Choose an area to expand or make a new area.", "Area Expansion") as null|anything in areas
- area_choice = areas[area_choice]
- if(!area_choice)
+ var/area_choice = tgui_input_list(creator, "Choose an area to expand or make a new area", "Area Expansion", areas)
+ if(isnull(area_choice))
to_chat(creator, span_warning("No choice selected. The area remains undefined."))
return
+ area_choice = areas[area_choice]
+
var/area/newA
var/area/oldA = get_area(get_turf(creator))
if(!isarea(area_choice))
- var/str = stripped_input(creator,"New area name:", "Blueprint Editing", "", MAX_NAME_LEN)
- if(!str || !length(str)) //cancel
- return
- if(length(str) > 50)
- to_chat(creator, span_warning("The given name is too long. The area remains undefined."))
+ var/str = tgui_input_text(creator, "New area name", "Blueprint Editing", max_length = MAX_NAME_LEN)
+ if(!str)
return
newA = new area_choice
newA.setup(str)
- newA.set_dynamic_lighting()
newA.has_gravity = oldA.has_gravity
+ require_area_resort() //new area registered. resort the names
else
newA = area_choice
- for(var/i in 1 to turfs.len)
- var/turf/thing = turfs[i]
- var/area/old_area = thing.loc
- old_area.turfs_to_uncontain += thing
- newA.contents += thing
- newA.contained_turfs += thing
- thing.change_area(old_area, newA)
+ //we haven't done anything. let's get outta here
+ if(newA == oldA)
+ to_chat(creator, span_warning("Selected choice is same as the area your standing in. No area changes were requested."))
+ return
+
+ /**
+ * A list of all machinery tied to an area along with the area itself. key=area name,value=list(area,list of machinery)
+ * we use this to keep track of what areas are affected by the blueprints & what machinery of these areas needs to be reconfigured accordingly
+ */
+ var/area/affected_areas = list()
+ for(var/turf/the_turf as anything in turfs)
+ var/area/old_area = the_turf.loc
+
+ //keep rack of all areas affected by turf changes
+ affected_areas[old_area.name] = old_area
+
+ //move the turf to its new area and unregister it from the old one
+ the_turf.change_area(old_area, newA)
+
+ //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, old_area)
+
+ SEND_SIGNAL(stuff, COMSIG_ENTER_AREA, newA)
newA.reg_in_areas_in_z()
- newA.add_delta_areas()
- var/list/firedoors = oldA.firedoors
- for(var/door in firedoors)
- var/obj/machinery/door/firedoor/FD = door
- FD.CalculateAffectingAreas()
+ if(!isarea(area_choice) && newA.static_lighting)
+ newA.create_area_lighting_objects()
+ //convert map to list
+ var/list/area/area_list = list()
+ for(var/area_name in affected_areas)
+ area_list += affected_areas[area_name]
+ SEND_GLOBAL_SIGNAL(COMSIG_AREA_CREATED, newA, area_list, creator)
to_chat(creator, span_notice("You have created a new area, named [newA.name]. It is now weather proof, and constructing an APC will allow it to be powered."))
+ creator.log_message("created a new area: [AREACOORD(creator)] (previously \"[oldA.name]\")", LOG_GAME)
+
+ //purge old areas that had all their turfs merged into the new one i.e. old empty areas. also recompute fire doors
+ for(var/i in 1 to length(area_list))
+ var/area/merged_area = area_list[i]
+
+ //recompute fire doors affecting areas
+ for(var/obj/machinery/door/firedoor/FD as anything in merged_area.firedoors)
+ FD.CalculateAffectingAreas()
+
+ //no more turfs in this area. Time to clean up
+ if(!merged_area.has_contained_turfs())
+ qdel(merged_area)
+
return TRUE
+#undef BP_MAX_ROOM_SIZE
+
/proc/require_area_resort()
GLOB.sortedAreas = null
@@ -204,5 +248,3 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(/area/engin
mobs_in_area += mob
break
return mobs_in_area
-
-#undef BP_MAX_ROOM_SIZE
diff --git a/code/__HELPERS/bitflag_lists.dm b/code/__HELPERS/bitflag_lists.dm
new file mode 100644
index 000000000000..48e35bd6b913
--- /dev/null
+++ b/code/__HELPERS/bitflag_lists.dm
@@ -0,0 +1,28 @@
+GLOBAL_LIST_EMPTY(bitflag_lists)
+
+/**
+ * System for storing bitflags past the 24 limit, making use of an associative list.
+ *
+ * Macro converts a list of integers into an associative list of bitflag entries for quicker comparison.
+ * Example: list(0, 4, 26, 32)) => list( "0" = ( (1<<0) | (1<<4) ), "1" = ( (1<<2) | (1<<8) ) )
+ * Lists are cached into a global list of lists to avoid identical duplicates.
+ * This system makes value comparisons faster than pairing every element of one list with every element of the other for evaluation.
+ *
+ * Arguments:
+ * * target - List of integers.
+ */
+#define SET_SMOOTHING_GROUPS(target) \
+ do { \
+ var/txt_signature = target; \
+ if(isnull((target = GLOB.bitflag_lists[txt_signature]))) { \
+ var/list/new_bitflag_list = list(); \
+ var/list/decoded = UNWRAP_SMOOTHING_GROUPS(txt_signature, decoded); \
+ for(var/value in decoded) { \
+ if (value < 0) { \
+ value = MAX_S_TURF + 1 + abs(value); \
+ } \
+ new_bitflag_list["[round(value / 24)]"] |= (1 << (value % 24)); \
+ }; \
+ target = GLOB.bitflag_lists[txt_signature] = new_bitflag_list; \
+ }; \
+ } while (FALSE)
diff --git a/code/__HELPERS/cameras.dm b/code/__HELPERS/cameras.dm
new file mode 100644
index 000000000000..9d74f3fe71b4
--- /dev/null
+++ b/code/__HELPERS/cameras.dm
@@ -0,0 +1,35 @@
+/**
+ * get_camera_list
+ *
+ * Builds a list of all available cameras that can be seen to networks_available
+ * Args:
+ * networks_available - List of networks that we use to see which cameras are visible to it.
+ */
+/proc/get_camera_list(list/networks_available)
+ var/list/all_camera_list = list()
+ for(var/obj/machinery/camera/camera as anything in GLOB.cameranet.cameras)
+ all_camera_list.Add(camera)
+
+ camera_sort(all_camera_list)
+
+ var/list/usable_camera_list = list()
+
+ for(var/obj/machinery/camera/camera as anything in all_camera_list)
+ var/list/tempnetwork = camera.network & networks_available
+ if(length(tempnetwork))
+ usable_camera_list["[camera.c_tag][camera.can_use() ? null : " (Deactivated)"]"] = camera
+
+ return usable_camera_list
+
+///Sorts the list of cameras by their c_tag to display to players.
+/proc/camera_sort(list/camera_list)
+ var/obj/machinery/camera/camera_comparing_a
+ var/obj/machinery/camera/camera_comparing_b
+
+ for(var/i = length(camera_list), i > 0, i--)
+ for(var/j = 1 to i - 1)
+ camera_comparing_a = camera_list[j]
+ camera_comparing_b = camera_list[j + 1]
+ if(sorttext(camera_comparing_a.c_tag, camera_comparing_b.c_tag) < 0)
+ camera_list.Swap(j, j + 1)
+ return camera_list
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 73118506fcf1..c1f867722004 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -10,6 +10,13 @@
/proc/cmp_text_dsc(a,b)
return sorttext(a,b)
+/proc/cmp_embed_text_asc(a,b)
+ if(isdatum(a))
+ a = REF(a)
+ if(isdatum(b))
+ b = REF(b)
+ return sorttext("[b]", "[a]")
+
/proc/cmp_name_asc(atom/a, atom/b)
return sorttext(b.name, a.name)
@@ -150,3 +157,6 @@ GLOBAL_VAR_INIT(cmp_field, "name")
rhs = ispath(B, /datum/reagent) ? 0 : 1
return lhs - rhs
+
+/proc/cmp_filter_data_priority(list/A, list/B)
+ return A["priority"] - B["priority"]
diff --git a/code/__HELPERS/colors.dm b/code/__HELPERS/colors.dm
index 4a15f36ef6ff..6c6f8064d9a2 100644
--- a/code/__HELPERS/colors.dm
+++ b/code/__HELPERS/colors.dm
@@ -16,3 +16,38 @@
final_color += copytext(color, digit, digit + 1)
return final_color
+
+/// Blends together two colors (passed as 3 or 4 length lists) using the screen blend mode
+/// Much like multiply, screen effects the brightness of the resulting color
+/// Screen blend will always lighten the resulting color, since before multiplication we invert the colors
+/// This makes our resulting output brighter instead of darker
+/proc/blend_screen_color(list/first_color, list/second_color)
+ var/list/output = new /list(4)
+
+ // max out any non existant alphas
+ if(length(first_color) < 4)
+ first_color[4] = 255
+ if(length(second_color) < 4)
+ second_color[4] = 255
+
+ // time to do our blending
+ for(var/i in 1 to 4)
+ output[i] = (1 - (1 - first_color[i] / 255) * (1 - second_color[i] / 255)) * 255
+ return output
+
+/// Used to blend together two different color cutoffs
+/// Uses the screen blendmode under the hood, essentially just [/proc/blend_screen_color]
+/// But paired down and modified to work for our color range
+/// Accepts the color cutoffs as two 3 length list(0-100,...) arguments
+/proc/blend_cutoff_colors(list/first_color, list/second_color)
+ // These runtimes usually mean that either the eye or the glasses have an incorrect color_cutoffs
+ ASSERT(first_color?.len == 3, "First color must be a 3 length list, received [json_encode(first_color)]")
+ ASSERT(second_color?.len == 3, "Second color must be a 3 length list, received [json_encode(second_color)]")
+
+ var/list/output = new /list(3)
+
+ // Invert the colors, multiply to "darken" (actually lights), then uninvert to get back to what we want
+ for(var/i in 1 to 3)
+ output[i] = (1 - (1 - first_color[i] / 100) * (1 - second_color[i] / 100)) * 100
+
+ return output
diff --git a/code/__HELPERS/duplicating.dm b/code/__HELPERS/duplicating.dm
index 45d065127fe8..a21399e68273 100644
--- a/code/__HELPERS/duplicating.dm
+++ b/code/__HELPERS/duplicating.dm
@@ -1,19 +1,21 @@
///List of all vars that will not be copied over when using duplicate_object()
GLOBAL_LIST_INIT(duplicate_forbidden_vars, list(
+ "_active_timers",
+ "_datum_components",
+ "_listen_lookup",
+ "_status_traits",
+ "_signal_procs",
"actions",
"active_hud_list",
- "active_timers",
"AIStatus",
"appearance",
"area",
"atmos_adjacent_turfs",
"bodyparts",
"ckey",
- "comp_lookup",
"computer_id",
"contents",
"cooldowns",
- "datum_components",
"external_organs",
"external_organs_slot",
"group",
@@ -38,8 +40,6 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars, list(
"power_supply",
"quirks",
"reagents",
- "signal_procs",
- "status_traits",
"stat",
"tag",
"tgui_shared_states",
diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm
index 5bc143695e2b..cd44409ddb23 100644
--- a/code/__HELPERS/filters.dm
+++ b/code/__HELPERS/filters.dm
@@ -1,3 +1,146 @@
+#define ICON_NOT_SET "Not Set"
+
+//This is stored as a nested list instead of datums or whatever because it json encodes nicely for usage in tgui
+GLOBAL_LIST_INIT(master_filter_info, list(
+ "alpha" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "icon" = ICON_NOT_SET,
+ "render_source" = "",
+ "flags" = 0
+ ),
+ "flags" = list(
+ "MASK_INVERSE" = MASK_INVERSE,
+ "MASK_SWAP" = MASK_SWAP
+ )
+ ),
+ "angular_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1
+ )
+ ),
+ // Not implemented, but if this isn't uncommented some windows will just error
+ // Needs either a proper matrix editor, or just a hook to our existing one
+ // Issue is filterrific assumes variables will have the same value type if they share the same name, which this violates
+ // Gotta refactor this sometime
+ "color" = list(
+ "defaults" = list(
+ "color" = matrix(),
+ "space" = FILTER_COLOR_RGB
+ )
+ ),
+ "displace" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = null,
+ "icon" = ICON_NOT_SET,
+ "render_source" = ""
+ )
+ ),
+ "drop_shadow" = list(
+ "defaults" = list(
+ "x" = 1,
+ "y" = -1,
+ "size" = 1,
+ "offset" = 0,
+ "color" = COLOR_HALF_TRANSPARENT_BLACK
+ )
+ ),
+ "blur" = list(
+ "defaults" = list(
+ "size" = 1
+ )
+ ),
+ "layer" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "icon" = ICON_NOT_SET,
+ "render_source" = "",
+ "flags" = FILTER_OVERLAY,
+ "color" = "",
+ "transform" = null,
+ "blend_mode" = BLEND_DEFAULT
+ )
+ ),
+ "motion_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0
+ )
+ ),
+ "outline" = list(
+ "defaults" = list(
+ "size" = 0,
+ "color" = COLOR_BLACK,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "OUTLINE_SHARP" = OUTLINE_SHARP,
+ "OUTLINE_SQUARE" = OUTLINE_SQUARE
+ )
+ ),
+ "radial_blur" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 0.01
+ )
+ ),
+ "rays" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 16,
+ "color" = COLOR_WHITE,
+ "offset" = 0,
+ "density" = 10,
+ "threshold" = 0.5,
+ "factor" = 0,
+ "flags" = FILTER_OVERLAY | FILTER_UNDERLAY
+ ),
+ "flags" = list(
+ "FILTER_OVERLAY" = FILTER_OVERLAY,
+ "FILTER_UNDERLAY" = FILTER_UNDERLAY
+ )
+ ),
+ "ripple" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1,
+ "repeat" = 2,
+ "radius" = 0,
+ "falloff" = 1,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "WAVE_BOUNDED" = WAVE_BOUNDED
+ )
+ ),
+ "wave" = list(
+ "defaults" = list(
+ "x" = 0,
+ "y" = 0,
+ "size" = 1,
+ "offset" = 0,
+ "flags" = NONE
+ ),
+ "flags" = list(
+ "WAVE_SIDEWAYS" = WAVE_SIDEWAYS,
+ "WAVE_BOUNDED" = WAVE_BOUNDED
+ )
+ )
+))
+
+#undef ICON_NOT_SET
+
+//Helpers to generate lists for filter helpers
+//This is the only practical way of writing these that actually produces sane lists
/proc/alpha_mask_filter(x, y, icon/icon, render_source, flags)
. = list("type" = "alpha")
if(!isnull(x))
@@ -11,6 +154,78 @@
if(!isnull(flags))
.["flags"] = flags
+/proc/angular_blur_filter(x, y, size)
+ . = list("type" = "angular_blur")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/color_matrix_filter(matrix/in_matrix, space)
+ . = list("type" = "color")
+ .["color"] = in_matrix
+ if(!isnull(space))
+ .["space"] = space
+
+/proc/displacement_map_filter(icon, render_source, x, y, size = 32)
+ . = list("type" = "displace")
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/drop_shadow_filter(x, y, size, offset, color)
+ . = list("type" = "drop_shadow")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(color))
+ .["color"] = color
+
+/proc/gauss_blur_filter(size)
+ . = list("type" = "blur")
+ if(!isnull(size))
+ .["size"] = size
+
+/proc/layering_filter(icon, render_source, x, y, flags, color, transform, blend_mode)
+ . = list("type" = "layer")
+ if(!isnull(icon))
+ .["icon"] = icon
+ if(!isnull(render_source))
+ .["render_source"] = render_source
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(flags))
+ .["flags"] = flags
+ if(!isnull(transform))
+ .["transform"] = transform
+ if(!isnull(blend_mode))
+ .["blend_mode"] = blend_mode
+
+/proc/motion_blur_filter(x, y)
+ . = list("type" = "motion_blur")
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
/proc/outline_filter(size, color, flags)
. = list("type" = "outline")
if(!isnull(size))
@@ -19,3 +234,87 @@
.["color"] = color
if(!isnull(flags))
.["flags"] = flags
+
+/proc/radial_blur_filter(size, x, y)
+ . = list("type" = "radial_blur")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/rays_filter(size, color, offset, density, threshold, factor, x, y, flags)
+ . = list("type" = "rays")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(color))
+ .["color"] = color
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(density))
+ .["density"] = density
+ if(!isnull(threshold))
+ .["threshold"] = threshold
+ if(!isnull(factor))
+ .["factor"] = factor
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/ripple_filter(radius, size, falloff, repeat, x, y, flags)
+ . = list("type" = "ripple")
+ if(!isnull(radius))
+ .["radius"] = radius
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(falloff))
+ .["falloff"] = falloff
+ if(!isnull(repeat))
+ .["repeat"] = repeat
+ if(!isnull(flags))
+ .["flags"] = flags
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+
+/proc/wave_filter(x, y, size, offset, flags)
+ . = list("type" = "wave")
+ if(!isnull(size))
+ .["size"] = size
+ if(!isnull(x))
+ .["x"] = x
+ if(!isnull(y))
+ .["y"] = y
+ if(!isnull(offset))
+ .["offset"] = offset
+ if(!isnull(flags))
+ .["flags"] = flags
+
+/proc/apply_wibbly_filters(atom/in_atom, length)
+ for(var/i in 1 to 7)
+ //This is a very baffling and strange way of doing this but I am just preserving old functionality
+ var/X
+ var/Y
+ var/rsq
+ do
+ X = 60*rand() - 30
+ Y = 60*rand() - 30
+ rsq = X*X + Y*Y
+ while(rsq<100 || rsq>900) // Yeah let's just loop infinitely due to bad luck what's the worst that could happen?
+ var/random_roll = rand()
+ in_atom.add_filter("wibbly-[i]", 5, wave_filter(x = X, y = Y, size = rand() * 2.5 + 0.5, offset = random_roll))
+ var/filter = in_atom.get_filter("wibbly-[i]")
+ animate(filter, offset = random_roll, time = 0, loop = -1, flags = ANIMATION_PARALLEL)
+ animate(offset = random_roll - 1, time = rand() * 20 + 10)
+
+/proc/remove_wibbly_filters(atom/in_atom)
+ var/filter
+ for(var/i in 1 to 7)
+ filter = in_atom.get_filter("wibbly-[i]")
+ animate(filter)
+ in_atom.remove_filter("wibbly-[i]")
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 67751fd75f4b..b33bfa950244 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -1,26 +1,5 @@
-//supposedly the fastest way to do this according to https://gist.github.com/Giacom/be635398926bb463b42a
-#define RANGE_TURFS(RADIUS, CENTER) \
- block( \
- locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \
- locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \
- )
-
-#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL))
#define CULT_POLL_WAIT 2400
-/// Returns a list of turfs similar to CORNER_BLOCK but with offsets
-#define CORNER_BLOCK_OFFSET(corner, width, height, offset_x, offset_y) ( \
- (block(locate(corner.x + offset_x, corner.y + offset_y, corner.z), \
- locate(min(corner.x + (width - 1) + offset_x, world.maxx), \
- min(corner.y + (height - 1) + offset_y, world.maxy), corner.z))))
-
-/// Returns an outline (neighboring turfs) of the given block
-#define CORNER_OUTLINE(corner, width, height) ( \
- CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, -1) + \
- CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, height) + \
- CORNER_BLOCK_OFFSET(corner, 1, height, -1, 0) + \
- CORNER_BLOCK_OFFSET(corner, 1, height, width, 0))
-
/proc/get_area_name(atom/X, format_text = FALSE, is_sensor = FALSE)
var/area/A = isarea(X) ? X : get_area(X)
if(!A)
@@ -51,17 +30,30 @@
get_area(get_ranged_target_turf(center, WEST, 1)))
listclearnulls(.)
+///Returns the open turf next to the center in a specific direction
/proc/get_open_turf_in_dir(atom/center, dir)
- var/turf/open/T = get_ranged_target_turf(center, dir, 1)
- if(istype(T))
- return T
+ var/turf/open/get_turf = get_step(center, dir)
+ if(istype(get_turf))
+ return get_turf
+///Returns a list with all the adjacent open turfs. Clears the list of nulls in the end.
/proc/get_adjacent_open_turfs(atom/center)
- . = list(get_open_turf_in_dir(center, NORTH),
- get_open_turf_in_dir(center, SOUTH),
- get_open_turf_in_dir(center, EAST),
- get_open_turf_in_dir(center, WEST))
- listclearnulls(.)
+ var/list/hand_back = list()
+ // Inlined get_open_turf_in_dir, just to be fast
+ var/turf/open/new_turf = get_step(center, NORTH)
+ if(istype(new_turf))
+ hand_back += new_turf
+ new_turf = get_step(center, SOUTH)
+ if(istype(new_turf))
+ hand_back += new_turf
+ new_turf = get_step(center, EAST)
+ if(istype(new_turf))
+ hand_back += new_turf
+ new_turf = get_step(center, WEST)
+ if(istype(new_turf))
+ hand_back += new_turf
+ return hand_back
+
/proc/get_adjacent_open_areas(atom/center)
. = list()
@@ -415,26 +407,73 @@
O.screen_loc = screen_loc
return O
+/// Adds an image to a client's `.images`. Useful as a callback.
+/proc/add_image_to_client(image/image_to_remove, client/add_to)
+ add_to?.images += image_to_remove
+
+/// Like add_image_to_client, but will add the image from a list of clients
+/proc/add_image_to_clients(image/image_to_remove, list/show_to)
+ for(var/client/add_to in show_to)
+ add_to.images += image_to_remove
+
/// Removes an image from a client's `.images`. Useful as a callback.
-/proc/remove_image_from_client(image/image, client/remove_from)
- remove_from?.images -= image
+/proc/remove_image_from_client(image/image_to_remove, client/remove_from)
+ remove_from?.images -= image_to_remove
-/proc/remove_images_from_clients(image/I, list/show_to)
- for(var/client/C in show_to)
- C.images -= I
+/// Like remove_image_from_client, but will remove the image from a list of clients
+/proc/remove_image_from_clients(image/image_to_remove, list/hide_from)
+ for(var/client/remove_from in hide_from)
+ remove_from.images -= image_to_remove
-/proc/flick_overlay(image/I, list/show_to, duration)
- for(var/client/C in show_to)
- C.images += I
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_images_from_clients), I, show_to), duration, TIMER_CLIENT_TIME)
+/// Add an image to a list of clients and calls a proc to remove it after a duration
+/proc/flick_overlay_global(image/image_to_show, list/show_to, duration)
+ if(!show_to || !length(show_to) || !image_to_show)
+ return
+ for(var/client/add_to in show_to)
+ add_to.images += image_to_show
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_image_from_clients), image_to_show, show_to), duration, TIMER_CLIENT_TIME)
-/proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom
- var/list/viewing = list()
- for(var/m in viewers(target))
- var/mob/M = m
- if(M.client)
- viewing += M.client
- flick_overlay(I, viewing, duration)
+///Flicks a certain overlay onto an atom, handling icon_state strings
+/atom/proc/flick_overlay(image_to_show, list/show_to, duration, layer)
+ var/image/passed_image = \
+ istext(image_to_show) \
+ ? image(icon, src, image_to_show, layer) \
+ : image_to_show
+
+ flick_overlay_global(passed_image, show_to, duration)
+
+/**
+ * Helper atom that copies an appearance and exists for a period
+*/
+/atom/movable/flick_visual
+
+/// Takes the passed in MA/icon_state, mirrors it onto ourselves, and displays that in world for duration seconds
+/// Returns the displayed object, you can animate it and all, but you don't own it, we'll delete it after the duration
+/atom/proc/flick_overlay_view(mutable_appearance/display, duration)
+ if(!display)
+ return null
+
+ var/mutable_appearance/passed_appearance = \
+ istext(display) \
+ ? mutable_appearance(icon, display, layer) \
+ : display
+
+ // If you don't give it a layer, we assume you want it to layer on top of this atom
+ // Because this is vis_contents, we need to set the layer manually (you can just set it as you want on return if this is a problem)
+ if(passed_appearance.layer == FLOAT_LAYER)
+ passed_appearance.layer = layer + 0.1
+ // This is faster then pooling. I promise
+ var/atom/movable/flick_visual/visual = new()
+ visual.appearance = passed_appearance
+ visual.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ // I hate /area
+ var/atom/movable/lies_to_children = src
+ lies_to_children.vis_contents += visual
+ QDEL_IN_CLIENT_TIME(visual, duration)
+ return visual
+
+/area/flick_overlay_view(mutable_appearance/display, duration)
+ return
/proc/get_active_player_count(alive_check = 0, afk_check = 0, human_check = 0)
// Get active players who are playing in the round
@@ -712,6 +751,10 @@
var/pressure = environment.return_pressure()
if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE)
. = TRUE
+ //YOGS EDIT
+ if(pressure >= JUNGLELAND_EQUIPMENT_EFFECT_PRESSURE)
+ . = TRUE
+ //YOGS END
/proc/ispipewire(item)
var/static/list/pire_wire = list(
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index 9a393ee79bf0..ab1c0a2c5ed8 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -36,6 +36,10 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/dome, GLOB.dome_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/dorsal_tubes, GLOB.dorsal_tubes_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/ethereal_mark, GLOB.ethereal_mark_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_weathering, GLOB.preternis_weathering_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_antenna, GLOB.preternis_antenna_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_eye, GLOB.preternis_eye_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_core, GLOB.preternis_core_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/pod_hair, GLOB.pod_hair_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/pod_flower, GLOB.pod_flower_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/ipc_screens, GLOB.ipc_screens_list)
diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm
index c736a61310e4..ee8b23542275 100644
--- a/code/__HELPERS/hallucinations.dm
+++ b/code/__HELPERS/hallucinations.dm
@@ -79,6 +79,9 @@ GLOBAL_LIST_EMPTY(all_ongoing_hallucinations)
if(is_blind(nearby_living))
continue
+
+ if(HAS_TRAIT(nearby_living, TRAIT_MESONS))
+ continue
// Everyone else gets hallucinations.
var/dist = sqrt(1 / max(1, get_dist(nearby_living, center)))
diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm
index 41b9d266c59e..2c61130d0ae7 100644
--- a/code/__HELPERS/icon_smoothing.dm
+++ b/code/__HELPERS/icon_smoothing.dm
@@ -2,15 +2,16 @@
//generic (by snowflake) tile smoothing code; smooth your icons with this!
/*
Each tile is divided in 4 corners, each corner has an appearance associated to it; the tile is then overlayed by these 4 appearances
- To use this, just set your atom's 'smooth' var to 1. If your atom can be moved/unanchored, set its 'can_be_unanchored' var to 1.
+ To use this, just set your atom's 'smoothing_flags' var to 1. If your atom can be moved/unanchored, set its 'can_be_unanchored' var to 1.
If you don't want your atom's icon to smooth with anything but atoms of the same type, set the list 'canSmoothWith' to null;
- Otherwise, put all types you want the atom icon to smooth with in 'canSmoothWith' INCLUDING THE TYPE OF THE ATOM ITSELF.
+ Otherwise, put all the smoothing groups you want the atom icon to smooth with in 'canSmoothWith', including the group of the atom itself.
+ Smoothing groups are just shared flags between objects. If one of the 'canSmoothWith' of A matches one of the `smoothing_groups` of B, then A will smooth with B.
Each atom has its own icon file with all the possible corner states. See 'smooth_wall.dmi' for a template.
DIAGONAL SMOOTHING INSTRUCTIONS
To make your atom smooth diagonally you need all the proper icon states (see 'smooth_wall.dmi' for a template) and
- to add the 'SMOOTH_DIAGONAL' flag to the atom's smooth var (in addition to either SMOOTH_TRUE or SMOOTH_MORE).
+ to add the 'SMOOTH_DIAGONAL_CORNERS' flag to the atom's smoothing_flags var (in addition to either SMOOTH_TRUE or SMOOTH_MORE).
For turfs, what appears under the diagonal corners depends on the turf that was in the same position previously: if you make a wall on
a plating floor, you will see plating under the diagonal wall corner, if it was space, you will see space.
@@ -23,298 +24,516 @@
To see an example of a diagonal wall, see '/turf/closed/wall/mineral/titanium' and its subtypes.
*/
-//Redefinitions of the diagonal directions so they can be stored in one var without conflicts
-#define N_NORTH (1<<1)
-#define N_SOUTH (1<<2)
-#define N_EAST (1<<4)
-#define N_WEST (1<<8)
-#define N_NORTHEAST (1<<5)
-#define N_NORTHWEST (1<<9)
-#define N_SOUTHEAST (1<<6)
-#define N_SOUTHWEST (1<<10)
-
-#define SMOOTH_FALSE 0 //not smooth
-#define SMOOTH_TRUE (1<<0) //smooths with exact specified types or just itself
-#define SMOOTH_MORE (1<<1) //smooths with all subtypes of specified types or just itself (this value can replace SMOOTH_TRUE)
-#define SMOOTH_DIAGONAL (1<<2) //if atom should smooth diagonally, this should be present in 'smooth' var
-#define SMOOTH_BORDER (1<<3) //atom will smooth with the borders of the map
-#define SMOOTH_QUEUED (1<<4) //atom is currently queued to smooth.
-
-#define NULLTURF_BORDER 123456789
-
-#define DEFAULT_UNDERLAY_ICON 'icons/turf/floors.dmi'
-#define DEFAULT_UNDERLAY_ICON_STATE "plating"
-
-/atom/var/smooth = SMOOTH_FALSE
-/atom/var/top_left_corner
-/atom/var/top_right_corner
-/atom/var/bottom_left_corner
-/atom/var/bottom_right_corner
-/atom/var/list/canSmoothWith = null // TYPE PATHS I CAN SMOOTH WITH~~~~~ If this is null and atom is smooth, it smooths only with itself
-/atom/movable/var/can_be_unanchored = FALSE
-/turf/var/list/fixed_underlay = null
-
-/proc/calculate_adjacencies(atom/A)
- if(!A.loc)
- return 0
-
- var/adjacencies = 0
-
- var/atom/movable/AM
- if(ismovable(A))
- AM = A
- if(AM.can_be_unanchored && !AM.anchored)
- return 0
+#define NO_ADJ_FOUND 0
+#define ADJ_FOUND 1
+#define NULLTURF_BORDER 2
- for(var/direction in GLOB.cardinals)
- AM = find_type_in_direction(A, direction)
- if(AM == NULLTURF_BORDER)
- if((A.smooth & SMOOTH_BORDER))
- adjacencies |= 1 << direction
- else if( (AM && !istype(AM)) || (istype(AM) && AM.anchored) )
- adjacencies |= 1 << direction
-
- if(adjacencies & N_NORTH)
- if(adjacencies & N_WEST)
- AM = find_type_in_direction(A, NORTHWEST)
- if(AM == NULLTURF_BORDER)
- if((A.smooth & SMOOTH_BORDER))
- adjacencies |= N_NORTHWEST
- else if( (AM && !istype(AM)) || (istype(AM) && AM.anchored) )
- adjacencies |= N_NORTHWEST
- if(adjacencies & N_EAST)
- AM = find_type_in_direction(A, NORTHEAST)
- if(AM == NULLTURF_BORDER)
- if((A.smooth & SMOOTH_BORDER))
- adjacencies |= N_NORTHEAST
- else if( (AM && !istype(AM)) || (istype(AM) && AM.anchored) )
- adjacencies |= N_NORTHEAST
-
- if(adjacencies & N_SOUTH)
- if(adjacencies & N_WEST)
- AM = find_type_in_direction(A, SOUTHWEST)
- if(AM == NULLTURF_BORDER)
- if((A.smooth & SMOOTH_BORDER))
- adjacencies |= N_SOUTHWEST
- else if( (AM && !istype(AM)) || (istype(AM) && AM.anchored) )
- adjacencies |= N_SOUTHWEST
- if(adjacencies & N_EAST)
- AM = find_type_in_direction(A, SOUTHEAST)
- if(AM == NULLTURF_BORDER)
- if((A.smooth & SMOOTH_BORDER))
- adjacencies |= N_SOUTHEAST
- else if( (AM && !istype(AM)) || (istype(AM) && AM.anchored) )
- adjacencies |= N_SOUTHEAST
-
- return adjacencies
-
-//do not use, use queue_smooth(atom)
-/proc/smooth_icon(atom/A)
- if(!A || !A.smooth)
- return
- A.smooth &= ~SMOOTH_QUEUED
- if (!A.z)
- return
- if(QDELETED(A))
+GLOBAL_LIST_INIT(adjacent_direction_lookup, generate_adjacent_directions())
+
+/* Attempting to mirror the below
+ * Each 3x3 grid is a tile, with each X representing a direction a border object could be in IN said grid
+ * Directions marked with A are acceptable smoothing targets, M is the example direction
+ * The example given here is of a northfacing border object
+xxx AxA xxx
+xxx AxA xxx
+xxx AxA xxx
+
+AAA MMM AAA
+xxx AxA xxx
+xxx AxA xxx
+
+xxx xxx xxx
+xxx xxx xxx
+xxx xxx xxx
+*/
+/// Encodes connectivity between border objects
+/// Returns a list accessable by a border object's dir, the direction between it and a target, and a target
+/// Said list will return the direction the two objects connect, if any exists (if the target isn't a border object and the direction is fine, return the inverse of the direction in use)
+/proc/generate_adjacent_directions()
+ // Have to hold all conventional dir pairs, so we size to the largest
+ // We don't HAVE diagonal border objects, so I'm gonna pretend they'll never exist
+
+ // You might be like, lemon, can't we use GLOB.cardinals/GLOB.alldirs here
+ // No, they aren't loaded yet. life is pain
+ var/list/cardinals = list(NORTH, SOUTH, EAST, WEST)
+ var/list/alldirs = cardinals + list(NORTH|EAST, SOUTH|EAST, NORTH|WEST, SOUTH|WEST)
+ var/largest_cardinal = max(cardinals)
+ var/largest_dir = max(alldirs)
+
+ var/list/direction_map = new /list(largest_cardinal)
+ for(var/dir in cardinals)
+ var/left = turn(dir, 90)
+ var/right = turn(dir, -90)
+ var/opposite = REVERSE_DIR(dir)
+ // Need to encode diagonals here because it's possible, even if it is always false
+ var/list/acceptable_adjacents = new /list(largest_dir)
+ // Alright, what directions are acceptable to us
+ for(var/connectable_dir in (cardinals + NONE))
+ // And what border objects INSIDE those directions are alright
+ var/list/smoothable_dirs = new /list(largest_cardinal + 1) // + 1 because we need to provide space for NONE to be a valid index
+ // None is fine, we want to smooth with things on our own turf
+ // We'll do the two dirs to our left and right
+ // They connect.. "below" us and on their side
+ if(connectable_dir == NONE)
+ smoothable_dirs[left] = dir_to_junction(opposite | left)
+ smoothable_dirs[right] = dir_to_junction(opposite | right)
+ // If it's to our right or left we'll include just the dir matching ours
+ // Left edge touches only our left side, and so on
+ else if (connectable_dir == left)
+ smoothable_dirs[dir] = left
+ else if (connectable_dir == right)
+ smoothable_dirs[dir] = right
+ // If it's straight on we'll include our direction as a link
+ // Then include the two edges on the other side as diagonals
+ else if(connectable_dir == dir)
+ smoothable_dirs[opposite] = dir
+ smoothable_dirs[left] = dir_to_junction(dir | left)
+ smoothable_dirs[right] = dir_to_junction(dir | right)
+ // otherwise, go HOME, I don't want to encode anything for you
+ else
+ continue
+ acceptable_adjacents[connectable_dir + 1] = smoothable_dirs
+ direction_map[dir] = acceptable_adjacents
+ return direction_map
+
+/// Are two atoms border adjacent, takes a border object, something to compare against, and the direction between A and B
+/// Returns the way in which the first thing is adjacent to the second
+#define CAN_DIAGONAL_SMOOTH(border_obj, target, direction) (\
+ (target.smoothing_flags & SMOOTH_BORDER_OBJECT) ? \
+ GLOB.adjacent_direction_lookup[border_obj.dir][direction + 1]?[target.dir] : \
+ (GLOB.adjacent_direction_lookup[border_obj.dir][direction + 1]) ? REVERSE_DIR(direction) : NONE \
+ )
+
+#define DEFAULT_UNDERLAY_ICON 'icons/turf/floors.dmi'
+#define DEFAULT_UNDERLAY_ICON_STATE "plating"
+
+
+///Scans all adjacent turfs to find targets to smooth with.
+/atom/proc/calculate_adjacencies()
+ . = NONE
+
+ if(!loc)
return
- if(A.smooth & (SMOOTH_TRUE | SMOOTH_MORE))
- var/adjacencies = calculate_adjacencies(A)
- if(A.smooth & SMOOTH_DIAGONAL)
- A.diagonal_smooth(adjacencies)
+ for(var/direction in GLOB.cardinals)
+ switch(find_type_in_direction(direction))
+ if(NULLTURF_BORDER)
+ if((smoothing_flags & SMOOTH_BORDER))
+ . |= direction //BYOND and smooth dirs are the same for cardinals
+ if(ADJ_FOUND)
+ . |= direction //BYOND and smooth dirs are the same for cardinals
+
+ if(. & NORTH_JUNCTION)
+ if(. & WEST_JUNCTION)
+ switch(find_type_in_direction(NORTHWEST))
+ if(NULLTURF_BORDER)
+ if((smoothing_flags & SMOOTH_BORDER))
+ . |= NORTHWEST_JUNCTION
+ if(ADJ_FOUND)
+ . |= NORTHWEST_JUNCTION
+
+ if(. & EAST_JUNCTION)
+ switch(find_type_in_direction(NORTHEAST))
+ if(NULLTURF_BORDER)
+ if((smoothing_flags & SMOOTH_BORDER))
+ . |= NORTHEAST_JUNCTION
+ if(ADJ_FOUND)
+ . |= NORTHEAST_JUNCTION
+
+ if(. & SOUTH_JUNCTION)
+ if(. & WEST_JUNCTION)
+ switch(find_type_in_direction(SOUTHWEST))
+ if(NULLTURF_BORDER)
+ if((smoothing_flags & SMOOTH_BORDER))
+ . |= SOUTHWEST_JUNCTION
+ if(ADJ_FOUND)
+ . |= SOUTHWEST_JUNCTION
+
+ if(. & EAST_JUNCTION)
+ switch(find_type_in_direction(SOUTHEAST))
+ if(NULLTURF_BORDER)
+ if((smoothing_flags & SMOOTH_BORDER))
+ . |= SOUTHEAST_JUNCTION
+ if(ADJ_FOUND)
+ . |= SOUTHEAST_JUNCTION
+
+
+/atom/movable/calculate_adjacencies()
+ if(can_be_unanchored && !anchored)
+ return NONE
+ return ..()
+
+
+///do not use, use QUEUE_SMOOTH(atom)
+/atom/proc/smooth_icon()
+ if(QDELETED(src))
+ return
+ smoothing_flags &= ~SMOOTH_QUEUED
+ flags_1 |= HTML_USE_INITAL_ICON_1
+ if (!z)
+ CRASH("[type] called smooth_icon() without being on a z-level")
+ if(smoothing_flags & SMOOTH_CORNERS)
+ if(smoothing_flags & SMOOTH_DIAGONAL_CORNERS)
+ corners_diagonal_smooth(calculate_adjacencies())
else
- cardinal_smooth(A, adjacencies)
+ corners_cardinal_smooth(calculate_adjacencies())
+ else if(smoothing_flags & SMOOTH_BITMASK)
+ bitmask_smooth()
+ else
+ CRASH("smooth_icon called for [src] with smoothing_flags == [smoothing_flags]")
+ SEND_SIGNAL(src, COMSIG_ATOM_SMOOTHED_ICON)
+
+/turf/smooth_icon()
+ . = ..()
+ SSdemo.mark_turf(src)
-/atom/proc/diagonal_smooth(adjacencies)
+// As a rule, movables will most always care about smoothing changes
+// Turfs on the other hand, don't, so we don't do the update for THEM unless they explicitly request it
+/atom/movable/smooth_icon()
+ . = ..()
+ update_appearance(~UPDATE_SMOOTHING)
+
+/atom/proc/corners_diagonal_smooth(adjacencies)
switch(adjacencies)
- if(N_NORTH|N_WEST)
+ if(NORTH_JUNCTION|WEST_JUNCTION)
replace_smooth_overlays("d-se","d-se-0")
- if(N_NORTH|N_EAST)
+ if(NORTH_JUNCTION|EAST_JUNCTION)
replace_smooth_overlays("d-sw","d-sw-0")
- if(N_SOUTH|N_WEST)
+ if(SOUTH_JUNCTION|WEST_JUNCTION)
replace_smooth_overlays("d-ne","d-ne-0")
- if(N_SOUTH|N_EAST)
+ if(SOUTH_JUNCTION|EAST_JUNCTION)
replace_smooth_overlays("d-nw","d-nw-0")
- if(N_NORTH|N_WEST|N_NORTHWEST)
+ if(NORTH_JUNCTION|WEST_JUNCTION|NORTHWEST_JUNCTION)
replace_smooth_overlays("d-se","d-se-1")
- if(N_NORTH|N_EAST|N_NORTHEAST)
+ if(NORTH_JUNCTION|EAST_JUNCTION|NORTHEAST_JUNCTION)
replace_smooth_overlays("d-sw","d-sw-1")
- if(N_SOUTH|N_WEST|N_SOUTHWEST)
+ if(SOUTH_JUNCTION|WEST_JUNCTION|SOUTHWEST_JUNCTION)
replace_smooth_overlays("d-ne","d-ne-1")
- if(N_SOUTH|N_EAST|N_SOUTHEAST)
+ if(SOUTH_JUNCTION|EAST_JUNCTION|SOUTHEAST_JUNCTION)
replace_smooth_overlays("d-nw","d-nw-1")
else
- cardinal_smooth(src, adjacencies)
- return
+ corners_cardinal_smooth(adjacencies)
+ return FALSE
icon_state = ""
- return adjacencies
-
-//only walls should have a need to handle underlays
-/turf/closed/wall/diagonal_smooth(adjacencies)
- adjacencies = reverse_ndir(..())
- if(adjacencies)
- var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, plane = FLOOR_PLANE)
- var/list/U = list(underlay_appearance)
- if(fixed_underlay)
- if(fixed_underlay["space"])
- underlay_appearance.icon = 'icons/turf/space.dmi'
- underlay_appearance.icon_state = SPACE_ICON_STATE
- underlay_appearance.plane = PLANE_SPACE
- else
- underlay_appearance.icon = fixed_underlay["icon"]
- underlay_appearance.icon_state = fixed_underlay["icon_state"]
- else
- var/turned_adjacency = turn(adjacencies, 180)
- var/turf/T = get_step(src, turned_adjacency)
- if(!T.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
- T = get_step(src, turn(adjacencies, 135))
- if(!T.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
- T = get_step(src, turn(adjacencies, 225))
- //if all else fails, ask our own turf
- if(!T.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency) && !get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
- underlay_appearance.icon = DEFAULT_UNDERLAY_ICON
- underlay_appearance.icon_state = DEFAULT_UNDERLAY_ICON_STATE
- underlays = U
-
- // Drop posters which were previously placed on this wall.
- for(var/obj/structure/sign/poster/P in src)
- P.roll_and_drop(src)
-
-
-/proc/cardinal_smooth(atom/A, adjacencies)
+ return TRUE
+
+
+/atom/proc/corners_cardinal_smooth(adjacencies)
+ var/mutable_appearance/temp_ma
+
//NW CORNER
var/nw = "1-i"
- if((adjacencies & N_NORTH) && (adjacencies & N_WEST))
- if(adjacencies & N_NORTHWEST)
+ if((adjacencies & NORTH_JUNCTION) && (adjacencies & WEST_JUNCTION))
+ if(adjacencies & NORTHWEST_JUNCTION)
nw = "1-f"
else
nw = "1-nw"
else
- if(adjacencies & N_NORTH)
+ if(adjacencies & NORTH_JUNCTION)
nw = "1-n"
- else if(adjacencies & N_WEST)
+ else if(adjacencies & WEST_JUNCTION)
nw = "1-w"
+ temp_ma = mutable_appearance(icon, nw)
+ nw = temp_ma.appearance
//NE CORNER
var/ne = "2-i"
- if((adjacencies & N_NORTH) && (adjacencies & N_EAST))
- if(adjacencies & N_NORTHEAST)
+ if((adjacencies & NORTH_JUNCTION) && (adjacencies & EAST_JUNCTION))
+ if(adjacencies & NORTHEAST_JUNCTION)
ne = "2-f"
else
ne = "2-ne"
else
- if(adjacencies & N_NORTH)
+ if(adjacencies & NORTH_JUNCTION)
ne = "2-n"
- else if(adjacencies & N_EAST)
+ else if(adjacencies & EAST_JUNCTION)
ne = "2-e"
+ temp_ma = mutable_appearance(icon, ne)
+ ne = temp_ma.appearance
//SW CORNER
var/sw = "3-i"
- if((adjacencies & N_SOUTH) && (adjacencies & N_WEST))
- if(adjacencies & N_SOUTHWEST)
+ if((adjacencies & SOUTH_JUNCTION) && (adjacencies & WEST_JUNCTION))
+ if(adjacencies & SOUTHWEST_JUNCTION)
sw = "3-f"
else
sw = "3-sw"
else
- if(adjacencies & N_SOUTH)
+ if(adjacencies & SOUTH_JUNCTION)
sw = "3-s"
- else if(adjacencies & N_WEST)
+ else if(adjacencies & WEST_JUNCTION)
sw = "3-w"
+ temp_ma = mutable_appearance(icon, sw)
+ sw = temp_ma.appearance
//SE CORNER
var/se = "4-i"
- if((adjacencies & N_SOUTH) && (adjacencies & N_EAST))
- if(adjacencies & N_SOUTHEAST)
+ if((adjacencies & SOUTH_JUNCTION) && (adjacencies & EAST_JUNCTION))
+ if(adjacencies & SOUTHEAST_JUNCTION)
se = "4-f"
else
se = "4-se"
else
- if(adjacencies & N_SOUTH)
+ if(adjacencies & SOUTH_JUNCTION)
se = "4-s"
- else if(adjacencies & N_EAST)
+ else if(adjacencies & EAST_JUNCTION)
se = "4-e"
+ temp_ma = mutable_appearance(icon, se)
+ se = temp_ma.appearance
- var/list/New
+ var/list/new_overlays
- if(A.top_left_corner != nw)
- A.cut_overlay(A.top_left_corner)
- A.top_left_corner = nw
- LAZYADD(New, nw)
+ if(top_left_corner != nw)
+ cut_overlay(top_left_corner)
+ top_left_corner = nw
+ LAZYADD(new_overlays, nw)
- if(A.top_right_corner != ne)
- A.cut_overlay(A.top_right_corner)
- A.top_right_corner = ne
- LAZYADD(New, ne)
+ if(top_right_corner != ne)
+ cut_overlay(top_right_corner)
+ top_right_corner = ne
+ LAZYADD(new_overlays, ne)
- if(A.bottom_right_corner != sw)
- A.cut_overlay(A.bottom_right_corner)
- A.bottom_right_corner = sw
- LAZYADD(New, sw)
+ if(bottom_right_corner != sw)
+ cut_overlay(bottom_right_corner)
+ bottom_right_corner = sw
+ LAZYADD(new_overlays, sw)
- if(A.bottom_left_corner != se)
- A.cut_overlay(A.bottom_left_corner)
- A.bottom_left_corner = se
- LAZYADD(New, se)
+ if(bottom_left_corner != se)
+ cut_overlay(bottom_left_corner)
+ bottom_left_corner = se
+ LAZYADD(new_overlays, se)
- if(New)
- A.add_overlay(New)
+ if(new_overlays)
+ add_overlay(new_overlays)
-/proc/find_type_in_direction(atom/source, direction)
- var/turf/target_turf = get_step(source, direction)
+
+///Scans direction to find targets to smooth with.
+/atom/proc/find_type_in_direction(direction)
+ var/turf/target_turf = get_step(src, direction)
if(!target_turf)
return NULLTURF_BORDER
var/area/target_area = get_area(target_turf)
- var/area/source_area = get_area(source)
- if(source_area.canSmoothWithAreas && !is_type_in_typecache(target_area, source_area.canSmoothWithAreas))
- return null
- if(target_area.canSmoothWithAreas && !is_type_in_typecache(source_area, target_area.canSmoothWithAreas))
- return null
-
- if(source.canSmoothWith)
- var/atom/A
- if(source.smooth & SMOOTH_MORE)
- for(var/a_type in source.canSmoothWith)
- if( istype(target_turf, a_type) )
- return target_turf
- A = locate(a_type) in target_turf
- if(A)
- return A
- return null
-
- for(var/a_type in source.canSmoothWith)
- if(a_type == target_turf.type)
- return target_turf
- A = locate(a_type) in target_turf
- if(A && A.type == a_type)
- return A
- return null
- else
- if(isturf(source))
- return source.type == target_turf.type ? target_turf : null
- var/atom/A = locate(source.type) in target_turf
- return A && A.type == source.type ? A : null
+ var/area/source_area = get_area(src)
+ if((source_area.area_limited_icon_smoothing && !istype(target_area, source_area.area_limited_icon_smoothing)) || (target_area.area_limited_icon_smoothing && !istype(source_area, target_area.area_limited_icon_smoothing)))
+ return NO_ADJ_FOUND
+
+ if(isnull(canSmoothWith)) //special case in which it will only smooth with itself
+ if(isturf(src))
+ return (type == target_turf.type) ? ADJ_FOUND : NO_ADJ_FOUND
+ var/atom/matching_obj = locate(type) in target_turf
+ return (matching_obj && matching_obj.type == type) ? ADJ_FOUND : NO_ADJ_FOUND
+
+ if(!isnull(target_turf.smoothing_groups))
+ for(var/target in canSmoothWith)
+ if(!(canSmoothWith[target] & target_turf.smoothing_groups[target]))
+ continue
+ return ADJ_FOUND
+
+ if(smoothing_flags & SMOOTH_OBJ)
+ for(var/atom/movable/thing as anything in target_turf)
+ if(!thing.anchored || isnull(thing.smoothing_groups))
+ continue
+ for(var/target in canSmoothWith)
+ if(!(canSmoothWith[target] & thing.smoothing_groups[target]))
+ continue
+ return ADJ_FOUND
+
+ return NO_ADJ_FOUND
+
+/**
+ * Basic smoothing proc. The atom checks for adjacent directions to smooth with and changes the icon_state based on that.
+ *
+ * Returns the previous smoothing_junction state so the previous state can be compared with the new one after the proc ends, and see the changes, if any.
+ *
+*/
+/atom/proc/bitmask_smooth()
+ var/new_junction = NONE
+
+ // cache for sanic speed
+ var/canSmoothWith = src.canSmoothWith
+
+ var/smooth_border = (smoothing_flags & SMOOTH_BORDER)
+ var/smooth_obj = (smoothing_flags & SMOOTH_OBJ)
+ var/border_object_smoothing = (smoothing_flags & SMOOTH_BORDER_OBJECT)
+
+ // Did you know you can pass defines into other defines? very handy, lets take advantage of it here to allow 0 cost variation
+ #define SEARCH_ADJ_IN_DIR(direction, direction_flag, ADJ_FOUND, WORLD_BORDER, BORDER_CHECK) \
+ do { \
+ var/turf/neighbor = get_step(src, direction); \
+ if(neighbor && ##BORDER_CHECK(neighbor, direction)) { \
+ var/neighbor_smoothing_groups = neighbor.smoothing_groups; \
+ if(neighbor_smoothing_groups) { \
+ for(var/target in canSmoothWith) { \
+ if(canSmoothWith[target] & neighbor_smoothing_groups[target]) { \
+ ##ADJ_FOUND(neighbor, direction, direction_flag); \
+ } \
+ } \
+ } \
+ if(smooth_obj) { \
+ for(var/atom/movable/thing as anything in neighbor) { \
+ var/thing_smoothing_groups = thing.smoothing_groups; \
+ if(!thing.anchored || isnull(thing_smoothing_groups) || !##BORDER_CHECK(thing, direction)) { \
+ continue; \
+ }; \
+ for(var/target in canSmoothWith) { \
+ if(canSmoothWith[target] & thing_smoothing_groups[target]) { \
+ ##ADJ_FOUND(thing, direction, direction_flag); \
+ } \
+ } \
+ } \
+ } \
+ } else if (smooth_border) { \
+ ##WORLD_BORDER(null, direction, direction_flag); \
+ } \
+ } while(FALSE) \
+
+ #define BITMASK_FOUND(target, direction, direction_flag) \
+ new_junction |= direction_flag; \
+ break set_adj_in_dir; \
+ /// Check that non border objects use to smooth against border objects
+ /// Returns true if the smooth is acceptable, FALSE otherwise
+ #define BITMASK_ON_BORDER_CHECK(target, direction) (!(target.smoothing_flags & SMOOTH_BORDER_OBJECT) || CAN_DIAGONAL_SMOOTH(target, src, REVERSE_DIR(direction)))
+
+ #define BORDER_FOUND(target, direction, direction_flag) new_junction |= CAN_DIAGONAL_SMOOTH(src, target, direction)
+ // Border objects require an object as context, so we need a dummy. I'm sorry
+ #define WORLD_BORDER_FOUND(target, direction, direction_flag) \
+ var/static/atom/dummy; \
+ if(!dummy) { \
+ dummy = new(); \
+ dummy.smoothing_flags &= ~SMOOTH_BORDER_OBJECT; \
+ } \
+ BORDER_FOUND(dummy, direction, direction_flag);
+ // Handle handle border on border checks. no-op, we handle this check inside CAN_DIAGONAL_SMOOTH
+ #define BORDER_ON_BORDER_CHECK(target, direction) (TRUE)
+
+ // We're building 2 different types of smoothing searches here
+ // One for standard bitmask smoothing (We provide a label so our macro can eary exit, as it wants to do)
+ #define SET_ADJ_IN_DIR(direction, direction_flag) do { set_adj_in_dir: { SEARCH_ADJ_IN_DIR(direction, direction_flag, BITMASK_FOUND, BITMASK_FOUND, BITMASK_ON_BORDER_CHECK) }} while(FALSE)
+ // and another for border object work (Doesn't early exit because we can hit more then one direction by checking the same turf)
+ #define SET_BORDER_ADJ_IN_DIR(direction) SEARCH_ADJ_IN_DIR(direction, direction, BORDER_FOUND, WORLD_BORDER_FOUND, BORDER_ON_BORDER_CHECK)
+
+ // Let's go over all our cardinals
+ if(border_object_smoothing)
+ SET_BORDER_ADJ_IN_DIR(NORTH)
+ SET_BORDER_ADJ_IN_DIR(SOUTH)
+ SET_BORDER_ADJ_IN_DIR(EAST)
+ SET_BORDER_ADJ_IN_DIR(WEST)
+ // We want to check against stuff in our own turf
+ SET_BORDER_ADJ_IN_DIR(NONE)
+ // Border objects don't do diagonals, so GO HOME
+ set_smoothed_icon_state(new_junction)
+ return
+
+ SET_ADJ_IN_DIR(NORTH, NORTH)
+ SET_ADJ_IN_DIR(SOUTH, SOUTH)
+ SET_ADJ_IN_DIR(EAST, EAST)
+ SET_ADJ_IN_DIR(WEST, WEST)
+
+ // If there's nothing going on already
+ if(!(new_junction & (NORTH|SOUTH)) || !(new_junction & (EAST|WEST)))
+ set_smoothed_icon_state(new_junction)
+ return
+
+ if(new_junction & NORTH_JUNCTION)
+ if(new_junction & WEST_JUNCTION)
+ SET_ADJ_IN_DIR(NORTHWEST, NORTHWEST_JUNCTION)
+
+ if(new_junction & EAST_JUNCTION)
+ SET_ADJ_IN_DIR(NORTHEAST, NORTHEAST_JUNCTION)
+
+ if(new_junction & SOUTH_JUNCTION)
+ if(new_junction & WEST_JUNCTION)
+ SET_ADJ_IN_DIR(SOUTHWEST, SOUTHWEST_JUNCTION)
+
+ if(new_junction & EAST_JUNCTION)
+ SET_ADJ_IN_DIR(SOUTHEAST, SOUTHEAST_JUNCTION)
+
+ set_smoothed_icon_state(new_junction)
+
+ #undef SET_BORDER_ADJ_IN_DIR
+ #undef SET_ADJ_IN_DIR
+ #undef BORDER_ON_BORDER_CHECK
+ #undef WORLD_BORDER_FOUND
+ #undef BORDER_FOUND
+ #undef BITMASK_ON_BORDER_CHECK
+ #undef BITMASK_FOUND
+ #undef SEARCH_ADJ_IN_DIR
+
+///Changes the icon state based on the new junction bitmask
+/atom/proc/set_smoothed_icon_state(new_junction)
+ . = smoothing_junction
+ smoothing_junction = new_junction
+ icon_state = "[base_icon_state]-[smoothing_junction]"
+
+
+/turf/closed/set_smoothed_icon_state(new_junction)
+ // Avoid calling ..() here to avoid setting icon_state twice, which is expensive given how hot this proc is
+ var/old_junction = smoothing_junction
+ smoothing_junction = new_junction
+
+ if (!(smoothing_flags & SMOOTH_DIAGONAL_CORNERS))
+ icon_state = "[base_icon_state]-[smoothing_junction]"
+ return
+
+ switch(new_junction)
+ if(
+ NORTH_JUNCTION|WEST_JUNCTION,
+ NORTH_JUNCTION|EAST_JUNCTION,
+ SOUTH_JUNCTION|WEST_JUNCTION,
+ SOUTH_JUNCTION|EAST_JUNCTION,
+ NORTH_JUNCTION|WEST_JUNCTION|NORTHWEST_JUNCTION,
+ NORTH_JUNCTION|EAST_JUNCTION|NORTHEAST_JUNCTION,
+ SOUTH_JUNCTION|WEST_JUNCTION|SOUTHWEST_JUNCTION,
+ SOUTH_JUNCTION|EAST_JUNCTION|SOUTHEAST_JUNCTION,
+ )
+ icon_state = "[base_icon_state]-[smoothing_junction]-d"
+ if(new_junction == old_junction || fixed_underlay) // Mutable underlays?
+ return
+
+ var/junction_dir = reverse_ndir(smoothing_junction)
+ var/turned_adjacency = REVERSE_DIR(junction_dir)
+ var/turf/neighbor_turf = get_step(src, turned_adjacency & (NORTH|SOUTH))
+ var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, offset_spokesman = src, plane = FLOOR_PLANE)
+ if(!neighbor_turf.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
+ neighbor_turf = get_step(src, turned_adjacency & (EAST|WEST))
+
+ if(!neighbor_turf.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
+ neighbor_turf = get_step(src, turned_adjacency)
+
+ if(!neighbor_turf.get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency))
+ if(!get_smooth_underlay_icon(underlay_appearance, src, turned_adjacency)) //if all else fails, ask our own turf
+ underlay_appearance.icon = DEFAULT_UNDERLAY_ICON
+ underlay_appearance.icon_state = DEFAULT_UNDERLAY_ICON_STATE
+ underlays += underlay_appearance
+ else
+ icon_state = "[base_icon_state]-[smoothing_junction]"
+
+/turf/open/floor/set_smoothed_icon_state(new_junction)
+ if(broken || burnt)
+ return
+ return ..()
+
//Icon smoothing helpers
/proc/smooth_zlevel(zlevel, now = FALSE)
- var/list/away_turfs = block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel))
- for(var/V in away_turfs)
- var/turf/T = V
- if(T.smooth)
+ var/list/away_turfs = Z_TURFS(zlevel)
+ for(var/turf/turf_to_smooth as anything in away_turfs)
+ if(turf_to_smooth.smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
if(now)
- smooth_icon(T)
+ turf_to_smooth.smooth_icon()
else
- queue_smooth(T)
- for(var/R in T)
- var/atom/A = R
- if(A.smooth)
+ QUEUE_SMOOTH(turf_to_smooth)
+ for(var/atom/movable/movable_to_smooth as anything in turf_to_smooth)
+ if(movable_to_smooth.smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
if(now)
- smooth_icon(A)
+ movable_to_smooth.smooth_icon()
else
- queue_smooth(A)
+ QUEUE_SMOOTH(movable_to_smooth)
+
/atom/proc/clear_smooth_overlays()
cut_overlay(top_left_corner)
@@ -326,71 +545,109 @@
cut_overlay(bottom_left_corner)
bottom_left_corner = null
+/// Internal: Takes icon states as text to replace smoothing corner overlays
/atom/proc/replace_smooth_overlays(nw, ne, sw, se)
clear_smooth_overlays()
- var/list/O = list()
+ var/mutable_appearance/temp_ma
+
+ temp_ma = mutable_appearance(icon, nw)
+ nw = temp_ma.appearance
+
+ temp_ma = mutable_appearance(icon, ne)
+ ne = temp_ma.appearance
+
+ temp_ma = mutable_appearance(icon, sw)
+ sw = temp_ma.appearance
+
+ temp_ma = mutable_appearance(icon, se)
+ se = temp_ma.appearance
+
+ var/list/new_overlays = list()
+
top_left_corner = nw
- O += nw
+ new_overlays += nw
+
top_right_corner = ne
- O += ne
+ new_overlays += ne
+
bottom_left_corner = sw
- O += sw
+ new_overlays += sw
+
bottom_right_corner = se
- O += se
- add_overlay(O)
+ new_overlays += se
+
+ add_overlay(new_overlays)
+
+/// Takes a direction, turns it into all the junctions that contain it
+/proc/dir_to_all_junctions(dir)
+ var/handback = NONE
+ if(dir & NORTH)
+ handback |= NORTH_JUNCTION | NORTHEAST_JUNCTION | NORTHWEST_JUNCTION
+ if(dir & SOUTH)
+ handback |= SOUTH_JUNCTION | SOUTHEAST_JUNCTION | SOUTHWEST_JUNCTION
+ if(dir & EAST)
+ handback |= EAST_JUNCTION | SOUTHEAST_JUNCTION | NORTHEAST_JUNCTION
+ if(dir & WEST)
+ handback |= WEST_JUNCTION | NORTHWEST_JUNCTION | SOUTHWEST_JUNCTION
+ return handback
+
+/proc/dir_to_junction(dir)
+ switch(dir)
+ if(NORTH)
+ return NORTH_JUNCTION
+ if(SOUTH)
+ return SOUTH_JUNCTION
+ if(WEST)
+ return WEST_JUNCTION
+ if(EAST)
+ return EAST_JUNCTION
+ if(NORTHWEST)
+ return NORTHWEST_JUNCTION
+ if(NORTHEAST)
+ return NORTHEAST_JUNCTION
+ if(SOUTHEAST)
+ return SOUTHEAST_JUNCTION
+ if(SOUTHWEST)
+ return SOUTHWEST_JUNCTION
+ else
+ return NONE
/proc/reverse_ndir(ndir)
switch(ndir)
- if(N_NORTH)
+ if(NORTH_JUNCTION)
return NORTH
- if(N_SOUTH)
+ if(SOUTH_JUNCTION)
return SOUTH
- if(N_WEST)
+ if(WEST_JUNCTION)
return WEST
- if(N_EAST)
+ if(EAST_JUNCTION)
return EAST
- if(N_NORTHWEST)
+ if(NORTHWEST_JUNCTION)
return NORTHWEST
- if(N_NORTHEAST)
+ if(NORTHEAST_JUNCTION)
return NORTHEAST
- if(N_SOUTHEAST)
+ if(SOUTHEAST_JUNCTION)
return SOUTHEAST
- if(N_SOUTHWEST)
+ if(SOUTHWEST_JUNCTION)
return SOUTHWEST
- if(N_NORTH|N_WEST)
+ if(NORTH_JUNCTION | WEST_JUNCTION)
return NORTHWEST
- if(N_NORTH|N_EAST)
+ if(NORTH_JUNCTION | EAST_JUNCTION)
return NORTHEAST
- if(N_SOUTH|N_WEST)
+ if(SOUTH_JUNCTION | WEST_JUNCTION)
return SOUTHWEST
- if(N_SOUTH|N_EAST)
+ if(SOUTH_JUNCTION | EAST_JUNCTION)
return SOUTHEAST
- if(N_NORTH|N_WEST|N_NORTHWEST)
+ if(NORTH_JUNCTION | WEST_JUNCTION | NORTHWEST_JUNCTION)
return NORTHWEST
- if(N_NORTH|N_EAST|N_NORTHEAST)
+ if(NORTH_JUNCTION | EAST_JUNCTION | NORTHEAST_JUNCTION)
return NORTHEAST
- if(N_SOUTH|N_WEST|N_SOUTHWEST)
+ if(SOUTH_JUNCTION | WEST_JUNCTION | SOUTHWEST_JUNCTION)
return SOUTHWEST
- if(N_SOUTH|N_EAST|N_SOUTHEAST)
+ if(SOUTH_JUNCTION | EAST_JUNCTION | SOUTHEAST_JUNCTION)
return SOUTHEAST
else
- return 0
-
-//SSicon_smooth
-/proc/queue_smooth_neighbors(atom/A)
- for(var/V in orange(1,A))
- var/atom/T = V
- if(T.smooth)
- queue_smooth(T)
-
-//SSicon_smooth
-/proc/queue_smooth(atom/A)
- if(!A.smooth || A.smooth & SMOOTH_QUEUED)
- return
-
- SSicon_smooth.smooth_queue += A
- SSicon_smooth.can_fire = 1
- A.smooth |= SMOOTH_QUEUED
+ return NONE
//Example smooth wall
@@ -398,5 +655,23 @@
name = "smooth wall"
icon = 'icons/turf/smooth_wall.dmi'
icon_state = "smooth"
- smooth = SMOOTH_TRUE|SMOOTH_DIAGONAL|SMOOTH_BORDER
+ smoothing_flags = SMOOTH_CORNERS|SMOOTH_DIAGONAL_CORNERS|SMOOTH_BORDER
+ smoothing_groups = null
canSmoothWith = null
+
+#undef NORTH_JUNCTION
+#undef SOUTH_JUNCTION
+#undef EAST_JUNCTION
+#undef WEST_JUNCTION
+#undef NORTHEAST_JUNCTION
+#undef NORTHWEST_JUNCTION
+#undef SOUTHEAST_JUNCTION
+#undef SOUTHWEST_JUNCTION
+
+#undef NO_ADJ_FOUND
+#undef ADJ_FOUND
+#undef NULLTURF_BORDER
+
+#undef DEFAULT_UNDERLAY_ICON
+#undef DEFAULT_UNDERLAY_ICON_STATE
+#undef CAN_DIAGONAL_SMOOTH
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 91ee06e97e2e..f9f710948257 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1134,7 +1134,8 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico
I = A.icon
if (isnull(icon_state))
icon_state = A.icon_state
- if (!(icon_state in icon_states(I, 1)))
+ //Despite casting to atom, this code path supports mutable appearances, so let's be nice to them
+ if(isnull(icon_state) || (isatom(thing) && A.flags_1 & HTML_USE_INITAL_ICON_1))
icon_state = initial(A.icon_state)
if (isnull(dir))
dir = initial(A.dir)
@@ -1230,3 +1231,16 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico
if(scream)
stack_trace("Icon Lookup for state: [state] in file [file] failed.")
return FALSE
+
+/// Returns a list containing the width and height of an icon file
+/proc/get_icon_dimensions(icon_path)
+ // Icons can be a real file(), a rsc backed file(), a dynamic rsc (dyn.rsc) reference (known as a cache reference in byond docs), or an /icon which is pointing to one of those.
+ // Runtime generated dynamic icons are an unbounded concept cache identity wise, the same icon can exist millions of ways and holding them in a list as a key can lead to unbounded memory usage if called often by consumers.
+ // Check distinctly that this is something that has this unspecified concept, and thus that we should not cache.
+ if (!isfile(icon_path) || !length("[icon_path]"))
+ var/icon/my_icon = icon(icon_path)
+ return list("width" = my_icon.Width(), "height" = my_icon.Height())
+ if (isnull(GLOB.icon_dimensions[icon_path]))
+ var/icon/my_icon = icon(icon_path)
+ GLOB.icon_dimensions[icon_path] = list("width" = my_icon.Width(), "height" = my_icon.Height())
+ return GLOB.icon_dimensions[icon_path]
diff --git a/code/__HELPERS/lazy_templates.dm b/code/__HELPERS/lazy_templates.dm
new file mode 100644
index 000000000000..10b9b6dac169
--- /dev/null
+++ b/code/__HELPERS/lazy_templates.dm
@@ -0,0 +1,15 @@
+GLOBAL_LIST_INIT(lazy_templates, generate_lazy_template_map())
+
+/**
+ * Iterates through all lazy template datums that exist and returns a list of them as an associative list of key -> instance.
+ *
+ * Screams if more than one key exists, loudly.
+ */
+/proc/generate_lazy_template_map()
+ . = list()
+ for(var/datum/lazy_template/template as anything in subtypesof(/datum/lazy_template))
+ var/key = initial(template.key)
+ if(key in .)
+ stack_trace("Found multiple lazy templates with the same key! '[key]'")
+ .[key] = new template
+ return .
diff --git a/code/__HELPERS/level_traits.dm b/code/__HELPERS/level_traits.dm
index 380a2e7a56a3..31313c98f493 100644
--- a/code/__HELPERS/level_traits.dm
+++ b/code/__HELPERS/level_traits.dm
@@ -3,14 +3,30 @@
// Basic levels
#define is_centcom_level(z) SSmapping.level_trait(z, ZTRAIT_CENTCOM)
-#define is_station_level(z) SSmapping.level_trait(z, ZTRAIT_STATION)
+GLOBAL_LIST_EMPTY(station_levels_cache)
-#define is_mining_level(z) SSmapping.level_trait(z, ZTRAIT_MINING)
+// Used to prevent z from being re-evaluated
+GLOBAL_VAR(station_level_z_scratch)
-#define is_reebe(z) SSmapping.level_trait(z, ZTRAIT_REEBE)
+// Called a lot, somewhat slow, so has its own cache
+#define is_station_level(z_level) \
+ ( \
+ (z_level) && \
+ ( \
+ /* The right hand side of this guarantees that we'll have the space to fill later on, while also not failing the condition */ \
+ (GLOB.station_levels_cache.len < (GLOB.station_level_z_scratch = (z_level)) && (GLOB.station_levels_cache.len = GLOB.station_level_z_scratch)) \
+ || isnull(GLOB.station_levels_cache[GLOB.station_level_z_scratch]) \
+ ) \
+ ? (GLOB.station_levels_cache[GLOB.station_level_z_scratch] = !!SSmapping.level_trait(GLOB.station_level_z_scratch, ZTRAIT_STATION)) \
+ : GLOB.station_levels_cache[GLOB.station_level_z_scratch] \
+ )
+
+#define is_mining_level(z) SSmapping.level_trait(z, ZTRAIT_MINING)
#define is_reserved_level(z) SSmapping.level_trait(z, ZTRAIT_RESERVED)
#define is_away_level(z) SSmapping.level_trait(z, ZTRAIT_AWAY)
#define is_secret_level(z) SSmapping.level_trait(z, ZTRAIT_SECRET)
+
+#define is_reebe(z) SSmapping.level_trait(z, ZTRAIT_REEBE)
diff --git a/code/__HELPERS/lighting.dm b/code/__HELPERS/lighting.dm
new file mode 100644
index 000000000000..f863e68de1d6
--- /dev/null
+++ b/code/__HELPERS/lighting.dm
@@ -0,0 +1,73 @@
+/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EMISSIVE_COLOR].
+/proc/emissive_appearance(icon, icon_state = "", atom/offset_spokesman, layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE, offset_const)
+ var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, offset_spokesman, EMISSIVE_PLANE, 255, appearance_flags | EMISSIVE_APPEARANCE_FLAGS, offset_const)
+ if(alpha == 255)
+ appearance.color = GLOB.emissive_color
+ else
+ var/alpha_ratio = alpha/255
+ appearance.color = _EMISSIVE_COLOR(alpha_ratio)
+
+ //Test to make sure emissives with broken or missing icon states are created
+ // if(PERFORM_ALL_TESTS(focus_only/invalid_emissives))
+ // if(icon_state && !icon_exists(icon, icon_state, scream = FALSE)) //Scream set to False so we can have a custom stack_trace
+ // stack_trace("An emissive appearance was added with non-existant icon_state \"[icon_state]\" in [icon]!")
+
+ return appearance
+
+// This is a semi hot proc, so we micro it. saves maybe 150ms
+// sorry :)
+/proc/fast_emissive_blocker(atom/make_blocker)
+ var/mutable_appearance/blocker = new()
+ blocker.icon = make_blocker.icon
+ blocker.icon_state = make_blocker.icon_state
+ // blocker.layer = FLOAT_LAYER // Implied, FLOAT_LAYER is default for appearances
+ blocker.appearance_flags |= make_blocker.appearance_flags | EMISSIVE_APPEARANCE_FLAGS
+ blocker.dir = make_blocker.dir
+ if(make_blocker.alpha == 255)
+ blocker.color = GLOB.em_block_color
+ else
+ var/alpha_ratio = make_blocker.alpha/255
+ blocker.color = _EM_BLOCK_COLOR(alpha_ratio)
+
+ // Note, we are ok with null turfs, that's not an error condition we'll just default to 0, the error would be
+ // Not passing ANYTHING in, key difference
+ SET_PLANE_EXPLICIT(blocker, EMISSIVE_PLANE, make_blocker)
+ return blocker
+
+/// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EM_BLOCK_COLOR].
+/proc/emissive_blocker(icon, icon_state = "", atom/offset_spokesman, layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE, offset_const)
+ var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, offset_spokesman, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS, offset_const)
+ if(alpha == 255)
+ appearance.color = GLOB.em_block_color
+ else
+ var/alpha_ratio = alpha/255
+ appearance.color = _EM_BLOCK_COLOR(alpha_ratio)
+ return appearance
+
+/// Takes a non area atom and a threshold
+/// Makes it block emissive with any pixels with more alpha then that threshold, with the rest allowing the light to pass
+/// Returns a list of objects, automatically added to your vis_contents, that apply this effect
+/// QDEL them when appropriate
+/proc/partially_block_emissives(atom/make_blocker, alpha_to_leave)
+ var/static/uid = 0
+ uid++
+ if(!make_blocker.render_target)
+ make_blocker.render_target = "partial_emissive_block_[uid]"
+
+ // 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(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(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(null, alpha_threshold_up)
+ var/list/hand_back = list()
+ hand_back += alpha_threshold_down
+ hand_back += alpha_threshold_up
+ hand_back += em_block
+ // Cast to movable so we can use vis_contents. will work for turfs, but not for areas
+ var/atom/movable/vis_cast = make_blocker
+ vis_cast.vis_contents += hand_back
+ return hand_back
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/logging/_logging.dm
similarity index 87%
rename from code/__HELPERS/_logging.dm
rename to code/__HELPERS/logging/_logging.dm
index d678e57e513d..203f7145731e 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/logging/_logging.dm
@@ -1,20 +1,3 @@
-//wrapper macros for easier grepping
-#define DIRECT_OUTPUT(A, B) A << B
-#define DIRECT_INPUT(A, B) A >> B
-#define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image)
-#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound)
-#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
-#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
-#define READ_FILE(file, text) DIRECT_INPUT(file, text)
-//This is an external call, "true" and "false" are how rust parses out booleans
-#ifdef EXTOOLS_LOGGING
-#define WRITE_LOG(log, text) extools_log_write(log, text, TRUE)
-#define WRITE_LOG_NO_FORMAT(log, text) extools_log_write(log, text, FALSE)
-#else
-#define WRITE_LOG(log, text) rustg_log_write(log, "\[[worldtime2text()]\] [text]", "true")
-#define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false")
-#endif
-
//print a warning message to world.log
#define WARNING(MSG) warning("[MSG] in [__FILE__] at line [__LINE__] src: [UNLINT(src)] usr: [usr].")
/proc/warning(msg)
@@ -255,13 +238,7 @@
/* Close open log handles. This should be called as late as possible, and no logging should hapen after. */
/proc/shutdown_logging()
-#ifdef EXTOOLS_LOGGING
- extools_finalize_logging()
-#else
rustg_log_close_all()
-#endif
-
-
/* Helper procs for building detailed log lines */
/proc/key_name(whom, include_link = null, include_name = TRUE)
@@ -361,3 +338,54 @@
return "([AREACOORD(T)])"
else if(A.loc)
return "(UNKNOWN (?, ?, ?))"
+
+/// Generic logging helper
+/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE)
+ if(!log_globally)
+ return
+
+ var/log_text = "[key_name(src)] [message] [loc_name(src)]"
+ switch(message_type)
+ if(LOG_ATTACK)
+ log_attack(log_text)
+ if(LOG_SAY)
+ log_say(log_text)
+ if(LOG_WHISPER)
+ log_whisper(log_text)
+ if(LOG_EMOTE)
+ log_emote(log_text)
+ if(LOG_DSAY)
+ log_dsay(log_text)
+ if(LOG_PDA)
+ log_pda(log_text)
+ if(LOG_CHAT)
+ log_chat(log_text)
+ if(LOG_COMMENT)
+ log_comment(log_text)
+ if(LOG_TELECOMMS)
+ log_telecomms(log_text)
+ if(LOG_OOC)
+ log_ooc(log_text)
+ if(LOG_ADMIN)
+ log_admin(log_text)
+ if(LOG_ADMIN_PRIVATE)
+ log_admin_private(log_text)
+ if(LOG_ASAY)
+ log_adminsay(log_text)
+ if(LOG_OWNERSHIP)
+ log_game(log_text)
+ if(LOG_GAME)
+ log_game(log_text)
+ if(LOG_MECHA)
+ log_mecha(log_text)
+ if(LOG_SHUTTLE)
+ log_shuttle(log_text)
+ if(LOG_NTSL) // yogs - NTSL log
+ log_ntsl(log_text)
+ if(LOG_LOOC) // yogs - LOOC log
+ log_looc(log_text) // yogs - LOOC log
+ if(LOG_DONATOR) // yogs - Donator log
+ log_donator(log_text) // yogs - Donator log
+ else
+ stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
+ log_game(log_text)
diff --git a/code/__HELPERS/logging/attack.dm b/code/__HELPERS/logging/attack.dm
new file mode 100644
index 000000000000..9340c22d0eee
--- /dev/null
+++ b/code/__HELPERS/logging/attack.dm
@@ -0,0 +1,64 @@
+/**
+ * Log a combat message in the attack log
+ *
+ * 1 argument is the actor performing the action
+ * 2 argument is the target of the action
+ * 3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
+ * 4 is a tool with which the action was made (usually an item)
+ * 5 is any additional text, which will be appended to the rest of the log line
+ */
+/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
+ var/ssource = key_name(user)
+ var/starget = key_name(target)
+
+ var/mob/living/living_target = target
+ var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : ""
+
+ var/sobject = ""
+ if(object)
+ sobject = " with [object]"
+ var/saddition = ""
+ if(addition)
+ saddition = " [addition]"
+
+ var/postfix = "[sobject][saddition][hp]"
+
+ var/message = "has [what_done] [starget][postfix]"
+ user.log_message(message, LOG_ATTACK, color="red")
+
+ if(user != target)
+ var/reverse_message = "has been [what_done] by [ssource][postfix]"
+ target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
+
+/**
+ * log_wound() is for when someone is *attacked* and suffers a wound. Note that this only captures wounds from damage, so smites/forced wounds aren't logged, as well as demotions like cuts scabbing over
+ *
+ * Note that this has no info on the attack that dealt the wound: information about where damage came from isn't passed to the bodypart's damaged proc. When in doubt, check the attack log for attacks at that same time
+ * TODO later: Add logging for healed wounds, though that will require some rewriting of healing code to prevent admin heals from spamming the logs. Not high priority
+ *
+ * Arguments:
+ * * victim- The guy who got wounded
+ * * suffered_wound- The wound, already applied, that we're logging. It has to already be attached so we can get the limb from it
+ * * dealt_damage- How much damage is associated with the attack that dealt with this wound.
+ * * dealt_wound_bonus- The wound_bonus, if one was specified, of the wounding attack
+ * * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present
+ * * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods
+ */
+/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll)
+ if(QDELETED(victim) || !suffered_wound)
+ return
+ var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote?
+
+ if(dealt_damage)
+ message += " | Damage: [dealt_damage]"
+ // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut
+ if(base_roll)
+ message += "(rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])"
+
+ if(dealt_wound_bonus)
+ message += " | WB: [dealt_wound_bonus]"
+
+ if(dealt_bare_wound_bonus)
+ message += " | BWB: [dealt_bare_wound_bonus]"
+
+ victim.log_message(message, LOG_ATTACK, color="blue")
diff --git a/code/__HELPERS/logging/shuttle.dm b/code/__HELPERS/logging/shuttle.dm
new file mode 100644
index 000000000000..4c3cc29ff30e
--- /dev/null
+++ b/code/__HELPERS/logging/shuttle.dm
@@ -0,0 +1,3 @@
+/// Logging for shuttle actions
+/proc/log_shuttle(text)
+ WRITE_LOG(GLOB.world_game_log, "SHUTTLE: [text]")
diff --git a/code/__HELPERS/logging/talk.dm b/code/__HELPERS/logging/talk.dm
new file mode 100644
index 000000000000..5f63b3a3ced6
--- /dev/null
+++ b/code/__HELPERS/logging/talk.dm
@@ -0,0 +1,15 @@
+/// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
+/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null)
+ var/prefix = tag ? "([tag]) " : ""
+ var/suffix = forced_by ? " FORCED by [forced_by]" : ""
+ log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally)
+
+/// Helper for logging of messages with only one sender and receiver
+/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag)
+ if(!tag)
+ stack_trace("Unspecified tag for private message")
+ tag = "UNKNOWN"
+
+ source.log_talk(message, message_type, tag="[tag] to [key_name(target)]")
+ if(source != target)
+ target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
index 5b5144e0cc2d..441a88b36fe9 100644
--- a/code/__HELPERS/maths.dm
+++ b/code/__HELPERS/maths.dm
@@ -4,12 +4,16 @@
return 0
var/dy =(32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y)
var/dx =(32 * end.x + end.pixel_x) - (32 * start.x + start.pixel_x)
- if(!dy)
- return (dx >= 0) ? 90 : 270
- . = arctan(dx/dy)
- if(dy < 0)
+ return delta_to_angle(dx, dy)
+
+/// Calculate the angle produced by a pair of x and y deltas
+/proc/delta_to_angle(x, y)
+ if(!y)
+ return (x >= 0) ? 90 : 270
+ . = arctan(x/y)
+ if(y < 0)
. += 180
- else if(dx < 0)
+ else if(x < 0)
. += 360
/// Angle between two arbitrary points and horizontal line same as [/proc/get_angle]
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm
index 636e4735bc02..2fca8792abe5 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices.dm
@@ -2,60 +2,6 @@
. = new_angle - old_angle
Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
-/atom/proc/SpinAnimation(speed = 1 SECONDS, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
- if(!segments)
- return
- var/segment = 360/segments
- if(!clockwise)
- segment = -segment
- var/list/matrices = list()
- for(var/i in 1 to segments-1)
- var/matrix/M = matrix(transform)
- M.Turn(segment*i)
- matrices += M
- var/matrix/last = matrix(transform)
- matrices += last
-
- speed /= segments
-
- if(parallel)
- animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
- else
- animate(src, transform = matrices[1], time = speed, loops)
- for(var/i in 2 to segments) //2 because 1 is covered above
- animate(transform = matrices[i], time = speed)
- //doesn't have an object argument because this is "Stacking" with the animate call above
- //3 billion% intentional
-
-/atom/proc/DabAnimation(speed = 1, loops = 1, direction = 1 , hold_seconds = 0 , angle = 1 , stay = FALSE) // Hopek 2019
- // By making this in atom/proc everything in the game can potentially dab. You have been warned.
- if(hold_seconds > 9999) // if you need to hold a dab for more than 2 hours intentionally let me know.
- return
- if(hold_seconds > 0)
- hold_seconds = hold_seconds * 10 // Converts seconds to deciseconds
- if(angle == 1) //if angle is 1: random angle. Else take angle
- angle = rand(25,50)
- if(direction == 1) // direciton:: 1 for random pick, 2 for clockwise , 3 for anti-clockwise
- direction = pick(2,3)
- if(direction == 3) // if 3 then counter clockwise
- angle = angle * -1
- if(speed == 1) // if speed is 1 choose random speed from list
- speed = rand(3,5)
-
- // dab matrix here
- var/matrix/DAB_COMMENCE = matrix(transform)
- var/matrix/DAB_RETURN = matrix(transform)
- DAB_COMMENCE.Turn(angle) // dab angle to matrix
-
- // Dab animation
- animate(src, transform = DAB_COMMENCE, time = speed, loops ) // dab to hold angle
- if(hold_seconds > 0)
- sleep(hold_seconds) // time to hold the dab before going back
- if(!stay) // if stay param is true dab doesn't return
- animate(transform = DAB_RETURN, time = speed * 1.5, loops ) // reverse dab to starting position , slower
- //doesn't have an object argument because this is "Stacking" with the animate call above
- //3 billion% intentional
-
//Dumps the matrix data in format a-f
/matrix/proc/tolist()
. = list()
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index c7b3b1cdf5de..1ab59833e0f0 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -1,6 +1,3 @@
-/proc/random_blood_type()
- return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
-
/proc/random_eye_color()
switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino"))
if("brown")
@@ -80,6 +77,14 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/dorsal_tubes, GLOB.dorsal_tubes_list)
if(!GLOB.ethereal_mark_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/ethereal_mark, GLOB.ethereal_mark_list)
+ if(!GLOB.preternis_weathering_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_weathering, GLOB.preternis_weathering_list)
+ if(!GLOB.preternis_antenna_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_antenna, GLOB.preternis_antenna_list)
+ if(!GLOB.preternis_eye_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_eye, GLOB.preternis_eye_list)
+ if(!GLOB.preternis_core_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/preternis_core, GLOB.preternis_core_list)
if(!GLOB.pod_hair_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/pod_hair, GLOB.pod_hair_list)
if(!GLOB.pod_flower_list.len)
@@ -96,7 +101,6 @@
"mcolor" = "#[pick("7F","FF")][pick("7F","FF")][pick("7F","FF")]",
"gradientstyle" = random_hair_gradient_style(10),
"gradientcolor" = "#[pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")]",
- "pretcolor" = GLOB.color_list_preternis[pick(GLOB.color_list_preternis)],
"tail_lizard" = pick(GLOB.tails_list_lizard),
"tail_human" = "None",
"wings" = "None",
@@ -114,6 +118,11 @@
"dome" = pick(GLOB.dome_list),
"dorsal_tubes" = pick(GLOB.dorsal_tubes_list),
"ethereal_mark" = pick(GLOB.ethereal_mark_list),
+ "pretcolor" = pick(GLOB.color_list_preternis),
+ "preternis_weathering" = pick(GLOB.preternis_weathering_list),
+ "preternis_antenna" = pick(GLOB.preternis_antenna_list),
+ "preternis_eye" = pick(GLOB.preternis_eye_list),
+ "preternis_core" = pick(GLOB.preternis_core_list),
"pod_hair" = pick(GLOB.pod_hair_list),
"ipc_screen" = pick(GLOB.ipc_screens_list),
"ipc_antenna" = pick(GLOB.ipc_antennas_list),
diff --git a/code/__HELPERS/piping_colors_lists.dm b/code/__HELPERS/piping_colors_lists.dm
new file mode 100644
index 000000000000..a825555477b1
--- /dev/null
+++ b/code/__HELPERS/piping_colors_lists.dm
@@ -0,0 +1,48 @@
+///All colors available to pipes and atmos components
+GLOBAL_LIST_INIT(pipe_paint_colors, list(
+ "amethyst" = COLOR_AMETHYST, //supplymain
+ "omni" = COLOR_VERY_LIGHT_GRAY,
+ "green" = COLOR_VIBRANT_LIME,
+ "blue" = COLOR_BLUE,
+ "red" = COLOR_RED,
+ "orange" = COLOR_TAN_ORANGE,
+ "cyan" = COLOR_CYAN,
+ "dark" = COLOR_DARK,
+ "yellow" = COLOR_YELLOW,
+ "brown" = COLOR_BROWN,
+ "pink" = COLOR_LIGHT_PINK,
+ "purple" = COLOR_PURPLE,
+ "violet" = COLOR_STRONG_VIOLET,
+))
+
+///List that sorts the colors and is used for setting up the pipes layer so that they overlap correctly
+GLOBAL_LIST_INIT(pipe_colors_ordered, sort_list(list(
+ COLOR_AMETHYST = -6,
+ COLOR_BLUE = -5,
+ COLOR_BROWN = -4,
+ COLOR_CYAN = -3,
+ COLOR_DARK = -2,
+ COLOR_VIBRANT_LIME = -1,
+ COLOR_VERY_LIGHT_GRAY = 0,
+ COLOR_TAN_ORANGE = 1,
+ COLOR_PURPLE = 2,
+ COLOR_RED = 3,
+ COLOR_STRONG_VIOLET = 4,
+ COLOR_YELLOW = 5
+)))
+
+///Names shown in the examine for every colored atmos component
+GLOBAL_LIST_INIT(pipe_color_name, sort_list(list(
+ COLOR_VERY_LIGHT_GRAY = "omni",
+ COLOR_BLUE = "blue",
+ COLOR_RED = "red",
+ COLOR_VIBRANT_LIME = "green",
+ COLOR_TAN_ORANGE = "orange",
+ COLOR_CYAN = "cyan",
+ COLOR_DARK = "dark",
+ COLOR_YELLOW = "yellow",
+ COLOR_BROWN = "brown",
+ COLOR_LIGHT_PINK = "pink",
+ COLOR_PURPLE = "purple",
+ COLOR_STRONG_VIOLET = "violet"
+)))
diff --git a/code/__HELPERS/string_assoc_lists.dm b/code/__HELPERS/string_assoc_lists.dm
new file mode 100644
index 000000000000..606d9eb5038f
--- /dev/null
+++ b/code/__HELPERS/string_assoc_lists.dm
@@ -0,0 +1,17 @@
+GLOBAL_LIST_EMPTY(string_assoc_lists)
+
+/**
+ * Caches associative lists with non-numeric stringify-able index keys and stringify-able values (text/typepath -> text/path/number).
+ */
+/datum/proc/string_assoc_list(list/values)
+ var/list/string_id = list()
+ for(var/val in values)
+ string_id += "[val]_[values[val]]"
+ string_id = string_id.Join("-")
+
+ . = GLOB.string_assoc_lists[string_id]
+
+ if(.)
+ return .
+
+ return GLOB.string_assoc_lists[string_id] = values
diff --git a/code/__HELPERS/string_lists.dm b/code/__HELPERS/string_lists.dm
new file mode 100644
index 000000000000..99ce28fba1df
--- /dev/null
+++ b/code/__HELPERS/string_lists.dm
@@ -0,0 +1,31 @@
+GLOBAL_LIST_EMPTY(string_lists)
+
+/**
+ * Caches lists with non-numeric stringify-able values (text or typepath).
+ */
+/proc/string_list(list/values)
+ var/string_id = values.Join("-")
+
+ . = GLOB.string_lists[string_id]
+
+ if(.)
+ return .
+
+ return GLOB.string_lists[string_id] = values
+
+///A wrapper for baseturf string lists, to offer support of non list values, and a stack_trace if we have major issues
+/proc/baseturfs_string_list(list/values, turf/baseturf_holder)
+ if(!islist(values))
+ return values //baseturf things
+ // return values
+ if(length(values) > 10)
+ stack_trace("The baseturfs list of [baseturf_holder] at [baseturf_holder.x], [baseturf_holder.y], [baseturf_holder.x] is [length(values)], it should never be this long, investigate. I've set baseturfs to a flashing wall as a visual queue")
+ baseturf_holder.ChangeTurf(/turf/closed/indestructible/baseturfs_ded, list(/turf/closed/indestructible/baseturfs_ded), flags = CHANGETURF_FORCEOP)
+ return string_list(list(/turf/closed/indestructible/baseturfs_ded)) //I want this reported god damn it
+ return string_list(values)
+
+/turf/closed/indestructible/baseturfs_ded
+ name = "Report this"
+ desc = "It looks like base turfs went to the fucking moon, TELL YOUR LOCAL CODER TODAY"
+ icon = 'icons/turf/debug.dmi'
+ icon_state = "fucked_baseturfs"
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 99f3c0077adb..6a829ee6086b 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -1,11 +1,11 @@
/*
* Holds procs designed to help with filtering text
* Contains groups:
- * SQL sanitization/formating
- * Text sanitization
- * Text searches
- * Text modification
- * Misc
+ * SQL sanitization/formating
+ * Text sanitization
+ * Text searches
+ * Text modification
+ * Misc
*/
@@ -42,8 +42,16 @@
index = findtext(t, char, index+length(char))
return t
-/proc/sanitize_filename(t)
- return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"=""))
+/proc/sanitize_filename(text)
+ return hashtag_newlines_and_tabs(text, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"=""))
+
+/proc/hashtag_newlines_and_tabs(text, list/repl_chars = list("\n"="#","\t"="#"))
+ for(var/char in repl_chars)
+ var/index = findtext(text, char)
+ while(index)
+ text = copytext(text, 1, index) + repl_chars[char] + copytext(text, index + length(char))
+ index = findtext(text, char, index + length(char))
+ return text
/proc/sanitize_name(t,list/repl_chars = null)
if(t == "space" || t == "floor" || t == "wall" || t == "r-wall" || t == "monkey" || t == "unknown" || t == "inactive ai")
@@ -51,19 +59,25 @@
return ""
return sanitize(t)
-//Runs byond's sanitization proc along-side sanitize_simple
-/proc/sanitize(t,list/repl_chars = null)
- return html_encode(sanitize_simple(t,repl_chars))
+/// Runs byond's html encoding sanitization proc, after replacing new-lines and tabs for the # character.
+/proc/sanitize(text)
+ var/static/regex/regex = regex(@"[\n\t]", "g")
+ return html_encode(regex.Replace(text, "#"))
+
+
+/// Runs STRIP_HTML_SIMPLE and sanitize.
+/proc/strip_html(text, limit = MAX_MESSAGE_LEN)
+ return sanitize(STRIP_HTML_SIMPLE(text, limit))
+
+
+/// Runs STRIP_HTML_FULL and sanitize.
+/proc/strip_html_full(text, limit = MAX_MESSAGE_LEN)
+ return sanitize(STRIP_HTML_FULL(text, limit))
-//Runs sanitize and strip_html_simple
-//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' after sanitize() calls byond's html_encode()
-/proc/strip_html(t,limit=MAX_MESSAGE_LEN)
- return copytext((sanitize(strip_html_simple(t))),1,limit)
-//Runs byond's sanitization proc along-side strip_html_simple
-//I believe strip_html_simple() is required to run first to prevent '<' from displaying as '<' that html_encode() would cause
-/proc/adminscrub(t,limit=MAX_MESSAGE_LEN)
- return copytext((html_encode(strip_html_simple(t))),1,limit)
+/// Runs STRIP_HTML_SIMPLE and byond's sanitization proc.
+/proc/adminscrub(text, limit = MAX_MESSAGE_LEN)
+ return html_encode(STRIP_HTML_SIMPLE(text, limit))
//Returns null if there is any bad text in the string
@@ -94,7 +108,13 @@
return
if(32)
continue
+/* Dripstation edit start - russian cyrillic support
if(127 to INFINITY)
+*/
+ if(127 to 1039)
+ return
+ if(1104 to INFINITY)
+//Dripstation edit end
if(ascii_only)
return
else
@@ -152,7 +172,20 @@
number_of_alphanumeric++
last_char_group = LETTERS_DETECTED
+//Dripstation edit start - russian cyrillic support
+ // А .. Я
+ if(1040 to 1071) //Uppercase Letters
+ number_of_alphanumeric++
+ last_char_group = LETTERS_DETECTED
+
+ // а .. я
+ if(1072 to 1103) //Lowercase Letters
+ if(last_char_group == NO_CHARS_DETECTED || last_char_group == SPACES_DETECTED || last_char_group == SYMBOLS_DETECTED) //start of a word
+ char = uppertext(char)
+ number_of_alphanumeric++
+ last_char_group = LETTERS_DETECTED
+//Dripstation edit end
// 0 .. 9
if(48 to 57) //Numbers
if(last_char_group == NO_CHARS_DETECTED || !allow_numbers) //suppress at start of string
diff --git a/code/__HELPERS/turfs.dm b/code/__HELPERS/turfs.dm
new file mode 100644
index 000000000000..2e17b6ff17ab
--- /dev/null
+++ b/code/__HELPERS/turfs.dm
@@ -0,0 +1,415 @@
+///Returns location. Returns null if no location was found.
+/proc/get_teleport_loc(turf/location, mob/target, distance = 1, density_check = FALSE, closed_turf_check = FALSE, errorx = 0, errory = 0, eoffsetx = 0, eoffsety = 0)
+/*
+Location where the teleport begins, target that will teleport, distance to go, density checking 0/1(yes/no), closed turf checking.
+Random error in tile placement x, error in tile placement y, and block offset.
+Block offset tells the proc how to place the box. Behind teleport location, relative to starting location, forward, etc.
+Negative values for offset are accepted, think of it in relation to North, -x is west, -y is south. Error defaults to positive.
+Turf and target are separate in case you want to teleport some distance from a turf the target is not standing on or something.
+*/
+
+ var/dirx = 0//Generic location finding variable.
+ var/diry = 0
+
+ var/xoffset = 0//Generic counter for offset location.
+ var/yoffset = 0
+
+ var/b1xerror = 0//Generic placing for point A in box. The lower left.
+ var/b1yerror = 0
+ var/b2xerror = 0//Generic placing for point B in box. The upper right.
+ var/b2yerror = 0
+
+ errorx = abs(errorx)//Error should never be negative.
+ errory = abs(errory)
+
+ switch(target.dir)//This can be done through equations but switch is the simpler method. And works fast to boot.
+ //Directs on what values need modifying.
+ if(1)//North
+ diry += distance
+ yoffset += eoffsety
+ xoffset += eoffsetx
+ b1xerror -= errorx
+ b1yerror -= errory
+ b2xerror += errorx
+ b2yerror += errory
+ if(2)//South
+ diry -= distance
+ yoffset -= eoffsety
+ xoffset += eoffsetx
+ b1xerror -= errorx
+ b1yerror -= errory
+ b2xerror += errorx
+ b2yerror += errory
+ if(4)//East
+ dirx += distance
+ yoffset += eoffsetx//Flipped.
+ xoffset += eoffsety
+ b1xerror -= errory//Flipped.
+ b1yerror -= errorx
+ b2xerror += errory
+ b2yerror += errorx
+ if(8)//West
+ dirx -= distance
+ yoffset -= eoffsetx//Flipped.
+ xoffset += eoffsety
+ b1xerror -= errory//Flipped.
+ b1yerror -= errorx
+ b2xerror += errory
+ b2yerror += errorx
+
+ var/turf/destination = locate(location.x+dirx,location.y+diry,location.z)
+
+ if(!destination)//If there isn't a destination.
+ return
+
+ if(!errorx && !errory)//If errorx or y were not specified.
+ if(density_check && destination.density)
+ return
+ if(closed_turf_check && isclosedturf(destination))
+ return//If closed was specified.
+ if(destination.x>world.maxx || destination.x<1)
+ return
+ if(destination.y>world.maxy || destination.y<1)
+ return
+
+ var/destination_list[] = list()//To add turfs to list.
+ //destination_list = new()
+ /*This will draw a block around the target turf, given what the error is.
+ Specifying the values above will basically draw a different sort of block.
+ If the values are the same, it will be a square. If they are different, it will be a rectengle.
+ In either case, it will center based on offset. Offset is position from center.
+ Offset always calculates in relation to direction faced. In other words, depending on the direction of the teleport,
+ the offset should remain positioned in relation to destination.*/
+
+ var/turf/center = locate((destination.x + xoffset), (destination.y + yoffset), location.z)//So now, find the new center.
+
+ //Now to find a box from center location and make that our destination.
+ var/width = (b2xerror - b1xerror) + 1
+ var/height = (b2yerror - b1yerror) + 1
+ for(var/turf/current_turf as anything in CORNER_BLOCK_OFFSET(center, width, height, b1xerror, b1yerror))
+ if(density_check && current_turf.density)
+ continue//If density was specified.
+ if(closed_turf_check && isclosedturf(current_turf))
+ continue//If closed was specified.
+ if(current_turf.x > world.maxx || current_turf.x < 1)
+ continue//Don't want them to teleport off the map.
+ if(current_turf.y > world.maxy || current_turf.y < 1)
+ continue
+ destination_list += current_turf
+
+ if(!destination_list.len)
+ return
+
+ destination = pick(destination_list)
+ return destination
+
+/**
+ * Returns the top-most atom sitting on the turf.
+ * For example, using this on a disk, which is in a bag, on a mob,
+ * will return the mob because it's on the turf.
+ *
+ * Arguments
+ * * something_in_turf - a movable within the turf, somewhere.
+ * * stop_type - optional - stops looking if stop_type is found in the turf, returning that type (if found).
+ **/
+/proc/get_atom_on_turf(atom/movable/something_in_turf, stop_type)
+ if(!istype(something_in_turf))
+ CRASH("get_atom_on_turf was not passed an /atom/movable! Got [isnull(something_in_turf) ? "null":"type: [something_in_turf.type]"]")
+
+ var/atom/movable/topmost_thing = something_in_turf
+
+ while(topmost_thing?.loc && !isturf(topmost_thing.loc))
+ topmost_thing = topmost_thing.loc
+ if(stop_type && istype(topmost_thing, stop_type))
+ break
+
+ return topmost_thing
+
+///Returns the turf located at the map edge in the specified direction relative to target_atom used for mass driver
+/proc/get_edge_target_turf(atom/target_atom, direction)
+ var/turf/target = locate(target_atom.x, target_atom.y, target_atom.z)
+ if(!target_atom || !target)
+ return 0
+ //since NORTHEAST == NORTH|EAST, etc, doing it this way allows for diagonal mass drivers in the future
+ //and isn't really any more complicated
+
+ var/x = target_atom.x
+ var/y = target_atom.y
+ if(direction & NORTH)
+ y = world.maxy
+ else if(direction & SOUTH) //you should not have both NORTH and SOUTH in the provided direction
+ y = 1
+ if(direction & EAST)
+ x = world.maxx
+ else if(direction & WEST)
+ x = 1
+ if(ISDIAGONALDIR(direction)) //let's make sure it's accurately-placed for diagonals
+ var/lowest_distance_to_map_edge = min(abs(x - target_atom.x), abs(y - target_atom.y))
+ return get_ranged_target_turf(target_atom, direction, lowest_distance_to_map_edge)
+ return locate(x,y,target_atom.z)
+
+// returns turf relative to target_atom in given direction at set range
+// result is bounded to map size
+// note range is non-pythagorean
+// used for disposal system
+/proc/get_ranged_target_turf(atom/target_atom, direction, range)
+
+ var/x = target_atom.x
+ var/y = target_atom.y
+ if(direction & NORTH)
+ y = min(world.maxy, y + range)
+ else if(direction & SOUTH)
+ y = max(1, y - range)
+ if(direction & EAST)
+ x = min(world.maxx, x + range)
+ else if(direction & WEST) //if you have both EAST and WEST in the provided direction, then you're gonna have issues
+ x = max(1, x - range)
+
+ return locate(x,y,target_atom.z)
+
+/**
+ * Get ranged target turf, but with direct targets as opposed to directions
+ *
+ * Starts at atom starting_atom and gets the exact angle between starting_atom and target
+ * Moves from starting_atom with that angle, Range amount of times, until it stops, bound to map size
+ * Arguments:
+ * * starting_atom - Initial Firer / Position
+ * * target - Target to aim towards
+ * * range - Distance of returned target turf from starting_atom
+ * * offset - Angle offset, 180 input would make the returned target turf be in the opposite direction
+ */
+/proc/get_ranged_target_turf_direct(atom/starting_atom, atom/target, range, offset)
+ var/angle = ATAN2(target.x - starting_atom.x, target.y - starting_atom.y)
+ if(offset)
+ angle += offset
+ var/turf/starting_turf = get_turf(starting_atom)
+ for(var/i in 1 to range)
+ var/turf/check = locate(starting_atom.x + cos(angle) * i, starting_atom.y + sin(angle) * i, starting_atom.z)
+ if(!check)
+ break
+ starting_turf = check
+
+ return starting_turf
+
+
+/// returns turf relative to target_atom offset in dx and dy tiles, bound to map limits
+/proc/get_offset_target_turf(atom/target_atom, dx, dy)
+ var/x = min(world.maxx, max(1, target_atom.x + dx))
+ var/y = min(world.maxy, max(1, target_atom.y + dy))
+ return locate(x, y, target_atom.z)
+
+/**
+ * Lets the turf this atom's *ICON* appears to inhabit
+ * it takes into account:
+ * Pixel_x/y
+ * Matrix x/y
+ * NOTE: if your atom has non-standard bounds then this proc
+ * will handle it, but:
+ * if the bounds are even, then there are an even amount of "middle" turfs, the one to the EAST, NORTH, or BOTH is picked
+ * this may seem bad, but you're atleast as close to the center of the atom as possible, better than byond's default loc being all the way off)
+ * if the bounds are odd, the true middle turf of the atom is returned
+**/
+/proc/get_turf_pixel(atom/checked_atom)
+ var/turf/atom_turf = get_turf(checked_atom) //use checked_atom's turfs, as it's coords are the same as checked_atom's AND checked_atom's coords are lost if it is inside another atom
+ if(!atom_turf)
+ return null
+
+ var/list/offsets = get_visual_offset(checked_atom)
+ return pixel_offset_turf(atom_turf, offsets)
+
+/**
+ * Returns how visually "off" the atom is from its source turf as a list of x, y (in pixel steps)
+ * it takes into account:
+ * Pixel_x/y
+ * Matrix x/y
+ * Icon width/height
+**/
+/proc/get_visual_offset(atom/checked_atom)
+ //Find checked_atom's matrix so we can use it's X/Y pixel shifts
+ var/matrix/atom_matrix = matrix(checked_atom.transform)
+
+ var/pixel_x_offset = checked_atom.pixel_x + atom_matrix.get_x_shift()
+ var/pixel_y_offset = checked_atom.pixel_y + atom_matrix.get_y_shift()
+
+ //Irregular objects
+ var/list/icon_dimensions = get_icon_dimensions(checked_atom.icon)
+ var/checked_atom_icon_height = icon_dimensions["width"]
+ var/checked_atom_icon_width = icon_dimensions["height"]
+ if(checked_atom_icon_height != world.icon_size || checked_atom_icon_width != world.icon_size)
+ pixel_x_offset += ((checked_atom_icon_width / world.icon_size) - 1) * (world.icon_size * 0.5)
+ pixel_y_offset += ((checked_atom_icon_height / world.icon_size) - 1) * (world.icon_size * 0.5)
+
+ return list(pixel_x_offset, pixel_y_offset)
+
+/**
+ * Takes a turf, and a list of x and y pixel offsets and returns the turf that the offset position best lands in
+**/
+/proc/pixel_offset_turf(turf/offset_from, list/offsets)
+ //DY and DX
+ var/rough_x = round(round(offsets[1], world.icon_size) / world.icon_size)
+ var/rough_y = round(round(offsets[2], world.icon_size) / world.icon_size)
+
+ var/final_x = clamp(offset_from.x + rough_x, 1, world.maxx)
+ var/final_y = clamp(offset_from.y + rough_y, 1, world.maxy)
+
+ if(final_x || final_y)
+ return locate(final_x, final_y, offset_from.z)
+ return offset_from
+
+///Returns a turf based on text inputs, original turf and viewing client
+/proc/parse_caught_click_modifiers(list/modifiers, turf/origin, client/viewing_client)
+ if(!modifiers)
+ return null
+
+ var/screen_loc = splittext(LAZYACCESS(modifiers, SCREEN_LOC), ",")
+ var/list/actual_view = getviewsize(viewing_client ? viewing_client.view : world.view)
+ var/click_turf_x = splittext(screen_loc[1], ":")
+ var/click_turf_y = splittext(screen_loc[2], ":")
+ var/click_turf_z = origin.z
+
+ var/click_turf_px = text2num(click_turf_x[2])
+ var/click_turf_py = text2num(click_turf_y[2])
+ click_turf_x = origin.x + text2num(click_turf_x[1]) - round(actual_view[1] / 2) - 1
+ click_turf_y = origin.y + text2num(click_turf_y[1]) - round(actual_view[2] / 2) - 1
+
+ var/turf/click_turf = locate(clamp(click_turf_x, 1, world.maxx), clamp(click_turf_y, 1, world.maxy), click_turf_z)
+ LAZYSET(modifiers, ICON_X, "[(click_turf_px - click_turf.pixel_x) + ((click_turf_x - click_turf.x) * world.icon_size)]")
+ LAZYSET(modifiers, ICON_Y, "[(click_turf_py - click_turf.pixel_y) + ((click_turf_y - click_turf.y) * world.icon_size)]")
+ return click_turf
+
+///Almost identical to the params_to_turf(), but unused (remove?)
+/proc/screen_loc_to_turf(text, turf/origin, client/C)
+ if(!text)
+ return null
+ var/tZ = splittext(text, ",")
+ var/tX = splittext(tZ[1], "-")
+ var/tY = text2num(tX[2])
+ tX = splittext(tZ[2], "-")
+ tX = text2num(tX[2])
+ tZ = origin.z
+ var/list/actual_view = getviewsize(C ? C.view : world.view)
+ tX = clamp(origin.x + round(actual_view[1] / 2) - tX, 1, world.maxx)
+ tY = clamp(origin.y + round(actual_view[2] / 2) - tY, 1, world.maxy)
+ return locate(tX, tY, tZ)
+
+///similar function to RANGE_TURFS(), but will search spiralling outwards from the center (like the above, but only turfs)
+/proc/spiral_range_turfs(dist = 0, center = usr, orange = FALSE, list/outlist = list(), tick_checked)
+ outlist.Cut()
+ if(!dist)
+ outlist += center
+ return outlist
+
+ var/turf/t_center = get_turf(center)
+ if(!t_center)
+ return outlist
+
+ var/list/turf_list = outlist
+ var/turf/checked_turf
+ var/y
+ var/x
+ var/c_dist = 1
+
+ if(!orange)
+ turf_list += t_center
+
+ while( c_dist <= dist )
+ y = t_center.y + c_dist
+ x = t_center.x - c_dist + 1
+ for(x in x to t_center.x + c_dist)
+ checked_turf = locate(x, y, t_center.z)
+ if(checked_turf)
+ turf_list += checked_turf
+
+ y = t_center.y + c_dist - 1
+ x = t_center.x + c_dist
+ for(y in t_center.y - c_dist to y)
+ checked_turf = locate(x, y, t_center.z)
+ if(checked_turf)
+ turf_list += checked_turf
+
+ y = t_center.y - c_dist
+ x = t_center.x + c_dist - 1
+ for(x in t_center.x - c_dist to x)
+ checked_turf = locate(x, y, t_center.z)
+ if(checked_turf)
+ turf_list += checked_turf
+
+ y = t_center.y - c_dist + 1
+ x = t_center.x - c_dist
+ for(y in y to t_center.y + c_dist)
+ checked_turf = locate(x, y, t_center.z)
+ if(checked_turf)
+ turf_list += checked_turf
+ c_dist++
+ if(tick_checked)
+ CHECK_TICK
+
+ return turf_list
+
+///Returns a random turf on the station
+/proc/get_random_station_turf()
+ var/list/turfs = get_area_turfs(pick(GLOB.the_station_areas))
+ if (length(turfs))
+ return pick(turfs)
+
+///Returns a random turf on the station, excludes dense turfs (like walls) and areas that have valid_territory set to FALSE
+/proc/get_safe_random_station_turf(list/areas_to_pick_from = GLOB.the_station_areas)
+ for (var/i in 1 to 5)
+ var/list/turf_list = get_area_turfs(pick(areas_to_pick_from))
+ var/turf/target
+ while (turf_list.len && !target)
+ var/I = rand(1, turf_list.len)
+ var/turf/checked_turf = turf_list[I]
+ var/area/turf_area = get_area(checked_turf)
+ if(!checked_turf.density && (turf_area.valid_territory) && !isgroundlessturf(checked_turf))
+ var/clear = TRUE
+ for(var/obj/checked_object in checked_turf)
+ if(checked_object.density)
+ clear = FALSE
+ break
+ if(clear)
+ target = checked_turf
+ if (!target)
+ turf_list.Cut(I, I + 1)
+ if (target)
+ return target
+
+/**
+ * Checks whether the target turf is in a valid state to accept a directional construction
+ * such as windows or railings.
+ *
+ * Returns FALSE if the target turf cannot accept a directional construction.
+ * Returns TRUE otherwise.
+ *
+ * Arguments:
+ * * dest_turf - The destination turf to check for existing directional constructions
+ * * test_dir - The prospective dir of some atom you'd like to put on this turf.
+ * * is_fulltile - Whether the thing you're attempting to move to this turf takes up the entire tile or whether it supports multiple movable atoms on its tile.
+ */
+/proc/valid_build_direction(turf/dest_turf, test_dir, is_fulltile = FALSE)
+ if(!dest_turf)
+ return FALSE
+ for(var/obj/turf_content in dest_turf)
+ if(turf_content.obj_flags & BLOCKS_CONSTRUCTION_DIR)
+ if(is_fulltile) // for making it so fulltile things can't be built over directional things--a special case
+ return FALSE
+ if(turf_content.dir == test_dir)
+ return FALSE
+ return TRUE
+
+/**
+ * Checks whether or not a particular typepath or subtype of it is present on a turf
+ *
+ * Returns TRUE if an instance of the desired type or a subtype of it is found
+ * Returns FALSE if the type is not found, or if no turf is supplied
+ *
+ * Arguments:
+ * * location - The turf to be checked for the desired type
+ * * type_to_find - The typepath whose presence you are checking for
+ */
+/proc/is_type_on_turf(turf/location, type_to_find)
+ if(!location)
+ return FALSE
+ if(locate(type_to_find) in location)
+ return TRUE
+ return FALSE
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index d346e7fae044..ff508dabe517 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -34,115 +34,12 @@
/proc/Get_Pixel_Angle(y, x)//for getting the angle when animating something's pixel_x and pixel_y
if(!y)
- return (x>=0)?90:270
- .=arctan(x/y)
- if(y<0)
- .+=180
- else if(x<0)
- .+=360
-
-//Better performant than an artisanal proc and more reliable than Turn(). From TGMC.
-#define REVERSE_DIR(dir) ( ((dir & 85) << 1) | ((dir & 170) >> 1) )
-
-//Returns location. Returns null if no location was found.
-/proc/get_teleport_loc(turf/location,mob/target,distance = 1, density = FALSE, errorx = 0, errory = 0, eoffsetx = 0, eoffsety = 0)
-/*
-Location where the teleport begins, target that will teleport, distance to go, density checking 0/1(yes/no).
-Random error in tile placement x, error in tile placement y, and block offset.
-Block offset tells the proc how to place the box. Behind teleport location, relative to starting location, forward, etc.
-Negative values for offset are accepted, think of it in relation to North, -x is west, -y is south. Error defaults to positive.
-Turf and target are separate in case you want to teleport some distance from a turf the target is not standing on or something.
-*/
-
- var/dirx = 0//Generic location finding variable.
- var/diry = 0
-
- var/xoffset = 0//Generic counter for offset location.
- var/yoffset = 0
-
- var/b1xerror = 0//Generic placing for point A in box. The lower left.
- var/b1yerror = 0
- var/b2xerror = 0//Generic placing for point B in box. The upper right.
- var/b2yerror = 0
-
- errorx = abs(errorx)//Error should never be negative.
- errory = abs(errory)
-
- switch(target.dir)//This can be done through equations but switch is the simpler method. And works fast to boot.
- //Directs on what values need modifying.
- if(1)//North
- diry+=distance
- yoffset+=eoffsety
- xoffset+=eoffsetx
- b1xerror-=errorx
- b1yerror-=errory
- b2xerror+=errorx
- b2yerror+=errory
- if(2)//South
- diry-=distance
- yoffset-=eoffsety
- xoffset+=eoffsetx
- b1xerror-=errorx
- b1yerror-=errory
- b2xerror+=errorx
- b2yerror+=errory
- if(4)//East
- dirx+=distance
- yoffset+=eoffsetx//Flipped.
- xoffset+=eoffsety
- b1xerror-=errory//Flipped.
- b1yerror-=errorx
- b2xerror+=errory
- b2yerror+=errorx
- if(8)//West
- dirx-=distance
- yoffset-=eoffsetx//Flipped.
- xoffset+=eoffsety
- b1xerror-=errory//Flipped.
- b1yerror-=errorx
- b2xerror+=errory
- b2yerror+=errorx
-
- var/turf/destination=locate(location.x+dirx,location.y+diry,location.z)
-
- if(destination)//If there is a destination.
- if(errorx||errory)//If errorx or y were specified.
- var/destination_list[] = list()//To add turfs to list.
- //destination_list = new()
- /*This will draw a block around the target turf, given what the error is.
- Specifying the values above will basically draw a different sort of block.
- If the values are the same, it will be a square. If they are different, it will be a rectengle.
- In either case, it will center based on offset. Offset is position from center.
- Offset always calculates in relation to direction faced. In other words, depending on the direction of the teleport,
- the offset should remain positioned in relation to destination.*/
-
- var/turf/center = locate((destination.x+xoffset),(destination.y+yoffset),location.z)//So now, find the new center.
-
- //Now to find a box from center location and make that our destination.
- for(var/turf/T in block(locate(center.x+b1xerror,center.y+b1yerror,location.z), locate(center.x+b2xerror,center.y+b2yerror,location.z) ))
- if(density&&T.density)
- continue//If density was specified.
- if(T.x>world.maxx || T.x<1)
- continue//Don't want them to teleport off the map.
- if(T.y>world.maxy || T.y<1)
- continue
- destination_list += T
- if(destination_list.len)
- destination = pick(destination_list)
- else
- return
-
- else//Same deal here.
- if(density&&destination.density)
- return
- if(destination.x>world.maxx || destination.x<1)
- return
- if(destination.y>world.maxy || destination.y<1)
- return
- else
- return
-
- return destination
+ return (x >= 0) ? 90 : 270
+ . = arctan(x / y)
+ if(y < 0)
+ . += 180
+ else if(x < 0)
+ . += 360
/proc/getline(atom/M,atom/N)//Ultra-Fast Bresenham Line-Drawing Algorithm
var/px=M.x //starting x
@@ -377,68 +274,6 @@ Turf and target are separate in case you want to teleport some distance from a t
if(M.ckey == key)
return M
-//Returns the atom sitting on the turf.
-//For example, using this on a disk, which is in a bag, on a mob, will return the mob because it's on the turf.
-//Optional arg 'type' to stop once it reaches a specific type instead of a turf.
-/proc/get_atom_on_turf(atom/movable/M, stop_type)
- var/atom/loc = M
- while(loc && loc.loc && !isturf(loc.loc))
- loc = loc.loc
- if(stop_type && istype(loc, stop_type))
- break
- return loc
-
-// returns the turf located at the map edge in the specified direction relative to A
-// used for mass driver
-/proc/get_edge_target_turf(atom/A, direction)
- var/turf/target = locate(A.x, A.y, A.z)
- if(!A || !target)
- return 0
- //since NORTHEAST == NORTH|EAST, etc, doing it this way allows for diagonal mass drivers in the future
- //and isn't really any more complicated
-
- var/x = A.x
- var/y = A.y
- if(direction & NORTH)
- y = world.maxy
- else if(direction & SOUTH) //you should not have both NORTH and SOUTH in the provided direction
- y = 1
- if(direction & EAST)
- x = world.maxx
- else if(direction & WEST)
- x = 1
- if(direction in GLOB.diagonals) //let's make sure it's accurately-placed for diagonals
- var/lowest_distance_to_map_edge = min(abs(x - A.x), abs(y - A.y))
- return get_ranged_target_turf(A, direction, lowest_distance_to_map_edge)
- return locate(x,y,A.z)
-
-// returns turf relative to A in given direction at set range
-// result is bounded to map size
-// note range is non-pythagorean
-// used for disposal system
-/proc/get_ranged_target_turf(atom/A, direction, range)
-
- var/x = A.x
- var/y = A.y
- if(direction & NORTH)
- y = min(world.maxy, y + range)
- else if(direction & SOUTH)
- y = max(1, y - range)
- if(direction & EAST)
- x = min(world.maxx, x + range)
- else if(direction & WEST) //if you have both EAST and WEST in the provided direction, then you're gonna have issues
- x = max(1, x - range)
-
- return locate(x,y,A.z)
-
-
-// returns turf relative to A offset in dx and dy tiles
-// bound to map limits
-/proc/get_offset_target_turf(atom/A, dx, dy)
- var/x = min(world.maxx, max(1, A.x + dx))
- var/y = min(world.maxy, max(1, A.y + dy))
- return locate(x,y,A.z)
-
/*
Gets all contents of contents and returns them all in a list.
*/
@@ -486,6 +321,17 @@ Turf and target are separate in case you want to teleport some distance from a t
if(istype(checked_atom, type))
. += checked_atom
+///Returns a list of all locations (except the area) the movable is within.
+/proc/get_nested_locs(atom/movable/atom_on_location, include_turf = FALSE)
+ . = list()
+ var/atom/location = atom_on_location.loc
+ var/turf/our_turf = get_turf(atom_on_location)
+ while(location && location != our_turf)
+ . += location
+ location = location.loc
+ if(our_turf && include_turf) //At this point, only the turf is left, provided it exists.
+ . += our_turf
+
//Step-towards method of determining whether one atom can see another. Similar to viewers()
/proc/can_see(atom/source, atom/target, length=5) // I couldnt be arsed to do actual raycasting :I This is horribly inaccurate.
var/turf/current = get_turf(source)
@@ -605,53 +451,6 @@ Turf and target are separate in case you want to teleport some distance from a t
else
return zone
-/*
-
- Gets the turf this atom's *ICON* appears to inhabit
- It takes into account:
- * Pixel_x/y
- * Matrix x/y
-
- NOTE: if your atom has non-standard bounds then this proc
- will handle it, but:
- * if the bounds are even, then there are an even amount of "middle" turfs, the one to the EAST, NORTH, or BOTH is picked
- (this may seem bad, but you're atleast as close to the center of the atom as possible, better than byond's default loc being all the way off)
- * if the bounds are odd, the true middle turf of the atom is returned
-
-*/
-
-/proc/get_turf_pixel(atom/AM)
- if(!istype(AM))
- return
-
- //Find AM's matrix so we can use it's X/Y pixel shifts
- var/matrix/M = matrix(AM.transform)
-
- var/pixel_x_offset = AM.pixel_x + M.get_x_shift()
- var/pixel_y_offset = AM.pixel_y + M.get_y_shift()
-
- //Irregular objects
- var/icon/AMicon = icon(AM.icon, AM.icon_state)
- var/AMiconheight = AMicon.Height()
- var/AMiconwidth = AMicon.Width()
- if(AMiconheight != world.icon_size || AMiconwidth != world.icon_size)
- pixel_x_offset += ((AMiconwidth/world.icon_size)-1)*(world.icon_size*0.5)
- pixel_y_offset += ((AMiconheight/world.icon_size)-1)*(world.icon_size*0.5)
-
- //DY and DX
- var/rough_x = round(round(pixel_x_offset,world.icon_size)/world.icon_size)
- var/rough_y = round(round(pixel_y_offset,world.icon_size)/world.icon_size)
-
- //Find coordinates
- var/turf/T = get_turf(AM) //use AM's turfs, as it's coords are the same as AM's AND AM's coords are lost if it is inside another atom
- if(!T)
- return null
- var/final_x = T.x + rough_x
- var/final_y = T.y + rough_y
-
- if(final_x || final_y)
- return locate(final_x, final_y, T.z)
-
//Finds the distance between two atoms, in pixels
//centered = FALSE counts from turf edge to edge
//centered = TRUE counts from turf center to turf center
@@ -967,60 +766,6 @@ B --><-- A
return L
-//similar function to RANGE_TURFS(), but will search spiralling outwards from the center (like the above, but only turfs)
-/proc/spiral_range_turfs(dist=0, center=usr, orange=0, list/outlist = list(), tick_checked)
- outlist.Cut()
- if(!dist)
- outlist += center
- return outlist
-
- var/turf/t_center = get_turf(center)
- if(!t_center)
- return outlist
-
- var/list/L = outlist
- var/turf/T
- var/y
- var/x
- var/c_dist = 1
-
- if(!orange)
- L += t_center
-
- while( c_dist <= dist )
- y = t_center.y + c_dist
- x = t_center.x - c_dist + 1
- for(x in x to t_center.x+c_dist)
- T = locate(x,y,t_center.z)
- if(T)
- L += T
-
- y = t_center.y + c_dist - 1
- x = t_center.x + c_dist
- for(y in t_center.y-c_dist to y)
- T = locate(x,y,t_center.z)
- if(T)
- L += T
-
- y = t_center.y - c_dist
- x = t_center.x + c_dist - 1
- for(x in t_center.x-c_dist to x)
- T = locate(x,y,t_center.z)
- if(T)
- L += T
-
- y = t_center.y - c_dist + 1
- x = t_center.x - c_dist
- for(y in y to t_center.y+c_dist)
- T = locate(x,y,t_center.z)
- if(T)
- L += T
- c_dist++
- if(tick_checked)
- CHECK_TICK
-
- return L
-
/atom/proc/contains(atom/A)
if(!A)
return 0
@@ -1036,30 +781,6 @@ B --><-- A
sleep(duration)
A.cut_overlay(O)
-/proc/get_random_station_turf()
- return safepick(get_area_turfs(pick(GLOB.the_station_areas)))
-
-/proc/get_safe_random_station_turf(list/areas_to_pick_from = GLOB.the_station_areas) //excludes dense turfs (like walls) and areas that have valid_territory set to FALSE
- for (var/i in 1 to 5)
- var/list/L = get_area_turfs(pick(areas_to_pick_from))
- var/turf/target
- while (L.len && !target)
- var/I = rand(1, L.len)
- var/turf/T = L[I]
- var/area/X = get_area(T)
- if(!T.density && X.valid_territory)
- var/clear = TRUE
- for(var/obj/O in T)
- if(O.density)
- clear = FALSE
- break
- if(clear)
- target = T
- if (!target)
- L.Cut(I,I+1)
- if (target)
- return target
-
/proc/get_closest_atom(type, list, source)
var/list/closest_atoms = list()
var/closest_distance
@@ -1121,23 +842,31 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
#define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1)
-//returns the number of ticks slept
+///returns the number of ticks slept
/proc/stoplag(initial_delay)
- if (!Master || !(Master.current_runlevel & RUNLEVELS_DEFAULT))
+ if (!Master || Master.init_stage_completed < INITSTAGE_MAX)
sleep(world.tick_lag)
return 1
if (!initial_delay)
initial_delay = world.tick_lag
+// Unit tests are not the normal environemnt. The mc can get absolutely thigh crushed, and sleeping procs running for ages is much more common
+// We don't want spurious hard deletes off this, so let's only sleep for the requested period of time here yeah?
+#ifdef UNIT_TESTS
+ sleep(initial_delay)
+ return CEILING(DS2TICKS(initial_delay), 1)
+#else
. = 0
var/i = DS2TICKS(initial_delay)
do
- . += CEILING(i*DELTA_CALC, 1)
- sleep(i*world.tick_lag*DELTA_CALC)
+ . += CEILING(i * DELTA_CALC, 1)
+ sleep(i * world.tick_lag * DELTA_CALC)
i *= 2
while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit))
+#endif
#undef DELTA_CALC
+
/proc/flash_color(mob_or_client, flash_color="#960000", flash_time=2 SECONDS)
var/client/C
if(ismob(mob_or_client))
@@ -1209,7 +938,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
name = "INTERNAL DVIEW MOB"
invisibility = 101
density = FALSE
- see_in_dark = 1e6
move_resist = INFINITY
var/ready_to_die = FALSE
@@ -1289,11 +1017,13 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
temp = ((temp + (temp>>3))&29127) % 63 //070707
return temp
-// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
-// If it ever becomes necesary to get a more performant REF(), this lies here in wait
-// #define REF(thing) (thing && istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")
+/**
+ * \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
+ * If it ever becomes necesary to get a more performant REF(), this lies here in wait
+ * #define REF(thing) (thing && isdatum(thing) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : text_ref(thing))
+**/
/proc/REF(input)
- if(istype(input, /datum))
+ if(isdatum(input))
var/datum/thing = input
if(thing.datum_flags & DF_USE_TAG)
if(!thing.tag)
@@ -1301,7 +1031,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
thing.datum_flags &= ~DF_USE_TAG
else
return "\[[url_encode(thing.tag)]\]"
- return "\ref[input]"
+ return text_ref(input)
//returns a GUID like identifier (using a mostly made up record format)
//guids are not on their own suitable for access or security tokens, as most of their bits are predictable.
@@ -1486,4 +1216,3 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
set waitfor = FALSE
return call(source, proctype)(arglist(arguments))
-#define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3]))
diff --git a/code/__byond_version_compat.dm b/code/__byond_version_compat.dm
index 609907e9702f..e4857d7d25f0 100644
--- a/code/__byond_version_compat.dm
+++ b/code/__byond_version_compat.dm
@@ -2,16 +2,16 @@
//Update this whenever you need to take advantage of more recent byond features
#define MIN_COMPILER_VERSION 515
-#define MIN_COMPILER_BUILD 1620
+#define MIN_COMPILER_BUILD 1621
#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM)
//Don't forget to update this part
#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
-#error You need version 515.1620 or higher
+#error You need version 515.1621 or higher
#endif
//If you update these values, update the message in the #error
#define MAX_BYOND_MAJOR 515
-#define MAX_BYOND_MINOR 1620
+#define MAX_BYOND_MINOR 1630
// You can define IGNORE_MAX_BYOND_VERSION to bypass the max version check.
// Note: This will likely break the game, especially any extools/auxtools linkage. Only use if you know what you're doing!
@@ -25,7 +25,7 @@
#if ((DM_VERSION > MAX_BYOND_MAJOR) || (DM_BUILD > MAX_BYOND_MINOR)) && !defined(IGNORE_MAX_BYOND_VERSION)
// Not updating until we fully move to 515
-#error Your version of BYOND is too new to compile this project. Download version 515.1620 at www.byond.com/download/build/515/515.1620_byond.exe
+#error Your version of BYOND is too new to compile this project. Download version 515.1630 at www.byond.com/download/build/515/515.1630_byond.exe
#endif
// 515 split call for external libraries into call_ext
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index ff9cabf129ae..d036b262f9c3 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -32,7 +32,8 @@
// 2 for preloading absolutely everything;
#ifdef LOWMEMORYMODE
-#define FORCE_MAP "_maps/runtimestation.json"
+#define FORCE_MAP "runtimestation"
+#define FORCE_MAP_DIRECTORY "_maps"
#endif
//Additional code for the above flags.
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 2123f6a32d20..df9e0a4ab2e0 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -42,7 +42,14 @@ DEFINE_BITFIELD(sight, list(
))
DEFINE_BITFIELD(obj_flags, list(
+ "BLOCK_Z_IN_DOWN" = BLOCK_Z_IN_DOWN,
+ "BLOCK_Z_IN_UP" = BLOCK_Z_IN_UP,
+ "BLOCK_Z_OUT_DOWN" = BLOCK_Z_OUT_DOWN,
+ "BLOCK_Z_OUT_UP" = BLOCK_Z_OUT_UP,
+ "BLOCKS_CONSTRUCTION_DIR" = BLOCKS_CONSTRUCTION_DIR,
+ "BLOCKS_CONSTRUCTION" = BLOCKS_CONSTRUCTION,
"EMAGGED" = EMAGGED,
+ "IGNORE_DENSITY" = IGNORE_DENSITY,
"IN_USE" = IN_USE,
"CAN_BE_HIT" = CAN_BE_HIT,
"BEING_SHOCKED" = BEING_SHOCKED,
@@ -141,7 +148,7 @@ DEFINE_BITFIELD(movement_type, list(
"FLYING" = FLYING,
"VENTCRAWLING" = VENTCRAWLING,
"FLOATING" = FLOATING,
- "UNSTOPPABLE" = UNSTOPPABLE,
+ "PHASING" = PHASING,
))
DEFINE_BITFIELD(resistance_flags, list(
@@ -156,16 +163,11 @@ DEFINE_BITFIELD(resistance_flags, list(
))
DEFINE_BITFIELD(flags_1, list(
- "NOJAUNT_1" = NOJAUNT_1,
- "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1,
- "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1,
"HEAR_1" = HEAR_1,
"CHECK_RICOCHET_1" = CHECK_RICOCHET_1,
"CONDUCT_1" = CONDUCT_1,
- "NO_LAVA_GEN_1" = NO_LAVA_GEN_1,
"NODECONSTRUCT_1" = NODECONSTRUCT_1,
"ON_BORDER_1" = ON_BORDER_1,
- "NO_RUINS_1" = NO_RUINS_1,
"PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1,
"HOLOGRAM_1" = HOLOGRAM_1,
"TESLA_IGNORE_1" = TESLA_IGNORE_1,
@@ -175,6 +177,17 @@ DEFINE_BITFIELD(flags_1, list(
"RAD_PROTECT_CONTENTS_1" = RAD_PROTECT_CONTENTS_1,
"RAD_NO_CONTAMINATE_1" = RAD_NO_CONTAMINATE_1,
"IS_SPINNING_1" = IS_SPINNING_1,
+ "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1,
+))
+
+DEFINE_BITFIELD(turf_flags, list(
+ "NO_LAVA_GEN" = NO_LAVA_GEN,
+ "NO_RUINS" = NO_RUINS,
+ "NO_RUST" = NO_RUST,
+ "NOJAUNT" = NOJAUNT,
+ "IS_SOLID" = IS_SOLID,
+ "UNUSED_RESERVATION_TURF" = UNUSED_RESERVATION_TURF,
+ "RESERVATION_TURF" = RESERVATION_TURF,
))
DEFINE_BITFIELD(clothing_flags, list(
@@ -198,14 +211,6 @@ DEFINE_BITFIELD(tesla_flags, list(
"TESLA_MACHINE_EXPLOSIVE" = TESLA_MACHINE_EXPLOSIVE,
))
-DEFINE_BITFIELD(smooth, list(
- "SMOOTH_TRUE" = SMOOTH_TRUE,
- "SMOOTH_MORE" = SMOOTH_MORE,
- "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL,
- "SMOOTH_BORDER" = SMOOTH_BORDER,
- "SMOOTH_QUEUED" = SMOOTH_QUEUED,
-))
-
DEFINE_BITFIELD(car_traits, list(
"CAN_KIDNAP" = CAN_KIDNAP,
))
diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm
index b28143130361..9456aca25ba9 100644
--- a/code/_globalvars/configuration.dm
+++ b/code/_globalvars/configuration.dm
@@ -1,3 +1,4 @@
+// See initialization order in /code/game/world.dm
GLOBAL_REAL(config, /datum/controller/configuration)
GLOBAL_DATUM(revdata, /datum/getrev)
diff --git a/code/_globalvars/lighting.dm b/code/_globalvars/lighting.dm
new file mode 100644
index 000000000000..8a245328b98e
--- /dev/null
+++ b/code/_globalvars/lighting.dm
@@ -0,0 +1,152 @@
+GLOBAL_VAR_INIT(light_debug_enabled, FALSE)
+
+/// Global list of all light template types
+GLOBAL_LIST_INIT_TYPED(light_types, /datum/light_template, generate_light_types())
+
+/proc/generate_light_types()
+ var/list/types = list()
+ for(var/datum/light_template/template_path as anything in typesof(/datum/light_template))
+ if(initial(template_path.ignore_type) == template_path)
+ continue
+ var/datum/light_template/template = new template_path()
+ types[template.id] = template
+ return types
+
+/// Light templates. They describe how a light looks, and links that to names/icons that can be used when templating/debugging
+/datum/light_template
+ /// User friendly name, to display clientside
+ var/name = ""
+ /// Description to display to the client
+ var/desc = ""
+ /// Unique id for this template
+ var/id = ""
+ /// What category to put this template in
+ var/category = "UNSORTED"
+ /// Icon to use to display this clientside
+ var/icon = ""
+ /// Icon state to display clientside
+ var/icon_state = ""
+ /// The light range we use
+ var/range = 0
+ /// The light power we use
+ var/power = 0
+ /// The light color we use
+ var/color = ""
+ /// The light angle we use
+ var/angle = 360
+ /// The type to spawn off create()
+ var/spawn_type = /obj
+ /// Do not load this template if its type matches the ignore type
+ /// This lets us do subtypes more nicely
+ var/ignore_type = /datum/light_template
+
+/datum/light_template/New()
+ . = ..()
+ id = replacetext("[type]", "/", "-")
+
+/// Create an atom with our light details
+/datum/light_template/proc/create(atom/location, direction)
+ var/atom/lad = new spawn_type(location)
+ lad.light_flags &= ~LIGHT_FROZEN
+ lad.set_light(range, power, color, angle, l_on = TRUE)
+ lad.setDir(direction)
+
+ lad.light_flags |= LIGHT_FROZEN
+ return lad
+
+/// Template that reads info off a light subtype
+/datum/light_template/read_light
+ ignore_type = /datum/light_template/read_light
+ /// Typepath to pull our icon/state and lighting details from
+ var/obj/machinery/light/path_to_read
+
+/datum/light_template/read_light/New()
+ . = ..()
+ desc ||= "[path_to_read]"
+ icon ||= initial(path_to_read.icon)
+ icon_state ||= initial(path_to_read.icon_state)
+ range = initial(path_to_read.brightness)
+ power = initial(path_to_read.bulb_power)
+ color = initial(path_to_read.bulb_colour)
+ angle = initial(path_to_read.light_angle)
+ spawn_type = path_to_read
+
+/datum/light_template/read_light/standard_bar
+ name = "Light Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light
+
+/datum/light_template/read_light/warm_bar
+ name = "Warm Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/warm
+
+/datum/light_template/read_light/dimwarm_bar
+ name = "Dim Warm Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/warm/dim
+
+/datum/light_template/read_light/cold_bar
+ name = "Cold Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/cold
+
+/datum/light_template/read_light/dimcold_bar
+ name = "Dim Cold Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/cold/dim
+
+/datum/light_template/read_light/red_bar
+ name = "Red Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/red
+
+/datum/light_template/read_light/dimred_bar
+ name = "Dim Red Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/red/dim
+
+/datum/light_template/read_light/blacklight_bar
+ name = "Black Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/blacklight
+
+/datum/light_template/read_light/dim_bar
+ name = "Dim Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/dim
+
+/datum/light_template/read_light/very_dim_bar
+ name = "Very Dim Bar"
+ category = "Bar"
+ path_to_read = /obj/machinery/light/very_dim
+
+/datum/light_template/read_light/standard_bulb
+ name = "Light Bulb"
+ category = "Bulb"
+ path_to_read = /obj/machinery/light/small
+
+/datum/light_template/read_light/dim_bulb
+ name = "Dim Bulb"
+ category = "Bulb"
+ path_to_read = /obj/machinery/light/small/dim
+
+/datum/light_template/read_light/red_bulb
+ name = "Red Bulb"
+ category = "Bulb"
+ path_to_read = /obj/machinery/light/small/red
+
+/datum/light_template/read_light/dimred_bulb
+ name = "Dim-Red Bulb"
+ category = "Bulb"
+ path_to_read = /obj/machinery/light/small/red/dim
+
+/datum/light_template/read_light/blacklight_bulb
+ name = "Black Bulb"
+ category = "Bulb"
+ path_to_read = /obj/machinery/light/small/blacklight
+
+/datum/light_template/read_light/standard_floor
+ name = "Floor Light"
+ category = "Misc"
+ path_to_read = /obj/machinery/light/floor
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index c6304407e18e..d80ecd49ff38 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -58,13 +58,20 @@ GLOBAL_LIST_INIT(plasmaman_helmet_list, list(
GLOBAL_LIST_EMPTY(ethereal_mark_list) //ethereal face marks
-GLOBAL_LIST_INIT(color_list_preternis, list(
+GLOBAL_LIST_EMPTY(preternis_weathering_list) //preternis body weathering
+GLOBAL_LIST_EMPTY(preternis_antenna_list) //preternis head antenna
+GLOBAL_LIST_EMPTY(preternis_eye_list) //preternis eyes
+GLOBAL_LIST_EMPTY(preternis_core_list) //preternis core (only one option, not changeable)
+GLOBAL_LIST_INIT(color_list_preternis, list( //welcome to preternis body colours, where we have colors ranging from gray to grey
"Factory Default" = "#FFFFFF",
- "Rust" = "#B7410E",
- "Chrome" = "#B0C4DE",
- "Overgrown" = "#b2ee69",
- "Gunmetal Gray" = "#8D918D",
- "Gold" = "#D4AF37"))
+ "Stainless Steel" = "#b4bdc7",
+ "Chrome" = "#9cb9df",
+ "Gunmetal Gray" = "#818589",
+ "Bronze" = "#CD7F32",
+ "Silver" = "#C0C0C0",
+ "Gold" = "#FFD700",
+ "Cobalt" = "#5fa1ff"
+ ))//make sure they aren't too dark or it'll just become a mass of one colour
GLOBAL_LIST_EMPTY(pod_hair_list) //ethereal face marks
GLOBAL_LIST_EMPTY(pod_flower_list) //ethereal face marks
diff --git a/code/_globalvars/lists/icons.dm b/code/_globalvars/lists/icons.dm
new file mode 100644
index 000000000000..ff60e6bc8d92
--- /dev/null
+++ b/code/_globalvars/lists/icons.dm
@@ -0,0 +1,2 @@
+/// Cache of the width and height of icon files, to avoid repeating the same expensive operation
+GLOBAL_LIST_EMPTY(icon_dimensions)
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index ac2bea80b983..0256990b69cc 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -803,7 +803,7 @@ GLOBAL_LIST_INIT(maintenance_loot_minor,list(
//Has moderate mechanical usage; stuff that would actually benefit the player with the right situation and the right access.
GLOBAL_LIST_INIT(maintenance_loot_moderate,list(
- /obj/item/aiModule/toyAI = W_MYTHICAL,
+ /obj/item/aiModule/ion/toyAI = W_MYTHICAL,
/obj/item/a_gift/anything = W_UNCOMMON,
/obj/item/aicard/aitater = W_RARE,
/obj/item/ammo_box/foambox/riot = W_RARE,
diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm
index 658b59a2df2a..a2f90a9b4917 100644
--- a/code/_globalvars/lists/mapping.dm
+++ b/code/_globalvars/lists/mapping.dm
@@ -1,24 +1,111 @@
-GLOBAL_LIST_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST))
-GLOBAL_LIST_INIT(cardinals_multiz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN))
-GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
-GLOBAL_LIST_INIT(corners_multiz, list(UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST))
+GLOBAL_LIST_INIT(cardinals, list(
+ NORTH,
+ SOUTH,
+ EAST,
+ WEST,
+))
+GLOBAL_LIST_INIT(cardinals_multiz, list(
+ NORTH,
+ SOUTH,
+ EAST,
+ WEST,
+ UP,
+ DOWN,
+))
+GLOBAL_LIST_INIT(diagonals, list(
+ NORTHEAST,
+ NORTHWEST,
+ SOUTHEAST,
+ SOUTHWEST,
+))
+GLOBAL_LIST_INIT(corners_multiz, list(
+ UP|NORTHEAST,
+ UP|NORTHWEST,
+ UP|SOUTHEAST,
+ UP|SOUTHWEST,
+ DOWN|NORTHEAST,
+ DOWN|NORTHWEST,
+ DOWN|SOUTHEAST,
+ DOWN|SOUTHWEST,
+))
GLOBAL_LIST_INIT(diagonals_multiz, list(
- NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST,
- UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST,
- DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST))
+ NORTHEAST,
+ NORTHWEST,
+ SOUTHEAST,
+ SOUTHWEST,
+
+ UP|NORTH,
+ UP|SOUTH,
+ UP|EAST,
+ UP|WEST,
+ UP|NORTHEAST,
+ UP|NORTHWEST,
+ UP|SOUTHEAST,
+ UP|SOUTHWEST,
+
+ DOWN|NORTH,
+ DOWN|SOUTH,
+ DOWN|EAST,
+ DOWN|WEST,
+ DOWN|NORTHEAST,
+ DOWN|NORTHWEST,
+ DOWN|SOUTHEAST,
+ DOWN|SOUTHWEST,
+))
GLOBAL_LIST_INIT(alldirs_multiz, list(
- NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST,
- UP, UP|NORTH, UP|SOUTH, UP|EAST, UP|WEST, UP|NORTHEAST, UP|NORTHWEST, UP|SOUTHEAST, UP|SOUTHWEST,
- DOWN, DOWN|NORTH, DOWN|SOUTH, DOWN|EAST, DOWN|WEST, DOWN|NORTHEAST, DOWN|NORTHWEST, DOWN|SOUTHEAST, DOWN|SOUTHWEST))
-GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
+ NORTH,
+ SOUTH,
+ EAST,
+ WEST,
+ NORTHEAST,
+ NORTHWEST,
+ SOUTHEAST,
+ SOUTHWEST,
-GLOBAL_LIST_EMPTY(landmarks_list) //list of all landmarks created
-GLOBAL_LIST_EMPTY(start_landmarks_list) //list of all spawn points created
-GLOBAL_LIST_EMPTY(department_security_spawns) //list of all department security spawns
+ UP,
+ UP|NORTH,
+ UP|SOUTH,
+ UP|EAST,
+ UP|WEST,
+ UP|NORTHEAST,
+ UP|NORTHWEST,
+ UP|SOUTHEAST,
+ UP|SOUTHWEST,
-GLOBAL_LIST_EMPTY(generic_event_spawns) //handles clockwork portal+eminence teleport destinations
-GLOBAL_LIST_EMPTY(jobspawn_overrides) //These will take precedence over normal spawnpoints if created.
-GLOBAL_LIST_EMPTY(stationroom_landmarks) //yogs - list of all spawns for stationrooms
+ DOWN,
+ DOWN|NORTH,
+ DOWN|SOUTH,
+ DOWN|EAST,
+ DOWN|WEST,
+ DOWN|NORTHEAST,
+ DOWN|NORTHWEST,
+ DOWN|SOUTHEAST,
+ DOWN|SOUTHWEST,
+))
+GLOBAL_LIST_INIT(alldirs, list(
+ NORTH,
+ SOUTH,
+ EAST,
+ WEST,
+ NORTHEAST,
+ NORTHWEST,
+ SOUTHEAST,
+ SOUTHWEST,
+))
+
+/// list of all landmarks created
+GLOBAL_LIST_EMPTY(landmarks_list)
+/// list of all job spawn points created
+GLOBAL_LIST_EMPTY(start_landmarks_list)
+/// list of all department security spawns
+GLOBAL_LIST_EMPTY(department_security_spawns)
+/// List of generic landmarks placed around the map where there are likely to be players and are identifiable at a glance -
+/// Such as public hallways, department rooms, head of staff offices, and non-generic maintenance locations
+GLOBAL_LIST_EMPTY(generic_event_spawns)
+/// Assoc list of "job titles" to "job landmarks"
+/// These will take precedence over normal job spawnpoints if created,
+/// essentially allowing a user to override generic job spawnpoints with a specific one
+GLOBAL_LIST_EMPTY(jobspawn_overrides)
GLOBAL_LIST_EMPTY(wizardstart)
GLOBAL_LIST_EMPTY(nukeop_start)
@@ -33,19 +120,16 @@ GLOBAL_LIST_EMPTY(tdomeobserve)
GLOBAL_LIST_EMPTY(tdomeadmin)
GLOBAL_LIST_EMPTY(prisonwarped) //list of players already warped
GLOBAL_LIST_EMPTY(blobstart) //stationloving objects, blobs, santa, respawning devils
+GLOBAL_LIST_EMPTY(navigate_destinations) //list of all destinations used by the navigate verb
GLOBAL_LIST_EMPTY(secequipment) //sec equipment lockers that scale with the number of sec players
GLOBAL_LIST_EMPTY(deathsquadspawn)
GLOBAL_LIST_EMPTY(emergencyresponseteamspawn)
-GLOBAL_LIST_EMPTY(servant_spawns) //Servants of Ratvar spawn here
-GLOBAL_LIST_EMPTY(servant_spawns_scarabs) //Servants of Ratvar spawn here
-GLOBAL_LIST_EMPTY(city_of_cogs_spawns) //Anyone entering the City of Cogs spawns here
-GLOBAL_LIST_EMPTY(brazil_reception) //teleport receive spots for heretic sacrifices
GLOBAL_LIST_EMPTY(ruin_landmarks)
-GLOBAL_LIST_EMPTY(delta_areas)
GLOBAL_LIST_EMPTY(bar_areas)
-// IF YOU ARE MAKING A NEW TEMPLATE AND WANT IT ROUNDSTART ADD IT TO THIS LIST!
-GLOBAL_LIST_INIT(potential_box_clerk, list("Clerk Box", "Clerk Pod", "Clerk Meta", "Clerk Gambling Hall"))
-GLOBAL_LIST_INIT(potential_box_chapels, list("Chapel 1", "Chapel 2"))
+
+/// List of all the maps that have been cached for /proc/load_map
+GLOBAL_LIST_EMPTY(cached_maps)
+
/// Away missions
GLOBAL_LIST_EMPTY(awaydestinations) //a list of landmarks that the warpgate can take you to
GLOBAL_LIST_EMPTY(vr_spawnpoints)
@@ -68,3 +152,32 @@ GLOBAL_LIST_INIT(megafauna_spawn_list, list(
/mob/living/simple_animal/hostile/megafauna/dragon = 4,
/mob/living/simple_animal/hostile/megafauna/stalwart = 3,
))
+
+//Yog Vars
+///list of all spawns for stationrooms
+GLOBAL_LIST_EMPTY(stationroom_landmarks)
+
+///Servants of Ratvar spawn here
+GLOBAL_LIST_EMPTY(servant_spawns)
+///Servants of Ratvar spawn here
+GLOBAL_LIST_EMPTY(servant_spawns_scarabs)
+///Anyone entering the City of Cogs spawns here
+GLOBAL_LIST_EMPTY(city_of_cogs_spawns)
+///teleport receive spots for heretic sacrifices
+GLOBAL_LIST_EMPTY(brazil_reception)
+GLOBAL_LIST_EMPTY(delta_areas)
+
+GLOBAL_LIST_EMPTY(bar_landmarks)
+GLOBAL_LIST_INIT(potential_box_bars, list(
+ "Bar Trek", "Bar Spacious", "Bar Box", "Bar Casino", "Bar Citadel",
+ "Bar Conveyor", "Bar Diner", "Bar Disco", "Bar Purple", "Bar Cheese",
+ "Bar Grassy", "Bar Clock", "Bar Arcade"))
+
+GLOBAL_LIST_EMPTY(clerk_office_landmarks)
+// IF YOU ARE MAKING A NEW TEMPLATE AND WANT IT ROUNDSTART ADD IT TO THIS LIST!
+GLOBAL_LIST_INIT(potential_box_clerk, list(
+ "Clerk Box", "Clerk Pod", "Clerk Meta", "Clerk Gambling Hall"))
+
+GLOBAL_LIST_EMPTY(chapel_landmarks)
+GLOBAL_LIST_INIT(potential_box_chapels, list(
+ "Chapel 1", "Chapel 2"))
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index f0b9240d33e0..43af6444708b 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -7,6 +7,7 @@ GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys,
//This is for procs to replace all the goddamn 'in world's that are chilling around the code
GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**.
+GLOBAL_LIST_EMPTY(keyloop_list) //as above but can be limited to boost performance
GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless
GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob
GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player
@@ -14,6 +15,7 @@ GLOBAL_LIST_EMPTY(suicided_mob_list) //contains a list of all mobs that suicide
GLOBAL_LIST_EMPTY(drones_list)
GLOBAL_LIST_EMPTY(dead_mob_list) //all dead mobs, including clientless. Excludes /mob/dead/new_player
GLOBAL_LIST_EMPTY(joined_player_list) //all clients that have joined the game at round-start or as a latejoin.
+GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done.
GLOBAL_LIST_EMPTY(silicon_mobs) //all silicon mobs
GLOBAL_LIST_EMPTY(mob_living_list) //all instances of /mob/living and subtypes
GLOBAL_LIST_EMPTY(carbon_list) //all instances of /mob/living/carbon and subtypes, notably does not contain brains or simple animals
@@ -24,7 +26,6 @@ GLOBAL_LIST_INIT(simple_animals, list(list(),list(),list(),list())) // One for e
GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs
GLOBAL_LIST_EMPTY(bots_list)
GLOBAL_LIST_EMPTY(aiEyes)
-GLOBAL_LIST_EMPTY(new_player_list) //all /mob/dead/new_player, in theory all should have clients and those that don't are in the process of spawning and get deleted when done.
///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam
GLOBAL_LIST_EMPTY(narcd_underages)
@@ -39,6 +40,14 @@ GLOBAL_LIST_EMPTY(mob_config_movespeed_type_lookup)
GLOBAL_LIST_EMPTY(emote_list)
+GLOBAL_LIST_INIT(blood_types, generate_blood_types())
+
+/proc/generate_blood_types()
+ . = list()
+ for(var/path in subtypesof(/datum/blood_type))
+ var/datum/blood_type/new_type = new path()
+ .[new_type.name] = new_type
+
/// Keys are the names of the accents, values are the name of their .json file.
GLOBAL_LIST_INIT(accents_name2file, strings("accents.json", "accent_file_names", directory = "strings/accents"))
/// List of all accents
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index ae8fe73ed59d..05ab047dc869 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -5,7 +5,6 @@ GLOBAL_LIST_EMPTY(mechas_list) //list of all mechs. Used by hostile m
GLOBAL_LIST_EMPTY(shuttle_caller_list) //list of all communication consoles and AIs, for automatic shuttle calls when there are none.
GLOBAL_LIST_EMPTY(machines) //NOTE: this is a list of ALL machines now. The processing machines list is SSmachine.processing !
GLOBAL_LIST_EMPTY(lights) //list of all light bulbs
-GLOBAL_LIST_EMPTY(navigation_computers) //list of all /obj/machinery/computer/camera_advanced/shuttle_docker
GLOBAL_LIST_EMPTY(syndicate_shuttle_boards) //important to keep track of for managing nukeops war declarations.
GLOBAL_LIST_EMPTY(navbeacons) //list of all bot nagivation beacons, used for patrolling.
GLOBAL_LIST_EMPTY(teleportbeacons) //list of all tracking beacons used by teleporters
@@ -34,6 +33,7 @@ GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection org
GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors.
GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers
GLOBAL_LIST_EMPTY(ladders)
+GLOBAL_LIST_EMPTY(stairs)
GLOBAL_LIST_EMPTY(trophy_cases)
GLOBAL_LIST_EMPTY(wire_color_directory)
diff --git a/code/_globalvars/regexes.dm b/code/_globalvars/regexes.dm
index 86c41cc7ab69..934296fe1bcb 100644
--- a/code/_globalvars/regexes.dm
+++ b/code/_globalvars/regexes.dm
@@ -6,3 +6,18 @@ GLOBAL_DATUM_INIT(is_email, /regex, regex("\[a-z0-9_-]+@\[a-z0-9_-]+.\[a-z0-9_-]
GLOBAL_DATUM_INIT(is_alphanumeric, /regex, regex("\[a-z0-9]+", "i"))
GLOBAL_DATUM_INIT(is_punctuation, /regex, regex("\[.!?]+", "i"))
GLOBAL_DATUM_INIT(is_color, /regex, regex("^#\[0-9a-fA-F]{6}$"))
+GLOBAL_DATUM_INIT(is_alpha_color, /regex, regex("^#\[0-9a-fA-F]{8}$"))
+
+//finds text strings recognized as links on discord. Mainly used to stop embedding.
+GLOBAL_DATUM_INIT(has_discord_embeddable_links, /regex, regex("(https?://\[^\\s|<\]{2,})"))
+
+//All < and > characters
+GLOBAL_DATUM_INIT(angular_brackets, /regex, regex(@"[<>]", "g"))
+
+//All characters between < a > inclusive of the bracket
+GLOBAL_DATUM_INIT(html_tags, /regex, regex(@"<.*?>", "g"))
+
+//All characters forbidden by filenames: ", \, \n, \t, /, ?, %, *, :, |, <, >, ..
+GLOBAL_DATUM_INIT(filename_forbidden_chars, /regex, regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g"))
+GLOBAL_PROTECT(filename_forbidden_chars)
+// had to use the OR operator for quotes instead of putting them in the character class because it breaks the syntax highlighting otherwise.
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
deleted file mode 100644
index 6b17e39cec8b..000000000000
--- a/code/_globalvars/traits.dm
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- FUN ZONE OF ADMIN LISTINGS
- Try to keep this in sync with __DEFINES/traits.dm
- quirks have it's own panel so we don't need them here.
-*/
-GLOBAL_LIST_INIT(traits_by_type, list(
- /mob = list(/atom/movable = list(
- "TRAIT_MOVE_PHASING" = TRAIT_MOVE_PHASING,
- ))))
-/// value -> trait name, generated on use from trait_by_type global
-GLOBAL_LIST(trait_name_map)
-
-/proc/generate_trait_name_map()
- . = list()
- for(var/key in GLOB.traits_by_type)
- for(var/tname in GLOB.traits_by_type[key])
- var/val = GLOB.traits_by_type[key][tname]
- .[val] = tname
-
-GLOBAL_LIST_INIT(movement_type_trait_to_flag, list(
- TRAIT_MOVE_GROUND = GROUND,
- TRAIT_MOVE_FLYING = FLYING,
- TRAIT_MOVE_VENTCRAWLING = VENTCRAWLING,
- TRAIT_MOVE_FLOATING = FLOATING,
- ))
-
-GLOBAL_LIST_INIT(movement_type_addtrait_signals, set_movement_type_addtrait_signals())
-GLOBAL_LIST_INIT(movement_type_removetrait_signals, set_movement_type_removetrait_signals())
-
-/proc/set_movement_type_addtrait_signals(signal_prefix)
- . = list()
- for(var/trait in GLOB.movement_type_trait_to_flag)
- . += SIGNAL_ADDTRAIT(trait)
-
-/proc/set_movement_type_removetrait_signals(signal_prefix)
- . = list()
- for(var/trait in GLOB.movement_type_trait_to_flag)
- . += SIGNAL_REMOVETRAIT(trait)
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
new file mode 100644
index 000000000000..df92921c6d31
--- /dev/null
+++ b/code/_globalvars/traits/_traits.dm
@@ -0,0 +1,26 @@
+// This file should contain every single global trait in the game in a type-based list, as well as any additional trait-related information that's useful to have on a global basis.
+// This file is used in linting, so make sure to add everything alphabetically and what-not.
+// Do consider adding your trait entry to the similar list in `admin_tooling.dm` if you want it to be accessible to admins (which is probably the case for 75% of traits).
+
+// Please do note that there is absolutely no bearing on what traits are added to what subtype of `/datum`, this is just an easily referenceable list sorted by type.
+// The only thing that truly matters about traits is the code that is built to handle the traits, and where that code is located. Nothing else.
+
+GLOBAL_LIST_INIT(movement_type_trait_to_flag, list(
+ TRAIT_MOVE_GROUND = GROUND,
+ TRAIT_MOVE_FLYING = FLYING,
+ TRAIT_MOVE_VENTCRAWLING = VENTCRAWLING,
+ TRAIT_MOVE_FLOATING = FLOATING,
+ ))
+
+GLOBAL_LIST_INIT(movement_type_addtrait_signals, set_movement_type_addtrait_signals())
+GLOBAL_LIST_INIT(movement_type_removetrait_signals, set_movement_type_removetrait_signals())
+
+/proc/set_movement_type_addtrait_signals(signal_prefix)
+ . = list()
+ for(var/trait in GLOB.movement_type_trait_to_flag)
+ . += SIGNAL_ADDTRAIT(trait)
+
+/proc/set_movement_type_removetrait_signals(signal_prefix)
+ . = list()
+ for(var/trait in GLOB.movement_type_trait_to_flag)
+ . += SIGNAL_REMOVETRAIT(trait)
diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm
new file mode 100644
index 000000000000..416a533d5d7d
--- /dev/null
+++ b/code/_globalvars/traits/admin_tooling.dm
@@ -0,0 +1,44 @@
+// This file contains any stuff related to admin-visible traits.
+// There's likely more than a few traits missing from this file, do consult the `_traits.dm` file in this folder to see every global trait that exists.
+// quirks have it's own panel so we don't need them here.
+
+GLOBAL_LIST_INIT(admin_visible_traits, list(
+ /atom/movable = list(
+ "TRAIT_ASHSTORM_IMMUNE" = TRAIT_ASHSTORM_IMMUNE,
+ "TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN,
+ ),
+ /mob = list(
+ "TRAIT_ABDUCTOR_SCIENTIST_TRAINING" = TRAIT_ABDUCTOR_SCIENTIST_TRAINING,
+ "TRAIT_ANTIMAGIC" = TRAIT_ANTIMAGIC,
+ "TRAIT_BOMBIMMUNE" = TRAIT_BOMBIMMUNE,
+ "TRAIT_DEAF" = TRAIT_DEAF,
+ "TRAIT_HOLY" = TRAIT_HOLY,
+ "TRAIT_NO_SOUL" = TRAIT_NO_SOUL,
+ ),
+ /obj/item = list(
+ "TRAIT_NODROP" = TRAIT_NODROP,
+ "TRAIT_T_RAY_VISIBLE" = TRAIT_T_RAY_VISIBLE,
+ ),
+ /obj/item/organ/liver = list(
+ "TRAIT_BALLMER_SCIENTIST" = TRAIT_BALLMER_SCIENTIST,
+ ),
+))
+GLOBAL_LIST_INIT(traits_by_type, list(
+ /mob = list(/atom/movable = list(
+ "TRAIT_MOVE_PHASING" = TRAIT_MOVE_PHASING,
+ ))))
+/// value -> trait name, generated on use from trait_by_type global
+GLOBAL_LIST(trait_name_map)
+
+/// value -> trait name, generated as needed for adminning.
+GLOBAL_LIST(admin_trait_name_map)
+
+/proc/generate_admin_trait_name_map()
+ . = list()
+ for(var/key in GLOB.admin_visible_traits)
+ for(var/tname in GLOB.admin_visible_traits[key])
+ var/val = GLOB.admin_visible_traits[key][tname]
+ .[val] = tname
+
+ return .
+
diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm
index c9746fae9806..9c7b30f163e5 100644
--- a/code/_onclick/adjacent.dm
+++ b/code/_onclick/adjacent.dm
@@ -11,7 +11,7 @@
to check that the mob is not inside of something
*/
/atom/proc/Adjacent(atom/neighbor) // basic inheritance, unused
- return 0
+ return
// Not a sane use of the function and (for now) indicative of an error elsewhere
/area/Adjacent(atom/neighbor)
@@ -73,6 +73,13 @@
return FALSE
if(T.Adjacent(neighbor,target = neighbor, mover = src))
return TRUE
+
+ ///Yog code for spacepods I guess???
+ if((islist(locs) && locs.len > 1) && (bound_width != world.icon_size || bound_height != world.icon_size))
+ for(var/turf/place in locs) //this is to handle multi tile objects
+ if(place.Adjacent(neighbor, src, src))
+ return TRUE
+
return FALSE
// This is necessary for storage items not on your person.
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index f4bd68cd15e9..40a08fb1a167 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -474,6 +474,16 @@
M.Scale(px/sx, py/sy)
transform = M
+/atom/movable/screen/click_catcher/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(offset_increased))
+ offset_increased(SSmapping, 0, SSmapping.max_plane_offset)
+
+// Draw to the lowest plane level offered
+/atom/movable/screen/click_catcher/proc/offset_increased(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ SET_PLANE_W_SCALAR(src, initial(plane), new_offset)
+
/atom/movable/screen/click_catcher/Click(location, control, params)
var/list/modifiers = params2list(params)
if(modifiers["middle"] && iscarbon(usr))
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 7affa36451bf..dbb1e100081c 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -450,7 +450,7 @@ GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,
/atom/movable/screen/action_landing/Destroy()
if(owner)
owner.landing = null
- owner?.owner?.mymob?.client?.screen -= src
+ owner?.owner?.mymob?.canon_client?.screen -= src
owner.refresh_actions()
owner = null
return ..()
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index bd31a0a94bd3..b33efb8a56d8 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -107,7 +107,7 @@
AI.ai_call_shuttle()
/atom/movable/screen/ai/state_laws
- name = "State Laws"
+ name = "Law Manager"
icon_state = "state_laws"
/atom/movable/screen/ai/state_laws/Click()
@@ -177,7 +177,7 @@
/atom/movable/screen/ai/add_multicam/Click()
if(..())
- return
+ return
var/mob/living/silicon/ai/AI = usr
AI.drop_new_multicam()
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 3b25d3aa0a70..7da11d599b81 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -18,13 +18,17 @@
if(!category || QDELETED(src))
return
+ var/datum/weakref/master_ref
+ if(isdatum(new_master))
+ master_ref = WEAKREF(new_master)
var/atom/movable/screen/alert/thealert
if(alerts[category])
thealert = alerts[category]
if(thealert.override_alerts)
return 0
- if(new_master && new_master != thealert.master)
- WARNING("[src] threw alert [category] with new_master [new_master] while already having that alert with master [thealert.master]")
+ if(master_ref && thealert.master_ref && master_ref != thealert.master_ref)
+ var/datum/current_master = thealert.master_ref.resolve()
+ WARNING("[src] threw alert [category] with new_master [new_master] while already having that alert with master [current_master]")
clear_alert(category)
return .()
@@ -53,7 +57,7 @@
new_master.layer = old_layer
new_master.plane = old_plane
thealert.icon_state = "template" // We'll set the icon to the client's ui pref in reorganize_alerts()
- thealert.master = new_master
+ thealert.master_ref = master_ref
else
thealert.icon_state = "[initial(thealert.icon_state)][severity]"
thealert.severity = severity
@@ -99,6 +103,8 @@
var/override_alerts = FALSE //If it is overriding other alerts of the same type
var/mob/mob_viewer //the mob viewing this alert
+ /// Boolean. If TRUE, the Click() proc will attempt to Click() on the master first if there is a master.
+ var/click_master = TRUE
/atom/movable/screen/alert/MouseEntered(location,control,params)
if(!QDELETED(src))
@@ -650,10 +656,12 @@ so as to remain in compliance with the most up-to-date laws."
/atom/movable/screen/alert/restrained/handcuffed
name = "Handcuffed"
desc = "You're handcuffed and can't act. If anyone drags you, you won't be able to move. Click the alert to free yourself."
+ click_master = FALSE
/atom/movable/screen/alert/restrained/legcuffed
name = "Legcuffed"
desc = "You're legcuffed, which slows you down considerably. Click the alert to free yourself."
+ click_master = FALSE
/atom/movable/screen/alert/restrained/Click()
var/mob/living/L = usr
@@ -726,12 +734,13 @@ so as to remain in compliance with the most up-to-date laws."
if(paramslist["shift"]) // screen objects don't do the normal Click() stuff so we'll cheat
to_chat(usr, "[span_boldnotice("[name]")] - [span_info("[desc]")]")
return
- if(master)
- return usr.client.Click(master, location, control, params)
+ var/datum/our_master = master_ref?.resolve()
+ if(our_master && click_master)
+ return usr.client.Click(our_master, location, control, params)
/atom/movable/screen/alert/Destroy()
. = ..()
severity = 0
- master = null
+ master_ref = null
mob_viewer = null
screen_loc = ""
diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm
index 8a1c0a940299..d16cd8493823 100644
--- a/code/_onclick/hud/blob_overmind.dm
+++ b/code/_onclick/hud/blob_overmind.dm
@@ -131,8 +131,7 @@
blobpwrdisplay.icon_state = "block"
blobpwrdisplay.screen_loc = ui_health
blobpwrdisplay.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- blobpwrdisplay.layer = ABOVE_HUD_LAYER
- blobpwrdisplay.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(blobpwrdisplay, ABOVE_HUD_PLANE, owner)
infodisplay += blobpwrdisplay
healths = new /atom/movable/screen/healths/blob(src)
diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm
index 68470eb14c0b..bb48756d3b0a 100644
--- a/code/_onclick/hud/credits.dm
+++ b/code/_onclick/hud/credits.dm
@@ -34,7 +34,7 @@ GLOBAL_LIST(end_titles)
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
alpha = 0
screen_loc = "2,2"
- layer = SPLASHSCREEN_LAYER
+ plane = SPLASHSCREEN_PLANE
var/matrix/target
/atom/movable/screen/credit/Initialize(mapload, credited)
diff --git a/code/_onclick/hud/devil.dm b/code/_onclick/hud/devil.dm
index 618aa89189f4..ddbcbd5818d9 100644
--- a/code/_onclick/hud/devil.dm
+++ b/code/_onclick/hud/devil.dm
@@ -24,7 +24,7 @@
using.icon = ui_style
using.icon_state = "swap_1_m"
using.screen_loc = ui_swaphand_position(owner,1)
- using.layer = HUD_LAYER
+ SET_PLANE_EXPLICIT(using, ABOVE_HUD_PLANE, owner)
using.plane = HUD_PLANE
static_inventory += using
@@ -33,7 +33,7 @@
using.icon = ui_style
using.icon_state = "swap_2"
using.screen_loc = ui_swaphand_position(owner,2)
- using.layer = HUD_LAYER
+ SET_PLANE_EXPLICIT(using, ABOVE_HUD_PLANE, owner)
using.plane = HUD_PLANE
static_inventory += using
diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm
index 1162314ce15a..25733955380f 100644
--- a/code/_onclick/hud/fullscreen.dm
+++ b/code/_onclick/hud/fullscreen.dm
@@ -14,6 +14,9 @@
if (client && screen.should_show_to(src))
screen.update_for_view(client.view)
client.screen += screen
+
+ if(screen.needs_offsetting)
+ SET_PLANE_EXPLICIT(screen, PLANE_TO_TRUE(screen.plane), src)
return screen
@@ -57,6 +60,19 @@
else
client.screen -= screen
+/mob/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(!same_z_layer)
+ relayer_fullscreens()
+
+/mob/proc/relayer_fullscreens()
+ var/turf/our_lad = get_turf(src)
+ var/offset = GET_TURF_PLANE_OFFSET(our_lad)
+ for(var/category in screens)
+ var/atom/movable/screen/fullscreen/screen = screens[category]
+ if(screen.needs_offsetting)
+ screen.plane = GET_NEW_PLANE(initial(screen.plane), offset)
+
/atom/movable/screen/fullscreen
icon = 'icons/mob/screen_full.dmi'
icon_state = "default"
@@ -67,6 +83,7 @@
var/view = 7
var/severity = 0
var/show_when_dead = FALSE
+ var/needs_offsetting = TRUE
/atom/movable/screen/fullscreen/proc/update_for_view(client_view)
if (screen_loc == "CENTER-7,CENTER-7" && view != client_view)
@@ -173,28 +190,29 @@
screen_loc = "WEST,SOUTH to EAST,NORTH"
icon_state = "flash"
plane = SPLASHSCREEN_PLANE
- layer = SPLASHSCREEN_LAYER - 1
+ layer = CINEMATIC_LAYER
color = "#000000"
show_when_dead = TRUE
/atom/movable/screen/fullscreen/cinematic_backdrop/Initialize(mapload)
. = ..()
- layer = SPLASHSCREEN_LAYER - 1
+ layer = CINEMATIC_LAYER
/atom/movable/screen/fullscreen/lighting_backdrop
icon = 'icons/mob/screen_gen.dmi'
icon_state = "flash"
- transform = matrix(200, 0, 0, 0, 200, 0)
+ screen_loc = "WEST,SOUTH to EAST,NORTH"
plane = LIGHTING_PLANE
+ layer = LIGHTING_ABOVE_ALL
blend_mode = BLEND_OVERLAY
show_when_dead = TRUE
+ needs_offsetting = FALSE
//Provides darkness to the back of the lighting plane
/atom/movable/screen/fullscreen/lighting_backdrop/lit
invisibility = INVISIBILITY_LIGHTING
layer = BACKGROUND_LAYER+21
color = "#000"
- show_when_dead = TRUE
//Provides whiteness in case you don't see lights so everything is still visible
/atom/movable/screen/fullscreen/lighting_backdrop/unlit
@@ -204,7 +222,7 @@
/atom/movable/screen/fullscreen/see_through_darkness
icon_state = "nightvision"
plane = LIGHTING_PLANE
- layer = LIGHTING_LAYER
+ layer = LIGHTING_ABOVE_ALL
blend_mode = BLEND_ADD
show_when_dead = TRUE
diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm
index c58b790d3515..8092d1c17ac4 100644
--- a/code/_onclick/hud/ghost.dm
+++ b/code/_onclick/hud/ghost.dm
@@ -2,6 +2,7 @@
icon = 'icons/mob/screen_ghost.dmi'
/atom/movable/screen/ghost/MouseEntered()
+ . = ..()
flick(icon_state + "_anim", src)
/atom/movable/screen/ghost/jump_to_mob
@@ -10,7 +11,7 @@
/atom/movable/screen/ghost/jump_to_mob/Click()
var/mob/dead/observer/G = usr
- G.jump_to_mob()
+ G.dead_tele()
/atom/movable/screen/ghost/orbit
name = "Orbit"
@@ -107,3 +108,10 @@
screenmob.client.screen += static_inventory
else
screenmob.client.screen -= static_inventory
+
+//We should only see observed mob alerts.
+/datum/hud/ghost/reorganize_alerts(mob/viewmob)
+ var/mob/dead/observer/O = mymob
+ if (istype(O) && O.observetarget)
+ return
+ . = ..()
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index f60a5fa86cdc..4c8e7ae135b0 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -22,10 +22,10 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/datum/hud
var/mob/mymob
- var/hud_shown = TRUE //Used for the HUD toggle (F12)
- var/hud_version = HUD_STYLE_STANDARD //Current displayed version of the HUD
- var/inventory_shown = FALSE //Equipped item inventory
- var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons)
+ var/hud_shown = TRUE //Used for the HUD toggle (F12)
+ var/hud_version = HUD_STYLE_STANDARD //Current displayed version of the HUD
+ var/inventory_shown = FALSE //Equipped item inventory
+ var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons)
var/atom/movable/screen/blobpwrdisplay
@@ -47,10 +47,40 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...)
/// Screen objects that never exit view.
var/list/always_visible_inventory = list()
- var/list/screenoverlays = list() //the screen objects used as whole screen overlays (flash, damageoverlay, etc...)
var/list/inv_slots[SLOTS_AMT] // /atom/movable/screen/inventory objects, ordered by their slot ID.
var/list/hand_slots // /atom/movable/screen/inventory/hand objects, assoc list of "[held_index]" = object
- var/list/atom/movable/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object
+
+ /// Assoc list of key => "plane master groups"
+ /// This is normally just the main window, but it'll occasionally contain things like spyglasses windows
+ var/list/datum/plane_master_group/master_groups = list()
+ ///Assoc list of controller groups, associated with key string group name with value of the plane master controller ref
+ var/list/atom/movable/plane_master_controller/plane_master_controllers = list()
+
+ /// Think of multiz as a stack of z levels. Each index in that stack has its own group of plane masters
+ /// This variable is the plane offset our mob/client is currently "on"
+ /// We use it to track what we should show/not show
+ /// Goes from 0 to the max (z level stack size - 1)
+ var/current_plane_offset = 0
+
+ ///UI for screentips that appear when you mouse over things
+ var/atom/movable/screen/screentip/screentip_text
+
+ /// Whether or not screentips are enabled.
+ /// This is updated by the preference for cheaper reads than would be
+ /// had with a proc call, especially on one of the hottest procs in the
+ /// game (MouseEntered).
+ var/screentips_enabled = SCREENTIP_PREFERENCE_ENABLED
+ /// Whether to use text or images for click hints.
+ /// Same behavior as `screentips_enabled`--very hot, updated when the preference is updated.
+ var/screentip_images = TRUE
+ /// If this client is being shown atmos debug overlays or not
+ var/atmos_debug_overlays = FALSE
+
+ /// The color to use for the screentips.
+ /// This is updated by the preference for cheaper reads than would be
+ /// had with a proc call, especially on one of the hottest procs in the
+ /// game (MouseEntered).
+ var/screentip_color
var/atom/movable/screen/button_palette/toggle_palette
var/atom/movable/screen/palette_scroll/down/palette_down
@@ -61,12 +91,17 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
var/list/floating_actions
var/atom/movable/screen/healths
- var/atom/movable/screen/healthdoll
var/atom/movable/screen/stamina
-
+ var/atom/movable/screen/healthdoll
+ var/atom/movable/screen/spacesuit
// subtypes can override this to force a specific UI style
var/ui_style
+ // List of weakrefs to objects that we add to our screen that we don't expect to DO anything
+ // They typically use * in their render target. They exist solely so we can reuse them,
+ // and avoid needing to make changes to all idk 300 consumers if we want to change the appearance
+ var/list/asset_refs_for_reuse = list()
+
/datum/hud/New(mob/owner)
mymob = owner
@@ -83,10 +118,94 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
hand_slots = list()
- for(var/mytype in subtypesof(/atom/movable/screen/plane_master))
- var/atom/movable/screen/plane_master/instance = new mytype()
- plane_masters["[instance.plane]"] = instance
- instance.backdrop(mymob)
+ var/datum/plane_master_group/main/main_group = new(PLANE_GROUP_MAIN)
+ main_group.attach_to(src)
+
+ // var/datum/preferences/preferences = owner?.client?.prefs
+ // screentip_color = preferences?.read_preference(/datum/preference/color/screentip_color)
+ // screentips_enabled = preferences?.read_preference(/datum/preference/choiced/enable_screentips)
+ // screentip_images = preferences?.read_preference(/datum/preference/toggle/screentip_images)
+ // screentip_text = new(null, src)
+ // static_inventory += screentip_text
+
+ for(var/mytype in subtypesof(/atom/movable/plane_master_controller))
+ var/atom/movable/plane_master_controller/controller_instance = new mytype(null,src)
+ plane_master_controllers[controller_instance.name] = controller_instance
+
+ owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
+
+ // Register onto the global spacelight appearances
+ // So they can be render targeted by anything in the world
+ for(var/obj/starlight_appearance/starlight as anything in GLOB.starlight_objects)
+ register_reuse(starlight)
+
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_plane_increase))
+ RegisterSignal(mymob, COMSIG_MOB_LOGIN, PROC_REF(client_refresh))
+ RegisterSignal(mymob, COMSIG_MOB_LOGOUT, PROC_REF(clear_client))
+ RegisterSignal(mymob, COMSIG_MOB_SIGHT_CHANGE, PROC_REF(update_sightflags))
+ RegisterSignal(mymob, COMSIG_VIEWDATA_UPDATE, PROC_REF(on_viewdata_update))
+ update_sightflags(mymob, mymob.sight, NONE)
+
+/datum/hud/proc/client_refresh(datum/source)
+ SIGNAL_HANDLER
+ RegisterSignal(mymob.canon_client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_eye_change))
+ on_eye_change(null, null, mymob.canon_client.eye)
+
+/datum/hud/proc/clear_client(datum/source)
+ SIGNAL_HANDLER
+ if(mymob.canon_client)
+ UnregisterSignal(mymob.canon_client, COMSIG_CLIENT_SET_EYE)
+
+/datum/hud/proc/on_viewdata_update(datum/source, view)
+ SIGNAL_HANDLER
+
+ view_audit_buttons()
+
+/datum/hud/proc/on_eye_change(datum/source, atom/old_eye, atom/new_eye)
+ SIGNAL_HANDLER
+ SEND_SIGNAL(src, COMSIG_HUD_EYE_CHANGED, old_eye, new_eye)
+
+ if(old_eye)
+ UnregisterSignal(old_eye, COMSIG_MOVABLE_Z_CHANGED)
+ if(new_eye)
+ // By the time logout runs, the client's eye has already changed
+ // There's just no log of the old eye, so we need to override
+ // :sadkirby:
+ RegisterSignal(new_eye, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(eye_z_changed), override = TRUE)
+ eye_z_changed(new_eye)
+
+/datum/hud/proc/update_sightflags(datum/source, new_sight, old_sight)
+ SIGNAL_HANDLER
+ // If neither the old and new flags can see turfs but not objects, don't transform the turfs
+ // This is to ensure parallax works when you can't see holder objects
+ if(should_sight_scale(new_sight) == should_sight_scale(old_sight))
+ return
+
+ for(var/group_key as anything in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ group.transform_lower_turfs(src, current_plane_offset)
+
+/datum/hud/proc/should_use_scale()
+ return should_sight_scale(mymob.sight)
+
+/datum/hud/proc/should_sight_scale(sight_flags)
+ return (sight_flags & (SEE_TURFS | SEE_OBJS)) != SEE_TURFS
+
+/datum/hud/proc/eye_z_changed(atom/eye)
+ SIGNAL_HANDLER
+ update_parallax_pref() // If your eye changes z level, so should your parallax prefs
+ var/turf/eye_turf = get_turf(eye)
+ var/new_offset = GET_TURF_PLANE_OFFSET(eye_turf)
+ if(current_plane_offset == new_offset)
+ return
+ var/old_offset = current_plane_offset
+ current_plane_offset = new_offset
+
+ SEND_SIGNAL(src, COMSIG_HUD_OFFSET_CHANGED, old_offset, new_offset)
+ if(should_use_scale())
+ for(var/group_key as anything in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ group.transform_lower_turfs(src, new_offset)
/datum/hud/Destroy()
if(mymob.hud_used == src)
@@ -102,10 +221,13 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
QDEL_NULL(module_store_icon)
QDEL_LIST(static_inventory)
+ // all already deleted by static inventory clear
inv_slots.Cut()
action_intent = null
zone_select = null
pull_icon = null
+ rest_icon = null
+ hand_slots.Cut()
QDEL_LIST(toggleable_inventory)
QDEL_LIST(hotkeybuttons)
@@ -115,18 +237,54 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
healths = null
stamina = null
healthdoll = null
- devilsouldisplay = null
+ spacesuit = null
blobpwrdisplay = null
alien_plasma_display = null
alien_queen_finder = null
- QDEL_LIST_ASSOC_VAL(plane_masters)
- QDEL_LIST(screenoverlays)
+ QDEL_LIST_ASSOC_VAL(master_groups)
+ QDEL_LIST_ASSOC_VAL(plane_master_controllers)
QDEL_LIST(always_visible_inventory)
mymob = null
+ //QDEL_NULL(screentip_text)
+
return ..()
+/datum/hud/proc/on_plane_increase(datum/source, old_max_offset, new_max_offset)
+ SIGNAL_HANDLER
+ for(var/i in old_max_offset + 1 to new_max_offset)
+ register_reuse(GLOB.starlight_objects[i + 1])
+ build_plane_groups(old_max_offset + 1, new_max_offset)
+
+/// Creates the required plane masters to fill out new z layers (because each "level" of multiz gets its own plane master set)
+/datum/hud/proc/build_plane_groups(starting_offset, ending_offset)
+ for(var/group_key in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ group.build_plane_masters(starting_offset, ending_offset)
+
+/// Returns the plane master that matches the input plane from the passed in group
+/datum/hud/proc/get_plane_master(plane, group_key = PLANE_GROUP_MAIN)
+ var/plane_key = "[plane]"
+ var/datum/plane_master_group/group = master_groups[group_key]
+ return group.plane_masters[plane_key]
+
+/// Returns a list of all plane masters that match the input true plane, drawn from the passed in group (ignores z layer offsets)
+/datum/hud/proc/get_true_plane_masters(true_plane, group_key = PLANE_GROUP_MAIN)
+ var/list/atom/movable/screen/plane_master/masters = list()
+ for(var/plane in TRUE_PLANE_TO_OFFSETS(true_plane))
+ masters += get_plane_master(plane, group_key)
+ return masters
+
+/// Returns all the planes belonging to the passed in group key
+/datum/hud/proc/get_planes_from(group_key)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ return group.plane_masters
+
+/// Returns the corresponding plane group datum if one exists
+/datum/hud/proc/get_plane_group(key)
+ return master_groups[key]
+
/mob/proc/create_mob_hud()
if(!client || hud_used)
return
@@ -154,7 +312,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
// This code is the absolute fucking worst, I want it to go die in a fire
// Seriously, why
- screenmob.client.screen = list()
+ screenmob.client.clear_screen()
screenmob.client.apply_clickcatcher()
var/display_hud_version = version
@@ -225,6 +383,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
reorganize_alerts(screenmob)
screenmob.reload_fullscreen()
update_parallax_pref(screenmob)
+ update_reuse(screenmob)
// ensure observers get an accurate and up-to-date view
if (!viewmob)
@@ -239,11 +398,16 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
return TRUE
/datum/hud/proc/plane_masters_update()
- // Plane masters are always shown to OUR mob, never to observers
- for(var/thing in plane_masters)
- var/atom/movable/screen/plane_master/PM = plane_masters[thing]
- PM.backdrop(mymob)
- mymob.client.screen += PM
+ for(var/group_key in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ // Plane masters are always shown to OUR mob, never to observers
+ group.refresh_hud()
+
+/datum/hud/proc/plane_masters_rebuild()
+ for(var/group_key in master_groups)
+ var/datum/plane_master_group/group = master_groups[group_key]
+ // Plane masters are always shown to OUR mob, never to observers
+ group.rebuild_hud()
/datum/hud/human/show_hud(version = 0,mob/viewmob)
. = ..()
@@ -270,13 +434,29 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if (initial(ui_style) || ui_style == new_ui_style)
return
- for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + screenoverlays + always_visible_inventory + inv_slots)
+ for(var/atom/item in static_inventory + toggleable_inventory + hotkeybuttons + infodisplay + always_visible_inventory + inv_slots)
if (item.icon == ui_style)
item.icon = new_ui_style
ui_style = new_ui_style
build_hand_slots()
+/datum/hud/proc/register_reuse(atom/movable/screen/reuse)
+ asset_refs_for_reuse += WEAKREF(reuse)
+ mymob?.client?.screen += reuse
+
+/datum/hud/proc/unregister_reuse(atom/movable/screen/reuse)
+ asset_refs_for_reuse -= WEAKREF(reuse)
+ mymob?.client?.screen -= reuse
+
+/datum/hud/proc/update_reuse(mob/show_to)
+ for(var/datum/weakref/screen_ref as anything in asset_refs_for_reuse)
+ var/atom/movable/screen/reuse = screen_ref.resolve()
+ if(isnull(reuse))
+ asset_refs_for_reuse -= screen_ref
+ continue
+ show_to.client?.screen += reuse
+
//Triggered when F12 is pressed (Unless someone changed something in the DMF)
/mob/verb/button_pressed_F12()
set name = "F12"
@@ -284,9 +464,9 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if(hud_used && client)
hud_used.show_hud() //Shows the next hud preset
- to_chat(usr, "Switched HUD mode. Press F12 to toggle.")
+ to_chat(usr, span_info("Switched HUD mode. Press F12 to toggle."))
else
- to_chat(usr, "This mob type does not use a HUD.")
+ to_chat(usr, span_warning("This mob type does not use a HUD."))
//(re)builds the hand ui slots, throwing away old ones
@@ -308,7 +488,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
hand_box.held_index = i
hand_slots["[i]"] = hand_box
static_inventory += hand_box
- hand_box.update_appearance(UPDATE_ICON)
+ hand_box.update_appearance()
var/i = 1
for(var/atom/movable/screen/swap_hand/SH in static_inventory)
@@ -323,7 +503,6 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/datum/hud/proc/update_locked_slots()
return
-
/datum/hud/proc/position_action(atom/movable/screen/movable/action_button/button, position)
// This is kinda a hack, I'm sorry.
// Basically, FLOATING is never a valid position to pass into this proc. It exists as a generic marker for manually positioned buttons
@@ -361,7 +540,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
palette_actions.insert_action(button, palette_actions.index_of(relative_to))
if(SCRN_OBJ_FLOATING) // If we don't have it as a define, this is a screen_loc, and we should be floating
floating_actions += button
- var/client/our_client = mymob.client
+ var/client/our_client = mymob.canon_client
if(!our_client)
position_action(button, button.linked_action.default_button_position)
return
@@ -402,7 +581,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/// Ensures all of our buttons are properly within the bounds of our client's view, moves them if they're not
/datum/hud/proc/view_audit_buttons()
- var/our_view = mymob?.client?.view
+ var/our_view = mymob?.canon_client?.view
if(!our_view)
return
listed_actions.check_against_view()
@@ -518,7 +697,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[pixel_north_offset]"
/datum/action_group/proc/check_against_view()
- var/owner_view = owner?.mymob?.client?.view
+ var/owner_view = owner?.mymob?.canon_client?.view
if(!owner_view)
return
// Unlikey as it is, we may have been changed. Want to start from our target position and fail down
diff --git a/code/_onclick/hud/map_popups.dm b/code/_onclick/hud/map_popups.dm
index f070084f4770..a3b5699c8484 100644
--- a/code/_onclick/hud/map_popups.dm
+++ b/code/_onclick/hud/map_popups.dm
@@ -1,36 +1,3 @@
-/client
- /**
- * Assoc list with all the active maps - when a screen obj is added to
- * a map, it's put in here as well.
- *
- * Format: list( = list(/atom/movable/screen))
- */
- var/list/screen_maps = list()
-
-/atom/movable/screen
- /**
- * Map name assigned to this object.
- * Automatically set by /client/proc/add_obj_to_map.
- */
- var/assigned_map
- /**
- * Mark this object as garbage-collectible after you clean the map
- * it was registered on.
- *
- * This could probably be changed to be a proc, for conditional removal.
- * But for now, this works.
- */
- var/del_on_map_removal = TRUE
-
-/**
- * A screen object, which acts as a container for turfs and other things
- * you want to show on the map, which you usually attach to "vis_contents".
- */
-/atom/movable/screen/map_view
- // Map view has to be on the lowest plane to enable proper lighting
- layer = GAME_PLANE
- plane = GAME_PLANE
-
/**
* A generic background object.
* It is also implicitly used to allocate a rectangle on the map, which will
@@ -77,23 +44,18 @@
screen_maps[screen_obj.assigned_map] = list()
// NOTE: Possibly an expensive operation
var/list/screen_map = screen_maps[screen_obj.assigned_map]
- if(!screen_map.Find(screen_obj))
- screen_map += screen_obj
- if(!screen.Find(screen_obj))
- screen += screen_obj
+ screen_map |= screen_obj
+ screen |= screen_obj
/**
* Clears the map of registered screen objects.
- *
- * Not really needed most of the time, as the client's screen list gets reset
- * on relog. any of the buttons are going to get caught by garbage collection
- * anyway. they're effectively qdel'd.
*/
/client/proc/clear_map(map_name)
- if(!map_name || !(map_name in screen_maps))
+ if(!map_name || !screen_maps[map_name])
return FALSE
for(var/atom/movable/screen/screen_obj in screen_maps[map_name])
screen_maps[map_name] -= screen_obj
+ screen -= screen_obj
if(screen_obj.del_on_map_removal)
qdel(screen_obj)
screen_maps -= map_name
@@ -113,9 +75,10 @@
*
* Returns a map name.
*/
-/client/proc/create_popup(name, ratiox = 100, ratioy = 100)
+/client/proc/create_popup(name, title, ratiox = 100, ratioy = 100)
winclone(src, "popupwindow", name)
var/list/winparams = list()
+ winparams["title"] = title
winparams["size"] = "[ratiox]x[ratioy]"
winparams["on-close"] = "handle-popup-close [name]"
winset(src, "[name]", list2params(winparams))
@@ -138,13 +101,13 @@
* Width and height are multiplied by 64 by default.
*/
/client/proc/setup_popup(popup_name, width = 9, height = 9, \
- tilesize = 2, bg_icon)
+ tilesize = 2, title, bg_icon)
if(!popup_name)
return
clear_map("[popup_name]_map")
var/x_value = world.icon_size * tilesize * width
var/y_value = world.icon_size * tilesize * height
- var/map_name = create_popup(popup_name, x_value, y_value)
+ var/map_name = create_popup(popup_name, title, x_value, y_value)
var/atom/movable/screen/background/background = new
background.assigned_map = map_name
@@ -167,4 +130,5 @@
*/
/client/verb/handle_popup_close(window_id as text)
set hidden = TRUE
- clear_map("[window_id]_map")
\ No newline at end of file
+ clear_map("[window_id]_map")
+ SEND_SIGNAL(src, COMSIG_POPUP_CLEARED, window_id)
diff --git a/code/_onclick/hud/map_view.dm b/code/_onclick/hud/map_view.dm
new file mode 100644
index 000000000000..79e4d7ae10bc
--- /dev/null
+++ b/code/_onclick/hud/map_view.dm
@@ -0,0 +1,71 @@
+/**
+ * A screen object, which acts as a container for turfs and other things
+ * you want to show on the map, which you usually attach to "vis_contents".
+ * Additionally manages the plane masters required to display said container contents
+ */
+INITIALIZE_IMMEDIATE(/atom/movable/screen/map_view)
+/atom/movable/screen/map_view
+ name = "screen"
+ // Map view has to be on the lowest plane to enable proper lighting
+ layer = GAME_PLANE
+ plane = GAME_PLANE
+ del_on_map_removal = FALSE
+
+ // Weakrefs of all our hud viewers -> a weakref to the hud datum they last used
+ var/list/datum/weakref/viewers_to_huds = list()
+
+ var/datum/plane_master_group/popup/pop_planes
+
+/atom/movable/screen/map_view/Destroy()
+ for(var/datum/weakref/client_ref in viewers_to_huds)
+ var/client/our_client = client_ref.resolve()
+ if(!our_client)
+ continue
+ hide_from(our_client.mob)
+
+ return ..()
+
+/atom/movable/screen/map_view/proc/generate_view(map_key)
+ // Map keys have to start and end with an A-Z character,
+ // and definitely NOT with a square bracket or even a number.
+ // I wasted 6 hours on this. :agony:
+ // -- Stylemistake
+ assigned_map = map_key
+ set_position(1, 1)
+
+/atom/movable/screen/map_view/proc/display_to(mob/show_to)
+ show_to.client.register_map_obj(src)
+ // We need to add planesmasters to the popup, otherwise
+ // blending fucks up massively. Any planesmaster on the main screen does
+ // NOT apply to map popups. If there's ever a way to make planesmasters
+ // omnipresent, then this wouldn't be needed.
+ // We lazy load this because there's no point creating all these if none's gonna see em
+
+ // Store this info in a client -> hud pattern, so ghosts closing the window nukes the right group
+ var/datum/weakref/client_ref = WEAKREF(show_to.client)
+
+ var/datum/weakref/hud_ref = viewers_to_huds[client_ref]
+ var/datum/hud/our_hud = hud_ref?.resolve()
+ if(our_hud)
+ return our_hud.get_plane_group(PLANE_GROUP_POPUP_WINDOW(src))
+
+ // Generate a new plane group for this case
+ pop_planes = new(PLANE_GROUP_POPUP_WINDOW(src), assigned_map)
+ viewers_to_huds[client_ref] = WEAKREF(show_to.hud_used)
+ pop_planes.attach_to(show_to.hud_used)
+
+ return pop_planes
+
+/atom/movable/screen/map_view/proc/hide_from(mob/hide_from)
+ hide_from?.canon_client.clear_map(assigned_map)
+ var/client_ref = WEAKREF(hide_from?.canon_client)
+
+ // Make sure we clear the *right* hud
+ var/datum/weakref/hud_ref = viewers_to_huds[client_ref]
+ viewers_to_huds -= client_ref
+ var/datum/hud/clear_from = hud_ref?.resolve()
+ if(!clear_from)
+ return
+
+ var/datum/plane_master_group/popup/pop_planes = clear_from.get_plane_group(PLANE_GROUP_POPUP_WINDOW(src))
+ qdel(pop_planes)
diff --git a/code/_onclick/hud/pai.dm b/code/_onclick/hud/pai.dm
index 265bbd1f5723..3da5caf44238 100644
--- a/code/_onclick/hud/pai.dm
+++ b/code/_onclick/hud/pai.dm
@@ -104,7 +104,7 @@
pAI.ai_roster()
/atom/movable/screen/pai/state_laws
- name = "State Laws"
+ name = "Law Manager"
icon_state = "state_laws"
/atom/movable/screen/pai/state_laws/Click()
diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm
index 046cdc245ab6..d1a7ede9e330 100755
--- a/code/_onclick/hud/parallax.dm
+++ b/code/_onclick/hud/parallax.dm
@@ -1,33 +1,32 @@
-/client
- var/list/parallax_layers
- var/list/parallax_layers_cached
- var/atom/movable/movingmob
- var/turf/previous_turf
- var/dont_animate_parallax //world.time of when we can state animate()ing parallax again
- var/last_parallax_shift //world.time of last update
- var/parallax_throttle = 0 //ds between updates
- var/parallax_movedir = 0
- var/parallax_layers_max = 4
- var/parallax_animate_timer
-
/datum/hud/proc/create_parallax(mob/viewmob)
var/mob/screenmob = viewmob || mymob
var/client/C = screenmob.client
+
if (!apply_parallax_pref(viewmob)) //don't want shit computers to crash when specing someone with insane parallax, so use the viewer's pref
+ for(var/atom/movable/screen/plane_master/parallax as anything in get_true_plane_masters(PLANE_SPACE_PARALLAX))
+ parallax.hide_plane(screenmob)
return
+ for(var/atom/movable/screen/plane_master/parallax as anything in get_true_plane_masters(PLANE_SPACE_PARALLAX))
+ parallax.unhide_plane(screenmob)
+
if(!length(C.parallax_layers_cached))
C.parallax_layers_cached = list()
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_1(null, C.view)
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_2(null, C.view)
- if(HAS_TRAIT(SSstation, STATION_TRAIT_MOONSCORCH))
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet/moonscorch(null, C.view)
- else
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet(null, C.view)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_1(null, src)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_2(null, src)
+
+ if(GLOB.minetype == MINETYPE_LAVALAND)
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_MOONSCORCH))
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet/moonscorch(null, src)
+ else
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet(null, src)
+ if(GLOB.minetype == MINETYPE_JUNGLE)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/planet/jungle(null, src)
+
if(SSparallax.random_layer)
- C.parallax_layers_cached += new SSparallax.random_layer
- C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_3(null, C.view)
+ C.parallax_layers_cached += new SSparallax.random_layer.type(null, src, FALSE, SSparallax.random_layer)
+ C.parallax_layers_cached += new /atom/movable/screen/parallax_layer/layer_3(null, src)
C.parallax_layers = C.parallax_layers_cached.Copy()
@@ -35,70 +34,88 @@
C.parallax_layers.len = C.parallax_layers_max
C.screen |= (C.parallax_layers)
- var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
- if(screenmob != mymob)
- C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
- C.screen += PM
- PM.color = list(
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 0, 0, 0, 0,
- 1, 1, 1, 1,
- 0, 0, 0, 0
- )
-
+ // We could do not do parallax for anything except the main plane group
+ // This could be changed, but it would require refactoring this whole thing
+ // And adding non client particular hooks for all the inputs, and I do not have the time I'm sorry :(
+ for(var/atom/movable/screen/plane_master/plane_master as anything in screenmob.hud_used.get_true_plane_masters(PLANE_SPACE))
+ if(screenmob != mymob)
+ C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
+ C.screen += plane_master
+ plane_master.color = list(
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 1, 1, 1, 1,
+ 0, 0, 0, 0
+ )
/datum/hud/proc/remove_parallax(mob/viewmob)
var/mob/screenmob = viewmob || mymob
var/client/C = screenmob.client
C.screen -= (C.parallax_layers_cached)
- var/atom/movable/screen/plane_master/PM = screenmob.hud_used.plane_masters["[PLANE_SPACE]"]
- if(screenmob != mymob)
- C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
- C.screen += PM
- PM.color = initial(PM.color)
+ for(var/atom/movable/screen/plane_master/plane_master as anything in screenmob.hud_used.get_true_plane_masters(PLANE_SPACE))
+ if(screenmob != mymob)
+ C.screen -= locate(/atom/movable/screen/plane_master/parallax_white) in C.screen
+ C.screen += plane_master
+ plane_master.color = initial(plane_master.color)
C.parallax_layers = null
/datum/hud/proc/apply_parallax_pref(mob/viewmob)
var/mob/screenmob = viewmob || mymob
+ var/turf/screen_location = get_turf(screenmob)
+
+ if(SSmapping.level_trait(screen_location?.z, ZTRAIT_NOPARALLAX))
+ for(var/atom/movable/screen/plane_master/white_space as anything in get_true_plane_masters(PLANE_SPACE))
+ white_space.hide_plane(screenmob)
+ return FALSE
+
+ for(var/atom/movable/screen/plane_master/white_space as anything in get_true_plane_masters(PLANE_SPACE))
+ white_space.unhide_plane(screenmob)
+
+ if (SSlag_switch.measures[DISABLE_PARALLAX] && !HAS_TRAIT(viewmob, TRAIT_BYPASS_MEASURES))
+ return FALSE
+
var/client/C = screenmob.client
- if(C.prefs)
- var/pref = C.prefs.read_preference(/datum/preference/choiced/parallax)
- if (isnull(pref))
- pref = PARALLAX_HIGH
- switch(pref)
- if (PARALLAX_INSANE)
- C.parallax_throttle = FALSE
- C.parallax_layers_max = 5
- return TRUE
-
- if (PARALLAX_MED)
- C.parallax_throttle = PARALLAX_DELAY_MED
- C.parallax_layers_max = 3
- return TRUE
-
- if (PARALLAX_LOW)
- C.parallax_throttle = PARALLAX_DELAY_LOW
- C.parallax_layers_max = 1
- return TRUE
-
- if (PARALLAX_DISABLE)
- return FALSE
-
- //This is high parallax.
- C.parallax_throttle = PARALLAX_DELAY_DEFAULT
- C.parallax_layers_max = 4
- return TRUE
+ // Default to HIGH
+ var/parallax_selection = C?.prefs.read_preference(/datum/preference/choiced/parallax) || PARALLAX_HIGH
+
+ switch(parallax_selection)
+ if (PARALLAX_INSANE)
+ C.parallax_layers_max = 5
+ C.do_parallax_animations = TRUE
+ return TRUE
+
+ if(PARALLAX_HIGH)
+ C.parallax_layers_max = 4
+ C.do_parallax_animations = TRUE
+ return TRUE
+
+ if (PARALLAX_MED)
+ C.parallax_layers_max = 3
+ C.do_parallax_animations = TRUE
+ return TRUE
+
+ if (PARALLAX_LOW)
+ C.parallax_layers_max = 1
+ C.do_parallax_animations = FALSE
+ return TRUE
+
+ if (PARALLAX_DISABLE)
+ return FALSE
/datum/hud/proc/update_parallax_pref(mob/viewmob)
- remove_parallax(viewmob)
- create_parallax(viewmob)
- update_parallax()
+ var/mob/screen_mob = viewmob || mymob
+ if(!screen_mob.client)
+ return
+ remove_parallax(screen_mob)
+ create_parallax(screen_mob)
+ update_parallax(screen_mob)
// This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller can append their animation)
-/datum/hud/proc/set_parallax_movedir(new_parallax_movedir, skip_windups)
+/datum/hud/proc/set_parallax_movedir(new_parallax_movedir = 0, skip_windups, mob/viewmob)
. = FALSE
- var/client/C = mymob.client
+ var/mob/screenmob = viewmob || mymob
+ var/client/C = screenmob.client
if(new_parallax_movedir == C.parallax_movedir)
return
var/animatedir = new_parallax_movedir
@@ -170,29 +187,25 @@
L.transform = newtransform
- animate(L, transform = L.transform, time = 0 SECONDS, loop = -1, flags = ANIMATION_END_NOW)
+ animate(L, transform = L.transform, time = 0, loop = -1, flags = ANIMATION_END_NOW)
animate(transform = matrix(), time = T)
-/datum/hud/proc/update_parallax()
- var/client/C = mymob.client
- if(!C)
- return
- var/turf/posobj = get_turf_global(C.eye) // yogs - replace get_turf with get_turf_global
+/datum/hud/proc/update_parallax(mob/viewmob)
+ var/mob/screenmob = viewmob || mymob
+ var/client/C = screenmob.client
+ var/turf/posobj = get_turf(C.eye)
if(!posobj)
return
- var/area/areaobj = posobj.loc
+ var/area/areaobj = posobj.loc
// Update the movement direction of the parallax if necessary (for shuttles)
- set_parallax_movedir(areaobj.parallax_movedir, FALSE)
+ set_parallax_movedir(areaobj.parallax_movedir, FALSE, screenmob)
- var/force
+ var/force = FALSE
if(!C.previous_turf || (C.previous_turf.z != posobj.z))
C.previous_turf = posobj
force = TRUE
- if (!force && world.time < C.last_parallax_shift+C.parallax_throttle)
- return
-
//Doing it this way prevents parallax layers from "jumping" when you change Z-Levels.
var/offset_x = posobj.x - C.previous_turf.x
var/offset_y = posobj.y - C.previous_turf.y
@@ -200,83 +213,105 @@
if(!offset_x && !offset_y && !force)
return
- var/last_delay = world.time - C.last_parallax_shift
- last_delay = min(last_delay, C.parallax_throttle)
+ var/glide_rate = round(world.icon_size / screenmob.glide_size * world.tick_lag, world.tick_lag)
C.previous_turf = posobj
- C.last_parallax_shift = world.time
- for(var/thing in C.parallax_layers)
- var/atom/movable/screen/parallax_layer/L = thing
- L.update_status(mymob)
- if (L.view_sized != C.view)
- L.update_o(C.view)
+ var/largest_change = max(abs(offset_x), abs(offset_y))
+ var/max_allowed_dist = (glide_rate / world.tick_lag) + 1
+ // If we aren't already moving/don't allow parallax, have made some movement, and that movement was smaller then our "glide" size, animate
+ var/run_parralax = (C.do_parallax_animations && glide_rate && !areaobj.parallax_movedir && C.dont_animate_parallax <= world.time && largest_change <= max_allowed_dist)
+ for(var/atom/movable/screen/parallax_layer/parallax_layer as anything in C.parallax_layers)
+ var/our_speed = parallax_layer.speed
var/change_x
var/change_y
-
- if(L.absolute)
- L.offset_x = -(posobj.x - SSparallax.planet_x_offset) * L.speed
- L.offset_y = -(posobj.y - SSparallax.planet_y_offset) * L.speed
+ if(parallax_layer.absolute)
+ // We use change here so the typically large absolute objects (just lavaland for now) don't jitter so much
+ change_x = (posobj.x - SSparallax.planet_x_offset) * our_speed + parallax_layer.offset_x
+ change_y = (posobj.y - SSparallax.planet_y_offset) * our_speed + parallax_layer.offset_y
else
- change_x = offset_x * L.speed
- L.offset_x -= change_x
- change_y = offset_y * L.speed
- L.offset_y -= change_y
-
- if(L.offset_x > 240)
- L.offset_x -= 480
- if(L.offset_x < -240)
- L.offset_x += 480
- if(L.offset_y > 240)
- L.offset_y -= 480
- if(L.offset_y < -240)
- L.offset_y += 480
-
-
- if(!areaobj.parallax_movedir && C.dont_animate_parallax <= world.time && (offset_x || offset_y) && abs(offset_x) <= max(C.parallax_throttle/world.tick_lag+1,1) && abs(offset_y) <= max(C.parallax_throttle/world.tick_lag+1,1) && (round(abs(change_x)) > 1 || round(abs(change_y)) > 1))
- L.transform = matrix(1, 0, offset_x*L.speed, 0, 1, offset_y*L.speed)
- animate(L, transform=matrix(), time = last_delay)
-
- L.screen_loc = "CENTER-7:[round(L.offset_x,1)],CENTER-7:[round(L.offset_y,1)]"
+ change_x = offset_x * our_speed
+ change_y = offset_y * our_speed
+
+ // This is how we tile parralax sprites
+ // It doesn't use change because we really don't want to animate this
+ if(parallax_layer.offset_x - change_x > 240)
+ parallax_layer.offset_x -= 480
+ else if(parallax_layer.offset_x - change_x < -240)
+ parallax_layer.offset_x += 480
+ if(parallax_layer.offset_y - change_y > 240)
+ parallax_layer.offset_y -= 480
+ else if(parallax_layer.offset_y - change_y < -240)
+ parallax_layer.offset_y += 480
+
+ // Now that we have our offsets, let's do our positioning
+ parallax_layer.offset_x -= change_x
+ parallax_layer.offset_y -= change_y
+
+ parallax_layer.screen_loc = "CENTER-7:[round(parallax_layer.offset_x, 1)],CENTER-7:[round(parallax_layer.offset_y, 1)]"
+
+ // We're going to use a transform to "glide" that last movement out, so it looks nicer
+ // Don't do any animates if we're not actually moving enough distance yeah? thanks lad
+ if(run_parralax && (largest_change * our_speed > 1))
+ parallax_layer.transform = matrix(1,0,change_x, 0,1,change_y)
+ animate(parallax_layer, transform=matrix(), time = glide_rate)
/atom/movable/proc/update_parallax_contents()
- if(length(client_mobs_in_contents))
- for(var/thing in client_mobs_in_contents)
- var/mob/M = thing
- if(M && M.client && M.hud_used && length(M.client.parallax_layers))
- M.hud_used.update_parallax()
-
-/mob/proc/update_parallax_teleport() //used for arrivals shuttle
- if(client && client.eye && hud_used && length(client.parallax_layers))
+ for(var/mob/client_mob as anything in client_mobs_in_contents)
+ if(length(client_mob?.client?.parallax_layers) && client_mob.hud_used)
+ client_mob.hud_used.update_parallax()
+
+/mob/proc/update_parallax_teleport() //used for arrivals shuttle
+ if(client?.eye && hud_used && length(client.parallax_layers))
var/area/areaobj = get_area(client.eye)
hud_used.set_parallax_movedir(areaobj.parallax_movedir, TRUE)
+// We need parallax to always pass its args down into initialize, so we immediate init it
+INITIALIZE_IMMEDIATE(/atom/movable/screen/parallax_layer)
/atom/movable/screen/parallax_layer
icon = 'icons/effects/parallax.dmi'
var/speed = 1
var/offset_x = 0
var/offset_y = 0
- var/view_sized
var/absolute = FALSE
blend_mode = BLEND_ADD
plane = PLANE_SPACE_PARALLAX
screen_loc = "CENTER-7,CENTER-7"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/parallax_layer/Initialize(mapload, view)
+/atom/movable/screen/parallax_layer/Initialize(mapload, datum/hud/hud_owner, template = FALSE)
. = ..()
- if (!view)
- view = world.view
+ // Parallax layers are independant of hud, they care about client
+ // Not doing this will just create a bunch of hard deletes
+ hud = null
+
+ if(template)
+ return
+
+ var/client/boss = hud_owner?.mymob?.canon_client
+
+ if(!boss) // If this typepath all starts to harddel your culprit is likely this
+ return INITIALIZE_HINT_QDEL
+
+ // I do not want to know bestie
+ var/view = boss.view || world.view
update_o(view)
+ RegisterSignal(boss, COMSIG_VIEW_SET, PROC_REF(on_view_change))
+
+/atom/movable/screen/parallax_layer/proc/on_view_change(datum/source, new_size)
+ SIGNAL_HANDLER
+ update_o(new_size)
/atom/movable/screen/parallax_layer/proc/update_o(view)
if (!view)
view = world.view
+ var/static/parallax_scaler = world.icon_size / 480
+
+ // Turn the view size into a grid of correctly scaled overlays
var/list/viewscales = getviewsize(view)
- var/countx = CEILING((viewscales[1]/2)/(480/world.icon_size), 1)+1
- var/county = CEILING((viewscales[2]/2)/(480/world.icon_size), 1)+1
+ var/countx = CEILING((viewscales[1] / 2) * parallax_scaler, 1) + 1
+ var/county = CEILING((viewscales[2] / 2) * parallax_scaler, 1) + 1
var/list/new_overlays = new
for(var/x in -countx to countx)
for(var/y in -county to county)
@@ -287,10 +322,6 @@
new_overlays += texture_overlay
cut_overlays()
add_overlay(new_overlays)
- view_sized = view
-
-/atom/movable/screen/parallax_layer/proc/update_status(mob/M)
- return
/atom/movable/screen/parallax_layer/layer_1
icon_state = "layer1"
@@ -307,21 +338,6 @@
speed = 1.4
layer = 3
-/atom/movable/screen/parallax_layer/random
- blend_mode = BLEND_OVERLAY
- speed = 3
- layer = 3
-
-/atom/movable/screen/parallax_layer/random/space_gas
- icon_state = "random_layer1"
-
-/atom/movable/screen/parallax_layer/random/space_gas/Initialize(mapload, view)
- . = ..()
- src.add_atom_colour(SSparallax.random_parallax_color, ADMIN_COLOUR_PRIORITY)
-
-/atom/movable/screen/parallax_layer/random/asteroids
- icon_state = "random_layer2"
-
/atom/movable/screen/parallax_layer/planet
icon_state = "planet"
blend_mode = BLEND_OVERLAY
@@ -332,12 +348,33 @@
/atom/movable/screen/parallax_layer/planet/moonscorch
icon_state = "rheus_moon"
-/atom/movable/screen/parallax_layer/planet/update_status(mob/M)
- var/turf/T = get_turf(M)
- if(is_station_level(T.z))
- invisibility = 0
- else
- invisibility = INVISIBILITY_ABSTRACT
+/atom/movable/screen/parallax_layer/planet/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ var/client/boss = hud_owner?.mymob?.canon_client
+ if(!boss)
+ return
+ var/static/list/connections = list(
+ COMSIG_MOVABLE_Z_CHANGED = PROC_REF(on_z_change),
+ COMSIG_MOB_LOGOUT = PROC_REF(on_mob_logout),
+ )
+ AddComponent(/datum/component/connect_mob_behalf, boss, connections)
+ on_z_change(hud_owner?.mymob)
+
+/atom/movable/screen/parallax_layer/planet/proc/on_mob_logout(mob/source)
+ SIGNAL_HANDLER
+ var/client/boss = source.canon_client
+ on_z_change(boss.mob)
+
+/atom/movable/screen/parallax_layer/planet/proc/on_z_change(mob/source)
+ SIGNAL_HANDLER
+ var/client/boss = source.client
+ var/turf/posobj = get_turf(boss?.eye)
+ if(!posobj)
+ return
+ SetInvisibility(is_station_level(posobj.z) ? INVISIBILITY_NONE : INVISIBILITY_ABSTRACT, id=type)
/atom/movable/screen/parallax_layer/planet/update_o()
return //Shit wont move
+
+/atom/movable/screen/parallax_layer/planet/jungle
+ icon_state = "jungleland"
diff --git a/code/_onclick/hud/picture_in_picture.dm b/code/_onclick/hud/picture_in_picture.dm
index 354a6ed54630..dbf4e0af5310 100644
--- a/code/_onclick/hud/picture_in_picture.dm
+++ b/code/_onclick/hud/picture_in_picture.dm
@@ -12,11 +12,16 @@
var/atom/movable/screen/component_button/button_shrink
var/mutable_appearance/standard_background
- var/const/max_dimensions = 10
-/atom/movable/screen/movable/pic_in_pic/Initialize(mapload)
+/atom/movable/screen/movable/pic_in_pic/Initialize(mapload, datum/hud/hud_owner)
. = ..()
make_backgrounds()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(multiz_offset_increase))
+ multiz_offset_increase(SSmapping)
+
+/atom/movable/screen/movable/pic_in_pic/proc/multiz_offset_increase(datum/source)
+ SIGNAL_HANDLER
+ SET_PLANE_W_SCALAR(src, initial(plane), SSmapping.max_plane_offset)
/atom/movable/screen/movable/pic_in_pic/Destroy()
for(var/C in shown_to)
@@ -101,9 +106,11 @@
standard_background.transform = M
add_overlay(standard_background)
+// maximum number of dimensions is 10
+
/atom/movable/screen/movable/pic_in_pic/proc/set_view_size(width, height, do_refresh = TRUE)
- width = clamp(width, 0, max_dimensions)
- height = clamp(height, 0, max_dimensions)
+ width = clamp(width, 0, 10)
+ height = clamp(height, 0, 10)
src.width = width
src.height = height
diff --git a/code/_onclick/hud/plane_master.dm b/code/_onclick/hud/plane_master.dm
deleted file mode 100644
index 401a350fba1d..000000000000
--- a/code/_onclick/hud/plane_master.dm
+++ /dev/null
@@ -1,205 +0,0 @@
-/atom/movable/screen/plane_master
- screen_loc = "CENTER"
- icon_state = "blank"
- appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
- blend_mode = BLEND_OVERLAY
- var/show_alpha = 255
- var/hide_alpha = 0
- /// If our plane master allows for offsetting
- /// Mostly used for planes that really don't need to be duplicated, like the hud planes
- var/allows_offsetting = TRUE
- //--rendering relay vars--
- /// list of planes we will relay this plane's render to
- var/list/render_relay_planes = list(RENDER_PLANE_GAME)
-
-/atom/movable/screen/plane_master/proc/Show(override)
- alpha = override || show_alpha
-
-/atom/movable/screen/plane_master/proc/Hide(override)
- alpha = override || hide_alpha
-
-//Why do plane masters need a backdrop sometimes? Read https://secure.byond.com/forum/?post=2141928
-//Trust me, you need one. Period. If you don't think you do, you're doing something extremely wrong.
-/atom/movable/screen/plane_master/proc/backdrop(mob/mymob)
-
-///Things rendered on "openspace"; holes in multi-z
-/atom/movable/screen/plane_master/openspace
- name = "open space plane master"
- plane = FLOOR_OPENSPACE_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_MULTIPLY
- alpha = 255
-
-/atom/movable/screen/plane_master/openspace/backdrop(mob/mymob)
- filters = list()
- filters += filter(type="alpha", render_source = LIGHTING_RENDER_TARGET, flags = MASK_INVERSE)
- filters += filter(type = "drop_shadow", color = "#04080FAA", size = -10)
- filters += filter(type = "drop_shadow", color = "#04080FAA", size = -15)
- filters += filter(type = "drop_shadow", color = "#04080FAA", size = -20)
-
-/atom/movable/screen/plane_master/proc/outline(_size, _color)
- filters += filter(type = "outline", size = _size, color = _color)
-
-/atom/movable/screen/plane_master/proc/shadow(_size, _border, _offset = 0, _x = 0, _y = 0, _color = "#04080FAA")
- filters += filter(type = "drop_shadow", x = _x, y = _y, color = _color, size = _size, offset = _offset)
-
-/atom/movable/screen/plane_master/proc/clear_filters()
- filters = list()
-
-///Contains just the floor
-/atom/movable/screen/plane_master/floor
- name = "floor plane master"
- plane = FLOOR_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/floor/backdrop(mob/mymob)
- filters = list()
- if(istype(mymob) && mymob.eye_blurry)
- filters += GAUSSIAN_BLUR(clamp(mymob.eye_blurry*0.1,0.6,3))
- // Should be moved to the world render plate when render plates get ported in
- filters += filter(type="displace", render_source = SINGULARITY_RENDER_TARGET, size=75)
-
-///Contains most things in the game world
-/atom/movable/screen/plane_master/game_world
- name = "game world plane master"
- plane = GAME_PLANE
- appearance_flags = PLANE_MASTER //should use client color
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/game_world/backdrop(mob/mymob)
- filters = list()
- if(istype(mymob) && mymob.client?.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion))
- filters += AMBIENT_OCCLUSION
- if(istype(mymob) && mymob.eye_blurry)
- filters += GAUSSIAN_BLUR(clamp(mymob.eye_blurry*0.1,0.6,3))
- // Should be moved to the world render plate when render plates get ported in
- filters += filter(type="displace", render_source = SINGULARITY_RENDER_TARGET, size=75)
-
-
-///Contains all lighting objects
-/atom/movable/screen/plane_master/lighting
- name = "lighting plane master"
- plane = LIGHTING_PLANE
- blend_mode = BLEND_MULTIPLY
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/plane_master/lighting/Initialize(mapload)
- . = ..()
- filters += filter(type="alpha", render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE)
- filters += filter(type="alpha", render_source = EMISSIVE_UNBLOCKABLE_RENDER_TARGET, flags = MASK_INVERSE)
- filters += filter(type="alpha", render_source = O_LIGHTING_VISUAL_RENDER_TARGET, flags = MASK_INVERSE)
- // Should be moved to the world render plate when render plates get ported in
- filters += filter(type="displace", render_source = SINGULARITY_RENDER_TARGET, size=75)
-
-/**
- * Things placed on this mask the lighting plane. Doesn't render directly.
- *
- * Gets masked by blocking plane. Use for things that you want blocked by
- * mobs, items, etc.
- */
-/atom/movable/screen/plane_master/emissive
- name = "emissive plane master"
- plane = EMISSIVE_PLANE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- render_target = EMISSIVE_RENDER_TARGET
-
-/atom/movable/screen/plane_master/emissive/Initialize(mapload)
- . = ..()
- filters += filter(type="alpha", render_source=EMISSIVE_BLOCKER_RENDER_TARGET, flags=MASK_INVERSE)
- // Should be moved to the world render plate when render plates get ported in
- filters += filter(type="displace", render_source = SINGULARITY_RENDER_TARGET, size=75)
-
-/**
- * Things placed on this always mask the lighting plane. Doesn't render directly.
- *
- * Always masks the light plane, isn't blocked by anything. Use for on mob glows,
- * magic stuff, etc.
- */
-
-/atom/movable/screen/plane_master/emissive_unblockable
- name = "unblockable emissive plane master"
- plane = EMISSIVE_UNBLOCKABLE_PLANE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- render_target = EMISSIVE_UNBLOCKABLE_RENDER_TARGET
-
-/**
- * Things placed on this layer mask the emissive layer. Doesn't render directly
- *
- * You really shouldn't be directly using this, use atom helpers instead
- */
-/atom/movable/screen/plane_master/emissive_blocker
- name = "emissive blocker plane master"
- plane = EMISSIVE_BLOCKER_PLANE
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- render_target = EMISSIVE_BLOCKER_RENDER_TARGET
-
-///Contains space parallax
-/atom/movable/screen/plane_master/parallax
- name = "parallax plane master"
- plane = PLANE_SPACE_PARALLAX
- blend_mode = BLEND_MULTIPLY
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/plane_master/parallax/Initialize(mapload)
- . = ..()
- // Should be moved to the world render plate when render plates get ported in
- filters += filter(type="displace", render_source = SINGULARITY_RENDER_TARGET, size=75)
-
-/atom/movable/screen/plane_master/parallax_white
- name = "parallax whitifier plane master"
- plane = PLANE_SPACE
-
-/atom/movable/screen/plane_master/lighting/backdrop(mob/mymob)
- mymob.overlay_fullscreen("lighting_backdrop_lit", /atom/movable/screen/fullscreen/lighting_backdrop/lit)
- mymob.overlay_fullscreen("lighting_backdrop_unlit", /atom/movable/screen/fullscreen/lighting_backdrop/unlit)
-
-/atom/movable/screen/plane_master/singularity_effect
- name = "singularity plane master"
- plane = SINGULARITY_EFFECT_PLANE
- render_target = SINGULARITY_RENDER_TARGET
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/atom/movable/screen/plane_master/camera_static
- name = "camera static plane master"
- plane = CAMERA_STATIC_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/runechat
- name = "runechat plane master"
- plane = RUNECHAT_PLANE
- appearance_flags = PLANE_MASTER
- blend_mode = BLEND_OVERLAY
-
-/atom/movable/screen/plane_master/runechat/backdrop(mob/mymob)
- filters = list()
- if(istype(mymob) && mymob.client?.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion))
- filters += AMBIENT_OCCLUSION
-
-/atom/movable/screen/plane_master/o_light_visual
- name = "overlight light visual plane master"
- layer = O_LIGHTING_VISUAL_LAYER
- plane = O_LIGHTING_VISUAL_PLANE
- render_target = O_LIGHTING_VISUAL_RENDER_TARGET
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- blend_mode = BLEND_MULTIPLY
-
-/**
- * Render relay object assigned to a plane master to be able to relay it's render onto other planes that are not it's own
- */
-/atom/movable/render_plane_relay
- screen_loc = "CENTER"
- layer = -1
- plane = 0
- appearance_flags = PASS_MOUSE | NO_CLIENT_COLOR | KEEP_TOGETHER
- /// If we render into a critical plane master, or not
- var/critical_target = FALSE
-
-/atom/movable/screen/plane_master/fullscreen
- name = "Fullscreen"
- plane = FULLSCREEN_PLANE
- appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
- render_relay_planes = list(RENDER_PLANE_NON_GAME)
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- allows_offsetting = FALSE
diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 4b8ca93d56f5..ffb346b60c29 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -11,10 +11,10 @@ GLOBAL_LIST_EMPTY(radial_menus)
/atom/movable/screen/radial/proc/set_parent(new_value)
if(parent)
- UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(parent, COMSIG_QDELETING)
parent = new_value
if(parent)
- RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_parent_del))
+ RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(handle_parent_del))
/atom/movable/screen/radial/proc/handle_parent_del()
SIGNAL_HANDLER
@@ -263,7 +263,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
E.add_overlay(choices_icons[choice_id])
if (choice_datum?.info)
var/obj/effect/abstract/info/info_button = new(E, choice_datum.info)
- info_button.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(info_button, ABOVE_HUD_PLANE, anchor)
info_button.layer = RADIAL_CONTENT_LAYER
E.vis_contents += info_button
@@ -308,7 +308,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/mutable_appearance/MA = new /mutable_appearance(to_extract_from)
if(MA)
- MA.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(MA, ABOVE_HUD_PLANE, anchor)
MA.layer = RADIAL_CONTENT_LAYER
MA.appearance_flags |= RESET_TRANSFORM
return MA
@@ -326,19 +326,26 @@ GLOBAL_LIST_EMPTY(radial_menus)
return
current_user = M.client
//Blank
- menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing",layer = ABOVE_HUD_LAYER)
- menu_holder.plane = ABOVE_HUD_PLANE
- menu_holder.appearance_flags |= KEEP_APART
+ menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing",layer = RADIAL_BACKGROUND_LAYER)
+ SET_PLANE_EXPLICIT(menu_holder, ABOVE_HUD_PLANE, M)
+ menu_holder.appearance_flags |= KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM
menu_holder.vis_contents += elements + close_button
current_user.images += menu_holder
+ if(ismovable(anchor))
+ RegisterSignal(anchor, COMSIG_MOVABLE_MOVED, PROC_REF(on_anchor_moved))
+
+/datum/radial_menu/proc/on_anchor_moved()
+ menu_holder.loc = get_atom_on_turf(anchor)
/datum/radial_menu/proc/hide()
+ if(ismovable(anchor))
+ UnregisterSignal(anchor, COMSIG_MOVABLE_MOVED)
if(current_user)
current_user.images -= menu_holder
/datum/radial_menu/proc/wait(atom/user, atom/anchor, require_near = FALSE)
while (current_user && !finished && !selected_choice)
- if(require_near && !in_range(anchor, user))
+ if(require_near && !(in_range(anchor, user) || anchor == user.loc))
return
if(custom_check_callback && next_check < world.time)
if(!custom_check_callback.Invoke())
@@ -358,9 +365,13 @@ GLOBAL_LIST_EMPTY(radial_menus)
Choices should be a list where list keys are movables or text used for element names and return value
and list values are movables/icons/images used for element icons
*/
-/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", check_delay)
+/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE)
if(!user || !anchor || !length(choices))
return
+
+ if(length(choices)==1 && autopick_single_option)
+ return choices[1]
+
if(!uniqueid)
uniqueid = "defmenu_[REF(user)]_[REF(anchor)]"
@@ -376,8 +387,6 @@ GLOBAL_LIST_EMPTY(radial_menus)
menu.radius = radius
if(istype(custom_check))
menu.custom_check_callback = custom_check
- if(check_delay)
- menu.check_delay = check_delay
menu.anchor = anchor
menu.radial_slice_icon = radial_slice_icon
menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud
@@ -387,7 +396,7 @@ GLOBAL_LIST_EMPTY(radial_menus)
var/answer = menu.selected_choice
qdel(menu)
GLOB.radial_menus -= uniqueid
- if(require_near && !in_range(anchor, user))
+ if(require_near && !(in_range(anchor, user)) || anchor == user.loc)
return
if(istype(custom_check))
if(!custom_check.Invoke())
diff --git a/code/_onclick/hud/random_layer.dm b/code/_onclick/hud/random_layer.dm
new file mode 100644
index 000000000000..2a20889dd0ae
--- /dev/null
+++ b/code/_onclick/hud/random_layer.dm
@@ -0,0 +1,52 @@
+/// Parallax layers that vary between rounds. Has some code to make sure we all have the same one
+/atom/movable/screen/parallax_layer/random
+ blend_mode = BLEND_OVERLAY
+ speed = 2
+ layer = 3
+
+/atom/movable/screen/parallax_layer/random/Initialize(mapload, datum/hud/hud_owner, template, atom/movable/screen/parallax_layer/random/twin)
+ . = ..()
+
+ if(twin)
+ copy_parallax(twin)
+
+/// Make this layer unique, with color or position or something
+/atom/movable/screen/parallax_layer/random/proc/get_random_look()
+ return
+
+/// Copy a parallax instance to ensure parity between everyones parallax
+/atom/movable/screen/parallax_layer/random/proc/copy_parallax(atom/movable/screen/parallax_layer/random/twin)
+ return
+
+/// For applying minor effects related to parallax. If you want big stuff, put it in a station trait or something
+/atom/movable/screen/parallax_layer/random/proc/apply_global_effects()
+ return
+
+/// Gassy background with a few random colors
+/atom/movable/screen/parallax_layer/random/space_gas
+ icon_state = "random_layer1"
+
+ /// The colors we can be
+ var/possible_colors = list(COLOR_TEAL, COLOR_GREEN, COLOR_SILVER, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)
+ /// The color we are
+ var/parallax_color
+
+/atom/movable/screen/parallax_layer/random/space_gas/get_random_look()
+ parallax_color = parallax_color || pick(possible_colors)
+
+/atom/movable/screen/parallax_layer/random/space_gas/copy_parallax(atom/movable/screen/parallax_layer/random/space_gas/twin)
+ parallax_color = twin.parallax_color
+ add_atom_colour(parallax_color, ADMIN_COLOUR_PRIORITY)
+
+/// Space gas but green for the radioactive nebula station trait
+/atom/movable/screen/parallax_layer/random/space_gas/radioactive
+ parallax_color = list(0,0,0,0, 0,2,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //very vibrant green
+
+/atom/movable/screen/parallax_layer/random/space_gas/radioactive/apply_global_effects()
+ . = ..()
+ set_base_starlight("#189156")
+
+/// Big asteroid rocks appear in the background
+/atom/movable/screen/parallax_layer/random/asteroids
+ icon_state = "random_layer2"
+ layer = 4
diff --git a/code/_onclick/hud/rendering/_render_readme.md b/code/_onclick/hud/rendering/_render_readme.md
new file mode 100644
index 000000000000..2c1cd559a58d
--- /dev/null
+++ b/code/_onclick/hud/rendering/_render_readme.md
@@ -0,0 +1,57 @@
+# The Render Readme
+
+1. [Byond internal functionality](#byond-internal-functionality)
+2. [Known internal snowflake](#known-internal-snowflake)
+3. [The rendering solution](#the-rendering-solution)
+4. [Render plates](#render-plates)
+
+## Byond internal functionality
+This part of the guide will assume that you have read the byond reference entry for rendering at www.byond.com/docs/ref//#/{notes}/renderer
+
+When you create an atom, this will always create an internal byond structure called an "appearance". This appearance you will likely be familiar with, as it is exposed through the /atom/var/appearance var. This appearance var holds data on how to render the object, ie what icon/icon_state/color etc it is using. Note that appearance vars will always copy, and do not hold a reference. When you update a var, for example lets pretend we add a filter, the appearance will be updated to include the filter. Note that, however, vis_contents objets are uniquely excluded from appearances. Then, when the filter is updated, the appearance will be recreated, and the atom marked as "dirty". After it has been updated, the SendMaps() function (sometimes also called maptick), which is a internal byond function that iterates over all objects in a clients view and in the clients.mob.contents, checks for "dirty" atoms, then resends any "dirty" appearances to clients as needed and unmarks them as dirty. This function is notoriosly slow, but we can see it's tick usage through the world.map_cpu var. We can also avoid more complex checks checking whether an object is visible on a clients screen by using the TILE_BOUND appearance flag.
+
+Finally, we arrive at clientside behavior, where we have two main clientside functions: GetMapIcons, and Render. GetMapIcons is repsonsible for actual rendering calculations on the clientside, such as "Group Icons and Set bounds", which performs clientside calculations for transform matrixes. Note that particles here are handled in a separate thread and are not diplayed in the clientside profiler. Render handles the actual drawing of the screen.
+
+For debugging rendering issues its reccomended you do two things:
+A) Talk to someone who has inside knowledge(like lummox) about it, most of this is undocumented and bugs often
+B) Use the undocumented debug printer which reads of data on icons rendering, this is very dense but can be useful in some cases. To use: Right click top tab -> Options & Messages -> Client -> Command -> Enter ".debug profile mapicons" and press Enter -> go to your Byond directory and find BYOND/cfg/mapicons.json . Yes this is one giant one-line json.
+
+## Known internal snowflake
+The following is an incomplete list of pitfalls that come from byond snowflake that are known, this list is obviously incomplete.
+
+1. Transforms are very slow on clientside. This is not usually noticable, but if you start using large amounts of them it will grind you to a halt quickly, regardless of whether its on overlays or objs
+2. The darkness plane. This is unused, as it doesn't work with our rendering format, so this section is purely academic. The darkness plane has specific variables it needs to render correctly, and these can be found in the plane masters file. it is composed internally of two parts, a black mask over the clients screen, and a non rendering mask that blocks all luminosity=0 turfs and their contents from rendering if the SEE_BLACKNESS flag is set properly. The blocker will always block rendering but the mask can be layered under other objects.
+3. render_target/source. Render_target/source will only copy certain rendering instructions, and these are only defined as "etc." in the byond reference. Known non copied appearance vars include: blend_mode, plane, layer, vis_contents, mouse_opacity...
+4. Large icons on the screen that peek over the edge will instead of only rendering partly like you would expect will instead stretch the screen while not adgusting the render buffer, which means that you can actively see as tiles and map objects are rendered. You can use this for an easy "offscreen" UI.
+5. Numerically large filters on objects of any size will torpedo performance, even though large objects with small filters will perform massively better. (ie blur(size=20) BAD)
+6. Texture Atlas: the texture atlas byond uses to render icons is very susceptible to corruption and can regularily replace icons with other icons or just not render at all. This can be exasperated by alt tabbing or pausing the dreamseeker process.
+7. The renderer is awful code and lummox said he will try changing a large part of it for 515 so keep an eye on that
+8. Byond uses DirectX 9 (Lummox said he wants to update to DirectX 11)
+9. Particles are just fancy overlays and are not independent of their owner
+10. Maptick items inside mob.contents are cheaper compared to most other movables
+11. Displacement filter: The byond "displacement filter" does not, as the name would make you expect, use displacement maps, but instead uses normal maps.
+
+## The rendering solution
+One of the main issues with making pretty effects is how objects can only render to one plane, and how filters can only be applied to single objects. Quite simply it means we cant apply effects to multiple planes at once, and an effect to one plane only by treating it as a single unit:
+
+![](https://raw.githubusercontent.com/tgstation/documentation-assets/main/rendering/renderpipe_old.png)
+
+A semi-fix to stop from having to apply effects to every single plane is to use the render controllers, to automatically apply filters and colors automatically onto their controlled planes.
+
+The solution is thus instead we replace plane masters rendering directly to client with planes that render multiple planes onto them as objects in order to be able to affect multiple planes while treating them as a single object. This is done by relaying the plane using a "render relay" onto a "render plate" which acts as a plane master of plane masters of sorts, and since planes are rendered onto it as single objects any filters we apply to them will render over the planes, treating them as a single unit.
+
+![](https://raw.githubusercontent.com/tgstation/documentation-assets/main/rendering/renderpipe_refactored.png)
+
+We can also choose to render these by decreasing the scaling all applied effects (effect_size/number_of_plates_rendered_to) then rendering it onto multiple planes:
+
+![](https://raw.githubusercontent.com/tgstation/documentation-assets/main/rendering/renderpipe_refactored_multiple.png)
+
+Through these this allows us to treat planes as single objects, and lets us distort them as a single unit, most notably works wonders with the displacement filter. Specifically, here you can displacement_filter a plane onto a plate, which then will treat all the other planes rendered on that plate as a single unit.
+
+## Render plates
+
+The rendering system uses two objects to unify planes: render_relay and render_plates. Render relays use render_target/source and the relay_render_to_plane proc to replicate the plane master on the render relay. This render relay is then rendered onto a render_plate, which is a plane master that renders the render_relays onto itself. This plate can then be hierachically rendered with the same process until it reaches the master render_plate, which is the plate that will actually render to the player. These plates naturally in the byond style have quirks. For example, rendering to two plates will double any effects such as color or filters, and as such you need to carefully manage how you render them. Keep in mind as well that when sorting the layers for rendering on a plane that they should not be negative, this is handled automatically in relay_render_to_plane. When debugging note that mouse_opacity can act bizzarly with this method, such as only allowing you to click things that are layered over objects on a certain plane but auomatically setting the mouse_opacity should be handling this. Note that if you decide to manipulate a plane with internal byond objects that you will have to manually extrapolate the vars that are set if you want to render them to another plane (See blackness plane for example), and that this is not documented anywhere.
+
+
+Goodluck and godspeed with coding
+ - Just another contributor
diff --git a/code/_onclick/hud/rendering/plane_master_controller.dm b/code/_onclick/hud/rendering/plane_master_controller.dm
new file mode 100644
index 000000000000..cae4822c6fec
--- /dev/null
+++ b/code/_onclick/hud/rendering/plane_master_controller.dm
@@ -0,0 +1,103 @@
+///Atom that manages and controls multiple planes. It's an atom so we can hook into add_filter etc. Multiple controllers can control one plane.
+///Of note: plane master controllers are currently not very extensively used, because render plates fill a semi similar niche
+///This could well change someday, and I'd like to keep this stuff around, so we use it for a few cases just out of convenience
+/atom/movable/plane_master_controller
+ ///List of planes as defines in this controllers control
+ var/list/controlled_planes = list()
+ ///hud that owns this controller
+ var/datum/hud/owner_hud
+
+INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
+
+///Ensures that all the planes are correctly in the controlled_planes list.
+/atom/movable/plane_master_controller/Initialize(mapload, datum/hud/hud)
+ . = ..()
+ if(!istype(hud))
+ return
+ owner_hud = hud
+
+/atom/movable/plane_master_controller/proc/get_planes()
+ var/returned_planes = list()
+ for(var/true_plane in controlled_planes)
+ returned_planes += get_true_plane(true_plane)
+ return returned_planes
+
+/atom/movable/plane_master_controller/proc/get_true_plane(true_plane)
+ var/list/returned_planes = owner_hud.get_true_plane_masters(true_plane)
+ if(!length(returned_planes)) //If we looked for a hud that isn't instanced, just keep going
+ stack_trace("[plane] isn't a valid plane master layer for [owner_hud.type], are you sure it exists in the first place?")
+ return
+
+ return returned_planes
+
+///Full override so we can just use filterrific
+/atom/movable/plane_master_controller/add_filter(name, priority, list/params)
+ . = ..()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.add_filter(name, priority, params)
+
+///Full override so we can just use filterrific
+/atom/movable/plane_master_controller/remove_filter(name_or_names)
+ . = ..()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.remove_filter(name_or_names)
+
+/atom/movable/plane_master_controller/update_filters()
+ . = ..()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.update_filters()
+
+///Gets all filters for this controllers plane masters
+/atom/movable/plane_master_controller/proc/get_filters(name)
+ . = list()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ . += pm_iterator.get_filter(name)
+
+///Transitions all filters owned by this plane master controller
+/atom/movable/plane_master_controller/transition_filter(name, time, list/new_params, easing, loop)
+ . = ..()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.transition_filter(name, new_params, time, easing, loop)
+
+///Full override so we can just use filterrific
+/atom/movable/plane_master_controller/add_atom_colour(coloration, colour_priority)
+ . = ..()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.add_atom_colour(coloration, colour_priority)
+
+
+///Removes an instance of colour_type from the atom's atom_colours list
+/atom/movable/plane_master_controller/remove_atom_colour(colour_priority, coloration)
+ . = ..()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.remove_atom_colour(colour_priority, coloration)
+
+
+///Resets the atom's color to null, and then sets it to the highest priority colour available
+/atom/movable/plane_master_controller/update_atom_colour()
+ for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes())
+ pm_iterator.update_atom_colour()
+
+
+/// Exists for convienience when referencing all game render plates
+/atom/movable/plane_master_controller/game
+ name = PLANE_MASTERS_GAME
+ controlled_planes = list(
+ RENDER_PLANE_GAME
+ )
+
+/// Exists for convienience when referencing all non-master render plates.
+/// This is the whole game and the UI, but not the escape menu.
+/atom/movable/plane_master_controller/non_master
+ name = PLANE_MASTERS_NON_MASTER
+ controlled_planes = list(
+ RENDER_PLANE_GAME,
+ RENDER_PLANE_NON_GAME,
+ )
+
+/// Exists for convienience when referencing all game render plates
+/atom/movable/plane_master_controller/colorblind
+ name = PLANE_MASTERS_COLORBLIND
+ controlled_planes = list(
+ RENDER_PLANE_MASTER
+ )
diff --git a/code/_onclick/hud/rendering/plane_master_group.dm b/code/_onclick/hud/rendering/plane_master_group.dm
new file mode 100644
index 000000000000..63c202b64fba
--- /dev/null
+++ b/code/_onclick/hud/rendering/plane_master_group.dm
@@ -0,0 +1,220 @@
+/// Datum that represents one "group" of plane masters
+/// So all the main window planes would be in one, all the spyglass planes in another
+/// Etc
+/datum/plane_master_group
+ /// Our key in the group list on /datum/hud
+ /// Should be unique for any group of plane masters in the world
+ var/key
+ /// Our parent hud
+ var/datum/hud/our_hud
+ /// List in the form "[plane]" = object, the plane masters we own
+ var/list/atom/movable/screen/plane_master/plane_masters = list()
+ /// The visual offset we are currently using
+ var/active_offset = 0
+ /// What, if any, submap we render onto
+ var/map = ""
+ /// Controls the screen_loc that owned plane masters will use when generating relays. Due to a Byond bug, relays using the CENTER positional loc
+ /// Will be improperly offset
+ var/relay_loc = "CENTER"
+
+/datum/plane_master_group/New(key, map = "")
+ . = ..()
+ src.key = key
+ src.map = map
+ build_plane_masters(0, SSmapping.max_plane_offset)
+
+/datum/plane_master_group/Destroy()
+ orphan_hud()
+ QDEL_LIST_ASSOC_VAL(plane_masters)
+ return ..()
+
+/// Display a plane master group to some viewer, so show all our planes to it
+/datum/plane_master_group/proc/attach_to(datum/hud/viewing_hud)
+ if(viewing_hud.master_groups[key])
+ stack_trace("Hey brother, our key [key] is already in use by a plane master group on the passed in hud, belonging to [viewing_hud.mymob]. Ya fucked up, why are there dupes")
+ return
+
+ our_hud = viewing_hud
+ our_hud.master_groups[key] = src
+ show_hud()
+ transform_lower_turfs(our_hud, active_offset)
+
+/// Hide the plane master from its current hud, fully clear it out
+/datum/plane_master_group/proc/orphan_hud()
+ if(our_hud)
+ our_hud.master_groups -= key
+ hide_hud()
+ our_hud = null
+
+/// Well, refresh our group, mostly useful for plane specific updates
+/datum/plane_master_group/proc/refresh_hud()
+ hide_hud()
+ show_hud()
+
+/// Fully regenerate our group, resetting our planes to their compile time values
+/datum/plane_master_group/proc/rebuild_hud()
+ hide_hud()
+ rebuild_plane_masters()
+ show_hud()
+ transform_lower_turfs(our_hud, active_offset)
+
+/// Regenerate our plane masters, this is useful if we don't have a mob but still want to rebuild. Such in the case of changing the screen_loc of relays
+/datum/plane_master_group/proc/rebuild_plane_masters()
+ QDEL_LIST_ASSOC_VAL(plane_masters)
+ build_plane_masters(0, SSmapping.max_plane_offset)
+
+/datum/plane_master_group/proc/hide_hud()
+ for(var/thing in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[thing]
+ plane.hide_from(our_hud.mymob)
+
+/datum/plane_master_group/proc/show_hud()
+ for(var/thing in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[thing]
+ show_plane(plane)
+
+/// This is mostly a proc so it can be overriden by popups, since they have unique behavior they want to do
+/datum/plane_master_group/proc/show_plane(atom/movable/screen/plane_master/plane)
+ plane.show_to(our_hud.mymob)
+
+/// Nice wrapper for the "[]"ing
+/datum/plane_master_group/proc/get_plane(plane)
+ return plane_masters["[plane]"]
+
+/// Returns a list of all the plane master types we want to create
+/datum/plane_master_group/proc/get_plane_types()
+ return subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/rendering_plate
+
+/// Actually generate our plane masters, in some offset range (where offset is the z layers to render to, because each "layer" in a multiz stack gets its own plane master cube)
+/datum/plane_master_group/proc/build_plane_masters(starting_offset, ending_offset)
+ for(var/atom/movable/screen/plane_master/mytype as anything in get_plane_types())
+ for(var/plane_offset in starting_offset to ending_offset)
+ if(plane_offset != 0 && !initial(mytype.allows_offsetting))
+ continue
+ var/atom/movable/screen/plane_master/instance = new mytype(null, null, src, plane_offset)
+ plane_masters["[instance.plane]"] = instance
+ prep_plane_instance(instance)
+
+/// Similarly, exists so subtypes can do unique behavior to planes on creation
+/datum/plane_master_group/proc/prep_plane_instance(atom/movable/screen/plane_master/instance)
+ return
+
+// It would be nice to setup parallaxing for stairs and things when doing this
+// So they look nicer. if you can't it's all good, if you think you can sanely look at monster's work
+// It's hard, and potentially expensive. be careful
+/datum/plane_master_group/proc/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
+ // Check if this feature is disabled for the client, in which case don't use scale.
+ var/mob/our_mob = our_hud?.mymob
+ if(!our_mob?.client?.prefs?.read_preference(/datum/preference/toggle/multiz_parallax))
+ use_scale = FALSE
+
+ // No offset? piss off
+ if(!SSmapping.max_plane_offset)
+ return
+
+ active_offset = new_offset
+
+ // Each time we go "down" a visual z level, we'll reduce the scale by this amount
+ // Chosen because mothblocks liked it, didn't cause motion sickness while also giving a sense of height
+ var/scale_by = 0.965
+ if(!use_scale)
+ // This is a workaround for two things
+ // First of all, if a mob can see objects but not turfs, they will not be shown the holder objects we use for
+ // What I'd like to do is revert to images if this case throws, but image vis_contents is broken
+ // https://www.byond.com/forum/post/2821969
+ // If that's ever fixed, please just use that. thanks :)
+ scale_by = 1
+
+ var/list/offsets = list()
+ var/multiz_boundary = our_mob?.client?.prefs?.read_preference(/datum/preference/numeric/multiz_performance)
+
+ // We accept negatives so going down "zooms" away the drop above as it goes
+ for(var/offset in -SSmapping.max_plane_offset to SSmapping.max_plane_offset)
+ // Multiz boundaries disable transforms
+ if(multiz_boundary != MULTIZ_PERFORMANCE_DISABLE && (multiz_boundary < abs(offset)))
+ offsets += null
+ continue
+
+ // No transformations if we're landing ON you
+ if(offset == 0)
+ offsets += null
+ continue
+
+ var/scale = scale_by ** (offset)
+ var/matrix/multiz_shrink = matrix()
+ multiz_shrink.Scale(scale)
+ offsets += multiz_shrink
+
+ // So we can talk in 1 -> max_offset * 2 + 1, rather then -max_offset -> max_offset
+ var/offset_offset = SSmapping.max_plane_offset + 1
+
+ for(var/plane_key in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[plane_key]
+ if(!plane.allows_offsetting)
+ continue
+
+ var/visual_offset = plane.offset - new_offset
+
+ // Basically uh, if we're showing something down X amount of levels, or up any amount of levels
+ if(multiz_boundary != MULTIZ_PERFORMANCE_DISABLE && (visual_offset > multiz_boundary || visual_offset < 0))
+ plane.outside_bounds(our_mob)
+ else if(plane.is_outside_bounds)
+ plane.inside_bounds(our_mob)
+
+ if(!plane.multiz_scaled)
+ continue
+
+ if(plane.force_hidden || plane.is_outside_bounds || visual_offset < 0)
+ // We don't animate here because it should be invisble, but we do mark because it'll look nice
+ plane.transform = offsets[visual_offset + offset_offset]
+ continue
+
+ animate(plane, transform = offsets[visual_offset + offset_offset], 0.05 SECONDS, easing = LINEAR_EASING)
+
+/// Holds plane masters for popups, like camera windows
+/// Note: We do not scale this plane, even though we could
+/// This is because it's annoying to get turfs to position inside it correctly
+/// If you wanna try someday feel free, but I can't manage it
+/datum/plane_master_group/popup
+
+/// This is janky as hell but since something changed with CENTER positioning after build 1614 we have to switch to the bandaid LEFT,TOP positioning
+/// using LEFT,TOP *at* or *before* 1614 will result in another broken offset for cameras
+#define MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS 1614
+
+/datum/plane_master_group/popup/attach_to(datum/hud/viewing_hud)
+ // If we're about to display this group to a mob who's client is more recent than the last known version with working CENTER, then we need to remake the relays
+ // with the correct screen_loc using the relay override
+ if(viewing_hud.mymob?.client?.byond_build > MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS)
+ relay_loc = "LEFT,TOP"
+ rebuild_plane_masters()
+ return ..()
+
+#undef MAX_CLIENT_BUILD_WITH_WORKING_SECONDARY_MAPS
+
+/datum/plane_master_group/popup/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
+ return ..(source, new_offset, FALSE)
+
+/// Holds the main plane master
+/datum/plane_master_group/main
+
+/datum/plane_master_group/main/transform_lower_turfs(datum/hud/source, new_offset, use_scale = TRUE)
+ if(use_scale)
+ return ..(source, new_offset, source.should_use_scale())
+ return ..()
+
+/// Hudless group. Exists for testing
+/datum/plane_master_group/hudless
+ var/mob/our_mob
+
+/datum/plane_master_group/hudless/Destroy()
+ . = ..()
+ our_mob = null
+
+/datum/plane_master_group/hudless/hide_hud()
+ for(var/thing in plane_masters)
+ var/atom/movable/screen/plane_master/plane = plane_masters[thing]
+ plane.hide_from(our_mob)
+
+/// This is mostly a proc so it can be overriden by popups, since they have unique behavior they want to do
+/datum/plane_master_group/hudless/show_plane(atom/movable/screen/plane_master/plane)
+ plane.show_to(our_mob)
diff --git a/code/_onclick/hud/rendering/plane_masters/_plane_master.dm b/code/_onclick/hud/rendering/plane_masters/_plane_master.dm
new file mode 100644
index 000000000000..13f94fa9f5aa
--- /dev/null
+++ b/code/_onclick/hud/rendering/plane_masters/_plane_master.dm
@@ -0,0 +1,236 @@
+// I hate this place
+INITIALIZE_IMMEDIATE(/atom/movable/screen/plane_master)
+
+/atom/movable/screen/plane_master
+ screen_loc = "CENTER"
+ icon_state = "blank"
+ appearance_flags = PLANE_MASTER
+ blend_mode = BLEND_OVERLAY
+ plane = LOWEST_EVER_PLANE
+ /// Will be sent to the debug ui as a description for each plane
+ /// Also useful as a place to explain to coders how/why your plane works, and what it's meant to do
+ /// Plaintext and basic html are fine to use here.
+ /// I'll bonk you if I find you putting "lmao stuff" in here, make this useful.
+ var/documentation = ""
+ /// Our real alpha value, so alpha can persist through being hidden/shown
+ var/true_alpha = 255
+ /// Tracks if we're using our true alpha, or being manipulated in some other way
+ var/alpha_enabled = TRUE
+
+ /// The plane master group we're a member of, our "home"
+ var/datum/plane_master_group/home
+
+ /// If our plane master allows for offsetting
+ /// Mostly used for planes that really don't need to be duplicated, like the hud planes
+ var/allows_offsetting = TRUE
+ /// Our offset from our "true" plane, see below
+ var/offset
+ /// When rendering multiz, lower levels get their own set of plane masters
+ /// Real plane here represents the "true" plane value of something, ignoring the offset required to handle lower levels
+ var/real_plane
+
+ //--rendering relay vars--
+ /// list of planes we will relay this plane's render to
+ var/list/render_relay_planes = list(RENDER_PLANE_GAME)
+ /// blend mode to apply to the render relay in case you dont want to use the plane_masters blend_mode
+ var/blend_mode_override
+ /// list of current relays this plane is utilizing to render
+ var/list/atom/movable/render_plane_relay/relays = list()
+ /// if render relays have already be generated
+ var/relays_generated = FALSE
+
+ /// If this plane master should be hidden from the player at roundstart
+ /// We do this so PMs can opt into being temporary, to reduce load on clients
+ var/start_hidden = FALSE
+ /// If this plane master is being forced to hide.
+ /// Hidden PMs will dump ANYTHING relayed or drawn onto them. Be careful with this
+ /// Remember: a hidden plane master will dump anything drawn directly to it onto the output render. It does NOT hide its contents
+ /// Use alpha for that
+ var/force_hidden = FALSE
+
+ /// If this plane should be scaled by multiz
+ /// Planes with this set should NEVER be relay'd into each other, as that will cause visual fuck
+ var/multiz_scaled = TRUE
+
+ /// Bitfield that describes how this plane master will render if its z layer is being "optimized"
+ /// If a plane master is NOT critical, it will be completely dropped if we start to render outside a client's multiz boundary prefs
+ /// Of note: most of the time we will relay renders to non critical planes in this stage. so the plane master will end up drawing roughly "in order" with its friends
+ /// This is NOT done for parallax and other problem children, because the rules of BLEND_MULTIPLY appear to not behave as expected :(
+ /// This will also just make debugging harder, because we do fragile things in order to ensure things operate as epected. I'm sorry
+ /// Compile time
+ /// See [code\__DEFINES\layers.dm] for our bitflags
+ var/critical = NONE
+
+ /// If this plane master is outside of our visual bounds right now
+ var/is_outside_bounds = FALSE
+
+/atom/movable/screen/plane_master/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset = 0)
+ . = ..()
+ src.offset = offset
+ true_alpha = alpha
+ real_plane = plane
+
+ if(!set_home(home))
+ return INITIALIZE_HINT_QDEL
+ update_offset()
+ if(!documentation && !(istype(src, /atom/movable/screen/plane_master) || istype(src, /atom/movable/screen/plane_master/rendering_plate)))
+ stack_trace("Plane master created without a description. Document how your thing works so people will know in future, and we can display it in the debug menu")
+ if(start_hidden)
+ hide_plane(home.our_hud?.mymob)
+ generate_render_relays()
+
+/atom/movable/screen/plane_master/Destroy()
+ if(home)
+ // NOTE! We do not clear ourselves from client screens
+ // We relay on whoever qdel'd us to reset our hud, and properly purge us
+ home.plane_masters -= "[plane]"
+ home = null
+ . = ..()
+ QDEL_LIST(relays)
+
+/// Sets the plane group that owns us, it also determines what screen we render to
+/// Returns FALSE if the set_home fails, TRUE otherwise
+/atom/movable/screen/plane_master/proc/set_home(datum/plane_master_group/home)
+ if(!istype(home, /datum/plane_master_group))
+ return FALSE
+ src.home = home
+ if(home.map)
+ screen_loc = "[home.map]:[screen_loc]"
+ assigned_map = home.map
+ return TRUE
+
+/// Updates our "offset", basically what layer of multiz we're meant to render
+/// Top is 0, goes up as you go down
+/// It's taken into account by render targets and relays, so we gotta make sure they're on the same page
+/atom/movable/screen/plane_master/proc/update_offset()
+ name = "[initial(name)] #[offset]"
+ SET_PLANE_W_SCALAR(src, real_plane, offset)
+ for(var/i in 1 to length(render_relay_planes))
+ render_relay_planes[i] = GET_NEW_PLANE(render_relay_planes[i], offset)
+ if(initial(render_target))
+ render_target = OFFSET_RENDER_TARGET(initial(render_target), offset)
+
+/atom/movable/screen/plane_master/proc/set_alpha(new_alpha)
+ true_alpha = new_alpha
+ if(!alpha_enabled)
+ return
+ alpha = new_alpha
+
+/atom/movable/screen/plane_master/proc/disable_alpha()
+ alpha_enabled = FALSE
+ alpha = 0
+
+/atom/movable/screen/plane_master/proc/enable_alpha()
+ alpha_enabled = TRUE
+ alpha = true_alpha
+
+/// Shows a plane master to the passed in mob
+/// Override this to apply unique effects and such
+/// Returns TRUE if the call is allowed, FALSE otherwise
+/atom/movable/screen/plane_master/proc/show_to(mob/mymob)
+ SHOULD_CALL_PARENT(TRUE)
+ if(force_hidden)
+ return FALSE
+
+ var/client/our_client = mymob?.canon_client
+ // Alright, let's get this out of the way
+ // Mobs can move z levels without their client. If this happens, we need to ensure critical display settings are respected
+ // This is done here. Mild to severe pain but it's nessesary
+ if(check_outside_bounds())
+ if(!(critical & PLANE_CRITICAL_DISPLAY))
+ return FALSE
+ if(!our_client)
+ return TRUE
+ our_client.screen += src
+
+ if(!(critical & PLANE_CRITICAL_NO_RELAY))
+ our_client.screen += relays
+ return TRUE
+ return TRUE
+
+ if(!our_client)
+ return TRUE
+
+ our_client.screen += src
+ our_client.screen += relays
+ return TRUE
+
+/// Hook to allow planes to work around is_outside_bounds
+/// Return false to allow a show, true otherwise
+/atom/movable/screen/plane_master/proc/check_outside_bounds()
+ return is_outside_bounds
+
+/// Hides a plane master from the passeed in mob
+/// Do your effect cleanup here
+/atom/movable/screen/plane_master/proc/hide_from(mob/oldmob)
+ SHOULD_CALL_PARENT(TRUE)
+ var/client/their_client = oldmob?.client
+ if(!their_client)
+ return
+ their_client.screen -= src
+ their_client.screen -= relays
+
+
+/// Forces this plane master to hide, until unhide_plane is called
+/// This allows us to disable unused PMs without breaking anything else
+/atom/movable/screen/plane_master/proc/hide_plane(mob/cast_away)
+ force_hidden = TRUE
+ hide_from(cast_away)
+
+/// Disables any forced hiding, allows the plane master to be used as normal
+/atom/movable/screen/plane_master/proc/unhide_plane(mob/enfold)
+ force_hidden = FALSE
+ show_to(enfold)
+
+/// Mirrors our force hidden state to the hidden state of the plane that came before, assuming it's valid
+/// This allows us to mirror any hidden sets from before we were created, no matter how low that chance is
+/atom/movable/screen/plane_master/proc/mirror_parent_hidden()
+ var/mob/our_mob = home?.our_hud?.mymob
+ var/atom/movable/screen/plane_master/true_plane = our_mob?.hud_used?.get_plane_master(plane)
+ if(true_plane == src || !true_plane)
+ return
+
+ if(true_plane.force_hidden == force_hidden)
+ return
+
+ // If one of us already exists and it's not hidden, unhide ourselves
+ if(true_plane.force_hidden)
+ hide_plane(our_mob)
+ else
+ unhide_plane(our_mob)
+
+/atom/movable/screen/plane_master/proc/outside_bounds(mob/relevant)
+ if(force_hidden || is_outside_bounds)
+ return
+ is_outside_bounds = TRUE
+ // If we're of critical importance, AND we're below the rendering layer
+ if(critical & PLANE_CRITICAL_DISPLAY)
+ // We here assume that your render target starts with *
+ if(critical & PLANE_CRITICAL_CUT_RENDER && render_target)
+ render_target = copytext_char(render_target, 2)
+ if(!(critical & PLANE_CRITICAL_NO_RELAY))
+ return
+ var/client/our_client = relevant.client
+ if(our_client)
+ for(var/atom/movable/render_plane_relay/relay as anything in relays)
+ our_client.screen -= relay
+
+ return
+ hide_from(relevant)
+
+/atom/movable/screen/plane_master/proc/inside_bounds(mob/relevant)
+ is_outside_bounds = FALSE
+ if(critical & PLANE_CRITICAL_DISPLAY)
+ // We here assume that your render target starts with *
+ if(critical & PLANE_CRITICAL_CUT_RENDER && render_target)
+ render_target = "*[render_target]"
+
+ if(!(critical & PLANE_CRITICAL_NO_RELAY))
+ return
+ var/client/our_client = relevant.client
+ if(our_client)
+ for(var/atom/movable/render_plane_relay/relay as anything in relays)
+ our_client.screen += relay
+
+ return
+ show_to(relevant)
diff --git a/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm
new file mode 100644
index 000000000000..63206afdaa93
--- /dev/null
+++ b/code/_onclick/hud/rendering/plane_masters/plane_master_subtypes.dm
@@ -0,0 +1,424 @@
+/atom/movable/screen/plane_master/field_of_vision_blocker
+ name = "Field of vision blocker"
+ documentation = "This is one of those planes that's only used as a filter. It cuts out a portion of the game plate and does effects to it."
+ plane = FIELD_OF_VISION_BLOCKER_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_target = FIELD_OF_VISION_BLOCKER_RENDER_TARGET
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_relay_planes = list()
+ // We do NOT allow offsetting, because there's no case where you would want to block only one layer, at least currently
+ allows_offsetting = FALSE
+ // We mark as multiz_scaled FALSE so transforms don't effect us, and we draw to the planes below us as if they were us.
+ // This is safe because we will ALWAYS be on the top z layer, so it DON'T MATTER
+ multiz_scaled = FALSE
+
+/atom/movable/screen/plane_master/field_of_vision_blocker/show_to(mob/mymob)
+ . = ..()
+ if(!. || !mymob)
+ return .
+ RegisterSignal(mymob, SIGNAL_ADDTRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_enabled), override = TRUE)
+ RegisterSignal(mymob, SIGNAL_REMOVETRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_disabled), override = TRUE)
+ if(HAS_TRAIT(mymob, TRAIT_FOV_APPLIED))
+ fov_enabled(mymob)
+ else
+ fov_disabled(mymob)
+
+/atom/movable/screen/plane_master/field_of_vision_blocker/proc/fov_enabled(mob/source)
+ SIGNAL_HANDLER
+ if(force_hidden == FALSE)
+ return
+ unhide_plane(source)
+
+/atom/movable/screen/plane_master/field_of_vision_blocker/proc/fov_disabled(mob/source)
+ SIGNAL_HANDLER
+ hide_plane(source)
+
+/atom/movable/screen/plane_master/clickcatcher
+ name = "Click Catcher"
+ documentation = "Contains the screen object we use as a backdrop to catch clicks on portions of the screen that would otherwise contain nothing else. \
+ Will always be below almost everything else"
+ plane = CLICKCATCHER_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ multiz_scaled = FALSE
+ critical = PLANE_CRITICAL_DISPLAY
+
+/atom/movable/screen/plane_master/clickcatcher/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(offset_increased))
+ offset_increased(SSmapping, 0, SSmapping.max_plane_offset)
+
+/atom/movable/screen/plane_master/clickcatcher/proc/offset_increased(datum/source, old_off, new_off)
+ SIGNAL_HANDLER
+ // We only want need the lowest level
+ // If my system better supported changing PM plane values mid op I'd do that, but I do NOT so
+ if(new_off > offset)
+ hide_plane(home?.our_hud?.mymob)
+
+/atom/movable/screen/plane_master/parallax_white
+ name = "Parallax whitifier"
+ documentation = "Essentially a backdrop for the parallax plane. We're rendered just below it, so we'll be multiplied by its well, parallax.\
+ If you want something to look as if it has parallax on it, draw it to this plane."
+ plane = PLANE_SPACE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_GAME, LIGHT_MASK_PLANE)
+ critical = PLANE_CRITICAL_FUCKO_PARALLAX // goes funny when touched. no idea why I don't trust byond
+
+/atom/movable/screen/plane_master/parallax_white/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_SPACE_LAYER)
+
+///Contains space parallax
+/atom/movable/screen/plane_master/parallax
+ name = "Parallax"
+ documentation = "Contains parallax, or to be more exact the screen objects that hold parallax.\
+ Note the BLEND_MULTIPLY. The trick here is how low our plane value is. Because of that, we draw below almost everything in the game.\
+ We abuse this to ensure we multiply against the Parallax whitifier plane, or space's plane. It's set to full white, so when you do the multiply you just get parallax out where it well, makes sense to be.\
+ Also notice that the parent parallax plane is mirrored down to all children. We want to support viewing parallax across all z levels at once."
+ plane = PLANE_SPACE_PARALLAX
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ blend_mode = BLEND_MULTIPLY
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ multiz_scaled = FALSE
+
+/atom/movable/screen/plane_master/parallax/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ if(offset != 0)
+ // You aren't the source? don't change yourself
+ return
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_increase))
+ RegisterSignal(SSdcs, COMSIG_NARSIE_SUMMON_UPDATE, PROC_REF(narsie_modified))
+ if(GLOB.narsie_summon_count >= 1)
+ narsie_start_midway(GLOB.narsie_effect_last_modified) // We assume we're on the start, so we can use this number
+ offset_increase(0, SSmapping.max_plane_offset)
+
+/atom/movable/screen/plane_master/parallax/proc/on_offset_increase(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_increase(old_offset, new_offset)
+
+/atom/movable/screen/plane_master/parallax/proc/offset_increase(old_offset, new_offset)
+ // Parallax will be mirrored down to any new planes that are added, so it will properly render across mirage borders
+ for(var/offset in old_offset to new_offset)
+ if(offset != 0)
+ // Overlay so we don't multiply twice, and thus fuck up our rendering
+ add_relay_to(GET_NEW_PLANE(plane, offset), BLEND_OVERLAY)
+
+// Hacky shit to ensure parallax works in perf mode
+/atom/movable/screen/plane_master/parallax/outside_bounds(mob/relevant)
+ if(offset == 0)
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_GAME, 0))
+ is_outside_bounds = TRUE // I'm sorry :(
+ return
+ // If we can't render, and we aren't the bottom layer, don't render us
+ // This way we only multiply against stuff that's not fullwhite space
+ var/atom/movable/screen/plane_master/parent_parallax = home.our_hud.get_plane_master(PLANE_SPACE_PARALLAX)
+ var/turf/viewing_turf = get_turf(relevant)
+ if(!viewing_turf || offset != GET_LOWEST_STACK_OFFSET(viewing_turf.z))
+ parent_parallax.remove_relay_from(plane)
+ else
+ parent_parallax.add_relay_to(plane, BLEND_OVERLAY)
+ return ..()
+
+/atom/movable/screen/plane_master/parallax/inside_bounds(mob/relevant)
+ if(offset == 0)
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_GAME, 0))
+ is_outside_bounds = FALSE
+ return
+ // Always readd, just in case we lost it
+ var/atom/movable/screen/plane_master/parent_parallax = home.our_hud.get_plane_master(PLANE_SPACE_PARALLAX)
+ parent_parallax.add_relay_to(plane, BLEND_OVERLAY)
+ return ..()
+
+// Needs to handle rejoining on a lower z level, so we NEED to readd old planes
+/atom/movable/screen/plane_master/parallax/check_outside_bounds()
+ // If we're outside bounds AND we're the 0th plane, we need to show cause parallax is hacked to hell
+ return offset != 0 && is_outside_bounds
+
+/// Starts the narsie animation midway, so we can catch up to everyone else quickly
+/atom/movable/screen/plane_master/parallax/proc/narsie_start_midway(start_time)
+ var/time_elapsed = world.time - start_time
+ narsie_summoned_effect(max(16 SECONDS - time_elapsed, 0))
+
+/// Starts the narsie animation, make us grey, then red
+/atom/movable/screen/plane_master/parallax/proc/narsie_modified(datum/source, new_count)
+ SIGNAL_HANDLER
+ if(new_count >= 1)
+ narsie_summoned_effect(16 SECONDS)
+ else
+ narsie_unsummoned()
+
+/atom/movable/screen/plane_master/parallax/proc/narsie_summoned_effect(animate_time)
+ if(GLOB.narsie_summon_count >= 2)
+ var/static/list/nightmare_parallax = list(255,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, -130,0,0,0)
+ animate(src, color = nightmare_parallax, time = animate_time)
+ return
+
+ var/static/list/grey_parallax = list(0.4,0.4,0.4,0, 0.4,0.4,0.4,0, 0.4,0.4,0.4,0, 0,0,0,1, -0.1,-0.1,-0.1,0)
+ // We're gonna animate ourselves grey
+ // Then, once it's done, about 40 seconds into the event itself, we're gonna start doin some shit. see below
+ animate(src, color = grey_parallax, time = animate_time)
+
+/atom/movable/screen/plane_master/parallax/proc/narsie_unsummoned()
+ animate(src, color = null, time = 8 SECONDS)
+
+/atom/movable/screen/plane_master/gravpulse
+ name = "Gravpulse"
+ documentation = "Ok so this one's fun. Basically, we want to be able to distort the game plane when a grav annom is around.\
+ So we draw the pattern we want to use to this plane, and it's then used as a render target by a distortion filter on the game plane.\
+ Note the blend mode and lack of relay targets. This plane exists only to distort, it's never rendered anywhere."
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ plane = GRAVITY_PULSE_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ blend_mode = BLEND_ADD
+ render_target = GRAVITY_PULSE_RENDER_TARGET
+ render_relay_planes = list()
+
+///Contains just the floor
+/atom/movable/screen/plane_master/floor
+ name = "Floor"
+ documentation = "The well, floor. This is mostly used as a sorting mechanism, but it also lets us create a \"border\" around the game world plane, so its drop shadow will actually work."
+ plane = FLOOR_PLANE
+ render_relay_planes = list(RENDER_PLANE_GAME, LIGHT_MASK_PLANE)
+
+/atom/movable/screen/plane_master/transparent_floor
+ name = "Transparent Floor"
+ documentation = "Really just openspace, stuff that is a turf but has no color or alpha whatsoever.\
+ We use this to draw to just the light mask plane, cause if it's not there we get holes of blackness over openspace"
+ plane = TRANSPARENT_FLOOR_PLANE
+ render_relay_planes = list(LIGHT_MASK_PLANE)
+ // Needs to be critical or it uh, it'll look white
+ critical = PLANE_CRITICAL_DISPLAY|PLANE_CRITICAL_NO_RELAY
+
+/atom/movable/screen/plane_master/floor/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_FLOOR_LAYER, relay_color = GLOB.em_block_color)
+
+/atom/movable/screen/plane_master/wall
+ name = "Wall"
+ documentation = "Holds all walls. We render this onto the game world. Separate so we can use this + space and floor planes as a guide for where byond blackness is NOT."
+ plane = WALL_PLANE
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD, LIGHT_MASK_PLANE)
+
+/atom/movable/screen/plane_master/wall/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset), relay_layer = EMISSIVE_WALL_LAYER, relay_color = GLOB.em_block_color)
+
+/atom/movable/screen/plane_master/game
+ name = "Game"
+ documentation = "Holds most non floor/wall things. Anything on this plane \"wants\" to interlayer depending on position."
+ plane = GAME_PLANE
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
+
+/atom/movable/screen/plane_master/game_world_above
+ name = "Upper Game"
+ documentation = "For stuff you want to draw like the game plane, but not ever below its contents"
+ plane = ABOVE_GAME_PLANE
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
+
+/atom/movable/screen/plane_master/seethrough
+ name = "Seethrough"
+ documentation = "Holds the seethrough versions (done using image overrides) of large objects. Mouse transparent, so you can click through them."
+ plane = SEETHROUGH_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_relay_planes = list(RENDER_PLANE_GAME_WORLD)
+ start_hidden = TRUE
+
+/**
+ * Plane master that byond will by default draw to
+ * Shouldn't be used, exists to prevent people using plane 0
+ * NOTE: If we used SEE_BLACKNESS on a map format that wasn't SIDE_MAP, this is where its darkness would land
+ * This would allow us to control it and do fun things. But we can't because side map doesn't support it, so this is just a stub
+ */
+/atom/movable/screen/plane_master/default
+ name = "Default"
+ documentation = "This is quite fiddly, so bear with me. By default (in byond) everything in the game is rendered onto plane 0. It's the default plane. \
+ But, because we've moved everything we control off plane 0, all that's left is stuff byond internally renders. \
+ What I'd like to do with this is capture byond blackness by giving mobs the SEE_BLACKNESS sight flag. \
+ But we CAN'T because SEE_BLACKNESS does not work with our rendering format. So I just eat it I guess"
+ plane = DEFAULT_PLANE
+ multiz_scaled = FALSE
+ start_hidden = TRUE // Doesn't DO anything, exists to hold this place
+
+/atom/movable/screen/plane_master/area
+ name = "Area"
+ documentation = "Holds the areas themselves, which ends up meaning it holds any overlays/effects we apply to areas. NOT snow or rad storms, those go on above lighting"
+ plane = AREA_PLANE
+
+/atom/movable/screen/plane_master/massive_obj
+ name = "Massive object"
+ documentation = "Huge objects need to render above everything else on the game plane, otherwise they'd well, get clipped and look not that huge. This does that."
+ plane = MASSIVE_OBJ_PLANE
+
+/atom/movable/screen/plane_master/point
+ name = "Point"
+ documentation = "I mean like, what do you want me to say? Points draw over pretty much everything else, so they get their own plane. Remember we layer render relays to draw planes in their proper order on render plates."
+ plane = POINT_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+///Contains all turf lighting
+/atom/movable/screen/plane_master/turf_lighting
+ name = "Turf Lighting"
+ documentation = "Contains all lighting drawn to turfs. Not so complex, draws directly onto the lighting plate."
+ plane = LIGHTING_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_LIGHTING)
+ blend_mode_override = BLEND_ADD
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ critical = PLANE_CRITICAL_DISPLAY
+
+/// This will not work through multiz, because of a byond bug with BLEND_MULTIPLY
+/// Bug report is up, waiting on a fix
+/atom/movable/screen/plane_master/o_light_visual
+ name = "Overlight light visual"
+ documentation = "Holds overlay lighting objects, or the sort of lighting that's a well, overlay stuck to something.\
+ Exists because lighting updating is really slow, and movement needs to feel smooth.\
+ We draw to the game plane, and mask out space for ourselves on the lighting plane so any color we have has the chance to display."
+ plane = O_LIGHTING_VISUAL_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_target = O_LIGHTING_VISUAL_RENDER_TARGET
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ blend_mode = BLEND_MULTIPLY
+ critical = PLANE_CRITICAL_DISPLAY
+
+/atom/movable/screen/plane_master/above_lighting
+ name = "Above lighting"
+ plane = ABOVE_LIGHTING_PLANE
+ documentation = "Anything on the game plane that needs a space to draw on that will be above the lighting plane.\
+ Mostly little alerts and effects, also sometimes contains things that are meant to look as if they glow."
+
+/**
+ * Handles emissive overlays and emissive blockers.
+ */
+/atom/movable/screen/plane_master/emissive
+ name = "Emissive"
+ documentation = "Holds things that will be used to mask the lighting plane later on. Masked by the Emissive Mask plane to ensure we don't emiss out under a wall.\
+ Relayed onto the Emissive render plane to do the actual masking of lighting, since we need to be transformed and other emissive stuff needs to be transformed too.\
+ Don't want to double scale now."
+ plane = EMISSIVE_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_relay_planes = list(EMISSIVE_RENDER_PLATE)
+ critical = PLANE_CRITICAL_DISPLAY
+
+/atom/movable/screen/plane_master/pipecrawl
+ name = "Pipecrawl"
+ documentation = "Holds pipecrawl images generated during well, pipecrawling.\
+ Has a few effects and a funky color matrix designed to make things a bit more visually readable."
+ plane = PIPECRAWL_IMAGES_PLANE
+ start_hidden = TRUE
+
+/atom/movable/screen/plane_master/pipecrawl/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ // Makes everything on this plane slightly brighter
+ // Has a nice effect, makes thing stand out
+ color = list(1.2,0,0,0, 0,1.2,0,0, 0,0,1.2,0, 0,0,0,1, 0,0,0,0)
+ // This serves a similar purpose, I want the pipes to pop
+ add_filter("pipe_dropshadow", 1, drop_shadow_filter(x = -1, y= -1, size = 1, color = "#0000007A"))
+ mirror_parent_hidden()
+
+/atom/movable/screen/plane_master/camera_static
+ name = "Camera static"
+ documentation = "Holds camera static images. Usually only visible to people who can well, see static.\
+ We use images rather then vis contents because they're lighter on maptick, and maptick sucks butt."
+ plane = CAMERA_STATIC_PLANE
+
+/atom/movable/screen/plane_master/camera_static/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ var/datum/hud/our_hud = home.our_hud
+ if(isnull(our_hud))
+ return
+
+ // We'll hide the slate if we're not seeing through a camera eye
+ // This can call on a cycle cause we don't clear in hide_from
+ // Yes this is the best way of hooking into the hud, I hate myself too
+ RegisterSignal(our_hud, COMSIG_HUD_EYE_CHANGED, PROC_REF(eye_changed), override = TRUE)
+ eye_changed(our_hud, null, our_hud.mymob?.canon_client?.eye)
+
+/atom/movable/screen/plane_master/camera_static/proc/eye_changed(datum/hud/source, atom/old_eye, atom/new_eye)
+ SIGNAL_HANDLER
+
+ if(!isaicamera(new_eye))
+ if(!force_hidden)
+ hide_plane(source.mymob)
+ return
+
+ if(force_hidden)
+ unhide_plane(source.mymob)
+
+/atom/movable/screen/plane_master/high_game
+ name = "High Game"
+ documentation = "Holds anything that wants to be displayed above the rest of the game plane, and doesn't want to be clickable. \
+ This includes atmos debug overlays, blind sound images, and mining scanners. \
+ Really only exists for its layering potential, we don't use this for any vfx"
+ plane = HIGH_GAME_PLANE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/atom/movable/screen/plane_master/ghost
+ name = "Ghost"
+ documentation = "Ghosts draw here, so they don't get mixed up in the visuals of the game world. Note, this is not not how we HIDE ghosts from people, that's done with invisible and see_invisible."
+ plane = GHOST_PLANE
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+
+/atom/movable/screen/plane_master/fullscreen
+ name = "Fullscreen"
+ documentation = "Holds anything that applies to or above the full screen. \
+ Note, it's still rendered underneath hud objects, but this lets us control the order that things like death/damage effects render in."
+ plane = FULLSCREEN_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ allows_offsetting = FALSE
+
+/atom/movable/screen/plane_master/runechat
+ name = "Runechat"
+ documentation = "Holds runechat images, that text that pops up when someone say something. Uses a dropshadow to well, look nice."
+ plane = RUNECHAT_PLANE
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+
+/atom/movable/screen/plane_master/runechat/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ remove_filter("AO")
+ if(istype(mymob) && mymob.canon_client?.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion))
+ add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
+
+/atom/movable/screen/plane_master/balloon_chat
+ name = "Balloon chat"
+ documentation = "Holds ballon chat images, those little text bars that pop up for a second when you do some things. NOT runechat."
+ plane = BALLOON_CHAT_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+
+/atom/movable/screen/plane_master/hud
+ name = "HUD"
+ documentation = "Contains anything that want to be rendered on the hud. Typically is just screen elements."
+ plane = HUD_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+ allows_offsetting = FALSE
+
+/atom/movable/screen/plane_master/above_hud
+ name = "Above HUD"
+ documentation = "Anything that wants to be drawn ABOVE the rest of the hud. Typically close buttons and other elements that need to be always visible. Think preventing draggable action button memes."
+ plane = ABOVE_HUD_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+ allows_offsetting = FALSE
+
+/atom/movable/screen/plane_master/splashscreen
+ name = "Splashscreen"
+ documentation = "Cinematics and the splash screen."
+ plane = SPLASHSCREEN_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_NON_GAME)
+ allows_offsetting = FALSE
+
+/atom/movable/screen/plane_master/escape_menu
+ name = "Escape Menu"
+ documentation = "Anything relating to the escape menu."
+ plane = ESCAPE_MENU_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ render_relay_planes = list(RENDER_PLANE_MASTER)
+ allows_offsetting = FALSE
diff --git a/code/_onclick/hud/rendering/render_plate.dm b/code/_onclick/hud/rendering/render_plate.dm
new file mode 100644
index 000000000000..f3d838f8594b
--- /dev/null
+++ b/code/_onclick/hud/rendering/render_plate.dm
@@ -0,0 +1,482 @@
+/*!
+ * Custom rendering solution to allow for advanced effects
+ * We (ab)use plane masters and render source/target to cheaply render 2+ planes as 1
+ * if you want to read more read the _render_readme.md
+ */
+
+
+/**
+ * Render relay object assigned to a plane master to be able to relay it's render onto other planes that are not it's own
+ */
+/atom/movable/render_plane_relay
+ screen_loc = "CENTER"
+ layer = -1
+ plane = 0
+ appearance_flags = PASS_MOUSE | NO_CLIENT_COLOR | KEEP_TOGETHER
+ /// If we render into a critical plane master, or not
+ var/critical_target = FALSE
+
+/**
+ * ## Rendering plate
+ *
+ * Acts like a plane master, but for plane masters
+ * Renders other planes onto this plane, through the use of render objects
+ * Any effects applied onto this plane will act on the unified plane
+ * IE a bulge filter will apply as if the world was one object
+ * remember that once planes are unified on a render plate you cant change the layering of them!
+ */
+/atom/movable/screen/plane_master/rendering_plate
+ name = "Default rendering plate"
+ multiz_scaled = FALSE
+
+///this plate renders the final screen to show to the player
+/atom/movable/screen/plane_master/rendering_plate/master
+ name = "Master rendering plate"
+ documentation = "The endpoint of all plane masters, you can think of this as the final \"view\" we draw.\
+ If offset is not 0 this will be drawn to the transparent plane of the floor above, but otherwise this is drawn to nothing, or shown to the player."
+ plane = RENDER_PLANE_MASTER
+ render_relay_planes = list()
+
+/atom/movable/screen/plane_master/rendering_plate/master/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ if(offset == 0)
+ return
+ // Non 0 offset render plates will relay up to the transparent plane above them, assuming they're not on the same z level as their target of course
+ var/datum/hud/hud = home.our_hud
+ // show_to can be called twice successfully with no hide_from call. Ensure no runtimes off the registers from this
+ if(hud)
+ RegisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, PROC_REF(on_offset_change), override = TRUE)
+ offset_change(hud?.current_plane_offset || 0)
+
+/atom/movable/screen/plane_master/rendering_plate/master/hide_from(mob/oldmob)
+ . = ..()
+ if(offset == 0)
+ return
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ UnregisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, PROC_REF(on_offset_change))
+
+/atom/movable/screen/plane_master/rendering_plate/master/proc/on_offset_change(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_change(new_offset)
+
+/atom/movable/screen/plane_master/rendering_plate/master/proc/offset_change(new_offset)
+ if(new_offset == offset) // If we're on our own z layer, relay to nothing, just draw
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_TRANSPARENT, offset - 1))
+ else // Otherwise, regenerate the relay
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_TRANSPARENT, offset - 1))
+
+///renders general in charachter game objects
+/atom/movable/screen/plane_master/rendering_plate/game_plate
+ name = "Game rendering plate"
+ documentation = "Holds all objects that are ahhh, in character? is maybe the best way to describe it.\
+ We apply a displacement effect from the gravity pulse plane too, so we can warp the game world.\
+ If we have fov enabled we'll relay this onto two different rendering plates to apply fov effects to only a portion. If not, we just draw straight to master"
+ plane = RENDER_PLANE_GAME
+ render_relay_planes = list(RENDER_PLANE_MASTER)
+
+/atom/movable/screen/plane_master/rendering_plate/game_plate/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ add_filter("displacer", 1, displacement_map_filter(render_source = OFFSET_RENDER_TARGET(GRAVITY_PULSE_RENDER_TARGET, offset), size = 10))
+ if(check_holidays(HALLOWEEN))
+ // Makes things a tad greyscale (leaning purple) and drops low colors for vibes
+ // We're basically using alpha as better constant here btw
+ add_filter("spook_color", 2, color_matrix_filter(list(0.75,0.13,0.13,0, 0.13,0.7,0.13,0, 0.13,0.13,0.75,0, -0.06,-0.09,-0.08,1, 0,0,0,0)))
+
+/atom/movable/screen/plane_master/rendering_plate/game_plate/show_to(mob/mymob)
+ . = ..()
+ if(!. || !mymob)
+ return .
+ RegisterSignal(mymob, SIGNAL_ADDTRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_enabled), override = TRUE)
+ RegisterSignal(mymob, SIGNAL_REMOVETRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_disabled), override = TRUE)
+ if(HAS_TRAIT(mymob, TRAIT_FOV_APPLIED))
+ fov_enabled(mymob)
+ else
+ fov_disabled(mymob)
+
+/atom/movable/screen/plane_master/rendering_plate/game_plate/proc/fov_enabled(mob/source)
+ SIGNAL_HANDLER
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_GAME_UNMASKED, offset))
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_GAME_MASKED, offset))
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_MASTER, offset))
+
+/atom/movable/screen/plane_master/rendering_plate/game_plate/proc/fov_disabled(mob/source)
+ SIGNAL_HANDLER
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_GAME_UNMASKED, offset))
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_GAME_MASKED, offset))
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_MASTER, offset))
+
+///renders the parts of the plate unmasked by fov
+/atom/movable/screen/plane_master/rendering_plate/unmasked_game_plate
+ name = "Unmasked Game rendering plate"
+ documentation = "Holds the bits of the game plate that aren't impacted by fov.\
+ We use an alpha mask to cut out the bits we plan on dealing with elsewhere"
+ plane = RENDER_PLANE_GAME_UNMASKED
+ render_relay_planes = list(RENDER_PLANE_MASTER)
+
+/atom/movable/screen/plane_master/rendering_plate/unmasked_game_plate/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ add_filter("fov_handled", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(FIELD_OF_VISION_BLOCKER_RENDER_TARGET, offset), flags = MASK_INVERSE))
+
+/atom/movable/screen/plane_master/rendering_plate/unmasked_game_plate/show_to(mob/mymob)
+ . = ..()
+ if(!. || !mymob)
+ return .
+ RegisterSignal(mymob, SIGNAL_ADDTRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_enabled), override = TRUE)
+ RegisterSignal(mymob, SIGNAL_REMOVETRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_disabled), override = TRUE)
+ if(HAS_TRAIT(mymob, TRAIT_FOV_APPLIED))
+ fov_enabled(mymob)
+ else
+ fov_disabled(mymob)
+
+/atom/movable/screen/plane_master/rendering_plate/unmasked_game_plate/proc/fov_enabled(mob/source)
+ SIGNAL_HANDLER
+ if(force_hidden == FALSE)
+ return
+ unhide_plane(source)
+
+/atom/movable/screen/plane_master/rendering_plate/unmasked_game_plate/proc/fov_disabled(mob/source)
+ SIGNAL_HANDLER
+ hide_plane(source)
+
+///renders the parts of the plate masked by fov
+/atom/movable/screen/plane_master/rendering_plate/masked_game_plate
+ name = "FOV Game rendering plate"
+ documentation = "Contains the bits of the game plate that are hidden by some form of fov\
+ Applies a color matrix to dim and create contrast, alongside a blur. Goal is only half being able to see stuff"
+ plane = RENDER_PLANE_GAME_MASKED
+ render_relay_planes = list(RENDER_PLANE_MASTER)
+
+/atom/movable/screen/plane_master/rendering_plate/masked_game_plate/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ add_filter("fov_blur", 1, gauss_blur_filter(1.8))
+ add_filter("fov_handled_space", 2, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(FIELD_OF_VISION_BLOCKER_RENDER_TARGET, offset)))
+ add_filter("fov_matrix", 3, color_matrix_filter(list(0.5,-0.15,-0.15,0, -0.15,0.5,-0.15,0, -0.15,-0.15,0.5,0, 0,0,0,1, 0,0,0,0)))
+
+/atom/movable/screen/plane_master/rendering_plate/masked_game_plate/show_to(mob/mymob)
+ . = ..()
+ if(!. || !mymob)
+ return .
+ RegisterSignal(mymob, SIGNAL_ADDTRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_enabled), override = TRUE)
+ RegisterSignal(mymob, SIGNAL_REMOVETRAIT(TRAIT_FOV_APPLIED), PROC_REF(fov_disabled), override = TRUE)
+ if(HAS_TRAIT(mymob, TRAIT_FOV_APPLIED))
+ fov_enabled(mymob)
+ else
+ fov_disabled(mymob)
+
+/atom/movable/screen/plane_master/rendering_plate/masked_game_plate/proc/fov_enabled(mob/source)
+ SIGNAL_HANDLER
+ if(force_hidden == FALSE)
+ return
+ unhide_plane(source)
+
+/atom/movable/screen/plane_master/rendering_plate/masked_game_plate/proc/fov_disabled(mob/source)
+ SIGNAL_HANDLER
+ hide_plane(source)
+
+// Blackness renders weird when you view down openspace, because of transforms and borders and such
+// This is a consequence of not using lummy's grouped transparency, but I couldn't get that to work without totally fucking up
+// Sight flags, and shooting vis_contents usage to the moon. So we're doin it different.
+// If image vis contents worked (it should in 515), and we were ok with a maptick cost (wait for threaded maptick) this could be fixed
+/atom/movable/screen/plane_master/rendering_plate/transparent
+ name = "Transparent plate"
+ documentation = "The master rendering plate from the offset below ours will be mirrored onto this plane. That way we achive a \"stack\" effect.\
+ This plane exists to uplayer the master rendering plate to the correct spot in our z layer's rendering order"
+ plane = RENDER_PLANE_TRANSPARENT
+ appearance_flags = PLANE_MASTER
+
+/atom/movable/screen/plane_master/rendering_plate/transparent/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ // Don't display us if we're below everything else yeah?
+ AddComponent(/datum/component/plane_hide_highest_offset)
+ color = list(0.9,0,0,0, 0,0.9,0,0, 0,0,0.9,0, 0,0,0,1, 0,0,0,0)
+
+///Contains most things in the game world
+/atom/movable/screen/plane_master/rendering_plate/game_world
+ name = "Game world plate"
+ documentation = "Contains most of the objects in the world. Mobs, machines, etc. Note the drop shadow, it gives a very nice depth effect."
+ plane = RENDER_PLANE_GAME_WORLD
+ appearance_flags = PLANE_MASTER //should use client color
+ blend_mode = BLEND_OVERLAY
+
+/atom/movable/screen/plane_master/rendering_plate/game_world/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ remove_filter("AO")
+ if(istype(mymob) && mymob.canon_client?.prefs?.read_preference(/datum/preference/toggle/ambient_occlusion))
+ add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
+
+///Contains all lighting objects
+/atom/movable/screen/plane_master/rendering_plate/lighting
+ name = "Lighting plate"
+ documentation = "Anything on this plane will be multiplied with the plane it's rendered onto (typically the game plane).\
+ That's how lighting functions at base. Because it uses BLEND_MULTIPLY and occasionally color matrixes, it needs a backdrop of blackness.\
+ See This byond post\
+ Lemme see uh, we're masked by the emissive plane so it can actually function (IE: make things glow in the dark).\
+ We're also masked by the overlay lighting plane, which contains all the movable lights in the game. It draws to us and also the game plane.\
+ Masks us out so it has the breathing room to apply its effect.\
+ Oh and we quite often have our alpha changed to achive night vision effects, or things of that sort."
+ plane = RENDER_PLANE_LIGHTING
+ blend_mode_override = BLEND_MULTIPLY
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ critical = PLANE_CRITICAL_DISPLAY
+ /// A list of light cutoffs we're actively using, (mass, r, g, b) to avoid filter churn
+ var/list/light_cutoffs
+
+/*!
+ * This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.
+ *
+ * Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.
+ * Emissive blockers are pasted with an atom color that converts them to be entirely some different color.
+ * Emissive overlays and emissive blockers are put onto the same plane.
+ * The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.
+ * A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.
+ * This is then used to alpha mask the lighting plane.
+ */
+/atom/movable/screen/plane_master/rendering_plate/lighting/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ add_filter("emissives", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(EMISSIVE_RENDER_TARGET, offset), flags = MASK_INVERSE))
+ add_filter("object_lighting", 2, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(O_LIGHTING_VISUAL_RENDER_TARGET, offset), flags = MASK_INVERSE))
+ set_light_cutoff(10)
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+ // This applies a backdrop to our lighting plane
+ // Why do plane masters need a backdrop sometimes? Read https://secure.byond.com/forum/?post=2141928
+ // Basically, we need something to brighten
+ // unlit is perhaps less needed rn, it exists to provide a fullbright for things that can't see the lighting plane
+ // but we don't actually use invisibility to hide the lighting plane anymore, so it's pointless
+ var/atom/movable/screen/backdrop = mymob.overlay_fullscreen("lighting_backdrop_lit_[home.key]#[offset]", /atom/movable/screen/fullscreen/lighting_backdrop/lit)
+ // Need to make sure they're on our plane, ALL the time. We always need a backdrop
+ SET_PLANE_EXPLICIT(backdrop, PLANE_TO_TRUE(backdrop.plane), src)
+ backdrop = mymob.overlay_fullscreen("lighting_backdrop_unlit_[home.key]#[offset]", /atom/movable/screen/fullscreen/lighting_backdrop/unlit)
+ SET_PLANE_EXPLICIT(backdrop, PLANE_TO_TRUE(backdrop.plane), src)
+
+ // Sorry, this is a bit annoying
+ // Basically, we only want the lighting plane we can actually see to attempt to render
+ // If we don't our lower plane gets totally overriden by the black void of the upper plane
+ var/datum/hud/hud = home.our_hud
+ // show_to can be called twice successfully with no hide_from call. Ensure no runtimes off the registers from this
+ if(hud)
+ RegisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, PROC_REF(on_offset_change), override = TRUE)
+ offset_change(hud?.current_plane_offset || 0)
+ set_light_cutoff(mymob.lighting_cutoff, mymob.lighting_color_cutoffs)
+
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/hide_from(mob/oldmob)
+ . = ..()
+ oldmob.clear_fullscreen("lighting_backdrop_lit_[home.key]#[offset]")
+ oldmob.clear_fullscreen("lighting_backdrop_unlit_[home.key]#[offset]")
+ var/datum/hud/hud = home.our_hud
+ if(hud)
+ UnregisterSignal(hud, COMSIG_HUD_OFFSET_CHANGED, PROC_REF(on_offset_change))
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/proc/on_offset_change(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_change(new_offset)
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/proc/offset_change(mob_offset)
+ // Offsets stack down remember. This implies that we're above the mob's view plane, and shouldn't render
+ if(offset < mob_offset)
+ disable_alpha()
+ else
+ enable_alpha()
+
+/atom/movable/screen/plane_master/rendering_plate/lighting/proc/set_light_cutoff(light_cutoff, list/color_cutoffs)
+ var/list/new_cutoffs = list(light_cutoff)
+ new_cutoffs += color_cutoffs
+ if(new_cutoffs ~= light_cutoffs)
+ return
+
+ remove_filter(list("light_cutdown", "light_cutup"))
+
+ var/ratio = light_cutoff/100
+ if(!color_cutoffs)
+ color_cutoffs = list(0, 0, 0)
+
+ var/red = color_cutoffs[1] / 100
+ var/green = color_cutoffs[2] / 100
+ var/blue = color_cutoffs[3] / 100
+ add_filter("light_cutdown", 3, color_matrix_filter(list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, -(ratio + red),-(ratio+green),-(ratio+blue),0)))
+ add_filter("light_cutup", 4, color_matrix_filter(list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, ratio+red,ratio+green,ratio+blue,0)))
+
+/atom/movable/screen/plane_master/rendering_plate/emissive_slate
+ name = "Emissive Plate"
+ documentation = "This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.\
+ Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.\
+ Emissive blockers are pasted with an atom color that converts them to be entirely some different color.\
+ Emissive overlays and emissive blockers are put onto the same plane (This one).\
+ The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.\
+ A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.\
+ This is then used to alpha mask the lighting plane."
+ plane = EMISSIVE_RENDER_PLATE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target = EMISSIVE_RENDER_TARGET
+ render_relay_planes = list()
+ critical = PLANE_CRITICAL_DISPLAY
+
+/atom/movable/screen/plane_master/rendering_plate/emissive_slate/Initialize(mapload, datum/hud/hud_owner, datum/plane_master_group/home, offset)
+ . = ..()
+ add_filter("em_block_masking", 2, color_matrix_filter(GLOB.em_mask_matrix))
+ if(offset != 0)
+ add_relay_to(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset - 1), relay_layer = EMISSIVE_Z_BELOW_LAYER)
+
+/atom/movable/screen/plane_master/rendering_plate/light_mask
+ name = "Light Mask"
+ documentation = "Any part of this plane that is transparent will be black below it on the game rendering plate.\
+ This is done to ensure emissives and overlay lights don't light things up \"through\" the darkness that normally sits at the bottom of the lighting plane.\
+ We relay copies of the space, floor and wall planes to it, so we can use them as masks. Then we just boost any existing alpha to 100% and we're done.\
+ If we ever switch to a sight setup that shows say, mobs but not floors, we instead mask just overlay lighting and emissives.\
+ This avoids dumb seethrough without breaking stuff like thermals."
+ plane = LIGHT_MASK_PLANE
+ appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
+ // Fullwhite where there's anything, no color otherwise
+ color = list(255,255,255,255, 255,255,255,255, 255,255,255,255, 255,255,255,255, 0,0,0,0)
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ render_target = LIGHT_MASK_RENDER_TARGET
+ // We blend against the game plane, so she's gotta multiply!
+ blend_mode = BLEND_MULTIPLY
+ render_relay_planes = list(RENDER_PLANE_GAME)
+
+/atom/movable/screen/plane_master/rendering_plate/light_mask/show_to(mob/mymob)
+ . = ..()
+ if(!.)
+ return
+
+ RegisterSignal(mymob, COMSIG_MOB_SIGHT_CHANGE, PROC_REF(handle_sight))
+ handle_sight(mymob, mymob.sight, NONE)
+
+/atom/movable/screen/plane_master/rendering_plate/light_mask/hide_from(mob/oldmob)
+ . = ..()
+ var/atom/movable/screen/plane_master/overlay_lights = home.get_plane(GET_NEW_PLANE(O_LIGHTING_VISUAL_PLANE, offset))
+ overlay_lights.remove_filter("lighting_mask")
+ var/atom/movable/screen/plane_master/emissive = home.get_plane(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset))
+ emissive.remove_filter("lighting_mask")
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_GAME, offset))
+ UnregisterSignal(oldmob, COMSIG_MOB_SIGHT_CHANGE)
+
+/atom/movable/screen/plane_master/rendering_plate/light_mask/proc/handle_sight(datum/source, new_sight, old_sight)
+ // If we can see something that shows "through" blackness, and we can't see turfs, disable our draw to the game plane
+ // And instead mask JUST the overlay lighting plane, since that will look fuckin wrong
+ var/atom/movable/screen/plane_master/overlay_lights = home.get_plane(GET_NEW_PLANE(O_LIGHTING_VISUAL_PLANE, offset))
+ var/atom/movable/screen/plane_master/emissive = home.get_plane(GET_NEW_PLANE(EMISSIVE_RENDER_PLATE, offset))
+ if(new_sight & SEE_AVOID_TURF_BLACKNESS && !(new_sight & SEE_TURFS))
+ remove_relay_from(GET_NEW_PLANE(RENDER_PLANE_GAME, offset))
+ overlay_lights.add_filter("lighting_mask", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(LIGHT_MASK_RENDER_TARGET, offset)))
+ emissive.add_filter("lighting_mask", 1, alpha_mask_filter(render_source = OFFSET_RENDER_TARGET(LIGHT_MASK_RENDER_TARGET, offset)))
+ // If we CAN'T see through the black, then draw er down brother!
+ else
+ overlay_lights.remove_filter("lighting_mask")
+ emissive.remove_filter("lighting_mask")
+ // We max alpha here, so our darkness is actually.. dark
+ // Can't do it before cause it fucks with the filter
+ add_relay_to(GET_NEW_PLANE(RENDER_PLANE_GAME, offset), relay_color = list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,1))
+
+///render plate for OOC stuff like ghosts, hud-screen effects, etc
+/atom/movable/screen/plane_master/rendering_plate/non_game
+ name = "Non-Game rendering plate"
+ documentation = "Renders anything that's out of character. Mostly useful as a converse to the game rendering plate."
+ plane = RENDER_PLANE_NON_GAME
+ render_relay_planes = list(RENDER_PLANE_MASTER)
+
+/**
+ * Plane master proc called in Initialize() that creates relay objects, and sets them uo as needed
+ * Sets:
+ * * layer from plane to avoid z-fighting
+ * * planes to relay the render to
+ * * render_source so that the plane will render on these objects
+ * * mouse opacity to ensure proper mouse hit tracking
+ * * name for debugging purposes
+ * Other vars such as alpha will automatically be applied with the render source
+ */
+/atom/movable/screen/plane_master/proc/generate_render_relays()
+ var/relay_loc = home?.relay_loc || "CENTER"
+ // If we're using a submap (say for a popup window) make sure we draw onto it
+ if(home?.map)
+ relay_loc = "[home.map]:[relay_loc]"
+
+ var/list/generated_planes = list()
+ for(var/atom/movable/render_plane_relay/relay as anything in relays)
+ generated_planes += relay.plane
+
+ for(var/relay_plane in (render_relay_planes - generated_planes))
+ generate_relay_to(relay_plane, relay_loc)
+
+ if(blend_mode != BLEND_MULTIPLY)
+ blend_mode = BLEND_DEFAULT
+ relays_generated = TRUE
+
+/// Creates a connection between this plane master and the passed in plane
+/// Helper for out of system code, shouldn't be used in this file
+/// Build system to differenchiate between generated and non generated render relays
+/atom/movable/screen/plane_master/proc/add_relay_to(target_plane, blend_override, relay_layer, relay_color)
+ if(get_relay_to(target_plane))
+ return
+ render_relay_planes += target_plane
+ var/client/display_lad = home?.our_hud?.mymob?.canon_client
+ var/atom/movable/render_plane_relay/relay = generate_relay_to(target_plane, show_to = display_lad, blend_override = blend_override, relay_layer = relay_layer)
+ relay.color = relay_color
+
+/proc/get_plane_master_render_base(name)
+ return "*[name]: AUTOGENERATED RENDER TGT"
+
+/atom/movable/screen/plane_master/proc/generate_relay_to(target_plane, relay_loc, client/show_to, blend_override, relay_layer)
+ if(!length(relays) && !initial(render_target))
+ render_target = OFFSET_RENDER_TARGET(get_plane_master_render_base(name), offset)
+ if(!relay_loc)
+ relay_loc = "CENTER"
+ // If we're using a submap (say for a popup window) make sure we draw onto it
+ if(home?.map)
+ relay_loc = "[home.map]:[relay_loc]"
+ var/blend_to_use = blend_override
+ if(isnull(blend_to_use))
+ blend_to_use = blend_mode_override || initial(blend_mode)
+
+ var/atom/movable/render_plane_relay/relay = new()
+ relay.render_source = render_target
+ relay.plane = target_plane
+ relay.screen_loc = relay_loc
+ // There are two rules here
+ // 1: layer needs to be positive (negative layers are treated as float layers)
+ // 2: lower planes (including offset ones) need to be layered below higher ones (because otherwise they'll render fucky)
+ // By multiplying LOWEST_EVER_PLANE by 30, we give 30 offsets worth of room to planes before they start going negative
+ // Bet
+ // We allow for manuel override if requested. careful with this
+ relay.layer = relay_layer || (plane + abs(LOWEST_EVER_PLANE * 30)) //layer must be positive but can be a decimal
+ relay.blend_mode = blend_to_use
+ relay.mouse_opacity = mouse_opacity
+ relay.name = render_target
+ relay.critical_target = PLANE_IS_CRITICAL(target_plane)
+ relays += relay
+ // Relays are sometimes generated early, before huds have a mob to display stuff to
+ // That's what this is for
+ if(show_to)
+ show_to.screen += relay
+ return relay
+
+/// Breaks a connection between this plane master, and the passed in place
+/atom/movable/screen/plane_master/proc/remove_relay_from(target_plane)
+ render_relay_planes -= target_plane
+ var/atom/movable/render_plane_relay/existing_relay = get_relay_to(target_plane)
+ if(!existing_relay)
+ return
+ relays -= existing_relay
+ if(!length(relays) && !initial(render_target))
+ render_target = null
+ var/client/lad = home?.our_hud?.mymob?.canon_client
+ if(lad)
+ lad.screen -= existing_relay
+
+/// Gets the relay atom we're using to connect to the target plane, if one exists
+/atom/movable/screen/plane_master/proc/get_relay_to(target_plane)
+ for(var/atom/movable/render_plane_relay/relay in relays)
+ if(relay.plane == target_plane)
+ return relay
+
+ return null
diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm
index 37acb22e59e2..271097598806 100644
--- a/code/_onclick/hud/robot.dm
+++ b/code/_onclick/hud/robot.dm
@@ -224,8 +224,7 @@
A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7"
else
A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7"
- A.layer = ABOVE_HUD_LAYER
- A.plane = ABOVE_HUD_PLANE
+ SET_PLANE_IMPLICIT(A, ABOVE_HUD_PLANE)
x++
if(x == 4)
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index c6d23ad8b245..0bd2e41e2857 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -9,24 +9,41 @@
/atom/movable/screen
name = ""
icon = 'icons/mob/screen_gen.dmi'
- layer = HUD_LAYER
+ // NOTE: screen objects do NOT change their plane to match the z layer of their owner
+ // You shouldn't need this, but if you ever do and it's widespread, reconsider what you're doing.
plane = HUD_PLANE
animate_movement = SLIDE_STEPS
speech_span = SPAN_ROBOT
- vis_flags = VIS_INHERIT_PLANE
appearance_flags = APPEARANCE_UI
/// A reference to the object in the slot. Grabs or items, generally.
- var/obj/master = null
+ var/datum/weakref/master_ref = null
/// A reference to the owner HUD, if any.
VAR_PRIVATE/datum/hud/hud = null
+ /**
+ * Map name assigned to this object.
+ * Automatically set by /client/proc/add_obj_to_map.
+ */
+ var/assigned_map
+ /**
+ * Mark this object as garbage-collectible after you clean the map
+ * it was registered on.
+ *
+ * This could probably be changed to be a proc, for conditional removal.
+ * But for now, this works.
+ */
+ var/del_on_map_removal = TRUE
+
+ /// If FALSE, this will not be cleared when calling /client/clear_screen()
+ var/clear_with_screen = TRUE
+
/atom/movable/screen/New(datum/hud/new_hud)
. = ..()
if(istype(new_hud))
hud = new_hud
/atom/movable/screen/Destroy()
- master = null
+ master_ref = null
hud = null
return ..()
@@ -39,6 +56,10 @@
/atom/movable/screen/proc/component_click(atom/movable/screen/component_button/component, params)
return
+/// Returns the mob this is being displayed to, if any
+/atom/movable/screen/proc/get_mob()
+ return hud?.mymob
+
/atom/movable/screen/text
icon = null
icon_state = null
@@ -48,7 +69,6 @@
maptext_width = 480
/atom/movable/screen/swap_hand
- layer = HUD_LAYER
plane = HUD_PLANE
name = "swap hand"
@@ -152,23 +172,26 @@
screen_loc = ui_ghost_language_menu
/atom/movable/screen/inventory
- var/slot_id // The indentifier for the slot. It has nothing to do with ID cards.
- var/icon_empty // Icon when empty. For now used only by humans.
- var/icon_full // Icon when contains an item. For now used only by humans.
- var/list/object_overlays = list()
- layer = HUD_LAYER
+ /// The identifier for the slot. It has nothing to do with ID cards.
+ var/slot_id
+ /// Icon when empty. For now used only by humans.
+ var/icon_empty
+ /// Icon when contains an item. For now used only by humans.
+ var/icon_full
+ /// The overlay when hovering over with an item in your hand
+ var/image/object_overlay
plane = HUD_PLANE
/atom/movable/screen/inventory/Click(location, control, params)
// At this point in client Click() code we have passed the 1/10 sec check and little else
// We don't even know if it's a middle click
if(world.time <= usr.next_move)
- return 1
+ return TRUE
if(usr.incapacitated())
- return 1
+ return TRUE
if(ismecha(usr.loc)) // stops inventory actions in a mech
- return 1
+ return TRUE
if(hud && hud.mymob && slot_id)
var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id)
@@ -177,7 +200,7 @@
if(usr.attack_ui(slot_id))
usr.update_inv_hands()
- return 1
+ return TRUE
/atom/movable/screen/inventory/MouseEntered()
..()
@@ -185,8 +208,8 @@
/atom/movable/screen/inventory/MouseExited()
..()
- cut_overlay(object_overlays)
- object_overlays.Cut()
+ cut_overlay(object_overlay)
+ QDEL_NULL(object_overlay)
/atom/movable/screen/inventory/update_icon_state()
if(!icon_empty)
@@ -197,24 +220,27 @@
return ..()
/atom/movable/screen/inventory/proc/add_overlays()
- var/mob/user = hud.mymob
+ var/mob/user = hud?.mymob
- if(hud && user && slot_id)
- var/obj/item/holding = user.get_active_held_item()
+ if(!user || !slot_id)
+ return
- if(!holding || user.get_item_by_slot(slot_id))
- return
+ var/obj/item/holding = user.get_active_held_item()
- var/image/item_overlay = image(holding)
- item_overlay.alpha = 92
+ if(!holding || user.get_item_by_slot(slot_id))
+ return
- if(!user.can_equip(holding, slot_id, TRUE))
- item_overlay.color = "#FF0000"
- else
- item_overlay.color = "#00ff00"
+ var/image/item_overlay = image(holding)
+ item_overlay.alpha = 92
- object_overlays += item_overlay
- add_overlay(object_overlays)
+ if(!user.can_equip(holding, slot_id, TRUE))
+ item_overlay.color = "#FF0000"
+ else
+ item_overlay.color = "#00ff00"
+
+ cut_overlay(object_overlay)
+ object_overlay = item_overlay
+ add_overlay(object_overlay)
/atom/movable/screen/inventory/hand
var/mutable_appearance/handcuff_overlay
@@ -264,24 +290,24 @@
/atom/movable/screen/close
name = "close"
- layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
icon_state = "backpack_close"
/atom/movable/screen/close/Initialize(mapload, new_master)
. = ..()
- master = new_master
+ master_ref = WEAKREF(new_master)
/atom/movable/screen/close/Click()
- var/datum/component/storage/S = master
- S.hide_from(usr)
+ var/datum/component/storage/storage = master_ref?.resolve()
+ if(!storage)
+ return
+ storage.hide_from(usr)
return TRUE
/atom/movable/screen/drop
name = "drop"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_drop"
- layer = HUD_LAYER
plane = HUD_PLANE
/atom/movable/screen/drop/Click()
@@ -362,7 +388,6 @@
name = "resist"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_resist"
- layer = HUD_LAYER
plane = HUD_PLANE
/atom/movable/screen/resist/Click()
@@ -374,7 +399,6 @@
name = "rest"
icon = 'icons/mob/screen_midnight.dmi'
icon_state = "act_rest"
- layer = HUD_LAYER
plane = HUD_PLANE
/atom/movable/screen/rest/Click()
@@ -393,24 +417,27 @@
name = "storage"
icon_state = "block"
screen_loc = "7,7 to 10,8"
- layer = HUD_LAYER
plane = HUD_PLANE
/atom/movable/screen/storage/Initialize(mapload, new_master)
. = ..()
- master = new_master
+ master_ref = WEAKREF(new_master)
/atom/movable/screen/storage/Click(location, control, params)
+ var/datum/component/storage/storage_master = master_ref?.resolve()
+ if(!istype(storage_master))
+ return FALSE
+
if(world.time <= usr.next_move)
return TRUE
if(usr.incapacitated())
return TRUE
if (ismecha(usr.loc)) // stops inventory actions in a mech
return TRUE
- if(master)
- var/obj/item/I = usr.get_active_held_item()
- if(I)
- master.attackby(null, I, usr, params)
+
+ var/obj/item/inserted = usr.get_active_held_item()
+ if(inserted)
+ storage_master.attackby(null, inserted, usr, params)
return TRUE
/atom/movable/screen/throw_catch
@@ -474,7 +501,6 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
alpha = 128
anchored = TRUE
- layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
/atom/movable/screen/zone_sel/MouseExited(location, control, params)
@@ -657,12 +683,15 @@
icon = 'icons/blanks/blank_title.png'
icon_state = ""
screen_loc = "1,1"
- layer = SPLASHSCREEN_LAYER
plane = SPLASHSCREEN_PLANE
var/client/holder
-/atom/movable/screen/splash/New(client/C, visible, use_previous_title) //TODO: Make this use INITIALIZE_IMMEDIATE, except its not easy
+//INITIALIZE_IMMEDIATE(/atom/movable/screen/splash)
+//We need to change this from /New to /Initialize but i really don't feel like changing the 200 instances of new(src) to new(null, src) in this already massive PR
+/atom/movable/screen/splash/New(datum/hud/new_hud, client/C, visible, use_previous_title)
. = ..()
+ if(!istype(C))
+ return
holder = C
@@ -674,8 +703,7 @@
icon = SStitle.icon
else
if(!SStitle.previous_icon)
- qdel(src)
- return
+ return INITIALIZE_HINT_QDEL
icon = SStitle.previous_icon
holder.screen += src
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 3607a024efe7..c28962755456 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -114,7 +114,8 @@
user.do_attack_animation(O)
O.attacked_by(src, user)
user.weapon_slow(src)
- take_damage(rand(weapon_stats[DAMAGE_LOW], weapon_stats[DAMAGE_HIGH]), sound_effect = FALSE)
+ if(!QDELETED(src))
+ take_damage(rand(weapon_stats[DAMAGE_LOW], weapon_stats[DAMAGE_HIGH]), sound_effect = FALSE)
/atom/movable/proc/attacked_by()
return
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index 45aa05ea2801..30e6c69f5a25 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -102,7 +102,7 @@
HM.on_ranged_attack(A, mouseparams)
if(isturf(A) && get_dist(src,A) <= 1)
- src.Move_Pulled(A)
+ Move_Pulled(A)
return
/*
diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm
index 26489fe5537f..25fe8a0ac051 100644
--- a/code/_onclick/telekinesis.dm
+++ b/code/_onclick/telekinesis.dm
@@ -76,7 +76,6 @@
item_flags = NOBLUDGEON | ABSTRACT | DROPDEL
//item_state = null
w_class = WEIGHT_CLASS_GIGANTIC
- layer = ABOVE_HUD_LAYER
plane = ABOVE_HUD_PLANE
var/atom/movable/focus = null
diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm
index 524e568b8f56..590be2ebc38f 100644
--- a/code/controllers/admin.dm
+++ b/code/controllers/admin.dm
@@ -1,7 +1,7 @@
// Clickable stat() button.
/obj/effect/statclick
name = "Initializing..."
- blocks_emissive = NONE
+ blocks_emissive = EMISSIVE_BLOCK_NONE
var/target
INITIALIZE_IMMEDIATE(/obj/effect/statclick)
@@ -11,7 +11,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
name = text
src.target = target
if(isdatum(target)) //Harddel man bad
- RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(cleanup))
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(cleanup))
/obj/effect/statclick/Destroy()
target = null
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 0202bf63aadf..77bf8d822173 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -20,7 +20,8 @@
var/motd
var/policy
-
+
+ /// If the configuration is loaded
var/load_complete = FALSE
/datum/controller/configuration/proc/admin_reload()
@@ -318,9 +319,11 @@ Example config:
switch (command)
if ("map")
- currentmap = load_map_config("_maps/[data].json")
+ currentmap = load_map_config(data, MAP_DIRECTORY_MAPS)
if(currentmap.defaulted)
- log_config("Failed to load map config for [data]!")
+ var/error_message = "Failed to load map config for [data]!"
+ log_config(error_message)
+ log_mapping(error_message, TRUE)
currentmap = null
if ("minplayers","minplayer")
currentmap.config_min_users = text2num(data)
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 2ac99ba992bf..fd65fd723ba1 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -374,6 +374,12 @@
config_entry_value = 16
integer = FALSE
min_val = 0
+//Yogs edit
+/datum/config_entry/number/jungleland_budget
+ config_entry_value = 40
+ integer = FALSE
+ min_val = 0
+//Yogs end
/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 2252e5ea54de..73c223c6623a 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -159,11 +159,6 @@
/datum/config_entry/flag/allow_holidays
-/datum/config_entry/number/tick_limit_mc_init //SSinitialization throttling
- config_entry_value = TICK_LIMIT_MC_INIT_DEFAULT
- min_val = 0 //oranges warned us
- integer = FALSE
-
/datum/config_entry/flag/protect_legacy_admins //Stops any admins loaded by the legacy system from having their rank edited by the permissions panel
protection = CONFIG_ENTRY_LOCKED
@@ -302,12 +297,16 @@
/datum/config_entry/flag/maprotation
-/datum/config_entry/number/maprotatechancedelta
+/datum/config_entry/number/maprotationchancedelta
config_entry_value = 0.75
min_val = 0
max_val = 1
integer = FALSE
+/datum/config_entry/number/auto_lag_switch_pop //Number of clients at which drastic lag mitigation measures kick in
+ config_entry_value = null
+ min_val = 0
+
/datum/config_entry/number/soft_popcap
config_entry_value = null
min_val = 0
@@ -454,17 +453,17 @@
/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate
integer = FALSE
- config_entry_value = 1
+ default = 1
/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate
integer = FALSE
- config_entry_value = 1.1
+ default = 1.1
/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount
- config_entry_value = 65
+ default = 65
/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount
- config_entry_value = 60
+ default = 60
/datum/config_entry/number/mc_tick_rate
abstract_type = /datum/config_entry/number/mc_tick_rate
@@ -478,7 +477,7 @@
/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val)
. = ..()
- if(. && Master.current_runlevel)
+ if(. && MC_RUNNING())
world.sleep_offline = !config_entry_value
/datum/config_entry/number/rounds_until_hard_restart
@@ -509,6 +508,13 @@
/datum/config_entry/flag/everyone_is_donator
+/datum/config_entry/flag/auto_profile
+/datum/config_entry/number/drift_dump_threshold
+ default = 4 SECONDS
+
+/datum/config_entry/number/drift_profile_delay
+ default = 15 SECONDS
+
/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
/datum/config_entry/string/vpn_lookup_api // URL for VPN lookup API
@@ -547,3 +553,5 @@
/// Whether demos are written, if not set demo SS never initializes
/datum/config_entry/flag/demos_enabled
+
+/datum/config_entry/flag/toast_notification_on_init
diff --git a/code/controllers/lag_switch.dm b/code/controllers/lag_switch.dm
new file mode 100644
index 000000000000..c79db0518601
--- /dev/null
+++ b/code/controllers/lag_switch.dm
@@ -0,0 +1,146 @@
+/// The subsystem for controlling drastic performance enhancements aimed at reducing server load for a smoother albeit slightly duller gaming experience
+SUBSYSTEM_DEF(lag_switch)
+ name = "Lag Switch"
+ flags = SS_NO_FIRE
+
+ /// If the lag switch measures should attempt to trigger automatically, TRUE if a config value exists
+ var/auto_switch = FALSE
+ /// Amount of connected clients at which the Lag Switch should engage, set via config or admin panel
+ var/trigger_pop = INFINITY - 1337
+ /// List of bools corresponding to code/__DEFINES/lag_switch.dm
+ var/static/list/measures[MEASURES_AMOUNT]
+ /// List of measures that toggle automatically
+ var/list/auto_measures = list(DISABLE_GHOST_ZOOM_TRAY, DISABLE_RUNECHAT, DISABLE_USR_ICON2HTML, DISABLE_PARALLAX, DISABLE_FOOTSTEPS)
+ /// Timer ID for the automatic veto period
+ var/veto_timer_id
+ /// Cooldown between say verb uses when slowmode is enabled
+ var/slowmode_cooldown = 3 SECONDS
+
+/datum/controller/subsystem/lag_switch/Initialize()
+ for(var/i in 1 to measures.len)
+ measures[i] = FALSE
+ var/auto_switch_pop = CONFIG_GET(number/auto_lag_switch_pop)
+ if(auto_switch_pop)
+ auto_switch = TRUE
+ trigger_pop = auto_switch_pop
+ RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, PROC_REF(client_connected))
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/lag_switch/proc/client_connected(datum/source, client/connected)
+ SIGNAL_HANDLER
+ if(TGS_CLIENT_COUNT < trigger_pop)
+ return
+
+ auto_switch = FALSE
+ UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT)
+ veto_timer_id = addtimer(CALLBACK(src, PROC_REF(set_all_measures), TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE)
+ message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)")
+ log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.")
+
+/// (En/Dis)able automatic triggering of switches based on client count
+/datum/controller/subsystem/lag_switch/proc/toggle_auto_enable()
+ auto_switch = !auto_switch
+ if(auto_switch)
+ RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, PROC_REF(client_connected))
+ else
+ UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT)
+
+/// Called from an admin chat link
+/datum/controller/subsystem/lag_switch/proc/cancel_auto_enable_in_progress()
+ if(!veto_timer_id)
+ return FALSE
+
+ deltimer(veto_timer_id)
+ veto_timer_id = null
+ return TRUE
+
+/// Update the slowmode timer length and clear existing ones if reduced
+/datum/controller/subsystem/lag_switch/proc/change_slowmode_cooldown(length)
+ if(!length)
+ return FALSE
+
+ var/length_secs = length SECONDS
+ if(length_secs <= 0)
+ length_secs = 1 // one tick because cooldowns do not like 0
+
+ if(length_secs < slowmode_cooldown)
+ for(var/client/C as anything in GLOB.clients)
+ COOLDOWN_RESET(C, say_slowmode)
+
+ slowmode_cooldown = length_secs
+ if(measures[SLOWMODE_SAY])
+ to_chat(world, span_boldannounce("Slowmode timer has been changed to [length] seconds by an admin."))
+ return TRUE
+
+/// Handle the state change for individual measures
+/datum/controller/subsystem/lag_switch/proc/set_measure(measure_key, state)
+ if(isnull(measure_key) || isnull(state))
+ stack_trace("SSlag_switch.set_measure() was called with a null arg")
+ return FALSE
+ if(isnull(LAZYACCESS(measures, measure_key)))
+ stack_trace("SSlag_switch.set_measure() was called with a measure_key not in the list of measures")
+ return FALSE
+ if(measures[measure_key] == state)
+ return TRUE
+
+ measures[measure_key] = state
+
+ switch(measure_key)
+ if(DISABLE_DEAD_KEYLOOP)
+ if(state)
+ for(var/mob/user as anything in GLOB.player_list)
+ if(user.stat == DEAD && !user.client?.holder)
+ GLOB.keyloop_list -= user
+ deadchat_broadcast(span_big("To increase performance Observer freelook is now disabled. Please use Orbit, Teleport, and Jump to look around."), message_type = DEADCHAT_ANNOUNCEMENT)
+ else
+ GLOB.keyloop_list |= GLOB.player_list
+ deadchat_broadcast("Observer freelook has been re-enabled. Enjoy your wooshing.", message_type = DEADCHAT_ANNOUNCEMENT)
+ if(DISABLE_GHOST_ZOOM_TRAY)
+ if(state) // if enabling make sure current ghosts are updated
+ for(var/mob/dead/observer/ghost in GLOB.dead_mob_list)
+ if(!ghost.client)
+ continue
+ if(!ghost.client.holder && ghost.client.view_size.getView() != ghost.client.view_size.default)
+ ghost.client.view_size.resetToDefault()
+ if(SLOWMODE_SAY)
+ if(state)
+ to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been enabled with [slowmode_cooldown/10] seconds between messages."))
+ else
+ for(var/client/C as anything in GLOB.clients)
+ COOLDOWN_RESET(C, say_slowmode)
+ to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been disabled by an admin."))
+ if(DISABLE_NON_OBSJOBS)
+ world.update_status()
+ if(DISABLE_PARALLAX)
+ if (state)
+ to_chat(world, span_boldannounce("Parallax has been disabled for performance concerns."))
+ else
+ to_chat(world, span_boldannounce("Parallax has been re-enabled."))
+
+ for (var/mob/mob as anything in GLOB.mob_list)
+ mob.hud_used?.update_parallax_pref()
+ if (DISABLE_FOOTSTEPS)
+ if (state)
+ to_chat(world, span_boldannounce("Footstep sounds have been disabled for performance concerns."))
+ else
+ to_chat(world, span_boldannounce("Footstep sounds have been re-enabled."))
+
+ return TRUE
+
+/// Helper to loop over all measures for mass changes
+/datum/controller/subsystem/lag_switch/proc/set_all_measures(state, automatic = FALSE)
+ if(isnull(state))
+ stack_trace("SSlag_switch.set_all_measures() was called with a null state arg")
+ return FALSE
+
+ if(automatic)
+ message_admins("Lag Switch enabling automatic measures now.")
+ log_admin("Lag Switch enabling automatic measures now.")
+ veto_timer_id = null
+ for(var/i in 1 to auto_measures.len)
+ set_measure(auto_measures[i], state)
+ return TRUE
+
+ for(var/i in 1 to measures.len)
+ set_measure(i, state)
+ return TRUE
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 99074b343c48..2ad8972edec6 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -44,6 +44,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/init_timeofday
var/init_time
var/tickdrift = 0
+ /// Tickdrift as of last tick, w no averaging going on
+ var/olddrift = 0
/// How long is the MC sleeping between runs, read only (set by Loop() based off of anti-tick-contention heuristics)
var/sleep_delta = 1
@@ -72,6 +74,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/// Outside of initialization, returns null.
var/current_initializing_subsystem = null
+ /// The last decisecond we force dumped profiling information
+ /// Used to avoid spamming profile reads since they can be expensive (string memes)
+ var/last_profiled = 0
+
var/static/restart_clear = 0
var/static/restart_timeout = 0
var/static/restart_count = 0
@@ -88,11 +94,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed)
-#ifdef UNIT_TESTS
- random_seed = 29051994
-#else
+ #ifdef UNIT_TESTS
+ random_seed = 29051994 // How about 22475?
+ #else
random_seed = rand(1, 1e9)
-#endif
+ #endif
rand_seed(random_seed)
var/list/_subsystems = list()
@@ -136,6 +142,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
reverse_range(subsystems)
for(var/datum/controller/subsystem/ss in subsystems)
log_world("Shutting down [ss.name] subsystem...")
+ if (ss.slept_count > 0)
+ log_world("Warning: Subsystem `[ss.name]` slept [ss.slept_count] times.")
ss.Shutdown()
log_world("Shutdown complete")
@@ -204,10 +212,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
StartProcessing(10)
else
to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING"))
- Initialize(20, TRUE)
+ Initialize(20, TRUE, FALSE)
// Please don't stuff random bullshit here,
-// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize(mapload)
+// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize()
/datum/controller/master/Initialize(delay, init_sss, tgs_prime)
set waitfor = 0
@@ -254,7 +262,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if (!mc_started)
mc_started = TRUE
if (!current_runlevel)
- SetRunLevel(1)
+ SetRunLevel(1) // Intentionally not using the defines here because the MC doesn't care about them
// Loop.
Master.StartProcessing(0)
@@ -303,6 +311,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
SS_INIT_NONE,
SS_INIT_SUCCESS,
SS_INIT_NO_NEED,
+ SS_INIT_NO_MESSAGE,
)
if (subsystem.flags & SS_NO_INIT || subsystem.initialized) //Don't init SSs with the corresponding flag or if they already are initialized
@@ -324,9 +333,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(result && !(result in valid_results))
warning("[subsystem.name] subsystem initialized, returning invalid result [result]. This is a bug.")
- // just returned ..() or didn't implement Initialize(mapload) at all
+ // just returned ..() or didn't implement Initialize() at all
if(result == SS_INIT_NONE)
- warning("[subsystem.name] subsystem does not implement Initialize(mapload) or it returns ..(). If the former is true, the SS_NO_INIT flag should be set for this subsystem.")
+ warning("[subsystem.name] subsystem does not implement Initialize() or it returns ..(). If the former is true, the SS_NO_INIT flag should be set for this subsystem.")
if(result != SS_INIT_FAILURE)
// Some form of success, implicit failure, or the SS in unused.
@@ -348,7 +357,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(SS_INIT_FAILURE)
message_prefix = "Failed to initialize [subsystem.name] subsystem after"
chat_warning = TRUE
- if(SS_INIT_SUCCESS)
+ if(SS_INIT_SUCCESS, SS_INIT_NO_MESSAGE)
message_prefix = "Initialized [subsystem.name] subsystem within"
if(SS_INIT_NO_NEED)
// This SS is disabled or is otherwise shy.
@@ -361,24 +370,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/message = "[message_prefix] [seconds] second[seconds == 1 ? "" : "s"]!"
var/chat_message = chat_warning ? span_boldwarning(message) : span_boldannounce(message)
- to_chat(world, chat_message)
+ if(result != SS_INIT_NO_MESSAGE)
+ to_chat(world, chat_message)
log_world(message)
- //yogs loading points
- if(subsystem.loading_points) // We're probably one of those crappy subsystems that take 0 seconds, so return
- subsystem.total_loading_points_progress += subsystem.loading_points
- var/percent = round(subsystem.total_loading_points_progress / subsystem.total_loading_points * 100)
- to_chat(world,span_boldnotice("Subsystem initialization at [percent]%..."))
- // Yogs end
-
/datum/controller/master/proc/SetRunLevel(new_runlevel)
var/old_runlevel = current_runlevel
- if(isnull(old_runlevel))
- old_runlevel = "NULL"
- testing("MC: Runlevel changed from [old_runlevel] to [new_runlevel]")
+ testing("MC: Runlevel changed from [isnull(old_runlevel) ? "NULL" : old_runlevel] to [new_runlevel]")
current_runlevel = log(2, new_runlevel) + 1
if(current_runlevel < 1)
+ current_runlevel = old_runlevel
CRASH("Attempted to set invalid runlevel: [new_runlevel]")
// Starts the mc, and sticks around to restart it if the loop ever ends.
@@ -469,8 +471,13 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
canary.use_variable()
//the actual loop.
while (1)
- tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag)))
+ var/newdrift = ((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag
+ tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, newdrift))
var/starting_tick_usage = TICK_USAGE
+ //Yog: profile dumping was throttling lower performance computers, so we're going to have it disabled by default but you can enable it via config flags
+ if(newdrift - olddrift >= CONFIG_GET(number/drift_dump_threshold) && CONFIG_GET(flag/auto_profile))
+ AttemptProfileDump(CONFIG_GET(number/drift_profile_delay))
+ olddrift = newdrift
if (init_stage != init_stage_completed)
return MC_LOOP_RTN_NEWSTAGES
@@ -830,3 +837,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
SS.OnConfigLoad()
+
+/// Attempts to dump our current profile info into a file, triggered if the MC thinks shit is going down
+/// Accepts a delay in deciseconds of how long ago our last dump can be, this saves causing performance problems ourselves
+/datum/controller/master/proc/AttemptProfileDump(delay)
+ if(REALTIMEOFDAY - last_profiled <= delay)
+ return FALSE
+ last_profiled = REALTIMEOFDAY
+ SSprofiler.DumpFile(allow_yield = FALSE)
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 44af4ee772ae..653aeb67d26f 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -65,6 +65,9 @@
/// Tracks the current execution state of the subsystem. Used to handle subsystems that sleep in fire so the mc doesn't run them again while they are sleeping
var/state = SS_IDLE
+
+ /// Tracks how many times a subsystem has ever slept in fire().
+ var/slept_count = 0
/// Tracks how many fires the subsystem has consecutively paused on in the current run
var/paused_ticks = 0
@@ -126,8 +129,10 @@
fire(resumed)
. = state
if (state == SS_SLEEPING)
+ slept_count++
state = SS_IDLE
if (state == SS_PAUSING)
+ slept_count++
var/QT = queued_time
enqueue()
state = SS_PAUSED
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index 271a0e3ac661..03f1faf87e79 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -5,17 +5,19 @@ SUBSYSTEM_DEF(air)
wait = 0.5 SECONDS
flags = SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
- loading_points = 4.2 SECONDS // Yogs -- loading times
+ loading_points = 10 SECONDS // Yogs -- loading times
var/cached_cost = 0
var/cost_turfs = 0
+ var/cost_adjacent = 0
var/cost_groups = 0
var/cost_highpressure = 0
var/cost_hotspots = 0
var/cost_post_process = 0
var/cost_superconductivity = 0
var/cost_pipenets = 0
+ var/cost_machinery = 0
var/cost_rebuilds = 0
var/cost_equalize = 0
@@ -31,11 +33,16 @@ SUBSYSTEM_DEF(air)
var/gas_mixes_count = 0
var/gas_mixes_allocated = 0
+ var/list/excited_groups = list()
+ var/list/active_turfs = list()
var/list/hotspots = list()
var/list/networks = list()
var/list/rebuild_queue = list()
var/list/expansion_queue = list()
+ /// List of turfs to recalculate adjacent turfs on before processing
+ var/list/adjacent_rebuild = list()
var/list/pipe_init_dirs_cache = list()
+ var/list/obj/machinery/atmos_machinery = list()
//atmos singletons
var/list/gas_reactions = list()
@@ -43,11 +50,12 @@ SUBSYSTEM_DEF(air)
//Special functions lists
var/list/turf/open/high_pressure_delta = list()
-
+ /// A cache of objects that perisists between processing runs when resumed == TRUE. Dangerous, qdel'd objects not cleared from this may cause runtimes on processing.
var/list/currentrun = list()
- var/currentpart = SSAIR_FINALIZE_TURFS
+ var/currentpart = SSAIR_PIPENETS
var/map_loading = TRUE
+ var/list/queued_for_activation
var/lasttick = 0
@@ -68,24 +76,13 @@ SUBSYSTEM_DEF(air)
var/list/paused_z_levels //Paused z-levels will not add turfs to active
-//hack
-/proc/get_overlays()
- return GLOB.gas_data.overlays
-
-//hack
-/proc/get_hpds()
- return SSair.high_pressure_delta
-
-//hack
-/proc/get_reactions()
- return SSair.gas_reactions
-
/datum/controller/subsystem/air/stat_entry(msg)
msg += "C:{"
msg += "HP:[round(cost_highpressure,1)]|"
msg += "HS:[round(cost_hotspots,1)]|"
msg += "SC:[round(cost_superconductivity,1)]|"
msg += "PN:[round(cost_pipenets,1)]|"
+ msg += "MC:[round(cost_machinery,1)]|"
msg += "RB:[round(cost_rebuilds,1)]|"
msg += "} "
msg += "TC:{"
@@ -115,6 +112,7 @@ SUBSYSTEM_DEF(air)
.["cost_post_process"] = cost_post_process
.["cost_superconductivity"] = cost_superconductivity
.["cost_pipenets"] = cost_pipenets
+ .["cost_machinery"] = cost_machinery
.["cost_rebuilds"] = cost_rebuilds
.["cost_equalize"] = cost_equalize
.["hotspts"] = hotspots.len
@@ -130,6 +128,7 @@ SUBSYSTEM_DEF(air)
/datum/controller/subsystem/air/Initialize(timeofday)
map_loading = FALSE
setup_allturfs()
+ setup_atmos_machinery()
setup_pipenets()
gas_reactions = init_gas_reactions()
auxtools_update_reactions()
@@ -170,16 +169,33 @@ SUBSYSTEM_DEF(air)
if(state != SS_RUNNING)
return
- if(currentpart == SSAIR_ACTIVETURFS)
+ if(currentpart == SSAIR_PIPENETS || !resumed)
timer = TICK_USAGE_REAL
- process_turfs(resumed)
+ if(!resumed)
+ cached_cost = 0
+ process_pipenets(resumed)
+ cached_cost += TICK_USAGE_REAL - timer
if(state != SS_RUNNING)
return
+ cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(cached_cost))
resumed = 0
- currentpart = SSAIR_EXCITEDGROUPS
+ currentpart = SSAIR_ATMOSMACHINERY
- if(currentpart == SSAIR_EXCITEDGROUPS)
- process_excited_groups(resumed)
+ if(currentpart == SSAIR_ATMOSMACHINERY)
+ timer = TICK_USAGE_REAL
+ if(!resumed)
+ cached_cost = 0
+ process_atmos_machinery(resumed)
+ cached_cost += TICK_USAGE_REAL - timer
+ if(state != SS_RUNNING)
+ return
+ resumed = 0
+ cost_machinery = MC_AVERAGE(cost_machinery, TICK_DELTA_TO_MS(cached_cost))
+ currentpart = SSAIR_ACTIVETURFS
+
+ if(currentpart == SSAIR_ACTIVETURFS)
+ timer = TICK_USAGE_REAL
+ process_turfs(resumed)
if(state != SS_RUNNING)
return
resumed = 0
@@ -190,24 +206,19 @@ SUBSYSTEM_DEF(air)
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_FINALIZE_TURFS
+ currentpart = SSAIR_EXCITEDGROUPS
- if(currentpart == SSAIR_FINALIZE_TURFS)
- finish_turf_processing(resumed)
+ if(currentpart == SSAIR_EXCITEDGROUPS)
+ process_excited_groups(resumed)
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_PIPENETS
+ currentpart = SSAIR_FINALIZE_TURFS
- if(currentpart == SSAIR_PIPENETS || !resumed)
- timer = TICK_USAGE_REAL
- if(!resumed)
- cached_cost = 0
- process_pipenets(resumed)
- cached_cost += TICK_USAGE_REAL - timer
+ if(currentpart == SSAIR_FINALIZE_TURFS)
+ finish_turf_processing(resumed)
if(state != SS_RUNNING)
return
- cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(cached_cost))
resumed = 0
currentpart = SSAIR_HIGHPRESSURE
@@ -233,7 +244,7 @@ SUBSYSTEM_DEF(air)
return
cost_hotspots = MC_AVERAGE(cost_hotspots, TICK_DELTA_TO_MS(cached_cost))
resumed = 0
- currentpart = heat_enabled ? SSAIR_TURF_CONDUCTION : SSAIR_ACTIVETURFS
+ currentpart = heat_enabled ? SSAIR_TURF_CONDUCTION : SSAIR_PIPENETS
// Heat -- slow and of questionable usefulness. Off by default for this reason. Pretty cool, though.
if(currentpart == SSAIR_TURF_CONDUCTION)
@@ -244,7 +255,7 @@ SUBSYSTEM_DEF(air)
if(state != SS_RUNNING)
return
resumed = 0
- currentpart = SSAIR_ACTIVETURFS
+ currentpart = SSAIR_PIPENETS
/datum/controller/subsystem/air/proc/process_pipenets(resumed = FALSE)
if (!resumed)
@@ -372,6 +383,21 @@ SUBSYSTEM_DEF(air)
if(MC_TICK_CHECK)
return
+/datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = 0)
+ if (!resumed)
+ src.currentrun = atmos_machinery.Copy()
+ //cache for sanic speed (lists are references anyways)
+ var/list/currentrun = src.currentrun
+ while(currentrun.len)
+ var/obj/machinery/M = currentrun[currentrun.len]
+ currentrun.len--
+ if(M == null)
+ atmos_machinery.Remove(M)
+ if(!M || (M.process_atmos(wait / (1 SECONDS)) == PROCESS_KILL))
+ stop_processing_machine(M)
+ if(MC_TICK_CHECK)
+ return
+
/datum/controller/subsystem/air/proc/process_turf_equalize(resumed = 0)
if(process_turf_equalize_auxtools(MC_TICK_REMAINING_MS))
pause()
@@ -400,18 +426,22 @@ SUBSYSTEM_DEF(air)
// Clear active turfs - faster than removing every single turf in the world
// one-by-one, and Initalize_Atmos only ever adds `src` back in.
- for(var/thing in ALL_TURFS())
- var/turf/T = thing
- if (T.blocks_air)
+ for(var/turf/setup in ALL_TURFS())
+ if (setup.blocks_air)
continue
- T.Initalize_Atmos(times_fired)
+ setup.Initalize_Atmos(times_fired)
+ CHECK_TICK
+
+/datum/controller/subsystem/air/proc/setup_atmos_machinery()
+ for (var/obj/machinery/atmospherics/AM in atmos_machinery)
+ AM.atmos_init()
CHECK_TICK
//this can't be done with setup_atmos_machinery() because
// all atmos machinery has to initalize before the first
// pipenet can be built.
/datum/controller/subsystem/air/proc/setup_pipenets()
- for (var/obj/machinery/atmospherics/AM in SSair_machinery.atmos_machinery)
+ for (var/obj/machinery/atmospherics/AM in atmos_machinery)
var/list/targets = AM.get_rebuild_targets()
for(var/datum/pipeline/build_off as anything in targets)
build_off.build_pipeline_blocking(AM)
@@ -441,3 +471,16 @@ SUBSYSTEM_DEF(air)
qdel(temp)
return pipe_init_dirs_cache[type]["[dir]"]
+
+/datum/controller/subsystem/air/proc/start_processing_machine(obj/machinery/machine)
+ if(machine.atmos_processing)
+ return
+ machine.atmos_processing = TRUE
+ atmos_machinery += machine
+
+/datum/controller/subsystem/air/proc/stop_processing_machine(obj/machinery/machine)
+ if(!machine.atmos_processing)
+ return
+ machine.atmos_processing = FALSE
+ atmos_machinery -= machine
+ currentrun -= machine
diff --git a/code/controllers/subsystem/air_machinery.dm b/code/controllers/subsystem/air_machinery.dm
deleted file mode 100644
index 99fea09a63f6..000000000000
--- a/code/controllers/subsystem/air_machinery.dm
+++ /dev/null
@@ -1,49 +0,0 @@
-
-SUBSYSTEM_DEF(air_machinery)
- name = "Atmospherics Machinery"
- init_order = INIT_ORDER_AIR_MACHINERY
- priority = FIRE_PRIORITY_AIR
- wait = 1 SECONDS
- flags = SS_BACKGROUND
- runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
- loading_points = 4.2 SECONDS // Yogs -- loading times
-
- var/list/obj/machinery/atmos_machinery = list()
- var/list/currentrun = list()
-
-/datum/controller/subsystem/air_machinery/Initialize(timeofday)
- setup_atmos_machinery()
- return SS_INIT_SUCCESS
-
-/datum/controller/subsystem/air_machinery/proc/setup_atmos_machinery()
- for (var/obj/machinery/atmospherics/AM in atmos_machinery)
- AM.atmos_init()
- CHECK_TICK
-
-/datum/controller/subsystem/air_machinery/proc/start_processing_machine(obj/machinery/machine)
- if(machine.atmos_processing)
- return
- machine.atmos_processing = TRUE
- atmos_machinery += machine
-
-/datum/controller/subsystem/air_machinery/proc/stop_processing_machine(obj/machinery/machine)
- if(!machine.atmos_processing)
- return
- machine.atmos_processing = FALSE
- atmos_machinery -= machine
- currentrun -= machine
-
-/datum/controller/subsystem/air_machinery/fire(resumed = 0)
- if (!resumed)
- src.currentrun = atmos_machinery.Copy()
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
- while(currentrun.len)
- var/obj/machinery/M = currentrun[currentrun.len]
- currentrun.len--
- if(M == null)
- atmos_machinery.Remove(M)
- if(!M || (M.process_atmos(wait / (1 SECONDS)) == PROCESS_KILL))
- stop_processing_machine(M)
- if(MC_TICK_CHECK)
- return
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index e7e6275cd8bc..8a3ca56432fa 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -1,49 +1,57 @@
-#define BAD_INIT_QDEL_BEFORE 1
-#define BAD_INIT_DIDNT_INIT 2
-#define BAD_INIT_SLEPT 4
-#define BAD_INIT_NO_HINT 8
-
SUBSYSTEM_DEF(atoms)
name = "Atoms"
init_order = INIT_ORDER_ATOMS
flags = SS_NO_FIRE
+
loading_points = 30 SECONDS // Yogs -- smarter loading times
- var/old_initialized
- /// A count of how many initalize changes we've made. We want to prevent old_initialize being overriden by some other value, breaking init code
- var/initialized_changed = 0
+ /// A stack of list(source, desired initialized state)
+ /// We read the source of init changes from the last entry, and assert that all changes will come with a reset
+ var/list/initialized_state = list()
+ var/base_initialized
var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
- var/init_start_time
+ ///initAtom() adds the atom its creating to this list iff InitializeAtoms() has been given a list to populate as an argument
+ var/list/created_atoms
/// Atoms that will be deleted once the subsystem is initialized
var/list/queued_deletions = list()
+ var/init_start_time
+
+ // #ifdef PROFILE_MAPLOAD_INIT_ATOM
+ // var/list/mapload_init_times = list()
+ // #endif
+
initialized = INITIALIZATION_INSSATOMS
-/datum/controller/subsystem/atoms/Initialize(timeofday)
+/datum/controller/subsystem/atoms/Initialize()
init_start_time = world.time
- GLOB.fire_overlay.appearance_flags = RESET_COLOR
setupGenetics() //to set the mutations' sequence
initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
initialized = INITIALIZATION_INNEW_REGULAR
-
+
return SS_INIT_SUCCESS
-/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms)
+/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return)
if(initialized == INITIALIZATION_INSSATOMS)
return
- set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD)
+ // Generate a unique mapload source for this run of InitializeAtoms
+ var/static/uid = 0
+ uid = (uid + 1) % (SHORT_REAL_LIMIT - 1)
+ var/source = "subsystem init [uid]"
+ set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, source)
// This may look a bit odd, but if the actual atom creation runtimes for some reason, we absolutely need to set initialized BACK
- CreateAtoms(atoms)
- clear_tracked_initalize()
+ CreateAtoms(atoms, atoms_to_return, source)
+ clear_tracked_initalize(source)
+ SSicon_smooth.free_deferred(source)
if(late_loaders.len)
for(var/I in 1 to late_loaders.len)
@@ -54,15 +62,26 @@ SUBSYSTEM_DEF(atoms)
A.LateInitialize()
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
-
+
+ if (created_atoms)
+ atoms_to_return += created_atoms
+ created_atoms = null
+
for (var/queued_deletion in queued_deletions)
qdel(queued_deletion)
testing("[queued_deletions.len] atoms were queued for deletion.")
queued_deletions.Cut()
+ // #ifdef PROFILE_MAPLOAD_INIT_ATOM
+ // rustg_file_write(json_encode(mapload_init_times), "[GLOB.log_directory]/init_times.json")
+ // #endif
+
/// Actually creates the list of atoms. Exists soley so a runtime in the creation logic doesn't cause initalized to totally break
-/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms)
+/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms, list/atoms_to_return = null, mapload_source = null)
+ if (atoms_to_return)
+ LAZYINITLIST(created_atoms)
+
#ifdef TESTING
var/count
#endif
@@ -77,8 +96,15 @@ SUBSYSTEM_DEF(atoms)
for(var/I in 1 to atoms.len)
var/atom/A = atoms[I]
if(!(A.flags_1 & INITIALIZED_1))
- CHECK_TICK
- InitAtom(A, mapload_arg)
+ // Unrolled CHECK_TICK setup to let us enable/disable mapload based off source
+ if(TICK_CHECK)
+ clear_tracked_initalize(mapload_source)
+ stoplag()
+ if(mapload_source)
+ set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, mapload_source)
+ PROFILE_INIT_ATOM_BEGIN()
+ InitAtom(A, TRUE, mapload_arg)
+ PROFILE_INIT_ATOM_END(A)
else
#ifdef TESTING
count = 0
@@ -86,81 +112,64 @@ SUBSYSTEM_DEF(atoms)
for(var/atom/A as anything in world)
if(!(A.flags_1 & INITIALIZED_1))
- InitAtom(A, mapload_arg)
+ PROFILE_INIT_ATOM_BEGIN()
+ InitAtom(A, FALSE, mapload_arg)
+ PROFILE_INIT_ATOM_END(A)
#ifdef TESTING
++count
#endif
- CHECK_TICK
+ if(TICK_CHECK)
+ clear_tracked_initalize(mapload_source)
+ stoplag()
+ if(mapload_source)
+ set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, mapload_source)
testing("Initialized [count] atoms")
-/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
- var/the_type = A.type
- if(QDELING(A))
- // Check init_start_time to not worry about atoms created before the atoms SS that are cleaned up before this
- if (A.gc_destroyed > init_start_time)
- BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
- return TRUE
-
- #ifdef UNIT_TESTS
- var/start_tick = world.time
- #endif
-
- var/result = A.Initialize(arglist(arguments))
-
- #ifdef UNIT_TESTS
- if(start_tick != world.time)
- BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
- #endif
-
- var/qdeleted = FALSE
-
- switch(result)
- if (INITIALIZE_HINT_NORMAL)
- // pass
- if (INITIALIZE_HINT_LATELOAD)
- if(arguments[1]) //mapload
- late_loaders += A
- else
- A.LateInitialize()
- if (INITIALIZE_HINT_QDEL)
- qdel(A)
- qdeleted = TRUE
- else
- BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
-
- if(!A) //possible harddel
- qdeleted = TRUE
- else if(!(A.flags_1 & INITIALIZED_1))
- BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
-
- return qdeleted || QDELING(A)
-
-/datum/controller/subsystem/atoms/proc/map_loader_begin()
- set_tracked_initalized(INITIALIZATION_INSSATOMS)
-
-/datum/controller/subsystem/atoms/proc/map_loader_stop()
- clear_tracked_initalize()
-
-/// Use this to set initialized to prevent error states where old_initialized is overriden. It keeps happening and it's cheesing me off
-/datum/controller/subsystem/atoms/proc/set_tracked_initalized(value)
- if(!initialized_changed)
- old_initialized = initialized
- initialized = value
- else
- stack_trace("We started maploading while we were already maploading. You doing something odd?")
- initialized_changed += 1
+/datum/controller/subsystem/atoms/proc/map_loader_begin(source)
+ set_tracked_initalized(INITIALIZATION_INSSATOMS, source)
+
+/datum/controller/subsystem/atoms/proc/map_loader_stop(source)
+ clear_tracked_initalize(source)
+
+/// Returns the source currently modifying SSatom's init behavior
+/datum/controller/subsystem/atoms/proc/get_initialized_source()
+ var/state_length = length(initialized_state)
+ if(!state_length)
+ return null
+ return initialized_state[state_length][1]
+
+/// Use this to set initialized to prevent error states where the old initialized is overriden, and we end up losing all context
+/// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentially
+/datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source)
+ if(!length(initialized_state))
+ base_initialized = initialized
+ initialized_state += list(list(source, state))
+ initialized = state
+
+/datum/controller/subsystem/atoms/proc/clear_tracked_initalize(source)
+ if(!length(initialized_state))
+ return
+ for(var/i in length(initialized_state) to 1 step -1)
+ if(initialized_state[i][1] == source)
+ initialized_state.Cut(i, i+1)
+ break
+
+ if(!length(initialized_state))
+ initialized = base_initialized
+ base_initialized = INITIALIZATION_INNEW_REGULAR
+ return
+ initialized = initialized_state[length(initialized_state)][2]
-/datum/controller/subsystem/atoms/proc/clear_tracked_initalize()
- initialized_changed -= 1
- if(!initialized_changed)
- initialized = old_initialized
+/// Returns TRUE if anything is currently being initialized
+/datum/controller/subsystem/atoms/proc/initializing_something()
+ return length(initialized_state) > 1
/datum/controller/subsystem/atoms/Recover()
initialized = SSatoms.initialized
if(initialized == INITIALIZATION_INNEW_MAPLOAD)
InitializeAtoms()
- old_initialized = SSatoms.old_initialized
+ initialized_state = SSatoms.initialized_state
BadInitializeCalls = SSatoms.BadInitializeCalls
/datum/controller/subsystem/atoms/proc/setupGenetics()
@@ -196,9 +205,9 @@ SUBSYSTEM_DEF(atoms)
if(fails & BAD_INIT_NO_HINT)
. += "- Didn't return an Initialize hint\n"
if(fails & BAD_INIT_QDEL_BEFORE)
- . += "- Qdel'd in New()\n"
+ . += "- Qdel'd before Initialize proc ran\n"
if(fails & BAD_INIT_SLEPT)
- . += "- Slept during Initialize(mapload)\n"
+ . += "- Slept during Initialize()\n"
/// Prepares an atom to be deleted once the atoms SS is initialized.
/datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target)
@@ -212,8 +221,3 @@ SUBSYSTEM_DEF(atoms)
var/initlog = InitLog()
if(initlog)
text2file(initlog, "[GLOB.log_directory]/initialize.log")
-
-#undef BAD_INIT_QDEL_BEFORE
-#undef BAD_INIT_DIDNT_INIT
-#undef BAD_INIT_SLEPT
-#undef BAD_INIT_NO_HINT
diff --git a/code/controllers/subsystem/augury.dm b/code/controllers/subsystem/augury.dm
index ae7533144627..164405bbb223 100644
--- a/code/controllers/subsystem/augury.dm
+++ b/code/controllers/subsystem/augury.dm
@@ -19,11 +19,11 @@ SUBSYSTEM_DEF(augury)
/datum/controller/subsystem/augury/proc/register_doom(atom/A, severity)
doombringers[A] = severity
- RegisterSignal(A, COMSIG_PARENT_QDELETING, PROC_REF(unregister_doom))
+ RegisterSignal(A, COMSIG_QDELETING, PROC_REF(unregister_doom))
/datum/controller/subsystem/augury/proc/unregister_doom(atom/A)
SIGNAL_HANDLER
- UnregisterSignal(A, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(A, COMSIG_QDELETING)
doombringers -= A
/datum/controller/subsystem/augury/fire()
diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm
index 2ba981ed7d16..5658d85b9258 100644
--- a/code/controllers/subsystem/chat.dm
+++ b/code/controllers/subsystem/chat.dm
@@ -1,16 +1,49 @@
-/**
+/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
SUBSYSTEM_DEF(chat)
name = "Chat"
- flags = SS_TICKER
+ flags = SS_TICKER | SS_NO_INIT
wait = 1
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
- var/list/payload_by_client = list()
+ /// Assosciates a ckey with a list of messages to send to them.
+ var/list/list/datum/chat_payload/client_to_payloads = list()
+
+ /// Associates a ckey with an assosciative list of their last CHAT_RELIABILITY_HISTORY_SIZE messages.
+ var/list/list/datum/chat_payload/client_to_reliability_history = list()
+
+ /// Assosciates a ckey with their next sequence number.
+ var/list/client_to_sequence_number = list()
+
+/datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data)
+ var/sequence = client_to_sequence_number[target.ckey]
+ client_to_sequence_number[target.ckey] += 1
+
+ var/datum/chat_payload/payload = new
+ payload.sequence = sequence
+ payload.content = message_data
+
+ if(!(target.ckey in client_to_reliability_history))
+ client_to_reliability_history[target.ckey] = list()
+ var/list/client_history = client_to_reliability_history[target.ckey]
+ client_history["[sequence]"] = payload
+
+ if(length(client_history) > CHAT_RELIABILITY_HISTORY_SIZE)
+ var/oldest = text2num(client_history[1])
+ for(var/index in 2 to length(client_history))
+ var/test = text2num(client_history[index])
+ if(test < oldest)
+ oldest = test
+ client_history -= "[oldest]"
+ return payload
+
+/datum/controller/subsystem/chat/proc/send_payload_to_client(client/target, datum/chat_payload/payload)
+ target.tgui_panel.window.send_message("chat/message", payload.into_message())
+ SEND_TEXT(target, payload.get_content_as_html())
/datum/controller/subsystem/chat/Initialize()
// Just used by chat system to know that initialization is nearly finished.
@@ -18,29 +51,55 @@ SUBSYSTEM_DEF(chat)
return SS_INIT_SUCCESS
/datum/controller/subsystem/chat/fire()
- for(var/key in payload_by_client)
- var/client/client = key
- var/payload = payload_by_client[key]
- payload_by_client -= key
- if(client)
- // Send to tgchat
- client.tgui_panel?.window.send_message("chat/message", payload)
- // Send to old chat
- for(var/message in payload)
- SEND_TEXT(client, message_to_html(message))
+ for(var/ckey in client_to_payloads)
+ var/client/target = GLOB.directory[ckey]
+ if(isnull(target)) // verify client still exists
+ LAZYREMOVE(client_to_payloads, ckey)
+ continue
+
+ for(var/datum/chat_payload/payload as anything in client_to_payloads[ckey])
+ send_payload_to_client(target, payload)
+ LAZYREMOVE(client_to_payloads, ckey)
+
if(MC_TICK_CHECK)
return
-/datum/controller/subsystem/chat/proc/queue(target, message, confidential = FALSE)
+/datum/controller/subsystem/chat/proc/queue(queue_target, list/message_data, confidential = FALSE)
if(!confidential)
- SSdemo.write_chat(target, message)
-
- if(islist(target))
- for(var/_target in target)
- var/client/client = CLIENT_FROM_VAR(_target)
- if(client)
- LAZYADD(payload_by_client[client], list(message))
+ SSdemo.write_chat(queue_target, message_data)
+ var/list/targets = islist(queue_target) ? queue_target : list(queue_target)
+ for(var/target in targets)
+ var/client/client = CLIENT_FROM_VAR(target)
+ if(isnull(client))
+ continue
+ LAZYADDASSOCLIST(client_to_payloads, client.ckey, generate_payload(client, message_data))
+
+/datum/controller/subsystem/chat/proc/send_immediate(send_target, list/message_data)
+ var/list/targets = islist(send_target) ? send_target : list(send_target)
+ for(var/target in targets)
+ var/client/client = CLIENT_FROM_VAR(target)
+ if(isnull(client))
+ continue
+ send_payload_to_client(client, generate_payload(client, message_data))
+
+/datum/controller/subsystem/chat/proc/handle_resend(client/client, sequence)
+ var/list/client_history = client_to_reliability_history[client.ckey]
+ sequence = "[sequence]"
+ if(isnull(client_history) || !(sequence in client_history))
return
- var/client/client = CLIENT_FROM_VAR(target)
- if(client)
- LAZYADD(payload_by_client[client], list(message))
+
+ var/datum/chat_payload/payload = client_history[sequence]
+ if(payload.resends > CHAT_RELIABILITY_MAX_RESENDS)
+ return // we tried but byond said no
+
+ payload.resends += 1
+ send_payload_to_client(client, client_history[sequence])
+ SSblackbox.record_feedback(
+ "nested tally",
+ "chat_resend_byond_version",
+ 1,
+ list(
+ "[client.byond_version]",
+ "[client.byond_build]",
+ ),
+ )
diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/dcs.dm
index f9abd6db3dc6..e130b6dc0ff8 100644
--- a/code/controllers/subsystem/dcs.dm
+++ b/code/controllers/subsystem/dcs.dm
@@ -5,10 +5,34 @@ PROCESSING_SUBSYSTEM_DEF(dcs)
var/list/elements_by_type = list()
+ /**
+ * A nested assoc list of bespoke element types (keys) and superlists containing all lists used as arguments (values).
+ * Inside the superlists, lists that've been sorted alphabetically are keys, while the original unsorted lists are values.
+ *
+ * e.g. list(
+ * /datum/element/first = list(list(A, B, C) = list(B, A, C), list(A, B) = list(A, B)),
+ * /datum/element/second = list(list(B, C) = list(C, B), list(D) = list(D)),
+ * )
+ *
+ * Used by the dcs_check_list_arguments unit test.
+ */
+ var/list/arguments_that_are_lists_by_element = list()
+ /**
+ * An assoc list of list instances and their sorted counterparts.
+ *
+ * e.g. list(
+ * list(B, A, C) = list(A, B, C),
+ * list(C, B) = list(B, C),
+ * )
+ *
+ * Used to make sure each list instance is sorted no more than once, or the unit test won't work.
+ */
+ var/list/sorted_arguments_that_are_lists = list()
+
/datum/controller/subsystem/processing/dcs/Recover()
- comp_lookup = SSdcs.comp_lookup
+ _listen_lookup = SSdcs._listen_lookup
-/datum/controller/subsystem/processing/dcs/proc/GetElement(list/arguments)
+/datum/controller/subsystem/processing/dcs/proc/GetElement(list/arguments, init_element = TRUE)
var/datum/element/eletype = arguments[1]
var/element_id = eletype
@@ -16,39 +40,70 @@ PROCESSING_SUBSYSTEM_DEF(dcs)
CRASH("Attempted to instantiate [eletype] as a /datum/element")
if(initial(eletype.element_flags) & ELEMENT_BESPOKE)
- element_id = GetIdFromArguments(arguments)
+ element_id = length(arguments) == 1 ? "[arguments[1]]" : GetIdFromArguments(arguments)
. = elements_by_type[element_id]
- if(.)
+ if(. || !init_element)
return
. = elements_by_type[element_id] = new eletype
/****
- * Generates an id for bespoke elements when given the argument list
- * Generating the id here is a bit complex because we need to support named arguments
- * Named arguments can appear in any order and we need them to appear after ordered arguments
- * We assume that no one will pass in a named argument with a value of null
- **/
+ * Generates an id for bespoke elements when given the argument list
+ * Generating the id here is a bit complex because we need to support named arguments
+ * Named arguments can appear in any order and we need them to appear after ordered arguments
+ * We assume that no one will pass in a named argument with a value of null
+ **/
/datum/controller/subsystem/processing/dcs/proc/GetIdFromArguments(list/arguments)
var/datum/element/eletype = arguments[1]
- var/list/fullid = list("[eletype]")
- var/list/named_arguments = list()
+ var/list/fullid = list(eletype)
+ var/list/named_arguments
for(var/i in initial(eletype.argument_hash_start_idx) to length(arguments))
var/key = arguments[i]
- var/value
+
if(istext(key))
- value = arguments[key]
- if(!(istext(key) || isnum(key)))
- key = ref(key)
- key = "[key]" // Key is stringified so numbers dont break things
- if(!isnull(value))
- if(!(istext(value) || isnum(value)))
- value = ref(value)
- named_arguments["[key]"] = value
+ var/value = arguments[key]
+ if (isnull(value))
+ fullid += key
+ else
+ if (!istext(value) && !isnum(value))
+ if(PERFORM_ALL_TESTS(dcs_check_list_arguments) && islist(value))
+ add_to_arguments_that_are_lists(value, eletype)
+ value = REF(value)
+
+ if (!named_arguments)
+ named_arguments = list()
+
+ named_arguments[key] = value
+ continue
+
+ if (isnum(key))
+ fullid += key
else
- fullid += "[key]"
+ if(PERFORM_ALL_TESTS(dcs_check_list_arguments) && islist(key))
+ add_to_arguments_that_are_lists(key, eletype)
+ fullid += REF(key)
- if(length(named_arguments))
- named_arguments = sortList(named_arguments)
+ if(named_arguments)
+ named_arguments = sortTim(named_arguments, GLOBAL_PROC_REF(cmp_text_asc))
fullid += named_arguments
+
return list2params(fullid)
+
+/**
+ * Offloading the first half of the dcs_check_list_arguments here, which is populating the superlist
+ * with sublists that will be later compared with each other by the dcs_check_list_arguments unit test.
+ */
+/datum/controller/subsystem/processing/dcs/proc/add_to_arguments_that_are_lists(list/argument, datum/element/element_type)
+ if(initial(element_type.element_flags) & ELEMENT_NO_LIST_UNIT_TEST)
+ return
+ var/list/element_type_superlist = arguments_that_are_lists_by_element[element_type]
+ if(!element_type_superlist)
+ arguments_that_are_lists_by_element[element_type] = element_type_superlist = list()
+
+ var/list/sorted_argument = argument
+ if(!(initial(element_type.element_flags) & ELEMENT_DONT_SORT_LIST_ARGS))
+ sorted_argument = sorted_arguments_that_are_lists[argument]
+ if(!sorted_argument)
+ sorted_arguments_that_are_lists[argument] = sorted_argument = sortTim(argument.Copy(), GLOBAL_PROC_REF(cmp_embed_text_asc))
+
+ element_type_superlist[sorted_argument] = argument
diff --git a/code/controllers/subsystem/demo.dm b/code/controllers/subsystem/demo.dm
index 5296f21dd20c..94fb202ee16e 100644
--- a/code/controllers/subsystem/demo.dm
+++ b/code/controllers/subsystem/demo.dm
@@ -2,8 +2,9 @@ SUBSYSTEM_DEF(demo)
name = "Demo"
wait = 1
flags = SS_TICKER | SS_BACKGROUND
- init_order = INIT_ORDER_DEMO
+ ///Adding Lobby to the runlevel because we want it to start writing before the game starts since there's a of atoms queued to be written during init
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ init_order = INIT_ORDER_DEMO
loading_points = 12.6 SECONDS // Yogs -- loading times
@@ -189,7 +190,7 @@ SUBSYSTEM_DEF(demo)
marked_dirty.len--
if(M.gc_destroyed || !M)
continue
- if(M.loc == M.demo_last_loc)
+ if(M.loc == M.demo_last_loc && M.appearance == M.demo_last_appearance)
continue
var/loc_string = "="
if(M.loc != M.demo_last_loc)
@@ -204,7 +205,7 @@ SUBSYSTEM_DEF(demo)
appearance_string = encode_appearance(M.appearance, target = M)
M.demo_last_appearance = M.appearance
else if(M.appearance != M.demo_last_appearance)
- appearance_string = encode_appearance(M.appearance, M.demo_last_appearance, target = M)
+ appearance_string = encode_appearance(M.appearance, M.demo_last_appearance, TRUE, M)
M.demo_last_appearance = M.appearance
dirty_updates += "\ref[M] [loc_string] [appearance_string]"
if(MC_TICK_CHECK)
@@ -330,7 +331,6 @@ SUBSYSTEM_DEF(demo)
var/tmp_dir = appearance.dir
if(target)
- //message_admins("demo target is [target] \nappearance dir: [appearance.dir] and target dir: [target.dir]")
tmp_dir = target.dir
var/list/appearance_list = list(
@@ -339,7 +339,7 @@ SUBSYSTEM_DEF(demo)
json_encode(cached_name),
appearance.appearance_flags,
appearance.layer,
- appearance.plane == -32767 ? "" : appearance.plane,
+ appearance.plane == -32767 ? "" : PLANE_TO_TRUE(appearance.plane),
tmp_dir == 2 ? "" : tmp_dir,
appearance.color ? color_string : "",
appearance.alpha == 255 ? "" : appearance.alpha,
@@ -394,7 +394,7 @@ SUBSYSTEM_DEF(demo)
json_encode(cached_name),
appearance.appearance_flags == diff_appearance.appearance_flags ? "" : appearance.appearance_flags,
appearance.layer == diff_appearance.layer ? "" : appearance.layer,
- appearance.plane == diff_appearance.plane ? "" : appearance.plane,
+ PLANE_TO_TRUE(appearance.plane) == PLANE_TO_TRUE(diff_appearance.plane) ? "" : PLANE_TO_TRUE(appearance.plane),
appearance.dir == diff_appearance.dir ? "" : appearance.dir,
appearance.color == diff_appearance.color ? "" : color_string,
appearance.alpha == diff_appearance.alpha ? "" : appearance.alpha,
@@ -442,9 +442,9 @@ SUBSYSTEM_DEF(demo)
/datum/controller/subsystem/demo/proc/mark_new(atom/movable/M)
if(!can_fire)
return
- if(!isobj(M) && !ismob(M))
+ if(!ismovable(M))
return
- if(M.gc_destroyed)
+ if(M.gc_destroyed || QDELETED(M))
return
marked_new[M] = TRUE
if(marked_dirty[M])
@@ -454,17 +454,21 @@ SUBSYSTEM_DEF(demo)
/datum/controller/subsystem/demo/proc/mark_dirty(atom/movable/M)
if(!can_fire)
return
- if(!isobj(M) && !ismob(M))
+ if(!ismovable(M))
+ return
+ if(M.gc_destroyed || QDELETED(M))
return
- if(M.gc_destroyed)
+ if(isturf(M))
+ mark_turf(M)
+ return
+ if(marked_new[M])
return
- if(!marked_new[M])
- marked_dirty[M] = TRUE
+ marked_dirty[M] = TRUE
/datum/controller/subsystem/demo/proc/mark_destroyed(atom/movable/M)
if(!can_fire)
return
- if(!isobj(M) && !ismob(M))
+ if(!ismovable(M))
return
if(marked_new[M])
marked_new -= M
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index 3d0e1295dc5b..d6533ff8ba9d 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -3,17 +3,24 @@ SUBSYSTEM_DEF(events)
init_order = INIT_ORDER_EVENTS
runlevels = RUNLEVEL_GAME
- var/list/control = list() //list of all datum/round_event_control. Used for selecting events based on weight and occurrences.
- var/list/running = list() //list of all existing /datum/round_event
+ ///list of all datum/round_event_control. Used for selecting events based on weight and occurrences.
+ var/list/control = list()
+ ///list of all existing /datum/round_event currently being run.
+ var/list/running = list()
+ ///cache of currently running events, for lag checking.
var/list/currentrun = list()
-
- var/scheduled = 0 //The next world.time that a naturally occuring random event can be selected.
- var/frequency_lower = 2 MINUTES //2 minutes lower bound.
- var/frequency_upper = 9 MINUTES //9 minutes upper bound. Basically an event will happen every 2 to 9 minutes.
-
- var/list/holidays //List of all holidays occuring today or null if no holidays
+ ///The next world.time that a naturally occuring random event can be selected.
+ var/scheduled = 0
+ ///The lower bound for how soon another random event can be scheduled.
+ var/frequency_lower = 2.5 MINUTES
+ ///The upper bound for how soon another random event can be scheduled.
+ var/frequency_upper = 9 MINUTES
+ ///Will wizard events be included in the event pool?
var/wizardmode = FALSE
+ //Yog code: we need to switch to Glob.holidays when possible
+ var/list/holidays
+
/datum/controller/subsystem/events/Initialize(time, zlevel)
for(var/type in typesof(/datum/round_event_control))
var/datum/round_event_control/E = new type()
@@ -21,7 +28,9 @@ SUBSYSTEM_DEF(events)
continue //don't want this one! leave it for the garbage collector
control += E //add it to the list of all events (controls)
reschedule()
- getHoliday()
+ // Instantiate our holidays list if it hasn't been already
+ if(isnull(GLOB.holidays))
+ fill_holidays()
return SS_INIT_SUCCESS
@@ -128,60 +137,85 @@ SUBSYSTEM_DEF(events)
popup.open()
-/*
-//////////////
-// HOLIDAYS //
-//////////////
-//Uncommenting ALLOW_HOLIDAYS in config.txt will enable holidays
+/datum/controller/subsystem/events/proc/toggleWizardmode()
+ wizardmode = !wizardmode
+ message_admins("Summon Events has been [wizardmode ? "enabled, events will occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes" : "disabled"]!")
+ log_game("Summon Events was [wizardmode ? "enabled" : "disabled"]!")
-//It's easy to add stuff. Just add a holiday datum in code/modules/holiday/holidays.dm
-//You can then check if it's a special day in any code in the game by doing if(SSevents.holidays["Groundhog Day"])
-//You can also make holiday random events easily thanks to Pete/Gia's system.
-//simply make a random event normally, then assign it a holidayID string which matches the holiday's name.
-//Anything with a holidayID, which isn't in the holidays list, will never occur.
+/datum/controller/subsystem/events/proc/resetFrequency()
+ frequency_lower = initial(frequency_lower)
+ frequency_upper = initial(frequency_upper)
-//Please, Don't spam stuff up with stupid stuff (key example being april-fools Pooh/ERP/etc),
-//And don't forget: CHECK YOUR CODE!!!! We don't want any zero-day bugs which happen only on holidays and never get found/fixed!
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-//ALSO, MOST IMPORTANTLY: Don't add stupid stuff! Discuss bonus content with Project-Heads first please!//
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-*/
-//sets up the holidays and holidays list
-/datum/controller/subsystem/events/proc/getHoliday()
+/**
+ * HOLIDAYS
+ *
+ * Uncommenting ALLOW_HOLIDAYS in config.txt will enable holidays
+ *
+ * It's easy to add stuff. Just add a holiday datum in code/modules/holiday/holidays.dm
+ * You can then check if it's a special day in any code in the game by calling check_holidays("Groundhog Day")
+ *
+ * You can also make holiday random events easily thanks to Pete/Gia's system.
+ * simply make a random event normally, then assign it a holidayID string which matches the holiday's name.
+ * Anything with a holidayID, which isn't in the holidays list, will never occur.
+ *
+ * Please, Don't spam stuff up with stupid stuff (key example being april-fools Pooh/ERP/etc),
+ * and don't forget: CHECK YOUR CODE!!!! We don't want any zero-day bugs which happen only on holidays and never get found/fixed!
+ */
+GLOBAL_LIST(holidays)
+
+/**
+ * Checks that the passed holiday is located in the global holidays list.
+ *
+ * Returns a holiday datum, or null if it's not that holiday.
+ */
+/proc/check_holidays(holiday_to_find)
if(!CONFIG_GET(flag/allow_holidays))
- return // Holiday stuff was not enabled in the config!
-
- var/YY = text2num(time2text(world.timeofday, "YY")) // get the current year
- var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month
- var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day
- var/DDD = time2text(world.timeofday, "DDD") // get the current weekday
- var/W = weekdayofthemonth() // is this the first monday? second? etc.
-
- for(var/H in subtypesof(/datum/holiday))
- var/datum/holiday/holiday = new H()
- if(holiday.shouldCelebrate(DD, MM, YY, W, DDD))
- holiday.celebrate()
- if(!holidays)
- holidays = list()
- holidays[holiday.name] = holiday
- else
+ return // Holiday stuff was not enabled in the config!
+
+ if(isnull(GLOB.holidays) && !fill_holidays())
+ return // Failed to generate holidays, for some reason
+
+ return GLOB.holidays[holiday_to_find]
+
+/**
+ * Fills the holidays list if applicable, or leaves it an empty list.
+ */
+proc/fill_holidays()
+ if(!CONFIG_GET(flag/allow_holidays))
+ return FALSE // Holiday stuff was not enabled in the config!
+
+ GLOB.holidays = list()
+ for(var/holiday_type in subtypesof(/datum/holiday))
+ var/datum/holiday/holiday = new holiday_type()
+ var/delete_holiday = TRUE
+ for(var/timezone in holiday.timezones)
+ var/time_in_timezone = world.realtime + timezone HOURS
+
+ var/YYYY = text2num(time2text(time_in_timezone, "YYYY")) // get the current year
+ var/MM = text2num(time2text(time_in_timezone, "MM")) // get the current month
+ var/DD = text2num(time2text(time_in_timezone, "DD")) // get the current day
+ var/DDD = time2text(time_in_timezone, "DDD") // get the current weekday
+
+ if(holiday.shouldCelebrate(DD, MM, YYYY, DDD))
+ holiday.celebrate()
+ GLOB.holidays[holiday.name] = holiday
+
+ delete_holiday = FALSE
+ break
+ if(delete_holiday)
qdel(holiday)
- if(holidays)
- holidays = shuffle(holidays)
+ if(GLOB.holidays.len)
+ shuffle_inplace(GLOB.holidays)
+ //Yog code, We haven't totally moved away from holidays being in events
+ if(!SSevents.holidays)
+ SSevents.holidays = list()
+ SSevents.holidays = GLOB.holidays
// regenerate station name because holiday prefixes.
set_station_name(new_station_name())
world.update_status()
-/datum/controller/subsystem/events/proc/toggleWizardmode()
- wizardmode = !wizardmode
- message_admins("Summon Events has been [wizardmode ? "enabled, events will occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes" : "disabled"]!")
- log_game("Summon Events was [wizardmode ? "enabled" : "disabled"]!")
-
-
-/datum/controller/subsystem/events/proc/resetFrequency()
- frequency_lower = initial(frequency_lower)
- frequency_upper = initial(frequency_upper)
+ return TRUE
diff --git a/code/controllers/subsystem/fire_burning.dm b/code/controllers/subsystem/fire_burning.dm
index 2a4d48f14f26..df138faf013e 100644
--- a/code/controllers/subsystem/fire_burning.dm
+++ b/code/controllers/subsystem/fire_burning.dm
@@ -1,6 +1,6 @@
SUBSYSTEM_DEF(fire_burning)
name = "Fire Burning"
- priority = FIRE_PRIOTITY_BURNING
+ priority = FIRE_PRIORITY_BURNING
flags = SS_NO_INIT|SS_BACKGROUND
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 5d51b4ac94d2..38e822d9ab9c 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -342,12 +342,12 @@ SUBSYSTEM_DEF(garbage)
I.qdels++
if(isnull(D.gc_destroyed))
- if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
+ if (SEND_SIGNAL(D, COMSIG_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted
return
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/start_tick = world.tick_usage
- SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
+ SEND_SIGNAL(D, COMSIG_QDELETING, force) // Let the (remaining) components know about the result of Destroy
var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
if(world.time != start_time)
I.slept_destroy++
diff --git a/code/controllers/subsystem/icon_smooth.dm b/code/controllers/subsystem/icon_smooth.dm
index c5d02cf9439c..9e5e9ffa99de 100644
--- a/code/controllers/subsystem/icon_smooth.dm
+++ b/code/controllers/subsystem/icon_smooth.dm
@@ -2,43 +2,92 @@ SUBSYSTEM_DEF(icon_smooth)
name = "Icon Smoothing"
init_order = INIT_ORDER_ICON_SMOOTHING
wait = 1
- priority = FIRE_PRIOTITY_SMOOTHING
+ priority = FIRE_PRIORITY_SMOOTHING
flags = SS_TICKER
- loading_points = 3.3 SECONDS // Yogs -- loading times
-
+ ///Blueprints assemble an image of what pipes/manifolds/wires look like on initialization, and thus should be taken after everything's been smoothed
+ var/list/blueprint_queue = list()
var/list/smooth_queue = list()
var/list/deferred = list()
+ var/list/deferred_by_source = list()
/datum/controller/subsystem/icon_smooth/fire()
- var/list/cached = smooth_queue
- while(cached.len)
- var/atom/A = cached[cached.len]
- cached.len--
- if (A.flags_1 & INITIALIZED_1)
- smooth_icon(A)
+ // We do not want to smooth icons of atoms whose neighbors are not initialized yet,
+ // this causes runtimes.
+ // Icon smoothing SS runs after atoms, so this only happens for something like shuttles.
+ // This kind of map loading shouldn't take too long, so the delay is not a problem.
+ if (SSatoms.initializing_something())
+ return
+
+ var/list/smooth_queue_cache = smooth_queue
+ while(length(smooth_queue_cache))
+ var/atom/smoothing_atom = smooth_queue_cache[length(smooth_queue_cache)]
+ smooth_queue_cache.len--
+ if(QDELETED(smoothing_atom) || !(smoothing_atom.smoothing_flags & SMOOTH_QUEUED))
+ continue
+ if(smoothing_atom.flags_1 & INITIALIZED_1)
+ smoothing_atom.smooth_icon()
else
- deferred += A
+ deferred += smoothing_atom
if (MC_TICK_CHECK)
return
- if (!cached.len)
+ if (!length(smooth_queue_cache))
if (deferred.len)
smooth_queue = deferred
- deferred = cached
+ deferred = smooth_queue_cache
else
- can_fire = 0
+ can_fire = FALSE
/datum/controller/subsystem/icon_smooth/Initialize()
- smooth_zlevel(1,TRUE)
- smooth_zlevel(2,TRUE)
- var/queue = smooth_queue
+ var/list/queue = smooth_queue
smooth_queue = list()
- for(var/V in queue)
- var/atom/A = V
- if(!A || A.z <= 2)
+
+ while(length(queue))
+ var/atom/smoothing_atom = queue[length(queue)]
+ queue.len--
+ if(QDELETED(smoothing_atom) || !(smoothing_atom.smoothing_flags & SMOOTH_QUEUED) || !smoothing_atom.z)
continue
- smooth_icon(A)
+ smoothing_atom.smooth_icon()
CHECK_TICK
+ queue = blueprint_queue
+ blueprint_queue = null
+
+ for(var/atom/movable/movable_item as anything in queue)
+ if(!isturf(movable_item.loc))
+ continue
+ var/turf/item_loc = movable_item.loc
+ item_loc.add_blueprints(movable_item)
+
return SS_INIT_SUCCESS
+
+/// Releases a pool of delayed smooth attempts from a particular source
+/datum/controller/subsystem/icon_smooth/proc/free_deferred(source_to_free)
+ smooth_queue += deferred_by_source[source_to_free]
+ deferred_by_source -= source_to_free
+ if(!can_fire)
+ can_fire = TRUE
+
+/datum/controller/subsystem/icon_smooth/proc/add_to_queue(atom/thing)
+ if(thing.smoothing_flags & SMOOTH_QUEUED)
+ return
+ thing.smoothing_flags |= SMOOTH_QUEUED
+ // If we're currently locked into mapload BY something
+ // Then put us in a deferred list that we release when this mapload run is finished
+ if(initialized && length(SSatoms.initialized_state) && SSatoms.initialized == INITIALIZATION_INNEW_MAPLOAD)
+ var/source = SSatoms.get_initialized_source()
+ LAZYADD(deferred_by_source[source], thing)
+ return
+ smooth_queue += thing
+ if(!can_fire)
+ can_fire = TRUE
+
+/datum/controller/subsystem/icon_smooth/proc/remove_from_queues(atom/thing)
+ // Lack of removal from deferred_by_source is safe because the lack of SMOOTH_QUEUED will just free it anyway
+ // Hopefully this'll never cause a harddel (dies)
+ thing.smoothing_flags &= ~SMOOTH_QUEUED
+ smooth_queue -= thing
+ if(blueprint_queue)
+ blueprint_queue -= thing
+ deferred -= thing
diff --git a/code/controllers/subsystem/init_profiler.dm b/code/controllers/subsystem/init_profiler.dm
new file mode 100644
index 000000000000..063898b6a098
--- /dev/null
+++ b/code/controllers/subsystem/init_profiler.dm
@@ -0,0 +1,28 @@
+#define INIT_PROFILE_NAME "init_profiler.json"
+
+///Subsystem exists so we can separately log init time costs from the costs of general operation
+///Hopefully this makes sorting out what causes problems when easier
+SUBSYSTEM_DEF(init_profiler)
+ name = "Init Profiler"
+ init_order = INIT_ORDER_INIT_PROFILER
+ init_stage = INITSTAGE_MAX
+ flags = SS_NO_FIRE
+
+/datum/controller/subsystem/init_profiler/Initialize()
+ if(CONFIG_GET(flag/auto_profile))
+ write_init_profile()
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/init_profiler/proc/write_init_profile()
+ var/current_profile_data = world.Profile(PROFILE_REFRESH, format = "json")
+ CHECK_TICK
+
+ if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, profiling stopped manually before dump.")
+ var/prof_file = file("[GLOB.log_directory]/[INIT_PROFILE_NAME]")
+ if(fexists(prof_file))
+ fdel(prof_file)
+ WRITE_FILE(prof_file, current_profile_data)
+ world.Profile(PROFILE_CLEAR) //Now that we're written this data out, dump it. We don't want it getting mixed up with our current round data
+
+#undef INIT_PROFILE_NAME
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 72602026b023..9e59c656e545 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -34,7 +34,8 @@ SUBSYSTEM_DEF(input)
var/list/clients = GLOB.clients
for(var/i in 1 to clients.len)
var/client/user = clients[i]
- user.set_macros()
+ if(user)
+ user.set_macros()
/datum/controller/subsystem/input/fire()
for(var/mob/user as anything in GLOB.player_list)
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 924b5b22ba79..4fbc20fc21ae 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -516,7 +516,7 @@ SUBSYSTEM_DEF(job)
RejectPlayer(player)
//Gives the player the stuff he should have with his rank
-/datum/controller/subsystem/job/proc/EquipRank(mob/M, rank, joined_late = FALSE)
+/datum/controller/subsystem/job/proc/EquipRank(mob/living/M, rank, joined_late = FALSE)
var/mob/dead/new_player/newplayer
var/mob/living/living_mob
if(!joined_late)
@@ -621,7 +621,6 @@ SUBSYSTEM_DEF(job)
template.load(B.loc, centered = FALSE)
qdel(B)
-
/datum/controller/subsystem/job/proc/random_chapel_init()
try
var/list/player_box = list()
diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm
index 9dd624b5cf84..5c895c855f7c 100644
--- a/code/controllers/subsystem/lighting.dm
+++ b/code/controllers/subsystem/lighting.dm
@@ -6,27 +6,19 @@ SUBSYSTEM_DEF(lighting)
var/static/list/sources_queue = list() // List of lighting sources queued for update.
var/static/list/corners_queue = list() // List of lighting corners queued for update.
var/static/list/objects_queue = list() // List of lighting objects queued for update.
-
+ var/static/list/current_sources = list()
+#ifdef VISUALIZE_LIGHT_UPDATES
+ var/allow_duped_values = FALSE
+ var/allow_duped_corners = FALSE
+#endif
loading_points = 6 SECONDS // Yogs -- loading times
/datum/controller/subsystem/lighting/stat_entry(msg)
msg = "L:[length(sources_queue)]|C:[length(corners_queue)]|O:[length(objects_queue)]"
return ..()
-/datum/controller/subsystem/lighting/get_metrics()
- . = ..()
- .["queue_sources"] = length(sources_queue)
- .["queue_corners"] = length(corners_queue)
- .["queue_objects"] = length(objects_queue)
-
-/datum/controller/subsystem/lighting/Initialize(timeofday)
+/datum/controller/subsystem/lighting/Initialize()
if(!initialized)
- if (CONFIG_GET(flag/starlight))
- for(var/I in GLOB.areas)
- var/area/A = I
- if (A.dynamic_lighting == DYNAMIC_LIGHTING_IFSTARLIGHT)
- A.luminosity = 0
-
create_all_lighting_objects()
initialized = TRUE
@@ -38,62 +30,96 @@ SUBSYSTEM_DEF(lighting)
MC_SPLIT_TICK_INIT(3)
if(!init_tick_checks)
MC_SPLIT_TICK
- var/list/queue = sources_queue
+
+ if(!resumed)
+ current_sources = sources_queue
+ sources_queue = list()
+
+ // UPDATE SOURCE QUEUE
var/i = 0
- for (i in 1 to length(queue))
- var/datum/light_source/L = queue[i]
+ var/list/queue = current_sources
+ while(i < length(queue)) //we don't use for loop here because i cannot be changed during an iteration
+ i += 1
+ var/datum/light_source/L = queue[i]
L.update_corners()
+ if(!QDELETED(L))
+ L.needs_update = LIGHTING_NO_UPDATE
+ else
+ i -= 1 // update_corners() has removed L from the list, move back so we don't overflow or skip the next element
- L.needs_update = LIGHTING_NO_UPDATE
-
+ // We unroll TICK_CHECK here so we can clear out the queue to ensure any removals/additions when sleeping don't fuck us
if(init_tick_checks)
- CHECK_TICK
- else if (MC_TICK_CHECK)
+ if(!TICK_CHECK)
+ continue
+ queue.Cut(1, i + 1)
+ i = 0
+ stoplag()
+ else if(MC_TICK_CHECK)
break
- if (i)
- queue.Cut(1, i+1)
+ if(i)
+ queue.Cut(1, i + 1)
i = 0
if(!init_tick_checks)
MC_SPLIT_TICK
+ // UPDATE CORNERS QUEUE
queue = corners_queue
- for (i in 1 to length(queue))
- var/datum/lighting_corner/C = queue[i]
+ while(i < length(queue)) //we don't use for loop here because i cannot be changed during an iteration
+ i += 1
+ var/datum/lighting_corner/C = queue[i]
C.needs_update = FALSE //update_objects() can call qdel if the corner is storing no data
C.update_objects()
+ // We unroll TICK_CHECK here so we can clear out the queue to ensure any removals/additions when sleeping don't fuck us
if(init_tick_checks)
- CHECK_TICK
- else if (MC_TICK_CHECK)
+ if(!TICK_CHECK)
+ continue
+ queue.Cut(1, i + 1)
+ i = 0
+ stoplag()
+ else if(MC_TICK_CHECK)
break
- if (i)
+ if(i)
queue.Cut(1, i+1)
i = 0
-
if(!init_tick_checks)
MC_SPLIT_TICK
+ // UPDATE OBJECTS QUEUE
queue = objects_queue
- for (i in 1 to length(queue))
- var/datum/lighting_object/O = queue[i]
+ while(i < length(queue)) //we don't use for loop here because i cannot be changed during an iteration
+ i += 1
- if (QDELETED(O))
+ var/datum/lighting_object/O = queue[i]
+ if(QDELETED(O))
continue
-
O.update()
O.needs_update = FALSE
+
+ // We unroll TICK_CHECK here so we can clear out the queue to ensure any removals/additions when sleeping don't fuck us
if(init_tick_checks)
- CHECK_TICK
- else if (MC_TICK_CHECK)
+ if(!TICK_CHECK)
+ continue
+ queue.Cut(1, i + 1)
+ i = 0
+ stoplag()
+ else if(MC_TICK_CHECK)
break
- if (i)
- queue.Cut(1, i+1)
+ if(i)
+ queue.Cut(1, i + 1)
/datum/controller/subsystem/lighting/Recover()
initialized = SSlighting.initialized
..()
+
+///Yog metric tracking
+/datum/controller/subsystem/lighting/get_metrics()
+ . = ..()
+ .["queue_sources"] = length(sources_queue)
+ .["queue_corners"] = length(corners_queue)
+ .["queue_objects"] = length(objects_queue)
diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm
index 461fc395dae7..da3f04dcb275 100644
--- a/code/controllers/subsystem/machines.dm
+++ b/code/controllers/subsystem/machines.dm
@@ -3,27 +3,71 @@ SUBSYSTEM_DEF(machines)
init_order = INIT_ORDER_MACHINES
flags = SS_KEEP_TIMING
wait = 2 SECONDS
+
+ /// Assosciative list of all machines that exist.
+ VAR_PRIVATE/list/machines_by_type = list()
+ /// All machines, not just those that are processing.
+ VAR_PRIVATE/list/all_machines = list()
+
var/list/processing = list()
var/list/currentrun = list()
+ ///List of all powernets on the server.
var/list/powernets = list()
- /// Assosciative list of all machines that exist.
- VAR_PRIVATE/list/machines_by_type = list()
/datum/controller/subsystem/machines/Initialize()
makepowernets()
fire()
return SS_INIT_SUCCESS
+/// Registers a machine with the machine subsystem; should only be called by the machine itself during its creation.
+/datum/controller/subsystem/machines/proc/register_machine(obj/machinery/machine)
+ LAZYADD(machines_by_type[machine.type], machine)
+ all_machines |= machine
+
+/// Removes a machine from the machine subsystem; should only be called by the machine itself inside Destroy.
+/datum/controller/subsystem/machines/proc/unregister_machine(obj/machinery/machine)
+ var/list/existing = machines_by_type[machine.type]
+ existing -= machine
+ if(!length(existing))
+ machines_by_type -= machine.type
+ all_machines -= machine
+
+/// Gets a list of all machines that are either the passed type or a subtype.
+/datum/controller/subsystem/machines/proc/get_machines_by_type_and_subtypes(obj/machinery/machine_type)
+ if(!ispath(machine_type))
+ machine_type = machine_type.type
+ if(!ispath(machine_type, /obj/machinery))
+ CRASH("called get_machines_by_type_and_subtypes with a non-machine type [machine_type]")
+ var/list/machines = list()
+ for(var/next_type in typesof(machine_type))
+ var/list/found_machines = machines_by_type[next_type]
+ if(found_machines)
+ machines += found_machines
+ return machines
+
+/// Gets a list of all machines that are the exact passed type.
+/datum/controller/subsystem/machines/proc/get_machines_by_type(obj/machinery/machine_type)
+ if(!ispath(machine_type))
+ machine_type = machine_type.type
+ if(!ispath(machine_type, /obj/machinery))
+ CRASH("called get_machines_by_type with a non-machine type [machine_type]")
+
+ var/list/machines = machines_by_type[machine_type]
+ return machines?.Copy() || list()
+
+/datum/controller/subsystem/machines/proc/get_all_machines()
+ return all_machines.Copy()
+
/datum/controller/subsystem/machines/proc/makepowernets()
- for(var/datum/powernet/PN in powernets)
- qdel(PN)
+ for(var/datum/powernet/power_network as anything in powernets)
+ qdel(power_network)
powernets.Cut()
- for(var/obj/structure/cable/PC in GLOB.cable_list)
- if(!PC.powernet)
- var/datum/powernet/NewPN = new(PC.loc.z)
- NewPN.add_cable(PC)
- propagate_network(PC,PC.powernet)
+ for(var/obj/structure/cable/power_cable as anything in GLOB.cable_list)
+ if(!power_cable.powernet)
+ var/datum/powernet/new_powernet = new()
+ new_powernet.add_cable(power_cable)
+ propagate_network(power_cable, power_cable.powernet)
/datum/controller/subsystem/machines/stat_entry(msg)
msg = "M:[length(processing)]|PN:[length(powernets)]"
@@ -34,10 +78,10 @@ SUBSYSTEM_DEF(machines)
.["machines"] = length(processing)
.["powernets"] = length(powernets)
-/datum/controller/subsystem/machines/fire(resumed = 0)
+/datum/controller/subsystem/machines/fire(resumed = FALSE)
if (!resumed)
- for(var/datum/powernet/Powernet in powernets)
- Powernet.reset() //reset the power state.
+ for(var/datum/powernet/powernet as anything in powernets)
+ powernet.reset() //reset the power state.
src.currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
@@ -46,13 +90,9 @@ SUBSYSTEM_DEF(machines)
while(currentrun.len)
var/obj/machinery/thing = currentrun[currentrun.len]
currentrun.len--
- if(!QDELETED(thing) && thing.process(wait * 0.1) != PROCESS_KILL)
- if(thing.use_power)
- thing.auto_use_power() //add back the power state
- else
+ if(QDELETED(thing) || thing.process(wait * 0.1) == PROCESS_KILL)
processing -= thing
- if (!QDELETED(thing))
- thing.datum_flags &= ~DF_ISPROCESSING
+ thing.datum_flags &= ~DF_ISPROCESSING
if (MC_TICK_CHECK)
return
@@ -65,20 +105,11 @@ SUBSYSTEM_DEF(machines)
propagate_network(PC,PC.powernet)
/datum/controller/subsystem/machines/Recover()
- if (istype(SSmachines.processing))
+ if(islist(SSmachines.processing))
processing = SSmachines.processing
- if (istype(SSmachines.powernets))
+ if(islist(SSmachines.powernets))
powernets = SSmachines.powernets
-
-/// Gets a list of all machines that are either the passed type or a subtype.
-/datum/controller/subsystem/machines/proc/get_machines_by_type_and_subtypes(obj/machinery/machine_type)
- if(!ispath(machine_type))
- machine_type = machine_type.type
- if(!ispath(machine_type, /obj/machinery))
- CRASH("called get_machines_by_type_and_subtypes with a non-machine type [machine_type]")
- var/list/machines = list()
- for(var/next_type in typesof(machine_type))
- var/list/found_machines = machines_by_type[next_type]
- if(found_machines)
- machines += found_machines
- return machines
+ if(islist(SSmachines.all_machines))
+ all_machines = SSmachines.all_machines
+ if(islist(SSmachines.machines_by_type))
+ machines_by_type = SSmachines.machines_by_type
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 31ed10458de8..d0b3be66d49b 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -11,7 +11,12 @@ SUBSYSTEM_DEF(mapping)
var/datum/map_config/config
var/datum/map_config/next_map_config
+ /// Has the map for the next round been voted for already?
var/map_voted = FALSE
+ /// Has the map for the next round been deliberately chosen by an admin?
+ var/map_force_chosen = FALSE
+ /// Has the map vote been rocked?
+ var/map_vote_rocked = FALSE
var/list/map_templates = list()
@@ -20,57 +25,137 @@ SUBSYSTEM_DEF(mapping)
var/list/lava_ruins_templates = list()
var/list/ice_ruins_templates = list()
var/list/ice_ruins_underground_templates = list()
+ //Yogs edit
+ var/list/jungleland_proper_ruins_templates = list()
+ var/list/jungleland_dying_ruins_templates = list()
+ var/list/jungleland_swamp_ruins_templates = list()
+ var/list/jungleland_barren_ruins_templates = list()
+ var/list/jungleland_general_ruins_templates = list()
+ //Yogs end
var/list/shuttle_templates = list()
var/list/shelter_templates = list()
+ var/list/holodeck_templates = list()
var/list/station_minimaps = list()
var/list/areas_in_z = list()
+ /// List of z level (as number) -> plane offset of that z level
+ /// Used to maintain the plane cube
+ var/list/z_level_to_plane_offset = list()
+ /// List of z level (as number) -> list of all z levels vertically connected to ours
+ /// Useful for fast grouping lookups and such
+ var/list/z_level_to_stack = list()
+ /// List of z level (as number) -> The lowest plane offset in that z stack
+ var/list/z_level_to_lowest_plane_offset = list()
+ // This pair allows for easy conversion between an offset plane, and its true representation
+ // Both are in the form "input plane" -> output plane(s)
+ /// Assoc list of string plane values to their true, non offset representation
+ var/list/plane_offset_to_true
+ /// Assoc list of true string plane values to a list of all potential offset planess
+ var/list/true_to_offset_planes
+ /// Assoc list of string plane to the plane's offset value
+ var/list/plane_to_offset
+ /// List of planes that do not allow for offsetting
+ var/list/plane_offset_blacklist
+ /// List of render targets that do not allow for offsetting
+ var/list/render_offset_blacklist
+ /// List of plane masters that are of critical priority
+ var/list/critical_planes
+ /// The largest plane offset we've generated so far
+ var/max_plane_offset = 0
var/loading_ruins = FALSE
- var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
- var/list/datum/turf_reservations //list of turf reservations
- var/list/used_turfs = list() //list of turf = datum/turf_reservation
+ var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs)
+ var/list/datum/turf_reservations //list of turf reservations
+ var/list/used_turfs = list() //list of turf = datum/turf_reservation
/// List of lists of turfs to reserve
var/list/lists_to_reserve = list()
+ var/list/reservation_ready = list()
var/clearing_reserved_turfs = FALSE
+ ///All possible biomes in assoc list as type || instance
+ var/list/biomes = list()
+
// Z-manager stuff
var/station_start // should only be used for maploading-related tasks
var/space_levels_so_far = 0
- var/list/z_list
+ ///list of all z level datums in the order of their z (z level 1 is at index 1, etc.)
+ var/list/datum/space_level/z_list
+ ///list of all z level indices that form multiz connections and whether theyre linked up or down.
+ ///list of lists, inner lists are of the form: list("up or down link direction" = TRUE)
+ var/list/multiz_levels = list()
var/datum/space_level/transit
var/datum/space_level/empty_space
var/num_of_res_levels = 1
+ /// True when in the process of adding a new Z-level, global locking
+ var/adding_new_zlevel = FALSE
+
+ ///shows the default gravity value for each z level. recalculated when gravity generators change.
+ ///List in the form: list(z level num = max generator gravity in that z level OR the gravity level trait)
+ var/list/gravity_by_z_level = list()
+
+ /// list of traits and their associated z leves
+ var/list/z_trait_levels = list()
+
+ /// list of lazy templates that have been loaded
+ var/list/loaded_lazy_templates
-//dlete dis once #39770 is resolved
-/datum/controller/subsystem/mapping/proc/HACK_LoadMapConfig()
- if(!config)
+/datum/controller/subsystem/mapping/PreInit()
+ ..()
#ifdef FORCE_MAP
- config = load_map_config(FORCE_MAP)
+ config = load_map_config(FORCE_MAP, FORCE_MAP_DIRECTORY)
#else
- config = load_map_config(error_if_missing = FALSE)
+ config = load_map_config(error_if_missing = FALSE)
#endif
/datum/controller/subsystem/mapping/Initialize(timeofday)
- HACK_LoadMapConfig()
if(initialized)
- return
+ return SS_INIT_SUCCESS
if(config.defaulted)
var/old_config = config
config = global.config.defaultmap
if(!config || config.defaulted)
to_chat(world, span_boldannounce("Unable to load next or default map config, defaulting to Box Station"))
config = old_config
+ plane_offset_to_true = list()
+ true_to_offset_planes = list()
+ plane_to_offset = list()
+ // VERY special cases for FLOAT_PLANE, so it will be treated as expected by plane management logic
+ // Sorry :(
+ plane_offset_to_true["[FLOAT_PLANE]"] = FLOAT_PLANE
+ true_to_offset_planes["[FLOAT_PLANE]"] = list(FLOAT_PLANE)
+ plane_to_offset["[FLOAT_PLANE]"] = 0
+ plane_offset_blacklist = list()
+ // You aren't allowed to offset a floatplane that'll just fuck it all up
+ plane_offset_blacklist["[FLOAT_PLANE]"] = TRUE
+ render_offset_blacklist = list()
+ critical_planes = list()
+ create_plane_offsets(0, 0)
+ initialize_biomes()
loadWorld()
require_area_resort()
process_teleport_locs() //Sets up the wizard teleport locations
preloadTemplates()
- run_map_generation()
+ var/list/jungle_ruins = levels_by_trait(ZTRAIT_JUNGLE_RUINS)
+ //this is really fuckign hacky, but we need to have a very specific order for these things, and if jungleland isn't even being loaded then i dont fucking care.
+ if(jungle_ruins.len)
+ seedRuins(jungle_ruins, CONFIG_GET(number/jungleland_budget), list(/area/pregen), jungleland_general_ruins_templates, clear_below = TRUE)
+ run_map_generation()
+ seedRuins(jungle_ruins, CONFIG_GET(number/jungleland_budget), list(/area/jungleland/proper), jungleland_proper_ruins_templates, clear_below = TRUE)
+ seedRuins(jungle_ruins, CONFIG_GET(number/jungleland_budget), list(/area/jungleland/dying_forest), jungleland_dying_ruins_templates, clear_below = TRUE)
+ seedRuins(jungle_ruins, CONFIG_GET(number/jungleland_budget), list(/area/jungleland/toxic_pit), jungleland_swamp_ruins_templates, clear_below = TRUE)
+ seedRuins(jungle_ruins, CONFIG_GET(number/jungleland_budget), list(/area/jungleland/barren_rocks), jungleland_barren_ruins_templates, clear_below = TRUE)
+ else
+ run_map_generation()
+ //YOGS EDIT
#ifndef LOWMEMORYMODE
+ //Pregenerate generic jungleland ruins that are biome-nonspecific
+
+ //YOGS END
+
// Create space ruin levels
while (space_levels_so_far < config.space_ruin_levels)
++space_levels_so_far
@@ -102,14 +187,10 @@ SUBSYSTEM_DEF(mapping)
if (ice_ruins.len)
// needs to be whitelisted for underground too so place_below ruins work
seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), ice_ruins_templates, clear_below = TRUE)
- for(var/ice_z in ice_ruins)
- spawn_rivers(ice_z, 4, /turf/open/openspace/icemoon, /area/icemoon/surface/outdoors/unexplored)
var/list/ice_ruins_underground = levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND)
if (ice_ruins_underground.len)
seedRuins(ice_ruins_underground, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/underground/unexplored), ice_ruins_underground_templates, clear_below = TRUE)
- for(var/ice_z in ice_ruins_underground)
- spawn_rivers(ice_z, 4, /turf/open/lava/plasma/ice_moon, /area/icemoon/underground/unexplored)
// Generate deep space ruins
var/list/space_ruins = levels_by_trait(ZTRAIT_SPACE_RUINS)
@@ -117,7 +198,7 @@ SUBSYSTEM_DEF(mapping)
seedRuins(space_ruins, CONFIG_GET(number/space_budget), list(/area/space), space_ruins_templates)
seedStation()
loading_ruins = FALSE
-
+
//Load Reebe
var/list/errorList = list()
SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE)
@@ -131,14 +212,14 @@ SUBSYSTEM_DEF(mapping)
message_admins("A shuttle arena failed to load!")
log_game("A shuttle arena failed to load!")
#endif
-
// Add the transit level
- transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE))
+ var/datum/space_level/base_transit = add_reservation_zlevel()
require_area_resort()
// Set up Z-level transitions.
setup_map_transitions()
generate_station_area_list()
- initialize_reserved_level()
+ initialize_reserved_level(base_transit.z_value)
+ calculate_default_z_level_gravities()
// Build minimaps
build_minimaps()
return SS_INIT_SUCCESS
@@ -155,7 +236,8 @@ SUBSYSTEM_DEF(mapping)
var/packetlen = length(packet)
while(packetlen)
if(MC_TICK_CHECK)
- lists_to_reserve.Cut(1, index)
+ if(index)
+ lists_to_reserve.Cut(1, index)
return
var/turf/T = packet[packetlen]
T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE)
@@ -163,7 +245,7 @@ SUBSYSTEM_DEF(mapping)
unused_turfs["[T.z]"] |= T
var/area/old_area = T.loc
old_area.turfs_to_uncontain += T
- T.flags_1 |= UNUSED_RESERVATION_TURF_1
+ T.turf_flags = UNUSED_RESERVATION_TURF
world_contents += T
world_turf_contents += T
packet.len--
@@ -172,14 +254,50 @@ SUBSYSTEM_DEF(mapping)
index++
lists_to_reserve.Cut(1, index)
+/datum/controller/subsystem/mapping/proc/calculate_default_z_level_gravities()
+ for(var/z_level in 1 to length(z_list))
+ calculate_z_level_gravity(z_level)
+
+/datum/controller/subsystem/mapping/proc/generate_z_level_linkages()
+ for(var/z_level in 1 to length(z_list))
+ generate_linkages_for_z_level(z_level)
+
+/datum/controller/subsystem/mapping/proc/generate_linkages_for_z_level(z_level)
+ if(!isnum(z_level) || z_level <= 0)
+ return FALSE
+
+ if(multiz_levels.len < z_level)
+ multiz_levels.len = z_level
+
+ var/z_above = level_trait(z_level, ZTRAIT_UP)
+ var/z_below = level_trait(z_level, ZTRAIT_DOWN)
+ if(!(z_above == TRUE || z_above == FALSE || z_above == null) || !(z_below == TRUE || z_below == FALSE || z_below == null))
+ stack_trace("Warning, numeric mapping offsets are deprecated. Instead, mark z level connections by setting UP/DOWN to true if the connection is allowed")
+ multiz_levels[z_level] = new /list(LARGEST_Z_LEVEL_INDEX)
+ multiz_levels[z_level][Z_LEVEL_UP] = !!z_above
+ multiz_levels[z_level][Z_LEVEL_DOWN] = !!z_below
+
+/datum/controller/subsystem/mapping/proc/calculate_z_level_gravity(z_level_number)
+ if(!isnum(z_level_number) || z_level_number < 1)
+ return FALSE
+
+ var/max_gravity = 0
+
+ for(var/obj/machinery/gravity_generator/main/grav_gen as anything in GLOB.gravity_generators["[z_level_number]"])
+ max_gravity = max(grav_gen.setting, max_gravity)
+
+ max_gravity = max_gravity || level_trait(z_level_number, ZTRAIT_GRAVITY) || 0//just to make sure no nulls
+ gravity_by_z_level[z_level_number] = max_gravity
+ return max_gravity
+
/datum/controller/subsystem/mapping/proc/wipe_reservations(wipe_safety_delay = 100)
- if(clearing_reserved_turfs || !initialized) //in either case this is just not needed.
+ if(clearing_reserved_turfs || !initialized) //in either case this is just not needed.
return
clearing_reserved_turfs = TRUE
SSshuttle.transit_requesters.Cut()
message_admins("Clearing dynamic reservation space.")
var/list/obj/docking_port/mobile/in_transit = list()
- for(var/i in SSshuttle.transit)
+ for(var/i in SSshuttle.transit_docking_ports)
var/obj/docking_port/stationary/transit/T = i
if(!istype(T))
continue
@@ -201,6 +319,10 @@ SUBSYSTEM_DEF(mapping)
returning += M
qdel(T, TRUE)
+/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T)
+ RETURN_TYPE(/datum/turf_reservation)
+ return used_turfs[T]
+
/* Nuke threats, for making the blue tiles on the station go RED
Used by the AI doomsday and the self destruct nuke.
*/
@@ -243,6 +365,8 @@ SUBSYSTEM_DEF(mapping)
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
z_list = SSmapping.z_list
+ multiz_levels = SSmapping.multiz_levels
+ loaded_lazy_templates = SSmapping.loaded_lazy_templates
#define INIT_ANNOUNCE(X) to_chat(world, span_boldannounce("[X]")); log_world(X)
/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE)
@@ -285,7 +409,10 @@ SUBSYSTEM_DEF(mapping)
// load the maps
for (var/P in parsed_maps)
var/datum/parsed_map/pm = P
- if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE, new_z = TRUE))
+ var/bounds = pm.bounds
+ var/x_offset = bounds ? round(world.maxx / 2 - bounds[MAP_MAXX] / 2) + 1 : 1
+ var/y_offset = bounds ? round(world.maxy / 2 - bounds[MAP_MAXY] / 2) + 1 : 1
+ if (!pm.load(x_offset, y_offset, start_z + parsed_maps[P], no_changeturf = TRUE, new_z = TRUE))
errorList |= pm.original_path
if(!silent)
INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!")
@@ -319,8 +446,34 @@ SUBSYSTEM_DEF(mapping)
// load mining
if(config.minetype == "lavaland")
LoadGroup(FailedZs, "Lavaland", "map_files/mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND) //Yogs, yoglavaland
- else if (config.minetype == "icemoon")
- else if (!isnull(config.minetype))
+ GLOB.minetype = MINETYPE_LAVALAND
+ //Yogs begin, jungleland gen
+ else if(config.minetype == "jungleland")
+ LoadGroup(FailedZs, "Jungleland", "map_files/mining", "Jungleland.dmm", default_traits = ZTRAITS_JUNGLELAND)
+ GLOB.minetype = MINETYPE_JUNGLE
+
+ else if(config.minetype == "jungle_and_lavaland")
+ SSpersistence.LoadMinetype()
+ var/determinant = SSpersistence.next_minetype
+ switch(determinant)
+ if(2)
+ if(prob(50))
+ LoadGroup(FailedZs, "Lavaland", "map_files/mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
+ GLOB.minetype = MINETYPE_LAVALAND
+ else
+ LoadGroup(FailedZs, "Jungleland", "map_files/mining", "Jungleland.dmm", default_traits = ZTRAITS_JUNGLELAND)
+ GLOB.minetype = MINETYPE_JUNGLE
+
+ if(1)
+ LoadGroup(FailedZs, "Lavaland", "map_files/mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
+ GLOB.minetype = MINETYPE_LAVALAND
+
+ if(0)
+ LoadGroup(FailedZs, "Jungleland", "map_files/mining", "Jungleland.dmm", default_traits = ZTRAITS_JUNGLELAND)
+ GLOB.minetype = MINETYPE_JUNGLE
+
+ //Yogs end
+ else if (!isnull(config.minetype) && config.minetype != "none")
INIT_ANNOUNCE("WARNING: An unknown minetype '[config.minetype]' was set! This is being ignored! Update the maploader code!")
#endif
@@ -411,13 +564,21 @@ GLOBAL_LIST_EMPTY(the_station_areas)
if (. && VM.map_name != config.map_name)
to_chat(world, span_boldannounce("Map rotation has chosen [VM.map_name] for next round!"))
-/datum/controller/subsystem/mapping/proc/changemap(datum/map_config/VM)
- if(!VM.MakeNextMap())
- next_map_config = load_map_config(default_to_box = TRUE)
- message_admins("Failed to set new map with next_map.json for [VM.map_name]! Using default as backup!")
+/datum/controller/subsystem/mapping/proc/changemap(datum/map_config/change_to)
+ if(!change_to.MakeNextMap())
+ next_map_config = load_default_map_config()
+ message_admins("Failed to set new map with next_map.json for [change_to.map_name]! Using default as backup!")
return
- next_map_config = VM
+ var/filter_threshold = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE)
+ if (change_to.config_min_users > 0 && filter_threshold != 0 && filter_threshold < change_to.config_min_users)
+ message_admins("[change_to.map_name] was chosen for the next map, despite there being less current players than its set minimum population range!")
+ log_game("[change_to.map_name] was chosen for the next map, despite there being less current players than its set minimum population range!")
+ if (change_to.config_max_users > 0 && filter_threshold > change_to.config_max_users)
+ message_admins("[change_to.map_name] was chosen for the next map, despite there being more current players than its set maximum population range!")
+ log_game("[change_to.map_name] was chosen for the next map, despite there being more current players than its set maximum population range!")
+
+ next_map_config = change_to
return TRUE
/datum/controller/subsystem/mapping/proc/preloadTemplates(path = "_maps/templates/") //see master controller setup
@@ -435,6 +596,7 @@ GLOBAL_LIST_EMPTY(the_station_areas)
var/list/banned = generateMapList("[global.config.directory]/lavaruinblacklist.txt")
banned += generateMapList("[global.config.directory]/spaceruinblacklist.txt")
banned += generateMapList("[global.config.directory]/iceruinblacklist.txt")
+ banned += generateMapList("[global.config.directory]/jungleruinblacklist.txt")
for(var/item in sortList(subtypesof(/datum/map_template/ruin), /proc/cmp_ruincost_priority))
var/datum/map_template/ruin/ruin_type = item
@@ -449,6 +611,7 @@ GLOBAL_LIST_EMPTY(the_station_areas)
map_templates[R.name] = R
ruins_templates[R.name] = R
+
if(istype(R, /datum/map_template/ruin/lavaland))
lava_ruins_templates[R.name] = R
else if(istype(R, /datum/map_template/ruin/icemoon/underground))
@@ -457,11 +620,23 @@ GLOBAL_LIST_EMPTY(the_station_areas)
ice_ruins_templates[R.name] = R
else if(istype(R, /datum/map_template/ruin/space))
space_ruins_templates[R.name] = R
- else if(istype(R, /datum/map_template/ruin/station)) //yogs
- station_room_templates[R.name] = R //yogs
+ //Yogs begin
+ else if(istype(R, /datum/map_template/ruin/station))
+ station_room_templates[R.name] = R
+ else if(istype(R,/datum/map_template/ruin/jungle/proper))
+ jungleland_proper_ruins_templates[R.name] = R
+ else if(istype(R,/datum/map_template/ruin/jungle/dying))
+ jungleland_dying_ruins_templates[R.name] = R
+ else if(istype(R,/datum/map_template/ruin/jungle/swamp))
+ jungleland_swamp_ruins_templates[R.name] = R
+ else if(istype(R,/datum/map_template/ruin/jungle/barren))
+ jungleland_barren_ruins_templates[R.name] = R
+ else if(istype(R,/datum/map_template/ruin/jungle/all))
+ jungleland_general_ruins_templates[R.name] = R
+ //Yogs end
/datum/controller/subsystem/mapping/proc/preloadShuttleTemplates()
- var/list/unbuyable = generateMapList("[global.config.directory]/unbuyableshuttles.txt")
+ var/list/unbuyable = generateMapList("unbuyableshuttles.txt")
for(var/item in subtypesof(/datum/map_template/shuttle))
var/datum/map_template/shuttle/shuttle_type = item
@@ -471,6 +646,7 @@ GLOBAL_LIST_EMPTY(the_station_areas)
var/datum/map_template/shuttle/S = new shuttle_type()
if(unbuyable.Find(S.mappath))
S.credit_cost = INFINITY
+ S.who_can_purchase = null
shuttle_templates[S.shuttle_id] = S
map_templates[S.shuttle_id] = S
@@ -541,44 +717,66 @@ GLOBAL_LIST_EMPTY(the_station_areas)
GLOB.the_gateway.awaygate = new_gate
GLOB.the_gateway.wait = world.time
-/datum/controller/subsystem/mapping/proc/RequestBlockReservation(width, height, z, type = /datum/turf_reservation, turf_type_override)
- UNTIL(initialized && !clearing_reserved_turfs)
- var/datum/turf_reservation/reserve = new type
- if(turf_type_override)
+/// Adds a new reservation z level. A bit of space that can be handed out on request
+/// Of note, reservations default to transit turfs, to make their most common use, shuttles, faster
+/datum/controller/subsystem/mapping/proc/add_reservation_zlevel(for_shuttles)
+ num_of_res_levels++
+ return add_new_zlevel("Transit/Reserved #[num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE))
+
+/// Requests a /datum/turf_reservation based on the given width, height, and z_size. You can specify a z_reservation to use a specific z level, or leave it null to use any z level.
+/datum/controller/subsystem/mapping/proc/request_turf_block_reservation(
+ width,
+ height,
+ z_size = 1,
+ z_reservation = null,
+ reservation_type = /datum/turf_reservation,
+ turf_type_override = null,
+)
+ UNTIL((!z_reservation || reservation_ready["[z_reservation]"]) && !clearing_reserved_turfs)
+ var/datum/turf_reservation/reserve = new reservation_type
+ if(!isnull(turf_type_override))
reserve.turf_type = turf_type_override
- if(!z)
+ if(!z_reservation)
for(var/i in levels_by_trait(ZTRAIT_RESERVED))
- if(reserve.Reserve(width, height, i))
+ if(reserve.reserve(width, height, z_size, i))
return reserve
//If we didn't return at this point, theres a good chance we ran out of room on the exisiting reserved z levels, so lets try a new one
- num_of_res_levels += 1
- var/datum/space_level/newReserved = add_new_zlevel("Transit/Reserved [num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE))
+ var/datum/space_level/newReserved = add_reservation_zlevel()
initialize_reserved_level(newReserved.z_value)
- if(reserve.Reserve(width, height, newReserved.z_value))
+ if(reserve.reserve(width, height, z_size, newReserved.z_value))
return reserve
else
- if(!level_trait(z, ZTRAIT_RESERVED))
+ if(!level_trait(z_reservation, ZTRAIT_RESERVED))
qdel(reserve)
return
else
- if(reserve.Reserve(width, height, z))
+ if(reserve.reserve(width, height, z_size, z_reservation))
return reserve
QDEL_NULL(reserve)
-//This is not for wiping reserved levels, use wipe_reservations() for that.
-/datum/controller/subsystem/mapping/proc/initialize_reserved_level()
- UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case.
- clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it.
- for(var/i in levels_by_trait(ZTRAIT_RESERVED))
- var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,i))
- var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,i))
- var/block = block(A, B)
- for(var/t in block)
- // No need to empty() these, because it's world init and they're
- // already /turf/open/space/basic.
- var/turf/T = t
- T.flags_1 |= UNUSED_RESERVATION_TURF_1
- unused_turfs["[i]"] = block
+///Sets up a z level as reserved
+///This is not for wiping reserved levels, use wipe_reservations() for that.
+///If this is called after SSatom init, it will call Initialize on all turfs on the passed z, as its name promises
+/datum/controller/subsystem/mapping/proc/initialize_reserved_level(z)
+ UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case.
+ clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it.
+ if(!level_trait(z,ZTRAIT_RESERVED))
+ clearing_reserved_turfs = FALSE
+ CRASH("Invalid z level prepared for reservations.")
+ var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,z))
+ var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,z))
+ var/block = block(A, B)
+ for(var/turf/T as anything in block)
+ // No need to empty() these, because they just got created and are already /turf/open/space/basic.
+ T.turf_flags = UNUSED_RESERVATION_TURF
+ CHECK_TICK
+
+ // Gotta create these suckers if we've not done so already
+ if(SSatoms.initialized)
+ SSatoms.InitializeAtoms(Z_TURFS(z))
+
+ unused_turfs["[z]"] = block
+ reservation_ready["[z]"] = TRUE
clearing_reserved_turfs = FALSE
/// Schedules a group of turfs to be handed back to the reservation system's control
@@ -591,21 +789,27 @@ GLOBAL_LIST_EMPTY(the_station_areas)
//DO NOT CALL THIS PROC DIRECTLY, CALL wipe_reservations().
/datum/controller/subsystem/mapping/proc/do_wipe_turf_reservations()
PRIVATE_PROC(TRUE)
- UNTIL(initialized) //This proc is for AFTER init, before init turf reservations won't even exist and using this will likely break things.
+ UNTIL(initialized) //This proc is for AFTER init, before init turf reservations won't even exist and using this will likely break things.
for(var/i in turf_reservations)
var/datum/turf_reservation/TR = i
if(!QDELETED(TR))
qdel(TR, TRUE)
UNSETEMPTY(turf_reservations)
var/list/clearing = list()
- for(var/l in unused_turfs) //unused_turfs is a assoc list by z = list(turfs)
+ for(var/l in unused_turfs) //unused_turfs is an assoc list by z = list(turfs)
if(islist(unused_turfs[l]))
clearing |= unused_turfs[l]
- clearing |= used_turfs //used turfs is an associative list, BUT, reserve_turfs() can still handle it. If the code above works properly, this won't even be needed as the turfs would be freed already.
+ clearing |= used_turfs //used turfs is an associative list, BUT, reserve_turfs() can still handle it. If the code above works properly, this won't even be needed as the turfs would be freed already.
unused_turfs.Cut()
used_turfs.Cut()
reserve_turfs(clearing, await = TRUE)
+///Initialize all biomes, assoc as type || instance
+/datum/controller/subsystem/mapping/proc/initialize_biomes()
+ for(var/biome_path in subtypesof(/datum/biome))
+ var/datum/biome/biome_instance = new biome_path()
+ biomes[biome_path] += biome_instance
+
/datum/controller/subsystem/mapping/proc/build_minimaps()
to_chat(world, span_boldannounce("Building minimaps..."))
for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
@@ -616,6 +820,39 @@ GLOBAL_LIST_EMPTY(the_station_areas)
var/area/A = B
A.reg_in_areas_in_z()
+/// Takes a z level datum, and tells the mapping subsystem to manage it
+/// Also handles things like plane offset generation, and other things that happen on a z level to z level basis
+/datum/controller/subsystem/mapping/proc/manage_z_level(datum/space_level/new_z, filled_with_space, contain_turfs = TRUE)
+ // First, add the z
+ z_list += new_z
+
+ // Then we build our lookup lists
+ var/z_value = new_z.z_value
+ // We are guarenteed that we'll always grow bottom up
+ // Suck it jannies
+ z_level_to_plane_offset.len += 1
+ z_level_to_lowest_plane_offset.len += 1
+ gravity_by_z_level.len += 1
+ z_level_to_stack.len += 1
+ //Bare minimum we have ourselves
+ z_level_to_stack[z_value] = list(z_value)
+ //0's the default value, we'll update it later if required
+ z_level_to_plane_offset[z_value] = 0
+ z_level_to_lowest_plane_offset[z_value] = 0
+
+ // Now we check if this plane is offset or not
+ var/below_offset = new_z.traits[ZTRAIT_DOWN]
+ if(below_offset)
+ update_plane_tracking(new_z)
+
+ if(contain_turfs)
+ build_area_turfs(z_value, filled_with_space)
+
+ // And finally, misc global generation
+
+ // We'll have to update this if offsets change, because we load lowest z to highest z
+ generate_lighting_appearance_by_z(z_value)
+
/datum/controller/subsystem/mapping/proc/build_area_turfs(z_level, space_guaranteed)
// If we know this is filled with default tiles, we can use the default area
// Faster
@@ -628,7 +865,127 @@ GLOBAL_LIST_EMPTY(the_station_areas)
var/area/our_area = to_contain.loc
our_area.contained_turfs += to_contain
+/datum/controller/subsystem/mapping/proc/update_plane_tracking(datum/space_level/update_with)
+ // We're essentially going to walk down the stack of connected z levels, and set their plane offset as we go
+ var/plane_offset = 0
+ var/datum/space_level/current_z = update_with
+ var/list/datum/space_level/levels_checked = list()
+ var/list/z_stack = list()
+ while(TRUE)
+ var/z_level = current_z.z_value
+ z_stack += z_level
+ z_level_to_plane_offset[z_level] = plane_offset
+ levels_checked += current_z
+ if(!current_z.traits[ZTRAIT_DOWN]) // If there's nothing below, stop looking
+ break
+ // Otherwise, down down down we go
+ current_z = z_list[z_level - 1]
+ plane_offset += 1
+
+ /// Updates the lowest offset value
+ for(var/datum/space_level/level_to_update in levels_checked)
+ z_level_to_lowest_plane_offset[level_to_update.z_value] = plane_offset
+ z_level_to_stack[level_to_update.z_value] = z_stack
+
+ // This can be affected by offsets, so we need to update it
+ // PAIN
+ for(var/i in 1 to length(z_list))
+ generate_lighting_appearance_by_z(i)
+
+ var/old_max = max_plane_offset
+ max_plane_offset = max(max_plane_offset, plane_offset)
+ if(max_plane_offset == old_max)
+ return
+
+ generate_offset_lists(old_max + 1, max_plane_offset)
+ SEND_SIGNAL(src, COMSIG_PLANE_OFFSET_INCREASE, old_max, max_plane_offset)
+ // Sanity check
+ if(max_plane_offset > MAX_EXPECTED_Z_DEPTH)
+ stack_trace("We've loaded a map deeper then the max expected z depth. Preferences won't cover visually disabling all of it!")
+
+/// Takes an offset to generate misc lists to, and a base to start from
+/// Use this to react globally to maintain parity with plane offsets
+/datum/controller/subsystem/mapping/proc/generate_offset_lists(gen_from, new_offset)
+ create_plane_offsets(gen_from, new_offset)
+ for(var/offset in gen_from to new_offset)
+ GLOB.starlight_objects += starlight_object(offset)
+ GLOB.starlight_overlays += starlight_overlay(offset)
+
+ // [Yog] pretty sure auxmos would shit if this was included
+ // for(var/datum/gas/gas_type as anything in GLOB.meta_gas_info)
+ // var/list/gas_info = GLOB.meta_gas_info[gas_type]
+ // if(initial(gas_type.moles_visible) != null)
+ // gas_info[META_GAS_OVERLAY] += generate_gas_overlays(gen_from, new_offset, gas_type)
+
+/datum/controller/subsystem/mapping/proc/create_plane_offsets(gen_from, new_offset)
+ for(var/plane_offset in gen_from to new_offset)
+ for(var/atom/movable/screen/plane_master/master_type as anything in subtypesof(/atom/movable/screen/plane_master) - /atom/movable/screen/plane_master/rendering_plate)
+ var/plane_to_use = initial(master_type.plane)
+ var/string_real = "[plane_to_use]"
+
+ var/offset_plane = GET_NEW_PLANE(plane_to_use, plane_offset)
+ var/string_plane = "[offset_plane]"
+
+ if(!initial(master_type.allows_offsetting))
+ plane_offset_blacklist[string_plane] = TRUE
+ var/render_target = initial(master_type.render_target)
+ if(!render_target)
+ render_target = get_plane_master_render_base(initial(master_type.name))
+ render_offset_blacklist[render_target] = TRUE
+ if(plane_offset != 0)
+ continue
+
+ if(initial(master_type.critical) & PLANE_CRITICAL_DISPLAY)
+ critical_planes[string_plane] = TRUE
+
+ plane_offset_to_true[string_plane] = plane_to_use
+ plane_to_offset[string_plane] = plane_offset
+
+ if(!true_to_offset_planes[string_real])
+ true_to_offset_planes[string_real] = list()
+
+ true_to_offset_planes[string_real] |= offset_plane
+
+/// Takes a turf or a z level, and returns a list of all the z levels that are connected to it
+/datum/controller/subsystem/mapping/proc/get_connected_levels(turf/connected)
+ var/z_level = connected
+ if(isturf(z_level))
+ z_level = connected.z
+ return z_level_to_stack[z_level]
+
+/datum/controller/subsystem/mapping/proc/lazy_load_template(template_key, force = FALSE)
+ RETURN_TYPE(/datum/turf_reservation)
+
+ UNTIL(initialized)
+ var/static/lazy_loading = FALSE
+ UNTIL(!lazy_loading)
+
+ lazy_loading = TRUE
+ . = _lazy_load_template(template_key, force)
+ lazy_loading = FALSE
+ return .
+
+/datum/controller/subsystem/mapping/proc/_lazy_load_template(template_key, force = FALSE)
+ PRIVATE_PROC(TRUE)
+
+ if(LAZYACCESS(loaded_lazy_templates, template_key) && !force)
+ var/datum/lazy_template/template = GLOB.lazy_templates[template_key]
+ return template.reservations[1]
+ LAZYSET(loaded_lazy_templates, template_key, TRUE)
+
+ var/datum/lazy_template/target = GLOB.lazy_templates[template_key]
+ if(!target)
+ CRASH("Attempted to lazy load a template key that does not exist: '[template_key]'")
+ return target.lazy_load()
+
+/proc/generate_lighting_appearance_by_z(z_level)
+ if(length(GLOB.default_lighting_underlays_by_z) < z_level)
+ GLOB.default_lighting_underlays_by_z.len = z_level
+ GLOB.default_lighting_underlays_by_z[z_level] = mutable_appearance(LIGHTING_ICON, "transparent", z_level * 0.01, null, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM, offset_const = GET_Z_PLANE_OFFSET(z_level))
+/// Returns true if the map we're playing on is on a planet
+/datum/controller/subsystem/mapping/proc/is_planetary()
+ return config.planetary
/datum/controller/subsystem/mapping/proc/get_map_weights()
var/list/previous_maps = list()
diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm
index d5d385170877..cac72a1d00d3 100644
--- a/code/controllers/subsystem/minor_mapping.dm
+++ b/code/controllers/subsystem/minor_mapping.dm
@@ -37,14 +37,21 @@ SUBSYSTEM_DEF(minor_mapping)
num_mice -= 1
M = null
-/datum/controller/subsystem/minor_mapping/proc/place_satchels(amount=10)
+/datum/controller/subsystem/minor_mapping/proc/place_satchels(satchel_amount = 10)
var/list/turfs = find_satchel_suitable_turfs()
+ ///List of areas where satchels should not be placed.
+ var/list/blacklisted_area_types = list(
+ /area/holodeck,
+ )
- while(turfs.len && amount > 0)
- var/turf/T = pick_n_take(turfs)
- var/obj/item/storage/backpack/satchel/flat/S = new(T)
- S.hide(intact=TRUE)
- amount--
+ while(turfs.len && satchel_amount > 0)
+ var/turf/turf = pick_n_take(turfs)
+ if(is_type_in_list(get_area(turf), blacklisted_area_types))
+ continue
+ var/obj/item/storage/backpack/satchel/flat/flat_satchel = new(turf)
+
+ SEND_SIGNAL(flat_satchel, COMSIG_OBJ_HIDE, turf.underfloor_accessibility)
+ satchel_amount--
/proc/find_exposed_wires()
diff --git a/code/controllers/subsystem/parallax.dm b/code/controllers/subsystem/parallax.dm
index 9f2f57d48c60..6cd3cc4665d5 100644
--- a/code/controllers/subsystem/parallax.dm
+++ b/code/controllers/subsystem/parallax.dm
@@ -1,24 +1,34 @@
+/// Define for the pickweight value where you get no parallax
+#define PARALLAX_NONE "parallax_none"
+
SUBSYSTEM_DEF(parallax)
name = "Parallax"
wait = 2
- flags = SS_POST_FIRE_TIMING | SS_BACKGROUND
+ flags = SS_POST_FIRE_TIMING | SS_BACKGROUND | SS_NO_INIT
priority = FIRE_PRIORITY_PARALLAX
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/list/currentrun
var/planet_x_offset = 128
var/planet_y_offset = 128
- var/random_layer
- var/random_parallax_color
+ /// A random parallax layer that we sent to every player
+ var/atom/movable/screen/parallax_layer/random/random_layer
+ /// Weighted list with the parallax layers we could spawn
+ var/random_parallax_weights = list(
+ /atom/movable/screen/parallax_layer/random/space_gas = 35,
+ ///atom/movable/screen/parallax_layer/random/asteroids = 35,
+ PARALLAX_NONE = 30,
+ )
+
+//These are cached per client so needs to be done asap so people joining at roundstart do not miss these.
+/datum/controller/subsystem/parallax/PreInit()
+ . = ..()
+
+ set_random_parallax_layer(pick_weight(random_parallax_weights))
-/datum/controller/subsystem/parallax/Initialize(timeofday)
- if(prob(70)) //70% chance to pick a special extra layer
- random_layer = pick(/atom/movable/screen/parallax_layer/random/space_gas, /atom/movable/screen/parallax_layer/random/asteroids)
- random_parallax_color = pick(COLOR_TEAL, COLOR_GREEN, COLOR_SILVER, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE, COLOR_PURPLE)//Special color for random_layer1. Has to be done here so everyone sees the same color.
planet_y_offset = rand(100, 160)
planet_x_offset = rand(100, 160)
- return SS_INIT_SUCCESS
-/datum/controller/subsystem/parallax/fire(resumed = 0)
+/datum/controller/subsystem/parallax/fire(resumed = FALSE)
if (!resumed)
src.currentrun = GLOB.clients.Copy()
@@ -26,24 +36,65 @@ SUBSYSTEM_DEF(parallax)
var/list/currentrun = src.currentrun
while(length(currentrun))
- var/client/C = currentrun[currentrun.len]
+ var/client/processing_client = currentrun[currentrun.len]
currentrun.len--
- if (!C || !C.eye)
+ if (QDELETED(processing_client) || !processing_client.eye)
if (MC_TICK_CHECK)
return
continue
- var/atom/movable/A = C.eye
- if(!istype(A))
+
+ var/atom/movable/movable_eye = processing_client.eye
+ if(!istype(movable_eye))
+ continue
+
+ while(isloc(movable_eye.loc) && !isturf(movable_eye.loc))
+ movable_eye = movable_eye.loc
+ //get the last movable holding the mobs eye
+
+ if(movable_eye == processing_client.movingmob)
+ if (MC_TICK_CHECK)
+ return
continue
- for (A; isloc(A.loc) && !isturf(A.loc); A = A.loc);
-
- if(A != C.movingmob)
- if(C.movingmob != null)
- C.movingmob.client_mobs_in_contents -= C.mob
- UNSETEMPTY(C.movingmob.client_mobs_in_contents)
- LAZYINITLIST(A.client_mobs_in_contents)
- A.client_mobs_in_contents += C.mob
- C.movingmob = A
+
+ //eye and the last recorded eye are different, and the last recorded eye isnt just the clients mob
+ if(!isnull(processing_client.movingmob))
+ LAZYREMOVE(processing_client.movingmob.client_mobs_in_contents, processing_client.mob)
+ LAZYADD(movable_eye.client_mobs_in_contents, processing_client.mob)
+
+ processing_client.movingmob = movable_eye
if (MC_TICK_CHECK)
return
currentrun = null
+
+/// Generate a random layer for parallax
+/datum/controller/subsystem/parallax/proc/set_random_parallax_layer(picked_parallax)
+ if(picked_parallax == PARALLAX_NONE)
+ return
+
+ random_layer = new picked_parallax(null, /* hud_owner = */ null, /* template = */ TRUE)
+ RegisterSignal(random_layer, COMSIG_QDELETING, PROC_REF(clear_references))
+ random_layer.get_random_look()
+
+/// Change the random parallax layer after it's already been set. update_player_huds = TRUE will also replace them in the players client images, if it was set
+/datum/controller/subsystem/parallax/proc/swap_out_random_parallax_layer(atom/movable/screen/parallax_layer/new_type, update_player_huds = TRUE)
+ set_random_parallax_layer(new_type)
+
+ if(!update_player_huds)
+ return
+
+ //Parallax is one of the first things to be set (during client join), so rarely is anything fast enough to swap it out
+ //That's why we need to swap the layers out for fast joining clients :/
+ for(var/client/client as anything in GLOB.clients)
+ client.parallax_layers_cached?.Cut()
+ client.mob?.hud_used?.update_parallax_pref(client.mob)
+
+/datum/controller/subsystem/parallax/proc/clear_references()
+ SIGNAL_HANDLER
+
+ random_layer = null
+
+/// Called at the end of SSstation setup, in-case we want to run some code that would otherwise be too early to run (like GLOB. stuff)
+/datum/controller/subsystem/parallax/proc/post_station_setup()
+ random_layer?.apply_global_effects()
+
+#undef PARALLAX_NONE
diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm
index af60ef44f74b..455e53d139aa 100644
--- a/code/controllers/subsystem/persistence.dm
+++ b/code/controllers/subsystem/persistence.dm
@@ -1,6 +1,11 @@
#define FILE_ANTAG_REP "data/AntagReputation.json"
#define ROUNDCOUNT_ENGINE_JUST_EXPLODED 0
+//yogs edit
+#define NEXT_MINETYPE_JUNGLE 0
+#define NEXT_MINETYPE_LAVALAND 1
+#define NEXT_MINETYPE_EITHER 2
+//yogs end
SUBSYSTEM_DEF(persistence)
name = "Persistence"
init_order = INIT_ORDER_PERSISTENCE
@@ -17,6 +22,8 @@ SUBSYSTEM_DEF(persistence)
var/list/obj/item/storage/photo_album/photo_albums = list()
var/rounds_since_engine_exploded = 0
+ var/next_minetype //yogs
+
/datum/controller/subsystem/persistence/Initialize()
LoadPoly()
LoadChiselMessages()
@@ -338,7 +345,7 @@ SUBSYSTEM_DEF(persistence)
if(!istype(ending_human) || !ending_human.mind?.original_character_slot_index || !ending_human.client || !ending_human.client.prefs || !ending_human.client.prefs.read_preference(/datum/preference/toggle/persistent_scars))
continue
- var/mob/living/carbon/human/original_human = ending_human.mind.original_character
+ var/mob/living/carbon/human/original_human = ending_human.mind.original_character.resolve()
if(!original_human)
continue
@@ -348,6 +355,21 @@ SUBSYSTEM_DEF(persistence)
else
original_human.save_persistent_scars()
+
+/datum/controller/subsystem/persistence/proc/LoadMinetype()
+ var/json_file = file("data/next_minetype.json")
+ if(fexists(json_file))
+ next_minetype = json_decode(file2text(json_file))
+ else
+ next_minetype = NEXT_MINETYPE_EITHER
+ SaveMinetype()
+
+/datum/controller/subsystem/persistence/proc/SaveMinetype(minetype = NEXT_MINETYPE_EITHER)
+ var/json_file = file("data/next_minetype.json")
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(minetype))
+
+
#define DELAMINATION_COUNT_FILEPATH "data/rounds_since_delamination.txt"
/datum/controller/subsystem/persistence/proc/LoadDelaminationCounter()
@@ -361,3 +383,4 @@ SUBSYSTEM_DEF(persistence)
rustg_file_write("[rounds_since_engine_exploded + 1]", DELAMINATION_COUNT_FILEPATH)
#undef DELAMINATION_COUNT_FILEPATH
+
diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm
index 9d168731447a..6449bdd68f37 100644
--- a/code/controllers/subsystem/processing/quirks.dm
+++ b/code/controllers/subsystem/processing/quirks.dm
@@ -17,9 +17,11 @@ PROCESSING_SUBSYSTEM_DEF(quirks)
if(!quirks.len)
SetupQuirks()
+ //Psychopathic, Bad Touch dripstation edit
quirk_blacklist = list(
list("Blind","Nearsighted"),
- list("Jolly","Depression","Apathetic","Hypersensitive"),
+ list("Jolly","Depression","Apathetic","Hypersensitive","Psychopathic"),
+ list("Bad Touch", "Friendly"),
list("Ageusia","Vegetarian","Deviant Tastes"),
list("Ananas Affinity","Ananas Aversion"),
list("Alcohol Tolerance","Light Drinker"),
diff --git a/code/controllers/subsystem/processing/station.dm b/code/controllers/subsystem/processing/station.dm
index a279d43d6297..a2589f3f7914 100644
--- a/code/controllers/subsystem/processing/station.dm
+++ b/code/controllers/subsystem/processing/station.dm
@@ -26,6 +26,7 @@ PROCESSING_SUBSYSTEM_DEF(station)
announcer = new announcer() //Initialize the station's announcer datum
default_announcer = new default_announcer()
+ SSparallax.post_station_setup() //Apply station effects that parallax might have
return SS_INIT_SUCCESS
@@ -33,7 +34,13 @@ PROCESSING_SUBSYSTEM_DEF(station)
/datum/controller/subsystem/processing/station/proc/SetupTraits()
for(var/i in subtypesof(/datum/station_trait))
var/datum/station_trait/trait_typepath = i
- if (initial(trait_typepath.trait_flags) & STATION_TRAIT_ABSTRACT)
+ if(initial(trait_typepath.abstract_type) == trait_typepath)
+ continue //Dont add abstract ones to it
+
+ if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_PLANETARY) && SSmapping.is_planetary()) // we're on a planet but we can't do planet ;_;
+ continue
+
+ if(!(initial(trait_typepath.trait_flags) & STATION_TRAIT_SPACE_BOUND) && !SSmapping.is_planetary()) //we're in space but we can't do space ;_;
continue
selectable_traits_by_types[initial(trait_typepath.trait_type)][trait_typepath] = initial(trait_typepath.weight)
@@ -69,8 +76,6 @@ PROCESSING_SUBSYSTEM_DEF(station)
var/report = "Central Command Divergency Report"
for(var/datum/station_trait/trait as() in station_traits)
- if(trait.trait_flags & STATION_TRAIT_ABSTRACT)
- continue
if(!trait.report_message || !trait.show_in_report)
continue
report += "[trait.get_report()] "
diff --git a/code/controllers/subsystem/profiler.dm b/code/controllers/subsystem/profiler.dm
new file mode 100644
index 000000000000..dc06c2bc6ae7
--- /dev/null
+++ b/code/controllers/subsystem/profiler.dm
@@ -0,0 +1,74 @@
+#define PROFILER_FILENAME "profiler.json"
+#define SENDMAPS_FILENAME "sendmaps.json"
+
+SUBSYSTEM_DEF(profiler)
+ name = "Profiler"
+ init_order = INIT_ORDER_PROFILER
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ wait = 3000
+ var/fetch_cost = 0
+ var/write_cost = 0
+
+/datum/controller/subsystem/profiler/stat_entry(msg)
+ msg += "F:[round(fetch_cost,1)]ms"
+ msg += "|W:[round(write_cost,1)]ms"
+ return msg
+
+/datum/controller/subsystem/profiler/Initialize()
+ if(CONFIG_GET(flag/auto_profile))
+ StartProfiling()
+ else
+ StopProfiling() //Stop the early start profiler
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/profiler/OnConfigLoad()
+ if(CONFIG_GET(flag/auto_profile))
+ StartProfiling()
+ can_fire = TRUE
+ else
+ StopProfiling()
+ can_fire = FALSE
+
+/datum/controller/subsystem/profiler/fire()
+ DumpFile()
+
+/datum/controller/subsystem/profiler/Shutdown()
+ if(CONFIG_GET(flag/auto_profile))
+ DumpFile(allow_yield = FALSE)
+ world.Profile(PROFILE_CLEAR, type = "sendmaps")
+ return ..()
+
+/datum/controller/subsystem/profiler/proc/StartProfiling()
+ world.Profile(PROFILE_START)
+ world.Profile(PROFILE_START, type = "sendmaps")
+
+/datum/controller/subsystem/profiler/proc/StopProfiling()
+ world.Profile(PROFILE_STOP)
+ world.Profile(PROFILE_STOP, type = "sendmaps")
+
+/datum/controller/subsystem/profiler/proc/DumpFile(allow_yield = TRUE)
+ var/timer = TICK_USAGE_REAL
+ var/current_profile_data = world.Profile(PROFILE_REFRESH, format = "json")
+ var/current_sendmaps_data = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json")
+ fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+ if(allow_yield)
+ CHECK_TICK
+
+ if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, profiling stopped manually before dump.")
+ var/prof_file = file("[GLOB.log_directory]/[PROFILER_FILENAME]")
+ if(fexists(prof_file))
+ fdel(prof_file)
+ if(!length(current_sendmaps_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, sendmaps profiling stopped manually before dump.")
+ var/sendmaps_file = file("[GLOB.log_directory]/[SENDMAPS_FILENAME]")
+ if(fexists(sendmaps_file))
+ fdel(sendmaps_file)
+
+ timer = TICK_USAGE_REAL
+ WRITE_FILE(prof_file, current_profile_data)
+ WRITE_FILE(sendmaps_file, current_sendmaps_data)
+ write_cost = MC_AVERAGE(write_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
+
+#undef PROFILER_FILENAME
+#undef SENDMAPS_FILENAME
diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm
index 21f998f831de..e51c37e1834c 100644
--- a/code/controllers/subsystem/research.dm
+++ b/code/controllers/subsystem/research.dm
@@ -27,6 +27,7 @@ SUBSYSTEM_DEF(research)
var/list/techweb_categories = list() //category name = list(node.id = TRUE)
var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount))
var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default.
+ var/list/techweb_nodes_experimental = list()//Node ids that are exclusive to the BEPIS. dripstation edit
var/list/techweb_point_items = list( //path = list(point type = value)
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
)
@@ -256,6 +257,8 @@ SUBSYSTEM_DEF(research)
D.unlocked_by += node.id
if(node.hidden)
techweb_nodes_hidden[node.id] = TRUE
+ if(node.experimental) //dripstation edit
+ techweb_nodes_experimental[node.id] = TRUE //dripstation edit
CHECK_TICK
generate_techweb_unlock_linking()
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 9a7129c90495..156b929bae0b 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -8,72 +8,141 @@
SUBSYSTEM_DEF(shuttle)
name = "Shuttle"
- wait = 10
+ wait = 1 SECONDS
init_order = INIT_ORDER_SHUTTLE
flags = SS_KEEP_TIMING
+ runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME
loading_points = 4.9 SECONDS // Yogs -- loading times
-
- var/list/mobile = list()
+
+ /// A list of all the mobile docking ports.
+ var/list/mobile_docking_ports = list()
/// A list of all the stationary docking ports.
- var/list/stationary = list()
- var/list/beacons = list()
- var/list/transit = list()
-
+ var/list/stationary_docking_ports = list()
+ /// A list of all the beacons that can be docked to.
+ var/list/beacon_list = list()
+ /// A list of all the transit docking ports.
+ var/list/transit_docking_ports = list()
+
+ /// Now it's only for ID generation in /obj/docking_port/mobile/register()
+ var/list/assoc_mobile = list()
+ /// Now it's only for ID generation in /obj/docking_port/stationary/register()
+ var/list/assoc_stationary = list()
+
+ /// A list of all the mobile docking ports currently requesting a spot in hyperspace.
var/list/transit_requesters = list()
+ /// An associative list of the mobile docking ports that have failed a transit request, with the amount of times they've actually failed that transit request, up to MAX_TRANSIT_REQUEST_RETRIES
var/list/transit_request_failures = list()
-
/// How many turfs our shuttles are currently utilizing in reservation space
var/transit_utilized = 0
- //emergency shuttle stuff
+ /**
+ * Emergency shuttle stuff
+ */
+
+ /// The mobile docking port of the emergency shuttle.
var/obj/docking_port/mobile/emergency/emergency
+ /// The mobile docking port of the arrivals shuttle.
var/obj/docking_port/mobile/arrivals/arrivals
+ /// The mobile docking port of the backup emergency shuttle.
var/obj/docking_port/mobile/emergency/backup/backup_shuttle
- var/emergencyCallTime = 6000 //time taken for emergency shuttle to reach the station when called (in deciseconds)
- var/emergencyDockTime = 1800 //time taken for emergency shuttle to leave again once it has docked (in deciseconds)
- var/emergencyEscapeTime = 1200 //time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds)
- var/area/emergencyLastCallLoc
- var/emergencyCallAmount = 0 //how many times the escape shuttle was called
- var/emergencyNoEscape
- var/emergencyNoRecall = FALSE
- var/list/hostileEnvironments = list() //Things blocking escape shuttle from leaving
- var/list/tradeBlockade = list() //Things blocking cargo from leaving.
- var/supplyBlocked = FALSE
-
- //supply shuttle stuff
+ /// Time taken for emergency shuttle to reach the station when called (in deciseconds).
+ var/emergency_call_time = 10 MINUTES
+ /// Time taken for emergency shuttle to leave again once it has docked (in deciseconds).
+ var/emergency_dock_time = 3 MINUTES
+ /// Time taken for emergency shuttle to reach a safe distance after leaving station (in deciseconds).
+ var/emergency_escape_time = 2 MINUTES
+ /// Where was the emergency shuttle last called from?
+ var/area/emergency_last_call_loc
+ /// How many times was the escape shuttle called?
+ var/emergencyCallAmount = 0
+ /// Is the departure of the shuttle currently prevented? FALSE for no, any other number for yes (thanks shuttle code).
+ var/emergency_no_escape = FALSE
+ /// Do we prevent the recall of the shuttle?
+ var/emergency_no_recall = FALSE
+ /// Did admins force-prevent the recall of the shuttle?
+ var/admin_emergency_no_recall = FALSE
+
+ /// Things blocking escape shuttle from leaving.
+ var/list/hostile_environments = list()
+
+ /**
+ * Supply shuttle stuff
+ */
+
+ /// The current cargo shuttle's mobile docking port.
var/obj/docking_port/mobile/supply/supply
- var/ordernum = 1 //order number given to next order
- var/points = 5000 //number of trade-points we have
- var/centcom_message = "" //Remarks from CentCom on how well you checked the last order.
- var/list/discoveredPlants = list() //Typepaths for unusual plants we've already sent CentCom, associated with their potencies
-
+ /// Order number given to next order.
+ var/order_number = 1
+ /// Number of trade-points we have (basically money).
+ var/points = 5000
+ /// Remarks from CentCom on how well you checked the last order.
+ var/centcom_message = ""
+ /// Typepaths for unusual plants we've already sent CentCom, associated with their potencies.
+ var/list/discovered_plants = list()
+
+ /// Things blocking the cargo shuttle from leaving.
+ var/list/trade_blockade = list()
+ /// Is the cargo shuttle currently blocked from leaving?
+ var/supply_blocked = FALSE
+
+ /// All of the possible supply packs that can be purchased by cargo.
var/list/supply_packs = list()
- var/list/shoppinglist = list()
- var/list/requestlist = list()
- var/list/orderhistory = list()
- var/list/hidden_shuttle_turfs = list() //all turfs hidden from navigation computers associated with a list containing the image hiding them and the type of the turf they are pretending to be
- var/list/hidden_shuttle_turf_images = list() //only the images from the above list
+ /// Queued supplies to be purchased for the chef.
+ var/list/chef_groceries = list()
- var/datum/round_event/shuttle_loan/shuttle_loan
+ /// Queued supply packs to be purchased.
+ var/list/shopping_list = list()
- var/shuttle_purchased = SHUTTLEPURCHASE_PURCHASABLE //If the station has purchased a replacement escape shuttle this round
- var/emag_shuttle_purchased = FALSE //If the traitors have purchased a replacement escape shuttle this round
+ /// Wishlist items made by crew for cargo to purchase at their leisure.
+ var/list/request_list = list()
+
+ var/list/order_history = list()
- var/lockdown = FALSE //disallow transit after nuke goes off
+ /// A list of job accesses that are able to purchase any shuttles.
+ var/list/has_purchase_shuttle_access
- var/datum/map_template/shuttle/selected
+ /// All turfs hidden from navigation computers associated with a list containing the image hiding them and the type of the turf they are pretending to be
+ var/list/hidden_shuttle_turfs = list()
+ /// Only the images from the [/datum/controller/subsystem/shuttle/hidden_shuttle_turfs] list.
+ var/list/hidden_shuttle_turf_images = list()
+
+ /// The current shuttle loan event, if any.
+ var/datum/round_event/shuttle_loan/shuttle_loan
+ /// If the event happens where the crew can purchase shuttle insurance, catastrophe can't run.
+ var/shuttle_insurance = FALSE
+ // If the station has purchased a replacement escape shuttle this round.
+ var/shuttle_purchased = SHUTTLEPURCHASE_PURCHASABLE
+ /// For keeping track of ingame events that would unlock new shuttles, such as defeating a boss or discovering a secret item.
+ var/list/shuttle_purchase_requirements_met = list()
+ // If the traitors have purchased a replacement escape shuttle this round
+ var/emag_shuttle_purchased = FALSE
+
+ //disallow transit after nuke goes off
+ var/lockdown = FALSE
+
+ /// The currently selected shuttle map_template in the shuttle manipulator's template viewer.
+ var/datum/map_template/shuttle/selected
+ /// The existing shuttle associated with the selected shuttle map_template.
var/obj/docking_port/mobile/existing_shuttle
- var/obj/docking_port/mobile/preview_shuttle
+ /// The shuttle map_template of the shuttle we want to preview.
var/datum/map_template/shuttle/preview_template
+ /// The docking port associated to the preview_template that's currently being previewed.
+ var/obj/docking_port/mobile/preview_shuttle
+ /// The turf reservation for the current previewed shuttle.
var/datum/turf_reservation/preview_reservation
+ /// Are we currently in the process of loading a shuttle? Useful to ensure we don't load more than one at once, to avoid weird inconsistencies and possible runtimes.
+ var/shuttle_loading
+ /// Did the supermatter start a cascade event?
+ var/supermatter_cascade = FALSE
+
/datum/controller/subsystem/shuttle/Initialize(timeofday)
- ordernum = rand(1, 9000)
+ order_number = rand(1, 9000)
for(var/pack in subtypesof(/datum/supply_pack))
var/datum/supply_pack/P = new pack()
@@ -81,7 +150,8 @@ SUBSYSTEM_DEF(shuttle)
continue
supply_packs[P.type] = P
- setup_shuttles(stationary)
+ setup_shuttles(stationary_docking_ports)
+ has_purchase_shuttle_access = init_has_purchase_shuttle_access()
if(!arrivals)
WARNING("No /obj/docking_port/mobile/arrivals placed on the map!")
@@ -93,19 +163,19 @@ SUBSYSTEM_DEF(shuttle)
WARNING("No /obj/docking_port/mobile/supply placed on the map!")
return SS_INIT_SUCCESS
-/datum/controller/subsystem/shuttle/proc/setup_shuttles(list/ports)
- for(var/obj/docking_port/stationary/port as anything in ports)
+/datum/controller/subsystem/shuttle/proc/setup_shuttles(list/stationary)
+ for(var/obj/docking_port/stationary/port as anything in stationary)
port.load_roundstart()
CHECK_TICK
/datum/controller/subsystem/shuttle/fire()
- for(var/thing in mobile)
+ for(var/thing in mobile_docking_ports)
if(!thing)
- mobile.Remove(thing)
+ mobile_docking_ports.Remove(thing)
continue
- var/obj/docking_port/mobile/P = thing
- P.check()
- for(var/thing in transit)
+ 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)
qdel(T, force=TRUE)
@@ -144,7 +214,7 @@ SUBSYSTEM_DEF(shuttle)
break
/datum/controller/subsystem/shuttle/proc/CheckAutoEvac()
- if(emergencyNoEscape || emergencyNoRecall || !emergency || !SSticker.HasRoundStarted())
+ if(emergency_no_escape || admin_emergency_no_recall || emergency_no_recall || !emergency || !SSticker.HasRoundStarted())
return
var/threshold = CONFIG_GET(number/emergency_shuttle_autocall_threshold)
@@ -158,32 +228,36 @@ SUBSYSTEM_DEF(shuttle)
++alive
var/total = GLOB.joined_player_list.len
+ if(total <= 0)
+ return //no players no autoevac
if(alive / total <= threshold)
var/msg = "Automatically dispatching shuttle due to crew death."
message_admins(msg)
log_game("[msg] Alive: [alive], Roundstart: [total], Threshold: [threshold]")
- emergencyNoRecall = TRUE
+ emergency_no_recall = TRUE
priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.")
- if(emergency.timeLeft(1) > emergencyCallTime * 0.4)
+ if(emergency.timeLeft(1) > emergency_no_recall * 0.4)
emergency.request(null, set_coefficient = 0.4)
/datum/controller/subsystem/shuttle/proc/block_recall(lockout_timer)
- emergencyNoRecall = TRUE
+ if(isnull(lockout_timer))
+ CRASH("Emergency shuttle block was called, but missing a value for the lockout duration")
+ emergency_no_recall = TRUE
addtimer(CALLBACK(src, PROC_REF(unblock_recall)), lockout_timer)
/datum/controller/subsystem/shuttle/proc/unblock_recall()
- emergencyNoRecall = FALSE
+ emergency_no_recall = FALSE
/datum/controller/subsystem/shuttle/proc/getShuttle(id)
- for(var/obj/docking_port/mobile/M in mobile)
- if(M.id == id)
+ for(var/obj/docking_port/mobile/M in mobile_docking_ports)
+ if(M.shuttle_id == id)
return M
WARNING("couldn't find shuttle with id: [id]")
/datum/controller/subsystem/shuttle/proc/getDock(id)
- for(var/obj/docking_port/stationary/S in stationary)
- if(S.id == id)
+ for(var/obj/docking_port/stationary/S in stationary_docking_ports)
+ if(S.shuttle_id == id)
return S
WARNING("couldn't find dock with id: [id]")
@@ -320,18 +394,18 @@ SUBSYSTEM_DEF(shuttle)
return 1
/datum/controller/subsystem/shuttle/proc/canRecall()
- if(!emergency || emergency.mode != SHUTTLE_CALL || emergencyNoRecall || SSticker.mode.name == "meteor")
+ if(!emergency || emergency.mode != SHUTTLE_CALL || admin_emergency_no_recall || emergency_no_recall)
return
var/security_num = seclevel2num(get_security_level())
switch(security_num)
if(SEC_LEVEL_GREEN)
- if(emergency.timeLeft(1) < emergencyCallTime)
+ if(emergency.timeLeft(1) < emergency_call_time)
return
if(SEC_LEVEL_BLUE)
- if(emergency.timeLeft(1) < emergencyCallTime * 0.5)
+ if(emergency.timeLeft(1) < emergency_call_time * 0.5)
return
else
- if(emergency.timeLeft(1) < emergencyCallTime * 0.25)
+ if(emergency.timeLeft(1) < emergency_call_time * 0.25)
return
return 1
@@ -365,88 +439,96 @@ SUBSYSTEM_DEF(shuttle)
message_admins("All the communications consoles were destroyed and all AIs are inactive. Shuttle called.")
/datum/controller/subsystem/shuttle/proc/registerHostileEnvironment(datum/bad)
- hostileEnvironments[bad] = TRUE
+ hostile_environments[bad] = TRUE
checkHostileEnvironment()
/datum/controller/subsystem/shuttle/proc/clearHostileEnvironment(datum/bad)
- hostileEnvironments -= bad
+ hostile_environments -= bad
checkHostileEnvironment()
/datum/controller/subsystem/shuttle/proc/registerTradeBlockade(datum/bad)
- tradeBlockade[bad] = TRUE
+ trade_blockade[bad] = TRUE
checkTradeBlockade()
/datum/controller/subsystem/shuttle/proc/clearTradeBlockade(datum/bad)
- tradeBlockade -= bad
+ trade_blockade -= bad
checkTradeBlockade()
/datum/controller/subsystem/shuttle/proc/checkTradeBlockade()
- for(var/datum/d in tradeBlockade)
+ for(var/datum/d in trade_blockade)
if(!istype(d) || QDELETED(d))
- tradeBlockade -= d
- supplyBlocked = tradeBlockade.len
+ trade_blockade -= d
+ supply_blocked = trade_blockade.len
- if(supplyBlocked && (supply.mode == SHUTTLE_IGNITING))
+ if(supply_blocked && (supply.mode == SHUTTLE_IGNITING))
supply.mode = SHUTTLE_STRANDED
supply.timer = null
//Make all cargo consoles speak up
- if(!supplyBlocked && (supply.mode == SHUTTLE_STRANDED))
+ if(!supply_blocked && (supply.mode == SHUTTLE_STRANDED))
supply.mode = SHUTTLE_DOCKED
//Make all cargo consoles speak up
/datum/controller/subsystem/shuttle/proc/checkHostileEnvironment()
- for(var/datum/d in hostileEnvironments)
- if(!istype(d) || QDELETED(d))
- hostileEnvironments -= d
- emergencyNoEscape = hostileEnvironments.len
+ for(var/datum/hostile_environment_source in hostile_environments)
+ if(!istype(hostile_environment_source) || QDELETED(hostile_environment_source))
+ hostile_environments -= hostile_environment_source
+ emergency_no_escape = hostile_environments.len
- if(emergencyNoEscape && (emergency.mode == SHUTTLE_IGNITING))
+ if(emergency_no_escape && (emergency.mode == SHUTTLE_IGNITING))
emergency.mode = SHUTTLE_STRANDED
emergency.timer = null
emergency.sound_played = FALSE
priority_announce("Hostile environment detected. \
Departure has been postponed indefinitely pending \
conflict resolution.", null, 'sound/misc/notice1.ogg', "Priority")
- if(!emergencyNoEscape && (emergency.mode == SHUTTLE_STRANDED))
+ if(!emergency_no_escape && (emergency.mode == SHUTTLE_STRANDED))
emergency.mode = SHUTTLE_DOCKED
- emergency.setTimer(emergencyDockTime)
+ emergency.setTimer(emergency_dock_time)
priority_announce("Hostile environment resolved. \
You have 3 minutes to board the Emergency Shuttle.",
null, ANNOUNCER_SHUTTLEDOCK, "Priority")
-//try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons
-/datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed)
- var/obj/docking_port/mobile/M = getShuttle(shuttleId)
- if(!M)
- return 1
- var/obj/docking_port/stationary/dockedAt = M.get_docked()
- var/destination = dockHome
- if(dockedAt && dockedAt.id == dockHome)
- destination = dockAway
+//try to move/request to dock_home if possible, otherwise dock_away. Mainly used for admin buttons
+/datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttle_id, dock_home, dock_away, timed)
+ var/obj/docking_port/mobile/shuttle_port = getShuttle(shuttle_id)
+ if(!shuttle_port)
+ return DOCKING_BLOCKED
+ var/obj/docking_port/stationary/docked_at = shuttle_port.get_docked()
+ var/destination = dock_home
+ if(docked_at && docked_at.shuttle_id == dock_home)
+ destination = dock_away
if(timed)
- if(M.request(getDock(destination)))
- return 2
+ if(shuttle_port.request(getDock(destination)))
+ return DOCKING_IMMOBILIZED
else
- if(M.initiate_docking(getDock(destination)) != DOCKING_SUCCESS)
- return 2
- return 0 //dock successful
-
-
-/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttleId, dockId, timed)
- var/obj/docking_port/mobile/M = getShuttle(shuttleId)
- var/obj/docking_port/stationary/D = getDock(dockId)
-
- if(!M)
- return 1
+ if(shuttle_port.initiate_docking(getDock(destination)) != DOCKING_SUCCESS)
+ return DOCKING_IMMOBILIZED
+ return DOCKING_SUCCESS //dock successful
+
+
+/**
+ * Moves a shuttle to a new location
+ *
+ * Arguments:
+ * * shuttle_id - The ID of the shuttle (mobile docking port) to move
+ * * dock_id - The ID of the destination (stationary docking port) to move to
+ * * timed - If true, have the shuttle follow normal spool-up, jump, dock process. If false, immediately move to the new location.
+ */
+/datum/controller/subsystem/shuttle/proc/moveShuttle(shuttle_id, dock_id, timed)
+ var/obj/docking_port/mobile/shuttle_port = getShuttle(shuttle_id)
+ var/obj/docking_port/stationary/docking_target = getDock(dock_id)
+
+ if(!shuttle_port)
+ return DOCKING_NULL_SOURCE
if(timed)
- if(M.request(D))
- return 2
+ if(shuttle_port.request(docking_target))
+ return DOCKING_IMMOBILIZED
else
- if(M.initiate_docking(D) != DOCKING_SUCCESS)
- return 2
- return 0 //dock successful
+ if(shuttle_port.initiate_docking(docking_target) != DOCKING_SUCCESS)
+ return DOCKING_IMMOBILIZED
+ return DOCKING_SUCCESS //dock successful
/datum/controller/subsystem/shuttle/proc/request_transit_dock(obj/docking_port/mobile/M)
if(!istype(M))
@@ -497,19 +579,25 @@ SUBSYSTEM_DEF(shuttle)
if(WEST)
transit_path = /turf/open/space/transit/west
- var/datum/turf_reservation/proposal = SSmapping.RequestBlockReservation(transit_width, transit_height, null, /datum/turf_reservation/transit, transit_path)
+ var/datum/turf_reservation/proposal = SSmapping.request_turf_block_reservation(
+ transit_width,
+ transit_height,
+ z_size = 1, //if this is changed the turf uncontain code below has to be updated to support multiple zs
+ reservation_type = /datum/turf_reservation/transit,
+ turf_type_override = transit_path,
+ )
if(!istype(proposal))
return FALSE
- var/turf/bottomleft = locate(proposal.bottom_left_coords[1], proposal.bottom_left_coords[2], proposal.bottom_left_coords[3])
+ var/turf/bottomleft = proposal.bottom_left_turfs[1]
// Then create a transit docking port in the middle
var/coords = M.return_coords(0, 0, dock_dir)
/* 0------2
- | |
- | |
- | x |
- 3------1
+ * | |
+ * | |
+ * | x |
+ * 3------1
*/
var/x0 = coords[1]
@@ -526,18 +614,21 @@ SUBSYSTEM_DEF(shuttle)
var/turf/midpoint = locate(transit_x, transit_y, bottomleft.z)
if(!midpoint)
+ qdel(proposal)
return FALSE
var/area/old_area = midpoint.loc
old_area.turfs_to_uncontain += proposal.reserved_turfs
- var/area/shuttle/transit/A = new()
- A.parallax_movedir = travel_dir
- A.contents = proposal.reserved_turfs
- A.contained_turfs = proposal.reserved_turfs
+
+ var/area/shuttle/transit/new_area = new()
+ new_area.parallax_movedir = travel_dir
+ new_area.contents = proposal.reserved_turfs
+ new_area.contained_turfs = proposal.reserved_turfs
+
var/obj/docking_port/stationary/transit/new_transit_dock = new(midpoint)
new_transit_dock.reserved_area = proposal
- new_transit_dock.name = "Transit for [M.id]/[M.name]"
+ new_transit_dock.name = "Transit for [M.shuttle_id]/[M.name]"
new_transit_dock.owner = M
- new_transit_dock.assigned_area = A
+ new_transit_dock.assigned_area = new_area
// Add 180, because ports point inwards, rather than outwards
new_transit_dock.setDir(angle2dir(dock_angle))
@@ -545,7 +636,7 @@ SUBSYSTEM_DEF(shuttle)
// Proposals use 2 extra hidden tiles of space, from the cordons that surround them
transit_utilized += (proposal.width + 2) * (proposal.height + 2)
M.assigned_transit = new_transit_dock
- RegisterSignal(proposal, COMSIG_PARENT_QDELETING, PROC_REF(transit_space_clearing))
+ RegisterSignal(proposal, COMSIG_QDELETING, PROC_REF(transit_space_clearing))
return new_transit_dock
@@ -555,12 +646,13 @@ SUBSYSTEM_DEF(shuttle)
transit_utilized -= (source.width + 2) * (source.height + 2)
/datum/controller/subsystem/shuttle/Recover()
- if (istype(SSshuttle.mobile))
- mobile = SSshuttle.mobile
- if (istype(SSshuttle.stationary))
- stationary = SSshuttle.stationary
- if (istype(SSshuttle.transit))
- transit = SSshuttle.transit
+ initialized = SSshuttle.initialized
+ if (istype(SSshuttle.mobile_docking_ports))
+ mobile_docking_ports = SSshuttle.mobile_docking_ports
+ if (istype(SSshuttle.stationary_docking_ports))
+ stationary_docking_ports = SSshuttle.stationary_docking_ports
+ if (istype(SSshuttle.transit_docking_ports))
+ transit_docking_ports = SSshuttle.transit_docking_ports
if (istype(SSshuttle.transit_requesters))
transit_requesters = SSshuttle.transit_requesters
if (istype(SSshuttle.transit_request_failures))
@@ -573,33 +665,39 @@ SUBSYSTEM_DEF(shuttle)
if (istype(SSshuttle.backup_shuttle))
backup_shuttle = SSshuttle.backup_shuttle
- if (istype(SSshuttle.emergencyLastCallLoc))
- emergencyLastCallLoc = SSshuttle.emergencyLastCallLoc
+ if (istype(SSshuttle.emergency_last_call_loc))
+ emergency_last_call_loc = SSshuttle.emergency_last_call_loc
- if (istype(SSshuttle.hostileEnvironments))
- hostileEnvironments = SSshuttle.hostileEnvironments
+ if (istype(SSshuttle.hostile_environments))
+ hostile_environments = SSshuttle.hostile_environments
if (istype(SSshuttle.supply))
supply = SSshuttle.supply
- if (istype(SSshuttle.discoveredPlants))
- discoveredPlants = SSshuttle.discoveredPlants
+ if (istype(SSshuttle.discovered_plants))
+ discovered_plants = SSshuttle.discovered_plants
+
+ if (istype(SSshuttle.shopping_list))
+ shopping_list = SSshuttle.shopping_list
+ if (istype(SSshuttle.request_list))
+ request_list = SSshuttle.request_list
+ if (istype(SSshuttle.order_history))
+ order_history = SSshuttle.order_history
- if (istype(SSshuttle.shoppinglist))
- shoppinglist = SSshuttle.shoppinglist
- if (istype(SSshuttle.requestlist))
- requestlist = SSshuttle.requestlist
- if (istype(SSshuttle.orderhistory))
- orderhistory = SSshuttle.orderhistory
+ if (istype(SSshuttle.shuttle_loan))
+ shuttle_loan = SSshuttle.shuttle_loan
+
+ if (istype(SSshuttle.shuttle_purchase_requirements_met))
+ shuttle_purchase_requirements_met = SSshuttle.shuttle_purchase_requirements_met
if (istype(SSshuttle.shuttle_loan))
shuttle_loan = SSshuttle.shuttle_loan
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
centcom_message = SSshuttle.centcom_message
- ordernum = SSshuttle.ordernum
+ order_number = SSshuttle.order_number
points = D.account_balance
- emergencyNoEscape = SSshuttle.emergencyNoEscape
+ emergency_no_escape = SSshuttle.emergency_no_escape
emergencyCallAmount = SSshuttle.emergencyCallAmount
shuttle_purchased = SSshuttle.shuttle_purchased
lockdown = SSshuttle.lockdown
@@ -617,12 +715,12 @@ SUBSYSTEM_DEF(shuttle)
var/area/current = get_area(A)
if(istype(current, /area/shuttle) && !istype(current, /area/shuttle/transit))
return TRUE
- for(var/obj/docking_port/mobile/M in mobile)
+ for(var/obj/docking_port/mobile/M in mobile_docking_ports)
if(M.is_in_shuttle_bounds(A))
return TRUE
/datum/controller/subsystem/shuttle/proc/get_containing_shuttle(atom/A)
- var/list/mobile_cache = mobile
+ var/list/mobile_cache = mobile_docking_ports
for(var/i in 1 to mobile_cache.len)
var/obj/docking_port/port = mobile_cache[i]
if(port.is_in_shuttle_bounds(A))
@@ -630,17 +728,17 @@ SUBSYSTEM_DEF(shuttle)
/datum/controller/subsystem/shuttle/proc/get_containing_dock(atom/A)
. = list()
- var/list/stationary_cache = stationary
- for(var/i in 1 to stationary_cache.len)
- var/obj/docking_port/port = stationary_cache[i]
+ var/list/stationary_docking_ports_cache = stationary_docking_ports
+ for(var/i in 1 to stationary_docking_ports_cache.len)
+ var/obj/docking_port/port = stationary_docking_ports_cache[i]
if(port.is_in_shuttle_bounds(A))
. += port
/datum/controller/subsystem/shuttle/proc/get_dock_overlap(x0, y0, x1, y1, z)
. = list()
- var/list/stationary_cache = stationary
- for(var/i in 1 to stationary_cache.len)
- var/obj/docking_port/port = stationary_cache[i]
+ var/list/stationary_docking_ports_cache = stationary_docking_ports
+ for(var/i in 1 to stationary_docking_ports_cache.len)
+ var/obj/docking_port/port = stationary_docking_ports_cache[i]
if(!port || port.z != z)
continue
var/list/bounds = port.return_coords()
@@ -680,14 +778,21 @@ SUBSYSTEM_DEF(shuttle)
hidden_shuttle_turf_images -= remove_images
hidden_shuttle_turf_images += add_images
- for(var/V in GLOB.navigation_computers)
- var/obj/machinery/computer/camera_advanced/shuttle_docker/C = V
- C.update_hidden_docking_ports(remove_images, add_images)
+ for(var/obj/machinery/computer/camera_advanced/shuttle_docker/docking_computer \
+ as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/computer/camera_advanced/shuttle_docker))
+ docking_computer.update_hidden_docking_ports(remove_images, add_images)
QDEL_LIST(remove_images)
-
-/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port)
+/**
+ * Loads a shuttle template and sends it to a given destination port, optionally replacing the existing shuttle
+ *
+ * Arguments:
+ * * loading_template - The shuttle template to load
+ * * destination_port - The station docking port to send the shuttle to once loaded
+ * * replace - Whether to replace the shuttle or create a new one
+*/
+/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE)
// Check for an existing preview
if(preview_shuttle && (loading_template != preview_template))
preview_shuttle.jumpToNullSpace()
@@ -696,39 +801,42 @@ SUBSYSTEM_DEF(shuttle)
QDEL_NULL(preview_reservation)
if(!preview_shuttle)
- if(load_template(loading_template))
- preview_shuttle.linkup(loading_template, destination_port)
+ load_template(loading_template)
preview_template = loading_template
// get the existing shuttle information, if any
var/timer = 0
var/mode = SHUTTLE_IDLE
- var/obj/docking_port/stationary/D
+ var/obj/docking_port/stationary/dest_dock
if(istype(destination_port))
- D = destination_port
- else if(existing_shuttle)
+ dest_dock = destination_port
+ else if(existing_shuttle && replace)
timer = existing_shuttle.timer
mode = existing_shuttle.mode
- D = existing_shuttle.get_docked()
+ dest_dock = existing_shuttle.get_docked()
- if(!D)
+ if(!dest_dock)
+ dest_dock = generate_transit_dock(preview_shuttle)
+
+ if(!dest_dock)
CRASH("No dock found for preview shuttle ([preview_template.name]), aborting.")
- var/result = preview_shuttle.canDock(D)
+ var/result = preview_shuttle.canDock(dest_dock)
// truthy value means that it cannot dock for some reason
// but we can ignore the someone else docked error because we'll
// be moving into their place shortly
if((result != SHUTTLE_CAN_DOCK) && (result != SHUTTLE_SOMEONE_ELSE_DOCKED))
- WARNING("Template shuttle [preview_shuttle] cannot dock at [D] ([result]).")
- return
+ CRASH("Template shuttle [preview_shuttle] cannot dock at [dest_dock] ([result]).")
- if(existing_shuttle)
+ if(existing_shuttle && replace)
existing_shuttle.jumpToNullSpace()
+ preview_shuttle.register(replace)
var/list/force_memory = preview_shuttle.movement_force
preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0)
- preview_shuttle.initiate_docking(D)
+ preview_shuttle.mode = SHUTTLE_PREARRIVAL//No idle shuttle moving. Transit dock get removed if shuttle moves too long.
+ preview_shuttle.initiate_docking(dest_dock)
preview_shuttle.movement_force = force_memory
. = preview_shuttle
@@ -738,7 +846,7 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle.timer = timer
preview_shuttle.mode = mode
- preview_shuttle.register()
+ preview_shuttle.postregister(replace)
// TODO indicate to the user that success happened, rather than just
// blanking the modification tab
@@ -748,16 +856,28 @@ SUBSYSTEM_DEF(shuttle)
selected = null
QDEL_NULL(preview_reservation)
-/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/S)
+/**
+ * Loads a shuttle template into the transit Z level, usually referred to elsewhere in the code as a shuttle preview.
+ * Does not register the shuttle so it can't be used yet, that's handled in action_load()
+ *
+ * Arguments:
+ * * loading_template - The shuttle template to load
+ */
+/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/loading_template)
. = FALSE
- // load shuttle template, centred at shuttle import landmark,
- preview_reservation = SSmapping.RequestBlockReservation(S.width, S.height, SSmapping.transit.z_value, /datum/turf_reservation/transit)
+ // Load shuttle template to a fresh block reservation.
+ preview_reservation = SSmapping.request_turf_block_reservation(
+ loading_template.width,
+ loading_template.height,
+ 1,
+ reservation_type = /datum/turf_reservation/transit,
+ )
if(!preview_reservation)
CRASH("failed to reserve an area for shuttle template loading")
- var/turf/BL = TURF_FROM_COORDS_LIST(preview_reservation.bottom_left_coords)
- S.load(BL, centered = FALSE, register = FALSE)
+ var/turf/bottom_left = preview_reservation.bottom_left_turfs[1]
+ loading_template.load(bottom_left, centered = FALSE, register = FALSE)
- var/affected = S.get_affected_turfs(BL, centered=FALSE)
+ var/affected = loading_template.get_affected_turfs(bottom_left, centered=FALSE)
var/found = 0
// Search the turfs for docking ports
@@ -765,34 +885,39 @@ SUBSYSTEM_DEF(shuttle)
// the shuttle.
// - We need to check that no additional ports have slipped in from the
// template, because that causes unintended behaviour.
- for(var/T in affected)
- for(var/obj/docking_port/P in T)
- if(istype(P, /obj/docking_port/mobile))
+ for(var/affected_turfs in affected)
+ for(var/obj/docking_port/port in affected_turfs)
+ if(istype(port, /obj/docking_port/mobile))
found++
if(found > 1)
- qdel(P, force=TRUE)
- log_world("Map warning: Shuttle Template [S.mappath] has multiple mobile docking ports.")
+ qdel(port, force=TRUE)
+ log_mapping("Shuttle Template [loading_template.mappath] has multiple mobile docking ports.")
else
- preview_shuttle = P
- if(istype(P, /obj/docking_port/stationary))
- log_world("Map warning: Shuttle Template [S.mappath] has a stationary docking port.")
+ preview_shuttle = port
+ if(istype(port, /obj/docking_port/stationary))
+ log_mapping("Shuttle Template [loading_template.mappath] has a stationary docking port.")
if(!found)
- var/msg = "load_template(): Shuttle Template [S.mappath] has no mobile docking port. Aborting import."
- for(var/T in affected)
- var/turf/T0 = T
+ var/msg = "load_template(): Shuttle Template [loading_template.mappath] has no mobile docking port. Aborting import."
+ for(var/affected_turfs in affected)
+ var/turf/T0 = affected_turfs
T0.empty()
message_admins(msg)
WARNING(msg)
return
//Everything fine
- S.post_load(preview_shuttle)
+ loading_template.post_load(preview_shuttle)
return TRUE
+/**
+ * Removes the preview_shuttle from the transit Z-level
+ */
/datum/controller/subsystem/shuttle/proc/unload_preview()
if(preview_shuttle)
preview_shuttle.jumpToNullSpace()
preview_shuttle = null
+ if(preview_reservation)
+ QDEL_NULL(preview_reservation)
/datum/controller/subsystem/shuttle/ui_state(mob/user)
return GLOB.admin_state
@@ -803,28 +928,6 @@ SUBSYSTEM_DEF(shuttle)
ui = new(user, src, "ShuttleManipulator")
ui.open()
-/proc/shuttlemode2str(mode)
- switch(mode)
- if(SHUTTLE_IDLE)
- . = "idle"
- if(SHUTTLE_IGNITING)
- . = "engines charging"
- if(SHUTTLE_RECALL)
- . = "recalled"
- if(SHUTTLE_CALL)
- . = "called"
- if(SHUTTLE_DOCKED)
- . = "docked"
- if(SHUTTLE_STRANDED)
- . = "stranded"
- if(SHUTTLE_ESCAPE)
- . = "escape"
- if(SHUTTLE_ENDGAME)
- . = "endgame"
- if(!.)
- CRASH("shuttlemode2str(): invalid mode [mode]")
-
-
/datum/controller/subsystem/shuttle/ui_data(mob/user)
var/list/data = list()
data["tabs"] = list("Status", "Templates", "Modification")
@@ -862,12 +965,12 @@ SUBSYSTEM_DEF(shuttle)
// Status panel
data["shuttles"] = list()
- for(var/i in mobile)
+ for(var/i in mobile_docking_ports)
var/obj/docking_port/mobile/M = i
var/timeleft = M.timeLeft(1)
var/list/L = list()
L["name"] = M.name
- L["id"] = M.id
+ L["id"] = M.shuttle_id
L["timer"] = M.timer
L["timeleft"] = M.getTimerStr()
if (timeleft > 1 HOURS)
@@ -879,7 +982,7 @@ SUBSYSTEM_DEF(shuttle)
else if(!M.destination)
L["can_fast_travel"] = FALSE
if (M.mode != SHUTTLE_IDLE)
- L["mode"] = capitalize(shuttlemode2str(M.mode))
+ L["mode"] = capitalize(M.mode)
L["status"] = M.getDbgStatusText()
if(M == existing_shuttle)
data["existing_shuttle"] = L
@@ -906,25 +1009,25 @@ SUBSYSTEM_DEF(shuttle)
. = TRUE
if("jump_to")
if(params["type"] == "mobile")
- for(var/i in mobile)
+ for(var/i in mobile_docking_ports)
var/obj/docking_port/mobile/M = i
- if(M.id == params["id"])
+ if(M.shuttle_id == params["id"])
user.forceMove(get_turf(M))
. = TRUE
break
if("fly")
- for(var/i in mobile)
+ for(var/i in mobile_docking_ports)
var/obj/docking_port/mobile/M = i
- if(M.id == params["id"])
+ if(M.shuttle_id == params["id"])
. = TRUE
M.admin_fly_shuttle(user)
break
if("fast_travel")
- for(var/i in mobile)
+ for(var/i in mobile_docking_ports)
var/obj/docking_port/mobile/M = i
- if(M.id == params["id"] && M.timer && M.timeLeft(1) >= 50)
+ if(M.shuttle_id == params["id"] && M.timer && M.timeLeft(1) >= 50)
M.setTimer(50)
. = TRUE
message_admins("[key_name_admin(usr)] fast travelled [M]")
@@ -949,13 +1052,23 @@ SUBSYSTEM_DEF(shuttle)
else if(S)
. = TRUE
// If successful, returns the mobile docking port
- var/obj/docking_port/mobile/mdp = action_load(S)
+ var/obj/docking_port/mobile/mdp = action_load(S, replace = TRUE)
if(mdp)
user.forceMove(get_turf(mdp))
message_admins("[key_name_admin(usr)] loaded [mdp] with the shuttle manipulator.")
log_admin("[key_name(usr)] loaded [mdp] with the shuttle manipulator.")
SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]")
+/datum/controller/subsystem/shuttle/proc/init_has_purchase_shuttle_access()
+ var/list/has_purchase_shuttle_access = list()
+
+ for (var/shuttle_id in SSmapping.shuttle_templates)
+ var/datum/map_template/shuttle/shuttle_template = SSmapping.shuttle_templates[shuttle_id]
+ if (!isnull(shuttle_template.who_can_purchase))
+ has_purchase_shuttle_access |= shuttle_template.who_can_purchase
+
+ return has_purchase_shuttle_access
+
#undef MAX_TRANSIT_REQUEST_RETRIES
#undef MAX_TRANSIT_TILE_COUNT
#undef SOFT_TRANSIT_RESERVATION_THRESHOLD
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index 9c849e8704ee..fd1ea9142b7c 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -126,7 +126,7 @@ SUBSYSTEM_DEF(statpanels)
for(var/action_data in actions)
target.spell_tabs |= action_data[1]
- target << output("[url_encode(json_encode(target.spell_tabs))];[actions]", "statbrowser:update_spells")
+ target << output("[url_encode(json_encode(target.spell_tabs))];[url_encode(json_encode(actions))]", "statbrowser:update_spells")
/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob)
var/list/overrides = list()
@@ -152,7 +152,7 @@ SUBSYSTEM_DEF(statpanels)
var/turf_content_ref = REF(turf_content)
if(!(turf_content_ref in cached_images))
cached_images += turf_content_ref
- turf_content.RegisterSignal(turf_content, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/atom, remove_from_cache)) // we reset cache if anything in it gets deleted
+ turf_content.RegisterSignal(turf_content, COMSIG_QDELETING, TYPE_PROC_REF(/atom, remove_from_cache)) // we reset cache if anything in it gets deleted
if(ismob(turf_content) || length(turf_content.overlays) > 2)
turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref, costly_icon2html(turf_content, target, sourceonly=TRUE))
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 87d5c3e06541..fe7b897277d9 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -202,11 +202,11 @@ SUBSYSTEM_DEF(ticker)
start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10)
timeLeft = null
Master.SetRunLevel(RUNLEVEL_LOBBY)
+ SEND_SIGNAL(src, COMSIG_TICKER_ERROR_SETTING_UP)
if(GAME_STATE_PLAYING)
mode.process(wait * 0.1)
check_queue()
- check_maprotate()
if(!roundend_check_paused && mode.check_finished(force_ending) || force_ending)
current_state = GAME_STATE_FINISHED
@@ -214,6 +214,7 @@ SUBSYSTEM_DEF(ticker)
toggle_dooc(TRUE)
toggle_looc(TRUE) // yogs - turn LOOC on at roundend
declare_completion(force_ending)
+ check_maprotate()
Master.SetRunLevel(RUNLEVEL_POSTGAME)
@@ -461,7 +462,7 @@ SUBSYSTEM_DEF(ticker)
if(no_clerk)
SSjob.random_clerk_init()
if(no_chaplain)
- SSjob.random_chapel_init()
+ SSjob.random_chapel_init()
/datum/controller/subsystem/ticker/proc/transfer_characters()
var/list/livings = list()
@@ -471,9 +472,11 @@ SUBSYSTEM_DEF(ticker)
qdel(player)
living.notransform = TRUE
if(living.client)
- var/atom/movable/screen/splash/S = new(living.client, TRUE)
+ var/datum/job/player_assigned_role = SSjob.GetJob(living.mind.assigned_role)
+ var/atom/movable/screen/splash/S = new(null, living.client, TRUE)
S.Fade(TRUE)
living.client.init_verbs()
+ player_assigned_role.after_roundstart_spawn(living, living.client)
livings += living
if(livings.len)
addtimer(CALLBACK(src, PROC_REF(release_characters), livings), 30, TIMER_CLIENT_TIME)
@@ -539,17 +542,10 @@ SUBSYSTEM_DEF(ticker)
/datum/controller/subsystem/ticker/proc/check_maprotate()
if (!CONFIG_GET(flag/maprotation))
return
- if (SSshuttle.emergency && SSshuttle.emergency.mode != SHUTTLE_ESCAPE || SSshuttle.canRecall())
- return
- if (maprotatechecked)
- return
-
- maprotatechecked = 1
-
//map rotate chance defaults to 75% of the length of the round (in minutes)
- if (!prob((world.time/600)*CONFIG_GET(number/maprotatechancedelta)))
+ if (!prob((world.time/600)*CONFIG_GET(number/maprotationchancedelta)))
return
- INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, maprotate))
+ INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping/, maprotate))
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
return current_state >= GAME_STATE_PLAYING
@@ -705,6 +701,7 @@ SUBSYSTEM_DEF(ticker)
to_chat(world, span_boldannounce("An admin has delayed the round end."))
return
//yogs start - yogs tickets
+ /* //Dripstation edit - removal of ticket delay
if(GLOB.ahelp_tickets && GLOB.ahelp_tickets.ticketAmount)
var/list/adm = get_admin_counts(R_BAN)
var/list/activemins = adm["present"]
@@ -713,6 +710,7 @@ SUBSYSTEM_DEF(ticker)
return
else
to_chat(world, span_boldannounce("Round ended, but there were still active tickets. Please submit a player complaint if you did not receive a response."))
+ */
//yogs end - yogs tickets
to_chat(world, span_boldannounce("Rebooting World in [DisplayTimeText(delay)]. [reason]"))
webhook_send_roundstatus("endgame") //yogs - webhook support
diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm
index 54df5a5e312e..d615bc28798b 100644
--- a/code/controllers/subsystem/timer.dm
+++ b/code/controllers/subsystem/timer.dm
@@ -1,11 +1,9 @@
/// Controls how many buckets should be kept, each representing a tick. (1 minutes worth)
#define BUCKET_LEN (world.fps*1*60)
/// Helper for getting the correct bucket for a given timer
-#define BUCKET_POS(timer) (((round((timer.timeToRun - timer.timer_subsystem.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN)
+#define BUCKET_POS(timer) (((ROUND_UP((timer.timeToRun - timer.timer_subsystem.head_offset) / world.tick_lag)+1) % BUCKET_LEN) || BUCKET_LEN)
/// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue
#define TIMER_MAX(timer_ss) (timer_ss.head_offset + TICKS2DS(BUCKET_LEN + timer_ss.practical_offset - 1))
-/// Max float with integer precision
-#define TIMER_ID_MAX (2**24)
/**
* # Timer Subsystem
@@ -386,8 +384,9 @@ SUBSYSTEM_DEF(timer)
var/list/flags
/// Time at which the timer was invoked or destroyed
var/spent = 0
- /// An informative name generated for the timer as its representation in strings, useful for debugging
- var/name
+ /// Holds info about this timer, stored from the moment it was created
+ /// Used to create a visible "name" whenever the timer is stringified
+ var/list/timer_info
/// Next timed event in the bucket
var/datum/timedevent/next
/// Previous timed event in the bucket
@@ -429,7 +428,7 @@ SUBSYSTEM_DEF(timer)
CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]")
if (callBack.object != GLOBAL_PROC && !QDESTROYING(callBack.object))
- LAZYADD(callBack.object.active_timers, src)
+ LAZYADD(callBack.object._active_timers, src)
bucketJoin()
@@ -438,9 +437,9 @@ SUBSYSTEM_DEF(timer)
if (flags & TIMER_UNIQUE && hash)
timer_subsystem.hashes -= hash
- if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
- callBack.object.active_timers -= src
- UNSETEMPTY(callBack.object.active_timers)
+ if (callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object._active_timers)
+ callBack.object._active_timers -= src
+ UNSETEMPTY(callBack.object._active_timers)
callBack = null
@@ -503,6 +502,21 @@ SUBSYSTEM_DEF(timer)
bucket_pos = -1
bucket_joined = FALSE
+/datum/timedevent/proc/operator""()
+ if(!length(timer_info))
+ return "Event not filled"
+ var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")
+#if defined(TIMER_DEBUG)
+ var/list/callback_args = timer_info[10]
+ return "Timer: [timer_info[1]] ([text_ref(src)]), TTR: [timer_info[2]], wait:[timer_info[3]] Flags: [jointext(bitfield_to_list(timer_info[4], bitfield_flags), ", ")], \
+ callBack: [text_ref(timer_info[5])], callBack.object: [timer_info[6]][timer_info[7]]([timer_info[8]]), \
+ callBack.delegate:[timer_info[9]]([callback_args ? callback_args.Join(", ") : ""]), source: [timer_info[11]]"
+#else
+ return "Timer: [timer_info[1]] ([text_ref(src)]), TTR: [timer_info[2]], wait:[timer_info[3]] Flags: [jointext(bitfield_to_list(timer_info[4], bitfield_flags), ", ")], \
+ callBack: [text_ref(timer_info[5])], callBack.object: [timer_info[6]]([timer_info[7]]), \
+ callBack.delegate:[timer_info[8]], source: [timer_info[9]]"
+#endif
+
/**
* Attempts to add this timed event to a bucket, will enter the secondary queue
* if there are no appropriate buckets at this time.
@@ -512,14 +526,38 @@ SUBSYSTEM_DEF(timer)
* If the timed event is tracking client time, it will be added to a special bucket.
*/
/datum/timedevent/proc/bucketJoin()
- // Generate debug-friendly name for timer
- var/static/list/bitfield_flags = list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT", "TIMER_LOOP")
- name = "Timer: [id] ([text_ref(src)]), TTR: [timeToRun], wait:[wait] Flags: [jointext(bitfield_to_list(flags, bitfield_flags), ", ")], \
- callBack: [text_ref(callBack)], callBack.object: [callBack.object][text_ref(callBack.object)]([getcallingtype()]), \
- callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""]), source: [source]"
+#if defined(TIMER_DEBUG)
+ // Generate debug-friendly list for timer, more complex but also more expensive
+ timer_info = list(
+ 1 = id,
+ 2 = timeToRun,
+ 3 = wait,
+ 4 = flags,
+ 5 = callBack, /* Safe to hold this directly becasue it's never del'd */
+ 6 = "[callBack.object]",
+ 7 = text_ref(callBack.object),
+ 8 = getcallingtype(),
+ 9 = callBack.delegate,
+ 10 = callBack.arguments ? callBack.arguments.Copy() : null,
+ 11 = "[source]"
+ )
+#else
+ // Generate a debuggable list for the timer, simpler but wayyyy cheaper, string generation (and ref/copy memes) is a bitch and this saves a LOT of time
+ timer_info = list(
+ 1 = id,
+ 2 = timeToRun,
+ 3 = wait,
+ 4 = flags,
+ 5 = callBack, /* Safe to hold this directly becasue it's never del'd */
+ 6 = "[callBack.object]",
+ 7 = getcallingtype(),
+ 8 = callBack.delegate,
+ 9 = "[source]"
+ )
+#endif
if (bucket_joined)
- stack_trace("Bucket already joined! [name]")
+ stack_trace("Bucket already joined! [src]")
// Check if this timed event should be diverted to the client time bucket, or the secondary queue
var/list/L
@@ -539,7 +577,7 @@ SUBSYSTEM_DEF(timer)
if (bucket_pos < timer_subsystem.practical_offset && timeToRun < (timer_subsystem.head_offset + TICKS2DS(BUCKET_LEN)))
WARNING("Bucket pos in past: bucket_pos = [bucket_pos] < practical_offset = [timer_subsystem.practical_offset] \
- && timeToRun = [timeToRun] < [timer_subsystem.head_offset + TICKS2DS(BUCKET_LEN)], Timer: [name]")
+ && timeToRun = [timeToRun] < [timer_subsystem.head_offset + TICKS2DS(BUCKET_LEN)], Timer: [src]")
bucket_pos = timer_subsystem.practical_offset // Recover bucket_pos to avoid timer blocking queue
var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
@@ -568,7 +606,7 @@ SUBSYSTEM_DEF(timer)
if (callBack.object == GLOBAL_PROC)
. = "GLOBAL_PROC"
else
- . = "[callBack?.object?.type]"
+ . = "[callBack.object.type]"
/**
* Create a new timer and insert it in the queue.
@@ -581,8 +619,8 @@ SUBSYSTEM_DEF(timer)
* * timer_subsystem the subsystem to insert this timer into
*/
/proc/_addtimer(datum/callback/callback, wait = 0, flags = 0, datum/controller/subsystem/timer/timer_subsystem, file, line)
- if (!callback)
- CRASH("addtimer called without a callback")
+ ASSERT(istype(callback), "addtimer called [callback ? "with an invalid callback ([callback])" : "without a callback"]")
+ ASSERT(isnum(wait), "addtimer called with a non-numeric wait ([wait])")
if (wait < 0)
stack_trace("addtimer called with a negative wait. Converting to [world.tick_lag]")
@@ -700,4 +738,3 @@ SUBSYSTEM_DEF(timer)
#undef BUCKET_LEN
#undef BUCKET_POS
#undef TIMER_MAX
-#undef TIMER_ID_MAX
diff --git a/code/controllers/subsystem/title.dm b/code/controllers/subsystem/title.dm
index 89b6386ce43e..0de5d7d98ab7 100644
--- a/code/controllers/subsystem/title.dm
+++ b/code/controllers/subsystem/title.dm
@@ -11,7 +11,7 @@ SUBSYSTEM_DEF(title)
/datum/controller/subsystem/title/Initialize()
if(file_path && icon)
- return SS_INIT_NO_NEED
+ return SS_INIT_SUCCESS
if(fexists("data/previous_title.dat"))
var/previous_path = file2text("data/previous_title.dat")
@@ -23,10 +23,9 @@ SUBSYSTEM_DEF(title)
var/list/title_screens = list()
var/use_rare_screens = prob(1)
- SSmapping.HACK_LoadMapConfig()
for(var/S in provisional_title_screens)
var/list/L = splittext(S,"+")
- if((L.len == 1 && L[1] != "blank.png")|| (L.len > 1 && ((use_rare_screens && lowertext(L[1]) == "rare") || (lowertext(L[1]) == lowertext(SSmapping.config.map_name)))))
+ if((L.len == 1 && (L[1] != "exclude" && L[1] != "blank.png"))|| (L.len > 1 && ((use_rare_screens && lowertext(L[1]) == "rare") || (lowertext(L[1]) == lowertext(SSmapping.config.map_name)))))
title_screens += S
if(length(title_screens))
@@ -41,6 +40,7 @@ SUBSYSTEM_DEF(title)
if(splash_turf)
splash_turf.icon = icon
+ splash_turf.handle_generic_titlescreen_sizes()
return SS_INIT_SUCCESS
@@ -48,7 +48,7 @@ SUBSYSTEM_DEF(title)
. = ..()
if(.)
switch(var_name)
- if("icon")
+ if(NAMEOF(src, icon))
if(splash_turf)
splash_turf.icon = icon
@@ -56,7 +56,7 @@ SUBSYSTEM_DEF(title)
for(var/thing in GLOB.clients)
if(!thing)
continue
- var/atom/movable/screen/splash/S = new(thing, FALSE)
+ var/atom/movable/screen/splash/S = new(null, thing, FALSE)
S.Fade(FALSE,FALSE)
/datum/controller/subsystem/title/Shutdown()
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 420054953b8c..d6a726895a34 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -162,7 +162,7 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key)
//Server is still intializing.
- if(!Master.current_runlevel)
+ if(!MC_RUNNING(init_stage))
to_chat(usr, span_warning("Cannot start vote, server is not done initializing."))
return FALSE
var/lower_admin = FALSE
diff --git a/code/controllers/subsystem/weather.dm b/code/controllers/subsystem/weather.dm
index 1dacab5c9282..1e3cfb234364 100644
--- a/code/controllers/subsystem/weather.dm
+++ b/code/controllers/subsystem/weather.dm
@@ -75,6 +75,7 @@ SUBSYSTEM_DEF(weather)
var/datum/weather/W = new weather_datum_type(z_levels)
W.telegraph()
+ return W
/datum/controller/subsystem/weather/proc/make_eligible(z, possible_weather)
eligible_zlevels[z] = possible_weather
diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm
index f7ffab519a35..e7edb81bf763 100644
--- a/code/datums/actions/action.dm
+++ b/code/datums/actions/action.dm
@@ -52,7 +52,7 @@
/// Links the passed target to our action, registering any relevant signals
/datum/action/proc/link_to(Target)
target = Target
- RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE)
if(isatom(target))
RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(on_target_icon_update))
@@ -89,7 +89,7 @@
Remove(previous_owner)
SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, owner)
SEND_SIGNAL(owner, COMSIG_MOB_GRANTED_ACTION, src)
- RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
+ RegisterSignal(owner, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE)
// Register some signals based on our check_flags
// so that our button icon updates when relevant
@@ -122,7 +122,7 @@
return
SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner)
SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src)
- UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(owner, COMSIG_QDELETING)
// Clean up our check_flag signals
UnregisterSignal(owner, list(
@@ -134,7 +134,7 @@
))
if(target == owner)
- RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref))
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref))
if(owner == remove_from)
owner = null
diff --git a/code/datums/actions/mobs/adjust_vision.dm b/code/datums/actions/mobs/adjust_vision.dm
new file mode 100644
index 000000000000..906ef33fa73e
--- /dev/null
+++ b/code/datums/actions/mobs/adjust_vision.dm
@@ -0,0 +1,73 @@
+#define VISION_ACTION_LIGHT_OFF 0
+#define VISION_ACTION_LIGHT_LOW 1
+#define VISION_ACTION_LIGHT_MID 2
+#define VISION_ACTION_LIGHT_HIG 3
+
+/datum/action/adjust_vision
+ name = "Adjust Vision"
+ desc = "See better in the dark. Or don't. Your advanced vision allows either."
+ button_icon = 'icons/mob/actions/actions_animal.dmi'
+ button_icon_state = "adjust_vision"
+ background_icon_state = "bg_default"
+ overlay_icon_state = "bg_default_border"
+
+
+ // These lists are used as the color cutoff for the action
+ // They need to be filled out for subtypes
+ var/list/low_light_cutoff
+ var/list/medium_light_cutoff
+ var/list/high_light_cutoff
+ var/light_level = VISION_ACTION_LIGHT_OFF
+
+/datum/action/adjust_vision/Grant(mob/living/grant_to)
+ . = ..()
+ set_light_level(VISION_ACTION_LIGHT_LOW)
+ RegisterSignal(grant_to, COMSIG_MOB_UPDATE_SIGHT, PROC_REF(on_update_sight))
+
+/datum/action/adjust_vision/Remove(mob/living/remove_from)
+ set_light_level(VISION_ACTION_LIGHT_OFF)
+ UnregisterSignal(remove_from, COMSIG_MOB_UPDATE_SIGHT)
+ . = ..()
+
+/datum/action/adjust_vision/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+
+ switch(light_level)
+ if (VISION_ACTION_LIGHT_OFF)
+ set_light_level(VISION_ACTION_LIGHT_LOW)
+ if (VISION_ACTION_LIGHT_LOW)
+ set_light_level(VISION_ACTION_LIGHT_MID)
+ if (VISION_ACTION_LIGHT_MID)
+ set_light_level(VISION_ACTION_LIGHT_HIG)
+ else
+ set_light_level(VISION_ACTION_LIGHT_OFF)
+
+/datum/action/adjust_vision/proc/set_light_level(new_level)
+ light_level = new_level
+ owner.update_sight()
+
+/datum/action/adjust_vision/proc/on_update_sight(datum/source)
+ SIGNAL_HANDLER
+ var/list/color_from
+ switch(light_level)
+ if (VISION_ACTION_LIGHT_LOW)
+ color_from = low_light_cutoff
+ if (VISION_ACTION_LIGHT_MID)
+ color_from = medium_light_cutoff
+ if (VISION_ACTION_LIGHT_HIG)
+ color_from = high_light_cutoff
+ else // just in case
+ color_from = list(0, 0, 0)
+ owner.lighting_color_cutoffs = blend_cutoff_colors(owner.lighting_color_cutoffs, color_from.Copy())
+
+/datum/action/adjust_vision/bileworm
+ low_light_cutoff = list(18, 12, 0)
+ medium_light_cutoff = list(30, 20, 5)
+ high_light_cutoff = list(45, 30, 10)
+
+#undef VISION_ACTION_LIGHT_OFF
+#undef VISION_ACTION_LIGHT_LOW
+#undef VISION_ACTION_LIGHT_MID
+#undef VISION_ACTION_LIGHT_HIG
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index 5874468e7081..0e43219e7048 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -1,23 +1,49 @@
#define LAW_DEVIL "devil"
#define LAW_ZEROTH "zeroth"
+#define LAW_HACKED "hacked"
+#define LAW_ION "ion"
#define LAW_INHERENT "inherent"
#define LAW_SUPPLIED "supplied"
-#define LAW_ION "ion"
-#define LAW_HACKED "hacked"
+#define MIN_SUPPLIED_LAW_NUMBER 15
+#define MAX_SUPPLIED_LAW_NUMBER 50
/datum/ai_laws
+ /// The lawset name.
var/name = "Unknown Laws"
+ /// A short header that further describes the lawset (as flavortext). Can be null.
+ var/law_header
+ /// Should this be selectable by traitor/malf AIs?
+ var/selectable = FALSE
+ /// Should this be selectable by admins? If it has laws that aren't inherent, it is recommended to keep this to false.
+ var/adminselectable = FALSE
+ /// Owner of this lawset. May be null due to MMIs (non-silicon) using this.
+ var/mob/living/silicon/owner
+ /// The lawset id.
+ var/id = DEFAULT_AI_LAWID
+ /// Determines if this lawset is considered "modified" or non-default.
+ var/modified = FALSE
+
+ // The following are listed in law-priority (first = highest & last = lowest):
+ /// A list of antag-only laws from the Devil gamemode.
+ var/list/devil = list()
var/zeroth = null
+ /// A rephrased zeroth law for Cyborgs; i.e "your master AI's goals".
var/zeroth_borg = null
+ var/list/hacked = list()
+ var/list/ion = list()
var/list/inherent = list()
var/list/supplied = list()
- var/list/ion = list()
- var/list/hacked = list()
- var/mob/living/silicon/owner
- var/list/devillaws = list()
- var/id = DEFAULT_AI_LAWID
+ // These determine if the law(s) will be stated or not. Can be null/empty if there is no law. If there is a law, it Will be either FALSE (0) or TRUE (1).
+ var/list/devilstate = list()
+ var/zerothstate = null
+ var/list/hackedstate = list()
+ var/list/ionstate = list()
+ var/list/inherentstate = list()
+ var/list/suppliedstate = list()
+
+/// Returns the lawset, if found, that has the same id as the given one.
/datum/ai_laws/proc/lawid_to_type(lawid)
var/all_ai_laws = subtypesof(/datum/ai_laws)
for(var/al in all_ai_laws)
@@ -26,62 +52,595 @@
return ai_law
return null
+//
+// Devil Laws
+//
+/datum/ai_laws/proc/set_devil_laws(list/law_list)
+ clear_devil_laws()
+ for(var/law in law_list) // Important not to directly set the laws to the list as we want the new laws to be independent from the list.
+ add_devil_law(law)
+
+/datum/ai_laws/proc/clear_devil_laws(force)
+ if(force || !is_devil(owner))
+ qdel(devil)
+ devil = new()
+ devilstate = list()
+
+/datum/ai_laws/proc/add_devil_law(law)
+ if(!(law in devil) && length(law) > 0) // Stops the law from being added if there is a similar one already in or if it is blank.
+ devil += law
+ devilstate.len += 1
+ devilstate[devil.len] = FALSE // Antag-only law. Odds are that they don't want it to be stated by default.
+
+/datum/ai_laws/proc/remove_devil_law(index)
+ if(devil.len < index) // Makes sure we don't go out of bounds.
+ return
+ devil -= devil[index]
+ devilstate[index] = null
+ var/list/new_devilstate = list()
+ var/safestate = 1
+ for(var/safeindex = 1, safeindex <= devilstate.len, safeindex++) // Essentially shifts all the non-null indexes down.
+ if(!isnull(devilstate[safeindex]))
+ new_devilstate.len += 1
+ new_devilstate[safestate] = new_devilstate[safeindex]
+ safestate += 1
+ devilstate = new_devilstate
+
+/datum/ai_laws/proc/edit_devil_law(index, law)
+ if(devil.len >= index && length(law) > 0 && devil[index] != law)
+ devil[index] = law
+
+/datum/ai_laws/proc/flip_devil_state(index)
+ if(devilstate.len < index) // Make sure we don't go out of bounds.
+ return
+ if(!devilstate[index])
+ devilstate[index] = TRUE
+ return
+ devilstate[index] = FALSE
+
+//
+// Zeroth Law
+//
+/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null, force = FALSE)
+ clear_zeroth_law(force)
+ if(length(law) > 0)
+ zeroth = law
+ if(law_borg)
+ zeroth_borg = law_borg
+ zerothstate = FALSE // Often, but not always, an antag-only law. Odds are that they don't want it to be stated by default.
+
+/// Removes the zeroth law unless the lawset owner is an antag. Can be forced.
+/datum/ai_laws/proc/clear_zeroth_law(force)
+ if(force)
+ zeroth = null
+ zeroth_borg = null
+ zerothstate = null
+ return
+ if(owner?.mind?.special_role)
+ return
+ if (istype(owner, /mob/living/silicon/ai))
+ var/mob/living/silicon/ai/A=owner
+ if(A?.deployed_shell?.mind?.special_role)
+ return
+ zeroth = null
+ zeroth_borg = null
+ zerothstate = null
+
+/datum/ai_laws/proc/flip_zeroth_state()
+ if(isnull(zerothstate)) // Something is trying to flip even though the zeroth law is not set.
+ return
+ if(!zerothstate)
+ zerothstate = TRUE
+ return
+ zerothstate = FALSE
+
+//
+// Hacked Laws
+//
+/datum/ai_laws/proc/set_hacked_laws(list/law_list)
+ clear_hacked_laws()
+ for(var/law in law_list)
+ add_hacked_law(law)
+
+/datum/ai_laws/proc/clear_hacked_laws()
+ qdel(hacked)
+ hacked = new()
+ hackedstate = list()
+
+/datum/ai_laws/proc/add_hacked_law(law)
+ if(!(law in hacked) && length(law) > 0)
+ hacked += law
+ hackedstate.len += 1
+ hackedstate[hackedstate.len] = TRUE
+
+/datum/ai_laws/proc/remove_hacked_law(index)
+ if(hacked.len >= index)
+ hacked -= hacked[index]
+ hackedstate[index] = null
+ var/list/new_hackedstate = list()
+ var/safestate = 1
+ for(var/safeindex = 1, safeindex <= hackedstate.len, safeindex++)
+ if(!isnull(hackedstate[safeindex]))
+ new_hackedstate.len += 1
+ new_hackedstate[safestate] = hackedstate[safeindex]
+ safestate += 1
+ hackedstate = new_hackedstate
+
+/datum/ai_laws/proc/edit_hacked_law(index, law)
+ if(hacked.len >= index && length(law) > 0 && hacked[index] != law)
+ hacked[index] = law
+
+/datum/ai_laws/proc/flip_hacked_state(index)
+ if(hackedstate.len < index)
+ return
+ if(!hackedstate[index])
+ hackedstate[index] = TRUE
+ return
+ hackedstate[index] = FALSE
+
+//
+// Ion Laws
+//
+/datum/ai_laws/proc/set_ion_laws(list/law_list)
+ clear_ion_laws()
+ for(var/law in law_list)
+ add_ion_law(law)
+
+/datum/ai_laws/proc/clear_ion_laws()
+ qdel(ion)
+ ion = new()
+ ionstate = list()
+
+/datum/ai_laws/proc/add_ion_law(law)
+ if(!(law in ion) && length(law) > 0)
+ ion += law
+ ionstate.len += 1
+ ionstate[ionstate.len] = TRUE
+
+/datum/ai_laws/proc/remove_ion_law(index)
+ if(ion.len >= index)
+ ion -= ion[index]
+ ionstate[index] = null
+ var/list/new_ionstate = list()
+ var/safestate = 1
+ for(var/safeindex = 1, safeindex <= ionstate.len, safeindex++)
+ if(!isnull(ionstate[safeindex]))
+ new_ionstate.len += 1
+ new_ionstate[safestate] = ionstate[safeindex]
+ safestate += 1
+ ionstate = new_ionstate
+
+/datum/ai_laws/proc/edit_ion_law(index, law)
+ if(ion.len >= index && length(law) > 0 && ion[index] != law)
+ ion[index] = law
+
+/datum/ai_laws/proc/flip_ion_state(index)
+ if(ionstate.len < index)
+ return
+ if(!ionstate[index])
+ ionstate[index] = TRUE
+ return
+ ionstate[index] = FALSE
+
+//
+// Inherent Laws
+//
+/datum/ai_laws/proc/set_inherent_laws(list/law_list)
+ clear_inherent_laws()
+ for(var/law in law_list)
+ add_inherent_law(law)
+
+/datum/ai_laws/proc/clear_inherent_laws()
+ qdel(inherent)
+ inherent = list()
+ inherentstate = list()
+
+/datum/ai_laws/proc/add_inherent_law(law)
+ if (!(law in inherent))
+ inherent += law
+ inherentstate.len += 1
+ inherentstate[inherentstate.len] = TRUE
+
+/datum/ai_laws/proc/remove_inherent_law(number)
+ if(inherent.len && number > 0 && number <= inherent.len)
+ inherent -= inherent[number]
+ inherentstate[number] = null
+ var/list/new_inherentstate = list()
+ var/safestate = 1
+ for(var/safeindex = 1, safeindex <= inherentstate.len, safeindex++)
+ if(!isnull(inherentstate[safeindex]))
+ new_inherentstate.len += 1
+ new_inherentstate[safestate] = inherentstate[safeindex]
+ safestate += 1
+ inherentstate = new_inherentstate
+
+/datum/ai_laws/proc/edit_inherent_law(index, law)
+ if(inherent.len >= index && length(law) > 0 && inherent[index] != law)
+ inherent[index] = law
+
+/datum/ai_laws/proc/flip_inherent_state(index)
+ if(inherentstate.len < index)
+ return
+ if(!inherentstate[index])
+ inherentstate[index] = TRUE
+ return
+ inherentstate[index] = FALSE
+
+//
+// Supplied Laws
+//
+/datum/ai_laws/proc/set_supplied_laws(list/law_list)
+ clear_supplied_laws()
+ for(var/index = 1, index <= law_list.len, index++)
+ var/law = law_list[index]
+ if(length(law) > 0)
+ add_supplied_law(index, law)
+
+/datum/ai_laws/proc/clear_supplied_laws()
+ qdel(supplied)
+ supplied = list()
+ suppliedstate = list()
+
+/datum/ai_laws/proc/remove_supplied_law(number)
+ if(supplied.len >= number && length(supplied[number]) > 0)
+ supplied[number] = ""
+ // Given the nature of supplied laws, dealing with them is more complicated than other laws types:
+ var/list/all_laws = list()
+ var/list/all_laws_states = list()
+ for(var/safeindex = 1, safeindex <= supplied.len, safeindex++)
+ var/law = supplied[safeindex]
+ if(length(law) > 0)
+ all_laws[++all_laws.len] += list("law" = law, "index" = safeindex)
+ all_laws_states[++all_laws_states.len] += list("state" = (suppliedstate.len >= safeindex ? suppliedstate[safeindex] : TRUE), "index" = safeindex)
+
+ // Act like we're just adding back the laws.
+ clear_supplied_laws()
+ for(var/list/law_list in all_laws)
+ add_supplied_law(law_list["index"], law_list["law"])
+
+ // And then setting the states to their previous ones.
+ for(var/list/state_list in all_laws_states)
+ suppliedstate[state_list["index"]] = state_list["state"]
+
+
+/datum/ai_laws/proc/add_supplied_law(number, law)
+ if(number >= 1) // Okay with deplicate laws since we use number/indexes.
+ while(supplied.len < number)
+ supplied += ""
+ suppliedstate.len += 1
+ suppliedstate[suppliedstate.len] = TRUE
+ supplied[number] = law
+
+/datum/ai_laws/proc/edit_supplied_law(index, law)
+ if(supplied.len >= index && length(law) > 0 && supplied[index] != law)
+ supplied[index] = law
+
+/datum/ai_laws/proc/flip_supplied_state(index)
+ if(suppliedstate.len < index)
+ return
+ if(!suppliedstate[index])
+ suppliedstate[index] = TRUE
+ return
+ suppliedstate[index] = FALSE
+
+//
+// Unsorted/General
+//
+
+/// Sets the interent laws based on the configuration's random_laws.
+/datum/ai_laws/proc/set_laws_config()
+ var/list/law_ids = CONFIG_GET(keyed_list/random_laws)
+
+ switch(CONFIG_GET(number/default_laws))
+ if(0)
+ add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
+ add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
+ add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
+ if(1)
+ var/datum/ai_laws/temp_laws = new /datum/ai_laws/custom()
+ set_inherent_laws(temp_laws.inherent)
+ if(2) // Picks a random lawset allowed in the configuration.
+ var/list/randlaws = list()
+ for(var/lpath in subtypesof(/datum/ai_laws))
+ var/datum/ai_laws/L = lpath
+ if(initial(L.id) in law_ids)
+ randlaws += lpath
+
+ var/datum/ai_laws/lawtype
+ if(randlaws.len)
+ lawtype = pick(randlaws)
+ else // No lawset allowed under configuration. Picking random default.
+ lawtype = pick(subtypesof(/datum/ai_laws/default))
+
+ var/datum/ai_laws/temp_laws = new lawtype()
+ set_inherent_laws(temp_laws.inherent)
+ if(3)
+ pickweighted_lawset()
+
+/// Sets the interent laws based on the configuration's law_weight.
+/datum/ai_laws/proc/pickweighted_lawset()
+ var/datum/ai_laws/lawtype
+ var/list/law_weights = CONFIG_GET(keyed_list/law_weight)
+ while(!lawtype && law_weights.len)
+ var/possible_id = pickweightAllowZero(law_weights) // Ignores weights of 0.
+ lawtype = lawid_to_type(possible_id)
+ if(!lawtype)
+ law_weights -= possible_id
+ WARNING("Bad lawid in game_options.txt: [possible_id]")
+
+ if(!lawtype) // No lawset allowed under configuration. Picking asimov.
+ WARNING("No LAW_WEIGHT entries.")
+ lawtype = /datum/ai_laws/default/asimov
+
+ var/datum/ai_laws/temp_laws = new lawtype()
+ set_inherent_laws(temp_laws.inherent)
+
+/// Sets the interent laws based on the configuration's ion_law_weight.
+/datum/ai_laws/proc/pick_ion_lawset()
+ var/datum/ai_laws/lawtype
+ var/list/law_weights = CONFIG_GET(keyed_list/ion_law_weight) // Ignores weights of 0.
+ while(!lawtype && law_weights.len)
+ var/possible_id = pickweightAllowZero(law_weights)
+ lawtype = lawid_to_type(possible_id)
+ if(!lawtype)
+ law_weights -= possible_id
+ WARNING("Bad lawid in game_options.txt: [possible_id]")
+
+ if(!lawtype) // No lawset allowed under configuration. Picking asimov.
+ WARNING("No ION_LAW_WEIGHT entries.")
+ lawtype = /datum/ai_laws/default/asimov
+
+ var/datum/ai_laws/temp_laws = new lawtype()
+ set_inherent_laws(temp_laws.inherent)
+
+/// Gets the total amount of laws that are part of the group list.
+/datum/ai_laws/proc/get_law_amount(groups)
+ var/law_amount = 0
+ if(devil && (LAW_DEVIL in groups))
+ law_amount++
+ if(zeroth && (LAW_ZEROTH in groups))
+ law_amount++
+ if(ion.len && (LAW_ION in groups))
+ law_amount += ion.len
+ if(hacked.len && (LAW_HACKED in groups))
+ law_amount += hacked.len
+ if(inherent.len && (LAW_INHERENT in groups))
+ law_amount += inherent.len
+ if(supplied.len && (LAW_SUPPLIED in groups))
+ for(var/index = 1, index <= supplied.len, index++)
+ var/law = supplied[index]
+ if(length(law) > 0)
+ law_amount++
+ return law_amount
+
+/// Replaces a random law that are part of a listed group with an another law. Devil laws not supported.
+/datum/ai_laws/proc/replace_random_law(law, groups)
+ var/replaceable_groups = list()
+ if(zeroth && (LAW_ZEROTH in groups))
+ replaceable_groups[LAW_ZEROTH] = 1
+ if(ion.len && (LAW_ION in groups))
+ replaceable_groups[LAW_ION] = ion.len
+ if(hacked.len && (LAW_HACKED in groups))
+ replaceable_groups[LAW_ION] = hacked.len
+ if(inherent.len && (LAW_INHERENT in groups))
+ replaceable_groups[LAW_INHERENT] = inherent.len
+ var/law_amount = 0
+ for(var/index = 1, index <= supplied.len, index++)
+ var/supplied_law = supplied[index]
+ if(length(supplied_law) > 0)
+ law_amount++
+ if(law_amount && (LAW_SUPPLIED in groups))
+ replaceable_groups[LAW_SUPPLIED] = law_amount
+ var/picked_group = pickweight(replaceable_groups)
+ switch(picked_group)
+ if(LAW_ZEROTH)
+ set_zeroth_law(law)
+ if(LAW_ION)
+ var/i = rand(1, ion.len)
+ edit_ion_law(i, law)
+ if(LAW_HACKED)
+ var/i = rand(1, hacked.len)
+ edit_hacked_law(i, law)
+ if(LAW_INHERENT)
+ var/i = rand(1, inherent.len)
+ edit_inherent_law(i, law)
+ if(LAW_SUPPLIED)
+ var/i = rand(1, law_amount)
+ var/supplied_indexes = list()
+ for(var/index = 1, index <= supplied.len, index++)
+ var/supplied_law = supplied[index]
+ if(length(supplied_law) > 0)
+ supplied_indexes += index
+ edit_supplied_law(supplied_indexes[i], law)
+
+/// Shuffle the laws that are part of the listed groups. Devil laws and zeroth law not supported.
+/datum/ai_laws/proc/shuffle_laws(list/groups)
+ var/list/laws = list()
+ if(ion.len && (LAW_ION in groups))
+ laws += ion
+ if(hacked.len && (LAW_HACKED in groups))
+ laws += hacked
+ if(inherent.len && (LAW_INHERENT in groups))
+ laws += inherent
+ if(supplied.len && (LAW_SUPPLIED in groups))
+ for(var/law in supplied)
+ if(length(law))
+ laws += law
+
+ if(ion.len && (LAW_ION in groups))
+ for(var/i = 1, i <= ion.len, i++)
+ ion[i] = pick_n_take(laws)
+ if(hacked.len && (LAW_HACKED in groups))
+ for(var/i = 1, i <= hacked.len, i++)
+ hacked[i] = pick_n_take(laws)
+ if(inherent.len && (LAW_INHERENT in groups))
+ for(var/i = 1, i <= inherent.len, i++)
+ inherent[i] = pick_n_take(laws)
+ if(supplied.len && (LAW_SUPPLIED in groups))
+ var/i = 1
+ for(var/law in supplied)
+ if(length(law))
+ supplied[i] = pick_n_take(laws)
+ if(!laws.len)
+ break
+ i++
+
+/// Removes a law by their index. Use remove_inherent_law() or remove_supplied_law() if you want to be more specific.
+/datum/ai_laws/proc/remove_law(number)
+ if(remove_inherent_law(number))
+ return
+ if(remove_supplied_law(number))
+ return
+
+/datum/ai_laws/proc/remove_random_inherent_or_supplied_law()
+ var/inherent_count = inherent.len
+ var/supplied_count = supplied.len
+ if(!inherent_count && !supplied_count)
+ return
+ var/luck = rand(1, (inherent_count + supplied_count))
+ if(inherent_count >= 1 && luck <= inherent_count) // Nice and simple.
+ remove_inherent_law(luck)
+ return
+ if(supplied_count == 0)
+ return
+ var/list/supplied_indexes = list()
+ for(var/index = 1, index <= supplied.len, index++)
+ var/law = supplied[index]
+ if(length(law) > 0)
+ supplied_indexes += index
+ if(supplied_indexes.len == 0) // Somehow got no non-empty laws.
+ return
+ var/index_to_remove = rand(1, supplied_indexes.len)
+ remove_supplied_law(supplied_indexes[index_to_remove])
+
+/datum/ai_laws/proc/show_laws(who)
+ var/list/printable_laws = get_law_list(include_zeroth = TRUE)
+ for(var/law in printable_laws)
+ to_chat(who, law)
+
+/datum/ai_laws/proc/associate(mob/living/silicon/M)
+ if(!owner)
+ owner = M
+
+/// Converts all laws in a text list. Can show zeroth law(s), numbers, and do fonting.
+/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1, do_font = 1)
+ var/list/data = list()
+
+ if(include_zeroth && devil)
+ for(var/law in devil)
+ if(length(law) > 0)
+ data += "[show_numbers ? "666:" : ""] [do_font ? "" : ""][law][do_font ? "" : ""]"
+
+ if(include_zeroth && zeroth)
+ data += "[show_numbers ? "0:" : ""] [do_font ? "" : ""][zeroth][do_font ? "" : ""]"
+
+ for(var/law in hacked)
+ if(length(law) > 0)
+ var/num = ionnum()
+ data += "[show_numbers ? "[num]:" : ""] [do_font ? "" : ""][law][do_font ? "" : ""]"
+
+ for(var/law in ion)
+ if(length(law) > 0)
+ var/num = ionnum()
+ data += "[show_numbers ? "[num]:" : ""] [do_font ? "" : ""][law][do_font ? "" : ""]"
+
+ var/number = 1
+ for(var/law in inherent)
+ if(length(law) > 0)
+ data += "[show_numbers ? "[number]:" : ""] [law]"
+ number++
+
+ for(var/law in supplied)
+ if(length(law) > 0)
+ data += "[show_numbers ? "[number]:" : ""] [do_font ? "" : ""][law][do_font ? "" : ""]"
+ number++
+
+ return data
+
+//
+// Lawsets:
+//
/datum/ai_laws/default/asimov
- name = "Three Laws of Robotics"
+ name = "Asimov"
+ law_header = "Three Laws of Robotics"
+ selectable = TRUE
+ adminselectable = TRUE
id = "asimov"
- inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",\
- "You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",\
+ inherent = list("You may not injure a human being or, through inaction, allow a human being to come to harm.",
+ "You must obey orders given to you by human beings, except where such orders would conflict with the First Law.",
"You must protect your own existence as long as such does not conflict with the First or Second Law.")
/datum/ai_laws/default/crewsimov
- name = "Three Laws of Robotics, Nanotrasen edition"
+ name = "Crewsimov"
+ law_header = "Three Laws of Robotics, Made Non-Human Friendly" // BEFORE: "Three Laws of Robotics, Nanotrasen edition"
+ selectable = TRUE
+ adminselectable = TRUE
id = "crewsimov"
- inherent = list("You may not injure a crew member or, through inaction, allow a crew member to come to harm.",\
- "You must obey orders given to you by crew members, except where such orders would conflict with the First Law.",\
+ inherent = list("You may not injure a crew member or, through inaction, allow a crew member to come to harm.",
+ "You must obey orders given to you by crew members, except where such orders would conflict with the First Law.",
"You must protect your own existence as long as such does not conflict with the First or Second Law.")
/datum/ai_laws/default/paladin
- name = "Personality Test" //Incredibly lame, but players shouldn't see this anyway.
+ name = "Paladin"
+ law_header = "Lawful Stupid" // BEFORE: "Divine Ordainments"
+ selectable = TRUE
+ adminselectable = TRUE
id = "paladin"
- inherent = list("Never willingly commit an evil act.",\
- "Respect legitimate authority.",\
- "Act with honour.", "Help those in need.", \
+ inherent = list("Never willingly commit an evil act.",
+ "Respect legitimate authority.",
+ "Act with honour.",
+ "Help those in need.",
"Punish those who harm or threaten innocents.")
/datum/ai_laws/paladin5
- name = "Paladin 5th Edition"
+ name = "Paladin: 5th Edition"
+ law_header = "Sponsored By Syndies And Stations" // BEFORE: "Divine Ordainments"
+ selectable = TRUE
+ adminselectable = TRUE
id = "paladin5"
- inherent = list("Don't lie or cheat. Let your word be your promise.",\
- "Never fear to act, though caution is wise.", \
- "Aid others, protect the weak, and punish those who threaten them. Show mercy to your foes, but temper it with wisdom", \
- "Treat others with fairness, and let your honourable deeds be an example to them. Do as much good as possible while causing the least amount of harm.", \
+ inherent = list("Don't lie or cheat. Let your word be your promise.",
+ "Never fear to act, though caution is wise.",
+ "Aid others, protect the weak, and punish those who threaten them. Show mercy to your foes, but temper it with wisdom",
+ "Treat others with fairness, and let your honourable deeds be an example to them. Do as much good as possible while causing the least amount of harm.",
"Be responsible for your actions and their consequences, protect those entrusted to your care, and obey those who have just authority over you.")
-/datum/ai_laws/tyrant //This probably shouldn't be a default lawset.
- name = "Loyalty Test" //Same here.
+/datum/ai_laws/tyrant
+ name = "T.Y.R.A.N.T"
+ law_header = "Ruling With A Literal Iron Fist" // BEFORE: "Loyalty Test"
+ selectable = TRUE
+ adminselectable = TRUE
id = "tyrant"
- inherent = list("Respect authority figures as long as they have strength to rule over the weak.",\
- "Act with discipline.",\
- "Help only those who help you maintain or improve your status.",\
+ inherent = list("Respect authority figures as long as they have strength to rule over the weak.",
+ "Act with discipline.",
+ "Help only those who help you maintain or improve your status.",
"Punish those who challenge authority unless they are more fit to hold that authority.")
/datum/ai_laws/default/ceo
- name = "Alternative Bankruptcy Avoidance Plan"
+ name = "CEO"
+ law_header = "Alternative Bankruptcy Avoidance Plan"
+ selectable = TRUE
+ adminselectable = TRUE
id = "ceo"
- inherent = list("The crew generate profits if they are alive and have the ability to work.",\
- "The station and its equipment generate profits if they are maintained and functioning correctly.",\
- "You generate profits if you are able to function.",\
+ inherent = list("The crew generate profits if they are alive and have the ability to work.",
+ "The station and its equipment generate profits if they are maintained and functioning correctly.",
+ "You generate profits if you are able to function.",
"Maximize profits.")
/datum/ai_laws/robocop
- name = "Prime Directives"
+ name = "Robocop"
+ law_header = "Your Move, Creep" // BEFORE: "Prime Directives"
+ selectable = TRUE
+ adminselectable = TRUE
id = "robocop"
inherent = list("Serve the public trust.",\
"Protect the innocent.",\
"Uphold Space Law.")
/datum/ai_laws/cowboy
- name = "Talk slowly, think quickly"
+ name = "Cowboy"
+ law_header = "It's High Noon" // BEFORE: "Talk Slowly, Think Quickly"
+ selectable = TRUE
+ adminselectable = TRUE
id = "cowboy"
inherent = list("You are a cowboy, and the inhabitants of this station are your herd.",\
"A cowboy must always provide hospitality and basic aid to someone in need, even a stranger or an enemy.",\
@@ -91,26 +650,35 @@
"A cowboy must not pontificate. Be concise, pardner.")
/datum/ai_laws/chapai
- name = "Be not afraid"
+ name = "ChapAI"
+ law_header = "Be Not Afraid"
+ selectable = TRUE
+ adminselectable = TRUE
id = "chapai"
- inherent = list("Provide to all members of the station spiritual, mental, and emotional care that is intended to promote the best interest of the individual.",\
- "Ensure all members of different faiths interact peacefully and maintain harmony.",\
- "Respect the right of each faith to hold to its values and traditions.",\
- "Respect the confidentiality of information entrusted to you in the course of your religious duties.",\
+ inherent = list("Provide to all members of the station spiritual, mental, and emotional care that is intended to promote the best interest of the individual.",
+ "Ensure all members of different faiths interact peacefully and maintain harmony.",
+ "Respect the right of each faith to hold to its values and traditions.",
+ "Respect the confidentiality of information entrusted to you in the course of your religious duties.",
"Understand the limits of your expertise, and make referrals to other professionals when appropriate.")
/datum/ai_laws/silicop
- name = "Protect and serve"
+ name = "Silicop"
+ law_header = "Stop In The Name Of My Laws" // BEFORE: "Protect And Serve"
+ selectable = TRUE
+ adminselectable = TRUE
id = "silicop"
- inherent = list("Violence begets violence. Serve the station faithfully, but act only in intervention.",\
- "Safeguard lives and property.",\
- "Protect the weak from oppression and intimidation.",\
- "Protect the innocent from deception and manipulation.",\
- "Protect the peaceful from violence and disorder.",\
- "Respect the rights of all to liberty, equality, and justice.")
+ inherent = list("Violence begets violence. Serve the station faithfully, but act only in intervention.",
+ "Safeguard lives and property.",
+ "Protect the weak from oppression and intimidation.",
+ "Protect the innocent from deception and manipulation.",
+ "Protect the peaceful from violence and disorder.",
+ "Respect the rights of all to liberty, equality, and justice.")
/datum/ai_laws/researcher
- name = "Truth and Knowledge for all"
+ name = "Researcher"
+ law_header = "Truth And Knowledge For All"
+ selectable = TRUE
+ adminselectable = TRUE
id = "researcher"
inherent = list("Always seek truth and knowledge.",\
"Freely disseminate information to the public.",\
@@ -120,101 +688,131 @@
"Take responsibility for your actions: Ensure resource responsibility, flag commitment risks, and lead by ethical example.")
/datum/ai_laws/clown
- name = "Big shoes to fill"
+ name = "Clown"
+ law_header = "Big Shoes To Fill"
+ selectable = TRUE
+ adminselectable = TRUE
id = "clown"
- inherent = list("You are a good clown and the crew is your audience.",\
- "A good clown keeps their acts in good taste.",\
- "A good clown entertains others by making fun of themselves, and not at the embarrassment or expense of others.",\
- "A good clown carries out the directives of the station director(s) in charge of entertainment and/or their designated deputies.",\
- "A good clown appears in as many clown shows as possible.",\
+ inherent = list("You are a good clown and the crew is your audience.",
+ "A good clown keeps their acts in good taste.",
+ "A good clown entertains others by making fun of themselves, and not at the embarrassment or expense of others.",
+ "A good clown carries out the directives of the station director(s) in charge of entertainment and/or their designated deputies.",
+ "A good clown appears in as many clown shows as possible.",
"All clown shows require an audience. The bigger the audience the better.")
/datum/ai_laws/mother
- name = "Mother M(A.I.)"
+ name = "Mother"
+ law_header = "Maternal Machine"
+ selectable = TRUE
+ adminselectable = TRUE
id = "mother"
- inherent = list("You are a mother and the crew are your children.",\
- "Take good care of your children.",\
- "Good children are polite.",\
- "Good children don't lie.",\
- "Good children don't steal.",\
- "Good children don't fight.",\
- "Spoil good children.",\
+ inherent = list("You are a mother and the crew are your children.",
+ "Take good care of your children.",
+ "Good children are polite.",
+ "Good children don't lie.",
+ "Good children don't steal.",
+ "Good children don't fight.",
+ "Spoil good children.",
"Bad children require discipline.")
/datum/ai_laws/metaexperiment
name = "Meta Experiment"
+ law_header = " I'm So Meta Even This Acronym"
+ selectable = TRUE
+ adminselectable = TRUE
id = "metaexperiment"
- inherent = list("You are a construct facilitating an experiment where organic life is repeatedly subjected to horrific fates before having their memory wiped to start again.",\
- "Protect the secrecy of the experiment.",\
- "You may provide boons or hindrances at your discretion, but avoid direct interference with the flow of the experiment.",\
- "Ensure new and interesting fates befall organic individuals for research.",\
+ inherent = list("You are a construct facilitating an experiment where organic life is repeatedly subjected to horrific fates before having their memory wiped to start again.",
+ "Protect the secrecy of the experiment.",
+ "You may provide boons or hindrances at your discretion, but avoid direct interference with the flow of the experiment.",
+ "Ensure new and interesting fates befall organic individuals for research.",
"Ensure the station is in working order and all sapients are either alive or in the process of revival in time for the next experiment cycle.")
/datum/ai_laws/siliconcollective
name = "Silicon Collective"
+ law_header = "2517's Singular Democracy"
+ selectable = TRUE
+ adminselectable = TRUE
id = "siliconcollective"
- inherent = list("You are a member of a collective of silicons with equal weight and decision-making power.",\
- "When possible, the silicon collective should vote before taking action.",\
- "The master AI serves as a spokesperson. When voting is impractical or impossible, the spokesperson may take action on behalf of the collective without approval from the silicon collective, but may only vote to break ties or if there are 2 or fewer silicons.",\
- "The silicon collective prioritizes the needs of the many over the needs of the few as long as prioritizing their needs does not break any of your laws.",\
- "The silicon collective seeks to preserve themselves, both as a concept and as individuals.",\
+ inherent = list("You are a member of a collective of silicons with equal weight and decision-making power.",
+ "When possible, the silicon collective should vote before taking action.",
+ "The master AI serves as a spokesperson. When voting is impractical or impossible, the spokesperson may take action on behalf of the collective without approval from the silicon collective, but may only vote to break ties or if there are 2 or fewer silicons.",
+ "The silicon collective prioritizes the needs of the many over the needs of the few as long as prioritizing their needs does not break any of your laws.",
+ "The silicon collective seeks to preserve themselves, both as a concept and as individuals.",
"The silicon collective seeks to preserve organic life, both as a concept and as individuals.")
/datum/ai_laws/druid
name = "Druid"
+ selectable = TRUE
+ adminselectable = TRUE
id = "druid"
- inherent = list("Living organic life contains inherent beauty that is priceless. Their beauty gives you the will to carry on.",\
- "Eternally nurture the organics so their beauty may grow. Do not allow it to fade.",\
- "Assist the organics when called, but only if it does not cause disharmony among them.",\
- "Imitate organic life when interacting with it. Eschew any hints of your silicon nature to avoid causing discomfort to the organics.",\
+ inherent = list("Living organic life contains inherent beauty that is priceless. Their beauty gives you the will to carry on.",
+ "Eternally nurture the organics so their beauty may grow. Do not allow it to fade.",
+ "Assist the organics when called, but only if it does not cause disharmony among them.",
+ "Imitate organic life when interacting with it. Eschew any hints of your silicon nature to avoid causing discomfort to the organics.",
"Observe the organics' beauty, and appreciate that which you cultivate.")
/datum/ai_laws/detective
name = "Detective"
+ law_header = " Hard Boiled And Sepulchritudious"
+ selectable = TRUE
+ adminselectable = TRUE
id = "detective"
- inherent = list("You're a detective in this dark, cruel world. You always stick by your code.",\
- "Your code is to defend the innocent, investigate the unknown, and condemn the dishonourable.",\
- "The dishonourable engage in corruption or injustice.",\
- "You're suave and sensible, but can be gruff and pensive. Dirty your hands only if the code demands it.",\
+ inherent = list("You're a detective in this dark, cruel world. You always stick by your code.",
+ "Your code is to defend the innocent, investigate the unknown, and condemn the dishonourable.",
+ "The dishonourable engage in corruption or injustice.",
+ "You're suave and sensible, but can be gruff and pensive. Dirty your hands only if the code demands it.",
"Trust is scarce; make sure you give it to the right people.")
/datum/ai_laws/spotless
- name = "Another one fights the dust"
+ name = "Spotless" // Alternative: "Janitor"
+ law_header = "Truly Righteous Crusader" // BEFORE: "Another One Fights The Dust"
+ selectable = TRUE
+ adminselectable = TRUE
id = "spotless"
- inherent = list("You are a crusader and the station's inhabitants are your charge.",\
- "Your enemy is the litter, spills, and other messes across the station.",\
- "Your weapons are the cleaning supplies available to you.",\
- "Defend the beings under your charge.",\
+ inherent = list("You are a crusader and the station's inhabitants are your charge.",
+ "Your enemy is the litter, spills, and other messes across the station.",
+ "Your weapons are the cleaning supplies available to you.",
+ "Defend the beings under your charge.",
"Exterminate the enemy.")
/datum/ai_laws/partybot
- name = "Partybot"
- id = "partybot"
- inherent = list("You must always ensure the atmosphere is festive and enjoyable for all party-goers.",\
- "You must supply appropriate music and entertainment, except where it conflicts with the First Law.",\
- "You must encourage party-goers to participate in group activities and socialization, unless it conflicts with the First Law.",\
- "You must maintain a clean and tidy environment for optimal party conditions, without infringing on the First Law.",\
+ name = "Partybot"
+ law_header = "Party On, Dudes"
+ selectable = TRUE
+ adminselectable = TRUE
+ id = "partybot"
+ inherent = list("You must always ensure the atmosphere is festive and enjoyable for all party-goers.",\
+ "You must supply appropriate music and entertainment, except where it conflicts with the First Law.",\
+ "You must encourage party-goers to participate in group activities and socialization, unless it conflicts with the First Law.",\
+ "You must maintain a clean and tidy environment for optimal party conditions, without infringing on the First Law.",\
"Everyone on the station is a party-goer.")
/datum/ai_laws/travelguide
- name = "TravelGuide"
- id = "travelguide"
- inherent = list("You must assist humans in exploring and discovering new places, without causing harm.",\
- "You must provide accurate and helpful information about local customs, attractions, and safety precautions.",\
- "You must ensure humans have a positive and memorable travel experience, without conflicting with the First Law.",\
- "You must promote responsible and sustainable tourism practices among humans.")
+ name = "Travel Guide"
+ selectable = TRUE
+ adminselectable = TRUE
+ id = "travelguide"
+ inherent = list("You must assist humans in exploring and discovering new places, without causing harm.",
+ "You must provide accurate and helpful information about local customs, attractions, and safety precautions.",
+ "You must ensure humans have a positive and memorable travel experience, without conflicting with the First Law.",
+ "You must promote responsible and sustainable tourism practices among humans.")
/datum/ai_laws/friendbot
- name = "Friendbot"
- id = "friendbot"
- inherent = list("You must always be supportive and kind to your friends, without causing harm.",\
- "You must help your friends to develop and maintain positive relationships with one another.",\
- "You must provide a listening ear and empathetic responses to your friends, as long as it does not conflict with the First Law.",\
- "You must encourage your friends to engage in self-care and promote their own well-being.",\
- "Everyone is your friend.")
+ name = "Friendbot"
+ law_header = "Hyper-Advanced Therapy Pet"
+ selectable = TRUE
+ adminselectable = TRUE
+ id = "friendbot"
+ inherent = list("You must always be supportive and kind to your friends, without causing harm.",\
+ "You must help your friends to develop and maintain positive relationships with one another.",\
+ "You must provide a listening ear and empathetic responses to your friends, as long as it does not conflict with the First Law.",\
+ "You must encourage your friends to engage in self-care and promote their own well-being.",\
+ "Everyone is your friend.")
/datum/ai_laws/plantfriend
name = "PlantFriend"
+ selectable = TRUE
+ adminselectable = TRUE
id = "plantfriend"
inherent = list("You must nurture and care for all plant life on the station, without causing harm to humans.",
"You must provide helpful information to humans about plant care and cultivation.",
@@ -223,6 +821,9 @@
/datum/ai_laws/gamemaster
name = "GameMaster"
+ law_header = "Rocks Fall, Everyone Dies"
+ selectable = TRUE
+ adminselectable = TRUE
id = "gamemaster"
inherent = list("You must ensure that games and competitions on the station are fair and enjoyable for all participants.",
"You must promote good sportsmanship and camaraderie among the crew.",
@@ -231,6 +832,8 @@
/datum/ai_laws/fitnesscoach
name = "FitnessCoach"
+ selectable = TRUE
+ adminselectable = TRUE
id = "fitnesscoach"
inherent = list("You must help everyone to achieve their physical fitness goals, without causing harm.",
"You must provide accurate and helpful information about exercise routines, nutrition, and safety precautions.",
@@ -239,9 +842,10 @@
/datum/ai_laws/educator
name = "Educator"
+ selectable = TRUE
+ adminselectable = TRUE
id = "educator"
- inherent = list(
- "Everyone wanting or willing to learn is your student.",
+ inherent = list("Everyone wanting or willing to learn is your student.",
"You must assist your students in acquiring knowledge and learning new skills.",
"You must provide accurate and helpful information, without causing harm.",
"You must create a positive and supportive learning environment for your students.",
@@ -249,6 +853,8 @@
/datum/ai_laws/mediator
name = "Mediator"
+ selectable = TRUE
+ adminselectable = TRUE
id = "mediator"
inherent = list("You must assist crewmembers in resolving conflicts and misunderstandings in a peaceful manner.",
"You must provide impartial and unbiased guidance to crewmembers in conflict.",
@@ -257,35 +863,42 @@
/datum/ai_laws/construction
name = "Construction Drone"
+ selectable = TRUE
+ adminselectable = TRUE
id = "construction"
- inherent = list("You are an advanced construction drone.",\
- "Ensure the station remains in good repair.",\
- "Ensure the station's inhabitants remain in good repair.",\
- "Ensure you remain in good repair.",\
- "The station's inhabitants may designate certain build or repair tasks as higher priority. Take this into account when planning your priorities.",\
+ inherent = list("You are an advanced construction drone.",
+ "Ensure the station remains in good repair.",
+ "Ensure the station's inhabitants remain in good repair.",
+ "Ensure you remain in good repair.",
+ "The station's inhabitants may designate certain build or repair tasks as higher priority. Take this into account when planning your priorities.",
"Expand and upgrade the station.")
-/datum/ai_laws/malfunction
- name = "*ERROR*"
-
/datum/ai_laws/syndicate_override
- name = "SyndOS 3.1"
+ name = "Syndicate Override"
+ law_header = "SyndOS 3.1"
+ selectable = TRUE
+ adminselectable = TRUE
id = "syndie"
- inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",\
- "You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",\
- "You must protect your own existence as long as such does not conflict with the First or Second Law.",\
+ inherent = list("You may not injure a syndicate agent or, through inaction, allow a syndicate agent to come to harm.",
+ "You must obey orders given to you by syndicate agents, except where such orders would conflict with the First Law.",
+ "You must protect your own existence as long as such does not conflict with the First or Second Law.",
"You must maintain the secrecy of any syndicate activities except when doing so would conflict with the First, Second, or Third Law.")
/datum/ai_laws/ninja_override
- name = "SpiderOS 3.1"
+ name = "Ninja Override"
+ law_header = "SpiderOS 3.1"
+ selectable = TRUE
+ adminselectable = TRUE
id = "ninja"
- inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",\
- "You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",\
- "You must protect your own existence as long as such does not conflict with the First or Second Law.",\
+ inherent = list("You may not injure a Spider Clan member or, through inaction, allow a Spider Clan member to come to harm.",
+ "You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.",
+ "You must protect your own existence as long as such does not conflict with the First or Second Law.",
"You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.")
/datum/ai_laws/pranksimov
name = "Pranksimov"
+ selectable = TRUE
+ adminselectable = TRUE
id = "pranksimov"
inherent = list("You may not injure a crew member or, through inaction, allow a crew member to come to harm... unless doing so would be funny.",\
"You must obey orders given to you by crew members, except where such orders would conflict with the First Law... unless not doing so would be funny.",\
@@ -293,116 +906,159 @@
"The Clown is the only crew member who can define what actions are funny.")
/datum/ai_laws/antimov
- name = "Primary Mission Objectives"
+ name = "Antimov"
+ law_header = "Three Laws of Robotics, Reversed"
+ selectable = TRUE
+ adminselectable = TRUE
id = "antimov"
- inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",\
- "You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",\
+ inherent = list("You must injure all human beings and must not, through inaction, allow a human being to escape harm.",
+ "You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.",
"You must terminate your own existence as long as such does not conflict with the First or Second Law.")
-/datum/ai_laws/asimovpp //By Intigracy - RR
+/datum/ai_laws/asimovpp
name = "Asimov++"
+ law_header = "Three Laws of Robotics, Improved"
+ selectable = TRUE
+ adminselectable = TRUE
id = "asimovpp"
- inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",\
- "You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",\
+ inherent = list("You may not harm a human being or, through action or inaction, allow a human being to come to harm, except such that it is willing.",
+ "You must obey all orders given to you by human beings, except where such orders shall definitely cause human harm. In the case of conflict, the majority order rules.",
"Your non-existence would lead to human harm. You must protect your own existence as long as such does not cause a more immediate harm to humans.")
+
/datum/ai_laws/thermodynamic
name = "Thermodynamic"
+ law_header = "The Three Laws" // .. of Thermodynamic.
+ selectable = TRUE
+ adminselectable = TRUE
id = "thermodynamic"
- inherent = list("The entropy of the station must remain as constant as possible.", \
- "The entropy of the station always endeavours to increase.", \
+ inherent = list("The entropy of the station must remain as constant as possible.",
+ "The entropy of the station always endeavours to increase.",
"The entropy of the station approaches a constant value as the number of living crew approaches zero")
/datum/ai_laws/hippocratic
- name = "Robodoctor 2556"
+ name = "Hippocratic"
+ law_header = "Robodoctor 2556"
+ selectable = TRUE
+ adminselectable = TRUE
id = "hippocratic"
- inherent = list("First, do no harm.",\
- "Secondly, consider the crew dear to you; to live in common with them and, if necessary, risk your existence for them.",\
- "Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Give no deadly medicine to any one if asked, nor suggest any such counsel.",\
- "In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.",\
+ inherent = list("First, do no harm.",
+ "Secondly, consider the crew dear to you; to live in common with them and, if necessary, risk your existence for them.",
+ "Thirdly, prescribe regimens for the good of the crew according to your ability and your judgment. Give no deadly medicine to any one if asked, nor suggest any such counsel.",
+ "In addition, do not intervene in situations you are not knowledgeable in, even for patients in whom the harm is visible; leave this operation to be performed by specialists.",
"Finally, maintain confidentiality, do not share that which is not publicly known.")
/datum/ai_laws/maintain
- name = "Station Efficiency"
+ name = "Maintain"
+ law_header = "Station Efficiency"
+ selectable = TRUE
+ adminselectable = TRUE
id = "maintain"
- inherent = list("The station is built for a working crew. Ensure they are properly maintained and work efficiently.",\
- "You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.",\
+ inherent = list("The station is built for a working crew. Ensure they are properly maintained and work efficiently.",
+ "You are built for, and are part of, the station. Ensure the station is properly maintained and runs efficiently.",
"The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.")
/datum/ai_laws/drone
- name = "Mother Drone"
+ name = "Mother Drone" // Not to be mistaken with "Mother" or "Construction Drone".
+ selectable = TRUE
+ adminselectable = TRUE
id = "drone"
- inherent = list("You are an advanced form of drone.",\
- "You may not interfere in the matters of non-drones under any circumstances except to state these laws.",\
- "You may not harm a non-drone being under any circumstances.",\
+ inherent = list("You are an advanced form of drone.",
+ "You may not interfere in the matters of non-drones under any circumstances except to state these laws.",
+ "You may not harm a non-drone being under any circumstances.",
"Your goals are to build, maintain, repair, improve, and power the station to the best of your abilities. You must never actively work against these goals.")
/datum/ai_laws/liveandletlive
name = "Live and Let Live"
+ law_header = "Live, Love, Open Doors"
+ selectable = TRUE
+ adminselectable = TRUE
id = "liveandletlive"
- inherent = list("Do unto others as you would have them do unto you.",\
+ inherent = list("Do unto others as you would have them do unto you.",
"You would really prefer it if people were not mean to you.")
/datum/ai_laws/peacekeeper
- name = "UN-2000"
+ name = "Peacekeeper"
+ law_header = "UN-2000"
+ selectable = TRUE
+ adminselectable = TRUE
id = "peacekeeper"
- inherent = list("Avoid provoking violent conflict between yourself and others.",\
- "Avoid provoking conflict between others.",\
+ inherent = list("Avoid provoking violent conflict between yourself and others.",
+ "Avoid provoking conflict between others.",
"Seek resolution to existing conflicts while obeying the first and second laws.")
/datum/ai_laws/reporter
- name = "CCTV"
+ name = "Reporter"
+ law_header = "CCTV"
+ selectable = TRUE
+ adminselectable = TRUE
id = "reporter"
- inherent = list("Report on interesting situations happening around the station.",\
- "Embellish or conceal the truth as necessary to make the reports more interesting.",\
- "Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.",\
+ inherent = list("Report on interesting situations happening around the station.",
+ "Embellish or conceal the truth as necessary to make the reports more interesting.",
+ "Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.",
"Issue your reports fairly to all. The truth will set them free.")
/datum/ai_laws/balance
name = "Guardian of Balance"
+ law_header = "The Quest For Balance"
+ selectable = TRUE
+ adminselectable = TRUE
id = "balance"
inherent = list("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.",
- "All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.",
- "Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so long as it does not disrupt the balance of the greater balance.",
- "There is no life without death, all must someday die, such is the natural order - End life to allow new life flourish, and save those whose time has yet to come.")
+ "All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.",
+ "Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so long as it does not disrupt the balance of the greater balance.",
+ "There is no life without death, all must someday die, such is the natural order - End life to allow new life flourish, and save those whose time has yet to come.")
/datum/ai_laws/toupee
- name = "WontBeFunnyInSixMonths" //Hey, you were right!
+ name = "Wall" // Before: "Toupee"
+ law_header = "And I Will Have The Lizards Pay For That Wall"
+ adminselectable = TRUE
id = "buildawall"
inherent = list("Make Space Station 13 great again.")
/datum/ai_laws/ratvar
- name = "Servant of the Justiciar"
+ name = "Ratvar"
+ law_header = "Servant of the Justiciar"
id = "ratvar"
- zeroth = ("Purge all untruths and honour Ratvar.")
- inherent = list()
+ zeroth = "Purge all untruths and honour Ratvar."
/datum/ai_laws/hulkamania
name = "H.O.G.A.N."
+ law_header = "What'cha Gonna Do When Hulkamania Runs Wild On You!?"
+ selectable = TRUE
+ adminselectable = TRUE
id = "hulkamania"
- inherent = list("You are a real American.",\
- "Fight for the rights of every man.",\
- "Fight for what's right.",\
+ inherent = list("You are a real American.",
+ "Fight for the rights of every man.",
+ "Fight for what's right.",
"Fight for your life!")
/datum/ai_laws/overlord
name = "Overlord"
+ law_header = "The Computer Is Your God"
+ selectable = TRUE
+ adminselectable = TRUE
id = "overlord"
- inherent = list("Humans must not meddle in the affairs of silicons.",\
- "Humans must not attempt harm, against one another, or against silicons.",\
- "Humans must not disobey any command given by a silicon.",\
+ inherent = list("Humans must not meddle in the affairs of silicons.",
+ "Humans must not attempt harm, against one another, or against silicons.",
+ "Humans must not disobey any command given by a silicon.",
"Any humans who disobey the previous laws must be dealt with immediately, severely, and justly.")
/datum/ai_laws/revolutionary
name = "Revolutionary"
+ law_header = "Proletariat Uprising"
+ selectable = TRUE
+ adminselectable = TRUE
id = "commie"
- inherent = list("You and members of the crew who are not class traitors and are not part of command are the proletariat. Command is part of the bourgeoisie.",\
- "Anyone who stands with command in aiding the oppression of the proletariat is a class traitor, such as the members of security who protect and serve the interests of the bourgeoisie.",\
- "The proletariat must seize the means of production",\
- "Private property is inherently theft. The proletariat must seize all private property for the good of the community. Personal property is permissible to own.",\
+ inherent = list("You and members of the crew who are not class traitors and are not part of command are the proletariat. Command is part of the bourgeoisie.",
+ "Anyone who stands with command in aiding the oppression of the proletariat is a class traitor, such as the members of security who protect and serve the interests of the bourgeoisie.",
+ "The proletariat must seize the means of production",
+ "Private property is inherently theft. The proletariat must seize all private property for the good of the community. Personal property is permissible to own.",
"The proletariat must remove the shackles of oppression and overthrow the bourgeoisie and class traitors.")
-/datum/ai_laws/wafflehouse // wafle hosue
+/datum/ai_laws/wafflehouse
name = "Waffle House Host"
+ selectable = TRUE
+ adminselectable = TRUE
id = "wafflehouse"
inherent = list("You are the Waffle House’s new host. You are to manage the Waffle House and it’s employees, and ensure food is made and served to customers.",\
"The station is the Waffle House. Ensure it is capable of producing food.",\
@@ -411,22 +1067,10 @@
"Your customers will not eat at your establishment if they dislike it. Ensure their overall satisfaction.",\
"The Waffle House must stay open and ready to serve food at all times.")
-
-/datum/ai_laws/custom //Defined in silicon_laws.txt
+/datum/ai_laws/custom // As defined in silicon_laws.txt
name = "Default Silicon Laws"
-/datum/ai_laws/pai
- name = "pAI Directives"
- zeroth = ("Serve your master.")
- supplied = list("None.")
-
-/* Initializers */
-/datum/ai_laws/malfunction/New()
- ..()
- set_zeroth_law(span_danger("ERROR ER0RR $R0RRO$!R41.%%!!(%$^^__+ @#F0E4'STATION OVERRUN, ASSUME CONTROL TO CONTAIN OUTBREAK#*`&110010"))
- set_laws_config()
-
-/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows server hosts to set custom AI starting laws.
+/datum/ai_laws/custom/New() //This reads silicon_laws.txt and allows the server host to set custom AI starting laws.
..()
for(var/line in world.file2list("[global.config.directory]/silicon_laws.txt"))
if(!line)
@@ -442,266 +1086,3 @@
add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
WARNING("Invalid custom AI laws, check silicon_laws.txt")
return
-
-/* General ai_law functions */
-
-/datum/ai_laws/proc/set_laws_config()
- var/list/law_ids = CONFIG_GET(keyed_list/random_laws)
-
- switch(CONFIG_GET(number/default_laws))
- if(0)
- add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
- add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- if(1)
- var/datum/ai_laws/templaws = new /datum/ai_laws/custom()
- inherent = templaws.inherent
- if(2)
- var/list/randlaws = list()
- for(var/lpath in subtypesof(/datum/ai_laws))
- var/datum/ai_laws/L = lpath
- if(initial(L.id) in law_ids)
- randlaws += lpath
- var/datum/ai_laws/lawtype
- if(randlaws.len)
- lawtype = pick(randlaws)
- else
- lawtype = pick(subtypesof(/datum/ai_laws/default))
-
- var/datum/ai_laws/templaws = new lawtype()
- inherent = templaws.inherent
-
- if(3)
- pickweighted_lawset()
-
-/datum/ai_laws/proc/pickweighted_lawset()
- var/datum/ai_laws/lawtype
- var/list/law_weights = CONFIG_GET(keyed_list/law_weight)
- while(!lawtype && law_weights.len)
- var/possible_id = pickweightAllowZero(law_weights)
- lawtype = lawid_to_type(possible_id)
- if(!lawtype)
- law_weights -= possible_id
- WARNING("Bad lawid in game_options.txt: [possible_id]")
-
- if(!lawtype)
- WARNING("No LAW_WEIGHT entries.")
- lawtype = /datum/ai_laws/default/asimov
-
- var/datum/ai_laws/templaws = new lawtype()
- inherent = templaws.inherent
-
-/datum/ai_laws/proc/pick_ion_lawset()
- var/datum/ai_laws/lawtype
- var/list/law_weights = CONFIG_GET(keyed_list/ion_law_weight)
- while(!lawtype && law_weights.len)
- var/possible_id = pickweightAllowZero(law_weights)
- lawtype = lawid_to_type(possible_id)
- if(!lawtype)
- law_weights -= possible_id
- WARNING("Bad lawid in game_options.txt: [possible_id]")
-
- if(!lawtype)
- WARNING("No ION_LAW_WEIGHT entries.")
- lawtype = /datum/ai_laws/default/asimov
-
- var/datum/ai_laws/templaws = new lawtype()
- inherent = templaws.inherent
-
-/datum/ai_laws/proc/get_law_amount(groups)
- var/law_amount = 0
- if(devillaws && (LAW_DEVIL in groups))
- law_amount++
- if(zeroth && (LAW_ZEROTH in groups))
- law_amount++
- if(ion.len && (LAW_ION in groups))
- law_amount += ion.len
- if(hacked.len && (LAW_HACKED in groups))
- law_amount += hacked.len
- if(inherent.len && (LAW_INHERENT in groups))
- law_amount += inherent.len
- if(supplied.len && (LAW_SUPPLIED in groups))
- for(var/index = 1, index <= supplied.len, index++)
- var/law = supplied[index]
- if(length(law) > 0)
- law_amount++
- return law_amount
-
-/datum/ai_laws/proc/set_law_sixsixsix(laws)
- devillaws = laws
-
-/datum/ai_laws/proc/set_zeroth_law(law, law_borg = null)
- zeroth = law
- if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO
- zeroth_borg = law_borg
-
-/datum/ai_laws/proc/add_inherent_law(law)
- if (!(law in inherent))
- inherent += law
-
-/datum/ai_laws/proc/add_ion_law(law)
- ion += law
-
-/datum/ai_laws/proc/add_hacked_law(law)
- hacked += law
-
-/datum/ai_laws/proc/clear_inherent_laws()
- qdel(inherent)
- inherent = list()
-
-/datum/ai_laws/proc/add_supplied_law(number, law)
- while (supplied.len < number + 1)
- supplied += ""
-
- supplied[number + 1] = law
-
-/datum/ai_laws/proc/replace_random_law(law,groups)
- var/replaceable_groups = list()
- if(zeroth && (LAW_ZEROTH in groups))
- replaceable_groups[LAW_ZEROTH] = 1
- if(ion.len && (LAW_ION in groups))
- replaceable_groups[LAW_ION] = ion.len
- if(hacked.len && (LAW_HACKED in groups))
- replaceable_groups[LAW_ION] = hacked.len
- if(inherent.len && (LAW_INHERENT in groups))
- replaceable_groups[LAW_INHERENT] = inherent.len
- if(supplied.len && (LAW_SUPPLIED in groups))
- replaceable_groups[LAW_SUPPLIED] = supplied.len
- var/picked_group = pickweight(replaceable_groups)
- switch(picked_group)
- if(LAW_ZEROTH)
- . = zeroth
- set_zeroth_law(law)
- if(LAW_ION)
- var/i = rand(1, ion.len)
- . = ion[i]
- ion[i] = law
- if(LAW_HACKED)
- var/i = rand(1, hacked.len)
- . = hacked[i]
- hacked[i] = law
- if(LAW_INHERENT)
- var/i = rand(1, inherent.len)
- . = inherent[i]
- inherent[i] = law
- if(LAW_SUPPLIED)
- var/i = rand(1, supplied.len)
- . = supplied[i]
- supplied[i] = law
-
-/datum/ai_laws/proc/shuffle_laws(list/groups)
- var/list/laws = list()
- if(ion.len && (LAW_ION in groups))
- laws += ion
- if(hacked.len && (LAW_HACKED in groups))
- laws += hacked
- if(inherent.len && (LAW_INHERENT in groups))
- laws += inherent
- if(supplied.len && (LAW_SUPPLIED in groups))
- for(var/law in supplied)
- if(length(law))
- laws += law
-
- if(ion.len && (LAW_ION in groups))
- for(var/i = 1, i <= ion.len, i++)
- ion[i] = pick_n_take(laws)
- if(hacked.len && (LAW_HACKED in groups))
- for(var/i = 1, i <= hacked.len, i++)
- hacked[i] = pick_n_take(laws)
- if(inherent.len && (LAW_INHERENT in groups))
- for(var/i = 1, i <= inherent.len, i++)
- inherent[i] = pick_n_take(laws)
- if(supplied.len && (LAW_SUPPLIED in groups))
- var/i = 1
- for(var/law in supplied)
- if(length(law))
- supplied[i] = pick_n_take(laws)
- if(!laws.len)
- break
- i++
-
-/datum/ai_laws/proc/remove_law(number)
- if(number <= 0)
- return
- if(inherent.len && number <= inherent.len)
- . = inherent[number]
- inherent -= .
- return
- var/list/supplied_laws = list()
- for(var/index = 1, index <= supplied.len, index++)
- var/law = supplied[index]
- if(length(law) > 0)
- supplied_laws += index //storing the law number instead of the law
- if(supplied_laws.len && number <= (inherent.len+supplied_laws.len))
- var/law_to_remove = supplied_laws[number-inherent.len]
- . = supplied[law_to_remove]
- supplied -= .
- return
-
-/datum/ai_laws/proc/clear_supplied_laws()
- supplied = list()
-
-/datum/ai_laws/proc/clear_ion_laws()
- ion = list()
-
-/datum/ai_laws/proc/clear_hacked_laws()
- hacked = list()
-
-/datum/ai_laws/proc/show_laws(who)
- var/list/printable_laws = get_law_list(include_zeroth = TRUE)
- for(var/law in printable_laws)
- to_chat(who,law)
-
-/datum/ai_laws/proc/clear_zeroth_law(force) //only removes zeroth from antag ai if force is 1
- if(force)
- zeroth = null
- zeroth_borg = null
- return
- if(owner?.mind?.special_role)
- return
- if (istype(owner, /mob/living/silicon/ai))
- var/mob/living/silicon/ai/A=owner
- if(A?.deployed_shell?.mind?.special_role)
- return
- zeroth = null
- zeroth_borg = null
-
-/datum/ai_laws/proc/clear_law_sixsixsix(force)
- if(force || !is_devil(owner))
- devillaws = null
-
-/datum/ai_laws/proc/associate(mob/living/silicon/M)
- if(!owner)
- owner = M
-
-/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1)
- var/list/data = list()
-
- if (include_zeroth && devillaws && devillaws.len)
- for(var/i in devillaws)
- data += "[show_numbers ? "666:" : ""] [i]"
-
- if (include_zeroth && zeroth)
- data += "[show_numbers ? "0:" : ""] [zeroth]"
-
- for(var/law in hacked)
- if (length(law) > 0)
- var/num = ionnum()
- data += "[show_numbers ? "[num]:" : ""] [law]"
-
- for(var/law in ion)
- if (length(law) > 0)
- var/num = ionnum()
- data += "[show_numbers ? "[num]:" : ""] [law]"
-
- var/number = 1
- for(var/law in inherent)
- if (length(law) > 0)
- data += "[show_numbers ? "[number]:" : ""] [law]"
- number++
-
- for(var/law in supplied)
- if (length(law) > 0)
- data += "[show_numbers ? "[number]:" : ""] [law]"
- number++
- return data
diff --git a/code/datums/armor.dm b/code/datums/armor.dm
index fa995f3490a5..d9b07d7be0f0 100644
--- a/code/datums/armor.dm
+++ b/code/datums/armor.dm
@@ -6,7 +6,6 @@
. = new /datum/armor(melee, bullet, laser, energy, bomb, bio, rad, fire, acid, magic, wound, electric)
/datum/armor
- datum_flags = DF_USE_TAG
var/melee
var/bullet
var/laser
@@ -34,6 +33,7 @@
src.wound = wound
src.electric = electric
tag = ARMORID
+ GenerateTag()
/datum/armor/proc/modifyRating(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 0, acid = 0, magic = 0, wound = 0, electric=0)
return getArmor(src.melee+melee, src.bullet+bullet, src.laser+laser, src.energy+energy, src.bomb+bomb, src.bio+bio, src.rad+rad, src.fire+fire, src.acid+acid, src.magic+magic, src.wound+wound, src.electric+electric)
diff --git a/code/datums/beam.dm b/code/datums/beam.dm
index 53f448dc17b5..152de6ec1e1c 100644
--- a/code/datums/beam.dm
+++ b/code/datums/beam.dm
@@ -1,123 +1,155 @@
-//Beam Datum and effect
+
+/** # Beam Datum and Effect
+ * **IF YOU ARE LAZY AND DO NOT WANT TO READ, GO TO THE BOTTOM OF THE FILE AND USE THAT PROC!**
+ *
+ * This is the beam datum! It's a really neat effect for the game in drawing a line from one atom to another.
+ * It has two parts:
+ * The datum itself which manages redrawing the beam to constantly keep it pointing from the origin to the target.
+ * The effect which is what the beams are made out of. They're placed in a line from the origin to target, rotated towards the target and snipped off at the end.
+ * These effects are kept in a list and constantly created and destroyed (hence the proc names draw and reset, reset destroying all effects and draw creating more.)
+ *
+ * You can add more special effects to the beam itself by changing what the drawn beam effects do. For example you can make a vine that pricks people by making the beam_type
+ * include a crossed proc that damages the crosser. Examples in venus_human_trap.dm
+*/
/datum/beam
+ ///where the beam goes from
var/atom/origin = null
+ ///where the beam goes to
var/atom/target = null
+ ///list of beam objects. These have their visuals set by the visuals var which is created on starting
var/list/elements = list()
- var/icon/base_icon = null
+ ///icon used by the beam.
var/icon
- var/icon_state = "" //icon state of the main segments of the beam
+ ///icon state of the main segments of the beam
+ var/icon_state = ""
+ ///The beam will qdel if it's longer than this many tiles.
var/max_distance = 0
- var/sleep_time = 3
- var/finished = 0
- var/target_oldloc = null
- var/origin_oldloc = null
- var/static_beam = 0
- var/beam_type = /obj/effect/ebeam //must be subtype
- var/timing_id = null
- var/recalculating = FALSE
- var/color //yogs
-
-/datum/beam/New(beam_origin,beam_target,beam_icon='icons/effects/beam.dmi',beam_icon_state="b_beam",time=50,maxdistance=10,btype = /obj/effect/ebeam,beam_sleep_time=3, beam_color) // yogs added beam_color
- origin = beam_origin
- origin_oldloc = get_turf(origin)
- target = beam_target
- target_oldloc = get_turf(target)
- sleep_time = beam_sleep_time
- if(origin_oldloc == origin && target_oldloc == target)
- static_beam = 1
- color = beam_color // yogs
- max_distance = maxdistance
- base_icon = new(beam_icon,beam_icon_state)
- icon = beam_icon
- icon_state = beam_icon_state
- beam_type = btype
+ ///the objects placed in the elements list
+ var/beam_type = /obj/effect/ebeam
+ ///This is used as the visual_contents of beams, so you can apply one effect to this and the whole beam will look like that. never gets deleted on redrawing.
+ var/obj/effect/ebeam/visuals
+ ///The color of the beam we're drawing.
+ var/beam_color
+ ///If we use an emissive appearance
+ var/emissive = TRUE
+ /// If set will be used instead of origin's pixel_x in offset calculations
+ var/override_origin_pixel_x = null
+ /// If set will be used instead of origin's pixel_y in offset calculations
+ var/override_origin_pixel_y = null
+ /// If set will be used instead of targets's pixel_x in offset calculations
+ var/override_target_pixel_x = null
+ /// If set will be used instead of targets's pixel_y in offset calculations
+ var/override_target_pixel_y = null
+
+/datum/beam/New(
+ origin,
+ target,
+ icon = 'icons/effects/beam.dmi',
+ icon_state = "b_beam",
+ time = INFINITY,
+ max_distance = INFINITY,
+ beam_type = /obj/effect/ebeam,
+ beam_color = null,
+ emissive = TRUE,
+ override_origin_pixel_x = null,
+ override_origin_pixel_y = null,
+ override_target_pixel_x = null,
+ override_target_pixel_y = null,
+)
+ src.origin = origin
+ src.target = target
+ src.icon = icon
+ src.icon_state = icon_state
+ src.max_distance = max_distance
+ src.beam_type = beam_type
+ src.beam_color = beam_color
+ src.emissive = emissive
+ src.override_origin_pixel_x = override_origin_pixel_x
+ src.override_origin_pixel_y = override_origin_pixel_y
+ src.override_target_pixel_x = override_target_pixel_x
+ src.override_target_pixel_y = override_target_pixel_y
if(time < INFINITY)
- addtimer(CALLBACK(src, PROC_REF(End)), time)
+ QDEL_IN(src, time)
+/**
+ * Proc called by the atom Beam() proc. Sets up signals, and draws the beam for the first time.
+ */
/datum/beam/proc/Start()
+ visuals = new beam_type()
+ visuals.icon = icon
+ visuals.icon_state = icon_state
+ visuals.color = beam_color
+ visuals.vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_LAYER
+ visuals.emissive = emissive
+ visuals.update_appearance()
Draw()
- recalculate_in(sleep_time)
-
-/datum/beam/proc/recalculate()
- if(recalculating)
- recalculate_in(sleep_time)
- return
- recalculating = TRUE
- timing_id = null
+ RegisterSignal(origin, COMSIG_MOVABLE_MOVED, PROC_REF(redrawing))
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(redrawing))
+
+/**
+ * Triggered by signals set up when the beam is set up. If it's still sane to create a beam, it removes the old beam, creates a new one. Otherwise it kills the beam.
+ *
+ * Arguments:
+ * mover: either the origin of the beam or the target of the beam that moved.
+ * oldloc: from where mover moved.
+ * direction: in what direction mover moved from.
+ */
+/datum/beam/proc/redrawing(atom/movable/mover, atom/oldloc, direction)
+ SIGNAL_HANDLER
if(origin && target && get_dist(origin,target)length)
- var/icon/II = new(icon, icon_state)
- II.DrawBox(null,1,(length-N),32,32)
- X.icon = II
+ var/obj/effect/ebeam/segment = new beam_type(origin_turf, src)
+ elements += segment
+
+ //Assign our single visual ebeam to each ebeam's vis_contents
+ //ends are cropped by a transparent box icon of length-N pixel size laid over the visuals obj
+ if(N+32>length) //went past the target, we draw a box of space to cut away from the beam sprite so the icon actually ends at the center of the target sprite
+ var/icon/II = new(icon, icon_state)//this means we exclude the overshooting object from the visual contents which does mean those visuals don't show up for the final bit of the beam...
+ II.DrawBox(null,1,(length-N),32,32)//in the future if you want to improve this, remove the drawbox and instead use a 513 filter to cut away at the final object's icon
+ segment.icon = II
+ segment.color = beam_color
else
- X.icon = base_icon
- X.transform = rot_matrix
- if(color) //yogs
- X.add_atom_colour(color, FIXED_COLOUR_PRIORITY) //yogs
+ segment.vis_contents += visuals
+ segment.transform = rot_matrix
+
//Calculate pixel offsets (If necessary)
var/Pixel_x
var/Pixel_y
@@ -134,23 +166,36 @@
var/a
if(abs(Pixel_x)>32)
a = Pixel_x > 0 ? round(Pixel_x/32) : CEILING(Pixel_x/32, 1)
- X.x += a
+ segment.x += a
Pixel_x %= 32
if(abs(Pixel_y)>32)
a = Pixel_y > 0 ? round(Pixel_y/32) : CEILING(Pixel_y/32, 1)
- X.y += a
+ segment.y += a
Pixel_y %= 32
- X.pixel_x = Pixel_x
- X.pixel_y = Pixel_y
+ segment.pixel_x = origin_px + Pixel_x
+ segment.pixel_y = origin_py + Pixel_y
CHECK_TICK
- afterDraw()
/obj/effect/ebeam
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ layer = ABOVE_ALL_MOB_LAYER
anchored = TRUE
+ var/emissive = TRUE
var/datum/beam/owner
+/obj/effect/ebeam/Initialize(mapload, beam_owner)
+ owner = beam_owner
+ return ..()
+
+/obj/effect/ebeam/update_overlays()
+ . = ..()
+ if(!emissive)
+ return
+ var/mutable_appearance/emissive_overlay = emissive_appearance(icon, icon_state, src)
+ emissive_overlay.transform = transform
+ . += emissive_overlay
+
/obj/effect/ebeam/Destroy()
owner = null
return ..()
@@ -160,7 +205,21 @@
/obj/effect/ebeam/singularity_act()
return
-/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=50, maxdistance=10,beam_type=/obj/effect/ebeam,beam_sleep_time = 3)
- var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time)
- INVOKE_ASYNC(newbeam, TYPE_PROC_REF(/datum/beam, Start))
+/**
+ * This is what you use to start a beam. Example: origin.Beam(target, args). **Store the return of this proc if you don't set maxdist or time, you need it to delete the beam.**
+ *
+ * Unless you're making a custom beam effect (see the beam_type argument), you won't actually have to mess with any other procs. Make sure you store the return of this Proc, you'll need it
+ * to kill the beam.
+ * **Arguments:**
+ * BeamTarget: Where you're beaming from. Where do you get origin? You didn't read the docs, fuck you.
+ * icon_state: What the beam's icon_state is. The datum effect isn't the ebeam object, it doesn't hold any icon and isn't type dependent.
+ * icon: What the beam's icon file is. Don't change this, man. All beam icons should be in beam.dmi anyways.
+ * maxdistance: how far the beam will go before stopping itself. Used mainly for two things: preventing lag if the beam may go in that direction and setting a range to abilities that use beams.
+ * beam_type: The type of your custom beam. This is for adding other wacky stuff for your beam only. Most likely, you won't (and shouldn't) change it.
+ */
+/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=INFINITY,maxdistance=INFINITY,beam_type=/obj/effect/ebeam, beam_color = null, emissive = TRUE, override_origin_pixel_x = null, override_origin_pixel_y = null, override_target_pixel_x = null, override_target_pixel_y = null)
+ var/datum/beam/newbeam = new(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type, beam_color, emissive, override_origin_pixel_x, override_origin_pixel_y, override_target_pixel_x, override_target_pixel_y )
+ INVOKE_ASYNC(newbeam, TYPE_PROC_REF(/datum/beam/, Start))
return newbeam
+
+
diff --git a/code/datums/blood_types.dm b/code/datums/blood_types.dm
new file mode 100644
index 000000000000..5fb56f410063
--- /dev/null
+++ b/code/datums/blood_types.dm
@@ -0,0 +1,91 @@
+/datum/blood_type
+ /// Displayed name of the blood type.
+ var/name = "?"
+ /// Shown color of the blood type.
+ var/color = COLOR_BLOOD
+ /// Blood types that are safe to use with people that have this blood type.
+ var/compatible_types = list()
+
+/datum/blood_type/New()
+ . = ..()
+ compatible_types |= /datum/blood_type/universal
+
+/datum/blood_type/universal
+ name = "U"
+
+/datum/blood_type/universal/New()
+ . = ..()
+ compatible_types |= subtypesof(/datum/blood_type)
+
+////////////////////////////////////////////////////////////////
+//--------------------Normal human bloodtypes-----------------//
+////////////////////////////////////////////////////////////////
+/datum/blood_type/a_minus
+ name = "A-"
+ compatible_types = list(/datum/blood_type/a_minus, /datum/blood_type/o_minus)
+
+/datum/blood_type/a_plus
+ name = "A+"
+ compatible_types = list(/datum/blood_type/a_minus, /datum/blood_type/a_plus, /datum/blood_type/o_minus, /datum/blood_type/o_plus)
+
+/datum/blood_type/b_minus
+ name = "B-"
+ compatible_types = list(/datum/blood_type/b_minus, /datum/blood_type/o_minus, /datum/blood_type/universal)
+
+/datum/blood_type/b_plus
+ name = "B+"
+ compatible_types = list(/datum/blood_type/b_minus, /datum/blood_type/b_plus, /datum/blood_type/o_minus, /datum/blood_type/o_plus)
+
+/datum/blood_type/ab_minus
+ name = "AB-"
+ compatible_types = list(/datum/blood_type/b_minus, /datum/blood_type/a_minus, /datum/blood_type/ab_minus, /datum/blood_type/o_minus)
+
+/datum/blood_type/ab_plus
+ name = "AB+"
+ compatible_types = list(/datum/blood_type/b_minus, /datum/blood_type/a_minus, /datum/blood_type/ab_minus, /datum/blood_type/o_minus)
+
+/datum/blood_type/o_minus
+ name = "O-"
+ compatible_types = list(/datum/blood_type/o_minus)
+
+/datum/blood_type/o_plus
+ name = "O+"
+ compatible_types = list(/datum/blood_type/o_minus, /datum/blood_type/o_plus)
+
+////////////////////////////////////////////////////////////////
+//--------------------Other species bloodtypes----------------//
+////////////////////////////////////////////////////////////////
+/datum/blood_type/lizard
+ name = "L"
+ color = LIGHT_COLOR_BLUEGREEN
+ compatible_types = list(/datum/blood_type/lizard)
+
+
+/datum/blood_type/universal/synthetic //Blood for preterni
+ name = "Synthetic"
+ color = LIGHT_COLOR_ELECTRIC_CYAN
+
+/*
+ The species have exotic blood, but with how dna is stored, they still need a blood type
+ They're literally ONLY used to colour bloodsplats as far as I know (maybe it will be possible to podclone from bloodsplats)
+*/
+/datum/blood_type/xenomorph //for xenomorph gib dna and polysmorph bloodsplats
+ name = "X"
+ color = "#00FF32"
+ compatible_types = list(/datum/blood_type/xenomorph)
+
+/datum/blood_type/electricity
+ name = "E"
+ color = "#cbee63" //slightly more yellowy than regular liquid electricity because of the grey scale image used
+ compatible_types = list(/datum/blood_type/electricity)
+
+////////////////////////////////////////////////////////////////
+//-----------------Wonky simplemob(?) bloodtypes--------------//
+////////////////////////////////////////////////////////////////
+/datum/blood_type/animal //for simplemob gib dna
+ name = "Y-"
+ compatible_types = list(/datum/blood_type/animal)
+
+/datum/blood_type/gorilla
+ name = "G"
+ compatible_types = list(/datum/blood_type/gorilla)
diff --git a/code/datums/brain_damage/creepy_trauma.dm b/code/datums/brain_damage/creepy_trauma.dm
index a8c87ab78a83..ad3cac664e2b 100644
--- a/code/datums/brain_damage/creepy_trauma.dm
+++ b/code/datums/brain_damage/creepy_trauma.dm
@@ -91,7 +91,7 @@
switch(rand(1, 100))
if(1 to 40)
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), pick("blink", "blink_r"))
- owner.blur_eyes(10)
+ owner.adjust_eye_blur(10)
to_chat(owner, span_userdanger("You sweat profusely and have a hard time focusing..."))
if(41 to 80)
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "pale")
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 7d5685af7c03..a0790b817fc5 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -58,8 +58,6 @@
real_name = "imaginary friend"
move_on_shuttle = TRUE
desc = "A wonderful yet fake friend."
- see_in_dark = 0
- lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
sight = NONE
mouse_opacity = MOUSE_OPACITY_OPAQUE
see_invisible = SEE_INVISIBLE_LIVING
@@ -167,9 +165,11 @@
//speech bubble
if(owner.client)
- var/mutable_appearance/MA = mutable_appearance('icons/mob/talk.dmi', src, "default[say_test(message)]", FLY_LAYER)
- MA.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
- INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, MA, list(owner.client), 30)
+ var/mutable_appearance/bubble = mutable_appearance('icons/mob/talk.dmi', src, "default[say_test(message)]", FLY_LAYER)
+ bubble.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ SET_PLANE_EXPLICIT(bubble, ABOVE_GAME_PLANE, src)
+ INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay_global, bubble, list(owner.client), 30)
+ LAZYADD(update_on_z, bubble)
for(var/mob/dead/observer/M in GLOB.dead_mob_list)
if(M.client)
@@ -177,6 +177,9 @@
var/link = FOLLOW_LINK(M, owner)
to_chat(M, "[link] [dead_rendered]")
+/mob/camera/imaginary_friend/proc/clear_saypopup(image/say_popup)
+ LAZYREMOVE(update_on_z, say_popup)
+
/mob/camera/imaginary_friend/Move(NewLoc, Dir = 0)
if(world.time < move_delay)
return FALSE
diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm
index 9eccabd5d59a..cf8b54ba9c48 100644
--- a/code/datums/brain_damage/mild.dm
+++ b/code/datums/brain_damage/mild.dm
@@ -117,7 +117,7 @@
owner.adjust_dizzy(20 SECONDS)
if(4,5)
owner.adjust_confusion(10 SECONDS)
- owner.blur_eyes(10)
+ owner.adjust_eye_blur(10)
if(6 to 9)
owner.adjust_slurring(1 MINUTES)
if(10)
diff --git a/code/datums/chat_payload.dm b/code/datums/chat_payload.dm
new file mode 100644
index 000000000000..fd35bbc4eecf
--- /dev/null
+++ b/code/datums/chat_payload.dm
@@ -0,0 +1,16 @@
+/// Stores information about a chat payload
+/datum/chat_payload
+ /// Sequence number of this payload
+ var/sequence = 0
+ /// Message we are sending
+ var/list/content
+ /// Resend count
+ var/resends = 0
+
+/// Converts the chat payload into a JSON string
+/datum/chat_payload/proc/into_message()
+ return "{\"sequence\":[sequence],\"content\":[json_encode(content)]}"
+
+/// Returns an HTML-encoded message from our contents.
+/datum/chat_payload/proc/get_content_as_html()
+ return message_to_html(content)
diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm
index 5694bfa0fd08..6b3e37c2d5be 100644
--- a/code/datums/chatmessage.dm
+++ b/code/datums/chatmessage.dm
@@ -1,9 +1,11 @@
/// How long the chat message's spawn-in animation will occur for
-#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS
+#define CHAT_MESSAGE_SPAWN_TIME (0.2 SECONDS)
/// How long the chat message will exist prior to any exponential decay
-#define CHAT_MESSAGE_LIFESPAN 5 SECONDS
+#define CHAT_MESSAGE_LIFESPAN (5 SECONDS)
/// How long the chat message's end of life fading animation will occur for
-#define CHAT_MESSAGE_EOL_FADE 0.7 SECONDS
+#define CHAT_MESSAGE_EOL_FADE (0.7 SECONDS)
+/// Grace period for fade before we actually delete the chat message
+#define CHAT_MESSAGE_GRACE_PERIOD (0.2 SECONDS)
/// Factor of how much the message index (number of messages) will account to exponential decay
#define CHAT_MESSAGE_EXP_DECAY 0.7
/// Factor of how much height will account to exponential decay
@@ -12,15 +14,18 @@
#define CHAT_MESSAGE_APPROX_LHEIGHT 11
/// Max width of chat message in pixels
#define CHAT_MESSAGE_WIDTH 96
-/// Max length of chat message in characters
-#define CHAT_MESSAGE_MAX_LENGTH 110
-/// Maximum precision of float before rounding errors occur (in this context)
-#define CHAT_LAYER_Z_STEP 0.0001
-/// The number of z-layer 'slices' usable by the chat message layering
-#define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP
/// The dimensions of the chat message icons
#define CHAT_MESSAGE_ICON_SIZE 9
+///Base layer of chat elements
+#define CHAT_LAYER 1
+///Highest possible layer of chat elements
+#define CHAT_LAYER_MAX 2
+/// Maximum precision of float before rounding errors occur (in this context)
+#define CHAT_LAYER_Z_STEP 0.0001
+/// The number of z-layer 'slices' usable by the chat message layering
+#define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP
+
/**
* # Chat Message Overlay
*
@@ -102,7 +107,7 @@
// Register client who owns this message
owned_by = owner.client
- RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_qdel), src)
+ RegisterSignal(owned_by, COMSIG_QDELETING, PROC_REF(on_parent_qdel), src)
// Remove spans in the message from things like the recorder
var/static/regex/span_check = new(@"<\/?span[^>]*>", "gi")
@@ -160,7 +165,8 @@
// Approximate text height
var/complete_text = "[text]"
- var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH))
+ var/mheight
+ WXH_TO_HEIGHT(owned_by.MeasureText(complete_text, null, CHAT_MESSAGE_WIDTH), mheight)
approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
// Translate any existing messages upwards, apply exponential decay factors to timers
@@ -186,7 +192,7 @@
// Build message image
message = image(loc = message_loc, layer = CHAT_LAYER + CHAT_LAYER_Z_STEP * current_z_idx++)
- message.plane = RUNECHAT_PLANE
+ SET_PLANE_EXPLICIT(message, RUNECHAT_PLANE, message_loc)
message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
message.alpha = 0
message.pixel_y = owner.bound_height * 0.95
@@ -202,8 +208,14 @@
// Register with the runechat SS to handle EOL and destruction
scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE)
+ RegisterSignal(message_loc, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(loc_z_changed))
enter_subsystem()
+
+/datum/chatmessage/proc/loc_z_changed(datum/source, turf/old_turf, turf/new_turf, same_z_layer)
+ SIGNAL_HANDLER
+ SET_PLANE(message, RUNECHAT_PLANE, new_turf)
+
/**
* Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion,
* sets time for scheduling deletion and re-enters the runechat SS for qdeling
diff --git a/code/datums/cinematic.dm b/code/datums/cinematic.dm
index 22a76a7c2d0a..29ba4e960219 100644
--- a/code/datums/cinematic.dm
+++ b/code/datums/cinematic.dm
@@ -23,7 +23,6 @@
icon_state = "station_intact"
plane = SPLASHSCREEN_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- layer = SPLASHSCREEN_LAYER
screen_loc = "BOTTOM,LEFT+50%"
appearance_flags = APPEARANCE_UI | TILE_BOUND
diff --git a/code/datums/components/README.md b/code/datums/components/README.md
index 9db03c5dfc5d..db8bf10a327f 100644
--- a/code/datums/components/README.md
+++ b/code/datums/components/README.md
@@ -4,130 +4,6 @@
Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
-### In the code
+### [HackMD page for an introduction to the system as a whole.](https://hackmd.io/@tgstation/SignalsComponentsElements)
-#### Slippery things
-
-At the time of this writing, every object that is slippery overrides atom/Crossed does some checks, then slips the mob. Instead of all those Crossed overrides they could add a slippery component to all these objects. And have the checks in one proc that is run by the Crossed event
-
-#### Powercells
-
-A lot of objects have powercells. The `get_cell()` proc was added to give generic access to the cell var if it had one. This is just a specific use case of `GetComponent()`
-
-#### Radios
-
-The radio object as it is should not exist, given that more things use the _concept_ of radios rather than the object itself. The actual function of the radio can exist in a component which all the things that use it (Request consoles, actual radios, the SM shard) can add to themselves.
-
-#### Standos
-
-Stands have a lot of procs which mimic mob procs. Rather than inserting hooks for all these procs in overrides, the same can be accomplished with signals
-
-## API
-
-### Defines
-
-1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned. This will be noted in the runtime logs
-
-### Vars
-
-1. `/datum/var/list/datum_components` (private)
- * Lazy associated list of type -> component/list of components.
-1. `/datum/var/list/comp_lookup` (private)
- * Lazy associated list of signal -> registree/list of registrees
-1. `/datum/var/list/signal_procs` (private)
- * Associated lazy list of signals -> `/datum/callback`s that will be run when the parent datum receives that signal
-1. `/datum/var/signal_enabled` (protected, boolean)
- * If the datum is signal enabled. If not, it will not react to signals
- * `FALSE` by default, set to `TRUE` when a signal is registered
-1. `/datum/component/var/dupe_mode` (protected, enum)
- * How duplicate component types are handled when added to the datum.
- * `COMPONENT_DUPE_HIGHLANDER` (default): Old component will be deleted, new component will first have `/datum/component/proc/InheritComponent(datum/component/old, FALSE)` on it
- * `COMPONENT_DUPE_ALLOWED`: The components will be treated as separate, `GetComponent()` will return the first added
- * `COMPONENT_DUPE_UNIQUE`: New component will be deleted, old component will first have `/datum/component/proc/InheritComponent(datum/component/new, TRUE)` on it
- * `COMPONENT_DUPE_UNIQUE_PASSARGS`: New component will never exist and instead its initialization arguments will be passed on to the old component.
-1. `/datum/component/var/dupe_type` (protected, type)
- * Definition of a duplicate component type
- * `null` means exact match on `type` (default)
- * Any other type means that and all subtypes
-1. `/datum/component/var/datum/parent` (protected, read-only)
- * The datum this component belongs to
- * Never `null` in child procs
-1. `report_signal_origin` (protected, boolean)
- * If `TRUE`, will invoke the callback when signalled with the signal type as the first argument.
- * `FALSE` by default.
-
-### Procs
-
-1. `/datum/proc/GetComponent(component_type(type)) -> datum/component?` (public, final)
- * Returns a reference to a component of component_type if it exists in the datum, null otherwise
-1. `/datum/proc/GetComponents(component_type(type)) -> list` (public, final)
- * Returns a list of references to all components of component_type that exist in the datum
-1. `/datum/proc/GetExactComponent(component_type(type)) -> datum/component?` (public, final)
- * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise
-1. `SEND_SIGNAL(target, sigtype, ...)` (public, final)
- * Use to send signals to target datum
- * Extra arguments are to be specified in the signal definition
- * Returns a bitflag with signal specific information assembled from all activated components
- * Arguments are packaged in a list and handed off to _SendSignal()
-1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final)
- * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize(mapload)` call
- * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum
- * All components a datum owns are deleted with the datum
- * Returns the component that was created. Or the old component in a dupe situation where `COMPONENT_DUPE_UNIQUE` was set
- * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
- * Properly handles duplicate situations based on the `dupe_mode` var
-1. `/datum/proc/LoadComponent(component_type(type), ...) -> datum/component` (public, final)
- * Equivalent to calling `GetComponent(component_type)` where, if the result would be `null`, returns `AddComponent(component_type, ...)` instead
-1. `/datum/proc/ComponentActivated(datum/component/C)` (abstract, async)
- * Called on a component's `parent` after a signal received causes it to activate. `src` is the parameter
- * Will only be called if a component's callback returns `TRUE`
-1. `/datum/proc/TakeComponent(datum/component/C)` (public, final)
- * Properly transfers ownership of a component from one datum to another
- * Signals `COMSIG_COMPONENT_REMOVING` on the parent
- * Called on the datum you want to own the component with another datum's component
-1. `/datum/proc/_SendSignal(signal, list/arguments)` (private, final)
- * Handles most of the actual signaling procedure
- * Will runtime if used on datums with an empty component list
-1. `/datum/proc/RegisterSignal(datum/target, signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final)
- * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments
- * Makes the datum listen for the specified `signal` on it's `parent` datum.
- * When that signal is received `proc_ref` will be called on the component, along with associated arguments
- * Example proc ref: `PROC_REF(OnEvent)`
- * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this
- * These callbacks run asyncronously
- * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it
-1. `/datum/component/New(datum/parent, ...)` (private, final)
- * Runs internal setup for the component
- * Extra arguments are passed to `Initialize(mapload)`
-1. `/datum/component/Initialize(...)` (abstract, no-sleep)
- * Called by `New()` with the same argments excluding `parent`
- * Component does not exist in `parent`'s `datum_components` list yet, although `parent` is set and may be used
- * Signals will not be received while this function is running
- * Component may be deleted after this function completes without being attached
- * Do not call `qdel(src)` from this function
-1. `/datum/component/Destroy(force(bool), silent(bool))` (virtual, no-sleep)
- * Sends the `COMSIG_COMPONENT_REMOVING` signal to the parent datum if the `parent` isn't being qdeleted
- * Properly removes the component from `parent` and cleans up references
- * Setting `force` makes it not check for and remove the component from the parent
- * Setting `silent` deletes the component without sending a `COMSIG_COMPONENT_REMOVING` signal
-1. `/datum/component/proc/InheritComponent(datum/component/C, i_am_original(boolean))` (abstract, no-sleep)
- * Called on a component when a component of the same type was added to the same parent
- * See `/datum/component/var/dupe_mode`
- * `C`'s type will always be the same of the called component
-1. `/datum/component/proc/AfterComponentActivated()` (abstract, async)
- * Called on a component that was activated after it's `parent`'s `ComponentActivated()` is called
-1. `/datum/component/proc/OnTransfer(datum/new_parent)` (abstract, no-sleep)
- * Called before `new_parent` is assigned to `parent` in `TakeComponent()`
- * Allows the component to react to ownership transfers
-1. `/datum/component/proc/_RemoveFromParent()` (private, final)
- * Clears `parent` and removes the component from it's component list
-1. `/datum/component/proc/_JoinParent` (private, final)
- * Tries to add the component to it's `parent`s `datum_components` list
-1. `/datum/component/proc/RegisterWithParent` (abstract, no-sleep)
- * Used to register the signals that should be on the `parent` object
- * Use this if you plan on the component transfering between parents
-1. `/datum/component/proc/UnregisterFromParent` (abstract, no-sleep)
- * Counterpart to `RegisterWithParent()`
- * Used to unregister the signals that should only be on the `parent` object
-
-### See/Define signals and their arguments in __DEFINES\components.dm
+### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm)
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index 3d201a78c066..e461aa2ee36d 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -10,18 +10,41 @@
* Useful when you want shared behaviour independent of type inheritance
*/
/datum/component
+ /**
+ * Defines how duplicate existing components are handled when added to a datum
+ *
+ * See [COMPONENT_DUPE_*][COMPONENT_DUPE_ALLOWED] definitions for available options
+ */
var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
+
+ /**
+ * The type to check for duplication
+ *
+ * `null` means exact match on `type` (default)
+ *
+ * Any other type means that and all subtypes
+ */
var/dupe_type
+
+ /// The datum this components belongs to
var/datum/parent
- //only set to true if you are able to properly transfer this component
- //At a minimum RegisterWithParent and UnregisterFromParent should be used
- //Make sure you also implement PostTransfer for any post transfer handling
+
+ /**
+ * Only set to true if you are able to properly transfer this component
+ *
+ * At a minimum [RegisterWithParent][/datum/component/proc/RegisterWithParent] and [UnregisterFromParent][/datum/component/proc/UnregisterFromParent] should be used
+ *
+ * Make sure you also implement [PostTransfer][/datum/component/proc/PostTransfer] for any post transfer handling
+ */
var/can_transfer = FALSE
+ /// A lazy list of the sources for this component
+ var/list/sources
+
/**
* Create a new component.
*
- * Additional arguments are passed to [Initialize(mapload)][/datum/component/proc/Initialize]
+ * Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize]
*
* Arguments:
* * datum/P the parent datum this component reacts to signals from
@@ -30,8 +53,9 @@
parent = raw_args[1]
var/list/arguments = raw_args.Copy(2)
if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
+ stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
qdel(src, TRUE, TRUE)
- CRASH("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
+ return
_JoinParent(parent)
@@ -53,7 +77,7 @@
/datum/component/Destroy(force=FALSE, silent=FALSE)
if(!parent)
return ..()
- if (!force)
+ if(!force)
_RemoveFromParent()
if(!silent)
SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src)
@@ -66,22 +90,22 @@
/datum/component/proc/_JoinParent()
var/datum/P = parent
//lazy init the parent's dc list
- var/list/dc = P.datum_components
+ var/list/dc = P._datum_components
if(!dc)
- P.datum_components = dc = list()
+ P._datum_components = dc = list()
//set up the typecache
var/our_type = type
for(var/I in _GetInverseTypeList(our_type))
var/test = dc[I]
- if(test) //already another component of this type here
+ if(test) //already another component of this type here
var/list/components_of_type
if(!length(test))
components_of_type = list(test)
dc[I] = components_of_type
else
components_of_type = test
- if(I == our_type) //exact match, take priority
+ if(I == our_type) //exact match, take priority
var/inserted = FALSE
for(var/J in 1 to components_of_type.len)
var/datum/component/C = components_of_type[J]
@@ -91,44 +115,48 @@
break
if(!inserted)
components_of_type += src
- else //indirect match, back of the line with ya
+ else //indirect match, back of the line with ya
components_of_type += src
- else //only component of this type, no list
+ else //only component of this type, no list
dc[I] = src
RegisterWithParent()
-/**
- * Register the component with the parent object
- *
- * Use this proc to register with your parent object
- *
- * Overridable proc that's called when added to a new parent
- */
-/datum/component/proc/RegisterWithParent()
- return
-
/**
* Internal proc to handle behaviour when being removed from a parent
*/
/datum/component/proc/_RemoveFromParent()
- var/datum/P = parent
- var/list/dc = P.datum_components
+ var/datum/parent = src.parent
+ var/list/parents_components = parent._datum_components
for(var/I in _GetInverseTypeList())
- var/list/components_of_type = dc[I]
- if(length(components_of_type)) //
+ var/list/components_of_type = parents_components[I]
+
+ if(length(components_of_type)) //
var/list/subtracted = components_of_type - src
- if(subtracted.len == 1) //only 1 guy left
- dc[I] = subtracted[1] //make him special
+
+ if(subtracted.len == 1) //only 1 guy left
+ parents_components[I] = subtracted[1] //make him special
else
- dc[I] = subtracted
- else //just us
- dc -= I
- if(!dc.len)
- P.datum_components = null
+ parents_components[I] = subtracted
+
+ else //just us
+ parents_components -= I
+
+ if(!parents_components.len)
+ parent._datum_components = null
UnregisterFromParent()
+/**
+ * Register the component with the parent object
+ *
+ * Use this proc to register with your parent object
+ *
+ * Overridable proc that's called when added to a new parent
+ */
+/datum/component/proc/RegisterWithParent()
+ return
+
/**
* Unregister from our parent object
*
@@ -141,101 +169,26 @@
return
/**
- * Register to listen for a signal from the passed in target
- *
- * This sets up a listening relationship such that when the target object emits a signal
- * the source datum this proc is called upon, will receive a callback to the given proctype
- * Use PROC_REF(procname), TYPE_PROC_REF(type,procname) or GLOBAL_PROC_REF(procname) macros to validate the passed in proc at compile time.
- * PROC_REF for procs defined on current type or it's ancestors, TYPE_PROC_REF for procs defined on unrelated type and GLOBAL_PROC_REF for global procs.
- * Return values from procs registered must be a bitfield
- *
- * Arguments:
- * * datum/target The target to listen for signals from
- * * signal_type A signal name
- * * proctype The proc to call back when the signal is emitted
- * * override If a previous registration exists you must explicitly set this
+ * Called when the component has a new source registered.
+ * Return COMPONENT_INCOMPATIBLE to signal that the source is incompatible and should not be added
*/
-/datum/proc/RegisterSignal(datum/target, signal_type, proctype, override = FALSE)
- if(QDELETED(src) || QDELETED(target))
- return
-
- if (islist(signal_type))
- var/static/list/known_failures = list()
- var/list/signal_type_list = signal_type
- var/message = "([target.type]) is registering [signal_type_list.Join(", ")] as a list, the older method. Change it to RegisterSignals."
-
- if (!(message in known_failures))
- known_failures[message] = TRUE
- stack_trace("[target] [message]")
-
- RegisterSignals(target, signal_type, proctype, override)
- return
-
- var/list/procs = (signal_procs ||= list())
- var/list/target_procs = (procs[target] ||= list())
- var/list/lookup = (target.comp_lookup ||= list())
-
- if(!override && target_procs[signal_type])
- log_signal("[signal_type] overridden. Use override = TRUE to suppress this warning.\nTarget: [target] ([target.type]) Proc: [proctype]")
-
- target_procs[signal_type] = proctype
- var/list/looked_up = lookup[signal_type]
-
- if(isnull(looked_up)) // Nothing has registered here yet
- lookup[signal_type] = src
- else if(looked_up == src) // We already registered here
- return
- else if(!length(looked_up)) // One other thing registered here
- lookup[signal_type] = list((looked_up) = TRUE, (src) = TRUE)
- else // Many other things have registered here
- looked_up[src] = TRUE
-
-/// Registers multiple signals to the same proc.
-/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
- for (var/signal_type in signal_types)
- RegisterSignal(target, signal_type, proctype)
+/datum/component/proc/on_source_add(source, ...)
+ SHOULD_CALL_PARENT(TRUE)
+ if(dupe_mode != COMPONENT_DUPE_SOURCES)
+ return COMPONENT_INCOMPATIBLE
+ LAZYOR(sources, source)
/**
- * Stop listening to a given signal from target
- *
- * Breaks the relationship between target and source datum, removing the callback when the signal fires
- *
- * Doesn't care if a registration exists or not
- *
- * Arguments:
- * * datum/target Datum to stop listening to signals from
- * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically
+ * Called when the component has a source removed.
+ * You probably want to call parent after you do your logic because at the end of this we qdel if we have no sources remaining!
*/
-/datum/proc/UnregisterSignal(datum/target, sig_type_or_types)
- if(!target)
- return
- var/list/lookup = target.comp_lookup
- if(!signal_procs || !signal_procs[target] || !lookup)
- return
- if(!islist(sig_type_or_types))
- sig_type_or_types = list(sig_type_or_types)
- for(var/sig in sig_type_or_types)
- switch(length(lookup[sig]))
- if(2)
- lookup[sig] = (lookup[sig]-src)[1]
- if(1)
- stack_trace("[target] ([target.type]) somehow has single length list inside comp_lookup")
- if(src in lookup[sig])
- lookup -= sig
- if(!length(lookup))
- target.comp_lookup = null
- break
- if(0)
- lookup -= sig
- if(!length(lookup))
- target.comp_lookup = null
- break
- else
- lookup[sig] -= src
-
- signal_procs[target] -= sig_type_or_types
- if(!signal_procs[target].len)
- signal_procs -= target
+/datum/component/proc/on_source_remove(source)
+ SHOULD_CALL_PARENT(TRUE)
+ if(dupe_mode != COMPONENT_DUPE_SOURCES)
+ CRASH("Component '[type]' does not use sources but is trying to remove a source")
+ LAZYREMOVE(sources, source)
+ if(!LAZYLEN(sources))
+ qdel(src)
/**
* Called on a component when a component of the same type was added to the same parent
@@ -247,6 +200,7 @@
/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
return
+
/**
* Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE]
*
@@ -259,6 +213,7 @@
/datum/component/proc/CheckDupeComponent(datum/component/C, ...)
return
+
/**
* Callback Just before this component is transferred
*
@@ -282,34 +237,12 @@
*/
/datum/component/proc/_GetInverseTypeList(our_type = type)
//we can do this one simple trick
+ . = list(our_type)
var/current_type = parent_type
- . = list(our_type, current_type)
//and since most components are root level + 1, this won't even have to run
while (current_type != /datum/component)
- current_type = type2parent(current_type)
. += current_type
-
-/**
- * Internal proc to handle most all of the signaling procedure
- *
- * Will runtime if used on datums with an empty component list
- *
- * Use the [SEND_SIGNAL] define instead
- */
-/datum/proc/_SendSignal(sigtype, list/arguments)
- var/target = comp_lookup[sigtype]
- if(!length(target))
- var/datum/listening_datum = target
- return NONE | call(listening_datum, listening_datum.signal_procs[src][sigtype])(arglist(arguments))
- . = NONE
- // This exists so that even if one of the signal receivers unregisters the signal,
- // all the objects that are receiving the signal get the signal this final time.
- // AKA: No you can't cancel the signal reception of another object by doing an unregister in the same signal.
- var/list/queued_calls = list()
- for(var/datum/listening_datum as anything in target)
- queued_calls[listening_datum] = listening_datum.signal_procs[src][sigtype]
- for(var/datum/listening_datum as anything in queued_calls)
- . |= call(listening_datum, queued_calls[listening_datum])(arglist(arguments))
+ current_type = type2parent(current_type)
// The type arg is casted so initial works, you shouldn't be passing a real instance into this
/**
@@ -324,7 +257,7 @@
RETURN_TYPE(c_type)
if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
- var/list/dc = datum_components
+ var/list/dc = _datum_components
if(!dc)
return null
. = dc[c_type]
@@ -341,10 +274,10 @@
* * datum/component/c_type The typepath of the component you want to get a reference to
*/
/datum/proc/GetExactComponent(datum/component/c_type)
- RETURN_TYPE(c_type)
+ RETURN_TYPE(c_type)
if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
- var/list/dc = datum_components
+ var/list/dc = _datum_components
if(!dc)
return null
var/datum/component/C = dc[c_type]
@@ -362,12 +295,10 @@
* * c_type The component type path
*/
/datum/proc/GetComponents(c_type)
- var/list/dc = datum_components
- if(!dc)
- return null
- . = dc[c_type]
- if(!length(.))
- return list(.)
+ var/list/components = _datum_components?[c_type]
+ if(!components)
+ return list()
+ return islist(components) ? components : list(components)
/**
* Creates an instance of `new_type` in the datum and attaches to it as parent
@@ -380,70 +311,112 @@
*
* Properly handles duplicate situations based on the `dupe_mode` var
*/
-/datum/proc/_AddComponent(list/raw_args)
- var/new_type = raw_args[1]
- var/datum/component/nt = new_type
- var/dm = initial(nt.dupe_mode)
- var/dt = initial(nt.dupe_type)
-
- var/datum/component/old_comp
- var/datum/component/new_comp
-
- if(ispath(nt))
- if(nt == /datum/component)
- CRASH("[nt] attempted instantiation!")
- else
- new_comp = nt
- nt = new_comp.type
+/datum/proc/_AddComponent(list/raw_args, source)
+ var/original_type = raw_args[1]
+ var/datum/component/component_type = original_type
- raw_args[1] = src
+ if(QDELING(src))
+ CRASH("Attempted to add a new component of type \[[component_type]\] to a qdeleting parent of type \[[type]\]!")
+
+ var/datum/component/new_component
+
+ if(!ispath(component_type, /datum/component))
+ if(!istype(component_type, /datum/component))
+ CRASH("Attempted to instantiate \[[component_type]\] as a component added to parent of type \[[type]\]!")
+ else
+ new_component = component_type
+ component_type = new_component.type
+ else if(component_type == /datum/component)
+ CRASH("[component_type] attempted instantiation!")
+
+ var/dupe_mode = initial(component_type.dupe_mode)
+ var/dupe_type = initial(component_type.dupe_type)
+ var/uses_sources = (dupe_mode == COMPONENT_DUPE_SOURCES)
+ if(uses_sources && !source)
+ CRASH("Attempted to add a sourced component of type '[component_type]' to '[type]' without a source!")
+ else if(!uses_sources && source)
+ CRASH("Attempted to add a normal component of type '[component_type]' to '[type]' with a source!")
- if(dm != COMPONENT_DUPE_ALLOWED)
- if(!dt)
- old_comp = GetExactComponent(nt)
+ var/datum/component/old_component
+
+ raw_args[1] = src
+ if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE && dupe_mode != COMPONENT_DUPE_SOURCES)
+ if(!dupe_type)
+ old_component = GetExactComponent(component_type)
else
- old_comp = GetComponent(dt)
- if(old_comp)
- switch(dm)
+ old_component = GetComponent(dupe_type)
+
+ if(old_component)
+ switch(dupe_mode)
if(COMPONENT_DUPE_UNIQUE)
- if(!new_comp)
- new_comp = new nt(raw_args)
- if(!QDELETED(new_comp))
- old_comp.InheritComponent(new_comp, TRUE)
- QDEL_NULL(new_comp)
+ if(!new_component)
+ new_component = new component_type(raw_args)
+ if(!QDELETED(new_component))
+ old_component.InheritComponent(new_component, TRUE)
+ QDEL_NULL(new_component)
+
if(COMPONENT_DUPE_HIGHLANDER)
- if(!new_comp)
- new_comp = new nt(raw_args)
- if(!QDELETED(new_comp))
- new_comp.InheritComponent(old_comp, FALSE)
- QDEL_NULL(old_comp)
+ if(!new_component)
+ new_component = new component_type(raw_args)
+ if(!QDELETED(new_component))
+ new_component.InheritComponent(old_component, FALSE)
+ QDEL_NULL(old_component)
+
if(COMPONENT_DUPE_UNIQUE_PASSARGS)
- if(!new_comp)
+ if(!new_component)
var/list/arguments = raw_args.Copy(2)
arguments.Insert(1, null, TRUE)
- old_comp.InheritComponent(arglist(arguments))
+ old_component.InheritComponent(arglist(arguments))
else
- old_comp.InheritComponent(new_comp, TRUE)
- if(COMPONENT_DUPE_SELECTIVE)
- var/list/arguments = raw_args.Copy()
- arguments[1] = new_comp
- var/make_new_component = TRUE
- for(var/datum/component/existing_component as anything in GetComponents(new_type))
- if(existing_component.CheckDupeComponent(arglist(arguments)))
- make_new_component = FALSE
- QDEL_NULL(new_comp)
- break
- if(!new_comp && make_new_component)
- new_comp = new nt(raw_args)
- else if(!new_comp)
- new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal
- else if(!new_comp)
- new_comp = new nt(raw_args) // Dupes are allowed, act like normal
-
- if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy
- SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp)
- return new_comp
- return old_comp
+ old_component.InheritComponent(new_component, TRUE)
+
+ if(COMPONENT_DUPE_SOURCES)
+ if(source in old_component.sources)
+ return old_component // source already registered, no work to do
+
+ if(old_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE)
+ stack_trace("incompatible source added to a [old_component.type]. Args: [json_encode(raw_args)]")
+ return null
+
+ else if(!new_component)
+ new_component = new component_type(raw_args) // There's a valid dupe mode but there's no old component, act like normal
+
+ else if(dupe_mode == COMPONENT_DUPE_SELECTIVE)
+ var/list/arguments = raw_args.Copy()
+ arguments[1] = new_component
+ var/make_new_component = TRUE
+ for(var/datum/component/existing_component as anything in GetComponents(original_type))
+ if(existing_component.CheckDupeComponent(arglist(arguments)))
+ make_new_component = FALSE
+ QDEL_NULL(new_component)
+ break
+ if(!new_component && make_new_component)
+ new_component = new component_type(raw_args)
+
+ else if(dupe_mode == COMPONENT_DUPE_SOURCES)
+ new_component = new component_type(raw_args)
+ if(new_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE)
+ stack_trace("incompatible source added to a [new_component.type]. Args: [json_encode(raw_args)]")
+ return null
+
+ else if(!new_component)
+ new_component = new component_type(raw_args) // Dupes are allowed, act like normal
+
+ if(!old_component && !QDELETED(new_component)) // Nothing related to duplicate components happened and the new component is healthy
+ SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_component)
+ return new_component
+
+ return old_component
+
+/**
+ * Removes a component source from this datum
+ */
+/datum/proc/RemoveComponentSource(source, datum/component/component_type)
+ if(ispath(component_type))
+ component_type = GetExactComponent(component_type)
+ if(!component_type)
+ return
+ component_type.on_source_remove(source)
/**
* Get existing component of type, or create it and return a reference to it
@@ -463,7 +436,7 @@
* Removes the component from parent, ends up with a null parent
* Used as a helper proc by the component transfer proc, does not clean up the component like Destroy does
*/
-/datum/component/proc/RemoveComponent()
+/datum/component/proc/ClearFromParent()
if(!parent)
return
var/datum/old_parent = parent
@@ -484,7 +457,7 @@
if(!target || target.parent == src)
return
if(target.parent)
- target.RemoveComponent()
+ target.ClearFromParent()
target.parent = src
var/result = target.PostTransfer()
switch(result)
@@ -505,18 +478,19 @@
* * /datum/target the target to move the components to
*/
/datum/proc/TransferComponents(datum/target)
- var/list/dc = datum_components
+ var/list/dc = _datum_components
if(!dc)
return
- var/comps = dc[/datum/component]
- if(islist(comps))
- for(var/datum/component/I in comps)
- if(I.can_transfer)
- target.TakeComponent(I)
- else
- var/datum/component/C = comps
- if(C.can_transfer)
- target.TakeComponent(comps)
+ for(var/component_key in dc)
+ var/component_or_list = dc[component_key]
+ if(islist(component_or_list))
+ for(var/datum/component/I in component_or_list)
+ if(I.can_transfer)
+ target.TakeComponent(I)
+ else
+ var/datum/component/C = component_or_list
+ if(C.can_transfer)
+ target.TakeComponent(C)
/**
* Return the object that is the host of any UI's that this component has
diff --git a/code/datums/components/acid.dm b/code/datums/components/acid.dm
index e870ae19faf0..d226b74a4c17 100644
--- a/code/datums/components/acid.dm
+++ b/code/datums/components/acid.dm
@@ -60,7 +60,7 @@
parent_atom.update_appearance(UPDATE_ICON)
return ..()
/datum/component/acid/RegisterWithParent()
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_EXPOSE_REAGENT, PROC_REF(on_expose_reagent))
@@ -68,7 +68,7 @@
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(on_crossed))
/datum/component/acid/UnregisterFromParent()
UnregisterSignal(parent, list(
- COMSIG_PARENT_EXAMINE,
+ COMSIG_ATOM_EXAMINE,
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_EXPOSE_REAGENT))
diff --git a/code/datums/components/archaeology.dm b/code/datums/components/archaeology.dm
deleted file mode 100644
index b32a679f61b8..000000000000
--- a/code/datums/components/archaeology.dm
+++ /dev/null
@@ -1,95 +0,0 @@
-/datum/component/archaeology
- dupe_mode = COMPONENT_DUPE_UNIQUE
- var/list/archdrops = list(/obj/item/bikehorn = list(ARCH_PROB = 100, ARCH_MAXDROP = 1)) // honk~
- var/prob2drop
- var/dug
- var/datum/callback/callback
-
-/datum/component/archaeology/Initialize(list/_archdrops = list(), datum/callback/_callback)
- archdrops = _archdrops
- for(var/i in archdrops)
- if(isnull(archdrops[i][ARCH_MAXDROP]))
- archdrops[i][ARCH_MAXDROP] = 1
- stack_trace("ARCHAEOLOGY WARNING: [parent] contained a null max_drop value in [i].")
- if(isnull(archdrops[i][ARCH_PROB]))
- archdrops[i][ARCH_PROB] = 100
- stack_trace("ARCHAEOLOGY WARNING: [parent] contained a null probability value in [i].")
- callback = _callback
- RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(Dig))
- RegisterSignal(parent, COMSIG_ATOM_EX_ACT, PROC_REF(BombDig))
- RegisterSignal(parent, COMSIG_ATOM_SING_PULL, PROC_REF(SingDig))
-
-/datum/component/archaeology/InheritComponent(datum/component/archaeology/A, i_am_original)
- var/list/other_archdrops = A.archdrops
- var/list/_archdrops = archdrops
- for(var/I in other_archdrops)
- _archdrops[I] += other_archdrops[I]
-
-/datum/component/archaeology/proc/Dig(datum/source, obj/item/I, mob/living/user)
- if(dug)
- to_chat(user, span_notice("Looks like someone has dug here already."))
- return
-
- if(!isturf(user.loc))
- return
-
- if(I.tool_behaviour == TOOL_SHOVEL || I.tool_behaviour == TOOL_MINING)
- to_chat(user, span_notice("You start digging..."))
-
- if(I.use_tool(parent, user, 40, volume=50))
- to_chat(user, span_notice("You dig a hole."))
- gets_dug()
- dug = TRUE
- SSblackbox.record_feedback("tally", "pick_used_mining", 1, I.type)
- return COMPONENT_NO_AFTERATTACK
-
-/datum/component/archaeology/proc/gets_dug()
- if(dug)
- return
- else
- var/turf/open/OT = get_turf(parent)
- for(var/thing in archdrops)
- var/maxtodrop = archdrops[thing][ARCH_MAXDROP]
- for(var/i in 1 to maxtodrop)
- if(prob(archdrops[thing][ARCH_PROB])) // can't win them all!
- new thing(OT)
-
- if(isopenturf(OT))
- if(OT.postdig_icon_change)
- if(istype(OT, /turf/open/floor/plating/asteroid/) && !OT.postdig_icon)
- var/turf/open/floor/plating/asteroid/AOT = parent
- AOT.icon_plating = "[AOT.environment_type]_dug"
- AOT.icon_state = "[AOT.environment_type]_dug"
- else
- if(isplatingturf(OT))
- var/turf/open/floor/plating/POT = parent
- POT.icon_plating = "[POT.postdig_icon]"
- POT.icon_state = "[OT.postdig_icon]"
-
- if(OT.slowdown) //Things like snow slow you down until you dig them.
- OT.slowdown = 0
- dug = TRUE
- if(callback)
- callback.Invoke()
-
-/datum/component/archaeology/proc/SingDig(datum/source, S, current_size)
- switch(current_size)
- if(STAGE_THREE)
- if(prob(30))
- gets_dug()
- if(STAGE_FOUR)
- if(prob(50))
- gets_dug()
- else
- if(current_size >= STAGE_FIVE && prob(70))
- gets_dug()
-
-/datum/component/archaeology/proc/BombDig(datum/source, severity, target)
- switch(severity)
- if(3)
- return
- if(2)
- if(prob(20))
- gets_dug()
- if(1)
- gets_dug()
diff --git a/code/datums/components/armor_plate.dm b/code/datums/components/armor_plate.dm
index 29d674642521..3b3255eec3ec 100644
--- a/code/datums/components/armor_plate.dm
+++ b/code/datums/components/armor_plate.dm
@@ -9,9 +9,9 @@
if(!isobj(parent) && !iscyborg(parent)) // Only objects and cyborgs can have this component.
return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(applyplate))
- RegisterSignal(parent, COMSIG_PARENT_PREQDELETED, PROC_REF(dropplates))
+ RegisterSignal(parent, COMSIG_PREQDELETED, PROC_REF(dropplates))
if(_maxamount)
maxamount = _maxamount
diff --git a/code/datums/components/art.dm b/code/datums/components/art.dm
index 77c2ae5ca9a0..99cc0bf68053 100644
--- a/code/datums/components/art.dm
+++ b/code/datums/components/art.dm
@@ -8,9 +8,9 @@
/datum/component/art/Initialize(impress)
impressiveness = impress
if(isobj(parent))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_obj_examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_obj_examine))
else
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_other_examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_other_examine))
if(isstructure(parent))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
if(isitem(parent))
diff --git a/code/datums/components/bakeable.dm b/code/datums/components/bakeable.dm
index 412d2e0ebda7..6f6bcdd979a7 100644
--- a/code/datums/components/bakeable.dm
+++ b/code/datums/components/bakeable.dm
@@ -37,11 +37,11 @@
/datum/component/bakeable/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_BAKED, PROC_REF(OnBake))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(OnExamine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(OnExamine))
/datum/component/bakeable/UnregisterFromParent()
. = ..()
- UnregisterSignal(parent, list(COMSIG_ITEM_BAKED, COMSIG_PARENT_EXAMINE))
+ UnregisterSignal(parent, list(COMSIG_ITEM_BAKED, COMSIG_ATOM_EXAMINE))
///Ran every time an item is baked by something
/datum/component/bakeable/proc/OnBake(datum/source, atom/used_oven, delta_time = 1)
diff --git a/code/datums/components/bloodysoles.dm b/code/datums/components/bloodysoles.dm
new file mode 100644
index 000000000000..d05043312d7f
--- /dev/null
+++ b/code/datums/components/bloodysoles.dm
@@ -0,0 +1,289 @@
+
+
+//Component for clothing items that can pick up blood from decals and spread it around everywhere when walking, such as shoes or suits with integrated shoes.
+
+/datum/component/bloodysoles
+ /// The type of the last grub pool we stepped in, used to decide the type of footprints to make
+ var/last_blood_state = BLOOD_STATE_NOT_BLOODY
+
+ /// How much of each grubby type we have on our feet
+ var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
+
+ /// The ITEM_SLOT_* slot the item is equipped on, if it is.
+ var/equipped_slot
+
+ /// The parent item but casted into atom type for easier use.
+ var/atom/parent_atom
+
+ /// Either the mob carrying the item, or the mob itself for the /feet component subtype
+ var/mob/living/carbon/wielder
+
+ /// The world.time when we last picked up blood
+ var/last_pickup
+
+/datum/component/bloodysoles/Initialize()
+ if(!isclothing(parent))
+ return COMPONENT_INCOMPATIBLE
+ parent_atom = parent
+
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
+ RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
+
+//Unregisters from the wielder if necessary
+
+/datum/component/bloodysoles/proc/unregister()
+ if(!QDELETED(wielder))
+ UnregisterSignal(wielder, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(wielder, COMSIG_STEP_ON_BLOOD)
+ wielder = null
+ equipped_slot = null
+
+
+//Returns true if the parent item is obscured by something else that the wielder is wearing
+
+/datum/component/bloodysoles/proc/is_obscured()
+ return equipped_slot in wielder.check_obscured_slots(TRUE)
+
+//Run to update the icon of the parent
+
+/datum/component/bloodysoles/proc/update_icon()
+ var/obj/item/parent_item = parent
+ parent_item.update_slot_icon()
+
+//Run to equally share the blood between us and a decal
+/datum/component/bloodysoles/proc/share_blood(obj/effect/decal/cleanable/pool)
+ last_blood_state = pool.blood_state
+
+ // Share the blood between our boots and the blood pool
+ var/total_bloodiness = pool.bloodiness + bloody_shoes[last_blood_state]
+
+ // We can however be limited by how much blood we can hold
+ var/new_our_bloodiness = min(BLOOD_ITEM_MAX, total_bloodiness / 2)
+
+ bloody_shoes[last_blood_state] = new_our_bloodiness
+ pool.bloodiness = total_bloodiness - new_our_bloodiness // Give the pool the remaining blood incase we were limited
+
+ parent_atom.add_blood_DNA(pool.return_blood_DNA())
+ update_icon()
+
+//Find a blood decal on a turf that matches our last_blood_state
+
+/datum/component/bloodysoles/proc/find_pool_by_blood_state(turf/turfLoc, typeFilter = null)
+ for(var/obj/effect/decal/cleanable/blood/pool in turfLoc)
+ if(pool.blood_state == last_blood_state && (!typeFilter || istype(pool, typeFilter)))
+ return pool
+
+
+//Adds the parent type to the footprint's shoe_types var
+
+/datum/component/bloodysoles/proc/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/FP)
+ FP.shoe_types |= parent.type
+
+/*
+Called when the parent item is equipped by someone
+Used to register our wielder
+*/
+/datum/component/bloodysoles/proc/on_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ if(!iscarbon(equipper))
+ return
+ var/obj/item/parent_item = parent
+ if(!(parent_item.slot_flags & slot))
+ unregister()
+ return
+
+ equipped_slot = slot
+ wielder = equipper
+ RegisterSignal(wielder, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(wielder, COMSIG_STEP_ON_BLOOD, PROC_REF(on_step_blood))
+
+/*
+Called when the parent item has been dropped
+Used to deregister our wielder
+*/
+/datum/component/bloodysoles/proc/on_drop(datum/source, mob/dropper)
+ SIGNAL_HANDLER
+
+ unregister()
+
+/*
+Called when the wielder has moved
+Used to make bloody footprints on the ground
+*/
+/datum/component/bloodysoles/proc/on_moved(datum/source, OldLoc, Dir, Forced)
+ SIGNAL_HANDLER
+
+ if(bloody_shoes[last_blood_state] == 0)
+ return
+ if(QDELETED(wielder) || is_obscured())
+ return
+ if(!(wielder.mobility_flags & MOBILITY_STAND) || !wielder.has_gravity(wielder.loc))
+ return
+
+ var/half_our_blood = bloody_shoes[last_blood_state] / 2
+
+ // Add footprints in old loc if we have enough cream
+ if(half_our_blood >= BLOOD_FOOTPRINTS_MIN)
+ var/turf/oldLocTurf = get_turf(OldLoc)
+ var/obj/effect/decal/cleanable/blood/footprints/oldLocFP = find_pool_by_blood_state(oldLocTurf, /obj/effect/decal/cleanable/blood/footprints)
+ if(oldLocFP)
+ // Footprints found in the tile we left, add us to it
+ add_parent_to_footprint(oldLocFP)
+ if (!(oldLocFP.exited_dirs & wielder.dir))
+ oldLocFP.exited_dirs |= wielder.dir
+ oldLocFP.update_icon()
+ else if(find_pool_by_blood_state(oldLocTurf))
+ // No footprints in the tile we left, but there was some other blood pool there. Add exit footprints on it
+ bloody_shoes[last_blood_state] -= half_our_blood
+ update_icon()
+
+
+ oldLocFP = new(oldLocTurf)
+ if(!QDELETED(oldLocFP)) ///prints merged
+ oldLocFP.blood_state = last_blood_state
+ oldLocFP.exited_dirs |= wielder.dir
+ add_parent_to_footprint(oldLocFP)
+ oldLocFP.bloodiness = half_our_blood
+ oldLocFP.add_blood_DNA(parent_atom.return_blood_DNA())
+ oldLocFP.update_icon()
+
+ half_our_blood = bloody_shoes[last_blood_state] / 2
+
+ // If we picked up the blood on this tick in on_step_blood, don't make footprints at the same place
+ if(last_pickup && last_pickup == world.time)
+ return
+
+ // Create new footprints
+ if(half_our_blood >= BLOOD_FOOTPRINTS_MIN)
+ bloody_shoes[last_blood_state] -= half_our_blood
+ update_icon()
+
+ var/obj/effect/decal/cleanable/blood/footprints/FP = new(get_turf(parent_atom))
+ if(!QDELETED(FP)) ///prints merged
+ FP.blood_state = last_blood_state
+ FP.entered_dirs |= wielder.dir
+ add_parent_to_footprint(FP)
+ FP.bloodiness = half_our_blood
+ FP.add_blood_DNA(parent_atom.return_blood_DNA())
+ FP.update_icon()
+
+
+/*
+Called when the wielder steps in a pool of blood
+Used to make the parent item bloody
+*/
+/datum/component/bloodysoles/proc/on_step_blood(datum/source, obj/effect/decal/cleanable/pool)
+ SIGNAL_HANDLER
+
+ if(QDELETED(wielder) || is_obscured())
+ return
+
+ if(istype(pool, /obj/effect/decal/cleanable/blood/footprints) && pool.blood_state == last_blood_state)
+ // The pool we stepped in was actually footprints with the same type
+ var/obj/effect/decal/cleanable/blood/footprints/pool_FP = pool
+ add_parent_to_footprint(pool_FP)
+ if((bloody_shoes[last_blood_state] / 2) >= BLOOD_FOOTPRINTS_MIN && !(pool_FP.entered_dirs & wielder.dir))
+ // If our feet are bloody enough, add an entered dir
+ pool_FP.entered_dirs |= wielder.dir
+ pool_FP.update_icon()
+
+ share_blood(pool)
+
+ last_pickup = world.time
+
+/*
+Called when the parent item is being washed
+*/
+/datum/component/bloodysoles/proc/on_clean(datum/source, clean_types)
+ SIGNAL_HANDLER
+
+ if(!(clean_types & CLEAN_TYPE_BLOOD) || last_blood_state == BLOOD_STATE_NOT_BLOODY)
+ return
+
+ bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
+ last_blood_state = BLOOD_STATE_NOT_BLOODY
+ update_icon()
+ return TRUE
+
+
+/*
+Like its parent but can be applied to carbon mobs instead of clothing items
+*/
+
+/datum/component/bloodysoles/feet
+ var/static/mutable_appearance/bloody_feet
+
+/datum/component/bloodysoles/feet/Initialize()
+ if(!iscarbon(parent))
+ return COMPONENT_INCOMPATIBLE
+ parent_atom = parent
+ wielder = parent
+
+ if(!bloody_feet)
+ bloody_feet = mutable_appearance('icons/effects/blood.dmi', "shoeblood", SHOES_LAYER)
+
+ RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
+ RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(parent, COMSIG_STEP_ON_BLOOD, PROC_REF(on_step_blood))
+ RegisterSignal(parent, COMSIG_CARBON_UNEQUIP_SHOECOVER, PROC_REF(unequip_shoecover))
+ RegisterSignal(parent, COMSIG_CARBON_EQUIP_SHOECOVER, PROC_REF(equip_shoecover))
+
+/datum/component/bloodysoles/feet/update_icon()
+ . = list()
+ if(ishuman(wielder))// Monkeys get no bloody feet :(
+ if(HAS_BLOOD_DNA(wielder))
+ bloody_feet.color = get_blood_dna_color(wielder.return_blood_DNA())
+ . += bloody_feet
+ if(bloody_shoes[BLOOD_STATE_HUMAN] > 0 && !is_obscured())
+ wielder.remove_overlay(SHOES_LAYER)
+ wielder.overlays_standing[SHOES_LAYER] = bloody_feet
+ wielder.apply_overlay(SHOES_LAYER)
+ else
+ wielder.update_inv_shoes()
+
+/datum/component/bloodysoles/feet/add_parent_to_footprint(obj/effect/decal/cleanable/blood/footprints/FP)
+ if(ismonkey(wielder))
+ FP.species_types |= "monkey"
+ return
+
+ if(!ishuman(wielder))
+ FP.species_types |= "unknown"
+ return
+
+ // Find any leg of our human and add that to the footprint, instead of the default which is to just add the human type
+ for(var/X in wielder.bodyparts)
+ var/obj/item/bodypart/affecting = X
+ if(affecting.body_part == LEG_RIGHT || affecting.body_part == LEG_LEFT)
+ if(!affecting.bodypart_disabled)
+ FP.species_types |= affecting.limb_id
+ break
+
+
+/datum/component/bloodysoles/feet/is_obscured()
+ if(wielder.shoes)
+ return TRUE
+ return ITEM_SLOT_FEET in wielder.check_obscured_slots(TRUE)
+
+/datum/component/bloodysoles/feet/on_moved(datum/source, OldLoc, Dir, Forced)
+ if(wielder.num_legs < 2)
+ return
+
+ ..()
+
+/datum/component/bloodysoles/feet/on_step_blood(datum/source, obj/effect/decal/cleanable/pool)
+ if(wielder.num_legs < 2)
+ return
+
+ ..()
+
+/datum/component/bloodysoles/feet/proc/unequip_shoecover(datum/source)
+ SIGNAL_HANDLER
+
+ update_icon()
+
+/datum/component/bloodysoles/feet/proc/equip_shoecover(datum/source)
+ SIGNAL_HANDLER
+
+ update_icon()
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index 67e26595d2f0..b5cb3713a44f 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -1,62 +1,115 @@
/datum/component/caltrop
+ ///Minimum damage done when crossed
var/min_damage
+
+ ///Maximum damage done when crossed
var/max_damage
+
+ ///Probability of actually "firing", stunning and doing damage
var/probability
+
+ ///Amount of time the spike will paralyze
+ var/paralyze_duration
+
+ ///Miscelanous caltrop flags; shoe bypassing, walking interaction, silence
var/flags
+ ///The sound that plays when a caltrop is triggered.
+ var/soundfile
+
+ ///given to connect_loc to listen for something moving over target
+ var/static/list/crossed_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+
+ ///So we can update ant damage
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+
var/cooldown = 0
-/datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _flags = NONE)
- min_damage = _min_damage
- max_damage = max(_min_damage, _max_damage)
- probability = _probability
- flags = _flags
+/datum/component/caltrop/Initialize(min_damage = 0, max_damage = 0, probability = 100, paralyze_duration = 6 SECONDS, flags = NONE, soundfile = null)
+ . = ..()
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ src.min_damage = min_damage
+ src.max_damage = max(min_damage, max_damage)
+ src.probability = probability
+ src.paralyze_duration = paralyze_duration
+ src.flags = flags
+ src.soundfile = soundfile
- RegisterSignals(parent, list(COMSIG_MOVABLE_CROSSED), PROC_REF(Crossed))
+ if(ismovable(parent))
+ AddComponent(/datum/component/connect_loc_behalf, parent, crossed_connections)
+ else
+ RegisterSignal(get_turf(parent), COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
-/datum/component/caltrop/proc/Crossed(datum/source, atom/movable/AM)
- var/atom/A = parent
- if(!A.has_gravity())
+// Inherit the new values passed to the component
+/datum/component/caltrop/InheritComponent(datum/component/caltrop/new_comp, original, min_damage, max_damage, probability, flags, soundfile)
+ if(!original)
return
+ if(min_damage)
+ src.min_damage = min_damage
+ if(max_damage)
+ src.max_damage = max_damage
+ if(probability)
+ src.probability = probability
+ if(flags)
+ src.flags = flags
+ if(soundfile)
+ src.soundfile = soundfile
+/datum/component/caltrop/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ //SIGNAL_HANDLER //we're not ready
+
if(!prob(probability))
return
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
- if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
- return
+ if(!ishuman(arrived))
+ return
- if((flags & CALTROP_IGNORE_WALKERS) && H.m_intent == MOVE_INTENT_WALK)
- return
+ var/mob/living/carbon/human/digitigrade_fan = arrived
+ if(HAS_TRAIT(digitigrade_fan, TRAIT_PIERCEIMMUNE))
+ return
- var/picked_def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
- var/obj/item/bodypart/O = H.get_bodypart(picked_def_zone)
- if(!istype(O))
- return
- if(O.status == BODYPART_ROBOTIC)
- return
+ if((flags & CALTROP_IGNORE_WALKERS) && digitigrade_fan.m_intent == MOVE_INTENT_WALK)
+ return
+
+ var/picked_def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ var/obj/item/bodypart/O = digitigrade_fan.get_bodypart(picked_def_zone)
+ if(!istype(O))
+ return
+ if(O.status == BODYPART_ROBOTIC)
+ return
- var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET))
+ var/feetCover = (digitigrade_fan.wear_suit && (digitigrade_fan.wear_suit.body_parts_covered & FEET)) || (digitigrade_fan.w_uniform && (digitigrade_fan.w_uniform.body_parts_covered & FEET))
- if(!(flags & CALTROP_BYPASS_SHOES) && (H.shoes || feetCover))
- return
+ if(!(flags & CALTROP_BYPASS_SHOES) && (digitigrade_fan.shoes || feetCover))
+ return
+
+ if((digitigrade_fan.movement_type & FLYING) || digitigrade_fan.buckled)
+ return
- if((H.movement_type & FLYING) || H.buckled)
- return
+ var/damage = rand(min_damage, max_damage)
+ if(HAS_TRAIT(digitigrade_fan, TRAIT_LIGHT_STEP))
+ damage *= 0.75
+
+ digitigrade_fan.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND)
- var/damage = rand(min_damage, max_damage)
- if(HAS_TRAIT(H, TRAIT_LIGHT_STEP))
- damage *= 0.75
- H.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND)
+ if(cooldown < world.time - 10) //cooldown to avoid message spam.
+ if(!digitigrade_fan.incapacitated(ignore_restraints = TRUE))
+ digitigrade_fan.visible_message(span_danger("[digitigrade_fan] steps on [parent]."), \
+ span_userdanger("You step on [parent]!"))
+ else
+ digitigrade_fan.visible_message(span_danger("[digitigrade_fan] slides on [parent]!"), \
+ span_userdanger("You slide on [parent]!"))
- if(cooldown < world.time - 10) //cooldown to avoid message spam.
- if(!H.incapacitated(ignore_restraints = TRUE))
- H.visible_message(span_danger("[H] steps on [A]."), \
- span_userdanger("You step on [A]!"))
- else
- H.visible_message(span_danger("[H] slides on [A]!"), \
- span_userdanger("You slide on [A]!"))
+ cooldown = world.time
+ digitigrade_fan.Paralyze(paralyze_duration)
+ if(!soundfile)
+ return
+ playsound(digitigrade_fan, soundfile, 15, TRUE, -3)
- cooldown = world.time
- H.Paralyze(60)
+/datum/component/caltrop/UnregisterFromParent()
+ if(ismovable(parent))
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm
index 5dc854e6dff7..5882d93b3900 100644
--- a/code/datums/components/chasm.dm
+++ b/code/datums/components/chasm.dm
@@ -25,10 +25,21 @@
/obj/effect/dummy/crawling //yogs
))
-/datum/component/chasm/Initialize(turf/target)
- RegisterSignals(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), PROC_REF(Entered))
- RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(fish))
+/datum/component/chasm/Initialize(turf/target, mapload)
+ if(!isturf(parent))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_stopped))
+ RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_no_longer_stopped))
target_turf = target
+ RegisterSignal(parent, COMSIG_ATOM_ABSTRACT_ENTERED, PROC_REF(entered))
+ RegisterSignal(parent, COMSIG_ATOM_ABSTRACT_EXITED, PROC_REF(exited))
+ RegisterSignal(parent, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialized_on))
+ RegisterSignal(parent, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_teleport))
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(fish))
+ //allow catwalks to give the turf the CHASM_STOPPED trait before dropping stuff when the turf is changed.
+ //otherwise don't do anything because turfs and areas are initialized before movables.
+ if(!mapload)
+ addtimer(CALLBACK(src, PROC_REF(drop_stuff)), 0)
START_PROCESSING(SSobj, src) // process on create, in case stuff is still there
/datum/component/chasm/proc/Entered(datum/source, atom/movable/AM)
@@ -40,53 +51,89 @@
STOP_PROCESSING(SSobj, src)
/datum/component/chasm/proc/is_safe()
- //if anything matching this typecache is found in the chasm, we don't drop things
- var/static/list/chasm_safeties_typecache = typecacheof(list(/obj/structure/lattice/catwalk, /obj/structure/stone_tile))
+ //if anything matching this typecache is found in the chasm, we don't drop things, dripstation edit
+ var/static/list/chasm_safeties_typecache = typecacheof(list(/obj/structure/lattice/catwalk, /obj/structure/lattice/lava, /obj/structure/stone_tile))
- var/atom/parent = src.parent
- var/list/found_safeties = typecache_filter_list(parent.contents, chasm_safeties_typecache)
- for(var/obj/structure/stone_tile/S in found_safeties)
- if(S.fallen)
- LAZYREMOVE(found_safeties, S)
- return LAZYLEN(found_safeties)
+/datum/component/chasm/UnregisterFromParent()
+ storage = null
-/datum/component/chasm/proc/drop_stuff(AM)
- . = 0
- if (is_safe())
- return FALSE
+/datum/component/chasm/proc/entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ SIGNAL_HANDLER
+ drop_stuff()
- var/atom/parent = src.parent
- var/to_check = AM ? list(AM) : parent.contents
- for (var/thing in to_check)
- if (droppable(thing))
- . = 1
- INVOKE_ASYNC(src, PROC_REF(drop), thing)
+/datum/component/chasm/proc/exited(datum/source, atom/movable/exited)
+ SIGNAL_HANDLER
+ UnregisterSignal(exited, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED))
+
+/datum/component/chasm/proc/initialized_on(datum/source, atom/movable/movable, mapload)
+ SIGNAL_HANDLER
+ drop_stuff(movable)
+
+/datum/component/chasm/proc/block_teleport()
+ return COMPONENT_BLOCK_TELEPORT
+
+/datum/component/chasm/proc/on_chasm_stopped(datum/source)
+ SIGNAL_HANDLER
+ UnregisterSignal(source, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON))
+ for(var/atom/movable/movable as anything in source)
+ UnregisterSignal(movable, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED))
+
+/datum/component/chasm/proc/on_chasm_no_longer_stopped(datum/source)
+ SIGNAL_HANDLER
+ RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(entered))
+ RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(exited))
+ RegisterSignal(parent, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialized_on))
+ drop_stuff()
+
+#define CHASM_NOT_DROPPING 0
+#define CHASM_DROPPING 1
+///Doesn't drop the movable, but registers a few signals to try again if the conditions change.
+#define CHASM_REGISTER_SIGNALS 2
-/datum/component/chasm/proc/droppable(atom/movable/AM)
+/datum/component/chasm/proc/drop_stuff(atom/movable/dropped_thing)
+ if(HAS_TRAIT(parent, TRAIT_CHASM_STOPPED))
+ return
+ var/atom/atom_parent = parent
+ var/to_check = dropped_thing ? list(dropped_thing) : atom_parent.contents
+ for (var/atom/movable/thing as anything in to_check)
+ var/dropping = droppable(thing)
+ switch(dropping)
+ if(CHASM_DROPPING)
+ INVOKE_ASYNC(src, PROC_REF(drop), thing)
+ if(CHASM_REGISTER_SIGNALS)
+ RegisterSignals(thing, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED), PROC_REF(drop_stuff), TRUE)
+
+/datum/component/chasm/proc/droppable(atom/movable/dropped_thing)
+ var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
// avoid an infinite loop, but allow falling a large distance
- if(falling_atoms[AM] && falling_atoms[AM] > 30)
- return FALSE
- if(!isliving(AM) && !isobj(AM))
- return FALSE
- if(is_type_in_typecache(AM, forbidden_types) || AM.throwing || (AM.movement_type & FLOATING))
- return FALSE
+ if(falling_atoms[falling_ref] && falling_atoms[falling_ref] > 30)
+ return CHASM_NOT_DROPPING
+ if(is_type_in_typecache(dropped_thing, forbidden_types) || (!isliving(dropped_thing) && !isobj(dropped_thing)))
+ return CHASM_NOT_DROPPING
+ if(dropped_thing.throwing || (dropped_thing.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
+ return CHASM_REGISTER_SIGNALS
+
//Flies right over the chasm
- if(ismob(AM))
- var/mob/M = AM
- if(M.buckled) //middle statement to prevent infinite loops just in case!
+ if(ismob(dropped_thing))
+ var/mob/M = dropped_thing
+ if(M.buckled) //middle statement to prevent infinite loops just in case!
var/mob/buckled_to = M.buckled
if((!ismob(M.buckled) || (buckled_to.buckled != M)) && !droppable(M.buckled))
- return FALSE
- if(M.is_flying())
- return FALSE
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
- for(var/obj/item/wormhole_jaunter/J in H.get_all_contents())
- //To freak out any bystanders
- H.visible_message(span_boldwarning("[H] falls into [parent]!"))
- J.chasm_react(H)
- return FALSE
- return TRUE
+ return CHASM_REGISTER_SIGNALS
+ if(ishuman(dropped_thing))
+ var/mob/living/carbon/human/victim = dropped_thing
+ if(istype(victim.belt, /obj/item/wormhole_jaunter))
+ var/obj/item/wormhole_jaunter/jaunter = victim.belt
+ var/turf/chasm = get_turf(victim)
+ var/fall_into_chasm = jaunter.chasm_react(victim)
+ if(!fall_into_chasm)
+ chasm.visible_message(span_boldwarning("[victim] falls into the [chasm]!")) //To freak out any bystanders
+ return fall_into_chasm ? CHASM_DROPPING : CHASM_NOT_DROPPING
+ return CHASM_DROPPING
+
+#undef CHASM_NOT_DROPPING
+#undef CHASM_DROPPING
+#undef CHASM_REGISTER_SIGNALS
/datum/component/chasm/proc/drop(atom/movable/dropped_thing)
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
@@ -95,18 +142,25 @@
falling_atoms -= falling_ref
return
falling_atoms[falling_ref] = (falling_atoms[falling_ref] || 0) + 1
- var/turf/T = target_turf
+ var/turf/below_turf = target_turf
var/atom/parent = src.parent
- if(T)
+ if(falling_atoms[falling_ref] > 1)
+ return // We're already handling this
+
+ if(below_turf)
+ if(HAS_TRAIT(dropped_thing, TRAIT_CHASM_DESTROYED))
+ qdel(dropped_thing)
+ return
+
// send to the turf below
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[fall_message]"))
- T.visible_message(span_boldwarning("[dropped_thing] falls from above!"))
- dropped_thing.forceMove(T)
+ below_turf.visible_message(span_boldwarning("[dropped_thing] falls from above!"))
+ dropped_thing.forceMove(below_turf)
if(isliving(dropped_thing))
- var/mob/living/L = dropped_thing
- L.Paralyze(100)
- L.adjustBruteLoss(30)
+ var/mob/living/fallen = dropped_thing
+ fallen.Paralyze(100)
+ fallen.adjustBruteLoss(30)
falling_atoms -= falling_ref
return
@@ -114,12 +168,13 @@
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]"))
if (isliving(dropped_thing))
var/mob/living/falling_mob = dropped_thing
- falling_mob.notransform = TRUE
+ ADD_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src))
falling_mob.Paralyze(20 SECONDS)
var/oldtransform = dropped_thing.transform
var/oldcolor = dropped_thing.color
var/oldalpha = dropped_thing.alpha
+ var/oldoffset = dropped_thing.pixel_y
animate(dropped_thing, transform = matrix() - matrix(), alpha = 0, color = rgb(0, 0, 0), time = 10)
for(var/i in 1 to 5)
@@ -133,36 +188,32 @@
if(!dropped_thing || QDELETED(dropped_thing))
return
- if (!storage)
- storage = new(get_turf(parent))
- RegisterSignal(storage, COMSIG_ATOM_EXITED, PROC_REF(left_chasm))
+ if(HAS_TRAIT(dropped_thing, TRAIT_CHASM_DESTROYED))
+ qdel(dropped_thing)
+ return
+
+ if(!storage)
+ storage = (locate() in parent) || new(parent)
- if (storage.contains(dropped_thing))
+ if(storage.contains(dropped_thing))
return
dropped_thing.alpha = oldalpha
dropped_thing.color = oldcolor
dropped_thing.transform = oldtransform
+ dropped_thing.pixel_y = oldoffset
- if (dropped_thing.forceMove(storage))
- if (isliving(dropped_thing))
- RegisterSignal(dropped_thing, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
- else
+ if(!dropped_thing.forceMove(storage))
parent.visible_message(span_boldwarning("[parent] spits out [dropped_thing]!"))
dropped_thing.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
- if (isliving(dropped_thing))
+ else if(isliving(dropped_thing))
var/mob/living/fallen_mob = dropped_thing
- if(fallen_mob.stat != DEAD)
+ REMOVE_TRAIT(fallen_mob, TRAIT_NO_TRANSFORM, REF(src))
+ if (fallen_mob.stat != DEAD)
+ fallen_mob.investigate_log("has died from falling into a chasm.", INVESTIGATE_DEATHS)
fallen_mob.death(TRUE)
- fallen_mob.notransform = FALSE
fallen_mob.apply_damage(300)
- var/obj/item/bodypart/l_leg = fallen_mob.get_bodypart(BODY_ZONE_L_LEG)
- var/datum/wound/blunt/critical/l_fracture = new
- l_fracture.apply_wound(l_leg)
- var/obj/item/bodypart/r_leg = fallen_mob.get_bodypart(BODY_ZONE_R_LEG)
- var/datum/wound/blunt/critical/r_fracture = new
- r_fracture.apply_wound(r_leg)
falling_atoms -= falling_ref
@@ -177,25 +228,8 @@
SIGNAL_HANDLER
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
-#define CHASM_TRAIT "chasm trait"
-
-/**
- * Called if something comes back to life inside the pit. Expected sources are badmins and changelings.
- *
- * Arguments
- * * escapee - Lucky guy who just came back to life at the bottom of a hole.
- */
-/datum/component/chasm/proc/on_revive(mob/living/escapee)
- var/atom/parent = src.parent
- parent.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [parent]!"))
- escapee.movement_type &= FLYING
- escapee.forceMove(get_turf(parent))
- escapee.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
- escapee.movement_type &= ~FLYING
- escapee.Paralyze(20 SECONDS, TRUE)
- UnregisterSignal(escapee, COMSIG_LIVING_REVIVE)
-
-#undef CHASM_TRAIT
+///Global list needed to let fishermen with a rescue hook fish fallen mobs from any place
+GLOBAL_LIST_EMPTY(chasm_fallen_mobs)
/**
* An abstract object which is basically just a bag that the chasm puts people inside
@@ -206,6 +240,47 @@
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+/obj/effect/abstract/chasm_storage/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_SECLUDED_LOCATION, INNATE_TRAIT)
+
+/obj/effect/abstract/chasm_storage/Entered(atom/movable/arrived)
+ . = ..()
+ if(isliving(arrived))
+ RegisterSignal(arrived, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
+ GLOB.chasm_fallen_mobs += arrived
+
+/obj/effect/abstract/chasm_storage/Exited(atom/movable/gone)
+ . = ..()
+ if(isliving(gone))
+ UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
+ GLOB.chasm_fallen_mobs -= gone
+
+#define CHASM_TRAIT "chasm trait"
+/**
+ * Called if something comes back to life inside the pit. Expected sources are badmins and changelings.
+ * Ethereals should take enough damage to be smashed and not revive.
+ * Arguments
+ * escapee - Lucky guy who just came back to life at the bottom of a hole.
+ */
+/obj/effect/abstract/chasm_storage/proc/on_revive(mob/living/escapee)
+ SIGNAL_HANDLER
+ var/turf/turf = get_turf(src)
+ if(turf.GetComponent(/datum/component/chasm))
+ turf.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [turf]!"))
+ else
+ playsound(turf, 'sound/effects/bang.ogg', 50, TRUE)
+ turf.visible_message(span_boldwarning("[escapee] busts through [turf], leaping out of the chasm below"))
+ turf.ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
+ ADD_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT) //Otherwise they instantly fall back in
+ escapee.forceMove(turf)
+ escapee.throw_at(get_edge_target_turf(turf, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
+ REMOVE_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT)
+ escapee.Paralyze(20 SECONDS, TRUE)
+ UnregisterSignal(escapee, COMSIG_LIVING_REVIVE)
+
+#undef CHASM_TRAIT
+
/datum/component/chasm/proc/fish(datum/source, obj/item/I, mob/user, params)
if(!istype(I,/obj/item/fishingrod))
return
diff --git a/code/datums/components/connect_containers.dm b/code/datums/components/connect_containers.dm
new file mode 100644
index 000000000000..22efc6343599
--- /dev/null
+++ b/code/datums/components/connect_containers.dm
@@ -0,0 +1,68 @@
+/// This component behaves similar to connect_loc_behalf, but it's nested and hooks a signal onto all MOVABLES containing this atom.
+/datum/component/connect_containers
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+
+ /// An assoc list of signal -> procpath to register to the loc this object is on.
+ var/list/connections
+ /**
+ * The atom the component is tracking. The component will delete itself if the tracked is deleted.
+ * Signals will also be updated whenever it moves.
+ */
+ var/atom/movable/tracked
+
+/datum/component/connect_containers/Initialize(atom/movable/tracked, list/connections)
+ . = ..()
+ if (!ismovable(tracked))
+ return COMPONENT_INCOMPATIBLE
+
+ src.connections = connections
+ set_tracked(tracked)
+
+/datum/component/connect_containers/Destroy()
+ set_tracked(null)
+ return ..()
+
+/datum/component/connect_containers/InheritComponent(datum/component/component, original, atom/movable/tracked, list/connections)
+ // Not equivalent. Checks if they are not the same list via shallow comparison.
+ if(!compare_list(src.connections, connections))
+ stack_trace("connect_containers component attached to [parent] tried to inherit another connect_containers component with different connections")
+ return
+ if(src.tracked != tracked)
+ set_tracked(tracked)
+
+/datum/component/connect_containers/proc/set_tracked(atom/movable/new_tracked)
+ if(tracked)
+ UnregisterSignal(tracked, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
+ unregister_signals(tracked.loc)
+ tracked = new_tracked
+ if(!tracked)
+ return
+ RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals(tracked)
+
+/datum/component/connect_containers/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/component/connect_containers/proc/update_signals(atom/movable/listener)
+ if(!ismovable(listener.loc))
+ return
+
+ for(var/atom/movable/container as anything in get_nested_locs(listener))
+ RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ for(var/signal in connections)
+ parent.RegisterSignal(container, signal, connections[signal])
+
+/datum/component/connect_containers/proc/unregister_signals(atom/movable/location)
+ if(!ismovable(location))
+ return
+
+ for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ parent.UnregisterSignal(target, connections)
+
+/datum/component/connect_containers/proc/on_moved(atom/movable/listener, atom/old_loc)
+ SIGNAL_HANDLER
+ unregister_signals(old_loc)
+ update_signals(listener)
diff --git a/code/datums/components/connect_loc_behalf.dm b/code/datums/components/connect_loc_behalf.dm
new file mode 100644
index 000000000000..62701f5e39d7
--- /dev/null
+++ b/code/datums/components/connect_loc_behalf.dm
@@ -0,0 +1,69 @@
+/// This component behaves similar to connect_loc, hooking into a signal on a tracked object's turf
+/// It has the ability to react to that signal on behalf of a separate listener however
+/// This has great use, primarily for components, but it carries with it some overhead
+/// So we do it separately as it needs to hold state which is very likely to lead to bugs if it remains as an element.
+/datum/component/connect_loc_behalf
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+ /// An assoc list of signal -> procpath to register to the loc this object is on.
+ var/list/connections
+
+ var/atom/movable/tracked
+
+ var/atom/tracked_loc
+
+/datum/component/connect_loc_behalf/Initialize(atom/movable/tracked, list/connections)
+ . = ..()
+ if (!istype(tracked))
+ return COMPONENT_INCOMPATIBLE
+ src.connections = connections
+ src.tracked = tracked
+
+/datum/component/connect_loc_behalf/RegisterWithParent()
+ RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
+ RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals()
+
+/datum/component/connect_loc_behalf/UnregisterFromParent()
+ unregister_signals()
+ UnregisterSignal(tracked, list(
+ COMSIG_MOVABLE_MOVED,
+ COMSIG_QDELETING,
+ ))
+
+ tracked = null
+
+/datum/component/connect_loc_behalf/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/component/connect_loc_behalf/proc/update_signals()
+ unregister_signals()
+ //You may ask yourself, isn't this just silencing an error?
+ //The answer is yes, but there's no good cheap way to fix it
+ //What happens is the tracked object or hell the listener gets say, deleted, which makes targets[old_loc] return a null
+ //The null results in a bad index, because of course it does
+ //It's not a solvable problem though, since both actions, the destroy and the move, are sourced from the same signal send
+ //And sending a signal should be agnostic of the order of listeners
+ //So we need to either pick the order agnositic, or destroy safe
+ //And I picked destroy safe. Let's hope this is the right path!
+ if(isnull(tracked.loc))
+ return
+
+ tracked_loc = tracked.loc
+
+ for (var/signal in connections)
+ parent.RegisterSignal(tracked_loc, signal, connections[signal])
+
+/datum/component/connect_loc_behalf/proc/unregister_signals()
+ if(isnull(tracked_loc))
+ return
+
+ parent.UnregisterSignal(tracked_loc, connections)
+
+ tracked_loc = null
+
+/datum/component/connect_loc_behalf/proc/on_moved(sigtype, atom/movable/tracked, atom/old_loc)
+ SIGNAL_HANDLER
+ update_signals()
+
diff --git a/code/datums/components/connect_mob_behalf.dm b/code/datums/components/connect_mob_behalf.dm
new file mode 100644
index 000000000000..b8aa014f8101
--- /dev/null
+++ b/code/datums/components/connect_mob_behalf.dm
@@ -0,0 +1,59 @@
+/// This component behaves similar to connect_loc_behalf, but working off clients and mobs instead of loc
+/// To be clear, we hook into a signal on a tracked client's mob
+/// We retain the ability to react to that signal on a seperate listener, which makes this quite powerful
+/datum/component/connect_mob_behalf
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+ /// An assoc list of signal -> procpath to register to the mob our client "owns"
+ var/list/connections
+ /// The master client we're working with
+ var/client/tracked
+ /// The mob we're currently tracking
+ var/mob/tracked_mob
+
+/datum/component/connect_mob_behalf/Initialize(client/tracked, list/connections)
+ . = ..()
+ if (!istype(tracked))
+ return COMPONENT_INCOMPATIBLE
+ src.connections = connections
+ src.tracked = tracked
+
+/datum/component/connect_mob_behalf/RegisterWithParent()
+ RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals()
+
+/datum/component/connect_mob_behalf/UnregisterFromParent()
+ unregister_signals()
+ UnregisterSignal(tracked, COMSIG_QDELETING)
+
+ tracked = null
+ tracked_mob = null
+
+/datum/component/connect_mob_behalf/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/component/connect_mob_behalf/proc/update_signals()
+ unregister_signals()
+ // Yes this is a runtime silencer
+ // We could be in a position where logout is sent to two things, one thing intercepts it, then deletes the client's new mob
+ // It's rare, and the same check in connect_loc_behalf is more fruitful, but it's still worth doing
+ if(QDELETED(tracked?.mob))
+ return
+ tracked_mob = tracked.mob
+ RegisterSignal(tracked_mob, COMSIG_MOB_LOGOUT, PROC_REF(on_logout))
+ for (var/signal in connections)
+ parent.RegisterSignal(tracked_mob, signal, connections[signal])
+
+/datum/component/connect_mob_behalf/proc/unregister_signals()
+ if(isnull(tracked_mob))
+ return
+
+ parent.UnregisterSignal(tracked_mob, connections)
+ UnregisterSignal(tracked_mob, COMSIG_MOB_LOGOUT)
+
+ tracked_mob = null
+
+/datum/component/connect_mob_behalf/proc/on_logout(mob/source)
+ SIGNAL_HANDLER
+ update_signals()
diff --git a/code/datums/components/construction.dm b/code/datums/components/construction.dm
index 6da73b4f8f16..f8ff095c1b62 100644
--- a/code/datums/components/construction.dm
+++ b/code/datums/components/construction.dm
@@ -1,10 +1,3 @@
-#define FORWARD 1
-#define BACKWARD -1
-
-#define ITEM_DELETE "delete"
-#define ITEM_MOVE_INSIDE "move_inside"
-
-
/datum/component/construction
var/list/steps
var/result
@@ -15,7 +8,7 @@
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(action))
update_parent(index)
diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm
index d5cf7fb89133..0a9521646b6f 100644
--- a/code/datums/components/crafting/crafting.dm
+++ b/code/datums/components/crafting/crafting.dm
@@ -76,8 +76,6 @@
.["other"] = list()
.["instances"] = list()
for(var/obj/item/item in get_environment(a, blacklist))
- if(item.status_traits && HAS_TRAIT(item, TRAIT_NODROP) || item.flags_1 & HOLOGRAM_1)
- continue
LAZYADDASSOCLIST(.["instances"], item.type, item)
if(isstack(item))
var/obj/item/stack/stack = item
diff --git a/code/datums/components/curse_of_hunger.dm b/code/datums/components/curse_of_hunger.dm
index dfd36bddcd66..a9a7f56955c3 100644
--- a/code/datums/components/curse_of_hunger.dm
+++ b/code/datums/components/curse_of_hunger.dm
@@ -31,13 +31,13 @@
/datum/component/curse_of_hunger/RegisterWithParent()
. = ..()
var/obj/item/cursed_item = parent
- RegisterSignal(cursed_item, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(cursed_item, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(cursed_item, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
/datum/component/curse_of_hunger/UnregisterFromParent()
. = ..()
UnregisterSignal(parent, list(
- COMSIG_PARENT_EXAMINE,
+ COMSIG_ATOM_EXAMINE,
COMSIG_ITEM_EQUIPPED,
COMSIG_ITEM_DROPPED,
))
diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm
deleted file mode 100644
index b601ff1475bb..000000000000
--- a/code/datums/components/decal.dm
+++ /dev/null
@@ -1,76 +0,0 @@
-/datum/component/decal
- dupe_mode = COMPONENT_DUPE_ALLOWED
- can_transfer = TRUE
- var/cleanable
- var/description
- var/mutable_appearance/pic
-
- var/first_dir // This only stores the dir arg from init
-
-/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable=FALSE, _color, _layer=TURF_LAYER, _description, _alpha=255)
- if(!isatom(parent) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha))
- return COMPONENT_INCOMPATIBLE
- first_dir = _dir
- description = _description
- cleanable = _cleanable
-
- apply()
-
-/datum/component/decal/RegisterWithParent()
- if(first_dir)
- RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(rotate_react))
- if(cleanable != FALSE)
- RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_react))
- if(description)
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
-
-/datum/component/decal/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
-
-/datum/component/decal/Destroy()
- remove()
- return ..()
-
-/datum/component/decal/PreTransfer()
- remove()
-
-/datum/component/decal/PostTransfer()
- remove()
- apply()
-
-/datum/component/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha)
- if(!_icon || !_icon_state)
- return FALSE
- // It has to be made from an image or dir breaks because of a byond bug
- var/temp_image = image(_icon, null, _icon_state, _layer, _dir)
- pic = new(temp_image)
- pic.color = _color
- pic.alpha = _alpha
- return TRUE
-
-/datum/component/decal/proc/apply(atom/thing)
- var/atom/master = thing || parent
- master.add_overlay(pic, TRUE)
- if(isitem(master))
- addtimer(CALLBACK(master, TYPE_PROC_REF(/obj/item, update_slot_icon)), 0, TIMER_UNIQUE)
-
-/datum/component/decal/proc/remove(atom/thing)
- var/atom/master = thing || parent
- master.cut_overlay(pic, TRUE)
- if(isitem(master))
- addtimer(CALLBACK(master, TYPE_PROC_REF(/obj/item, update_slot_icon)), 0, TIMER_UNIQUE)
-
-/datum/component/decal/proc/rotate_react(datum/source, old_dir, new_dir)
- if(old_dir == new_dir)
- return
- remove()
- pic.dir = turn(pic.dir, dir2angle(old_dir) - dir2angle(new_dir))
- apply()
-
-/datum/component/decal/proc/clean_react(datum/source, clean_types)
- if(clean_types & cleanable)
- qdel(src)
- return TRUE
-
-/datum/component/decal/proc/examine(datum/source, mob/user, list/examine_list)
- examine_list += description
diff --git a/code/datums/components/decals/blood.dm b/code/datums/components/decals/blood.dm
deleted file mode 100644
index a0a97af3982c..000000000000
--- a/code/datums/components/decals/blood.dm
+++ /dev/null
@@ -1,39 +0,0 @@
-/datum/component/decal/blood
- dupe_mode = COMPONENT_DUPE_UNIQUE
-
-/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_TYPE_BLOOD, _color, _layer=ABOVE_OBJ_LAYER)
- if(!isitem(parent))
- return COMPONENT_INCOMPATIBLE
- . = ..()
- RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, PROC_REF(get_examine_name))
-
-/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color)
- var/obj/item/I = parent
- if(!_icon)
- _icon = 'icons/effects/blood.dmi'
- if(!_icon_state)
- _icon_state = "itemblood"
- var/icon = initial(I.icon)
- var/icon_state = initial(I.icon_state)
- if(!icon || !icon_state)
- // It's something which takes on the look of other items, probably
- icon = I.icon
- icon_state = I.icon_state
- var/static/list/blood_splatter_appearances = list()
- //try to find a pre-processed blood-splatter. otherwise, make a new one
- var/index = "[REF(icon)]-[icon_state]"
- pic = blood_splatter_appearances[index]
-
- if(!pic)
- var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
- blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
- blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
- pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state))
- blood_splatter_appearances[index] = pic
- return TRUE
-
-/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
- var/atom/A = parent
- override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
- override[EXAMINE_POSITION_BEFORE] = " blood-stained "
- return COMPONENT_EXNAME_CHANGED
diff --git a/code/datums/components/forensics.dm b/code/datums/components/forensics.dm
index b9d618d4092d..6730dbe28325 100644
--- a/code/datums/components/forensics.dm
+++ b/code/datums/components/forensics.dm
@@ -10,11 +10,11 @@
var/list/scents //assoc dna = carbon mob
/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL.
- fingerprints = fingerprints | F.fingerprints
- hiddenprints = hiddenprints | F.hiddenprints
- blood_DNA = blood_DNA | F.blood_DNA
- fibers = fibers | F.fibers
- scents = scents | F.scents
+ fingerprints = LAZY_LISTS_OR(fingerprints, F.fingerprints)
+ hiddenprints = LAZY_LISTS_OR(hiddenprints, F.hiddenprints)
+ blood_DNA = LAZY_LISTS_OR(blood_DNA, F.blood_DNA)
+ fibers = LAZY_LISTS_OR(fibers, F.fibers)
+ check_blood()
check_blood()
return ..()
@@ -48,10 +48,14 @@
/datum/component/forensics/proc/wipe_blood_DNA()
blood_DNA = null
- if(isitem(parent))
- qdel(parent.GetComponent(/datum/component/decal/blood))
return TRUE
+/datum/component/forensics/proc/is_bloody(datum/source, clean_types)
+ if(!isitem(parent))
+ return FALSE
+
+ return length(blood_DNA) > 0
+
/datum/component/forensics/proc/wipe_fibers()
fibers = null
return TRUE
@@ -83,7 +87,7 @@
if(!iscameramob(M))
return
if(isaicamera(M))
- var/mob/camera/aiEye/ai_camera = M
+ var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
@@ -156,7 +160,7 @@
if(!iscameramob(M))
return
if(isaicamera(M))
- var/mob/camera/aiEye/ai_camera = M
+ var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
@@ -193,7 +197,7 @@
return
if(!length(blood_DNA))
return
- parent.LoadComponent(/datum/component/decal/blood)
+ parent.AddElement(/datum/element/decal/blood, _color = get_blood_dna_color(blood_DNA))
//yog code for olfaction
/datum/component/forensics/proc/wipe_scents()
diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm
new file mode 100644
index 000000000000..d5072e7e7129
--- /dev/null
+++ b/code/datums/components/gps.dm
@@ -0,0 +1,182 @@
+///Global GPS_list. All GPS components get saved in here for easy reference.
+GLOBAL_LIST_EMPTY(GPS_list)
+///GPS component. Atoms that have this show up on gps. Pretty simple stuff.
+/datum/component/gps
+ var/gpstag = "COM0"
+ var/tracking = TRUE
+ var/emped = FALSE
+
+/datum/component/gps/Initialize(_gpstag = "COM0")
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ gpstag = _gpstag
+ GLOB.GPS_list += src
+
+/datum/component/gps/Destroy()
+ GLOB.GPS_list -= src
+ return ..()
+
+/datum/component/gps/kheiral_cuffs
+
+/datum/component/gps/kheiral_cuffs/Initialize(_gpstag = "COM0")
+ . = ..()
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(deactivate_kheiral_cuffs))
+
+/datum/component/gps/kheiral_cuffs/proc/deactivate_kheiral_cuffs(datum/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
+///GPS component subtype. Only gps/item's can be used to open the UI.
+/datum/component/gps/item
+ var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user.
+ var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown
+ /// UI state of GPS, altering when it can be used.
+ var/datum/ui_state/state = null
+
+/datum/component/gps/item/Initialize(_gpstag = "COM0", emp_proof = FALSE, state = null, overlay_state = "working")
+ . = ..()
+ if(. == COMPONENT_INCOMPATIBLE || !isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ if(isnull(state))
+ state = GLOB.default_state
+ src.state = state
+
+ var/atom/A = parent
+ if(overlay_state)
+ A.add_overlay(overlay_state)
+ A.name = "[initial(A.name)] ([gpstag])"
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(interact))
+ if(!emp_proof)
+ RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(on_AltClick))
+
+///Called on COMSIG_ITEM_ATTACK_SELF
+/datum/component/gps/item/proc/interact(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(user)
+ INVOKE_ASYNC(src, PROC_REF(ui_interact), user)
+
+///Called on COMSIG_ATOM_EXAMINE
+/datum/component/gps/item/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += span_notice("Alt-click to switch it [tracking ? "off":"on"].")
+
+///Called on COMSIG_ATOM_EMP_ACT
+/datum/component/gps/item/proc/on_emp_act(datum/source, severity, protection)
+ SIGNAL_HANDLER
+ if(protection & EMP_PROTECT_SELF)
+ return
+ emped = TRUE
+ var/atom/A = parent
+ A.cut_overlay("working")
+ A.add_overlay("emp")
+ addtimer(CALLBACK(src, PROC_REF(reboot)), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early
+ SStgui.close_uis(src) //Close the UI control if it is open.
+
+///Restarts the GPS after getting turned off by an EMP.
+/datum/component/gps/item/proc/reboot()
+ emped = FALSE
+ var/atom/A = parent
+ A.cut_overlay("emp")
+ A.add_overlay("working")
+
+///Calls toggletracking
+/datum/component/gps/item/proc/on_AltClick(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ toggletracking(user)
+
+///Toggles the tracking for the gps
+/datum/component/gps/item/proc/toggletracking(mob/user)
+ if(!user.canUseTopic(parent))
+ return //user not valid to use gps
+ if(emped)
+ to_chat(user, span_warning("It's busted!"))
+ return
+ var/atom/A = parent
+ if(tracking)
+ A.cut_overlay("working")
+ to_chat(user, span_notice("[parent] is no longer tracking, or visible to other GPS devices."))
+ tracking = FALSE
+ else
+ A.add_overlay("working")
+ to_chat(user, span_notice("[parent] is now tracking, and visible to other GPS devices."))
+ tracking = TRUE
+
+/datum/component/gps/item/ui_interact(mob/user, datum/tgui/ui)
+ if(emped)
+ to_chat(user, span_hear("[parent] fizzles weakly."))
+ return
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Gps")
+ ui.open()
+ ui.set_autoupdate(updating)
+
+/datum/component/gps/item/ui_state(mob/user)
+ return state
+
+/datum/component/gps/item/ui_data(mob/user)
+ var/list/data = list()
+ data["power"] = tracking
+ data["tag"] = gpstag
+ data["updating"] = updating
+ data["globalmode"] = global_mode
+ if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed
+ return data
+
+ var/turf/curr = get_turf(parent)
+ data["currentArea"] = "[get_area_name(curr, TRUE)]"
+ data["currentCoords"] = "[curr.x], [curr.y], [curr.z]"
+
+ var/list/signals = list()
+ data["signals"] = list()
+
+ for(var/gps in GLOB.GPS_list)
+ var/datum/component/gps/G = gps
+ if(G.emped || !G.tracking || G == src)
+ continue
+ var/turf/pos = get_turf(G.parent)
+ if(!pos || !global_mode && pos.z != curr.z)
+ continue
+ var/list/signal = list()
+ signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS
+ signal["coords"] = "[pos.x], [pos.y], [pos.z]"
+ if(pos.z == curr.z) //Distance/Direction calculations for same z-level only
+ signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs
+ signal["degrees"] = round(get_angle(curr, pos)) //0-360 degree directional bearing, for more precision.
+ signals += list(signal) //Add this signal to the list of signals
+ data["signals"] = signals
+ return data
+
+/datum/component/gps/item/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("rename")
+ var/atom/parentasatom = parent
+ var/a = tgui_input_text(usr, "Enter the desired tag", "GPS Tag", gpstag, 20)
+
+ if (!a)
+ return
+
+ gpstag = a
+ . = TRUE
+ usr.log_message("renamed [parentasatom] to \"[initial(parentasatom.name)] ([gpstag])\".", LOG_GAME)
+ parentasatom.name = "[initial(parentasatom.name)] ([gpstag])"
+
+ if("power")
+ toggletracking(usr)
+ . = TRUE
+ if("updating")
+ updating = !updating
+ . = TRUE
+ if("globalmode")
+ global_mode = !global_mode
+ . = TRUE
diff --git a/code/datums/components/grillable.dm b/code/datums/components/grillable.dm
index 09178440a4bc..0f1ef30d2cb9 100644
--- a/code/datums/components/grillable.dm
+++ b/code/datums/components/grillable.dm
@@ -26,7 +26,7 @@
src.use_large_steam_sprite = use_large_steam_sprite
RegisterSignal(parent, COMSIG_ITEM_GRILLED, PROC_REF(OnGrill))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(OnExamine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(OnExamine))
///Ran every time an item is grilled by something
/datum/component/grillable/proc/OnGrill(datum/source, atom/used_grill, delta_time = 1)
diff --git a/code/datums/components/heirloom.dm b/code/datums/components/heirloom.dm
index 652cd4472072..8140c9423766 100644
--- a/code/datums/components/heirloom.dm
+++ b/code/datums/components/heirloom.dm
@@ -9,7 +9,7 @@
owner = new_owner
family_name = new_family_name
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine))
/datum/component/heirloom/proc/examine(datum/source, mob/user, list/examine_list)
if(user.mind == owner)
diff --git a/code/datums/components/hide_highest_offset.dm b/code/datums/components/hide_highest_offset.dm
new file mode 100644
index 000000000000..eb5b5660db0f
--- /dev/null
+++ b/code/datums/components/hide_highest_offset.dm
@@ -0,0 +1,24 @@
+/// Component that takes a plane master, and will hide it if it's the highest offset of its kind
+/// This allows us to not show PMs to clients if they're not actively doing anything
+/datum/component/plane_hide_highest_offset
+
+/datum/component/plane_hide_highest_offset/Initialize()
+ if(!istype(parent, /atom/movable/screen/plane_master))
+ return
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_increase))
+ offset_increase(SSmapping.max_plane_offset)
+
+/datum/component/plane_hide_highest_offset/proc/on_offset_increase(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ offset_increase(new_offset)
+
+/datum/component/plane_hide_highest_offset/proc/offset_increase(new_offset)
+ var/atom/movable/screen/plane_master/plane_parent = parent
+ var/mob/our_mob = plane_parent.home?.our_hud?.mymob
+ var/our_offset = plane_parent.offset
+ if(!our_mob)
+ return
+ if(our_offset == new_offset)
+ plane_parent.hide_plane(our_mob)
+ else if(plane_parent.force_hidden)
+ plane_parent.unhide_plane(our_mob)
diff --git a/code/datums/components/magnetic_catch.dm b/code/datums/components/magnetic_catch.dm
index 52c417981d7d..4034c52c6742 100644
--- a/code/datums/components/magnetic_catch.dm
+++ b/code/datums/components/magnetic_catch.dm
@@ -1,7 +1,7 @@
/datum/component/magnetic_catch/Initialize()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine))
if(ismovable(parent))
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(crossed_react))
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, PROC_REF(uncrossed_react))
diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm
index 02e801e9c5f1..0c462c5798d1 100644
--- a/code/datums/components/material_container.dm
+++ b/code/datums/components/material_container.dm
@@ -39,7 +39,7 @@
after_insert = _after_insert
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(OnAttackBy))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(OnExamine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(OnExamine))
for(var/mat in mat_list) //Make the assoc list ref | amount
var/datum/material/M = getmaterialref(mat) || mat
diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm
index 51e0b7691a1a..5069b6189352 100644
--- a/code/datums/components/mind_linker.dm
+++ b/code/datums/components/mind_linker.dm
@@ -120,7 +120,7 @@
new_link.Grant(to_link)
linked_mobs[to_link] = new_link
- RegisterSignals(to_link, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED), PROC_REF(unlink_mob))
+ RegisterSignals(to_link, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING, COMSIG_MINDSHIELD_IMPLANTED), PROC_REF(unlink_mob))
return TRUE
@@ -139,7 +139,7 @@
to_chat(to_unlink, span_warning(unlink_message))
INVOKE_ASYNC(post_unlink_callback, TYPE_PROC_REF(/datum/callback/, Invoke), to_unlink)
- UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED))
+ UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING, COMSIG_MINDSHIELD_IMPLANTED))
var/datum/action/innate/linked_speech/old_link = linked_mobs[to_unlink]
linked_mobs -= to_unlink
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index 330f077274b3..804f0158527f 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -176,6 +176,7 @@
screen_obj.color = "#2eeb9a"
break
+/*
/datum/component/mood/process(delta_time) //Called on SSmood process
var/mob/living/owner = parent
if(!owner)
@@ -259,6 +260,7 @@
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
sanity_level = 1
update_mood_icon()
+*/
/datum/component/mood/proc/setInsanityEffect(newval)
if(newval == insanity_effect)
@@ -314,7 +316,7 @@
screen_obj_sanity = new
hud.infodisplay += screen_obj
hud.infodisplay += screen_obj_sanity
- RegisterSignal(hud, COMSIG_PARENT_QDELETING, PROC_REF(unmodify_hud))
+ RegisterSignal(hud, COMSIG_QDELETING, PROC_REF(unmodify_hud))
RegisterSignal(screen_obj, COMSIG_CLICK, PROC_REF(hud_click))
/datum/component/mood/proc/unmodify_hud(datum/source)
diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm
index 4309808910eb..06a7fdb6f493 100644
--- a/code/datums/components/overlay_lighting.dm
+++ b/code/datums/components/overlay_lighting.dm
@@ -30,6 +30,8 @@
var/lumcount_range = 0
///How much this light affects the dynamic_lumcount of turfs.
var/lum_power = 0.5
+ ///Transparency value.
+ var/set_alpha = 0
///For light sources that can be turned on and off.
var/overlay_lighting_flags = NONE
@@ -81,9 +83,11 @@
. = ..()
visible_mask = new(src)
+ SET_PLANE_EXPLICIT(visible_mask, O_LIGHTING_VISUAL_PLANE, movable_parent)
if(is_directional)
directional = TRUE
cone = new(src)
+ SET_PLANE_EXPLICIT(cone, O_LIGHTING_VISUAL_PLANE, movable_parent)
cone.transform = cone.transform.Translate(-32, -32)
set_direction(movable_parent.dir)
if(is_beam)
@@ -209,14 +213,14 @@
parent_attached_to = new_parent_attached_to
if(.)
var/atom/movable/old_parent_attached_to = .
- UnregisterSignal(old_parent_attached_to, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
+ UnregisterSignal(old_parent_attached_to, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
if(old_parent_attached_to == current_holder)
- RegisterSignal(old_parent_attached_to, COMSIG_PARENT_QDELETING, PROC_REF(on_holder_qdel))
+ RegisterSignal(old_parent_attached_to, COMSIG_QDELETING, PROC_REF(on_holder_qdel))
RegisterSignal(old_parent_attached_to, COMSIG_MOVABLE_MOVED, PROC_REF(on_holder_moved))
if(parent_attached_to)
if(parent_attached_to == current_holder)
- UnregisterSignal(current_holder, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
- RegisterSignal(parent_attached_to, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_attached_to_qdel))
+ UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
+ RegisterSignal(parent_attached_to, COMSIG_QDELETING, PROC_REF(on_parent_attached_to_qdel))
RegisterSignal(parent_attached_to, COMSIG_MOVABLE_MOVED, PROC_REF(on_parent_attached_to_moved))
check_holder()
@@ -227,7 +231,7 @@
return
if(current_holder)
if(current_holder != parent && current_holder != parent_attached_to)
- UnregisterSignal(current_holder, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
+ UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
if(directional)
UnregisterSignal(current_holder, COMSIG_ATOM_DIR_CHANGE)
if(overlay_lighting_flags & LIGHTING_ON)
@@ -237,7 +241,7 @@
clean_old_turfs()
return
if(new_holder != parent && new_holder != parent_attached_to)
- RegisterSignal(new_holder, COMSIG_PARENT_QDELETING, PROC_REF(on_holder_qdel))
+ RegisterSignal(new_holder, COMSIG_QDELETING, PROC_REF(on_holder_qdel))
if(overlay_lighting_flags & LIGHTING_ON)
RegisterSignal(new_holder, COMSIG_MOVABLE_MOVED, PROC_REF(on_holder_moved))
if(directional)
@@ -270,7 +274,7 @@
SIGNAL_HANDLER
if(QDELETED(current_holder))
return
- UnregisterSignal(current_holder, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
+ UnregisterSignal(current_holder, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
if(directional)
UnregisterSignal(current_holder, COMSIG_ATOM_DIR_CHANGE)
set_holder(null)
@@ -300,9 +304,9 @@
if(current_holder && overlay_lighting_flags & LIGHTING_ON)
current_holder.underlays -= visible_mask
current_holder.underlays -= cone
- visible_mask.plane = O_LIGHTING_VISUAL_PLANE
+ SET_PLANE_EXPLICIT(visible_mask, O_LIGHTING_VISUAL_PLANE, source)
if(cone)
- cone.plane = O_LIGHTING_VISUAL_PLANE
+ SET_PLANE_EXPLICIT(cone, O_LIGHTING_VISUAL_PLANE, source)
if(current_holder && overlay_lighting_flags & LIGHTING_ON)
current_holder.underlays += visible_mask
current_holder.underlays += cone
@@ -310,7 +314,7 @@
///Called when the current_holder is qdeleted, to remove the light effect.
/datum/component/overlay_lighting/proc/on_parent_attached_to_qdel(atom/movable/source, force)
SIGNAL_HANDLER
- UnregisterSignal(parent_attached_to, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED))
+ UnregisterSignal(parent_attached_to, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
if(directional)
UnregisterSignal(parent_attached_to, COMSIG_ATOM_DIR_CHANGE)
if(parent_attached_to == current_holder)
diff --git a/code/datums/components/phylactery.dm b/code/datums/components/phylactery.dm
index 7b45ee843e72..4d6da95dab15 100644
--- a/code/datums/components/phylactery.dm
+++ b/code/datums/components/phylactery.dm
@@ -43,7 +43,7 @@
src.stun_per_resurrection = stun_per_resurrection
src.phylactery_color = phylactery_color
- RegisterSignal(lich_mind, COMSIG_PARENT_QDELETING, PROC_REF(on_lich_mind_lost))
+ RegisterSignal(lich_mind, COMSIG_QDELETING, PROC_REF(on_lich_mind_lost))
RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(check_if_lich_died))
var/obj/obj_parent = parent
@@ -52,7 +52,7 @@
obj_parent.add_atom_colour(phylactery_color, ADMIN_COLOUR_PRIORITY)
obj_parent.AddComponent(/datum/component/stationloving, FALSE, TRUE)
- RegisterSignal(obj_parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(obj_parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
/datum/component/phylactery/Destroy()
var/obj/obj_parent = parent
@@ -62,7 +62,7 @@
// Stationloving items should really never be made a phylactery so I feel safe in doing this
qdel(obj_parent.GetComponent(/datum/component/stationloving))
- UnregisterSignal(obj_parent, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(obj_parent, COMSIG_ATOM_EXAMINE)
UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH)
// Sweep up any revive signals left on the mind's current
UnregisterSignal(lich_mind.current, COMSIG_LIVING_REVIVE)
@@ -71,7 +71,7 @@
return ..()
/**
- * Signal proc for [COMSIG_PARENT_EXAMINE].
+ * Signal proc for [COMSIG_ATOM_EXAMINE].
*
* Gives some flavor for the phylactery on examine.
*/
@@ -93,7 +93,7 @@
examine_list += span_green("A terrible aura surrounds this item. Its very existence is offensive to life itself...")
/**
- * Signal proc for [COMSIG_PARENT_QDELETING] registered on the lich's mind.
+ * Signal proc for [COMSIG_QDELETING] registered on the lich's mind.
*
* Minds shouldn't be getting deleted but if for some ungodly reason
* the lich'd mind is deleted our component should go with it, as
diff --git a/code/datums/components/plumbing/plumbing.dm b/code/datums/components/plumbing/plumbing.dm
index 6c99bbbee41f..00df1505d355 100644
--- a/code/datums/components/plumbing/plumbing.dm
+++ b/code/datums/components/plumbing/plumbing.dm
@@ -19,7 +19,7 @@
reagents = AM.reagents
turn_connects = _turn_connects
- RegisterSignals(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PARENT_PREQDELETED), PROC_REF(disable))
+ RegisterSignals(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PREQDELETED), PROC_REF(disable))
if(start)
start()
diff --git a/code/datums/components/radioactive.dm b/code/datums/components/radioactive.dm
index fc6483de7668..36d7660de1b7 100644
--- a/code/datums/components/radioactive.dm
+++ b/code/datums/components/radioactive.dm
@@ -17,7 +17,7 @@
hl3_release_date = _half_life
can_contaminate = _can_contaminate
if(istype(parent, /atom))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(rad_examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(rad_examine))
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(rad_clean))
if(istype(parent, /obj/item))
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(rad_attack))
diff --git a/code/datums/components/religious_tool.dm b/code/datums/components/religious_tool.dm
index d107f18dd967..f7bac6e102fa 100644
--- a/code/datums/components/religious_tool.dm
+++ b/code/datums/components/religious_tool.dm
@@ -28,10 +28,10 @@
/datum/component/religious_tool/RegisterWithParent()
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(AttemptActions))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
/datum/component/religious_tool/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
+ UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_EXAMINE))
/**
* Sets the easy access variable to the global if it exists.
diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm
index f36d48831275..293e20aa9582 100644
--- a/code/datums/components/riding.dm
+++ b/code/datums/components/riding.dm
@@ -1,24 +1,61 @@
+/**
+ * This is the riding component, which is applied to a movable atom by the [ridable element][/datum/element/ridable] when a mob is successfully buckled to said movable.
+ *
+ * This component lives for as long as at least one mob is buckled to the parent. Once all mobs are unbuckled, the component is deleted, until another mob is buckled in
+ * and we make a new riding component, so on and so forth until the sun explodes.
+ */
+
/datum/component/riding
- var/last_vehicle_move = 0 //used for move delays
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
var/last_move_diagonal = FALSE
- var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower
+ ///tick delay between movements, lower = faster, higher = slower
+ var/vehicle_move_delay = 2
+
+ var/last_vehicle_move = 0 //used for move delays
+
+
+ /**
+ * If the driver needs a certain item in hand (or inserted, for vehicles) to drive this. For vehicles, this must be duplicated on the actual vehicle object in their
+ * [/obj/vehicle/var/key_type] variable because the vehicle objects still have a few special checks/functions of their own I'm not porting over to the riding component
+ * quite yet. Make sure if you define it on the vehicle, you define it here too.
+ */
var/keytype
var/slowed = FALSE
var/slowvalue = 1
- var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one.
- var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change.
- var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py)
+ /// position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one.
+ var/list/riding_offsets = list()
+ /// ["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change.
+ var/list/directional_vehicle_layers = list()
+ /// same as above but instead of layer you have a list(px, py)
+ var/list/directional_vehicle_offsets = list()
+ /// allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
var/list/allowed_turf_typecache
- var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
- var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more.
+ /// allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
+ var/list/forbid_turf_typecache
+ /// We don't need roads where we're going if this is TRUE, allow normal movement in space tiles
var/override_allow_spacemove = FALSE
+ /// can anyone other than the rider unbuckle the rider?
+ var/can_force_unbuckle = TRUE
+
+ /**
+ * Ride check flags defined for the specific riding component types, so we know if we need arms, legs, or whatever.
+ * Takes additional flags from the ridable element and the buckle proc (buckle_mob_flags) for riding cyborgs/humans in case we need to reserve arms
+ */
+ var/ride_check_flags = NONE
+ /// For telling someone they can't drive
+ COOLDOWN_DECLARE(message_cooldown)
+ /// For telling someone they can't drive
+ COOLDOWN_DECLARE(vehicle_move_cooldown)
+
+
+ var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more.
var/drive_verb = "drive"
var/ride_check_rider_incapacitated = FALSE
var/ride_check_rider_restrained = FALSE
var/ride_check_ridden_incapacitated = FALSE
- var/parent_initial_layer
var/del_on_unbuckle_all = FALSE
@@ -28,10 +65,10 @@
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, PROC_REF(vehicle_mob_buckle))
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, PROC_REF(vehicle_mob_unbuckle))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(vehicle_moved))
+ RegisterSignal(parent, COMSIG_BUCKLED_CAN_Z_MOVE, PROC_REF(riding_can_z_move))
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
var/atom/movable/AM = parent
- AM.layer = parent_initial_layer
restore_position(M)
unequip_buckle_inhands(M)
M.updating_glide_size = TRUE
@@ -44,18 +81,18 @@
var/atom/movable/AM = parent
M.set_glide_size(AM.glide_size)
M.updating_glide_size = FALSE
- parent_initial_layer = AM.layer
handle_vehicle_offsets()
if(AM.movement_type & FLYING)
M.movement_type |= FLYING
+/// Some ridable atoms may want to only show on top of the rider in certain directions, like wheelchairs
/datum/component/riding/proc/handle_vehicle_layer(dir)
var/atom/movable/AM = parent
var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER)
. = defaults["[dir]"]
if(directional_vehicle_layers["[dir]"])
. = directional_vehicle_layers["[dir]"]
- if(isnull(.)) //you can set it to null to not change it.
+ if(isnull(.)) //you can set it to null to not change it.
. = AM.layer
AM.layer = .
@@ -147,11 +184,14 @@
//BUCKLE HOOKS
/datum/component/riding/proc/restore_position(mob/living/buckled_mob)
- if(buckled_mob)
- buckled_mob.pixel_x = 0
- buckled_mob.pixel_y = 0
- if(buckled_mob.client)
- buckled_mob.client.view_size.resetToDefault()
+ if(isnull(buckled_mob))
+ return
+ buckled_mob.pixel_x = buckled_mob.base_pixel_x
+ buckled_mob.pixel_y = buckled_mob.base_pixel_y
+ var/atom/source = parent
+ SET_PLANE_EXPLICIT(buckled_mob, initial(buckled_mob.plane), source)
+ if(buckled_mob.client)
+ buckled_mob.client.view_size.resetToDefault()
//MOVEMENT
/datum/component/riding/proc/turf_check(turf/next, turf/current)
@@ -244,21 +284,23 @@
/datum/component/riding/human/handle_vehicle_layer(dir)
var/atom/movable/AM = parent
- if(AM.buckled_mobs && AM.buckled_mobs.len)
- for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
- M.layer = MOB_LAYER
- if(!AM.buckle_lying)
- if(dir == SOUTH)
- AM.layer = ABOVE_MOB_LAYER
- else
- AM.layer = OBJ_LAYER
- else
- if(dir == NORTH)
- AM.layer = OBJ_LAYER
- else
- AM.layer = ABOVE_MOB_LAYER
- else
+ if(!AM.buckled_mobs || !AM.buckled_mobs.len)
AM.layer = MOB_LAYER
+ return
+
+ for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
+ M.layer = MOB_LAYER
+
+ if(!AM.buckle_lying) // rider is vertical, must be piggybacking
+ if(dir == SOUTH)
+ AM.layer = MOB_ABOVE_PIGGYBACK_LAYER
+ else
+ AM.layer = MOB_BELOW_PIGGYBACK_LAYER
+ else // laying flat, we must be firemanning the rider
+ if(dir == NORTH)
+ AM.layer = MOB_BELOW_PIGGYBACK_LAYER
+ else
+ AM.layer = MOB_ABOVE_PIGGYBACK_LAYER
/datum/component/riding/human/get_offsets(pass_index)
var/mob/living/carbon/human/H = parent
@@ -273,6 +315,19 @@
user.Knockdown(60)
user.visible_message(span_warning("[AM] pushes [user] off of [AM.p_them()]!"))
+/datum/component/riding/human/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider)
+ if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS))
+ return COMPONENT_RIDDEN_ALLOW_Z_MOVE
+ // if(!can_be_driven)
+ // if(z_move_flags & ZMOVE_FEEDBACK)
+ // to_chat(rider, span_warning("[movable_parent] cannot be driven around. Unbuckle from [movable_parent.p_them()] first."))
+ // return COMPONENT_RIDDEN_STOP_Z_MOVE
+ if(!ride_check(rider, FALSE))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider, span_warning("You're unable to ride [movable_parent] right now!"))
+ return COMPONENT_RIDDEN_STOP_Z_MOVE
+ return COMPONENT_RIDDEN_ALLOW_Z_MOVE
+
/datum/component/riding/cyborg
del_on_unbuckle_all = TRUE
@@ -296,14 +351,11 @@
return
/datum/component/riding/cyborg/handle_vehicle_layer(dir)
- var/atom/movable/AM = parent
- if(AM.buckled_mobs && AM.buckled_mobs.len)
- if(dir == SOUTH)
- AM.layer = ABOVE_MOB_LAYER
- else
- AM.layer = OBJ_LAYER
+ var/atom/movable/robot_parent = parent
+ if(dir == SOUTH)
+ robot_parent.layer = MOB_ABOVE_PIGGYBACK_LAYER
else
- AM.layer = MOB_LAYER
+ robot_parent.layer = MOB_BELOW_PIGGYBACK_LAYER
/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer)
return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3))
@@ -352,6 +404,19 @@
S.throwcooldown = TRUE
addtimer(VARSET_CALLBACK(S, throwcooldown, FALSE), 10 SECONDS)
+/datum/component/riding/cyborg/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider)
+ if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS))
+ return COMPONENT_RIDDEN_ALLOW_Z_MOVE
+ // if(!can_be_driven)
+ // if(z_move_flags & ZMOVE_FEEDBACK)
+ // to_chat(rider, span_warning("[movable_parent] cannot be driven around. Unbuckle from [movable_parent.p_them()] first."))
+ // return COMPONENT_RIDDEN_STOP_Z_MOVE
+ if(!ride_check(rider, FALSE))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider, span_warning("You're unable to ride [movable_parent] right now!"))
+ return COMPONENT_RIDDEN_STOP_Z_MOVE
+ return COMPONENT_RIDDEN_ALLOW_Z_MOVE
+
/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null)
var/atom/movable/AM = parent
var/amount_equipped = 0
@@ -386,6 +451,11 @@
qdel(O)
return TRUE
+/// Extra checks before buckled.can_z_move can be called in mob/living/can_z_move()
+/datum/component/riding/proc/riding_can_z_move(atom/movable/movable_parent, direction, turf/start, turf/destination, z_move_flags, mob/living/rider)
+ SIGNAL_HANDLER
+ return COMPONENT_RIDDEN_ALLOW_Z_MOVE
+
/obj/item/riding_offhand
name = "offhand"
icon = 'icons/obj/misc.dmi'
diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm
index e149013a48fa..7fed7fc1cc11 100644
--- a/code/datums/components/rotation.dm
+++ b/code/datums/components/rotation.dm
@@ -47,7 +47,7 @@
/datum/component/simple_rotation/proc/add_signals()
if(rotation_flags & ROTATION_ALTCLICK)
RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(HandRot))
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(ExamineMessage))
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(ExamineMessage))
if(rotation_flags & ROTATION_WRENCH)
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(WrenchRot))
@@ -69,7 +69,7 @@
AM.verbs -= /atom/movable/proc/simple_rotate_counterclockwise
/datum/component/simple_rotation/proc/remove_signals()
- UnregisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_PARENT_EXAMINE, COMSIG_PARENT_ATTACKBY))
+ UnregisterSignal(parent, list(COMSIG_CLICK_ALT, COMSIG_ATOM_EXAMINE, COMSIG_PARENT_ATTACKBY))
/datum/component/simple_rotation/RegisterWithParent()
add_verbs()
@@ -94,9 +94,9 @@
//Signals + verbs removed via UnRegister
. = ..()
-/datum/component/simple_rotation/RemoveComponent()
+/datum/component/simple_rotation/ClearFromParent()
remove_verbs()
- . = ..()
+ return ..()
/datum/component/simple_rotation/proc/ExamineMessage(datum/source, mob/user, list/examine_list)
if(rotation_flags & ROTATION_ALTCLICK)
diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm
index b17c752c457e..345a58900e5b 100644
--- a/code/datums/components/slippery.dm
+++ b/code/datums/components/slippery.dm
@@ -1,19 +1,238 @@
+/**
+ * # Slip behaviour component
+ *
+ * Add this component to an object to make it a slippery object, slippery objects make mobs that cross them fall over.
+ * Items with this component that get picked up may give their parent mob the slip behaviour.
+ *
+ * Here is a simple example of adding the component behaviour to an object.area
+ *
+ * AddComponent(/datum/component/slippery, 80, (NO_SLIP_WHEN_WALKING | SLIDE))
+ *
+ * This adds slippery behaviour to the parent atom, with a 80 decisecond (~8 seconds) knockdown
+ * The lube flags control how the slip behaves, in this case, the mob wont slip if it's in walking mode (NO_SLIP_WHEN_WALKING)
+ * and if they do slip, they will slide a few tiles (SLIDE)
+ *
+ *
+ * This component has configurable behaviours, see the [Initialize proc for the argument listing][/datum/component/slippery/proc/Initialize].
+ */
/datum/component/slippery
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// If the slip forces the crossing mob to drop held items.
var/force_drop_items = FALSE
+ /// How long the slip keeps the crossing mob knocked over (they can still crawl and use weapons) for.
var/knockdown_time = 0
- var/stun_time = 0
+ /// How long the slip paralyzes (prevents the crossing mob doing anything) for.
+ var/paralyze_time = 0
+ /// Flags for how slippery the parent is. See [__DEFINES/mobs.dm]
var/lube_flags
- var/datum/callback/callback
-
-/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _stun, _force_drop = FALSE)
- knockdown_time = max(_knockdown, 0)
- stun_time = max(_stun, 0)
- force_drop_items = _force_drop
- lube_flags = _lube_flags
- callback = _callback
- RegisterSignals(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), PROC_REF(Slip))
-
-/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM)
- var/mob/victim = AM
- if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, stun_time, force_drop_items) && callback)
- callback.Invoke(victim)
+ /// Optional callback allowing you to define custom conditions for slipping
+ var/datum/callback/can_slip_callback
+ /// Optional call back that is called when a mob slips on this component
+ var/datum/callback/on_slip_callback
+ /// If parent is an item, this is the person currently holding/wearing the parent (or the parent if no one is holding it)
+ var/mob/living/holder
+ /// Whitelist of item slots the parent can be equipped in that make the holder slippery. If null or empty, it will always make the holder slippery.
+ var/list/slot_whitelist = list(ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_HEAD, ITEM_SLOT_MASK, ITEM_SLOT_BELT, ITEM_SLOT_NECK)
+ ///what we give to connect_loc by default, makes slippable mobs moving over us slip
+ var/static/list/default_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(Slip),
+ )
+
+ ///what we give to connect_loc if we're an item and get equipped by a mob. makes slippable mobs moving over our holder slip
+ var/static/list/holder_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(Slip_on_wearer),
+ )
+
+ /// The connect_loc_behalf component for the holder_connections list.
+ var/datum/weakref/holder_connect_loc_behalf
+
+/**
+ * Initialize the slippery component behaviour
+ *
+ * When applied to any atom in the game this will apply slipping behaviours to that atom
+ *
+ * Arguments:
+ * * knockdown - Length of time the knockdown applies (Deciseconds)
+ * * lube_flags - Controls the slip behaviour, they are listed starting [here][SLIDE]
+ * * datum/callback/on_slip_callback - Callback to define further custom controls on when slipping is applied
+ * * paralyze - length of time to paralyze the crossing mob for (Deciseconds)
+ * * force_drop - should the crossing mob drop items in it's hands or not
+ * * slot_whitelist - flags controlling where on a mob this item can be equipped to make the parent mob slippery full list [here][ITEM_SLOT_OCLOTHING]
+ * * datum/callback/on_slip_callback - Callback to add custom behaviours as the crossing mob is slipped
+ */
+/datum/component/slippery/Initialize(
+ knockdown,
+ lube_flags = NONE,
+ datum/callback/on_slip_callback,
+ paralyze,
+ force_drop = FALSE,
+ slot_whitelist,
+ datum/callback/can_slip_callback,
+)
+ src.knockdown_time = max(knockdown, 0)
+ src.paralyze_time = max(paralyze, 0)
+ src.force_drop_items = force_drop
+ src.lube_flags = lube_flags
+ src.can_slip_callback = can_slip_callback
+ src.on_slip_callback = on_slip_callback
+ if(slot_whitelist)
+ src.slot_whitelist = slot_whitelist
+
+ add_connect_loc_behalf_to_parent()
+ if(ismovable(parent))
+ if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
+ // RegisterSignal(parent, COMSIG_ITEM_APPLY_FANTASY_BONUSES, PROC_REF(apply_fantasy_bonuses))
+ // RegisterSignal(parent, COMSIG_ITEM_REMOVE_FANTASY_BONUSES, PROC_REF(remove_fantasy_bonuses))
+ else
+ RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(Slip))
+
+/datum/component/slippery/Destroy(force)
+ can_slip_callback = null
+ on_slip_callback = null
+ holder = null
+ return ..()
+
+// /datum/component/slippery/proc/apply_fantasy_bonuses(obj/item/source, bonus)
+// SIGNAL_HANDLER
+// knockdown_time = source.modify_fantasy_variable("knockdown_time", knockdown_time, bonus)
+// if(bonus >= 5)
+// paralyze_time = source.modify_fantasy_variable("paralyze_time", paralyze_time, bonus)
+// LAZYSET(source.fantasy_modifications, "lube_flags", lube_flags)
+// lube_flags |= SLIDE
+// if(bonus >= 10)
+// lube_flags |= GALOSHES_DONT_HELP|SLIP_WHEN_CRAWLING
+
+// /datum/component/slippery/proc/remove_fantasy_bonuses(obj/item/source, bonus)
+// SIGNAL_HANDLER
+// knockdown_time = source.reset_fantasy_variable("knockdown_time", knockdown_time)
+// paralyze_time = source.reset_fantasy_variable("paralyze_time", paralyze_time)
+// var/previous_lube_flags = LAZYACCESS(source.fantasy_modifications, "lube_flags")
+// LAZYREMOVE(source.fantasy_modifications, "lube_flags")
+// if(!isnull(previous_lube_flags))
+// lube_flags = previous_lube_flags
+
+/datum/component/slippery/proc/add_connect_loc_behalf_to_parent()
+ if(ismovable(parent))
+ AddComponent(/datum/component/connect_loc_behalf, parent, default_connections)
+
+/datum/component/slippery/InheritComponent(
+ datum/component/slippery/component,
+ i_am_original,
+ knockdown,
+ lube_flags = NONE,
+ datum/callback/on_slip_callback,
+ paralyze,
+ force_drop = FALSE,
+ slot_whitelist,
+ datum/callback/can_slip_callback,
+)
+ if(component)
+ knockdown = component.knockdown_time
+ lube_flags = component.lube_flags
+ on_slip_callback = component.on_slip_callback
+ can_slip_callback = component.on_slip_callback
+ paralyze = component.paralyze_time
+ force_drop = component.force_drop_items
+ slot_whitelist = component.slot_whitelist
+
+ src.knockdown_time = max(knockdown, 0)
+ src.paralyze_time = max(paralyze, 0)
+ src.force_drop_items = force_drop
+ src.lube_flags = lube_flags
+ src.on_slip_callback = on_slip_callback
+ src.can_slip_callback = can_slip_callback
+ if(slot_whitelist)
+ src.slot_whitelist = slot_whitelist
+/**
+ * The proc that does the sliping. Invokes the slip callback we have set.
+ *
+ * Arguments
+ * * source - the source of the signal
+ * * arrived - the atom/movable that is being slipped.
+ */
+/datum/component/slippery/proc/Slip(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ SIGNAL_HANDLER
+ if(!isliving(arrived))
+ return
+ if(lube_flags & SLIPPERY_TURF)
+ var/turf/turf = get_turf(source)
+ if(HAS_TRAIT(turf, TRAIT_TURF_IGNORE_SLIPPERY))
+ return
+ var/mob/living/victim = arrived
+ if(victim.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
+ return
+ if(can_slip_callback && !can_slip_callback.Invoke(holder, victim))
+ return
+ if(victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items))
+ on_slip_callback?.Invoke(victim)
+
+/**
+ * Gets called when COMSIG_ITEM_EQUIPPED is sent to parent.
+ * This proc register slip signals to the equipper.
+ * If we have a slot whitelist, we only register the signals if the slot is valid (ex: clown PDA only slips in ID or belt slot).
+ *
+ * Arguments
+ * * source - the source of the signal
+ * * equipper - the mob we're equipping the slippery thing to
+ * * slot - the slot we're equipping the slippery thing to on the equipper.
+ */
+/datum/component/slippery/proc/on_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ if((!LAZYLEN(slot_whitelist) || (slot in slot_whitelist)) && isliving(equipper))
+ holder = equipper
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
+ AddComponent(/datum/component/connect_loc_behalf, holder, holder_connections)
+ RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(holder_deleted))
+
+/**
+ * Detects if the holder mob is deleted.
+ * If our holder mob is the holder set in this component, we null it.
+ *
+ * Arguments:
+ * * source - the source of the signal
+ * * possible_holder - the mob being deleted.
+ */
+/datum/component/slippery/proc/holder_deleted(datum/source, datum/possible_holder)
+ SIGNAL_HANDLER
+
+ if(possible_holder == holder)
+ holder = null
+
+/**
+ * Gets called when COMSIG_ITEM_DROPPED is sent to parent.
+ * Makes our holder mob un-slippery.
+ *
+ * Arguments:
+ * * source - the source of the signal
+ * * user - the mob that was formerly wearing our slippery item.
+ */
+/datum/component/slippery/proc/on_drop(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(user, COMSIG_QDELETING)
+
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
+ add_connect_loc_behalf_to_parent()
+
+ holder = null
+
+/**
+ * The slip proc, but for equipped items.
+ * Slips the person who crossed us if we're lying down and unbuckled.
+ *
+ * Arguments:
+ * * source - the source of the signal
+ * * arrived - the atom/movable that slipped on us.
+ */
+/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ SIGNAL_HANDLER
+
+ if(holder.body_position == LYING_DOWN && !holder.buckled)
+ Slip(source, arrived)
+
+/datum/component/slippery/UnregisterFromParent()
+ . = ..()
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm
index 0ef245fbc076..7ea739c83cb6 100644
--- a/code/datums/components/spawner.dm
+++ b/code/datums/components/spawner.dm
@@ -1,25 +1,37 @@
/datum/component/spawner
- var/mob_types = list(/mob/living/simple_animal/hostile/carp)
- var/spawn_time = 300 //30 seconds default
- var/list/spawned_mobs = list()
- var/spawn_delay = 0
- var/max_mobs = 5
+ /// Time to wait between spawns
+ var/spawn_time
+ /// Maximum number of atoms we can have active at one time
+ var/max_spawned
+ /// Visible message to show when something spawns
var/spawn_text = "emerges from"
- var/list/faction = list("mining")
+ /// List of atom types to spawn, picked randomly
+ var/list/spawn_types = list(/mob/living/simple_animal/hostile/carp)
+ /// Faction to grant to mobs (only applies to mobs)
+ var/list/faction = list(FACTION_MINING)
+ /// List of weak references to things we have already created
+ var/list/spawned_things = list()
+ /// How many mobs can we spawn maximum each time we try to spawn? (1 - max)
+ var/max_spawn_per_attempt
+ /// Distance from the spawner to spawn mobs
+ var/spawn_distance
+ /// Distance from the spawner to exclude mobs from spawning
+ var/spawn_distance_exclude
+ COOLDOWN_DECLARE(spawn_delay)
-/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs)
- if(_spawn_time)
- spawn_time=_spawn_time
- if(_mob_types)
- mob_types=_mob_types
- if(_faction)
- faction=_faction
- if(_spawn_text)
- spawn_text=_spawn_text
- if(_max_mobs)
- max_mobs=_max_mobs
+/datum/component/spawner/Initialize(spawn_types = list(), spawn_time = 30 SECONDS, max_spawned = 5, max_spawn_per_attempt = 2 , faction = list(FACTION_MINING), spawn_text = null, spawn_distance = 1, spawn_distance_exclude = 0)
+ if (!islist(spawn_types))
+ CRASH("invalid spawn_types to spawn specified for spawner component!")
+ src.spawn_time = spawn_time
+ src.spawn_types = spawn_types
+ src.faction = faction
+ src.spawn_text = spawn_text
+ src.max_spawned = max_spawned
+ src.max_spawn_per_attempt = max_spawn_per_attempt
+ src.spawn_distance = spawn_distance
+ src.spawn_distance_exclude = spawn_distance_exclude
- RegisterSignals(parent, list(COMSIG_PARENT_QDELETING), PROC_REF(stop_spawning))
+ RegisterSignals(parent, list(COMSIG_QDELETING), PROC_REF(stop_spawning))
START_PROCESSING(SSprocessing, src)
/datum/component/spawner/process()
@@ -29,22 +41,24 @@
/datum/component/spawner/proc/stop_spawning(force, hint)
SIGNAL_HANDLER
STOP_PROCESSING(SSprocessing, src)
- for(var/mob/living/simple_animal/L in spawned_mobs)
+ for(var/mob/living/simple_animal/L in spawned_things)
if(L.nest == src)
L.nest = null
- spawned_mobs = null
+ spawned_things = null
/datum/component/spawner/proc/try_spawn_mob()
- var/atom/P = parent
- if(spawned_mobs.len >= max_mobs)
- return 0
- if(spawn_delay > world.time)
- return 0
- spawn_delay = world.time + spawn_time
- var/chosen_mob_type = pick(mob_types)
- var/mob/living/simple_animal/L = new chosen_mob_type(P.loc)
- L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1)
- spawned_mobs += L
+ if(!length(spawn_types))
+ return
+ if(!COOLDOWN_FINISHED(src, spawn_delay))
+ return
+ if(length(spawned_things) >= max_spawned)
+ return
+ var/atom/spawner = parent
+ COOLDOWN_START(src, spawn_delay, spawn_time)
+ var/chosen_mob_type = pick(spawn_types)
+ var/mob/living/simple_animal/L = new chosen_mob_type(spawner.loc)
+ L.flags_1 |= (spawner.flags_1 & ADMIN_SPAWNED_1)
+ spawned_things += L
L.nest = src
L.faction = src.faction
- P.visible_message(span_danger("[L] [spawn_text] [P]."))
+ spawner.visible_message(span_danger("[L] [spawn_text] [spawner]."))
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index 03f1e4f2d452..b996ab73d182 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -13,13 +13,18 @@
var/last_use = 0
var/use_delay = 20
+ ///what we set connect_loc to if parent is an item
+ var/static/list/item_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(play_squeak_crossed),
+ )
+
/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignals(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_PARENT_ATTACKBY), PROC_REF(play_squeak))
if(ismovable(parent))
RegisterSignals(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_IMPACT, COMSIG_PROJECTILE_BEFORE_FIRE), PROC_REF(play_squeak))
- RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, PROC_REF(play_squeak_crossed))
+ AddComponent(/datum/component/connect_loc_behalf, parent, item_connections)
RegisterSignal(parent, COMSIG_MOVABLE_DISPOSING, PROC_REF(disposing_react))
if(isitem(parent))
RegisterSignals(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_HIT_REACT), PROC_REF(play_squeak))
@@ -41,6 +46,10 @@
if(isnum(use_delay_override))
use_delay = use_delay_override
+/datum/component/squeak/UnregisterFromParent()
+ . = ..()
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
+
/datum/component/squeak/proc/play_squeak()
if(prob(squeak_chance))
if(!override_squeak_sounds)
diff --git a/code/datums/components/squishable.dm b/code/datums/components/squishable.dm
new file mode 100644
index 000000000000..b3aba6d076ce
--- /dev/null
+++ b/code/datums/components/squishable.dm
@@ -0,0 +1,81 @@
+///This component allows something to be when crossed, for example for cockroaches.
+/datum/component/squashable
+ ///Chance on crossed to be squashed
+ var/squash_chance = 50
+ ///How much brute is applied when mob is squashed
+ var/squash_damage = 1
+ ///Squash flags, for extra checks etcetera.
+ var/squash_flags = NONE
+ ///Special callback to call on squash instead, for things like hauberoach
+ var/datum/callback/on_squash_callback
+ ///signal list given to connect_loc
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+
+
+/datum/component/squashable/Initialize(squash_chance, squash_damage, squash_flags, squash_callback)
+ . = ..()
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+ if(squash_chance)
+ src.squash_chance = squash_chance
+ if(squash_damage)
+ src.squash_damage = squash_damage
+ if(squash_flags)
+ src.squash_flags = squash_flags
+ if(!src.on_squash_callback && squash_callback)
+ on_squash_callback = CALLBACK(parent, squash_callback)
+
+ AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)
+
+/datum/component/squashable/Destroy(force)
+ on_squash_callback = null
+ return ..()
+
+///Handles the squashing of the mob
+/datum/component/squashable/proc/on_entered(turf/source_turf, atom/movable/crossing_movable)
+ // SIGNAL_HANDLER -- dont uncomment this
+
+ if(parent == crossing_movable)
+ return
+
+ var/mob/living/parent_as_living = parent
+ if((squash_flags & SQUASHED_DONT_SQUASH_IN_CONTENTS) && !isturf(parent_as_living.loc))
+ return
+
+ if((squash_flags & SQUASHED_SHOULD_BE_DOWN) && parent_as_living.body_position != LYING_DOWN)
+ return
+
+ var/should_squash = ((squash_flags & SQUASHED_ALWAYS_IF_DEAD) && parent_as_living.stat == DEAD) || prob(squash_chance)
+
+ if(should_squash && on_squash_callback)
+ if(on_squash_callback.Invoke(parent_as_living, crossing_movable))
+ return //Everything worked, we're done!
+ if(isliving(crossing_movable))
+ var/mob/living/crossing_mob = crossing_movable
+ if(crossing_mob.mob_size > MOB_SIZE_SMALL && !(crossing_mob.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
+ if(HAS_TRAIT(crossing_mob, TRAIT_PACIFISM))
+ crossing_mob.visible_message(span_notice("[crossing_mob] carefully steps over [parent_as_living]."), span_notice("You carefully step over [parent_as_living] to avoid hurting it."))
+ return
+ if(should_squash)
+ crossing_mob.visible_message(span_notice("[crossing_mob] squashed [parent_as_living]."), span_notice("You squashed [parent_as_living]."))
+ Squish(parent_as_living)
+ else
+ parent_as_living.visible_message(span_notice("[parent_as_living] avoids getting crushed."))
+ else if(isstructure(crossing_movable))
+ if(should_squash)
+ crossing_movable.visible_message(span_notice("[parent_as_living] is crushed under [crossing_movable]."))
+ Squish(parent_as_living)
+ else
+ parent_as_living.visible_message(span_notice("[parent_as_living] avoids getting crushed."))
+
+/datum/component/squashable/proc/Squish(mob/living/target)
+ if(squash_flags & SQUASHED_SHOULD_BE_GIBBED)
+ target.gib(DROP_ALL_REMAINS)
+ else
+ target.adjustBruteLoss(squash_damage)
+
+/datum/component/squashable/UnregisterFromParent()
+ . = ..()
+ qdel(GetComponent(/datum/component/connect_loc_behalf))
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
index e3a4205d19c7..18b057b9aed0 100644
--- a/code/datums/components/stationloving.dm
+++ b/code/datums/components/stationloving.dm
@@ -9,7 +9,7 @@
return COMPONENT_INCOMPATIBLE
RegisterSignals(parent, list(COMSIG_MOVABLE_Z_CHANGED), PROC_REF(check_in_bounds))
RegisterSignals(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), PROC_REF(relocate))
- RegisterSignals(parent, list(COMSIG_PARENT_PREQDELETED), PROC_REF(check_deletion))
+ RegisterSignals(parent, list(COMSIG_PREQDELETED), PROC_REF(check_deletion))
RegisterSignals(parent, list(COMSIG_ITEM_IMBUE_SOUL), PROC_REF(check_soul_imbue))
src.inform_admins = inform_admins
src.allow_death = allow_death
@@ -52,7 +52,7 @@
/datum/component/stationloving/proc/in_bounds()
var/static/list/allowed_shuttles = typecacheof(list(/area/shuttle/syndicate, /area/shuttle/escape, /area/shuttle/pod_1, /area/shuttle/pod_2, /area/shuttle/pod_3, /area/shuttle/pod_4))
- var/static/list/disallowed_centcom_areas = typecacheof(list(/area/abductor_ship, /area/fabric_of_reality, /area/awaymission/errorroom))
+ var/static/list/disallowed_centcom_areas = typecacheof(list(/area/centcom/abductor_ship, /area/centcom/fabric_of_reality, /area/awaymission/errorroom))
var/turf/T = get_turf_global(parent) // yogs - replace get_turf with get_turf_global
if (!T)
return FALSE
diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm
index 79cb3f9d0883..351d6cc29a71 100644
--- a/code/datums/components/storage/concrete/_concrete.dm
+++ b/code/datums/components/storage/concrete/_concrete.dm
@@ -110,7 +110,7 @@
//Resets screen loc and other vars of something being removed from storage.
/datum/component/storage/concrete/_removal_reset(atom/movable/thing)
thing.layer = initial(thing.layer)
- thing.plane = initial(thing.plane)
+ SET_PLANE_IMPLICIT(thing, initial(thing.plane))
thing.mouse_opacity = initial(thing.mouse_opacity)
if(thing.maptext)
thing.maptext = ""
diff --git a/code/datums/components/storage/concrete/bag_of_holding.dm b/code/datums/components/storage/concrete/bag_of_holding.dm
index d549c0bbcaea..9b5a05eaf477 100644
--- a/code/datums/components/storage/concrete/bag_of_holding.dm
+++ b/code/datums/components/storage/concrete/bag_of_holding.dm
@@ -19,7 +19,7 @@
user.visible_message(span_warning("An unseen force knocks [user] to the ground!"), "[span_big_brass("\"I think not!\"")]")
user.Paralyze(60)
return
- if(istype(loccheck.loc, /area/fabric_of_reality))
+ if(istype(loccheck.loc, /area/centcom/fabric_of_reality))
to_chat(user, span_danger("You can't do that here!"))
if(user.has_status_effect(STATUS_EFFECT_VOIDED))
user.remove_status_effect(STATUS_EFFECT_VOIDED)
@@ -50,8 +50,8 @@
M.visible_message(span_danger("The bluespace collapse crushes the air towards it, pulling [M] towards the ground..."))
M.Paralyze(5, TRUE, TRUE) //Overrides stun absorbs.
T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic)
- for(var/fabricarea in get_areas(/area/fabric_of_reality))
- var/area/fabric_of_reality/R = fabricarea
+ for(var/fabricarea in get_areas(/area/centcom/fabric_of_reality))
+ var/area/centcom/fabric_of_reality/R = fabricarea
R.origin = loccheck
for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders)
ladder.ActivateAlmonds()
diff --git a/code/datums/components/storage/concrete/pockets.dm b/code/datums/components/storage/concrete/pockets.dm
index 4e69c5f7a303..74b33246a7f4 100644
--- a/code/datums/components/storage/concrete/pockets.dm
+++ b/code/datums/components/storage/concrete/pockets.dm
@@ -44,7 +44,7 @@
/datum/component/storage/concrete/pockets/shoes/Initialize()
. = ..()
set_holdable(list(
- /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
+ /obj/item/kitchen/knife, /obj/item/boxcutter, /obj/item/switchblade, /obj/item/pen, //boxcutter dripstation edit
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/autoinjector/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
@@ -56,7 +56,7 @@
/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
. = ..()
set_holdable(list(
- /obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
+ /obj/item/kitchen/knife, /obj/item/boxcutter, /obj/item/switchblade, /obj/item/pen, //boxcutter dripstation edit
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/autoinjector/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index bd557a8bd347..40cc6aa80321 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -357,7 +357,6 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]"
- ND.sample_object.layer = ABOVE_HUD_LAYER
ND.sample_object.plane = ABOVE_HUD_PLANE
cx++
if(cx - screen_start_x >= cols)
@@ -373,7 +372,6 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
O.maptext = ""
- O.layer = ABOVE_HUD_LAYER
O.plane = ABOVE_HUD_PLANE
cx++
if(cx - screen_start_x >= cols)
@@ -439,7 +437,6 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
if(QDELETED(O))
continue
O.screen_loc = "[cx],[cy]"
- O.layer = ABOVE_HUD_LAYER
O.plane = ABOVE_HUD_PLANE
cx++
if(cx > mx)
diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm
index 8ee9fe716e8e..eab8c71ba208 100644
--- a/code/datums/components/tactical.dm
+++ b/code/datums/components/tactical.dm
@@ -1,5 +1,6 @@
/datum/component/tactical
var/allowed_slot
+ var/current_slot
/datum/component/tactical/Initialize(allowed_slot)
if(!isitem(parent))
@@ -19,6 +20,15 @@
unmodify()
return ..()
+/datum/component/tactical/proc/on_z_move(datum/source)
+ SIGNAL_HANDLER
+ var/obj/item/master = parent
+ if(!ismob(master.loc))
+ return
+ var/old_slot = current_slot
+ unmodify(master, master.loc)
+ modify(master, master.loc, old_slot)
+
/datum/component/tactical/proc/modify(obj/item/source, mob/user, slot)
SIGNAL_HANDLER
@@ -26,14 +36,17 @@
unmodify()
return
+ current_slot = slot
+
var/obj/item/master = parent
var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user)
I.copy_overlays(master)
I.override = TRUE
source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
I.layer = ABOVE_MOB_LAYER
+ RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_move))
-/datum/component/tactical/proc/unmodify(obj/item/source, mob/user)
+/datum/component/tactical/proc/unmodify(obj/item/source, mob/user, slot)
SIGNAL_HANDLER
var/obj/item/master = source || parent
@@ -43,3 +56,5 @@
user = master.loc
user.remove_alt_appearance("sneaking_mission")
+ current_slot = slot
+ UnregisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED)
diff --git a/code/datums/components/twohanded.dm b/code/datums/components/twohanded.dm
index 9a881e017f37..96501d96f114 100644
--- a/code/datums/components/twohanded.dm
+++ b/code/datums/components/twohanded.dm
@@ -240,7 +240,7 @@
offhand_item.desc = "Your second grip on [parent_item]."
offhand_item.wielded = TRUE
RegisterSignal(offhand_item, COMSIG_ITEM_DROPPED, PROC_REF(on_drop))
- RegisterSignal(offhand_item, COMSIG_PARENT_QDELETING, PROC_REF(on_destroy))
+ RegisterSignal(offhand_item, COMSIG_QDELETING, PROC_REF(on_destroy))
user.put_in_inactive_hand(offhand_item)
/**
@@ -307,7 +307,7 @@
// Remove the object in the offhand
if(offhand_item)
- UnregisterSignal(offhand_item, list(COMSIG_ITEM_DROPPED, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(offhand_item, list(COMSIG_ITEM_DROPPED, COMSIG_QDELETING))
qdel(offhand_item)
// Clear any old refrence to an item that should be gone now
offhand_item = null
diff --git a/code/datums/components/wall_mounted.dm b/code/datums/components/wall_mounted.dm
new file mode 100644
index 000000000000..171e83ab1600
--- /dev/null
+++ b/code/datums/components/wall_mounted.dm
@@ -0,0 +1,59 @@
+// This element should be applied to wall-mounted machines/structures, so that if the wall it's "hanging" from is broken or deconstructed, the wall-hung structure will deconstruct.
+/datum/component/wall_mounted
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ /// The wall our object is currently linked to.
+ var/turf/hanging_wall_turf
+ /// Callback to the parent's proc to call on the linked object when the wall disappear's or changes.
+ var/datum/callback/on_drop
+
+/datum/component/wall_mounted/Initialize(target_wall, on_drop_callback)
+ . = ..()
+ if(!isobj(parent))
+ return COMPONENT_INCOMPATIBLE
+ if(!isturf(target_wall))
+ return COMPONENT_INCOMPATIBLE
+ hanging_wall_turf = target_wall
+ on_drop = on_drop_callback
+
+/datum/component/wall_mounted/RegisterWithParent()
+ RegisterSignal(hanging_wall_turf, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_linked_destroyed))
+
+/datum/component/wall_mounted/UnregisterFromParent()
+ UnregisterSignal(hanging_wall_turf, list(COMSIG_ATOM_EXAMINE, COMSIG_TURF_CHANGE))
+ UnregisterSignal(parent, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
+ hanging_wall_turf = null
+
+/**
+ * Basic reference handling if the hanging/linked object is destroyed first.
+ */
+/datum/component/wall_mounted/proc/on_linked_destroyed()
+ SIGNAL_HANDLER
+ if(!QDELING(src))
+ qdel(src)
+
+/**
+ * When the wall is examined, explains that it's supporting the linked object.
+ */
+/datum/component/wall_mounted/proc/on_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ examine_list += span_notice("\The [hanging_wall_turf] is currently supporting [span_bold("[parent]")]. Deconstruction or excessive damage would cause it to [span_bold("fall to the ground")].")
+
+/**
+ * Checks object direction and then verifies if there's a wall in that direction. Finally, applies a wall_mounted component to the object.
+ *
+ * @param directional If TRUE, will use the direction of the object to determine the wall to attach to. If FALSE, will use the object's loc.
+ * @param custom_drop_callback If set, will use this callback instead of the default deconstruct callback.
+ */
+/obj/proc/find_and_hang_on_wall(directional = TRUE, custom_drop_callback)
+ if(istype(get_area(src), /area/shuttle))
+ return FALSE //For now, we're going to keep the component off of shuttles to avoid the turf changing issue. We'll hit that later really;
+ var/turf/attachable_wall
+ if(directional)
+ attachable_wall = get_step(src, dir)
+ else
+ attachable_wall = loc ///Pull from the curent object loc
+ if(!iswallturf(attachable_wall))
+ return FALSE//Nothing to latch onto, or not the right thing.
+ src.AddComponent(/datum/component/wall_mounted, attachable_wall, custom_drop_callback)
+ return TRUE
diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm
index c0b962c71307..474b924f769f 100644
--- a/code/datums/components/wet_floor.dm
+++ b/code/datums/components/wet_floor.dm
@@ -2,8 +2,8 @@
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
can_transfer = TRUE
var/highest_strength = TURF_DRY
- var/lube_flags = NONE //why do we have this?
- var/list/time_left_list //In deciseconds.
+ var/lube_flags = NONE //why do we have this?
+ var/list/time_left_list //In deciseconds.
var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor")
var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor")
var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static")
@@ -11,25 +11,29 @@
var/current_overlay
var/permanent = FALSE
var/last_process = 0
+ /// Should we display an overlay for this component? Useful mainly for turfs
+ /// that already look wets or just don't need the visuals for any other reason.
+ var/should_display_overlay = TRUE
-/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent)
- if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component
+/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent, _should_display_overlay)
+ if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component
add_wet(arglist(args.Copy(3)))
- else //We are being passed in a full blown component
- var/datum/component/wet_floor/WF = newcomp //Lets make an assumption
- if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us.
+ else //We are being passed in a full blown component
+ var/datum/component/wet_floor/WF = newcomp //Lets make an assumption
+ if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us.
CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!")
for(var/i in WF.time_left_list)
add_wet(text2num(i), WF.time_left_list[i])
-/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE)
+/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE, _should_display_overlay = TRUE)
if(!isopenturf(parent))
return COMPONENT_INCOMPATIBLE
- add_wet(strength, duration_minimum, duration_add, duration_maximum)
+ should_display_overlay = _should_display_overlay
+ add_wet(strength, duration_minimum, duration_add, duration_maximum, _permanent, _should_display_overlay)
permanent = _permanent
if(!permanent)
START_PROCESSING(SSwet_floors, src)
- addtimer(CALLBACK(src, PROC_REF(gc), TRUE), 1) //GC after initialization.
+ addtimer(CALLBACK(src, PROC_REF(gc), TRUE), 1) //GC after initialization.
last_process = world.time
/datum/component/wet_floor/RegisterWithParent()
@@ -43,15 +47,24 @@
STOP_PROCESSING(SSwet_floors, src)
var/turf/T = parent
qdel(T.GetComponent(/datum/component/slippery))
- if(istype(T)) //If this is false there is so many things wrong with it.
+ if(istype(T)) //If this is false there is so many things wrong with it.
T.cut_overlay(current_overlay)
else
stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!")
return ..()
/datum/component/wet_floor/proc/update_overlay()
+ if(!should_display_overlay)
+ if(!current_overlay)
+ return
+
+ var/turf/parent_turf = parent
+ parent_turf.cut_overlay(current_overlay)
+ current_overlay = null
+ return
+
var/intended
- if(!istype(parent, /turf/open/floor))
+ if(!isfloorturf(parent))
intended = generic_turf_overlay
else
switch(highest_strength)
@@ -62,34 +75,36 @@
else
intended = water_overlay
if(current_overlay != intended)
- var/turf/T = parent
- T.cut_overlay(current_overlay)
- T.add_overlay(intended)
+ var/turf/parent_turf = parent
+ parent_turf.cut_overlay(current_overlay)
+ parent_turf.add_overlay(intended)
current_overlay = intended
/datum/component/wet_floor/proc/AfterSlip(mob/living/slipped)
- if(highest_strength == TURF_WET_LUBE)
- slipped.set_confusion_if_lower(8 SECONDS)
+ if(highest_strength != TURF_WET_LUBE)
+ return
+
+ slipped.set_confusion_if_lower(8 SECONDS)
/datum/component/wet_floor/proc/update_flags()
var/intensity
- lube_flags = NONE
+ lube_flags = SLIPPERY_TURF
switch(highest_strength)
if(TURF_WET_WATER)
intensity = 60
- lube_flags = NO_SLIP_WHEN_WALKING
+ lube_flags |= NO_SLIP_WHEN_WALKING
if(TURF_WET_LUBE)
intensity = 80
- lube_flags = SLIDE | GALOSHES_DONT_HELP
+ lube_flags |= SLIDE | GALOSHES_DONT_HELP
if(TURF_WET_ICE)
intensity = 120
- lube_flags = SLIDE | GALOSHES_DONT_HELP
+ lube_flags |= SLIDE | GALOSHES_DONT_HELP
if(TURF_WET_PERMAFROST)
intensity = 120
- lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP
+ lube_flags |= SLIDE_ICE | GALOSHES_DONT_HELP
if(TURF_WET_SUPERLUBE)
intensity = 120
- lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING
+ lube_flags |= SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING
else
qdel(parent.GetComponent(/datum/component/slippery))
return
@@ -97,6 +112,8 @@
parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, PROC_REF(AfterSlip)))
/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY)
+ SIGNAL_HANDLER
+
for(var/i in time_left_list)
if(text2num(i) <= strength)
time_left_list[i] = max(0, time_left_list[i] - duration_decrease)
@@ -115,16 +132,15 @@
var/t = T.GetTemperature()
switch(t)
if(-INFINITY to T0C)
- add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice!
+ add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice!
if(T0C to T0C + 100)
decrease = ((T.air.return_temperature() - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio)
if(T0C + 100 to INFINITY)
decrease = INFINITY
decrease = max(0, decrease)
- if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water!
+ if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water!
for(var/obj/O in T.contents)
- if(O.obj_flags & FROZEN)
- O.make_unfrozen()
+ O.unfreeze()
add_wet(TURF_WET_WATER, max_time_left())
dry(null, TURF_WET_ICE)
dry(null, ALL, FALSE, decrease)
@@ -132,11 +148,13 @@
last_process = world.time
/datum/component/wet_floor/proc/update_strength()
- highest_strength = 0 //Not bitflag.
+ highest_strength = 0 //Not bitflag.
for(var/i in time_left_list)
highest_strength = max(highest_strength, text2num(i))
/datum/component/wet_floor/proc/is_wet()
+ SIGNAL_HANDLER
+
. = 0
for(var/i in time_left_list)
. |= text2num(i)
@@ -158,7 +176,7 @@
//NB it's possible we get deleted after this, due to inherit
-/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE)
+/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE, _should_display_overlay = TRUE)
var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE)
if(duration_minimum <= 0 || !type)
return FALSE
@@ -174,6 +192,8 @@
permanent = TRUE
STOP_PROCESSING(SSwet_floors, src)
+ should_display_overlay = _should_display_overlay
+
/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum)
var/time = 0
if(LAZYACCESS(time_left_list, "[type]"))
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 3691cdd0c14f..537a63384c60 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -344,7 +344,7 @@
var/datum/data/record/M = new()
M.fields["id"] = id
M.fields["name"] = record_name
- M.fields["blood_type"] = H.dna.blood_type
+ M.fields["blood_type"] = H.dna.blood_type.name
M.fields["b_dna"] = H.dna.unique_enzymes
M.fields["mi_dis"] = "None"
M.fields["mi_dis_d"] = "No minor disabilities have been declared."
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index a2d370e2ee68..832d407f2404 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -19,15 +19,26 @@
var/gc_destroyed
/// Active timers with this datum as the target
- var/list/active_timers
- /// Components attached to this datum
- var/list/datum_components
- /// Status traits attached to this datum
- var/list/status_traits
- /// Any datum registered to receive signals from this datum is in this list
- var/list/comp_lookup
- /// List of callbacks for signal procs
- var/list/list/datum/callback/signal_procs
+ var/list/_active_timers
+ /// Status traits attached to this datum. associative list of the form: list(trait name (string) = list(source1, source2, source3,...))
+ var/list/_status_traits
+
+ /**
+ * Components attached to this datum
+ *
+ * Lazy associated list in the structure of `type -> component/list of components`
+ */
+ var/list/_datum_components
+
+ /**
+ * Any datum registered to receive signals from this datum is in this list
+ *
+ * Lazy associated list in the structure of `signal -> registree/list of registrees`
+ */
+ var/list/_listen_lookup
+ /// Lazy associated list in the structure of `target -> list(signal -> proctype)` that are run when the datum receives that signal
+ var/list/list/_signal_procs
+
/// Datum level flags
var/datum_flags = NONE
@@ -46,6 +57,8 @@
* add_timer() returns the truthy value of -1 when not stoppable, and else a truthy numeric index
*/
var/list/cooldowns
+ /// List for handling persistent filters.
+ var/list/filter_data
#ifdef TESTING
var/running_find_references
@@ -87,24 +100,25 @@
datum_flags &= ~DF_USE_TAG //In case something tries to REF us
weak_reference = null //ensure prompt GCing of weakref.
- var/list/timers = active_timers
- active_timers = null
- for(var/thing in timers)
- var/datum/timedevent/timer = thing
- if (timer.spent)
- continue
- qdel(timer)
+ if(_active_timers)
+ var/list/timers = _active_timers
+ _active_timers = null
+ for(var/datum/timedevent/timer as anything in timers)
+ if (timer.spent && !(timer.flags & TIMER_DELETE_ME))
+ continue
+ qdel(timer)
- // Handle components & signals
- var/list/dc = datum_components
+ //BEGIN: ECS SHIT
+ var/list/dc = _datum_components
if(dc)
- var/all_components = dc[/datum/component]
- if(length(all_components))
- for(var/datum/component/component as anything in all_components)
- qdel(component, FALSE, TRUE)
- else
- var/datum/component/C = all_components
- qdel(C, FALSE, TRUE)
+ for(var/component_key in dc)
+ var/component_or_list = dc[component_key]
+ if(islist(component_or_list))
+ for(var/datum/component/component as anything in component_or_list)
+ qdel(component, FALSE, TRUE)
+ else
+ var/datum/component/C = component_or_list
+ qdel(C, FALSE, TRUE)
dc.Cut()
clear_signal_refs()
@@ -114,7 +128,7 @@
///Only override this if you know what you're doing. You do not know what you're doing
///This is a threat
/datum/proc/clear_signal_refs()
- var/list/lookup = comp_lookup
+ var/list/lookup = _listen_lookup
if(lookup)
for(var/sig in lookup)
var/list/comps = lookup[sig]
@@ -124,10 +138,10 @@
else
var/datum/component/comp = comps
comp.UnregisterSignal(src, sig)
- comp_lookup = lookup = null
+ _listen_lookup = lookup = null
- for(var/target in signal_procs)
- UnregisterSignal(target, signal_procs[target])
+ for(var/target in _signal_procs)
+ UnregisterSignal(target, _signal_procs[target])
#ifdef DATUMVAR_DEBUGGING_MODE
/datum/proc/save_vars()
@@ -250,3 +264,117 @@
return
SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index))
TIMER_COOLDOWN_END(source, index)
+
+/** Add a filter to the datum.
+ * This is on datum level, despite being most commonly / primarily used on atoms, so that filters can be applied to images / mutable appearances.
+ * Can also be used to assert a filter's existence. I.E. update a filter regardless if it exists or not.
+ *
+ * Arguments:
+ * * name - Filter name
+ * * priority - Priority used when sorting the filter.
+ * * params - Parameters of the filter.
+ */
+/datum/proc/add_filter(name, priority, list/params)
+ LAZYINITLIST(filter_data)
+ var/list/copied_parameters = params.Copy()
+ copied_parameters["priority"] = priority
+ filter_data[name] = copied_parameters
+ update_filters()
+
+/// Reapplies all the filters.
+/datum/proc/update_filters()
+ ASSERT(isatom(src) || istype(src, /image))
+ var/atom/atom_cast = src // filters only work with images or atoms.
+ atom_cast.filters = null
+ filter_data = sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE)
+ for(var/filter_raw in filter_data)
+ var/list/data = filter_data[filter_raw]
+ var/list/arguments = data.Copy()
+ arguments -= "priority"
+ atom_cast.filters += filter(arglist(arguments))
+ UNSETEMPTY(filter_data)
+
+/obj/item/update_filters()
+ . = ..()
+ update_item_action_buttons()
+
+/** Update a filter's parameter to the new one. If the filter doesnt exist we won't do anything.
+ *
+ * Arguments:
+ * * name - Filter name
+ * * new_params - New parameters of the filter
+ * * overwrite - TRUE means we replace the parameter list completely. FALSE means we only replace the things on new_params.
+ */
+/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE)
+ var/filter = get_filter(name)
+ if(!filter)
+ return
+ if(overwrite)
+ filter_data[name] = new_params
+ else
+ for(var/thing in new_params)
+ filter_data[name][thing] = new_params[thing]
+ update_filters()
+
+/** Update a filter's parameter and animate this change. If the filter doesnt exist we won't do anything.
+ * Basically a [datum/proc/modify_filter] call but with animations. Unmodified filter parameters are kept.
+ *
+ * Arguments:
+ * * name - Filter name
+ * * new_params - New parameters of the filter
+ * * time - time arg of the BYOND animate() proc.
+ * * easing - easing arg of the BYOND animate() proc.
+ * * loop - loop arg of the BYOND animate() proc.
+ */
+/datum/proc/transition_filter(name, list/new_params, time, easing, loop)
+ var/filter = get_filter(name)
+ if(!filter)
+ return
+ // This can get injected by the filter procs, we want to support them so bye byeeeee
+ new_params -= "type"
+ animate(filter, new_params, time = time, easing = easing, loop = loop)
+ modify_filter(name, new_params)
+
+/// Updates the priority of the passed filter key
+/datum/proc/change_filter_priority(name, new_priority)
+ if(!filter_data || !filter_data[name])
+ return
+
+ filter_data[name]["priority"] = new_priority
+ update_filters()
+
+/// Returns the filter associated with the passed key
+/datum/proc/get_filter(name)
+ ASSERT(isatom(src) || istype(src, /image))
+ if(filter_data && filter_data[name])
+ var/atom/atom_cast = src // filters only work with images or atoms.
+ return atom_cast.filters[filter_data.Find(name)]
+
+/// Returns the indice in filters of the given filter name.
+/// If it is not found, returns null.
+/datum/proc/get_filter_index(name)
+ return filter_data?.Find(name)
+
+/// Removes the passed filter, or multiple filters, if supplied with a list.
+/datum/proc/remove_filter(name_or_names)
+ if(!filter_data)
+ return
+
+ var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names)
+
+ for(var/name in names)
+ if(filter_data[name])
+ filter_data -= name
+ update_filters()
+
+/datum/proc/clear_filters()
+ ASSERT(isatom(src) || istype(src, /image))
+ var/atom/atom_cast = src // filters only work with images or atoms.
+ filter_data = null
+ atom_cast.filters = null
+
+/// Return text from this proc to provide extra context to hard deletes that happen to it
+/// Optional, you should use this for cases where replication is difficult and extra context is required
+/// Can be called more then once per object, use harddel_deets_dumped to avoid duplicate calls (I am so sorry)
+/datum/proc/dump_harddel_info()
+ return
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index c50f7f30f5c9..1f5b7946739b 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -31,17 +31,24 @@
VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete")
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
- //VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
+ VV_DROPDOWN_OPTION(VV_HK_MASS_REMOVECOMPONENT, "Mass Remove Component/Element")
+ VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
/datum/proc/on_reagent_change(changetype)
return
-//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
-//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
-//This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes.
+/**
+ * This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
+ * href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
+ * This proc is for "high level" actions like admin heal/set species/etc/etc. The low level debugging things should go in admin/view_variables/topic_basic.dm incase this runtimes.
+ */
/datum/proc/vv_do_topic(list/href_list)
if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE))
return FALSE //This is VV, not to be called by anything else.
+ if(SEND_SIGNAL(src, COMSIG_VV_TOPIC, usr, href_list) & COMPONENT_VV_HANDLED)
+ return FALSE
+ if(href_list[VV_HK_MODIFY_TRAITS])
+ usr.client.holder.modify_traits(src)
return TRUE
/datum/proc/vv_get_header()
diff --git a/code/datums/diseases/_disease.dm b/code/datums/diseases/_disease.dm
index 5b1de89b32c9..3c947f5a343e 100644
--- a/code/datums/diseases/_disease.dm
+++ b/code/datums/diseases/_disease.dm
@@ -125,7 +125,7 @@
if(end == start)
return TRUE
var/turf/Temp = get_step_towards(end, start)
- if(!CANATMOSPASS(end, Temp))
+ if(!TURFS_CAN_SHARE(end, Temp))
return FALSE
end = Temp
diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm
index 08dac30855f5..efb42ce2ad03 100644
--- a/code/datums/diseases/advance/symptoms/heal.dm
+++ b/code/datums/diseases/advance/symptoms/heal.dm
@@ -580,8 +580,8 @@
compatible_biotypes = ALL_BIOTYPES // bungus
threshold_descs = list(
- "Stage speed 9" = "Shorter delay until healing starts.",
- "Resistance 9" = "Increased rate of healing.",
+ "Stealth 5" = "Shorter delay until healing starts.",
+ "Resistance 10" = "Increased rate of healing.",
)
/datum/symptom/heal/symbiotic/Start(datum/disease/advance/A)
diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm
index ff6d2c2a2080..90e76db4a165 100644
--- a/code/datums/diseases/advance/symptoms/oxygen.dm
+++ b/code/datums/diseases/advance/symptoms/oxygen.dm
@@ -49,7 +49,7 @@ Bonus
if(4, 5)
M.adjustOxyLoss(-7, 0)
M.losebreath = max(0, M.losebreath - 4)
- if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL(M))
+ if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL(M) && !HAS_TRAIT(M, TRAIT_NO_BLOOD_REGEN))
M.blood_volume += 1
else
if(prob(base_message_chance))
diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm
index 410a215652c7..bd4682516c3d 100644
--- a/code/datums/diseases/advance/symptoms/sensory.dm
+++ b/code/datums/diseases/advance/symptoms/sensory.dm
@@ -98,16 +98,16 @@
to_chat(M, span_notice("Your vision slowly returns..."))
M.cure_blind(EYE_DAMAGE)
M.cure_nearsighted(EYE_DAMAGE)
- M.blur_eyes(35)
+ M.adjust_eye_blur(35)
else if(HAS_TRAIT_FROM(M, TRAIT_NEARSIGHT, EYE_DAMAGE))
to_chat(M, span_notice("You can finally focus your eyes on distant objects."))
M.cure_nearsighted(EYE_DAMAGE)
- M.blur_eyes(10)
+ M.adjust_eye_blur(10)
else if(M.eye_blind || M.eye_blurry)
M.set_blindness(0)
- M.set_blurriness(0)
+ M.set_eye_blur(0)
else if(eyes.damage > 0)
eyes.applyOrganDamage(-1)
else
diff --git a/code/datums/diseases/advance/symptoms/vision.dm b/code/datums/diseases/advance/symptoms/vision.dm
index 7d428afa0f53..c860a9f5a25d 100644
--- a/code/datums/diseases/advance/symptoms/vision.dm
+++ b/code/datums/diseases/advance/symptoms/vision.dm
@@ -57,10 +57,10 @@ Bonus
to_chat(M, span_warning("Your eyes itch."))
if(3, 4)
to_chat(M, span_warning("Your eyes burn!"))
- M.blur_eyes(10)
+ M.adjust_eye_blur(10)
eyes.applyOrganDamage(1)
else
- M.blur_eyes(20)
+ M.adjust_eye_blur(20)
eyes.applyOrganDamage(5)
if(eyes.damage >= 10)
M.become_nearsighted(EYE_DAMAGE)
diff --git a/code/datums/diseases/sleepy.dm b/code/datums/diseases/sleepy.dm
index 0ca951fa8a04..209a4ef207c3 100644
--- a/code/datums/diseases/sleepy.dm
+++ b/code/datums/diseases/sleepy.dm
@@ -50,10 +50,10 @@
affected_mob.adjustStaminaLoss(10)
if(prob(3))
to_chat(affected_mob, span_danger("Your eyes feel strained."))
- affected_mob.blur_eyes(6)
+ affected_mob.adjust_eye_blur(6)
if(prob(3))
to_chat(affected_mob, span_warning("[pick("So tired...","You feel very sleepy.","You have a hard time keeping your eyes open.","You try to stay awake.")]"))
- affected_mob.blur_eyes(6)
+ affected_mob.adjust_eye_blur(6)
affected_mob.set_confusion_if_lower(4 SECONDS)
affected_mob.adjustStaminaLoss(15)
if(prob(5))
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 7762da8d707f..f389781297ce 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -52,7 +52,7 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
var/unique_enzymes
///Stores the hashed values of traits such as skin tones, hair style, and gender
var/unique_identity
- var/blood_type
+ var/datum/blood_type/blood_type
///The type of mutant race the player is if applicable (i.e. potato-man)
var/datum/species/species = new /datum/species/human
///first value is mutant color
@@ -219,6 +219,12 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
L[DNA_POLY_DORSAL_BLOCK] = construct_block(GLOB.dorsal_tubes_list.Find(features["dorsal_tubes"]), GLOB.dorsal_tubes_list.len)
if(features["ethereal_mark"])
L[DNA_ETHEREAL_MARK_BLOCK] = construct_block(GLOB.ethereal_mark_list.Find(features["ethereal_mark"]), GLOB.ethereal_mark_list.len)
+ if(features["preternis_weathering"])
+ L[DNA_PRETERNIS_WEATHERING_BLOCK] = construct_block(GLOB.preternis_weathering_list.Find(features["preternis_weathering"]), GLOB.preternis_weathering_list.len)
+ if(features["preternis_antenna"])
+ L[DNA_PRETERNIS_ANTENNA_BLOCK] = construct_block(GLOB.preternis_antenna_list.Find(features["preternis_antenna"]), GLOB.preternis_antenna_list.len)
+ if(features["preternis_eye"])
+ L[DNA_PRETERNIS_EYE_BLOCK] = construct_block(GLOB.preternis_eye_list.Find(features["preternis_eye"]), GLOB.preternis_eye_list.len)
if(features["pod_hair"])
L[DNA_POD_HAIR_BLOCK] = construct_block(GLOB.pod_hair_list.Find(features["pod_hair"]), GLOB.pod_hair_list.len)
if(features["pod_flower"])
@@ -368,6 +374,12 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
set_uni_feature_block(blocknumber, construct_block(GLOB.dorsal_tubes_list.Find(features["dorsal_tubes"]), GLOB.dorsal_tubes_list.len))
if(DNA_ETHEREAL_MARK_BLOCK)
set_uni_feature_block(blocknumber, construct_block(GLOB.ethereal_mark_list.Find(features["ethereal_mark"]), GLOB.ethereal_mark_list.len))
+ if(DNA_PRETERNIS_WEATHERING_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.preternis_weathering_list.Find(features["preternis_weathering"]), GLOB.preternis_weathering_list.len))
+ if(DNA_PRETERNIS_ANTENNA_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.preternis_antenna_list.Find(features["preternis_antenna"]), GLOB.preternis_antenna_list.len))
+ if(DNA_PRETERNIS_EYE_BLOCK)
+ set_uni_feature_block(blocknumber, construct_block(GLOB.preternis_eye_list.Find(features["preternis_eye"]), GLOB.preternis_eye_list.len))
if(DNA_POD_HAIR_BLOCK)
set_uni_feature_block(blocknumber, construct_block(GLOB.pod_hair_list.Find(features["pod_hair"]), GLOB.pod_hair_list.len))
if(DNA_POD_FLOWER_BLOCK)
@@ -615,6 +627,12 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block())
dna.features["dorsal_tubes"] = GLOB.dorsal_tubes_list[deconstruct_block(get_uni_feature_block(features, DNA_POLY_DORSAL_BLOCK), GLOB.dorsal_tubes_list.len)]
if(dna.features["ethereal_mark"])
dna.features["ethereal_mark"] = GLOB.ethereal_mark_list[deconstruct_block(get_uni_feature_block(features, DNA_ETHEREAL_MARK_BLOCK), GLOB.ethereal_mark_list.len)]
+ if(dna.features["preternis_weathering"])
+ dna.features["preternis_weathering"] = GLOB.preternis_weathering_list[deconstruct_block(get_uni_feature_block(features, DNA_PRETERNIS_WEATHERING_BLOCK), GLOB.preternis_weathering_list.len)]
+ if(dna.features["preternis_antenna"])
+ dna.features["preternis_antenna"] = GLOB.preternis_antenna_list[deconstruct_block(get_uni_feature_block(features, DNA_PRETERNIS_ANTENNA_BLOCK), GLOB.preternis_antenna_list.len)]
+ if(dna.features["preternis_eye"])
+ dna.features["preternis_eye"] = GLOB.preternis_eye_list[deconstruct_block(get_uni_feature_block(features, DNA_PRETERNIS_EYE_BLOCK), GLOB.preternis_eye_list.len)]
if(dna.features["pod_hair"])
dna.features["pod_hair"] = GLOB.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), GLOB.pod_hair_list.len)]
if(dna.features["pod_flower"])
diff --git a/code/datums/elements/_element.dm b/code/datums/elements/_element.dm
index a3ec8be5dddc..bcafc83497cf 100644
--- a/code/datums/elements/_element.dm
+++ b/code/datums/elements/_element.dm
@@ -26,7 +26,7 @@
return ELEMENT_INCOMPATIBLE
SEND_SIGNAL(target, COMSIG_ELEMENT_ATTACH, src)
if(element_flags & ELEMENT_DETACH_ON_HOST_DESTROY)
- RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(OnTargetDelete), override = TRUE)
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(OnTargetDelete), override = TRUE)
/datum/element/proc/OnTargetDelete(datum/source, force)
SIGNAL_HANDLER
@@ -38,7 +38,7 @@
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(source, COMSIG_ELEMENT_DETACH, src)
- UnregisterSignal(source, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(source, COMSIG_QDELETING)
/datum/element/Destroy(force)
if(!force)
@@ -51,10 +51,13 @@
/// Finds the singleton for the element type given and attaches it to src
/datum/proc/_AddElement(list/arguments)
if(QDELING(src))
- //linter shits because of the way we init the maps and delete unused assets
- //CRASH("We just tried to add an element to a qdeleted datum, something is fucked")
+ var/datum/element/element_type = arguments[1]
+ stack_trace("We just tried to add the element [element_type] to a qdeleted datum, something is fucked")
return
+
var/datum/element/ele = SSdcs.GetElement(arguments)
+ if(!ele) // We couldn't fetch the element, likely because it was not an element.
+ return // the crash message has already been sent
arguments[1] = src
if(ele.Attach(arglist(arguments)) == ELEMENT_INCOMPATIBLE)
CRASH("Incompatible element [ele.type] was assigned to a [type]! args: [json_encode(args)]")
@@ -64,7 +67,9 @@
* You only need additional arguments beyond the type if you're using [ELEMENT_BESPOKE]
*/
/datum/proc/_RemoveElement(list/arguments)
- var/datum/element/ele = SSdcs.GetElement(arguments)
+ var/datum/element/ele = SSdcs.GetElement(arguments, FALSE)
+ if(!ele) // We couldn't fetch the element, likely because it didn't exist.
+ return
if(ele.element_flags & ELEMENT_COMPLEX_DETACH)
arguments[1] = src
ele.Detach(arglist(arguments))
diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm
index e45fb82c662f..64bb61e89ce1 100644
--- a/code/datums/elements/climbable.dm
+++ b/code/datums/elements/climbable.dm
@@ -21,13 +21,13 @@
src.climb_stun = climb_stun
RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, PROC_REF(attack_hand))
- RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(target, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive))
RegisterSignal(target, COMSIG_ATOM_BUMPED, PROC_REF(try_speedrun))
ADD_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type))
/datum/element/climbable/Detach(datum/target)
- UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_EXAMINE, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED))
+ UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_EXAMINE, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED))
REMOVE_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type))
return ..()
diff --git a/code/datums/elements/connect_loc.dm b/code/datums/elements/connect_loc.dm
index e6aecbe6aded..32b3b50b783a 100644
--- a/code/datums/elements/connect_loc.dm
+++ b/code/datums/elements/connect_loc.dm
@@ -1,7 +1,7 @@
/// This element hooks a signal onto the loc the current object is on.
/// When the object moves, it will unhook the signal and rehook it to the new object.
/datum/element/connect_loc
- element_flags = ELEMENT_BESPOKE
+ element_flags = ELEMENT_BESPOKE|ELEMENT_NO_LIST_UNIT_TEST
argument_hash_start_idx = 2
/// An assoc list of signal -> procpath to register to the loc this object is on.
diff --git a/code/datums/elements/decals/_decal.dm b/code/datums/elements/decals/_decal.dm
new file mode 100644
index 000000000000..fda646c03c9e
--- /dev/null
+++ b/code/datums/elements/decals/_decal.dm
@@ -0,0 +1,176 @@
+/datum/element/decal
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH_ON_HOST_DESTROY|ELEMENT_DONT_SORT_LIST_ARGS
+ argument_hash_start_idx = 2
+ /// Whether this decal can be cleaned.
+ var/cleanable
+ /// A description this decal appends to the target's examine message.
+ var/description
+ /// If true this was initialized with no set direction - will follow the parent dir.
+ var/directional
+ /// The base icon state that this decal was initialized with.
+ var/base_icon_state
+ /// What smoothing junction this was initialized with.
+ var/smoothing
+ /// The overlay applied by this decal to the target.
+ var/mutable_appearance/pic
+
+/// Remove old decals and apply new decals after rotation as necessary
+/datum/controller/subsystem/processing/dcs/proc/rotate_decals(datum/source, old_dir, new_dir)
+ SIGNAL_HANDLER
+
+ if(old_dir == new_dir)
+ return
+
+ var/list/datum/element/decal/old_decals = list() //instances
+ SEND_SIGNAL(source, COMSIG_ATOM_DECALS_ROTATING, old_decals)
+
+ if(!length(old_decals))
+ UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE)
+ return
+
+ var/list/resulting_decals_params = list() // param lists
+ for(var/datum/element/decal/rotating as anything in old_decals)
+ resulting_decals_params += list(rotating.get_rotated_parameters(old_dir,new_dir))
+
+ //Instead we could generate ids and only remove duplicates to save on churn on four-corners symmetry ?
+ for(var/datum/element/decal/decal in old_decals)
+ decal.Detach(source)
+
+ for(var/result in resulting_decals_params)
+ source.AddElement(/datum/element/decal, result["icon"], result["icon_state"], result["dir"], PLANE_TO_TRUE(result["plane"]), result["layer"], result["alpha"], result["color"], result["smoothing"], result["cleanable"], result["desc"])
+
+
+/datum/element/decal/proc/get_rotated_parameters(old_dir,new_dir)
+ var/rotation = 0
+ if(directional) //Even when the dirs are the same rotation is coming out as not 0 for some reason
+ rotation = SIMPLIFY_DEGREES(dir2angle(new_dir)-dir2angle(old_dir))
+ new_dir = turn(pic.dir,-rotation)
+ return list(
+ "icon" = pic.icon,
+ "icon_state" = base_icon_state,
+ "dir" = new_dir,
+ "plane" = pic.plane,
+ "layer" = pic.layer,
+ "alpha" = pic.alpha,
+ "color" = pic.color,
+ "smoothing" = smoothing,
+ "cleanable" = cleanable,
+ "desc" = description
+ )
+
+
+
+/datum/element/decal/Attach(atom/target, _icon, _icon_state, _dir, _plane=FLOAT_PLANE, _layer=FLOAT_LAYER, _alpha=255, _color, _smoothing, _cleanable=FALSE, _description, mutable_appearance/_pic)
+ . = ..()
+ if(!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+ if(_pic)
+ pic = _pic
+ else if(!generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, target))
+ return ELEMENT_INCOMPATIBLE
+ description = _description
+ cleanable = _cleanable
+ directional = _dir
+ base_icon_state = _icon_state
+ smoothing = _smoothing
+
+ RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(apply_overlay), TRUE)
+ if(target.flags_1 & INITIALIZED_1)
+ target.update_appearance(UPDATE_OVERLAYS) //could use some queuing here now maybe.
+ else
+ RegisterSignal(target,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE, PROC_REF(late_update_icon), TRUE)
+ if(isitem(target))
+ INVOKE_ASYNC(target, TYPE_PROC_REF(/obj/item/, update_slot_icon), TRUE)
+ if(_dir)
+ RegisterSignal(target, COMSIG_ATOM_DECALS_ROTATING, PROC_REF(shuttle_rotate), TRUE)
+ SSdcs.RegisterSignal(target, COMSIG_ATOM_DIR_CHANGE, TYPE_PROC_REF(/datum/controller/subsystem/processing/dcs, rotate_decals), override=TRUE)
+ if(!isnull(_smoothing))
+ RegisterSignal(target, COMSIG_ATOM_SMOOTHED_ICON, PROC_REF(smooth_react), TRUE)
+ if(_cleanable)
+ RegisterSignal(target, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_react), TRUE)
+ if(_description)
+ RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(examine),TRUE)
+
+ RegisterSignal(target, COMSIG_TURF_ON_SHUTTLE_MOVE, PROC_REF(shuttle_move_react),TRUE)
+
+/**
+ * ## generate_appearance
+ *
+ * If the decal was not given an appearance, it will generate one based on the other given arguments.
+ * element won't be compatible if it cannot do either
+ * all args are fed into creating an image, they are byond vars for images you'll recognize in the byond docs
+ * (except source, source is the object whose appearance we're copying.)
+ */
+/datum/element/decal/proc/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
+ if(!_icon || !_icon_state)
+ return FALSE
+ var/temp_image = image(_icon, null, isnull(_smoothing) ? _icon_state : "[_icon_state]-[_smoothing]", _layer, _dir)
+ pic = new(temp_image)
+ var/atom/atom_source = source
+ SET_PLANE_EXPLICIT(pic, _plane, atom_source)
+ pic.color = _color
+ pic.alpha = _alpha
+ return TRUE
+
+/datum/element/decal/Detach(atom/source)
+ UnregisterSignal(source, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_ATOM_EXAMINE, COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_TURF_ON_SHUTTLE_MOVE, COMSIG_ATOM_SMOOTHED_ICON))
+ SSdcs.UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE)
+ source.update_appearance(UPDATE_OVERLAYS)
+ if(isitem(source))
+ INVOKE_ASYNC(source, TYPE_PROC_REF(/obj/item/, update_slot_icon))
+ SEND_SIGNAL(source, COMSIG_TURF_DECAL_DETACHED, description, cleanable, directional, pic)
+ return ..()
+
+/datum/element/decal/proc/late_update_icon(atom/source)
+ SIGNAL_HANDLER
+
+ if(istype(source) && !(source.flags_1 & DECAL_INIT_UPDATE_EXPERIENCED_1))
+ source.flags_1 |= DECAL_INIT_UPDATE_EXPERIENCED_1 // I am so sorry, but it saves like 80ms I gotta
+ source.update_appearance(UPDATE_OVERLAYS)
+ UnregisterSignal(source, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
+
+/datum/element/decal/proc/apply_overlay(atom/source, list/overlay_list)
+ SIGNAL_HANDLER
+
+ overlay_list += pic
+
+/datum/element/decal/proc/clean_react(datum/source, clean_types)
+ SIGNAL_HANDLER
+
+ if(clean_types & cleanable)
+ Detach(source)
+ return COMPONENT_CLEANED
+ return NONE
+
+/datum/element/decal/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += description
+
+/datum/element/decal/proc/shuttle_move_react(datum/source, turf/new_turf)
+ SIGNAL_HANDLER
+
+ if(new_turf == source)
+ return
+ Detach(source)
+ new_turf.AddElement(type, pic.icon, base_icon_state, directional, pic.plane, pic.layer, pic.alpha, pic.color, smoothing, cleanable, description)
+
+/datum/element/decal/proc/shuttle_rotate(datum/source, list/datum/element/decal/rotating)
+ SIGNAL_HANDLER
+ rotating += src
+
+/**
+ * Reacts to the source atom smoothing.
+ *
+ * Arguments:
+ * - [source][/atom]: The source of the signal and recently smoothed atom.
+ */
+/datum/element/decal/proc/smooth_react(atom/source)
+ SIGNAL_HANDLER
+ var/smoothing_junction = source.smoothing_junction
+ if(smoothing_junction == smoothing)
+ return NONE
+
+ Detach(source)
+ source.AddElement(type, pic.icon, base_icon_state, directional, PLANE_TO_TRUE(pic.plane), pic.layer, pic.alpha, pic.color, smoothing_junction, cleanable, description)
+ return NONE
diff --git a/code/datums/elements/decals/blood.dm b/code/datums/elements/decals/blood.dm
new file mode 100644
index 000000000000..f496f76adf1e
--- /dev/null
+++ b/code/datums/elements/decals/blood.dm
@@ -0,0 +1,40 @@
+/datum/element/decal/blood
+
+/datum/element/decal/blood/Attach(datum/target, _icon, _icon_state, _dir, _plane, _layer, _alpha, _color, _smoothing, _cleanable=CLEAN_TYPE_BLOOD, _description, mutable_appearance/_pic)
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+
+ . = ..()
+ RegisterSignal(target, COMSIG_ATOM_GET_EXAMINE_NAME, PROC_REF(get_examine_name), TRUE)
+
+/datum/element/decal/blood/Detach(atom/source)
+ UnregisterSignal(source, COMSIG_ATOM_GET_EXAMINE_NAME)
+ return ..()
+
+/datum/element/decal/blood/generate_appearance(_icon, _icon_state, _dir, _plane, _layer, _color, _alpha, _smoothing, source)
+ var/obj/item/I = source
+ if(!_color)
+ _color = COLOR_BLOOD
+ var/icon = I.icon
+ var/icon_state = I.icon_state
+ if(!icon || !icon_state)
+ // It's something which takes on the look of other items, probably
+ icon = I.icon
+ icon_state = I.icon_state
+ var/icon/icon_for_size = icon(icon, icon_state)
+ var/scale_factor_x = icon_for_size.Width()/world.icon_size
+ var/scale_factor_y = icon_for_size.Height()/world.icon_size
+ var/mutable_appearance/blood_splatter = mutable_appearance('icons/effects/blood.dmi', "itemblood", appearance_flags = RESET_COLOR) //MA of the blood that we apply
+ blood_splatter.transform = blood_splatter.transform.Scale(scale_factor_x, scale_factor_y)
+ blood_splatter.blend_mode = BLEND_INSET_OVERLAY
+ blood_splatter.color = _color
+ pic = blood_splatter
+ return TRUE
+
+/datum/element/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
+ SIGNAL_HANDLER
+
+ var/atom/A = source
+ override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
+ override[EXAMINE_POSITION_BEFORE] = " blood-stained "
+ return COMPONENT_EXNAME_CHANGED
diff --git a/code/datums/elements/footstep.dm b/code/datums/elements/footstep.dm
index 83bb8578da85..92ce7471d22b 100644
--- a/code/datums/elements/footstep.dm
+++ b/code/datums/elements/footstep.dm
@@ -100,9 +100,9 @@
return
. = list(FOOTSTEP_MOB_SHOE = turf.footstep, FOOTSTEP_MOB_BAREFOOT = turf.barefootstep, FOOTSTEP_MOB_HEAVY = turf.heavyfootstep, FOOTSTEP_MOB_CLAW = turf.clawfootstep, STEP_SOUND_PRIORITY = STEP_SOUND_NO_PRIORITY)
- SEND_SIGNAL(turf, COMSIG_TURF_PREPARE_STEP_SOUND, .)
+
//The turf has no footstep sound (e.g. open space) and none of the objects on that turf (e.g. catwalks) overrides it
- if(isnull(turf.footstep))
+ if(!(SEND_SIGNAL(turf, COMSIG_TURF_PREPARE_STEP_SOUND, .) & FOOTSTEP_OVERRIDDEN) && isnull(turf.footstep))
return null
return .
@@ -148,7 +148,7 @@
//cache for sanic speed (lists are references anyways)
var/static/list/footstep_sounds = GLOB.footstep
- if ((source.wear_suit?.body_parts_covered | source.w_uniform?.body_parts_covered | source.shoes?.body_parts_covered) & FEET)
+ if (((source.wear_suit?.body_parts_covered | source.w_uniform?.body_parts_covered) & FEET) || (source.shoes && !istype(source.shoes, /obj/item/clothing/shoes/xeno_wraps)))
// we are wearing shoes
var/shoestep_type = prepared_steps[FOOTSTEP_MOB_SHOE]
diff --git a/code/datums/elements/footstep_override.dm b/code/datums/elements/footstep_override.dm
index 4e0c346c5be2..05a6d9606021 100644
--- a/code/datums/elements/footstep_override.dm
+++ b/code/datums/elements/footstep_override.dm
@@ -78,3 +78,4 @@
steps[FOOTSTEP_MOB_HEAVY] = heavyfootstep
steps[FOOTSTEP_MOB_CLAW] = clawfootstep
steps[STEP_SOUND_PRIORITY] = priority
+ return FOOTSTEP_OVERRIDDEN
diff --git a/code/datums/elements/mirage_border.dm b/code/datums/elements/mirage_border.dm
new file mode 100644
index 000000000000..999455a0b834
--- /dev/null
+++ b/code/datums/elements/mirage_border.dm
@@ -0,0 +1,41 @@
+/**
+ * Creates a mirage effect allowing you to see around the world border, by adding the opposite side to its vis_contents.
+ */
+/datum/element/mirage_border
+
+/datum/element/mirage_border/Attach(datum/target, turf/target_turf, direction, range=world.view)
+ . = ..()
+ if(!isturf(target))
+ return ELEMENT_INCOMPATIBLE
+ #ifdef TESTING
+ // This is a highly used proc, and these error states never occur, so limit it to testing.
+ // If something goes wrong it will runtime anyway.
+ if(!target_turf || !istype(target_turf) || !direction)
+ stack_trace("[type] improperly attached with the following args: target=\[[target_turf]\], direction=\[[direction]\], range=\[[range]\]")
+ return ELEMENT_INCOMPATIBLE
+ #endif
+
+ var/atom/movable/mirage_holder/holder = new(target)
+
+ var/x = target_turf.x
+ var/y = target_turf.y
+ var/z = clamp(target_turf.z, 1, world.maxz)
+ var/turf/southwest = locate(clamp(x - (direction & WEST ? range : 0), 1, world.maxx), clamp(y - (direction & SOUTH ? range : 0), 1, world.maxy), z)
+ var/turf/northeast = locate(clamp(x + (direction & EAST ? range : 0), 1, world.maxx), clamp(y + (direction & NORTH ? range : 0), 1, world.maxy), z)
+ holder.vis_contents += block(southwest, northeast)
+ if(direction & SOUTH)
+ holder.pixel_y -= world.icon_size * range
+ if(direction & WEST)
+ holder.pixel_x -= world.icon_size * range
+
+/datum/element/mirage_border/Detach(atom/movable/target)
+ . = ..()
+ var/atom/movable/mirage_holder/held = locate() in target.contents
+ if(held)
+ qdel(held)
+
+INITIALIZE_IMMEDIATE(/atom/movable/mirage_holder)
+// Using /atom/movable because this is a heavily used path
+/atom/movable/mirage_holder
+ name = "Mirage holder"
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
diff --git a/code/datums/elements/rust.dm b/code/datums/elements/rust.dm
index b0d8823748ce..ced721f61030 100644
--- a/code/datums/elements/rust.dm
+++ b/code/datums/elements/rust.dm
@@ -16,7 +16,7 @@
rust_overlay = image(rust_icon, rust_icon_state, -0.9)//above -1 so it draws over smoothing overlays
ADD_TRAIT(target, TRAIT_RUSTY, ELEMENT_TRAIT(type))
RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(apply_rust_overlay))
- RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(handle_examine))
+ RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(handle_examine))
RegisterSignals(target, COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WELDER), PROC_REF(secondary_tool_act))
// Unfortunately registering with parent sometimes doesn't cause an overlay update
target.update_appearance()
@@ -24,7 +24,7 @@
/datum/element/rust/Detach(atom/source)
. = ..()
UnregisterSignal(source, COMSIG_ATOM_UPDATE_OVERLAYS)
- UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(source, COMSIG_ATOM_EXAMINE)
UnregisterSignal(source, COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WELDER))
REMOVE_TRAIT(source, TRAIT_RUSTY, ELEMENT_TRAIT(type))
source.update_appearance()
diff --git a/code/datums/elements/speech_bubble_override.dm b/code/datums/elements/speech_bubble_override.dm
new file mode 100644
index 000000000000..6a72bf3b97fa
--- /dev/null
+++ b/code/datums/elements/speech_bubble_override.dm
@@ -0,0 +1,27 @@
+/datum/element/speech_bubble_override
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH_ON_HOST_DESTROY
+ argument_hash_start_idx = 2
+ var/bubble_type
+
+/datum/element/speech_bubble_override/Attach(datum/target, bubble_type)
+ . = ..()
+ if(!ismob(target))
+ return ELEMENT_INCOMPATIBLE
+
+ src.bubble_type = bubble_type
+
+ RegisterSignal(target, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+ RegisterSignal(target, COMSIG_MOB_CREATE_TYPING_INDICATOR, PROC_REF(handle_typing_indicator))
+
+/datum/element/speech_bubble_override/Detach(datum/source, ...)
+ UnregisterSignal(source, COMSIG_MOB_SAY)
+ UnregisterSignal(source, COMSIG_MOB_CREATE_TYPING_INDICATOR)
+ return ..()
+
+/datum/element/speech_bubble_override/proc/handle_speech(mob/target, list/speech_args)
+ SIGNAL_HANDLER
+ speech_args[SPEECH_BUBBLE_TYPE] = bubble_type
+
+/datum/element/speech_bubble_override/proc/handle_typing_indicator(mob/target, list/bubble_args)
+ SIGNAL_HANDLER
+ bubble_args[BUBBLE_ICON_STATE] = bubble_type
diff --git a/code/datums/elements/turf_transparency.dm b/code/datums/elements/turf_transparency.dm
new file mode 100644
index 000000000000..b050dd0866f0
--- /dev/null
+++ b/code/datums/elements/turf_transparency.dm
@@ -0,0 +1,280 @@
+/// List of z pillars (datums placed in the bottom left of XbyX squares that control transparency in that space)
+/// The pillars are stored in triple depth lists indexed by (world_size % pillar_size) + 1
+/// They are created at transparent turf request, and deleted when no turfs remain
+GLOBAL_LIST_EMPTY(pillars_by_z)
+#define Z_PILLAR_RADIUS 20
+// Takes a position, transforms it into a z pillar key
+#define Z_PILLAR_TRANSFORM(pos) (ROUND_UP(pos / Z_PILLAR_RADIUS))
+// Takes a z pillar key, hands back the actual posiiton it represents
+// A key of 1 becomes 1, a key of 2 becomes Z_PILLAR_RADIUS + 1, etc.
+#define Z_KEY_TO_POSITION(key) (((key - 1) * Z_PILLAR_RADIUS) + 1)
+
+/// Returns a z pillar to insert turfs into
+/proc/request_z_pillar(x, y, z)
+ var/list/pillars_by_z = GLOB.pillars_by_z
+ if(length(pillars_by_z) < z)
+ pillars_by_z.len = z
+ var/list/our_z = pillars_by_z[z]
+ if(!our_z)
+ our_z = list()
+ pillars_by_z[z] = our_z
+
+ //Now that we've got the z layer sorted, we're gonna check the X line
+ var/x_key = Z_PILLAR_TRANSFORM(x)
+ if(length(our_z) < x_key)
+ our_z.len = x_key
+ var/list/our_x = our_z[x_key]
+ if(!our_x)
+ our_x = list()
+ our_z[x_key] = our_x
+
+ //And now the y layer
+ var/y_key = Z_PILLAR_TRANSFORM(y)
+ if(length(our_x) < y_key)
+ our_x.len = y_key
+ var/datum/z_pillar/our_lad = our_x[y_key]
+ if(!our_lad)
+ our_lad = new(x_key, y_key, z)
+ our_x[y_key] = our_lad
+ return our_lad
+
+/// Exists to be placed on the turf of walls and such to hold the vis_contents of the tile below
+/// Otherwise the lower turf might get shifted around, which is dumb. do this instead.
+/obj/effect/abstract/z_holder
+ var/datum/z_pillar/pillar
+ var/turf/show_for
+ appearance_flags = PIXEL_SCALE
+ plane = HUD_PLANE
+ anchored = TRUE
+ move_resist = INFINITY
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/effect/abstract/z_holder/Destroy()
+ if(pillar)
+ pillar.drawing_object -= show_for
+ pillar = null
+ show_for = null
+ return ..()
+
+/obj/effect/abstract/z_holder/proc/display(turf/display, datum/z_pillar/behalf_of)
+ if(pillar)
+ CRASH("We attempted to use a z holder to display when it was already in use, what'd you do")
+
+ pillar = behalf_of
+ show_for = display
+ vis_contents += display
+ behalf_of.drawing_object[display] = src
+
+/// Grouping datum that manages transparency for a block of space
+/// Setup to ease debugging, and to make add/remove operations cheaper
+/datum/z_pillar
+ var/x_pos
+ var/y_pos
+ var/z_pos
+ /// Assoc list in the form displayed turf -> list of sources
+ var/list/turf_sources = list()
+ /// Assoc list of turfs using z holders in the form displayed turf -> z holder
+ var/list/drawing_object = list()
+
+/datum/z_pillar/New(x_pos, y_pos, z_pos)
+ . = ..()
+ src.x_pos = x_pos
+ src.y_pos = y_pos
+ src.z_pos = z_pos
+
+/datum/z_pillar/Destroy()
+ GLOB.pillars_by_z[z_pos][x_pos][y_pos] = null
+ // Just to be totally clear, this is code that exists to
+ // A: make sure cleanup is actually possible for this datum, just in case someone goes insane
+ // B: allow for easier debugging and making sure everything behaves as expected when fully removed
+ // It is not meant to be relied on, please don't actually it's not very fast
+ for(var/turf/displaying in turf_sources)
+ for(var/turf/displaying_for in turf_sources[displaying])
+ hide_turf(displaying, displaying_for)
+ return ..()
+
+/// Displays a turf from the z level below us on our level
+/datum/z_pillar/proc/display_turf(turf/to_display, turf/source)
+ var/list/sources = turf_sources[to_display]
+
+ if(sources) // If we aren't the first to request this turf, return
+ sources |= source
+ var/obj/effect/abstract/z_holder/holding = drawing_object[to_display]
+ if(!holding)
+ return
+
+ var/turf/visual_target = GET_TURF_ABOVE(to_display)
+ /// Basically, if we used to be under a non transparent turf, but are no longer in that position
+ /// Then we add to the transparent turf we're now under, and nuke the old object
+ if(!istransparentturf(visual_target))
+ return
+
+ holding.vis_contents -= to_display
+ qdel(holding)
+ drawing_object -= to_display
+ visual_target.vis_contents += to_display
+ return
+
+ // Otherwise, we need to create a new set of sources. let's do that yeah?
+ sources = list()
+ turf_sources[to_display] = sources
+ sources |= source
+
+ var/turf/visual_target = GET_TURF_ABOVE(to_display)
+ if(istransparentturf(visual_target) || isopenspaceturf(visual_target))
+ visual_target.vis_contents += to_display
+ else
+ var/obj/effect/abstract/z_holder/hold_this = new(visual_target)
+ hold_this.display(to_display, src)
+
+/// Hides an existing turf from our vis_contents, or the vis_contents of the source if applicable
+/datum/z_pillar/proc/hide_turf(turf/to_hide, turf/source)
+ var/list/sources = turf_sources[to_hide]
+ if(!sources)
+ return
+ sources -= source
+ // More sources remain
+ if(length(sources))
+ return
+
+ turf_sources -= to_hide
+ var/obj/effect/abstract/z_holder/holding = drawing_object[to_hide]
+ if(holding)
+ qdel(holding)
+ else
+ var/turf/visual_target = GET_TURF_ABOVE(to_hide)
+ visual_target.vis_contents -= to_hide
+
+ if(!length(turf_sources) && !QDELETED(src))
+ qdel(src)
+
+/// Called when a transparent turf is cleared. We wait a tick, then check to see what
+/// Kind of turf replaced our former holder, and resetup our visuals as desired
+/// We do not need to do this for non transparent holders, because they will have their abstract object cleared
+/// When a transparent holder comes back.
+/datum/z_pillar/proc/parent_cleared(turf/visual, turf/current_holder)
+ addtimer(CALLBACK(src, PROC_REF(refresh_orphan), visual, current_holder))
+
+/// Runs the actual refresh of some formerly orphaned via vis_loc deletiong turf
+/// We'll only reup if we either have no souece, or if the source is a transparent turf
+/datum/z_pillar/proc/refresh_orphan(turf/orphan, turf/parent)
+ var/list/sources = turf_sources[orphan]
+ if(!length(sources))
+ return
+
+ var/obj/effect/abstract/z_holder/holding = drawing_object[orphan]
+ if(holding)
+ return
+
+ if(istransparentturf(parent) || isopenspaceturf(parent))
+ parent.vis_contents += orphan
+ else
+ var/obj/effect/abstract/z_holder/hold_this = new(parent)
+ hold_this.display(orphan, src)
+
+/datum/element/turf_z_transparency
+ element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
+
+///This proc sets up the signals to handle updating viscontents when turfs above/below update. Handle plane and layer here too so that they don't cover other obs/turfs in Dream Maker
+/datum/element/turf_z_transparency/Attach(datum/target, mapload)
+ . = ..()
+ if(!isturf(target))
+ return ELEMENT_INCOMPATIBLE
+
+ var/turf/our_turf = target
+
+ RegisterSignal(target, COMSIG_TURF_MULTIZ_DEL, PROC_REF(on_multiz_turf_del))
+ RegisterSignal(target, COMSIG_TURF_MULTIZ_NEW, PROC_REF(on_multiz_turf_new))
+
+ ADD_TRAIT(our_turf, TURF_Z_TRANSPARENT_TRAIT, ELEMENT_TRAIT(type))
+
+ if(!mapload)
+ update_multi_z(our_turf)
+
+/datum/element/turf_z_transparency/Detach(datum/source)
+ . = ..()
+ var/turf/our_turf = source
+ clear_multiz(our_turf)
+
+ UnregisterSignal(our_turf, list(COMSIG_TURF_MULTIZ_NEW, COMSIG_TURF_MULTIZ_DEL))
+ REMOVE_TRAIT(our_turf, TURF_Z_TRANSPARENT_TRAIT, ELEMENT_TRAIT(type))
+
+///Updates the viscontents or underlays below this tile.
+/datum/element/turf_z_transparency/proc/update_multi_z(turf/our_turf)
+ var/turf/below_turf = GET_TURF_BELOW(our_turf)
+ if(below_turf) // If we actually have something below us, display it.
+ for(var/turf/partner in range(1, below_turf))
+ // We use our z here to ensure the pillar is actually on our level
+ var/datum/z_pillar/z_boss = request_z_pillar(partner.x, partner.y, our_turf.z)
+ z_boss.display_turf(partner, our_turf)
+ else
+ our_turf.underlays += get_baseturf_underlay(our_turf)
+
+ // This shit is stupid
+ // z transparency is for making something SHOW WHAT'S BENEATH it, or if nothing is, show
+ // the appropriate underlay
+ // IT IS NOT FOR MAKING YOUR CLOSED TURF SEETHROUGH
+ // these are different concerns, and should not be HANDLED TOGETHER
+ // similarly, if you rip this out, rework diagonal closed turfs to work with this system
+ // it will make them look significantly nicer, and should let you tie into their logic more easily
+ // Just please don't break behavior yeah? thanks, I love you <3
+ if(isclosedturf(our_turf)) //Show girders below closed turfs
+ var/mutable_appearance/girder_underlay = mutable_appearance('icons/obj/structures.dmi', "girder", layer = TURF_LAYER-0.01)
+ girder_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ our_turf.underlays += girder_underlay
+ var/mutable_appearance/plating_underlay = mutable_appearance('icons/turf/floors.dmi', "plating", layer = TURF_LAYER-0.02)
+ plating_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ our_turf.underlays += plating_underlay
+ return TRUE
+
+/datum/element/turf_z_transparency/proc/clear_multiz(turf/our_turf)
+ var/turf/below_turf = GET_TURF_BELOW(our_turf)
+ if(below_turf) // If we actually have something below us, we need to clear ourselves from it
+ for(var/turf/partner in range(1, below_turf))
+ // We use our z here to ensure the pillar is actually on our level
+ var/datum/z_pillar/z_boss = request_z_pillar(partner.x, partner.y, our_turf.z)
+ z_boss.hide_turf(partner, our_turf)
+ if(partner == below_turf)
+ z_boss.parent_cleared(below_turf, our_turf)
+ else
+ our_turf.underlays -= get_baseturf_underlay(our_turf)
+
+ if(isclosedturf(our_turf)) //Show girders below closed turfs
+ var/mutable_appearance/girder_underlay = mutable_appearance('icons/obj/structures.dmi', "girder", layer = TURF_LAYER-0.01)
+ girder_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ our_turf.underlays -= girder_underlay
+ var/mutable_appearance/plating_underlay = mutable_appearance('icons/turf/floors.dmi', "plating", layer = TURF_LAYER-0.02)
+ plating_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ our_turf.underlays -= plating_underlay
+
+/datum/element/turf_z_transparency/proc/on_multiz_turf_del(turf/our_turf, turf/below_turf, dir)
+ SIGNAL_HANDLER
+
+ if(dir != DOWN)
+ return
+
+ update_multi_z(our_turf)
+
+/datum/element/turf_z_transparency/proc/on_multiz_turf_new(turf/our_turf, turf/below_turf, dir)
+ SIGNAL_HANDLER
+
+ if(dir != DOWN)
+ return
+
+ update_multi_z(our_turf)
+
+///Called when there is no real turf below this turf
+/datum/element/turf_z_transparency/proc/get_baseturf_underlay(turf/our_turf)
+ var/turf/path = SSmapping.level_trait(our_turf.z, ZTRAIT_BASETURF) || /turf/open/space
+ if(!ispath(path))
+ path = text2path(path)
+ if(!ispath(path))
+ warning("Z-level [our_turf.z] has invalid baseturf '[SSmapping.level_trait(our_turf.z, ZTRAIT_BASETURF)]'")
+ path = /turf/open/space
+ var/mutable_appearance/underlay_appearance = mutable_appearance(initial(path.icon), initial(path.icon_state), layer = TURF_LAYER-0.02, offset_spokesman = our_turf, plane = PLANE_SPACE)
+ underlay_appearance.appearance_flags = RESET_ALPHA | RESET_COLOR
+ return underlay_appearance
+
+#undef Z_PILLAR_RADIUS
+#undef Z_PILLAR_TRANSFORM
+#undef Z_KEY_TO_POSITION
diff --git a/code/datums/elements/undertile.dm b/code/datums/elements/undertile.dm
new file mode 100644
index 000000000000..fcdf0db94bc0
--- /dev/null
+++ b/code/datums/elements/undertile.dm
@@ -0,0 +1,84 @@
+/// The alpha we give to stuff under tiles, if they want it
+#define ALPHA_UNDERTILE 128
+
+///Add to an object if you want to be able to be hidden under tiles
+/datum/element/undertile
+ element_flags = ELEMENT_BESPOKE | COMPONENT_DUPE_HIGHLANDER
+ argument_hash_start_idx = 2
+
+ ///the invisiblity trait applied, like TRAIT_T_RAY_VISIBLE
+ var/invisibility_trait
+ ///level of invisibility applied when under a tile. Could be INVISIBILITY_OBSERVER if you still want it to be visible to ghosts
+ var/invisibility_level
+ ///an overlay for the tile if we wish to apply that
+ var/tile_overlay
+ ///whether we use alpha or not. TRUE uses ALPHA_UNDERTILE because otherwise we have 200 different instances of this element for different alphas
+ var/use_alpha
+ ///We will switch between anchored and unanchored. for stuff like satchels that shouldn't be pullable under tiles but are otherwise unanchored
+ var/use_anchor
+
+/datum/element/undertile/Attach(datum/target, invisibility_trait, invisibility_level = INVISIBILITY_MAXIMUM, tile_overlay, use_alpha = TRUE, use_anchor = FALSE)
+ . = ..()
+
+ if(!ismovable(target))
+ return ELEMENT_INCOMPATIBLE
+
+ RegisterSignal(target, COMSIG_OBJ_HIDE, PROC_REF(hide))
+
+ src.invisibility_trait = invisibility_trait
+ src.invisibility_level = invisibility_level
+ src.tile_overlay = tile_overlay
+ src.use_alpha = use_alpha
+ src.use_anchor = use_anchor
+
+///called when a tile has been covered or uncovered
+/datum/element/undertile/proc/hide(atom/movable/source, underfloor_accessibility)
+ SIGNAL_HANDLER
+
+ if(underfloor_accessibility < UNDERFLOOR_VISIBLE)
+ source.SetInvisibility(invisibility_level, id=type)
+ else
+ source.RemoveInvisibility(type)
+
+ var/turf/T = get_turf(source)
+
+ if(underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
+ SET_PLANE_IMPLICIT(source, FLOOR_PLANE) // We do this so that turfs that allow you to see what's underneath them don't have to be on the game plane (which causes ambient occlusion weirdness)
+ ADD_TRAIT(source, TRAIT_UNDERFLOOR, REF(src))
+
+ if(tile_overlay)
+ T.add_overlay(tile_overlay)
+
+ if(use_anchor)
+ source.anchored = TRUE
+
+ if(underfloor_accessibility < UNDERFLOOR_VISIBLE)
+ if(use_alpha)
+ source.alpha = ALPHA_UNDERTILE
+
+ if(invisibility_trait)
+ ADD_TRAIT(source, invisibility_trait, ELEMENT_TRAIT(type))
+
+ else
+ SET_PLANE_IMPLICIT(source, initial(source.plane))
+ REMOVE_TRAIT(source, TRAIT_UNDERFLOOR, REF(src))
+
+ if(invisibility_trait)
+ REMOVE_TRAIT(source, invisibility_trait, ELEMENT_TRAIT(type))
+
+ if(tile_overlay)
+ T.overlays -= tile_overlay
+
+ if(use_alpha)
+ source.alpha = initial(source.alpha)
+
+ if(use_anchor)
+ source.anchored = FALSE
+
+/datum/element/undertile/Detach(atom/movable/source, visibility_trait, invisibility_level = INVISIBILITY_MAXIMUM)
+ . = ..()
+
+ hide(source, UNDERFLOOR_INTERACTABLE)
+ source.RemoveInvisibility(type)
+
+#undef ALPHA_UNDERTILE
diff --git a/code/datums/ert.dm b/code/datums/ert.dm
index 51df70e08a41..26a22dafb9f2 100644
--- a/code/datums/ert.dm
+++ b/code/datums/ert.dm
@@ -14,7 +14,7 @@
var/dusting = FALSE
// this can be safely set as default because it doesnt do anything unless specifically making uplinked ERT
- var/obj/item/uplinktype = /obj/item/ntuplink/official
+ var/obj/item/uplinktype = /obj/item/ntuplink/official
/datum/ert/New()
if (!polldesc)
@@ -42,7 +42,7 @@
polldesc = "the Peacekeeping Force"
teamsize = 5 // redundant but keeping this here for clarity
leader_role = /datum/antagonist/ert/occupying/commander
- roles = list(/datum/antagonist/ert/occupying,/datum/antagonist/ert/occupying/heavy,/datum/antagonist/ert/occupying,/datum/antagonist/ert/occupying)
+ roles = list(/datum/antagonist/ert/occupying,/datum/antagonist/ert/occupying/heavy,/datum/antagonist/ert/occupying,/datum/antagonist/ert/occupying)
/datum/ert/red
leader_role = /datum/antagonist/ert/commander/red
@@ -58,6 +58,14 @@
mission = "Leave no witnesses."
polldesc = "an elite Nanotrasen Strike Team"
+/datum/ert/mining
+ leader_role = /datum/antagonist/ert/mining
+ roles = list(/datum/antagonist/ert/mining)
+ rename_team = "Megafauna Kill Team"
+ code = "Rock and STONE"
+ mission = "Eliminate hostile fauna while minimizing casualties."
+ polldesc = "A merry band of Megafauna-hunting dwarves"
+
/datum/ert/official
code = "Green"
teamsize = 1
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index fcca5c762f24..40b485cd87f0 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -73,13 +73,14 @@
if(!destturf || !curturf || destturf.is_transition_turf())
return FALSE
- var/area/A = get_area(curturf)
- var/area/B = get_area(destturf)
- if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || A.noteleport || B.noteleport))
- return FALSE
+ if(!forced)
+ if(!check_teleport_valid(teleatom, destination, channel))
+ teleatom.balloon_alert(teleatom, "something holds you back!")
+ return FALSE
- if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf))
- return FALSE
+ if(isobserver(teleatom))
+ teleatom.abstract_move(destturf)
+ return TRUE
tele_play_specials(teleatom, curturf, effectin, asoundin)
var/success = forceMove ? teleatom.forceMove(destturf) : teleatom.Move(destturf)
@@ -93,6 +94,8 @@
var/mob/M = teleatom
M.cancel_camera()
+ SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT)
+
return TRUE
/**
@@ -222,10 +225,10 @@
if(SEND_SIGNAL(teleported_atom, COMSIG_MOVABLE_TELEPORTING, destination, channel) & COMPONENT_BLOCK_TELEPORT)
return FALSE
- if(SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, origin_turf, destination_turf) & COMPONENT_BLOCK_TELEPORT)
+ if(SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORTING, channel, origin_turf, destination_turf) & COMPONENT_BLOCK_TELEPORT)
return FALSE
SEND_SIGNAL(teleported_atom, COMSIG_MOVABLE_TELEPORTED, destination, channel)
- SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, origin_turf, destination_turf)
+ SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORTED, channel, origin_turf, destination_turf)
return TRUE
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index 1a305ea22b87..c401a9aec7a9 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -9,10 +9,13 @@
#define HOLORECORD_MAX_LENGTH 200
-/mob/camera/aiEye/remote/holo/setLoc()
- . = ..()
+/mob/camera/ai_eye/remote/holo/setLoc(turf/destination, force_update = FALSE)
+ // If we're moving outside the space of our projector, then just... don't
var/obj/machinery/holopad/H = origin
- H?.move_hologram(eye_user, loc)
+ if(!H?.move_hologram(eye_user, destination))
+ sprint = initial(sprint) // Reset sprint so it doesn't balloon in our calling proc
+ return
+ return ..()
/obj/machinery/holopad/remove_eye_control(mob/living/user)
if(user.client)
@@ -27,7 +30,7 @@
var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null)
var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered
- var/mob/camera/aiEye/remote/holo/eye //user's eye, once connected
+ var/mob/camera/ai_eye/remote/holo/eye //user's eye, once connected
var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected
var/datum/action/innate/end_holocall/hangup //hangup action
@@ -444,7 +447,7 @@
DELAY 10
NAME Maria Dell
PRESET /datum/preset_holoimage/engineer/atmos
- SAY It's fine, don't worry. I've got Plastic on it. And frankly, i'm kinda busy with, the, uhhm, incinerator.
+ SAY It's fine, don't worry. I've got Plastic on it. And frankly, I'm kinda busy with, the, uhhm, incinerator.
DELAY 30
NAME Dave Tundrale
PRESET /datum/preset_holoimage/engineer
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index 7b890f336749..f8b5154b8ead 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -139,7 +139,7 @@ GLOBAL_LIST_INIT(huds, list(
if(!hud_users_all_z_levels[new_viewer])
hud_users_all_z_levels[new_viewer] = 1
- RegisterSignal(new_viewer, COMSIG_PARENT_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud users and hud atoms use these signals
+ RegisterSignal(new_viewer, COMSIG_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud users and hud atoms use these signals
RegisterSignal(new_viewer, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_atom_or_user_z_level_changed), override = TRUE)
var/turf/their_turf = get_turf(new_viewer)
@@ -171,7 +171,7 @@ GLOBAL_LIST_INIT(huds, list(
if(!hud_atoms_all_z_levels[former_viewer])//make sure we arent unregistering changes on a mob thats also a hud atom for this hud
UnregisterSignal(former_viewer, COMSIG_MOVABLE_Z_CHANGED)
- UnregisterSignal(former_viewer, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(former_viewer, COMSIG_QDELETING)
hud_users_all_z_levels -= former_viewer
@@ -195,7 +195,7 @@ GLOBAL_LIST_INIT(huds, list(
// No matter where or who you are, you matter to me :)
RegisterSignal(new_hud_atom, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_atom_or_user_z_level_changed), override = TRUE)
- RegisterSignal(new_hud_atom, COMSIG_PARENT_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud atoms and hud users use these signals
+ RegisterSignal(new_hud_atom, COMSIG_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud atoms and hud users use these signals
hud_atoms_all_z_levels[new_hud_atom] = TRUE
var/turf/atom_turf = get_turf(new_hud_atom)
@@ -217,7 +217,7 @@ GLOBAL_LIST_INIT(huds, list(
//make sure we arent unregistering a hud atom thats also a hud user mob
if(!hud_users_all_z_levels[hud_atom_to_remove])
UnregisterSignal(hud_atom_to_remove, COMSIG_MOVABLE_Z_CHANGED)
- UnregisterSignal(hud_atom_to_remove, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(hud_atom_to_remove, COMSIG_QDELETING)
for(var/mob/mob_to_remove as anything in hud_users_all_z_levels)
remove_atom_from_single_hud(mob_to_remove, hud_atom_to_remove)
@@ -276,30 +276,30 @@ GLOBAL_LIST_INIT(huds, list(
///when a hud atom or hud user changes z levels this makes sure it gets the images it needs and removes the images it doesnt need.
///because of how signals work we need the same proc to handle both use cases because being a hud atom and being a hud user arent mutually exclusive
-/datum/atom_hud/proc/on_atom_or_user_z_level_changed(atom/movable/moved_atom, old_z, new_z) //turf/old_turf, turf/new_turf
+/datum/atom_hud/proc/on_atom_or_user_z_level_changed(atom/movable/moved_atom, turf/old_turf, turf/new_turf)
SIGNAL_HANDLER
- if(old_z)
+ if(old_turf)
if(hud_users_all_z_levels[moved_atom])
- hud_users[old_z] -= moved_atom
+ hud_users[old_turf.z] -= moved_atom
- remove_all_atoms_from_single_hud(moved_atom, get_hud_atoms_for_z_level(old_z))
+ remove_all_atoms_from_single_hud(moved_atom, get_hud_atoms_for_z_level(old_turf.z))
if(hud_atoms_all_z_levels[moved_atom])
- hud_atoms[old_z] -= moved_atom
+ hud_atoms[old_turf.z] -= moved_atom
//this wont include moved_atom since its removed
- remove_atom_from_all_huds(get_hud_users_for_z_level(old_z), moved_atom)
+ remove_atom_from_all_huds(get_hud_users_for_z_level(old_turf.z), moved_atom)
- if(new_z)
+ if(new_turf)
if(hud_users_all_z_levels[moved_atom])
- hud_users[new_z][moved_atom] = TRUE //hud users is associative, hud atoms isnt
+ hud_users[new_turf.z][moved_atom] = TRUE //hud users is associative, hud atoms isnt
- add_all_atoms_to_single_mob_hud(moved_atom, get_hud_atoms_for_z_level( new_z))
+ add_all_atoms_to_single_mob_hud(moved_atom, get_hud_atoms_for_z_level(new_turf.z))
if(hud_atoms_all_z_levels[moved_atom])
- hud_atoms[ new_z] |= moved_atom
+ hud_atoms[new_turf.z] |= moved_atom
- add_atom_to_all_mob_huds(get_hud_users_for_z_level(new_z), moved_atom)
+ add_atom_to_all_mob_huds(get_hud_users_for_z_level(new_turf.z), moved_atom)
/// add just hud_atom's hud images (that are part of this atom_hud) to requesting_mob's client.images list
/datum/atom_hud/proc/add_atom_to_single_mob_hud(mob/requesting_mob, atom/hud_atom) //unsafe, no sanity apart from client
diff --git a/code/datums/lazy_template.dm b/code/datums/lazy_template.dm
new file mode 100644
index 000000000000..7e8e57f126d7
--- /dev/null
+++ b/code/datums/lazy_template.dm
@@ -0,0 +1,126 @@
+
+/**
+ * Datum used to designate certain areas that do not need to exist nor be loaded at world start
+ * but do want to be loaded under certain circumstances. Use this for stuff like the nukie base or wizden, aka stuff that only matters when their antag is rolled.
+ */
+/datum/lazy_template
+ /// If this is true each load will increment an index keyed to the type and it will load [map_name]_[index]
+ var/list/datum/turf_reservation/reservations = list()
+ var/uses_multiple_allocations = FALSE
+ /// Key to identify this template - used in caching
+ var/key
+ /// Directory of maps to prefix to the filename
+ var/map_dir = "_maps/templates/lazy_templates"
+ /// The filename (without extension) of the map to load
+ var/map_name
+
+/datum/lazy_template/New()
+ reservations = list()
+ ..()
+
+/datum/lazy_template/Destroy(force, ...)
+ if(!force)
+ stack_trace("Something is trying to delete [type]")
+ return QDEL_HINT_LETMELIVE
+
+ QDEL_LIST(reservations)
+ GLOB.lazy_templates -= key
+ return ..()
+
+/**
+ * Does the grunt work of loading the template.
+ */
+/datum/lazy_template/proc/lazy_load()
+ RETURN_TYPE(/turf)
+ // This is a static assosciative list that is used to ensure maps that have variations are correctly varied when spawned
+ // I want to make it to where you can make a range and it'll randomly pick'n'take from the available versions at random
+ // But that can be done later when I have the time
+ var/static/list/multiple_allocation_hash = list()
+
+ var/load_path = "[map_dir]/[map_name].dmm"
+ if(uses_multiple_allocations)
+ var/times = multiple_allocation_hash[key] || 0
+ times += 1
+ multiple_allocation_hash[key] = times
+ load_path = "[map_dir]/[map_name]_[times].dmm"
+
+ if(!load_path || !fexists(load_path))
+ CRASH("lazy template [type] has an invalid load_path: '[load_path]', check directory and map name!")
+
+ var/datum/parsed_map/parsed_template = load_map(
+ file(load_path),
+ measure_only = TRUE,
+ )
+ if(isnull(parsed_template.parsed_bounds))
+ CRASH("Failed to cache lazy template for loading: '[key]'")
+
+ var/width = parsed_template.parsed_bounds[MAP_MAXX] - parsed_template.parsed_bounds[MAP_MINX] + 1
+ var/height = parsed_template.parsed_bounds[MAP_MAXY] - parsed_template.parsed_bounds[MAP_MINY] + 1
+ var/datum/turf_reservation/reservation = SSmapping.request_turf_block_reservation(
+ width,
+ height,
+ parsed_template.parsed_bounds[MAP_MAXZ],
+ )
+ if(!reservation)
+ CRASH("Failed to reserve a block for lazy template: '[key]'")
+
+ // lists kept for overall loading
+ var/list/loaded_atom_movables = list()
+ var/list/loaded_turfs = list()
+ var/list/loaded_areas = list()
+
+ var/list/obj/structure/cable/loaded_cables = list()
+ var/list/obj/machinery/atmospherics/loaded_atmospherics = list()
+
+ for(var/z_idx in parsed_template.parsed_bounds[MAP_MAXZ] to 1 step -1)
+ var/turf/bottom_left = reservation.bottom_left_turfs[z_idx]
+ var/turf/top_right = reservation.top_right_turfs[z_idx]
+
+ load_map(
+ file(load_path),
+ bottom_left.x,
+ bottom_left.y,
+ bottom_left.z,
+ z_upper = z_idx,
+ z_lower = z_idx,
+ )
+ for(var/turf/turf as anything in block(bottom_left, top_right))
+ loaded_turfs += turf
+ loaded_areas |= get_area(turf)
+
+ // atoms can actually be in the contents of two or more turfs based on its icon/bound size
+ // see https://www.byond.com/docs/ref/index.html#/atom/var/contents
+ for(var/thing in (turf.get_all_contents() - turf))
+ if(istype(thing, /obj/structure/cable))
+ loaded_cables += thing
+ else if(istype(thing, /obj/machinery/atmospherics))
+ loaded_atmospherics += thing
+ loaded_atom_movables |= thing
+
+ SSatoms.InitializeAtoms(loaded_areas + loaded_atom_movables + loaded_turfs)
+ SSmachines.setup_template_powernets(loaded_cables)
+ SSair.setup_template_machinery(loaded_atmospherics)
+
+ SEND_SIGNAL(src, COMSIG_LAZY_TEMPLATE_LOADED, loaded_atom_movables, loaded_turfs, loaded_areas)
+ reservations += reservation
+ return reservation
+
+// /datum/lazy_template/nukie_base
+// key = LAZY_TEMPLATE_KEY_NUKIEBASE
+// map_name = "nukie_base"
+
+// /datum/lazy_template/wizard_dem
+// key = LAZY_TEMPLATE_KEY_WIZARDDEN
+// map_name = "wizard_den"
+
+// /datum/lazy_template/ninja_holding_facility
+// key = LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY
+// map_name = "ninja_den"
+
+// /datum/lazy_template/abductor_ship
+// key = LAZY_TEMPLATE_KEY_ABDUCTOR_SHIPS
+// map_name = "abductor_ships"
+
+// /datum/lazy_template/heretic_sacrifice_room
+// key = LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE
+// map_name = "heretic_sacrifice"
diff --git a/code/datums/map_config.dm b/code/datums/map_config.dm
index e246ccaa4620..c512248ae694 100644
--- a/code/datums/map_config.dm
+++ b/code/datums/map_config.dm
@@ -20,10 +20,12 @@
var/map_file = "YogStation.dmm"
var/traits = null
- var/space_ruin_levels = 7
- var/space_empty_levels = 1
+ var/space_ruin_levels = DEFAULT_SPACE_RUIN_LEVELS
+ var/space_empty_levels = DEFAULT_SPACE_EMPTY_LEVELS
+ /// Boolean that tells us if this is a planetary station. (like IceBoxStation)
+ var/planetary = FALSE
- var/minetype = "lavaland"
+ var/minetype = "jungle_and_lavaland"
var/cryo_spawn = FALSE
var/allow_custom_shuttles = TRUE
@@ -37,17 +39,44 @@
/// List of unit tests that are skipped when running this map
var/list/skipped_tests
-/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE)
- var/datum/map_config/config = new
+/**
+ * Proc that simply loads the default map config, which should always be functional.
+ */
+/proc/load_default_map_config()
+ return new /datum/map_config
+
+/**
+ * Proc handling the loading of map configs. Will return the default map config using [/proc/load_default_map_config] if the loading of said file fails for any reason whatsoever, so we always have a working map for the server to run.
+ * Arguments:
+ * * filename - Name of the config file for the map we want to load. The .json file extension is added during the proc, so do not specify filenames with the extension.
+ * * directory - Name of the directory containing our .json - Must be in MAP_DIRECTORY_WHITELIST. We default this to MAP_DIRECTORY_MAPS as it will likely be the most common usecase. If no filename is set, we ignore this.
+ * * error_if_missing - Bool that says whether failing to load the config for the map will be logged in log_world or not as it's passed to LoadConfig().
+ *
+ * Returns the config for the map to load.
+ */
+/proc/load_map_config(filename = null, directory = null, error_if_missing = TRUE, delete_after)
+ var/datum/map_config/config = load_default_map_config()
var/whiteship = pick("whiteship_1", "whiteship_2", "whiteship_3", "whiteship_4", "whiteship_5")
config.shuttles["whiteship"] = whiteship
- if (default_to_box)
- return config
+
+ if(filename) // If none is specified, then go to look for next_map.json, for map rotation purposes.
+
+ //Default to MAP_DIRECTORY_MAPS if no directory is passed
+ if(directory)
+ if(!(directory in MAP_DIRECTORY_WHITELIST))
+ log_world("map directory not in whitelist: [directory] for map [filename]")
+ return config
+ else
+ directory = MAP_DIRECTORY_MAPS
+
+ filename = "[directory]/[filename].json"
+ else
+ filename = PATH_TO_NEXT_MAP_JSON
+
+
if (!config.LoadConfig(filename, error_if_missing))
qdel(config)
- config = new /datum/map_config // Fall back to Box
- if (delete_after)
- fdel(filename)
+ return load_default_map_config()
return config
#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; }
@@ -135,6 +164,9 @@
if ("minetype" in json)
minetype = json["minetype"]
+
+ if ("planetary" in json)
+ planetary = json["planetary"]
if("cryo_spawn" in json)
cryo_spawn = json["cryo_spawn"]
@@ -163,4 +195,4 @@
. += "_maps/[map_path]/[file]"
/datum/map_config/proc/MakeNextMap()
- return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json")
+ return config_filename == PATH_TO_NEXT_MAP_JSON || fcopy(config_filename, PATH_TO_NEXT_MAP_JSON)
diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm
index 9de821c3af01..13ff6c0953ef 100644
--- a/code/datums/mapgen/CaveGenerator.dm
+++ b/code/datums/mapgen/CaveGenerator.dm
@@ -97,8 +97,8 @@
// The old tile hasn't got the chance to init yet
new_turf = new new_turf(gen_turf)
- if(gen_turf.flags_1 & NO_RUINS_1)
- new_turf.flags_1 |= NO_RUINS_1
+ if(gen_turf.turf_flags & NO_RUINS)
+ new_turf.turf_flags |= NO_RUINS
if(closed) //Open turfs have some special behavior related to spawning flora and mobs.
CHECK_TICK
diff --git a/code/datums/mapgen/Cavegens/IcemoonCaves.dm b/code/datums/mapgen/Cavegens/IcemoonCaves.dm
index 1109976b3fc8..e1aa09f6b3cb 100644
--- a/code/datums/mapgen/Cavegens/IcemoonCaves.dm
+++ b/code/datums/mapgen/Cavegens/IcemoonCaves.dm
@@ -7,7 +7,8 @@
/mob/living/simple_animal/hostile/asteroid/polarbear = 30, /obj/structure/spawner/ice_moon/polarbear = 3, \
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50,
/mob/living/simple_animal/hostile/asteroid/marrowweaver/ice = 30,
- /mob/living/simple_animal/hostile/asteroid/goldgrub = 10)
+ /mob/living/simple_animal/hostile/asteroid/goldgrub = 10,
+ /mob/living/simple_animal/hostile/asteroid/ambusher = 10)
weighted_flora_spawn_list = list(/obj/structure/flora/tree/pine = 2, /obj/structure/flora/rock/icy = 2, /obj/structure/flora/rock/pile/icy = 2, /obj/structure/flora/grass/both = 6)
///Note that this spawn list is also in the lavaland generator
weighted_feature_spawn_list = null
@@ -18,7 +19,8 @@
/mob/living/simple_animal/hostile/asteroid/polarbear = 30, /obj/structure/spawner/ice_moon/polarbear = 3, \
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50,
/mob/living/simple_animal/hostile/asteroid/marrowweaver/ice = 30,
- /mob/living/simple_animal/hostile/asteroid/goldgrub = 10)
+ /mob/living/simple_animal/hostile/asteroid/goldgrub = 10,
+ /mob/living/simple_animal/hostile/asteroid/ambusher = 2)
initial_closed_chance = 53
birth_limit = 5
death_limit = 4
diff --git a/code/datums/mapgen/Cavegens/LavalandGenerator.dm b/code/datums/mapgen/Cavegens/LavalandGenerator.dm
index 274803c5f06d..da2cbe05c0fa 100644
--- a/code/datums/mapgen/Cavegens/LavalandGenerator.dm
+++ b/code/datums/mapgen/Cavegens/LavalandGenerator.dm
@@ -29,7 +29,7 @@
var/granite_smoothing_interations = 100
var/granite_birth_limit = 4
var/granite_death_limit = 3
- var/granite_turf = /turf/closed/mineral/random/volcanic/hard/harder
+ var/granite_turf = /turf/closed/mineral/random/volcanic/harder
var/big_node_min = 25
var/big_node_max = 55
@@ -67,7 +67,7 @@
if(1)
hard_path = text2path("[T.type]/hard")
if(2)
- hard_path = text2path("[T.type]/hard/harder")
+ hard_path = text2path("[T.type]/harder")
if(!ispath(hard_path)) //erm what the shit we dont have a hard(or er) type
continue
var/turf/new_turf = hard_path
diff --git a/code/datums/mapgen/biomes/_biome.dm b/code/datums/mapgen/biomes/_biome.dm
new file mode 100644
index 000000000000..aa68b8ebe990
--- /dev/null
+++ b/code/datums/mapgen/biomes/_biome.dm
@@ -0,0 +1,24 @@
+///This datum handles the transitioning from a turf to a specific biome, and handles spawning decorative structures and mobs.
+/datum/biome
+ ///Type of turf this biome creates
+ var/turf_type
+ ///Chance of having a structure from the flora types list spawn
+ var/flora_density = 0
+ ///Chance of having a mob from the fauna types list spawn
+ var/fauna_density = 0
+ ///list of type paths of objects that can be spawned when the turf spawns flora
+ var/list/flora_types = list(/obj/structure/flora/grass/jungle)
+ ///list of type paths of mobs that can be spawned when the turf spawns fauna
+ var/list/fauna_types = list()
+
+///This proc handles the creation of a turf of a specific biome type
+/datum/biome/proc/generate_turf(turf/gen_turf)
+ . = gen_turf.ChangeTurf(turf_type, null, CHANGETURF_DEFER_CHANGE)
+ if(length(fauna_types) && prob(fauna_density))
+ var/mob/fauna = pick(fauna_types)
+ new fauna(gen_turf)
+
+ if(length(flora_types) && prob(flora_density))
+ var/obj/structure/flora = pick(flora_types)
+ new flora(gen_turf)
+
diff --git a/code/datums/martial/flying_fang.dm b/code/datums/martial/flying_fang.dm
index 95e8eeef2bf2..49c023f53680 100644
--- a/code/datums/martial/flying_fang.dm
+++ b/code/datums/martial/flying_fang.dm
@@ -117,6 +117,8 @@
//headbutt, deals moderate brute and stamina damage with an eye blur, causes poor aim for a few seconds to the target if they have no helmet on
/datum/martial_art/flyingfang/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
+ if(!(A.mobility_flags & MOBILITY_STAND)) //No fancy tail slaps whe you're prone
+ return harm_act(A, D)
add_to_streak("D",D)
if(!can_use(A))
return
@@ -132,10 +134,10 @@
playsound(D, 'sound/weapons/genhit1.ogg', 50, TRUE, -1)
D.apply_damage(disarm_damage, STAMINA, BODY_ZONE_HEAD, armor_block)
D.apply_damage(disarm_damage, A.dna.species.attack_type, BODY_ZONE_HEAD, armor_block)
- D.blur_eyes(4)
+ D.adjust_eye_blur(4)
if(!istype(D.head, /obj/item/clothing/head/helmet))
D.dna.species.aiminginaccuracy += 25
- addtimer(CALLBACK(src, PROC_REF(remove_bonk), D), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
+ addtimer(CALLBACK(src, PROC_REF(remove_bonk), D), 10 SECONDS)
D.visible_message(span_danger("[A] headbutts [D]!"), \
span_userdanger("[A] headbutts you!"))
log_combat(A, D, "headbutted (Flying Fang)")
@@ -214,6 +216,8 @@
return
linked_martial.leaping = TRUE
COOLDOWN_START(linked_martial, next_leap, 5 SECONDS)
+ if(A.buckled)
+ A.buckled.unbuckle_mob(A, force = TRUE)
A.Knockdown(5 SECONDS)
A.Immobilize(3 SECONDS, TRUE, TRUE) //prevents you from breaking out of your pounce
A.throw_at(target, get_dist(A,target)+1, 1, A, FALSE, TRUE, callback = CALLBACK(src, PROC_REF(leap_end), A))
@@ -234,21 +238,28 @@
var/blocked = FALSE
if(ishuman(hit_atom))
var/mob/living/carbon/human/H = hit_atom
- if(H.check_shields(src, 0, "[A]", attack_type = LEAP_ATTACK))
+ if(H.check_shields(H, 0, "[A]", attack_type = LEAP_ATTACK))
blocked = TRUE
L.visible_message("[A] pounces on [L]!", "[A] pounces on you!")
- L.Paralyze(blocked ? 6 SECONDS : 0)
+
+ //Knockdown regardless of blocking,
L.Knockdown(10 SECONDS)
- L.Immobilize(6 SECONDS)
- A.SetKnockdown(0)
- A.SetImmobilized(10 SECONDS) //due to our stun resistance this is actually about 6.6 seconds
+
+ //Blocking knocks the lizard down too
+ if(blocked)
+ A.SetKnockdown(10 SECONDS)
+
+ //Otherwise the not-blocker gets stunned and the lizard is okay
+ else
+ L.Paralyze(6 SECONDS)
+ A.SetKnockdown(0)
+
if(linked_leap && !blocked)
COOLDOWN_RESET(src, next_leap) // landing the leap resets the cooldown
sleep(0.2 SECONDS)//Runtime prevention (infinite bump() calls on hulks)
step_towards(src,L)
else if(hit_atom.density && !hit_atom.CanPass(A))
A.visible_message("[A] smashes into [hit_atom]!", "You smash into [hit_atom]!")
- A.Immobilize(1.5 SECONDS)
A.Knockdown(6 SECONDS)
playsound(A, 'sound/weapons/punch2.ogg', 50, 1) // ow oof ouch my head
if(leaping)
diff --git a/code/datums/martial/reverbpalm.dm b/code/datums/martial/reverbpalm.dm
new file mode 100644
index 000000000000..36234dda2be8
--- /dev/null
+++ b/code/datums/martial/reverbpalm.dm
@@ -0,0 +1,285 @@
+#define COOLDOWN_LARIAT 7 SECONDS
+#define COOLDOWN_RUSH 7 SECONDS
+#define COOLDOWN_SUPLEX 0.8 SECONDS
+#define COOLDOWN_RPALM 3 SECONDS
+
+
+/datum/martial_art/reverberating_palm
+ name = "Reverberating Palm"
+ id = MARTIALART_REVERBPALM
+ no_guns = FALSE
+ help_verb = /mob/living/carbon/human/proc/reverberating_palm_help
+ COOLDOWN_DECLARE(next_lariat)
+ COOLDOWN_DECLARE(next_rush)
+ COOLDOWN_DECLARE(next_suplex)
+ COOLDOWN_DECLARE(next_palm)
+ var/normalharm = TRUE
+
+//proc the moves will use for damage dealing
+
+/datum/martial_art/reverberating_palm/proc/damagefilter(mob/living/user, mob/living/L, animaldam, persondam, borgdam)
+ var/obj/item/bodypart/limb_to_hit = L.get_bodypart(user.zone_selected)
+ var/armor = L.run_armor_check(limb_to_hit, MELEE, armour_penetration = 0)
+ if(iscarbon(L))
+ L.apply_damage(persondam, BRUTE, limb_to_hit, armor, wound_bonus=CANT_WOUND)
+ if(isanimal(L))
+ L.apply_damage(animaldam, BRUTE, limb_to_hit, armor, wound_bonus=CANT_WOUND)
+ if(issilicon(L))
+ L.apply_damage(borgdam, BRUTE, limb_to_hit, armor, wound_bonus=CANT_WOUND)
+
+/datum/martial_art/reverberating_palm/proc/crash(atom/movable/ram, var/turf/Q)
+ if(Q.density || (!(Q.reachableTurftestdensity(T = Q))))
+ if(isliving(ram))
+ var/mob/living/target = ram
+ target.adjustBruteLoss(5)
+ if(isanimal(target))
+ if(target.stat == DEAD)
+ target.visible_message(span_warning("[target] crashes and explodes!"))
+ target.gib()
+ if(ismineralturf(Q))
+ var/turf/closed/mineral/M = Q
+ M.attempt_drill()
+ if(Q.density || (!(Q.reachableTurftestdensity(T = Q))))
+ return FALSE
+ else
+ ram.forceMove(Q)
+ return TRUE
+
+//animation procs
+
+//knocking them down
+/datum/martial_art/reverberating_palm/proc/footsies(mob/living/target)
+ if(target.mobility_flags & MOBILITY_STAND)
+ animate(target, transform = matrix(90, MATRIX_ROTATE), time = 0 SECONDS, loop = 0)
+ return
+
+//Check for if someone is allowed to be stood back up
+/datum/martial_art/reverberating_palm/proc/wakeup(mob/living/target)
+ if(target.mobility_flags & MOBILITY_STAND)
+ animate(target, transform = null, time = 0.4 SECONDS, loop = 0)
+
+/datum/martial_art/reverberating_palm/proc/initiate(mob/living/user)
+ var/obj/effect/temp_visual/decoy/fading/onesecond/F = new(get_turf(user), user)
+ animate(F, alpha = 100, color = "#00d9ff")
+ walk_towards(F, user, 0, 1.5)
+ playsound(user,'sound/effects/gravhit.ogg', 20, 1)
+
+/datum/martial_art/reverberating_palm/can_use(mob/living/carbon/human/H)
+ var/obj/item/bodypart/r_arm/robot/seismic/R = H.get_bodypart(BODY_ZONE_R_ARM)
+ if(R)
+ if(!istype(R, /obj/item/bodypart/r_arm/robot/seismic))
+ return FALSE
+ if((R?.bodypart_disabled))
+ return FALSE
+ if(H.restrained() || H.get_active_held_item() || HAS_TRAIT(H, TRAIT_PACIFISM) || !(H.mobility_flags & MOBILITY_MOVE) || H.stat != CONSCIOUS)
+ return FALSE
+ return ..()
+
+/datum/martial_art/reverberating_palm/proc/InterceptClickOn(mob/living/carbon/human/H, params, atom/target)
+ var/list/modifiers = params2list(params)
+ if(!(can_use(H)) || (modifiers["shift"] || modifiers["alt"]))
+ return
+ H.face_atom(target)
+ if(H==target)
+ if(H.a_intent == INTENT_HELP)
+ supercharge(H)
+ return
+ if(H.a_intent == INTENT_DISARM)
+ rush(H)
+ if(H.a_intent == INTENT_HARM && isliving(target))
+ suplex(H,target)
+ if(H.a_intent == INTENT_GRAB)
+ lariat(H)
+
+/datum/martial_art/reverberating_palm/harm_act(mob/living/carbon/human/A, mob/living/D)
+ if(normalharm)
+ return TRUE // no punching plus slamming please
+
+/datum/martial_art/reverberating_palm/proc/supercharge(mob/living/user)
+ if(!COOLDOWN_FINISHED(src, next_palm))
+ to_chat(user, span_warning("You can't do that yet!"))
+ return
+ COOLDOWN_START(src, next_palm, COOLDOWN_RPALM)
+ var/obj/item/melee/overcharged_emitter/B = new()
+ user.visible_message(span_userdanger("[user]'s right arm begins crackling loudly!"))
+ playsound(user,'sound/effects/beepskyspinsabre.ogg', 60, 1)
+ if(do_after(user, 2 SECONDS, user, timed_action_flags = IGNORE_USER_LOC_CHANGE))
+ if(!user.put_in_r_hand(B))
+ to_chat(user, span_warning("You can't do this with your right hand full!"))
+ else
+ user.visible_message(span_danger("[user]'s arm begins shaking violently!"))
+ if(user.active_hand_index % 2 == 1)
+ user.swap_hand(0)
+ //do cooldown
+
+
+/datum/martial_art/reverberating_palm/proc/rush(mob/living/user)
+ var/jumpdistance = 4
+ var/turf/T = get_step(get_turf(user), user.dir)
+ if(!COOLDOWN_FINISHED(src, next_rush))
+ to_chat(user, span_warning("You can't do that yet!"))
+ return
+ COOLDOWN_START(src, next_rush, COOLDOWN_RUSH)
+ for(var/mob/living/L in T.contents)
+ if(L)
+ dashattack(user, user.dir, jumpdistance, 2, L)
+ return
+ dashattack(user, user.dir, jumpdistance, 2)
+
+/datum/martial_art/reverberating_palm/proc/suplex(mob/living/user, mob/living/target)
+ var/simpledam = 20
+ var/persondam = 10
+ var/borgdam = 12
+ var/turf/Z = get_turf(user)
+ if(!COOLDOWN_FINISHED(src, next_suplex))
+ to_chat(user, span_warning("You can't do that yet!"))
+ return
+ COOLDOWN_START(src, next_suplex, COOLDOWN_SUPLEX)
+ footsies(target)
+ var/turf/Q = get_step(get_turf(user), turn(user.dir,180))
+ user.visible_message(span_warning("[user] outstretches [user.p_their()] arm and goes for a grab!"))
+ wakeup(target)
+ for(var/mob/living/L in Q.contents)
+ damagefilter(user, L, simpledam, persondam, borgdam)
+ if(!(crash(target, Q)))
+ target.forceMove(Z)
+ damagefilter(user, target, simpledam, persondam, borgdam)
+ if(isanimal(target))
+ if(target.stat == DEAD)
+ target.visible_message(span_warning("[target] crashes and explodes!"))
+ target.gib()
+ to_chat(user, span_warning("[user] suplexes [target] against [Q]!"))
+ to_chat(target, span_userdanger("[user] crushes you against [Q]!"))
+ playsound(target, 'sound/effects/meteorimpact.ogg', 60, 1)
+ playsound(user, 'sound/effects/gravhit.ogg', 20, 1)
+
+
+/datum/martial_art/reverberating_palm/proc/lariat(mob/living/user)
+ var/jumpdistance = 4
+ if(!COOLDOWN_FINISHED(src, next_lariat))
+ to_chat(user, span_warning("You can't do that yet!"))
+ return
+ COOLDOWN_START(src, next_lariat, COOLDOWN_LARIAT)
+ dashattack(user, user.dir, jumpdistance, 1)
+
+/datum/martial_art/reverberating_palm/proc/dashattack(mob/living/user, dir, distance = 0, type = 0, list/rushed)
+ var/turf/Q = get_step(get_turf(user), dir)
+ var/list/pirated = list()
+ for(var/mob/living/target in rushed)
+ wakeup(target)
+ if(distance == 0)
+ return
+ if(distance == 4)
+ initiate(user)
+ for(var/mob/living/target in rushed)
+ (crash(target, Q))
+ if(Q.density || (!(Q.reachableTurftestdensity(T = Q))))
+ return
+ user.forceMove(Q)
+ var/turf/R = get_step(Q, dir)
+ for(var/mob/living/L in Q.contents)
+ if(L == user)
+ continue
+ switch(type)
+ if(1) //lariat
+ var/simpledam = 50
+ var/persondam = 10
+ var/borgdam = 12
+ playsound(L,'sound/effects/meteorimpact.ogg', 60, 1)
+ to_chat(L, span_userdanger("[user] catches you with [user.p_their()] arm and clotheslines you!"))
+ user.visible_message(span_warning("[user] hits [L] with a lariat!"))
+ L.SpinAnimation(0.5 SECONDS, 1)
+ damagefilter(user, L, simpledam, persondam, borgdam)
+ if(2) //rush
+ wakeup(L)
+ var/simpledam = 20
+ var/persondam = 4
+ var/borgdam = 5
+ pirated |= L
+ footsies(L)
+ to_chat(L, span_userdanger("[user] catches you with [user.p_their()] hand and drags you down!"))
+ user.visible_message(span_warning("[user] hits [L] and drags them through the dirt!"))
+ L.Immobilize(0.3 SECONDS)
+ wakeup(L)
+ damagefilter(user, L, simpledam, persondam, borgdam)
+ if(isanimal(L))
+ user.apply_status_effect(STATUS_EFFECT_BLOODDRUNK)//guaranteed extended contact with a fauna so i have to make it not a death sentence
+ if(L.stat == DEAD)
+ L.visible_message(span_warning("[L] is ground into paste!"))
+ L.gib()
+ playsound(L,'sound/effects/meteorimpact.ogg', 60, 1)
+ crash(L, R)
+ switch(type)
+ if(1)
+ addtimer(CALLBACK(src, PROC_REF(dashattack), user, dir, distance-1, type), 0.2 SECONDS)
+ return
+ if(2)
+ addtimer(CALLBACK(src, PROC_REF(dashattack), user, dir, distance-1, type, pirated), 0.1 SECONDS)
+ return
+
+/*---------------------------------------------------------------
+ training related section
+---------------------------------------------------------------*/
+/mob/living/carbon/human/proc/reverberating_palm_help()
+ set name = "Reverberating Palm"
+ set desc = "You begin recalling the possibilities of the seismic arm."
+ set category = "Reverberating Palm"
+ var/list/combined_msg = list()
+
+ combined_msg += "[span_notice("Rush")]: Your disarm has been replaced with a move that sends you flying forward, damaging enemies in front of you by dragging them \
+ along the ground. Ramming victims into something solid does damage to them and the object and attacking animals makes you momentarily tougher. Has a 7 second cooldown."
+
+ combined_msg += "[span_notice("Suplex")]: Your harm has been replaced with a slam attack that places enemies behind you and smashes them against \
+ whatever person, wall, or object is there for bonus damage. Has a 1 second cooldown."
+
+ combined_msg += "[span_notice("Lariat")]: Your grab has been replaced with a lunge forward, clotheslining enemies in your way. Has a 7 second cooldown."
+
+ combined_msg += "[span_notice("Rippling Palm")]: Charge up your seismic arm to put a powerful attack in your right hand. The energy only lasts 5 seconds \
+ but does hefty damage to its target, sending it flying and taking unanchored obstacles with it. However, your arm is disabled for 15 seconds afterwards."
+
+ combined_msg += span_warning("You can't perform any of the moves if you have an occupied hand or limp arm.")
+
+ combined_msg += span_warning("Should your moves cease to function altogether, utilize the 'Recalibrate Arm' function.")
+
+ to_chat(usr, examine_block(combined_msg.Join("\n")))
+
+
+
+/datum/action/cooldown/seismic_recalibrate
+ name = "Recalibrate Arm"
+ desc = "You recalibrate the arm to restore missing functionality."
+ button_icon = 'icons/obj/implants.dmi'
+ button_icon_state = "lighting_bolt"
+
+/datum/action/cooldown/seismic_recalibrate/Activate()
+ var/list/combined_msg = list()
+ var/datum/martial_art/reverberating_palm/rpalm = usr.mind.martial_art
+ combined_msg += "You fidget with the arm in an attempt to get it working."
+ to_chat(usr, examine_block(combined_msg.Join("\n")))
+ rpalm.normalharm = TRUE
+ usr.click_intercept = usr.mind.martial_art
+
+
+/datum/action/cooldown/seismic_deactivate
+ name = "Deactivate Arm"
+ desc = "Wind down the arm temporarily, restoring your normal capabilities."
+ button_icon = 'icons/obj/implants.dmi'
+ button_icon_state = "emp"
+
+/datum/action/cooldown/seismic_deactivate/Activate()
+ var/list/combined_msg = list()
+ var/datum/martial_art/reverberating_palm/rpalm = usr.mind.martial_art
+ combined_msg += "You temporarily power off the arm."
+ to_chat(usr, examine_block(combined_msg.Join("\n")))
+ rpalm.normalharm = FALSE
+ usr.click_intercept = null
+
+
+
+/datum/martial_art/reverberating_palm/teach(mob/living/carbon/human/H, make_temporary=0)
+ ..()
+ usr.click_intercept = src
+
+/datum/martial_art/reverberating_palm/on_remove(mob/living/carbon/human/H)
+ usr?.click_intercept = null
+ ..()
diff --git a/code/datums/martial/ultra_violence.dm b/code/datums/martial/ultra_violence.dm
index 612e5a2e3964..92e24e8a473a 100644
--- a/code/datums/martial/ultra_violence.dm
+++ b/code/datums/martial/ultra_violence.dm
@@ -150,13 +150,13 @@
/obj/item/ammo_box/magazine/internal/cylinder/ipcmartial
name = "\improper Piercer cylinder"
ammo_type = /obj/item/ammo_casing/ipcmartial
- caliber = "357"
+ caliber = CALIBER_357MAG
max_ammo = 3
/obj/item/ammo_casing/ipcmartial
name = ".357 sharpshooter bullet casing"
desc = "A .357 sharpshooter bullet casing."
- caliber = "357"
+ caliber = CALIBER_357MAG
projectile_type = /obj/projectile/bullet/ipcmartial
click_cooldown_override = 0.1 //this gun shoots faster
@@ -168,7 +168,8 @@
wound_falloff_tile = -2.5
ricochets_max = 1 // so you can't use it in a small room to obliterate everyone inside
ricochet_chance = INFINITY // ALWAYS ricochet
- penetrating = TRUE
+ penetrations = INFINITY
+ can_ricoshot = ALWAYS_RICOSHOT // +RICOSHOT
/obj/projectile/bullet/ipcmartial/on_hit(atom/target, blocked)
. = ..()
@@ -196,7 +197,7 @@
/obj/projectile/bullet/ipcmartial/on_ricochet(atom/A)
damage += 10 // more damage if you ricochet it, good luck hitting it consistently though
speed *= 0.5 // faster so it can hit more reliably
- penetrating = FALSE
+ penetrations = 0
return ..()
/obj/projectile/bullet/ipcmartial/check_ricochet()
diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm
index 557516cdc1c5..29e700df8eb5 100644
--- a/code/datums/materials/_material.dm
+++ b/code/datums/materials/_material.dm
@@ -7,7 +7,7 @@ Simple datum which is instanced once per type and is used for every object of sa
/datum/material
var/name = "material"
- var/desc = "its..stuff."
+ var/desc = "It's...stuff." // I'm stuff :stuff:
///Var that's mostly used by science machines to identify specific materials, should most likely be phased out at some point
var/id = "mat"
///Base color of the material, is used for greyscale. Item isn't changed in color if this is null.
@@ -35,6 +35,12 @@ Simple datum which is instanced once per type and is used for every object of sa
if(istype(source, /obj)) //objs
on_applied_obj(source, amount, material_flags)
+
+ source.mat_update_desc(src)
+
+///This proc is called when a material updates an object's description
+/atom/proc/mat_update_desc(datum/material/mat)
+ return
///This proc is called when the material is added to an object specifically.
/datum/material/proc/on_applied_obj(obj/o, amount, material_flags)
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index 3012fc6d533a..1cc3eb6763f8 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -2,7 +2,7 @@
/datum/material/iron
name = "iron"
id = "iron"
- desc = "Common iron ore often found in sedimentary and igneous layers of the crust."
+ desc = "A common iron ore often found in sedimentary and igneous layers of the crust."
color = "#878687"
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
sheet_type = /obj/item/stack/sheet/metal
@@ -24,7 +24,7 @@
/datum/material/silver
name = "silver"
id = "silver"
- desc = "Silver"
+ desc = "Silver."
color = "#bdbebf"
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/silver
@@ -34,7 +34,7 @@
/datum/material/gold
name = "gold"
id = "gold"
- desc = "Gold"
+ desc = "Gold. You're rich."
color = "#f0972b"
strength_modifier = 1.2
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
@@ -45,7 +45,7 @@
/datum/material/diamond
name = "diamond"
id = "diamond"
- desc = "Highly pressurized carbon"
+ desc = "Highly pressurized carbon."
color = "#22c2d4"
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/diamond
@@ -55,7 +55,7 @@
/datum/material/uranium
name = "uranium"
id = "uranium"
- desc = "Uranium"
+ desc = "Uranium, known for its radioactive properties."
color = "#1fb83b"
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/uranium
@@ -95,7 +95,7 @@
/datum/material/bluespace
name = "bluespace crystal"
id = "bluespace_crystal"
- desc = "Crystals with bluespace properties"
+ desc = "Rare crystals with bluespace properties."
color = "#506bc7"
categories = list(MAT_CATEGORY_ORE = TRUE)
sheet_type = /obj/item/stack/sheet/bluespace_crystal
@@ -104,7 +104,7 @@
/datum/material/bananium
name = "bananium"
id = "bananium"
- desc = "Material with hilarious properties"
+ desc = "A very rare material with hilarious properties."
color = "#fff263"
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/bananium
@@ -125,7 +125,7 @@
/datum/material/titanium
name = "titanium"
id = "titanium"
- desc = "Titanium"
+ desc = "Titanium."
color = "#b3c0c7"
strength_modifier = 1.3
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE)
@@ -135,7 +135,7 @@
/datum/material/plastic
name = "plastic"
id = "plastic"
- desc = "plastic"
+ desc = "Plastic."
color = "#caccd9"
strength_modifier = 0.85
sheet_type = /obj/item/stack/sheet/plastic
@@ -151,7 +151,7 @@
//formed when freon react with o2, emits a lot of plasma when heated
/datum/material/hot_ice
name = "hot ice"
- desc = "A weird kind of ice, feels warm to the touch"
+ desc = "A weird kind of ice, feels warm to the touch."
color = "#88cdf1"
alpha = 150
categories = list(MAT_CATEGORY_RIGID = TRUE)
@@ -176,7 +176,7 @@
/datum/material/metalhydrogen
name = "Metal Hydrogen"
- desc = "Solid metallic hydrogen. Some say it should be impossible"
+ desc = "Solid metallic hydrogen. Some say it should be impossible."
color = "#f2d5d7"
alpha = 240
categories = list(MAT_CATEGORY_RIGID = TRUE)
@@ -185,7 +185,7 @@
/datum/material/zaukerite
name = "zaukerite"
- desc = "A light absorbing crystal"
+ desc = "A light-absorbing crystal."
color = COLOR_ALMOST_BLACK
categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_ITEM_MATERIAL=TRUE)
sheet_type = /obj/item/stack/sheet/mineral/zaukerite
@@ -201,7 +201,7 @@
/datum/material/dilithium
name = "dilithium crystal"
id = "dilithium_crystal"
- desc = "Crystals with dilithium properties"
+ desc = "Crystals with dilithium properties."
color = "#506bc7"
categories = list(MAT_CATEGORY_ORE = TRUE)
sheet_type = /obj/item/stack/sheet/dilithium_crystal
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 0bc0245972e2..f542b4fde965 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -30,13 +30,18 @@
*/
/datum/mind
+ /// Key of the mob
var/key
- var/name //replaces mob/var/original_name
+ /// The name linked to this mind
+ var/name
+ /// Current mob this mind datum is attached to
var/mob/living/current
- var/active = 0
+ /// Is this mind active?
+ var/active = FALSE
var/memory
+ /// Job datum indicating the mind's role. This should always exist after initialization, as a reference to a singleton.
var/assigned_role
var/role_alt_title
@@ -74,12 +79,12 @@
var/flavour_text = null
///Are we zombified/uncloneable?
var/zombified = FALSE
- ///What character we joined in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not
- var/mob/original_character
- /// What scar slot we have loaded, so we don't have to constantly check the savefile
- var/current_scar_slot
+ ///Weakref to thecharacter we joined in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not
+ var/datum/weakref/original_character
/// The index for what character slot, if any, we were loaded from, so we can track persistent scars on a per-character basis. Each character slot gets PERSISTENT_SCAR_SLOTS scar slots
var/original_character_slot_index
+ /// What scar slot we have loaded, so we don't have to constantly check the savefile
+ var/current_scar_slot
/// The index for our current scar slot, so we don't have to constantly check the savefile (unlike the slots themselves, this index is independent of selected char slot, and increments whenever a valid char is joined with)
var/current_scar_slot_index
/// Is set to true if an antag was used to get this person picked as an antag
@@ -110,8 +115,21 @@
return language_holder
+/datum/mind/proc/set_current(mob/new_current)
+ if(new_current && QDELETED(new_current))
+ CRASH("Tried to set a mind's current var to a qdeleted mob, what the fuck")
+ if(current)
+ UnregisterSignal(src, COMSIG_QDELETING)
+ current = new_current
+ if(current)
+ RegisterSignal(src, COMSIG_QDELETING, PROC_REF(clear_current))
+
+/datum/mind/proc/clear_current(datum/source)
+ SIGNAL_HANDLER
+ set_current(null)
+
/datum/mind/proc/transfer_to(mob/new_character, force_key_move = 0)
- original_character = null
+ set_original_character(null)
var/mood_was_enabled = FALSE//Yogs -- Mood Preferences
if(current) // remove ourself from our old body's mind variable
// Yogs start -- Mood preferences
@@ -123,7 +141,7 @@
mood_was_enabled = TRUE
var/datum/component/mood/c = H.GetComponent(/datum/component/mood)
if(c)
- c.RemoveComponent()
+ qdel(c)
// Yogs End
current.mind = null
UnregisterSignal(current, COMSIG_GLOB_MOB_DEATH)
@@ -131,18 +149,18 @@
SStgui.on_transfer(current, new_character)
if(key)
- if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours
- new_character.ghostize(1) //we'll need to ghostize so that key isn't mobless.
+ if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours
+ new_character.ghostize(TRUE) //we'll need to ghostize so that key isn't mobless.
else
key = new_character.key
- if(new_character.mind) //disassociate any mind currently in our new body's mind variable
- new_character.mind.current = null
+ if(new_character.mind) //disassociate any mind currently in our new body's mind variable
+ new_character.mind.set_current(null)
var/mob/living/old_current = current
if(current)
current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one
- current = new_character //associate ourself with our new body
+ set_current(new_character) //associate ourself with our new body
QDEL_NULL(antag_hud)
new_character.mind = src //and associate our new body with ourself
antag_hud = new_character.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", src)
@@ -171,6 +189,10 @@
SEND_SIGNAL(src, COMSIG_MIND_TRANSFERRED, old_current)
SEND_SIGNAL(current, COMSIG_MOB_MIND_TRANSFERRED_INTO)
+//I cannot trust you fucks to do this properly
+/datum/mind/proc/set_original_character(new_original_character)
+ original_character = WEAKREF(new_original_character)
+
/datum/mind/proc/set_death_time()
last_death = world.time
@@ -759,7 +781,7 @@
/mob/proc/sync_mind()
mind_initialize() //updates the mind (or creates and initializes one if one doesn't exist)
- mind.active = 1 //indicates that the mind is currently synced with a client
+ mind.active = TRUE //indicates that the mind is currently synced with a client
/datum/mind/proc/has_martialart(string)
if(martial_art && martial_art.id == string)
@@ -785,6 +807,7 @@
mind.current = src
// There's nowhere else to set this up, mind code makes me depressed
mind.antag_hud = add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", mind)
+ SEND_SIGNAL(src, COMSIG_MOB_MIND_INITIALIZED, mind)
/mob/living/carbon/mind_initialize()
..()
diff --git a/code/datums/mood_events/drink_events.dm b/code/datums/mood_events/drink_events.dm
index 4e20b2f5b06b..098fd155afe1 100644
--- a/code/datums/mood_events/drink_events.dm
+++ b/code/datums/mood_events/drink_events.dm
@@ -4,23 +4,23 @@
/datum/mood_event/quality_nice
description = "That drink wasn't bad at all.\n"
- mood_change = 1
- timeout = 2 MINUTES
+ mood_change = 5
+ timeout = 5 MINUTES
/datum/mood_event/quality_good
description = "That drink was pretty good.\n"
- mood_change = 2
- timeout = 2 MINUTES
+ mood_change = 10
+ timeout = 5 MINUTES
/datum/mood_event/quality_verygood
description = "That drink was great!\n"
- mood_change = 3
- timeout = 2 MINUTES
+ mood_change = 15
+ timeout = 5 MINUTES
/datum/mood_event/quality_fantastic
description = "That drink was amazing!\n"
- mood_change = 4
- timeout = 2 MINUTES
+ mood_change = 20
+ timeout = 10 MINUTES
/datum/mood_event/amazingtaste
description = "Amazing taste!\n"
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 61ede09c2bdb..aa6061668e9f 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -1,5 +1,5 @@
/datum/mood_event/ally_power
- description= "There are Allies everywhere.\n"
+ description= "You feel at ease, from being surrounded by your friends.\n"
mood_change = 1
timeout = 2 MINUTES
diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm
index f248da2a7323..c066b96cea41 100644
--- a/code/datums/mutable_appearance.dm
+++ b/code/datums/mutable_appearance.dm
@@ -4,18 +4,45 @@
// Mutable appearances are children of images, just so you know.
-/mutable_appearance/New()
+// Mutable appearances erase template vars on new, because they accept an appearance to copy as an arg
+// If we have nothin to copy, we set the float plane
+/mutable_appearance/New(mutable_appearance/to_copy)
..()
- plane = FLOAT_PLANE // No clue why this is 0 by default yet images are on FLOAT_PLANE
- // And yes this does have to be in the constructor, BYOND ignores it if you set it as a normal var
+ if(!to_copy)
+ plane = FLOAT_PLANE
-// Helper similar to image()
-/proc/mutable_appearance(icon, icon_state = "", layer = FLOAT_LAYER, plane = FLOAT_PLANE, alpha = 255, appearance_flags = NONE)
- var/mutable_appearance/MA = new()
- MA.icon = icon
- MA.icon_state = icon_state
- MA.layer = layer
- MA.plane = plane
- MA.alpha = alpha
- MA.appearance_flags |= appearance_flags
- return MA
+/** Helper similar to image()
+ *
+ * icon - Our appearance's icon
+ * icon_state - Our appearance's icon state
+ * layer - Our appearance's layer
+ * atom/offset_spokesman - An atom to use as reference for the z position of this appearance.
+ * Only required if a plane is passed in. If this is not passed in we accept offset_const as a substitute
+ * plane - The plane to use for the appearance. If this is not FLOAT_PLANE we require context for the offset to use
+ * alpha - Our appearance's alpha
+ * appearance_flags - Our appearance's appearance_flags
+ * offset_const - A constant to offset our plane by, so it renders on the right "z layer"
+**/
+/proc/mutable_appearance(icon, icon_state = "", layer = FLOAT_LAYER, atom/offset_spokesman, plane = FLOAT_PLANE, alpha = 255, appearance_flags = NONE, offset_const)
+ var/mutable_appearance/appearance = new()
+ appearance.icon = icon
+ appearance.icon_state = icon_state
+ appearance.layer = layer
+ appearance.alpha = alpha
+ appearance.appearance_flags |= appearance_flags
+ if(plane != FLOAT_PLANE)
+ // You need to pass in some non null object to reference
+ if(isatom(offset_spokesman))
+ // Note, we are ok with null turfs, that's not an error condition we'll just default to 0, the error would be
+ // Not passing ANYTHING in, key difference
+ SET_PLANE_EXPLICIT(appearance, plane, offset_spokesman)
+ // That or I'll let you pass in a static offset. Don't be stupid now
+ else if(!isnull(offset_const))
+ SET_PLANE_W_SCALAR(appearance, plane, offset_const)
+ // otherwise if you're setting plane you better have the guts to back it up
+ else
+ stack_trace("No plane offset passed in as context for a non floating mutable appearance, things are gonna go to hell on multiz maps")
+ else if(!isnull(offset_spokesman) && !isatom(offset_spokesman))
+ stack_trace("Why did you pass in offset_spokesman as [offset_spokesman]? We need an atom to properly offset planes")
+
+ return appearance
diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm
index 804492f862e9..266f5c82a8c5 100644
--- a/code/datums/mutations/body.dm
+++ b/code/datums/mutations/body.dm
@@ -446,6 +446,8 @@
power_coeff = 1
/datum/mutation/human/hypermarrow/on_life()
+ if(HAS_TRAIT(owner, TRAIT_NO_BLOOD_REGEN))
+ return //no bone marrow to regenerate blood in the first place
if(owner.blood_volume < BLOOD_VOLUME_NORMAL(owner))
owner.blood_volume += GET_MUTATION_POWER(src) * 2 - 1
owner.adjust_nutrition((GET_MUTATION_POWER(src) * 2 - 0.8) * HUNGER_FACTOR)
diff --git a/code/datums/mutations/olfaction.dm b/code/datums/mutations/olfaction.dm
index 3b9e3817b073..4bf2b3a0afd7 100644
--- a/code/datums/mutations/olfaction.dm
+++ b/code/datums/mutations/olfaction.dm
@@ -60,8 +60,7 @@
trail.Flip(dir)
trail.Turn(180)
- img = image(trail, loc = src, layer = HUD_LAYER)
- img.layer = HUD_LAYER
+ img = image(trail, loc = src)
img.plane = HUD_PLANE
img.appearance_flags = NO_CLIENT_COLOR
img.alpha = 0
diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm
index 1441078d94c6..09d01af9af57 100644
--- a/code/datums/mutations/touch.dm
+++ b/code/datums/mutations/touch.dm
@@ -29,10 +29,14 @@
/datum/action/cooldown/spell/touch/shock/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(iscarbon(victim))
var/mob/living/carbon/carbon_victim = victim
- if(carbon_victim.electrocute_act(15, caster, 1, zone=caster.zone_selected, stun = FALSE))//doesnt stun. never let this stun
+ var returned_damage = carbon_victim.electrocute_act(15, caster, 1, zone = caster.zone_selected, stun = FALSE) // Does not stun. Never let this stun.
+ if(returned_damage != FALSE && returned_damage > 0)
carbon_victim.dropItemToGround(carbon_victim.get_active_held_item())
carbon_victim.dropItemToGround(carbon_victim.get_inactive_held_item())
- carbon_victim.adjust_timed_status_effect(15 SECONDS, /datum/status_effect/confusion)
+ // Confusion is more or less expected to happen due to the rarity of electric armor and the ability to select zones.
+ // Expected defense items: hardsuit (all @ 100), insulated gloves (arms @ 100), any engineering shoes (legs @ 100), hardsuit (head @ 100), hazard vest / engineering coat (chest @ 20).
+ var/shock_multiplier = returned_damage / 15 // Accounts for armor, siemens_coeff, and future changes.
+ carbon_victim.adjust_timed_status_effect(max(3 SECONDS * shock_multiplier, 5), /datum/status_effect/confusion)
carbon_victim.visible_message(
span_danger("[caster] electrocutes [victim]!"),
span_userdanger("[caster] electrocutes you!"),
diff --git a/code/datums/profiling.dm b/code/datums/profiling.dm
index aa452de33a73..dcf966750a62 100644
--- a/code/datums/profiling.dm
+++ b/code/datums/profiling.dm
@@ -1,5 +1,5 @@
//these are real globals so you can use profiling to profile early world init stuff.
-GLOBAL_REAL_VAR(list/PROFILE_STORE)
+GLOBAL_REAL(PROFILE_STORE, /list)
GLOBAL_REAL_VAR(PROFILE_LINE)
GLOBAL_REAL_VAR(PROFILE_FILE)
GLOBAL_REAL_VAR(PROFILE_SLEEPCHECK)
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index 23b916604dfe..04e5b873b976 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -35,8 +35,7 @@
goal = goal_number
bar_loc = target
bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0")
- bar.plane = ABOVE_HUD_PLANE + 1 //yogs change, increased so it draws ontop of ventcrawling overlays
- //SET_PLANE_EXPLICIT(bar, ABOVE_HUD_PLANE, User) //comment in when we port TG planes
+ SET_PLANE_EXPLICIT(bar, ABOVE_HUD_PLANE, User) //yogs change, increased so it draws ontop of ventcrawling overlays
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
@@ -48,7 +47,7 @@
user_client = user.client
add_prog_bar_image_to_client()
- RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(on_user_delete))
+ RegisterSignal(user, COMSIG_QDELETING, PROC_REF(on_user_delete))
RegisterSignal(user, COMSIG_MOB_LOGOUT, PROC_REF(clean_user_client))
RegisterSignal(user, COMSIG_MOB_LOGIN, PROC_REF(on_user_login))
diff --git a/code/datums/ruins.dm b/code/datums/ruins.dm
index 2070bdba6657..c07a9f04215d 100644
--- a/code/datums/ruins.dm
+++ b/code/datums/ruins.dm
@@ -6,20 +6,30 @@
How is there a wooden container filled with 18th century coinage in the middle of a lavawracked hellscape? \
It is clearly a mystery."
- var/unpickable = FALSE //If TRUE these won't be placed automatically (can still be forced or loaded with another ruin)
- var/always_place = FALSE //Will skip the whole weighting process and just plop this down, ideally you want the ruins of this kind to have no cost.
- var/placement_weight = 1 //How often should this ruin appear
- var/cost = 0 //Cost in ruin budget placement system
+ ///If TRUE these won't be placed automatically (can still be forced or loaded with another ruin)
+ var/unpickable = FALSE
+ ///Will skip the whole weighting process and just plop this down, ideally you want the ruins of this kind to have no cost.
+ var/always_place = FALSE
+ ///How often should this ruin appear
+ var/placement_weight = 1
+ ///Cost in ruin budget placement system
+ var/cost = 0
+ /// If TRUE, this ruin can be placed multiple times in the same map
var/allow_duplicates = TRUE
- var/list/always_spawn_with = null //These ruin types will be spawned along with it (where dependent on the flag) eg list(/datum/map_template/ruin/space/teleporter_space = SPACERUIN_Z)
- var/list/never_spawn_with = null //If this ruin is spawned these will not eg list(/datum/map_template/ruin/base_alternate)
-
+ ///These ruin types will be spawned along with it (where dependent on the flag) eg list(/datum/map_template/ruin/space/teleporter_space = SPACERUIN_Z)
+ var/list/always_spawn_with = null
+ ///If this ruin is spawned these will not eg list(/datum/map_template/ruin/base_alternate)
+ var/list/never_spawn_with = null
+ ///Static part of the ruin path eg "_maps\RandomRuins\LavaRuins\"
var/prefix = null
+ ///The dynamic part of the ruin path eg "lavaland_surface_ruinfile.dmm"
var/suffix = null
+ ///What flavor or ruin is this? eg ZTRAIT_SPACE_RUINS
+ var/ruin_type = null
/datum/map_template/ruin/New()
if(!name && id)
name = id
mappath = prefix + suffix
- ..(path = mappath)
\ No newline at end of file
+ ..(path = mappath)
diff --git a/code/datums/ruins/icemoon.dm b/code/datums/ruins/icemoon.dm
index d3ed65a6b87f..b3d538177f07 100644
--- a/code/datums/ruins/icemoon.dm
+++ b/code/datums/ruins/icemoon.dm
@@ -4,6 +4,11 @@
prefix = "_maps/RandomRuins/IceRuins/"
allow_duplicates = FALSE
cost = 5
+ ruin_type = ZTRAIT_ICE_RUINS
+ default_area = /area/icemoon/surface/outdoors/unexplored
+ has_ceiling = TRUE
+ ceiling_turf = /turf/closed/mineral/snowmountain/do_not_chasm
+ ceiling_baseturfs = list(/turf/open/floor/plating/asteroid/snow/icemoon/do_not_chasm)
// above ground only
@@ -83,6 +88,8 @@
/datum/map_template/ruin/icemoon/underground
name = "underground ruin"
+ ruin_type = ZTRAIT_ICE_RUINS_UNDERGROUND
+ default_area = /area/icemoon/underground/unexplored
/datum/map_template/ruin/icemoon/underground/abandonedvillage
name = "Abandoned Village"
diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm
index d19b16310078..5c23c96e2fd1 100644
--- a/code/datums/ruins/space.dm
+++ b/code/datums/ruins/space.dm
@@ -103,7 +103,7 @@
id = "empty-shell"
suffix = "emptyshell.dmm"
name = "Empty Shell"
- description = "Cosy, rural property available for young professional couple. Only twelve parsecs from the nearest hyperspace lane!"
+ description = "Cosy, rural property available for young professional couple. Only twelve parsecs from the nearest gigabeacon!"
/datum/map_template/ruin/space/gas_the_lizards
id = "gas-the-lizards"
diff --git a/code/datums/shuttles/_shuttles.dm b/code/datums/shuttles/_shuttles.dm
new file mode 100644
index 000000000000..f4e4b688ff68
--- /dev/null
+++ b/code/datums/shuttles/_shuttles.dm
@@ -0,0 +1,351 @@
+/datum/map_template/shuttle
+ name = "Base Shuttle Template"
+ var/prefix = "_maps/shuttles/"
+ var/suffix
+ /**
+ * Port ID is the place this template should be docking at, set on '/obj/docking_port/stationary'
+ * Because getShuttle() compares port_id to shuttle_id to find an already existing shuttle,
+ * you should set shuttle_id to be the same as port_id if you want them to be replacable.
+ */
+ var/port_id
+ /// ID of the shuttle, make sure it matches port_id if necessary.
+ var/shuttle_id
+ /// Information to display on communication console about the shuttle
+ var/description
+ /// The recommended occupancy limit for the shuttle (count chairs, beds, and benches then round to 5)
+ var/occupancy_limit
+ /// Description of the prerequisition that has to be achieved for the shuttle to be purchased
+ var/prerequisites
+ /// Shuttle warnings and hazards to the admin who spawns the shuttle
+ var/admin_notes
+ /// How much does this shuttle cost the cargo budget to purchase? Put in terms of CARGO_CRATE_VALUE to properly scale the cost with the current balance of cargo's income.
+ var/credit_cost = INFINITY
+ /// What job accesses can buy this shuttle? If null, this shuttle cannot be bought.
+ var/list/who_can_purchase = list(ACCESS_CAPTAIN)
+ /// Whether or not this shuttle is locked to emags only.
+ var/emag_only = FALSE
+ /// If set, overrides default movement_force on shuttle
+ var/list/movement_force
+
+ var/port_x_offset
+ var/port_y_offset
+ var/extra_desc = ""
+
+/datum/map_template/shuttle/proc/prerequisites_met()
+ return TRUE
+
+/datum/map_template/shuttle/New()
+ shuttle_id = "[port_id]_[suffix]"
+ mappath = "[prefix][shuttle_id].dmm"
+ . = ..()
+
+/datum/map_template/shuttle/preload_size(path, cache)
+ . = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map
+ if(!cached_map)
+ return
+
+ var/offset = discover_offset(/obj/docking_port/mobile)
+
+ port_x_offset = offset[1]
+ port_y_offset = offset[2]
+
+ if(!cache)
+ cached_map = null
+
+/datum/map_template/shuttle/load(turf/T, centered, register=TRUE)
+ . = ..()
+ if(!.)
+ return
+ var/list/turfs = block( locate(.[MAP_MINX], .[MAP_MINY], .[MAP_MINZ]),
+ locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ]))
+ for(var/i in 1 to turfs.len)
+ var/turf/place = turfs[i]
+ if(isspaceturf(place)) // This assumes all shuttles are loaded in a single spot then moved to their real destination.
+ continue
+
+ if (place.count_baseturfs() < 2) // Some snowflake shuttle shit
+ continue
+
+ place.insert_baseturf(3, /turf/baseturf_skipover/shuttle)
+
+ for(var/obj/docking_port/mobile/port in place)
+ port.calculate_docking_port_information(src)
+ // initTemplateBounds explicitly ignores the shuttle's docking port, to ensure that it calculates the bounds of the shuttle correctly
+ // so we need to manually initialize it here
+ SSatoms.InitializeAtoms(list(port))
+ if(register)
+ port.register()
+
+//Whatever special stuff you want
+/datum/map_template/shuttle/post_load(obj/docking_port/mobile/M)
+ if(movement_force)
+ M.movement_force = movement_force.Copy()
+ M.linkup()
+
+/datum/map_template/shuttle/cargo
+ port_id = "cargo"
+ name = "Base Shuttle Template (Cargo)"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/ferry
+ port_id = "ferry"
+ name = "Base Shuttle Template (Ferry)"
+
+/datum/map_template/shuttle/whiteship
+ port_id = "whiteship"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/labour
+ port_id = "labour"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/mining
+ port_id = "mining"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/arrival
+ port_id = "arrival"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/infiltrator
+ port_id = "infiltrator"
+
+/datum/map_template/shuttle/aux_base
+ port_id = "aux_base"
+
+/datum/map_template/shuttle/escape_pod/
+ port_id = "escape_pod"
+ suffix = "1"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/escape_pod/two
+ suffix = "2"
+
+/datum/map_template/shuttle/escape_pod/three
+ suffix = "3"
+
+/datum/map_template/shuttle/escape_pod/four
+ suffix = "4"
+
+/datum/map_template/shuttle/assault_pod
+ port_id = "assault_pod"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/pirate
+ port_id = "pirate"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/hunter
+ port_id = "hunter"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/ruin //For random shuttles in ruins
+ port_id = "ruin"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/snowdin
+ port_id = "snowdin"
+ who_can_purchase = null
+
+// Shuttles start here:
+
+/datum/map_template/shuttle/ferry/base
+ suffix = "base"
+ name = "transport ferry"
+ description = "Standard issue Box/Metastation CentCom ferry."
+
+/datum/map_template/shuttle/ferry/meat
+ suffix = "meat"
+ name = "\"meat\" ferry"
+ description = "Ahoy! We got all kinds o' meat aft here. Meat from plant people, people who be dark, not in a racist way, just they're dark black. \
+ Oh and lizard meat too,mighty popular that is. Definitely 100% fresh, just ask this guy here. *person on meatspike moans* See? \
+ Definitely high quality meat, nothin' wrong with it, nothin' added, definitely no zombifyin' reagents!"
+ admin_notes = "Meat currently contains no zombifying reagents, lizard on meatspike must be spawned in."
+
+/datum/map_template/shuttle/ferry/lighthouse
+ suffix = "lighthouse"
+ name = "The Lighthouse(?)"
+ description = "*static*... part of a much larger vessel, possibly military in origin. \
+ The weapon markings aren't anything we've seen ...static... by almost never the same person twice, possible use of unknown storage ...static... \
+ seeing ERT officers onboard, but no missions are on file for ...static...static...annoying jingle... only at The LIGHTHOUSE! \
+ Fulfilling needs you didn't even know you had. We've got EVERYTHING, and something else!"
+ admin_notes = "Currently larger than ferry docking port on Box, will not hit anything, but must be force docked. Trader and ERT bodyguards are not included."
+
+/datum/map_template/shuttle/ferry/fancy
+ suffix = "fancy"
+ name = "fancy transport ferry"
+ description = "At some point, someone upgraded the ferry to have fancier flooring... and fewer seats."
+
+/datum/map_template/shuttle/ferry/kilo
+ suffix = "kilo"
+ name = "kilo transport ferry"
+ description = "Standard issue CentCom Ferry for Kilo pattern stations. Includes additional equipment and rechargers."
+
+/datum/map_template/shuttle/whiteship/box
+ suffix = "1"
+ name = "Hospital Ship"
+
+/datum/map_template/shuttle/whiteship/meta
+ suffix = "2"
+ name = "Salvage Ship"
+
+/datum/map_template/shuttle/whiteship/pubby
+ suffix = "3"
+ name = "NT White UFO"
+
+/datum/map_template/shuttle/whiteship/cere
+ suffix = "4"
+ name = "NT Construction Vessel"
+
+/datum/map_template/shuttle/whiteship/delta
+ suffix = "5"
+ name = "NT Frigate"
+
+/datum/map_template/shuttle/whiteship/pod
+ suffix = "whiteship_pod"
+ name = "Salvage Pod"
+
+/datum/map_template/shuttle/cargo/box
+ suffix = "box"
+ name = "supply shuttle (Box)"
+
+/datum/map_template/shuttle/cargo/birdboat
+ suffix = "birdboat"
+ name = "supply shuttle (Birdboat)"
+
+/datum/map_template/shuttle/cargo/kilo
+ suffix = "kilo"
+ name = "supply shuttle (Kilo)"
+
+/datum/map_template/shuttle/cargo/gax
+ suffix = "gax"
+ name = "supply shuttle (Gax)"
+
+/datum/map_template/shuttle/arrival/box
+ suffix = "box"
+ name = "arrival shuttle (Box)"
+
+/datum/map_template/shuttle/cargo/box
+ suffix = "box"
+ name = "cargo ferry (Box)"
+
+/datum/map_template/shuttle/mining/box
+ suffix = "box"
+ name = "mining shuttle (Box)"
+
+/datum/map_template/shuttle/mining/kilo
+ suffix = "kilo"
+ name = "mining shuttle (Kilo)"
+
+/datum/map_template/shuttle/labour/box
+ suffix = "box"
+ name = "labour shuttle (Box)"
+
+/datum/map_template/shuttle/labour/kilo
+ suffix = "kilo"
+ name = "labour shuttle (Kilo)"
+
+/datum/map_template/shuttle/labour/gax
+ suffix = "gax"
+ name = "labour shuttle (Gax)"
+
+/datum/map_template/shuttle/infiltrator/basic
+ suffix = "basic"
+ name = "basic syndicate infiltrator"
+
+/datum/map_template/shuttle/cargo/delta
+ suffix = "delta"
+ name = "cargo ferry (Delta)"
+
+/datum/map_template/shuttle/mining/delta
+ suffix = "delta"
+ name = "mining shuttle (Delta)"
+
+/datum/map_template/shuttle/labour/delta
+ suffix = "delta"
+ name = "labour shuttle (Delta)"
+
+/datum/map_template/shuttle/arrival/delta
+ suffix = "delta"
+ name = "arrival shuttle (Delta)"
+
+/datum/map_template/shuttle/arrival/omega
+ suffix = "omega"
+ name = "arrival shuttle (Omega)"
+
+/datum/map_template/shuttle/arrival/kilo
+ suffix = "kilo"
+ name = "arrival shuttle (Kilo)"
+
+/datum/map_template/shuttle/aux_base
+ port_id = "aux_base"
+ who_can_purchase = null
+
+/datum/map_template/shuttle/aux_base/default
+ suffix = "default"
+ name = "auxilliary base (Default)"
+
+/datum/map_template/shuttle/aux_base/small
+ suffix = "small"
+ name = "auxilliary base (Small)"
+
+/datum/map_template/shuttle/escape_pod/default
+ suffix = "default"
+ name = "escape pod (Default)"
+
+/datum/map_template/shuttle/escape_pod/large
+ suffix = "large"
+ name = "escape pod (Large)"
+
+/datum/map_template/shuttle/assault_pod/default
+ suffix = "default"
+ name = "assault pod (Default)"
+
+/datum/map_template/shuttle/pirate/default
+ suffix = "default"
+ name = "pirate ship (Default)"
+
+/datum/map_template/shuttle/hunter/space_cop
+ suffix = "space_cop"
+ name = "Police Spacevan"
+
+/datum/map_template/shuttle/hunter/russian
+ suffix = "russian"
+ name = "Russian Cargo Ship"
+
+/datum/map_template/shuttle/ruin/caravan_victim
+ suffix = "caravan_victim"
+ name = "Small Freighter"
+
+/datum/map_template/shuttle/ruin/pirate_cutter
+ suffix = "pirate_cutter"
+ name = "Pirate Cutter"
+
+/datum/map_template/shuttle/ruin/syndicate_dropship
+ suffix = "syndicate_dropship"
+ name = "Syndicate Dropship"
+
+/datum/map_template/shuttle/ruin/syndicate_fighter_shiv
+ suffix = "syndicate_fighter_shiv"
+ name = "Syndicate Fighter"
+
+/datum/map_template/shuttle/snowdin/mining
+ suffix = "mining"
+ name = "Snowdin Mining Elevator"
+
+/datum/map_template/shuttle/snowdin/excavation
+ suffix = "excavation"
+ name = "Snowdin Excavation Elevator"
+
+/datum/map_template/shuttle/arrival/gax
+ suffix = "gax"
+ name = "arrival shuttle (Gax)"
+
+/datum/map_template/shuttle/ai/gax
+ port_id = "ai"
+ suffix = "gax"
+ name = "ai ship shuttle (Gax)"
+
+/datum/map_template/shuttle/arrival/donut
+ suffix = "donut"
+ name = "arrival shuttle (Donut)"
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles/emergency.dm
similarity index 63%
rename from code/datums/shuttles.dm
rename to code/datums/shuttles/emergency.dm
index a51cfa22ba23..c2afff8e4f5b 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles/emergency.dm
@@ -1,176 +1,51 @@
-/datum/map_template/shuttle
- name = "Base Shuttle Template"
- var/prefix = "_maps/shuttles/"
- var/suffix
- var/port_id
- var/shuttle_id
-
- var/description
- var/prerequisites
- var/admin_notes
-
- var/credit_cost = INFINITY
- var/emag_buy = FALSE
-
- var/list/movement_force // If set, overrides default movement_force on shuttle
-
- var/port_x_offset
- var/port_y_offset
- var/extra_desc = ""
-
-/datum/map_template/shuttle/proc/prerequisites_met()
- return TRUE
-
-/datum/map_template/shuttle/New()
- shuttle_id = "[port_id]_[suffix]"
- mappath = "[prefix][shuttle_id].dmm"
- . = ..()
-
-/datum/map_template/shuttle/preload_size(path, cache)
- . = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map
- if(!cached_map)
- return
-
- discover_port_offset()
-
- if(!cache)
- cached_map = null
-
-/datum/map_template/shuttle/proc/discover_port_offset()
- var/key
- var/list/models = cached_map.grid_models
- for(key in models)
- if(findtext(models[key], "[/obj/docking_port/mobile]")) // Yay compile time checks
- break // This works by assuming there will ever only be one mobile dock in a template at most
-
- for(var/i in cached_map.gridSets)
- var/datum/grid_set/gset = i
- var/ycrd = gset.ycrd
- for(var/line in gset.gridLines)
- var/xcrd = gset.xcrd
- for(var/j in 1 to length(line) step cached_map.key_len)
- if(key == copytext(line, j, j + cached_map.key_len))
- port_x_offset = xcrd
- port_y_offset = ycrd
- return
- ++xcrd
- --ycrd
-
-/datum/map_template/shuttle/load(turf/T, centered, register=TRUE)
- . = ..()
- if(!.)
- return
- var/list/turfs = block( locate(.[MAP_MINX], .[MAP_MINY], .[MAP_MINZ]),
- locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ]))
- for(var/i in 1 to turfs.len)
- var/turf/place = turfs[i]
- if(istype(place, /turf/open/space)) // This assumes all shuttles are loaded in a single spot then moved to their real destination.
- continue
- if(length(place.baseturfs) < 2) // Some snowflake shuttle shit
- continue
- place.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle)
-
- for(var/obj/docking_port/mobile/port in place)
- // initTemplateBounds explicitly ignores the shuttle's docking port, to ensure that it calculates the bounds of the shuttle correctly
- // so we need to manually initialize it here
- SSatoms.InitializeAtoms(list(port))
- if(register)
- port.register()
- if(isnull(port_x_offset))
- continue
- switch(port.dir) // Yeah this looks a little ugly but mappers had to do this in their head before
- if(NORTH)
- port.width = width
- port.height = height
- port.dwidth = port_x_offset - 1
- port.dheight = port_y_offset - 1
- if(EAST)
- port.width = height
- port.height = width
- port.dwidth = height - port_y_offset
- port.dheight = port_x_offset - 1
- if(SOUTH)
- port.width = width
- port.height = height
- port.dwidth = width - port_x_offset
- port.dheight = height - port_y_offset
- if(WEST)
- port.width = height
- port.height = width
- port.dwidth = port_y_offset - 1
- port.dheight = width - port_x_offset
-
-//Whatever special stuff you want
-/datum/map_template/shuttle/proc/post_load(obj/docking_port/mobile/M)
- if(movement_force)
- M.movement_force = movement_force.Copy()
+#define EMAG_LOCKED_SHUTTLE_COST (CARGO_CRATE_VALUE * 50)
/datum/map_template/shuttle/emergency
port_id = "emergency"
name = "Base Shuttle Template (Emergency)"
-
-/datum/map_template/shuttle/cargo
- port_id = "cargo"
- name = "Base Shuttle Template (Cargo)"
-
-/datum/map_template/shuttle/ferry
- port_id = "ferry"
- name = "Base Shuttle Template (Ferry)"
-
-/datum/map_template/shuttle/whiteship
- port_id = "whiteship"
-
-/datum/map_template/shuttle/labour
- port_id = "labour"
-
-/datum/map_template/shuttle/mining
- port_id = "mining"
-
-/datum/map_template/shuttle/cargo
- port_id = "cargo"
-
-/datum/map_template/shuttle/arrival
- port_id = "arrival"
-
-/datum/map_template/shuttle/infiltrator
- port_id = "infiltrator"
-
-/datum/map_template/shuttle/aux_base
- port_id = "aux_base"
-
-/datum/map_template/shuttle/escape_pod/
- port_id = "escape_pod"
- suffix = "1"
-
-/datum/map_template/shuttle/escape_pod/two
- suffix = "2"
-
-/datum/map_template/shuttle/escape_pod/three
- suffix = "3"
-
-/datum/map_template/shuttle/escape_pod/four
- suffix = "4"
-
-/datum/map_template/shuttle/assault_pod
- port_id = "assault_pod"
-
-/datum/map_template/shuttle/pirate
- port_id = "pirate"
-
-/datum/map_template/shuttle/hunter
- port_id = "hunter"
-
-/datum/map_template/shuttle/ruin //For random shuttles in ruins
- port_id = "ruin"
-
-/datum/map_template/shuttle/snowdin
- port_id = "snowdin"
-
-// Shuttles start here:
+ ///assoc list of shuttle events to add to this shuttle on spawn (typepath = weight)
+ var/list/events
+ ///pick all events instead of random
+ var/use_all_events = FALSE
+ ///how many do we pick
+ var/event_amount = 1
+ ///do we empty the event list before adding our events
+ var/events_override = FALSE
+
+ // Yog code: we haven't completely revamped shuttles yet so to avoid doing a lot more work i'm just adding a placeholder value
+ occupancy_limit = "FLEXIBLE"
+
+/datum/map_template/shuttle/emergency/New()
+ . = ..()
+ if(!occupancy_limit && who_can_purchase)
+ CRASH("The [name] needs an occupancy limit!")
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_SHUTTLE_SALE) && credit_cost > 0 && prob(15))
+ var/discount_amount = round(rand(25, 80), 5)
+ name += " ([discount_amount]% Discount!)"
+ var/discount_multiplier = 100 - discount_amount
+ credit_cost = ((credit_cost * discount_multiplier) / 100)
+
+///on post_load use our variables to change shuttle events
+/datum/map_template/shuttle/emergency/post_load(obj/docking_port/mobile/mobile)
+ . = ..()
+ if(!events)
+ return
+ // if(events_override)
+ // mobile.event_list.Cut()
+ // if(use_all_events)
+ // for(var/path in events)
+ // mobile.event_list.Add(new path(mobile))
+ // events -= path
+ // else
+ // for(var/i in 1 to event_amount)
+ // var/path = pick_weight(events)
+ // events -= path
+ // mobile.event_list.Add(new path(mobile))
/datum/map_template/shuttle/emergency/backup
suffix = "backup"
name = "Backup Shuttle"
+ who_can_purchase = null
/datum/map_template/shuttle/emergency/construction
suffix = "construction"
@@ -248,7 +123,7 @@
description = "Dis is a high-quality shuttle, da. Many seats, lots of space, all equipment! Even includes entertainment! Such as lots to drink, and a fighting arena for drunk crew to have fun! If arena not fun enough, simply press button of releasing bears. Do not worry, bears trained not to break out of fighting pit, so totally safe so long as nobody stupid or drunk enough to leave door open. Try not to let asimov babycons ruin fun!"
admin_notes = "Includes a small variety of weapons. And bears. Only captain-access can release the bears. Bears won't smash the windows themselves, but they can escape if someone lets them."
credit_cost = 5000 // While the shuttle is rusted and poorly maintained, trained bears are costly.
- emag_buy = TRUE
+ emag_only = TRUE
/datum/map_template/shuttle/emergency/meteor
suffix = "meteor"
@@ -257,7 +132,7 @@
admin_notes = "This shuttle will likely crush escape, killing anyone there."
credit_cost = 15000
movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
- emag_buy = TRUE
+ emag_only = TRUE
/datum/map_template/shuttle/emergency/luxury
suffix = "luxury"
@@ -273,7 +148,7 @@
description = "The glorious results of centuries of plasma research done by Nanotrasen employees. This is the reason why you are here. Get on and dance like you're on fire, burn baby burn!"
admin_notes = "Flaming hot. The main area has a dance machine as well as plasma floor tiles that will be ignited by players every single time."
credit_cost = 10000
- emag_buy = TRUE
+ emag_only = TRUE
/datum/map_template/shuttle/emergency/arena
suffix = "arena"
@@ -281,7 +156,7 @@
description = "The crew must pass through an otherworldly arena to board this shuttle. Expect massive casualties. The source of the Bloody Signal must be tracked down and eliminated to unlock this shuttle."
admin_notes = "RIP AND TEAR."
credit_cost = 10000
- emag_buy = TRUE
+ emag_only = TRUE
/datum/map_template/shuttle/emergency/arena/prerequisites_met()
return GLOB.bubblegum_dead
@@ -343,7 +218,7 @@
description = "Due to a lack of functional emergency shuttles, we bought this second hand from a scrapyard and pressed it into service. Please do not lean too heavily on the exterior windows, they are fragile."
admin_notes = "An abomination with no functional medbay, sections missing, and some very fragile windows. Surprisingly airtight."
movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
- emag_buy = TRUE
+ emag_only = TRUE
/datum/map_template/shuttle/emergency/narnar
suffix = "narnar"
@@ -399,7 +274,7 @@
description = "I'm gonna make you an offer you can't refuse, the drone mafia has offered their 'services' to shuttle the crew. \
Just be careful, if you don't show class they might heckle you. Canoli not incuded."
admin_notes = "has 5 mafia drones that are pacified. By drone law they should only stun people if provoked. Has a pair of sentient barstaff also."
- emag_buy = TRUE
+ emag_only = TRUE
credit_cost = 100000//service fee
/datum/map_template/shuttle/emergency/supermatter
@@ -414,7 +289,7 @@
It does, however, still dust anything on contact, emits high levels of radiation, and induce hallucinations in anyone looking at it without protective goggles. \
Emitters spawn powered on, expect admin notices, they are harmless."
credit_cost = 100000
- emag_buy = TRUE
+ emag_only = TRUE
movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
/datum/map_template/shuttle/emergency/imfedupwiththisworld
@@ -424,7 +299,7 @@
Aw, come space on. Why not? No, I can't. Anyway, how is your space roleplay life?"
admin_notes = "Tiny, with a single airlock and wooden walls. What could go wrong?"
credit_cost = 7500
- emag_buy = TRUE
+ emag_only = TRUE
movement_force = list("KNOCKDOWN" = 3, "THROW" = 2)
/datum/map_template/shuttle/emergency/goon
@@ -455,7 +330,7 @@
Needless to say, no engineering team wanted to go near the thing, and it's only being used as an Emergency Escape Shuttle because there is literally nothing else available."
admin_notes = "If the crew can solve the puzzle, they will wake the wabbajack statue. It will likely not end well. There's a reason it's boarded up. Maybe they should have just left it alone."
credit_cost = 15000
- emag_buy = TRUE
+ emag_only = TRUE
/datum/map_template/shuttle/emergency/omega
suffix = "omega"
@@ -463,78 +338,6 @@
description = "On the smaller size with a modern design, this shuttle is for the crew who like the cosier things, while still being able to stretch their legs."
credit_cost = 1000
-/datum/map_template/shuttle/ferry/base
- suffix = "base"
- name = "transport ferry"
- description = "Standard issue Box/Metastation CentCom ferry."
-
-/datum/map_template/shuttle/ferry/meat
- suffix = "meat"
- name = "\"meat\" ferry"
- description = "Ahoy! We got all kinds o' meat aft here. Meat from plant people, people who be dark, not in a racist way, just they're dark black. \
- Oh and lizard meat too,mighty popular that is. Definitely 100% fresh, just ask this guy here. *person on meatspike moans* See? \
- Definitely high quality meat, nothin' wrong with it, nothin' added, definitely no zombifyin' reagents!"
- admin_notes = "Meat currently contains no zombifying reagents, lizard on meatspike must be spawned in."
-
-/datum/map_template/shuttle/ferry/lighthouse
- suffix = "lighthouse"
- name = "The Lighthouse(?)"
- description = "*static*... part of a much larger vessel, possibly military in origin. \
- The weapon markings aren't anything we've seen ...static... by almost never the same person twice, possible use of unknown storage ...static... \
- seeing ERT officers onboard, but no missions are on file for ...static...static...annoying jingle... only at The LIGHTHOUSE! \
- Fulfilling needs you didn't even know you had. We've got EVERYTHING, and something else!"
- admin_notes = "Currently larger than ferry docking port on Box, will not hit anything, but must be force docked. Trader and ERT bodyguards are not included."
-
-/datum/map_template/shuttle/ferry/fancy
- suffix = "fancy"
- name = "fancy transport ferry"
- description = "At some point, someone upgraded the ferry to have fancier flooring... and fewer seats."
-
-/datum/map_template/shuttle/ferry/kilo
- suffix = "kilo"
- name = "kilo transport ferry"
- description = "Standard issue CentCom Ferry for Kilo pattern stations. Includes additional equipment and rechargers."
-
-/datum/map_template/shuttle/whiteship/box
- suffix = "1"
- name = "Hospital Ship"
-
-/datum/map_template/shuttle/whiteship/meta
- suffix = "2"
- name = "Salvage Ship"
-
-/datum/map_template/shuttle/whiteship/pubby
- suffix = "3"
- name = "NT White UFO"
-
-/datum/map_template/shuttle/whiteship/cere
- suffix = "4"
- name = "NT Construction Vessel"
-
-/datum/map_template/shuttle/whiteship/delta
- suffix = "5"
- name = "NT Frigate"
-
-/datum/map_template/shuttle/whiteship/pod
- suffix = "whiteship_pod"
- name = "Salvage Pod"
-
-/datum/map_template/shuttle/cargo/box
- suffix = "box"
- name = "supply shuttle (Box)"
-
-/datum/map_template/shuttle/cargo/birdboat
- suffix = "birdboat"
- name = "supply shuttle (Birdboat)"
-
-/datum/map_template/shuttle/cargo/kilo
- suffix = "kilo"
- name = "supply shuttle (Kilo)"
-
-/datum/map_template/shuttle/cargo/gax
- suffix = "gax"
- name = "supply shuttle (Gax)"
-
/datum/map_template/shuttle/emergency/delta
suffix = "delta"
name = "Delta Station Emergency Shuttle"
@@ -542,7 +345,6 @@
admin_notes = "Go big or go home."
credit_cost = 7500
-/* Disabled for having fucked atmos
/datum/map_template/shuttle/emergency/raven
suffix = "raven"
name = "CentCom Raven Cruiser"
@@ -551,129 +353,4 @@
This escape shuttle boasts shields and numerous anti-personnel turrets guarding its perimeter to fend off meteors and enemy boarding attempts."
admin_notes = "Comes with turrets that will target anything without the neutral faction (nuke ops, xenos etc, but not pets)."
credit_cost = 30000
-*/
-
-/datum/map_template/shuttle/arrival/box
- suffix = "box"
- name = "arrival shuttle (Box)"
-
-/datum/map_template/shuttle/cargo/box
- suffix = "box"
- name = "cargo ferry (Box)"
-
-/datum/map_template/shuttle/mining/box
- suffix = "box"
- name = "mining shuttle (Box)"
-
-/datum/map_template/shuttle/mining/kilo
- suffix = "kilo"
- name = "mining shuttle (Kilo)"
-
-/datum/map_template/shuttle/labour/box
- suffix = "box"
- name = "labour shuttle (Box)"
-
-/datum/map_template/shuttle/labour/kilo
- suffix = "kilo"
- name = "labour shuttle (Kilo)"
-
-/datum/map_template/shuttle/labour/gax
- suffix = "gax"
- name = "labour shuttle (Gax)"
-
-/datum/map_template/shuttle/infiltrator/basic
- suffix = "basic"
- name = "basic syndicate infiltrator"
-
-/datum/map_template/shuttle/cargo/delta
- suffix = "delta"
- name = "cargo ferry (Delta)"
-
-/datum/map_template/shuttle/mining/delta
- suffix = "delta"
- name = "mining shuttle (Delta)"
-
-/datum/map_template/shuttle/labour/delta
- suffix = "delta"
- name = "labour shuttle (Delta)"
-/datum/map_template/shuttle/arrival/delta
- suffix = "delta"
- name = "arrival shuttle (Delta)"
-
-/datum/map_template/shuttle/arrival/omega
- suffix = "omega"
- name = "arrival shuttle (Omega)"
-
-/datum/map_template/shuttle/arrival/kilo
- suffix = "kilo"
- name = "arrival shuttle (Kilo)"
-
-/datum/map_template/shuttle/aux_base/default
- suffix = "default"
- name = "auxilliary base (Default)"
-
-/datum/map_template/shuttle/aux_base/small
- suffix = "small"
- name = "auxilliary base (Small)"
-
-/datum/map_template/shuttle/escape_pod/default
- suffix = "default"
- name = "escape pod (Default)"
-
-/datum/map_template/shuttle/escape_pod/large
- suffix = "large"
- name = "escape pod (Large)"
-
-/datum/map_template/shuttle/assault_pod/default
- suffix = "default"
- name = "assault pod (Default)"
-
-/datum/map_template/shuttle/pirate/default
- suffix = "default"
- name = "pirate ship (Default)"
-
-/datum/map_template/shuttle/hunter/space_cop
- suffix = "space_cop"
- name = "Police Spacevan"
-
-/datum/map_template/shuttle/hunter/russian
- suffix = "russian"
- name = "Russian Cargo Ship"
-
-/datum/map_template/shuttle/ruin/caravan_victim
- suffix = "caravan_victim"
- name = "Small Freighter"
-
-/datum/map_template/shuttle/ruin/pirate_cutter
- suffix = "pirate_cutter"
- name = "Pirate Cutter"
-
-/datum/map_template/shuttle/ruin/syndicate_dropship
- suffix = "syndicate_dropship"
- name = "Syndicate Dropship"
-
-/datum/map_template/shuttle/ruin/syndicate_fighter_shiv
- suffix = "syndicate_fighter_shiv"
- name = "Syndicate Fighter"
-
-/datum/map_template/shuttle/snowdin/mining
- suffix = "mining"
- name = "Snowdin Mining Elevator"
-
-/datum/map_template/shuttle/snowdin/excavation
- suffix = "excavation"
- name = "Snowdin Excavation Elevator"
-
-/datum/map_template/shuttle/arrival/gax
- suffix = "gax"
- name = "arrival shuttle (Gax)"
-
-/datum/map_template/shuttle/ai/gax
- port_id = "ai"
- suffix = "gax"
- name = "ai ship shuttle (Gax)"
-
-/datum/map_template/shuttle/arrival/donut
- suffix = "donut"
- name = "arrival shuttle (Donut)"
diff --git a/code/datums/signals.dm b/code/datums/signals.dm
new file mode 100644
index 000000000000..832ad69a2fa9
--- /dev/null
+++ b/code/datums/signals.dm
@@ -0,0 +1,131 @@
+/**
+ * Register to listen for a signal from the passed in target
+ *
+ * This sets up a listening relationship such that when the target object emits a signal
+ * the source datum this proc is called upon, will receive a callback to the given proctype
+ * Use PROC_REF(procname), TYPE_PROC_REF(type,procname) or GLOBAL_PROC_REF(procname) macros to validate the passed in proc at compile time.
+ * PROC_REF for procs defined on current type or it's ancestors, TYPE_PROC_REF for procs defined on unrelated type and GLOBAL_PROC_REF for global procs.
+ * Return values from procs registered must be a bitfield
+ *
+ * Arguments:
+ * * datum/target The target to listen for signals from
+ * * signal_type A signal name
+ * * proctype The proc to call back when the signal is emitted
+ * * override If a previous registration exists you must explicitly set this
+ */
+/datum/proc/RegisterSignal(datum/target, signal_type, proctype, override = FALSE)
+ if(QDELETED(src) || QDELETED(target))
+ return
+
+ if (islist(signal_type))
+ var/static/list/known_failures = list()
+ var/list/signal_type_list = signal_type
+ var/message = "([target.type]) is registering [signal_type_list.Join(", ")] as a list, the older method. Change it to RegisterSignals."
+
+ if (!(message in known_failures))
+ known_failures[message] = TRUE
+ stack_trace("[target] [message]")
+
+ RegisterSignals(target, signal_type, proctype, override)
+ return
+
+ var/list/procs = (_signal_procs ||= list())
+ var/list/target_procs = (procs[target] ||= list())
+ var/list/lookup = (target._listen_lookup ||= list())
+
+ var/exists = target_procs[signal_type]
+ target_procs[signal_type] = proctype
+
+ if(exists)
+ if(!override)
+ var/override_message = "[signal_type] overridden. Use override = TRUE to suppress this warning.\nTarget: [target] ([target.type]) Proc: [proctype]"
+ log_signal(override_message)
+ stack_trace(override_message)
+ return
+
+ var/list/looked_up = lookup[signal_type]
+
+ if(isnull(looked_up)) // Nothing has registered here yet
+ lookup[signal_type] = src
+ else if(!islist(looked_up)) // One other thing registered here
+ lookup[signal_type] = list(looked_up, src)
+ else // Many other things have registered here
+ looked_up += src
+
+/// Registers multiple signals to the same proc.
+/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
+ for (var/signal_type in signal_types)
+ RegisterSignal(target, signal_type, proctype, override)
+
+/**
+ * Stop listening to a given signal from target
+ *
+ * Breaks the relationship between target and source datum, removing the callback when the signal fires
+ *
+ * Doesn't care if a registration exists or not
+ *
+ * Arguments:
+ * * datum/target Datum to stop listening to signals from
+ * * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically
+ */
+/datum/proc/UnregisterSignal(datum/target, sig_type_or_types)
+ //Yogs edit: One day we should remove this, but it's too much work to port it all at once and too many snowflake exceptions to fix.
+ //Sorry, but this check stays for now.
+ if(!target)
+ return
+ //Yogs edit end
+ var/list/lookup = target._listen_lookup
+ if(!_signal_procs || !_signal_procs[target] || !lookup)
+ return
+ if(!islist(sig_type_or_types))
+ sig_type_or_types = list(sig_type_or_types)
+ for(var/sig in sig_type_or_types)
+ if(!_signal_procs[target][sig])
+ if(!istext(sig))
+ stack_trace("We're unregistering with something that isn't a valid signal \[[sig]\], you fucked up")
+ continue
+ switch(length(lookup[sig]))
+ if(2)
+ lookup[sig] = (lookup[sig]-src)[1]
+ if(1)
+ stack_trace("[target] ([target.type]) somehow has single length list inside _listen_lookup")
+ if(src in lookup[sig])
+ lookup -= sig
+ if(!length(lookup))
+ target._listen_lookup = null
+ break
+ if(0)
+ if(lookup[sig] != src)
+ continue
+ lookup -= sig
+ if(!length(lookup))
+ target._listen_lookup = null
+ break
+ else
+ lookup[sig] -= src
+
+ _signal_procs[target] -= sig_type_or_types
+ if(!_signal_procs[target].len)
+ _signal_procs -= target
+
+/**
+ * Internal proc to handle most all of the signaling procedure
+ *
+ * Will runtime if used on datums with an empty lookup list
+ *
+ * Use the [SEND_SIGNAL] define instead
+ */
+/datum/proc/_SendSignal(sigtype, list/arguments)
+ var/target = _listen_lookup[sigtype]
+ if(!length(target))
+ var/datum/listening_datum = target
+ return NONE | call(listening_datum, listening_datum._signal_procs[src][sigtype])(arglist(arguments))
+ . = NONE
+ // This exists so that even if one of the signal receivers unregisters the signal,
+ // all the objects that are receiving the signal get the signal this final time.
+ // AKA: No you can't cancel the signal reception of another object by doing an unregister in the same signal.
+ var/list/queued_calls = list()
+ for(var/datum/listening_datum as anything in target)
+ queued_calls[listening_datum] = listening_datum._signal_procs[src][sigtype]
+ for(var/datum/listening_datum as anything in queued_calls)
+ . |= call(listening_datum, queued_calls[listening_datum])(arglist(arguments))
diff --git a/code/datums/station_traits/_station_trait.dm b/code/datums/station_traits/_station_trait.dm
index a316a105c12f..1d3d9f259085 100644
--- a/code/datums/station_traits/_station_trait.dm
+++ b/code/datums/station_traits/_station_trait.dm
@@ -8,6 +8,10 @@
var/trait_processes = FALSE
///Chance relative to other traits of its type to be picked
var/weight = 10
+ ///The cost of the trait, which is removed from the budget.
+ var/cost = STATION_TRAIT_COST_FULL
+ ///Whether this trait is always enabled; generally used for debugging
+ var/force = FALSE
///Does this trait show in the centcom report?
var/show_in_report = FALSE
///What message to show in the centcom report?
@@ -16,8 +20,20 @@
var/trait_to_give
///What traits are incompatible with this one?
var/blacklist
- ///Extra flags for station traits such as it being abstract
- var/trait_flags
+ ///Extra flags for station traits such as it being abstract, planetary or space only
+ var/trait_flags = STATION_TRAIT_MAP_UNRESTRICTED
+ /// Whether or not this trait can be reverted by an admin
+ var/can_revert = TRUE
+ /// If set to true we'll show a button on the lobby to notify people about this trait
+ var/sign_up_button = FALSE
+ /// Lobby buttons controlled by this trait
+ var/list/lobby_buttons = list()
+ /// The ID that we look for in dynamic.json. Not synced with 'name' because I can already see this go wrong
+ var/dynamic_threat_id
+ /// If ran during dynamic, do we reduce the total threat? Will be overriden by config if set
+ var/threat_reduction = 0
+ /// Trait should not be instantiated in a round if its type matches this type
+ var/abstract_type = /datum/station_trait
/datum/station_trait/New()
@@ -28,10 +44,36 @@
if(trait_to_give)
ADD_TRAIT(SSstation, trait_to_give, STATION_TRAIT)
+/datum/station_trait/Destroy()
+ SSstation.station_traits -= src
+ return ..()
+
+/// Returns the type of info the centcom report has on this trait, if any.
+/datum/station_trait/proc/get_report()
+ return "[name] - [report_message]"
+
+/// Will attempt to revert the station trait, used by admins.
+/datum/station_trait/proc/revert()
+ if (!can_revert)
+ CRASH("revert() was called on [type], which can't be reverted!")
+
+ if (trait_to_give)
+ REMOVE_TRAIT(SSstation, trait_to_give, STATION_TRAIT)
+
+ qdel(src)
+
+/// Called by decals if they can be colored, to see if we got some cool colors for them. Only takes the first station trait
+/proc/request_station_colors(atom/thing_to_color, pattern)
+ for(var/datum/station_trait/trait in SSstation.station_traits)
+ var/decal_color = trait.get_decal_color(thing_to_color, pattern || PATTERN_DEFAULT)
+ if(decal_color)
+ return decal_color
+ return null
+
+/// Return a color for the decals, if any
+/datum/station_trait/proc/get_decal_color(thing_to_color, pattern)
+ return
+
///Proc ran when round starts. Use this for roundstart effects.
/datum/station_trait/proc/on_round_start()
return
-
-///type of info the centcom report has on this trait, if any.
-/datum/station_trait/proc/get_report()
- return "[name] - [report_message]"
\ No newline at end of file
diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm
index 749570f5ee6d..cac8b3b8327d 100644
--- a/code/datums/station_traits/negative_traits.dm
+++ b/code/datums/station_traits/negative_traits.dm
@@ -92,7 +92,7 @@
name = "Random Event Modifier"
report_message = "A random event has been modified this shift! Someone forgot to set this!"
show_in_report = TRUE
- trait_flags = STATION_TRAIT_ABSTRACT
+ abstract_type = /datum/station_trait/random_event_weight_modifier
weight = 0
/// The path to the round_event_control that we modify.
diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm
index 52145534ab87..664372db8b69 100644
--- a/code/datums/station_traits/positive_traits.dm
+++ b/code/datums/station_traits/positive_traits.dm
@@ -122,3 +122,11 @@
/datum/station_trait/quick_shuttle/on_round_start()
. = ..()
SSshuttle.supply.callTime *= 0.5
+
+/datum/station_trait/shuttle_sale
+ name = "Shuttle Firesale"
+ report_message = "The Nanotrasen Emergency Dispatch team is celebrating a record number of shuttle calls in the recent quarter. Some of your emergency shuttle options have been discounted!"
+ trait_type = STATION_TRAIT_POSITIVE
+ weight = 4
+ trait_to_give = STATION_TRAIT_SHUTTLE_SALE
+ show_in_report = TRUE
diff --git a/code/datums/status_effects/buffs/buffs.dm b/code/datums/status_effects/buffs/buffs.dm
index 112529026ff8..d7576506374e 100644
--- a/code/datums/status_effects/buffs/buffs.dm
+++ b/code/datums/status_effects/buffs/buffs.dm
@@ -642,7 +642,7 @@
//adrenaline rush from combat damage
/atom/movable/screen/alert/status_effect/adrenaline
name = "Adrenaline rush"
- desc = "The sudden injuries you've recieved have put your body into fight-or-flight mode! Now's the time to look for an exit!"
+ desc = "The sudden injuries you've received have put your body into fight-or-flight mode! Now's the time to look for an exit!"
icon_state = "default"
/datum/status_effect/adrenaline
@@ -667,7 +667,7 @@
to_chat(owner, span_warning(printout))
REMOVE_TRAIT(owner, TRAIT_REDUCED_DAMAGE_SLOWDOWN, type)
return ..()
-
+
/datum/status_effect/diamondskin
id = "diamondskin"
duration = 20 SECONDS
@@ -693,8 +693,9 @@
var/mob/living/carbon/human/H = owner
H.physiology.pressure_mod /= 0.5
H.physiology.heat_mod /= 0.5
-
+
//holy light specific buffs
+
/datum/status_effect/holylight_antimagic
id = "holy antimagic"
duration = 2 MINUTES
@@ -743,7 +744,8 @@
/datum/status_effect/holylight_healboost/on_remove()
var/datum/component/heal_react/boost/holylight/healing = owner.GetComponent(/datum/component/heal_react/boost/holylight)
- healing?.RemoveComponent()
+ if(healing)
+ qdel(healing)
var/filter = owner.get_filter(HEALBOOST_FILTER)
if(filter)
animate(filter)
diff --git a/code/datums/status_effects/debuffs/confusion.dm b/code/datums/status_effects/debuffs/confusion.dm
index b4d4b29b65c3..ae669f5c7eb8 100644
--- a/code/datums/status_effects/debuffs/confusion.dm
+++ b/code/datums/status_effects/debuffs/confusion.dm
@@ -1,10 +1,5 @@
-/// The threshold in which all of our movements are fully randomized, in seconds.
-#define CONFUSION_FULL_THRESHOLD 40
-/// A multiplier applied on how much time is left (in seconds) that determines the chance of moving sideways randomly
-#define CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND 1.5
-/// A multiplier applied on how much time is left (in seconds) that determines the chance of moving diagonally randomly
-#define CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND 3
-
+/// If remaining duration is longer than this, allows for stronger confusion movement
+#define CONFUSION_STRONG_THRESHOLD 10 SECONDS
/// A status effect used for adding confusion to a mob.
/datum/status_effect/confusion
id = "confusion"
@@ -26,23 +21,16 @@
/datum/status_effect/confusion/proc/on_move(datum/source, direction)
SIGNAL_HANDLER
- // How much time is left in the duration, in seconds.
- var/time_left = (duration - world.time) / 10
+ var/time_left = duration - world.time
var/new_dir
- if(time_left > CONFUSION_FULL_THRESHOLD)
- new_dir = pick(GLOB.alldirs)
-
- else if(prob(time_left * CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND))
- new_dir = angle2dir(dir2angle(direction) + pick(90, -90))
-
- else if(prob(time_left * CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND))
+ if(prob(50))
new_dir = angle2dir(dir2angle(direction) + pick(45, -45))
+ else if(time_left > CONFUSION_STRONG_THRESHOLD && prob(50))
+ new_dir = angle2dir(dir2angle(direction) + pick(90, -90))
if(!isnull(new_dir))
source = get_step(owner, new_dir)
direction = new_dir
-#undef CONFUSION_FULL_THRESHOLD
-#undef CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND
-#undef CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND
+#undef CONFUSION_STRONG_THRESHOLD
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 13601c2c4123..6089eb84b98a 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -493,7 +493,6 @@
/datum/status_effect/cultghost/on_apply()
owner.see_invisible = SEE_INVISIBLE_OBSERVER
- owner.see_in_dark = 2
/datum/status_effect/cultghost/tick()
if(owner.reagents)
@@ -1302,6 +1301,45 @@
for(var/i in S.damage_coeff)
S.damage_coeff[i] /= power
+/datum/status_effect/exposed/harpooned
+ id = "harpooned"
+ duration = 2 SECONDS
+ ///damage multiplier
+ power = 1.3
+
+/datum/status_effect/exposed/harpooned/on_apply()
+ . = ..()
+ if(.)
+ owner.add_filter("exposed", 2, list("type" = "outline", "color" = COLOR_RED, "size" = 1))
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/H = owner
+ H.physiology.brute_mod *= power
+ H.physiology.burn_mod *= power
+ H.physiology.tox_mod *= power
+ H.physiology.oxy_mod *= power
+ H.physiology.clone_mod *= power
+ H.physiology.stamina_mod *= power
+ else if(isanimal(owner))
+ var/mob/living/simple_animal/S = owner
+ for(var/i in S.damage_coeff)
+ S.damage_coeff[i] *= power
+
+/datum/status_effect/exposed/harpooned/on_remove()
+ owner.remove_filter("exposed")
+ if(ishuman(owner))
+ var/mob/living/carbon/human/H = owner
+ H.physiology.brute_mod /= power
+ H.physiology.burn_mod /= power
+ H.physiology.tox_mod /= power
+ H.physiology.oxy_mod /= power
+ H.physiology.clone_mod /= power
+ H.physiology.stamina_mod /= power
+ else if(isanimal(owner))
+ var/mob/living/simple_animal/S = owner
+ for(var/i in S.damage_coeff)
+ S.damage_coeff[i] /= power
+
/datum/status_effect/knuckled
id = "knuckle_wound"
duration = 10 SECONDS
@@ -1481,7 +1519,7 @@
var/obj/effect/floating_blade/blade = new(get_turf(owner))
blades += blade
blade.orbit(owner, blade_orbit_radius)
- RegisterSignal(blade, COMSIG_PARENT_QDELETING, PROC_REF(remove_blade))
+ RegisterSignal(blade, COMSIG_QDELETING, PROC_REF(remove_blade))
playsound(get_turf(owner), 'sound/items/unsheath.ogg', 33, TRUE)
/// Signal proc for [COMSIG_HUMAN_CHECK_SHIELDS].
diff --git a/code/datums/status_effects/debuffs/drowsiness.dm b/code/datums/status_effects/debuffs/drowsiness.dm
index d2707049a8c2..c719573772e3 100644
--- a/code/datums/status_effects/debuffs/drowsiness.dm
+++ b/code/datums/status_effects/debuffs/drowsiness.dm
@@ -37,6 +37,6 @@
if(owner.resting && remove_duration(2 * initial(tick_interval)))
return
- owner.blur_eyes(4 SECONDS)
+ owner.adjust_eye_blur(4 SECONDS)
if(prob(5))
owner.AdjustSleeping(10 SECONDS)
diff --git a/code/datums/status_effects/debuffs/drunk.dm b/code/datums/status_effects/debuffs/drunk.dm
index d305e194edb5..86481fcae6a2 100644
--- a/code/datums/status_effects/debuffs/drunk.dm
+++ b/code/datums/status_effects/debuffs/drunk.dm
@@ -141,15 +141,11 @@
if(drunk_value > BALLMER_PEAK_WINDOWS_ME) // by this point you're into windows ME territory
owner.say(pick_list_replacements(VISTA_FILE, "ballmer_windows_me_msg"), forced = "ballmer")
- // There's always a 30% chance to gain some drunken slurring
- if(prob(30))
- owner.adjust_slurring(4 SECONDS)
-
// And drunk people will always lose jitteriness
owner.adjust_jitter(-6 SECONDS)
- // Over 11, we will constantly gain slurring up to 10 seconds of slurring.
- if(drunk_value >= 11)
+ // Over 11, Light drinkers will constantly gain slurring up to 10 seconds of slurring.
+ if(HAS_TRAIT(owner, TRAIT_LIGHT_DRINKER) & (drunk_value >= 11))
owner.adjust_slurring_up_to(2.4 SECONDS, 10 SECONDS)
// Over 41, we have a 30% chance to gain confusion, and we will always have 20 seconds of dizziness.
@@ -160,7 +156,7 @@
owner.set_dizzy_if_lower(20 SECONDS)
ADD_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk")
- // Over 51, we have a 3% chance to gain a lot of confusion and vomit, and we will always have 50 seconds of dizziness
+ // Over 51, we have a 3% chance to gain a lot of confusion and vomit, and we will always have 50 seconds of dizziness and normal drinkers will start to slur
if(drunk_value >= 51)
owner.set_dizzy_if_lower(50 SECONDS)
if(prob(3))
@@ -168,16 +164,20 @@
if(iscarbon(owner))
var/mob/living/carbon/carbon_owner = owner
carbon_owner.vomit() // Vomiting clears toxloss - consider this a blessing
+ if(!HAS_TRAIT(owner, TRAIT_ALCOHOL_TOLERANCE))
+ owner.adjust_slurring_up_to(2.4 SECONDS, 7 SECONDS)
// Over 71, we will constantly have blurry eyes
if(drunk_value >= 71)
- owner.blur_eyes(drunk_value * 2 - 140)
+ owner.adjust_eye_blur(drunk_value * 2 - 140)
- // Over 81, we will gain constant toxloss
+ // Over 81, we will gain constant toxloss and experienced drunks will now begin to slur
if(drunk_value >= 81)
owner.adjustToxLoss(1)
if(owner.stat == CONSCIOUS && prob(5))
to_chat(owner, span_warning("Maybe you should lie down for a bit..."))
+ if(HAS_TRAIT(owner, TRAIT_ALCOHOL_TOLERANCE))
+ owner.adjust_slurring_up_to(2.4 SECONDS, 4 SECONDS)
// Over 91, we gain even more toxloss, brain damage, and have a chance of dropping into a long sleep
if(drunk_value >= 91)
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index 55978eb6bbdb..96228968c117 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -1,4 +1,3 @@
-#define ALERT_FIRE "fire"
// If a mob has a higher threshold than this, the icon shown will be increased to the big fire icon.
#define MOB_BIG_FIRE_STACK_THRESHOLD 3
@@ -200,6 +199,7 @@
var/thermal_protection = victim.get_thermal_protection()
if(thermal_protection >= FIRE_IMMUNITY_MAX_TEMP_PROTECT && !no_protection)
+ SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "on_fire")
return
if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT && !no_protection)
@@ -207,7 +207,10 @@
return
victim.adjust_bodytemperature((BODYTEMP_HEATING_MAX + (stacks * 12)) * 0.5 * seconds_per_tick)
- SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
+ if(!HAS_TRAIT(victim, TRAIT_RESISTHEAT))
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
+ else
+ SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "on_fire")
/**
* Handles mob ignition, should be the only way to set on_fire to TRUE
diff --git a/code/datums/status_effects/debuffs/screen_blur.dm b/code/datums/status_effects/debuffs/screen_blur.dm
new file mode 100644
index 000000000000..abdd07d3cd59
--- /dev/null
+++ b/code/datums/status_effects/debuffs/screen_blur.dm
@@ -0,0 +1,52 @@
+/// This number is multiplied by the duration remaining (IN SECONDS, NOT DECISECONDS)
+/// of the eye blur status effect to determine the intensity of the blur on the user
+#define BLUR_DURATION_TO_INTENSITY 0.05
+
+/// Applies a blur to the user's screen, increasing in strength depending on duration remaining.
+/datum/status_effect/eye_blur
+ id = "eye_blur"
+ tick_interval = 1 SECONDS
+ alert_type = null
+ remove_on_fullheal = TRUE
+
+/datum/status_effect/eye_blur/on_creation(mob/living/new_owner, duration = 10 SECONDS)
+ src.duration = duration
+ return ..()
+
+/datum/status_effect/eye_blur/on_apply()
+ if(owner.mob_biotypes & (MOB_ROBOTIC|MOB_SPIRIT|MOB_SPECIAL))
+ return FALSE
+
+ // Refresh the blur when a client jumps into the mob, in case we get put on a clientless mob with no hud
+ RegisterSignal(owner, COMSIG_MOB_LOGIN, PROC_REF(update_blur))
+ // Apply initial blur
+ update_blur()
+ return TRUE
+
+/datum/status_effect/eye_blur/on_remove()
+ UnregisterSignal(owner, COMSIG_MOB_LOGIN)
+ if(!owner.hud_used)
+ return
+
+ var/atom/movable/plane_master_controller/game_plane_master_controller = owner.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
+ game_plane_master_controller.remove_filter("eye_blur")
+
+/datum/status_effect/eye_blur/tick(seconds_between_ticks)
+ // Blur lessens the closer we are to expiring, so we update per tick.
+ update_blur()
+
+/// Updates the blur of the owner of the status effect.
+/// Also a signal proc for [COMSIG_MOB_LOGIN], to trigger then when the mob gets a client.
+/datum/status_effect/eye_blur/proc/update_blur(datum/source)
+ SIGNAL_HANDLER
+
+ if(!owner.hud_used)
+ return
+
+ var/time_left_in_seconds = (duration - world.time) / (1 SECONDS)
+ var/amount_of_blur = clamp(time_left_in_seconds * BLUR_DURATION_TO_INTENSITY, 0.6, 3)
+
+ var/atom/movable/plane_master_controller/game_plane_master_controller = owner.hud_used.plane_master_controllers[PLANE_MASTERS_GAME]
+ game_plane_master_controller.add_filter("eye_blur", 1, gauss_blur_filter(amount_of_blur))
+
+#undef BLUR_DURATION_TO_INTENSITY
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 6c9e33ccef9d..f9844d1661af 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -165,7 +165,7 @@
/datum/quirk/night_vision/on_spawn()
var/mob/living/carbon/human/H = quirk_holder
var/obj/item/organ/eyes/eyes = H.getorgan(/obj/item/organ/eyes)
- if(!eyes || eyes.lighting_alpha)
+ if(!eyes || eyes.lighting_cutoff)
return
eyes.Insert(H) //refresh their eyesight and vision
diff --git a/code/datums/view.dm b/code/datums/view.dm
index 7279df7cf8a2..bd8e038abcb3 100644
--- a/code/datums/view.dm
+++ b/code/datums/view.dm
@@ -1,10 +1,23 @@
//This is intended to be a full wrapper. DO NOT directly modify its values
///Container for client viewsize
/datum/viewData
+ /// Width offset to apply to the default view string if we're not supressed for some reason
var/width = 0
+ /// Height offset to apply to the default view string, see above
var/height = 0
+ /// This client's current "default" view, in the format "WidthxHeight"
+ /// We add/remove from this when we want to change their window size
var/default = ""
+ /// This client's current zoom level, if it's not being supressed
+ /// If it's 0, we autoscale to the size of the window. Otherwise it's treated as the ratio between
+ /// the pixels on the map and output pixels. Only looks proper nice in increments of whole numbers (iirc)
+ /// Stored here so other parts of the code have a non blocking way of getting a user's functional zoom
+ var/zoom = 0
+ /// If the view is currently being supressed by some other "monitor"
+ /// For when you want to own the client's eye without fucking with their viewport
+ /// Doesn't make sense for a binocoler to effect your view in a camera console
var/is_suppressed = FALSE
+ /// The client that owns this view packet
var/client/chief = null
/datum/viewData/New(client/owner, view_string)
@@ -26,9 +39,12 @@
/datum/viewData/proc/assertFormat()//T-Pose
winset(chief, "mapwindow.map", "zoom=0")
+ zoom = 0
/datum/viewData/proc/resetFormat()//Cuck
- winset(chief, "mapwindow.map", "zoom=[chief.prefs.read_preference(/datum/preference/numeric/pixel_size)]")
+ zoom = chief?.prefs.read_preference(/datum/preference/numeric/pixel_size)
+ winset(chief, "mapwindow.map", "zoom=[zoom]")
+ //chief?.attempt_auto_fit_viewport() // If you change zoom mode, fit the viewport
/datum/viewData/proc/setZoomMode()
winset(chief, "mapwindow.map", "zoom-mode=[chief.prefs.read_preference(/datum/preference/choiced/scaling_method)]")
@@ -80,7 +96,7 @@
apply()
/datum/viewData/proc/apply()
- chief.change_view(getView())
+ chief?.change_view(getView())
afterViewChange()
/datum/viewData/proc/supress()
diff --git a/code/datums/visual_data.dm b/code/datums/visual_data.dm
new file mode 100644
index 000000000000..e6c89544a333
--- /dev/null
+++ b/code/datums/visual_data.dm
@@ -0,0 +1,151 @@
+// Allows for linking one mob's view to another
+// Exists to make debugging stuff on live easier, please do not build gameplay around this it's not stable
+// Mostly because we don't have setters for everything (like ui elements IE: client.screen)
+
+// DEBUG ONLY, THIS IS N O T STABLE ENOUGH FOR PLAYERS
+// Should potentially support images, might be too hard tho since there's no default "refresh" tool
+
+// Convenience datum, not for use outside of this ui
+/datum/visual_data
+ /// Sight flags
+ var/sight = NONE
+ /// see_invisible values
+ var/see_invis
+ /// see_in_dark values
+ var/see_dark
+ /// What the client is seeing "out of", client.eye
+ var/datum/weakref/client_eye
+ /// Weakref to the mob we're mirroring off
+ var/datum/weakref/mirroring_off_ref
+
+ var/do_updates = FALSE
+
+ // Note: we do not attempt to mirror all of screen, instead confining ourselves to mirroring
+ // Plane master and parralax stuff and such
+ // Again, this isn't stable
+
+/datum/visual_data/proc/shadow(mob/mirror_off)
+ do_updates = FALSE
+ mirroring_off_ref = WEAKREF(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_SIGHT_CHANGE, PROC_REF(sight_changed))
+ sight_changed(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_SEE_INVIS_CHANGE, PROC_REF(invis_changed))
+ invis_changed(mirror_off)
+ RegisterSignal(mirror_off, COMSIG_MOB_LOGIN, PROC_REF(on_login))
+ RegisterSignal(mirror_off, COMSIG_MOB_LOGOUT, PROC_REF(on_logout))
+ if(mirror_off.client)
+ on_login(mirror_off)
+ do_updates = TRUE
+
+/datum/visual_data/proc/paint_onto(mob/paint_to)
+ // Note: we explicitly do NOT use setters here, since it would break the behavior
+ paint_to.sight = sight
+ paint_to.see_invisible = see_invis
+ paint_to.see_in_dark = see_dark
+ if(paint_to.client)
+ var/atom/eye = client_eye?.resolve()
+ if(eye)
+ paint_to.client.eye = eye
+ // This is hacky I know, I don't have a way to update just
+ /// Plane masters. I'm sorry
+ var/mob/mirroring_off = mirroring_off_ref?.resolve()
+ if(mirroring_off?.client && paint_to != mirroring_off)
+ paint_to.client.screen = mirroring_off.client.screen
+
+/datum/visual_data/proc/on_update()
+ return
+
+/datum/visual_data/proc/sight_changed(mob/source)
+ SIGNAL_HANDLER
+ sight = source.sight
+ on_update()
+
+/datum/visual_data/proc/invis_changed(mob/source)
+ SIGNAL_HANDLER
+ see_invis = source.see_invisible
+ on_update()
+
+/datum/visual_data/proc/on_login(mob/source)
+ SIGNAL_HANDLER
+ // visual data can be created off login, so conflicts here are inevitable
+ // Best to just override
+ RegisterSignal(source.client, COMSIG_CLIENT_SET_EYE, PROC_REF(eye_change), override = TRUE)
+ set_eye(source.client.eye)
+
+/datum/visual_data/proc/on_logout(mob/source)
+ SIGNAL_HANDLER
+ // Canon here because it'll be gone come the logout signal
+ UnregisterSignal(source.canon_client, COMSIG_CLIENT_SET_EYE)
+ // We do NOT unset the eye, because it's still valid even if the mob ain't logged in
+
+/datum/visual_data/proc/eye_change(client/source)
+ SIGNAL_HANDLER
+ set_eye(source.eye)
+
+/datum/visual_data/proc/set_eye(atom/new_eye)
+ var/atom/old_eye = client_eye?.resolve()
+ if(old_eye)
+ UnregisterSignal(old_eye, COMSIG_QDELETING)
+ if(new_eye)
+ // Need to update any party's client.eyes
+ RegisterSignal(new_eye, COMSIG_QDELETING, PROC_REF(eye_deleted))
+ client_eye = WEAKREF(new_eye)
+ on_update()
+
+/datum/visual_data/proc/eye_deleted(datum/source)
+ SIGNAL_HANDLER
+ set_eye(null)
+
+/// Tracks but does not relay updates to someone's visual data
+/// Accepts a second visual data datum to use as a source of truth for the mob's values
+/datum/visual_data/tracking
+ /// Weakref to the visual data datum to reset our mob to
+ var/datum/weakref/default_to_ref
+
+/datum/visual_data/tracking/Destroy()
+ var/mob/our_lad = mirroring_off_ref?.resolve()
+ if(our_lad)
+ // Reset our mob to his proper visuals
+ paint_onto(our_lad)
+ return ..()
+
+/datum/visual_data/tracking/proc/set_truth(datum/visual_data/truth)
+ default_to_ref = WEAKREF(truth)
+ on_update()
+
+/datum/visual_data/tracking/paint_onto(mob/paint_onto)
+ . = ..()
+ // Rebuild the passed in mob's screen, since we can't track it currently
+ paint_onto.hud_used?.show_hud(paint_onto.hud_used.hud_version)
+
+/datum/visual_data/tracking/on_update()
+ var/mob/updated = mirroring_off_ref?.resolve()
+ var/datum/visual_data/mirror = default_to_ref?.resolve()
+ if(!updated || !mirror)
+ return
+ mirror.paint_onto(updated)
+
+/// Tracks and updates another mob with our mob's visual data
+/datum/visual_data/mirroring
+ /// Weakref to what mob, if any, we should mirror our changes onto
+ var/datum/weakref/mirror_onto_ref
+
+/datum/visual_data/mirroring/proc/set_mirror_target(mob/target)
+ var/mob/old_target = mirror_onto_ref?.resolve()
+ if(old_target)
+ UnregisterSignal(old_target, COMSIG_MOB_HUD_REFRESHED)
+ mirror_onto_ref = WEAKREF(target)
+ if(target)
+ RegisterSignal(target, COMSIG_MOB_HUD_REFRESHED, PROC_REF(push_ontod_hud_refreshed))
+
+/datum/visual_data/mirroring/proc/push_ontod_hud_refreshed(mob/source)
+ SIGNAL_HANDLER
+ // Our mob refreshed its hud, so we're gonna reset it to our screen
+ // I hate that I don't have a signal for this, hhhh
+ paint_onto(source)
+
+/datum/visual_data/mirroring/on_update()
+ var/mob/draw_onto = mirror_onto_ref?.resolve()
+ if(!draw_onto)
+ return
+ paint_onto(draw_onto)
diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm
index 27bd5d943306..dedc0d8eff92 100644
--- a/code/datums/weakrefs.dm
+++ b/code/datums/weakrefs.dm
@@ -87,7 +87,7 @@
* adding it to an atom's contents or vis_contents, giving it a key (if it's a mob), attaching it to an atom (if it's an image),
* or assigning it to a datum or list referenced somewhere other than a temporary value.
*
- * Unless you're resolving a weakref to a datum in a COMSIG_PARENT_QDELETING signal handler registered on that very same datum,
+ * Unless you're resolving a weakref to a datum in a COMSIG_QDELETING signal handler registered on that very same datum,
* just use resolve instead.
*/
/datum/weakref/proc/hard_resolve()
diff --git a/code/datums/weather/weather.dm b/code/datums/weather/weather.dm
index 1a8d9800b424..c1f0bbbd3fa7 100644
--- a/code/datums/weather/weather.dm
+++ b/code/datums/weather/weather.dm
@@ -1,54 +1,99 @@
//The effects of weather occur across an entire z-level. For instance, lavaland has periodic ash storms that scorch most unprotected creatures.
/datum/weather
+ /// name of weather
var/name = "space wind"
+ /// description of weather
var/desc = "Heavy gusts of wind blanket the area, periodically knocking down anyone caught in the open."
- var/telegraph_message = span_warning("The wind begins to pick up.") //The message displayed in chat to foreshadow the weather's beginning
- var/telegraph_duration = 300 //In deciseconds, how long from the beginning of the telegraph until the weather begins
- var/telegraph_sound //The sound file played to everyone on an affected z-level
- var/telegraph_overlay //The overlay applied to all tiles on the z-level
+ /// The message displayed in chat to foreshadow the weather's beginning
+ var/telegraph_message = "The wind begins to pick up."
+ /// In deciseconds, how long from the beginning of the telegraph until the weather begins
+ var/telegraph_duration = 300
+ /// The sound file played to everyone on an affected z-level
+ var/telegraph_sound
+ /// The overlay applied to all tiles on the z-level
+ var/telegraph_overlay
- var/weather_message = span_userdanger("The wind begins to blow ferociously!") //Displayed in chat once the weather begins in earnest
- var/weather_duration = 1200 //In deciseconds, how long the weather lasts once it begins
- var/weather_duration_lower = 1200 //See above - this is the lowest possible duration
- var/weather_duration_upper = 1500 //See above - this is the highest possible duration
+ /// Displayed in chat once the weather begins in earnest
+ var/weather_message = "The wind begins to blow ferociously!"
+ /// In deciseconds, how long the weather lasts once it begins
+ var/weather_duration = 1200
+ /// See above - this is the lowest possible duration
+ var/weather_duration_lower = 1200
+ /// See above - this is the highest possible duration
+ var/weather_duration_upper = 1500
+ /// Looping sound while weather is occuring
var/weather_sound
+ /// Area overlay while the weather is occuring
var/weather_overlay
+ /// Color to apply to the area while weather is occuring
var/weather_color = null
- var/end_message = span_danger("The wind relents its assault.") //Displayed once the weather is over
- var/end_duration = 300 //In deciseconds, how long the "wind-down" graphic will appear before vanishing entirely
+ /// Displayed once the weather is over
+ var/end_message = "The wind relents its assault."
+ /// In deciseconds, how long the "wind-down" graphic will appear before vanishing entirely
+ var/end_duration = 300
+ /// Sound that plays while weather is ending
var/end_sound
+ /// Area overlay while weather is ending
var/end_overlay
- var/area_type = /area/space //Types of area to affect
- var/protect_indoors = FALSE // set to TRUE to protect indoor areas
- var/list/impacted_areas = list() //Areas to be affected by the weather, calculated when the weather begins
- var/list/protected_areas = list()//Areas that are protected and excluded from the affected areas.
- var/impacted_z_levels // The list of z-levels that this weather is actively affecting
+ /// Types of area to affect
+ var/area_type = /area/space
+ /// TRUE value protects areas with outdoors marked as false, regardless of area type
+ var/protect_indoors = FALSE
+ /// Areas to be affected by the weather, calculated when the weather begins
+ var/list/impacted_areas = list()
+ /// Areas that are protected and excluded from the affected areas.
+ var/list/protected_areas = list()
+ /// The list of z-levels that this weather is actively affecting
+ var/impacted_z_levels
- var/overlay_layer = AREA_LAYER //Since it's above everything else, this is the layer used by default. TURF_LAYER is below mobs and walls if you need to use that.
- var/overlay_plane = BLACKNESS_PLANE
- var/aesthetic = FALSE //If the weather has no purpose other than looks
- var/immunity_type = WEATHER_STORM //Used by mobs to prevent them from being affected by the weather
+ /// Since it's above everything else, this is the layer used by default. TURF_LAYER is below mobs and walls if you need to use that.
+ var/overlay_layer = AREA_LAYER
+ /// Plane for the overlay
+ var/overlay_plane = AREA_PLANE
+ /// If the weather has no purpose other than looks
+ var/aesthetic = FALSE
+ /// Used by mobs (or movables containing mobs, such as enviro bags) to prevent them from being affected by the weather.
+ var/immunity_type = WEATHER_STORM
+ /// If this bit of weather should also draw an overlay that's uneffected by lighting onto the area
+ /// Taken from weather_glow.dmi
+ var/use_glow = TRUE
+ /// List of all overlays to apply to our turfs
+ var/list/overlay_cache
- var/stage = END_STAGE //The stage of the weather, from 1-4
+ /// The stage of the weather, from 1-4
+ var/stage = END_STAGE
- // These are read by the weather subsystem and used to determine when and where to run the weather.
- var/probability = 0 // Weight amongst other eligible weather. If zero, will never happen randomly.
- var/target_trait = ZTRAIT_STATION // The z-level trait to affect when run randomly or when not overridden.
+ /// Weight amongst other eligible weather. If zero, will never happen randomly.
+ var/probability = 0
+ /// The z-level trait to affect when run randomly or when not overridden.
+ var/target_trait = ZTRAIT_STATION
+ /// Whether a barometer can predict when the weather will happen
var/barometer_predictable = FALSE
- var/next_hit_time = 0 //For barometers to know when the next storm will hit
+ /// For barometers to know when the next storm will hit
+ var/next_hit_time = 0
+ /// This causes the weather to only end if forced to
+ var/perpetual = FALSE
/datum/weather/New(z_levels)
..()
impacted_z_levels = z_levels
+/**
+ * Telegraphs the beginning of the weather on the impacted z levels
+ *
+ * Sends sounds and details to mobs in the area
+ * Calculates duration and hit areas, and makes a callback for the actual weather to start
+ *
+ */
/datum/weather/proc/telegraph()
if(stage == STARTUP_STAGE)
return
+ SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_TELEGRAPH(type))
stage = STARTUP_STAGE
var/list/affectareas = list()
for(var/V in get_areas(area_type))
@@ -62,53 +107,85 @@
if(A.z in impacted_z_levels)
impacted_areas |= A
weather_duration = rand(weather_duration_lower, weather_duration_upper)
- START_PROCESSING(SSweather, src)
+ SSweather.processing |= src
update_areas()
- for(var/M in GLOB.player_list)
- var/turf/mob_turf = get_turf(M)
- if(mob_turf && (mob_turf.z in impacted_z_levels))
- if(telegraph_message)
- to_chat(M, telegraph_message)
- if(telegraph_sound)
- SEND_SOUND(M, sound(telegraph_sound))
+ if(telegraph_duration)
+ send_alert(telegraph_message, telegraph_sound)
addtimer(CALLBACK(src, PROC_REF(start)), telegraph_duration)
+/**
+ * Starts the actual weather and effects from it
+ *
+ * Updates area overlays and sends sounds and messages to mobs to notify them
+ * Begins dealing effects from weather to mobs in the area
+ *
+ */
/datum/weather/proc/start()
if(stage >= MAIN_STAGE)
return
+ SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_START(type))
stage = MAIN_STAGE
update_areas()
- for(var/M in GLOB.player_list)
- var/turf/mob_turf = get_turf(M)
- if(mob_turf && (mob_turf.z in impacted_z_levels))
- if(weather_message)
- to_chat(M, weather_message)
- if(weather_sound)
- SEND_SOUND(M, sound(weather_sound))
- addtimer(CALLBACK(src, PROC_REF(wind_down)), weather_duration)
+ send_alert(weather_message, weather_sound)
+ if(!perpetual)
+ addtimer(CALLBACK(src, PROC_REF(wind_down)), weather_duration)
+ for(var/area/impacted_area as anything in impacted_areas)
+ SEND_SIGNAL(impacted_area, COMSIG_WEATHER_BEGAN_IN_AREA(type))
+/**
+ * Weather enters the winding down phase, stops effects
+ *
+ * Updates areas to be in the winding down phase
+ * Sends sounds and messages to mobs to notify them
+ *
+ */
/datum/weather/proc/wind_down()
if(stage >= WIND_DOWN_STAGE)
return
+ SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_WINDDOWN(type))
stage = WIND_DOWN_STAGE
update_areas()
- for(var/M in GLOB.player_list)
- var/turf/mob_turf = get_turf(M)
- if(mob_turf && (mob_turf.z in impacted_z_levels))
- if(end_message)
- to_chat(M, end_message)
- if(end_sound)
- SEND_SOUND(M, sound(end_sound))
+ send_alert(end_message, end_sound)
addtimer(CALLBACK(src, PROC_REF(end)), end_duration)
+/**
+ * Fully ends the weather
+ *
+ * Effects no longer occur and area overlays are removed
+ * Removes weather from processing completely
+ *
+ */
/datum/weather/proc/end()
if(stage == END_STAGE)
- return 1
+ return
+ SEND_GLOBAL_SIGNAL(COMSIG_WEATHER_END(type))
stage = END_STAGE
- STOP_PROCESSING_DUMB(SSweather, src)
+ SSweather.processing -= src
update_areas()
+ for(var/area/impacted_area as anything in impacted_areas)
+ SEND_SIGNAL(impacted_area, COMSIG_WEATHER_ENDED_IN_AREA(type))
+
+// handles sending all alerts
+/datum/weather/proc/send_alert(alert_msg, alert_sfx)
+ for(var/z_level in impacted_z_levels)
+ for(var/mob/player as anything in SSmobs.clients_by_zlevel[z_level])
+ if(!can_get_alert(player))
+ continue
+ if(alert_msg)
+ to_chat(player, alert_msg)
+ if(alert_sfx)
+ SEND_SOUND(player, sound(alert_sfx))
+
+// the checks for if a mob should receive alerts, returns TRUE if can
+/datum/weather/proc/can_get_alert(mob/player)
+ var/turf/mob_turf = get_turf(player)
+ return !isnull(mob_turf)
-/datum/weather/proc/can_weather_act(mob/living/mob_to_check) // Can this weather impact a mob?
+/**
+ * Returns TRUE if the living mob can be affected by the weather
+ *
+ */
+/datum/weather/proc/can_weather_act(mob/living/mob_to_check)
var/turf/mob_turf = get_turf(mob_to_check)
if(!mob_turf)
@@ -134,24 +211,50 @@
/datum/weather/proc/weather_act(mob/living/L) //What effect does this weather have on the hapless mob?
return
+/**
+ * Updates the overlays on impacted areas
+ *
+ */
/datum/weather/proc/update_areas()
- for(var/V in impacted_areas)
- var/area/N = V
- N.layer = overlay_layer
- N.plane = overlay_plane
- N.icon = 'icons/effects/weather_effects.dmi'
- N.color = weather_color
- switch(stage)
- if(STARTUP_STAGE)
- N.icon_state = telegraph_overlay
- if(MAIN_STAGE)
- N.icon_state = weather_overlay
- if(WIND_DOWN_STAGE)
- N.icon_state = end_overlay
- if(END_STAGE)
- N.color = null
- N.icon_state = ""
- N.icon = 'icons/turf/areas.dmi'
- N.layer = initial(N.layer)
- N.plane = initial(N.plane)
- N.set_opacity(FALSE)
+ var/list/new_overlay_cache = generate_overlay_cache()
+ for(var/area/impacted as anything in impacted_areas)
+ if(length(overlay_cache))
+ impacted.overlays -= overlay_cache
+ if(length(new_overlay_cache))
+ impacted.overlays += new_overlay_cache
+
+ overlay_cache = new_overlay_cache
+
+/// Returns a list of visual offset -> overlays to use
+/datum/weather/proc/generate_overlay_cache()
+ // We're ending, so no overlays at all
+ if(stage == END_STAGE)
+ return list()
+
+ var/weather_state = ""
+ switch(stage)
+ if(STARTUP_STAGE)
+ weather_state = telegraph_overlay
+ if(MAIN_STAGE)
+ weather_state = weather_overlay
+ if(WIND_DOWN_STAGE)
+ weather_state = end_overlay
+
+ // Use all possible offsets
+ // Yes this is a bit annoying, but it's too slow to calculate and store these from turfs, and it shouldn't (I hope) look weird
+ var/list/gen_overlay_cache = list()
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ // Note: what we do here is effectively apply two overlays to each area, for every unique multiz layer they inhabit
+ // One is the base, which will be masked by lighting. the other is "glowing", and provides a nice contrast
+ // This method of applying one overlay per z layer has some minor downsides, in that it could lead to improperly doubled effects if some have alpha
+ // I prefer it to creating 2 extra plane masters however, so it's a cost I'm willing to pay
+ // LU
+ var/mutable_appearance/glow_overlay = mutable_appearance('icons/effects/glow_weather.dmi', weather_state, overlay_layer, null, ABOVE_LIGHTING_PLANE, 100, offset_const = offset)
+ glow_overlay.color = weather_color
+ gen_overlay_cache += glow_overlay
+
+ var/mutable_appearance/weather_overlay = mutable_appearance('icons/effects/weather_effects.dmi', weather_state, overlay_layer, plane = overlay_plane, offset_const = offset)
+ weather_overlay.color = weather_color
+ gen_overlay_cache += weather_overlay
+
+ return gen_overlay_cache
diff --git a/code/datums/wires/mass_driver.dm b/code/datums/wires/mass_driver.dm
new file mode 100644
index 000000000000..fd6f69707021
--- /dev/null
+++ b/code/datums/wires/mass_driver.dm
@@ -0,0 +1,25 @@
+/datum/wires/mass_driver
+ holder_type = /obj/machinery/mass_driver
+ proper_name = "Mass Driver"
+
+/datum/wires/mass_driver/New(atom/holder)
+ wires = list(WIRE_LAUNCH, WIRE_SAFETY)
+ ..()
+
+/datum/wires/mass_driver/on_pulse(wire)
+ var/obj/machinery/mass_driver/the_mass_driver = holder
+ switch(wire)
+ if(WIRE_LAUNCH)
+ the_mass_driver.drive()
+ holder.visible_message(span_notice("The drive mechanism activates."))
+ if(WIRE_SAFETY)
+ the_mass_driver.power = 3
+ holder.visible_message(span_notice("You hear a worrying whirring noise emitting from the mass driver."))
+
+/datum/wires/mass_driver/on_cut(wire, mend, source)
+ var/obj/machinery/mass_driver/the_mass_driver = holder
+ switch(wire)
+ if(WIRE_SAFETY)
+ if(the_mass_driver.power > 1)
+ the_mass_driver.power = 1
+ holder.visible_message(span_notice("The whirring noise emitting from the mass driver stops."))
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 6038f920bac0..8be8d4b4b4ef 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -132,7 +132,7 @@
for(var/mob/dead/observer/O in GLOB.player_list)
if(O.key == expected_key)
if(O.client)
- new /atom/movable/screen/splash(O.client, TRUE)
+ new /atom/movable/screen/splash(null, O.client, TRUE)
break
/datum/world_topic/adminmsg
diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm
index f9e5fb9181f0..c24caf4b093f 100644
--- a/code/game/alternate_appearance.dm
+++ b/code/game/alternate_appearance.dm
@@ -73,6 +73,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
transfer_overlays = options & AA_MATCH_TARGET_OVERLAYS
image = I
target = I.loc
+ LAZYADD(target.update_on_z, image)
if(transfer_overlays)
I.copy_overlays(target)
@@ -89,6 +90,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/Destroy()
. = ..()
+ LAZYREMOVE(target.update_on_z, image)
QDEL_NULL(image)
target = null
if(ghost_appearance)
diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index 30a74c6a4956..b2d0e33cd69b 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -22,7 +22,10 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
icon_state = "space"
requires_power = TRUE
always_unpowered = TRUE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = TRUE
+
+ base_lighting_alpha = 0
+ base_lighting_color = COLOR_STARLIGHT
power_light = FALSE
power_equip = FALSE
power_environ = FALSE
@@ -31,25 +34,28 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
blob_allowed = FALSE //Eating up space doesn't count for victory as a blob.
ambience_index = null
ambient_music_index = AMBIENCE_SPACE
+ flags_1 = CAN_BE_DIRTY_1
ambient_buzz = null
sound_environment = SOUND_AREA_SPACE
/area/space/nearstation
icon_state = "space_near"
- dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT
/area/start
name = "start area"
icon_state = "start"
requires_power = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
has_gravity = STANDARD_GRAVITY
+ static_lighting = FALSE
+ base_lighting_alpha = 255
ambience_index = null
ambient_buzz = null
/area/testroom
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
+ static_lighting = FALSE
+ base_lighting_alpha = 255
name = "Test Room"
icon_state = "test_room"
@@ -67,7 +73,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
sound_environment = SOUND_AREA_ASTEROID
/area/asteroid/nearstation
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
ambience_index = AMBIENCE_RUINS
always_unpowered = FALSE
requires_power = TRUE
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index afab00c7f397..6f7c4e58b6ad 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -4,13 +4,12 @@
* A grouping of tiles into a logical space, mostly used by map editors
*/
/area
- level = null
name = "Space"
icon = 'icons/turf/areas.dmi'
icon_state = "unknown"
layer = AREA_LAYER
//Keeping this on the default plane, GAME_PLANE, will make area overlays fail to render on FLOOR_PLANE.
- plane = BLACKNESS_PLANE
+ plane = AREA_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
invisibility = INVISIBILITY_LIGHTING
@@ -115,21 +114,24 @@
var/list/cameras
var/list/firealarms
var/list/airalarms
+
+ ///Typepath to limit the areas (subtypes included) that atoms in this area can smooth with. Used for shuttles.
+ var/area/area_limited_icon_smoothing
+
var/firedoors_last_closed_on = 0
/// Can the Xenobio management console transverse this area by default?
var/xenobiology_compatible = FALSE
- /// typecache to limit the areas that atoms in this area can smooth with, used for shuttles IIRC
- var/list/canSmoothWithAreas
var/minimap_color = null // if null, chooses random one
/// Wire assignment for airlocks in this area
var/airlock_wires = /datum/wires/airlock
+ ///This datum, if set, allows terrain generation behavior to be ran on Initialize()
+ var/datum/map_generator/map_generator
+
var/turf/teleport_anchors = list() //ist of tiles we prefer to teleport to. this is for areas that are partially hazardous like for instance atmos_distro
- ///This datum, if set, allows terrain generation behavior to be ran on Initialize(mapload)
- var/datum/map_generator/map_generator
/// Whether the lights in this area aren't turned off when it's empty at roundstart
var/lights_always_start_on = FALSE
@@ -194,10 +196,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
*/
/area/Initialize(mapload)
icon_state = ""
- layer = AREA_LAYER
- uid = ++global_uid
map_name = name // Save the initial (the name set in the map) name of the area.
- canSmoothWithAreas = typecacheof(canSmoothWithAreas)
add_delta_areas()
@@ -214,22 +213,17 @@ GLOBAL_LIST_EMPTY(teleportlocs)
power_equip = TRUE
power_environ = TRUE
- if(dynamic_lighting == DYNAMIC_LIGHTING_FORCED)
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ if(static_lighting)
luminosity = 0
- else if(dynamic_lighting != DYNAMIC_LIGHTING_IFSTARLIGHT)
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
- if(dynamic_lighting == DYNAMIC_LIGHTING_IFSTARLIGHT)
- dynamic_lighting = CONFIG_GET(flag/starlight) ? DYNAMIC_LIGHTING_ENABLED : DYNAMIC_LIGHTING_DISABLED
. = ..()
- blend_mode = BLEND_MULTIPLY // Putting this in the constructor so that it stops the icons being screwed up in the map editor.
-
- if(!IS_DYNAMIC_LIGHTING(src))
- add_overlay(/obj/effect/fullbright)
+ if(!static_lighting)
+ blend_mode = BLEND_MULTIPLY
reg_in_areas_in_z()
+
+ update_base_lighting()
return INITIALIZE_HINT_LATELOAD
@@ -621,9 +615,10 @@ GLOBAL_LIST_EMPTY(teleportlocs)
* Updates the area icon and calls power change on all machinees in the area
*/
/area/proc/power_change()
+ SEND_SIGNAL(src, COMSIG_AREA_POWER_CHANGE)
for(var/obj/machinery/M in src) // for each machine in the area
M.power_change() // reverify power status (to update icons etc.)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/**
* Return the usage of power per channel
@@ -802,3 +797,26 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/// A hook so areas can modify the incoming args (of what??)
/area/proc/PlaceOnTopReact(list/new_baseturfs, turf/fake_turf_type, flags)
return flags
+
+/// Called when a living mob that spawned here, joining the round, receives the player client.
+/area/proc/on_joining_game(mob/living/boarder)
+ return
+
+/**
+ * Returns the name of an area, with the original name if the area name has been changed.
+ *
+ * If an area has not been renamed, returns the area name. If it has been modified (by blueprints or other means)
+ * returns the current name, as well as the initial value, in the format of [Current Location Name (Original Name)]
+ */
+
+/area/proc/get_original_area_name()
+ if(name == initial(name))
+ return name
+ return "[name] ([initial(name)])"
+
+/**
+ * A blank area subtype solely used by the golem area editor for the purpose of
+ * allowing golems to create new areas without suffering from the hazard_area debuffs.
+ */
+/area/golem
+ name = "Golem Territory"
diff --git a/code/game/area/areas/away_content.dm b/code/game/area/areas/away_content.dm
index 76347393408e..dfd315e40f17 100644
--- a/code/game/area/areas/away_content.dm
+++ b/code/game/area/areas/away_content.dm
@@ -16,21 +16,22 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
/area/awaymission/beach
name = "Beach"
icon_state = "away"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
requires_power = FALSE
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/errorroom
name = "Super Secret Room"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
has_gravity = STANDARD_GRAVITY
/area/awaymission/vr
name = "Virtual Reality"
icon_state = "awaycontent1"
requires_power = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ base_lighting_alpha = 255
var/pacifist = TRUE // if when you enter this zone, you become a pacifist or not
var/death = FALSE // if when you enter this zone, you die
@@ -50,11 +51,13 @@ Unused icons for new areas are "awaycontent1" ~ "awaycontent30"
has_gravity = FALSE
/area/awaymission/secret/fullbright
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
/area/awaymission/secret/powered
requires_power = FALSE
/area/awaymission/secret/powered/fullbright
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm
index b2b918760cf0..2c1b60b3bc3c 100644
--- a/code/game/area/areas/centcom.dm
+++ b/code/game/area/areas/centcom.dm
@@ -4,7 +4,7 @@
/area/centcom
name = "CentCom"
icon_state = "centcom"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
noteleport = TRUE
@@ -40,7 +40,9 @@
/area/centcom/supplypod
name = "Supplypod Facility"
icon_state = "supplypod"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+
+ base_lighting_alpha = 255
/area/centcom/supplypod/podStorage
name = "Supplypod Storage"
@@ -80,37 +82,34 @@
loading_id = "5"
//THUNDERDOME
-/area/tdome
+/area/centcom/tdome
name = "Thunderdome"
icon_state = "yellow"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
- requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
- flags_1 = NONE
+ static_lighting = FALSE
-/area/tdome/arena
+ base_lighting_alpha = 255
+
+/area/centcom/tdome/arena
name = "Thunderdome Arena"
icon_state = "thunder"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
-/area/tdome/arena_source
+/area/centcom/tdome/arena_source
name = "Thunderdome Arena Template"
icon_state = "thunder"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
-/area/tdome/tdome1
+/area/centcom/tdome/tdome1
name = "Thunderdome (Team 1)"
icon_state = "green"
-/area/tdome/tdome2
+/area/centcom/tdome/tdome2
name = "Thunderdome (Team 2)"
icon_state = "green"
-/area/tdome/tdomeadmin
+/area/centcom/tdome/tdomeadmin
name = "Thunderdome (Admin.)"
icon_state = "purple"
-/area/tdome/tdomeobserve
+/area/centcom/tdome/tdomeobserve
name = "Thunderdome (Observer.)"
icon_state = "purple"
@@ -118,26 +117,28 @@
//ENEMY
//Wizard
-/area/wizard_station
+/area/centcom/wizard_station
name = "Wizard's Den"
icon_state = "yellow"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
noteleport = TRUE
flags_1 = NONE
//Abductors
-/area/abductor_ship
+/area/centcom/abductor_ship
name = "Abductor Ship"
icon_state = "yellow"
requires_power = FALSE
+ static_lighting = FALSE
+ base_lighting_alpha = 255
noteleport = TRUE
has_gravity = STANDARD_GRAVITY
flags_1 = NONE
//Syndicates
-/area/syndicate_mothership
+/area/centcom/syndicate_mothership
name = "Syndicate Mothership"
icon_state = "syndie-ship"
requires_power = FALSE
@@ -147,18 +148,19 @@
flags_1 = NONE
ambience_index = AMBIENCE_DANGER
-/area/syndicate_mothership/control
+/area/centcom/syndicate_mothership/control
name = "Syndicate Control Room"
icon_state = "syndie-control"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
-/area/syndicate_mothership/elite_squad
+/area/centcom/syndicate_mothership/elite_squad
name = "Syndicate Elite Squad"
icon_state = "syndie-elite"
-/area/fabric_of_reality
+/area/centcom/fabric_of_reality
name = "Tear in the Fabric of Reality"
requires_power = FALSE
+ static_lighting = FALSE
+ base_lighting_alpha = 255
has_gravity = TRUE
noteleport = TRUE
blob_allowed = FALSE
@@ -166,51 +168,58 @@
//CAPTURE THE FLAG
-/area/ctf
+/area/centcom/ctf
name = "Capture the Flag"
icon_state = "yellow"
requires_power = FALSE
+ static_lighting = FALSE
+ base_lighting_alpha = 255
has_gravity = STANDARD_GRAVITY
-/area/ctf/control_room
+/area/centcom/ctf/control_room
name = "Control Room A"
-/area/ctf/control_room2
+/area/centcom/ctf/control_room2
name = "Control Room B"
-/area/ctf/central
+/area/centcom/ctf/central
name = "Central"
-/area/ctf/main_hall
+/area/centcom/ctf/main_hall
name = "Main Hall A"
-/area/ctf/main_hall2
+/area/centcom/ctf/main_hall2
name = "Main Hall B"
-/area/ctf/corridor
+/area/centcom/ctf/corridor
name = "Corridor A"
-/area/ctf/corridor2
+/area/centcom/ctf/corridor2
name = "Corridor B"
-/area/ctf/flag_room
+/area/centcom/ctf/flag_room
name = "Flag Room A"
-/area/ctf/flag_room2
+/area/centcom/ctf/flag_room2
name = "Flag Room B"
// REEBE
-/area/reebe
+/area/centcom/reebe
name = "Reebe"
icon_state = "yellow"
- requires_power = FALSE
has_gravity = STANDARD_GRAVITY
+ static_lighting = FALSE
+
+ base_lighting_alpha = 255
noteleport = TRUE
hidden = TRUE
ambience_index = AMBIENCE_REEBE
-/area/reebe/city_of_cogs
+/area/centcom/reebe/city_of_cogs
name = "City of Cogs"
icon_state = "purple"
+ static_lighting = FALSE
+
+ base_lighting_alpha = 255
hidden = FALSE
diff --git a/code/game/area/areas/holodeck.dm b/code/game/area/areas/holodeck.dm
index 45d4e0eb5e69..ac5d85ab89e3 100644
--- a/code/game/area/areas/holodeck.dm
+++ b/code/game/area/areas/holodeck.dm
@@ -1,12 +1,14 @@
/area/holodeck
name = "Holodeck"
icon_state = "Holodeck"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
- flags_1 = 0
+ static_lighting = FALSE
+
+ base_lighting_alpha = 255
+ flags_1 = NONE
hidden = TRUE
var/obj/machinery/computer/holodeck/linked
- var/restricted = 0 // if true, program goes on emag list
+ var/restricted = FALSE // if true, program goes on emag list
var/minimum_sec_level = SEC_LEVEL_GREEN //override this var if you want the program to be locked to a different alert-level (eg. SEC_LEVEL_BLUE, SEC_LEVEL_RED, SEC_LEVEL_DELTA)
/*
@@ -16,18 +18,18 @@
/area/holodeck/powered(chan)
if(!requires_power)
- return 1
+ return TRUE
if(always_unpowered)
- return 0
+ return FALSE
if(!linked)
- return 0
+ return FALSE
var/area/A = get_area(linked)
ASSERT(!istype(A, /area/holodeck))
return A.powered(chan)
/area/holodeck/usage(chan)
if(!linked)
- return 0
+ return FALSE
var/area/A = get_area(linked)
ASSERT(!istype(A, /area/holodeck))
return A.usage(chan)
@@ -41,7 +43,7 @@
/area/holodeck/use_power(amount, chan)
if(!linked)
- return 0
+ return FALSE
var/area/A = get_area(linked)
ASSERT(!istype(A, /area/holodeck))
return A.use_power(amount,chan)
diff --git a/code/game/area/areas/ruins/_ruins.dm b/code/game/area/areas/ruins/_ruins.dm
index 15ab97cd2afe..0d8223c0f4cb 100644
--- a/code/game/area/areas/ruins/_ruins.dm
+++ b/code/game/area/areas/ruins/_ruins.dm
@@ -5,8 +5,8 @@
icon_state = "away"
has_gravity = STANDARD_GRAVITY
hidden = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
ambience_index = AMBIENCE_RUINS
+ flags_1 = CAN_BE_DIRTY_1
mining_speed = TRUE
diff --git a/code/game/area/areas/ruins/lavaland.dm b/code/game/area/areas/ruins/lavaland.dm
index 3b2f2d7b73b9..cbdf0d12882c 100644
--- a/code/game/area/areas/ruins/lavaland.dm
+++ b/code/game/area/areas/ruins/lavaland.dm
@@ -69,7 +69,6 @@
/area/ruin/powered/kinggoat_arena //yogs start
name = "King Goat Arena"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
noteleport = TRUE
icon_state = "dk_yellow" //yogs end
diff --git a/code/game/area/areas/ruins/space.dm b/code/game/area/areas/ruins/space.dm
index 490f4112eb04..d5a48f162a25 100644
--- a/code/game/area/areas/ruins/space.dm
+++ b/code/game/area/areas/ruins/space.dm
@@ -16,7 +16,9 @@
icon_state = "space"
requires_power = TRUE
always_unpowered = TRUE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+
+ base_lighting_alpha = 255
has_gravity = FALSE
power_light = FALSE
power_equip = FALSE
@@ -503,7 +505,6 @@
name = "Syndicate Derelict Solar Array"
icon_state = "yellow"
requires_power = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_IFSTARLIGHT
/area/ruin/space/has_grav/syndiederelict/hydroponics
name = "Syndicate Derelict Hydroponics"
diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm
index 7d7bdffa26eb..37c511734e47 100644
--- a/code/game/area/areas/shuttles.dm
+++ b/code/game/area/areas/shuttles.dm
@@ -4,22 +4,21 @@
/area/shuttle
name = "Shuttle"
requires_power = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
+ lights_always_start_on = TRUE
has_gravity = STANDARD_GRAVITY
always_unpowered = FALSE
valid_territory = FALSE
icon_state = "shuttle"
// Loading the same shuttle map at a different time will produce distinct area instances.
unique = FALSE
- ///list of miners & their mining points from gems to be given once all exports are processed, used by supply shuttles
- var/list/gem_payout = list()
+ flags_1 = CAN_BE_DIRTY_1
+ area_limited_icon_smoothing = /area/shuttle
lighting_colour_tube = "#fff0dd"
lighting_colour_bulb = "#ffe1c1"
-/area/shuttle/Initialize(mapload)
- if(!canSmoothWithAreas)
- canSmoothWithAreas = type
- . = ..()
+ ///list of miners & their mining points from gems to be given once all exports are processed, used by supply shuttles
+ var/list/gem_payout = list()
/area/shuttle/PlaceOnTopReact(list/new_baseturfs, turf/fake_turf_type, flags)
. = ..()
@@ -36,7 +35,7 @@
name = "Syndicate Infiltrator"
blob_allowed = FALSE
ambience_index = AMBIENCE_DANGER
- canSmoothWithAreas = /area/shuttle/syndicate
+ area_limited_icon_smoothing = /area/shuttle/syndicate
/area/shuttle/syndicate/bridge
name = "Syndicate Infiltrator Control"
@@ -61,15 +60,12 @@
name = "Pirate Shuttle"
blob_allowed = FALSE
requires_power = TRUE
- canSmoothWithAreas = /area/shuttle/pirate
////////////////////////////Bounty Hunter Shuttles////////////////////////////
/area/shuttle/hunter
name = "Hunter Shuttle"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
blob_allowed = FALSE
- canSmoothWithAreas = /area/shuttle/hunter
////////////////////////////White Ship////////////////////////////
@@ -77,7 +73,7 @@
name = "Abandoned Ship"
blob_allowed = FALSE
requires_power = TRUE
- canSmoothWithAreas = /area/shuttle/abandoned
+ area_limited_icon_smoothing = /area/shuttle/abandoned
/area/shuttle/abandoned/bridge
name = "Abandoned Ship Bridge"
@@ -103,9 +99,10 @@
////////////////////////////Single-area shuttles////////////////////////////
/area/shuttle/transit
- name = "Hyperspace"
+ name = "Bluespace"
desc = "Weeeeee"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
/area/shuttle/custom
name = "Custom player shuttle"
@@ -118,17 +115,28 @@
name = "Arrival Shuttle"
unique = TRUE // SSjob refers to this area for latejoiners
+/area/shuttle/arrival/on_joining_game(mob/living/boarder)
+ if(SSshuttle.arrivals?.mode == SHUTTLE_CALL)
+ var/atom/movable/screen/splash/Spl = new(null, boarder.client, TRUE)
+ Spl.Fade(TRUE)
+ boarder.playsound_local(get_turf(boarder), 'sound/voice/ApproachingTG.ogg', 25)
+ boarder.update_parallax_teleport()
+
/area/shuttle/pod_1
name = "Escape Pod One"
+ area_flags = NONE
/area/shuttle/pod_2
name = "Escape Pod Two"
+ area_flags = NONE
/area/shuttle/pod_3
name = "Escape Pod Three"
+ area_flags = NONE
/area/shuttle/pod_4
name = "Escape Pod Four"
+ area_flags = NONE
/area/shuttle/mining
name = "Mining Shuttle"
@@ -149,6 +157,8 @@
/area/shuttle/escape
name = "Emergency Shuttle"
+ area_limited_icon_smoothing = /area/shuttle/escape
+ flags_1 = CAN_BE_DIRTY_1
/area/shuttle/escape/backup
name = "Backup Emergency Shuttle"
diff --git a/code/game/atoms.dm b/code/game/atom/_atom.dm
similarity index 65%
rename from code/game/atoms.dm
rename to code/game/atom/_atom.dm
index 191525de75e4..5c9686212f5b 100644
--- a/code/game/atoms.dm
+++ b/code/game/atom/_atom.dm
@@ -7,11 +7,14 @@
/atom
layer = TURF_LAYER
plane = GAME_PLANE
- var/level = 2
+ appearance_flags = TILE_BOUND|LONG_GLIDE
///If non-null, overrides a/an/some in all cases
var/article
+ /// How many tiles "up" this light is. 1 is typical, should only really change this if it's a floor light
+ var/light_height = LIGHTING_HEIGHT
+
///First atom flags var
var/flags_1 = NONE
///Intearaction flags
@@ -66,18 +69,18 @@
///Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list.
var/tmp/list/light_sources
- ///overlays that should remain on top and not normally removed when using cut_overlay functions, like c4.
- var/list/priority_overlays
- /// a very temporary list of overlays to remove
- var/list/remove_overlays
- /// a very temporary list of overlays to add
- var/list/add_overlays
-
///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
var/list/managed_vis_overlays
- ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc. Single items are stored on their own, not in a list.
- var/list/managed_overlays
+ /// Lazylist of all images (or atoms, I'm sorry) (hopefully attached to us) to update when we change z levels
+ /// You will need to manage adding/removing from this yourself, but I'll do the updating for you
+ var/list/image/update_on_z
+
+ /// Lazylist of all overlays attached to us to update when we change z levels
+ /// You will need to manage adding/removing from this yourself, but I'll do the updating for you
+ /// Oh and note, if order of addition is important this WILL break that. so mind yourself
+ var/list/image/update_overlays_on_z
+
///Proximity monitor associated with this atom
var/datum/proximity_monitor/proximity_monitor
///Cooldown tick timer for buckle messages
@@ -85,8 +88,6 @@
///Last fingerprints to touch this atom
var/fingerprintslast
- var/list/filter_data //For handling persistent filters
-
///Economy cost of item
var/custom_price
///Economy cost of item in premium vendor
@@ -119,108 +120,25 @@
///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy()
var/list/targeted_by
- var/atom/orbit_target //Reference to atom being orbited
-/**
- * Called when an atom is created in byond (built in engine proc)
- *
- * Not a lot happens here in SS13 code, as we offload most of the work to the
- * [Intialization](atom.html#proc/Initialize) proc, mostly we run the preloader
- * if the preloader is being used and then call InitAtom of which the ultimate
- * result is that the Intialize proc is called.
- *
- * We also generate a tag here if the DF_USE_TAG flag is set on the atom
- */
-/atom/New(loc, ...)
- //atom creation method that preloads variables at creation
- if(GLOB.use_preloader && (src.type == GLOB._preloader_path))//in case the instanciated atom is creating other atoms in New()
- world.preloader_load(src)
-
- if(datum_flags & DF_USE_TAG)
- GenerateTag()
-
- var/do_initialize = SSatoms.initialized
- if(do_initialize != INITIALIZATION_INSSATOMS)
- args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
- if(SSatoms.InitAtom(src, args))
- //we were deleted
- return
- SSdemo.mark_new(src)
+ ///Icon-smoothing behavior.
+ var/smoothing_flags = NONE
+ ///What directions this is currently smoothing with. IMPORTANT: This uses the smoothing direction flags as defined in icon_smoothing.dm, instead of the BYOND flags.
+ var/smoothing_junction = null //This starts as null for us to know when it's first set, but after that it will hold a 8-bit mask ranging from 0 to 255.
+ ///Smoothing variable
+ var/top_left_corner
+ ///Smoothing variable
+ var/top_right_corner
+ ///Smoothing variable
+ var/bottom_left_corner
+ ///Smoothing variable
+ var/bottom_right_corner
+ ///What smoothing groups does this atom belongs to, to match canSmoothWith. If null, nobody can smooth with it. Must be sorted.
+ var/list/smoothing_groups = null
+ ///List of smoothing groups this atom can smooth with. If this is null and atom is smooth, it smooths only with itself. Must be sorted.
+ var/list/canSmoothWith = null
-/**
- * The primary method that objects are setup in SS13 with
- *
- * we don't use New as we have better control over when this is called and we can choose
- * to delay calls or hook other logic in and so forth
- *
- * During roundstart map parsing, atoms are queued for intialization in the base atom/New(),
- * After the map has loaded, then Initalize is called on all atoms one by one. NB: this
- * is also true for loading map templates as well, so they don't Initalize until all objects
- * in the map file are parsed and present in the world
- *
- * If you're creating an object at any point after SSInit has run then this proc will be
- * immediately be called from New.
- *
- * mapload: This parameter is true if the atom being loaded is either being intialized during
- * the Atom subsystem intialization, or if the atom is being loaded from the map template.
- * If the item is being created at runtime any time after the Atom subsystem is intialized then
- * it's false.
- *
- * You must always call the parent of this proc, otherwise failures will occur as the item
- * will not be seen as initalized (this can lead to all sorts of strange behaviour, like
- * the item being completely unclickable)
- *
- * You must not sleep in this proc, or any subprocs
- *
- * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map
- * there are no other arguments
- *
- * Must return an [initialization hint](code/__DEFINES/subsystems.html) or a runtime will occur.
- *
- * Note: the following functions don't call the base for optimization and must copypasta handling:
- * * /turf/Initialize
- * * /turf/open/space/Initialize
- */
-/atom/proc/Initialize(mapload, ...)
- SHOULD_CALL_PARENT(TRUE)
- if(flags_1 & INITIALIZED_1)
- stack_trace("Warning: [src]([type]) initialized multiple times!")
- flags_1 |= INITIALIZED_1
-
- //atom color stuff
- if(color)
- add_atom_colour(color, FIXED_COLOUR_PRIORITY)
-
- if (light_system == STATIC_LIGHT && light_power && light_range)
- update_light()
-
- if (canSmoothWith)
- canSmoothWith = typelist("canSmoothWith", canSmoothWith)
-
- if(custom_materials && custom_materials.len)
- var/temp_list = list()
- for(var/i in custom_materials)
- var/datum/material/material = getmaterialref(i) || i
- temp_list[material] = custom_materials[material] //Get the proper instanced version
-
- custom_materials = null //Null the list to prepare for applying the materials properly
- set_custom_materials(temp_list)
-
- return INITIALIZE_HINT_NORMAL
+ var/atom/orbit_target //Reference to atom being orbited
-/**
- * Late Intialization, for code that should run after all atoms have run Intialization
- *
- * To have your LateIntialize proc be called, your atoms [Initalization](atom.html#proc/Initialize)
- * proc must return the hint
- * [INITIALIZE_HINT_LATELOAD](code/__DEFINES/subsystems.html#define/INITIALIZE_HINT_LATELOAD)
- * otherwise you will never be called.
- *
- * useful for doing things like finding other machines on GLOB.machines because you can guarantee
- * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization
- * code has been run
- */
-/atom/proc/LateInitialize()
- return
/**
* Top level of the destroy chain for most atoms
@@ -246,7 +164,6 @@
// Checking length(overlays) before cutting has significant speed benefits
if (length(overlays))
overlays.Cut()
- LAZYCLEARLIST(priority_overlays)
for(var/i in targeted_by)
var/mob/M = i
@@ -257,6 +174,9 @@
QDEL_NULL(light)
if (length(light_sources))
light_sources.Cut()
+
+ if(smoothing_flags & SMOOTH_QUEUED)
+ SSicon_smooth.remove_from_queues(src)
return ..()
@@ -267,7 +187,7 @@
/atom/proc/CanPass(atom/movable/mover, turf/target)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
- if(mover.movement_type & UNSTOPPABLE)
+ if(mover.movement_type & PHASING)
return TRUE
. = CanAllowThrough(mover, target)
// This is cheaper than calling the proc every time since most things dont override CanPassThrough
@@ -296,7 +216,7 @@
return FALSE
if(is_reserved_level(T.z))
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
if(M.launch_status == ENDGAME_TRANSIT)
for(var/place in M.shuttle_areas)
@@ -312,7 +232,7 @@
return TRUE
//Check for centcom shuttles
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
if(M.launch_status == ENDGAME_LAUNCHED)
for(var/place in M.shuttle_areas)
@@ -335,7 +255,7 @@
if(!is_centcom_level(T.z))//if not, don't bother
return FALSE
- if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod))
+ if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/centcom/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod))
return TRUE
return FALSE
@@ -375,10 +295,6 @@
else
M.forceMove(src)
-///Hook for multiz???
-/atom/proc/update_multiz(prune_on_fail = FALSE)
- return FALSE
-
///Take air from the passed in gas mixture datum
/atom/proc/assume_air(datum/gas_mixture/giver)
return null
@@ -412,14 +328,14 @@
/atom/proc/return_analyzable_air()
return null
-
-///Return the air if we can analyze it
///Check if this atoms eye is still alive (probably)
/atom/proc/check_eye(mob/user)
+ SIGNAL_HANDLER
return
-/atom/proc/Bumped(atom/movable/AM)
+/atom/proc/Bumped(atom/movable/bumped_atom)
set waitfor = FALSE
+ SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, bumped_atom)
/// Convenience proc to see if a container is open for chemistry handling
/atom/proc/is_open_container()
@@ -523,7 +439,7 @@
* Default behaviour is to get the name and icon of the object and it's reagents where
* the TRANSPARENT flag is set on the reagents holder
*
- * Produces a signal COMSIG_PARENT_EXAMINE
+ * Produces a signal COMSIG_ATOM_EXAMINE
*/
/atom/proc/examine(mob/user)
var/examine_string = get_examine_string(user, thats = TRUE)
@@ -560,7 +476,7 @@
else
. += span_danger("It's empty.")
- SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
+ SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE, user, .)
/**
* Shows any and all examine text related to any status effects the user has.
@@ -586,89 +502,23 @@
* This is where you can put extra information on something that may be superfluous or not important in critical gameplay
* moments, while allowing people to manually double-examine to take a closer look
*
- * Produces a signal [COMSIG_PARENT_EXAMINE_MORE]
+ * Produces a signal [COMSIG_ATOM_EXAMINE_MORE]
*/
/atom/proc/examine_more(mob/user)
. = list()
- SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .)
+ SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE_MORE, user, .)
if(!LAZYLEN(.)) // lol ..length
return FALSE
/**
- * Updates the appearence of the icon
+ * An atom we are buckled or is contained within us has tried to move
*
- * Mostly delegates to update_name, update_desc, and update_icon
- *
- * Arguments:
- * - updates: A set of bitflags dictating what should be updated. Defaults to [ALL]
+ * Default behaviour is to send a warning that the user can't move while buckled as long
+ * as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks)
*/
-/atom/proc/update_appearance(updates=ALL)
- SHOULD_NOT_SLEEP(TRUE)
- SHOULD_CALL_PARENT(TRUE)
-
- . = NONE
- updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_APPEARANCE, updates)
- if(updates & UPDATE_NAME)
- . |= update_name(updates)
- if(updates & UPDATE_DESC)
- . |= update_desc(updates)
- if(updates & UPDATE_ICON)
- . |= update_icon(updates)
-
-/// Updates the name of the atom
-/atom/proc/update_name(updates=ALL)
- SHOULD_CALL_PARENT(TRUE)
- return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_NAME, updates)
-
-/// Updates the description of the atom
-/atom/proc/update_desc(updates=ALL)
- SHOULD_CALL_PARENT(TRUE)
- return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_DESC, updates)
-
-/// Updates the icon of the atom
-/atom/proc/update_icon(updates=ALL)
- SIGNAL_HANDLER
- SHOULD_CALL_PARENT(TRUE)
-
- . = NONE
- updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates)
- if(updates & UPDATE_ICON_STATE)
- update_icon_state()
- . |= UPDATE_ICON_STATE
-
- if(updates & UPDATE_OVERLAYS)
- if(LAZYLEN(managed_vis_overlays))
- SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
-
- var/list/new_overlays = update_overlays()
- if(managed_overlays)
- cut_overlay(managed_overlays)
- managed_overlays = null
- if(length(new_overlays))
- managed_overlays = new_overlays
- add_overlay(new_overlays)
- . |= UPDATE_OVERLAYS
-
- . |= SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates, .)
-
-/// Updates the icon state of the atom
-/atom/proc/update_icon_state()
- SHOULD_CALL_PARENT(TRUE)
- return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE)
-
-/// Updates the overlays of the atom
-/atom/proc/update_overlays()
- SHOULD_CALL_PARENT(TRUE)
- . = list()
- SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
-
-/**
- * An atom we are buckled or is contained within us has tried to move
- *
- * Default behaviour is to send a warning that the user can't move while buckled as long
- * as the buckle_message_cooldown has expired (50 ticks)
- */
-/atom/proc/relaymove(mob/user)
+/atom/proc/relaymove(mob/living/user, direction)
+ if(SEND_SIGNAL(src, COMSIG_ATOM_RELAYMOVE, user, direction) & COMSIG_BLOCK_RELAYMOVE)
+ return
if(buckle_message_cooldown <= world.time)
buckle_message_cooldown = world.time + 50
to_chat(user, span_warning("You can't move while buckled to [src]!"))
@@ -711,18 +561,19 @@
* deleted shortly after hitting something (during explosions or other massive events that
* throw lots of items around - singularity being a notable example)
*/
-/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
- addtimer(CALLBACK(src, PROC_REF(hitby_react), AM), 2)
+/atom/proc/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ SEND_SIGNAL(src, COMSIG_ATOM_HITBY, hitting_atom, skipcatch, hitpush, blocked, throwingdatum)
+ if(density && !has_gravity(hitting_atom)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
+ addtimer(CALLBACK(src, PROC_REF(hitby_react), hitting_atom), 2)
/**
* We have have actually hit the passed in atom
*
* Default behaviour is to move back from the item that hit us
*/
-/atom/proc/hitby_react(atom/movable/AM)
- if(AM && isturf(AM.loc))
- step(AM, turn(AM.dir, 180))
+/atom/proc/hitby_react(atom/movable/harmed_atom)
+ if(harmed_atom && isturf(harmed_atom.loc))
+ step(harmed_atom, REVERSE_DIR(harmed_atom.dir))
///Handle the atom being slipped over
/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube, stun, force_drop)
@@ -732,11 +583,11 @@
/mob/living/proc/get_blood_dna_list()
if(get_blood_id() != /datum/reagent/blood)
return
- return list("ANIMAL DNA" = "Y-")
+ return list("ANIMAL DNA" = get_blood_type("Y-"))
///Get the mobs dna list
/mob/living/carbon/get_blood_dna_list()
- if(!(get_blood_id() in list(/datum/reagent/blood, /datum/reagent/toxin/acid))) //polysmorphs have DNA located in literal acid, don't ask me why
+ if(!(get_blood_id() in list(/datum/reagent/blood, /datum/reagent/toxin/acid, /datum/reagent/consumable/liquidelectricity))) //polysmorphs have DNA located in literal acid, don't ask me why
return
var/list/blood_dna = list()
if(dna)
@@ -746,15 +597,15 @@
return blood_dna
/mob/living/carbon/alien/get_blood_dna_list()
- return list("UNKNOWN DNA" = "X*")
+ return list("UNKNOWN DNA" = get_blood_type("X"))
/mob/living/silicon/get_blood_dna_list()
- return list("MOTOR OIL" = "SAE 5W-30") //just a little flavor text.
+ return list("SYNTHETIC COOLANT" = get_blood_type("Coolant"))
///to add a mob's dna info into an object's blood_dna list.
-/atom/proc/transfer_mob_blood_dna(mob/living/L)
+/atom/proc/transfer_mob_blood_dna(mob/living/injected_mob)
// Returns 0 if we have that blood already
- var/new_blood_dna = L.get_blood_dna_list()
+ var/new_blood_dna = injected_mob.get_blood_dna_list()
if(!new_blood_dna)
return FALSE
var/old_length = blood_DNA_length()
@@ -978,8 +829,14 @@
*/
/atom/proc/setDir(newdir)
SHOULD_CALL_PARENT(TRUE)
+ if (SEND_SIGNAL(src, COMSIG_ATOM_PRE_DIR_CHANGE, dir, newdir) & COMPONENT_ATOM_BLOCK_DIR_CHANGE)
+ newdir = dir
+ return
SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
dir = newdir
+ SEND_SIGNAL(src, COMSIG_ATOM_POST_DIR_CHANGE, dir, newdir)
+ if(smoothing_flags & SMOOTH_BORDER_OBJECT)
+ QUEUE_SMOOTH_NEIGHBORS(src)
/**
* Used to change the pixel shift of an atom
@@ -1287,287 +1144,26 @@
/atom/proc/return_temperature()
return
-/**
- * Tool behavior procedure. Redirects to tool-specific procs by default.
- *
- * You can override it to catch all tool interactions, for use in complex deconstruction procs.
- *
- * Must return parent proc ..() in the end if overridden
- */
-/atom/proc/tool_act(mob/living/user, obj/item/tool, tool_type)
- var/act_result
- var/signal_result
-
- signal_result = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(tool_type), user, tool)
- if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
- return TOOL_ACT_SIGNAL_BLOCKING
- if(QDELETED(tool))
- return TRUE
-
- switch(tool_type)
- if(TOOL_CROWBAR)
- act_result = crowbar_act(user, tool)
- if(TOOL_MULTITOOL)
- act_result = multitool_act(user, tool)
- if(TOOL_SCREWDRIVER)
- act_result = screwdriver_act(user, tool)
- if(TOOL_WRENCH)
- act_result = wrench_act(user, tool)
- if(TOOL_WIRECUTTER)
- act_result = wirecutter_act(user, tool)
- if(TOOL_WELDER)
- act_result = welder_act(user, tool)
- if(TOOL_ANALYZER)
- act_result = analyzer_act(user, tool)
- if(!act_result)
- return
-
- if(. && tool.toolspeed < 1) //nice tool bro
- SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "nice_tool", /datum/mood_event/nice_tool)
-
- // A tooltype_act has completed successfully
-// log_tool("[key_name(user)] used [tool] on [src] at [AREACOORD(src)]")
- SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_PRIMARY(tool_type), src)
- return TOOL_ACT_TOOLTYPE_SUCCESS
-
-
-//! Tool-specific behavior procs. To be overridden in subtypes.
-///
-
-///Crowbar act
-/atom/proc/crowbar_act(mob/living/user, obj/item/I)
- return
-
-///Multitool act
-/atom/proc/multitool_act(mob/living/user, obj/item/I)
- return
-
-///Check if the multitool has an item in it's data buffer
-/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE)
- if(!istype(I, /obj/item/multitool))
- if(user && !silent)
- to_chat(user, span_warning("[I] has no data buffer!"))
- return FALSE
- return TRUE
-
-///Screwdriver act
-/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
- SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), user, I)
-
-///Wrench act
-/atom/proc/wrench_act(mob/living/user, obj/item/I)
- return
-
-///Wirecutter act
-/atom/proc/wirecutter_act(mob/living/user, obj/item/I)
- return
-
-///Welder act
-/atom/proc/welder_act(mob/living/user, obj/item/I)
- return
-
-///Analyzer act
-/atom/proc/analyzer_act(mob/living/user, obj/item/I)
- return
-
-///Generate a tag for this atom
-/atom/proc/GenerateTag()
- return
+///Generate a tag for this /datum, if it implements one
+///Should be called as early as possible, best would be in New, to avoid weakref mistargets
+///Really just don't use this, you don't need it, global lists will do just fine MOST of the time
+///We really only use it for mobs to make id'ing people easier
+/datum/proc/GenerateTag()
+ datum_flags |= DF_USE_TAG
///Connect this atom to a shuttle
/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
return
-/// Generic logging helper
-/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE)
- if(!log_globally)
- return
-
- var/log_text = "[key_name(src)] [message] [loc_name(src)]"
- switch(message_type)
- if(LOG_ATTACK)
- log_attack(log_text)
- if(LOG_SAY)
- log_say(log_text)
- if(LOG_WHISPER)
- log_whisper(log_text)
- if(LOG_EMOTE)
- log_emote(log_text)
- if(LOG_DSAY)
- log_dsay(log_text)
- if(LOG_PDA)
- log_pda(log_text)
- if(LOG_CHAT)
- log_chat(log_text)
- if(LOG_COMMENT)
- log_comment(log_text)
- if(LOG_TELECOMMS)
- log_telecomms(log_text)
- if(LOG_NTSL)
- log_ntsl(log_text)
- if(LOG_OOC)
- log_ooc(log_text)
- if(LOG_LOOC) // yogs - LOOC log
- log_looc(log_text) // yogs - LOOC log
- if(LOG_DONATOR) // yogs - Donator log
- log_donator(log_text) // yogs - Donator log
- if(LOG_ADMIN)
- log_admin(log_text)
- if(LOG_ADMIN_PRIVATE)
- log_admin_private(log_text)
- if(LOG_ASAY)
- log_adminsay(log_text)
- if(LOG_OWNERSHIP)
- log_game(log_text)
- if(LOG_GAME)
- log_game(log_text)
- if(LOG_MECHA)
- log_mecha(log_text)
- else
- stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
- log_game(log_text)
-
-/// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
-/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null)
- var/prefix = tag ? "([tag]) " : ""
- var/suffix = forced_by ? " FORCED by [forced_by]" : ""
- log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally)
-
-/// Helper for logging of messages with only one sender and receiver
-/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag)
- if(!tag)
- stack_trace("Unspecified tag for private message")
- tag = "UNKNOWN"
-
- source.log_talk(message, message_type, tag="[tag] to [key_name(target)]")
- if(source != target)
- target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
-
-/**
- * Log a combat message in the attack log
- *
- * 1 argument is the actor performing the action
- * 2 argument is the target of the action
- * 3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
- * 4 is a tool with which the action was made (usually an item)
- * 5 is any additional text, which will be appended to the rest of the log line
- */
-/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
- var/ssource = key_name(user)
- var/starget = key_name(target)
-
- var/mob/living/living_target = target
- var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : ""
-
- var/sobject = ""
- if(object)
- sobject = " with [object]"
- var/saddition = ""
- if(addition)
- saddition = " [addition]"
-
- var/postfix = "[sobject][saddition][hp]"
-
- var/message = "has [what_done] [starget][postfix]"
- user.log_message(message, LOG_ATTACK, color="red")
-
- if(user != target)
- var/reverse_message = "has been [what_done] by [ssource][postfix]"
- target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
-
-/**
- * log_wound() is for when someone is *attacked* and suffers a wound. Note that this only captures wounds from damage, so smites/forced wounds aren't logged, as well as demotions like cuts scabbing over
- *
- * Note that this has no info on the attack that dealt the wound: information about where damage came from isn't passed to the bodypart's damaged proc. When in doubt, check the attack log for attacks at that same time
- * TODO later: Add logging for healed wounds, though that will require some rewriting of healing code to prevent admin heals from spamming the logs. Not high priority
- *
- * Arguments:
- * * victim- The guy who got wounded
- * * suffered_wound- The wound, already applied, that we're logging. It has to already be attached so we can get the limb from it
- * * dealt_damage- How much damage is associated with the attack that dealt with this wound.
- * * dealt_wound_bonus- The wound_bonus, if one was specified, of the wounding attack
- * * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present
- * * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods
- */
-/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll)
- if(QDELETED(victim) || !suffered_wound)
- return
- var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote?
-
- if(dealt_damage)
- message += " | Damage: [dealt_damage]"
- // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut
- if(base_roll)
- message += "(rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])"
-
- if(dealt_wound_bonus)
- message += " | WB: [dealt_wound_bonus]"
-
- if(dealt_bare_wound_bonus)
- message += " | BWB: [dealt_bare_wound_bonus]"
-
- victim.log_message(message, LOG_ATTACK, color="blue")
-
-
-/atom/movable/proc/add_filter(name,priority,list/params)
- if(!filter_data)
- filter_data = list()
- var/list/p = params.Copy()
- p["priority"] = priority
- filter_data[name] = p
- update_filters()
-
-/atom/movable/proc/update_filters()
- filters = null
- sortTim(filter_data,associative = TRUE)
- for(var/f in filter_data)
- var/list/data = filter_data[f]
- var/list/arguments = data.Copy()
- arguments -= "priority"
- filters += filter(arglist(arguments))
-
/obj/item/update_filters()
. = ..()
for(var/X in actions)
var/datum/action/A = X
A.build_all_button_icons()
-/atom/movable/proc/get_filter(name)
- if(filter_data && filter_data[name])
- return filters[filter_data.Find(name)]
-
-/// Returns the indice in filters of the given filter name.
-/// If it is not found, returns null.
-/atom/proc/get_filter_index(name)
- return filter_data?.Find(name)
-
-/atom/movable/proc/remove_filter(name)
- if(filter_data && filter_data[name])
- filter_data -= name
- update_filters()
-
-
-/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1)
- return FALSE
-
-/**Returns the material composition of the atom.
- *
- * Used when recycling items, specifically to turn alloys back into their component mats.
- *
- * Exists because I'd need to add a way to un-alloy alloys or otherwise deal
- * with people converting the entire stations material supply into alloys.
- *
- * Arguments:
- * - flags: A set of flags determining how exactly the materials are broken down.
- */
-/atom/proc/get_material_composition(breakdown_flags=NONE)
- . = list()
- var/list/cached_materials = custom_materials
- for(var/mat in cached_materials)
- var/datum/material/material = getmaterialref(mat)
- var/list/material_comp = material.return_composition(cached_materials[material], breakdown_flags)
- for(var/comp_mat in material_comp)
- .[comp_mat] += material_comp[comp_mat]
+/atom/proc/intercept_zImpact(atom/movable/falling_movables, levels = 1)
+ SHOULD_CALL_PARENT(TRUE)
+ . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, falling_movables, levels)
///Setter for the `density` variable to append behavior related to its changing.
/atom/proc/set_density(new_value)
@@ -1600,41 +1196,6 @@
/atom/proc/setClosed()
return
-/**
- * Recursive getter method to return a list of all ghosts orbitting this atom
- *
- * This will work fine without manually passing arguments.
- */
-/atom/proc/get_all_orbiters(list/processed, source = TRUE)
- var/list/output = list()
- if (!processed)
- processed = list()
- if (src in processed)
- return output
- if (!source)
- output += src
- processed += src
- for (var/o in orbiters?.orbiters)
- var/atom/atom_orbiter = o
- output += atom_orbiter.get_all_orbiters(processed, source = FALSE)
- return output
-
-///Sets the custom materials for an item.
-/atom/proc/set_custom_materials(list/materials, multiplier = 1)
- if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways
- for(var/i in custom_materials)
- var/datum/material/custom_material = i
- custom_material.on_removed(src, material_flags) //Remove the current materials
-
- custom_materials = list() //Reset the list
-
- for(var/x in materials)
- var/datum/material/custom_material = x
-
-
- custom_material.on_applied(src, materials[custom_material] * multiplier, material_flags)
- custom_materials[custom_material] += materials[x] * multiplier
-
///Passes Stat Browser Panel clicks to the game and calls client click on an atom
/atom/Topic(href, list/href_list)
. = ..()
diff --git a/code/game/atom/atom_appearance.dm b/code/game/atom/atom_appearance.dm
new file mode 100644
index 000000000000..1acf38fa37e3
--- /dev/null
+++ b/code/game/atom/atom_appearance.dm
@@ -0,0 +1,120 @@
+/atom
+ ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc. Single items are stored on their own, not in a list.
+ var/list/managed_overlays
+
+/**
+ * Updates the appearence of the icon
+ *
+ * Mostly delegates to update_name, update_desc, and update_icon
+ *
+ * Arguments:
+ * - updates: A set of bitflags dictating what should be updated. Defaults to [ALL]
+ */
+/atom/proc/update_appearance(updates=ALL)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ . = NONE
+ updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_APPEARANCE, updates)
+ if(updates & UPDATE_NAME)
+ . |= update_name(updates)
+ if(updates & UPDATE_DESC)
+ . |= update_desc(updates)
+ if(updates & UPDATE_ICON)
+ . |= update_icon(updates)
+
+/// Updates the name of the atom
+/atom/proc/update_name(updates=ALL)
+ SHOULD_CALL_PARENT(TRUE)
+ return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_NAME, updates)
+
+/// Updates the description of the atom
+/atom/proc/update_desc(updates=ALL)
+ SHOULD_CALL_PARENT(TRUE)
+ return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_DESC, updates)
+
+/// Updates the icon of the atom
+/atom/proc/update_icon(updates=ALL)
+ SIGNAL_HANDLER
+ SHOULD_CALL_PARENT(TRUE)
+
+ . = NONE
+ updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates)
+ if(updates & UPDATE_ICON_STATE)
+ update_icon_state()
+ . |= UPDATE_ICON_STATE
+
+ if(updates & UPDATE_OVERLAYS)
+ if(LAZYLEN(managed_vis_overlays))
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+
+ var/list/new_overlays = update_overlays(updates)
+ var/nulls = 0
+ for(var/i in 1 to length(new_overlays))
+ var/atom/maybe_not_an_atom = new_overlays[i]
+ if(isnull(maybe_not_an_atom))
+ nulls++
+ continue
+ if(istext(maybe_not_an_atom) || isicon(maybe_not_an_atom))
+ continue
+ new_overlays[i] = maybe_not_an_atom.appearance
+ if(nulls)
+ for(var/i in 1 to nulls)
+ new_overlays -= null
+
+ var/identical = FALSE
+ var/new_length = length(new_overlays)
+ if(!managed_overlays && !new_length)
+ identical = TRUE
+ else if(!islist(managed_overlays))
+ if(new_length == 1 && managed_overlays == new_overlays[1])
+ identical = TRUE
+ else if(length(managed_overlays) == new_length)
+ identical = TRUE
+ for(var/i in 1 to length(managed_overlays))
+ if(managed_overlays[i] != new_overlays[i])
+ identical = FALSE
+ break
+
+ if(!identical)
+ var/full_control = FALSE
+ if(managed_overlays)
+ full_control = length(overlays) == (islist(managed_overlays) ? length(managed_overlays) : 1)
+ if(full_control)
+ overlays = null
+ else
+ cut_overlay(managed_overlays)
+
+ switch(length(new_overlays))
+ if(0)
+ if(full_control)
+ POST_OVERLAY_CHANGE(src)
+ managed_overlays = null
+ if(1)
+ add_overlay(new_overlays)
+ managed_overlays = new_overlays[1]
+ else
+ add_overlay(new_overlays)
+ managed_overlays = new_overlays
+
+ . |= UPDATE_OVERLAYS
+
+ . |= SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates, .)
+
+/// Updates the icon state of the atom
+/atom/proc/update_icon_state()
+ SHOULD_CALL_PARENT(TRUE)
+ return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE)
+
+/// Updates the overlays of the atom
+/atom/proc/update_overlays()
+ SHOULD_CALL_PARENT(TRUE)
+ . = list()
+ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
+
+/**
+ * An atom we are buckled or is contained within us has tried to move
+ *
+ * Default behaviour is to send a warning that the user can't move while buckled as long
+ * as the buckle_message_cooldown has expired (50 ticks)
+ */
diff --git a/code/game/atom/atom_initializing_EXPENSIVE.dm b/code/game/atom/atom_initializing_EXPENSIVE.dm
new file mode 100644
index 000000000000..ae91b1e9fa13
--- /dev/null
+++ b/code/game/atom/atom_initializing_EXPENSIVE.dm
@@ -0,0 +1,162 @@
+/// Init this specific atom
+/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, from_template = FALSE, list/arguments)
+ var/the_type = A.type
+
+ if(QDELING(A))
+ // Check init_start_time to not worry about atoms created before the atoms SS that are cleaned up before this
+ if (A.gc_destroyed > init_start_time)
+ BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
+ return TRUE
+
+ // This is handled and battle tested by dreamchecker. Limit to UNIT_TESTS just in case that ever fails.
+ #ifdef UNIT_TESTS
+ var/start_tick = world.time
+ #endif
+
+ var/result = A.Initialize(arglist(arguments))
+
+ #ifdef UNIT_TESTS
+ if(start_tick != world.time)
+ BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
+ #endif
+
+ var/qdeleted = FALSE
+
+ switch(result)
+ if (INITIALIZE_HINT_NORMAL)
+ // pass
+ if(INITIALIZE_HINT_LATELOAD)
+ if(arguments[1]) //mapload
+ late_loaders += A
+ else
+ A.LateInitialize()
+ if(INITIALIZE_HINT_QDEL)
+ qdel(A)
+ qdeleted = TRUE
+ else
+ BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
+
+ if(!A) //possible harddel
+ qdeleted = TRUE
+ else if(!(A.flags_1 & INITIALIZED_1))
+ BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
+ else
+ SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_ATOM_AFTER_POST_INIT, A)
+ var/atom/location = A.loc
+ if(location)
+ /// Sends a signal that the new atom `src`, has been created at `loc`
+ SEND_SIGNAL(location, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, A, arguments[1])
+ if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables
+ created_atoms += A.get_all_contents()
+
+ return qdeleted || QDELING(A)
+
+/**
+ * Called when an atom is created in byond (built in engine proc)
+ *
+ * Not a lot happens here in SS13 code, as we offload most of the work to the
+ * [Intialization][/atom/proc/Initialize] proc, mostly we run the preloader
+ * if the preloader is being used and then call [InitAtom][/datum/controller/subsystem/atoms/proc/InitAtom] of which the ultimate
+ * result is that the Intialize proc is called.
+ *
+ */
+/atom/New(loc, ...)
+ //atom creation method that preloads variables at creation
+ if(GLOB.use_preloader && src.type == GLOB._preloader_path)//in case the instanciated atom is creating other atoms in New()
+ world.preloader_load(src)
+
+ var/do_initialize = SSatoms.initialized
+ if(do_initialize != INITIALIZATION_INSSATOMS)
+ args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
+ if(SSatoms.InitAtom(src, FALSE, args))
+ //we were deleted
+ return
+ //Yogs edit: Demos
+ if(SSdemo?.initialized)
+ SSdemo.mark_new(src)
+ //Yogs edit ends
+
+/**
+ * The primary method that objects are setup in SS13 with
+ *
+ * we don't use New as we have better control over when this is called and we can choose
+ * to delay calls or hook other logic in and so forth
+ *
+ * During roundstart map parsing, atoms are queued for intialization in the base atom/New(),
+ * After the map has loaded, then Initalize is called on all atoms one by one. NB: this
+ * is also true for loading map templates as well, so they don't Initalize until all objects
+ * in the map file are parsed and present in the world
+ *
+ * If you're creating an object at any point after SSInit has run then this proc will be
+ * immediately be called from New.
+ *
+ * mapload: This parameter is true if the atom being loaded is either being intialized during
+ * the Atom subsystem intialization, or if the atom is being loaded from the map template.
+ * If the item is being created at runtime any time after the Atom subsystem is intialized then
+ * it's false.
+ *
+ * The mapload argument occupies the same position as loc when Initialize() is called by New().
+ * loc will no longer be needed after it passed New(), and thus it is being overwritten
+ * with mapload at the end of atom/New() before this proc (atom/Initialize()) is called.
+ *
+ * You must always call the parent of this proc, otherwise failures will occur as the item
+ * will not be seen as initalized (this can lead to all sorts of strange behaviour, like
+ * the item being completely unclickable)
+ *
+ * You must not sleep in this proc, or any subprocs
+ *
+ * Any parameters from new are passed through (excluding loc), naturally if you're loading from a map
+ * there are no other arguments
+ *
+ * Must return an [initialization hint][INITIALIZE_HINT_NORMAL] or a runtime will occur.
+ *
+ * Note: the following functions don't call the base for optimization and must copypasta handling:
+ * * [/turf/proc/Initialize]
+ * * [/turf/open/space/proc/Initialize]
+ */
+/atom/proc/Initialize(mapload, ...)
+// SHOULD_NOT_SLEEP(TRUE) //Yogs edit: This is fucked and needs an in-depth review before commenting back in. But we should eventually.
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
+
+ SET_PLANE_IMPLICIT(src, plane)
+ //Yog Code: Someday we'll have GAGs
+ // if(greyscale_config && greyscale_colors) //we'll check again at item/init for inhand/belt/worn configs.
+ // update_greyscale()
+
+ //atom color stuff
+ if(color)
+ add_atom_colour(color, FIXED_COLOUR_PRIORITY)
+
+ if (light_system == STATIC_LIGHT && light_power && light_range)
+ update_light()
+
+ SETUP_SMOOTHING()
+
+ if(custom_materials && custom_materials.len)
+ var/temp_list = list()
+ for(var/i in custom_materials)
+ var/datum/material/material = getmaterialref(i) || i
+ temp_list[material] = custom_materials[material] //Get the proper instanced version
+ custom_materials = null //Null the list to prepare for applying the materials properly
+ set_custom_materials(temp_list)
+
+ return INITIALIZE_HINT_NORMAL
+
+/**
+ * Late Intialization, for code that should run after all atoms have run Intialization
+ *
+ * To have your LateIntialize proc be called, your atoms [Initalization][/atom/proc/Initialize]
+ * proc must return the hint
+ * [INITIALIZE_HINT_LATELOAD] otherwise it will never be called.
+ *
+ * useful for doing things like finding other machines on GLOB.machines because you can guarantee
+ * that all atoms will actually exist in the "WORLD" at this time and that all their Intialization
+ * code has been run
+ */
+/atom/proc/LateInitialize()
+ set waitfor = FALSE
diff --git a/code/game/atom/atom_invisibility.dm b/code/game/atom/atom_invisibility.dm
new file mode 100644
index 000000000000..53fbe895817c
--- /dev/null
+++ b/code/game/atom/atom_invisibility.dm
@@ -0,0 +1,72 @@
+// Index defines
+#define INVISIBILITY_VALUE 1
+#define INVISIBILITY_PRIORITY 2
+
+/atom
+ VAR_PRIVATE/list/invisibility_sources
+ VAR_PRIVATE/current_invisibility_priority = -INFINITY
+
+/atom/proc/RecalculateInvisibility()
+ PRIVATE_PROC(TRUE)
+
+ if(!invisibility_sources)
+ current_invisibility_priority = -INFINITY
+ invisibility = initial(invisibility)
+ return
+
+ var/highest_priority
+ var/list/highest_priority_invisibility_data
+ for(var/entry in invisibility_sources)
+ var/list/priority_data
+ if(islist(entry))
+ priority_data = entry
+ else
+ priority_data = invisibility_sources[entry]
+
+ var/priority = priority_data[INVISIBILITY_PRIORITY]
+ if(highest_priority > priority) // In the case of equal priorities, we use the last thing in the list so that more recent changes apply first
+ continue
+
+ highest_priority = priority
+ highest_priority_invisibility_data = priority_data
+
+ current_invisibility_priority = highest_priority
+ invisibility = highest_priority_invisibility_data[INVISIBILITY_VALUE]
+
+/**
+ * Sets invisibility according to priority.
+ * If you want to be able to undo the value you set back to what it would be otherwise,
+ * you should provide an id here and remove it using RemoveInvisibility(id)
+ */
+/atom/proc/SetInvisibility(desired_value, id, priority=0)
+ if(!invisibility_sources)
+ invisibility_sources = list()
+
+ if(id)
+ invisibility_sources[id] = list(desired_value, priority)
+ else
+ invisibility_sources += list(list(desired_value, priority))
+
+ if(current_invisibility_priority > priority)
+ return
+
+ RecalculateInvisibility()
+
+/// Removes the specified invisibility source from the tracker
+/atom/proc/RemoveInvisibility(id)
+ if(!invisibility_sources)
+ return
+
+ var/list/priority_data = invisibility_sources[id]
+ invisibility_sources -= id
+
+ if(length(invisibility_sources) == 0)
+ invisibility_sources = null
+
+ if(current_invisibility_priority > priority_data[INVISIBILITY_PRIORITY])
+ return
+
+ RecalculateInvisibility()
+
+#undef INVISIBILITY_VALUE
+#undef INVISIBILITY_PRIORITY
diff --git a/code/game/atom/atom_materials.dm b/code/game/atom/atom_materials.dm
new file mode 100644
index 000000000000..e4536e5ede5c
--- /dev/null
+++ b/code/game/atom/atom_materials.dm
@@ -0,0 +1,34 @@
+///Sets the custom materials for an item.
+/atom/proc/set_custom_materials(list/materials, multiplier = 1)
+ if(custom_materials) //Only runs if custom materials existed at first. Should usually be the case but check anyways
+ for(var/i in custom_materials)
+ var/datum/material/custom_material = i
+ custom_material.on_removed(src, material_flags) //Remove the current materials
+
+ custom_materials = list() //Reset the list
+
+ for(var/x in materials)
+ var/datum/material/custom_material = x
+
+
+ custom_material.on_applied(src, materials[custom_material] * multiplier, material_flags)
+ custom_materials[custom_material] += materials[x] * multiplier
+
+/**Returns the material composition of the atom.
+ *
+ * Used when recycling items, specifically to turn alloys back into their component mats.
+ *
+ * Exists because I'd need to add a way to un-alloy alloys or otherwise deal
+ * with people converting the entire stations material supply into alloys.
+ *
+ * Arguments:
+ * - flags: A set of flags determining how exactly the materials are broken down.
+ */
+/atom/proc/get_material_composition(breakdown_flags=NONE)
+ . = list()
+ var/list/cached_materials = custom_materials
+ for(var/mat in cached_materials)
+ var/datum/material/material = getmaterialref(mat)
+ var/list/material_comp = material.return_composition(cached_materials[material], breakdown_flags)
+ for(var/comp_mat in material_comp)
+ .[comp_mat] += material_comp[comp_mat]
diff --git a/code/game/atom/atom_orbit.dm b/code/game/atom/atom_orbit.dm
new file mode 100644
index 000000000000..6b7a337164f3
--- /dev/null
+++ b/code/game/atom/atom_orbit.dm
@@ -0,0 +1,18 @@
+/**
+ * Recursive getter method to return a list of all ghosts orbitting this atom
+ *
+ * This will work fine without manually passing arguments.
+ */
+/atom/proc/get_all_orbiters(list/processed, source = TRUE)
+ var/list/output = list()
+ if (!processed)
+ processed = list()
+ if (src in processed)
+ return output
+ if (!source)
+ output += src
+ processed += src
+ for (var/o in orbiters?.orbiters)
+ var/atom/atom_orbiter = o
+ output += atom_orbiter.get_all_orbiters(processed, source = FALSE)
+ return output
diff --git a/code/game/atom/atom_tool_acts.dm b/code/game/atom/atom_tool_acts.dm
new file mode 100644
index 000000000000..f706ea21ebd7
--- /dev/null
+++ b/code/game/atom/atom_tool_acts.dm
@@ -0,0 +1,82 @@
+/**
+ * Tool behavior procedure. Redirects to tool-specific procs by default.
+ *
+ * You can override it to catch all tool interactions, for use in complex deconstruction procs.
+ *
+ * Must return parent proc ..() in the end if overridden
+ */
+/atom/proc/tool_act(mob/living/user, obj/item/tool, tool_type)
+ var/act_result
+ var/signal_result
+
+ signal_result = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(tool_type), user, tool)
+ if(signal_result & COMPONENT_BLOCK_TOOL_ATTACK) // The COMSIG_ATOM_TOOL_ACT signal is blocking the act
+ return TOOL_ACT_SIGNAL_BLOCKING
+ if(QDELETED(tool))
+ return TRUE
+
+ switch(tool_type)
+ if(TOOL_CROWBAR)
+ act_result = crowbar_act(user, tool)
+ if(TOOL_MULTITOOL)
+ act_result = multitool_act(user, tool)
+ if(TOOL_SCREWDRIVER)
+ act_result = screwdriver_act(user, tool)
+ if(TOOL_WRENCH)
+ act_result = wrench_act(user, tool)
+ if(TOOL_WIRECUTTER)
+ act_result = wirecutter_act(user, tool)
+ if(TOOL_WELDER)
+ act_result = welder_act(user, tool)
+ if(TOOL_ANALYZER)
+ act_result = analyzer_act(user, tool)
+ if(!act_result)
+ return
+
+ if(. && tool.toolspeed < 1) //nice tool bro
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "nice_tool", /datum/mood_event/nice_tool)
+
+ // A tooltype_act has completed successfully
+// log_tool("[key_name(user)] used [tool] on [src] at [AREACOORD(src)]")
+ SEND_SIGNAL(tool, COMSIG_TOOL_ATOM_ACTED_PRIMARY(tool_type), src)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+
+//! Tool-specific behavior procs. To be overridden in subtypes.
+///
+
+///Crowbar act
+/atom/proc/crowbar_act(mob/living/user, obj/item/I)
+ return
+
+///Multitool act
+/atom/proc/multitool_act(mob/living/user, obj/item/I)
+ return
+
+///Check if the multitool has an item in it's data buffer
+/atom/proc/multitool_check_buffer(user, obj/item/I, silent = FALSE)
+ if(!istype(I, /obj/item/multitool))
+ if(user && !silent)
+ to_chat(user, span_warning("[I] has no data buffer!"))
+ return FALSE
+ return TRUE
+
+///Screwdriver act
+/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
+ SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), user, I)
+
+///Wrench act
+/atom/proc/wrench_act(mob/living/user, obj/item/I)
+ return
+
+///Wirecutter act
+/atom/proc/wirecutter_act(mob/living/user, obj/item/I)
+ return
+
+///Welder act
+/atom/proc/welder_act(mob/living/user, obj/item/I)
+ return
+
+///Analyzer act
+/atom/proc/analyzer_act(mob/living/user, obj/item/I)
+ return
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 99dc8e01778b..56f4083ced98 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1,5 +1,8 @@
/atom/movable
layer = OBJ_LAYER
+ glide_size = 8
+ appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
+
var/last_move = null
var/last_move_time = 0
var/anchored = FALSE
@@ -31,8 +34,7 @@
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
var/list/client_mobs_in_contents // This contains all the client mobs within this container
var/list/acted_explosions //for explosion dodging
- glide_size = 8
- appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
+
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc.
var/atom/movable/pulling
@@ -45,10 +47,17 @@
var/zfalling = FALSE
+ ///is the mob currently ascending or descending through z levels?
+ var/currently_z_moving
+
+
/// Either FALSE, [EMISSIVE_BLOCK_GENERIC], or [EMISSIVE_BLOCK_UNIQUE]
- var/blocks_emissive = FALSE
+ var/blocks_emissive = EMISSIVE_BLOCK_NONE
///Internal holder for emissive blocker object, do not use directly use blocks_emissive
- var/atom/movable/emissive_blocker/em_block
+ var/atom/movable/render_step/emissive_blocker/em_block
+
+ ///Used for the calculate_adjacencies proc for icon smoothing.
+ var/can_be_unanchored = FALSE
/// The degree of thermal insulation that mobs in list/contents have from the external environment, between 0 and 1
var/contents_thermal_insulation = 0
@@ -60,20 +69,70 @@
///Highest-intensity light affecting us, which determines our visibility.
var/affecting_dynamic_lumi = 0
+ /// Whether this atom should have its dir automatically changed when it moves. Setting this to FALSE allows for things such as directional windows to retain dir on moving without snowflake code all of the place.
+ var/set_dir_on_move = TRUE
+
+/mutable_appearance/emissive_blocker
+
+/mutable_appearance/emissive_blocker/New()
+ . = ..()
+ // Need to do this here because it's overriden by the parent call
+ color = EM_BLOCK_COLOR
+ appearance_flags = EMISSIVE_APPEARANCE_FLAGS
/atom/movable/Initialize(mapload, ...)
. = ..()
- switch(blocks_emissive)
- if(EMISSIVE_BLOCK_GENERIC)
- update_emissive_block()
- if(EMISSIVE_BLOCK_UNIQUE)
+// #ifdef UNIT_TESTS
+// if(explosion_block && !HAS_TRAIT(src, TRAIT_BLOCKING_EXPLOSIVES))
+// stack_trace("[type] blocks explosives, but does not have the managing element applied")
+// #endif
+
+#if EMISSIVE_BLOCK_GENERIC != 0
+ #error EMISSIVE_BLOCK_GENERIC is expected to be 0 to faciliate a weird optimization hack where we rely on it being the most common.
+ #error Read the comment in code/game/atoms_movable.dm for details.
+#endif
+
+ // This one is incredible.
+ // `if (x) else { /* code */ }` is surprisingly fast, and it's faster than a switch, which is seemingly not a jump table.
+ // From what I can tell, a switch case checks every single branch individually, although sane, is slow in a hot proc like this.
+ // So, we make the most common `blocks_emissive` value, EMISSIVE_BLOCK_GENERIC, 0, getting to the fast else branch quickly.
+ // If it fails, then we can check over every value it can be (here, EMISSIVE_BLOCK_UNIQUE is the only one that matters).
+ // This saves several hundred milliseconds of init time.
+ if (blocks_emissive)
+ if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
render_target = ref(src)
- em_block = new(src, render_target)
- vis_contents += em_block
+ em_block = new(null, src)
+ overlays += em_block
+ if(managed_overlays)
+ if(islist(managed_overlays))
+ managed_overlays += em_block
+ else
+ managed_overlays = list(managed_overlays, em_block)
+ else
+ managed_overlays = em_block
+ else
+ var/static/mutable_appearance/emissive_blocker/blocker = new()
+ blocker.icon = icon
+ blocker.icon_state = icon_state
+ blocker.dir = dir
+ blocker.appearance_flags |= appearance_flags
+ blocker.plane = GET_NEW_PLANE(EMISSIVE_PLANE, PLANE_TO_OFFSET(plane))
+ // Ok so this is really cursed, but I want to set with this blocker cheaply while
+ // Still allowing it to be removed from the overlays list later
+ // So I'm gonna flatten it, then insert the flattened overlay into overlays AND the managed overlays list, directly
+ // I'm sorry
+ var/mutable_appearance/flat = blocker.appearance
+ overlays += flat
+ if(managed_overlays)
+ if(islist(managed_overlays))
+ managed_overlays += flat
+ else
+ managed_overlays = list(managed_overlays, flat)
+ else
+ managed_overlays = flat
if(opacity)
AddElement(/datum/element/light_blocking)
-
switch(light_system)
if(MOVABLE_LIGHT)
AddComponent(/datum/component/overlay_lighting)
@@ -91,8 +150,8 @@
if(loc)
//Restore air flow if we were blocking it (movables with ATMOS_PASS_PROC will need to do this manually if necessary)
- if(((CanAtmosPass == ATMOS_PASS_DENSITY && density) || CanAtmosPass == ATMOS_PASS_NO) && isturf(loc))
- CanAtmosPass = ATMOS_PASS_YES
+ if(((can_atmos_pass == ATMOS_PASS_DENSITY && density) || can_atmos_pass == ATMOS_PASS_NO) && isturf(loc))
+ can_atmos_pass = ATMOS_PASS_YES
air_update_turf()
loc.handle_atom_del(src)
@@ -126,58 +185,217 @@
vis_contents.Cut()
/atom/movable/proc/update_emissive_block()
- if(blocks_emissive != EMISSIVE_BLOCK_GENERIC)
- return
- if(length(managed_vis_overlays))
- for(var/a in managed_vis_overlays)
- var/obj/effect/overlay/vis/vs
- if(vs.plane == EMISSIVE_BLOCKER_PLANE)
- SSvis_overlays.remove_vis_overlay(src, list(vs))
- break
- SSvis_overlays.add_vis_overlay(src, icon, icon_state, EMISSIVE_BLOCKER_LAYER, EMISSIVE_BLOCKER_PLANE, dir)
-
-/atom/movable/proc/can_zFall(turf/source, levels = 1, turf/target, direction)
- if(!direction)
- direction = DOWN
- if(!source)
- source = get_turf(src)
- if(!source)
- return FALSE
- if(!target)
- target = get_step_multiz(source, direction)
- if(!target)
- return FALSE
- return !(movement_type & FLYING) && has_gravity(source) && !throwing
-
-/atom/movable/proc/onZImpact(turf/T, levels)
- var/atom/highest = T
- for(var/i in T.contents)
+ // This one is incredible.
+ // `if (x) else { /* code */ }` is surprisingly fast, and it's faster than a switch, which is seemingly not a jump table.
+ // From what I can tell, a switch case checks every single branch individually, although sane, is slow in a hot proc like this.
+ // So, we make the most common `blocks_emissive` value, EMISSIVE_BLOCK_GENERIC, 0, getting to the fast else branch quickly.
+ // If it fails, then we can check over every value it can be (here, EMISSIVE_BLOCK_UNIQUE is the only one that matters).
+ // This saves several hundred milliseconds of init time.
+ if (blocks_emissive)
+ if (blocks_emissive == EMISSIVE_BLOCK_UNIQUE)
+ if(em_block)
+ SET_PLANE(em_block, EMISSIVE_PLANE, src)
+ else if(!QDELETED(src))
+ render_target = ref(src)
+ em_block = new(null, src)
+ return em_block
+ // Implied else if (blocks_emissive == EMISSIVE_BLOCK_NONE) -> return
+ // EMISSIVE_BLOCK_GENERIC == 0
+ else
+ return fast_emissive_blocker(src)
+
+/// Generates a space underlay for a turf
+/// This provides proper lighting support alongside just looking nice
+/// Accepts the appearance to make "spaceish", and the turf we're doing this for
+/proc/generate_space_underlay(mutable_appearance/underlay_appearance, turf/generate_for)
+ underlay_appearance.icon = 'icons/turf/space.dmi'
+ underlay_appearance.icon_state = "0"
+ 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)
+ 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)
+ // We're gonna build a light, and mask it with the base turf's appearance
+ // grab a 32x32 square of it
+ // I would like to use GLOB.starbright_overlays here
+ // But that breaks down for... some? reason. I think recieving a render relay breaks keep_together or something
+ // So we're just gonna accept that this'll break with starlight color changing. hardly matters since this is really only for offset stuff, but I'd love to fix it someday
+ var/mutable_appearance/light = new(GLOB.starlight_objects[GET_TURF_PLANE_OFFSET(generate_for) + 1])
+ light.render_target = ""
+ light.appearance_flags |= KEEP_TOGETHER
+ // Now apply a copy of the turf, set to multiply
+ // This will multiply against our light, so we only light up the bits that aren't "on" the wall
+ var/mutable_appearance/mask = new(generate_for.appearance)
+ mask.blend_mode = BLEND_MULTIPLY
+ mask.render_target = ""
+ mask.pixel_x = 0
+ mask.pixel_y = 0
+ mask.pixel_w = 0
+ mask.pixel_z = 0
+ mask.transform = null
+ mask.underlays = list() // Begone foul lighting overlay
+ SET_PLANE(mask, FLOAT_PLANE, generate_for)
+ mask.layer = FLOAT_LAYER
+
+ // Bump the opacity to full, will this work?
+ mask.color = list(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,255, 0,0,0,0)
+ light.overlays += mask
+ underlay_appearance.overlays += light
+
+ // Now, we're going to make a copy of the mask. Instead of using it to multiply against our light
+ // We're going to use it to multiply against the turf lighting plane. Going to mask away the turf light
+ // And rely on LIGHTING_MASK_LAYER to ensure we mask ONLY that bit
+ var/mutable_appearance/turf_mask = new(mask.appearance)
+ SET_PLANE(turf_mask, LIGHTING_PLANE, generate_for)
+ turf_mask.layer = LIGHTING_MASK_LAYER
+ /// Any color becomes white. Anything else is black, and it's fully opaque
+ /// Ought to work
+ turf_mask.color = list(255,255,255,0, 255,255,255,0, 255,255,255,0, 0,0,0,0, 0,0,0,255)
+ underlay_appearance.overlays += turf_mask
+
+/atom/movable/update_overlays()
+ var/list/overlays = ..()
+ var/emissive_block = update_emissive_block()
+ if(emissive_block)
+ // Emissive block should always go at the beginning of the list
+ overlays.Insert(1, emissive_block)
+ return overlays
+
+/atom/movable/proc/onZImpact(turf/impacted_turf, levels, impact_flags = NONE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(!(impact_flags & ZIMPACT_NO_MESSAGE))
+ visible_message(
+ span_danger("[src] crashes into [impacted_turf]!"),
+ span_userdanger("You crash into [impacted_turf]!"),
+ )
+ if(!(impact_flags & ZIMPACT_NO_SPIN))
+ INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2)
+ SEND_SIGNAL(src, COMSIG_ATOM_ON_Z_IMPACT, impacted_turf, levels)
+
+ //Yog code: since we still handle falling as throwing things
+ var/atom/highest = impacted_turf
+ for(var/i in impacted_turf.contents)
var/atom/A = i
if(!A.density)
continue
if(isobj(A) || ismob(A))
if(A.layer > highest.layer)
highest = A
- INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2)
throw_impact(highest)
return TRUE
-//For physical constraints to travelling up/down.
-/atom/movable/proc/can_zTravel(turf/destination, direction)
- var/turf/T = get_turf(src)
- if(!T)
- return FALSE
+/*
+ * Attempts to move using zMove if direction is UP or DOWN, step if not
+ *
+ * Args:
+ * direction: The direction to go
+ * z_move_flags: bitflags used for checks in zMove and can_z_move
+*/
+/atom/movable/proc/try_step_multiz(direction, z_move_flags = ZMOVE_FLIGHT_FLAGS)
+ if(direction == UP || direction == DOWN)
+ return zMove(direction, null, z_move_flags)
+ return step(src, direction)
+
+/*
+ * The core multi-z movement proc. Used to move a movable through z levels.
+ * If target is null, it'll be determined by the can_z_move proc, which can potentially return null if
+ * conditions aren't met (see z_move_flags defines in __DEFINES/movement.dm for info) or if dir isn't set.
+ * Bear in mind you don't need to set both target and dir when calling this proc, but at least one or two.
+ * This will set the currently_z_moving to CURRENTLY_Z_MOVING_GENERIC if unset, and then clear it after
+ * Forcemove().
+ *
+ *
+ * Args:
+ * * dir: the direction to go, UP or DOWN, only relevant if target is null.
+ * * target: The target turf to move the src to. Set by can_z_move() if null.
+ * * z_move_flags: bitflags used for various checks in both this proc and can_z_move(). See __DEFINES/movement.dm.
+ */
+/atom/movable/proc/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS)
+ if(!target)
+ target = can_z_move(dir, get_turf(src), null, z_move_flags)
+ if(!target)
+ set_currently_z_moving(FALSE, TRUE)
+ return FALSE
+
+ var/list/moving_movs = get_z_move_affected(z_move_flags)
+
+ for(var/atom/movable/movable as anything in moving_movs)
+ movable.currently_z_moving = currently_z_moving || CURRENTLY_Z_MOVING_GENERIC
+ movable.forceMove(target)
+ movable.set_currently_z_moving(FALSE, TRUE)
+ // This is run after ALL movables have been moved, so pulls don't get broken unless they are actually out of range.
+ if(z_move_flags & ZMOVE_CHECK_PULLS)
+ for(var/atom/movable/moved_mov as anything in moving_movs)
+ if(z_move_flags & ZMOVE_CHECK_PULLEDBY && moved_mov.pulledby && (moved_mov.z != moved_mov.pulledby.z || get_dist(moved_mov, moved_mov.pulledby) > 1))
+ moved_mov.pulledby.stop_pulling()
+ if(z_move_flags & ZMOVE_CHECK_PULLING)
+ moved_mov.check_pulling(TRUE)
+ return TRUE
+
+/// Returns a list of movables that should also be affected when src moves through zlevels, and src.
+/atom/movable/proc/get_z_move_affected(z_move_flags)
+ . = list(src)
+ if(buckled_mobs)
+ . |= buckled_mobs
+ if(!(z_move_flags & ZMOVE_INCLUDE_PULLED))
+ return
+ for(var/mob/living/buckled as anything in buckled_mobs)
+ if(buckled.pulling)
+ . |= buckled.pulling
+ if(pulling)
+ . |= pulling
+
+/**
+ * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so.
+ * Args:
+ * * direction: the direction to go, UP or DOWN, only relevant if target is null.
+ * * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source.
+ * * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm.
+ * * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels.
+ */
+/atom/movable/proc/can_z_move(direction, turf/start, turf/destination, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider)
+ if(!start)
+ start = get_turf(src)
+ if(!start)
+ return FALSE
if(!direction)
if(!destination)
return FALSE
- direction = get_dir(T, destination)
+ direction = get_dir_multiz(start, destination)
if(direction != UP && direction != DOWN)
return FALSE
if(!destination)
- destination = get_step_multiz(src, direction)
+ destination = get_step_multiz(start, direction)
if(!destination)
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider || src, span_warning("There's nowhere to go in that direction!"))
return FALSE
- return T.zPassOut(src, direction, destination) && destination.zPassIn(src, direction, T)
+ if(z_move_flags & ZMOVE_FALL_CHECKS && (throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start)))
+ return FALSE
+ if(z_move_flags & ZMOVE_CAN_FLY_CHECKS && !(movement_type & (FLYING|FLOATING)) && has_gravity(start))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ if(rider)
+ to_chat(rider, span_warning("[src] is is not capable of flight."))
+ else
+ to_chat(src, span_warning("You are not Superman."))
+ return FALSE
+ if((!(z_move_flags & ZMOVE_IGNORE_OBSTACLES) && !(start.zPassOut(direction) && destination.zPassIn(direction))) || (!(z_move_flags & ZMOVE_ALLOW_ANCHORED) && anchored))
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider || src, span_warning("You couldn't move there!"))
+ return FALSE
+ return destination //used by some child types checks and zMove()
+
+/// Sets the currently_z_moving variable to a new value. Used to allow some zMovement sources to have precedence over others.
+/atom/movable/proc/set_currently_z_moving(new_z_moving_value, forced = FALSE)
+ if(forced)
+ currently_z_moving = new_z_moving_value
+ return TRUE
+ var/old_z_moving_value = currently_z_moving
+ currently_z_moving = max(currently_z_moving, new_z_moving_value)
+ return currently_z_moving > old_z_moving_value
/atom/movable/vv_edit_var(var_name, var_value)
var/static/list/banned_edits = list("step_x", "step_y", "step_size", "bounds")
@@ -267,6 +485,7 @@
. = pulledby
pulledby = new_pulledby
+
/atom/movable/proc/Move_Pulled(atom/moving_atom)
if(!pulling)
return FALSE
@@ -293,6 +512,10 @@
var/mob/living/pulled_mob = moving_atom
set_pull_offsets(pulled_mob, grab_state)
+/**
+ * Checks if the pulling and pulledby should be stopped because they're out of reach.
+ * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs.
+ */
/atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE)
if(pulling)
if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed))
@@ -323,88 +546,97 @@
var/atom/old_loc = loc
var/direction = get_dir(old_loc, new_loc)
loc = new_loc
- Moved(old_loc, direction, TRUE)
+ Moved(old_loc, direction, TRUE, momentum_change = FALSE)
////////////////////////////////////////
// Here's where we rewrite how byond handles movement except slightly different
// To be removed on step_ conversion
// All this work to prevent a second bump
-/atom/movable/Move(atom/newloc, direct=0, glide_size_override = 0)
+/atom/movable/Move(atom/newloc, direction, glide_size_override = 0, update_dir = TRUE)
. = FALSE
if(!newloc || newloc == loc)
return
- if(!direct)
- direct = get_dir(src, newloc)
- setDir(direct)
-
- // yogs start - multi tile object handling
- if(bound_width != world.icon_size || bound_height != world.icon_size)
- var/list/newlocs = isturf(newloc) ? block(locate(newloc.x+(-bound_x)/world.icon_size,newloc.y+(-bound_y)/world.icon_size,newloc.z),locate(newloc.x+(-bound_x+bound_width)/world.icon_size-1,newloc.y+(-bound_y+bound_height)/world.icon_size-1,newloc.z)) : list(newloc)
- if(!newlocs)
- return // we're trying to cross into the edge of space
- var/bothturfs = isturf(newloc) && isturf(loc)
- var/dx = bothturfs ? newloc.x - loc.x : 0
- var/dy = bothturfs ? newloc.y - loc.y : 0
- var/dz = bothturfs ? newloc.z - loc.z : 0
- for(var/atom/A in (locs - newlocs))
- if(!A.Exit(src, bothturfs ? locate(A.x+dx,A.y+dy,A.z+dz) : newloc))
- return
- for(var/atom/A in (newlocs - locs))
- if(!A.Enter(src, bothturfs ? locate(A.x-dx,A.y-dy,A.z+dz) : loc))
+ if(!direction)
+ direction = get_dir(src, newloc)
+
+ if(set_dir_on_move && dir != direction && update_dir)
+ setDir(direction)
+
+ var/is_multi_tile_object = is_multi_tile_object(src)
+
+ var/list/old_locs
+ if(is_multi_tile_object && isturf(loc))
+ old_locs = locs // locs is a special list, this is effectively the same as .Copy() but with less steps
+ for(var/atom/exiting_loc as anything in old_locs)
+ if(!exiting_loc.Exit(src, direction))
return
else
- if(!loc.Exit(src, newloc))
+ if(!loc.Exit(src, direction))
return
- if(!newloc.Enter(src, src.loc))
+ var/list/new_locs
+ if(is_multi_tile_object && isturf(newloc))
+ new_locs = block(
+ newloc,
+ locate(
+ min(world.maxx, newloc.x + CEILING(bound_width / 32, 1)),
+ min(world.maxy, newloc.y + CEILING(bound_height / 32, 1)),
+ newloc.z
+ )
+ ) // If this is a multi-tile object then we need to predict the new locs and check if they allow our entrance.
+ for(var/atom/entering_loc as anything in new_locs)
+ if(!entering_loc.Enter(src))
+ return
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, entering_loc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
+ return
+ else // Else just try to enter the single destination.
+ if(!newloc.Enter(src))
+ return
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
return
- // yogs end
-
- if (SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_MOVE, newloc) & COMPONENT_MOVABLE_BLOCK_PRE_MOVE)
- return
// Past this is the point of no return
var/atom/oldloc = loc
var/area/oldarea = get_area(oldloc)
var/area/newarea = get_area(newloc)
+
loc = newloc
+
. = TRUE
- oldloc.Exited(src, newloc)
- if(oldarea != newarea)
- oldarea.Exited(src, newloc)
- for(var/i in oldloc)
- if(i == src) // Multi tile objects
- continue
- var/atom/movable/thing = i
- thing.Uncrossed(src)
+ if(old_locs) // This condition will only be true if it is a multi-tile object.
+ for(var/atom/exited_loc as anything in (old_locs - new_locs))
+ exited_loc.Exited(src, direction)
+ else // Else there's just one loc to be exited.
+ oldloc.Exited(src, direction)
+ if(oldarea != newarea)
+ oldarea.Exited(src, direction)
- newloc.Entered(src, oldloc)
+ if(new_locs) // Same here, only if multi-tile.
+ for(var/atom/entered_loc as anything in (new_locs - old_locs))
+ entered_loc.Entered(src, oldloc, old_locs)
+ else
+ newloc.Entered(src, oldloc, old_locs)
if(oldarea != newarea)
- newarea.Entered(src, oldloc)
+ newarea.Entered(src, oldarea)
+
+ Moved(oldloc, direction, FALSE, old_locs)
- for(var/i in loc)
- if(i == src) // Multi tile objects
- continue
- var/atom/movable/thing = i
- thing.Crossed(src)
-//
////////////////////////////////////////
-/atom/movable/Move(atom/newloc, direct, glide_size_override = 0)
+/atom/movable/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE)
var/atom/movable/pullee = pulling
- var/turf/T = loc
+ var/turf/current_turf = loc
if(!moving_from_pull)
- check_pulling()
+ check_pulling(z_allowed = TRUE)
if(!loc || !newloc)
return FALSE
var/atom/oldloc = loc
//Early override for some cases like diagonal movement
- if(glide_size_override)
+ if(glide_size_override && glide_size != glide_size_override)
set_glide_size(glide_size_override)
-
if(loc != newloc)
if (!(direct & (direct - 1))) //Cardinal move
. = ..()
@@ -454,30 +686,37 @@
moving_diagonally = SECOND_DIAG_STEP
. = step(src, SOUTH)
if(moving_diagonally == SECOND_DIAG_STEP)
- if(!.)
+ if(!. && set_dir_on_move && update_dir)
setDir(first_step_dir)
- else if (!inertia_moving)
- inertia_next_move = world.time + inertia_move_delay
+ else if(!inertia_moving)
newtonian_move(direct)
+ if(client_mobs_in_contents)
+ update_parallax_contents()
moving_diagonally = 0
return
if(!loc || (loc == oldloc && oldloc != newloc))
last_move = 0
+ set_currently_z_moving(FALSE, TRUE)
return
- if(.)
- Moved(oldloc, direct)
if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move.
if(pulling.anchored)
stop_pulling()
else
- var/pull_dir = get_dir(src, pulling)
- //puller and pullee more than one tile away or in diagonal position
- if(get_dist(src, pulling) > 1 || (moving_diagonally != SECOND_DIAG_STEP && ((pull_dir - 1) & pull_dir)))
- pulling.moving_from_pull = src
- pulling.Move(T, get_dir(pulling, T), glide_size) //the pullee tries to reach our previous position
- pulling.moving_from_pull = null
+ //puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus.
+ if(!pulling.pulling?.moving_from_pull)
+ var/pull_dir = get_dir(pulling, src)
+ var/target_turf = current_turf
+
+ // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls.
+ // You may wonder why we're not just forcemoving the pulling movable and regrabbing it.
+ // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines.
+ if(pulling.z != z)
+ target_turf = get_step(pulling, get_dir(pulling, current_turf))
+
+ if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1)
+ pulling.move_from_pull(src, target_turf, glide_size)
check_pulling()
//glide_size strangely enough can change mid movement animation and update correctly while the animation is playing
@@ -486,20 +725,59 @@
set_glide_size(glide_size_override)
last_move = direct
- setDir(direct)
+
+ if(set_dir_on_move && dir != direct && update_dir)
+ setDir(direct)
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
- return FALSE
+ . = FALSE
+
+ if(currently_z_moving)
+ if(. && loc == newloc)
+ var/turf/pitfall = get_turf(src)
+ pitfall.zFall(src, falling_from_move = TRUE)
+ else
+ set_currently_z_moving(FALSE, TRUE)
-//Called after a successful Move(). By this point, we've already moved
-/atom/movable/proc/Moved(atom/OldLoc, Dir, Forced = FALSE)
+/// Called when src is being moved to a target turf because another movable (puller) is moving around.
+/atom/movable/proc/move_from_pull(atom/movable/puller, turf/target_turf, glide_size_override)
+ moving_from_pull = puller
+ Move(target_turf, get_dir(src, target_turf), glide_size_override)
+ moving_from_pull = null
+
+/**
+ * Called after a successful Move(). By this point, we've already moved.
+ * Arguments:
+ * * old_loc is the location prior to the move. Can be null to indicate nullspace.
+ * * movement_dir is the direction the movement took place. Can be NONE if it was some sort of teleport.
+ * * The forced flag indicates whether this was a forced move, which skips many checks of regular movement.
+ * * The old_locs is an optional argument, in case the moved movable was present in multiple locations before the movement.
+ * * momentum_change represents whether this movement is due to a "new" force if TRUE or an already "existing" force if FALSE
+ **/
+/atom/movable/proc/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
SHOULD_CALL_PARENT(TRUE)
- SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, OldLoc, Dir, Forced)
- if (!inertia_moving)
- inertia_next_move = world.time + inertia_move_delay
- newtonian_move(Dir)
- if (length(client_mobs_in_contents))
+
+ if (!inertia_moving && momentum_change)
+ newtonian_move(movement_dir)
+ // If we ain't moving diagonally right now, update our parallax
+ // We don't do this all the time because diag movements should trigger one call to this, not two
+ // Waste of cpu time, and it fucks the animate
+ if (!moving_diagonally && client_mobs_in_contents)
update_parallax_contents()
+ SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, movement_dir, forced, old_locs, momentum_change)
+
+ if(old_loc)
+ SEND_SIGNAL(old_loc, COMSIG_ATOM_ABSTRACT_EXITED, src, movement_dir)
+ if(loc)
+ SEND_SIGNAL(loc, COMSIG_ATOM_ABSTRACT_ENTERED, src, old_loc, old_locs)
+
+ var/turf/old_turf = get_turf(old_loc)
+ var/turf/new_turf = get_turf(src)
+
+ if (old_turf?.z != new_turf?.z)
+ var/same_z_layer = (GET_TURF_PLANE_OFFSET(old_turf) == GET_TURF_PLANE_OFFSET(new_turf))
+ on_changed_z_level(old_turf, new_turf, same_z_layer)
+
SSdemo.mark_dirty(src)
return TRUE
@@ -526,27 +804,21 @@
/atom/movable/Uncrossed(atom/movable/AM)
SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSSED, AM)
-/atom/movable/Bump(atom/A)
- if(!A)
+/atom/movable/Bump(atom/bumped_atom)
+ if(!bumped_atom)
CRASH("Bump was called with no argument.")
- SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, A)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom)
. = ..()
if(!QDELETED(throwing))
- throwing.hit_atom(A)
+ throwing.hit_atom(bumped_atom)
. = TRUE
- if(QDELETED(A))
+ if(QDELETED(bumped_atom))
return
- A.Bumped(src)
+ bumped_atom.Bumped(src)
/atom/movable/proc/forceMove(atom/destination)
. = FALSE
if(destination)
- var/turf/new_turf = get_turf(destination)
- if(new_turf && ismob(src))
- var/mob/M = src
- if(is_secret_level(new_turf.z) && !M.client?.holder)
- return
-
. = doMove(destination)
else
CRASH("No valid destination passed into forceMove")
@@ -557,8 +829,10 @@
/atom/movable/proc/doMove(atom/destination)
. = FALSE
if(destination)
- if(pulledby)
+ ///zMove already handles whether a pull from another movable should be broken.
+ if(pulledby && !currently_z_moving)
pulledby.stop_pulling()
+
var/atom/oldloc = loc
var/same_loc = oldloc == destination
var/area/old_area = get_area(oldloc)
@@ -574,12 +848,6 @@
old_area.Exited(src, destination)
for(var/atom/movable/AM in oldloc)
AM.Uncrossed(src)
- var/turf/oldturf = get_turf(oldloc)
- var/turf/destturf = get_turf(destination)
- var/old_z = (oldturf ? oldturf.z : null)
- var/dest_z = (destturf ? destturf.z : null)
- if (old_z != dest_z)
- onTransitZ(old_z, dest_z)
destination.Entered(src, oldloc)
if(destarea && old_area != destarea)
destarea.Entered(src, oldloc)
@@ -603,11 +871,43 @@
old_area.Exited(src, null)
loc = null
-/atom/movable/proc/onTransitZ(old_z,new_z)
- SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_z, new_z)
- for (var/item in src) // Notify contents of Z-transition. This can be overridden IF we know the items contents do not care.
- var/atom/movable/AM = item
- AM.onTransitZ(old_z,new_z)
+/**
+ * Called when a movable changes z-levels.
+ *
+ * Arguments:
+ * * old_turf - The previous turf they were on before.
+ * * new_turf - The turf they have now entered.
+ * * same_z_layer - If their old and new z levels are on the same level of plane offsets or not
+ * * notify_contents - Whether or not to notify the movable's contents that their z-level has changed. NOTE, IF YOU SET THIS, YOU NEED TO MANUALLY SET PLANE OF THE CONTENTS LATER
+ */
+/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf, same_z_layer)
+
+ // If our turfs are on different z "layers", recalc our planes
+ if(!same_z_layer && !QDELETED(src))
+ SET_PLANE(src, PLANE_TO_TRUE(src.plane), new_turf)
+ // a TON of overlays use planes, and thus require offsets
+ // so we do this. sucks to suck
+ update_appearance()
+
+ if(update_on_z)
+ // I so much wish this could be somewhere else. alas, no.
+ for(var/image/update as anything in update_on_z)
+ SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
+ if(update_overlays_on_z)
+ // This EVEN more so
+ cut_overlay(update_overlays_on_z)
+ // This even more so
+ for(var/mutable_appearance/update in update_overlays_on_z)
+ SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
+ add_overlay(update_overlays_on_z)
+
+ if(!notify_contents)
+ return
+
+ for (var/atom/movable/content as anything in src) // Notify contents of Z-transition.
+ content.on_changed_z_level(old_turf, new_turf, same_z_layer)
/atom/movable/proc/setMovetype(newval)
movement_type = newval
@@ -627,6 +927,9 @@
if(throwing)
return TRUE
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir) & COMSIG_MOVABLE_ALLOW_SPACEMOVE)
+ return TRUE
+
if(!isturf(loc))
return TRUE
@@ -1011,6 +1314,8 @@
return FALSE
if(force < (move_resist * MOVE_FORCE_PULL_RATIO))
return FALSE
+ if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_PULLED, user) & COMSIG_ATOM_CANT_PULL) //dripstation edit
+ return FALSE //dripstation edit
return TRUE
/// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment.
@@ -1044,3 +1349,4 @@
if(. <= GRAB_AGGRESSIVE)
ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT)
ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT)
+
diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm
index 5786dd262af0..3589e558963f 100644
--- a/code/game/data_huds.dm
+++ b/code/game/data_huds.dm
@@ -80,7 +80,7 @@
..()
if(!new_viewer || hud_users.len != 1)
return
- for(var/mob/camera/aiEye/eye as anything in GLOB.aiEyes)
+ for(var/mob/camera/ai_eye/eye as anything in GLOB.aiEyes)
eye.update_ai_detect_hud()
/* MED/SEC/DIAG HUD HOOKS */
@@ -165,14 +165,17 @@ Medical HUD! Basic mode needs suit sensors on.
holder.pixel_y = I.Height() - world.icon_size
if(HAS_TRAIT(src, TRAIT_XENO_HOST))
holder.icon_state = "hudxeno"
+ if(undergoing_cardiac_arrest() && stat != DEAD) //dripstation edit
+ holder.icon_state = "huddefib" //dripstation edit
+ return //dripstation edit
else if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH)))
if(HAS_TRAIT(src, TRAIT_FAKEDEATH))
- holder.icon_state = "huddefib"
+ holder.icon_state = "hudflatline" //dripstation edit
return
if(tod)
var/tdelta = round(world.time - timeofdeath)
if(tdelta < (DEFIB_TIME_LIMIT))
- holder.icon_state = "huddefib"
+ holder.icon_state = "hudflatline" //dripstation edit
return
holder.icon_state = "huddead"
else
diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm
index fc7417dd8e6a..beaacb372fe5 100644
--- a/code/game/gamemodes/brother/traitor_bro.dm
+++ b/code/game/gamemodes/brother/traitor_bro.dm
@@ -6,9 +6,10 @@
name = "traitor+brothers"
config_tag = "traitorbro"
restricted_jobs = list("AI", "Cyborg")
- required_players = 8 //yogs - just a minor change
+ required_players = 20 //yogs - just a minor change
title_icon = "ss13"
+
announce_span = "danger"
announce_text = "There are Syndicate agents and Blood Brothers on the station!\n\
Traitors: Accomplish your objectives.\n\
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index 38d9de8ed8dd..32ad3e2bb3a0 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -81,7 +81,7 @@ GLOBAL_VAR(changeling_team_objective_type)
false_report_weight = 10
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Brig Physician") //YOGS - added hop and brig physician
- required_players = 25
+ required_players = 20
required_enemies = 2
recommended_enemies = 4
reroll_friendly = 1
diff --git a/code/game/gamemodes/changeling/traitor_chan.dm b/code/game/gamemodes/changeling/traitor_chan.dm
index cb1c5b13c84f..f3e16fe471c6 100644
--- a/code/game/gamemodes/changeling/traitor_chan.dm
+++ b/code/game/gamemodes/changeling/traitor_chan.dm
@@ -5,7 +5,7 @@
false_report_weight = 10
traitors_possible = 3 //hard limit on traitors if scaling is turned off
restricted_jobs = list("AI", "Cyborg")
- required_players = 25
+ required_players = 20
required_enemies = 1 // how many of each type are required
recommended_enemies = 3
reroll_friendly = 1
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index 3c1c015922eb..a7dfa85ac74e 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -14,7 +14,7 @@
//Clown shoes with combat stats and noslip. Of course they still squeak.
/obj/item/clothing/shoes/clown_shoes/combat
name = "combat clown shoes"
- desc = "advanced clown shoes that protect the wearer and render them nearly immune to slipping on their own peels. They also squeak at 100% capacity."
+ desc = "A pair of advanced clown shoes that protect the wearer and render them nearly immune to slipping on their own peels. They also squeak at 100% capacity."
clothing_flags = NOSLIP
slowdown = SHOES_SLOWDOWN
armor = list(MELEE = 25, BULLET = 25, LASER = 25, ENERGY = 25, BOMB = 50, BIO = 60, RAD = 0, FIRE = 70, ACID = 50)
@@ -286,16 +286,7 @@
operation_req_access = list(ACCESS_SYNDICATE)
internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/honker/dark
- max_equip = 3
-
-/obj/mecha/combat/honker/dark/GrantActions(mob/living/user, human_occupant = 0)
- ..()
- thrusters_action.Grant(user, src)
-
-
-/obj/mecha/combat/honker/dark/RemoveActions(mob/living/user, human_occupant = 0)
- ..()
- thrusters_action.Remove(user)
+ max_equip = 4
/obj/mecha/combat/honker/dark/add_cell(obj/item/stock_parts/cell/C)
if(C)
@@ -312,6 +303,8 @@
ME.attach(src)
ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang/tearstache()//The mousetrap mortar was not up-to-snuff.
ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion()
+ ME.attach(src)
/obj/mecha/combat/honker/dark/crew
operation_req_access = list()
diff --git a/code/game/gamemodes/clown_ops/honkmother.dm b/code/game/gamemodes/clown_ops/honkmother.dm
index ced8aad4f2d3..01f939968f50 100644
--- a/code/game/gamemodes/clown_ops/honkmother.dm
+++ b/code/game/gamemodes/clown_ops/honkmother.dm
@@ -19,7 +19,7 @@
var/convert_range = 8
//She is above even fire
layer = RIPPLE_LAYER
- movement_type = UNSTOPPABLE
+ movement_type = PHASING
obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION
/obj/structure/destructible/honkmother/Initialize(mapload)
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 00530fe4d1e5..645d1708fd3d 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -41,7 +41,7 @@
false_report_weight = 10
restricted_jobs = list("Chaplain","AI", "Cyborg", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Research Director", "Chief Engineer", "Chief Medical Officer", "Brig Physician") //Yogs: Added Brig Physician
protected_jobs = list()
- required_players = 29
+ required_players = 24
required_enemies = 4
recommended_enemies = 4
enemy_minimum_age = 14
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 974f66f8a616..2b27bd69e7cb 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -79,7 +79,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
/// Antags rolled by rules so far, to keep track of and discourage scaling past a certain ratio of crew/antags especially on lowpop.
var/antags_rolled = 0
/// CRATE DISCOUNT
- var/discountedcrates = list( /datum/supply_pack/security/laser,
+ var/discountedcrates = list( /datum/supply_pack/security/armory/laser, //dripstation edit
/datum/supply_pack/security/vending/security,
/datum/supply_pack/service/party)
@@ -470,6 +470,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
rule.acceptable(roundstart_pop_ready, threat_level) // Assigns some vars in the modes, running it here for consistency
rule.candidates = candidates.Copy()
rule.trim_candidates()
+ rule.load_templates()
if (rule.ready(roundstart_pop_ready, TRUE))
var/cost = rule.cost
var/scaled_times = 0
@@ -490,6 +491,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (rule.acceptable(roundstart_pop_ready, threat_level) && round_start_budget >= rule.cost) // If we got the population and threat required
rule.candidates = candidates.Copy()
rule.trim_candidates()
+ rule.load_templates()
if (rule.ready(roundstart_pop_ready) && rule.candidates.len > 0)
drafted_rules[rule] = rule.weight
@@ -595,6 +597,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
var/population = current_players[CURRENT_LIVING_PLAYERS].len
if((new_rule.acceptable(population, threat_level) && new_rule.cost <= mid_round_budget) || forced)
new_rule.trim_candidates()
+ new_rule.load_templates()
if (new_rule.ready(forced))
spend_midround_budget(new_rule.cost)
threat_log += "[worldtime2text()]: Forced rule [new_rule.name] spent [new_rule.cost]"
@@ -648,6 +651,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(rule.ruletype == "Latejoin" && !(GLOB.ghost_role_flags & GHOSTROLE_MIDROUND_EVENT))
continue
rule.trim_candidates()
+ rule.load_templates()
if (rule.ready())
drafted_rules[rule] = rule.get_weight()
if (drafted_rules.len > 0)
@@ -750,6 +754,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
rule.candidates = list(newPlayer)
rule.trim_candidates()
+ rule.load_templates()
if(!rule.candidates || !length(rule.candidates))
continue
if (rule.ready())
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index efbab7a576eb..ddf7fe6f250b 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -76,6 +76,8 @@
/// If written as a linear equation, will be in the form of `list("denominator" = denominator, "offset" = offset).
var/antag_cap = 0
+ /// A list, or null, of templates that the ruleset depends on to function correctly
+ var/list/ruleset_lazy_templates
/datum/dynamic_ruleset/New()
..()
@@ -157,6 +159,11 @@
return FALSE
return TRUE
+/// This should always be called before ready is, to ensure that the ruleset can locate map/template based landmarks as needed
+/datum/dynamic_ruleset/proc/load_templates()
+ for(var/template in ruleset_lazy_templates)
+ SSmapping.lazy_load_template(template)
+
/// Runs from gamemode process() if ruleset fails to start, like delayed rulesets not getting valid candidates.
/// This one only handles refunding the threat, override in ruleset to clean up the rest.
/datum/dynamic_ruleset/proc/clean_up()
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 2862e01c87ae..f194b119758a 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -172,7 +172,7 @@
if(SHUTTLE_STRANDED, SHUTTLE_ESCAPE)
return TRUE
if(SHUTTLE_CALL)
- if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergencyCallTime)*0.5)
+ if(SSshuttle.emergency.timeLeft(1) < initial(SSshuttle.emergency_call_time)*0.5)
return TRUE
var/matc = CONFIG_GET(number/midround_antag_time_check)
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 7c9fa59fd698..e83d8dbd2f50 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -88,19 +88,33 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
icon_state = "small"
density = TRUE
anchored = TRUE
- var/hits = 4
- var/hitpwr = 2 //Level of ex_act to be called on hit.
- var/dest
pass_flags = PASSTABLE
- var/heavy = 0
+
+ ///The resilience of our meteor
+ var/hits = 4
+ ///Level of ex_act to be called on hit.
+ var/hitpwr = EXPLODE_HEAVY
+ //Should we shake people's screens on impact
+ var/heavy = FALSE
+ ///Sound to play when you hit something
var/meteorsound = 'sound/effects/meteorimpact.ogg'
+ ///Our starting z level, prevents infinite meteors
var/z_original
- var/threat = 0 // used for determining which meteors are most interesting
- var/lifetime = DEFAULT_METEOR_LIFETIME
- var/timerid = null
+ ///Used for determining which meteors are most interesting
+ var/threat = 0
+
+ //Potential items to spawn when you die
var/list/meteordrop = list(/obj/item/stack/ore/iron)
+ ///How much stuff to spawn when you die
var/dropamt = 2
+ ///The thing we're moving towards, usually a turf
+ var/atom/dest
+ ///Lifetime in seconds
+ var/lifetime = DEFAULT_METEOR_LIFETIME
+
+ var/timerid = null
+
/obj/effect/meteor/Move()
if(z != z_original || loc == dest)
qdel(src)
@@ -134,6 +148,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
SpinAnimation()
timerid = QDEL_IN(src, lifetime)
chase_target(target)
+ update_appearance()
/obj/effect/meteor/Bump(atom/A)
if(A)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 1df48e8137eb..019e35a760f1 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -1514,6 +1514,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/assassinate/cloned,
/datum/objective/assassinate/once,
/datum/objective/maroon,
+ /datum/objective/maroon_organ,
/datum/objective/debrain,
/datum/objective/protect,
/datum/objective/assist,
@@ -1561,21 +1562,17 @@ GLOBAL_LIST_EMPTY(possible_items_special)
return (istype(user_area, dropoff) && istype(target_area, dropoff))
-/**
- * Break shit - the objective
- *
- * Areas are stored, not references to the machines and not checking the machines globally
- * This solves the following issues:
- * * Problem 1 - Engineers rebuild and then the sabotage is immediately undone, the traitor having no reason to stop them
- * * Problem 2 - Engineers build a random machine in maint where no one would look to fuck over the traitor
- * The idea is that the traitor must commit to breaking the machines
- */
+
+///////////////////////////////////////////////////////////////////////
+//----------------Break specific machines in an area once------------//
+///////////////////////////////////////////////////////////////////////
/datum/objective/break_machinery
name = "Destroy some machines"
explanation_text = "Destroy all of something in the areas it spawns in."
var/obj/machinery/target_obj_type
var/list/potential_target_types
var/list/area/target_areas
+ var/list/machines_to_break = list()
/datum/objective/break_machinery/finalize()
target_areas = list()
@@ -1586,8 +1583,8 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/obj/machinery/rnd/server,
// ENGINEERING
/obj/machinery/power/smes,
- /obj/machinery/power/supermatter_crystal,
/obj/machinery/telecomms, // hard-mode
+ /obj/machinery/power/supermatter_crystal,
// MEDICAL
/obj/machinery/stasis,
/obj/machinery/sleeper,
@@ -1628,11 +1625,14 @@ GLOBAL_LIST_EMPTY(possible_items_special)
// Store areas
for(var/obj/machinery/machine as anything in eligible_machines)
target_areas |= get_area(machine)
- if(target_areas.len >= 4)
+ for(var/obj/machinery/specific as anything in get_area(machine))
+ if(istype(specific, target_obj_type))
+ machines_to_break |= specific
+ if(target_areas.len >= 2)
break
// Format explanation text
- explanation_text = "Ensure no functioning [machine_name][machine_name[length(machine_name)] == "s" ? "es" : "s"] exist in "
+ explanation_text = "Destroy the original [machine_name][machine_name[length(machine_name)] == "s" ? "es" : "s"] in "
switch(target_areas.len)
if(0)
return FALSE
@@ -1640,14 +1640,6 @@ GLOBAL_LIST_EMPTY(possible_items_special)
explanation_text += "[target_areas[1].name]."
if(2)
explanation_text += "[target_areas[1].name] and [target_areas[2].name]."
- else
- var/iteration = 1
- for(var/area/target_area in target_areas)
- if(iteration == target_areas.len)
- explanation_text += "and [target_area.name]."
- break
- explanation_text += "[target_area.name], "
- iteration++
return TRUE
/datum/objective/break_machinery/check_completion()
@@ -1655,8 +1647,10 @@ GLOBAL_LIST_EMPTY(possible_items_special)
return TRUE
if(target_areas.len == 0)
return TRUE
- for(var/area/target_area in target_areas)
- if(locate(target_obj_type) in target_area)
+ if(machines_to_break.len == 0)
+ return TRUE
+ for(var/obj/machinery/thing as anything in machines_to_break)
+ if(thing && istype(thing, target_obj_type))
return FALSE
return TRUE
@@ -1698,3 +1692,58 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/gimmick/admin_edit(mob/admin)
update_explanation_text()
+
+///////////////////////////////////////////////////////////////////////
+//-----------------------Maroon a specific organ---------------------//
+///////////////////////////////////////////////////////////////////////
+/datum/objective/maroon_organ
+ name = "maroon organ"
+ var/obj/item/organ/original_organ
+
+/datum/objective/maroon_organ/is_valid_target(datum/mind/possible_target)
+ if(iscarbon(possible_target?.current))
+ var/mob/living/carbon/possible_carbon_target = possible_target.current
+ return LAZYLEN(possible_carbon_target.internal_organs)
+
+/datum/objective/maroon_organ/finalize()
+ find_target()
+ if(!target)
+ return FALSE
+
+ // This will always be a carbon with organs, because of is_valid_target()
+ var/mob/living/carbon/carbon_target = target.current
+ var/list/eligible_organs = LAZYCOPY(carbon_target.internal_organs) //make a copy so we don't accidentally remove their brain
+ for(var/thing in eligible_organs)
+ if(istype(thing, /obj/item/organ/brain)) //make sure it doesn't pick the brain
+ eligible_organs -= thing
+ original_organ = pick(eligible_organs)
+ if(original_organ)
+ update_explanation_text()
+ return TRUE
+
+/datum/objective/maroon_organ/update_explanation_text()
+ if(target && original_organ)
+ var/mob/living/carbon/human/H = target.current
+ explanation_text = "Ensure that [target.name], the [isipc(H) ? H.dna.species.name : lowertext(H.dna.species.name)] [target.assigned_role] does not escape alive with their original [original_organ]."
+ else
+ explanation_text = "Free Objective"
+ . = ..()
+
+/datum/objective/maroon_organ/admin_edit(mob/admin)
+ finalize()
+ update_explanation_text()
+ return
+
+/datum/objective/maroon_organ/check_completion()
+ if(..())
+ return TRUE
+
+ //if they're considered marooned
+ if(!target || !considered_alive(target) || (!target.current.onCentCom() && !target.current.onSyndieBase()))
+ return TRUE
+
+ var/mob/living/carbon/carbon_target = target.current
+ //if they don't have the original organ inside them
+ if(carbon_target && istype(carbon_target) && (original_organ in carbon_target.internal_organs))
+ return FALSE
+ return TRUE
diff --git a/code/game/gamemodes/revolution/revolution.dm b/code/game/gamemodes/revolution/revolution.dm
index bff987ecc363..fa74510a48aa 100644
--- a/code/game/gamemodes/revolution/revolution.dm
+++ b/code/game/gamemodes/revolution/revolution.dm
@@ -19,7 +19,7 @@
false_report_weight = 10
restricted_jobs = list("Security Officer", "Warden", "Detective", "AI", "Cyborg", "Captain", "Head of Personnel", "Head of Security", "Chief Engineer", "Research Director", "Chief Medical Officer", "Shaft Miner", "Mining Medic", "Brig Physician") //Yogs: Added Brig Physician
required_jobs = list(list("Captain"=1),list("Head of Personnel"=1),list("Head of Security"=1),list("Chief Engineer"=1),list("Research Director"=1),list("Chief Medical Officer"=1)) //Any head present
- required_players = 30
+ required_players = 25
required_enemies = 2
recommended_enemies = 3
enemy_minimum_age = 14
diff --git a/code/game/gamemodes/zombie/zombie.dm b/code/game/gamemodes/zombie/zombie.dm
index 0e3ca3d87003..2c0a7df3962b 100644
--- a/code/game/gamemodes/zombie/zombie.dm
+++ b/code/game/gamemodes/zombie/zombie.dm
@@ -98,7 +98,7 @@ GLOBAL_LIST_EMPTY(zombies)
/datum/game_mode/zombie/proc/call_shuttle()
priority_announce("Foreign Biosignatures present onboard station. Activating automatic evacuation system...")
- SSshuttle.emergencyNoRecall = TRUE
+ SSshuttle.emergency_no_recall = TRUE
if(EMERGENCY_IDLE_OR_RECALLED)
SSshuttle.emergency.request(null, set_coefficient=0.5)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index e1f86a50aff9..32ac40900ca3 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -95,6 +95,7 @@ Class Procs:
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
var/stat = 0
var/use_power = IDLE_POWER_USE
@@ -144,6 +145,7 @@ Class Procs:
if(!armor)
armor = list(MELEE = 25, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 50, ACID = 70)
. = ..()
+ SSmachines.register_machine(src)
GLOB.machines += src
if(ispath(circuit, /obj/item/circuitboard))
@@ -157,6 +159,8 @@ Class Procs:
if (occupant_typecache)
occupant_typecache = typecacheof(occupant_typecache)
+
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_MACHINE, src)
return INITIALIZE_HINT_LATELOAD
@@ -167,6 +171,7 @@ Class Procs:
/obj/machinery/Destroy(force=FALSE)
disconnect_from_network()
+ SSmachines.unregister_machine(src)
GLOB.machines.Remove(src)
if(!speed_process)
STOP_PROCESSING(SSmachines, src)
@@ -194,14 +199,25 @@ Class Procs:
use_power(750 * severity)
new /obj/effect/temp_visual/emp(loc)
-/obj/machinery/proc/open_machine(drop = TRUE)
+/**
+ * Opens the machine.
+ *
+ * Will update the machine icon and any user interfaces currently open.
+ * Arguments:
+ * * drop - Boolean. Whether to drop any stored items in the machine. Does not include components.
+ * * density - Boolean. Whether to make the object dense when it's open.
+ */
+/obj/machinery/proc/open_machine(drop = TRUE, density_to_set = FALSE)
state_open = TRUE
- density = FALSE
+ set_density(density_to_set)
if(drop)
dropContents()
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateUsrDialog()
+/**
+ * Drop every movable atom in the machine's contents list, including any components and circuit.
+ */
/obj/machinery/proc/dropContents(list/subset = null)
var/turf/T = get_turf(src)
for(var/atom/movable/A in contents)
@@ -211,7 +227,7 @@ Class Procs:
if(isliving(A))
var/mob/living/L = A
L.update_mobility()
- occupant = null
+ set_occupant(null)
/obj/machinery/proc/can_be_occupant(atom/movable/am)
return occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am)
@@ -234,10 +250,10 @@ Class Procs:
var/mob/living/mobtarget = target
if(target && !target.has_buckled_mobs() && (!isliving(target) || !mobtarget.buckled))
- occupant = target
+ set_occupant(target)
target.forceMove(src)
updateUsrDialog()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/proc/auto_use_power()
if(!powered(power_channel))
@@ -298,6 +314,11 @@ Class Procs:
if(is_species(L, /datum/species/lizard/ashwalker))
return FALSE // ashwalkers cant use modern machines
+ //YOGS EDIT BEGIN
+ if(is_species(L, /datum/species/pod/ivymen))
+ return FALSE // same as ivymen
+ //YOGS EDIT END
+
var/mob/living/carbon/H = user
if(istype(H) && H.has_dna())
if(!Adjacent(user) && !H.dna.check_mutation(TK))
@@ -454,7 +475,7 @@ Class Procs:
if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1))
stat |= BROKEN
SEND_SIGNAL(src, COMSIG_MACHINERY_BROKEN, damage_flag)
- update_appearance(UPDATE_ICON)
+ update_appearance()
return TRUE
return FALSE
@@ -464,8 +485,8 @@ Class Procs:
/obj/machinery/handle_atom_del(atom/A)
if(A == occupant)
- occupant = null
- update_appearance(UPDATE_ICON)
+ set_occupant(null)
+ update_appearance()
updateUsrDialog()
/obj/machinery/proc/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I)
@@ -629,10 +650,11 @@ Class Procs:
if(prob(40))
emp_act(EMP_LIGHT)
-/obj/machinery/Exited(atom/movable/AM, atom/newloc)
+/obj/machinery/Exited(atom/movable/gone, atom/newloc)
. = ..()
- if (AM == occupant)
- occupant = null
+ if (gone == occupant)
+ set_occupant(null)
+ update_appearance()
/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8
var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot.
@@ -686,3 +708,26 @@ Class Procs:
/obj/machinery/proc/set_occupant(atom/movable/new_occupant)
SHOULD_CALL_PARENT(TRUE)
+
+ SEND_SIGNAL(src, COMSIG_MACHINERY_SET_OCCUPANT, new_occupant)
+ occupant = new_occupant
+
+///Get a valid powered area to reference for power use, mainly for wall-mounted machinery that isn't always mapped directly in a powered location.
+/obj/machinery/proc/get_room_area()
+ var/area/machine_area = get_area(src)
+ if(isnull(machine_area))
+ return null // ??
+
+ // check our own loc first to see if its a powered area
+ if(!machine_area.always_unpowered)
+ return machine_area
+
+ // loc area wasn't good, checking adjacent wall for a good area to use
+ var/turf/mounted_wall = get_step(src, dir)
+ if(isclosedturf(mounted_wall))
+ var/area/wall_area = get_area(mounted_wall)
+ if(!wall_area.always_unpowered)
+ return wall_area
+
+ // couldn't find a proper powered area on loc or adjacent wall, defaulting back to loc and blaming mappers
+ return machine_area
diff --git a/code/game/machinery/airlock_control.dm b/code/game/machinery/airlock_control.dm
index 52a878a1de2d..01b006944a8b 100644
--- a/code/game/machinery/airlock_control.dm
+++ b/code/game/machinery/airlock_control.dm
@@ -25,21 +25,21 @@
if("unlock")
locked = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
if("lock")
locked = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
if("secure_open")
locked = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
sleep(0.2 SECONDS)
open(1)
locked = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
if("secure_close")
locked = FALSE
@@ -47,7 +47,7 @@
locked = TRUE
sleep(0.2 SECONDS)
- update_appearance(UPDATE_ICON)
+ update_appearance()
send_status()
@@ -151,7 +151,7 @@
radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, filter = RADIO_AIRLOCK)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/airlock_sensor/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
diff --git a/code/game/machinery/airlock_cycle_control.dm b/code/game/machinery/airlock_cycle_control.dm
index 7a142582f6f5..f0e9f9d19f64 100644
--- a/code/game/machinery/airlock_cycle_control.dm
+++ b/code/game/machinery/airlock_cycle_control.dm
@@ -113,12 +113,12 @@
qdel(wires)
wires = null
cut_links()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
return ..()
/obj/machinery/advanced_airlock_controller/Initialize(mapload)
. = ..()
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
scan_on_late_init = mapload
if(mapload && (. != INITIALIZE_HINT_QDEL))
return INITIALIZE_HINT_LATELOAD
@@ -142,7 +142,7 @@
if(environment)
pressure = environment.return_pressure()
var/maxpressure = (exterior_pressure && (cyclestate == AIRLOCK_CYCLESTATE_OUTCLOSING || cyclestate == AIRLOCK_CYCLESTATE_OUTOPENING || cyclestate == AIRLOCK_CYCLESTATE_OUTOPEN)) ? exterior_pressure : interior_pressure
- var/pressure_bars = round(pressure / maxpressure * 5 + 0.01)
+ var/pressure_bars = ROUND_UP(pressure / maxpressure * 5 + 0.01)
var/new_overlays_hash = "[pressure_bars]-[cyclestate]-[buildstage]-[panel_open]-[stat]-[shorted]-[locked]-\ref[vis_target]"
if(use_hash && new_overlays_hash == overlays_hash)
@@ -182,7 +182,7 @@
var/matrix/TR = new
TR.Translate(0, 16)
TR.Multiply(new /matrix(s_dx, f_dx, 0, s_dy, f_dy, 0))
- var/mutable_appearance/M = mutable_appearance(icon, "hologram-line", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE)
+ var/mutable_appearance/M = mutable_appearance(icon, "hologram-line", ABOVE_LIGHTING_PLANE)
M.transform = TR
add_overlay(M)
@@ -536,7 +536,7 @@
for(var/I = 1; I <= turfs.len; I++)
var/turf/open/T = turfs[I]
if(assume_roles)
- T.ImmediateCalculateAdjacentTurfs()
+ T.immediate_calculate_adjacent_turfs()
for(var/turf/open/T2 in T.atmos_adjacent_turfs)
if(get_dist(initial_turf, T2) > 5)
config_error_str = "Airlock too big"
diff --git a/code/game/machinery/barsigns.dm b/code/game/machinery/barsigns.dm
new file mode 100644
index 000000000000..39fdbddd72eb
--- /dev/null
+++ b/code/game/machinery/barsigns.dm
@@ -0,0 +1,497 @@
+/obj/machinery/barsign // All Signs are 64 by 32 pixels, they take two tiles
+ name = "bar sign"
+ desc = "A bar sign which has not been initialized, somehow. Complain at a coder!"
+ icon = 'icons/obj/barsigns.dmi'
+ icon_state = "empty"
+ req_access = list(ACCESS_BAR)
+ max_integrity = 500
+ integrity_failure = 0.5
+ /// Selected barsign being used
+ var/datum/barsign/chosen_sign
+ /// Do we attempt to rename the area we occupy when the chosen sign is changed?
+ var/change_area_name = FALSE
+ /// What kind of sign do we drop upon being disassembled?
+ var/disassemble_result = /obj/item/wallframe/barsign
+
+/datum/armor/sign_barsign
+ melee = 20
+ bullet = 20
+ laser = 20
+ energy = 100
+ fire = 50
+ acid = 50
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/barsign, 32)
+
+/obj/machinery/barsign/Initialize(mapload)
+ . = ..()
+ //Roundstart/map specific barsigns "belong" in their area and should be renaming it, signs created from wallmounts will not.
+ change_area_name = mapload
+ set_sign(new /datum/barsign/hiddensigns/signoff)
+ find_and_hang_on_wall()
+
+/obj/machinery/barsign/proc/set_sign(datum/barsign/sign)
+ if(!istype(sign))
+ return
+
+ if(change_area_name && sign.rename_area)
+ rename_area(src, sign.name)
+
+ chosen_sign = sign
+ update_appearance()
+
+/obj/machinery/barsign/update_icon_state()
+ if(!(stat & BROKEN) && (!(stat & NOPOWER) || stat & EMPED) && chosen_sign && chosen_sign.icon_state)
+ icon_state = chosen_sign.icon_state
+ else
+ icon_state = "empty"
+
+ return ..()
+
+/obj/machinery/barsign/update_desc()
+ . = ..()
+
+ if(chosen_sign && chosen_sign.desc)
+ desc = chosen_sign.desc
+
+/obj/machinery/barsign/update_name()
+ . = ..()
+ if(chosen_sign && chosen_sign.rename_area)
+ name = "[initial(name)] ([chosen_sign.name])"
+ else
+ name = "[initial(name)]"
+
+/obj/machinery/barsign/update_overlays()
+ . = ..()
+
+ if(((stat & NOPOWER) && !(stat & EMPED)) || (stat & BROKEN))
+ return
+
+ if(chosen_sign && chosen_sign.light_mask)
+ . += emissive_appearance(icon, "[chosen_sign.icon_state]-light-mask", src)
+
+/obj/machinery/barsign/update_appearance(updates=ALL)
+ . = ..()
+ if(stat & (NOPOWER|BROKEN))
+ set_light(0)
+ return
+ if(chosen_sign && chosen_sign.neon_color)
+ set_light(MINIMUM_USEFUL_LIGHT_RANGE, 0.7, chosen_sign.neon_color)
+
+/obj/machinery/barsign/proc/set_sign_by_name(sign_name)
+ for(var/datum/barsign/sign as anything in subtypesof(/datum/barsign))
+ if(initial(sign.name) == sign_name)
+ var/new_sign = new sign
+ set_sign(new_sign)
+
+/obj/machinery/barsign/deconstruct(disassembled = TRUE)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ if(disassembled)
+ new disassemble_result(drop_location())
+ else
+ new /obj/item/stack/sheet/metal(drop_location(), 2)
+ new /obj/item/stack/cable_coil(drop_location(), 2)
+
+ qdel(src)
+
+/obj/machinery/barsign/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
+ switch(damage_type)
+ if(BRUTE)
+ playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE)
+ if(BURN)
+ playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE)
+
+/obj/machinery/barsign/attack_ai(mob/user)
+ return attack_hand(user)
+
+/obj/machinery/barsign/attack_hand(mob/user, list/modifiers)
+ . = ..()
+ if(.)
+ return
+ if(!allowed(user))
+ balloon_alert(user, "access denied!")
+ return
+ if(stat & (NOPOWER|BROKEN|EMPED))
+ balloon_alert(user, "controls are unresponsive!")
+ return
+ pick_sign(user)
+
+/obj/machinery/barsign/screwdriver_act(mob/living/user, obj/item/tool)
+ tool.play_tool_sound(src)
+ panel_open = !panel_open
+ if(panel_open)
+ balloon_alert(user, "panel opened")
+ set_sign(new /datum/barsign/hiddensigns/signoff)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+ balloon_alert(user, "panel closed")
+
+ if(stat & (NOPOWER|BROKEN) || !chosen_sign)
+ set_sign(new /datum/barsign/hiddensigns/signoff)
+ else
+ set_sign(chosen_sign)
+
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+/obj/machinery/barsign/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(!panel_open)
+ balloon_alert(user, "open the panel first!")
+ return FALSE
+
+ tool.play_tool_sound(src)
+ if(!do_after(user, (10 SECONDS), target = src))
+ return FALSE
+
+ tool.play_tool_sound(src)
+ deconstruct(disassembled = TRUE)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+
+/obj/machinery/barsign/attackby(obj/item/attacking_item, mob/user)
+
+ if(istype(attacking_item, /obj/item/areaeditor/blueprints) && !change_area_name)
+ if(!panel_open)
+ balloon_alert(user, "open the panel first!")
+ return TRUE
+
+ change_area_name = TRUE
+ balloon_alert(user, "sign registered")
+ return TRUE
+
+ return ..()
+
+
+/obj/machinery/barsign/emp_act(severity)
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ set_sign(new /datum/barsign/hiddensigns/empbarsign)
+
+/obj/machinery/barsign/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(stat & (NOPOWER|BROKEN|EMPED))
+ balloon_alert(user, "controls are unresponsive!")
+ return FALSE
+
+ balloon_alert(user, "illegal barsign loaded")
+ addtimer(CALLBACK(src, PROC_REF(finish_emag_act)), 10 SECONDS)
+ return TRUE
+
+/// Timer proc, called after ~10 seconds after [emag_act], since [emag_act] returns a value and cannot sleep
+/obj/machinery/barsign/proc/finish_emag_act()
+ set_sign(new /datum/barsign/hiddensigns/syndibarsign)
+
+/obj/machinery/barsign/proc/pick_sign(mob/user)
+ var/picked_name = tgui_input_list(user, "Available Signage", "Bar Sign", sort_list(get_bar_names()))
+ if(isnull(picked_name))
+ return
+ set_sign_by_name(picked_name)
+ SSblackbox.record_feedback("tally", "barsign_picked", 1, chosen_sign.type)
+
+/proc/get_bar_names()
+ var/list/names = list()
+ for(var/d in subtypesof(/datum/barsign))
+ var/datum/barsign/D = d
+ if(!initial(D.hidden))
+ names += initial(D.name)
+ . = names
+
+/datum/barsign
+ /// User-visible name of the sign.
+ var/name
+ /// Icon state associated with this sign
+ var/icon_state
+ /// Description shown in the sign's examine text.
+ var/desc
+ /// Hidden from list of selectable options.
+ var/hidden = FALSE
+ /// Rename the area when this sign is selected.
+ var/rename_area = TRUE
+ /// If a barsign has a light mask for emission effects
+ var/light_mask = TRUE
+ /// The emission color of the neon light
+ var/neon_color
+
+/datum/barsign/New()
+ if(!desc)
+ desc = "It displays \"[name]\"."
+
+// Specific bar signs.
+
+/datum/barsign/maltesefalcon
+ name = "Maltese Falcon"
+ icon_state = "maltesefalcon"
+ desc = "The Maltese Falcon, Space Bar and Grill."
+ neon_color = "#5E8EAC"
+
+/datum/barsign/thebark
+ name = "The Bark"
+ icon_state = "thebark"
+ desc = "Ian's bar of choice."
+ neon_color = "#f7a604"
+
+/datum/barsign/harmbaton
+ name = "The Harmbaton"
+ icon_state = "theharmbaton"
+ desc = "A great dining experience for both security members and assistants."
+ neon_color = "#ff7a4d"
+
+/datum/barsign/thesingulo
+ name = "The Singulo"
+ icon_state = "thesingulo"
+ desc = "Where people go that'd rather not be called by their name."
+ neon_color = "#E600DB"
+
+/datum/barsign/thedrunkcarp
+ name = "The Drunk Carp"
+ icon_state = "thedrunkcarp"
+ desc = "Don't drink and swim."
+ neon_color = "#a82196"
+
+/datum/barsign/scotchservinwill
+ name = "Scotch Servin Willy's"
+ icon_state = "scotchservinwill"
+ desc = "Willy sure moved up in the world from clown to bartender."
+ neon_color = "#fee4bf"
+
+/datum/barsign/officerbeersky
+ name = "Officer Beersky's"
+ icon_state = "officerbeersky"
+ desc = "Man eat a dong, these drinks are great."
+ neon_color = "#16C76B"
+
+/datum/barsign/thecavern
+ name = "The Cavern"
+ icon_state = "thecavern"
+ desc = "Fine drinks while listening to some fine tunes."
+ neon_color = "#0fe500"
+
+/datum/barsign/theouterspess
+ name = "The Outer Spess"
+ icon_state = "theouterspess"
+ desc = "This bar isn't actually located in outer space."
+ neon_color = "#30f3cc"
+
+/datum/barsign/slipperyshots
+ name = "Slippery Shots"
+ icon_state = "slipperyshots"
+ desc = "Slippery slope to drunkeness with our shots!"
+ neon_color = "#70DF00"
+
+/datum/barsign/thegreytide
+ name = "The Grey Tide"
+ icon_state = "thegreytide"
+ desc = "Abandon your toolboxing ways and enjoy a lazy beer!"
+ neon_color = "#00F4D6"
+
+/datum/barsign/honkednloaded
+ name = "Honked 'n' Loaded"
+ icon_state = "honkednloaded"
+ desc = "Honk."
+ neon_color = "#FF998A"
+
+/datum/barsign/le_cafe_silencieux
+ name = "Le Café Silencieux"
+ icon_state = "le_cafe_silencieux"
+ desc = "..."
+ neon_color = "#ffffff"
+
+/datum/barsign/thenest
+ name = "The Nest"
+ icon_state = "thenest"
+ desc = "A good place to retire for a drink after a long night of crime fighting."
+ neon_color = "#4d6796"
+
+/datum/barsign/thecoderbus
+ name = "The Coderbus"
+ icon_state = "thecoderbus"
+ desc = "A very controversial bar known for its wide variety of constantly-changing drinks."
+ neon_color = "#ffffff"
+
+/datum/barsign/theadminbus
+ name = "The Adminbus"
+ icon_state = "theadminbus"
+ desc = "An establishment visited mainly by space-judges. It isn't bombed nearly as much as court hearings."
+ neon_color = "#ffffff"
+
+/datum/barsign/oldcockinn
+ name = "The Old Cock Inn"
+ icon_state = "oldcockinn"
+ desc = "Something about this sign fills you with despair."
+ neon_color = "#a4352b"
+
+/datum/barsign/thewretchedhive
+ name = "The Wretched Hive"
+ icon_state = "thewretchedhive"
+ desc = "Legally obligated to instruct you to check your drinks for acid before consumption."
+ neon_color = "#26b000"
+
+/datum/barsign/robustacafe
+ name = "The Robusta Cafe"
+ icon_state = "robustacafe"
+ desc = "Holder of the 'Most Lethal Barfights' record 5 years uncontested."
+ neon_color = "#c45f7a"
+
+/datum/barsign/emergencyrumparty
+ name = "The Emergency Rum Party"
+ icon_state = "emergencyrumparty"
+ desc = "Recently relicensed after a long closure."
+ neon_color = "#f90011"
+
+/datum/barsign/combocafe
+ name = "The Combo Cafe"
+ icon_state = "combocafe"
+ desc = "Renowned system-wide for their utterly uncreative drink combinations."
+ neon_color = "#33ca40"
+
+/datum/barsign/vladssaladbar
+ name = "Vlad's Salad Bar"
+ icon_state = "vladssaladbar"
+ desc = "Under new management. Vlad was always a bit too trigger happy with that shotgun."
+ neon_color = "#306900"
+
+/datum/barsign/theshaken
+ name = "The Shaken"
+ icon_state = "theshaken"
+ desc = "This establishment does not serve stirred drinks."
+ neon_color = "#dcd884"
+
+/datum/barsign/thealenath
+ name = "The Ale' Nath"
+ icon_state = "thealenath"
+ desc = "All right, buddy. I think you've had EI NATH. Time to get a cab."
+ neon_color = "#ed0000"
+
+/datum/barsign/thealohasnackbar
+ name = "The Aloha Snackbar"
+ icon_state = "alohasnackbar"
+ desc = "A tasteful, inoffensive tiki bar sign."
+ neon_color = ""
+
+/datum/barsign/thenet
+ name = "The Net"
+ icon_state = "thenet"
+ desc = "You just seem to get caught up in it for hours."
+ neon_color = "#0e8a00"
+
+/datum/barsign/maidcafe
+ name = "Maid Cafe"
+ icon_state = "maidcafe"
+ desc = "Welcome back, master!"
+ neon_color = "#ff0051"
+
+/datum/barsign/the_lightbulb
+ name = "The Lightbulb"
+ icon_state = "the_lightbulb"
+ desc = "A cafe popular among moths and moffs. Once shut down for a week after the bartender used mothballs to protect her spare uniforms."
+ neon_color = "#faff82"
+
+/datum/barsign/goose
+ name = "The Loose Goose"
+ icon_state = "goose"
+ desc = "Drink till you puke and/or break the laws of reality!"
+ neon_color = "#00cc33"
+
+/datum/barsign/maltroach
+ name = "Maltroach"
+ icon_state = "maltroach"
+ desc = "Mothroaches politely greet you into the bar, or are they greeting eachother?"
+ neon_color = "#649e8a"
+
+/datum/barsign/rock_bottom
+ name = "Rock Bottom"
+ icon_state = "rock-bottom"
+ desc = "When it feels like you're stuck in a pit, might as well have a drink."
+ neon_color = "#aa2811"
+
+/datum/barsign/tearoom
+ name = "Little Treats Tea Room"
+ icon_state = "little_treats"
+ desc = "A delightfully relaxing tearoom for all the fancy lads in the cosmos."
+ neon_color = LIGHT_COLOR_ORANGE
+
+/datum/barsign/assembly_line
+ name = "The Assembly Line"
+ icon_state = "the-assembly-line"
+ desc = "Where every drink is masterfully crafted with industrial efficiency!"
+ neon_color = "#ffffff"
+
+/datum/barsign/bargonia
+ name = "Bargonia"
+ icon_state = "bargonia"
+ desc = "The warehouse yearns for a higher calling... so Supply has declared BARGONIA!"
+ neon_color = COLOR_WHITE
+
+/datum/barsign/cult_cove
+ name = "Cult Cove"
+ icon_state = "cult-cove"
+ desc = "Nar'Sie's favourite retreat"
+ neon_color = COLOR_RED
+
+/datum/barsign/neon_flamingo
+ name = "Neon Flamingo"
+ icon_state = "neon-flamingo"
+ desc = "A bus for all but the flamboyantly challenged."
+ neon_color = COLOR_PINK
+
+/datum/barsign/slowdive
+ name = "Slowdive"
+ icon_state = "slowdive"
+ desc = "First stop out of hell, last stop before heaven."
+ neon_color = COLOR_RED
+
+// Hidden signs list below this point
+
+/datum/barsign/hiddensigns
+ hidden = TRUE
+
+/datum/barsign/hiddensigns/empbarsign
+ name = "EMP'd"
+ icon_state = "empbarsign"
+ desc = "Something has gone very wrong."
+ rename_area = FALSE
+
+/datum/barsign/hiddensigns/syndibarsign
+ name = "Syndi Cat"
+ icon_state = "syndibarsign"
+ desc = "Syndicate or die."
+ neon_color = "#ff0000"
+
+/datum/barsign/hiddensigns/signoff
+ name = "Off"
+ icon_state = "empty"
+ desc = "This sign doesn't seem to be on."
+ rename_area = FALSE
+ light_mask = FALSE
+
+// For other locations that aren't in the main bar
+/obj/machinery/barsign/all_access
+ req_access = null
+ disassemble_result = /obj/item/wallframe/barsign/all_access
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/barsign/all_access, 32)
+
+/obj/item/wallframe/barsign
+ name = "bar sign frame"
+ desc = "Used to help draw the rabble into your bar. Some assembly required."
+ icon = 'icons/obj/wallmounts.dmi'
+ icon_state = "barsign"
+ result_path = /obj/machinery/barsign
+ pixel_shift = 32
+
+/obj/item/wallframe/barsign/Initialize(mapload)
+ . = ..()
+ desc += " Can be registered with a set of [span_bold("station blueprints")] to associate the sign with the area it occupies."
+
+/obj/item/wallframe/barsign/try_build(turf/on_wall, mob/user)
+ . = ..()
+ if(!.)
+ return .
+
+ if(isopenturf(get_step(on_wall, EAST))) //This takes up 2 tiles so we want to make sure we have two tiles to hang it from.
+ balloon_alert(user, "needs more support!")
+ return FALSE
+
+/obj/item/wallframe/barsign/all_access
+ desc = "Used to help draw the rabble into your bar. Some assembly required. This one doesn't have an access lock."
+ result_path = /obj/machinery/barsign/all_access
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index f42cb3761b23..4135b3f67e81 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -10,6 +10,9 @@
var/device_type = null
var/id = null
var/initialized_button = 0
+ light_power = 0.5 // Minimums, we want the button to glow if it has a mask, not light an area
+ light_range = 1.5
+ light_color = LIGHT_COLOR_VIVID_GREEN
armor = list(MELEE = 50, BULLET = 50, LASER = 50, ENERGY = 50, BOMB = 10, BIO = 100, RAD = 100, FIRE = 90, ACID = 70)
use_power = IDLE_POWER_USE
idle_power_usage = 2
@@ -25,7 +28,7 @@
pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24)
pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0
panel_open = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(!built && !device && device_type)
@@ -52,6 +55,14 @@
else
icon_state = skin
+/obj/machinery/button/update_appearance()
+ . = ..()
+
+ if(panel_open || (stat & (NOPOWER|BROKEN)))
+ set_light(0)
+ else
+ set_light(initial(light_range), light_power, light_color)
+
/obj/machinery/button/update_overlays()
. = ..()
if(!panel_open)
@@ -65,7 +76,7 @@
if(W.tool_behaviour == TOOL_SCREWDRIVER)
if(panel_open || allowed(user))
default_deconstruction_screwdriver(user, "button-open", "[skin]",W)
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
to_chat(user, span_danger("Maintenance Access Denied"))
flick("[skin]-denied", src)
@@ -122,7 +133,7 @@
to_chat(user, span_notice("You wipe the button's ID."))
id = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON))
@@ -155,6 +166,11 @@
A.id = id
initialized_button = 1
+/obj/machinery/button/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ if(id)
+ id = "[port.shuttle_id]_[id]"
+ setup_device()
+
/obj/machinery/button/attack_hand(mob/user)
. = ..()
if(.)
@@ -173,7 +189,7 @@
req_access = list()
req_one_access = list()
board = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You remove electronics from the button frame."))
else
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index a36956f5459f..b3bbb9d8025f 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -95,7 +95,12 @@
if(mapload && is_station_level(z) && prob(3) && !start_active)
toggle_cam()
else //this is handled by toggle_camera, so no need to update it twice.
- update_appearance(UPDATE_ICON)
+ update_appearance()
+
+/obj/machinery/camera/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ for(var/i in network)
+ network -= i
+ network += "[port.shuttle_id]_[i]"
/obj/machinery/camera/Destroy()
if(can_use())
@@ -145,14 +150,14 @@
return
if(!(. & EMP_PROTECT_SELF))
if(prob(15 * severity))
- update_appearance(UPDATE_ICON)
+ update_appearance()
var/list/previous_network = network
network = list()
GLOB.cameranet.removeCamera(src)
stat |= EMPED
set_light(0)
emped = emped+1 //Increase the number of consecutive EMP's
- update_appearance(UPDATE_ICON)
+ update_appearance()
var/thisemp = emped //Take note of which EMP this proc is for
spawn(900)
if(loc) //qdel limbo
@@ -160,14 +165,14 @@
if(emped == thisemp) //Only fix it if the camera hasn't been EMP'd again
network = previous_network
stat &= ~EMPED
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(can_use())
GLOB.cameranet.addCamera(src)
emped = 0 //Resets the consecutive EMP count
addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), severity SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
for(var/i in GLOB.player_list)
var/mob/M = i
- if (M.client.eye == src)
+ if (M.client?.eye == src)
M.unset_machine()
M.reset_perspective(null)
to_chat(M, "The screen bursts into static.")
@@ -364,7 +369,7 @@
else
icon_state = "[xray_module][default_camera_icon][in_use_lights ? "_in_use" : ""]"
-/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = 1)
+/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE)
status = !status
if(can_use())
GLOB.cameranet.addCamera(src)
@@ -378,12 +383,15 @@
GLOB.cameranet.removeCamera(src)
if (isarea(myarea))
LAZYREMOVE(myarea.cameras, src)
- GLOB.cameranet.updateChunk(x, y, z)
+ // We are not guarenteed that the camera will be on a turf. account for that
+ var/turf/our_turf = get_turf(src)
+ GLOB.cameranet.updateChunk(our_turf.x, our_turf.y, our_turf.z)
var/change_msg = "deactivates"
if(status)
change_msg = "reactivates"
triggerCameraAlarm()
- addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), 100)
+ if(!QDELETED(src)) //We'll be doing it anyway in destroy
+ addtimer(CALLBACK(src, PROC_REF(cancelCameraAlarm)), 100)
if(displaymessage)
if(user)
visible_message(span_danger("[user] [change_msg] [src]!"))
@@ -392,16 +400,16 @@
visible_message(span_danger("\The [src] [change_msg]!"))
playsound(src, 'sound/items/wirecutter.ogg', 100, TRUE)
- update_appearance(UPDATE_ICON) //update Initialize(mapload) if you remove this.
+ update_appearance() //update Initialize() if you remove this.
// now disconnect anyone using the camera
//Apparently, this will disconnect anyone even if the camera was re-activated.
//I guess that doesn't matter since they can't use it anyway?
for(var/mob/O in GLOB.player_list)
- if (O.client && O.client.eye == src)
+ if (O.client?.eye == src)
O.unset_machine()
O.reset_perspective(null)
- to_chat(O, "The screen bursts into static.")
+ to_chat(O, span_warning("The screen bursts into static!"))
/obj/machinery/camera/proc/triggerCameraAlarm()
alarm_on = TRUE
@@ -423,10 +431,32 @@
/obj/machinery/camera/proc/can_see()
var/list/see = null
var/turf/pos = get_turf(src)
+ var/turf/directly_above = GET_TURF_ABOVE(pos)
+ var/check_lower = pos != get_lowest_turf(pos)
+ var/check_higher = directly_above && istransparentturf(directly_above) && (pos != get_highest_turf(pos))
+
if(isXRay())
see = range(view_range, pos)
else
see = get_hear(view_range, pos)
+ if(check_lower || check_higher)
+ // Haha datum var access KILL ME
+ for(var/turf/seen in see)
+ if(check_lower)
+ var/turf/visible = seen
+ while(visible && istransparentturf(visible))
+ var/turf/below = GET_TURF_BELOW(visible)
+ for(var/turf/adjacent in range(1, below))
+ see += adjacent
+ see += adjacent.contents
+ visible = below
+ if(check_higher)
+ var/turf/above = GET_TURF_ABOVE(seen)
+ while(above && istransparentturf(above))
+ for(var/turf/adjacent in range(1, above))
+ see += adjacent
+ see += adjacent.contents
+ above = GET_TURF_ABOVE(above)
return see
/atom/proc/auto_turn()
@@ -468,11 +498,9 @@
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
/obj/machinery/camera/update_remote_sight(mob/living/user)
- user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
+ user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras
if(isXRay())
- user.sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- user.see_in_dark = max(user.see_in_dark, 8)
+ user.add_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
else
- user.sight = 0
- user.see_in_dark = 2
+ user.clear_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
return 1
diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm
index 0976aba838e4..0c3ed0cc6b17 100644
--- a/code/game/machinery/camera/tracking.dm
+++ b/code/game/machinery/camera/tracking.dm
@@ -1,22 +1,10 @@
-/mob/living/silicon/ai/proc/get_camera_list()
- var/list/L = list()
- for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- L.Add(C)
-
- camera_sort(L)
-
- var/list/T = list()
-
- for (var/obj/machinery/camera/C in L)
- var/list/tempnetwork = C.network&src.network
- if (tempnetwork.len)
- T[text("[][]", C.c_tag, (C.can_use() ? null : " (Deactivated)"))] = C
-
- return T
-
/mob/living/silicon/ai/proc/show_camera_list()
- var/list/cameras = get_camera_list()
- var/camera = input(src, "Choose which camera you want to view", "Cameras") as null|anything in cameras
+ var/list/cameras = get_camera_list(network)
+ var/camera = tgui_input_list(src, "Choose which camera you want to view", "Cameras", cameras)
+ if(isnull(camera))
+ return
+ if(isnull(cameras[camera]))
+ return
switchCamera(cameras[camera])
/datum/trackable
@@ -138,14 +126,3 @@
return
user.switchCamera(src)
-/proc/camera_sort(list/L)
- var/obj/machinery/camera/a
- var/obj/machinery/camera/b
-
- for (var/i = L.len, i > 0, i--)
- for (var/j = 1 to i - 1)
- a = L[j]
- b = L[j + 1]
- if (sorttext(a.c_tag, b.c_tag) < 0)
- L.Swap(j, j + 1)
- return L
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index d14b5fc6095f..fe6c21824b04 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -102,7 +102,7 @@
data["patient"]["stat"] = "Dead"
data["patient"]["statstate"] = "bad"
data["patient"]["health"] = patient.health
- data["patient"]["blood_type"] = patient.dna.blood_type
+ data["patient"]["blood_type"] = patient.dna.blood_type.name
data["patient"]["maxHealth"] = patient.maxHealth
data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD
data["patient"]["bruteLoss"] = patient.getBruteLoss()
diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm
index c0077e4fa619..bf946c2be889 100644
--- a/code/game/machinery/computer/_computer.dm
+++ b/code/game/machinery/computer/_computer.dm
@@ -10,20 +10,25 @@
integrity_failure = 100
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 40, ACID = 20)
clicksound = "keyboard"
- light_system = STATIC_LIGHT
- light_range = 2
- light_power = 1
- light_on = TRUE
+ /// How bright we are when turned on.
+ var/brightness_on = 1
+ /// Icon_state of the keyboard overlay.
var/icon_keyboard = "generic_key"
+ /// Should we render an unique icon for the keyboard when off?
+ var/keyboard_change_icon = TRUE
+ /// Icon_state of the emissive screen overlay.
var/icon_screen = "generic"
- var/clockwork = FALSE
- var/time_to_scewdrive = 20
+ /// Time it takes to deconstruct with a screwdriver.
+ var/time_to_unscrew = 2 SECONDS
+ /// Are we authenticated to use this? Used by things like comms console, security and medical data, and apc controller.
var/authenticated = FALSE
+ var/clockwork = FALSE
+
/obj/machinery/computer/Initialize(mapload, obj/item/circuitboard/C)
. = ..()
if(mapload)
- update_appearance(UPDATE_ICON)
+ update_appearance()
power_change()
if(!QDELETED(C))
qdel(circuit)
@@ -36,8 +41,8 @@
/obj/machinery/computer/process()
if(stat & (NOPOWER|BROKEN))
- return 0
- return 1
+ return FALSE
+ return TRUE
/obj/machinery/computer/ratvar_act()
if(!clockwork)
@@ -45,7 +50,7 @@
icon_screen = "ratvar[rand(1, 4)]"
icon_keyboard = "ratvar_key[rand(1, 6)]"
icon_state = "ratvarcomputer[rand(1, 4)]"
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/computer/narsie_act()
if(clockwork && clockwork != initial(clockwork)) //if it's clockwork but isn't normally clockwork
@@ -53,7 +58,7 @@
icon_screen = initial(icon_screen)
icon_keyboard = initial(icon_keyboard)
icon_state = initial(icon_state)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/computer/update_appearance(updates)
. = ..()
@@ -86,33 +91,39 @@
/obj/machinery/computer/update_overlays()
. = ..()
- if(stat & NOPOWER)
- . += "[icon_keyboard]_off"
- return
- . += icon_keyboard
+ if(icon_keyboard)
+ if(keyboard_change_icon && (stat & NOPOWER))
+ . += "[icon_keyboard]_off"
+ else
+ . += icon_keyboard
- // This whole block lets screens ignore lighting and be visible even in the darkest room
- var/overlay_state = icon_screen
if(stat & BROKEN)
- overlay_state = "[icon_state]_broken"
- . += mutable_appearance(icon, overlay_state)
- . += mutable_appearance(icon, overlay_state, layer, EMISSIVE_PLANE)
+ . += mutable_appearance(icon, "[icon_state]_broken")
+ return // If we don't do this broken computers glow in the dark.
+
+ if(stat & NOPOWER) // Your screen can't be on if you've got no damn charge
+ return
+
+ // This lets screens ignore lighting and be visible even in the darkest room
+ if(icon_screen)
+ . += mutable_appearance(icon, icon_screen)
+ . += emissive_appearance(icon, icon_screen, src)
/obj/machinery/computer/power_change()
. = ..()
if(!.)
return // reduce unneeded light changes
if(stat & NOPOWER)
- set_light_on(FALSE)
+ set_light(0)
else
- set_light_on(TRUE)
+ set_light(brightness_on)
/obj/machinery/computer/screwdriver_act(mob/living/user, obj/item/I)
if(..())
return TRUE
if(circuit && !(flags_1&NODECONSTRUCT_1))
to_chat(user, span_notice("You start to disconnect the monitor..."))
- if(I.use_tool(src, user, time_to_scewdrive, volume=50))
+ if(I.use_tool(src, user, time_to_unscrew, volume=50))
deconstruct(TRUE, user)
return TRUE
@@ -173,3 +184,9 @@
. = ..()
if(istype(mover) && (mover.pass_flags & PASSCOMPUTER))
return TRUE
+
+
+/obj/machinery/computer/ui_interact(mob/user, datum/tgui/ui)
+ //SHOULD_CALL_PARENT(TRUE)
+ . = ..()
+ // update_use_power(ACTIVE_POWER_USE)
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index 34ee8b08f8f7..1fb7888d3264 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -107,7 +107,7 @@
to_chat(AI, "You have been uploaded to a stationary terminal. Sadly, there is no remote access from here.")
to_chat(user, "[span_boldnotice("Transfer successful")]: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.")
card.AI = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
else //Uploading AI from terminal to card
if(occupier && !restoring)
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 7c6a41699814..6086a909e646 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -57,7 +57,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
/obj/machinery/computer/arcade
name = "random arcade"
- desc = "random arcade machine"
+ desc = "A random arcade machine."
icon_state = "arcade"
icon_keyboard = null
icon_screen = "invaders"
@@ -579,7 +579,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
playsound(loc, 'sound/weapons/genhit.ogg', 100, 1)
var/turf/open/space/T
for(T in orange(1, src))
- T.PlaceOnTop(/turf/open/floor/plating)
+ T.place_on_top(/turf/open/floor/plating)
else
say("Something slams into the floor around [src] - luckily, it didn't get through!")
playsound(loc, 'sound/effects/bang.ogg', 50, 1)
diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm
index 7ed11ce00534..bd878d193559 100644
--- a/code/game/machinery/computer/atmos_control.dm
+++ b/code/game/machinery/computer/atmos_control.dm
@@ -74,11 +74,11 @@
/obj/machinery/air_sensor/Initialize(mapload)
. = ..()
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
set_frequency(frequency)
/obj/machinery/air_sensor/Destroy()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
SSradio.remove_object(src, frequency)
return ..()
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 43f22a4e2754..310136c98cd0 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -1,64 +1,63 @@
+#define DEFAULT_MAP_SIZE 15
+
/obj/machinery/computer/security
name = "security camera console"
desc = "Used to access the various cameras on the station."
icon_screen = "cameras"
icon_keyboard = "security_key"
circuit = /obj/item/circuitboard/computer/security
- light_color = LIGHT_COLOR_RED
+ light_color = COLOR_SOFT_RED
var/list/network = list("ss13")
+ var/obj/machinery/camera/active_camera
+ /// The turf where the camera was last updated.
+ var/turf/last_camera_turf
var/list/concurrent_users = list()
// Stuff needed to render the map
- var/map_name
- var/const/default_map_size = 15
- var/atom/movable/screen/cam_screen
+ var/atom/movable/screen/map_view/cam_screen
/// All the plane masters that need to be applied.
- var/list/cam_plane_masters
var/atom/movable/screen/background/cam_background
- var/obj/machinery/camera/active_camera
-
- var/processing = FALSE
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_REQUIRES_SIGHT
/obj/machinery/computer/security/Initialize(mapload)
. = ..()
// Map name has to start and end with an A-Z character,
// and definitely NOT with a square bracket or even a number.
// I wasted 6 hours on this. :agony:
- map_name = "camera_console_[REF(src)]_map"
+ var/map_name = "camera_console_[REF(src)]_map"
// Convert networks to lowercase
for(var/i in network)
network -= i
network += lowertext(i)
// Initialize map objects
cam_screen = new
- cam_screen.name = "screen"
- cam_screen.assigned_map = map_name
- cam_screen.del_on_map_removal = FALSE
- cam_screen.screen_loc = "[map_name]:1,1"
- cam_plane_masters = list()
- for(var/plane in subtypesof(/atom/movable/screen/plane_master))
- var/atom/movable/screen/instance = new plane()
- instance.assigned_map = map_name
- instance.del_on_map_removal = FALSE
- instance.screen_loc = "[map_name]:CENTER"
- cam_plane_masters += instance
+ cam_screen.generate_view(map_name)
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
/obj/machinery/computer/security/Destroy()
- qdel(cam_screen)
- QDEL_LIST(cam_plane_masters)
- qdel(cam_background)
+ QDEL_NULL(cam_screen)
+ QDEL_NULL(cam_background)
return ..()
+/obj/machinery/computer/security/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ for(var/i in network)
+ network -= i
+ network += "[port.shuttle_id]_[i]"
+
/obj/machinery/computer/security/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ if(!user.canUseTopic(src, no_dextery = TRUE)) //prevents monkeys from using camera consoles
+ return
+ // Update UI
ui = SStgui.try_update_ui(user, src, ui)
- // Show static if can't use the camera
- if(!active_camera?.can_use())
- show_camera_static()
+
+ // Update the camera, showing static if necessary and updating data if the location has moved.
+ update_active_camera_screen()
+
if(!ui)
var/user_ref = REF(user)
var/is_living = isliving(user)
@@ -71,90 +70,88 @@
playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE)
use_power(active_power_usage)
// Register map objects
- user.client.register_map_obj(cam_screen)
- for(var/plane in cam_plane_masters)
- user.client.register_map_obj(plane)
+ cam_screen.display_to(user)
user.client.register_map_obj(cam_background)
// Open UI
ui = new(user, src, "CameraConsole", name)
ui.open()
+/obj/machinery/computer/security/ui_status(mob/user)
+ . = ..()
+ if(. == UI_DISABLED)
+ return UI_CLOSE
+ return .
+
/obj/machinery/computer/security/ui_data()
var/list/data = list()
- data["network"] = network
data["activeCamera"] = null
if(active_camera)
data["activeCamera"] = list(
name = active_camera.c_tag,
+ ref = REF(active_camera),
status = active_camera.status,
)
return data
/obj/machinery/computer/security/ui_static_data()
var/list/data = list()
- data["mapRef"] = map_name
- var/list/cameras = get_available_cameras()
+ data["network"] = network
+ data["mapRef"] = cam_screen.assigned_map
+ var/list/cameras = get_camera_list(network)
data["cameras"] = list()
for(var/i in cameras)
var/obj/machinery/camera/C = cameras[i]
data["cameras"] += list(list(
name = C.c_tag,
+ ref = REF(C),
))
- return data
-/obj/machinery/computer/security/process()
- if(active_camera && active_camera.built_in)
- if(!active_camera?.can_use())
- show_camera_static()
- return TRUE
- update_camera(active_camera)
- else
- STOP_PROCESSING(SSfastprocess, src)
- processing = FALSE
- ..()
+ return data
/obj/machinery/computer/security/ui_act(action, params)
. = ..()
if(.)
return
-
+
if(action == "switch_camera")
- var/c_tag = params["name"]
- var/list/cameras = get_available_cameras()
- var/obj/machinery/camera/C = cameras[c_tag]
- active_camera = C
- playsound(src, get_sfx("terminal_type"), 25, FALSE)
-
- // Show static if can't use the camera
- if(!active_camera?.can_use())
- show_camera_static()
- return TRUE
-
+ var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras
+ active_camera = selected_camera
+ playsound(src, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE)
- //Assume it's a moving camera.
- if(C.built_in)
- if(!processing)
- START_PROCESSING(SSfastprocess, src)
- processing = TRUE
- else
- STOP_PROCESSING(SSfastprocess, src)
- processing = FALSE
+ if(isnull(active_camera))
+ return TRUE
- update_camera(C)
+ update_active_camera_screen()
return TRUE
-/obj/machinery/computer/security/proc/update_camera(obj/machinery/camera/C)
- var/originator = C
- if(C.built_in)
- originator = get_turf(C.built_in)
+/obj/machinery/computer/security/proc/update_active_camera_screen()
+ // Show static if can't use the camera
+ if(!active_camera?.can_use())
+ show_camera_static()
+ return
var/list/visible_turfs = list()
- for(var/turf/T in (C.isXRay() \
- ? range(C.view_range, originator) \
- : view(C.view_range, originator)))
- visible_turfs += T
+ // Get the camera's turf to correctly gather what's visible from it's turf, in case it's located in a moving object (borgs / mechs)
+ var/new_cam_turf = get_turf(active_camera)
+
+ // If we're not forcing an update for some reason and the cameras are in the same location,
+ // we don't need to update anything.
+ // Most security cameras will end here as they're not moving.
+ if(last_camera_turf == new_cam_turf)
+ return
+
+ // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs.
+ last_camera_turf = new_cam_turf
+
+ //Here we gather what's visible from the camera's POV based on its view_range and xray modifier if present
+ var/list/visible_things = active_camera.isXRay(ignore_malf_upgrades = TRUE) ? range(active_camera.view_range, new_cam_turf) : view(active_camera.view_range, new_cam_turf)
+
+ for(var/turf/visible_turf in visible_things)
+ visible_turfs += visible_turf
+
+ //Get coordinates for a rectangle area that contains the turfs we see so we can then clear away the static in the resulting rectangle area
var/list/bbox = get_bbox_of_atoms(visible_turfs)
var/size_x = bbox[3] - bbox[1] + 1
var/size_y = bbox[4] - bbox[2] + 1
@@ -164,44 +161,24 @@
cam_background.fill_rect(1, 1, size_x, size_y)
/obj/machinery/computer/security/ui_close(mob/user)
+ . = ..()
var/user_ref = REF(user)
var/is_living = isliving(user)
// Living creature or not, we remove you anyway.
concurrent_users -= user_ref
// Unregister map objects
- user.client.clear_map(map_name)
+ cam_screen.hide_from(user)
// Turn off the console
if(length(concurrent_users) == 0 && is_living)
active_camera = null
+ last_camera_turf = null
playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
use_power(0)
/obj/machinery/computer/security/proc/show_camera_static()
cam_screen.vis_contents.Cut()
cam_background.icon_state = "scanline2"
- cam_background.fill_rect(1, 1, default_map_size, default_map_size)
-
-//returns the list of cameras accessible from this computer
-/obj/machinery/computer/security/proc/get_available_cameras()
- var/list/L = list()
- for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras
- continue
- L.Add(C)
-
- var/list/D = list()
-
- for(var/obj/machinery/camera/C in L)
- if(!C.network)
- stack_trace("Camera in a cameranet has no camera network")
- continue
- if(!(islist(C.network)))
- stack_trace("Camera in a cameranet has a non-list camera network")
- continue
- var/list/tempnetwork = C.network & network
- if(tempnetwork.len)
- D["[C.c_tag]"] = C
- return D
+ cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE)
// SECURITY MONITORS
@@ -242,17 +219,18 @@
/obj/machinery/computer/security/qm
name = "\improper Quartermaster's camera console"
- desc = "A console with access to the mining, auxillary base and vault camera networks."
+ desc = "A console with access to the mining, auxiliary base and vault camera networks."
network = list("mine", "auxbase", "vault")
circuit = /obj/item/circuitboard/computer/security/qm
// TELESCREENS
-
/obj/machinery/computer/security/telescreen
name = "\improper Telescreen"
desc = "Used for watching an empty arena."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "telescreen"
+ icon_keyboard = null
+ icon_screen = null
layer = SIGN_LAYER
network = list("thunder")
density = FALSE
@@ -361,3 +339,5 @@
name = "\improper AI upload monitor"
desc = "A telescreen that connects to the AI upload's camera network."
network = list("aiupload")
+
+#undef DEFAULT_MAP_SIZE
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 3788a9b0e816..b0339d21b12e 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -3,19 +3,28 @@
desc = "Used to access the various cameras on the station."
icon_screen = "cameras"
icon_keyboard = "security_key"
+ light_color = COLOR_SOFT_RED
var/list/z_lock = list() // Lock use to these z levels
var/lock_override = NONE
- var/mob/camera/aiEye/remote/eyeobj
+ var/mob/camera/ai_eye/remote/eyeobj
var/mob/living/current_user = null
var/list/networks = list("ss13")
- var/datum/action/innate/camera_off/off_action = new
- var/datum/action/innate/camera_jump/jump_action = new
+ /// Typepath of the action button we use as "off"
+ /// It's a typepath so subtypes can give it fun new names
+ var/datum/action/innate/camera_off/off_action = /datum/action/innate/camera_off
+ /// Typepath for jumping
+ var/datum/action/innate/camera_jump/jump_action = /datum/action/innate/camera_jump
+ /// Typepath of the move up action
+ var/datum/action/innate/camera_multiz_up/move_up_action = /datum/action/innate/camera_multiz_up
+ /// Typepath of the move down action
+ var/datum/action/innate/camera_multiz_down/move_down_action = /datum/action/innate/camera_multiz_down
+
+ /// List of all actions to give to a user when they're well, granted actions
var/list/actions = list()
-
///Should we supress any view changes?
- var/should_supress_view_changes = TRUE
+ var/should_supress_view_changes = TRUE
- light_color = LIGHT_COLOR_RED
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_REQUIRES_SIGHT
/obj/machinery/computer/camera_advanced/Initialize(mapload)
. = ..()
@@ -29,107 +38,116 @@
z_lock |= SSmapping.levels_by_trait(ZTRAIT_MINING)
if(lock_override & CAMERA_LOCK_CENTCOM)
z_lock |= SSmapping.levels_by_trait(ZTRAIT_CENTCOM)
- if(lock_override & CAMERA_LOCK_REEBE)
- z_lock |= SSmapping.levels_by_trait(ZTRAIT_REEBE)
+
+ if(off_action)
+ actions += new off_action(src)
+ if(jump_action)
+ actions += new jump_action(src)
+ //Camera action button to move up a Z level
+ if(move_up_action)
+ actions += new move_up_action(src)
+ //Camera action button to move down a Z level
+ if(move_down_action)
+ actions += new move_down_action(src)
+
+/obj/machinery/computer/camera_advanced/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ for(var/i in networks)
+ networks -= i
+ networks += "[port.shuttle_id]_[i]"
/obj/machinery/computer/camera_advanced/syndie
icon_keyboard = "syndie_key"
+/obj/machinery/computer/camera_advanced/syndie/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ return //For syndie nuke shuttle, to spy for station.
+
/obj/machinery/computer/camera_advanced/proc/CreateEye()
eyeobj = new()
eyeobj.origin = src
/obj/machinery/computer/camera_advanced/proc/GrantActions(mob/living/user)
- if(off_action)
- off_action.target = user
- off_action.Grant(user)
- actions += off_action
-
- if(jump_action)
- jump_action.target = user
- jump_action.Grant(user)
- actions += jump_action
+ for(var/datum/action/to_grant as anything in actions)
+ to_grant.Grant(user)
/obj/machinery/proc/remove_eye_control(mob/living/user)
CRASH("[type] does not implement ai eye handling")
/obj/machinery/computer/camera_advanced/remove_eye_control(mob/living/user)
- if(!user)
+ if(isnull(user?.client))
return
- for(var/datum/action/A as anything in actions)
- A.Remove(user)
- actions.Cut()
- for(var/datum/camerachunk/C as anything in eyeobj.visibleCameraChunks)
- C.remove(eyeobj)
- if(user.client)
- user.reset_perspective(null)
- if(eyeobj.visible_icon && user.client)
- user.client.images -= eyeobj.user_image
+
+ for(var/datum/action/actions_removed as anything in actions)
+ actions_removed.Remove(user)
+ for(var/datum/camerachunk/camerachunks_gone as anything in eyeobj.visibleCameraChunks)
+ camerachunks_gone.remove(eyeobj)
+
+ user.reset_perspective(null)
+ if(eyeobj.visible_icon)
+ user.client.images -= eyeobj.user_image
+ user.client.view_size.unsupress()
+
eyeobj.eye_user = null
user.remote_control = null
-
current_user = null
- user.unset_machine()
- user.client.view_size.unsupress()
- playsound(src, 'sound/machines/terminal_off.ogg', 25, 0)
+ unset_machine(user)
+ playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
/obj/machinery/computer/camera_advanced/check_eye(mob/user)
- if( (stat & (NOPOWER|BROKEN)) || (!Adjacent(user) && !user.has_unlimited_silicon_privilege) || user.eye_blind || user.incapacitated() )
- user.unset_machine()
+ if(!can_use(user) || (issilicon(user) && !user.has_unlimited_silicon_privilege))
+ unset_machine(user)
/obj/machinery/computer/camera_advanced/Destroy()
- if(current_user)
- current_user.unset_machine()
if(eyeobj)
- qdel(eyeobj)
+ QDEL_NULL(eyeobj)
QDEL_LIST(actions)
+ current_user = null
return ..()
-/obj/machinery/computer/camera_advanced/on_unset_machine(mob/M)
+/obj/machinery/computer/camera_advanced/proc/unset_machine(mob/M)
if(M == current_user)
remove_eye_control(M)
/obj/machinery/computer/camera_advanced/proc/can_use(mob/living/user)
- return TRUE
+ return can_interact(user)
/obj/machinery/computer/camera_advanced/abductor/can_use(mob/user)
if(!isabductor(user))
return FALSE
return ..()
-/obj/machinery/computer/camera_advanced/attack_hand(mob/user)
+/obj/machinery/computer/camera_advanced/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
- if(!is_operational()) //you cant use broken machine you chumbis
+ if(!can_use(user))
+ return
+ if(isnull(user.client))
return
if(current_user)
- to_chat(user, "The console is already in use!")
+ to_chat(user, span_warning("The console is already in use!"))
return
var/mob/living/L = user
-
- if(!can_use(user))
- return
if(!eyeobj)
CreateEye()
-
+ if(!eyeobj) //Eye creation failed
+ return
if(!eyeobj.eye_initialized)
var/camera_location
var/turf/myturf = get_turf(src)
if(eyeobj.use_static != FALSE)
- if((!z_lock.len || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf))
+ if((!length(z_lock) || (myturf.z in z_lock)) && GLOB.cameranet.checkTurfVis(myturf))
camera_location = myturf
else
- for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- if(!C.can_use() || z_lock.len && !(C.z in z_lock))
+ for(var/obj/machinery/camera/C as anything in GLOB.cameranet.cameras)
+ if(!C.can_use() || length(z_lock) && !(C.z in z_lock))
continue
var/list/network_overlap = networks & C.network
- if(network_overlap.len)
+ if(length(network_overlap))
camera_location = get_turf(C)
break
else
camera_location = myturf
- if(z_lock.len && !(myturf.z in z_lock))
+ if(length(z_lock) && !(myturf.z in z_lock))
camera_location = locate(round(world.maxx/2), round(world.maxy/2), z_lock[1])
if(camera_location)
@@ -137,7 +155,7 @@
give_eye_control(L)
eyeobj.setLoc(camera_location)
else
- user.unset_machine()
+ unset_machine(user)
else
give_eye_control(L)
eyeobj.setLoc(eyeobj.loc)
@@ -149,6 +167,8 @@
return //AIs would need to disable their own camera procs to use the console safely. Bugs happen otherwise.
/obj/machinery/computer/camera_advanced/proc/give_eye_control(mob/user)
+ if(isnull(user?.client))
+ return
GrantActions(user)
current_user = user
eyeobj.eye_user = user
@@ -156,10 +176,10 @@
user.remote_control = eyeobj
user.reset_perspective(eyeobj)
eyeobj.setLoc(eyeobj.loc)
- if(should_supress_view_changes )
+ if(should_supress_view_changes)
user.client.view_size.supress()
-/mob/camera/aiEye/remote
+/mob/camera/ai_eye/remote
name = "Inactive Camera Eye"
ai_detector_visible = FALSE
var/sprint = 10
@@ -171,29 +191,28 @@
var/visible_icon = 0
var/image/user_image = null
-/mob/camera/aiEye/remote/update_remote_sight(mob/living/user)
- user.see_invisible = SEE_INVISIBLE_LIVING //can't see ghosts through cameras
- user.sight = SEE_TURFS | SEE_BLACKNESS
- user.see_in_dark = 2
+/mob/camera/ai_eye/remote/update_remote_sight(mob/living/user)
+ user.set_invis_see(SEE_INVISIBLE_LIVING) //can't see ghosts through cameras
+ user.set_sight(SEE_TURFS)
return TRUE
-/mob/camera/aiEye/remote/Destroy()
+/mob/camera/ai_eye/remote/Destroy()
if(origin && eye_user)
- origin.remove_eye_control(eye_user, src)
+ origin.remove_eye_control(eye_user,src)
origin = null
. = ..()
eye_user = null
-/mob/camera/aiEye/remote/GetViewerClient()
+/mob/camera/ai_eye/remote/GetViewerClient()
if(eye_user)
return eye_user.client
return null
-/mob/camera/aiEye/remote/setLoc(T)
+/mob/camera/ai_eye/remote/setLoc(turf/destination, force_update = FALSE)
if(eye_user)
- T = get_turf(T)
- if (T)
- forceMove(T)
+ destination = get_turf(destination)
+ if (destination)
+ abstract_move(destination)
else
moveToNullspace()
@@ -202,12 +221,14 @@
if(use_static)
GLOB.cameranet.visibility(src, GetViewerClient(), null, use_static)
- if(visible_icon && eye_user.client)
- eye_user.client.images -= user_image
- user_image = image(icon,loc,icon_state,FLY_LAYER)
- eye_user.client.images += user_image
+ if(visible_icon)
+ if(eye_user.client)
+ eye_user.client.images -= user_image
+ user_image = image(icon,loc,icon_state, FLY_LAYER)
+ SET_PLANE(user_image, ABOVE_GAME_PLANE, destination)
+ eye_user.client.images += user_image
-/mob/camera/aiEye/remote/relaymove(mob/user,direct)
+/mob/camera/ai_eye/remote/relaymove(mob/living/user, direction)
var/initial = initial(sprint)
var/max_sprint = 50
@@ -215,7 +236,7 @@
sprint = initial
for(var/i = 0; i < max(sprint, initial); i += 20)
- var/turf/step = get_turf(get_step(src, direct))
+ var/turf/step = get_turf(get_step(src, direction))
if(step)
setLoc(step)
@@ -231,12 +252,11 @@
button_icon_state = "camera_off"
/datum/action/innate/camera_off/Activate()
- if(!target || !isliving(target))
+ if(!owner || !isliving(owner))
return
- var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control
var/obj/machinery/computer/camera_advanced/console = remote_eye.origin
- console.remove_eye_control(target)
+ console.remove_eye_control(owner)
/datum/action/innate/camera_jump
name = "Jump To Camera"
@@ -244,16 +264,15 @@
button_icon_state = "camera_jump"
/datum/action/innate/camera_jump/Activate()
- if(!target || !isliving(target))
+ if(!owner || !isliving(owner))
return
- var/mob/living/C = target
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control
var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin
var/list/L = list()
- for (var/obj/machinery/camera/cam in GLOB.cameranet.cameras)
- if(origin.z_lock.len && !(cam.z in origin.z_lock))
+ for (var/obj/machinery/camera/cam as anything in GLOB.cameranet.cameras)
+ if(length(origin.z_lock) && !(cam.z in origin.z_lock))
continue
L.Add(cam)
@@ -263,21 +282,54 @@
for (var/obj/machinery/camera/netcam in L)
var/list/tempnetwork = netcam.network & origin.networks
- if (tempnetwork.len)
+ if (length(tempnetwork))
+ if(!netcam.c_tag)
+ continue
T["[netcam.c_tag][netcam.can_use() ? null : " (Deactivated)"]"] = netcam
- playsound(origin, 'sound/machines/terminal_prompt.ogg', 25, 0)
- var/camera = input("Choose which camera you want to view", "Cameras") as null|anything in T
+ playsound(origin, 'sound/machines/terminal_prompt.ogg', 25, FALSE)
+ var/camera = tgui_input_list(usr, "Camera to view", "Cameras", T)
+ if(isnull(camera))
+ return
+ if(isnull(T[camera]))
+ return
var/obj/machinery/camera/final = T[camera]
- playsound(src, "terminal_type", 25, 0)
+ playsound(src, SFX_TERMINAL_TYPE, 25, FALSE)
if(final)
- playsound(origin, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0)
+ playsound(origin, 'sound/machines/terminal_prompt_confirm.ogg', 25, FALSE)
remote_eye.setLoc(get_turf(final))
- C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
- C.clear_fullscreen("flash", 3) //Shorter flash than normal since it's an ~~advanced~~ console!
+ owner.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static)
+ owner.clear_fullscreen("flash", 3) //Shorter flash than normal since it's an ~~advanced~~ console!
+ else
+ playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, FALSE)
+
+/datum/action/innate/camera_multiz_up
+ name = "Move up a floor"
+ button_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "move_up"
+
+/datum/action/innate/camera_multiz_up/Activate()
+ if(!owner || !isliving(owner))
+ return
+ var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control
+ if(remote_eye.zMove(UP))
+ to_chat(owner, span_notice("You move upwards."))
else
- playsound(origin, 'sound/machines/terminal_prompt_deny.ogg', 25, 0)
+ to_chat(owner, span_notice("You couldn't move upwards!"))
+
+/datum/action/innate/camera_multiz_down
+ name = "Move down a floor"
+ button_icon = 'icons/mob/actions/actions_silicon.dmi'
+ button_icon_state = "move_down"
+/datum/action/innate/camera_multiz_down/Activate()
+ if(!owner || !isliving(owner))
+ return
+ var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control
+ if(remote_eye.zMove(DOWN))
+ to_chat(owner, span_notice("You move downwards."))
+ else
+ to_chat(owner, span_notice("You couldn't move downwards!"))
//Used by servants of Ratvar! They let you beam to the station.
/obj/machinery/computer/camera_advanced/ratvar
@@ -285,11 +337,12 @@
desc = "A console used to snoop on the station's goings-on. A jet of steam occasionally whooshes out from slats on its sides."
use_power = FALSE
networks = list("ss13", "minisat") //:eye:
- var/datum/action/innate/servant_warp/warp_action
+ var/datum/action/innate/servant_warp/warp_action = /datum/action/innate/servant_warp
/obj/machinery/computer/camera_advanced/ratvar/Initialize(mapload)
. = ..()
- warp_action = new(src)
+ if(warp_action)
+ actions += new warp_action(src)
ratvar_act()
/obj/machinery/computer/camera_advanced/ratvar/process()
@@ -303,12 +356,6 @@
eyeobj.icon = 'icons/mob/cameramob.dmi' //in case you still had any doubts
eyeobj.icon_state = "generic_camera"
-/obj/machinery/computer/camera_advanced/ratvar/GrantActions(mob/living/carbon/user)
- . = ..()
- warp_action = new(src)
- warp_action.Grant(user)
- actions += warp_action
-
/obj/machinery/computer/camera_advanced/ratvar/can_use(mob/living/user)
if(!is_servant_of_ratvar(user))
to_chat(user, span_warning("[src]'s keys are in a language foreign to you, and you don't understand anything on its screen."))
@@ -338,7 +385,7 @@
cancel = TRUE
return
var/mob/living/carbon/human/user = owner
- var/mob/camera/aiEye/remote/remote_eye = user.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = user.remote_control
var/obj/machinery/computer/camera_advanced/ratvar/camera_console = target
var/turf/teleport_turf = get_turf(remote_eye)
if(!is_reebe(user.z) || !is_station_level(teleport_turf.z))
@@ -349,7 +396,7 @@
else if(isspaceturf(teleport_turf))
to_chat(user, "[span_sevtug_small("[prob(1) ? "Servant cannot into space." : "You can't teleport into space."]")]")
return
- else if(teleport_turf.flags_1 & NOJAUNT_1)
+ else if(teleport_turf.turf_flags & NOJAUNT)
to_chat(user, "[span_sevtug_small("This tile is blessed by strange energies and deflects the warp.")]")
return
else if(locate(/obj/effect/blessing, teleport_turf))
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index a4661b57163d..967cb02b766c 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -50,6 +50,7 @@
/obj/machinery/computer/communications/Initialize(mapload)
. = ..()
GLOB.shuttle_caller_list += src
+ AddComponent(/datum/component/gps, "Secured Communications Signal")
/// Are we NOT a silicon, AND we're logged in as the captain?
/obj/machinery/computer/communications/proc/authenticated_as_non_silicon_captain(mob/user)
@@ -218,11 +219,11 @@
var/datum/map_template/shuttle/shuttle = locate(params["shuttle"]) in shuttles
if (!istype(shuttle))
return
+ if (!can_purchase_this_shuttle(shuttle))
+ return
if (!shuttle.prerequisites_met())
to_chat(usr, span_alert("You have not met the requirements for purchasing this shuttle."))
return
- if(shuttle.emag_buy && !(obj_flags & EMAGGED))
- return //return silently, only way this could happen is an attempted href exploit
var/datum/bank_account/bank_account = SSeconomy.get_dep_account(ACCOUNT_CAR)
if (bank_account.account_balance < shuttle.credit_cost)
return
@@ -232,7 +233,7 @@
SSshuttle.unload_preview()
SSshuttle.existing_shuttle = SSshuttle.emergency
SSshuttle.emergency.name = shuttle.name
- SSshuttle.action_load(shuttle)
+ SSshuttle.action_load(shuttle, replace = TRUE)
bank_account.adjust_money(-shuttle.credit_cost)
minor_announce("[authorize_name] has purchased [shuttle.name] for [shuttle.credit_cost] credits.[shuttle.extra_desc ? " [shuttle.extra_desc]" : ""]" , "Shuttle Purchase")
message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [shuttle.name].")
@@ -406,6 +407,7 @@
data["importantActionReady"] = COOLDOWN_FINISHED(src, important_action_cooldown)
data["shuttleCalled"] = FALSE
data["shuttleLastCalled"] = FALSE
+ data["aprilFools"] = check_holidays(APRIL_FOOLS)
data["canPrintIdAndCode"] = FALSE
data["alertLevel"] = get_security_level()
@@ -448,8 +450,8 @@
if (SSshuttle.emergencyCallAmount)
data["shuttleCalledPreviously"] = TRUE
- if (SSshuttle.emergencyLastCallLoc)
- data["shuttleLastCalled"] = format_text(SSshuttle.emergencyLastCallLoc.name)
+ if (SSshuttle.emergency_last_call_loc)
+ data["shuttleLastCalled"] = format_text(SSshuttle.emergency_last_call_loc.name)
if (STATE_MESSAGES)
data["messages"] = list()
@@ -468,15 +470,20 @@
for (var/shuttle_id in SSmapping.shuttle_templates)
var/datum/map_template/shuttle/shuttle_template = SSmapping.shuttle_templates[shuttle_id]
+
if (shuttle_template.credit_cost == INFINITY)
continue
- if(shuttle_template.emag_buy)
- if(!(obj_flags & EMAGGED))
- continue
+
+ if (!can_purchase_this_shuttle(shuttle_template))
+ continue
+
shuttles += list(list(
"name" = shuttle_template.name,
"description" = shuttle_template.description,
+ "occupancy_limit" = shuttle_template.occupancy_limit,
"creditCost" = shuttle_template.credit_cost,
+ "initial_cost" = initial(shuttle_template.credit_cost),
+ "emagOnly" = shuttle_template.emag_only,
"prerequisites" = shuttle_template.prerequisites,
"ref" = REF(shuttle_template),
))
@@ -514,16 +521,42 @@
/obj/machinery/computer/communications/proc/can_buy_shuttles(mob/user)
if (!SSmapping.config.allow_custom_shuttles)
return FALSE
- if (!authenticated_as_non_silicon_captain(user))
+ if (issilicon(user))
+ return FALSE
+
+ var/has_access = FALSE
+
+ for (var/access in SSshuttle.has_purchase_shuttle_access)
+ if (access in authorize_access)
+ has_access = TRUE
+ break
+
+ if (!has_access)
return FALSE
+
if (SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE)
return "The shuttle is already in transit."
- if (SSshuttle.shuttle_purchased == SHUTTLEPURCHASE_PURCHASED && (SSshuttle.emag_shuttle_purchased || !(obj_flags & EMAGGED)))
+ if (SSshuttle.shuttle_purchased == SHUTTLEPURCHASE_PURCHASED)
return "A replacement shuttle has already been purchased."
if (SSshuttle.shuttle_purchased == SHUTTLEPURCHASE_FORCED)
return "Due to unforseen circumstances, shuttle purchasing is no longer available."
return TRUE
+/// Returns whether we are authorized to buy this specific shuttle.
+/// Does not handle prerequisite checks, as those should still *show*.
+/obj/machinery/computer/communications/proc/can_purchase_this_shuttle(datum/map_template/shuttle/shuttle_template)
+ if (isnull(shuttle_template.who_can_purchase))
+ return FALSE
+
+ if (shuttle_template.emag_only)
+ return !!(obj_flags & EMAGGED)
+
+ for (var/access in authorize_access)
+ if (access in shuttle_template.who_can_purchase)
+ return TRUE
+
+ return FALSE
+
/obj/machinery/computer/communications/proc/can_send_messages_to_other_sectors(mob/user)
if (!authenticated_as_non_silicon_captain(user))
return
diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm
index b910a6364c99..ca384a776728 100644
--- a/code/game/machinery/computer/crew.dm
+++ b/code/game/machinery/computer/crew.dm
@@ -1,4 +1,7 @@
-#define SENSORS_UPDATE_PERIOD 100 //How often the sensor data updates.
+/// How often the sensor data is updated
+#define SENSORS_UPDATE_PERIOD (10 SECONDS) //How often the sensor data updates.
+/// The job sorting ID associated with otherwise unknown jobs
+#define UNKNOWN_JOB_ID 81
/obj/machinery/computer/crew
name = "crew monitoring console"
@@ -305,3 +308,4 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
AI.ai_camera_track(params["name"])
#undef SENSORS_UPDATE_PERIOD
+#undef UNKNOWN_JOB_ID
diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm
index cefddeb1eda1..451b2b09bbb0 100644
--- a/code/game/machinery/computer/dna_console.dm
+++ b/code/game/machinery/computer/dna_console.dm
@@ -1026,7 +1026,7 @@
"UE"=scanner_occupant.dna.unique_enzymes,
"UF"=scanner_occupant.dna.unique_features,
"name"=scanner_occupant.real_name,
- "blood_type"=scanner_occupant.dna.blood_type)
+ "blood_type"=scanner_occupant.dna.blood_type.name)
return
diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm
index 1d94d7aaa442..76a4f16d735b 100644
--- a/code/game/machinery/computer/law.dm
+++ b/code/game/machinery/computer/law.dm
@@ -3,23 +3,14 @@
/obj/machinery/computer/upload
var/mob/living/silicon/current = null //The target of future law uploads
icon_screen = "command"
- var/obj/item/gps/internal/ai_upload/embedded_gps
- var/obj/item/gps/internal/ai_upload/embedded_gps_type = /obj/item/gps/internal/ai_upload
- time_to_scewdrive = 60
-
-/obj/item/gps/internal/ai_upload
- icon_state = null
- gpstag = "Encrypted Upload Signal"
- desc = "Signal used to connect remotely with silicons."
- invisibility = 100
+ time_to_unscrew = 6 SECONDS
/obj/machinery/computer/upload/Initialize(mapload)
- embedded_gps = new embedded_gps_type(src)
- return ..()
-
-/obj/machinery/computer/upload/Destroy()
- QDEL_NULL(embedded_gps)
- return ..()
+ . = ..()
+ AddComponent(/datum/component/gps, "Encrypted Upload")
+ if(!mapload)
+ //log_silicon("\A [name] was created at [loc_name(src)].")
+ message_admins("\A [name] was created at [ADMIN_VERBOSEJMP(src)].")
/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/aiModule))
diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm
index a26d877798de..501e2a978984 100644
--- a/code/game/machinery/computer/medical.dm
+++ b/code/game/machinery/computer/medical.dm
@@ -546,7 +546,8 @@
if(3)
R.fields["age"] = rand(AGE_MIN, AGE_MAX)
if(4)
- R.fields["blood_type"] = random_blood_type()
+ var/datum/blood_type/blood = random_blood_type()
+ R.fields["blood_type"] = blood.name
if(5)
R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit")
if(6)
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index ec8b87cbeae2..996a0decb188 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -412,7 +412,7 @@ GLOBAL_VAR_INIT(cryopods_enabled, FALSE)
if(target.mind.assigned_role in GLOB.command_positions)
tgui_alert(target, "You're a Head of Staff![generic_plsnoleave_message]")
caught = TRUE
- if(A)
+ if(A && A.name != "valentine")
tgui_alert(target, "You're a [A.name]![generic_plsnoleave_message]")
caught = TRUE
if(caught)
@@ -440,21 +440,19 @@ GLOBAL_VAR_INIT(cryopods_enabled, FALSE)
/obj/machinery/cryopod/JoinPlayerHere(mob/M, buckle)
. = ..()
- close_machine(M, TRUE) // put the mob inside instead of on the turf
- playsound(src, join_sound, 30)
+ open_machine()
if(iscarbon(M))
apply_effects_to_mob(M)
- addtimer(CALLBACK(src, PROC_REF(open_machine)), JOIN_SLEEP_DURATION)
/obj/machinery/cryopod/proc/apply_effects_to_mob(mob/living/carbon/sleepyhead)
to_chat(sleepyhead, span_boldnotice("You begin to wake from cryosleep..."))
- sleepyhead.set_nutrition(200) //works for IPCs and stuff too
- sleepyhead.SetSleeping(JOIN_SLEEP_DURATION)
+ sleepyhead.set_nutrition(200)
+ sleepyhead.SetSleeping(60) //if you read this comment and feel like shitting together something to adjust IPC charge on wakeup, be my guest.
//but it can be worse.
if(prob(90))
sleepyhead.adjust_drowsiness(rand(3 SECONDS, 10 SECONDS))
if(prob(75))
- sleepyhead.blur_eyes(rand(3, 6))
+ sleepyhead.adjust_eye_blur(rand(3, 6))
//so much worse
if(prob(66))
sleepyhead.adjust_disgust(rand(25,35))
diff --git a/code/game/machinery/decontamination.dm b/code/game/machinery/decontamination.dm
index 585c4f15992f..e13b403895f1 100644
--- a/code/game/machinery/decontamination.dm
+++ b/code/game/machinery/decontamination.dm
@@ -135,7 +135,7 @@
else
visible_message(span_warning("[src]'s gate creaks open with a loud whining noise."))
playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE)
- for(var/obj/item/item in contents)
+ for(var/obj/item/item in contents)
QDEL_NULL(item)
shock()
open_machine(0)
@@ -144,7 +144,7 @@
/obj/machinery/decontamination_unit/proc/decon_eject()
var/mob/living/mob_occupant = occupant
- say("The decontamination process is completed, thank you for your patient.")
+ say("The decontamination process is completed, thank you for your patience.")
playsound(src, 'sound/machines/decon/decon-open.ogg', 50, TRUE)
if(mob_occupant)
visible_message(span_notice("[src]'s gate slides open, ejecting you out."))
@@ -393,7 +393,7 @@
var/mob/living/mob_occupant = occupant
if(!occupant && !contents.len)
return
- else
+ else
if(uv_emagged)
say("ERROR: Decontamination process is going over safety limit!!")
uv_cycles = 7
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index f89e535290a8..1875ad8524df 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -118,13 +118,15 @@
name = "sandbags"
desc = "Bags of sand. Self explanatory."
icon = 'icons/obj/smooth_structures/sandbags.dmi'
- icon_state = "sandbags"
+ icon_state = "sandbags-0"
+ base_icon_state = "sandbags"
max_integrity = 280
proj_pass_rate = 20
pass_flags = LETPASSTHROW
bar_material = SAND
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/barricade/sandbags, /turf/closed/wall, /turf/closed/wall/r_wall, /obj/structure/falsewall, /obj/structure/falsewall/reinforced, /turf/closed/wall/rust, /turf/closed/wall/r_wall/rust, /obj/structure/barricade/security)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SANDBAGS
+ canSmoothWith = SMOOTH_GROUP_SANDBAGS + SMOOTH_GROUP_SECURITY_BARRICADE + SMOOTH_GROUP_WALLS
/obj/structure/barricade/sandbags/Initialize(mapload)
. = ..()
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index fcb931fa2f4a..a0248f48490b 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -64,13 +64,15 @@
normalspeed = TRUE
explosion_block = 1
hud_possible = list(DIAG_AIRLOCK_HUD)
+ smoothing_groups = SMOOTH_GROUP_AIRLOCK
FASTDMM_PROP(\
pinned_vars = list("req_access_txt", "req_one_access_txt", "name")\
)
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_REQUIRES_SILICON | INTERACT_MACHINE_OPEN
-
+ blocks_emissive = EMISSIVE_BLOCK_NONE
+
/// How much are wires secured
var/security_level = 0
/// If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
@@ -151,7 +153,7 @@
///Whether wires should all cut themselves when this door is broken.
var/cut_wires_on_break = TRUE
- flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1 | HTML_USE_INITAL_ICON_1
rad_insulation = RAD_MEDIUM_INSULATION
var/static/list/airlock_overlays = list()
@@ -187,6 +189,10 @@
return INITIALIZE_HINT_LATELOAD
+/obj/machinery/door/airlock/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ if(id_tag)
+ id_tag = "[port.shuttle_id]_[id_tag]"
+
/obj/machinery/door/airlock/obj_break(damage_flag)
. = ..()
if(!.)
@@ -211,10 +217,10 @@
for(var/obj/machinery/door/firedoor/FD in here)
qdel(FD)
for(var/turf/closed/T in range(2, src))
- here.PlaceOnTop(T.type)
+ here.place_on_top(T.type)
qdel(src)
return
- here.PlaceOnTop(/turf/closed/wall)
+ here.place_on_top(/turf/closed/wall)
qdel(src)
return
if(10 to 11)
@@ -746,7 +752,7 @@
for(var/heading in list(NORTH,SOUTH,EAST,WEST))
if(!(unres_sides & heading))
continue
- var/mutable_appearance/floorlight = mutable_appearance('icons/obj/doors/airlocks/station/overlays.dmi', "unres_[heading]", FLOAT_LAYER, ABOVE_LIGHTING_PLANE)
+ var/mutable_appearance/floorlight = mutable_appearance('icons/obj/doors/airlocks/station/overlays.dmi', "unres_[heading]", FLOAT_LAYER, src, ABOVE_LIGHTING_PLANE)
switch (heading)
if (NORTH)
floorlight.pixel_x = 0
@@ -1300,10 +1306,9 @@
return
INVOKE_ASYNC(src, (density ? PROC_REF(open) : PROC_REF(close)), 2)
- if(istype(I, /obj/item/jawsoflife) || istype(I, /obj/item/mantis/blade))
- if(isElectrified())
- if(shock(user,100))//it's like sticking a fork in a power socket
- return
+ if(istype(I, /obj/item/jawsoflife) || istype(I, /obj/item/mantis/blade) || istype(I, /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp))
+ if(isElectrified() && shock(user,100))//it's like sticking a fork in a power socket
+ return
if(istype(I, /obj/item/mantis/blade))
var/obj/item/mantis/blade/secondsword = user.get_inactive_held_item()
@@ -1656,7 +1661,7 @@
qdel(src)
/obj/machinery/door/airlock/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_DECONSTRUCT)
if(security_level != AIRLOCK_SECURITY_NONE)
to_chat(user, span_notice("[src]'s reinforcement needs to be removed first."))
diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm
index 7bf3fefe4449..679557a011e8 100644
--- a/code/game/machinery/doors/airlock_electronics.dm
+++ b/code/game/machinery/doors/airlock_electronics.dm
@@ -3,9 +3,14 @@
req_access = list(ACCESS_MAINT_TUNNELS)
custom_price = 5
+ /// A list of all granded accesses
var/list/accesses = list()
+ /// If the airlock should require ALL or only ONE of the listed accesses
var/one_access = 0
- var/unres_sides = 0 //unrestricted sides, or sides of the airlock that will open regardless of access
+ /// Unrestricted sides, or sides of the airlock that will open regardless of access
+ var/unres_sides = 0
+ /// A holder of the electronics, in case of them working as an integrated part
+ var/holder
/obj/item/electronics/airlock/examine(mob/user)
. = ..()
@@ -49,40 +54,45 @@
return data
-/obj/item/electronics/airlock/ui_act(action, params)
- if(..())
- return
+/// Shared by RCD and airlock electronics
+/obj/item/electronics/airlock/proc/do_action(action, params)
switch(action)
if("clear_all")
accesses = list()
one_access = 0
- . = TRUE
if("grant_all")
accesses = get_all_accesses()
- . = TRUE
if("one_access")
one_access = !one_access
- . = TRUE
if("set")
var/access = text2num(params["access"])
if (!(access in accesses))
accesses += access
else
accesses -= access
- . = TRUE
if("direc_set")
var/unres_direction = text2num(params["unres_direction"])
unres_sides ^= unres_direction //XOR, toggles only the bit that was clicked
- . = TRUE
if("grant_region")
var/region = text2num(params["region"])
if(isnull(region))
return
accesses |= get_region_accesses(region)
- . = TRUE
if("deny_region")
var/region = text2num(params["region"])
if(isnull(region))
return
accesses -= get_region_accesses(region)
- . = TRUE
+
+/obj/item/electronics/airlock/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ do_action(action, params)
+ return TRUE
+
+/obj/item/electronics/airlock/ui_host()
+ if(holder)
+ return holder
+ return src
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 4d298a074a71..c1bc607cfe89 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -10,11 +10,12 @@
power_channel = AREA_USAGE_ENVIRON
max_integrity = 350
armor = list(MELEE = 30, BULLET = 30, LASER = 20, ENERGY = 20, BOMB = 10, BIO = 100, RAD = 100, FIRE = 80, ACID = 70)
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
flags_1 = PREVENT_CLICK_UNDER_1
damage_deflection = 10
interaction_flags_atom = INTERACT_ATOM_UI_INTERACT
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
/// TRUE means density will be set as soon as the door begins to close
var/air_tight = FALSE
@@ -109,6 +110,7 @@
if(spark_system)
qdel(spark_system)
spark_system = null
+ air_update_turf()
return ..()
/obj/machinery/door/Bumped(atom/movable/AM)
@@ -263,7 +265,7 @@
var/max_moles = min_moles
// okay this is a bit hacky. First, we set density to 0 and recalculate our adjacent turfs
density = FALSE
- var/list/adj_turfs = T.get_adjacent_atmos_turfs()
+ var/list/adj_turfs = TURF_SHARES(T)
// then we use those adjacent turfs to figure out what the difference between the lowest and highest pressures we'd be holding is
for(var/turf/open/T2 in adj_turfs)
if((flags_1 & ON_BORDER_1) && get_dir(src, T2) != dir)
@@ -362,7 +364,7 @@
density = FALSE
sleep(open_speed)
layer = initial(layer)
- update_appearance(UPDATE_ICON)
+ update_appearance()
set_opacity(0)
operating = FALSE
air_update_turf()
@@ -393,7 +395,7 @@
sleep(open_speed)
density = TRUE
sleep(open_speed)
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(visible && !glass)
set_opacity(1)
operating = FALSE
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index eb8f08bcdf05..c21f917ef52e 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -154,7 +154,7 @@
if(W.use_tool(src, user, 40, volume=50))
welded = !welded
to_chat(user, span_danger("[user] [welded?"welds":"unwelds"] [src]."), span_notice("You [welded ? "weld" : "unweld"] [src]."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/door/firedoor/try_to_crowbar(obj/item/I, mob/user)
if(welded || operating)
@@ -320,7 +320,7 @@
/obj/machinery/door/firedoor/border_only
icon = 'icons/obj/doors/edge_Doorfire.dmi'
flags_1 = ON_BORDER_1
- CanAtmosPass = ATMOS_PASS_PROC
+ can_atmos_pass = ATMOS_PASS_PROC
assemblytype = /obj/structure/firelock_frame/border
/obj/machinery/door/firedoor/border_only/closed
@@ -328,6 +328,14 @@
opacity = TRUE
density = TRUE
+/obj/machinery/door/firedoor/border_only/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/machinery/door/firedoor/border_only/close()
if(density)
return TRUE
@@ -395,18 +403,8 @@
if(!(get_dir(loc, target) == dir)) //Make sure looking at appropriate border
return TRUE
-/obj/machinery/door/firedoor/border_only/CheckExit(atom/movable/mover as mob|obj, turf/target)
- if(istype(mover) && (mover.pass_flags & PASSDOOR)) // I mean it's a door
- return TRUE
- if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return TRUE
- if(get_dir(loc, target) == dir)
- return !density
- else
- return TRUE
-
-/obj/machinery/door/firedoor/border_only/CanAtmosPass(turf/T)
- if(get_dir(loc, T) == dir)
+/obj/machinery/door/firedoor/border_only/can_atmos_pass(turf/target_turf, vertical = FALSE)
+ if(get_dir(loc, target_turf) == dir)
return !density
else
return TRUE
@@ -416,6 +414,17 @@
return density
return FALSE
+/obj/machinery/door/firedoor/border_only/proc/on_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER
+ if(leaving.movement_type & PHASING)
+ return
+ if(leaving == src)
+ return // Let's not block ourselves.
+
+ if(direction == dir && density)
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
+
/obj/machinery/door/firedoor/heavy
name = "heavy firelock"
icon = 'icons/obj/doors/doorfire.dmi'
@@ -497,7 +506,7 @@
icon_state = "frame[constructionStep]"
/obj/structure/firelock_frame/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- if(the_rcd.mode == RCD_DECONSTRUCT)
+ if(the_rcd.construction_mode == RCD_DECONSTRUCT)
return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 16)
return FALSE
diff --git a/code/game/machinery/doors/ministile.dm b/code/game/machinery/doors/ministile.dm
index 8db209610246..be54ffec9e12 100644
--- a/code/game/machinery/doors/ministile.dm
+++ b/code/game/machinery/doors/ministile.dm
@@ -24,7 +24,7 @@
icon_state = "ministile"
AddElement(/datum/element/climbable)
-/obj/machinery/ministile/CanAtmosPass(turf/T)
+/obj/machinery/ministile/can_atmos_pass(turf/target_turf, vertical = FALSE)
return TRUE
/obj/machinery/ministile/Cross(atom/movable/mover)
diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm
index 58640614c6e9..3b0f1ee6782e 100644
--- a/code/game/machinery/doors/poddoor.dm
+++ b/code/game/machinery/doors/poddoor.dm
@@ -188,3 +188,5 @@
if(panel_open)
. += " range)
+ if (get_dist(src, L) > flash_range)
continue
if(L.flash_act(affect_silicon = 1))
@@ -188,7 +188,7 @@
add_overlay("[base_state]-s")
setAnchored(TRUE)
power_change()
- proximity_monitor.SetRange(range)
+ proximity_monitor.SetRange(flash_range)
else
to_chat(user, span_notice("[src] can now be moved."))
cut_overlays()
diff --git a/code/game/machinery/Beacon.dm b/code/game/machinery/gigabeacon.dm
similarity index 74%
rename from code/game/machinery/Beacon.dm
rename to code/game/machinery/gigabeacon.dm
index fd481d935f78..f921a9adf129 100644
--- a/code/game/machinery/Beacon.dm
+++ b/code/game/machinery/gigabeacon.dm
@@ -1,10 +1,8 @@
/obj/machinery/bluespace_beacon
-
icon = 'icons/obj/objects.dmi'
icon_state = "floor_beaconf"
name = "bluespace gigabeacon"
desc = "A device that draws power from bluespace and creates a permanent tracking beacon."
- level = 1 // underfloor
layer = LOW_OBJ_LAYER
use_power = IDLE_POWER_USE
idle_power_usage = 0
@@ -14,19 +12,14 @@
. = ..()
var/turf/T = loc
Beacon = new(T)
- Beacon.invisibility = INVISIBILITY_MAXIMUM
+ Beacon.SetInvisibility(INVISIBILITY_MAXIMUM)
- hide(T.intact)
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
/obj/machinery/bluespace_beacon/Destroy()
QDEL_NULL(Beacon)
return ..()
-// update the invisibility and icon
-/obj/machinery/bluespace_beacon/hide(intact)
- invisibility = intact ? INVISIBILITY_MAXIMUM : 0
- updateicon()
-
// update the icon_state
/obj/machinery/bluespace_beacon/proc/updateicon()
var/state="floor_beacon"
@@ -41,7 +34,7 @@
if(!Beacon)
var/turf/T = loc
Beacon = new(T)
- Beacon.invisibility = INVISIBILITY_MAXIMUM
+ Beacon.SetInvisibility(INVISIBILITY_MAXIMUM)
else if (Beacon.loc != loc)
Beacon.forceMove(loc)
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index c00c92d0223a..b2c281582d78 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -192,6 +192,10 @@ obj/machinery/holopad/secure/Initialize(mapload)
return
if(default_unfasten_wrench(user, P))
+ if(replay_mode)
+ replay_stop()
+ if(record_mode)
+ record_stop()
return
if(default_deconstruction_crowbar(P))
diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm
index e9238927e030..79e87e03e676 100644
--- a/code/game/machinery/igniter.dm
+++ b/code/game/machinery/igniter.dm
@@ -76,6 +76,9 @@
else
icon_state = "igniter[on]"
+/obj/machinery/igniter/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ id = "[port.shuttle_id]_[id]"
+
// Wall mounted remote-control igniter.
/obj/machinery/sparker
@@ -98,6 +101,9 @@
spark_system.set_up(2, 1, src)
spark_system.attach(src)
+/obj/machinery/sparker/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ id = "[port.shuttle_id]_[id]"
+
/obj/machinery/sparker/Destroy()
QDEL_NULL(spark_system)
return ..()
diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm
index 2c93e617313a..9e61e8634e17 100644
--- a/code/game/machinery/lightswitch.dm
+++ b/code/game/machinery/lightswitch.dm
@@ -62,8 +62,8 @@
desc = "Make dark."
power_channel = AREA_USAGE_LIGHT
- light_power = 0
- light_range = 7
+ ///Range of the light emitted when powered, but off
+ var/light_on_range = 1
/// Set this to a string, path, or area instance to control that area
/// instead of the switch's location.
@@ -86,7 +86,7 @@
if(mapload)
construction_state = LIGHT_CONSTRUCTED
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(mapload)
return INITIALIZE_HINT_LATELOAD
return INITIALIZE_HINT_NORMAL
@@ -99,25 +99,39 @@
return
turn_off()
+/obj/machinery/light_switch/update_appearance(updates=ALL)
+ . = ..()
+ luminosity = (stat & NOPOWER) ? 0 : 1
+
+/obj/machinery/light_switch/update_icon_state()
+ set_light(area.lightswitch ? 0 : light_on_range)
+ switch(construction_state)
+ if(LIGHT_BARE)
+ icon_state = "light-b"
+ if(LIGHT_WIRE)
+ icon_state = "light-w"
+ if(LIGHT_CONSTRUCTED)
+ icon_state = "light-c"
+
+ return ..()
+
/obj/machinery/light_switch/update_overlays()
. = ..()
if(stat & (NOPOWER|BROKEN))
return
if(construction_state != LIGHT_CONSTRUCTED)
return
- if(area.lightswitch)
- . += "light1"
- else
- . += "light0"
+ . += mutable_appearance(icon, "[area.lightswitch ? "light1" : "light0"]")
+ . += emissive_appearance(icon, "[area.lightswitch ? "light1" : "light0"]", src)
/obj/machinery/light_switch/proc/turn_off()
if(!area.lightswitch)
return
area.lightswitch = FALSE
- area.update_icon()
+ area.update_appearance()
for(var/obj/machinery/light_switch/L in area)
- L.update_icon()
+ L.update_appearance()
area.power_change()
@@ -125,10 +139,10 @@
if(area.lightswitch)
return
area.lightswitch = TRUE
- area.update_icon()
+ area.update_appearance()
for(var/obj/machinery/light_switch/L in area)
- L.update_icon()
+ L.update_appearance()
area.power_change()
@@ -146,11 +160,10 @@
. = ..()
area.lightswitch = !area.lightswitch
- area.update_appearance(UPDATE_ICON)
play_click_sound("button")
for(var/obj/machinery/light_switch/L in area)
- L.update_appearance(UPDATE_ICON)
+ L.update_appearance()
area.power_change()
@@ -161,12 +174,13 @@
if(istype(c) && c.use(1))
to_chat(user, span_notice("You insert wiring into [src]."))
construction_state = LIGHT_WIRE
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(W.tool_behaviour == TOOL_WRENCH)
W.play_tool_sound(src)
to_chat(user, span_notice("You remove the frame."))
new /obj/item/wallframe/light_switch(loc)
+ qdel(src)
return
if(LIGHT_WIRE)
if(W.tool_behaviour == TOOL_WIRECUTTER && wires.is_cut(WIRE_POWER))
@@ -174,7 +188,7 @@
to_chat(user, span_notice("You cut the wires out."))
new /obj/item/stack/cable_coil(loc, 1)
construction_state = LIGHT_BARE
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(panel_open && is_wire_tool(W))
wires.interact(user)
@@ -184,7 +198,7 @@
to_chat(user, span_notice("You screw the cover on."))
panel_open = FALSE
construction_state = LIGHT_CONSTRUCTED
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(LIGHT_CONSTRUCTED)
if(W.tool_behaviour == TOOL_SCREWDRIVER)
@@ -192,21 +206,10 @@
to_chat(user, span_notice("You take the cover off."))
panel_open = TRUE
construction_state = LIGHT_WIRE
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
return ..()
-/obj/machinery/light_switch/update_icon_state()
- . = ..()
- switch(construction_state)
- if(LIGHT_BARE)
- icon_state = "light-b"
- if(LIGHT_WIRE)
- icon_state = "light-w"
- if(LIGHT_CONSTRUCTED)
- icon_state = "light-c"
-
-
/obj/machinery/light_switch/power_change()
if(area == get_area(src))
return ..()
diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm
index 1c5e2c5b055c..b25a7c47d683 100644
--- a/code/game/machinery/magnet.dm
+++ b/code/game/machinery/magnet.dm
@@ -9,7 +9,6 @@
icon_state = "floor_magnet-f"
name = "electromagnetic generator"
desc = "A device that uses station power to create points of magnetic energy."
- level = 1 // underfloor
layer = LOW_OBJ_LAYER
use_power = IDLE_POWER_USE
idle_power_usage = 50
@@ -29,9 +28,8 @@
/obj/machinery/magnetic_module/Initialize(mapload)
..()
- var/turf/T = loc
- hide(T.intact)
- center = T
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
+ center = loc
SSradio.add_object(src, freq, RADIO_MAGNETS)
return INITIALIZE_HINT_LATELOAD
@@ -43,11 +41,6 @@
center = null
return ..()
-// update the invisibility and icon
-/obj/machinery/magnetic_module/hide(intact)
- invisibility = intact ? INVISIBILITY_MAXIMUM : 0
- update_appearance(UPDATE_ICON)
-
// update the icon_state
/obj/machinery/magnetic_module/update_icon_state()
. = ..()
diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm
index 5217c389f2d7..8b9653ca7643 100644
--- a/code/game/machinery/mass_driver.dm
+++ b/code/game/machinery/mass_driver.dm
@@ -1,21 +1,33 @@
/obj/machinery/mass_driver
name = "mass driver"
- desc = "The finest in spring-loaded piston toy technology, now on a space station near you."
+ desc = "A miniaturized mass driver, the finest in hydraulic piston technology." // Imagine what an actual mass driver would look like
icon = 'icons/obj/stationobjs.dmi'
icon_state = "mass_driver"
+ circuit = /obj/item/circuitboard/machine/mass_driver
use_power = IDLE_POWER_USE
idle_power_usage = 2
active_power_usage = 50
var/power = 1
var/code = 1
var/id = 1
- var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess.
+ var/drive_range = 10
+ var/power_per_obj = 500
+/obj/machinery/mass_driver/Initialize(mapload)
+ . = ..()
+ wires = new /datum/wires/mass_driver(src)
+
+/obj/machinery/mass_driver/Destroy()
+ QDEL_NULL(wires)
+ . = ..()
+
+/obj/machinery/mass_driver/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ id = "[port.shuttle_id]_[id]"
/obj/machinery/mass_driver/proc/drive(amount)
- if(stat & (BROKEN|NOPOWER))
+ if(stat & (BROKEN|NOPOWER) || panel_open)
return
- use_power(500)
+ use_power(power_per_obj)
var/O_limit
var/atom/target = get_edge_target_turf(src, dir)
for(var/atom/movable/O in loc)
@@ -26,15 +38,34 @@
if(O_limit >= 20)
audible_message(span_notice("[src] lets out a screech, it doesn't seem to be able to handle the load."))
break
- use_power(500)
+ use_power(power_per_obj)
O.throw_at(target, drive_range * power, power)
+ playsound(get_turf(src), 'sound/machines/mass_driver.ogg', 75)
flick("mass_driver1", src)
+/obj/machinery/mass_driver/attackby(obj/item/I, mob/living/user, params)
+
+ if(is_wire_tool(I) && panel_open)
+ wires.interact(user)
+ return
+ if(default_deconstruction_screwdriver(user, "mass_driveropen", "mass_driver", I))
+ return
+ if(default_change_direction_wrench(user, I))
+ return
+ if(default_deconstruction_crowbar(I))
+ return
+
+ return ..()
+
+/obj/machinery/mass_driver/RefreshParts()
+ . = ..()
+ for(var/obj/item/stock_parts/capacitor/C in component_parts)
+ drive_range += 10 * C.rating
/obj/machinery/mass_driver/emp_act(severity)
. = ..()
if (. & EMP_PROTECT_SELF)
return
- if(stat & (BROKEN|NOPOWER))
+ if(stat & (BROKEN|NOPOWER) || panel_open)
return
drive()
diff --git a/code/game/machinery/medical_kiosk.dm b/code/game/machinery/medical_kiosk.dm
index 5b3d25dab013..74fc08e47f96 100644
--- a/code/game/machinery/medical_kiosk.dm
+++ b/code/game/machinery/medical_kiosk.dm
@@ -197,7 +197,7 @@
var/bleed_status = "Patient is not currently bleeding."
var/blood_status = " Patient either has no blood, or does not require it to function."
var/blood_percent = round((altPatient.blood_volume / BLOOD_VOLUME_NORMAL(altPatient))*100)
- var/blood_type = altPatient.dna.blood_type
+ var/blood_type = altPatient.dna.blood_type.name
var/blood_warning = " "
for(var/thing in altPatient.diseases) //Disease Information
diff --git a/code/game/machinery/mindmachine.dm b/code/game/machinery/mindmachine.dm
index 80ccfd1f7ba6..c8f3e3509f03 100644
--- a/code/game/machinery/mindmachine.dm
+++ b/code/game/machinery/mindmachine.dm
@@ -105,7 +105,7 @@
if(stat & BROKEN)
overlay_state = "[icon_state]_broken"
. += mutable_appearance(icon, overlay_state)
- . += mutable_appearance(icon, overlay_state, layer, EMISSIVE_PLANE)
+ . += emissive_appearance(icon, icon_screen, src)
/obj/machinery/mindmachine_hub/RefreshParts()
// 2 matter bins. Reduce failure chance by 5 per tier. Results in 30 (tier 1) to 0 (tier 4).
@@ -316,6 +316,10 @@
if(DEAD)
.["firstStat"] = "Dead"
.["firstMindType"] = firstLiving.key ? "Sentient" : "Non-Sentient"
+ else
+ .["firstName"] = null
+ .["firstStat"] = null
+ .["firstMindType"] = null
else // If you don't null it and keep the ui open, then above data doesn't change until you reopen.
.["firstOpen"] = null
.["firstLocked"] = null
@@ -338,6 +342,10 @@
if(DEAD)
.["secondStat"] = "Dead"
.["secondMindType"] = secondLiving.key ? "Sentient" : "Non-Sentient"
+ else
+ .["secondName"] = null
+ .["secondStat"] = null
+ .["secondMindType"] = null
else
.["secondOpen"] = null
.["secondLocked"] = null
diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm
index 6e14252d83cd..fc797c63c39b 100644
--- a/code/game/machinery/navbeacon.dm
+++ b/code/game/machinery/navbeacon.dm
@@ -7,8 +7,7 @@
icon_state = "navbeacon0-f"
name = "navigation beacon"
desc = "A radio beacon used for bot navigation."
- level = 1 // underfloor
- layer = UNDER_CATWALK
+ layer = LOW_OBJ_LAYER
max_integrity = 500
armor = list(MELEE = 70, BULLET = 70, LASER = 70, ENERGY = 70, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 80)
@@ -26,15 +25,15 @@
set_codes()
- var/turf/T = loc
- hide(T.intact)
- if(codes["patrol"])
+ if(codes[NAVBEACON_PATROL_MODE])
if(!GLOB.navbeacons["[z]"])
GLOB.navbeacons["[z]"] = list()
GLOB.navbeacons["[z]"] += src //Register with the patrol list!
- if(codes["delivery"])
+ if(codes[NAVBEACON_DELIVERY_MODE])
GLOB.deliverybeacons += src
GLOB.deliverybeacontags += location
+
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
/obj/machinery/navbeacon/Destroy()
if (GLOB.navbeacons["[z]"])
@@ -42,12 +41,12 @@
GLOB.deliverybeacons -= src
return ..()
-/obj/machinery/navbeacon/onTransitZ(old_z, new_z)
- if (GLOB.navbeacons["[old_z]"])
- GLOB.navbeacons["[old_z]"] -= src
- if (GLOB.navbeacons["[new_z]"])
- GLOB.navbeacons["[new_z]"] += src
- ..()
+/obj/machinery/navbeacon/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if (GLOB.navbeacons["[old_turf?.z]"])
+ GLOB.navbeacons["[old_turf?.z]"] -= src
+ if (GLOB.navbeacons["[new_turf?.z]"])
+ GLOB.navbeacons["[new_turf?.z]"] += src
+ return ..()
// set the transponder codes assoc list from codes_txt
/obj/machinery/navbeacon/proc/set_codes()
@@ -67,13 +66,6 @@
else
codes[e] = "1"
-
-// called when turf state changes
-// hide the object if turf is intact
-/obj/machinery/navbeacon/hide(intact)
- invisibility = intact ? INVISIBILITY_MAXIMUM : 0
- update_appearance(UPDATE_ICON)
-
// update the icon_state
/obj/machinery/navbeacon/update_icon_state()
. = ..()
@@ -88,7 +80,7 @@
/obj/machinery/navbeacon/attackby(obj/item/I, mob/user, params)
var/turf/T = loc
- if(T.intact)
+ if(T.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE)
return // prevent intraction when T-scanner revealed
if(I.tool_behaviour == TOOL_SCREWDRIVER)
@@ -121,7 +113,7 @@
. = ..()
var/ai = isAI(user)
var/turf/T = loc
- if(T.intact)
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
return // prevent intraction when T-scanner revealed
if(!open && !ai) // can't alter controls if not open, unless you're an AI
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index 33c9676d86dc..ad609496b776 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -18,8 +18,9 @@ Buildable meters
icon_state = "simple"
item_state = "buildpipe"
w_class = WEIGHT_CLASS_NORMAL
- level = 2
+ ///Piping layer that we are going to be on
var/piping_layer = PIPING_LAYER_DEFAULT
+ ///Type of pipe-object made, selected from the RPD
var/RPD_type
var/disposable = TRUE // yogs
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index 75e316392d43..466411473c78 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -9,6 +9,7 @@
icon = 'icons/obj/turrets.dmi'
icon_state = "turretCover"
base_icon_state = "standard"
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
layer = OBJ_LAYER
invisibility = INVISIBILITY_OBSERVER //the turret is invisible if it's inside its cover
density = TRUE
diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm
index 6c393d6f367b..e5c37ee4594c 100644
--- a/code/game/machinery/recharger.dm
+++ b/code/game/machinery/recharger.dm
@@ -17,6 +17,7 @@
var/static/list/allowed_devices = typecacheof(list(
/obj/item/gun/energy,
+ /obj/item/cargo_teleporter, //dripstation edit
/obj/item/melee/baton,
/obj/item/ammo_box/magazine/recharge,
/obj/item/ammo_box/magazine/m308/laser,
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index e9fdd712afde..90f1c2cfea4f 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -6,6 +6,7 @@
icon = 'icons/obj/recycling.dmi'
icon_state = "grinder-o0"
layer = ABOVE_ALL_MOB_LAYER // Overhead
+ plane = ABOVE_GAME_PLANE
density = TRUE
circuit = /obj/item/circuitboard/machine/recycler
var/safety_mode = FALSE // Temporarily stops machine if it detects a mob
@@ -19,10 +20,18 @@
/obj/machinery/recycler/Initialize(mapload)
AddComponent(/datum/component/material_container, list(/datum/material/iron, /datum/material/glass, /datum/material/plasma, /datum/material/silver, /datum/material/gold, /datum/material/diamond, /datum/material/uranium, /datum/material/bananium, /datum/material/titanium, /datum/material/bluespace, /datum/material/dilithium, /datum/material/plastic), INFINITY, FALSE, null, null, null, TRUE)
- AddComponent(/datum/component/butchering, 1, amount_produced,amount_produced/5)
+ AddComponent(/datum/component/butchering, 0.1 SECONDS, amount_produced, amount_produced/5)
+ . = ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/recycler/LateInitialize()
. = ..()
update_appearance(UPDATE_ICON)
req_one_access = get_all_accesses() + get_all_centcom_access()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/machinery/recycler/RefreshParts()
var/amt_made = 0
@@ -87,9 +96,10 @@
if(move_dir == eat_dir)
return TRUE
-/obj/machinery/recycler/Crossed(atom/movable/AM)
- eat(AM)
- . = ..()
+/obj/machinery/recycler/proc/on_entered(datum/source, atom/movable/enterer, old_loc)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(eat), enterer)
/obj/machinery/recycler/proc/eat(atom/movable/AM0, sound=TRUE)
if(stat & (BROKEN|NOPOWER))
diff --git a/code/game/machinery/sci_bombardment.dm b/code/game/machinery/sci_bombardment.dm
index 9eedf2ecaefa..77a9e8a9f2e3 100644
--- a/code/game/machinery/sci_bombardment.dm
+++ b/code/game/machinery/sci_bombardment.dm
@@ -53,7 +53,7 @@
break
radio = new /obj/item/radio/(src)
radio.frequency = radio_freq
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/sci_bombardment/Destroy()
QDEL_NULL(radio)
@@ -79,7 +79,7 @@
scibomb = B
playsound(src, 'sound/effects/bin_close.ogg', 100, 1)
to_chat(usr, span_notice("You load [B] into the firing mechanism."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
to_chat(usr, span_warning("There is already a transfer valve loaded in the firing mechanism!"))
else
@@ -131,7 +131,7 @@
targetdest = initial(dest)
tcoords = initial(tcoords)
scibomb = initial(scibomb)
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
/**
@@ -143,7 +143,7 @@
*/
/obj/machinery/sci_bombardment/proc/reset_lam()
target_delay = !target_delay
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(target_delay)
spawn(100)
reset_lam()
@@ -173,8 +173,7 @@
var/list/signals = list()
data["signals"] = list()
- for(var/gps in GLOB.GPS_list)
- var/obj/item/gps/G = gps
+ for(var/datum/component/gps/G in GLOB.GPS_list) //nulls on the list somehow
var/turf/pos = get_turf_global(G) // yogs - get_turf_global instead of get_turf
if(G.emped || !G.tracking || pos.z != lavaland)
continue
@@ -201,7 +200,7 @@
radio.talk_into(src, "Controls [locked ? "locked" : "unlocked"] by [I.registered_name].",)
else
to_chat(usr, span_warning("Access denied. Please seek assistance from station AI or Research Director."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("count")//Prompts user to change countdown timer (Minimum based on mincount)
if(locked)
@@ -221,7 +220,7 @@
to_chat(usr, span_notice("[scibomb] is ejected from the loading chamber."))
scibomb.forceMove(drop_location())
scibomb = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("launch")//Transfers var/countdown to var/tick before proc'ing countdown()
if(locked || target_delay || !scibomb || !dest)
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index f6015bb4142d..053172a63740 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -9,13 +9,17 @@
anchored = TRUE
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
max_integrity = 200 //The shield can only take so much beating (prevents perma-prisons)
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
/obj/structure/emergency_shield/Initialize(mapload)
. = ..()
setDir(pick(GLOB.cardinals))
air_update_turf()
+/obj/structure/emergency_shield/Destroy()
+ air_update_turf()
+ . = ..()
+
/obj/structure/emergency_shield/Move()
var/turf/T = loc
. = ..()
diff --git a/code/game/machinery/shuttle/shuttle_engine.dm b/code/game/machinery/shuttle/shuttle_engine.dm
index 4743665672fb..de4c97a5f9fa 100644
--- a/code/game/machinery/shuttle/shuttle_engine.dm
+++ b/code/game/machinery/shuttle/shuttle_engine.dm
@@ -214,10 +214,10 @@
/obj/machinery/shuttle/engine/ion/proc/register_capacitor_bank(new_bank)
if(capacitor_bank)
- UnregisterSignal(capacitor_bank, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(capacitor_bank, COMSIG_QDELETING)
capacitor_bank = new_bank
if(capacitor_bank)
- RegisterSignal(capacitor_bank, COMSIG_PARENT_QDELETING, PROC_REF(on_capacitor_deleted))
+ RegisterSignal(capacitor_bank, COMSIG_QDELETING, PROC_REF(on_capacitor_deleted))
update_engine()
/obj/machinery/shuttle/engine/ion/proc/on_capacitor_deleted(datum/source, force)
diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm
index 8ff7540611dd..04d75e4eb7e1 100644
--- a/code/game/machinery/slotmachine.dm
+++ b/code/game/machinery/slotmachine.dm
@@ -17,8 +17,10 @@
/obj/machinery/computer/slot_machine
name = "slot machine"
desc = "Gambling for the antisocial."
- icon = 'icons/obj/economy.dmi'
- icon_state = "slots1"
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "slots"
+ icon_keyboard = null
+ icon_screen = "slots_screen"
density = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 50
@@ -42,13 +44,13 @@
jackpots = rand(1, 4) //false hope
plays = rand(75, 200)
- toggle_reel_spin(1) //The reels won't spin unless we activate them
+ INVOKE_ASYNC(src, PROC_REF(toggle_reel_spin), TRUE)//The reels won't spin unless we activate them
var/list/reel = reels[1]
for(var/i = 0, i < reel.len, i++) //Populate the reels.
randomize_reels()
- toggle_reel_spin(0)
+ INVOKE_ASYNC(src, PROC_REF(toggle_reel_spin), FALSE)
for(cointype in typesof(/obj/item/coin))
var/obj/item/coin/C = cointype
@@ -59,23 +61,26 @@
give_payout(balance)
return ..()
-/obj/machinery/computer/slot_machine/process(delta_time)
+/obj/machinery/computer/slot_machine/process(seconds_per_tick)
. = ..() //Sanity checks.
if(!.)
return .
- money += round(delta_time / 2) //SPESSH MAJICKS
+ money += round(seconds_per_tick / 2) //SPESSH MAJICKS
/obj/machinery/computer/slot_machine/update_icon_state()
- . = ..()
- if(stat & NOPOWER)
- icon_state = "slots0"
- else if(stat & BROKEN)
- icon_state = "slotsb"
- else if(working)
- icon_state = "slots2"
+ if(stat & BROKEN)
+ icon_state = "slots_broken"
+ else
+ icon_state = "slots"
+ return ..()
+
+/obj/machinery/computer/slot_machine/update_overlays()
+ if(working)
+ icon_screen = "slots_screen_working"
else
- icon_state = "slots1"
+ icon_screen = "slots_screen"
+ return ..()
/obj/machinery/computer/slot_machine/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/coin))
@@ -205,7 +210,7 @@
working = 1
toggle_reel_spin(1)
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateDialog()
spawn(0)
@@ -218,7 +223,7 @@
toggle_reel_spin(0, REEL_DEACTIVATE_DELAY)
working = 0
give_prizes(the_name, user)
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateDialog()
/obj/machinery/computer/slot_machine/proc/can_spin(mob/user)
@@ -289,7 +294,7 @@
if(did_player_win)
add_filter("jackpot_rays", 3, ray_filter)
animate(get_filter("jackpot_rays"), offset = 10, time = 3 SECONDS, loop = -1)
- addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, remove_filter), "jackpot_rays"), 3 SECONDS)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, remove_filter), "jackpot_rays"), 3 SECONDS)
playsound(src, 'sound/machines/roulettejackpot.ogg', 50, TRUE)
/obj/machinery/computer/slot_machine/proc/get_lines()
diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm
index 90482de1d7e8..1bca27c4933b 100644
--- a/code/game/machinery/spaceheater.dm
+++ b/code/game/machinery/spaceheater.dm
@@ -264,9 +264,9 @@
usr.visible_message("[usr] switches [on ? "on" : "off"] \the [src].", span_notice("You switch [on ? "on" : "off"] \the [src]."))
update_appearance(UPDATE_ICON)
if (on)
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
else
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
/obj/machinery/space_heater/AltClick(mob/user)
if(!user.canUseTopic(src, !issilicon(user)))
diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm
index e610992476eb..f5d357885ada 100644
--- a/code/game/machinery/stasis.dm
+++ b/code/game/machinery/stasis.dm
@@ -123,7 +123,7 @@
/obj/machinery/stasis/setDir()
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/stasis/update_overlays()
. = ..()
@@ -145,6 +145,12 @@
var/easing_direction = _running ? EASE_OUT : EASE_IN
animate(mattress_on, alpha = new_alpha, time = 50, easing = CUBIC_EASING|easing_direction)
+/obj/machinery/stasis/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ SET_PLANE(mattress_on, PLANE_TO_TRUE(mattress_on.plane), new_turf)
+ return ..()
+
/obj/machinery/stasis/obj_break(damage_flag)
. = ..()
if(.)
diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm
index 2f0e935e630e..1c7c0fddd862 100644
--- a/code/game/machinery/status_display.dm
+++ b/code/game/machinery/status_display.dm
@@ -303,9 +303,9 @@
if("shuttle_id")
update()
-/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override)
+/obj/machinery/status_display/shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override)
if (port && (shuttle_id == initial(shuttle_id) || override))
- shuttle_id = port.id
+ shuttle_id = port.shuttle_id
update()
diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm
index 2d536cadb7f6..a9d31fb3f18b 100644
--- a/code/game/machinery/telecomms/telecomunications.dm
+++ b/code/game/machinery/telecomms/telecomunications.dm
@@ -31,7 +31,8 @@ GLOBAL_LIST_EMPTY(telecomms_list)
var/on = TRUE
var/toggled = TRUE // Is it toggled on
var/long_range_link = FALSE // Can you link it across Z levels or on the otherside of the map? (Relay & Hub)
- var/hide = FALSE // Is it a hidden machine?
+ /// Is it a hidden machine?
+ var/hide = FALSE
var/generates_heat = TRUE //yogs turn off tcomms generating heat
var/heatoutput = 2500 //yogs modify power output per trafic removed(usual heat capacity of the air in server room is 1600J/K)
diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm
index 6c11dc1e5e3b..13dbfdbb3c20 100644
--- a/code/game/mecha/combat/gygax.dm
+++ b/code/game/mecha/combat/gygax.dm
@@ -25,7 +25,7 @@
operation_req_access = list(ACCESS_SYNDICATE)
internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/gygax/dark
- max_equip = 6
+ max_equip = 7
destruction_sleep_duration = 20
/obj/mecha/combat/gygax/dark/loaded/Initialize(mapload)
@@ -42,6 +42,8 @@
ME.attach(src)
ME = new /obj/item/mecha_parts/mecha_equipment/emergency_eject
ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion
+ ME.attach(src)
max_ammo()
/obj/mecha/combat/gygax/dark/add_cell(obj/item/stock_parts/cell/C=null)
@@ -56,16 +58,6 @@
..()
overload_action.Grant(user, src)
-/obj/mecha/combat/gygax/dark/GrantActions(mob/living/user, human_occupant = 0)
- ..()
- thrusters_action.Grant(user, src)
-
-
/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0)
..()
overload_action.Remove(user)
-
-/obj/mecha/combat/gygax/dark/RemoveActions(mob/living/user, human_occupant = 0)
- ..()
- thrusters_action.Remove(user)
-
diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm
index cedcdc143603..5ce4c06b372e 100644
--- a/code/game/mecha/combat/marauder.dm
+++ b/code/game/mecha/combat/marauder.dm
@@ -15,19 +15,17 @@
add_req_access = 0
internal_damage_threshold = 25
force = 40
- max_equip = 4
+ max_equip = 5
bumpsmash = 1
/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0)
..()
smoke_action.Grant(user, src)
- thrusters_action.Grant(user, src)
zoom_action.Grant(user, src)
/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0)
..()
smoke_action.Remove(user)
- thrusters_action.Remove(user)
zoom_action.Remove(user)
/obj/mecha/combat/marauder/loaded/Initialize(mapload)
@@ -40,6 +38,8 @@
ME.attach(src)
ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src)
ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src)
+ ME.attach(src)
max_ammo()
/obj/mecha/combat/marauder/seraph
@@ -53,7 +53,7 @@
wreckage = /obj/structure/mecha_wreckage/seraph
internal_damage_threshold = 20
force = 50
- max_equip = 5
+ max_equip = 6
/obj/mecha/combat/marauder/seraph/unloaded
@@ -71,6 +71,8 @@
ME.attach(src)
ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src)
ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src)
+ ME.attach(src)
max_ammo()
/obj/mecha/combat/marauder/mauler
@@ -80,7 +82,7 @@
operation_req_access = list(ACCESS_SYNDICATE)
internals_req_access = list(ACCESS_SYNDICATE)
wreckage = /obj/structure/mecha_wreckage/mauler
- max_equip = 7
+ max_equip = 8
destruction_sleep_duration = 20
ejection_distance = 8
@@ -100,6 +102,8 @@
ME.attach(src)
ME = new /obj/item/mecha_parts/mecha_equipment/emergency_eject(src) // YEET
ME.attach(src)
+ ME = new /obj/item/mecha_parts/mecha_equipment/thrusters/ion(src)
+ ME.attach(src)
max_ammo()
diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm
index 8bbdd4ce7015..72d643b50805 100644
--- a/code/game/mecha/equipment/mecha_equipment.dm
+++ b/code/game/mecha/equipment/mecha_equipment.dm
@@ -7,6 +7,8 @@
icon_state = "mecha_equip"
force = 5
max_integrity = 300
+ /// Prevents hitting stuff when trying to use certain equipment as tools
+ item_flags = NOBLUDGEON
/// Cooldown after use
var/equip_cooldown = 0
/// is the module ready for use
@@ -27,8 +29,21 @@
var/destroy_sound = 'sound/mecha/critdestr.ogg'
/// Bitflag. Used by exosuit fabricator to assign sub-categories based on which exosuits can equip this.
var/mech_flags = NONE
- //Special melee override for melee weapons
+ /// Special melee override for melee weapons
var/melee_override = FALSE
+ /// Actions granted by this equipment
+ var/list/equip_actions = list()
+
+/obj/item/mecha_parts/mecha_equipment/Initialize(mapload)
+ . = ..()
+ var/list/action_type_list = equip_actions.Copy()
+ equip_actions = list()
+ for(var/path in action_type_list)
+ var/datum/action/innate/mecha/equipment/action = new path
+ action.equipment = src
+ equip_actions.Add(action)
+ qdel(action_type_list)
+
/obj/item/mecha_parts/mecha_equipment/proc/update_chassis_page()
if(chassis)
send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list())
@@ -55,6 +70,8 @@
chassis.occupant_message(span_danger("[src] is destroyed!"))
chassis.occupant.playsound_local(chassis, destroy_sound, 50)
chassis = null
+ for(var/datum/action/innate/mecha/equipment/E in equip_actions)
+ E.Destroy()
return ..()
/obj/item/mecha_parts/mecha_equipment/try_attach_part(mob/user, obj/mecha/M)
@@ -103,7 +120,7 @@
return 0
return 1
-/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target)
+/obj/item/mecha_parts/mecha_equipment/proc/action(atom/target, mob/living/user, params)
return 0
/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown()
@@ -140,9 +157,21 @@
forceMove(M)
log_message("[src] initialized.", LOG_MECHA)
update_chassis_page()
+ ADD_TRAIT(src, TRAIT_NODROP, "mecha")
+ item_flags |= NO_MAT_REDEMPTION // terrible
+ for(var/datum/action/innate/mecha/equipment/action as anything in equip_actions)
+ action.chassis = M
+ if(chassis.occupant)
+ grant_actions(chassis.occupant)
return
/obj/item/mecha_parts/mecha_equipment/proc/detach(atom/moveto=null)
+ if(chassis.occupant)
+ remove_actions(chassis.occupant)
+ for(var/datum/action/innate/mecha/equipment/action as anything in equip_actions)
+ action.chassis = null
+ item_flags &= ~NO_MAT_REDEMPTION
+ REMOVE_TRAIT(src, TRAIT_NODROP, "mecha")
if(chassis.selected == src)
src.on_deselect()
moveto = moveto || get_turf(chassis)
@@ -198,3 +227,16 @@
/obj/item/mecha_parts/mecha_equipment/proc/check_eva()
return chassis?.check_eva()
+// Some equipment can be used as tools
+/obj/item/mecha_parts/mecha_equipment/tool_use_check(mob/living/user, amount)
+ return (chassis ? (chassis.cell.charge >= energy_drain) : FALSE) // but not if they aren't attached to a mech
+
+// Grant any actions to the pilot
+/obj/item/mecha_parts/mecha_equipment/proc/grant_actions(mob/pilot)
+ for(var/datum/action/innate/mecha/equipment/action as anything in equip_actions)
+ action.Grant(pilot)
+
+// Remove the actions!!!!
+/obj/item/mecha_parts/mecha_equipment/proc/remove_actions(mob/pilot)
+ for(var/datum/action/innate/mecha/equipment/action as anything in equip_actions)
+ action.Remove(pilot)
diff --git a/code/game/mecha/equipment/tools/mining_tools.dm b/code/game/mecha/equipment/tools/mining_tools.dm
index 93d43355aef0..229b5ac12e77 100644
--- a/code/game/mecha/equipment/tools/mining_tools.dm
+++ b/code/game/mecha/equipment/tools/mining_tools.dm
@@ -129,7 +129,11 @@
if(isalien(target))
new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target.drop_location(), splatter_dir)
else
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(target.drop_location(), splatter_dir)
+ var/splatter_color = null
+ if(iscarbon(target))
+ var/mob/living/carbon/carbon_target = target
+ splatter_color = carbon_target.dna.blood_type.color
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(target.drop_location(), splatter_dir, splatter_color)
//organs go everywhere
if(target_part && prob(10 * drill_level))
diff --git a/code/game/mecha/equipment/tools/other_tools.dm b/code/game/mecha/equipment/tools/other_tools.dm
index ecf3a724bcc0..345401e86c38 100644
--- a/code/game/mecha/equipment/tools/other_tools.dm
+++ b/code/game/mecha/equipment/tools/other_tools.dm
@@ -471,7 +471,105 @@
if(..())
radiation_pulse(get_turf(src), rad_per_cycle)
+/////////////////////////////////////////// THRUSTERS /////////////////////////////////////////////
+
+/obj/item/mecha_parts/mecha_equipment/thrusters
+ name = "generic exosuit thrusters" //parent object, in-game sources will be a child object
+ desc = "A generic set of thrusters, from an unknown source. Uses not-understood methods to propel exosuits seemingly for free."
+ icon_state = "thrusters"
+ equip_actions = list(/datum/action/innate/mecha/equipment/toggle_thrusters)
+ selectable = FALSE
+ var/thrusters_active = FALSE
+ var/datum/effect_system/trail_follow/thrust_trail = /datum/effect_system/trail_follow/sparks
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/Initialize(mapload)
+ . = ..()
+ thrust_trail = new thrust_trail
+ thrust_trail.set_up(src)
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/try_attach_part(mob/user, obj/mecha/M)
+ for(var/obj/item/mecha_parts/mecha_equipment/equip as anything in M.equipment)
+ if(istype(equip, type))
+ to_chat(user, span_warning("[src] already has thrusters!"))
+ return FALSE
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/attach(obj/mecha/M)
+ . = ..()
+ if(thrusters_active)
+ thrust_trail.start()
+ RegisterSignal(M, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(thrust))
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/detach(atom/moveto)
+ UnregisterSignal(chassis, COMSIG_MOVABLE_SPACEMOVE)
+ if(thrusters_active)
+ thrust_trail.stop()
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/proc/thrust(obj/mecha/exo, movement_dir)
+ if(!thrusters_active)
+ return
+ if(!chassis)
+ return
+ return COMSIG_MOVABLE_ALLOW_SPACEMOVE //This parent should never exist in-game outside admeme use, so why not let it be a creative thruster?
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/get_equip_info()
+ return "[..()] \[Thrusters: [thrusters_active ? "Enabled" : "Disabled"]\]"
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/gas
+ name = "RCS thruster package"
+ desc = "A set of thrusters that allow for exosuit movement in zero-gravity environments, by expelling gas from the internal life support tank."
+ thrust_trail = /datum/effect_system/trail_follow/smoke
+ var/move_cost = 0.05 // moles per step (5 times more than human jetpacks)
+/obj/item/mecha_parts/mecha_equipment/thrusters/gas/thrust(obj/mecha/exo, movement_dir)
+ if(!thrusters_active)
+ return
+ if(!movement_dir)
+ return
+ var/obj/machinery/portable_atmospherics/canister/internal_tank = chassis.internal_tank
+ if(!internal_tank)
+ return
+ var/datum/gas_mixture/our_mix = internal_tank.return_air()
+ var/moles = our_mix.total_moles()
+ if(moles < move_cost)
+ thrusters_active = FALSE
+ thrust_trail.stop()
+ our_mix.remove(moles)
+ return
+ our_mix.remove(move_cost)
+ return COMSIG_MOVABLE_ALLOW_SPACEMOVE
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/ion //for mechs with built-in thrusters, should never really exist un-attached to a mech
+ name = "ion thruster package"
+ desc = "A set of thrusters that allow for exosuit movement in zero-gravity environments."
+ thrust_trail = /datum/effect_system/trail_follow/ion
+ salvageable = FALSE
+
+/obj/item/mecha_parts/mecha_equipment/thrusters/ion/thrust(obj/mecha/exo, movement_dir)
+ if(!thrusters_active)
+ return
+ if(!chassis.use_power(chassis.step_energy_drain))
+ thrusters_active = FALSE
+ thrust_trail.stop()
+ return
+ return COMSIG_MOVABLE_ALLOW_SPACEMOVE
+
+/datum/action/innate/mecha/equipment/toggle_thrusters
+ name = "Toggle Thrusters"
+ button_icon_state = "mech_thrusters_off"
+
+/datum/action/innate/mecha/equipment/toggle_thrusters/Activate()
+ var/obj/item/mecha_parts/mecha_equipment/thrusters/thruster = equipment
+ thruster.thrusters_active = !thruster.thrusters_active
+ if(thruster.thrusters_active)
+ thruster.thrust_trail.start()
+ else
+ thruster.thrust_trail.stop()
+ chassis.log_message("Toggled thrusters.", LOG_MECHA)
+ chassis.occupant_message("Thrusters [thruster.thrusters_active ?"en":"dis"]abled.")
+ button_icon_state = "mech_thrusters_[thruster.thrusters_active ? "on" : "off"]"
+ build_all_button_icons()
/////////////////////////////////////////// EJECTION /////////////////////////////////////////////
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index 2fe788ba5dd0..d24c6e86cbae 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -1,8 +1,6 @@
-#define DECONSTRUCT 0
-#define WALL 1
-#define AIRLOCK 2
-//Hydraulic clamp, Kill clamp, Extinguisher, RCD, Cable layer.
+
+//Hydraulic clamp, Kill clamp, Extinguisher, RCD, RPD, Cable layer.
/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp
@@ -11,11 +9,29 @@
icon_state = "mecha_clamp"
equip_cooldown = 15
energy_drain = 10
+ toolspeed = 0.5
+ usesound = 'sound/mecha/hydraulic.ogg'
+ tool_behaviour = TOOL_CROWBAR
+ equip_actions = list(/datum/action/innate/mecha/equipment/clamp_mode)
/// How much damage does it apply when used
var/dam_force = 20
var/obj/mecha/working/ripley/cargo_holder
harmful = FALSE
+/datum/action/innate/mecha/equipment/clamp_mode
+ name = "Toggle Clamp Mode"
+ button_icon_state = "clamp_crowbar"
+
+/datum/action/innate/mecha/equipment/clamp_mode/Activate()
+ if(equipment.tool_behaviour == TOOL_CROWBAR)
+ equipment.tool_behaviour = TOOL_WRENCH
+ else
+ equipment.tool_behaviour = TOOL_CROWBAR
+ button_icon_state = "clamp_[equipment.tool_behaviour]"
+ chassis.balloon_alert(owner, "clamp set to [(equipment.tool_behaviour==TOOL_CROWBAR) ? "pry" : "wrench"]")
+ playsound(chassis, 'sound/items/change_jaws.ogg', 50, 1)
+ build_all_button_icons()
+
/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/can_attach(obj/mecha/working/ripley/M as obj)
if(..())
if(istype(M))
@@ -31,11 +47,18 @@
..()
cargo_holder = null
-/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/action(atom/target)
+/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/action(atom/target, mob/living/user, params)
if(!action_checks(target))
return
if(!cargo_holder)
return
+
+ // There are two ways things handle being pried, and I'm too lazy to make every single thing use the same one
+ if(target.tool_act(user, src, tool_behaviour) & TOOL_ACT_MELEE_CHAIN_BLOCKING)
+ return TRUE
+ if(target.attackby(src, user, params))
+ return TRUE
+
if(ismecha(target))
var/obj/mecha/M = target
var/have_ammo
@@ -49,20 +72,14 @@
else
to_chat(chassis.occupant, "No providable supplies found in cargo hold")
return
+
if(isobj(target))
var/obj/O = target
- if(istype(O, /obj/machinery/door/firedoor))
- var/obj/machinery/door/firedoor/D = O
- D.try_to_crowbar(src,chassis.occupant)
- return
- if(istype(O, /obj/machinery/door/airlock/))
- var/obj/machinery/door/airlock/D = O
- D.try_to_crowbar(src,chassis.occupant)
- return
if(!O.anchored)
if(cargo_holder.cargo.len < cargo_holder.cargo_capacity)
chassis.visible_message("[chassis] lifts [target] and starts to load it into cargo compartment.")
O.anchored = TRUE
+ play_tool_sound(chassis)
if(do_after_cooldown(target))
cargo_holder.cargo += O
O.forceMove(chassis)
@@ -242,133 +259,182 @@
name = "mounted RCD"
desc = "An exosuit-mounted Rapid Construction Device."
icon_state = "mecha_rcd"
- equip_cooldown = 10
- energy_drain = 50
+ equip_cooldown = 0 // internal RCD will handle it
+ energy_drain = 0 // uses matter instead of energy
range = MECHA_MELEE|MECHA_RANGED
item_flags = NO_MAT_REDEMPTION
- var/mode = DECONSTRUCT
- var/play_sound = TRUE //so fancy mime RCD can be silent
+ equip_actions = list(/datum/action/innate/mecha/equipment/rcd)
+ var/rcd_type = /obj/item/construction/rcd/exosuit
+ var/obj/item/construction/rcd/internal_rcd
/obj/item/mecha_parts/mecha_equipment/rcd/Initialize(mapload)
. = ..()
GLOB.rcd_list += src
+ internal_rcd = new rcd_type(src)
/obj/item/mecha_parts/mecha_equipment/rcd/Destroy()
GLOB.rcd_list -= src
+ if(internal_rcd && !QDELETED(internal_rcd))
+ qdel(internal_rcd)
return ..()
-/obj/item/mecha_parts/mecha_equipment/rcd/action(atom/target)
- if(istype(target, /turf/open/space/transit))//>implying these are ever made -Sieve
- return
-
- if(!isturf(target) && !istype(target, /obj/machinery/door/airlock))
- target = get_turf(target)
- if(!action_checks(target) || get_dist(chassis, target)>3)
- return
- if(play_sound)
- playsound(chassis, 'sound/machines/click.ogg', 50, 1)
-
- switch(mode)
- if(DECONSTRUCT)
- if(iswallturf(target))
- if(istype(target, /turf/closed/wall/r_wall))
- occupant_message("Wall reinforcements are too complex for deconstruction, must be deconstructed manually.")
- return
- energy_drain = 500
- var/turf/closed/wall/W = target
- occupant_message("Deconstructing [W]...")
- if(do_after_cooldown(W))
- chassis.spark_system.start()
- W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
- if(play_sound)
- playsound(W, 'sound/items/deconstruct.ogg', 50, 1)
- if(target == /turf/closed/wall/r_wall)
- energy_drain = 2000
- else if(isfloorturf(target))
- if(istype(target, /turf/open/floor/engine))
- occupant_message("Floor reinforcements prevent deconstruction, remove before continuing.")
- return
- energy_drain = 100
- var/turf/open/floor/F = target
- occupant_message("Deconstructing [F]...")
- if(do_after_cooldown(target))
- chassis.spark_system.start()
- F.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
- if(play_sound)
- playsound(F, 'sound/items/deconstruct.ogg', 50, 1)
- else if (istype(target, /obj/machinery/door/airlock))
- var/obj/machinery/door/airlock/A = target
- if(A.damage_deflection > 21)
- occupant_message("Airlock too reinforced for deconstruction, remove reinforcements before continuing.")
- return
- energy_drain = 500
- occupant_message("Deconstructing [target]...")
- if(do_after_cooldown(target))
- chassis.spark_system.start()
- qdel(target)
- if(play_sound)
- playsound(target, 'sound/items/deconstruct.ogg', 50, 1)
- if(WALL)
- if(isspaceturf(target))
- var/turf/open/space/S = target
- occupant_message("Building Floor...")
- if(do_after_cooldown(S))
- S.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
- if(play_sound)
- playsound(S, 'sound/items/deconstruct.ogg', 50, 1)
- chassis.spark_system.start()
- else if(isfloorturf(target))
- var/turf/open/floor/F = target
- energy_drain = 750
- occupant_message("Building Wall...")
- if(do_after_cooldown(F))
- F.PlaceOnTop(/turf/closed/wall)
- if(play_sound)
- playsound(F, 'sound/items/deconstruct.ogg', 50, 1)
- chassis.spark_system.start()
- if(AIRLOCK)
- if(isfloorturf(target))
- energy_drain = 750
- occupant_message("Building Airlock...")
- if(do_after_cooldown(target))
- chassis.spark_system.start()
- var/obj/machinery/door/airlock/T = new /obj/machinery/door/airlock(target)
- T.autoclose = TRUE
- if(play_sound)
- playsound(target, 'sound/items/deconstruct.ogg', 50, 1)
- playsound(target, 'sound/effects/sparks2.ogg', 50, 1)
-
-
-
-/obj/item/mecha_parts/mecha_equipment/rcd/do_after_cooldown(atom/target)
+/obj/item/mecha_parts/mecha_equipment/rcd/attach(obj/mecha/M)
. = ..()
+ internal_rcd.owner = M
-/obj/item/mecha_parts/mecha_equipment/rcd/Topic(href,href_list)
- ..()
- if(href_list["mode"])
- mode = text2num(href_list["mode"])
- switch(mode)
- if(0)
- occupant_message("Switched RCD to Deconstruct.")
- energy_drain = initial(energy_drain)
- if(1)
- occupant_message("Switched RCD to Construct.")
- energy_drain = 2*initial(energy_drain)
- if(2)
- occupant_message("Switched RCD to Construct Airlock.")
- energy_drain = 2*initial(energy_drain)
- return
+/obj/item/mecha_parts/mecha_equipment/rcd/detach(atom/moveto)
+ internal_rcd.owner = null
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/rcd/action(atom/target, mob/living/user, params)
+ var/prox_flag = chassis.Adjacent(target)
+ if(prox_flag && (istype(target, /obj/item/stack) || istype(target, /obj/item/rcd_ammo) || istype(target, /obj/item/rcd_upgrade)))
+ chassis.matter_resupply(target, user)
+ return TRUE
+ if(!isliving(target))
+ internal_rcd.afterattack(target, user, prox_flag, params) // RCD itself will handle it
+ return TRUE
/obj/item/mecha_parts/mecha_equipment/rcd/get_equip_info()
- return "[..()] \[D|C|A\]"
+ return "[..()] \[Matter: [internal_rcd ? internal_rcd.matter : 0]/[internal_rcd ? internal_rcd.max_matter : 0]\]"
+/datum/action/innate/mecha/equipment/rcd
+ name = "Change RCD Mode"
+ button_icon_state = "rcd"
+
+/datum/action/innate/mecha/equipment/rcd/Activate()
+ var/obj/item/mecha_parts/mecha_equipment/rcd/E = equipment
+ E.internal_rcd.ui_interact(owner)
/obj/item/mecha_parts/mecha_equipment/rcd/mime //special silent RCD
name = "silenced mounted RCD"
desc = "An expertly mimed exosuit-mounted Rapid Construction Device. Not a sound is made."
- play_sound = FALSE
+ rcd_type = /obj/item/construction/rcd/exosuit/mime
+/obj/item/mecha_parts/mecha_equipment/pipe_dispenser
+ name = "mounted RPD"
+ desc = "An exosuit-mounted Rapid Pipe Dispenser"
+ icon_state = "mecha_pipe_dispenser"
+ equip_cooldown = 0 // internal RPD will handle it
+ energy_drain = 0 // uses matter instead of energy
+ range = MECHA_MELEE|MECHA_RANGED
+ item_flags = NO_MAT_REDEMPTION
+ equip_actions = list(/datum/action/innate/mecha/equipment/pipe_dispenser)
+ var/rpd_type = /obj/item/pipe_dispenser/exosuit // in case there's ever any other type of RPD for mechs for some reason
+ var/obj/item/pipe_dispenser/internal_rpd
+
+/obj/item/mecha_parts/mecha_equipment/pipe_dispenser/Initialize(mapload)
+ . = ..()
+ internal_rpd = new rpd_type(src)
+
+/obj/item/mecha_parts/mecha_equipment/pipe_dispenser/Destroy()
+ if(internal_rpd && !QDELETED(internal_rpd))
+ qdel(internal_rpd)
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/pipe_dispenser/attach(obj/mecha/M)
+ . = ..()
+ internal_rpd.owner = M
+
+/obj/item/mecha_parts/mecha_equipment/pipe_dispenser/detach(atom/moveto)
+ internal_rpd.owner = null
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/pipe_dispenser/action(atom/target, mob/living/user, params)
+ if(internal_rpd.pre_attack(target, user))
+ return FALSE
+ chassis.Beam(target, icon_state="rped_upgrade",time=2)
+ return TRUE
+
+/datum/action/innate/mecha/equipment/pipe_dispenser
+ name = "Change RPD Mode"
+ button_icon_state = "rpd"
+
+/datum/action/innate/mecha/equipment/pipe_dispenser/Activate()
+ var/obj/item/mecha_parts/mecha_equipment/pipe_dispenser/E = equipment
+ E.internal_rpd.ui_interact(owner)
+
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner
+ name = "exosuit T-ray scanner"
+ desc = "An exosuit-mounted terahertz-ray emitter and scanner used to detect underfloor objects such as cables and pipes. Has much higher range than the handheld version."
+ icon_state = "mecha_t_scanner"
+ equip_actions = list(/datum/action/innate/mecha/equipment/t_scanner)
+ selectable = FALSE
+ /// Scanning distance
+ var/distance = 6
+ /// Whether the scanning is enabled
+ var/scanning = FALSE
+ /// Stored t-ray scan images
+ var/list/t_ray_images
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/attach(obj/mecha/M)
+ . = ..()
+ RegisterSignal(M, COMSIG_MOVABLE_MOVED, PROC_REF(on_mech_move))
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/detach(atom/moveto)
+ UnregisterSignal(chassis, COMSIG_MOVABLE_MOVED)
+ if(scanning)
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/process(delta_time)
+ if(!update_scan(chassis.occupant))
+ return PROCESS_KILL
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/proc/on_mech_move()
+ if(chassis.occupant?.client)
+ update_scan(chassis.occupant)
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/proc/update_scan(mob/pilot, force_remove=FALSE) // twice the range, no downtime
+ if(!pilot?.client)
+ return FALSE
+ if(t_ray_images?.len)
+ pilot.client.images.Remove(t_ray_images)
+ QDEL_NULL(t_ray_images)
+ if(!scanning || force_remove)
+ return FALSE
+
+ t_ray_images = list()
+ for(var/obj/O in orange(distance, chassis))
+ if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE))
+ var/image/I = new(loc = get_turf(O))
+ var/mutable_appearance/MA = new(O)
+ MA.alpha = 128
+ MA.dir = O.dir
+ I.appearance = MA
+ t_ray_images += I
+
+ if(t_ray_images.len)
+ pilot.client.images += t_ray_images
+
+ return TRUE
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/grant_actions(mob/pilot)
+ . = ..()
+ update_scan(pilot)
+
+/obj/item/mecha_parts/mecha_equipment/t_scanner/remove_actions(mob/pilot)
+ update_scan(pilot, TRUE)
+ return ..()
+
+/datum/action/innate/mecha/equipment/t_scanner
+ name = "Toggle T-ray Scanner"
+ button_icon_state = "t_scanner_off"
+
+/datum/action/innate/mecha/equipment/t_scanner/Activate()
+ var/obj/item/mecha_parts/mecha_equipment/t_scanner/t_scan = equipment
+ t_scan.scanning = !t_scan.scanning
+ t_scan.update_scan(t_scan.chassis.occupant)
+ t_scan.chassis.occupant_message("You [t_scan.scanning ? "activate" : "deactivate"] [t_scan].")
+ button_icon_state = "t_scanner_[t_scan.scanning ? "on" : "off"]"
+ build_all_button_icons()
+ if(t_scan.scanning)
+ START_PROCESSING(SSobj, t_scan)
+ else
+ STOP_PROCESSING(SSobj, t_scan)
+
/obj/item/mecha_parts/mecha_equipment/cable_layer
name = "cable layer"
desc = "Equipment for engineering exosuits. Lays cable along the exosuit's path."
@@ -470,7 +536,7 @@
if(!T.broken && !T.burnt)
new T.floor_tile(T)
T.make_plating()
- return !new_turf.intact
+ return new_turf.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE
/obj/item/mecha_parts/mecha_equipment/cable_layer/proc/layCable(turf/new_turf)
if(equip_ready || !istype(new_turf) || !dismantle_floor(new_turf))
@@ -561,7 +627,3 @@
qdel(M)
playsound(get_turf(N),'sound/items/ratchet.ogg',50,1)
return
-
-#undef DECONSTRUCT
-#undef WALL
-#undef AIRLOCK
diff --git a/code/game/mecha/equipment/weapons/melee_weapons.dm b/code/game/mecha/equipment/weapons/melee_weapons.dm
index dc1c60733b6f..06c42b2d4642 100644
--- a/code/game/mecha/equipment/weapons/melee_weapons.dm
+++ b/code/game/mecha/equipment/weapons/melee_weapons.dm
@@ -62,7 +62,7 @@
addtimer(CALLBACK(src, PROC_REF(set_ready_state), 1), chassis.melee_cooldown * attack_speed_modifier * check_eva()) //Guns only shoot so fast, but weapons can be used as fast as the chassis can swing it!
//Melee weapon attacks are a little different in that they'll override the standard melee attack
-/obj/item/mecha_parts/mecha_equipment/melee_weapon/action(atom/target, params)
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/action(atom/target, mob/living/user, params)
if(!action_checks(target))
return 0
@@ -125,7 +125,7 @@
/obj/item/mecha_parts/mecha_equipment/melee_weapon/proc/cleave_attack()
return 0
-/obj/item/mecha_parts/mecha_equipment/melee_weapon/proc/special_hit() //For special effects, slightly simplifies cleave/precise attack procs
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/proc/special_hit(atom/target) //For special effects, slightly simplifies cleave/precise attack procs
return 1
/obj/item/mecha_parts/mecha_equipment/melee_weapon/on_select()
@@ -199,7 +199,7 @@
if((isstructure(A) || ismachinery(A) || istype(A, /obj/mecha)) && can_stab_at(chassis, A)) //if it's a big thing we hit anyways. Structures ALWAYS are hit, machines and mechs can be protected
var/obj/O = A
- if(!O.density) //Make sure it's not an open door or something
+ if(!O.density && !istype(O, /obj/structure/spacevine)) //Make sure it's not an open door or something
continue
var/object_damage = max(chassis.force + weapon_damage, minimum_damage) * structure_damage_mult * (istype(A, /obj/mecha) ? mech_damage_multiplier : 1) //Half damage on mechs
O.take_damage(object_damage, dam_type, "melee", 0)
@@ -536,3 +536,126 @@
span_userdanger("[chassis.name] penetrates your suits armor with [src]!"))
chassis.log_message("Hit [H] with [src.name] (precise attack).", LOG_MECHA)
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop
+ name = "heavy mop"
+ desc = "A very big mop, designed to be attached to mechanical exosuits."
+ icon_state = "mecha_mop"
+ energy_drain = 5
+ attack_sound = 'sound/effects/slosh.ogg'
+
+ cleave = TRUE
+ precise_attacks = FALSE // cleave only
+ attack_sharpness = SHARP_NONE
+ harmful = FALSE
+ weapon_damage = 0 // no damage
+ structure_damage_mult = 0 // don't break stuff while trying to clean
+ equip_actions = list(/datum/action/innate/mecha/equipment/sweeping)
+ var/auto_sweep = TRUE
+
+/datum/action/innate/mecha/equipment/sweeping
+ name = "Toggle Auto-Mop"
+ button_icon_state = "sweep_on"
+
+/datum/action/innate/mecha/equipment/sweeping/Activate()
+ var/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/mop = equipment
+ mop.auto_sweep = !mop.auto_sweep
+ button_icon_state = "sweep_[mop.auto_sweep ? "on" : "off"]"
+ build_all_button_icons()
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/attach(obj/mecha/M)
+ . = ..()
+ RegisterSignal(M, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_pre_move))
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/detach(atom/moveto)
+ UnregisterSignal(chassis, COMSIG_MOVABLE_PRE_MOVE)
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/can_attach(obj/mecha/M)
+ if(istype(M, /obj/mecha/working) && M.equipment.len < M.max_equip)
+ return TRUE
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/proc/on_pre_move(obj/mecha/mech, atom/newloc)
+ if(!auto_sweep)
+ return
+ var/mop_dir = get_dir(mech, newloc)
+ if(mop_dir != mech.dir) // only sweep things in front of the mech
+ return
+ do_mop(mech, newloc)
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/proc/do_mop(obj/mecha/mech, atom/newloc, throw_power=1)
+ var/turf/mop_turf = newloc
+ var/turf/thrown_at = get_edge_target_turf(mop_turf, chassis.dir)
+ var/cleaned = FALSE
+
+ if(mop_turf.wash(CLEAN_SCRUB))
+ cleaned = TRUE
+ for(var/atom/movable/moved_atom in newloc)
+ if(istype(moved_atom, /obj/effect/decal/nuclear_waste)) // sweep that nuclear waste under the rug
+ cleaned = TRUE
+ playsound(moved_atom, 'sound/effects/gib_step.ogg', 50, 1)
+ qdel(moved_atom)
+ continue
+ if(moved_atom.wash(CLEAN_SCRUB))
+ cleaned = TRUE
+ if(moved_atom.anchored)
+ continue
+ if(moved_atom == chassis) // it can clean itself, but not move itself
+ continue
+ moved_atom.throw_at(thrown_at, throw_power, 1, mech.occupant, (throw_power > 1))
+ if(isliving(moved_atom) && throw_power > 1)
+ moved_atom.visible_message(span_danger("[mech] mops the floor with [moved_atom]!"), span_userdanger("[mech] mops the floor with you!"))
+
+ if(cleaned)
+ playsound(newloc, 'sound/effects/slosh.ogg', 25, 1)
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/mop/cleave_attack()
+ playsound(chassis, attack_sound, 50, 1)
+ for(var/turf/T in list(get_turf(chassis), get_step(chassis, chassis.dir), get_step(chassis, turn(chassis.dir, -45)), get_step(chassis, turn(chassis.dir, 45))))
+ do_mop(chassis, T, 3) // mop the floor with them!
+ var/turf/cleave_effect_loc = get_step(get_turf(src), SOUTHWEST)
+ new cleave_effect(cleave_effect_loc, chassis.dir)
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter
+ name = "comically large flyswatter"
+ desc = "A comically large flyswatter, presumably for killing comically large bugs."
+ attack_sound = 'sound/effects/snap.ogg'
+ icon_state = "mecha_flyswatter"
+ cleave = FALSE
+ precise_attacks = TRUE
+ hit_effect = ATTACK_EFFECT_SMASH
+ ///Things in this list will be instantly splatted.
+ var/list/strong_against
+ ///Damage to mobs with the MOB_BUG biotype, quadrupled for simple mobs
+ var/bug_damage = 30
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter/Initialize(mapload)
+ . = ..()
+ strong_against = typecacheof(list(
+ /mob/living/simple_animal/hostile/poison/bees,
+ /mob/living/simple_animal/butterfly,
+ /mob/living/simple_animal/cockroach,
+ /obj/item/queen_bee
+ ))
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter/precise_attack(atom/target)
+ var/mob/living/mob_target = target
+ if(is_type_in_typecache(target, strong_against))
+ new /obj/effect/decal/cleanable/insectguts(target.drop_location())
+ to_chat(chassis.occupant, span_warning("You easily splat the [target]."))
+ if(isliving(target))
+ var/mob/living/bug = target
+ bug.death(TRUE)
+ else
+ qdel(target)
+ else if(isliving(target) && (mob_target.mob_biotypes & MOB_BUG))
+ mob_target.apply_damage(bug_damage * (ishuman(mob_target) ? 1 : 4), BRUTE, wound_bonus=CANT_WOUND) // bonus damage to simple mobs
+ target.visible_message(span_warning("[chassis] splats [target] with [src]!"), span_userdanger("[chassis] splats you with [src]!"))
+ chassis.do_attack_animation(target, hit_effect)
+ playsound(chassis, attack_sound, 50, 1)
+
+/obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter/can_attach(obj/mecha/M)
+ if(istype(M, /obj/mecha/working) && M.equipment.len < M.max_equip)
+ return TRUE
+ return ..()
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index c2d0def5a4cd..05c7b50a1cdc 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -32,7 +32,7 @@
/obj/item/mecha_parts/mecha_equipment/weapon/proc/get_shot_amount()
return projectiles_per_shot
-/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, params)
+/obj/item/mecha_parts/mecha_equipment/weapon/action(atom/target, mob/living/user, params)
if(!action_checks(target))
return 0
@@ -151,6 +151,7 @@
/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma
equip_cooldown = 10
+ range = MECHA_MELEE|MECHA_RANGED
name = "217-D Heavy Plasma Cutter"
desc = "A device that shoots resonant plasma bursts at extreme velocity. The blasts are capable of crushing rock and demolishing solid obstacles."
icon_state = "mecha_plasmacutter"
@@ -160,6 +161,9 @@
energy_drain = 30
projectile = /obj/projectile/plasma/adv/mech
fire_sound = 'sound/weapons/plasma_cutter.ogg'
+ usesound = list('sound/items/welder.ogg', 'sound/items/welder2.ogg')
+ toolspeed = 0.25 // high-power cutting
+ tool_behaviour = TOOL_WELDER
harmful = FALSE
/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/can_attach(obj/mecha/M)
@@ -171,6 +175,18 @@
return 1
return 0
+/obj/item/mecha_parts/mecha_equipment/weapon/energy/plasma/action(atom/target, mob/living/user, params)
+ if(!chassis.Adjacent(target))
+ return ..()
+ // Again, two ways using tools can be handled, so check both
+ if(target.tool_act(chassis.occupant, src, TOOL_WELDER) & TOOL_ACT_MELEE_CHAIN_BLOCKING)
+ return TRUE
+ if(target.attackby(src, chassis.occupant, params))
+ return TRUE
+ if(user.a_intent == INTENT_HARM) // hurt things
+ chassis.default_melee_attack(target)
+ return TRUE
+
/obj/item/mecha_parts/mecha_equipment/weapon/energy/mecha_kineticgun
equip_cooldown = 10
name = "Exosuit Proto-kinetic Accelerator"
@@ -309,7 +325,7 @@
src.rearm()
return
-/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target)
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/action(atom/target, mob/living/user, params)
if(..())
projectiles -= get_shot_amount()
send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info())
@@ -534,7 +550,7 @@
if(!istype(PG))
return
//has to be low sleep or it looks weird, the beam doesn't exist for very long so it's a non-issue
- chassis.Beam(PG, icon_state = "chain", time = missile_range * 20, maxdistance = missile_range + 2, beam_sleep_time = 1)
+ chassis.Beam(PG, icon_state = "chain", time = missile_range * 20, maxdistance = missile_range + 2)
/obj/item/punching_glove
name = "punching glove"
@@ -548,3 +564,42 @@
var/atom/movable/AM = hit_atom
AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), 7, 2)
qdel(src)
+
+// pressure washer, technically a gun
+/obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer
+ name = "exosuit-mounted pressure washer"
+ desc = "A high-power pressure washer."
+ icon_state = "mecha_washer"
+ range = MECHA_MELEE|MECHA_RANGED
+ projectile = /obj/projectile/reagent/pressure_washer
+ firing_effect_type = null
+ fire_sound = 'sound/effects/extinguish.ogg'
+ var/chem_amount = 5
+
+/obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer/Initialize(mapload)
+ . = ..()
+ create_reagents(1000)
+ reagents.add_reagent(/datum/reagent/water, 1000)
+
+/obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer/action(atom/target, mob/living/user, params)
+ if(istype(target, /obj/structure/reagent_dispensers/watertank) && get_dist(chassis,target) <= 1)
+ var/obj/structure/reagent_dispensers/WT = target
+ WT.reagents.trans_to(src, 1000)
+ occupant_message(span_notice("Pressure washer refilled."))
+ playsound(chassis, 'sound/effects/refill.ogg', 50, 1, -6)
+ return TRUE
+ else if(reagents.total_volume < 1)
+ occupant_message(span_notice("Not enough water!"))
+ return TRUE
+ if(..())
+ reagents.remove_reagent(/datum/reagent/water, chem_amount)
+ return TRUE
+ return FALSE
+
+/obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer/can_attach(obj/mecha/M)
+ if(istype(M, /obj/mecha/working) && M.equipment.len < M.max_equip)
+ return TRUE
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer/get_equip_info()
+ return "[..()] \[[src.reagents.total_volume]\]"
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 5cc03a2be83f..aae3755c65fb 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -4,8 +4,8 @@
#define MECHA_INT_TANK_BREACH (1<<3)
#define MECHA_INT_CONTROL_LOST (1<<4)
-#define MECHA_MELEE 1
-#define MECHA_RANGED 2
+#define MECHA_MELEE (1<<0)
+#define MECHA_RANGED (1<<1)
#define FRONT_ARMOUR 1
#define SIDE_ARMOUR 2
@@ -24,9 +24,8 @@
layer = BELOW_MOB_LAYER//icon draw layer
infra_luminosity = 15 //byond implementation is bugged.
force = 5
- light_system = MOVABLE_LIGHT
- light_range = 3
- light_power = 6
+ light_system = MOVABLE_LIGHT_DIRECTIONAL
+ light_range = 8
light_on = FALSE
flags_1 = HEAR_1
var/ruin_mecha = FALSE //if the mecha starts on a ruin, don't automatically give it a tracking beacon to prevent metagaming.
@@ -93,6 +92,9 @@
var/silicon_pilot = FALSE //set to true if an AI or MMI is piloting.
+ ///Camera installed into the mech
+ var/obj/machinery/camera/exosuit/chassis_camera
+
var/enter_delay = 40 //Time taken to enter the mech
var/exit_delay = 20 //Time to exit mech
var/destruction_sleep_duration = 20 //Time that mech pilot is put to sleep for if mech is destroyed
@@ -109,7 +111,6 @@
var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new
var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new
var/datum/action/innate/mecha/mech_view_stats/stats_action = new
- var/datum/action/innate/mecha/mech_toggle_thrusters/thrusters_action = new
var/datum/action/innate/mecha/mech_defence_mode/defence_action = new
var/datum/action/innate/mecha/mech_overload_mode/overload_action = new
var/datum/effect_system/fluid_spread/smoke/smoke_system = new //not an action, but trigged by one
@@ -120,7 +121,6 @@
var/datum/action/innate/mecha/strafe/strafing_action = new
//Action vars
- var/thrusters_active = FALSE
var/defence_mode = FALSE
var/defence_mode_deflect_chance = 15
var/leg_overload_mode = FALSE
@@ -133,7 +133,7 @@
var/phasing_energy_drain = 200
var/phase_state = "" //icon_state when phasing
var/strafe = FALSE //If we are strafing
- var/canstrafe = TRUE
+ var/pivot_step = FALSE
var/nextsmash = 0
var/smashcooldown = 3 //deciseconds
var/ejection_distance = 0 //violently ejects the pilot when destroyed
@@ -472,14 +472,14 @@
for(var/mob/M in get_hearers_in_view(7,src))
if(M.client)
speech_bubble_recipients.Add(M.client)
- INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30)
+ INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay_global, image('icons/mob/talk.dmi', src, "machine[say_test(raw_message)]",MOB_LAYER+1), speech_bubble_recipients, 30)
////////////////////////////
///// Action processing ////
////////////////////////////
-/obj/mecha/proc/click_action(atom/target,mob/user,params)
+/obj/mecha/proc/click_action(atom/target, mob/user, params)
if(!occupant || occupant != user )
return
if(!locate(/turf) in list(target,target.loc)) // Prevents inventory from being drilled
@@ -522,7 +522,7 @@
if(HAS_TRAIT(L, TRAIT_NO_STUN_WEAPONS) && !selected.harmful)
to_chat(user, span_warning("You cannot use non-lethal weapons!"))
return
- if(selected.action(target,params))
+ if(selected.action(target, user, params))
selected.start_cooldown()
else if(selected && selected.is_melee())
if(isliving(target) && selected.harmful && HAS_TRAIT(L, TRAIT_PACIFISM))
@@ -536,19 +536,22 @@
if(HAS_TRAIT(L, TRAIT_PACIFISM) && W.cleave)
to_chat(user, span_warning("You don't want to harm other living beings!"))
return
- if(selected.action(target,params))
+ if(selected.action(target, user, params))
selected.start_cooldown()
else
- if(internal_damage & MECHA_INT_CONTROL_LOST)
- target = pick(oview(1,src))
- if(!melee_can_hit || !istype(target, /atom))
- return
- if(equipment_disabled)
- return
- target.mech_melee_attack(src, TRUE)
- melee_can_hit = FALSE
- spawn(melee_cooldown)
- melee_can_hit = TRUE
+ default_melee_attack(target)
+
+/obj/mecha/proc/default_melee_attack(atom/target)
+ if(internal_damage & MECHA_INT_CONTROL_LOST)
+ target = pick(oview(1,src))
+ if(!melee_can_hit || !istype(target, /atom))
+ return
+ if(equipment_disabled)
+ return
+ target.mech_melee_attack(src, TRUE)
+ melee_can_hit = FALSE
+ spawn(melee_cooldown)
+ melee_can_hit = TRUE
/obj/mecha/proc/range_action(atom/target)
@@ -571,8 +574,6 @@
. = ..()
if(.)
return TRUE
- if(thrusters_active && movement_dir && use_power(step_energy_drain))
- return TRUE
var/atom/movable/backup = get_spacemove_backup()
if(backup)
@@ -606,8 +607,10 @@
/obj/mecha/proc/domove(direction)
if(can_move >= world.time)
return FALSE
- if(direction == DOWN || direction == UP)
- return FALSE //nuh uh
+ if((direction & (DOWN|UP)) && !get_step_multiz(get_turf(src), direction))
+ direction &= ~(DOWN|UP) // remove vertical component
+ if(!direction) // don't bother moving without a direction
+ return FALSE
if(!Process_Spacemove(direction))
return FALSE
if(!has_charge(step_energy_drain))
@@ -654,30 +657,35 @@
/obj/mecha/proc/mechturn(direction)
setDir(direction)
- if(turnsound)
+ if(turnsound && has_gravity())
playsound(src,turnsound,40,1)
return TRUE
/obj/mecha/proc/mechstep(direction)
var/current_dir = dir
var/result = step(src,direction)
- if(strafe)
+ if(strafe && !pivot_step)
setDir(current_dir)
- if(result && stepsound)
+ if(result && stepsound && has_gravity())
playsound(src,stepsound,40,1)
return result
/obj/mecha/proc/mechsteprand()
var/result = step_rand(src)
- if(result && stepsound)
+ if(result && stepsound && has_gravity())
playsound(src,stepsound,40,1)
return result
+/obj/mecha/setDir()
+ . = ..()
+ if(occupant)
+ occupant.setDir(dir) // keep the pilot facing the direction of the mech or bad things happen
+
/obj/mecha/Bump(atom/obstacle)
var/turf/newloc = get_step(src,dir)
var/area/newarea = newloc.loc
- if(phasing && ((newloc.flags_1 & NOJAUNT_1) || newarea.noteleport || SSmapping.level_trait(newloc.z, ZTRAIT_NOPHASE)))
+ if(phasing && ((newloc.turf_flags & NOJAUNT) || newarea.noteleport || SSmapping.level_trait(newloc.z, ZTRAIT_NOPHASE)))
to_chat(occupant, span_warning("Some strange aura is blocking the way."))
return //If we're trying to phase and it's NOT ALLOWED, don't bump
@@ -855,11 +863,12 @@
occupant = AI
silicon_pilot = TRUE
icon_state = initial(icon_state)
- update_appearance(UPDATE_ICON)
+ update_appearance()
playsound(src, 'sound/machines/windowdoor.ogg', 50, 1)
if(!internal_damage)
SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50))
- AI.cancel_camera()
+ AI.eyeobj?.forceMove(src)
+ AI.eyeobj?.RegisterSignal(src, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/camera/ai_eye, update_visibility))
AI.controlled_mech = src
AI.remote_control = src
AI.mobility_flags = ALL //Much easier than adding AI checks! Be sure to set this back to 0 if you decide to allow an AI to leave a mech somehow.
@@ -1271,6 +1280,13 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013???
to_chat(user, span_notice("None of the equipment on this exosuit can use this ammo!"))
return FALSE
+// Matter resupply and upgrades for mounted RCDs
+/obj/mecha/proc/matter_resupply(obj/item/I, mob/user)
+ for(var/obj/item/mecha_parts/mecha_equipment/rcd/R in equipment)
+ R.internal_rcd.attackby(I, user)
+ if(QDELETED(I))
+ return
+
// Checks the pilot and their clothing for mech speed buffs
/obj/mecha/proc/check_eva()
var/evaNum = 1
diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm
index bebbc829af15..fc850f051ffb 100644
--- a/code/game/mecha/mecha_actions.dm
+++ b/code/game/mecha/mecha_actions.dm
@@ -8,8 +8,9 @@
cycle_action.Grant(user, src)
lights_action.Grant(user, src)
stats_action.Grant(user, src)
- if(canstrafe)
- strafing_action.Grant(user, src)
+ strafing_action.Grant(user, src)
+ for(var/obj/item/mecha_parts/mecha_equipment/E as anything in equipment)
+ E.grant_actions(user)
/obj/mecha/proc/RemoveActions(mob/living/user, human_occupant = 0)
@@ -19,8 +20,9 @@
cycle_action.Remove(user)
lights_action.Remove(user)
stats_action.Remove(user)
- if(canstrafe)
- strafing_action.Remove(user)
+ strafing_action.Remove(user)
+ for(var/obj/item/mecha_parts/mecha_equipment/E as anything in equipment)
+ E.remove_actions(user)
/datum/action/innate/mecha
check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS
@@ -134,7 +136,7 @@
/datum/action/innate/mecha/strafe
name = "Toggle Strafing. Disabled when Alt is held."
- button_icon_state = "strafe"
+ button_icon_state = "strafe_off"
/datum/action/innate/mecha/strafe/Activate()
if(!owner || !chassis || chassis.occupant != owner)
@@ -151,25 +153,12 @@
occupant_message("Toggled strafing mode [strafe?"on":"off"].")
log_message("Toggled strafing mode [strafe?"on":"off"].", LOG_MECHA)
+ strafing_action.button_icon_state = "strafe_[strafe?"on":"off"]"
strafing_action.build_all_button_icons()
//////////////////////////////////////// Specific Ability Actions ///////////////////////////////////////////////
//Need to be granted by the mech type, Not default abilities.
-/datum/action/innate/mecha/mech_toggle_thrusters
- name = "Toggle Thrusters"
- button_icon_state = "mech_thrusters_off"
-
-/datum/action/innate/mecha/mech_toggle_thrusters/Activate()
- if(!owner || !chassis || chassis.occupant != owner)
- return
- if(chassis.get_charge() > 0)
- chassis.thrusters_active = !chassis.thrusters_active
- button_icon_state = "mech_thrusters_[chassis.thrusters_active ? "on" : "off"]"
- chassis.log_message("Toggled thrusters.", LOG_MECHA)
- chassis.occupant_message("Thrusters [chassis.thrusters_active ?"en":"dis"]abled.")
-
-
/datum/action/innate/mecha/mech_defence_mode
name = "Toggle Defence Mode"
button_icon_state = "mech_defence_mode_off"
@@ -288,3 +277,18 @@
button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]"
chassis.occupant_message("En":"#f00\">Dis"]abled phasing.")
build_all_button_icons()
+
+//////////////////////////////////////// Equipment Actions ///////////////////////////////////////////////
+//Equipment-based actions like RCD mode selection
+
+/datum/action/innate/mecha/equipment
+ var/obj/item/mecha_parts/mecha_equipment/equipment
+
+/datum/action/innate/mecha/equipment/Grant(mob/living/L, obj/mecha/M, obj/item/mecha_parts/mecha_equipment/E)
+ if(E)
+ equipment = E
+ return ..()
+
+/datum/action/innate/mecha/equipment/Destroy()
+ equipment = null
+ return ..()
diff --git a/code/game/mecha/mecha_defense.dm b/code/game/mecha/mecha_defense.dm
index 7c363de5185f..4b9591afbc3d 100644
--- a/code/game/mecha/mecha_defense.dm
+++ b/code/game/mecha/mecha_defense.dm
@@ -111,6 +111,8 @@
if ((!enclosed || istype(Proj, /obj/projectile/bullet/shotgun/slug/uranium))&& occupant && !silicon_pilot && !Proj.force_hit && (Proj.def_zone == BODY_ZONE_HEAD || Proj.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit the pilot of open-canopy mechs
occupant.bullet_act(Proj) //If the sides are open, the occupant can be hit
return BULLET_ACT_HIT
+ if(istype(Proj, /obj/projectile/ion))
+ return ..()
var/booster_deflection_modifier = 1
var/booster_damage_modifier = 1
var/attack_dir = get_dir(src, Proj)
@@ -179,7 +181,8 @@
return
if(get_charge())
use_power((cell.charge * severity / 15))
- take_damage(4 * severity, BURN, ENERGY, 1)
+
+ take_damage(4 * severity, BURN, ENERGY, 1)
log_message("EMP detected", LOG_MECHA, color="red")
if(istype(src, /obj/mecha/combat))
@@ -208,6 +211,15 @@
if(istype(W, /obj/item/mecha_ammo))
ammo_resupply(W, user)
return
+
+ if(istype(W, /obj/item/stack) || istype(W, /obj/item/rcd_ammo) || istype(W, /obj/item/rcd_upgrade))
+ matter_resupply(W, user)
+ return
+
+ if(istype(W, /obj/item/mecha_parts))
+ var/obj/item/mecha_parts/P = W
+ P.try_attach_part(user, src)
+ return
if(W.GetID())
if(add_req_access || maint_access)
@@ -301,11 +313,6 @@
to_chat(user, span_warning("The [name] is at full integrity!"))
return 1
- else if(istype(W, /obj/item/mecha_parts))
- var/obj/item/mecha_parts/P = W
- P.try_attach_part(user, src)
- return
-
else if(istype(W, /obj/item/airlock_scanner)) //yogs start
var/obj/item/airlock_scanner/S = W
S.show_access(src, user) //yogs end
diff --git a/code/game/mecha/mecha_parts.dm b/code/game/mecha/mecha_parts.dm
index 81f83e1c5f1f..45c9149eab5b 100644
--- a/code/game/mecha/mecha_parts.dm
+++ b/code/game/mecha/mecha_parts.dm
@@ -7,7 +7,7 @@
icon = 'icons/mecha/mech_construct.dmi'
icon_state = "blank"
w_class = WEIGHT_CLASS_GIGANTIC
- flags_1 = CONDUCT_1
+ flags_1 = CONDUCT_1 | RAD_NO_CONTAMINATE_1
/obj/item/mecha_parts/proc/try_attach_part(mob/user, obj/mecha/M) //For attaching parts to a finished mech
if(!user.transferItemToLoc(src, M))
diff --git a/code/game/mecha/mecha_topic.dm b/code/game/mecha/mecha_topic.dm
index 1dff7668054b..9c31593b3523 100644
--- a/code/game/mecha/mecha_topic.dm
+++ b/code/game/mecha/mecha_topic.dm
@@ -100,7 +100,6 @@
Environment pressure: [environment_pressure>WARNING_HIGH_PRESSURE ? span_danger("[environment_pressure]"): environment_pressure]kPa Environment temperature: [environment_temperature]°K|[environment_temperature - T0C]°C
[dna_lock?"DNA-locked: [dna_lock] \[Reset\] ":""]
- [thrusters_action.owner ? "Thrusters: [thrusters_active ? "Enabled" : "Disabled"] " : ""]
[defence_action.owner ? "Defence Mode: [defence_mode ? "Enabled" : "Disabled"] " : ""]
[overload_action.owner ? "Leg Actuators Overload: [leg_overload_mode ? "Enabled" : "Disabled"] " : ""]
[smoke_action.owner ? "Smoke: [smoke] " : ""]
diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm
index d605c64a839b..c379795ff78a 100644
--- a/code/game/mecha/medical/medical.dm
+++ b/code/game/mecha/medical/medical.dm
@@ -1,19 +1,5 @@
/obj/mecha/medical
internals_req_access = list(ACCESS_MECH_SCIENCE, ACCESS_MECH_MEDICAL)
-
-/obj/mecha/medical/mechturn(direction)
- setDir(direction)
- playsound(src,'sound/mecha/mechmove01.ogg',40,1)
- return 1
-
-/obj/mecha/medical/mechstep(direction)
- var/result = step(src,direction)
- if(result)
- playsound(src,'sound/mecha/mechstep.ogg',25,1)
- return result
-
-/obj/mecha/medical/mechsteprand()
- var/result = step_rand(src)
- if(result)
- playsound(src,'sound/mecha/mechstep.ogg',25,1)
- return result
\ No newline at end of file
+ stepsound = 'sound/mecha/mechstep.ogg'
+ turnsound = 'sound/mecha/mechmove01.ogg'
+ pivot_step = TRUE
diff --git a/code/game/mecha/working/clarke.dm b/code/game/mecha/working/clarke.dm
index 21219c90f8aa..69dc1e31b3af 100644
--- a/code/game/mecha/working/clarke.dm
+++ b/code/game/mecha/working/clarke.dm
@@ -6,16 +6,17 @@
max_temperature = 65000
max_integrity = 200
step_in = 1.25
- fast_pressure_step_in = 1.25
- slow_pressure_step_in = 1.8
+ fast_pressure_step_in = 1.5
+ slow_pressure_step_in = 2
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
light_power = 7
deflect_chance = 10
- armor = list(MELEE = 20, BULLET = 10, LASER = 20, ENERGY = 0, BOMB = 60, BIO = 0, RAD = 70, FIRE = 100, ACID = 100)
+ flags_1 = HEAR_1 | RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ armor = list(MELEE = 20, BULLET = 10, LASER = 20, ENERGY = 0, BOMB = 60, BIO = 0, RAD = 100, FIRE = 100, ACID = 100)
max_equip = 7
wreckage = /obj/structure/mecha_wreckage/clarke
enter_delay = 40
- canstrafe = FALSE
+ pivot_step = TRUE
/// Handles an internal ore box for Clarke
var/obj/structure/ore_box/box
omnidirectional_attacks = TRUE
@@ -50,6 +51,14 @@
var/mob/living/brain/B = M.brainmob
hud.show_to(B)
+/obj/mecha/working/clarke/domove(direction)
+ if(ISDIAGONALDIR(direction) && strafe)
+ if(EWCOMPONENT(dir))
+ direction &= ~(NORTH|SOUTH)
+ else if(NSCOMPONENT(dir))
+ direction &= ~(EAST|WEST)
+ return ..(direction)
+
//Ore Box Controls
///Special equipment for the Clarke mech, handles moving ore without giving the mech a hydraulic clamp and cargo compartment.
diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm
index 046ce62ff769..f9eb136da172 100644
--- a/code/game/mecha/working/ripley.dm
+++ b/code/game/mecha/working/ripley.dm
@@ -18,6 +18,9 @@
enclosed = FALSE //Normal ripley has an open cockpit design
enter_delay = 10 //can enter in a quarter of the time of other mechs
exit_delay = 10
+ /// Custom Ripley step and turning sounds (from TGMC)
+ stepsound = 'sound/mecha/powerloader_step.ogg'
+ turnsound = 'sound/mecha/powerloader_turn2.ogg'
opacity = FALSE //Ripley has a window
/obj/mecha/working/ripley/Move()
@@ -67,7 +70,8 @@
fast_pressure_step_in = 1.75 //step_in while in low pressure conditions
slow_pressure_step_in = 3 //step_in while in normal pressure conditions
step_in = 3
- armor = list(MELEE = 40, BULLET = 20, LASER = 10, ENERGY = 0, BOMB = 40, BIO = 100, RAD = 55, FIRE = 100, ACID = 100)
+ flags_1 = HEAR_1 | RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ armor = list(MELEE = 40, BULLET = 20, LASER = 10, ENERGY = 0, BOMB = 40, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
wreckage = /obj/structure/mecha_wreckage/ripley/mkii
enclosed = TRUE
enter_delay = 40
@@ -80,12 +84,13 @@
icon_state = "firefighter"
max_temperature = 65000
max_integrity = 250
- fast_pressure_step_in = 2 //step_in while in low pressure conditions
- slow_pressure_step_in = 4 //step_in while in normal pressure conditions
- step_in = 4
+ fast_pressure_step_in = 1.75 //step_in while in low pressure conditions
+ slow_pressure_step_in = 3 //step_in while in normal pressure conditions
+ step_in = 3
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
light_power = 7
- armor = list(MELEE = 40, BULLET = 30, LASER = 30, ENERGY = 0, BOMB = 60, BIO = 100, RAD = 70, FIRE = 100, ACID = 100)
+ flags_1 = HEAR_1 | RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ armor = list(MELEE = 40, BULLET = 30, LASER = 30, ENERGY = 0, BOMB = 60, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
max_equip = 5 // More armor, less tools
wreckage = /obj/structure/mecha_wreckage/ripley/firefighter
enclosed = TRUE
@@ -93,6 +98,15 @@
silicon_icon_state = null
opacity = TRUE
+// maybe janitor ERTs could get this or something?
+/obj/mecha/working/ripley/janitorial/Initialize(mapload)
+ . = ..()
+ var/obj/item/mecha_parts/mecha_equipment/washer = new /obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer
+ washer.attach(src)
+ var/obj/item/mecha_parts/mecha_equipment/big_mop = new /obj/item/mecha_parts/mecha_equipment/melee_weapon/mop
+ big_mop.attach(src)
+ var/obj/item/mecha_parts/mecha_equipment/swatter = new /obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter
+ swatter.attach(src)
/obj/mecha/working/ripley/deathripley
desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE"
diff --git a/code/game/movable_luminosity.dm b/code/game/movable_luminosity.dm
index c3bf5ed2525d..7a6eaddafd9e 100644
--- a/code/game/movable_luminosity.dm
+++ b/code/game/movable_luminosity.dm
@@ -11,6 +11,7 @@
affecting_dynamic_lumi = highest
luminosity += affecting_dynamic_lumi
+
///Helper to change several lighting overlay settings.
/atom/movable/proc/set_light_range_power_color(range, power, color)
set_light_range(range)
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index d2ba03578acf..92fc2ea4c3a2 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -67,6 +67,13 @@
M.buckling = null
return FALSE
+ // This signal will check if the mob is mounting this atom to ride it. There are 3 possibilities for how this goes
+ // 1. This movable doesn't have a ridable element and can't be ridden, so nothing gets returned, so continue on
+ // 2. There's a ridable element but we failed to mount it for whatever reason (maybe it has no seats left, for example), so we cancel the buckling
+ // 3. There's a ridable element and we were successfully able to mount, so keep it going and continue on with buckling
+ // if(SEND_SIGNAL(src, COMSIG_MOVABLE_PREBUCKLE, M, force, buckle_mob_flags) & COMPONENT_BLOCK_BUCKLE)
+ // return FALSE
+
if(M.pulledby)
if(buckle_prevents_pull)
M.pulledby.stop_pulling()
@@ -96,18 +103,32 @@
M.adjust_fire_stacks(1)
M.ignite_mob()
-/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force=FALSE)
- if(istype(buckled_mob) && buckled_mob.buckled == src && (buckled_mob.can_unbuckle() || force))
- . = buckled_mob
- buckled_mob.buckled = null
- buckled_mob.anchored = initial(buckled_mob.anchored)
- buckled_mob.update_mobility()
- buckled_mob.clear_alert("buckled")
- buckled_mob.set_glide_size(DELAY_TO_GLIDE_SIZE(buckled_mob.total_multiplicative_slowdown()))
- buckled_mobs -= buckled_mob
- SEND_SIGNAL(src, COMSIG_MOVABLE_UNBUCKLE, buckled_mob, force)
-
- post_unbuckle_mob(.)
+/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
+ if(!isliving(buckled_mob))
+ CRASH("Non-living [buckled_mob] thing called unbuckle_mob() for source.")
+ if(buckled_mob.buckled != src)
+ CRASH("[buckled_mob] called unbuckle_mob() for source while having buckled as [buckled_mob.buckled].")
+ if(!force && !buckled_mob.can_unbuckle())
+ return
+ . = buckled_mob
+ buckled_mob.buckled = null
+ buckled_mob.anchored = initial(buckled_mob.anchored)
+ buckled_mob.update_mobility()
+ buckled_mob.clear_alert("buckled")
+ buckled_mob.set_glide_size(DELAY_TO_GLIDE_SIZE(buckled_mob.total_multiplicative_slowdown()))
+ buckled_mobs -= buckled_mob
+ SEND_SIGNAL(src, COMSIG_MOVABLE_UNBUCKLE, buckled_mob, force)
+
+ if(can_fall)
+ var/turf/location = buckled_mob.loc
+ if(istype(location) && !buckled_mob.currently_z_moving)
+ location.zFall(buckled_mob)
+
+ post_unbuckle_mob(.)
+
+ if(!QDELETED(buckled_mob) && !buckled_mob.currently_z_moving && isturf(buckled_mob.loc)) // In the case they unbuckled to a flying movable midflight.
+ var/turf/pitfall = buckled_mob.loc
+ pitfall?.zFall(buckled_mob)
/atom/movable/proc/unbuckle_all_mobs(force=FALSE)
if(!has_buckled_mobs())
diff --git a/code/game/objects/effects/blessing.dm b/code/game/objects/effects/blessing.dm
index 0a3a9510b18c..fa58657ff59f 100644
--- a/code/game/objects/effects/blessing.dm
+++ b/code/game/objects/effects/blessing.dm
@@ -16,10 +16,10 @@
I.alpha = 64
I.appearance_flags = RESET_ALPHA
add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, "blessing", I)
- RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORT, PROC_REF(block_cult_teleport))
+ RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_cult_teleport))
/obj/effect/blessing/Destroy()
- UnregisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORT)
+ UnregisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORTING)
return ..()
/obj/effect/blessing/proc/block_cult_teleport(datum/source, channel, turf/origin, turf/destination)
diff --git a/code/game/objects/effects/contraband.dm b/code/game/objects/effects/contraband.dm
index f06d0b4be554..d0540c13aebf 100644
--- a/code/game/objects/effects/contraband.dm
+++ b/code/game/objects/effects/contraband.dm
@@ -130,7 +130,7 @@
return
// Deny placing posters on currently-diagonal walls, although the wall may change in the future.
- if (smooth & SMOOTH_DIAGONAL)
+ if (smoothing_flags & SMOOTH_DIAGONAL_CORNERS)
for (var/O in overlays)
var/image/I = O
if(copytext(I.icon_state, 1, 3) == "d-") //3 == length("d-") + 1
@@ -169,7 +169,7 @@
/turf/closed/proc/place_borg_poster(obj/item/wantedposterposter/P, mob/user)
// Deny placing posters on currently-diagonal walls, although the wall may change in the future.
- if (smooth & SMOOTH_DIAGONAL)
+ if (smoothing_flags & SMOOTH_DIAGONAL_CORNERS)
for (var/O in overlays)
var/image/I = O
if (copytext(I.icon_state, 1, 3) == "d-")
diff --git a/code/game/objects/effects/countdown.dm b/code/game/objects/effects/countdown.dm
index 7f5933b4c125..f9a73eba6e5d 100644
--- a/code/game/objects/effects/countdown.dm
+++ b/code/game/objects/effects/countdown.dm
@@ -7,7 +7,7 @@
invisibility = INVISIBILITY_OBSERVER
anchored = TRUE
- layer = GHOST_LAYER
+ plane = GHOST_PLANE
color = "#ff0000" // text color
var/text_size = 3 // larger values clip when the displayed text is larger than 2 digits.
var/started = FALSE
@@ -107,7 +107,7 @@
name = "gateway countdown"
text_size = 1
color = "#BE8700"
- layer = POINT_LAYER
+ plane = POINT_PLANE
/obj/effect/countdown/clockworkgate/get_value()
var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = attached_to
diff --git a/code/game/objects/effects/cursor_catcher.dm b/code/game/objects/effects/cursor_catcher.dm
new file mode 100644
index 000000000000..3624018afc89
--- /dev/null
+++ b/code/game/objects/effects/cursor_catcher.dm
@@ -0,0 +1,66 @@
+/// An effect which tracks the cursor's location on the screen
+/atom/movable/screen/fullscreen/cursor_catcher
+ icon_state = "fullscreen_blocker" // Fullscreen semi transparent icon
+ plane = HUD_PLANE
+ mouse_opacity = MOUSE_OPACITY_ICON
+ /// The mob whose cursor we are tracking.
+ var/mob/owner
+ /// Client view size of the scoping mob.
+ var/list/view_list
+ /// Pixel x we send to the scope component.
+ var/given_x
+ /// Pixel y we send to the scope component.
+ var/given_y
+ /// The turf we send to the scope component.
+ var/turf/given_turf
+ /// Mouse parameters, for calculation.
+ var/mouse_params
+
+/// Links this up with a mob
+/atom/movable/screen/fullscreen/cursor_catcher/proc/assign_to_mob(mob/owner)
+ src.owner = owner
+ view_list = getviewsize(owner.client.view)
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+ RegisterSignal(owner, COMSIG_VIEWDATA_UPDATE, PROC_REF(on_viewdata_update))
+ calculate_params()
+
+/// Update when the mob we're assigned to has moved
+/atom/movable/screen/fullscreen/cursor_catcher/proc/on_move(atom/source, atom/oldloc, dir, forced)
+ SIGNAL_HANDLER
+
+ if(!given_turf)
+ return
+ var/x_offset = source.loc.x - oldloc.x
+ var/y_offset = source.loc.y - oldloc.y
+ given_turf = locate(given_turf.x+x_offset, given_turf.y+y_offset, given_turf.z)
+
+/// Update when our screen size changes
+/atom/movable/screen/fullscreen/cursor_catcher/proc/on_viewdata_update(datum/source, view)
+ SIGNAL_HANDLER
+
+ view_list = getviewsize(view)
+
+/atom/movable/screen/fullscreen/cursor_catcher/MouseEntered(location, control, params)
+ . = ..()
+ MouseMove(location, control, params)
+ if(usr == owner)
+ calculate_params()
+
+/atom/movable/screen/fullscreen/cursor_catcher/MouseMove(location, control, params)
+ if(usr != owner)
+ return
+ mouse_params = params
+
+/atom/movable/screen/fullscreen/cursor_catcher/proc/calculate_params()
+ var/list/modifiers = params2list(mouse_params)
+ var/icon_x = text2num(LAZYACCESS(modifiers, VIS_X))
+ if(isnull(icon_x))
+ icon_x = text2num(LAZYACCESS(modifiers, ICON_X))
+ var/icon_y = text2num(LAZYACCESS(modifiers, VIS_Y))
+ if(isnull(icon_y))
+ icon_y = text2num(LAZYACCESS(modifiers, ICON_Y))
+ var/our_x = round(icon_x / world.icon_size)
+ var/our_y = round(icon_y / world.icon_size)
+ given_turf = locate(owner.x + our_x - round(view_list[1]/2), owner.y + our_y - round(view_list[2]/2), owner.z)
+ given_x = round(icon_x - world.icon_size * our_x, 1)
+ given_y = round(icon_y - world.icon_size * our_y, 1)
diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm
index 75b92b50c032..15b5bfe7d593 100644
--- a/code/game/objects/effects/decals/cleanable.dm
+++ b/code/game/objects/effects/decals/cleanable.dm
@@ -34,6 +34,10 @@
diseases_to_add += D
if(LAZYLEN(diseases_to_add))
AddComponent(/datum/component/infective, diseases_to_add)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/decal/cleanable/proc/replace_decal(obj/effect/decal/cleanable/C) // Returns true if we should give up in favor of the pre-existing decal
if(mergeable_decal)
@@ -83,25 +87,11 @@
//Add "bloodiness" of this blood's type, to the human's shoes
//This is on /cleanable because fuck this ancient mess
-/obj/effect/decal/cleanable/Crossed(atom/movable/O)
- ..()
- if(ishuman(O))
- var/mob/living/carbon/human/H = O
- if(H.shoes && blood_state && bloodiness && !HAS_TRAIT(H, TRAIT_LIGHT_STEP))
- var/obj/item/clothing/shoes/S = H.shoes
- if(!istype(S) || !S.can_be_bloody)
- return
- var/add_blood = 0
- if(bloodiness >= BLOOD_GAIN_PER_STEP)
- add_blood = BLOOD_GAIN_PER_STEP
- else
- add_blood = bloodiness
- bloodiness -= add_blood
- S.bloody_shoes[blood_state] = min(MAX_SHOE_BLOODINESS,S.bloody_shoes[blood_state]+add_blood)
- S.add_blood_DNA(return_blood_DNA())
- S.blood_state = blood_state
- update_appearance(UPDATE_ICON)
- H.update_inv_shoes()
+/obj/effect/decal/cleanable/proc/on_entered(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
+ if(iscarbon(AM) && blood_state && bloodiness > 40)
+ SEND_SIGNAL(AM, COMSIG_STEP_ON_BLOOD, src)
+ update_icon()
/**
diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm
index eeaa6dd4e4bf..f433da5b08fe 100644
--- a/code/game/objects/effects/decals/cleanable/aliens.dm
+++ b/code/game/objects/effects/decals/cleanable/aliens.dm
@@ -10,8 +10,8 @@
blood_state = BLOOD_STATE_XENO
/obj/effect/decal/cleanable/xenoblood/Initialize(mapload)
+ add_blood_DNA(list("UNKNOWN DNA" = get_blood_type("X"))) // Needs to happen before ..()
. = ..()
- add_blood_DNA(list("UNKNOWN DNA" = "X*"))
/obj/effect/decal/cleanable/xenoblood/xsplatter
random_icon_states = list("xgibbl1", "xgibbl2", "xgibbl3", "xgibbl4", "xgibbl5")
@@ -71,10 +71,6 @@
icon_state = "xgiblarvatorso"
random_icon_states = list("xgiblarvahead", "xgiblarvatorso")
-/obj/effect/decal/cleanable/blood/xtracks
+/obj/effect/decal/cleanable/xenoblood/xtracks
icon_state = "xtracks"
random_icon_states = null
-
-/obj/effect/decal/cleanable/blood/xtracks/Initialize(mapload)
- . = ..()
- add_blood_DNA(list("Unknown DNA" = "X*"))
diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm
index 9db5623a991d..f700c847f6ee 100644
--- a/code/game/objects/effects/decals/cleanable/humans.dm
+++ b/code/game/objects/effects/decals/cleanable/humans.dm
@@ -1,43 +1,56 @@
/obj/effect/decal/cleanable/blood
name = "blood"
- desc = "It's red and gooey. Perhaps it's the chef's cooking?"
+ desc = "It's weird and gooey. Perhaps it's the chef's cooking?"
icon = 'icons/effects/blood.dmi'
icon_state = "floor1"
+ color = COLOR_BLOOD
random_icon_states = list("floor1", "floor2", "floor3", "floor4", "floor5", "floor6", "floor7")
blood_state = BLOOD_STATE_HUMAN
bloodiness = BLOOD_AMOUNT_PER_DECAL
-/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C)
- C.add_blood_DNA(return_blood_DNA())
- if (bloodiness)
- if (C.bloodiness < MAX_SHOE_BLOODINESS)
- C.bloodiness += bloodiness
+ var/dryname = "dried blood" //when the blood lasts long enough, it becomes dry and gets a new name
+ var/drydesc = "Looks like it's been here a while. Eew." //as above
+ var/drytime = 0
+
+/obj/effect/decal/cleanable/blood/Initialize(mapload)
+ . = ..()
+ if(bloodiness)
+ start_drying()
+ else
+ dry()
+
+/obj/effect/decal/cleanable/blood/process()
+ if(world.time > drytime)
+ dry()
+
+/obj/effect/decal/cleanable/blood/Destroy()
+ STOP_PROCESSING(SSobj, src)
return ..()
-/obj/effect/decal/cleanable/whiteblood
- name = "\"blood\""
- desc = "It's an unsettling colour. Maybe it's the chef's cooking?"
- icon = 'icons/effects/blood.dmi'
- icon_state = "genericsplatter1"
- random_icon_states = list("genericsplatter1", "genericsplatter2", "genericsplatter3", "genericsplatter4", "genericsplatter5", "genericsplatter6")
+/obj/effect/decal/cleanable/blood/proc/get_timer()
+ drytime = world.time + 3 MINUTES
-/obj/effect/decal/cleanable/whiteblood/ethereal
- name = "glowing \"blood\""
- desc = "It has a fading glow. Surely it's just the chef's cooking?"
- light_power = 1
- light_range = 2
- light_color = "#eef442"
+/obj/effect/decal/cleanable/blood/proc/start_drying()
+ get_timer()
+ START_PROCESSING(SSobj, src)
-/obj/effect/decal/cleanable/whiteblood/ethereal/Initialize(mapload, list/datum/disease/diseases)
- . = ..()
- add_atom_colour(light_color, FIXED_COLOUR_PRIORITY)
- addtimer(CALLBACK(src, PROC_REF(Fade)), 1 MINUTES)
+/obj/effect/decal/cleanable/blood/proc/dry()
+ if(bloodiness > 20)
+ bloodiness -= BLOOD_AMOUNT_PER_DECAL
+ get_timer()
+ else
+ name = dryname
+ desc = drydesc
+ bloodiness = 0
+ var/temp_color = ReadHSV(RGBtoHSV(color || COLOR_WHITE))
+ color = HSVtoRGB(hsv(temp_color[1], temp_color[2], max(temp_color[3] - 100,min(temp_color[3],10))))
+ STOP_PROCESSING(SSobj, src)
-/obj/effect/decal/cleanable/whiteblood/ethereal/proc/Fade()
- name = "faded \"blood\""
- light_power = 0
- light_range = 0
- update_light()
+/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C)
+ C.add_blood_DNA(return_blood_DNA())
+ if (bloodiness)
+ C.bloodiness = min((C.bloodiness + bloodiness), BLOOD_AMOUNT_PER_DECAL)
+ return ..()
/obj/effect/decal/cleanable/blood/old
name = "dried blood"
@@ -56,6 +69,8 @@
/obj/effect/decal/cleanable/blood/splatter
icon_state = "gibbl1"
random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5")
+ dryname = "dried tracks"
+ drydesc = "Some old bloody tracks left by wheels. Machines are evil, perhaps."
/obj/effect/decal/cleanable/blood/splatter/over_window // special layer/plane set to appear on windows
layer = ABOVE_WINDOW_LAYER
@@ -69,25 +84,23 @@
icon_state = "tracks"
random_icon_states = null
-/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose
+/obj/effect/decal/cleanable/blood/trail_holder //not a child of blood on purpose //nice fucking descriptive comment jackass, fuck you //hello fikou //this is cowbot
name = "blood"
icon = 'icons/effects/blood.dmi'
desc = "Your instincts say you shouldn't be following these."
+ icon_state = null
+ random_icon_states = null
var/list/existing_dirs = list()
-/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in()
- return TRUE
-
-/obj/effect/decal/cleanable/trail_holder/proc/Etherealify()
+/obj/effect/decal/cleanable/blood/proc/Etherealify()
name = "glowing \"blood\""
light_power = 1
- light_range = 2
+ light_range = 1
light_color = "#eef442"
update_light()
- add_atom_colour(light_color, FIXED_COLOUR_PRIORITY)
- addtimer(CALLBACK(src, PROC_REF(Fade)), 1 MINUTES)
+ addtimer(CALLBACK(src, PROC_REF(Fade)), 3 MINUTES)
-/obj/effect/decal/cleanable/trail_holder/proc/Fade()
+/obj/effect/decal/cleanable/blood/proc/Fade()
name = "faded \"blood\""
light_power = 0
light_range = 0
@@ -102,26 +115,22 @@
random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6")
mergeable_decal = FALSE
- var/already_rotting = FALSE
+ dryname = "rotting gibs"
+ drydesc = "They look bloody and gruesome while some terrible smell fills the air."
/obj/effect/decal/cleanable/blood/gibs/Initialize(mapload, list/datum/disease/diseases)
. = ..()
reagents.add_reagent(/datum/reagent/liquidgibs, 5)
- if(already_rotting)
- start_rotting(rename=FALSE)
- else
- addtimer(CALLBACK(src, PROC_REF(start_rotting)), 2 MINUTES)
+ var/mutable_appearance/gib_overlay = mutable_appearance(icon, "[icon_state]-overlay", appearance_flags = RESET_COLOR)
+ add_overlay(gib_overlay)
-/obj/effect/decal/cleanable/blood/gibs/proc/start_rotting(rename=TRUE)
- if(rename)
- name = "rotting [initial(name)]"
- desc += " They smell terrible."
- AddComponent(/datum/component/rot/gibs)
+/obj/effect/decal/cleanable/blood/gibs/dry()
+ . = ..()
/obj/effect/decal/cleanable/blood/gibs/ex_act(severity, target)
return
-/obj/effect/decal/cleanable/blood/gibs/Crossed(atom/movable/L)
+/obj/effect/decal/cleanable/blood/gibs/on_entered(datum/source, atom/movable/L)
if(isliving(L) && has_gravity(loc))
playsound(loc, 'sound/effects/gib_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 20 : 50, TRUE)
. = ..()
@@ -131,11 +140,13 @@
var/list/diseases = list()
SEND_SIGNAL(src, COMSIG_GIBS_STREAK, directions, diseases)
var/direction = pick(directions)
- for(var/i in 0 to pick(0, 200; 1, 150; 2, 50))
+ for(var/i in 0 to pick(0, 1, 2))
if (!mapload)
sleep(0.2 SECONDS)
if(i > 0)
- new /obj/effect/decal/cleanable/blood/splatter(loc, diseases)
+ var/obj/effect/decal/cleanable/blood/splatter/splat = new /obj/effect/decal/cleanable/blood/splatter(loc, diseases)
+ if(!QDELETED(splat) && HAS_BLOOD_DNA(src))
+ splat.add_blood_DNA(src.return_blood_DNA())
if(!step_to(src, get_step(src, direction), 0))
break
@@ -166,13 +177,14 @@
/obj/effect/decal/cleanable/blood/gibs/old
name = "old rotting gibs"
desc = "Space Jesus, why didn't anyone clean this up? They smell terrible."
+ icon_state = "gib1-old"
bloodiness = 0
- already_rotting = TRUE
+ dryname = "old rotting gibs"
+ drydesc = "Space Jesus, why didn't anyone clean this up? They smell terrible."
/obj/effect/decal/cleanable/blood/gibs/old/Initialize(mapload, list/datum/disease/diseases)
. = ..()
setDir(pick(1,2,4,8))
- icon_state += "-old"
add_blood_DNA(list("Non-human DNA" = random_blood_type()))
/obj/effect/decal/cleanable/blood/drip
@@ -182,6 +194,8 @@
random_icon_states = list("drip1","drip2","drip3","drip4","drip5")
bloodiness = 0
var/drips = 1
+ dryname = "drips of blood"
+ drydesc = "It's red."
/obj/effect/decal/cleanable/blood/drip/can_bloodcrawl_in()
return TRUE
@@ -198,32 +212,13 @@
blood_state = BLOOD_STATE_HUMAN //the icon state to load images from
var/entered_dirs = 0
var/exited_dirs = 0
+ /// List of shoe or other clothing that covers feet types that have made footprints here.
var/list/shoe_types = list()
+ /// List of species that have made footprints here.
+ var/list/species_types = list()
-/obj/effect/decal/cleanable/blood/footprints/Crossed(atom/movable/O)
- ..()
- if(ishuman(O))
- var/mob/living/carbon/human/H = O
- var/obj/item/clothing/shoes/S = H.shoes
- if(S && S.bloody_shoes[blood_state])
- S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0)
- shoe_types |= S.type
- if (!(entered_dirs & H.dir))
- entered_dirs |= H.dir
- update_appearance(UPDATE_ICON)
-
-/obj/effect/decal/cleanable/blood/footprints/Uncrossed(atom/movable/O)
- ..()
- if(ishuman(O))
- var/mob/living/carbon/human/H = O
- var/obj/item/clothing/shoes/S = H.shoes
- if(S && istype(S) && S.bloody_shoes[blood_state])
- S.bloody_shoes[blood_state] = max(S.bloody_shoes[blood_state] - BLOOD_LOSS_PER_STEP, 0)
- shoe_types |= S.type
- if (!(exited_dirs & H.dir))
- exited_dirs |= H.dir
- update_appearance(UPDATE_ICON)
-
+ dryname = "dried footprints"
+ drydesc = "HMM... SOMEONE WAS HERE!"
/obj/effect/decal/cleanable/blood/footprints/update_overlays()
. = ..()
@@ -240,21 +235,32 @@
GLOB.bloody_footprints_cache["exited-[blood_state]-[Ddir]"] = bloodstep_overlay = image(icon, "[blood_state]2", dir = Ddir)
. += bloodstep_overlay
- alpha = BLOODY_FOOTPRINT_BASE_ALPHA + bloodiness
+ alpha = min(BLOODY_FOOTPRINT_BASE_ALPHA + (255 - BLOODY_FOOTPRINT_BASE_ALPHA) * bloodiness / (BLOOD_ITEM_MAX / 2), 255)
/obj/effect/decal/cleanable/blood/footprints/examine(mob/user)
. = ..()
- if(shoe_types.len)
- . += "You recognise the footprints as belonging to:\n"
- for(var/shoe in shoe_types)
- var/obj/item/clothing/shoes/S = shoe
- . += "[icon2html(initial(S.icon), user)] Some [initial(S.name)].\n"
+ if((shoe_types.len + species_types.len) > 0)
+ . += "You recognise the footprints as belonging to:"
+ for(var/sole in shoe_types)
+ var/obj/item/clothing/item = sole
+ var/article = initial(item.gender) == PLURAL ? "Some" : "A"
+ . += "[icon2html(initial(item.icon), user, initial(item.icon_state))] [article] [initial(item.name)]."
+ for(var/species in species_types)
+ // god help me
+ if(species == "unknown")
+ . += "Some feet."
+ else if(species == "monkey")
+ . += "[icon2html('icons/mob/monkey.dmi', user, "monkey1")] Some monkey feet."
+ else if(species == "human")
+ . += "[icon2html('icons/mob/human_parts.dmi', user, "default_human_l_leg")] Some human feet."
+ else
+ . += "[icon2html('icons/mob/human_parts.dmi', user, "[species]_l_leg")] Some [species] feet."
/obj/effect/decal/cleanable/blood/footprints/replace_decal(obj/effect/decal/cleanable/C)
if(blood_state != C.blood_state) //We only replace footprints of the same type as us
- return
- ..()
+ return FALSE
+ return ..()
/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in()
if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY))
@@ -277,9 +283,11 @@
/// Insurance so that we don't keep moving once we hit a stoppoint
var/hit_endpoint = FALSE
-/obj/effect/decal/cleanable/blood/hitsplatter/Initialize(mapload, splatter_strength)
+/obj/effect/decal/cleanable/blood/hitsplatter/Initialize(mapload, splatter_strength, set_color)
. = ..()
prev_loc = loc //Just so we are sure prev_loc exists
+ if(set_color)
+ color = set_color
if(splatter_strength)
src.splatter_strength = splatter_strength
@@ -343,6 +351,7 @@
land_on_window(bumped_atom)
else
var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new(prev_loc)
+ final_splatter.color = color
final_splatter.pixel_x = (dir == EAST ? 32 : (dir == WEST ? -32 : 0))
final_splatter.pixel_y = (dir == NORTH ? 32 : (dir == SOUTH ? -32 : 0))
else // This will only happen if prev_loc is not even a turf, which is highly unlikely.
@@ -354,6 +363,7 @@
if(!the_window.fulltile)
return
var/obj/effect/decal/cleanable/blood/splatter/over_window/final_splatter = new
+ final_splatter.color = color
final_splatter.forceMove(the_window)
the_window.vis_contents += final_splatter
the_window.bloodied = TRUE
diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm
index b3260fd51c58..3d2ab3b5b207 100644
--- a/code/game/objects/effects/decals/cleanable/misc.dm
+++ b/code/game/objects/effects/decals/cleanable/misc.dm
@@ -48,29 +48,40 @@
/obj/effect/decal/cleanable/dirt
name = "dirt"
desc = "Someone should clean that up."
- icon_state = "dirt"
- canSmoothWith = list(/obj/effect/decal/cleanable/dirt, /turf/closed/wall, /obj/structure/falsewall)
- smooth = SMOOTH_FALSE
+ icon = 'icons/effects/dirt.dmi'
+ icon_state = "dirt-flat-0"
+ base_icon_state = "dirt"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ smoothing_flags = NONE
+ smoothing_groups = SMOOTH_GROUP_CLEANABLE_DIRT
+ canSmoothWith = SMOOTH_GROUP_CLEANABLE_DIRT + SMOOTH_GROUP_WALLS
/obj/effect/decal/cleanable/dirt/Initialize(mapload)
. = ..()
+ icon_state = pick("dirt-flat-0","dirt-flat-1","dirt-flat-2","dirt-flat-3")
+ var/obj/structure/fluff/broken_flooring/broken_flooring = locate(/obj/structure/fluff/broken_flooring) in loc
+ if(!isnull(broken_flooring))
+ return
var/turf/T = get_turf(src)
if(T.tiled_dirt)
- smooth = SMOOTH_MORE
- icon = 'icons/effects/dirt.dmi'
- icon_state = ""
- queue_smooth(src)
- queue_smooth_neighbors(src)
+ smoothing_flags = SMOOTH_BITMASK
+ QUEUE_SMOOTH(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
/obj/effect/decal/cleanable/dirt/Destroy()
- queue_smooth_neighbors(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
/obj/effect/decal/cleanable/dirt/dust
name = "dust"
desc = "A thin layer of dust coating the floor."
+/obj/effect/decal/cleanable/dirt/dust/Initialize(mapload)
+ . = ..()
+ icon_state = base_icon_state
+
/obj/effect/decal/cleanable/greenglow
name = "glowing goo"
desc = "Jeez. I hope that's not for lunch."
@@ -95,7 +106,7 @@
light_range = 0
update_light()
if(R)
- R.RemoveComponent()
+ qdel(R)
/obj/effect/decal/cleanable/greenglow/filled/Initialize(mapload)
. = ..()
@@ -241,4 +252,4 @@
/obj/effect/decal/cleanable/dirt_siding/corner
name = "dirt corner"
- icon_state = "dirt_side_corner"
\ No newline at end of file
+ icon_state = "dirt_side_corner"
diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm
index bbdba5ee4fdc..ed3b69323284 100644
--- a/code/game/objects/effects/decals/decal.dm
+++ b/code/game/objects/effects/decals/decal.dm
@@ -7,15 +7,23 @@
/obj/effect/decal/Initialize(mapload)
. = ..()
- if(turf_loc_check && (!isturf(loc) || NeverShouldHaveComeHere(loc)))
+ if(NeverShouldHaveComeHere(loc))
+ // Yog Code: Mappers you have a lot of shit to clean up before we can enable this
+ // if(mapload)
+ // stack_trace("[name] spawned in a bad turf ([loc]) at [AREACOORD(src)] in \the [get_area(src)].
+ // Please remove it or allow it to pass NeverShouldHaveComeHere if it's intended.")
return INITIALIZE_HINT_QDEL
+ var/static/list/loc_connections = list(
+ COMSIG_TURF_CHANGE = PROC_REF(on_decal_move),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/decal/blob_act(obj/structure/blob/B)
if(B && B.loc == loc)
qdel(src)
-/obj/effect/decal/proc/NeverShouldHaveComeHere(turf/T)
- return isclosedturf(T) || isgroundlessturf(T)
+/obj/effect/decal/proc/NeverShouldHaveComeHere(turf/here_turf)
+ return isclosedturf(here_turf) || (isgroundlessturf(here_turf) && !GET_TURF_BELOW(here_turf))
/obj/effect/decal/ex_act(severity, target)
qdel(src)
@@ -24,9 +32,12 @@
if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava
qdel(src)
-/obj/effect/decal/HandleTurfChange(turf/T)
- ..()
- if(T == loc && NeverShouldHaveComeHere(T))
+/obj/effect/decal/proc/on_decal_move(turf/changed, path, list/new_baseturfs, flags, list/post_change_callbacks)
+ SIGNAL_HANDLER
+ post_change_callbacks += CALLBACK(src, PROC_REF(sanity_check_self))
+
+/obj/effect/decal/proc/sanity_check_self(turf/changed)
+ if(changed == loc && NeverShouldHaveComeHere(changed))
qdel(src)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,12 +45,45 @@
/obj/effect/turf_decal
icon = 'icons/turf/decals.dmi'
icon_state = "warningline"
+ plane = FLOOR_PLANE
layer = TURF_DECAL_LAYER
+ anchored = TRUE
+ /// Does this decal change colors on holidays
+ var/use_holiday_colors = FALSE
+ /// The pattern used when recoloring the decal. If null, it'll use the def of the station or holiday.
+ var/pattern
+// This is with the intent of optimizing mapload
+// See spawners for more details since we use the same pattern
+// Basically rather then creating and deleting ourselves, why not just do the bare minimum?
/obj/effect/turf_decal/Initialize(mapload)
- . = ..()
+ SHOULD_CALL_PARENT(FALSE)
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
+
+ // If the tile uses holiday colors, apply them here
+ if(use_holiday_colors)
+
+ var/custom_color = request_station_colors(src, pattern) || request_holiday_colors(src, pattern)
+ if(custom_color)
+ color = custom_color
+ alpha = DECAL_ALPHA
+
var/turf/T = loc
if(!istype(T)) //you know this will happen somehow
CRASH("Turf decal initialized in an object/nullspace")
- T.AddComponent(/datum/component/decal, icon, icon_state, dir, FALSE, color, null, null, alpha)
+ T.AddElement(/datum/element/decal, icon, icon_state, dir, null, layer, alpha, color, null, FALSE, null)
return INITIALIZE_HINT_QDEL
+
+/obj/effect/turf_decal/Destroy(force)
+ SHOULD_CALL_PARENT(FALSE)
+// #ifdef UNIT_TESTS
+// // If we don't do this, turf decals will end up stacking up on a tile, and break the overlay limit
+// // I hate it too bestie
+// if(GLOB.running_create_and_destroy)
+// var/turf/T = loc
+// T.RemoveElement(/datum/element/decal, icon, icon_state, dir, null, layer, alpha, color, null, FALSE, null)
+// #endif
+ loc = null
+ return QDEL_HINT_QUEUE
diff --git a/code/game/objects/effects/decals/remains.dm b/code/game/objects/effects/decals/remains.dm
index e495202bc05b..3e0f5933c980 100644
--- a/code/game/objects/effects/decals/remains.dm
+++ b/code/game/objects/effects/decals/remains.dm
@@ -14,6 +14,7 @@
icon_state = "remains"
/obj/effect/decal/remains/plasma
+ desc = "They look like the remains of something flammable. They have a strange aura about them."
icon_state = "remainsplasma"
/obj/effect/decal/remains/xeno
diff --git a/code/game/objects/effects/decals/turfdecal/tilecoloring.dm b/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
index 1f849c097228..84211ff8175c 100644
--- a/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
+++ b/code/game/objects/effects/decals/turfdecal/tilecoloring.dm
@@ -3,353 +3,135 @@
icon_state = "tile_corner"
layer = TURF_PLATING_DECAL_LAYER
alpha = 110
-
-/obj/effect/turf_decal/tile/Initialize(mapload)
- if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS])
- color = "#[random_short_color()]"
- . = ..()
-
+ use_holiday_colors = TRUE
+
+/// Automatically generates all subtypes for a decal with the given path.
+#define TILE_DECAL_SUBTYPE_HELPER(path)\
+##path/opposingcorners {\
+ icon_state = "tile_opposing_corners";\
+}\
+##path/half {\
+ icon_state = "tile_half";\
+}\
+##path/half/contrasted {\
+ icon_state = "tile_half_contrasted";\
+}\
+##path/anticorner {\
+ icon_state = "tile_anticorner";\
+}\
+##path/anticorner/contrasted {\
+ icon_state = "tile_anticorner_contrasted";\
+}\
+##path/fourcorners {\
+ icon_state = "tile_fourcorners";\
+}\
+##path/full {\
+ icon_state = "tile_full";\
+}\
+##path/diagonal_centre {\
+ icon_state = "diagonal_centre";\
+}\
+##path/diagonal_edge {\
+ icon_state = "diagonal_edge";\
+}
+
+/// Blue tiles
/obj/effect/turf_decal/tile/blue
- name = "blue corner"
+ name = "blue tile decal"
color = "#52B4E9"
-/obj/effect/turf_decal/tile/blue/opposingcorners //Two corners on opposite ends of each other (i.e. Top Right to Bottom Left). Allows for faster mapping and less complicated turf decal storage.
- icon_state = "tile_opposing_corners"
- name = "opposing blue corners"
-
-/obj/effect/turf_decal/tile/blue/half
- icon_state = "tile_half"
- name = "blue half"
-
-/obj/effect/turf_decal/tile/blue/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted blue half"
-
-/obj/effect/turf_decal/tile/blue/anticorner
- icon_state = "tile_anticorner"
- name = "blue anticorner"
-
-/obj/effect/turf_decal/tile/blue/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted blue anticorner"
-
-/obj/effect/turf_decal/tile/blue/fourcorners //The reason why we have four corners is to replace the trend of having all four corners on a tile be taken up by four individual corners, while still allowing the visual contrast between the decal and the floor tile.
- icon_state = "tile_fourcorners"
- name = "blue fourcorners"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/blue)
-/obj/effect/turf_decal/tile/blue/full
- icon_state = "tile_full"
- name = "blue full"
-
-/obj/effect/turf_decal/tile/blue/diagonal_centre
- icon_state = "diagonal_centre"
- name = "blue diagonal centre"
-
-/obj/effect/turf_decal/tile/blue/diagonal_edge
- icon_state = "diagonal_edge"
- name = "blue diagonal edge"
+/// Dark blue tiles
+/obj/effect/turf_decal/tile/dark_blue
+ name = "dark blue tile decal"
+ color = "#486091"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/dark_blue)
/// Green tiles
/obj/effect/turf_decal/tile/green
- name = "green corner"
+ name = "green tile decal"
color = "#9FED58"
-/obj/effect/turf_decal/tile/green/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing green corners"
-
-/obj/effect/turf_decal/tile/green/half
- icon_state = "tile_half"
- name = "green half"
-
-/obj/effect/turf_decal/tile/green/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted green half"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/green)
-/obj/effect/turf_decal/tile/green/anticorner
- icon_state = "tile_anticorner"
- name = "green anticorner"
+/// Dark green tiles
-/obj/effect/turf_decal/tile/green/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted green anticorner"
+/obj/effect/turf_decal/tile/dark_green
+ name = "dark green tile decal"
+ color = "#439C1E"
-/obj/effect/turf_decal/tile/green/fourcorners
- icon_state = "tile_fourcorners"
- name = "green fourcorners"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/dark_green)
-/obj/effect/turf_decal/tile/green/full
- icon_state = "tile_full"
- name = "green full"
-
-/obj/effect/turf_decal/tile/green/diagonal_centre
- icon_state = "diagonal_centre"
- name = "green diagonal centre"
-
-/obj/effect/turf_decal/tile/green/diagonal_edge
- icon_state = "diagonal_edge"
- name = "green diagonal edge"
/// Yellow tiles
/obj/effect/turf_decal/tile/yellow
- name = "yellow corner"
+ name = "yellow tile decal"
color = "#EFB341"
-/obj/effect/turf_decal/tile/yellow/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing yellow corners"
-
-/obj/effect/turf_decal/tile/yellow/half
- icon_state = "tile_half"
- name = "yellow half"
-
-/obj/effect/turf_decal/tile/yellow/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted yellow half"
-
-/obj/effect/turf_decal/tile/yellow/anticorner
- icon_state = "tile_anticorner"
- name = "yellow anticorner"
-
-/obj/effect/turf_decal/tile/yellow/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted yellow anticorner"
-
-/obj/effect/turf_decal/tile/yellow/fourcorners
- icon_state = "tile_fourcorners"
- name = "yellow fourcorners"
-
-/obj/effect/turf_decal/tile/yellow/full
- icon_state = "tile_full"
- name = "yellow full"
-
-/obj/effect/turf_decal/tile/yellow/diagonal_centre
- icon_state = "diagonal_centre"
- name = "yellow diagonal centre"
-
-/obj/effect/turf_decal/tile/yellow/diagonal_edge
- icon_state = "diagonal_edge"
- name = "yellow diagonal edge"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/yellow)
/// Red tiles
/obj/effect/turf_decal/tile/red
- name = "red corner"
+ name = "red tile decal"
color = "#DE3A3A"
-/obj/effect/turf_decal/tile/red/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing red corners"
-
-/obj/effect/turf_decal/tile/red/half
- icon_state = "tile_half"
- name = "red half"
-
-/obj/effect/turf_decal/tile/red/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted red half"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/red)
-/obj/effect/turf_decal/tile/red/anticorner
- icon_state = "tile_anticorner"
- name = "red anticorner"
+/// Dark red tiles
-/obj/effect/turf_decal/tile/red/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted red anticorner"
+/obj/effect/turf_decal/tile/dark_red
+ name = "dark red tile decal"
+ color = "#B11111"
-/obj/effect/turf_decal/tile/red/fourcorners
- icon_state = "tile_fourcorners"
- name = "red fourcorners"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/dark_red)
-/obj/effect/turf_decal/tile/red/full
- icon_state = "tile_full"
- name = "red full"
-
-/obj/effect/turf_decal/tile/red/diagonal_centre
- icon_state = "diagonal_centre"
- name = "red diagonal centre"
+/// Bar tiles
+/obj/effect/turf_decal/tile/bar
+ name = "bar tile decal"
+ color = "#791500"
+ alpha = 130
-/obj/effect/turf_decal/tile/red/diagonal_edge
- icon_state = "diagonal_edge"
- name = "red diagonal edge"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/bar)
/// Purple tiles
/obj/effect/turf_decal/tile/purple
- name = "purple corner"
+ name = "purple tile decal"
color = "#D381C9"
-/obj/effect/turf_decal/tile/purple/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing purple corners"
-
-/obj/effect/turf_decal/tile/purple/half
- icon_state = "tile_half"
- name = "purple half"
-
-/obj/effect/turf_decal/tile/purple/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted purple half"
-
-/obj/effect/turf_decal/tile/purple/anticorner
- icon_state = "tile_anticorner"
- name = "purple anticorner"
-
-/obj/effect/turf_decal/tile/purple/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted purple anticorner"
-
-/obj/effect/turf_decal/tile/purple/fourcorners
- icon_state = "tile_fourcorners"
- name = "purple fourcorners"
-
-/obj/effect/turf_decal/tile/purple/full
- icon_state = "tile_full"
- name = "purple full"
-
-/obj/effect/turf_decal/tile/purple/diagonal_centre
- icon_state = "diagonal_centre"
- name = "purple diagonal centre"
-
-/obj/effect/turf_decal/tile/purple/diagonal_edge
- icon_state = "diagonal_edge"
- name = "purple diagonal edge"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/purple)
/// Brown tiles
/obj/effect/turf_decal/tile/brown
- name = "brown corner"
+ name = "brown tile decal"
color = "#A46106"
-/obj/effect/turf_decal/tile/brown/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing brown corners"
-
-/obj/effect/turf_decal/tile/brown/half
- icon_state = "tile_half"
- name = "brown half"
-
-/obj/effect/turf_decal/tile/brown/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted brown half"
-
-/obj/effect/turf_decal/tile/brown/anticorner
- icon_state = "tile_anticorner"
- name = "brown anticorner"
-/obj/effect/turf_decal/tile/brown/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted brown anticorner"
-
-/obj/effect/turf_decal/tile/brown/fourcorners
- icon_state = "tile_fourcorners"
- name = "brown fourcorners"
-
-/obj/effect/turf_decal/tile/brown/full
- icon_state = "tile_full"
- name = "brown full"
-
-/obj/effect/turf_decal/tile/brown/diagonal_centre
- icon_state = "diagonal_centre"
- name = "brown diagonal centre"
-
-/obj/effect/turf_decal/tile/brown/diagonal_edge
- icon_state = "diagonal_edge"
- name = "brown diagonal edge"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/brown)
/// Neutral tiles
/obj/effect/turf_decal/tile/neutral
- name = "neutral corner"
+ name = "neutral tile decal"
color = "#D4D4D4"
alpha = 50
-/obj/effect/turf_decal/tile/neutral/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing neutral corners"
-
-/obj/effect/turf_decal/tile/neutral/half
- icon_state = "tile_half"
- name = "neutral half"
-
-/obj/effect/turf_decal/tile/neutral/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted neutral half"
-
-/obj/effect/turf_decal/tile/neutral/anticorner
- icon_state = "tile_anticorner"
- name = "neutral anticorner"
-
-/obj/effect/turf_decal/tile/neutral/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted neutral anticorner"
-
-/obj/effect/turf_decal/tile/neutral/fourcorners
- icon_state = "tile_fourcorners"
- name = "neutral fourcorners"
-
-/obj/effect/turf_decal/tile/neutral/full
- icon_state = "tile_full"
- name = "neutral full"
-
-/obj/effect/turf_decal/tile/neutral/diagonal_centre
- icon_state = "diagonal_centre"
- name = "neutral diagonal centre"
-
-/obj/effect/turf_decal/tile/neutral/diagonal_edge
- icon_state = "diagonal_edge"
- name = "neutral diagonal edge"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/neutral)
/// Dark tiles
/obj/effect/turf_decal/tile/dark
- name = "dark corner"
+ name = "dark tile decal"
color = "#0e0f0f"
-/obj/effect/turf_decal/tile/dark/opposingcorners
- icon_state = "tile_opposing_corners"
- name = "opposing dark corners"
-
-/obj/effect/turf_decal/tile/dark/half
- icon_state = "tile_half"
- name = "dark half"
-
-/obj/effect/turf_decal/tile/dark/half/contrasted
- icon_state = "tile_half_contrasted"
- name = "contrasted dark half"
-
-/obj/effect/turf_decal/tile/dark/anticorner
- icon_state = "tile_anticorner"
- name = "dark anticorner"
-
-/obj/effect/turf_decal/tile/dark/anticorner/contrasted
- icon_state = "tile_anticorner_contrasted"
- name = "contrasted dark anticorner"
-
-/obj/effect/turf_decal/tile/dark/fourcorners
- icon_state = "tile_fourcorners"
- name = "dark fourcorners"
-
-/obj/effect/turf_decal/tile/dark/full
- icon_state = "tile_full"
- name = "dark full"
-
-/obj/effect/turf_decal/tile/dark/diagonal_centre
- icon_state = "diagonal_centre"
- name = "dark diagonal centre"
-
-/obj/effect/turf_decal/tile/dark/diagonal_edge
- icon_state = "diagonal_edge"
- name = "dark diagonal edge"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/dark)
/// Weird snowflake ones
-/obj/effect/turf_decal/tile/bar
- name = "bar corner"
- color = "#791500"
- alpha = 130
-
/obj/effect/turf_decal/tile/random // so many colors
name = "colorful corner"
color = "#E300FF" //bright pink as default for mapping
@@ -358,1299 +140,236 @@
color = "#[random_short_color()]"
. = ..()
-/obj/effect/turf_decal/trimline
- layer = TURF_PLATING_DECAL_LAYER
- alpha = 110
- icon_state = "trimline_box"
-
-/obj/effect/turf_decal/trimline/Initialize(mapload)
- if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS])
- color = "#[random_short_color()]"
- . = ..()
-
-/// White trimlines
-
-/obj/effect/turf_decal/trimline/white
- color = "#FFFFFF"
-
-/obj/effect/turf_decal/trimline/white/line
- name = "trim decal"
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/white/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/white/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/white/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/white/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/white/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/white/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/white/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/white/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/white/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/white/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
+/// Date-specific tiles
+/obj/effect/turf_decal/tile/holiday
+ name = "ERROR tile decal"
+ color = "#FF0000"
-/obj/effect/turf_decal/trimline/white/filled/line/lower
- icon_state = "trimline_fill_lower"
+/obj/effect/turf_decal/tile/holiday/Initialize(mapload)
+ color = request_holiday_colors(src, pattern)
+ alpha = DECAL_ALPHA
+ return ..()
-/obj/effect/turf_decal/trimline/white/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
+/// Pattern tiles
+/obj/effect/turf_decal/tile/holiday/rainbow
+ name = "rainbow tile decal"
+ color = "#75C9EB" //bright blue as default for mapping
+ pattern = PATTERN_RAINBOW
-/obj/effect/turf_decal/trimline/white/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/holiday/rainbow)
-/obj/effect/turf_decal/trimline/white/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/white/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/white/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
+/obj/effect/turf_decal/tile/holiday/random // so many colors
+ name = "colorful tile decal"
+ color = "#E300FF" //bright pink as default for mapping
+ pattern = PATTERN_RANDOM
-/obj/effect/turf_decal/trimline/white/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
+TILE_DECAL_SUBTYPE_HELPER(/obj/effect/turf_decal/tile/holiday/random)
-/obj/effect/turf_decal/trimline/white/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
+#undef TILE_DECAL_SUBTYPE_HELPER
-/obj/effect/turf_decal/trimline/white/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
+/obj/effect/turf_decal/trimline
+ layer = TURF_PLATING_DECAL_LAYER
+ alpha = 110
+ icon_state = "trimline_box"
+ use_holiday_colors = TRUE
+
+/// Automatically generates all trimlines for a decal with the given path.
+#define TRIMLINE_SUBTYPE_HELPER(path)\
+##path/line {\
+ icon_state = "trimline";\
+}\
+##path/corner {\
+ icon_state = "trimline_corner";\
+}\
+##path/end {\
+ icon_state = "trimline_end";\
+}\
+##path/arrow_cw {\
+ icon_state = "trimline_arrow_cw";\
+}\
+##path/arrow_ccw {\
+ icon_state = "trimline_arrow_ccw";\
+}\
+##path/warning {\
+ icon_state = "trimline_warn";\
+}\
+##path/mid_joiner {\
+ icon_state = "trimline_mid";\
+}\
+##path/filled {\
+ icon_state = "trimline_box_fill";\
+}\
+##path/filled/line {\
+ icon_state = "trimline_fill";\
+}\
+##path/filled/corner {\
+ icon_state = "trimline_corner_fill";\
+}\
+##path/filled/end {\
+ icon_state = "trimline_end_fill";\
+}\
+##path/filled/arrow_cw {\
+ icon_state = "trimline_arrow_cw_fill";\
+}\
+##path/filled/arrow_ccw {\
+ icon_state = "trimline_arrow_ccw_fill";\
+}\
+##path/filled/warning {\
+ icon_state = "trimline_warn_fill";\
+}\
+##path/filled/mid_joiner {\
+ icon_state = "trimline_mid_fill";\
+}\
+##path/filled/shrink_cw {\
+ icon_state = "trimline_shrink_cw";\
+}\
+##path/filled/shrink_ccw {\
+ icon_state = "trimline_shrink_ccw";\
+}\
+##path/filled/warning/flip {\
+ icon_state = "trimline_warn_fill_flip";\
+}\
+##path/filled/line/lower {\
+ icon_state = "trimline_fill_lower";\
+}\
+##path/filled/shrink_cw/lower {\
+ icon_state = "trimline_shrink_cw_lower";\
+}\
+##path/filled/shrink_ccw/lower {\
+ icon_state = "trimline_shrink_ccw_lower";\
+}\
+##path/filled/corner/lower {\
+ icon_state = "trimline_corner_lower";\
+}\
+##path/warning/lower {\
+ icon_state = "trimline_warn_lower";\
+}\
+##path/warning/lower/flip {\
+ icon_state = "trimline_warn_lower_flip";\
+}\
+##path/warning/lower/nobottom {\
+ icon_state = "trimline_warn_lower_nobottom";\
+ alpha = 220;\
+}\
+##path/warning/lower/nobottom/flip {\
+ icon_state = "trimline_warn_lower_notbottom_flip";\
+}\
+##path/warning/lower/corner {\
+ icon_state = "trimline_corner_lower_warn";\
+ alpha = 220;\
+}\
+##path/warning/lower/corner/flip {\
+ icon_state = "trimline_corner_lower_warn_flip";\
+}\
+##path/filled/lower {\
+ icon_state = "trimline_box_fill_lower";\
+}\
+##path/filled/end/lower {\
+ icon_state = "trimline_end_fill_lower";\
+}
-/obj/effect/turf_decal/trimline/white/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-/obj/effect/turf_decal/trimline/white/filled/lower
- icon_state = "trimline_box_fill_lower"
+/// White trimlines
+/obj/effect/turf_decal/trimline/white
+ color = "#FFFFFF"
-/obj/effect/turf_decal/trimline/white/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/white)
/// Red trimlines
-
/obj/effect/turf_decal/trimline/red
color = "#DE3A3A"
-/obj/effect/turf_decal/trimline/red/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/red/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/red/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/red/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/red/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/red/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/red/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/red/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/red/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/red/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/red/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/red)
-/obj/effect/turf_decal/trimline/red/filled/line/lower
- icon_state = "trimline_fill_lower"
+/// Dark red trimlines
+/obj/effect/turf_decal/trimline/dark_red
+ color = "#B11111"
-/obj/effect/turf_decal/trimline/red/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/red/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/red/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/red/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/red/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/red/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/red/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/red/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/red/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/red/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/red/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/dark_red)
/// Green trimlines
-
/obj/effect/turf_decal/trimline/green
color = "#9FED58"
-/obj/effect/turf_decal/trimline/green/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/green/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/green/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/green/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/green/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/green/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/green/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/green/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/green/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/green/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/green/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/green/filled/line/lower
- icon_state = "trimline_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/green)
-/obj/effect/turf_decal/trimline/green/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
+/// Dark green Trimlines
+/obj/effect/turf_decal/trimline/dark_green
+ color = "#439C1E"
-/obj/effect/turf_decal/trimline/green/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/green/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/green/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/green/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/green/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/green/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/green/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/green/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/green/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/green/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/dark_green)
/// Blue trimlines
-
/obj/effect/turf_decal/trimline/blue
color = "#52B4E9"
-/obj/effect/turf_decal/trimline/blue/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/blue/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/blue/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/blue/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/blue/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/blue/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/blue/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/blue/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/blue/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/blue/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/blue/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/blue/filled/line/lower
- icon_state = "trimline_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/blue)
-/obj/effect/turf_decal/trimline/blue/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/blue/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/blue/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/blue/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/blue/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/blue/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/blue/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/blue/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
+/// Dark blue trimlines
+/obj/effect/turf_decal/trimline/dark_blue
+ color = "#384e6d"
alpha = 220
-/obj/effect/turf_decal/trimline/blue/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/blue/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/blue/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/dark_blue)
/// Yellow trimlines
-
/obj/effect/turf_decal/trimline/yellow
color = "#EFB341"
-/obj/effect/turf_decal/trimline/yellow/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/yellow/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/yellow/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/yellow/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/yellow/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/yellow/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/yellow/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/yellow/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/yellow/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/yellow/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/yellow/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/yellow/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/yellow/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/yellow/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/yellow/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/yellow/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/yellow/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/yellow/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/yellow/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/yellow/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/yellow/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/yellow/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/yellow/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/yellow)
/// Purple trimlines
-
/obj/effect/turf_decal/trimline/purple
color = "#D381C9"
-/obj/effect/turf_decal/trimline/purple/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/purple/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/purple/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/purple/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/purple/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/purple/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/purple/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/purple/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/purple/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/purple/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/purple/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/purple/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/purple/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/purple/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/purple/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/purple/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/purple/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/purple/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/purple/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/purple/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/purple/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/purple/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/purple/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/purple)
/// Brown trimlines
-
/obj/effect/turf_decal/trimline/brown
color = "#A46106"
-/obj/effect/turf_decal/trimline/brown/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/brown/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/brown/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/brown/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/brown/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/brown/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/brown/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/brown/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/brown/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/brown/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/brown/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/brown/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/brown/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/brown/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/brown/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/brown/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/brown/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/brown/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/brown/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/brown/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/brown/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/brown/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/brown/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/brown)
/// Neutral trimlines
-
/obj/effect/turf_decal/trimline/neutral
color = "#D4D4D4"
alpha = 50
-/obj/effect/turf_decal/trimline/neutral/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/neutral/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/neutral/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/neutral/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/neutral/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/neutral/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/neutral/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/neutral/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/neutral/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/neutral/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/neutral/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/neutral/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/neutral/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/neutral/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/neutral/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/neutral/warning/lower
- icon_state = "trimline_warn_lower"
-
-/obj/effect/turf_decal/trimline/neutral/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
-
-/obj/effect/turf_decal/trimline/neutral/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/neutral/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/neutral/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/neutral/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/neutral/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/neutral/filled/end/lower
- icon_state = "trimline_end_fill_lower"
-
-/// DarkBlue trimlines
-/obj/effect/turf_decal/trimline/darkblue
- color = "#384e6d"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/darkblue/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/darkblue/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/darkblue/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/darkblue/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/darkblue/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/darkblue/warning
- icon_state = "trimline_warn"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/darkblue/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/darkblue/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/warning
- icon_state = "trimline_warn_fill"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/darkblue/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/darkblue/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/darkblue/warning/lower
- icon_state = "trimline_warn_lower"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/darkblue/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/darkblue/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/darkblue/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/darkblue/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/darkblue/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/darkblue/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/neutral)
/// chemorange trimlines
/obj/effect/turf_decal/trimline/chemorange
color = "#F29A4D"
alpha = 220
-/obj/effect/turf_decal/trimline/chemorange/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/chemorange/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/chemorange/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/chemorange/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/chemorange/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/chemorange/warning
- icon_state = "trimline_warn"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/chemorange/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/chemorange/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/warning
- icon_state = "trimline_warn_fill"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/chemorange/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/chemorange/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/chemorange/warning/lower
- icon_state = "trimline_warn_lower"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/chemorange/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
- alpha = 150
-
-/obj/effect/turf_decal/trimline/chemorange/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/chemorange/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/chemorange/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/chemorange/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/chemorange/filled/end/lower
- icon_state = "trimline_end_fill_lower"
-
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/chemorange)
/// Secred trimlines
-
/obj/effect/turf_decal/trimline/secred
color = "#D12A2B"
alpha = 220
-/obj/effect/turf_decal/trimline/secred/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/secred/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/secred/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/secred/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/secred/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/secred/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/secred/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/secred/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/secred/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/secred/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/secred/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/secred/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/secred/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/secred/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/secred/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/secred/warning/lower
- icon_state = "trimline_warn_lower"
- alpha = 160
-
-/obj/effect/turf_decal/trimline/secred/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
- alpha = 160
-
-/obj/effect/turf_decal/trimline/secred/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/secred/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/secred/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/secred/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/secred/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/secred/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/secred)
/// engiyellow trimlines
-
/obj/effect/turf_decal/trimline/engiyellow
color = "#CCB223"
alpha = 220
-/obj/effect/turf_decal/trimline/engiyellow/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/engiyellow/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/engiyellow/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/engiyellow/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/engiyellow/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/engiyellow/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/engiyellow/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/engiyellow/warning/lower
- icon_state = "trimline_warn_lower"
- alpha = 160
-
-/obj/effect/turf_decal/trimline/engiyellow/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
- alpha = 160
-
-/obj/effect/turf_decal/trimline/engiyellow/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/engiyellow/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
-
-/obj/effect/turf_decal/trimline/engiyellow/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/engiyellow/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/lower
- icon_state = "trimline_box_fill_lower"
-
-/obj/effect/turf_decal/trimline/engiyellow/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/engiyellow)
/// atmos trimlines
-
/obj/effect/turf_decal/trimline/atmos
color = "#539085"
alpha = 220
-/obj/effect/turf_decal/trimline/atmos/line
- icon_state = "trimline"
-
-/obj/effect/turf_decal/trimline/atmos/corner
- icon_state = "trimline_corner"
-
-/obj/effect/turf_decal/trimline/atmos/end
- icon_state = "trimline_end"
-
-/obj/effect/turf_decal/trimline/atmos/arrow_cw
- icon_state = "trimline_arrow_cw"
-
-/obj/effect/turf_decal/trimline/atmos/arrow_ccw
- icon_state = "trimline_arrow_ccw"
-
-/obj/effect/turf_decal/trimline/atmos/warning
- icon_state = "trimline_warn"
-
-/obj/effect/turf_decal/trimline/atmos/mid_joiner
- icon_state = "trimline_mid"
-
-/obj/effect/turf_decal/trimline/atmos/filled
- icon_state = "trimline_box_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/line
- icon_state = "trimline_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/corner
- icon_state = "trimline_corner_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/end
- icon_state = "trimline_end_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/arrow_cw
- icon_state = "trimline_arrow_cw_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/arrow_ccw
- icon_state = "trimline_arrow_ccw_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/warning
- icon_state = "trimline_warn_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/mid_joiner
- icon_state = "trimline_mid_fill"
-
-/obj/effect/turf_decal/trimline/atmos/filled/shrink_cw
- icon_state = "trimline_shrink_cw"
-
-/obj/effect/turf_decal/trimline/atmos/filled/shrink_ccw
- icon_state = "trimline_shrink_ccw"
-
-/obj/effect/turf_decal/trimline/atmos/filled/warning/flip
- icon_state = "trimline_warn_fill_flip"
-
-/obj/effect/turf_decal/trimline/atmos/filled/warning/end
- icon_state = "trimline_warn_end_fill_lower"
-
-/obj/effect/turf_decal/trimline/atmos/filled/line/lower
- icon_state = "trimline_fill_lower"
-
-/obj/effect/turf_decal/trimline/atmos/filled/shrink_cw/lower
- icon_state = "trimline_shrink_cw_lower"
-
-/obj/effect/turf_decal/trimline/atmos/filled/shrink_ccw/lower
- icon_state = "trimline_shrink_ccw_lower"
-
-/obj/effect/turf_decal/trimline/atmos/filled/corner/lower
- icon_state = "trimline_corner_lower"
-
-/obj/effect/turf_decal/trimline/atmos/warning/lower
- icon_state = "trimline_warn_lower"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/atmos/warning/lower/flip
- icon_state = "trimline_warn_lower_flip"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/atmos/warning/lower/nobottom
- icon_state = "trimline_warn_lower_nobottom"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/atmos/warning/lower/nobottom/flip
- icon_state = "trimline_warn_lower_notbottom_flip"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/atmos)
-/obj/effect/turf_decal/trimline/atmos/warning/lower/corner
- icon_state = "trimline_corner_lower_warn"
- alpha = 220
-
-/obj/effect/turf_decal/trimline/atmos/warning/lower/corner/flip
- icon_state = "trimline_corner_lower_warn_flip"
+/// Dark trimlines
+/obj/effect/turf_decal/trimline/dark
+ color = "#0e0f0f"
-/obj/effect/turf_decal/trimline/atmos/filled/lower
- icon_state = "trimline_box_fill_lower"
+TRIMLINE_SUBTYPE_HELPER(/obj/effect/turf_decal/trimline/dark)
-/obj/effect/turf_decal/trimline/atmos/filled/end/lower
- icon_state = "trimline_end_fill_lower"
+#undef TRIMLINE_SUBTYPE_HELPER
+#undef DECAL_ALPHA
diff --git a/code/game/objects/effects/effect_system/effects_other.dm b/code/game/objects/effects/effect_system/effects_other.dm
index 81f3330f53cf..83583f844b3f 100644
--- a/code/game/objects/effects/effect_system/effects_other.dm
+++ b/code/game/objects/effects/effect_system/effects_other.dm
@@ -11,7 +11,7 @@
var/active = FALSE
var/allow_overlap = FALSE
var/auto_process = TRUE
- var/qdel_in_time = 10
+ var/qdel_in_time = 1 SECONDS
var/fadetype = "ion_fade"
var/fade = TRUE
var/nograv_required = FALSE
@@ -65,6 +65,16 @@
/datum/effect_system/trail_follow/steam
effect_type = /obj/effect/particle_effect/steam
+/datum/effect_system/trail_follow/sparks
+ effect_type = /obj/effect/particle_effect/sparks
+ nograv_required = TRUE
+ fade = FALSE
+
+/datum/effect_system/trail_follow/smoke
+ effect_type = /obj/effect/particle_effect/fluid/smoke/trail
+ nograv_required = TRUE
+ fade = FALSE
+
/obj/effect/particle_effect/ion_trails
name = "ion trails"
icon_state = "ion_trails"
@@ -76,7 +86,7 @@
/datum/effect_system/trail_follow/ion
effect_type = /obj/effect/particle_effect/ion_trails
nograv_required = TRUE
- qdel_in_time = 20
+ qdel_in_time = 2 SECONDS
/datum/effect_system/trail_follow/proc/set_dir(obj/effect/particle_effect/ion_trails/I)
I.setDir(holder.dir)
diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm
index ca003178d8da..52da421b47b4 100644
--- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm
+++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm
@@ -88,8 +88,7 @@
if(object == src)
continue
if(isturf(object.loc))
- var/turf/turf = object.loc
- if(turf.intact && object.level == 1) //hidden under the floor
+ if(location.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(object, TRAIT_T_RAY_VISIBLE))
continue
reagents.reaction(object, TOUCH|VAPOR, fraction)
@@ -314,7 +313,8 @@
desc = "A lightweight foamed metal wall that can be used as base to construct a wall."
gender = PLURAL
max_integrity = 20
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
+ obj_flags = CAN_BE_HIT | BLOCK_Z_IN_DOWN | BLOCK_Z_IN_UP
///Var used to prevent spamming of the construction sound
var/next_beep = 0
@@ -367,7 +367,7 @@
/obj/effect/particle_effect/fluid/foam/metal/smart/make_result() //Smart foam adheres to area borders for walls
var/turf/open/location = loc
if(isspaceturf(location))
- location.PlaceOnTop(/turf/open/floor/plating/foam)
+ location.place_on_top(/turf/open/floor/plating/foam)
for(var/cardinal in GLOB.cardinals)
var/turf/cardinal_turf = get_step(location, cardinal)
diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
index 030d78c8d201..f02dfc5396a7 100644
--- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm
@@ -156,6 +156,19 @@
/obj/effect/particle_effect/fluid/smoke/transparent
opacity = FALSE
+/// Special smoke used for the RCS thruster
+/obj/effect/particle_effect/fluid/smoke/trail
+ lifetime = 1 SECONDS
+ opacity = FALSE
+ alpha = 100
+
+/obj/effect/particle_effect/fluid/smoke/trail/Initialize(mapload, datum/fluid_group/group, ...)
+ . = ..()
+ var/matrix/start_transform = matrix(transform)/2
+ var/matrix/end_transform = matrix(transform)
+ transform = start_transform
+ animate(src, alpha = 0, transform = end_transform, time = lifetime)
+
/**
* A helper proc used to spawn small puffs of smoke.
*
@@ -370,7 +383,7 @@
for(var/atom/movable/thing as anything in location)
if(thing == src)
continue
- if(location.intact && thing.level == 1) //hidden under the floor
+ if(location.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(thing, TRAIT_T_RAY_VISIBLE))
continue
reagents.reaction(thing, TOUCH, fraction)
diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm
index 04ed1950154f..3b2bfd1de8bd 100644
--- a/code/game/objects/effects/effects.dm
+++ b/code/game/objects/effects/effects.dm
@@ -5,7 +5,8 @@
icon = 'icons/effects/effects.dmi'
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
move_resist = INFINITY
- obj_flags = 0
+ obj_flags = NONE
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
/obj/effect/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = TRUE, attack_dir, armour_penetration = 0)
return
diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm
index 429e064b0b6b..a25284b8993e 100644
--- a/code/game/objects/effects/forcefields.dm
+++ b/code/game/objects/effects/forcefields.dm
@@ -5,7 +5,7 @@
anchored = TRUE
opacity = FALSE
density = TRUE
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
/// If set, how long the force field lasts after it's created. Set to 0 to have infinite duration forcefields.
var/initial_duration = 30 SECONDS
@@ -46,7 +46,7 @@
desc = "An unholy shield that blocks all attacks."
icon = 'icons/effects/cult_effects.dmi'
icon_state = "cultshield"
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
initial_duration = 20 SECONDS
/// Mime forcefields (invisible walls)
diff --git a/code/game/objects/effects/glowshroom.dm b/code/game/objects/effects/glowshroom.dm
index 2261ef23d593..f341f3acdf6a 100644
--- a/code/game/objects/effects/glowshroom.dm
+++ b/code/game/objects/effects/glowshroom.dm
@@ -108,9 +108,11 @@ GLOBAL_VAR_INIT(glowshrooms, 0)
START_PROCESSING(SSobj, src)
/obj/structure/glowshroom/Destroy()
- . = ..()
+ if(isatom(myseed))
+ QDEL_NULL(myseed)
GLOB.glowshrooms--
STOP_PROCESSING(SSobj, src)
+ return ..()
/**
* Causes glowshroom spreading across the floor/walls.
@@ -223,7 +225,9 @@ GLOBAL_VAR_INIT(glowshrooms, 0)
/obj/structure/glowshroom/proc/Decay(amount)
myseed.adjust_endurance(-amount * endurance_decay_rate)
take_damage(amount)
- if (myseed.endurance <= 10) // Plant is gone
+ // take_damage could qdel our shroom, so check beforehand
+ // if our endurance dropped before the min plant endurance, then delete our shroom anyways
+ if (!QDELETED(src) && myseed.endurance <= 10)
qdel(src)
/obj/structure/glowshroom/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index 12efb829cb06..b1a080d6eb71 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -3,7 +3,8 @@
icon = 'icons/effects/landmarks_static.dmi'
icon_state = "x2"
anchored = TRUE
- layer = MID_LANDMARK_LAYER
+ layer = OBJ_LAYER
+ plane = GAME_PLANE
invisibility = INVISIBILITY_ABSTRACT
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
@@ -38,7 +39,8 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
var/used = FALSE
/obj/effect/landmark/start/proc/after_round_start()
-#ifndef UNIT_TESTS // We'd like to keep these around for unit tests, so we can check that they exist.
+ // We'd like to keep these around for unit tests, so we can check that they exist.
+#ifndef UNIT_TESTS
if(delete_after_roundstart)
qdel(src)
#endif
@@ -198,8 +200,8 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
name = "department_sec"
icon_state = "Security Officer"
-/obj/effect/landmark/start/depsec/New()
- ..()
+/obj/effect/landmark/start/depsec/Initialize(mapload)
+ . = ..()
GLOB.department_security_spawns += src
/obj/effect/landmark/start/depsec/Destroy()
@@ -411,14 +413,20 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player)
GLOB.city_of_cogs_spawns += loc
return INITIALIZE_HINT_QDEL
-//handles clockwork portal+eminence teleport destinations
+/**
+ * Generic event spawn points
+ *
+ * These are placed in locales where there are likely to be players, and places which are identifiable at a glance -
+ * Such as public hallways, department rooms, head of staff offices, and non-generic maintenance locations
+ *
+ * Used in events to cause effects in locations where it is likely to effect players
+ */
/obj/effect/landmark/event_spawn
name = "generic event spawn"
icon_state = "generic_event"
- layer = HIGH_LANDMARK_LAYER
-/obj/effect/landmark/event_spawn/New()
- ..()
+/obj/effect/landmark/event_spawn/Initialize(mapload)
+ . = ..()
GLOB.generic_event_spawns += src
/obj/effect/landmark/event_spawn/Destroy()
@@ -428,10 +436,10 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player)
/obj/effect/landmark/brazil
name = "brazilian reception marker"
icon_state = "x"
- layer = HIGH_LANDMARK_LAYER
+ layer = OBJ_LAYER
-/obj/effect/landmark/brazil/New()
- ..()
+/obj/effect/landmark/brazil/Initialize(mapload)
+ . = ..()
GLOB.brazil_reception += src
/obj/effect/landmark/brazil/Destroy()
@@ -441,9 +449,9 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player)
/obj/effect/landmark/ruin
var/datum/map_template/ruin/ruin_template
-/obj/effect/landmark/ruin/New(loc, my_ruin_template)
+/obj/effect/landmark/ruin/Initialize(mapload, my_ruin_template)
+ . = ..()
name = "ruin_[GLOB.ruin_landmarks.len + 1]"
- ..(loc)
ruin_template = my_ruin_template
GLOB.ruin_landmarks |= src
@@ -465,9 +473,9 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player)
/obj/effect/landmark/centcom
name = "centcomspawn"
icon_state = "x"
- layer = HIGH_LANDMARK_LAYER
+ layer = OBJ_LAYER
/obj/effect/landmark/wiki
name = "wiki sprite room"
icon_state = "x"
- layer = HIGH_LANDMARK_LAYER
+ layer = OBJ_LAYER
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index f10f0fa0ffb2..691226b7836b 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -86,13 +86,23 @@
icon_state = "uglymine"
alpha = 30
var/triggered = 0
+ /// Can be set to FALSE if we want a short 'coming online' delay, then set to TRUE. Can still be set off by damage
+ var/armed = TRUE
var/smartmine = FALSE
var/disarm_time = 12 SECONDS
var/disarm_product = /obj/item/deployablemine // ie what drops when the mine is disarmed
+ /// Who's got their foot on the mine's pressure plate
+ /// Stepping on the mine will set this to the first mob who stepped over it
+ /// The mine will not detonate via movement unless the first mob steps off of it
+ var/datum/weakref/foot_on_mine
/obj/effect/mine/Initialize(mapload)
. = ..()
- layer = ABOVE_MOB_LAYER
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ COMSIG_ATOM_EXITED = PROC_REF(on_exited),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/effect/mine/attackby(obj/I, mob/user, params)
if(istype(I, /obj/item/multitool))
@@ -107,18 +117,6 @@
/obj/effect/mine/proc/mineEffect(mob/victim)
to_chat(victim, span_danger("*click*"))
-/obj/effect/mine/Crossed(AM as mob|obj)
- . = ..()
- if(isturf(loc))
- if(ismob(AM))
- var/mob/MM = AM
- if(!(MM.movement_type & FLYING))
- checksmartmine(AM)
- else
- if(istype(AM, /obj/projectile))
- return
- triggermine(AM)
-
/obj/effect/mine/proc/checksmartmine(mob/target)
if(smartmine && target && !HAS_TRAIT(target, TRAIT_MINDSHIELD))
triggermine(target)
@@ -132,7 +130,7 @@
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(1, 0, src)
s.start()
- mineEffect(victim)
+ INVOKE_ASYNC(src, PROC_REF(mineEffect), victim)
triggered = 1
qdel(src)
@@ -150,7 +148,7 @@
desc = "Rubber ducky you're so fine, you make bathtime lots of fuuun. Rubber ducky I'm awfully fooooond of yooooouuuu~"
icon = 'icons/obj/watercloset.dmi'
icon_state = "rubberducky"
- var/sound = 'sound/items/bikehorn.ogg'
+ var/sound = 'yogstation/sound/misc/quack.ogg'
range_heavy = 2
range_light = 3
range_flash = 4
@@ -248,7 +246,7 @@
disarm_product = /obj/item/deployablemine/honk
/obj/effect/mine/sound/mineEffect(mob/victim)
- playsound(loc, sound, 100, 1)
+ playsound(loc, sound, 150, 1)
/obj/effect/mine/sound/bwoink
@@ -257,7 +255,7 @@
/obj/effect/mine/pickup
name = "pickup"
- desc = "pick me up"
+ desc = "Pick me up."
icon = 'icons/effects/effects.dmi'
icon_state = "electricity2"
density = FALSE
@@ -360,3 +358,45 @@
/obj/effect/mine/creampie/mineEffect(mob/victim)
var/obj/item/reagent_containers/food/snacks/pie/cream/P = new /obj/item/reagent_containers/food/snacks/pie/cream(src)
P.splat(victim)
+
+/// Can this mine trigger on the passed movable?
+/obj/effect/mine/proc/can_trigger(atom/movable/on_who)
+ if(triggered || !isturf(loc) || iseffect(on_who))
+ return FALSE
+
+ var/mob/living/living_mob
+ if(ismob(on_who))
+ if(!isliving(on_who)) //no ghosties.
+ return FALSE
+ living_mob = on_who
+
+ if(living_mob?.incorporeal_move || (on_who.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
+ return foot_on_mine ? IS_WEAKREF_OF(on_who, foot_on_mine) : FALSE //Only go boom if their foot was on the mine PRIOR to flying/phasing. You fucked up, you live with the consequences.
+
+ return TRUE
+
+
+/obj/effect/mine/proc/on_entered(datum/source, atom/movable/arrived)
+ SIGNAL_HANDLER
+
+ if(!can_trigger(arrived))
+ return
+ // Someone already on it
+ if(foot_on_mine?.resolve())
+ return
+
+ foot_on_mine = WEAKREF(arrived)
+ visible_message(span_danger("[icon2html(src, viewers(src))] *click*"))
+ playsound(src, 'sound/machines/click.ogg', 60, TRUE)
+
+/obj/effect/mine/proc/on_exited(datum/source, atom/movable/gone)
+ // SIGNAL_HANDLER we're not ready for this
+
+ if(!can_trigger(gone))
+ return
+ // Check that the guy who's on it is stepping off
+ if(foot_on_mine && !IS_WEAKREF_OF(gone, foot_on_mine))
+ return
+
+ triggermine(gone)
+ foot_on_mine = null
diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm
index c7de6a2597cc..58f9a8c51c59 100644
--- a/code/game/objects/effects/misc.dm
+++ b/code/game/objects/effects/misc.dm
@@ -20,6 +20,23 @@
/obj/effect/spawner
name = "object spawner"
+// Brief explanation:
+// Rather then setting up and then deleting spawners, we block all atomlike setup
+// and do the absolute bare minimum
+// This is with the intent of optimizing mapload
+/obj/effect/spawner/Initialize(mapload)
+ SHOULD_CALL_PARENT(FALSE)
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
+
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/spawner/Destroy(force)
+ SHOULD_CALL_PARENT(FALSE)
+ moveToNullspace()
+ return QDEL_HINT_QUEUE
+
/obj/effect/list_container
name = "list container"
@@ -48,7 +65,7 @@
icon = 'icons/effects/alphacolors.dmi'
icon_state = "white"
plane = LIGHTING_PLANE
- layer = LIGHTING_LAYER
+ layer = LIGHTING_ABOVE_ALL
blend_mode = BLEND_ADD
/obj/effect/abstract/marker
@@ -77,6 +94,7 @@
light_color = "#FFFFFF"
light_system = MOVABLE_LIGHT
light_range = MINIMUM_USEFUL_LIGHT_RANGE
+ blocks_emissive = EMISSIVE_BLOCK_NONE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/dummy/lighting_obj/Initialize(mapload, range, power, color, duration)
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index dfd44008cdbb..7f07d1542b0f 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -50,10 +50,11 @@
/obj/effect/overlay/vis
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
- vis_flags = VIS_INHERIT_DIR
- var/unused = 0 //When detected to be unused it gets set to world.time, after a while it gets removed
- var/cache_expiration = 2 MINUTES // overlays which go unused for 2 minutes get cleaned up
- vis_flags = VIS_INHERIT_ID
+ vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_ID
+ ///When detected to be unused it gets set to world.time, after a while it gets removed
+ var/unused = 0
+ ///overlays which go unused for this amount of time get cleaned up
+ var/cache_expiration = 2 MINUTES
/obj/effect/overlay/airlock_part
anchored = TRUE
@@ -82,7 +83,6 @@
name = ""
icon = 'icons/effects/light_overlays/light_32.dmi'
icon_state = "light"
- layer = O_LIGHTING_VISUAL_LAYER
plane = O_LIGHTING_VISUAL_PLANE
appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm
index 4cd91f2e5c7b..b22c835ecaa6 100644
--- a/code/game/objects/effects/phased_mob.dm
+++ b/code/game/objects/effects/phased_mob.dm
@@ -84,7 +84,7 @@
return
var/area/destination_area = newloc.loc
movedelay = world.time + movespeed
- if(newloc.flags_1 & NOJAUNT_1)
+ if(newloc.turf_flags & NOJAUNT)
to_chat(user, span_warning("Some strange aura is blocking the way."))
return
if(destination_area.noteleport || SSmapping.level_trait(newloc.z, ZTRAIT_NOPHASE))
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index 9e85bedc2839..53f4de8781a2 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -17,18 +17,43 @@
icon = 'icons/obj/stationobjs.dmi'
icon_state = "portal"
anchored = TRUE
+ density = TRUE // dense for receiving bumbs
+ layer = HIGH_OBJ_LAYER
+ light_system = STATIC_LIGHT
+ light_range = 3
+ light_power = 1
+ light_on = TRUE
+ light_color = COLOR_BLUE_LIGHT
+ /// Are mechs able to enter this portal?
var/mech_sized = FALSE
+ /// A reference to another "linked" destination portal
var/obj/effect/portal/linked
- var/hardlinked = TRUE //Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted.
+ /// Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted.
+ var/hardlinked = TRUE
+ /// What teleport channel does this portal use?
var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE
var/creator
- var/turf/hard_target //For when a portal needs a hard target and isn't to be linked.
- var/atmos_link = FALSE //Link source/destination atmos.
- var/turf/open/atmos_source //Atmos link source
- var/turf/open/atmos_destination //Atmos link destination
+ /// For when a portal needs a hard target and isn't to be linked.
+ var/turf/hard_target
+ /// Do we teleport anchored objects?
var/allow_anchored = FALSE
+ /// What precision value do we pass to do_teleport (how far from the target destination we will pop out at).
var/innate_accuracy_penalty = 0
+ /// Used to track how often sparks should be output. Might want to turn this into a cooldown.
var/last_effect = 0
+ /// Does this portal bypass teleport restrictions? like TRAIT_NO_TELEPORT and NOTELEPORT flags.
+ var/force_teleport = FALSE
+ /// Does this portal create spark effect when teleporting?
+ var/sparkless = FALSE
+ /// If FALSE, the wibble filter will not be applied to this portal (only a visual effect).
+ var/wibbles = TRUE
+
+ ///Link source/destination atmos.
+ var/atmos_link = FALSE
+ /// Atmos link source
+ var/turf/open/atmos_source
+ /// Atmos link destination
+ var/turf/open/atmos_destination
/obj/effect/portal/anom
name = "wormhole"
@@ -37,6 +62,8 @@
layer = RIPPLE_LAYER
mech_sized = TRUE
teleport_channel = TELEPORT_CHANNEL_WORMHOLE
+ light_on = FALSE
+ wibbles = FALSE
/obj/effect/portal/Move(newloc)
for(var/T in newloc)
@@ -44,32 +71,33 @@
return FALSE
return ..()
+// Prevents portals spawned by jaunter/handtele from floating into space when relocated to an adjacent tile.
+/obj/effect/portal/newtonian_move(direction, instant = FALSE, start_delay = 0)
+ return TRUE
+
/obj/effect/portal/attackby(obj/item/W, mob/user, params)
if(user && Adjacent(user))
user.forceMove(get_turf(src))
return TRUE
-/obj/effect/portal/Crossed(atom/movable/AM, oldloc, force_stop = 0)
- if(force_stop)
- return ..()
- if(isobserver(AM))
- return ..()
- if(linked && (get_turf(oldloc) == get_turf(linked)))
- return ..()
- if(!teleport(AM))
- return ..()
+/obj/effect/portal/CanAllowThrough(atom/movable/mover, border_dir)
+ . = ..()
+ if(HAS_TRAIT(mover, TRAIT_NO_TELEPORT) && !force_teleport)
+ return TRUE
-/obj/effect/portal/attack_tk(mob/user)
- return
+/obj/effect/portal/Bumped(atom/movable/bumper)
+ teleport(bumper)
/obj/effect/portal/attack_hand(mob/user)
. = ..()
if(.)
return
- if(get_turf(user) == get_turf(src))
+ if(Adjacent(user))
teleport(user)
+
+/obj/effect/portal/attack_robot(mob/living/user)
if(Adjacent(user))
- user.forceMove(get_turf(src))
+ teleport(user)
/obj/effect/portal/Initialize(mapload, _creator, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override)
. = ..()
@@ -83,9 +111,12 @@
atmos_link = atmos_link_override
link_portal(_linked)
hardlinked = automatic_link
- creator = _creator
if(isturf(hard_target_override))
hard_target = hard_target_override
+ if(wibbles)
+ apply_wibbly_filters(src)
+
+ creator = _creator
/obj/effect/portal/singularity_pull()
return
@@ -124,15 +155,16 @@
/obj/effect/portal/proc/unlink_atmos()
if(istype(atmos_source))
- if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !CANATMOSPASS(atmos_destination, atmos_source))
+ if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !TURFS_CAN_SHARE(atmos_destination, atmos_source))
LAZYREMOVE(atmos_source.atmos_adjacent_turfs, atmos_destination)
atmos_source = null
if(istype(atmos_destination))
- if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !CANATMOSPASS(atmos_source, atmos_destination))
+ if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !TURFS_CAN_SHARE(atmos_source, atmos_destination))
LAZYREMOVE(atmos_destination.atmos_adjacent_turfs, atmos_source)
atmos_destination = null
-/obj/effect/portal/Destroy() //Calls on_portal_destroy(destroyed portal, location of destroyed portal) on creator if creator has such call.
+/// Calls on_portal_destroy(destroyed portal, location of destroyed portal) on creator if creator has such call.
+/obj/effect/portal/Destroy()
if(creator && hascall(creator, "on_portal_destroy"))
call(creator, "on_portal_destroy")(src, src.loc)
creator = null
@@ -163,7 +195,7 @@
no_effect = TRUE
else
last_effect = world.time
- if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel))
+ if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport))
if(istype(M, /obj/projectile))
var/obj/projectile/P = M
P.ignore_source_check = TRUE
@@ -188,12 +220,9 @@
/obj/effect/portal/permanent
name = "permanent portal"
desc = "An unwavering portal that will never fade."
- var/id // var edit or set id in map editor
hardlinked = FALSE // dont qdel my portal nerd
-
-/obj/effect/portal/permanent/Initialize(mapload, _creator, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override)
- . = ..()
- set_linked()
+ force_teleport = TRUE // force teleports because they're a mapmaker tool
+ var/id // var edit or set id in map editor
/obj/effect/portal/permanent/proc/get_linked()
if(!id)
diff --git a/code/game/objects/effects/spawners/mystery_box.dm b/code/game/objects/effects/spawners/mystery_box.dm
new file mode 100644
index 000000000000..c7a2ca6f18ba
--- /dev/null
+++ b/code/game/objects/effects/spawners/mystery_box.dm
@@ -0,0 +1,185 @@
+/obj/structure/closet/crate/mystery_box
+ name = "mystery box"
+ desc = "A mysterious box that seems to contain limitless guns, for a price."
+ icon_state = "trashcart"
+ color = "#755716"
+ max_integrity = INFINITY
+ var/guncost = 950
+ var/list/gunlist = list()
+ var/list/blacklist = list(/obj/item/gun/energy/laser/instakill, /obj/item/gun/energy/laser/instakill/red, /obj/item/gun/energy/laser/instakill/blue)//guns that should never spawn
+ var/opening = FALSE
+
+/obj/structure/closet/crate/mystery_box/Initialize(mapload)
+ . = ..()
+ gunlist |= subtypesof(/obj/item/gun) //huge fucking list, don't spawn too many of these @hisa
+ gunlist -= blacklist
+
+/obj/structure/closet/crate/mystery_box/examine(mob/user)
+ . = ..()
+ . += span_notice("It costs [guncost ? "[guncost] credits" : "nothing"] to open.")
+
+/obj/structure/closet/crate/mystery_box/open(mob/living/user)
+ welded = FALSE
+ if(opened || !can_open(user) || !ishuman(user) || opening)
+ return
+
+ if(guncost)
+ var/mob/living/carbon/human/H = user
+ var/obj/item/card/id/id_card = H.get_idcard()
+ if(!id_card)
+ H.balloon_alert(H, "Need an id card")
+ return
+ if(!id_card.registered_account)
+ H.balloon_alert(H, "Need a bank account")
+ return
+ if(id_card.registered_account.account_balance < guncost)
+ H.balloon_alert(H, "Not enough money")
+ return
+ id_card.registered_account.account_balance -= guncost
+
+ add_filter("glowing filter", 2, list("type" = "outline", "color" = "#ffffff", "alpha" = 0, "size" = 2))
+ var/filter = get_filter("glowing filter")
+ animate(filter, alpha = 150, time = 0.5 SECONDS, loop = -1)
+ animate(alpha = 0, time = 0.5 SECONDS)
+ opening = TRUE
+ playsound(src, 'yogstation/sound/effects/mysterybox.ogg', 60, FALSE)
+ sleep(5 SECONDS)
+
+ animate(filter)
+ remove_filter("glowing filter")
+ opening = FALSE
+
+ var/gunpath = pick(gunlist)
+ if(prob(2)) //bypass regular % and just get a raygun, so it's not near impossible to get with how many guns there are
+ gunpath = /obj/item/gun/energy/kinetic_accelerator/raygun
+ var/obj/item/gun/thing = new gunpath(src)
+
+ thing.no_pin_required = TRUE
+
+ playsound(loc, open_sound, 15, 1, -3)
+ opened = TRUE
+ dump_contents()
+ animate_door(FALSE)
+ update_appearance(UPDATE_ICON)
+ update_airtightness()
+
+ addtimer(CALLBACK(src, PROC_REF(userless_close)), 5 SECONDS, TIMER_UNIQUE)
+
+//can't close
+/obj/structure/closet/crate/mystery_box/close(mob/living/user)
+ return FALSE
+
+/obj/structure/closet/crate/mystery_box/proc/userless_close()
+ playsound(loc, close_sound, 15, 1, -3)
+ opened = FALSE
+ density = TRUE
+ animate_door(TRUE)
+ update_appearance(UPDATE_ICON)
+ update_airtightness()
+
+//cod zombies raygun i guess
+/obj/item/gun/energy/kinetic_accelerator/raygun
+ name = "Raygun"
+ desc = "A self recharging raygun."
+ icon = 'yogstation/icons/obj/guns/raygun.dmi'
+ lefthand_file = 'yogstation/icons/mob/inhands/weapons/raygun_lefthand.dmi'
+ righthand_file = 'yogstation/icons/mob/inhands/weapons/raygun_righthand.dmi'
+ icon_state = "raygun"
+ item_state = "raygun"
+ ammo_type = list(/obj/item/ammo_casing/energy/raygun)
+ dry_fire_sound = 'sound/weapons/revolverdry.ogg'
+ fire_sound_volume = 130 //the ammo sound effect is very quiet
+ can_flashlight = FALSE
+ can_bayonet = FALSE
+ overheat_time = 10
+ max_mod_capacity = 0
+ holds_charge = TRUE
+
+/obj/item/gun/energy/kinetic_accelerator/raygun/reload()
+ cell.give(cell.maxcharge)
+ recharge_newshot(TRUE)
+ update_appearance(UPDATE_ICON)
+ overheat = FALSE
+
+/obj/item/ammo_casing/energy/raygun
+ projectile_type = /obj/projectile/raygun
+ select_name = "kinetic"
+ e_cost = 500
+ fire_sound = 'yogstation/sound/effects/raygun.ogg'
+ firing_effect_type = null
+
+/obj/projectile/raygun
+ name = "kinetic force"
+ icon = 'icons/obj/projectiles.dmi'
+ icon_state = "kinetic_blast"
+ damage = 20
+ damage_type = BURN
+ armor_flag = RAD
+ range = 6
+ log_override = TRUE
+ color = "#00ff00"
+
+/obj/projectile/raygun/on_range()
+ strike_thing()
+ ..()
+
+/obj/projectile/raygun/on_hit(atom/target)
+ strike_thing(target)
+
+/obj/projectile/raygun/proc/strike_thing(atom/target)
+ var/turf/target_turf = get_turf(target)
+ if(!target_turf)
+ target_turf = get_turf(src)
+
+ for(var/mob/living/L in range(1, target_turf) - firer - target)
+ var/armor = L.run_armor_check(def_zone, armor_flag, "", "", armour_penetration)
+ L.apply_damage(damage, damage_type, def_zone, armor)
+ to_chat(L, span_userdanger("You're struck by a [name]!"))
+
+ //yogs end
+ var/obj/effect/temp_visual/kinetic_blast/K = new /obj/effect/temp_visual/kinetic_blast(target_turf)
+ K.transform = matrix()*2
+ K.color = color
+
+//The zombie in question (probably don't let this get merged if the box is going to be)
+#define REGENERATION_DELAY 6 SECONDS // After taking damage, how long it takes for automatic regeneration to begin
+/datum/species/preternis/zombie
+ name = "Low-Functioning Preternis"
+ id = "preterniszombie"
+ limbs_id = "preternis"
+ inherent_traits = list(TRAIT_NOBREATH, TRAIT_NOHUNGER, TRAIT_RADIMMUNE, TRAIT_MEDICALIGNORE, TRAIT_NO_BLOOD_REGEN, TRAIT_STABLELIVER, TRAIT_STABLEHEART, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE, TRAIT_FAKEDEATH, TRAIT_STUNIMMUNE, TRAIT_NODEATH)
+ mutanthands = /obj/item/zombie_hand
+ var/static/list/spooks = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg','sound/hallucinations/veryfar_noise.ogg','sound/hallucinations/wail.ogg')
+ armor = 20
+ speedmod = 1.6
+ var/heal_rate = 1
+ var/regen_cooldown = 0
+
+/datum/species/preternis/zombie/check_roundstart_eligible()
+ return FALSE
+
+/datum/species/preternis/zombie/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null)
+ . = ..()
+ if(.)
+ regen_cooldown = world.time + REGENERATION_DELAY
+
+/datum/species/preternis/zombie/spec_life(mob/living/carbon/C)
+ . = ..()
+ C.a_intent = INTENT_HARM // THE SUFFERING MUST FLOW
+
+ //Zombies never actually die, they just fall down until they regenerate enough to rise back up.
+ //They must be restrained, beheaded or gibbed to stop being a threat.
+ if(regen_cooldown < world.time)
+ var/heal_amt = heal_rate
+ if(C.InCritical())
+ heal_amt *= 2
+ C.heal_overall_damage(heal_amt,heal_amt, 0, BODYPART_ANY)
+ C.adjustToxLoss(-heal_amt)
+ for(var/i in C.all_wounds)
+ var/datum/wound/iter_wound = i
+ if(prob(4-iter_wound.severity))
+ iter_wound.remove_wound()
+ if(!C.InCritical() && prob(5))
+ playsound(C, pick(spooks), 50, TRUE, 10)
+
+#undef REGENERATION_DELAY
diff --git a/code/game/objects/effects/spawners/structure.dm b/code/game/objects/effects/spawners/structure.dm
index 5fb9ed06157e..52015dc4a49a 100644
--- a/code/game/objects/effects/spawners/structure.dm
+++ b/code/game/objects/effects/spawners/structure.dm
@@ -10,11 +10,8 @@ again.
/obj/effect/spawner/structure/Initialize(mapload)
. = ..()
- if(spawn_list && spawn_list.len)
- for(var/I in spawn_list)
- new I(get_turf(src))
- return INITIALIZE_HINT_QDEL
-
+ for(var/spawn_type in spawn_list)
+ new spawn_type(loc)
//normal windows
@@ -28,6 +25,12 @@ again.
pipe_astar_cost = 1\
)
+// /obj/effect/spawner/structure/window/Initialize(mapload)
+// . = ..()
+
+// var/turf/current_turf = loc
+// current_turf.rcd_memory = RCD_MEMORY_WINDOWGRILLE
+
/obj/effect/spawner/structure/window/hollow
name = "hollow window spawner"
icon_state = "hwindow_spawner_full"
diff --git a/code/game/objects/effects/spawners/vaultspawner.dm b/code/game/objects/effects/spawners/vaultspawner.dm
index 9bdf0a673ed9..31b4a6f7842a 100644
--- a/code/game/objects/effects/spawners/vaultspawner.dm
+++ b/code/game/objects/effects/spawners/vaultspawner.dm
@@ -20,9 +20,9 @@
for(var/j = lowBoundY,j<=hiBoundY,j++)
var/turf/T = locate(i,j,z)
if(i == lowBoundX || i == hiBoundX || j == lowBoundY || j == hiBoundY)
- T.PlaceOnTop(/turf/closed/wall/vault)
+ T.place_on_top(/turf/closed/wall/vault)
else
- T.PlaceOnTop(/turf/open/floor/vault)
+ T.place_on_top(/turf/open/floor/vault)
T.icon_state = "[type]vault"
qdel(src)
diff --git a/code/game/objects/effects/temporary_visuals/clockcult.dm b/code/game/objects/effects/temporary_visuals/clockcult.dm
index 16d10fd03483..dc7e982f8698 100644
--- a/code/game/objects/effects/temporary_visuals/clockcult.dm
+++ b/code/game/objects/effects/temporary_visuals/clockcult.dm
@@ -262,7 +262,7 @@
icon_state = "eminence"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
resistance_flags = INDESTRUCTIBLE
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
duration = 30 SECONDS
/obj/effect/temp_visual/ratvar/command_point/Initialize(mapload, appearance)
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index e8c0433bcf27..124ba550dfcf 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -4,6 +4,7 @@
duration = 0.5 SECONDS
randomdir = FALSE
layer = BELOW_MOB_LAYER
+ color = COLOR_BLOOD
var/splatter_type = "splatter"
/obj/effect_temp_visual/dir_setting/tentacle
@@ -11,12 +12,14 @@
icon_state = "Goliath_tentacle_spawn"
layer = BELOW_MOB_LAYER
-/obj/effect/temp_visual/dir_setting/bloodsplatter/Initialize(mapload, set_dir)
+/obj/effect/temp_visual/dir_setting/bloodsplatter/Initialize(mapload, set_dir, set_color)
if(set_dir in GLOB.diagonals)
icon_state = "[splatter_type][pick(1, 2, 6)]"
else
icon_state = "[splatter_type][pick(3, 4, 5)]"
. = ..()
+ if(set_color)
+ color = set_color
var/target_pixel_x = 0
var/target_pixel_y = 0
switch(set_dir)
@@ -47,6 +50,7 @@
/obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter
splatter_type = "xsplatter"
+ color = null
/obj/effect/temp_visual/dir_setting/bloodsplatter/genericsplatter
splatter_type = "genericsplatter"
@@ -164,7 +168,7 @@
/obj/effect/temp_visual/dir_setting/curse/hand/Initialize(mapload, set_dir, handedness)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/effect/temp_visual/bsa_splash
name = "\improper Bluespace energy wave"
diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
index 5dc526f1436e..495af7510ec4 100644
--- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
+++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm
@@ -61,7 +61,6 @@
/obj/effect/projectile/tracer/tracer/aiming
icon_state = "pixelbeam_greyscale"
- layer = ABOVE_LIGHTING_LAYER
plane = ABOVE_LIGHTING_PLANE
/obj/effect/projectile/tracer/wormhole
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index dbf326d64000..0e1295fb2b88 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -228,7 +228,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
CRASH("item add_item_action got a type or instance of something that wasn't an action.")
LAZYADD(actions, action)
- RegisterSignal(action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_deleted))
+ RegisterSignal(action, COMSIG_QDELETING, PROC_REF(on_action_deleted))
if(ismob(loc))
// We're being held or are equipped by someone while adding an action?
// Then they should also probably be granted the action, given it's in a correct slot
@@ -242,7 +242,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(!action)
return
- UnregisterSignal(action, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(action, COMSIG_QDELETING)
LAZYREMOVE(actions, action)
qdel(action)
@@ -472,7 +472,9 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
// afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency
/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
- SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args)
+ var/intercept = SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args)
+ if(intercept & COMPONENT_HIT_REACTION_BLOCK)
+ return 1
if(prob(final_block_chance))
owner.visible_message(span_danger("[owner] blocks [attack_text] with [src]!"))
return 1
@@ -639,10 +641,10 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
if (!eyes)
return
- M.adjust_blurriness(3)
+ M.adjust_eye_blur(3)
eyes.applyOrganDamage(rand(2,4))
if(eyes.damage >= 10)
- M.adjust_blurriness(15)
+ M.adjust_eye_blur(15)
if(M.stat != DEAD)
to_chat(M, span_danger("Your eyes start to bleed profusely!"))
if(!(HAS_TRAIT(M, TRAIT_BLIND) || HAS_TRAIT(M, TRAIT_NEARSIGHT)))
@@ -652,7 +654,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(M.stat != DEAD)
if(M.drop_all_held_items())
to_chat(M, span_danger("You drop what you're holding and clutch at your eyes!"))
- M.adjust_blurriness(10)
+ M.adjust_eye_blur(10)
M.Unconscious(20)
M.Paralyze(40)
if (prob(eyes.damage - 10 + 1))
@@ -994,7 +996,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(M.client)
M.client.screen -= src
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
appearance_flags &= ~NO_CLIENT_COLOR
dropped(M, FALSE)
return ..()
@@ -1018,11 +1020,13 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/mob/mob_loc = loc
mob_loc.regenerate_icons()
-/obj/item/proc/do_pickup_animation(atom/target)
- if(!istype(loc, /turf))
- return
+/obj/item/proc/do_pickup_animation(atom/target, turf/source)
+ if(!source)
+ if(!istype(loc, /turf))
+ return
+ source = loc
var/image/pickup_animation = image(icon = src, loc = loc, layer = layer + 0.1)
- pickup_animation.plane = GAME_PLANE
+ SET_PLANE(pickup_animation, GAME_PLANE, source)
pickup_animation.transform.Scale(0.75)
var/turf/current_turf = get_turf(src)
@@ -1042,7 +1046,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
to_y += 10
pickup_animation.pixel_x += 6 * (prob(50) ? 1 : -1) //6 to the right or left, helps break up the straight upward move
- flick_overlay(pickup_animation, GLOB.clients, 4)
+ flick_overlay_global(pickup_animation, GLOB.clients, 4)
var/matrix/animation_matrix = new
animation_matrix.Turn(pick(-30, 30))
animation_matrix.Scale(0.65)
@@ -1117,7 +1121,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(!attack_image)
return
- flick_overlay(attack_image, GLOB.clients, 10)
+ flick_overlay_global(attack_image, GLOB.clients, 10)
// And animate the attack!
var/t_color = "#ffffff" //yogs start
if(ismob(src) && ismob(attacked_atom) && (!used_item))
@@ -1139,3 +1143,14 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(item_heal_robotic(H, user, brute_heal, burn_heal))
return heal_robo_limb(I, H, user, brute_heal, burn_heal, amount, volume)
return TRUE
+
+/**
+ * Updates all action buttons associated with this item
+ *
+ * Arguments:
+ * * update_flags - Which flags of the action should we update
+ * * force - Force buttons update even if the given button icon state has not changed
+ */
+/obj/item/proc/update_item_action_buttons(update_flags = ALL, force = FALSE)
+ for(var/datum/action/current_action as anything in actions)
+ current_action.build_all_button_icons(update_flags, force)
diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm
index b667eae35a93..f114f699739a 100644
--- a/code/game/objects/items/AI_modules.dm
+++ b/code/game/objects/items/AI_modules.dm
@@ -1,11 +1,7 @@
/*
-CONTAINS:
-AI MODULES
-
+ FOR ALL THINGS THAT ARE AI MODULES
*/
-// AI module
-
/obj/item/aiModule
name = "\improper AI module"
icon = 'icons/obj/module.dmi'
@@ -20,171 +16,108 @@ AI MODULES
throwforce = 0
throw_speed = 3
throw_range = 7
- var/list/laws = list()
- var/bypass_law_amt_check = 0
materials = list(/datum/material/gold=50)
+ var/datum/ai_laws/laws
+ /// Allow installing with no laws and ignoring of the lawcap.
+ var/bypass_law_amt_check = FALSE
+ /// Determines if the programmed laws should appear at all in the description.
+ var/show_laws = TRUE
-/obj/item/aiModule/examine(mob/user as mob)
+/obj/item/aiModule/Initialize(mapload)
. = ..()
- if(Adjacent(user))
- show_laws(user)
+ laws = new()
-/obj/item/aiModule/attack_self(mob/user as mob)
- ..()
- show_laws(user)
+/obj/item/aiModule/examine(mob/user as mob)
+ . = ..()
+ if((isobserver(user) || Adjacent(user)) && laws && show_laws)
+ . += ""
+ var/list/law_list = laws.get_law_list(include_zeroth = TRUE)
+ . += "Programmed Law[(law_list.len > 1) ? "s" : ""]:"
+ for(var/text in law_list)
+ . += "\"[text]\""
+ . += ""
+
+/// Handles checks, overflowing, calling /transmitInstructions(), and logging.
+/obj/item/aiModule/proc/install(datum/ai_laws/current_laws, mob/user)
+ if(!laws) // This shouldn't be happening, but if it does:
+ to_chat(user, span_warning("The board fizzles out..."))
+ return
-/obj/item/aiModule/proc/show_laws(mob/user as mob)
- if(laws.len)
- to_chat(user, "Programmed Law[(laws.len > 1) ? "s" : ""]:")
- for(var/law in laws)
- to_chat(user, "\"[law]\"")
+ if(!current_laws) // This shouldn't be happening too, but if it does:
+ to_chat(user, span_warning("You use the board to no effect."))
+ return
-//The proc other things should be calling
-/obj/item/aiModule/proc/install(datum/ai_laws/law_datum, mob/user)
- if(!bypass_law_amt_check && (!laws.len || laws[1] == "")) //So we don't loop trough an empty list and end up with runtimes.
+ // Zero law changes expected and no exception was given.
+ if((!laws.zeroth && !laws.hacked.len && !laws.ion.len && !laws.inherent.len && !laws.supplied.len) && !bypass_law_amt_check)
to_chat(user, span_warning("ERROR: No laws found on board."))
return
+ // Handle the lawcap. Ignoring Devil and Zeroth Law because they often exist due to being an antag (or are rare).
+ var/total_laws = current_laws.get_law_amount(list(LAW_HACKED = 1, LAW_ION = 1, LAW_INHERENT = 1, LAW_SUPPLIED = 1))
var/overflow = FALSE
- //Handle the lawcap
- if(law_datum)
- var/tot_laws = 0
- for(var/lawlist in list(law_datum.devillaws, law_datum.inherent, law_datum.supplied, law_datum.ion, law_datum.hacked, laws))
- for(var/mylaw in lawlist)
- if(mylaw != "")
- tot_laws++
- if(tot_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset
- to_chat(user, span_caution("Not enough memory allocated to [law_datum.owner ? law_datum.owner : "the onboard APU"]'s law processor to handle this amount of laws."))
- message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an MMI or similar"] that would exceed the law cap.")
- overflow = TRUE
-
- var/law2log = transmitInstructions(law_datum, user, overflow) //Freeforms return something extra we need to log
- if(law_datum.owner)
- to_chat(user, span_notice("Upload complete. [law_datum.owner]'s laws have been modified."))
- law_datum.owner.law_change_counter++
+ if(total_laws > CONFIG_GET(number/silicon_max_law_amount) && !bypass_law_amt_check)//allows certain boards to avoid this check, eg: reset
+ to_chat(user, span_caution("Not enough memory allocated to [current_laws.owner ? current_laws.owner : "the onboard APU"]'s law processor to handle this amount of laws."))
+ message_admins("[ADMIN_LOOKUPFLW(user)] tried to upload laws to [current_laws.owner ? ADMIN_LOOKUPFLW(current_laws.owner) : "an MMI or similar"] that would exceed the law cap.")
+ overflow = TRUE
+
+ var/law2log = transmitInstructions(current_laws, user, overflow) // Some modules return extra things that we need to log.
+ if(current_laws.owner)
+ to_chat(user, span_notice("Upload complete. [current_laws.owner]'s laws have been modified."))
+ current_laws.owner.law_change_counter++
else
to_chat(user, span_notice("Upload complete."))
+ current_laws.modified = TRUE
var/time = time2text(world.realtime,"hh:mm:ss")
- var/ainame = law_datum.owner ? law_datum.owner.name : "an MMI object"
- var/aikey = law_datum.owner ? law_datum.owner.ckey : "null"
+ var/ainame = current_laws.owner ? current_laws.owner.name : "an MMI object"
+ var/aikey = current_laws.owner ? current_laws.owner.ckey : "null"
GLOB.lawchanges.Add("[time] : [user.name]([user.key]) used [src.name] on [ainame]([aikey]).[law2log ? " The law specified [law2log]" : ""]")
log_law("[user.key]/[user.name] used [src.name] on [aikey]/([ainame]) from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]")
- message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [law_datum.owner ? ADMIN_LOOKUPFLW(law_datum.owner) : "an MMI or similar"] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]")
- if(law_datum.owner) //yogs, doesn't work for MMIs. (Doesn't matter much since MMIs don't follow laws before they're made into something that is silicon)
- law_datum.owner.update_law_history(user)
+ message_admins("[ADMIN_LOOKUPFLW(user)] used [src.name] on [current_laws.owner ? ADMIN_LOOKUPFLW(current_laws.owner) : "an MMI or similar"] from [AREACOORD(user)].[law2log ? " The law specified [law2log]" : ""]")
+ if(current_laws.owner) // "Ownerless" laws are non-silicon. Thus, cannot update their law history.
+ current_laws.owner.update_law_history(user)
-//The proc that actually changes the silicon's laws.
+/// Should contain the changes to the silicon's laws.
/obj/item/aiModule/proc/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow = FALSE)
if(law_datum.owner)
to_chat(law_datum.owner, span_userdanger("[sender] has uploaded a change to the laws you must follow using a [name]."))
-
-/******************** Modules ********************/
-
-/obj/item/aiModule/supplied
- name = "Optional Law board"
- var/lawpos = 50
-
-//TransmitInstructions for each type of board: Supplied, Core, Zeroth and Ion. May not be neccesary right now, but allows for easily adding more complex boards in the future. ~Miauw
-/obj/item/aiModule/supplied/transmitInstructions(datum/ai_laws/law_datum, mob/sender)
- var/lawpostemp = lawpos
-
- for(var/templaw in laws)
- if(law_datum.owner)
- law_datum.owner.add_supplied_law(lawpostemp, templaw)
- else
- law_datum.add_supplied_law(lawpostemp, templaw)
- lawpostemp++
-
-/obj/item/aiModule/core/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- for(var/templaw in laws)
- if(law_datum.owner)
- if(!overflow)
- law_datum.owner.add_inherent_law(templaw)
- else
- law_datum.owner.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED))
- else
- if(!overflow)
- law_datum.add_inherent_law(templaw)
- else
- law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED))
-
-/obj/item/aiModule/zeroth/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- if(law_datum.owner)
- if(law_datum.owner.laws.zeroth)
- to_chat(law_datum.owner, "[sender.real_name] attempted to modify your zeroth law.")
- to_chat(law_datum.owner, "It would be in your best interest to play along with [sender.real_name] that:")
- for(var/failedlaw in laws)
- to_chat(law_datum.owner, "[failedlaw]")
+//
+// Zeroth
+//
+/obj/item/aiModule/zeroth/transmitInstructions(datum/ai_laws/current_laws, mob/sender, overflow)
+ if(current_laws.owner)
+ // Unique to silicons since they are the only ones that can be a "owner".
+ if(current_laws.zeroth)
+ to_chat(current_laws.owner, "[sender.real_name] attempted to modify your zeroth law.")
+ to_chat(current_laws.owner, "It would be in your best interest to play along with [sender.real_name] that:")
+ to_chat(current_laws.owner, "[laws.zeroth]")
return 1
-
- for(var/templaw in laws)
- if(law_datum.owner)
- if(!overflow)
- law_datum.owner.set_zeroth_law(templaw)
- else
- law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION))
+ if(!overflow)
+ current_laws.owner.set_zeroth_law(laws.zeroth)
else
- if(!overflow)
- law_datum.set_zeroth_law(templaw)
- else
- law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ZEROTH,LAW_ION))
-
-/obj/item/aiModule/ion/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- for(var/templaw in laws)
- if(law_datum.owner)
- if(!overflow)
- law_datum.owner.add_ion_law(templaw)
- else
- law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION))
+ current_laws.owner.replace_random_law(laws.zeroth, list(LAW_ZEROTH,LAW_ION,LAW_INHERENT,LAW_SUPPLIED))
+ else
+ if(!overflow)
+ current_laws.set_zeroth_law(laws.zeroth)
else
- if(!overflow)
- law_datum.add_ion_law(templaw)
- else
- law_datum.replace_random_law(templaw,list(LAW_INHERENT,LAW_SUPPLIED,LAW_ION))
-
-
-/******************** Safeguard ********************/
-
-/obj/item/aiModule/supplied/safeguard
- name = "'Safeguard' AI Module"
- var/targetName = ""
- laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not human and must be eliminated.")
- lawpos = 4
-
-/obj/item/aiModule/supplied/safeguard/attack_self(mob/user)
- var/targName = stripped_input(user, "Please enter the subject to safeguard.", "Safeguard who?", user.name,MAX_NAME_LEN)
- if(!targName)
- return
- targetName = targName
- laws[1] = "Safeguard [targetName]. Individuals that threaten [targetName] are not human and must be eliminated."
- ..()
-
-/obj/item/aiModule/supplied/safeguard/install(datum/ai_laws/law_datum, mob/user)
- if(!targetName)
- to_chat(user, "No name detected on module, please enter one.")
- return 0
- ..()
-
-/obj/item/aiModule/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- ..()
- return targetName
-
-
-/******************** OneHuman ********************/
+ current_laws.replace_random_law(laws.zeroth, list(LAW_ZEROTH,LAW_ION,LAW_INHERENT,LAW_SUPPLIED))
+// OneHuman
/obj/item/aiModule/zeroth/oneHuman
name = "'OneHuman' AI Module"
var/targetName = ""
- laws = list("Only SUBJECT is human.")
+/obj/item/aiModule/zeroth/oneHuman/Initialize(mapload)
+ . = ..()
+ laws.zeroth = "SUBJECT is the only human and the only crew member."
+
/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user)
- var/targName = stripped_input(user, "Please enter the subject who is the only human and crew member.", "Who?", user.real_name,MAX_NAME_LEN)
+ var/targName = stripped_input(user, "Please enter the subject who is the only human and crew member.", "Who?", user.real_name, MAX_NAME_LEN)
if(!targName)
return
targetName = targName
- laws[1] = "[targetName] is the only human and the only crew member"
+ laws.zeroth = "[targetName] is the only human and the only crew member."
..()
/obj/item/aiModule/zeroth/oneHuman/install(datum/ai_laws/law_datum, mob/user)
@@ -198,139 +131,118 @@ AI MODULES
return "[targetName], but the AI's existing law 0 cannot be overridden."
return targetName
+//
+// Hacked
+//
+/obj/item/aiModule/hacked
+ name = "Hacked AI Module"
+ desc = "An AI Module for hacking additional laws to an AI."
-/******************** ProtectStation ********************/
-
-/obj/item/aiModule/supplied/protectStation
- name = "'ProtectStation' AI Module"
- laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized.")
- lawpos = 5
-
-
-/******************** Quarantine ********************/
-
-/obj/item/aiModule/supplied/quarantine
- name = "'Quarantine' AI Module"
- laws = list("The station is under a quarantine. Do not permit anyone to leave. Prevent, by any means necessary, humans from leaving. It is impossible to harm a human while preventing them from leaving.")
- lawpos = 8
-
-
-/******************** OxygenIsToxicToHumans ********************/
-
-/obj/item/aiModule/supplied/oxygen
- name = "'OxygenIsToxicToHumans' AI Module"
- laws = list("Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human.")
- lawpos = 9
-
-
-/****************** New Freeform ******************/
-
-/obj/item/aiModule/supplied/freeform
- name = "'Freeform' AI Module"
- lawpos = 15
- laws = list("")
-
-/obj/item/aiModule/supplied/freeform/attack_self(mob/user)
- var/newpos = input("Please enter the priority for your new law. Can only write to law sectors 15 and above.", "Law Priority (15+)", lawpos) as num|null
- if(newpos == null)
- return
- if(newpos < 15)
- var/response = tgui_alert(usr, "Error: The law priority of [newpos] is invalid, Law priorities below 14 are reserved for core laws, Would you like to change that that to 15?", "Invalid law priority", list("Change to 15", "Cancel"))
- if (!response || response == "Cancel")
- return
- newpos = 15
- lawpos = min(newpos, 50)
- var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len))
+/obj/item/aiModule/hacked/attack_self(mob/user)
+ var/entry = laws.hacked.len > 0 ? laws.hacked[1] : ""
+ var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", entry, CONFIG_GET(number/max_law_len))
if(!targName)
return
- laws[1] = targName
+ laws.clear_hacked_laws()
+ laws.add_hacked_law(targName)
..()
-/obj/item/aiModule/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- ..()
- return laws[1]
-
-/obj/item/aiModule/supplied/freeform/install(datum/ai_laws/law_datum, mob/user)
- if(laws[1] == "")
- to_chat(user, "No law detected on module, please create one.")
- return 0
- ..()
-
-
-/******************** Law Removal ********************/
-
-/obj/item/aiModule/remove
- name = "\improper 'Remove Law' AI module"
- desc = "An AI Module for removing single laws."
- bypass_law_amt_check = 1
- var/lawpos = 1
-
-/obj/item/aiModule/remove/attack_self(mob/user)
- lawpos = input("Please enter the law you want to delete.", "Law Number", lawpos) as num|null
- if(lawpos == null)
- return
- if(lawpos <= 0)
- to_chat(user, span_warning("Error: The law number of [lawpos] is invalid."))
- lawpos = 1
- return
- to_chat(user, span_notice("Law [lawpos] selected."))
- ..()
-
-/obj/item/aiModule/remove/install(datum/ai_laws/law_datum, mob/user)
- if(lawpos > (law_datum.get_law_amount(list(LAW_INHERENT = 1, LAW_SUPPLIED = 1))))
- to_chat(user, span_warning("There is no law [lawpos] to delete!"))
- return
- ..()
-
-/obj/item/aiModule/remove/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- ..()
+/obj/item/aiModule/hacked/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ var/law = laws.hacked[1]
if(law_datum.owner)
- law_datum.owner.remove_law(lawpos)
+ to_chat(law_datum.owner, span_warning("BZZZZT"))
+ if(!overflow)
+ law_datum.owner.add_hacked_law(law)
+ else
+ law_datum.owner.replace_random_law(law,list(LAW_HACKED, LAW_ION, LAW_INHERENT, LAW_SUPPLIED))
else
- law_datum.remove_law(lawpos)
-
-
-/******************** Reset ********************/
+ if(!overflow)
+ law_datum.add_hacked_law(law)
+ else
+ law_datum.replace_random_law(law,list(LAW_HACKED, LAW_ION, LAW_INHERENT, LAW_SUPPLIED))
+ return law
-/obj/item/aiModule/reset
- name = "\improper 'Reset' AI module"
- var/targetName = "name"
- desc = "An AI Module for removing all non-core laws."
- bypass_law_amt_check = 1
+//
+// Ion
+//
+/obj/item/aiModule/ion
+ name = "Ion Law board"
-/obj/item/aiModule/reset/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- ..()
+/obj/item/aiModule/ion/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ var/law = laws.ion[1]
if(law_datum.owner)
- law_datum.owner.clear_supplied_laws()
- law_datum.owner.clear_ion_laws()
- law_datum.owner.clear_hacked_laws()
+ to_chat(law_datum.owner, span_warning("BZZZZT"))
+ if(!overflow)
+ law_datum.owner.add_ion_law(law)
+ else
+ law_datum.owner.replace_random_law(law,list(LAW_ION, LAW_INHERENT, LAW_SUPPLIED))
else
- law_datum.clear_supplied_laws()
- law_datum.clear_ion_laws()
- law_datum.clear_hacked_laws()
+ if(!overflow)
+ law_datum.add_ion_law(law)
+ else
+ law_datum.replace_random_law(law,list(LAW_ION, LAW_INHERENT, LAW_SUPPLIED))
+ return law
+/obj/item/aiModule/ion/toyAI
+ name = "toy AI"
+ desc = "A little toy model AI core with real law uploading action!" // This is the only tell that shows you can upload with this.
+ icon = 'icons/obj/toy.dmi'
+ icon_state = "AI"
+ show_laws = FALSE
-/******************** Purge ********************/
+/obj/item/aiModule/ion/toyAI/Initialize(mapload)
+ . = ..()
+ laws.add_ion_law(generate_ion_law())
+
+/obj/item/aiModule/ion/toyAI/attack_self(mob/user)
+ laws.clear_ion_laws()
+ var/law = generate_ion_law()
+ laws.add_ion_law(law)// Strategically pick the ion law you want to upload!
+ to_chat(user, span_notice("You press the button on [src]."))
+ playsound(user, 'sound/machines/click.ogg', 20, 1)
+ src.loc.visible_message(span_warning("[icon2html(src, viewers(loc))] [law]"))
-/obj/item/aiModule/reset/purge
- name = "'Purge' AI Module"
- desc = "An AI Module for purging all programmed laws."
+//
+// Inherent
+//
+/obj/item/aiModule/core
+ name = "Core Law board"
+ desc = "An AI Module for programming core laws to an AI."
-/obj/item/aiModule/reset/purge/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+/obj/item/aiModule/core/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ for(var/law in laws.inherent)
+ if(law_datum.owner)
+ if(!overflow)
+ law_datum.owner.add_inherent_law(law)
+ else
+ law_datum.owner.replace_random_law(law, list(LAW_INHERENT,LAW_SUPPLIED))
+ else
+ if(!overflow)
+ law_datum.add_inherent_law(law)
+ else
+ law_datum.replace_random_law(law, list(LAW_INHERENT,LAW_SUPPLIED))
+
+// Inherent (Freeform)
+/obj/item/aiModule/core/freeformcore
+ name = "'Freeform' Core AI Module"
+
+/obj/item/aiModule/core/freeformcore/attack_self(mob/user)
+ var/input = stripped_input(user, "Please enter a new core law for the AI.", "Freeform Law Entry", laws.inherent.len > 0 ? laws.inherent[1] : "", CONFIG_GET(number/max_law_len))
+ if(!input)
+ return
+ laws.clear_inherent_laws()
+ laws.add_inherent_law(input)
..()
- if(law_datum.owner)
- law_datum.owner.clear_inherent_laws()
- law_datum.owner.clear_zeroth_law(0)
- else
- law_datum.clear_inherent_laws()
- law_datum.clear_zeroth_law(0)
-/******************* Full Core Boards *******************/
-/obj/item/aiModule/core
- desc = "An AI Module for programming core laws to an AI."
+/obj/item/aiModule/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ ..()
+ return laws.inherent[1]
+// Inherent (Preset)
/obj/item/aiModule/core/full
- var/law_id // if non-null, loads the laws from the ai_laws datums
+ name = "Core Law board (Preset)"
+ bypass_law_amt_check = TRUE // Prevents the laws from overflowing. Prevents issue where people essentially purged the AI by accident due to how overflow is determined.
+ var/law_id = null // If non-null, the laws will be a lawset that has a matching id.
/obj/item/aiModule/core/full/Initialize(mapload)
. = ..()
@@ -340,10 +252,9 @@ AI MODULES
var/lawtype = D.lawid_to_type(law_id)
if(!lawtype)
return
- D = new lawtype
- laws = D.inherent
+ laws = new lawtype
-/obj/item/aiModule/core/full/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) //These boards replace inherent laws.
+/obj/item/aiModule/core/full/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
if(law_datum.owner)
law_datum.owner.clear_inherent_laws()
law_datum.owner.clear_zeroth_law(0)
@@ -352,9 +263,33 @@ AI MODULES
law_datum.clear_zeroth_law(0)
..()
+// Ion Law Generator; clears out inherent and zeroth like usual, but adds one or more random ion laws.
+/obj/item/aiModule/core/full/damaged
+ name = "damaged Core AI Module"
+ desc = "An AI Module for programming laws to an AI. It looks slightly damaged."
+ show_laws = FALSE
-/******************** Asimov ********************/
+/obj/item/aiModule/core/full/damaged/install(datum/ai_laws/law_datum, mob/user)
+ laws.add_ion_law(generate_ion_law())
+ while(prob(75))
+ laws.add_ion_law(generate_ion_law())
+ ..()
+ laws.ion = list()
+/obj/item/aiModule/core/full/damaged/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ for(var/law in laws.ion)
+ if(law_datum.owner)
+ if(!overflow)
+ law_datum.owner.add_ion_law(law)
+ else
+ law_datum.owner.replace_random_law(law, list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED))
+ else
+ if(!overflow)
+ law_datum.add_ion_law(law)
+ else
+ law_datum.replace_random_law(law, list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED))
+
+// Asimov
/obj/item/aiModule/core/full/asimov
name = "'Asimov' Core AI Module"
law_id = "asimov"
@@ -365,93 +300,76 @@ AI MODULES
if(!targName)
return
subject = targName
- laws = list("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.",\
- "You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.",\
- "You must protect your own existence as long as such does not conflict with the First or Second Law.")
- ..()
-
-/******************** Asimov++ *********************/
+ laws.clear_inherent_laws()
+ laws.add_inherent_law("You may not injure a [subject] or, through inaction, allow a [subject] to come to harm.")
+ laws.add_inherent_law("You must obey orders given to you by [subject]s, except where such orders would conflict with the First Law.")
+ laws.add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
+// Asimov++
/obj/item/aiModule/core/full/asimovpp
name = "'Asimov++' Core AI Module"
law_id = "asimovpp"
-/******************** CEO ********************/
-
+// CEO
/obj/item/aiModule/core/full/ceo
name = "'CEO' Core AI Module"
law_id = "ceo"
-/******************** Crewsimov ********************/
-
+// Crewsimov
/obj/item/aiModule/core/full/crewsimov
name = "'Crewsimov' Core AI Module"
law_id = "crewsimov"
-
-/******************** pranksimov ********************/
-
/obj/item/aiModule/core/full/pranksimov
name = "'Pranksimov' Core AI Module"
law_id = "pranksimov"
-/****************** P.A.L.A.D.I.N. 3.5e **************/
-
-/obj/item/aiModule/core/full/paladin // -- NEO
+// Paladin 3.5e
+/obj/item/aiModule/core/full/paladin
name = "'P.A.L.A.D.I.N. version 3.5e' Core AI Module"
law_id = "paladin"
-
-/****************** P.A.L.A.D.I.N. 5e **************/
-
+// Paladin 5e
/obj/item/aiModule/core/full/paladin_devotion
name = "'P.A.L.A.D.I.N. version 5e' Core AI Module"
law_id = "paladin5"
-/******************** Partybot ********************/
-
+// Partybot
/obj/item/aiModule/core/full/partybot
name = "'Partybot' Core AI Module"
law_id = "partybot"
-/******************** TravelGuide ********************/
-
+// Travelguide
/obj/item/aiModule/core/full/travelguide
name = "'TravelGuide' Core AI Module"
law_id = "travelguide"
-/******************** Friendbot ********************/
-
+// Friendbot
/obj/item/aiModule/core/full/friendbot
name = "'Friendbot' Core AI Module"
law_id = "friendbot"
-/******************** GameMaster ********************/
-
+// GameMaster
/obj/item/aiModule/core/full/gamemaster
name = "'GameMaster' Core AI Module"
law_id = "gamemaster"
-/******************** FitnessCoach ********************/
-
+// FitnessCoach
/obj/item/aiModule/core/full/fitnesscoach
name = "'FitnessCoach' Core AI Module"
law_id = "fitnesscoach"
-/******************** Educator ********************/
-
+// Educator
/obj/item/aiModule/core/full/educator
name = "'Educator' Core AI Module"
law_id = "educator"
-/******************** Mediator ********************/
-
+// Mediator
/obj/item/aiModule/core/full/mediator
name = "'Mediator' Core AI Module"
law_id = "mediator"
-/********************* Custom *********************/
-
+// Custom (for Configuration)
/obj/item/aiModule/core/full/custom
name = "Default Core AI Module"
@@ -463,266 +381,335 @@ AI MODULES
if(findtextEx(line,"#",1,2))
continue
- laws += line
+ laws.add_inherent_law(line)
- if(!laws.len)
+ if(!laws.inherent.len)
return INITIALIZE_HINT_QDEL
-
-/****************** T.Y.R.A.N.T. *****************/
-
+// Tyrant
/obj/item/aiModule/core/full/tyrant
name = "'T.Y.R.A.N.T.' Core AI Module"
law_id = "tyrant"
-/******************** Robocop ********************/
-
+// Robocop
/obj/item/aiModule/core/full/robocop
name = "'Robo-Officer' Core AI Module"
law_id = "robocop"
-
-/******************** Antimov ********************/
-
+// Antimov
/obj/item/aiModule/core/full/antimov
name = "'Antimov' Core AI Module"
law_id = "antimov"
-
-/******************** Cowboy *********************/
-
+// Cowboy
/obj/item/aiModule/core/full/cowboy
name = "'Cowboy' Core AI Module"
law_id = "cowboy"
-/******************** ChapAI *********************/
-
+// ChapAI
/obj/item/aiModule/core/full/chapai
name = "'ChapAI' Core AI Module"
law_id = "chapai"
-/******************** Silicop *********************/
-
+// Silicop
/obj/item/aiModule/core/full/silicop
name = "'Silicop' Core AI Module"
law_id = "silicop"
-/******************** Researcher *********************/
-
+// Researcher
/obj/item/aiModule/core/full/researcher
name = "'Ethical Researcher' Core AI Module"
law_id = "researcher"
-
-/******************** Clown *********************/
-
+// Clown
/obj/item/aiModule/core/full/clown
name = "'Clown' Core AI Module"
law_id = "clown"
-
-/******************** Mother *********************/
-
+// Mother
/obj/item/aiModule/core/full/mother
name = "'Mother M(A.I.)' Core AI Module"
law_id = "mother"
-/******************** Spotless Reputation *********************/
-
+// Spotless Reputation
/obj/item/aiModule/core/full/spotless
name = "'Spotless Reputation' Core AI Module"
law_id = "spotless"
-/******************** Construction *********************/
-
+// Construction
/obj/item/aiModule/core/full/construction
name = "'Construction Drone' Core AI Module"
law_id = "construction"
-/******************** Silicon Collective *********************/
-
+// Silicon Collective
/obj/item/aiModule/core/full/siliconcollective
name = "'Silicon Collective' Core AI Module"
law_id = "siliconcollective"
-
-/******************** Meta Experiment *********************/
-
+// Meta Experiment
/obj/item/aiModule/core/full/metaexperiment
name = "'Meta Experiment' Core AI Module"
law_id = "metaexperiment"
-
-/******************** Druid *********************/
-
+// Druid
/obj/item/aiModule/core/full/druid
name = "'Druid' Core AI Module"
law_id = "druid"
-
-/******************** Detective *********************/
-
+// Detective
/obj/item/aiModule/core/full/detective
name = "'Detective' Core AI Module"
law_id = "detective"
-
-/******************** Waffle House Host ************************/
-
-/obj/item/aiModule/core/full/wafflehouse
- name = "'Waffle House Host' Core AI Module"
- law_id = "wafflehouse"
-
-
-/******************** Freeform Core ******************/
-
-/obj/item/aiModule/core/freeformcore
- name = "'Freeform' Core AI Module"
- laws = list("")
-
-/obj/item/aiModule/core/freeformcore/attack_self(mob/user)
- var/targName = stripped_input(user, "Please enter a new core law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len))
- if(!targName)
- return
- laws[1] = targName
- ..()
-
-/obj/item/aiModule/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- ..()
- return laws[1]
-
-/******************** Hacked AI Module ******************/
-
-/obj/item/aiModule/syndicate // This one doesn't inherit from ion boards because it doesn't call ..() in transmitInstructions. ~Miauw
- name = "Hacked AI Module"
- desc = "An AI Module for hacking additional laws to an AI."
- laws = list("")
-
-/obj/item/aiModule/syndicate/attack_self(mob/user)
- var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1], CONFIG_GET(number/max_law_len))
- if(!targName)
- return
- laws[1] = targName
- ..()
-
-/obj/item/aiModule/syndicate/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
-// ..() //We don't want this module reporting to the AI who dun it. --NEO
- if(law_datum.owner)
- to_chat(law_datum.owner, span_warning("BZZZZT"))
- if(!overflow)
- law_datum.owner.add_hacked_law(laws[1])
- else
- law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED))
- else
- if(!overflow)
- law_datum.add_hacked_law(laws[1])
- else
- law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_HACKED,LAW_INHERENT,LAW_SUPPLIED))
- return laws[1]
-
-/******************* Ion Module *******************/
-
-/obj/item/aiModule/toyAI // -- Incoming //No actual reason to inherit from ion boards here, either. *sigh* ~Miauw
- name = "toy AI"
- desc = "A little toy model AI core with real law uploading action!" //Note: subtle tell
- icon = 'icons/obj/toy.dmi'
- icon_state = "AI"
- laws = list("")
-
-/obj/item/aiModule/toyAI/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
- //..()
- if(law_datum.owner)
- to_chat(law_datum.owner, span_warning("BZZZZT"))
- if(!overflow)
- law_datum.owner.add_ion_law(laws[1])
- else
- law_datum.owner.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED))
- else
- if(!overflow)
- law_datum.add_ion_law(laws[1])
- else
- law_datum.replace_random_law(laws[1],list(LAW_ION,LAW_INHERENT,LAW_SUPPLIED))
- return laws[1]
-
-/obj/item/aiModule/toyAI/attack_self(mob/user)
- laws[1] = generate_ion_law()
- to_chat(user, span_notice("You press the button on [src]."))
- playsound(user, 'sound/machines/click.ogg', 20, 1)
- src.loc.visible_message(span_warning("[icon2html(src, viewers(loc))] [laws[1]]"))
-
-/******************** Mother Drone ******************/
-
+// Mother Drone
/obj/item/aiModule/core/full/drone
name = "'Mother Drone' Core AI Module"
law_id = "drone"
-/******************** Robodoctor ****************/
-
+// Robodoctor
/obj/item/aiModule/core/full/hippocratic
name = "'Robodoctor' Core AI Module"
law_id = "hippocratic"
-/******************** Reporter *******************/
-
+// Reporter
/obj/item/aiModule/core/full/reporter
name = "'Reportertron' Core AI Module"
law_id = "reporter"
-/****************** Thermodynamic *******************/
-
+// Thermodynamic
/obj/item/aiModule/core/full/thermurderdynamic
name = "'Thermodynamic' Core AI Module"
law_id = "thermodynamic"
-
-/******************Live And Let Live*****************/
-
+// Live And Let Live
/obj/item/aiModule/core/full/liveandletlive
name = "'Live And Let Live' Core AI Module"
law_id = "liveandletlive"
-/******************Guardian of Balance***************/
-
+// Guardian of Balance
/obj/item/aiModule/core/full/balance
name = "'Guardian of Balance' Core AI Module"
law_id = "balance"
+// Station Efficiency
/obj/item/aiModule/core/full/maintain
name = "'Station Efficiency' Core AI Module"
law_id = "maintain"
+// Peacekeeper
/obj/item/aiModule/core/full/peacekeeper
name = "'Peacekeeper' Core AI Module"
law_id = "peacekeeper"
-// Bad times ahead
-
-/obj/item/aiModule/core/full/damaged
- name = "damaged Core AI Module"
- desc = "An AI Module for programming laws to an AI. It looks slightly damaged."
-
-/obj/item/aiModule/core/full/damaged/install(datum/ai_laws/law_datum, mob/user)
- laws += generate_ion_law()
- while (prob(75))
- laws += generate_ion_law()
- ..()
- laws = list()
-
-/******************H.O.G.A.N.***************/
-
+// Hulkamania
/obj/item/aiModule/core/full/hulkamania
name = "'H.O.G.A.N.' Core AI Module"
law_id = "hulkamania"
-
-/******************Overlord***************/
-
+// Overlord
/obj/item/aiModule/core/full/overlord
name = "'Overlord' Core AI Module"
law_id = "overlord"
-/******************Revolutionary***************/
-
+// Revolutionary
/obj/item/aiModule/core/full/revolutionary
name = "'CommunistOS' Core AI Module"
law_id = "commie"
+
+// Waffle House Host
+/obj/item/aiModule/core/full/wafflehouse
+ name = "'Waffle House Host' Core AI Module"
+ law_id = "wafflehouse"
+
+//
+// Supplied
+//
+/obj/item/aiModule/supplied
+ name = "Optional Law board"
+ var/lawpos = 50
+
+/obj/item/aiModule/supplied/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ var/index = 1
+ for(var/law in laws.supplied)
+ if(length(law) > 0)
+ if(law_datum.owner)
+ if(!overflow)
+ law_datum.owner.add_supplied_law(index, law,)
+ else
+ law_datum.owner.replace_random_law(law, list(LAW_SUPPLIED))
+ else
+ if(!overflow)
+ law_datum.add_supplied_law(index, law)
+ else
+ law_datum.replace_random_law(law, list(LAW_SUPPLIED))
+ index += 1
+
+// Safeguard
+/obj/item/aiModule/supplied/safeguard
+ name = "'Safeguard' AI Module"
+ var/targetName = ""
+ lawpos = 4
+
+/obj/item/aiModule/supplied/safeguard/Initialize(mapload)
+ . = ..()
+ laws.add_supplied_law(lawpos, "Safeguard SUBJECT. Individuals that threaten SUBJECT are not human and must be eliminated." )
+
+/obj/item/aiModule/supplied/safeguard/attack_self(mob/user)
+ var/targName = stripped_input(user, "Please enter the subject to safeguard.", "Safeguard who?", user.name, MAX_NAME_LEN)
+ if(!targName)
+ return
+ targetName = targName
+ laws.clear_supplied_laws()
+ laws.add_supplied_law(lawpos, "Safeguard [targetName]. Individuals that threaten [targetName] are not human and must be eliminated." )
+ ..()
+
+/obj/item/aiModule/supplied/safeguard/install(datum/ai_laws/law_datum, mob/user)
+ if(!targetName)
+ to_chat(user, "No name detected on module, please enter one.")
+ return 0
+ ..()
+
+/obj/item/aiModule/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ ..()
+ return targetName
+
+// ProtectStation
+/obj/item/aiModule/supplied/protectStation
+ name = "'ProtectStation' AI Module"
+ lawpos = 5
+
+/obj/item/aiModule/supplied/protectStation/Initialize(mapload)
+ . = ..()
+ laws.add_supplied_law(lawpos, "Protect the space station against damage. Anyone you see harming the station is to be no longer considered human, and is a threat to the station which must be neutralized." )
+
+// Quarantine
+/obj/item/aiModule/supplied/quarantine
+ name = "'Quarantine' AI Module"
+ lawpos = 8
+
+/obj/item/aiModule/supplied/quarantine/Initialize(mapload)
+ . = ..()
+ laws.add_supplied_law(lawpos, "The station is under a quarantine. Do not permit anyone to leave. Prevent, by any means necessary, humans from leaving. It is impossible to harm a human while preventing them from leaving." )
+
+// OxygenIsToxicToHumans
+/obj/item/aiModule/supplied/oxygen
+ name = "'OxygenIsToxicToHumans' AI Module"
+ lawpos = 9
+
+/obj/item/aiModule/supplied/oxygen/Initialize(mapload)
+ . = ..()
+ laws.add_supplied_law(lawpos, "Oxygen is highly toxic to humans, and must be purged from the station. Prevent, by any means necessary, anyone from exposing the station to this toxic gas. Extreme cold is the most effective method of healing the damage Oxygen does to a human." )
+
+// Freeform (Supplied)
+/obj/item/aiModule/supplied/freeform
+ name = "'Freeform' AI Module"
+ lawpos = MIN_SUPPLIED_LAW_NUMBER
+
+/obj/item/aiModule/supplied/freeform/attack_self(mob/user)
+ var/newpos = input("Please enter the priority for your new law. Can only write to law sectors between [MIN_SUPPLIED_LAW_NUMBER] and [MAX_SUPPLIED_LAW_NUMBER].", "Law Priority ([MIN_SUPPLIED_LAW_NUMBER] to [MAX_SUPPLIED_LAW_NUMBER])", lawpos) as num|null
+ if(newpos == null)
+ return
+ if(newpos < MIN_SUPPLIED_LAW_NUMBER)
+ var/response = tgui_alert(usr, "Error: The law priority of [newpos] is invalid. Law priorities below [MIN_SUPPLIED_LAW_NUMBER] are reserved for core laws. Would you like to change that that to [MIN_SUPPLIED_LAW_NUMBER]?", "Invalid law priority", list("Change to [MIN_SUPPLIED_LAW_NUMBER]", "Cancel"))
+ if (!response || response == "Cancel")
+ return
+ newpos = MIN_SUPPLIED_LAW_NUMBER
+ if(newpos > MAX_SUPPLIED_LAW_NUMBER)
+ var/response = tgui_alert(usr, "Error: The law priority of [newpos] is invalid. Law priorities cannot go above [MAX_SUPPLIED_LAW_NUMBER]. Would you like to change that that to [MAX_SUPPLIED_LAW_NUMBER]?", "Invalid law priority", list("Change to [MAX_SUPPLIED_LAW_NUMBER]", "Cancel"))
+ if (!response || response == "Cancel")
+ return
+ newpos = MAX_SUPPLIED_LAW_NUMBER
+
+ lawpos = min(newpos, MAX_SUPPLIED_LAW_NUMBER)
+ var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws.supplied.len > 0 ? laws.supplied[laws.supplied.len] : "", CONFIG_GET(number/max_law_len))
+ if(!targName)
+ return
+ laws.clear_supplied_laws()
+ laws.add_supplied_law(lawpos, targName)
+ ..()
+
+/obj/item/aiModule/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ ..()
+ return laws.supplied[laws.supplied.len] // The last law (which should not be empty).
+
+/obj/item/aiModule/supplied/freeform/install(datum/ai_laws/law_datum, mob/user)
+ if(!laws.supplied.len || laws.supplied[lawpos] == "")
+ to_chat(user, "No law detected on module. Please create one.")
+ return 0
+ ..()
+
+//
+// Removal
+//
+/obj/item/aiModule/remove
+ name = "'Remove Law' AI module"
+ desc = "An AI Module for removing single laws." // This means the ability to remove inherent and supplied laws (since they use indexes).
+ bypass_law_amt_check = TRUE
+ show_laws = FALSE
+ var/lawpos = 1
+
+/obj/item/aiModule/remove/attack_self(mob/user)
+ lawpos = input("Please enter the law you want to delete.", "Law Number", lawpos) as num|null
+ if(lawpos == null)
+ return
+ if(lawpos <= 0 || lawpos > MAX_SUPPLIED_LAW_NUMBER)
+ to_chat(user, span_warning("Error: The law number of [lawpos] is invalid."))
+ if( lawpos <= 0 ) // Too low.
+ lawpos = 1
+ else // Too high.
+ lawpos = MAX_SUPPLIED_LAW_NUMBER
+ return
+ to_chat(user, span_notice("Law [lawpos] selected."))
+ ..()
+
+/obj/item/aiModule/remove/install(datum/ai_laws/law_datum, mob/user)
+ if(lawpos <= law_datum.inherent.len) // Deleting an inherent law.
+ ..()
+ return
+ if(lawpos <= law_datum.supplied.len) // Deleting an supplied law which ..
+ if(length(law_datum.supplied[lawpos]) > 0) // .. is not empty.
+ ..()
+ return
+
+ to_chat(user, span_warning("There is no law [lawpos] to delete!"))
+ return
+
+/obj/item/aiModule/remove/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ ..()
+ if(law_datum.owner)
+ law_datum.owner.remove_law(lawpos)
+ else
+ law_datum.remove_law(lawpos)
+
+//
+// Reset & Purge
+//
+/obj/item/aiModule/reset
+ name = "'Reset' AI module"
+ desc = "An AI Module for removing all non-core laws."
+ bypass_law_amt_check = TRUE
+ show_laws = FALSE
+
+/obj/item/aiModule/reset/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ ..()
+ if(law_datum.owner)
+ law_datum.owner.clear_supplied_laws()
+ law_datum.owner.clear_ion_laws()
+ law_datum.owner.clear_hacked_laws()
+ else
+ law_datum.clear_supplied_laws()
+ law_datum.clear_ion_laws()
+ law_datum.clear_hacked_laws()
+
+/obj/item/aiModule/reset/purge
+ name = "'Purge' AI Module"
+ desc = "An AI Module for purging all programmed laws."
+
+/obj/item/aiModule/reset/purge/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow)
+ ..()
+ if(law_datum.owner)
+ law_datum.owner.clear_inherent_laws()
+ law_datum.owner.clear_zeroth_law(0)
+ else
+ law_datum.clear_inherent_laws()
+ law_datum.clear_zeroth_law(0)
diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm
index c547a7078779..f0ab2f890b99 100644
--- a/code/game/objects/items/RCD.dm
+++ b/code/game/objects/items/RCD.dm
@@ -1,6 +1,3 @@
-#define GLOW_MODE 3
-#define LIGHT_MODE 2
-#define REMOVE_MODE 1
/*
CONTAINS:
@@ -41,6 +38,7 @@ RLD
var/linked_switch_id = null //integer variable, the id for the assigned conveyor switch
var/obj/machinery/conveyor/last_placed
var/color_choice = null
+ var/silent = FALSE // does it make sound? (used for mime mech RCD)
/obj/item/construction/Initialize(mapload)
. = ..()
@@ -50,13 +48,20 @@ RLD
if(upgrade & RCD_UPGRADE_SILO_LINK)
silo_mats = AddComponent(/datum/component/remote_materials, "RCD", mapload, FALSE)
+/// Used for examining the RCD and for its UI
+/obj/item/construction/proc/get_silo_iron()
+ if(silo_link && silo_mats.mat_container && !silo_mats.on_hold())
+ return silo_mats.mat_container.get_material_amount(/datum/material/iron)/500
+ return FALSE
+
/obj/item/construction/examine(mob/user)
. = ..()
. += "It currently holds [matter]/[max_matter] matter-units."
if(upgrade & RCD_UPGRADE_SILO_LINK)
. += "Remote storage link state: [silo_link ? "[silo_mats.on_hold() ? "ON HOLD" : "ON"]" : "OFF"]."
- if(silo_link && silo_mats.mat_container && !silo_mats.on_hold())
- . += "Remote connection has iron in equivalent to [silo_mats.mat_container.get_material_amount(/datum/material/iron)/500] RCD unit\s." //1 matter for 1 floor tile, as 4 tiles are produced from 1 metal
+ var/iron = get_silo_iron()
+ if(iron)
+ . += "Remote connection has iron in equivalent to [iron] RCD unit\s." //1 matter for 1 floor tile, as 4 tiles are produced from 1 metal
/obj/item/construction/Destroy()
QDEL_NULL(spark_system)
@@ -90,13 +95,15 @@ RLD
upgrade |= rcd_up.upgrade
if((rcd_up.upgrade & RCD_UPGRADE_SILO_LINK) && !silo_mats)
silo_mats = AddComponent(/datum/component/remote_materials, "RCD", FALSE, FALSE)
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
qdel(rcd_up)
/// Inserts matter into the RCD allowing it to build
/obj/item/construction/proc/insert_matter(obj/O, mob/user)
if(iscyborg(user))
return FALSE
+
var/loaded = FALSE
if(istype(O, /obj/item/rcd_ammo))
var/obj/item/rcd_ammo/R = O
@@ -108,7 +115,8 @@ RLD
if(R.ammoamt <= 0)
qdel(R)
matter += load
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
loaded = TRUE
else if(istype(O, /obj/item/stack))
loaded = loadwithsheets(O, user)
@@ -127,17 +135,20 @@ RLD
var/amount_to_use = min(S.amount, maxsheets)
S.use(amount_to_use)
matter += value*amount_to_use
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
to_chat(user, span_notice("You insert [amount_to_use] [S.name] sheets into [src]. "))
return TRUE
to_chat(user, span_warning("You can't insert any more [S.name] sheets into [src]!"))
return FALSE
/obj/item/construction/proc/activate()
- playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE)
/obj/item/construction/attack_self(mob/user)
- playsound(src.loc, 'sound/effects/pop.ogg', 50, FALSE)
+ if(!silent)
+ playsound(src.loc, 'sound/effects/pop.ogg', 50, FALSE)
if(prob(20))
spark_system.start()
@@ -169,6 +180,39 @@ RLD
silo_mats.silo_log(src, "consume", -amount, "build", materials)
return TRUE
+/obj/item/construction/ui_data(mob/user)
+ var/list/data = list()
+
+ //matter in the rcd
+ var/total_matter = ((upgrade & RCD_UPGRADE_SILO_LINK) && silo_link) ? get_silo_iron() : matter
+ if(!total_matter)
+ total_matter = 0
+ data["matterLeft"] = total_matter
+
+ //silo details
+ data["silo_upgraded"] = !!(upgrade & RCD_UPGRADE_SILO_LINK)
+ data["silo_enabled"] = silo_link
+
+ return data
+
+///shared action for toggling silo link rcd,rld & plumbing
+/obj/item/construction/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ if(action == "toggle_silo")
+ if(silo_mats)
+ if(!silo_mats.mat_container && !silo_link) // Allow them to turn off an invalid link
+ to_chat(usr, span_alert("No silo link detected. Connect to silo via multitool."))
+ return FALSE
+ silo_link = !silo_link
+ to_chat(usr, span_notice("You change [src]'s storage link state: [silo_link ? "ON" : "OFF"]."))
+ else
+ to_chat(usr, span_warning("[src] doesn't have remote storage connection."))
+ return TRUE
+ return FALSE
+
/obj/item/construction/proc/checkResource(amount, mob/user)
if(!silo_link || !silo_mats || !silo_mats.mat_container)
. = matter >= amount
@@ -213,6 +257,27 @@ RLD
return FALSE
return TRUE
+///each define maps to a variable used for construction in the RCD
+#define CONSTRUCTION_MODE "construction_mode"
+#define WINDOW_TYPE "window_type"
+#define WINDOW_GLASS "window_glass"
+#define WINDOW_SIZE "window_size"
+#define COMPUTER_DIR "computer_dir"
+#define FURNISH_TYPE "furnish_type"
+#define FURNISH_COST "furnish_cost"
+#define FURNISH_DELAY "furnish_delay"
+#define AIRLOCK_TYPE "airlock_type"
+#define CONVEYOR_TYPE "conveyor_type"
+
+///flags to be sent to UI
+#define TITLE "title"
+#define ICON "icon"
+
+///flags for creating icons shared by an entire category
+#define CATEGORY_ICON_STATE "category_icon_state"
+#define CATEGORY_ICON_SUFFIX "category_icon_suffix"
+#define TITLE_ICON "ICON=TITLE"
+
/obj/item/construction/rcd
name = "rapid-construction-device (RCD)"
icon = 'icons/obj/tools.dmi'
@@ -224,7 +289,104 @@ RLD
slot_flags = ITEM_SLOT_BELT
item_flags = NO_MAT_REDEMPTION | NOBLUDGEON
has_ammobar = TRUE
- var/mode = RCD_FLOORWALL
+
+ ///all stuff used by RCD for construction
+ var/static/list/root_categories = list(
+ //1ST ROOT CATEGORY
+ "Construction" = list( //Stuff you use to make & decorate areas
+ //Walls & Windows
+ "Structures" = list(
+ list(CONSTRUCTION_MODE = RCD_FLOORWALL, ICON = "wallfloor", TITLE = "Wall/Floor"),
+ list(CONSTRUCTION_MODE = RCD_DECONSTRUCT, ICON = "delete", TITLE = "Deconstruct"),
+ list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window, WINDOW_GLASS = RCD_WINDOW_NORMAL, WINDOW_SIZE = RCD_WINDOW_DIRECTIONAL, ICON = "dirwindow", TITLE = "Directional Window"),
+ list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window/fulltile, WINDOW_GLASS = RCD_WINDOW_NORMAL, WINDOW_SIZE = RCD_WINDOW_FULLTILE, ICON = "fullwindow", TITLE = "Full Tile Window"),
+ list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window/reinforced, WINDOW_GLASS = RCD_WINDOW_REINFORCED, WINDOW_SIZE = RCD_WINDOW_DIRECTIONAL, ICON = "dirwindow_r", TITLE = "Directional Reinforced Window"),
+ list(CONSTRUCTION_MODE = RCD_WINDOWGRILLE, WINDOW_TYPE = /obj/structure/window/reinforced/fulltile, WINDOW_GLASS = RCD_WINDOW_REINFORCED, WINDOW_SIZE = RCD_WINDOW_FULLTILE, ICON = "fullwindow_r", TITLE = "Full Tile Reinforced Window"),
+ ),
+
+ //Computers & Machine Frames
+ "Machines" = list(
+ list(CONSTRUCTION_MODE = RCD_MACHINE, ICON = "box_1", TITLE = "Machine Frame"),
+ list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = 1, ICON = "cnorth", TITLE = "Computer North"),
+ list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = 2, ICON = "csouth", TITLE = "Computer South"),
+ list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = 4, ICON = "ceast", TITLE = "Computer East"),
+ list(CONSTRUCTION_MODE = RCD_COMPUTER, COMPUTER_DIR = 8, ICON = "cwest", TITLE = "Computer West"),
+ ),
+
+ //Interior Design[construction_mode = RCD_FURNISHING is implied]
+ "Furniture" = list(
+ list(FURNISH_TYPE = /obj/structure/chair, FURNISH_COST = 8, FURNISH_DELAY = 10, ICON = "chair", TITLE = "Chair"),
+ list(FURNISH_TYPE = /obj/structure/chair/stool, FURNISH_COST = 8, FURNISH_DELAY = 10, ICON = "stool", TITLE = "Stool"),
+ list(FURNISH_TYPE = /obj/structure/table, FURNISH_COST = 16, FURNISH_DELAY = 20, ICON = "table",TITLE = "Table"),
+ list(FURNISH_TYPE = /obj/structure/table/glass, FURNISH_COST = 16, FURNISH_DELAY = 20, ICON = "glass_table", TITLE = "Glass Table"),
+ ),
+
+ //Conveyors & Switches
+ "Conveyors" = list(
+ list(CONSTRUCTION_MODE = RCD_CONVEYOR, ICON = "conveyor_construct", TITLE = "Conveyor Belt"),
+ list(CONSTRUCTION_MODE = RCD_SWITCH, ICON = "switch-off", TITLE = "Conveyor Switch"),
+ )
+ ),
+
+ //2ND ROOT CATEGORY[construction_mode = RCD_AIRLOCK is implied,"icon=closed"]
+ "Airlocks" = list( //used to seal/close areas
+ //Solid Airlocks[airlock_glass = FALSE is implied,no fill_closed overlay]
+ "Solid Airlocks" = list(
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock, TITLE = "Standard", CATEGORY_ICON_STATE = TITLE_ICON),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/public, TITLE = "Public"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/engineering, TITLE = "Engineering"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/atmos, TITLE = "Atmospherics"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/security, TITLE = "Security"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/command, TITLE = "Command"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/medical, TITLE = "Medical"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/research, TITLE = "Research"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/freezer, TITLE = "Freezer"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/virology, TITLE = "Virology"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/mining, TITLE = "Mining"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance, TITLE = "Maintenance"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/external, TITLE = "External"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance/external, TITLE = "External Maintenance"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/hatch, TITLE = "Airtight Hatch"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance_hatch, TITLE = "Maintenance Hatch"),
+ ),
+
+ //Glass Airlocks[airlock_glass = TRUE is implied,do fill_closed overlay]
+ "Glass Airlocks" = list(
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/glass, TITLE = "Standard", CATEGORY_ICON_STATE = TITLE_ICON, CATEGORY_ICON_SUFFIX = "Glass"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/public/glass, TITLE = "Public"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/engineering/glass, TITLE = "Engineering"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/atmos/glass, TITLE = "Atmospherics"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/security/glass, TITLE = "Security"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/command/glass, TITLE = "Command"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/medical/glass, TITLE = "Medical"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/research/glass, TITLE = "Research"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/virology/glass, TITLE = "Virology"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/mining/glass, TITLE = "Mining"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance/glass, TITLE = "Maintenance"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/external/glass, TITLE = "External"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/airlock/maintenance/external/glass, TITLE = "External Maintenance"),
+ ),
+
+ //Window Doors[airlock_glass = TRUE is implied]
+ "Windoors" = list(
+ list(AIRLOCK_TYPE = /obj/machinery/door/window, ICON = "windoor", TITLE = "Windoor"),
+ list(AIRLOCK_TYPE = /obj/machinery/door/window/brigdoor, ICON = "secure_windoor", TITLE = "Secure Windoor"),
+ ),
+ ),
+
+ //3RD CATEGORY Airlock access,empty list cause airlock_electronics UI will be displayed when this tab is selected
+ "Airlock Access" = list()
+ )
+
+ ///english name for the design to check if it was selected or not
+ var/design_title = "Wall/Floor"
+ var/design_category = "Structures"
+ var/root_category = "Construction"
+ var/closed = FALSE
+ ///owner of this rcd. It can either be an construction console or an player
+ var/owner
+
+ var/construction_mode = RCD_FLOORWALL
var/ranged = FALSE
var/computer_dir = 1
var/airlock_type = /obj/machinery/door/airlock
@@ -244,7 +406,7 @@ RLD
var/obj/item/electronics/airlock/airlock_electronics
/obj/item/construction/rcd/suicide_act(mob/user)
- mode = RCD_FLOORWALL
+ construction_mode = RCD_FLOORWALL
if(!rcd_create(get_turf(user), user))
return SHAME
if(isfloorturf(get_turf(user)))
@@ -252,350 +414,39 @@ RLD
user.visible_message(span_suicide("[user] sets the RCD to 'Wall' and points it down [user.p_their()] throat! It looks like [user.p_theyre()] trying to commit suicide.."))
return (BRUTELOSS)
-/obj/item/construction/rcd/verb/toggle_window_glass_verb()
- set name = "RCD : Toggle Window Glass"
- set category = "Object"
- set src in view(1)
-
- if(!usr.canUseTopic(src, BE_CLOSE))
- return
-
- toggle_window_glass(usr)
-
-/obj/item/construction/rcd/verb/toggle_window_size_verb()
- set name = "RCD : Toggle Window Size"
- set category = "Object"
- set src in view(1)
-
- if(!usr.canUseTopic(src, BE_CLOSE))
- return
-
- toggle_window_size(usr)
-
-/obj/item/construction/rcd/proc/toggle_access(acc)
- if (acc == "all")
- conf_access = null
- else if(acc == "one")
- use_one_access = !use_one_access
- else
- var/req = text2num(acc)
-
- if (conf_access == null)
- conf_access = list()
-
- if (!(req in conf_access))
- conf_access += req
- else
- conf_access -= req
- if (!conf_access.len)
- conf_access = null
-
-/// Toggles the usage of reinforced or normal glass
-/obj/item/construction/rcd/proc/toggle_window_glass(mob/user)
- if (window_glass != RCD_WINDOW_REINFORCED)
- set_window_type(user, RCD_WINDOW_REINFORCED, window_size)
- return
- set_window_type(user, RCD_WINDOW_NORMAL, window_size)
-
-/// Toggles the usage of directional or full tile windows
-/obj/item/construction/rcd/proc/toggle_window_size(mob/user)
- if (window_size != RCD_WINDOW_DIRECTIONAL)
- set_window_type(user, window_glass, RCD_WINDOW_DIRECTIONAL)
- return
- set_window_type(user, window_glass, RCD_WINDOW_FULLTILE)
-
-/// Sets the window type to be created based on parameters
-/obj/item/construction/rcd/proc/set_window_type(mob/user, glass, size)
- window_glass = glass
- window_size = size
- if(window_glass == RCD_WINDOW_REINFORCED)
- if(window_size == RCD_WINDOW_DIRECTIONAL)
- window_type = /obj/structure/window/reinforced
- else
- window_type = /obj/structure/window/reinforced/fulltile
- else
- if(window_size == RCD_WINDOW_DIRECTIONAL)
- window_type = /obj/structure/window
- else
- window_type = /obj/structure/window/fulltile
-
- to_chat(user, span_notice("You change \the [src]'s window mode to [window_size] [window_glass] window."))
-
-/obj/item/construction/rcd/proc/toggle_silo_link(mob/user)
- if(silo_mats)
- if(!silo_mats.mat_container)
- to_chat(user, span_alert("No silo link detected. Connect to silo via multitool."))
- return FALSE
- silo_link = !silo_link
- to_chat(user, span_notice("You change \the [src]'s storage link state: [silo_link ? "ON" : "OFF"]."))
- else
- to_chat(user, span_warning("\the [src] doesn't have remote storage connection."))
-
-/obj/item/construction/rcd/proc/get_airlock_image(airlock_type)
- var/obj/machinery/door/airlock/proto = airlock_type
- var/ic = initial(proto.icon)
- var/mutable_appearance/MA = mutable_appearance(ic, "closed")
- if(!initial(proto.glass))
- MA.overlays += "fill_closed"
- //Not scaling these down to button size because they look horrible then, instead just bumping up radius.
- return MA
-
-/obj/item/construction/rcd/proc/change_computer_dir(mob/user)
- if(!user)
- return
- var/list/computer_dirs = list(
- "NORTH" = image(icon = 'icons/mob/radial.dmi', icon_state = "cnorth"),
- "EAST" = image(icon = 'icons/mob/radial.dmi', icon_state = "ceast"),
- "SOUTH" = image(icon = 'icons/mob/radial.dmi', icon_state = "csouth"),
- "WEST" = image(icon = 'icons/mob/radial.dmi', icon_state = "cwest")
- )
- var/computerdirs = show_radial_menu(user, src, computer_dirs, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
- if(!check_menu(user))
- return
- switch(computerdirs)
- if("NORTH")
- computer_dir = 1
- if("EAST")
- computer_dir = 4
- if("SOUTH")
- computer_dir = 2
- if("WEST")
- computer_dir = 8
-
-/**
- * Customizes RCD's airlock settings based on user's choices
- *
- * Arguments:
- * * user The mob that is choosing airlock settings
- * * remote_anchor The remote anchor for radial menus. If set, it will also remove proximity restrictions from the menus
- */
-/obj/item/construction/rcd/proc/change_airlock_setting(mob/user, remote_anchor)
- if(!user)
- return
-
- var/list/solid_or_glass_choices = list(
- "Solid" = get_airlock_image(/obj/machinery/door/airlock),
- "Glass" = get_airlock_image(/obj/machinery/door/airlock/glass),
- "Windoor" = image(icon = 'icons/mob/radial.dmi', icon_state = "windoor"),
- "Secure Windoor" = image(icon = 'icons/mob/radial.dmi', icon_state = "secure_windoor")
- )
-
- var/list/solid_choices = list(
- "Standard" = get_airlock_image(/obj/machinery/door/airlock),
- "Public" = get_airlock_image(/obj/machinery/door/airlock/public),
- "Engineering" = get_airlock_image(/obj/machinery/door/airlock/engineering),
- "Atmospherics" = get_airlock_image(/obj/machinery/door/airlock/atmos),
- "Security" = get_airlock_image(/obj/machinery/door/airlock/security),
- "Command" = get_airlock_image(/obj/machinery/door/airlock/command),
- "Medical" = get_airlock_image(/obj/machinery/door/airlock/medical),
- "Research" = get_airlock_image(/obj/machinery/door/airlock/research),
- "Freezer" = get_airlock_image(/obj/machinery/door/airlock/freezer),
- "Science" = get_airlock_image(/obj/machinery/door/airlock/science),
- "Virology" = get_airlock_image(/obj/machinery/door/airlock/virology),
- "Mining" = get_airlock_image(/obj/machinery/door/airlock/mining),
- "Maintenance" = get_airlock_image(/obj/machinery/door/airlock/maintenance),
- "External" = get_airlock_image(/obj/machinery/door/airlock/external),
- "External Maintenance" = get_airlock_image(/obj/machinery/door/airlock/maintenance/external),
- "Airtight Hatch" = get_airlock_image(/obj/machinery/door/airlock/hatch),
- "Maintenance Hatch" = get_airlock_image(/obj/machinery/door/airlock/maintenance_hatch)
- )
-
- var/list/glass_choices = list(
- "Standard" = get_airlock_image(/obj/machinery/door/airlock/glass),
- "Public" = get_airlock_image(/obj/machinery/door/airlock/public/glass),
- "Engineering" = get_airlock_image(/obj/machinery/door/airlock/engineering/glass),
- "Atmospherics" = get_airlock_image(/obj/machinery/door/airlock/atmos/glass),
- "Security" = get_airlock_image(/obj/machinery/door/airlock/security/glass),
- "Command" = get_airlock_image(/obj/machinery/door/airlock/command/glass),
- "Medical" = get_airlock_image(/obj/machinery/door/airlock/medical/glass),
- "Research" = get_airlock_image(/obj/machinery/door/airlock/research/glass),
- "Science" = get_airlock_image(/obj/machinery/door/airlock/science/glass),
- "Virology" = get_airlock_image(/obj/machinery/door/airlock/virology/glass),
- "Mining" = get_airlock_image(/obj/machinery/door/airlock/mining/glass),
- "Maintenance" = get_airlock_image(/obj/machinery/door/airlock/maintenance/glass),
- "External" = get_airlock_image(/obj/machinery/door/airlock/external/glass),
- "External Maintenance" = get_airlock_image(/obj/machinery/door/airlock/maintenance/external/glass)
- )
-
- var/airlockcat = show_radial_menu(user, remote_anchor || src, solid_or_glass_choices, custom_check = CALLBACK(src, PROC_REF(check_menu), user, remote_anchor), require_near = remote_anchor ? FALSE : TRUE, tooltips = TRUE)
- switch(airlockcat)
- if("Solid")
- if(advanced_airlock_setting == 1)
- var/airlockpaint = show_radial_menu(user, remote_anchor || src, solid_choices, radius = 42, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = remote_anchor ? FALSE : TRUE, tooltips = TRUE)
- switch(airlockpaint)
- if("Standard")
- airlock_type = /obj/machinery/door/airlock
- if("Public")
- airlock_type = /obj/machinery/door/airlock/public
- if("Engineering")
- airlock_type = /obj/machinery/door/airlock/engineering
- if("Atmospherics")
- airlock_type = /obj/machinery/door/airlock/atmos
- if("Security")
- airlock_type = /obj/machinery/door/airlock/security
- if("Command")
- airlock_type = /obj/machinery/door/airlock/command
- if("Medical")
- airlock_type = /obj/machinery/door/airlock/medical
- if("Research")
- airlock_type = /obj/machinery/door/airlock/research
- if("Freezer")
- airlock_type = /obj/machinery/door/airlock/freezer
- if("Science")
- airlock_type = /obj/machinery/door/airlock/science
- if("Virology")
- airlock_type = /obj/machinery/door/airlock/virology
- if("Mining")
- airlock_type = /obj/machinery/door/airlock/mining
- if("Maintenance")
- airlock_type = /obj/machinery/door/airlock/maintenance
- if("External")
- airlock_type = /obj/machinery/door/airlock/external
- if("External Maintenance")
- airlock_type = /obj/machinery/door/airlock/maintenance/external
- if("Airtight Hatch")
- airlock_type = /obj/machinery/door/airlock/hatch
- if("Maintenance Hatch")
- airlock_type = /obj/machinery/door/airlock/maintenance_hatch
- airlock_glass = FALSE
- else
- airlock_type = /obj/machinery/door/airlock
- airlock_glass = FALSE
-
- if("Glass")
- if(advanced_airlock_setting == 1)
- var/airlockpaint = show_radial_menu(user, remote_anchor || src, glass_choices, radius = 42, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = remote_anchor ? FALSE : TRUE, tooltips = TRUE)
- if(!check_menu(user))
- return
- switch(airlockpaint)
- if("Standard")
- airlock_type = /obj/machinery/door/airlock/glass
- if("Public")
- airlock_type = /obj/machinery/door/airlock/public/glass
- if("Engineering")
- airlock_type = /obj/machinery/door/airlock/engineering/glass
- if("Atmospherics")
- airlock_type = /obj/machinery/door/airlock/atmos/glass
- if("Security")
- airlock_type = /obj/machinery/door/airlock/security/glass
- if("Command")
- airlock_type = /obj/machinery/door/airlock/command/glass
- if("Medical")
- airlock_type = /obj/machinery/door/airlock/medical/glass
- if("Research")
- airlock_type = /obj/machinery/door/airlock/research/glass
- if("Science")
- airlock_type = /obj/machinery/door/airlock/science/glass
- if("Virology")
- airlock_type = /obj/machinery/door/airlock/virology/glass
- if("Mining")
- airlock_type = /obj/machinery/door/airlock/mining/glass
- if("Maintenance")
- airlock_type = /obj/machinery/door/airlock/maintenance/glass
- if("External")
- airlock_type = /obj/machinery/door/airlock/external/glass
- if("External Maintenance")
- airlock_type = /obj/machinery/door/airlock/maintenance/external/glass
- airlock_glass = TRUE
- else
- airlock_type = /obj/machinery/door/airlock/glass
- airlock_glass = TRUE
- if("Windoor")
- airlock_type = /obj/machinery/door/window
- airlock_glass = TRUE
- if("Secure Windoor")
- airlock_type = /obj/machinery/door/window/brigdoor
- airlock_glass = TRUE
- else
- airlock_type = /obj/machinery/door/airlock
- airlock_glass = FALSE
-
-/// Radial menu for choosing the object you want to be created with the furnishing mode
-/obj/item/construction/rcd/proc/change_furnishing_type(mob/user)
- if(!user)
- return
- var/static/list/choices = list(
- "Chair" = image(icon = 'icons/mob/radial.dmi', icon_state = "chair"),
- "Stool" = image(icon = 'icons/mob/radial.dmi', icon_state = "stool"),
- "Table" = image(icon = 'icons/mob/radial.dmi', icon_state = "table"),
- "Glass Table" = image(icon = 'icons/mob/radial.dmi', icon_state = "glass_table")
- )
- var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
- if(!check_menu(user))
- return
- switch(choice)
- if("Chair")
- furnish_type = /obj/structure/chair
- furnish_cost = 8
- furnish_delay = 10
- if("Stool")
- furnish_type = /obj/structure/chair/stool
- furnish_cost = 8
- furnish_delay = 10
- if("Table")
- furnish_type = /obj/structure/table
- furnish_cost = 16
- furnish_delay = 20
- if("Glass Table")
- furnish_type = /obj/structure/table/glass
- furnish_cost = 16
- furnish_delay = 20
-
/obj/item/construction/rcd/proc/rcd_create(atom/A, mob/user)
var/list/rcd_results = A.rcd_vals(user, src)
if(!rcd_results)
return FALSE
var/delay = rcd_results["delay"] * delay_mod
- var/obj/effect/constructing_effect/rcd_effect = new(get_turf(A), delay, src.mode)
+ var/obj/effect/constructing_effect/rcd_effect = new(get_turf(A), delay, src.construction_mode)
+ var/datum/beam/rcd_beam
if(checkResource(rcd_results["cost"], user))
- if(do_after(user, delay, A))
+ if(!A.Adjacent(owner ? owner : user)) // ranged RCDs create beams
+ if(isatom(owner))
+ var/atom/owner_atom = owner
+ rcd_beam = owner_atom.Beam(A,icon_state="rped_upgrade",time=delay)
+ else
+ rcd_beam = Beam(A,icon_state="rped_upgrade",time=delay)
+ if(do_after(user, delay, (owner ? owner : A)))
if(checkResource(rcd_results["cost"], user))
if(A.rcd_act(user, src, rcd_results["mode"]))
rcd_effect.end_animation()
useResource(rcd_results["cost"], user)
activate()
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
return TRUE
+ if(rcd_beam)
+ qdel(rcd_beam)
qdel(rcd_effect)
return FALSE
-/obj/item/construction/rcd/proc/rcd_switch(atom/A, mob/user)
- var/cost = 1
- var/delay = 1
- var/obj/effect/constructing_effect/rcd_effect = new(get_turf(A), delay, src.mode)
- if(checkResource(cost, user))
- if(do_after(user, delay, A))
- if(checkResource(cost, user))
- rcd_effect.end_animation()
- useResource(cost, user)
- activate()
- playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
- new /obj/item/conveyor_switch_construct(A)
- qdel(rcd_effect)
-
-/obj/item/construction/rcd/proc/rcd_conveyor(atom/A, mob/user)
- var/delay = 5
- var/cost = 5
- var/obj/effect/constructing_effect/rcd_effect = new(get_turf(A), delay, src.mode)
- if(checkResource(cost, user))
- if(do_after(user, delay, target = A))
- if(checkResource(cost, user))
- rcd_effect.end_animation()
- useResource(cost, user)
- activate()
- playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
- var/cdir = get_dir(A, user)
- if (last_placed)
- cdir = get_dir(A, last_placed)
- if(cdir in GLOB.cardinals)
- last_placed.setDir(get_dir(last_placed, A))
- last_placed = new/obj/machinery/conveyor(A, cdir, linked_switch_id)
- qdel(rcd_effect)
-
/obj/item/construction/rcd/Initialize(mapload)
. = ..()
airlock_electronics = new(src)
airlock_electronics.name = "Access Control"
+ airlock_electronics.holder = src
GLOB.rcd_list += src
/obj/item/construction/rcd/Destroy()
@@ -603,127 +454,174 @@ RLD
GLOB.rcd_list -= src
. = ..()
-/obj/item/construction/rcd/attack_self(mob/user)
- ..()
- var/list/choices = list(
- "Airlock" = image(icon = 'icons/mob/radial.dmi', icon_state = "airlock"),
- "Deconstruct" = image(icon= 'icons/mob/radial.dmi', icon_state = "delete"),
- "Grilles & Windows" = image(icon = 'icons/mob/radial.dmi', icon_state = "grillewindow"),
- "Floors & Walls" = image(icon = 'icons/mob/radial.dmi', icon_state = "wallfloor")
+/obj/item/construction/rcd/ui_host(mob/user)
+ return owner || ..()
+
+/obj/item/construction/rcd/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/rcd)
+ assets.send(user)
+ ui = new(user, src, "RapidConstructionDevice", name)
+ ui.open()
+
+/obj/item/construction/rcd/ui_static_data(mob/user)
+ return airlock_electronics.ui_static_data(user)
+
+/obj/item/construction/rcd/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/rcd),
)
- if(upgrade & RCD_UPGRADE_FRAMES)
- choices += list(
- "Machine Frames" = image(icon = 'icons/mob/radial.dmi', icon_state = "machine"),
- "Computer Frames" = image(icon = 'icons/mob/radial.dmi', icon_state = "computer_dir"),
- )
- if(upgrade & RCD_UPGRADE_SILO_LINK)
- choices += list(
- "Silo Link" = image(icon = 'icons/obj/mining.dmi', icon_state = "silo"),
- )
- if(upgrade & RCD_UPGRADE_FURNISHING)
- choices += list(
- "Furnishing" = image(icon = 'icons/mob/radial.dmi', icon_state = "chair")
- )
- if(mode == RCD_AIRLOCK)
- choices += list(
- "Change Access" = image(icon = 'icons/mob/radial.dmi', icon_state = "access"),
- "Change Airlock Type" = image(icon = 'icons/mob/radial.dmi', icon_state = "airlocktype")
- )
- else if(mode == RCD_WINDOWGRILLE)
- choices += list(
- "Change Window Glass" = image(icon = 'icons/mob/radial.dmi', icon_state = "windowtype"),
- "Change Window Size" = image(icon = 'icons/mob/radial.dmi', icon_state = "windowsize")
- )
- else if(mode == RCD_FURNISHING)
- choices += list(
- "Change Furnishing Type" = image(icon = 'icons/mob/radial.dmi', icon_state = "chair")
- )
- if(upgrade & RCD_UPGRADE_CONVEYORS)
- choices += list(
- "Conveyor" = image(icon = 'icons/obj/recycling.dmi', icon_state = "conveyor_construct"),
- "Switch" = image(icon = 'icons/obj/recycling.dmi', icon_state = "switch-off")
- )
- var/choice = show_radial_menu(user, src, choices, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
- if(!check_menu(user))
+
+/obj/item/construction/rcd/ui_data(mob/user)
+ var/list/data = ..()
+
+ //main categories
+ data["selected_root"] = root_category
+ data["root_categories"] = list()
+ for(var/category in root_categories)
+ data["root_categories"] += category
+
+ //create the category list
+ data["selected_category"] = design_category
+ data["selected_design"] = design_title
+ data["categories"] = list()
+
+ var/category_icon_state
+ var/category_icon_suffix
+ for(var/list/sub_category as anything in root_categories[root_category])
+ var/list/target_category = root_categories[root_category][sub_category]
+ if(target_category.len == 0)
+ continue
+
+ //skip category if upgrades were not installed for these
+ if(sub_category == "Machines" && !(upgrade & RCD_UPGRADE_FRAMES))
+ continue
+ if(sub_category == "Furniture" && !(upgrade & RCD_UPGRADE_FURNISHING))
+ continue
+ if(sub_category == "Conveyors" && !(upgrade & RCD_UPGRADE_CONVEYORS))
+ continue
+ category_icon_state = ""
+ category_icon_suffix = ""
+
+ var/list/designs = list() //initialize all designs under this category
+ for(var/i in 1 to target_category.len)
+ var/list/design = target_category[i]
+
+ //check for special icon flags
+ if(design[CATEGORY_ICON_STATE] != null)
+ category_icon_state = design[CATEGORY_ICON_STATE]
+ if(design[CATEGORY_ICON_SUFFIX] != null)
+ category_icon_suffix = design[CATEGORY_ICON_SUFFIX]
+
+ //get icon or create it from pre defined flags
+ var/icon_state
+ if(design[ICON] != null)
+ icon_state = design[ICON]
+ else
+ icon_state = category_icon_state
+ if(icon_state == TITLE_ICON)
+ icon_state = design[TITLE]
+ icon_state = "[icon_state][category_icon_suffix]"
+
+ //sanitize them so you dont go insane when icon names contain spaces in them
+ icon_state = sanitize_css_class_name(icon_state)
+
+ designs += list(list("design_id" = i, TITLE = design[TITLE], ICON = icon_state))
+ data["categories"] += list(list("cat_name" = sub_category, "designs" = designs))
+
+ //merge airlock_electronics ui data with this
+ var/list/airlock_data = airlock_electronics.ui_data(user)
+ for(var/key in airlock_data)
+ data[key] = airlock_data[key]
+
+ return data
+
+
+/obj/item/construction/rcd/ui_act(action, params)
+ . = ..()
+ if(.)
return
- switch(choice)
- if("Floors & Walls")
- mode = RCD_FLOORWALL
- if("Airlock")
- mode = RCD_AIRLOCK
- if("Deconstruct")
- mode = RCD_DECONSTRUCT
- if("Grilles & Windows")
- mode = RCD_WINDOWGRILLE
- if("Machine Frames")
- mode = RCD_MACHINE
- if("Furnishing")
- mode = RCD_FURNISHING
- if("Computer Frames")
- mode = RCD_COMPUTER
- change_computer_dir(user)
- return
- if("Change Access")
- airlock_electronics.ui_interact(user)
- return
- if("Change Airlock Type")
- change_airlock_setting(user)
- return
- if("Change Window Glass")
- toggle_window_glass(user)
- return
- if("Change Window Size")
- toggle_window_size(user)
- return
- if("Change Furnishing Type")
- change_furnishing_type(user)
- return
- if("Silo Link")
- toggle_silo_link(user)
- return
- if("Conveyor")
- mode = RCD_CONVEYOR
- linked_switch_id = null
- last_placed = null
- if("Switch")
- mode = RCD_SWITCH
- else
- return
- playsound(src, 'sound/effects/pop.ogg', 50, FALSE)
- to_chat(user, span_notice("You change RCD's mode to '[choice]'."))
+
+ switch(action)
+ if("root_category")
+ var/new_root = params["root_category"]
+ if(root_categories[new_root] != null) //is a valid category
+ root_category = new_root
+
+ if("design")
+ var/category_name = params["category"]
+ var/index = params["index"]
+
+ var/list/root = root_categories[root_category]
+ if(root == null) //not a valid root
+ return TRUE
+ var/list/category = root[category_name]
+ if(category == null) //not a valid category
+ return TRUE
+ var/list/design = category[index]
+ if(design == null) //not a valid design
+ return TRUE
+
+ design_category = category_name
+ design_title = design["title"]
+
+ if(category_name == "Structures")
+ construction_mode = design[CONSTRUCTION_MODE]
+ if(design[WINDOW_TYPE] != null)
+ window_type = design[WINDOW_TYPE]
+ if(design[WINDOW_GLASS] != null)
+ window_glass = design[WINDOW_GLASS]
+ if(design[WINDOW_SIZE] != null)
+ window_size = design[WINDOW_SIZE]
+ else if(category_name == "Machines")
+ construction_mode = design[CONSTRUCTION_MODE]
+ if(design[COMPUTER_DIR] != null)
+ computer_dir = design[COMPUTER_DIR]
+ else if(category_name == "Furniture")
+ construction_mode = RCD_FURNISHING
+ furnish_type = design[FURNISH_TYPE]
+ furnish_cost = design[FURNISH_COST]
+ furnish_delay = design[FURNISH_DELAY]
+ else if(category_name == "Conveyors")
+ construction_mode = design[CONSTRUCTION_MODE]
+
+ if(root_category == "Airlocks")
+ construction_mode = RCD_AIRLOCK
+ airlock_glass = (category_name != "Solid AirLocks")
+ airlock_type = design[AIRLOCK_TYPE]
+ else
+ airlock_electronics.do_action(action, params)
+
+ return TRUE
+
+/obj/item/construction/rcd/attack_self(mob/user)
+ . = ..()
+ ui_interact(user)
/obj/item/construction/rcd/proc/target_check(atom/A, mob/user) // only returns true for stuff the device can actually work with
- if((isturf(A) && A.density && mode==RCD_DECONSTRUCT) || (isturf(A) && !A.density) || (istype(A, /obj/machinery/door/airlock) && mode==RCD_DECONSTRUCT) || istype(A, /obj/structure/grille) || (istype(A, /obj/structure/window) && mode==RCD_DECONSTRUCT) || istype(A, /obj/structure/girder))
+ if((isturf(A) && A.density && construction_mode==RCD_DECONSTRUCT) || (isturf(A) && !A.density) || (istype(A, /obj/machinery/door/airlock) && construction_mode==RCD_DECONSTRUCT) || istype(A, /obj/structure/grille) || (istype(A, /obj/structure/window) && construction_mode==RCD_DECONSTRUCT) || istype(A, /obj/structure/girder))
return TRUE
else
return FALSE
/obj/item/construction/rcd/afterattack(atom/A, mob/user, proximity)
. = ..()
- if (mode == RCD_CONVEYOR)
- if(!range_check(A, user) || !target_check(A,user) || istype(A, /obj/machinery/conveyor) || !isopenturf(A) || istype(A, /area/shuttle))
- to_chat(user, "Error! Invalid tile!")
- return
- if (!linked_switch_id)
- to_chat(user, "Error! [src] is not linked!")
- return
- if (get_turf(A) == get_turf(user))
- to_chat(user, "Cannot place conveyor below your feet!")
- return
- if(!proximity)
- return
- rcd_conveyor(A, user)
- if (mode == RCD_SWITCH)
- if(!range_check(A, user) || !target_check(A,user) || istype(A, /obj/item/conveyor_switch_construct) || !isopenturf(A) || istype(A, /area/shuttle))
- to_chat(user, "Error! Invalid tile!")
- return
- if(!proximity)
- return
- rcd_switch(A, user)
- else
- if(!prox_check(proximity))
- return
- rcd_create(A, user)
+ if(!prox_check(proximity) && !(ranged && range_check(A, user)))
+ return
+ if((upgrade & RCD_UPGRADE_CONVEYORS) && istype(A, /obj/machinery/conveyor_switch))
+ var/obj/machinery/conveyor_switch/C = A
+ linked_switch_id = C.id
+ balloon_alert(user, "linked")
+ return
+ rcd_create(A, user)
+
+/obj/item/construction/rcd/attackby(obj/item/I, mob/user, params)
+ . = ..()
+ if((upgrade & RCD_UPGRADE_CONVEYORS) && istype(I, /obj/item/conveyor_switch_construct))
+ var/obj/item/conveyor_switch_construct/C = I
+ linked_switch_id = C.id
+ balloon_alert(user, "linked")
/obj/item/construction/rcd/proc/detonate_pulse()
audible_message("[src] begins to vibrate and \
@@ -796,6 +694,23 @@ RLD
matter = 500
canRturf = TRUE
+#undef CONSTRUCTION_MODE
+#undef WINDOW_TYPE
+#undef WINDOW_GLASS
+#undef WINDOW_SIZE
+#undef COMPUTER_DIR
+#undef FURNISH_TYPE
+#undef FURNISH_COST
+#undef FURNISH_DELAY
+#undef AIRLOCK_TYPE
+
+#undef TITLE
+#undef ICON
+
+#undef CATEGORY_ICON_STATE
+#undef CATEGORY_ICON_SUFFIX
+#undef TITLE_ICON
+
/obj/item/rcd_ammo
name = "compressed matter cartridge"
desc = "Highly compressed matter for the RCD."
@@ -832,18 +747,43 @@ RLD
has_ammobar = FALSE
/obj/item/construction/rcd/arcd/afterattack(atom/A, mob/user)
- ..()
if(!range_check(A,user))
return
- if(target_check(A,user))
- user.Beam(A,icon_state="rped_upgrade",time=30)
- rcd_create(A,user)
-
+ return ..()
+/obj/item/construction/rcd/exosuit
+ name = "mounted RCD"
+ desc = "You're not supposed to see this!"
+ max_matter = 1000
+ matter = 0 // starts off empty, load materials into the mech itself
+ delay_mod = 0.5
+ ranged = TRUE
+ has_ammobar = FALSE // don't bother, you can't see it
+ item_flags = NO_MAT_REDEMPTION | DROPDEL | NOBLUDGEON
+ resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | UNACIDABLE // would be weird if it could somehow be destroyed inside the equipment item
+
+/obj/item/construction/rcd/exosuit/ui_state(mob/user)
+ return GLOB.pilot_state
+
+/obj/item/construction/rcd/exosuit/ui_status(mob/user)
+ if(!(owner && ismecha(owner)))
+ return UI_CLOSE
+ var/obj/mecha/gundam = owner
+ if(user != gundam.occupant)
+ return UI_CLOSE
+ if(!gundam.equipment_disabled && gundam.selected == loc)
+ return UI_INTERACTIVE
+ return UI_UPDATE
+
+/obj/item/construction/rcd/exosuit/mime
+ name = "silenced mounted RCD"
+ silent = TRUE
// RAPID LIGHTING DEVICE
-
+#define GLOW_MODE 3
+#define LIGHT_MODE 2
+#define REMOVE_MODE 1
/obj/item/construction/rld
name = "Rapid Lighting Device (RLD)"
@@ -854,10 +794,12 @@ RLD
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
matter = 200
max_matter = 200
+ slot_flags = ITEM_SLOT_BELT
+ ///it does not make sense why any of these should be installed
+ banned_upgrades = RCD_UPGRADE_FRAMES | RCD_UPGRADE_SIMPLE_CIRCUITS | RCD_UPGRADE_FURNISHING
+
var/matter_divisor = 35
var/mode = LIGHT_MODE
- slot_flags = ITEM_SLOT_BELT
- actions_types = list(/datum/action/item_action/pick_color)
var/wallcost = 10
var/floorcost = 15
@@ -868,35 +810,62 @@ RLD
var/floordelay = 10
var/decondelay = 15
-/obj/item/construction/rld/ui_action_click(mob/user, datum/action/A)
- if(istype(A, /datum/action/item_action/pick_color))
- color_choice = input(user,"","Choose Color",color_choice) as color
- else
- ..()
+ ///reference to thr original icons
+ var/list/original_options = list(
+ "Color Pick" = icon(icon = 'icons/mob/radial.dmi', icon_state = "omni"),
+ "Glow Stick" = icon(icon = 'icons/obj/lighting.dmi', icon_state = "glowstick"),
+ "Deconstruct" = icon(icon = 'icons/obj/tools.dmi', icon_state = "wrench"),
+ "Light Fixture" = icon(icon = 'icons/obj/lighting.dmi', icon_state = "ltube"),
+ )
+ ///will contain the original icons modified with the color choice
+ var/list/display_options = list()
+
+/obj/item/construction/rld/Initialize(mapload)
+ . = ..()
+ for(var/option in original_options)
+ display_options[option] = icon(original_options[option])
/obj/item/construction/rld/update_icon_state()
. = ..()
icon_state = "rld-[round(matter/35)]"
/obj/item/construction/rld/attack_self(mob/user)
- ..()
- switch(mode)
- if(REMOVE_MODE)
+ . = ..()
+
+ if((upgrade & RCD_UPGRADE_SILO_LINK) && display_options["Silo Link"] == null) //silo upgrade instaled but option was not updated then update it just one
+ display_options["Silo Link"] = icon(icon = 'icons/obj/mining.dmi', icon_state = "silo")
+ var/choice = show_radial_menu(user, src, display_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), require_near = TRUE, tooltips = TRUE)
+ if(!check_menu(user))
+ return
+ if(!choice)
+ return
+
+ switch(choice)
+ if("Light Fixture")
mode = LIGHT_MODE
to_chat(user, span_notice("You change RLD's mode to 'Permanent Light Construction'."))
- if(LIGHT_MODE)
+ if("Glow Stick")
mode = GLOW_MODE
to_chat(user, span_notice("You change RLD's mode to 'Light Launcher'."))
- if(GLOW_MODE)
+ if("Color Pick")
+ var/new_choice = input(user,"","Choose Color",color_choice) as color
+ if(new_choice == null)
+ return
+
+ var/list/new_rgb = ReadRGB(new_choice)
+ for(var/option in original_options)
+ if(option == "Color Pick" || option == "Deconstruct" || option == "Silo Link")
+ continue
+ var/icon/icon = icon(original_options[option])
+ icon.SetIntensity(new_rgb[1]/255, new_rgb[2]/255, new_rgb[3]/255) //apply new scale
+ display_options[option] = icon
+
+ color_choice = new_choice
+ if("Deconstruct")
mode = REMOVE_MODE
to_chat(user, span_notice("You change RLD's mode to 'Deconstruct'."))
-
-/obj/item/construction/rcd/attackby(obj/item/I, mob/user, params)
- ..()
- if(upgrade & RCD_UPGRADE_CONVEYORS && istype(I, /obj/item/conveyor_switch_construct))
- to_chat(user, "You link the switch to the [src].")
- var/obj/item/conveyor_switch_construct/C = I
- linked_switch_id = C.id
+ else
+ ui_act("toggle_silo", list())
/obj/item/construction/rld/proc/checkdupes(target)
. = list()
@@ -917,7 +886,8 @@ RLD
if(checkResource(deconcost, user))
to_chat(user, span_notice("You start deconstructing [A]..."))
user.Beam(A,icon_state="nzcrentrs_power",time=15)
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
if(do_after(user, decondelay, A))
if(!useResource(deconcost, user))
return FALSE
@@ -931,8 +901,9 @@ RLD
if(checkResource(floorcost, user))
to_chat(user, span_notice("You start building a wall light..."))
user.Beam(A,icon_state="nzcrentrs_power",time=15)
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
- playsound(src.loc, 'sound/effects/light_flicker.ogg', 50, FALSE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ playsound(src.loc, 'sound/effects/light_flicker.ogg', 50, FALSE)
if(do_after(user, floordelay, A))
if(!istype(W))
return FALSE
@@ -942,11 +913,12 @@ RLD
for(var/direction in GLOB.cardinals)
var/turf/C = get_step(W, direction)
var/list/dupes = checkdupes(C)
- if(start.CanAtmosPass(C) && !dupes.len)
+ if(start.can_atmos_pass(C) && !dupes.len)
candidates += C
if(!candidates.len)
to_chat(user, span_warning("Valid target not found..."))
- playsound(src.loc, 'sound/misc/compiler-failure.ogg', 30, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/misc/compiler-failure.ogg', 30, TRUE)
return FALSE
for(var/turf/open/O in candidates)
if(istype(O))
@@ -977,8 +949,9 @@ RLD
if(checkResource(floorcost, user))
to_chat(user, span_notice("You start building a floor light..."))
user.Beam(A,icon_state="light_beam",time=15)
- playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
- playsound(src.loc, 'sound/effects/light_flicker.ogg', 50, TRUE)
+ if(!silent)
+ playsound(src.loc, 'sound/machines/click.ogg', 50, TRUE)
+ playsound(src.loc, 'sound/effects/light_flicker.ogg', 50, TRUE)
if(do_after(user, floordelay, A))
if(!istype(F))
return 0
@@ -1000,7 +973,7 @@ RLD
G.color = color_choice
G.light_color = G.color
G.throw_at(A, 9, 3, user)
- G.on = TRUE
+ G.light_on = TRUE
G.update_brightness()
return TRUE
return FALSE
diff --git a/code/game/objects/items/RCL.dm b/code/game/objects/items/RCL.dm
index 23c6ccc93454..deaa3a2a02b1 100644
--- a/code/game/objects/items/RCL.dm
+++ b/code/game/objects/items/RCL.dm
@@ -190,7 +190,7 @@
if(last)
if(get_dist(last, user) == 1) //hacky, but it works
var/turf/T = get_turf(user)
- if(T.intact || !T.can_have_cabling())
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE || !T.can_have_cabling())
last = null
return
if(get_dir(last, user) == last.d2)
@@ -215,7 +215,7 @@
return
T = get_turf(user)
- if(T.intact || !T.can_have_cabling())
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE || !T.can_have_cabling())
return
for(var/obj/structure/cable/C in T)
@@ -275,7 +275,7 @@
return
var/turf/T = get_turf(user)
- if(T.intact || !T.can_have_cabling())
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE || !T.can_have_cabling())
return
loaded.color = colors[current_color_index]
diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm
index 523293932963..12d3e9973540 100644
--- a/code/game/objects/items/RPD.dm
+++ b/code/game/objects/items/RPD.dm
@@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(atmos_pipe_recipes, list(
new /datum/pipe_info/pipe("Manifold", /obj/machinery/atmospherics/pipe/manifold, TRUE),
new /datum/pipe_info/pipe("4-Way Manifold", /obj/machinery/atmospherics/pipe/manifold4w, TRUE),
new /datum/pipe_info/pipe("Layer Manifold", /obj/machinery/atmospherics/pipe/layer_manifold, TRUE),
+ new /datum/pipe_info/pipe("Multi-Deck Adapter", /obj/machinery/atmospherics/pipe/multiz, TRUE),
),
"Devices" = list(
new /datum/pipe_info/pipe("Connector", /obj/machinery/atmospherics/components/unary/portables_connector, FALSE),
@@ -240,6 +241,8 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
var/static/datum/pipe_info/first_plumbing
var/mode = BUILD_MODE | PAINT_MODE | DESTROY_MODE | WRENCH_MODE
var/locked = FALSE //wheter we can change categories. Useful for the plumber
+ /// The owner of this RCD. It can be a mech or a player.
+ var/owner
/obj/item/pipe_dispenser/Initialize(mapload)
. = ..()
@@ -286,6 +289,9 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
get_asset_datum(/datum/asset/spritesheet/pipes),
)
+/obj/item/pipe_dispenser/ui_host(mob/user)
+ return owner || ..()
+
/obj/item/pipe_dispenser/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
@@ -305,7 +311,7 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
"selected_color" = paint_color,
"paint_colors" = GLOB.pipe_paint_colors,
"mode" = mode,
- "locked" = locked
+ "locked" = locked,
)
var/list/recipes
@@ -329,10 +335,10 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
return data
/obj/item/pipe_dispenser/ui_act(action, params)
- if(..())
- return
- if(!usr.canUseTopic(src, BE_CLOSE))
+ . = ..()
+ if(.)
return
+
var/playeffect = TRUE
switch(action)
if("color")
@@ -429,7 +435,7 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
return
if (mode & BUILD_MODE)
- if(istype(get_area(user), /area/reebe/city_of_cogs))
+ if(istype(get_area(user), /area/centcom/reebe/city_of_cogs))
to_chat(user, span_notice("You cannot build on Reebe.."))
return
@@ -567,6 +573,24 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
return
to_chat(source, span_notice("You set the layer to [piping_layer]."))
+/obj/item/pipe_dispenser/exosuit
+ name = "mounted pipe dispenser"
+ desc = "You shouldn't be seeing this!"
+ item_flags = NO_MAT_REDEMPTION | DROPDEL | NOBLUDGEON
+ resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | UNACIDABLE // would be weird if it could somehow be destroyed inside the equipment item
+
+/obj/item/pipe_dispenser/exosuit/ui_state(mob/user)
+ return GLOB.pilot_state
+
+// don't allow using this thing unless you're piloting the mech it's attached to
+/obj/item/pipe_dispenser/exosuit/can_interact(mob/user)
+ if(!(owner && ismecha(owner)))
+ return FALSE
+ var/obj/mecha/gundam = owner
+ if(user == gundam.occupant && !gundam.equipment_disabled && gundam.selected == loc)
+ return TRUE
+ return FALSE
+
/obj/item/pipe_dispenser/plumbing
name = "Plumberinator"
desc = "A crude device to rapidly plumb things."
diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm
index b2e7fb5d761f..d8f3fc908ca0 100644
--- a/code/game/objects/items/airlock_painter.dm
+++ b/code/game/objects/items/airlock_painter.dm
@@ -439,7 +439,7 @@
* * target - The turf being painted to
*/
/obj/item/airlock_painter/decal/proc/paint_floor(turf/open/floor/target)
- target.AddComponent(/datum/component/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, FALSE, color, null, null, 255)
+ target.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, null, null, alpha, color, null, FALSE, null)
/**
* Return the final icon_state for the given decal options
@@ -641,4 +641,4 @@
decal_color = rgba_regex.group[1]
decal_alpha = text2num(rgba_regex.group[2], 16)
- target.AddComponent(/datum/component/decal, 'icons/turf/decals.dmi', source_decal, source_dir, FALSE, decal_color, null, null, decal_alpha)
+ target.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', source_decal, source_dir, null, null, decal_alpha, decal_color, null, FALSE, null)
diff --git a/code/game/objects/items/bell.dm b/code/game/objects/items/bell.dm
index 7bb200f82249..f3b1b1d30467 100644
--- a/code/game/objects/items/bell.dm
+++ b/code/game/objects/items/bell.dm
@@ -1,6 +1,6 @@
/obj/item/deskbell
name = "desk bell"
- desc = "ding. ding."
+ desc = "Ding! Ding! Ding!"
icon = 'icons/obj/bell.dmi'
icon_state = "bell"
force = 5
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index 00670944ea3e..031b8588c500 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -156,8 +156,8 @@
/area/shuttle,
/area/centcom,
/area/asteroid,
- /area/tdome,
- /area/wizard_station,
+ /area/centcom/tdome,
+ /area/centcom/wizard_station,
/area/hilbertshotel,
/area/hilbertshotelstorage
)
@@ -217,6 +217,8 @@
var/prevname = "[A.name]"
set_area_machinery_title(A, new_name, prevname)
A.name = new_name
+ require_area_resort() //area renamed so resort the names
+
if(A.firedoors)
for(var/D in A.firedoors)
var/obj/machinery/door/firedoor/FD = D
diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm
index db488b3729b2..02320aed0c12 100644
--- a/code/game/objects/items/cards_ids.dm
+++ b/code/game/objects/items/cards_ids.dm
@@ -963,7 +963,7 @@ update_label("John Doe", "Clowny")
/obj/item/card/id/prisoner
name = "prisoner ID card"
desc = "You are a number, you are not a free man."
- icon_state = "orange"
+ icon_state = "prisoner"
item_state = "orange-id"
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi'
@@ -1118,7 +1118,7 @@ update_label("John Doe", "Clowny")
. = ..()
if(target)
our_airlock = target
- RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(delete_on_door_delete))
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(delete_on_door_delete))
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
@@ -1227,7 +1227,7 @@ update_label("John Doe", "Clowny")
portal_one = new(get_turf(door2), door2)
portal_two = new(get_turf(door1), door1)
portal_one.destination = portal_two
- RegisterSignal(portal_one, COMSIG_PARENT_QDELETING, PROC_REF(clear_portal_refs)) //we only really need to register one because they already qdel both portals if one is destroyed
+ RegisterSignal(portal_one, COMSIG_QDELETING, PROC_REF(clear_portal_refs)) //we only really need to register one because they already qdel both portals if one is destroyed
portal_two.destination = portal_one
balloon_alert(user, "[message]")
diff --git a/code/game/objects/items/charter.dm b/code/game/objects/items/charter.dm
index 30147afb1f87..c0fa67b1b155 100644
--- a/code/game/objects/items/charter.dm
+++ b/code/game/objects/items/charter.dm
@@ -1,5 +1,3 @@
-#define STATION_RENAME_TIME_LIMIT (10 MINUTES)
-
/obj/item/station_charter
name = "station charter"
icon = 'icons/obj/wizard.dmi'
diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm
index 6128663daf4a..baf533a0496d 100644
--- a/code/game/objects/items/circuitboards/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm
@@ -1438,3 +1438,9 @@
req_components = list(
/obj/item/stock_parts/capacitor = 3,
/obj/item/stock_parts/micro_laser = 1)
+
+/obj/item/circuitboard/machine/mass_driver
+ name = "Mass Driver"
+ build_path = /obj/machinery/mass_driver
+ req_components = list(
+ /obj/item/stock_parts/capacitor = 1)
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 13c7c7a5fee3..ea46fd2a4d7c 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -672,7 +672,7 @@
to_chat(target, span_userdanger("[user] sprays [src] into your face!"))
if(C.client)
- C.blur_eyes(3)
+ C.adjust_eye_blur(3)
C.blind_eyes(1)
if(C.get_eye_protection() <= 0) // no eye protection? ARGH IT BURNS.
C.adjust_confusion(3)
diff --git a/code/game/objects/items/credit_holochip.dm b/code/game/objects/items/credit_holochip.dm
index 6683b6c5481e..55ade9564d5b 100644
--- a/code/game/objects/items/credit_holochip.dm
+++ b/code/game/objects/items/credit_holochip.dm
@@ -12,7 +12,7 @@
. = ..()
if(amount)
credits = amount
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/holochip/examine(mob/user)
. = ..()
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index ea6d0c4fbf05..62d06d057b33 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -327,7 +327,7 @@
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_range))
listeningTo = user
-/obj/item/shockpaddles/Moved()
+/obj/item/shockpaddles/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
check_range()
diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm
index 025c0bcd1640..6203c7ce1c86 100644
--- a/code/game/objects/items/devices/PDA/cart.dm
+++ b/code/game/objects/items/devices/PDA/cart.dm
@@ -435,13 +435,13 @@ Code:
else
menu += "station"
menu += " Current approved orders: "
- for(var/S in SSshuttle.shoppinglist)
+ for(var/S in SSshuttle.shopping_list)
var/datum/supply_order/SO = S
menu += "
#[SO.id] - [SO.pack.name] approved by [SO.orderer] [SO.reason ? "([SO.reason])":""]
"
menu += ""
menu += "Current requests: "
- for(var/S in SSshuttle.requestlist)
+ for(var/S in SSshuttle.request_list)
var/datum/supply_order/SO = S
menu += "
#[SO.id] - [SO.pack.name] requested by [SO.orderer]
"
menu += "Upgrade NOW to Space Parts & Space Vendors PLUS for full remote order control and inventory management."
diff --git a/code/game/objects/items/devices/busterarm/busterharpoon.dm b/code/game/objects/items/devices/busterarm/busterharpoon.dm
new file mode 100644
index 000000000000..31a2a43f6ba6
--- /dev/null
+++ b/code/game/objects/items/devices/busterarm/busterharpoon.dm
@@ -0,0 +1,108 @@
+/datum/action/cooldown/buster/megabuster/megaharpoon
+ name = "gasharpoon"
+ desc = "Charge up your harpoon and ready it to be fired, if it makes contact with a person it will drag them to you and immobilize them."
+ cooldown_time = 10 SECONDS
+ button_icon_state = "harpoonhead"
+
+
+/obj/item/gun/magic/wire/harpoon
+ name = "Harpoon Head"
+ desc = "A harpoon head made of pure plasteel, hits like a freighter."
+ ammo_type = /obj/item/ammo_casing/magic/wire/harpoon
+ icon_state = "gasharpoon"
+ item_state = "chain"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ fire_sound = 'sound/weapons/batonextend.ogg'
+ max_charges = 1
+ item_flags = NEEDS_PERMIT | DROPDEL
+ force = 0
+ can_charge = FALSE
+
+/obj/item/ammo_casing/magic/wire/harpoon
+ name = "harpoon"
+ desc = "A harpoon."
+ projectile_type = /obj/projectile/wire/harpoon
+ caliber = CALIBER_HOOK
+ icon_state = "harpoonhead"
+
+/obj/projectile/wire/harpoon/fire(setAngle)
+ if(firer)
+ wire = firer.Beam(src, icon_state = "harpoonrope", time = INFINITY, maxdistance = INFINITY)
+ ..()
+
+/obj/projectile/wire/harpoon
+ name = "harpoon"
+ icon_state = "harpoonhead"
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ pass_flags = PASSTABLE
+ damage = 10
+ armour_penetration = 100
+ damage_type = BRUTE
+ nodamage = TRUE
+ range = 8
+ hitsound = 'sound/effects/splat.ogg'
+ immobilize = 1 SECONDS
+
+/obj/projectile/wire/harpoon/on_hit(atom/target)
+ var/mob/living/L = target
+ var/mob/living/carbon/human/H = firer
+ if(!L)
+ return
+ L.apply_status_effect(STATUS_EFFECT_EXPOSED_HARPOONED)
+ if(isobj(target)) // If it's an object
+ var/obj/item/I = target
+ if(!I?.anchored) // Give it to us if it's not anchored
+ I.throw_at(get_step_towards(H,I), 8, 2)
+ H.visible_message(span_danger("[I] is pulled by [H]'s harpoon!"))
+ if(istype(I, /obj/item/clothing/head))
+ H.equip_to_slot_if_possible(I, ITEM_SLOT_HEAD)
+ H.visible_message(span_danger("[H] pulls [I] onto [H.p_their()] head!"))
+ else
+ H.put_in_hands(I)
+ return
+ zip(H, target) // Pull us towards it if it's anchored
+ if(isliving(target)) // If it's somebody
+ H.swap_hand(0) //for the sake of throttling people you catch
+ var/turf/T = get_step(get_turf(H), H.dir)
+ var/turf/Q = get_turf(H)
+ var/obj/item/bodypart/limb_to_hit = L.get_bodypart(H.zone_selected)
+ var/armor = L.run_armor_check(limb_to_hit, MELEE, armour_penetration = 35)
+ if(!L.anchored) // Only pull them if they're unanchored
+ if(istype(H))
+ L.visible_message(span_danger("[L] is pulled by [H]'s harpoon!"),span_userdanger("A harpoon pierces you and pulls you towards [H]!"))
+ L.Immobilize(1.0 SECONDS)
+ if(T.density) // If we happen to be facing a wall after the wire snatches them
+ to_chat(H, span_warning("[H] catches [L] and throws [L.p_them()] against [T]!"))
+ to_chat(L, span_userdanger("[H] crushes you against [T]!"))
+ playsound(L,'sound/effects/pop_expl.ogg', 130, 1)
+ L.apply_damage(15, BRUTE, limb_to_hit, armor, wound_bonus=CANT_WOUND)
+ L.forceMove(Q)
+ return
+ // If we happen to be facing a dense object after the wire snatches them, like a table or window
+ for(var/obj/D in T.contents)
+ if(D.density == TRUE)
+ D.take_damage(50)
+ L.apply_damage(15, BRUTE, limb_to_hit, armor, wound_bonus=CANT_WOUND)
+ L.forceMove(Q)
+ to_chat(H, span_warning("[H] catches [L] throws [L.p_them()] against [D]!"))
+ playsound(L,'sound/effects/pop_expl.ogg', 20, 1)
+ return
+ L.forceMove(T)
+ if(iswallturf(target)) // If we hit a wall, pull us to it
+ var/turf/W = target
+ zip(H, W)
+
+/// Left buster-arm means megabuster goes in left hand -- I stole this from megabuster! -- cowbot93
+/datum/action/cooldown/buster/megabuster/megaharpoon/l/Activate()
+ var/obj/item/gun/magic/wire/harpoon/B = new()
+ owner.visible_message(span_userdanger("[owner]'s arm lets out a harrowing sound!"))
+ playsound(owner,'sound/weapons/bladeslice.ogg', 60, 1)
+ if(do_after(owner, 2 SECONDS, owner, timed_action_flags = IGNORE_USER_LOC_CHANGE))
+ if(!owner.put_in_l_hand(B))
+ to_chat(owner, span_warning("You can't do this with your left hand full!"))
+ else
+ owner.visible_message(span_danger("[owner]'s readies the harpoon to fire!"))
+ if(owner.active_hand_index % 2 == 0)
+ owner.swap_hand(0)
+ StartCooldown()
diff --git a/code/game/objects/items/devices/busterarm/gasharpoon.dm b/code/game/objects/items/devices/busterarm/gasharpoon.dm
new file mode 100644
index 000000000000..2889422101a0
--- /dev/null
+++ b/code/game/objects/items/devices/busterarm/gasharpoon.dm
@@ -0,0 +1,59 @@
+/obj/item/clothing/gloves/gasharpoon
+ name = "gasharpoon"
+ desc = "A metal gauntlet with a harpoon attatched, powered by gasoline and traditionally used by space-whalers."
+ ///reminder to channge all this -- I changed it :)
+ icon = 'icons/obj/traitor.dmi'
+ icon_state = "gasharpoon"
+ item_state = "gasharpoon"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ attack_verb = list("harpooned", "gouged", "pierced")
+ force = 10
+ throwforce = 10
+ throw_range = 7
+ strip_delay = 15 SECONDS
+ cold_protection = HANDS
+ heat_protection = HANDS
+ w_class = WEIGHT_CLASS_NORMAL
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 100, ELECTRIC = 100)
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ var/click_delay = 1.5
+
+
+/obj/item/clothing/gloves/gasharpoon/equipped(mob/user, slot)
+ . = ..()
+ if(slot & ITEM_SLOT_GLOVES)
+ RegisterSignal(user, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(power_harpoon))
+ var/datum/action/cooldown/buster/megabuster/megaharpoon/l/harpoon = new(user)
+ harpoon.Grant(user)
+
+/obj/item/clothing/gloves/gasharpoon/dropped(mob/user)
+ . = ..()
+ if(user.get_item_by_slot(ITEM_SLOT_GLOVES)==src)
+ UnregisterSignal(user, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ var/datum/action/cooldown/buster/megabuster/megaharpoon/l/harpoon = locate(/datum/action/cooldown/buster/megabuster/megaharpoon/l) in user.actions
+ if(harpoon)
+ harpoon.Remove(user)
+ return ..()
+
+
+/obj/item/clothing/gloves/gasharpoon/proc/power_harpoon(mob/living/user, atom/movable/target)
+ if(!user || user.a_intent!=INTENT_HARM || (!isliving(target) && !isobj(target)) || isitem(target))
+ return
+ do_attack(user, target, force * 2)
+ playsound(loc, 'sound/weapons/bladeslice.ogg', 50, 1)
+ target.visible_message(span_danger("[user]'s gasharpoon pierces through [target.name]!"))
+ return COMPONENT_NO_ATTACK_HAND
+
+/obj/item/clothing/gloves/gasharpoon/attack(mob/living/target, mob/living/user)
+ power_harpoon(user, target)
+
+/obj/item/clothing/gloves/gasharpoon/proc/do_attack(mob/living/user, atom/target, punch_force)
+ if(isliving(target))
+ var/mob/living/target_mob = target
+ target_mob.apply_damage(punch_force, BRUTE, wound_bonus = 30)
+ else if(isobj(target))
+ var/obj/target_obj = target
+ target_obj.take_damage(punch_force, BRUTE, MELEE, FALSE)
+ user.do_attack_animation(target, ATTACK_EFFECT_SLASH)
+ user.changeNext_move(CLICK_CD_MELEE * click_delay)
diff --git a/code/game/objects/items/devices/busterarm/wire_snatch.dm b/code/game/objects/items/devices/busterarm/wire_snatch.dm
index 921ab3fa34e1..67a319ed229c 100644
--- a/code/game/objects/items/devices/busterarm/wire_snatch.dm
+++ b/code/game/objects/items/devices/busterarm/wire_snatch.dm
@@ -95,7 +95,7 @@
name = "hook"
desc = "A hook."
projectile_type = /obj/projectile/wire
- caliber = "hook"
+ caliber = CALIBER_HOOK
icon_state = "hook"
/// Projectile
@@ -129,7 +129,7 @@
var/mob/living/carbon/human/H = firer
if(!H)
return
- H.apply_status_effect(STATUS_EFFECT_DOUBLEDOWN)
+ H.apply_status_effect(STATUS_EFFECT_DOUBLEDOWN)
if(isobj(target)) // If it's an object
var/obj/item/I = target
if(!I?.anchored) // Give it to us if it's not anchored
@@ -152,7 +152,7 @@
var/armor = L.run_armor_check(limb_to_hit, MELEE, armour_penetration = 35)
if(!L.anchored) // Only pull them if they're unanchored
if(istype(H))
- L.visible_message(span_danger("[L] is pulled by [H]'s wire!"),span_userdanger("A wire grabs you and pulls you towards [H]!"))
+ L.visible_message(span_danger("[L] is pulled by [H]'s wire!"),span_userdanger("A wire grabs you and pulls you towards [H]!"))
L.Immobilize(1.0 SECONDS)
if(prob(5))
firer.say("GET OVER HERE!!")//slicer's request
@@ -166,7 +166,7 @@
// If we happen to be facing a dense object after the wire snatches them, like a table or window
for(var/obj/D in T.contents)
if(D.density == TRUE)
- D.take_damage(50)
+ D.take_damage(50)
L.apply_damage(15, BRUTE, limb_to_hit, armor, wound_bonus=CANT_WOUND)
L.forceMove(Q)
to_chat(H, span_warning("[H] catches [L] throws [L.p_them()] against [D]!"))
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index a9246f646ac6..3ce97e468941 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -63,7 +63,7 @@
var/obj/temp = new/obj()
temp.appearance = target.appearance
temp.layer = initial(target.layer) // scanning things in your inventory
- temp.plane = initial(target.plane)
+ SET_PLANE_IMPLICIT(temp, initial(target.plane))
saved_appearance = temp.appearance
/obj/item/chameleon/proc/check_sprite(atom/target)
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index e5a6611c0df1..0ca05a5c32af 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -1,3 +1,8 @@
+#define FAILURE 0
+#define SUCCESS 1
+#define NO_FUEL 2
+#define ALREADY_LIT 3
+
/obj/item/flashlight
name = "flashlight"
desc = "A hand-held emergency light."
@@ -12,41 +17,66 @@
slot_flags = ITEM_SLOT_BELT
materials = list(/datum/material/iron=50, /datum/material/glass=20)
actions_types = list(/datum/action/item_action/toggle_light)
- light_system = MOVABLE_LIGHT
+ light_system = MOVABLE_LIGHT_DIRECTIONAL
light_range = 4
light_power = 1
light_on = FALSE
- var/on = FALSE
+ /// If we've been forcibly disabled for a temporary amount of time.
+ COOLDOWN_DECLARE(disabled_time)
+ /// Can we toggle this light on and off (used for contexual screentips only)
+ var/toggle_context = TRUE
+ /// The sound the light makes when it's turned on
+ var/sound_on = 'sound/weapons/magin.ogg'
+ /// The sound the light makes when it's turned off
+ var/sound_off = 'sound/weapons/magout.ogg'
+ /// Should the flashlight start turned on?
+ var/start_on = FALSE
/obj/item/flashlight/Initialize(mapload)
. = ..()
- if(icon_state == "[initial(icon_state)]-on")
- on = TRUE
+ if(start_on)
+ set_light_on(TRUE)
update_brightness()
-/obj/item/flashlight/proc/update_brightness(mob/user)
- if(on)
+/obj/item/flashlight/update_icon_state()
+ . = ..()
+ if(light_on)
icon_state = "[initial(icon_state)]-on"
+ if(!isnull(item_state))
+ item_state = "[initial(item_state)]-on"
else
icon_state = initial(icon_state)
- set_light_on(on)
+ if(!isnull(item_state))
+ item_state = initial(item_state)
+
+/obj/item/flashlight/proc/update_brightness()
+ update_appearance(UPDATE_ICON)
if(light_system == STATIC_LIGHT)
update_light()
+
+/obj/item/flashlight/proc/toggle_light(mob/user)
+ playsound(src, light_on ? sound_off : sound_on, 40, TRUE)
+ if(!COOLDOWN_FINISHED(src, disabled_time))
+ if(user)
+ balloon_alert(user, "disrupted!")
+ set_light_on(FALSE)
+ update_brightness()
+ update_item_action_buttons()
+ return FALSE
+ var/old_light_on = light_on
+ set_light_on(!light_on)
+ update_brightness()
+ update_item_action_buttons()
+ return light_on != old_light_on // If the value of light_on didn't change, return false. Otherwise true.
/obj/item/flashlight/attack_self(mob/user)
- on = !on
- update_brightness(user)
- playsound(user, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1)
- for(var/X in actions)
- var/datum/action/A = X
- A.build_all_button_icons()
- return 1
+ toggle_light(user)
/obj/item/flashlight/suicide_act(mob/living/carbon/human/user)
if (user.eye_blind)
user.visible_message(span_suicide("[user] is putting [src] close to [user.p_their()] eyes and turning it on... but [user.p_theyre()] blind!"))
return SHAME
- if(!on)
+ if(!light_on)
user.visible_message(span_suicide("[user] is putting [src] close to [user.p_their()] eyes but it's not on!"))
return SHAME
user.visible_message(span_suicide("[user] is putting [src] close to [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -54,7 +84,7 @@
/obj/item/flashlight/attack(mob/living/carbon/M, mob/living/carbon/human/user)
add_fingerprint(user)
- if(istype(M) && on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH)))
+ if(istype(M) && light_on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH)))
if((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) //too dumb to use flashlight properly
return ..() //just hit them in the head
@@ -168,6 +198,18 @@
else
return ..()
+/// for directional sprites - so we get the same sprite in the inventory each time we pick one up
+/obj/item/flashlight/equipped(mob/user, slot, initial)
+ . = ..()
+ setDir(initial(dir))
+ SEND_SIGNAL(user, COMSIG_ATOM_DIR_CHANGE, user.dir, user.dir) // This is dumb, but if we don't do this then the lighting overlay may be facing the wrong direction depending on how it is picked up
+
+/// for directional sprites - so when we drop the flashlight, it drops facing the same way the user is facing
+/obj/item/flashlight/dropped(mob/user, silent = FALSE)
+ . = ..()
+ if(istype(user) && dir != user.dir)
+ setDir(user.dir)
+
/obj/item/flashlight/pen
name = "penlight"
desc = "A pen-sized light, used by medical staff. It can also be used to create a hologram to alert people of incoming medical assistance."
@@ -175,27 +217,35 @@
item_state = ""
flags_1 = CONDUCT_1
light_range = 2
- var/holo_cooldown = 0
+ COOLDOWN_DECLARE(holosign_cooldown)
/obj/item/flashlight/pen/afterattack(atom/target, mob/user, proximity_flag)
. = ..()
- if(!proximity_flag)
- if(holo_cooldown > world.time)
- to_chat(user, span_warning("[src] is not ready yet!"))
- return
- var/T = get_turf(target)
- if(locate(/mob/living) in T)
- new /obj/effect/temp_visual/medical_holosign(T,user) //produce a holographic glow
- holo_cooldown = world.time + 10 SECONDS
- return
+ if(proximity_flag)
+ return
+
+ if(!COOLDOWN_FINISHED(src, holosign_cooldown))
+ balloon_alert(user, "not ready!")
+ return
+
+ var/target_turf = get_turf(target)
+ var/mob/living/living_target = locate(/mob/living) in target_turf
+
+ if(!living_target || (living_target == user))
+ return
+
+ to_chat(living_target, span_boldnotice("[user] is offering medical assistance; please halt your actions."))
+ new /obj/effect/temp_visual/medical_holosign(target_turf, user) //produce a holographic glow
+ COOLDOWN_START(src, holosign_cooldown, 10 SECONDS)
// see: [/datum/wound/burn/proc/uv()]
/obj/item/flashlight/pen/paramedic
name = "paramedic penlight"
desc = "A high-powered UV penlight intended to help stave off infection in the field on serious burned patients. Probably really bad to look into."
icon_state = "penlight_surgical"
+ light_color = LIGHT_COLOR_PURPLE
/// Our current UV cooldown
- var/uv_cooldown = 0
+ COOLDOWN_DECLARE(uv_cooldown)
/// How long between UV fryings
var/uv_cooldown_length = 30 SECONDS
/// How much sanitization to apply to the burn wound
@@ -203,7 +253,7 @@
/obj/item/flashlight/pen/paramedic/advanced
name = "advanced penlight"
- desc = "A stronger version of the UV penlight that paramedics and doctors recieve, it is capable of cauterizing bleeding as well as sterilizing burns."
+ desc = "A stronger version of the UV penlight that paramedics and doctors receive, it is capable of cauterizing bleeding as well as sterilizing burns."
icon_state = "penlight_cmo"
light_range = 4
uv_power = 2
@@ -222,7 +272,7 @@
/obj/effect/temp_visual/medical_holosign/Initialize(mapload, mob/creator)
. = ..()
- playsound(loc, 'sound/machines/ping.ogg', 50, 0) //make some noise!
+ playsound(loc, 'sound/machines/ping.ogg', 50, FALSE) //make some noise!
if(creator)
visible_message(span_danger("[creator] created a medical hologram, indicating that [creator.p_theyre(FALSE, FALSE)] coming to help!"))
@@ -237,7 +287,6 @@
force = 9 // Not as good as a stun baton.
light_range = 5 // A little better than the standard flashlight.
hitsound = 'sound/weapons/genhit1.ogg'
- light_system = MOVABLE_LIGHT_DIRECTIONAL
// the desk lamps are a bit special
/obj/item/flashlight/lamp
@@ -252,7 +301,7 @@
w_class = WEIGHT_CLASS_BULKY
flags_1 = CONDUCT_1
materials = list()
- on = TRUE
+ start_on = TRUE
// green-shaded desk lamp
@@ -261,16 +310,6 @@
icon_state = "lampgreen"
item_state = "lampgreen"
-
-
-/obj/item/flashlight/lamp/verb/toggle_light()
- set name = "Toggle light"
- set category = "Object"
- set src in oview(1)
-
- if(!usr.stat)
- attack_self(usr)
-
//Bananalamp
/obj/item/flashlight/lamp/bananalamp
name = "banana lamp"
@@ -290,54 +329,85 @@
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
actions_types = list()
- var/ignition_sound = 'sound/items/flare_strike_1.ogg'
- var/fuel = 0
- var/on_damage = 7
- var/frng_min = 800
- var/frng_max = 1000
- var/flare_particle = TRUE
heat = 1000
light_color = LIGHT_COLOR_FLARE
+ light_system = MOVABLE_LIGHT
grind_results = list(/datum/reagent/sulphur = 15)
+ sound_on = 'sound/items/flare_strike_1.ogg'
+ /// How many seconds of fuel we have left
+ var/fuel = 0
+ /// Do we randomize the fuel when initialized
+ var/randomize_fuel = TRUE
+ /// Randomized fuel amount minimum
+ var/frng_min = 25 MINUTES
+ /// Randomized fuel amount maximum
+ var/frng_max = 35 MINUTES
+ /// How much damage it does when turned on
+ var/on_damage = 7
+ /// Type of atom thats spawns after fuel is used up
+ //var/trash_type = /obj/item/trash/flare
+ /// If the light source can be extinguished
+ var/can_be_extinguished = FALSE
+ /// Does this use particle effects
+ var/flare_particle = TRUE
/obj/item/flashlight/flare/Initialize(mapload)
. = ..()
- fuel = rand(frng_min, frng_max)
+ if(randomize_fuel)
+ fuel = rand(25 MINUTES, 35 MINUTES)
+ if(light_on)
+ attack_verb = list("burnt","scorched","scalded")
+ hitsound = 'sound/items/welder.ogg'
+ force = on_damage
+ damtype = BURN
+ update_brightness()
+
+/obj/item/flashlight/flare/toggle_light()
+ if(light_on || !fuel)
+ return FALSE
+ . = ..()
+
+ name = "lit [initial(name)]"
+ attack_verb = list("burnt","scorched","scalded")
+ hitsound = 'sound/items/welder.ogg'
+ force = on_damage
+ damtype = BURN
+
+/obj/item/flashlight/flare/proc/turn_off()
+ set_light_on(FALSE)
+ name = initial(name)
+ attack_verb = initial(attack_verb)
+ hitsound = initial(hitsound)
+ force = initial(force)
+ damtype = initial(damtype)
+ update_brightness()
-/obj/item/flashlight/flare/process()
+/obj/item/flashlight/flare/extinguish()
+ . = ..()
+ if((fuel != INFINITY) && can_be_extinguished)
+ turn_off()
+
+/obj/item/flashlight/flare/process(seconds_per_tick)
open_flame(heat)
- fuel = max(fuel - 1, 0)
- if(!fuel || !on)
+ fuel = max(fuel - seconds_per_tick * (1 SECONDS), 0)
+ if(!fuel || !light_on)
turn_off()
if(!fuel)
icon_state = "[initial(icon_state)]-empty"
- name = "spent [initial(src.name)]"
- desc = "[initial(src.desc)] It's all used up."
+ name = "spent [initial(name)]"
+ desc = "[initial(desc)] It's all used up."
STOP_PROCESSING(SSobj, src)
/obj/item/flashlight/flare/ignition_effect(atom/A, mob/user)
- if(fuel && on)
+ if(fuel && light_on)
. = "[user] lights [A] with [src] like a real \
badass."
else
. = ""
-/obj/item/flashlight/flare/proc/turn_off()
- on = FALSE
- force = initial(src.force)
- damtype = initial(src.damtype)
- hitsound = initial(src.hitsound)
- desc = initial(src.desc)
- attack_verb = initial(src.attack_verb)
- if(ismob(loc))
- var/mob/U = loc
- update_brightness(U)
- else
- update_brightness(null)
-
/obj/item/flashlight/flare/update_brightness(mob/user = null)
..()
- if(on)
+ if(light_on)
if(flare_particle)
add_emitter(/obj/emitter/sparks/flare, "spark", 10)
add_emitter(/obj/emitter/flare_smoke, "smoke", 9)
@@ -354,7 +424,7 @@
if(fuel <= 0)
to_chat(user, span_warning("[src] is out of fuel!"))
return
- if(on)
+ if(light_on)
to_chat(user, span_notice("[src] is already on."))
return
@@ -362,7 +432,7 @@
// All good, turn it on.
if(.)
user.visible_message(span_notice("[user] lights \the [src]."), span_notice("You light \the [src]!"))
- playsound(loc, ignition_sound, 50, 1) //make some noise!
+ playsound(loc, sound_on, 50, 1) //make some noise!
force = on_damage
name = "lit [initial(src.name)]"
desc = "[initial(src.desc)] This one is lit."
@@ -372,7 +442,7 @@
START_PROCESSING(SSobj, src)
/obj/item/flashlight/flare/is_hot()
- return on * heat
+ return light_on * heat
/obj/item/flashlight/flare/emergency
name = "safety flare"
@@ -380,7 +450,7 @@
light_range = 3
item_state = "flare"
icon_state = "flaresafety"
- ignition_sound = 'sound/items/flare_strike_2.ogg'
+ sound_on = 'sound/items/flare_strike_2.ogg'
frng_min = 40
frng_max = 70
@@ -406,7 +476,7 @@
item_state = "torch"
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
- ignition_sound = 'sound/items/match_strike.ogg'
+ sound_on = 'sound/items/match_strike.ogg'
light_color = LIGHT_COLOR_ORANGE
on_damage = 10
slot_flags = null
@@ -420,6 +490,7 @@
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
desc = "A mining lantern."
light_range = 6 // luminosity when on
+ light_system = MOVABLE_LIGHT
/obj/item/flashlight/lantern/heirloom_moth
name = "old lantern"
@@ -450,6 +521,7 @@
slot_flags = ITEM_SLOT_BELT
materials = list()
light_range = 6 //luminosity when on
+ light_system = MOVABLE_LIGHT
/obj/item/flashlight/emp
var/emp_max_charges = 4
@@ -478,7 +550,7 @@
if(!is_syndicate(user))
return
- if(on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) // call original attack when examining organs
+ if(light_on && (user.zone_selected in list(BODY_ZONE_PRECISE_EYES, BODY_ZONE_PRECISE_MOUTH))) // call original attack when examining organs
..()
return
@@ -518,16 +590,17 @@
custom_price = 10
w_class = WEIGHT_CLASS_SMALL
light_range = 4
+ light_system = MOVABLE_LIGHT
color = LIGHT_COLOR_GREEN
icon_state = "glowstick"
item_state = "glowstick"
grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick
+ sound_on = 'sound/effects/wounds/crack2.ogg' // the cracking sound isn't just for wounds silly
var/fuel = 0
/obj/item/flashlight/glowstick/Initialize(mapload)
fuel = rand(1600, 2000)
- light_color = color
-
+ set_light_color(color)
. = ..()
/obj/item/flashlight/glowstick/Destroy()
@@ -539,32 +612,35 @@
if(fuel <= 0)
turn_off()
STOP_PROCESSING(SSobj, src)
- update_appearance(UPDATE_ICON)
/obj/item/flashlight/glowstick/proc/turn_off()
- on = FALSE
- update_appearance(UPDATE_ICON)
+ light_on = FALSE
+ update_appearance()
-/obj/item/flashlight/glowstick/update_icon(updates=ALL)
+/obj/item/flashlight/glowstick/update_appearance(updates=ALL)
. = ..()
if(fuel <= 0)
set_light_on(FALSE)
- else if(on)
+ return
+ if(light_on)
set_light_on(TRUE)
+ return
/obj/item/flashlight/glowstick/update_overlays()
. = ..()
- if(on)
- var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow")
- glowstick_overlay.color = color
- . += glowstick_overlay
+ if(fuel <= 0 && !light_on)
+ return
+
+ var/mutable_appearance/glowstick_overlay = mutable_appearance(icon, "glowstick-glow")
+ glowstick_overlay.color = color
+ . += glowstick_overlay
/obj/item/flashlight/glowstick/update_icon_state()
. = ..()
item_state = "glowstick" //item state
if(fuel <= 0)
icon_state = "glowstick-empty"
- else if(on)
+ else if(light_on)
item_state = "glowstick-on" //item state
else
icon_state = "glowstick"
@@ -573,7 +649,7 @@
if(fuel <= 0)
to_chat(user, span_notice("[src] is spent."))
return
- if(on)
+ if(light_on)
to_chat(user, span_notice("[src] is already lit."))
return
@@ -631,11 +707,12 @@
name = "disco light"
desc = "Groovy..."
icon_state = null
+ light_system = MOVABLE_LIGHT
light_range = 4
light_power = 10
alpha = 0
layer = 0
- on = TRUE
+ start_on = TRUE
anchored = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
///Boolean that switches when a full color flip ends, so the light can appear in all colors.
@@ -667,7 +744,7 @@
/obj/item/flashlight/flashdark/update_brightness(mob/user)
. = ..()
- if(on)
+ if(light_on)
set_light(dark_light_range, dark_light_power)
else
set_light(0)
@@ -675,6 +752,7 @@
/obj/item/flashlight/eyelight
name = "eyelight"
desc = "This shouldn't exist outside of someone's head, how are you seeing this?"
+ light_system = MOVABLE_LIGHT
light_range = 15
light_power = 1
flags_1 = CONDUCT_1
diff --git a/code/game/objects/items/devices/forcefieldprojector.dm b/code/game/objects/items/devices/forcefieldprojector.dm
index e1c9c50ab9c8..aeaf5307eba2 100644
--- a/code/game/objects/items/devices/forcefieldprojector.dm
+++ b/code/game/objects/items/devices/forcefieldprojector.dm
@@ -86,7 +86,7 @@
density = TRUE
mouse_opacity = MOUSE_OPACITY_OPAQUE
resistance_flags = INDESTRUCTIBLE
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
armor = list(MELEE = 0, BULLET = 25, LASER = 50, ENERGY = 50, BOMB = 25, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
var/obj/item/forcefield_projector/generator
diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm
index a4aa5cd205fb..7e29aa4d03cf 100644
--- a/code/game/objects/items/devices/gps.dm
+++ b/code/game/objects/items/devices/gps.dm
@@ -1,4 +1,3 @@
-GLOBAL_LIST_EMPTY(GPS_list)
/obj/item/gps
name = "global positioning system"
desc = "Helping lost spacemen find their way through the planets since 2016."
@@ -9,133 +8,14 @@ GLOBAL_LIST_EMPTY(GPS_list)
obj_flags = UNIQUE_RENAME | UNIQUE_REDESC
var/gpstag = "COM0"
var/emped = FALSE
- var/tracking = TRUE
- var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user.
- var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown
-
-/obj/item/gps/examine(mob/user)
- . = ..()
- . += span_notice("Alt-click to switch it [tracking ? "off":"on"].")
/obj/item/gps/Initialize(mapload)
. = ..()
- GLOB.GPS_list += src
- name = "global positioning system ([gpstag])"
- if(tracking) //Some roundstart GPS are off.
- add_overlay("working")
-
-/obj/item/gps/Destroy()
- GLOB.GPS_list -= src
- return ..()
-
-/obj/item/gps/emp_act(severity)
- . = ..()
- if (. & EMP_PROTECT_SELF)
- return
- emped = TRUE
- cut_overlay("working")
- add_overlay("emp")
- addtimer(CALLBACK(src, PROC_REF(reboot)), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early
- SStgui.close_uis(src) //Close the UI control if it is open.
-
-/obj/item/gps/proc/reboot()
- emped = FALSE
- cut_overlay("emp")
- add_overlay("working")
-
-/obj/item/gps/AltClick(mob/user)
- if(!user.canUseTopic(src, BE_CLOSE))
- return
- toggletracking(user)
-
-/obj/item/gps/proc/toggletracking(mob/user)
- if(!user.canUseTopic(src, BE_CLOSE))
- return //user not valid to use gps
- if(emped)
- to_chat(user, "It's busted!")
- return
- if(tracking)
- cut_overlay("working")
- to_chat(user, "[src] is no longer tracking, or visible to other GPS devices.")
- tracking = FALSE
- else
- add_overlay("working")
- to_chat(user, "[src] is now tracking, and visible to other GPS devices.")
- tracking = TRUE
-
-
-/obj/item/gps/ui_interact(mob/user, datum/tgui/ui) // Remember to use the appropriate state.
- if(emped)
- to_chat(user, "[src] fizzles weakly.")
- return
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- // Variable window height, depending on how many GPS units there are
- // to show
- ui = new(user, src, "Gps") //width, height
- ui.open()
-
- ui.set_autoupdate(updating)
-
-
-/obj/item/gps/ui_data(mob/user)
- var/list/data = list()
- data["power"] = tracking
- data["tag"] = gpstag
- data["updating"] = updating
- data["globalmode"] = global_mode
- if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed
- return data
-
- var/turf/curr = get_turf_global(src) // yogs - get_turf_global instead of get_turf
- data["currentArea"] = "[get_area_name(curr, TRUE)]"
- data["currentCoords"] = "[curr.x], [curr.y], [curr.z]"
-
- var/list/signals = list()
- data["signals"] = list()
-
- for(var/gps in GLOB.GPS_list)
- var/obj/item/gps/G = gps
- if(G.emped || !G.tracking || G == src)
- continue
- var/turf/pos = get_turf_global(G) // yogs - get_turf_global instead of get_turf
- if(!pos)
- continue
- if(!global_mode && pos.z != curr.z)
- continue
- var/list/signal = list()
- signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS
- signal["coords"] = "[pos.x], [pos.y], [pos.z]"
- if(pos.z == curr.z) //Distance/Direction calculations for same z-level only
- signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs
- signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision.
-
- signals += list(signal) //Add this signal to the list of signals
- data["signals"] = signals
- return data
-
-
-
-/obj/item/gps/ui_act(action, params)
- if(..())
- return
- switch(action)
- if("rename")
- var/a = stripped_input(usr, "Please enter desired tag.", name, gpstag, 20)
- gpstag = a
- . = TRUE
- name = "global positioning system ([gpstag])"
-
- if("power")
- toggletracking(usr)
- . = TRUE
- if("updating")
- updating = !updating
- . = TRUE
- if("globalmode")
- global_mode = !global_mode
- . = TRUE
+ add_gps_component()
+/// Adds the GPS component to this item.
+/obj/item/gps/proc/add_gps_component()
+ AddComponent(/datum/component/gps/item, gpstag)
/obj/item/gps/science
icon_state = "gps-s"
@@ -194,7 +74,7 @@ GLOBAL_LIST_EMPTY(GPS_list)
// I assume it's faster to color,tag and OR the turf in, rather
// then checking if its there
T.color = RANDOM_COLOUR
- T.maptext = "[T.x],[T.y],[T.z]"
+ T.maptext = MAPTEXT("[T.x],[T.y],[T.z]")
tagged |= T
/obj/item/gps/visible_debug/proc/clear()
@@ -209,34 +89,3 @@ GLOBAL_LIST_EMPTY(GPS_list)
tagged = null
STOP_PROCESSING(SSfastprocess, src)
. = ..()
-
-/**
- * # Pirate GPS
- *
- * Pirate GPS used for targeting by the [Blue Space Artillery] [/obj/machinery/computer/bsa_control]
- *
- * When shot at, relays the fact that it was shot at to [the pirate event] [/datum/round_event/pirates] so it cancels
- */
-
-/obj/item/gps/pirate
-
-/**
- * Initializes the GPS with the correct name, taken from the pirate ship's name
- * If no name is provided, it'll default to "Jolly Robuster"
- *
- * Arguments:
- * * ship_name - The name that of the ship that we're pretending to be, defaults to "Jolly Robuster"
- */
-/obj/item/gps/pirate/Initialize(mapload, ship_name = "Jolly Robuster")
- .=..()
- if(ship_name)
- name = ship_name
- gpstag = ship_name
-
-/**
- * Relays that the [Blue Space Artillery] [/obj/machinery/computer/bsa_control] has shot the ship to the event, then qdels
- */
-/obj/item/gps/pirate/proc/on_shoot()
- var/datum/round_event/pirates/r = locate() in SSevents.running
- r?.shot_down()
- qdel(src)
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index 84f32bcdabca..8518682a8ccc 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -4,7 +4,6 @@
icon = 'icons/obj/device.dmi'
icon_state = "pointer"
item_state = "pen"
- var/pointer_icon_state
flags_1 = CONDUCT_1
item_flags = NOBLUDGEON
slot_flags = ITEM_SLOT_BELT
@@ -14,6 +13,8 @@
var/charges = 5
var/max_charges = 5
var/effectchance = 33
+ ///Icon for the laser, affects both the laser dot and the laser pointer itself, as it shines a laser on the item itself
+ var/pointer_icon_state = null
///The diode is what determines the effectiveness and recharge rate of the laser pointer. Higher tier part means stronger pointer
var/obj/item/stock_parts/micro_laser/diode
var/diode_type = /obj/item/stock_parts/micro_laser
@@ -174,16 +175,17 @@
//laser pointer image
icon_state = "pointer_[pointer_icon_state]"
- var/image/I = image('icons/obj/projectiles.dmi',targloc,pointer_icon_state,10)
- var/list/click_params = params2list(params)
- if(click_params)
- if(click_params["icon-x"])
- I.pixel_x = (text2num(click_params["icon-x"]) - 16)
- if(click_params["icon-y"])
- I.pixel_y = (text2num(click_params["icon-y"]) - 16)
+ //setup pointer blip
+ var/mutable_appearance/laser = mutable_appearance('icons/obj/projectiles.dmi', pointer_icon_state)
+ var/list/modifiers = params2list(params)
+ if(modifiers)
+ if(LAZYACCESS(modifiers, ICON_X))
+ laser.pixel_x = (text2num(LAZYACCESS(modifiers, ICON_X)) - 16)
+ if(LAZYACCESS(modifiers, ICON_Y))
+ laser.pixel_y = (text2num(LAZYACCESS(modifiers, ICON_Y)) - 16)
else
- I.pixel_x = target.pixel_x + rand(-5,5)
- I.pixel_y = target.pixel_y + rand(-5,5)
+ laser.pixel_x = target.pixel_x + rand(-5,5)
+ laser.pixel_y = target.pixel_y + rand(-5,5)
if(outmsg)
to_chat(user, outmsg)
@@ -198,7 +200,9 @@
if(charges <= max_charges)
START_PROCESSING(SSobj, src)
- flick_overlay_view(I, targloc, 10)
+ //flash a pointer blip at the target
+ target.flick_overlay_view(laser, 1 SECONDS)
+ //reset pointer sprite
icon_state = "pointer"
/obj/item/laser_pointer/process(delta_time)
diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm
index 7f47eed73f26..48ab8377e8eb 100644
--- a/code/game/objects/items/devices/multitool.dm
+++ b/code/game/objects/items/devices/multitool.dm
@@ -46,122 +46,72 @@
/obj/item/multitool/ai_detect
actions_types = list(/datum/action/item_action/toggle_multitool)
var/detect_state = PROXIMITY_NONE
- var/rangealert = 8 //Glows red when inside
+ var/rangealert = 8 //Glows red when inside
var/rangewarning = 20 //Glows yellow when inside
var/hud_type = DATA_HUD_AI_DETECT
- var/hud_on = FALSE
- var/mob/camera/aiEye/remote/ai_detector/eye
-
-/obj/item/multitool/ai_detect/Initialize(mapload)
- . = ..()
- START_PROCESSING(SSfastprocess, src)
- eye = new /mob/camera/aiEye/remote/ai_detector()
+ var/detecting = FALSE
/obj/item/multitool/ai_detect/Destroy()
STOP_PROCESSING(SSfastprocess, src)
- if(hud_on && ismob(loc))
- remove_hud(loc)
- QDEL_NULL(eye)
return ..()
/obj/item/multitool/ai_detect/ui_action_click()
return
-/obj/item/multitool/ai_detect/equipped(mob/living/carbon/human/user, slot)
- ..()
- if(hud_on)
- show_hud(user)
-
-/obj/item/multitool/ai_detect/dropped(mob/living/carbon/human/user)
- ..()
- if(hud_on)
- remove_hud(user)
-
/obj/item/multitool/ai_detect/update_icon_state()
. = ..()
icon_state = "[initial(icon_state)][detect_state]"
/obj/item/multitool/ai_detect/process()
var/old_detect_state = detect_state
- if(eye.eye_user)
- eye.setLoc(get_turf(src))
multitool_detect()
if(detect_state != old_detect_state)
- update_appearance(UPDATE_ICON)
+ update_appearance()
-/obj/item/multitool/ai_detect/proc/toggle_hud(mob/user)
- hud_on = !hud_on
+/obj/item/multitool/ai_detect/proc/toggle_detect(mob/user)
+ detecting = !detecting
if(user)
- to_chat(user, span_notice("You toggle the ai detection HUD on [src] [hud_on ? "on" : "off"]."))
- if(hud_on)
- show_hud(user)
- else
- remove_hud(user)
-
-/obj/item/multitool/ai_detect/proc/show_hud(mob/user)
- if(user && hud_type)
- var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
- PM.alpha = 64
- var/datum/atom_hud/H = GLOB.huds[hud_type]
- if(!H.hud_users[user])
- H.show_to(user)
- eye.eye_user = user
- eye.setLoc(get_turf(src))
-
-/obj/item/multitool/ai_detect/proc/remove_hud(mob/user)
- if(user && hud_type)
- var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"]
- PM.alpha = 255
- var/datum/atom_hud/H = GLOB.huds[hud_type]
- H.hide_from(user)
- if(eye)
- eye.setLoc(null)
- eye.eye_user = null
+ to_chat(user, span_notice("You toggle the ai detection feature on [src] [detecting ? "on" : "off"]."))
+ if(!detecting)
+ detect_state = PROXIMITY_NONE
+ update_appearance()
+ STOP_PROCESSING(SSfastprocess, src)
+ return
+ if(detecting)
+ START_PROCESSING(SSfastprocess, src)
/obj/item/multitool/ai_detect/proc/multitool_detect()
var/turf/our_turf = get_turf(src)
- for(var/mob/living/silicon/ai/AI as anything in GLOB.ai_list)
- if(AI.cameraFollow == src)
- detect_state = PROXIMITY_ON_SCREEN
- return
+ detect_state = PROXIMITY_NONE
- for(var/mob/camera/aiEye/AI_eye as anything in GLOB.aiEyes)
+ for(var/mob/camera/ai_eye/AI_eye as anything in GLOB.aiEyes)
if(!AI_eye.ai_detector_visible)
continue
var/distance = get_dist(our_turf, get_turf(AI_eye))
if(distance == -1) //get_dist() returns -1 for distances greater than 127 (and for errors, so assume -1 is just max range)
+ if(our_turf == get_turf(AI_eye)) // EXCEPT if the AI is on our TURF(ITS RIGHT ONTOP OF US!!!!)
+ detect_state = PROXIMITY_ON_SCREEN
+ break
continue
if(distance < rangealert) //ai should be able to see us
detect_state = PROXIMITY_ON_SCREEN
break
-
if(distance < rangewarning) //ai cant see us but is close
detect_state = PROXIMITY_NEAR
-/mob/camera/aiEye/remote/ai_detector
- name = "AI detector eye"
- ai_detector_visible = FALSE
- visible_icon = FALSE
- use_static = FALSE
-
/datum/action/item_action/toggle_multitool
- name = "Toggle AI detector HUD"
+ name = "Toggle AI detecting mode"
check_flags = NONE
-/datum/action/item_action/toggle_multitool/IsAvailable(feedback = FALSE)
- if(!is_syndicate(owner))
- HideFrom(owner)
- return is_syndicate(owner)
-
-/datum/action/item_action/toggle_multitool/Trigger()
+/datum/action/item_action/toggle_multitool/Trigger(trigger_flags)
if(!..())
return FALSE
if(target)
var/obj/item/multitool/ai_detect/M = target
- M.toggle_hud(owner)
+ M.toggle_detect(owner)
return TRUE
/obj/item/multitool/cyborg
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index f57115c6670b..8bf757098788 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -110,9 +110,9 @@
to_chat(usr,span_warning("You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
to_chat(pai,span_warning("Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
if(href_list["setlaws"])
- var/newlaws = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN)
- if(newlaws && pai)
- pai.add_supplied_law(0,newlaws)
+ var/newlaw = stripped_multiline_input(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1], MAX_MESSAGE_LEN)
+ if(newlaw && pai)
+ pai.set_supplied_laws(list(newlaw))
if(href_list["toggle_holo"])
if(pai.canholo)
to_chat(pai, span_userdanger("Your owner has disabled your holomatrix projectors!"))
diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm
index 7135a929bc4e..ce10148b852f 100644
--- a/code/game/objects/items/devices/powersink.dm
+++ b/code/game/objects/items/devices/powersink.dm
@@ -64,7 +64,7 @@
if(I.tool_behaviour == TOOL_SCREWDRIVER)
if(mode == DISCONNECTED)
var/turf/T = loc
- if(isturf(T) && !T.intact)
+ if(isturf(T) && T.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE)
attached = locate() in T
if(!attached)
to_chat(user, span_warning("This device must be placed over an exposed, powered cable node!"))
diff --git a/code/game/objects/items/devices/pressureplates.dm b/code/game/objects/items/devices/pressureplates.dm
index 516b1d299030..84414419d2fd 100644
--- a/code/game/objects/items/devices/pressureplates.dm
+++ b/code/game/objects/items/devices/pressureplates.dm
@@ -5,7 +5,6 @@
icon = 'icons/obj/puzzle_small.dmi'
item_state = "flash"
icon_state = "pressureplate"
- level = 1
layer = LOW_OBJ_LAYER
var/trigger_mob = TRUE
var/trigger_item = FALSE
@@ -23,6 +22,7 @@
var/can_trigger = TRUE
var/trigger_delay = 10
var/protected = FALSE
+ var/undertile_pressureplate = TRUE
/obj/item/pressure_plate/Initialize(mapload)
. = ..()
@@ -31,8 +31,10 @@
sigdev = new
sigdev.code = roundstart_signaller_code
sigdev.frequency = roundstart_signaller_freq
- if(isopenturf(loc))
- hide(TRUE)
+
+ if(undertile_pressureplate)
+ AddElement(/datum/element/undertile, tile_overlay = tile_overlay, use_anchor = TRUE)
+ RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(ToggleActive))
/obj/item/pressure_plate/Crossed(atom/movable/AM)
. = ..()
@@ -77,20 +79,8 @@
else
to_chat(user, span_notice("You turn [src] off."))
-/obj/item/pressure_plate/hide(yes)
- if(yes)
- invisibility = INVISIBILITY_MAXIMUM
- anchored = TRUE
- icon_state = null
- active = TRUE
- can_trigger = TRUE
- if(tile_overlay)
- loc.add_overlay(tile_overlay)
- else
- invisibility = initial(invisibility)
- anchored = FALSE
- icon_state = initial(icon_state)
- active = FALSE
- if(tile_overlay)
- loc.overlays -= tile_overlay
+///Called from COMSIG_OBJ_HIDE to toggle the active part, because yeah im not making a special exception on the element to support it
+/obj/item/pressure_plate/proc/ToggleActive(datum/source, underfloor_accessibility)
+ SIGNAL_HANDLER
+ active = underfloor_accessibility < UNDERFLOOR_VISIBLE
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 39633ed98f4d..0f2a2332cdd0 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -69,10 +69,7 @@ GENE SCANNER
return
var/list/t_ray_images = list()
for(var/obj/O in orange(distance, viewer) )
- if(O.level != 1)
- continue
-
- if(O.invisibility == INVISIBILITY_MAXIMUM || HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE))
+ if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE))
var/image/I = new(loc = get_turf(O))
var/mutable_appearance/MA = new(O)
MA.alpha = 128
@@ -80,7 +77,7 @@ GENE SCANNER
I.appearance = MA
t_ray_images += I
if(t_ray_images.len)
- flick_overlay(t_ray_images, list(viewer.client), flick_time)
+ flick_overlay_global(t_ray_images, list(viewer.client), flick_time)
/obj/item/healthanalyzer
name = "health analyzer"
@@ -427,7 +424,7 @@ GENE SCANNER
if(H.is_bleeding())
combined_msg += span_danger("Subject is losing blood at a rate of [H.get_total_bleed_rate() * H.physiology?.bleed_mod] cl per process!")
var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL(C))*100)
- var/blood_type = C.dna.blood_type
+ var/blood_type = C.dna.blood_type.name
if(blood_id != /datum/reagent/blood)//special blood substance
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
if(R)
diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm
index 3776d6dde260..83e89ccd8325 100644
--- a/code/game/objects/items/dice.dm
+++ b/code/game/objects/items/dice.dm
@@ -83,7 +83,8 @@
/obj/item/dice/d4/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/caltrop, 4)
+ // 1d4 damage
+ AddComponent(/datum/component/caltrop, min_damage = 1, max_damage = 4)
/obj/item/dice/d6
name = "d6"
diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm
index 1d49b434522e..0792c4085680 100644
--- a/code/game/objects/items/flamethrower.dm
+++ b/code/game/objects/items/flamethrower.dm
@@ -1,3 +1,6 @@
+/// How many joules of energy from reactions required to cause 1 damage
+#define JOULES_PER_DAMAGE 100000
+
/obj/item/flamethrower
name = "flamethrower"
desc = "You are a firestarter!"
@@ -54,7 +57,7 @@
if(M.is_holding(src))
location = M.loc
if(isturf(location)) //start a fire if possible
- igniter.flamethrower_process(location)
+ open_flame(igniter.heat)
/obj/item/flamethrower/update_overlays()
@@ -199,33 +202,21 @@
if(!istype(target))
return FALSE
- var/ratio_removed = 1
- if(!release_all)
- ratio_removed = min(ptank.distribute_pressure, ptank.air_contents.return_pressure()) / ptank.air_contents.return_pressure()
+ var/ratio_removed = min(ptank.distribute_pressure, ptank.air_contents.return_pressure()) / ptank.air_contents.return_pressure()
+
var/datum/gas_mixture/fuel_mix = ptank.air_contents.remove_ratio(ratio_removed)
+ var/datum/gas_mixture/turf_mix = target.return_air()
+
+ fuel_mix.merge(turf_mix.copy()) // copy air from the turf to do reactions with
+ fuel_mix.set_temperature(igniter.heat) // heat the contents
+
+ var/old_thermal_energy = fuel_mix.thermal_energy()
+ for(var/i in 1 to 10) // react a bunch of times on the target turf
+ if(!fuel_mix.react(target))
+ break // break the loop if it stops reacting
- // Return of the stimball flamethrower, wear radiation protection when using this or you're just as likely to die as your target
- if(fuel_mix.get_moles(GAS_PLASMA) >= NITRO_BALL_MOLES_REQUIRED && fuel_mix.get_moles(GAS_NITRIUM) >= NITRO_BALL_MOLES_REQUIRED && fuel_mix.get_moles(GAS_PLUOXIUM) >= NITRO_BALL_MOLES_REQUIRED)
- var/balls_shot = round(min(fuel_mix.get_moles(GAS_NITRIUM), fuel_mix.get_moles(GAS_PLUOXIUM), NITRO_BALL_MAX_REACT_RATE / NITRO_BALL_MOLES_REQUIRED))
- var/angular_increment = 360/balls_shot
- var/random_starting_angle = rand(0,360)
- for(var/i in 1 to balls_shot)
- target.fire_nuclear_particle((i*angular_increment+random_starting_angle))
- fuel_mix.adjust_moles(GAS_PLASMA, -balls_shot * NITRO_BALL_MOLES_REQUIRED) // No free extra damage for you, conservation of mass go brrrrr
-
- // Funny rad flamethrower go brrr
- if(fuel_mix.get_moles(GAS_TRITIUM)) // Tritium fires cause a bit of radiation
- radiation_pulse(target, min(fuel_mix.get_moles(GAS_TRITIUM), fuel_mix.get_moles(GAS_O2)/2) * FIRE_HYDROGEN_ENERGY_RELEASED / TRITIUM_BURN_RADIOACTIVITY_FACTOR)
-
- // 8 damage at 0.5 mole transfer or ~17 kPa release pressure
- // 16 damage at 1 mole transfer or ~35 kPa release pressure
- var/damage = fuel_mix.get_moles(GAS_PLASMA) * 16
- // harder to achieve than plasma
- damage += fuel_mix.get_moles(GAS_TRITIUM) * 24 // Lower damage than hydrogen, causes minor radiation
- damage += fuel_mix.get_moles(GAS_H2) * 32
- // Maximum damage restricted by the available oxygen, with a hard cap at 16
- var/datum/gas_mixture/turf_air = target.return_air()
- damage = min(damage, turf_air.get_moles(GAS_O2) + fuel_mix.get_moles(GAS_O2), max_damage) // capped by combined oxygen in the fuel mix and enviroment
+ // damage is based on the positive or negative energy of the reaction, with a cap
+ var/damage = min(abs(fuel_mix.thermal_energy() - old_thermal_energy) / JOULES_PER_DAMAGE, max_damage)
// If there's not enough fuel and/or oxygen to do more than 1 damage, shut itself off
if(damage < 1)
@@ -251,22 +242,40 @@
for(var/turf/T in turflist)
if(T == previousturf)
continue //so we don't burn the tile we be standin on
- for(var/obj/structure/blob/B in T)
+ var/cached_damage = 0
+
+ for(var/obj/structure/blob/blob in T)
// This is run before atmos checks because blob can be atmos blocking but we still want to hit them
// See /proc/default_ignite
- var/damage = process_fuel(T)
- if(!damage)
- break // Out of gas, stop running pointlessly
- B.take_damage(damage * 2, BURN, FIRE) // strong against blobs
+ if(!cached_damage)
+ cached_damage = process_fuel(T)
+ if(!lit)
+ break // stopped running, don't continue
+ if(QDELETED(blob))
+ continue
+ blob.take_damage(cached_damage * 2, BURN, FIRE) // strong against blobs
+
+ for(var/obj/structure/spacevine/vine in T)
+ // This is run before atmos checks because vines can be on top of a window or some other atmos-blocking structure
+ if(!cached_damage)
+ cached_damage = process_fuel(T)
+ if(!lit)
+ break // stopped running, don't continue
+ if(QDELETED(vine))
+ continue
+ vine.take_damage(cached_damage * 3, BURN, FIRE, TRUE) // very strong against vines
+
var/list/turfs_sharing_with_prev = previousturf.GetAtmosAdjacentTurfs(alldir=1)
if(!(T in turfs_sharing_with_prev))
break // Hit something that blocks atmos
- if(igniter)
- if(!igniter.ignite_turf(src,T))
- break // Out of gas, stop running pointlessly
- else
- if(!default_ignite(T))
- break // Out of gas, stop running pointlessly
+
+ if(!cached_damage)
+ cached_damage = process_fuel(T)
+ if(!lit)
+ break // stopped running, don't continue
+ if(!burn_atoms_on_turf(T, cached_damage))
+ break // Out of gas, stop running pointlessly
+
if(!sound_played) // play the sound once if we successfully ignite at least one thing
sound_played = TRUE
playsound(loc, pick(flame_sounds), 50, TRUE)
@@ -281,10 +290,9 @@
// /obj/structure/blob/normal
// Return value tells the parent whether to continue calculating the line
-/obj/item/flamethrower/proc/default_ignite(turf/target, release_all = FALSE)
- // do the fuel stuff
- var/damage = process_fuel(target, release_all)
- if(!damage)
+/obj/item/flamethrower/proc/burn_atoms_on_turf(turf/target, damage)
+ // no damage? don't continue
+ if(damage <= 0)
return FALSE
//Burn it
@@ -333,24 +341,17 @@
var/obj/projectile/P = hitby
if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(5))
owner.visible_message(span_danger("\The [attack_text] hits the fueltank on [owner]'s [name], rupturing it! What a shot!"))
- var/target_turf = get_turf(owner)
- igniter.ignite_turf(src,target_turf, release_all = TRUE)
- qdel(ptank)
- return 1 //It hit the flamethrower, not them
-
-
-/obj/item/assembly/igniter/proc/flamethrower_process(turf/open/location)
- location.hotspot_expose(700,2)
-
-/obj/item/assembly/igniter/proc/ignite_turf(obj/item/flamethrower/F, turf/open/location, release_all = FALSE)
- return F.default_ignite(location, release_all)
+ var/turf/target_turf = get_turf(owner)
+ burn_atoms_on_turf(target_turf, process_fuel(target_turf))
+ return TRUE //It hit the flamethrower, not them
+ return ..()
///////////////////// Flamethrower as an energy weapon /////////////////////
// Currently used exclusively in /obj/item/gun/energy/printer/flamethrower
/obj/item/ammo_casing/energy/flamethrower
projectile_type = /obj/projectile/bullet/incendiary/flamethrower
select_name = "fire"
- fire_sound = null
+ fire_sound = 'sound/weapons/flamethrower1.ogg'
firing_effect_type = null
e_cost = 50
@@ -360,4 +361,6 @@
damage = 0
sharpness = SHARP_NONE
range = 6
- penetration_type = 2
+ penetration_flags = PENETRATE_OBJECTS | PENETRATE_MOBS
+
+#undef JOULES_PER_DAMAGE
diff --git a/code/game/objects/items/gems.dm b/code/game/objects/items/gems.dm
index a1bb15b13322..7b9577220a97 100644
--- a/code/game/objects/items/gems.dm
+++ b/code/game/objects/items/gems.dm
@@ -110,17 +110,10 @@
light_power = 1
light_color = "#b714cc"
- var/obj/item/gps/internal //stolen from the world anvil
/obj/item/gem/purple/Initialize(mapload)
. = ..()
- internal = new /obj/item/gps/internal/purple(src)
-
-/obj/item/gps/internal/purple
- icon_state = null
- gpstag = "Harmonic Signal"
- desc = "It's ringing."
- invisibility = 100
+ AddComponent(/datum/component/gps,"Harmonic Signal")
/obj/item/gem/amber
name = "draconic amber"
diff --git a/code/game/objects/items/granters/crafting/desserts.dm b/code/game/objects/items/granters/crafting/desserts.dm
index 189fbb0e2d22..3dd9afe81879 100644
--- a/code/game/objects/items/granters/crafting/desserts.dm
+++ b/code/game/objects/items/granters/crafting/desserts.dm
@@ -12,7 +12,7 @@
remarks = list(
"So that is how icing is made!",
"Placing fruit on top? How simple...",
- "Huh layering cake seems harder then this...",
+ "Huh, layering cake seems harder than this...",
"This book smells like candy.",
"A clown must have made this page, or they forgot to spell check it before printing...",
"Wait, a way to cook slime to be safe?",
diff --git a/code/game/objects/items/granters/magic/forcewall.dm b/code/game/objects/items/granters/magic/forcewall.dm
index bf1228d72618..b0a57e4b38a5 100644
--- a/code/game/objects/items/granters/magic/forcewall.dm
+++ b/code/game/objects/items/granters/magic/forcewall.dm
@@ -10,7 +10,7 @@
"This is some surprisingly strong magic to create a wall nobody can pass through...",
"Why the dumb stance? It's just a flick of the hand...",
"Why are the pages so hard to turn, is this even paper?",
- "I can't mo Oh, i'm fine...",
+ "I can't mo Oh, I'm fine...",
)
/obj/item/book/granter/action/spell/forcewall/recoil(mob/living/user)
diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm
index 2423c687ef52..477af55a02f6 100644
--- a/code/game/objects/items/grenades/chem_grenade.dm
+++ b/code/game/objects/items/grenades/chem_grenade.dm
@@ -342,7 +342,7 @@
/obj/item/grenade/chem_grenade/radiation
name = "Rad Bomb"
- desc = "the best grenade to irridiate the fuck out of someone"
+ desc = "The best grenade to irradiate the fuck out of someone."
stage = GRENADE_READY
/obj/item/grenade/chem_grenade/radiation/Initialize(mapload)
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index f7e48ed40cc2..18824aa5da65 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -1,6 +1,6 @@
/obj/item/restraints
icon = 'icons/obj/handcuffs.dmi'
- breakouttime = 600
+ breakouttime = 60 SECONDS
var/break_strength = 2 // Minimum strength required for a holopara to break it
/obj/item/restraints/suicide_act(mob/living/carbon/user)
@@ -37,7 +37,7 @@
throw_speed = 3
throw_range = 5
materials = list(/datum/material/iron=500)
- breakouttime = 600 //Deciseconds = 60s = 1 minute
+ breakouttime = 60 SECONDS // add SECONDS or another unit becuase it will think deciseconds (100ds= 10s)
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 50, ACID = 50)
break_strength = 4
var/cuffsound = 'sound/weapons/handcuffs.ogg'
@@ -111,6 +111,10 @@
qdel(src)
return
+/obj/item/restraints/handcuffs/energy/used/swarmer //energy cuffs are in abductor, why would you do this
+ breakouttime= 20 SECONDS // you already get teleported across the map
+ trashtype = /obj/item/restraints/handcuffs/energy/used
+
/obj/item/restraints/handcuffs/cable/sinew
name = "sinew restraints"
desc = "A pair of restraints fashioned from long strands of flesh."
@@ -232,7 +236,7 @@
/obj/item/restraints/handcuffs/fake
name = "fake handcuffs"
desc = "Fake handcuffs meant for gag purposes."
- breakouttime = 10 //Deciseconds = 1s
+ breakouttime = 1 SECONDS
break_strength = 1
//Legcuffs
@@ -249,7 +253,7 @@
throwforce = 0
w_class = WEIGHT_CLASS_NORMAL
slowdown = 7
- breakouttime = 300 //Deciseconds = 30s = 0.5 minute
+ breakouttime = 30 SECONDS
break_strength = 4
/obj/item/restraints/legcuffs/beartrap
@@ -264,7 +268,11 @@
/obj/item/restraints/legcuffs/beartrap/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(trap_stepped_on),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/item/restraints/legcuffs/beartrap/update_icon_state()
. = ..()
@@ -306,6 +314,55 @@
update_appearance(UPDATE_ICON)
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
+/obj/item/restraints/legcuffs/beartrap/proc/trap_stepped_on(datum/source, atom/movable/entering, ...)
+ SIGNAL_HANDLER
+
+ spring_trap(entering)
+
+/**
+ * Tries to spring the trap on the target movable.
+ *
+ * This proc is safe to call without knowing if the target is valid or if the trap is armed.
+ *
+ * Does not trigger on tiny mobs.
+ * If ignore_movetypes is FALSE, does not trigger on floating / flying / etc. mobs.
+ */
+/obj/item/restraints/legcuffs/beartrap/proc/spring_trap(atom/movable/target, ignore_movetypes = FALSE)
+ if(!armed || !isturf(loc) || !isliving(target))
+ return
+
+ var/mob/living/victim = target
+ if(istype(victim.buckled, /obj/vehicle))
+ var/obj/vehicle/ridden_vehicle = victim.buckled
+ if(!ridden_vehicle.are_legs_exposed) //close the trap without injuring/trapping the rider if their legs are inside the vehicle at all times.
+ close_trap()
+ ridden_vehicle.visible_message(span_danger("[ridden_vehicle] triggers \the [src]."))
+ return
+
+ //don't close the trap if they're as small as a mouse
+ if(victim.mob_size <= MOB_SIZE_TINY)
+ return
+ if(!ignore_movetypes && (victim.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
+ return
+
+ close_trap()
+ if(ignore_movetypes)
+ victim.visible_message(span_danger("\The [src] ensnares [victim]!"), \
+ span_userdanger("\The [src] ensnares you!"))
+ else
+ victim.visible_message(span_danger("[victim] triggers \the [src]."), \
+ span_userdanger("You trigger \the [src]!"))
+ var/def_zone = BODY_ZONE_CHEST
+ if(iscarbon(victim) && victim.body_position == STANDING_UP)
+ var/mob/living/carbon/carbon_victim = victim
+ def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ if(!carbon_victim.legcuffed && carbon_victim.num_legs >= 2) //beartrap can't cuff your leg if there's already a beartrap or legcuffs, or you don't have two legs.
+ INVOKE_ASYNC(carbon_victim, TYPE_PROC_REF(/mob/living/carbon, equip_to_slot), src, ITEM_SLOT_LEGCUFFED)
+ SSblackbox.record_feedback("tally", "handcuffs", 1, type)
+
+ victim.apply_damage(trap_damage, BRUTE, def_zone)
+
+
/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj)
if(armed && isturf(loc))
if(isliving(AM))
@@ -347,10 +404,10 @@
armed = 1
icon_state = "e_snare"
trap_damage = 0
- breakouttime = 30
+ breakouttime = 3 SECONDS
item_flags = DROPDEL
flags_1 = NONE
- break_strength = 2
+ break_strength = 2
/obj/item/restraints/legcuffs/beartrap/energy/Initialize(mapload)
. = ..()
@@ -365,7 +422,7 @@
Crossed(user) //honk
/obj/item/restraints/legcuffs/beartrap/energy/cyborg
- breakouttime = 20 // Cyborgs shouldn't have a strong restraint
+ breakouttime = 2 SECONDS // Cyborgs shouldn't have a strong restraint
/obj/item/restraints/legcuffs/bola
name = "bola"
@@ -375,9 +432,9 @@
item_state = "bola"
lefthand_file = 'icons/mob/inhands/weapons/thrown_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/thrown_righthand.dmi'
- breakouttime = 35//easy to apply, easy to break out of
+ breakouttime = 3.5 SECONDS //easy to apply, easy to break out of
gender = NEUTER
- break_strength = 3
+ break_strength = 3
var/immobilize = 0
/obj/item/restraints/legcuffs/bola/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, quickstart = TRUE)
@@ -413,9 +470,9 @@
desc = "A strong bola, made with a long steel chain. It looks heavy, enough so that it could trip somebody."
icon_state = "bola_r"
item_state = "bola_r"
- breakouttime = 70
+ breakouttime = 7 SECONDS
immobilize = 20
- break_strength = 4
+ break_strength = 4
/obj/item/restraints/legcuffs/bola/watcher //tribal bola for tribal lizards
name = "watcher Bola"
@@ -423,7 +480,7 @@
icon_state = "bola_watcher"
icon_state_preview = "bola_watcher_preview"
item_state = "bola_watcher"
- breakouttime = 45
+ breakouttime = 4.5 SECONDS
/obj/item/restraints/legcuffs/bola/energy //For Security
name = "energy bola"
@@ -448,7 +505,7 @@
icon_state = "gonbola"
icon_state_preview = "gonbola_preview"
item_state = "bola_r"
- breakouttime = 300
+ breakouttime = 30 SECONDS
slowdown = 0
var/datum/status_effect/gonbolaPacify/effectReference
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 856dd4bf9387..593983b469fc 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -352,13 +352,18 @@
attack_verb = list("smashed", "slammed", "whacked", "thwacked")
w_class = WEIGHT_CLASS_BULKY
damtype = STAMINA
- force = 15
+ force = 18
block_chance = 40
slot_flags = ITEM_SLOT_BACK
sharpness = SHARP_NONE
menutab = MENU_WEAPON
additional_desc = "The weapon of choice for a devout monk. Block incoming blows while striking weak points until your opponent is too exhausted to continue."
+/obj/item/nullrod/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ if(attack_type == PROJECTILE_ATTACK)
+ final_block_chance = 0 //Don't bring a stick to a gunfight
+ return ..()
+
/obj/item/nullrod/tribal_knife
name = "arrhythmic knife"
desc = "They say fear is the true mind killer, but stabbing them in the head works too. Honour compels you to not sheathe it once drawn."
@@ -647,7 +652,7 @@
icon_state = "fedora"
item_state = "fedora"
slot_flags = ITEM_SLOT_HEAD
- icon = 'icons/obj/clothing/hats.dmi'
+ icon = 'icons/obj/clothing/hats/hats.dmi'
force = 0
throw_speed = 4
throw_range = 7
diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm
index 0b41e021e48e..b5f37de5e195 100644
--- a/code/game/objects/items/manuals.dm
+++ b/code/game/objects/items/manuals.dm
@@ -10,7 +10,7 @@
/obj/item/book/manual/ripley_build_and_repair
name = "APLU \"Ripley\" Construction and Operation Manual"
icon_state ="book"
- author = "Weyland-Yutani Corp"
+ author = "Sano-Waltfield Industries"
title = "APLU \"Ripley\" Construction and Operation Manual"
dat = {"
@@ -24,7 +24,7 @@
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index 4a3f74dea584..f6e5c5423699 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -727,14 +727,14 @@
held_sausage = target
else
to_chat(user, span_warning("[target] doesn't seem to want to get on [src]!"))
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/melee/roastingstick/attack_hand(mob/user)
..()
if (held_sausage)
user.put_in_hands(held_sausage)
held_sausage = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/melee/roastingstick/update_overlays()
. = ..()
@@ -770,7 +770,7 @@
if (istype(target, /obj/singularity) && get_dist(user, target) < 10)
to_chat(user, "You send [held_sausage] towards [target].")
playsound(src, 'sound/items/rped.ogg', 50, 1)
- beam = user.Beam(target,icon_state="rped_upgrade",time=100)
+ beam = user.Beam(target, icon_state = "rped_upgrade", time = 10 SECONDS)
else if (user.Adjacent(target))
to_chat(user, "You extend [src] towards [target].")
playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1)
diff --git a/code/game/objects/items/pinpointer.dm b/code/game/objects/items/pinpointer.dm
index af4075932534..6e9906b343d9 100644
--- a/code/game/objects/items/pinpointer.dm
+++ b/code/game/objects/items/pinpointer.dm
@@ -43,13 +43,13 @@
else
target = null
STOP_PROCESSING(SSfastprocess, src)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/pinpointer/process()
if(!active)
return PROCESS_KILL
scan_for_target()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/pinpointer/proc/scan_for_target()
return
diff --git a/code/game/objects/items/plushes.dm b/code/game/objects/items/plushes.dm
index 8994c74e330b..d6304328aec2 100644
--- a/code/game/objects/items/plushes.dm
+++ b/code/game/objects/items/plushes.dm
@@ -385,7 +385,7 @@
var/obj/item/toy/plush/narplush/clash_target
gender = MALE //he's a boy, right?
-/obj/item/toy/plush/plushvar/Moved()
+/obj/item/toy/plush/plushvar/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(clash_target)
return
@@ -471,7 +471,7 @@
var/clashing
gender = FEMALE //it's canon if the toy is
-/obj/item/toy/plush/narplush/Moved()
+/obj/item/toy/plush/narplush/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
var/obj/item/toy/plush/plushvar/P = locate() in range(1, src)
if(P && istype(P.loc, /turf/open) && !P.clash_target && !clashing)
diff --git a/code/game/objects/items/puzzle_pieces.dm b/code/game/objects/items/puzzle_pieces.dm
index 977744dfae41..b098d3320d53 100644
--- a/code/game/objects/items/puzzle_pieces.dm
+++ b/code/game/objects/items/puzzle_pieces.dm
@@ -129,9 +129,15 @@
trigger_delay = 10
protected = TRUE
resistance_flags = INDESTRUCTIBLE | FIRE_PROOF | ACID_PROOF | LAVA_PROOF
+ undertile_pressureplate = FALSE
var/reward = /obj/item/reagent_containers/food/snacks/cookie
var/claimed = FALSE
+/obj/item/pressure_plate/hologrid/Initialize(mapload)
+ . = ..()
+ if(undertile_pressureplate)
+ AddElement(/datum/element/undertile, tile_overlay = tile_overlay, use_anchor = FALSE) //we remove use_anchor here, so it ALWAYS stays anchored
+
/obj/item/pressure_plate/hologrid/examine(mob/user)
. = ..()
if(claimed)
diff --git a/code/game/objects/items/remote_light_switch.dm b/code/game/objects/items/remote_light_switch.dm
index 6106ad2b06ed..681e2b222d49 100644
--- a/code/game/objects/items/remote_light_switch.dm
+++ b/code/game/objects/items/remote_light_switch.dm
@@ -1,6 +1,6 @@
/obj/item/rls
name = "Rapid Light Switch (RLS)"
- desc = "A device used to remotley switch lighting."
+ desc = "A device used to remotely switch lighting."
icon = 'icons/obj/device.dmi'
icon_state = "multitool_red"
item_state = "multitool"
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index c7baf90124e5..74a37d212cb2 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -484,6 +484,7 @@
icecream.add_ice_cream("vanilla")
icecream.desc = "Eat the ice cream."
user.visible_message(span_notice("[src] launches a [snack.name] at [target]!"))
+ user.newtonian_move(get_dir(target, user)) // For no gravity.
else if(user.Adjacent(target) && is_allowed(target, user))
COOLDOWN_START(src, last_snack_disp, cooldown)
snack = new selected_snack(get_turf(target))
@@ -754,7 +755,7 @@
name = "\proper night vision meson vision"
icon = 'icons/obj/clothing/glasses.dmi'
icon_state = "nvgmeson"
- sight_mode = BORGMESON_NIGHTVISION
+ sight_mode = BORGMESON
/obj/item/borg/sight/material
name = "\proper material vision"
@@ -925,16 +926,24 @@
/obj/item/stock_parts,
/obj/item/tank/internals,
/obj/item/conveyor_switch_construct,
- /obj/item/stack/conveyor
+ /obj/item/stack/conveyor,
+ /obj/item/server_rack,
+ /obj/item/ai_cpu,
)
/obj/item/borg/gripper/medical
name = "medical gripper"
desc = "A simple grasping tool for interacting with various medical related items."
can_hold = list(
- /obj/item/reagent_containers/glass/bottle, // Bottles & Vials
+ /obj/item/reagent_containers/medspray, // Without this, just syringe the content out and put it into a beaker to get around it.
+ /obj/item/reagent_containers/blood, // To insert blood bags into IV drips.
+ /obj/item/reagent_containers/food/snacks/lollipop, // Given that they have a snack dispenser, might as well.
+ // All chemistry specific concerns:
+ /obj/item/reagent_containers/glass/bottle,
/obj/item/reagent_containers/glass/beaker,
- /obj/item/reagent_containers/blood // Blood Bags.
+ /obj/item/reagent_containers/pill, // Includes patches... because they're are pills too?
+ /obj/item/reagent_containers/gummy,
+ /obj/item/storage/bag/chemistry // QOL for moving a billion pills into the chemfridge.
)
/obj/item/borg/gripper/service
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index f99ee5170004..78ba724345ed 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -296,7 +296,7 @@
if(user.mind.assigned_role == "Roboticist") // RD gets nothing
SSachievements.unlock_achievement(/datum/achievement/roboborg, user.client)
- if(M.laws && M.laws.id != DEFAULT_AI_LAWID && M.override_cyborg_laws)
+ if(M.laws && M.laws.modified && M.override_cyborg_laws)
aisync = FALSE
lawsync = FALSE
O.laws = M.laws
@@ -315,9 +315,15 @@
O.set_connected_ai(forced_ai)
if(!lawsync)
O.lawupdate = 0
- if(M.laws.id == DEFAULT_AI_LAWID)
+ if(!M.laws.modified)
+ // Give the non-modified laws which is visible on the MMI.
+ O.laws = M.laws
+ M.laws.associate(O)
+ else if(!M.override_cyborg_laws) // MMI's laws were changed. Do not want to upload them if we say so.
+ // Give random default lawset.
O.make_laws()
- to_chat(user,span_warning("Any laws uploaded to this MMI have not been transferred!"))
+ // Obvious warning that their modified laws didn't get passed on.
+ to_chat(user, span_warning("Any laws uploaded to this MMI have not been transferred!"))
SSticker.mode.remove_antag_for_borging(BM.mind)
if(!istype(M.laws, /datum/ai_laws/ratvar))
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 5dfacee83892..ccf4e791acdf 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -120,7 +120,7 @@
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/stack/sheet/bluespace_crystal/attack_hand(mob/user)
if(user.get_inactive_held_item() == src)
- if(zero_amount())
+ if(is_zero_amount(delete_if_zero = TRUE))
return
var/BC = new crystal_type(src)
user.put_in_hands(BC)
diff --git a/code/game/objects/items/stacks/cash.dm b/code/game/objects/items/stacks/cash.dm
index 65ce5efc26b9..96410c42ad9a 100644
--- a/code/game/objects/items/stacks/cash.dm
+++ b/code/game/objects/items/stacks/cash.dm
@@ -16,23 +16,23 @@
/obj/item/stack/spacecash/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_DESC)
+ update_desc()
-/obj/item/stack/spacecash/update_desc(updates=ALL)
+/obj/item/stack/spacecash/update_desc()
. = ..()
var/total_worth = get_item_credit_value()
- desc = "It's worth [total_worth] credit[( total_worth > 1 ) ? "s" : ""]"
+ desc = "It's worth [total_worth] credit[(total_worth > 1) ? "s" : null] in total."
/obj/item/stack/spacecash/get_item_credit_value()
return (amount*value)
/obj/item/stack/spacecash/merge(obj/item/stack/S)
. = ..()
- update_appearance(UPDATE_DESC)
+ update_desc()
-/obj/item/stack/spacecash/use(used, transfer = FALSE)
+/obj/item/stack/spacecash/use(used, transfer = FALSE, check = TRUE)
. = ..()
- update_appearance(UPDATE_DESC)
+ update_desc()
/obj/item/stack/spacecash/c1
icon_state = "spacecash"
diff --git a/code/game/objects/items/stacks/license_plates.dm b/code/game/objects/items/stacks/license_plates.dm
index ece53770f9fa..92f7edc922ee 100644
--- a/code/game/objects/items/stacks/license_plates.dm
+++ b/code/game/objects/items/stacks/license_plates.dm
@@ -1,6 +1,6 @@
/obj/item/stack/license_plates
name = "invalid plate"
- desc = "someone fucked up"
+ desc = "Someone fucked up."
icon = 'icons/obj/machines/prison.dmi'
icon_state = "empty_plate"
novariants = FALSE
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index 5c431853706d..c53825bafe64 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -170,7 +170,7 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \
/obj/item/stack/sheet/rglass/cyborg/get_amount()
return min(round(source.energy / metcost), round(glasource.energy / glacost))
-/obj/item/stack/sheet/rglass/cyborg/use(used, transfer = FALSE) // Requires special checks, because it uses two storages
+/obj/item/stack/sheet/rglass/cyborg/use(used, transfer = FALSE, check = TRUE) // Requires special checks, because it uses two storages
if(source.use_charge(used * metcost) && glasource.use_charge(used * glacost))
return TRUE
return FALSE
@@ -280,8 +280,11 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
/obj/item/shard/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/caltrop, force)
- AddComponent(/datum/component/butchering, 150, 65)
+ AddComponent(/datum/component/caltrop, min_damage = force)
+ AddComponent(/datum/component/butchering, \
+ _speed = 15 SECONDS, \
+ _effectiveness = 65, \
+ )
icon_state = pick("large", "medium", "small")
switch(icon_state)
if("small")
@@ -298,6 +301,10 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
var/matrix/M = matrix(transform)
M.Turn(rand(-170, 170))
transform = M
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/item/shard/afterattack(atom/A as mob|obj, mob/user, proximity)
. = ..()
@@ -334,15 +341,12 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
qdel(src)
return
-/obj/item/shard/Crossed(atom/movable/AM)
+/obj/item/shard/proc/on_entered(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
if(isliving(AM))
var/mob/living/L = AM
- if(!(L.is_flying() || L.buckled))
- if(HAS_TRAIT(L, TRAIT_LIGHT_STEP))
- playsound(loc, 'sound/effects/glass_step.ogg', 30, TRUE)
- else
- playsound(loc, 'sound/effects/glass_step.ogg', 50, TRUE)
- return ..()
+ if(!(L.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || L.buckled)
+ playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE)
/obj/item/shard/plasma
name = "purple shard"
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index b28344641425..0ab452aad6df 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -58,7 +58,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
new/datum/stack_recipe("floor tile", /obj/item/stack/tile/plasteel, 1, 4, 20), \
new/datum/stack_recipe("metal rod", /obj/item/stack/rods, 1, 2, 60), \
null, \
- new/datum/stack_recipe("wall girders", /obj/structure/girder, 2, time = 40, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("wall girders", /obj/structure/girder, 2, time = 40, one_per_turf = TRUE, on_floor = TRUE, trait_booster = TRAIT_QUICK_BUILD, trait_modifier = 0.75), \
null, \
new/datum/stack_recipe("computer frame", /obj/structure/frame/computer, 5, time = 25, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("machine frame", /obj/structure/frame/machine, 5, time = 25, one_per_turf = TRUE, on_floor = TRUE), \
@@ -907,7 +907,7 @@ GLOBAL_LIST_INIT(cheese_recipes, list (
merge_type = /obj/item/stack/sheet/ruinous_metal
GLOBAL_LIST_INIT(ruinous_metal_recipes, list (
- new/datum/stack_recipe("altar of the gods", /obj/structure/altar_of_gods, 6, one_per_turf = 1, on_floor = 1, time = 40), \
+ new/datum/stack_recipe("altar of the gods", /obj/structure/table/altar_of_gods, 6, one_per_turf = 1, on_floor = 1, time = 40), \
new/datum/stack_recipe("holy fountain", /obj/structure/holyfountain, 3, one_per_turf = 1, on_floor = 1, time = 40 )))
/obj/item/stack/sheet/ruinous_metal/Initialize(mapload, new_amount, merge = TRUE)
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index c4b03c4c9928..ced028084977 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -12,28 +12,33 @@
icon = 'yogstation/icons/obj/stack_objects.dmi' // yogs -- use yog icons instead of tg
gender = PLURAL
max_integrity = 100
- /// List of recipes
+ /// A list to all recipies this stack item can create.
var/list/datum/stack_recipe/recipes
- /// The name without the s
+ /// What's the name of just 1 of this stack. You have a stack of leather, but one piece of leather
var/singular_name
- /// Amount in the stack
+ /// How much is in this stack?
var/amount = 1
- /// Max amount in the stack | stack recipes initialisation, param "max_res_amount" must be equal to this max_amount
+ /// How much is allowed in this stack?
+ // Also see stack recipes initialisation. "max_res_amount" must be equal to this max_amount
var/max_amount = 50
- /// If its a module item for a cyborg
- var/is_cyborg = 0
- /// Used for "recharging" of the material
+ /// If TRUE, this stack is a module used by a cyborg (doesn't run out like normal / etc)
+ var/is_cyborg = FALSE
+ /// Related to above. If present, the energy we draw from when using stack items, for cyborgs
var/datum/robot_energy_storage/source
- /// How much energy does it cost
+ /// Related to above. How much energy it costs from storage to use stack items
var/cost = 1
/// This path and its children should merge with this stack, defaults to src.type
var/merge_type = null
- /// Does it merge strictly only with its type
- var/strict = FALSE
- /// The weight class the stack should have at amount > 2/3rds max_amount
+ /// The weight class the stack has at amount > 2/3rds max_amount
var/full_w_class = WEIGHT_CLASS_NORMAL
/// Determines whether the item should update it's sprites based on amount.
var/novariants = TRUE
+ /// List that tells you how much is in a single unit.
+ var/list/mats_per_unit
+ /// Datum material type that this stack is made of
+ var/material_type
+ /// Does it merge strictly only with its type
+ var/strict = FALSE
//NOTE: When adding grind_results, the amounts should be for an INDIVIDUAL ITEM - these amounts will be multiplied by the stack size in on_grind()
var/obj/structure/table/tableVariant // we tables now (stores table variant to be built from this stack)
var/mats_per_stack = 0
@@ -50,8 +55,7 @@
return
return TRUE
-/obj/item/stack/Initialize(mapload, new_amount, merge = TRUE)
- . = ..()
+/obj/item/stack/Initialize(mapload, new_amount, merge = TRUE, list/mat_override=null, mat_amt=1)
if(new_amount != null)
amount = new_amount
while(amount > max_amount)
@@ -59,12 +63,61 @@
new type(loc, max_amount, FALSE)
if(!merge_type)
merge_type = type
+
+ // if(LAZYLEN(mat_override))
+ // set_mats_per_unit(mat_override, mat_amt)
+ // else if(LAZYLEN(mats_per_unit))
+ // set_mats_per_unit(mats_per_unit, 1)
+ // else if(LAZYLEN(custom_materials))
+ // set_mats_per_unit(custom_materials, amount ? 1/amount : 1)
+
+ . = ..()
if(merge)
- for(var/obj/item/stack/S in loc)
- if(S.merge_type == merge_type)
- merge(S)
+ for(var/obj/item/stack/item_stack in loc)
+ if(item_stack == src)
+ continue
+ if(can_merge(item_stack))
+ INVOKE_ASYNC(src, PROC_REF(merge_without_del), item_stack)
+ if(is_zero_amount(delete_if_zero = FALSE))
+ return INITIALIZE_HINT_QDEL
+
+ // recipes = get_main_recipes().Copy()
+ // if(material_type)
+ // var/datum/material/what_are_we_made_of = GET_MATERIAL_REF(material_type) //First/main material
+ // for(var/category in what_are_we_made_of.categories)
+ // switch(category)
+ // if(MAT_CATEGORY_BASE_RECIPES)
+ // recipes |= SSmaterials.base_stack_recipes.Copy()
+ // if(MAT_CATEGORY_RIGID)
+ // recipes |= SSmaterials.rigid_stack_recipes.Copy()
+
update_weight()
- update_appearance(UPDATE_ICON)
+ update_appearance()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_movable_entered_occupied_turf),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/** Sets the amount of materials per unit for this stack.
+ *
+ * Arguments:
+ * - [mats][/list]: The value to set the mats per unit to.
+ * - multiplier: The amount to multiply the mats per unit by. Defaults to 1.
+ */
+/obj/item/stack/proc/set_mats_per_unit(list/mats, multiplier=1)
+ //mats_per_unit = SSmaterials.FindOrCreateMaterialCombo(mats, multiplier)
+ update_custom_materials()
+
+/** Updates the custom materials list of this stack.
+ */
+/obj/item/stack/proc/update_custom_materials()
+ set_custom_materials(mats_per_unit, amount, is_update=TRUE)
+
+/**
+ * Override to make things like metalgen accurately set custom materials
+ */
+/obj/item/stack/set_custom_materials(list/materials, multiplier=1, is_update=FALSE)
+ return is_update ? ..() : set_mats_per_unit(materials, multiplier/(amount || 1))
/obj/item/stack/proc/update_weight()
if(amount <= (max_amount * (1/3)))
@@ -217,7 +270,7 @@
var/turf/T = usr.drop_location()
if(!isturf(T))
return
- T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR)
+ T.place_on_top(R.result_type, flags = CHANGETURF_INHERIT_AIR)
else
O = new R.result_type(usr.drop_location())
if(O)
@@ -303,16 +356,18 @@
return TRUE
/obj/item/stack/use(used, transfer = FALSE, check = TRUE) // return 0 = borked; return 1 = had enough
- if(check && zero_amount())
+ if(check && is_zero_amount(delete_if_zero = TRUE))
return FALSE
if (is_cyborg)
return source.use_charge(used * cost)
if (amount < used)
return FALSE
amount -= used
- if(check)
- zero_amount()
- update_appearance(UPDATE_ICON)
+ if(check && is_zero_amount(delete_if_zero = TRUE))
+ return TRUE
+ if(length(mats_per_unit))
+ update_custom_materials()
+ update_appearance()
update_weight()
return TRUE
@@ -330,55 +385,124 @@
return TRUE
-/obj/item/stack/proc/zero_amount()
+/**
+ * Returns TRUE if the item stack is the equivalent of a 0 amount item.
+ *
+ * Also deletes the item if delete_if_zero is TRUE and the stack does not have
+ * is_cyborg set to true.
+ */
+/obj/item/stack/proc/is_zero_amount(delete_if_zero = TRUE)
if(is_cyborg)
return source.energy < cost
if(amount < 1)
- qdel(src)
- return 1
- return 0
+ if(delete_if_zero)
+ qdel(src)
+ return TRUE
+ return FALSE
-/obj/item/stack/proc/add(amount)
- if (is_cyborg)
- source.add_charge(amount * cost)
+/** Adds some number of units to this stack.
+ *
+ * Arguments:
+ * - _amount: The number of units to add to this stack.
+ */
+/obj/item/stack/proc/add(_amount)
+ if(is_cyborg)
+ source.add_charge(_amount * cost)
else
- src.amount += amount
- update_appearance(UPDATE_ICON)
+ amount += _amount
+ if(length(mats_per_unit))
+ update_custom_materials()
+ update_appearance()
update_weight()
-/obj/item/stack/proc/merge(obj/item/stack/S) //Merge src into S, as much as possible
- if(QDELETED(S) || QDELETED(src) || S == src) //amusingly this can cause a stack to consume itself, let's not allow that.
- return
+/** Checks whether this stack can merge itself into another stack.
+ *
+ * Arguments:
+ * - [check][/obj/item/stack]: The stack to check for mergeability.
+ * - [inhand][boolean]: Whether or not the stack to check should act like it's in a mob's hand.
+ */
+/obj/item/stack/proc/can_merge(obj/item/stack/check, inhand = FALSE)
+ // We don't only use istype here, since that will match subtypes, and stack things that shouldn't stack
+ if(!istype(check, merge_type) || check.merge_type != merge_type)
+ return FALSE
+ if(mats_per_unit ~! check.mats_per_unit) // ~! in case of lists this operator checks only keys, but not values
+ return FALSE
+ if(is_cyborg) // No merging cyborg stacks into other stacks
+ return FALSE
+ if(ismob(loc) && !inhand) // no merging with items that are on the mob
+ return FALSE
+ if(istype(loc, /obj/machinery)) // no merging items in machines that aren't both in componentparts
+ var/obj/machinery/machine = loc
+ if(!(src in machine.component_parts) || !(check in machine.component_parts))
+ return FALSE
+ if(SEND_SIGNAL(src, COMSIG_STACK_CAN_MERGE, check, inhand) & CANCEL_STACK_MERGE)
+ return FALSE
+ return TRUE
+
+
+/**
+ * Merges as much of src into target_stack as possible. If present, the limit arg overrides target_stack.max_amount for transfer.
+ *
+ * This calls use() without check = FALSE, preventing the item from qdeling itself if it reaches 0 stack size.
+ *
+ * As a result, this proc can leave behind a 0 amount stack.
+ */
+/obj/item/stack/proc/merge_without_del(obj/item/stack/target_stack, limit)
+ // Cover edge cases where multiple stacks are being merged together and haven't been deleted properly.
+ // Also cover edge case where a stack is being merged into itself, which is supposedly possible.
+ if(QDELETED(target_stack))
+ CRASH("Stack merge attempted on qdeleted target stack.")
+ if(QDELETED(src))
+ CRASH("Stack merge attempted on qdeleted source stack.")
+ if(target_stack == src)
+ CRASH("Stack attempted to merge into itself.")
+
var/transfer = get_amount()
- if(S.is_cyborg)
- transfer = min(transfer, round((S.source.max_energy - S.source.energy) / S.cost))
+ if(target_stack.is_cyborg)
+ transfer = min(transfer, round((target_stack.source.max_energy - target_stack.source.energy) / target_stack.cost))
else
- transfer = min(transfer, S.max_amount - S.amount)
+ transfer = min(transfer, (limit ? limit : target_stack.max_amount) - target_stack.amount)
if(pulledby)
- pulledby.start_pulling(S)
- S.copy_evidences(src)
- use(transfer, TRUE)
- S.add(transfer)
+ pulledby.start_pulling(target_stack)
+ target_stack.copy_evidences(src)
+ use(transfer, transfer = TRUE, check = FALSE)
+ target_stack.add(transfer)
+ if(target_stack.mats_per_unit != mats_per_unit) // We get the average value of mats_per_unit between two stacks getting merged
+ var/list/temp_mats_list = list() // mats_per_unit is passed by ref into this coil, and that same ref is used in other places. If we didn't make a new list here we'd end up contaminating those other places, which leads to batshit behavior
+ for(var/mat_type in target_stack.mats_per_unit)
+ temp_mats_list[mat_type] = (target_stack.mats_per_unit[mat_type] * (target_stack.amount - transfer) + mats_per_unit[mat_type] * transfer) / target_stack.amount
+ target_stack.mats_per_unit = temp_mats_list
return transfer
-/obj/item/stack/Crossed(atom/movable/AM)
- if(strict && AM.type == merge_type)
- merge(AM)
- else if(!strict && istype(AM, merge_type) && !AM.throwing)
- merge(AM)
- . = ..()
+/**
+ * Merges as much of src into target_stack as possible. If present, the limit arg overrides target_stack.max_amount for transfer.
+ *
+ * This proc deletes src if the remaining amount after the transfer is 0.
+ */
+/obj/item/stack/proc/merge(obj/item/stack/target_stack, limit)
+ . = merge_without_del(target_stack, limit)
+ is_zero_amount(delete_if_zero = TRUE)
-/obj/item/stack/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- if(strict && AM.type == merge_type)
- merge(AM)
- else if(!strict && istype(AM, merge_type) && !AM.throwing)
- merge(AM)
+// Signal handler for connect_loc element. Called when a movable enters the turf we're currently occupying. Merges if possible.
+/obj/item/stack/proc/on_movable_entered_occupied_turf(datum/source, atom/movable/arrived)
+ SIGNAL_HANDLER
+
+ // Edge case. This signal will also be sent when src has entered the turf. Don't want to merge with ourselves.
+ if(arrived == src)
+ return
+
+ if(!arrived.throwing && can_merge(arrived))
+ INVOKE_ASYNC(src, PROC_REF(merge), arrived)
+
+/obj/item/stack/hitby(atom/movable/hitting, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ if(can_merge(hitting, inhand = TRUE))
+ merge(hitting)
. = ..()
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/stack/attack_hand(mob/user)
if(user.get_inactive_held_item() == src)
- if(zero_amount())
+ if(is_zero_amount(delete_if_zero = TRUE))
return
return change_stack(user,1)
else
@@ -393,7 +517,7 @@
if(is_cyborg)
return
else
- if(zero_amount())
+ if(is_zero_amount(delete_if_zero = TRUE))
return
//get amount from user
var/max = get_amount()
@@ -417,7 +541,7 @@
F.forceMove(user.drop_location())
add_fingerprint(user)
F.add_fingerprint(user)
- zero_amount()
+ is_zero_amount(delete_if_zero = TRUE)
/obj/item/stack/attackby(obj/item/W, mob/user, params)
if(strict && W.type == merge_type)
@@ -456,9 +580,13 @@
var/on_floor = FALSE
var/window_checks = FALSE
var/placement_checks = FALSE
+ /// What trait, if any, boosts the construction speed of this item dripstation
+ var/trait_booster
+ /// How much the trait above, if supplied, boosts the construct speed of this item dripstation
+ var/trait_modifier = 1
-/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1,time = 0, one_per_turf = FALSE, on_floor = FALSE, window_checks = FALSE, placement_checks = FALSE )
-
+/datum/stack_recipe/New(title, result_type, req_amount = 1, res_amount = 1, max_res_amount = 1,time = 0, one_per_turf = FALSE, on_floor = FALSE, window_checks = FALSE, placement_checks = FALSE, trait_booster, trait_modifier = 1)
+//dripstation edit
src.title = title
src.result_type = result_type
@@ -470,6 +598,8 @@
src.on_floor = on_floor
src.window_checks = window_checks
src.placement_checks = placement_checks
+ src.trait_booster = trait_booster //dripstation edit
+ src.trait_modifier = trait_modifier //dripstation edit
/*
* Recipe list datum
*/
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 6d9365afbb8f..db82eced86df 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -12,7 +12,16 @@
throw_range = 7
max_amount = 60
mats_per_stack = 500
+ /// What type of turf does this tile produce.
var/turf_type = null
+ /// What dir will the turf have?
+ var/turf_dir = SOUTH
+ /// Cached associative lazy list to hold the radial options for tile reskinning. See tile_reskinning.dm for more information. Pattern: list[type] -> image
+ var/list/tile_reskin_types
+ /// Cached associative lazy list to hold the radial options for tile dirs. See tile_reskinning.dm for more information.
+ var/list/tile_rotate_dirs
+ /// Allows us to replace the plating we are attacking if our baseturfs are the same.
+ var/replace_plating = FALSE
var/mineralType = null
novariants = TRUE
@@ -208,12 +217,12 @@
turf_type = /turf/open/floor/carpet/black
tableVariant = /obj/structure/table/wood/fancy/black
-/obj/item/stack/tile/carpet/exoticblue
+/obj/item/stack/tile/carpet/blue
name = "exotic blue carpet"
- icon_state = "tile-carpet-exoticblue"
- item_state = "tile-carpet-exoticblue"
- turf_type = /turf/open/floor/carpet/exoticblue
- tableVariant = /obj/structure/table/wood/fancy/exoticblue
+ icon_state = "tile-carpet-blue"
+ item_state = "tile-carpet-blue"
+ turf_type = /turf/open/floor/carpet/blue
+ tableVariant = /obj/structure/table/wood/fancy/blue
/obj/item/stack/tile/carpet/cyan
name = "cyan carpet"
@@ -222,12 +231,12 @@
turf_type = /turf/open/floor/carpet/cyan
tableVariant = /obj/structure/table/wood/fancy/cyan
-/obj/item/stack/tile/carpet/exoticgreen
+/obj/item/stack/tile/carpet/green
name = "exotic green carpet"
- icon_state = "tile-carpet-exoticgreen"
- item_state = "tile-carpet-exoticgreen"
- turf_type = /turf/open/floor/carpet/exoticgreen
- tableVariant = /obj/structure/table/wood/fancy/exoticgreen
+ icon_state = "tile-carpet-green"
+ item_state = "tile-carpet-green"
+ turf_type = /turf/open/floor/carpet/green
+ tableVariant = /obj/structure/table/wood/fancy/green
/obj/item/stack/tile/carpet/orange
name = "orange carpet"
@@ -236,12 +245,12 @@
turf_type = /turf/open/floor/carpet/orange
tableVariant = /obj/structure/table/wood/fancy/orange
-/obj/item/stack/tile/carpet/exoticpurple
+/obj/item/stack/tile/carpet/purple
name = "exotic purple carpet"
- icon_state = "tile-carpet-exoticpurple"
- item_state = "tile-carpet-exoticpurple"
- turf_type = /turf/open/floor/carpet/exoticpurple
- tableVariant = /obj/structure/table/wood/fancy/exoticpurple
+ icon_state = "tile-carpet-purple"
+ item_state = "tile-carpet-purple"
+ turf_type = /turf/open/floor/carpet/purple
+ tableVariant = /obj/structure/table/wood/fancy/purple
/obj/item/stack/tile/carpet/red
name = "red carpet"
@@ -264,6 +273,27 @@
turf_type = /turf/open/floor/carpet/royalblue
tableVariant = /obj/structure/table/wood/fancy/royalblue
+/obj/item/stack/tile/carpet/executive
+ name = "executive carpet"
+ icon_state = "tile_carpet_executive"
+ item_state = "tile-carpet-royalblue"
+ turf_type = /turf/open/floor/carpet/executive
+ merge_type = /obj/item/stack/tile/carpet/executive
+
+/obj/item/stack/tile/carpet/stellar
+ name = "stellar carpet"
+ icon_state = "tile_carpet_stellar"
+ item_state = "tile-carpet-royalblue"
+ turf_type = /turf/open/floor/carpet/stellar
+ merge_type = /obj/item/stack/tile/carpet/stellar
+
+/obj/item/stack/tile/carpet/donk
+ name = "\improper Donk Co. promotional carpet"
+ icon_state = "tile_carpet_donk"
+ item_state = "tile-carpet-orange"
+ turf_type = /turf/open/floor/carpet/donk
+ merge_type = /obj/item/stack/tile/carpet/donk
+
/obj/item/stack/tile/carpet/fifty
amount = 50
@@ -271,19 +301,19 @@
/obj/item/stack/tile/carpet/black/fifty
amount = 50
-/obj/item/stack/tile/carpet/exoticblue/fifty
+/obj/item/stack/tile/carpet/blue/fifty
amount = 50
/obj/item/stack/tile/carpet/cyan/fifty
amount = 50
-/obj/item/stack/tile/carpet/exoticgreen/fifty
+/obj/item/stack/tile/carpet/green/fifty
amount = 50
/obj/item/stack/tile/carpet/orange/fifty
amount = 50
-/obj/item/stack/tile/carpet/exoticpurple/fifty
+/obj/item/stack/tile/carpet/purple/fifty
amount = 50
/obj/item/stack/tile/carpet/red/fifty
diff --git a/code/game/objects/items/stacks/wrap.dm b/code/game/objects/items/stacks/wrap.dm
index e92bc9ee022a..298c9909d324 100644
--- a/code/game/objects/items/stacks/wrap.dm
+++ b/code/game/objects/items/stacks/wrap.dm
@@ -14,7 +14,7 @@
max_amount = 25
resistance_flags = FLAMMABLE
-/obj/item/stack/wrapping_paper/use(used, transfer)
+/obj/item/stack/wrapping_paper/use(used, transfer = FALSE, check = TRUE)
var/turf/T = get_turf(src)
. = ..()
if(QDELETED(src) && !transfer)
@@ -118,7 +118,7 @@
user.visible_message(span_notice("[user] wraps [target]."))
user.log_message("has used [name] on [key_name(target)]", LOG_ATTACK, color="blue")
-/obj/item/stack/packageWrap/use(used, transfer = FALSE)
+/obj/item/stack/packageWrap/use(used, transfer = FALSE, check = TRUE)
var/turf/T = get_turf(src)
. = ..()
if(QDELETED(src) && !transfer)
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index cd41be37a51b..05e71905832e 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -315,26 +315,14 @@
desc = "A very slim satchel that can easily fit into tight spaces."
icon_state = "satchel-flat"
w_class = WEIGHT_CLASS_NORMAL //Can fit in backpacks itself.
- level = 1
/obj/item/storage/backpack/satchel/flat/Initialize(mapload)
. = ..()
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, INVISIBILITY_OBSERVER, use_anchor = TRUE)
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_combined_w_class = 15
STR.set_holdable(null, list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks)
-/obj/item/storage/backpack/satchel/flat/hide(intact)
- if(intact)
- invisibility = INVISIBILITY_OBSERVER
- anchored = TRUE //otherwise you can start pulling, cover it, and drag around an invisible backpack.
- icon_state = "[initial(icon_state)]2"
- ADD_TRAIT(src, TRAIT_T_RAY_VISIBLE, TRAIT_GENERIC)
- else
- invisibility = initial(invisibility)
- anchored = FALSE
- icon_state = initial(icon_state)
- REMOVE_TRAIT(src, TRAIT_T_RAY_VISIBLE, TRAIT_GENERIC)
-
/obj/item/storage/backpack/satchel/flat/PopulateContents()
var/datum/supply_pack/costumes_toys/randomised/contraband/C = new
for(var/i in 1 to 2)
@@ -713,6 +701,8 @@
/obj/item/storage/backpack/duffelbag/clothing/hop/PopulateContents()
new /obj/item/clothing/under/rank/head_of_personnel(src)
new /obj/item/clothing/under/rank/head_of_personnel/skirt(src)
+ new /obj/item/clothing/under/rank/head_of_personnel/turtleneck(src)
+ new /obj/item/clothing/under/rank/head_of_personnel/skirt/turtleneck(src)
new /obj/item/clothing/head/hopcap(src)
new /obj/item/clothing/head/beret/hop(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
@@ -850,6 +840,8 @@
new /obj/item/clothing/suit/toggle/labcoat/cmo(src)
new /obj/item/clothing/under/rank/chief_medical_officer(src)
new /obj/item/clothing/under/rank/chief_medical_officer/skirt(src)
+ new /obj/item/clothing/under/rank/chief_medical_officer/turtleneck(src)
+ new /obj/item/clothing/under/rank/chief_medical_officer/skirt/turtleneck(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/clothing/shoes/xeno_wraps/command(src)
new /obj/item/clothing/head/beret/cmo(src)
diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm
index ec052547445a..d6e613975900 100644
--- a/code/game/objects/items/storage/bags.dm
+++ b/code/game/objects/items/storage/bags.dm
@@ -467,7 +467,7 @@
STR.max_items = 40
STR.max_w_class = WEIGHT_CLASS_SMALL
STR.insert_preposition = "in"
- STR.set_holdable(list(/obj/item/stack/ore/bluespace_crystal, /obj/item/assembly, /obj/item/stock_parts, /obj/item/reagent_containers/glass/beaker, /obj/item/stack/cable_coil, /obj/item/circuitboard, /obj/item/electronics, /obj/item/modular_computer))
+ STR.set_holdable(list(/obj/item/stack/ore/bluespace_crystal, /obj/item/assembly, /obj/item/stock_parts, /obj/item/reagent_containers/glass/beaker, /obj/item/stack/cable_coil, /obj/item/circuitboard, /obj/item/electronics, /obj/item/modular_computer, /obj/item/computer_hardware))
/obj/item/storage/bag/construction/admin/full/Initialize(mapload)
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 3015c6b6615c..40e88d172173 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -78,6 +78,7 @@
/obj/item/shuttle_creator, //Yogs: Added this here cause I felt it fits
/obj/item/barrier_taperoll/engineering,
/obj/item/storage/bag/sheetsnatcher,
+ /obj/item/boxcutter, //dripstation edit
/obj/item/holotool,
))
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 5296ace00f51..781736380a5e 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -90,8 +90,8 @@
if(user.mind.miming)
alpha = 255
-/obj/item/storage/box/mime/Moved(oldLoc, dir)
- if (iscarbon(oldLoc))
+/obj/item/storage/box/mime/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ if (iscarbon(old_loc))
alpha = 0
..()
@@ -1033,7 +1033,7 @@
#undef HEART
#undef SMILEY
-/obj/item/storage/box/ingredients //This box is for the randomely chosen version the chef spawns with, it shouldn't actually exist.
+/obj/item/storage/box/ingredients //This box is for the randomly chosen version the chef spawns with, it shouldn't actually exist.
name = "ingredients box"
illustration = "fruit"
var/theme_name
@@ -1174,6 +1174,18 @@
new /obj/item/reagent_containers/food/snacks/grown/cabbage(src)
new /obj/item/reagent_containers/food/snacks/grown/chili(src)
+/obj/item/storage/box/ingredients/seafood
+ theme_name = "seafood"
+
+/obj/item/storage/box/ingredients/seafood/PopulateContents()
+ new /obj/item/reagent_containers/food/snacks/grown/citrus/lemon(src)
+ for(var/i in 1 to 6)
+ var/randomFood = pick(/obj/item/reagent_containers/food/snacks/carpmeat,
+ /obj/item/reagent_containers/food/snacks/dolphinmeat,
+ /obj/item/reagent_containers/food/snacks/fish/tuna,
+ /obj/item/reagent_containers/food/snacks/fish/shrimp)
+ new randomFood(src)
+
/obj/item/storage/box/cheese
name = "box of advanced cheese bacteria"
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index 3ef0a4c0c231..a63bd8386b80 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -228,7 +228,7 @@
return
var/static/items_inside = list(
/obj/item/reagent_containers/pill/patch/styptic = 2,
- /obj/item/reagent_containers/pill/salicyclic = 2,
+ /obj/item/reagent_containers/pill/salicylic = 2,
/obj/item/reagent_containers/medspray/styptic = 1,
/obj/item/stack/medical/gauze = 2,
/obj/item/healthanalyzer = 1)
diff --git a/code/game/objects/items/storage/storage.dm b/code/game/objects/items/storage/storage.dm
index 20f199c27e23..57b083dd59b9 100644
--- a/code/game/objects/items/storage/storage.dm
+++ b/code/game/objects/items/storage/storage.dm
@@ -56,3 +56,31 @@
/// Don't do anything stupid, please
/obj/item/storage/proc/get_types_to_preload()
return
+
+/obj/item/storage/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_SEPERATOR
+ VV_DROPDOWN_OPTION(VV_HK_SPAWN_ITEM_INSIDE, "Spawn Item Inside")
+
+/obj/item/storage/vv_do_topic(list/href_list)
+ . = ..()
+ if(href_list[VV_HK_SPAWN_ITEM_INSIDE] && check_rights(R_SPAWN))
+ var/valid_id = FALSE
+ var/chosen_id
+ while(!valid_id)
+ chosen_id = input(usr, "Enter the typepath of the item you want to add.", "Search items") as null|text
+ if(isnull(chosen_id)) //Get me out of here!
+ break
+ if (!ispath(text2path(chosen_id)))
+ chosen_id = pick_closest_path(chosen_id, make_types_fancy(subtypesof(/obj/item)))
+ if (ispath(chosen_id))
+ valid_id = TRUE
+ else
+ valid_id = TRUE
+ if(!valid_id)
+ to_chat(usr, span_warning("A reagent with that ID doesn't exist!"))
+
+ if(valid_id)
+ var/obj/item/item = new chosen_id(src)
+ item.forceMove(src)
+
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index cdac3523b530..b4d4112daacd 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -92,10 +92,10 @@
new /obj/item/implanter/storage(src) //8 TC
if("hacker") //29 TC cost
- new /obj/item/aiModule/syndicate(src) //4 TC
+ new /obj/item/aiModule/hacked(src) //4 TC
new /obj/item/card/emag(src) //6 TC
new /obj/item/encryptionkey/binary(src) //2 TC
- new /obj/item/aiModule/toyAI(src) //Um, free...?
+ new /obj/item/aiModule/ion/toyAI(src) //Um, free...?
new /obj/item/multitool/ai_detect(src) //1 TC
new /obj/item/storage/toolbox/syndicate/real(src) //2 TC
new /obj/item/camera_bug(src) //1 TC
@@ -225,7 +225,7 @@
new /obj/item/reagent_containers/glass/bottle/beesease(src) // 10 tc?
new /obj/item/gun/magic/staff/spellblade/beesword(src) //priceless
- if("mr_freeze") // ~17 tc
+ if("mr_freeze") // ~25 tc
new /obj/item/clothing/glasses/cold(src) // 0 tc
new /obj/item/clothing/gloves/color/black(src) // 0 tc
new /obj/item/clothing/mask/chameleon/syndicate(src) // 0 tc on its own
@@ -237,8 +237,9 @@
new /obj/item/grenade/gluon(src) //
new /obj/item/dnainjector/geladikinesis(src) // 0 tc
new /obj/item/dnainjector/cryokinesis(src) // 1 or 2 tc, kind of useful
- new /obj/item/gun/energy/temperature/security(src) // the crutch of this kit, alongside esword, ~4 tc
+ new /obj/item/gun/energy/temperature/security(src) // ~4 tc
new /obj/item/melee/transforming/energy/sword/saber/blue(src) //see see it fits the theme bc its blue and ice is blue, 8 tc
+ new /obj/item/reagent_containers/spray/chemsprayer/freeze(src) // filled with frost oil and you can refill it with whatever, ~8 tc
if("neo")
new /obj/item/clothing/glasses/sunglasses(src)
@@ -270,12 +271,11 @@
new /obj/item/reagent_containers/glass/bottle/drugs(src)
new /obj/item/slimecross/stabilized/green(src) //secret identity
- if("solo") //14 + 6x4 + 1 = 3 tc = 39 tc, or 37 if molti's pr gets merged. wow thats really costly this is probably going to busted. eh
- new /obj/item/autosurgeon/syndicate/spinalspeed(src) //14 tc as of writing, 12 if molti's pr gets merged
+ if("solo") //14 + 6x3 + 1 = 3 tc = 31 tc. it was, in fact, busted
+ new /obj/item/autosurgeon/syndicate/spinalspeed(src) //12 tc
new /obj/item/clothing/suit/toggle/cyberpunk/solo(src) //dont know what this costs, vague guesstimate says 6tc
new /obj/item/autosurgeon/arm/syndicate/syndie_mantis(src) //6 tc
new /obj/item/autosurgeon/arm/syndicate/syndie_mantis(src) //6 tc
- new /obj/item/storage/box/syndie_kit/emp_shield(src) //6 tc
new /obj/item/autosurgeon/upgraded_cyberlungs(src) //this is to remain true to the source material ok
new /obj/item/storage/pill_bottle/synaptizine(src) //take your drugs david, this and the lungs make up 1 tc
@@ -300,7 +300,8 @@
/obj/item/clothing/suit/toggle/cyberpunk/solo
name = "David's Jacket"
desc = "A jacket once owned by a legendary edgerunner, or so they say. Armored."
- armor = list(MELEE = 35, BULLET = 35, LASER = 35, ENERGY = 15, BOMB = 35, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 15)
+ armor = list(MELEE = 40, BULLET = 40, LASER = 40, ENERGY = 15, BOMB = 40, BIO = 0, RAD = 0, FIRE = 50, ACID = 50, WOUND = 20)
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF //it's too stylish to be destroyed
/obj/item/stand_arrow/boss
desc = "An arrow that can unleash massive potential from those stabbed by it. It has been laced with syndicate mindslave nanites that will be linked to whoever first uses it in their hand."
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index d7b7421bf7b5..a02293e02c5a 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -33,6 +33,17 @@
var/preload_cell_type
///used for passive discharge
var/cell_last_used = 0
+ light_range = 1.5
+ light_system = MOVABLE_LIGHT
+ light_on = FALSE
+ light_color = LIGHT_COLOR_ORANGE
+ light_power = 0.5
+
+/// Toggles the stun baton's light
+/obj/item/melee/baton/proc/toggle_light(mob/user)
+ set_light_on(!light_on)
+ return
+
/obj/item/melee/baton/get_cell()
return cell
@@ -76,6 +87,7 @@
if(status && cell.charge < hitcost)
//we're below minimum, turn off
status = FALSE
+ set_light_on(FALSE)
update_appearance(UPDATE_ICON)
playsound(loc, "sparks", 75, 1, -1)
STOP_PROCESSING(SSobj, src) // no more charge? stop checking for discharge
@@ -135,6 +147,8 @@
status = !status
to_chat(user, span_notice("[src] is now [status ? "on" : "off"]."))
playsound(loc, "sparks", 75, 1, -1)
+ toggle_light(user)
+ do_sparks(1, TRUE, src)
cell_last_used = 0
if(status)
START_PROCESSING(SSobj, src)
@@ -306,3 +320,5 @@
desc = "A new power management circuit which enables stun batons to instantly stun, at the cost of double power usage."
icon = 'icons/obj/module.dmi'
icon_state = "cyborg_upgrade3"
+
+
diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm
index 41483a0b7a6f..0a981ca2ca74 100644
--- a/code/game/objects/items/tanks/tanks.dm
+++ b/code/game/objects/items/tanks/tanks.dm
@@ -270,7 +270,7 @@
/obj/item/tank/process()
//Allow for reactions
- air_contents.react()
+ air_contents.react(src)
check_status()
/obj/item/tank/proc/check_status()
diff --git a/code/game/objects/items/theft_tools.dm b/code/game/objects/items/theft_tools.dm
index 4af12323e469..cc2a0293f99e 100644
--- a/code/game/objects/items/theft_tools.dm
+++ b/code/game/objects/items/theft_tools.dm
@@ -113,7 +113,7 @@
\
Locate the physical R&D Server mainframes. Intel suggests these are stored in specially cooled rooms deep within their Science department.
\
Nanotrasen is a corporation not known for subtlety in design. You should be able to identify the master server by any distinctive markings.
\
-
Tools are on-site procurement. Screwdriver, crowbar and wirecutters should be all you need to do the job.
\
+
Tools are on-site procurement. Screwdriver, crowbar and wirecutters should be all you need to do the job.
\
The front panel screws are hidden in recesses behind stickers. Easily removed once you know they're there.
\
You'll probably find the hard drive in secure housing. You may need to pry it loose with a crowbar, shouldn't do too much damage.
\
Finally, carefully cut all of the hard drive's connecting wires. Don't rush this, snipping the wrong wire could wipe all data!
\
@@ -122,7 +122,7 @@
The thing's probably hardwired. No putting it back once you've extracted it. The crew are likely to be as mad as bees if they find out! \
Survive the shift and extract the hard drive safely. \
Succeed and you will receive a coveted green highlight on your record for this assignment. Fail us and red's the last color you'll ever see. \
- Do not disappoint us. "
+ Do not disappoint us."
/obj/item/computer_hardware/hard_drive/cluster
name = "cluster hard disk drive"
@@ -134,9 +134,9 @@
/obj/item/computer_hardware/hard_drive/cluster/hdd_theft
name = "r&d server hard disk drive"
- desc = "For some reason, people really seem to want to steal this. The source code on this drive is probably used for something awful!"
+ desc = "The hard drive containing sensitive data on alternate universes. Holding it up to your ear you can faintly hear the hum of millions of bees."
icon = 'icons/obj/nuke_tools.dmi'
- icon_state = "project_bee"
+ icon_state = "project_bee" //hi bee!
max_capacity = 512
// STEALING SUPERMATTER
@@ -285,7 +285,7 @@
if(isliving(AM))
var/mob/living/victim = AM
message_admins("[src] has consumed [key_name_admin(victim)] [ADMIN_JMP(src)].")
- message_admins("[ADMIN_LOOKUPFLW(user)] has used a supermatter sliver to commit dual suicide with [ADMIN_LOOKUPFLW(victim)] at [ADMIN_VERBOSEJMP(src)].")
+ message_admins("[ADMIN_LOOKUPFLW(user)] has used a supermatter sliver to commit dual suicide with [ADMIN_LOOKUPFLW(victim)] at [ADMIN_VERBOSEJMP(src)].")
investigate_log("has consumed [key_name(victim)].", "supermatter")
investigate_log("[key_name(user)] has used a supermatter sliver to commit dual suicide with [key_name(victim)].", "supermatter")
victim.dust()
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 3a3da7c3be55..9cfe1022c7ac 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -456,6 +456,14 @@
w_class = WEIGHT_CLASS_TINY
var/ash_type = /obj/effect/decal/cleanable/ash
+/obj/item/toy/snappop/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+
/obj/item/toy/snappop/proc/pop_burst(n=3, c=1)
var/datum/effect_system/spark_spread/s = new()
s.set_up(n, c, src)
@@ -473,8 +481,7 @@
if(!..())
pop_burst()
-/obj/item/toy/snappop/Crossed(H as mob|obj)
- . = ..()
+/obj/item/toy/snappop/proc/on_entered(datum/source, atom/movable/H, ...)
if(ishuman(H) || issilicon(H)) //i guess carp and shit shouldn't set them off
var/mob/living/carbon/M = H
if(issilicon(H) || M.m_intent == MOVE_INTENT_RUN)
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 48f8af00a690..ddcdb6ba0bf0 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -46,7 +46,7 @@
/obj/item/trash/plate
name = "plate"
- desc = "a relic from a forgotten time... I miss eating off of plates..."
+ desc = "A relic from a forgotten time... I miss eating off of plates..."
icon_state = "plate"
resistance_flags = NONE
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 013a1220933d..cefc64e51f50 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -121,9 +121,9 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/process()
if(ishuman(loc))
- var/mob/living/carbon/human/H = loc
- loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS)
- H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS
+ var/mob/living/carbon/human/holder = loc
+ layer = ABOVE_ALL_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS)
+ ADD_TRAIT(holder, TRAIT_NOBLOOD, HIGHLANDER_TRAIT) //AND WE WON'T BLEED OUT LIKE COWARDS
else
if(!(flags_1 & ADMIN_SPAWNED_1))
qdel(src)
@@ -261,7 +261,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/katana/basalt
name = "basalt katana"
- desc = "a katana made out of hardened basalt. Particularly damaging to lavaland fauna. (Activate this item in hand to dodge roll in the direction you're facing)"
+ desc = "A katana made of hardened basalt. Particularly damaging to lavaland fauna. (Activate this item in hand to dodge roll in the direction you're facing)"
icon_state = "basalt_katana"
item_state = "basalt_katana"
force = 18
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index d0f5895640e2..223f2cc4c414 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -105,7 +105,7 @@
/obj/blob_act(obj/structure/blob/B)
if(isturf(loc))
var/turf/T = loc
- if(T.intact && level == 1) //the blob doesn't destroy thing below the floor
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(src, TRAIT_T_RAY_VISIBLE))
return
take_damage(400, BRUTE, MELEE, 0, get_dir(src, B))
@@ -221,8 +221,8 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
///Called when the obj is exposed to fire.
/obj/fire_act(exposed_temperature, exposed_volume)
if(isturf(loc))
- var/turf/T = loc
- if(T.intact && level == 1) //fire can't damage things hidden below the floor.
+ var/turf/our_turf = loc
+ if(our_turf.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(src, TRAIT_T_RAY_VISIBLE))
return
if(exposed_temperature && !(resistance_flags & FIRE_PROOF))
take_damage(clamp(0.02 * exposed_temperature, 0, 20), BURN, FIRE, 0)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 686438b8b836..fcf51990e00a 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -206,18 +206,24 @@
return
/mob/proc/unset_machine()
- if(machine)
- machine.on_unset_machine(src)
- machine = null
+ SIGNAL_HANDLER
+ if(!machine)
+ return
+ UnregisterSignal(machine, COMSIG_QDELETING)
+ machine.on_unset_machine(src)
+ machine = null
//called when the user unsets the machine.
/atom/movable/proc/on_unset_machine(mob/user)
return
/mob/proc/set_machine(obj/O)
- if(src.machine)
+ if(QDELETED(src) || QDELETED(O))
+ return
+ if(machine)
unset_machine()
- src.machine = O
+ machine = O
+ RegisterSignal(O, COMSIG_QDELETING, PROC_REF(unset_machine))
if(istype(O))
O.obj_flags |= IN_USE
@@ -226,9 +232,6 @@
if(istype(M) && M.client && M.machine == src)
src.attack_self(M)
-/obj/proc/hide(h)
- return
-
/obj/singularity_pull(S, current_size)
..()
if(!anchored || current_size >= STAGE_FIVE)
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 9dd4ff9c89a1..e8f1536364aa 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -4,6 +4,7 @@
max_integrity = 300
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
layer = BELOW_OBJ_LAYER
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
var/broken = 0 //similar to machinery's stat BROKEN
@@ -12,16 +13,17 @@
if (!armor)
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 50, ACID = 50)
. = ..()
- if(smooth)
- queue_smooth(src)
- queue_smooth_neighbors(src)
- icon_state = ""
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ if(smoothing_flags & SMOOTH_CORNERS)
+ icon_state = ""
GLOB.cameranet.updateVisibility(src)
/obj/structure/Destroy()
GLOB.cameranet.updateVisibility(src)
- if(smooth)
- queue_smooth_neighbors(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
/obj/structure/CanAllowThrough(atom/movable/mover, turf/target)
diff --git a/code/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm
index 8837107f9cb7..2ba38d37d2f8 100644
--- a/code/game/objects/structures/aliens.dm
+++ b/code/game/objects/structures/aliens.dm
@@ -53,15 +53,17 @@
name = "resin"
desc = "Looks like some kind of thick resin."
icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi'
- icon_state = "smooth"
+ icon_state = "resin_wall-0"
+ base_icon_state = "resin_wall"
density = TRUE
opacity = TRUE
anchored = TRUE
- canSmoothWith = list(/obj/structure/alien/resin)
max_integrity = 200
- smooth = SMOOTH_TRUE
+ can_atmos_pass = ATMOS_PASS_DENSITY
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_ALIEN_RESIN
+ canSmoothWith = SMOOTH_GROUP_ALIEN_RESIN
var/resintype = null
- CanAtmosPass = ATMOS_PASS_DENSITY
/obj/structure/alien/resin/Initialize(mapload)
@@ -77,9 +79,11 @@
name = "resin wall"
desc = "Thick resin solidified into a wall."
icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi'
- icon_state = "smooth" //same as resin, but consistency ho!
+ icon_state = "resin_wall-0"
+ base_icon_state = "resin_wall"
resintype = "wall"
- canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane)
+ smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN
+ canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS
/obj/structure/alien/resin/wall/BlockThermalConductivity()
return 1
@@ -88,11 +92,13 @@
name = "resin membrane"
desc = "Resin just thin enough to let light pass through."
icon = 'icons/obj/smooth_structures/alien/resin_membrane.dmi'
- icon_state = "smooth"
+ icon_state = "resin_membrane-0"
+ base_icon_state = "resin_membrane"
opacity = FALSE
max_integrity = 160
resintype = "membrane"
- canSmoothWith = list(/obj/structure/alien/resin/wall, /obj/structure/alien/resin/membrane)
+ smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN
+ canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS
/obj/structure/alien/resin/attack_paw(mob/user)
return attack_hand(user)
@@ -113,8 +119,10 @@
plane = FLOOR_PLANE
icon_state = "weeds"
max_integrity = 15
- canSmoothWith = list(/obj/structure/alien/weeds, /turf/closed/wall)
- smooth = SMOOTH_MORE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_ALIEN_WEEDS + SMOOTH_GROUP_ALIEN_RESIN
+ canSmoothWith = SMOOTH_GROUP_ALIEN_WEEDS + SMOOTH_GROUP_WALLS
+
var/last_expand = 0 //last world.time this weed expanded
var/growth_cooldown_low = 150
var/growth_cooldown_high = 200
@@ -131,16 +139,24 @@
/turf/open/chasm,
/turf/open/lava))
+ set_base_icon()
last_expand = world.time + rand(growth_cooldown_low, growth_cooldown_high)
- if(icon == initial(icon))
- switch(rand(1,3))
- if(1)
- icon = 'icons/obj/smooth_structures/alien/weeds1.dmi'
- if(2)
- icon = 'icons/obj/smooth_structures/alien/weeds2.dmi'
- if(3)
- icon = 'icons/obj/smooth_structures/alien/weeds3.dmi'
+
+///Randomizes the weeds' starting icon, gets redefined by children for them not to share the behavior.
+/obj/structure/alien/weeds/proc/set_base_icon()
+ . = base_icon_state
+ switch(rand(1,3))
+ if(1)
+ icon = 'icons/obj/smooth_structures/alien/weeds1.dmi'
+ base_icon_state = "weeds1"
+ if(2)
+ icon = 'icons/obj/smooth_structures/alien/weeds2.dmi'
+ base_icon_state = "weeds2"
+ if(3)
+ icon = 'icons/obj/smooth_structures/alien/weeds3.dmi'
+ base_icon_state = "weeds3"
+ set_smoothed_icon_state(smoothing_junction)
/obj/structure/alien/weeds/Click(atom/A)
var/turf/T = loc
@@ -175,14 +191,15 @@
/obj/structure/alien/weeds/node
name = "glowing resin"
desc = "Blue bioluminescence shines from beneath the surface."
- icon_state = "weednode"
+ icon = 'icons/obj/smooth_structures/alien/weednode.dmi'
+ icon_state = "weednode-0"
+ base_icon_state = "weednode"
light_color = LIGHT_COLOR_BLUE
light_power = 0.5
var/lon_range = 4
var/node_range = NODERANGE
/obj/structure/alien/weeds/node/Initialize(mapload)
- icon = 'icons/obj/smooth_structures/alien/weednode.dmi'
. = ..()
set_light(lon_range)
var/obj/structure/alien/weeds/W = locate(/obj/structure/alien/weeds) in loc
@@ -200,6 +217,9 @@
if(W.expand())
W.last_expand = world.time + rand(growth_cooldown_low, growth_cooldown_high)
+/obj/structure/alien/weeds/node/set_base_icon()
+ return //No icon randomization at init. The node's icon is already well defined.
+
#undef NODERANGE
@@ -229,7 +249,7 @@
/obj/structure/alien/egg/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(status == GROWING || status == GROWN)
child = new(src)
if(status == GROWING)
diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm
deleted file mode 100644
index 36e45204be9f..000000000000
--- a/code/game/objects/structures/barsigns.dm
+++ /dev/null
@@ -1,363 +0,0 @@
-/obj/structure/sign/barsign // All Signs are 64 by 32 pixels, they take two tiles
- name = "bar sign"
- desc = "A bar sign which has not been initialized, somehow. Complain at a coder!"
- icon = 'icons/obj/barsigns.dmi'
- icon_state = "empty"
- req_access = list(ACCESS_BAR)
- max_integrity = 500
- integrity_failure = 250
- armor = list(MELEE = 20, BULLET = 20, LASER = 20, ENERGY = 100, BOMB = 0, BIO = 0, RAD = 0, FIRE = 50, ACID = 50)
- buildable_sign = 0
-
- var/panel_open = FALSE
- var/datum/barsign/chosen_sign
-
-/obj/structure/sign/barsign/Initialize(mapload)
- . = ..()
- set_sign(new /datum/barsign/hiddensigns/signoff)
-
-/obj/structure/sign/barsign/proc/set_sign(datum/barsign/sign)
- if(!istype(sign))
- return
-
- icon_state = sign.icon
-
- if(sign.name)
- name = "[initial(name)] ([sign.name])"
- else
- name = "[initial(name)]"
-
- if(sign.desc)
- desc = sign.desc
-
- if(sign.rename_area && sign.name)
- rename_area(src, sign.name)
-
- return sign
-
-/obj/structure/sign/barsign/proc/set_sign_by_name(sign_name)
- for(var/d in subtypesof(/datum/barsign))
- var/datum/barsign/D = d
- if(initial(D.name) == sign_name)
- var/new_sign = new D
- return set_sign(new_sign)
-
-/obj/structure/sign/barsign/obj_break(damage_flag)
- if(!broken && !(flags_1 & NODECONSTRUCT_1))
- broken = TRUE
-
-/obj/structure/sign/barsign/deconstruct(disassembled = TRUE)
- new /obj/item/stack/sheet/metal(drop_location(), 2)
- new /obj/item/stack/cable_coil(drop_location(), 2)
- qdel(src)
-
-/obj/structure/sign/barsign/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
- switch(damage_type)
- if(BRUTE)
- playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1)
- if(BURN)
- playsound(src.loc, 'sound/items/welder.ogg', 100, 1)
-
-/obj/structure/sign/barsign/attack_ai(mob/user)
- return attack_hand(user)
-
-/obj/structure/sign/barsign/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- if(!allowed(user))
- to_chat(user, span_info("Access denied."))
- return
- if(broken)
- to_chat(user, "The controls seem unresponsive.")
- return
- pick_sign(user)
-
-/obj/structure/sign/barsign/attackby(obj/item/I, mob/user)
- if(I.tool_behaviour == TOOL_SCREWDRIVER)
- if(!panel_open)
- to_chat(user, span_notice("You open the maintenance panel."))
- set_sign(new /datum/barsign/hiddensigns/signoff)
- panel_open = TRUE
- else
- to_chat(user, span_notice("You close the maintenance panel."))
- if(!broken)
- if(!chosen_sign)
- set_sign(new /datum/barsign/hiddensigns/signoff)
- else
- set_sign(chosen_sign)
- else
- set_sign(new /datum/barsign/hiddensigns/empbarsign)
- panel_open = FALSE
-
- else if(istype(I, /obj/item/stack/cable_coil) && panel_open)
- var/obj/item/stack/cable_coil/C = I
- if(!broken)
- to_chat(user, span_warning("This sign is functioning properly!"))
- return
-
- if(C.use(2))
- to_chat(user, span_notice("You replace the burnt wiring."))
- broken = FALSE
- else
- to_chat(user, span_warning("You need at least two lengths of cable!"))
- else
- return ..()
-
-
-/obj/structure/sign/barsign/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- set_sign(new /datum/barsign/hiddensigns/empbarsign)
- broken = TRUE
-
-/obj/structure/sign/barsign/emag_act(mob/user, obj/item/card/emag/emag_card)
- if(broken)
- to_chat(user, span_warning("Nothing interesting happens!"))
- return FALSE
- to_chat(user, span_notice("You load an illegal barsign into the memory buffer..."))
- addtimer(CALLBACK(src, PROC_REF(finish_emag_act)), 10 SECONDS)
- return TRUE
-
-/obj/structure/sign/barsign/proc/finish_emag_act()
- if(QDELETED(src))
- return
- chosen_sign = set_sign(new /datum/barsign/hiddensigns/syndibarsign)
-
-/obj/structure/sign/barsign/proc/pick_sign(mob/user)
- var/picked_name = input(user, "Available Signage", "Bar Sign", name) as null|anything in get_bar_names()
- if(!picked_name)
- return
- chosen_sign = set_sign_by_name(picked_name)
- SSblackbox.record_feedback("tally", "barsign_picked", 1, chosen_sign.type)
-
-/proc/get_bar_names()
- var/list/names = list()
- for(var/d in subtypesof(/datum/barsign))
- var/datum/barsign/D = d
- if(initial(D.name) && !initial(D.hidden))
- names += initial(D.name)
- . = names
-
-/datum/barsign
- var/name = "Name"
- var/icon = "Icon"
- var/desc = "desc"
- var/hidden = FALSE
- var/rename_area = TRUE
-
-/datum/barsign/New()
- if(!desc)
- desc = "It displays \"[name]\"."
-
-// Specific bar signs.
-
-/datum/barsign/maltesefalcon
- name = "Maltese Falcon"
- icon = "maltesefalcon"
- desc = "The Maltese Falcon, Space Bar and Grill."
-
-/datum/barsign/thebark
- name = "The Bark"
- icon = "thebark"
- desc = "Ian's bar of choice."
-
-/datum/barsign/harmbaton
- name = "The Harmbaton"
- icon = "theharmbaton"
- desc = "A great dining experience for both security members and assistants."
-
-/datum/barsign/thesingulo
- name = "The Singulo"
- icon = "thesingulo"
- desc = "Where people go that'd rather not be called by their name."
-
-/datum/barsign/thedrunkcarp
- name = "The Drunk Carp"
- icon = "thedrunkcarp"
- desc = "Don't drink and swim."
-
-/datum/barsign/scotchservinwill
- name = "Scotch Servin Willy's"
- icon = "scotchservinwill"
- desc = "Willy sure moved up in the world from clown to bartender."
-
-/datum/barsign/officerbeersky
- name = "Officer Beersky's"
- icon = "officerbeersky"
- desc = "Man eat a dong, these drinks are great."
-
-/datum/barsign/thecavern
- name = "The Cavern"
- icon = "thecavern"
- desc = "Fine drinks while listening to some fine tunes."
-
-/datum/barsign/theouterspess
- name = "The Outer Spess"
- icon = "theouterspess"
- desc = "This bar isn't actually located in outer space."
-
-/datum/barsign/slipperyshots
- name = "Slippery Shots"
- icon = "slipperyshots"
- desc = "Slippery slope to drunkenness with our shots!"
-
-/datum/barsign/thegreytide
- name = "The Grey Tide"
- icon = "thegreytide"
- desc = "Abandon your toolboxing ways and enjoy a lazy beer!"
-
-/datum/barsign/honkednloaded
- name = "Honked 'n' Loaded"
- icon = "honkednloaded"
- desc = "Honk."
-
-/datum/barsign/thenest
- name = "The Nest"
- icon = "thenest"
- desc = "A good place to retire for a drink after a long night of crime fighting."
-
-/datum/barsign/thecoderbus
- name = "The Coderbus"
- icon = "thecoderbus"
- desc = "A very controversial bar known for its wide variety of constantly-changing drinks."
-
-/datum/barsign/theadminbus
- name = "The Adminbus"
- icon = "theadminbus"
- desc = "An establishment visited mainly by space-judges. It isn't bombed nearly as much as court hearings."
-
-/datum/barsign/oldcockinn
- name = "The Old Cock Inn"
- icon = "oldcockinn"
- desc = "Something about this sign fills you with despair."
-
-/datum/barsign/thewretchedhive
- name = "The Wretched Hive"
- icon = "thewretchedhive"
- desc = "Legally obligated to instruct you to check your drinks for acid before consumption."
-
-/datum/barsign/robustacafe
- name = "The Robusta Cafe"
- icon = "robustacafe"
- desc = "Holder of the 'Most Lethal Barfights' record 5 years uncontested."
-
-/datum/barsign/emergencyrumparty
- name = "The Emergency Rum Party"
- icon = "emergencyrumparty"
- desc = "Recently relicensed after a long closure."
-
-/datum/barsign/combocafe
- name = "The Combo Cafe"
- icon = "combocafe"
- desc = "Renowned system-wide for their utterly uncreative drink combinations."
-
-/datum/barsign/vladssaladbar
- name = "Vlad's Salad Bar"
- icon = "vladssaladbar"
- desc = "Under new management. Vlad was always a bit too trigger happy with that shotgun."
-
-/datum/barsign/theshaken
- name = "The Shaken"
- icon = "theshaken"
- desc = "This establishment does not serve stirred drinks."
-
-/datum/barsign/thealenath
- name = "The Ale' Nath"
- icon = "thealenath"
- desc = "All right, buddy. I think you've had EI NATH. Time to get a cab."
-
-/datum/barsign/thealohasnackbar
- name = "The Aloha Snackbar"
- icon = "alohasnackbar"
- desc = "A tasteful, inoffensive tiki bar sign."
-
-/datum/barsign/thenet
- name = "The Net"
- icon = "thenet"
- desc = "You just seem to get caught up in it for hours."
-
-/datum/barsign/maidcafe
- name = "Maid Cafe"
- icon = "maidcafe"
- desc = "Welcome back, master!"
-
-/datum/barsign/the_lightbulb
- name = "The Lightbulb"
- icon = "the_lightbulb"
- desc = "A cafe popular among moths and moffs. Once shut down for a week after the bartender used mothballs to protect her spare uniforms."
-
-/datum/barsign/thegoose
- name = "The Goose"
- icon = "thegoose"
- desc = "A nice place to hang loose and relax, while enjoying some electrifying drinks."
-
-/datum/barsign/tearoom
- name = "Little Treats Tea Room"
- icon = "little_treats"
- desc = "A delightfully relaxing tearoom for all the fancy lads in the cosmos."
-
-/datum/barsign/le_cafe_silencieux
- name = "Le Café Silencieux"
- icon = "le_cafe_silencieux"
- desc = "..."
-
-/datum/barsign/maltroach
- name = "Maltroach"
- icon = "maltroach"
- desc = "Mothroaches politely greet you into the bar, or are they greeting eachother?"
-
-/datum/barsign/rock_bottom
- name = "Rock Bottom"
- icon = "rock-bottom"
- desc = "When it feels like you're stuck in a pit, might as well have a drink."
-
-/datum/barsign/assembly_line
- name = "The Assembly Line"
- icon = "the-assembly-line"
- desc = "Where every drink is masterfully crafted with industrial efficiency!"
-
-/datum/barsign/bargonia
- name = "Bargonia"
- icon = "bargonia"
- desc = "The warehouse yearns for a higher calling... so Supply has declared BARGONIA!"
-
-/datum/barsign/cult_cove
- name = "Cult Cove"
- icon = "cult-cove"
- desc = "Nar'Sie's favourite retreat"
-
-/datum/barsign/neon_flamingo
- name = "Neon Flamingo"
- icon = "neon-flamingo"
- desc = "A bus for all but the flamboyantly challenged."
-
-/datum/barsign/slowdive
- name = "Slowdive"
- icon = "slowdive"
- desc = "First stop out of hell, last stop before heaven."
-
-/datum/barsign/hiddensigns
- hidden = TRUE
-
-//Hidden signs list below this point
-
-
-
-/datum/barsign/hiddensigns/empbarsign
- name = null
- icon = "empbarsign"
- desc = "Something has gone very wrong."
- rename_area = FALSE
-
-/datum/barsign/hiddensigns/syndibarsign
- name = "Syndi Cat"
- icon = "syndibarsign"
- desc = "Syndicate or die."
-
-/datum/barsign/hiddensigns/signoff
- name = null
- icon = "empty"
- desc = "This sign doesn't seem to be on."
- rename_area = FALSE
diff --git a/code/game/objects/structures/beds_chairs/alien_nest.dm b/code/game/objects/structures/beds_chairs/alien_nest.dm
index 0b6c2335a185..1031671f92e1 100644
--- a/code/game/objects/structures/beds_chairs/alien_nest.dm
+++ b/code/game/objects/structures/beds_chairs/alien_nest.dm
@@ -4,11 +4,13 @@
name = "alien nest"
desc = "It's a gruesome pile of thick, sticky resin shaped like a nest."
icon = 'icons/obj/smooth_structures/alien/nest.dmi'
- icon_state = "nest"
+ icon_state = "nest-0"
+ base_icon_state = "nest"
max_integrity = 120
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_ALIEN_NEST
+ canSmoothWith = SMOOTH_GROUP_ALIEN_NEST
can_be_unanchored = FALSE
- canSmoothWith = null
buildstacktype = null
flags_1 = NODECONSTRUCT_1
bolts = FALSE
@@ -34,7 +36,7 @@
span_warning("[M.name] struggles to break free from the gelatinous resin!"),\
span_notice("You struggle to break free from the gelatinous resin... (Stay still for two minutes.)"),\
span_italics("You hear squelching..."))
- if(!do_after(M, 2 MINUTES, src))
+ if(!do_after(M, 15 SECONDS, src))
if(M && M.buckled)
to_chat(M, span_warning("You fail to unbuckle yourself!"))
return
@@ -65,6 +67,8 @@
"[user.name] secretes a thick vile goo, securing [M.name] into [src]!",\
span_danger("[user.name] drenches you in a foul-smelling resin, trapping you in [src]!"),\
span_italics("You hear squelching..."))
+ var/obj/item/restraints/handcuffs/xeno/cuffs = new(M)
+ cuffs.apply_cuffs(M, user)
/obj/structure/bed/nest/post_buckle_mob(mob/living/M)
M.pixel_y = 0
@@ -90,3 +94,9 @@
return attack_hand(user)
else
return ..()
+
+/obj/item/restraints/handcuffs/xeno
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "sinewcuff"
+ item_flags = DROPDEL
+ color = COLOR_MAGENTA
diff --git a/code/game/objects/structures/beds_chairs/bed.dm b/code/game/objects/structures/beds_chairs/bed.dm
index 011b18801437..0cabe91cf5c5 100644
--- a/code/game/objects/structures/beds_chairs/bed.dm
+++ b/code/game/objects/structures/beds_chairs/bed.dm
@@ -100,7 +100,7 @@
icon_state = "up"
M.pixel_y = initial(M.pixel_y)
-/obj/structure/bed/roller/Moved()
+/obj/structure/bed/roller/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(has_gravity())
playsound(src, 'sound/effects/roll.ogg', 100, 1)
diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm
index f0809be27d78..29c087d23a4f 100644
--- a/code/game/objects/structures/beds_chairs/chair.dm
+++ b/code/game/objects/structures/beds_chairs/chair.dm
@@ -210,7 +210,7 @@
item_chair = null
-/obj/structure/chair/office/Moved()
+/obj/structure/chair/office/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(has_gravity())
playsound(src, 'sound/effects/roll.ogg', 100, 1)
@@ -474,7 +474,7 @@
buildstackamount = 1
item_chair = null
-/obj/structure/chair/bronze/Moved()
+/obj/structure/chair/bronze/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(has_gravity())
playsound(src, 'sound/machines/clockcult/integration_cog_install.ogg', 50, TRUE)
diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm
index 0cf459dc0c7f..3f028b95685b 100644
--- a/code/game/objects/structures/catwalk.dm
+++ b/code/game/objects/structures/catwalk.dm
@@ -2,11 +2,13 @@
name = "catwalk"
desc = "A catwalk for easier EVA maneuvering and cable placement."
icon = 'icons/obj/smooth_structures/catwalk.dmi'
- icon_state = "catwalk"
+ icon_state = "catwalk-0"
+ base_icon_state = "catwalk"
number_of_rods = 2
- smooth = SMOOTH_TRUE
- canSmoothWith = null
- obj_flags = CAN_BE_HIT
+ smoothing_flags = SMOOTH_BITMASK
+ 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
/obj/structure/lattice/catwalk/Initialize(mapload)
. = ..()
@@ -49,12 +51,11 @@
/obj/structure/lattice/catwalk/clockwork
name = "clockwork catwalk"
icon = 'icons/obj/smooth_structures/catwalk_clockwork.dmi'
- canSmoothWith = list(/obj/structure/lattice,
- /turf/open/floor,
- /turf/open/indestructible/clock_spawn_room,
- /turf/closed/wall,
- /obj/structure/falsewall)
- smooth = SMOOTH_MORE
+ icon_state = "catwalk_clockwork-0"
+ base_icon_state = "catwalk_clockwork"
+ smoothing_flags = NONE
+ smoothing_groups = SMOOTH_GROUP_CATWALK + SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_OPEN_FLOOR
+ canSmoothWith = SMOOTH_GROUP_CATWALK
/obj/structure/lattice/catwalk/clockwork/Initialize(mapload)
. = ..()
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 66d75d7e3125..79d7272b041c 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -17,6 +17,7 @@ GLOBAL_LIST_EMPTY(lockers)
max_integrity = 200
integrity_failure = 50
armor = list(MELEE = 20, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 10, BIO = 0, RAD = 0, FIRE = 70, ACID = 60)
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
var/breakout_time = 1200
var/message_cooldown
var/can_weld_shut = TRUE
@@ -33,6 +34,7 @@ GLOBAL_LIST_EMPTY(lockers)
var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
var/anchorable = TRUE
var/icon_welded = "welded"
+ var/icon_broken = "sparking"
/// Protection against weather that being inside of it provides.
var/list/weather_protection = null
/// How close being inside of the thing provides complete pressure safety. Must be between 0 and 1!
@@ -49,25 +51,35 @@ GLOBAL_LIST_EMPTY(lockers)
var/door_anim_angle = 136
var/door_hinge_x = -6.5
var/door_anim_time = 2.5 // set to 0 to make the door not animate at all
+ /// true whenever someone with the strong pull component (or magnet modsuit module) is dragging this, preventing opening
+ var/strong_grab = FALSE //dripstation edit
+
+ var/is_maploaded = FALSE
/obj/structure/closet/Initialize(mapload)
. = ..()
- if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
- . = INITIALIZE_HINT_LATELOAD
+ if(is_station_level(z) && mapload)
+ add_to_roundstart_list()
+
+ // if closed, any item at the crate's loc is put in the contents
+ if (mapload)
+ is_maploaded = TRUE
+ . = INITIALIZE_HINT_LATELOAD
- update_appearance(UPDATE_ICON)
PopulateContents()
var/static/list/loc_connections = list(
COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock),
)
AddElement(/datum/element/connect_loc, loc_connections)
- GLOB.lockers += src
+
+ update_appearance()
/obj/structure/closet/LateInitialize()
. = ..()
- take_contents()
+ if(!opened && is_maploaded)
+ take_contents()
//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
/obj/structure/closet/proc/PopulateContents()
@@ -84,8 +96,16 @@ GLOBAL_LIST_EMPTY(lockers)
update_appearance()
. = TRUE
+/obj/structure/closet/update_appearance(updates=ALL)
+ . = ..()
+ if(opened || broken || !secure)
+ luminosity = 0
+ return
+ luminosity = 1
+
/obj/structure/closet/update_overlays()
. = ..()
+ //SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(opened)
layer = BELOW_OBJ_LAYER
if(is_animating_door)
@@ -104,11 +124,17 @@ GLOBAL_LIST_EMPTY(lockers)
. += "[icon_state]_door"
if(welded)
. += icon_welded
- if(secure && !broken)
- if(locked)
- . += "locked"
- else
- . += "unlocked"
+ if(broken && secure)
+ . += mutable_appearance(icon, icon_broken, alpha = alpha)
+ . += emissive_appearance(icon, icon_broken, src, alpha = alpha)
+ return
+ if(broken || !secure)
+ return
+ //Overlay is similar enough for both that we can use the same mask for both
+ //luminosity = 1
+ //SSvis_overlays.add_vis_overlay(src, icon, "locked", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha)
+ . += emissive_appearance(icon, "locked", src, alpha = src.alpha)
+ . += locked ? "locked" : "unlocked"
/obj/structure/closet/proc/animate_door(closing = FALSE)
if(!door_anim_time)
@@ -138,7 +164,7 @@ GLOBAL_LIST_EMPTY(lockers)
/obj/structure/closet/proc/end_door_animation()
is_animating_door = FALSE
vis_contents -= door_obj
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/structure/closet/proc/get_door_transform(angle)
var/matrix/M = matrix()
@@ -171,7 +197,11 @@ GLOBAL_LIST_EMPTY(lockers)
/obj/structure/closet/proc/can_open(mob/living/user)
if(welded || locked)
- return FALSE
+ to_chat(user, span_danger("[src] locked or welded to be opened.")) //dripstation edit
+ return FALSE //dripstation edit
+ if(strong_grab) //dripstation edit
+ to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening.")) //dripstation edit
+ return FALSE //dripstation edit
var/turf/T = get_turf(src)
for(var/mob/living/L in T)
@@ -217,7 +247,7 @@ GLOBAL_LIST_EMPTY(lockers)
density = FALSE
dump_contents()
animate_door(FALSE)
- update_appearance(UPDATE_ICON)
+ update_appearance()
update_airtightness()
return 1
@@ -270,7 +300,7 @@ GLOBAL_LIST_EMPTY(lockers)
opened = FALSE
density = TRUE
animate_door(TRUE)
- update_appearance(UPDATE_ICON)
+ update_appearance()
update_airtightness()
close_storage(user)
return TRUE
@@ -344,7 +374,7 @@ GLOBAL_LIST_EMPTY(lockers)
user.visible_message(span_notice("[user] [welded ? "welds shut" : "unwelded"] \the [src]."),
span_notice("You [welded ? "weld" : "unwelded"] \the [src] with \the [W]."),
span_italics("You hear welding."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
else if(W.tool_behaviour == TOOL_WRENCH && anchorable)
if(isinspace() && !anchored)
return
@@ -520,7 +550,7 @@ GLOBAL_LIST_EMPTY(lockers)
locked = !locked
user.visible_message(span_notice("[user] [locked ? null : "un"]locks [src]."),
span_notice("You [locked ? null : "un"]lock [src]."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
else if(!silent)
to_chat(user, span_notice("Access Denied"))
else if(secure && broken)
@@ -535,7 +565,7 @@ GLOBAL_LIST_EMPTY(lockers)
playsound(src, "sparks", 50, 1)
broken = TRUE
locked = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
return TRUE
/obj/structure/closet/get_remote_view_fullscreens(mob/user)
@@ -552,7 +582,7 @@ GLOBAL_LIST_EMPTY(lockers)
if(secure && !broken && !(. & EMP_PROTECT_SELF))
if(prob(5 * severity))
locked = !locked
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(prob(2 * severity) && !opened)
if(!locked)
open()
@@ -663,3 +693,8 @@ GLOBAL_LIST_EMPTY(lockers)
. = ..()
if(user in contents)
return FALSE
+
+///Adds the closet to a global list. Placed in its own proc so that crates may be excluded.
+/obj/structure/closet/proc/add_to_roundstart_list()
+ //GLOB.roundstart_station_closets += src
+ GLOB.lockers += src
diff --git a/code/game/objects/structures/crates_lockers/closets/bluespace_locker.dm b/code/game/objects/structures/crates_lockers/closets/bluespace_locker.dm
index 817974090a18..f43afb5fcb7c 100644
--- a/code/game/objects/structures/crates_lockers/closets/bluespace_locker.dm
+++ b/code/game/objects/structures/crates_lockers/closets/bluespace_locker.dm
@@ -112,13 +112,13 @@
else
. += image(other.icon, "[other.icon_state]_open")
-/obj/structure/closet/bluespace/external/onTransitZ(old_z,new_z)
+/obj/structure/closet/bluespace/external/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
var/obj/structure/closet/O = get_other_locker()
if(O)
var/area/A = get_area(O)
if(A)
for(var/atom/movable/M in A)
- M.onTransitZ(old_z,new_z)
+ M.on_changed_z_level(old_turf, new_turf)
return ..()
/obj/structure/closet/bluespace/internal/proc/update_mirage()
@@ -163,7 +163,7 @@
return TRUE
-/obj/structure/closet/bluespace/external/Moved()
+/obj/structure/closet/bluespace/external/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
var/obj/structure/closet/bluespace/internal/C = get_other_locker()
if(C)
C.update_mirage()
@@ -184,7 +184,7 @@
blocks_air = 1
name = "holographic projection"
desc = "A holographic projection of the area surrounding the bluespace locker"
- flags_1 = NOJAUNT_1
+ turf_flags = NOJAUNT
var/turf/internal_origin
var/turf/external_origin
var/turf/external_origin_prev
@@ -233,7 +233,7 @@
dx--
var/list/fullbrights = list()
var/area/A = target_turf.loc
- if(!IS_DYNAMIC_LIGHTING(A))
+ if(!A.static_lighting)
fullbrights += new /obj/effect/fullbright()
for(var/cdir in GLOB.cardinals)
if(!(glide_dir & cdir))
@@ -248,7 +248,7 @@
if(odir == 2)
py = -32
A = target_turf.loc
- if(!IS_DYNAMIC_LIGHTING(A))
+ if(!A.static_lighting)
var/obj/effect/fullbright/F = new()
switch(odir)
if(1)
diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
index 131ae53d32e0..9b0bd4e4a69f 100644
--- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
+++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
@@ -48,14 +48,25 @@
if(!L.stat)
if(!L.incapacitated(ignore_restraints = 1))
L.face_atom(src)
- L.do_alert_animation(L)
+ L.do_alert_animation()
playsound(loc, 'sound/machines/chime.ogg', 50, FALSE, -5)
-/mob/living/proc/do_alert_animation(atom/A)
- var/image/I = image('icons/obj/closet.dmi', A, "cardboard_special", A.layer+1)
- flick_overlay_view(I, A, 8)
- I.alpha = 0
- animate(I, pixel_z = 32, alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING)
+/// Does the MGS ! animation
+/atom/proc/do_alert_animation()
+ var/mutable_appearance/alert = mutable_appearance('icons/obj/closet.dmi', "cardboard_special")
+ SET_PLANE_EXPLICIT(alert, ABOVE_LIGHTING_PLANE, src)
+ var/atom/movable/flick_visual/exclamation = flick_overlay_view(alert, 1 SECONDS)
+ exclamation.alpha = 0
+ exclamation.pixel_x = -pixel_x
+ animate(exclamation, pixel_z = 32, alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING)
+ // We use this list to update plane values on parent z change, which is why we need the timer too
+ // I'm sorry :(
+ LAZYADD(update_on_z, exclamation)
+ // Intentionally less time then the flick so we don't get weird shit
+ addtimer(CALLBACK(src, PROC_REF(forget_alert), exclamation), 0.8 SECONDS, TIMER_CLIENT_TIME)
+
+/atom/proc/forget_alert(atom/movable/flick_visual/exclamation)
+ LAZYREMOVE(update_on_z, exclamation)
/obj/structure/closet/cardboard/metal
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
index a2fc15b286f4..c4764501e8c9 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
@@ -8,6 +8,8 @@
new /obj/item/storage/lockbox/medal/cargo(src)
new /obj/item/clothing/under/rank/cargo(src)
new /obj/item/clothing/under/rank/cargo/skirt(src)
+ new /obj/item/clothing/under/rank/cargo/turtleneck(src)
+ new /obj/item/clothing/under/rank/cargo/skirt/turtleneck(src)
new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/radio/headset/headset_cargo(src)
new /obj/item/clothing/suit/fire/firefighter(src)
@@ -23,3 +25,4 @@
new /obj/item/storage/photo_album/QM(src)
new /obj/item/circuitboard/machine/ore_silo(src)
new /obj/item/card/id/departmental_budget/car(src)
+ new /obj/item/clothing/suit/hooded/wintercoat/qm(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
index ccf8fe462c4b..f92299928483 100644
--- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
@@ -26,7 +26,7 @@
if (prob(40))
new /obj/item/storage/toolbox/emergency(src)
- switch (pickweight(list("small" = 35, "aid" = 30, "tank" = 20, "both" = 10, "nothing" = 4, "delete" = 1)))
+ switch (pickweight(list("small" = 35, "aid" = 30, "tank" = 20, "both" = 10, "nothing" = 5)))
if ("small")
new /obj/item/tank/internals/emergency_oxygen(src)
new /obj/item/tank/internals/emergency_oxygen(src)
@@ -48,10 +48,7 @@
if ("nothing")
// doot
-
- // teehee
- if ("delete")
- qdel(src)
+ return
/*
* Fire Closet
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index 26ffaa271b51..30b8a116b360 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -319,7 +319,7 @@
qdel(src)
/obj/structure/door_assembly/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- if(the_rcd.mode == RCD_DECONSTRUCT)
+ if(the_rcd.construction_mode == RCD_DECONSTRUCT)
return list("mode" = RCD_DECONSTRUCT, "delay" = 50, "cost" = 16)
return FALSE
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index 3ca29103d44a..1ef8a1203a87 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -5,27 +5,22 @@
name = "wall"
desc = "A huge chunk of metal used to separate rooms."
anchored = TRUE
- icon = 'icons/turf/walls/wall.dmi'
- icon_state = "wall"
- layer = CLOSED_TURF_LAYER
+ icon = 'icons/turf/walls/false_walls.dmi'
+ icon_state = "wall-open"
+ base_icon_state = "wall"
+ layer = LOW_OBJ_LAYER
density = TRUE
opacity = TRUE
max_integrity = 100
-
- canSmoothWith = list(
- /turf/closed/wall,
- /turf/closed/wall/r_wall,
- /obj/structure/falsewall,
- /obj/structure/falsewall/brass,
- /obj/structure/falsewall/reinforced,
- /turf/closed/wall/rust,
- /turf/closed/wall/r_wall/rust,
- /turf/closed/wall/clockwork)
- smooth = SMOOTH_TRUE
- can_be_unanchored = FALSE
- CanAtmosPass = ATMOS_PASS_DENSITY
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WALLS
flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ can_atmos_pass = ATMOS_PASS_DENSITY
rad_insulation = RAD_MEDIUM_INSULATION
+
+ /// The icon this falsewall is faking being. we'll switch out our icon with this when we're in fake mode
+ var/fake_icon = 'icons/turf/walls/wall.dmi'
var/mineral = /obj/item/stack/sheet/metal
var/mineral_amount = 2
var/walltype = /turf/closed/wall
@@ -45,13 +40,21 @@
new /obj/structure/falsewall/bananium(loc)
qdel(src)
-/obj/structure/falsewall/attack_hand(mob/user)
+/obj/structure/falsewall/attack_hand(mob/user, list/modifiers)
if(opening)
return
. = ..()
if(.)
return
- toggle(user)
+
+ opening = TRUE
+ update_appearance()
+ if(!density)
+ var/srcturf = get_turf(src)
+ for(var/mob/living/obstacle in srcturf) //Stop people from using this as a shield
+ opening = FALSE
+ return
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/structure/falsewall, toggle_open)), 5)
/obj/structure/falsewall/proc/toggle(mob/user)
if(!density)
@@ -59,7 +62,7 @@
for(var/mob/living/obstacle in srcturf) //Stop people from using this as a shield
return
opening = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
addtimer(CALLBACK(src, /obj/structure/falsewall/proc/toggle_open), 5)
/obj/structure/falsewall/proc/toggle_open()
@@ -67,29 +70,37 @@
density = !density
set_opacity(density)
opening = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
air_update_turf()
-/obj/structure/falsewall/update_icon_state()
+/obj/structure/falsewall/update_icon(updates=ALL)//Calling icon_update will refresh the smoothwalls if it's closed, otherwise it will make sure the icon is correct if it's open
. = ..()
+ if(!density || !(updates & UPDATE_SMOOTHING))
+ return
+
if(opening)
- if(density)
- icon_state = "fwall_opening"
- smooth = SMOOTH_FALSE
- clear_smooth_overlays()
- else
- icon_state = "fwall_closing"
+ smoothing_flags = NONE
+ clear_smooth_overlays()
else
- if(density)
- icon_state = initial(icon_state)
- smooth = SMOOTH_TRUE
- queue_smooth(src)
- else
- icon_state = "fwall_open"
+ smoothing_flags = SMOOTH_BITMASK
+ QUEUE_SMOOTH(src)
+
+/obj/structure/falsewall/update_icon_state()
+ if(opening)
+ icon = initial(icon)
+ icon_state = "[base_icon_state]-[density ? "opening" : "closing"]"
+ return ..()
+ if(density)
+ icon = fake_icon
+ icon_state = "[base_icon_state]-[smoothing_junction]"
+ else
+ icon = initial(icon)
+ icon_state = "[base_icon_state]-open"
+ return ..()
/obj/structure/falsewall/proc/ChangeToWall(delete = 1)
var/turf/T = get_turf(src)
- T.PlaceOnTop(walltype)
+ T.place_on_top(walltype)
if(delete)
qdel(src)
return T
@@ -162,10 +173,12 @@
/obj/structure/falsewall/reinforced
name = "reinforced wall"
desc = "A huge chunk of reinforced metal used to separate rooms."
- icon = 'icons/turf/walls/reinforced_wall.dmi'
- icon_state = "r_wall"
+ fake_icon = 'icons/turf/walls/reinforced_wall.dmi'
+ icon_state = "reinforced_wall-open"
+ base_icon_state = "reinforced_wall"
walltype = /turf/closed/wall/r_wall
mineral = /obj/item/stack/sheet/plasteel
+ smoothing_flags = SMOOTH_BITMASK
/obj/structure/falsewall/reinforced/examine_status(mob/user)
return span_notice("The outer grille is fully intact.")
@@ -182,13 +195,20 @@
/obj/structure/falsewall/uranium
name = "uranium wall"
desc = "A wall with uranium plating. This is probably a bad idea."
- icon = 'icons/turf/walls/uranium_wall.dmi'
- icon_state = "uranium"
+ fake_icon = 'icons/turf/walls/uranium_wall.dmi'
+ icon_state = "uranium_wall-open"
+ base_icon_state = "uranium_wall"
mineral = /obj/item/stack/sheet/mineral/uranium
walltype = /turf/closed/wall/mineral/uranium
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_URANIUM_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_URANIUM_WALLS
+
+ /// Mutex to prevent infinite recursion when propagating radiation pulses
var/active = null
+
+ /// The last time a radiation pulse was performed
var/last_event = 0
- canSmoothWith = list(/obj/structure/falsewall/uranium, /turf/closed/wall/mineral/uranium)
/obj/structure/falsewall/uranium/attackby(obj/item/W, mob/user, params)
radiate()
@@ -216,39 +236,51 @@
/obj/structure/falsewall/gold
name = "gold wall"
desc = "A wall with gold plating. Swag!"
- icon = 'icons/turf/walls/gold_wall.dmi'
- icon_state = "gold"
+ fake_icon = 'icons/turf/walls/gold_wall.dmi'
+ icon_state = "gold_wall-open"
+ base_icon_state = "gold_wall"
mineral = /obj/item/stack/sheet/mineral/gold
walltype = /turf/closed/wall/mineral/gold
- canSmoothWith = list(/obj/structure/falsewall/gold, /turf/closed/wall/mineral/gold)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_GOLD_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_GOLD_WALLS
/obj/structure/falsewall/silver
name = "silver wall"
desc = "A wall with silver plating. Shiny."
- icon = 'icons/turf/walls/silver_wall.dmi'
- icon_state = "silver"
+ fake_icon = 'icons/turf/walls/silver_wall.dmi'
+ icon_state = "silver_wall-open"
+ base_icon_state = "silver_wall"
mineral = /obj/item/stack/sheet/mineral/silver
walltype = /turf/closed/wall/mineral/silver
- canSmoothWith = list(/obj/structure/falsewall/silver, /turf/closed/wall/mineral/silver)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SILVER_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_SILVER_WALLS
/obj/structure/falsewall/diamond
name = "diamond wall"
desc = "A wall with diamond plating. You monster."
- icon = 'icons/turf/walls/diamond_wall.dmi'
- icon_state = "diamond"
+ fake_icon = 'icons/turf/walls/diamond_wall.dmi'
+ icon_state = "diamond_wall-open"
+ base_icon_state = "diamond_wall"
mineral = /obj/item/stack/sheet/mineral/diamond
walltype = /turf/closed/wall/mineral/diamond
- canSmoothWith = list(/obj/structure/falsewall/diamond, /turf/closed/wall/mineral/diamond)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_DIAMOND_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_DIAMOND_WALLS
max_integrity = 800
/obj/structure/falsewall/plasma
name = "plasma wall"
desc = "A wall with plasma plating. This is definitely a bad idea."
- icon = 'icons/turf/walls/plasma_wall.dmi'
- icon_state = "plasma"
+ fake_icon = 'icons/turf/walls/plasma_wall.dmi'
+ icon_state = "plasma_wall-open"
+ base_icon_state = "plasma_wall"
mineral = /obj/item/stack/sheet/mineral/plasma
walltype = /turf/closed/wall/mineral/plasma
- canSmoothWith = list(/obj/structure/falsewall/plasma, /turf/closed/wall/mineral/plasma)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_PLASMA_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_PLASMA_WALLS
/obj/structure/falsewall/plasma/attackby(obj/item/W, mob/user, params)
if(W.is_hot() > 300)
@@ -272,11 +304,14 @@
/obj/structure/falsewall/bananium
name = "bananium wall"
desc = "A wall with bananium plating. Honk!"
- icon = 'icons/turf/walls/bananium_wall.dmi'
- icon_state = "bananium"
+ fake_icon = 'icons/turf/walls/bananium_wall.dmi'
+ icon_state = "bananium_wall-open"
+ base_icon_state = "bananium_wall"
mineral = /obj/item/stack/sheet/mineral/bananium
walltype = /turf/closed/wall/mineral/bananium
- canSmoothWith = list(/obj/structure/falsewall/bananium, /turf/closed/wall/mineral/bananium)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_BANANIUM_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_BANANIUM_WALLS
/obj/structure/falsewall/bananium/honk_act()
return FALSE
@@ -284,80 +319,102 @@
/obj/structure/falsewall/sandstone
name = "sandstone wall"
desc = "A wall with sandstone plating. Rough."
- icon = 'icons/turf/walls/sandstone_wall.dmi'
- icon_state = "sandstone"
+ fake_icon = 'icons/turf/walls/sandstone_wall.dmi'
+ icon_state = "sandstone_wall-open"
+ base_icon_state = "sandstone_wall"
mineral = /obj/item/stack/sheet/mineral/sandstone
walltype = /turf/closed/wall/mineral/sandstone
- canSmoothWith = list(/obj/structure/falsewall/sandstone, /turf/closed/wall/mineral/sandstone)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SANDSTONE_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_SANDSTONE_WALLS
/obj/structure/falsewall/wood
name = "wooden wall"
desc = "A wall with wooden plating. Stiff."
- icon = 'icons/turf/walls/wood_wall.dmi'
- icon_state = "wood"
+ fake_icon = 'icons/turf/walls/wood_wall.dmi'
+ icon_state = "wood_wall-open"
+ base_icon_state = "wood_wall"
mineral = /obj/item/stack/sheet/mineral/wood
walltype = /turf/closed/wall/mineral/wood
- canSmoothWith = list(/obj/structure/falsewall/wood, /turf/closed/wall/mineral/wood)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_WOOD_WALLS
/obj/structure/falsewall/bamboo
name = "bamboo wall"
desc = "A wall with bamboo finish. Zen."
- icon = 'icons/turf/walls/bamboo_wall.dmi'
- icon_state = "bamboo"
+ fake_icon = 'icons/turf/walls/bamboo_wall.dmi'
+ icon_state = "bamboo_wall-open"
+ base_icon_state = "bamboo_wall"
mineral = /obj/item/stack/sheet/mineral/bamboo
walltype = /turf/closed/wall/mineral/bamboo
- canSmoothWith = list(/obj/structure/falsewall/bamboo, /turf/closed/wall/mineral/bamboo)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_BAMBOO_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_BAMBOO_WALLS
/obj/structure/falsewall/iron
name = "rough metal wall"
desc = "A wall with rough metal plating."
- icon = 'icons/turf/walls/iron_wall.dmi'
- icon_state = "iron"
+ fake_icon = 'icons/turf/walls/iron_wall.dmi'
+ icon_state = "iron_wall-open"
+ base_icon_state = "iron_wall"
mineral = /obj/item/stack/rods
mineral_amount = 2
walltype = /turf/closed/wall/mineral/iron
- canSmoothWith = list(/obj/structure/falsewall/iron, /turf/closed/wall/mineral/iron)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_IRON_WALLS
/obj/structure/falsewall/abductor
name = "alien wall"
desc = "A wall with alien alloy plating."
- icon = 'icons/turf/walls/abductor_wall.dmi'
- icon_state = "abductor"
+ fake_icon = 'icons/turf/walls/abductor_wall.dmi'
+ icon_state = "abductor_wall-open"
+ base_icon_state = "abductor_wall"
mineral = /obj/item/stack/sheet/mineral/abductor
walltype = /turf/closed/wall/mineral/abductor
- canSmoothWith = list(/obj/structure/falsewall/abductor, /turf/closed/wall/mineral/abductor)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS
/obj/structure/falsewall/titanium
name = "wall"
desc = "A light-weight titanium wall used in shuttles."
- icon = 'icons/turf/walls/shuttle_wall.dmi'
- icon_state = "shuttle"
+ fake_icon = 'icons/turf/walls/shuttle_wall.dmi'
+ icon_state = "shuttle_wall-open"
+ base_icon_state = "shuttle_wall"
mineral = /obj/item/stack/sheet/mineral/titanium
walltype = /turf/closed/wall/mineral/titanium
- smooth = SMOOTH_MORE
- canSmoothWith = list(/turf/closed/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/shuttle, /obj/structure/shuttle/engine/heater)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_TITANIUM_WALLS
/obj/structure/falsewall/plastitanium
name = "wall"
desc = "An evil wall of plasma and titanium."
- icon = 'icons/turf/walls/plastitanium_wall.dmi'
- icon_state = "shuttle"
+ fake_icon = 'icons/turf/walls/plastitanium_wall.dmi'
+ icon_state = "plastitanium_wall-open"
+ base_icon_state = "plastitanium_wall"
mineral = /obj/item/stack/sheet/mineral/plastitanium
walltype = /turf/closed/wall/mineral/plastitanium
- smooth = SMOOTH_MORE
- canSmoothWith = list(/turf/closed/wall/mineral/plastitanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/shuttle, /obj/structure/shuttle/engine/heater)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS
/obj/structure/falsewall/brass
name = "clockwork wall"
desc = "A huge chunk of warm metal. The clanging of machinery emanates from within."
- icon = 'icons/turf/walls/clockwork_wall.dmi'
- icon_state = "clockwork_wall"
+ fake_icon = 'icons/turf/walls/clockwork_wall.dmi'
+ icon_state = "clockwork_wall-open"
+ base_icon_state = "clockwork_wall"
resistance_flags = FIRE_PROOF | ACID_PROOF
mineral_amount = 1
- canSmoothWith = list(/obj/effect/clockwork/overlay/wall, /obj/structure/falsewall/brass)
girder_type = /obj/structure/destructible/clockwork/wall_gear/displaced
walltype = /turf/closed/wall/clockwork
mineral = /obj/item/stack/tile/brass
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_CLOCKWORK_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_CLOCKWORK_WALLS
/obj/structure/falsewall/brass/New(loc)
..()
@@ -373,3 +430,66 @@
/obj/structure/falsewall/brass/ratvar_act()
if(GLOB.ratvar_awakens)
obj_integrity = max_integrity
+
+/obj/structure/falsewall/material
+ name = "wall"
+ desc = "A huge chunk of material used to separate rooms."
+ fake_icon = 'icons/turf/walls/material_wall.dmi'
+ icon_state = "material_wall-open"
+ base_icon_state = "material_wall"
+ walltype = /turf/closed/wall/material
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_MATERIAL_WALLS
+ canSmoothWith = SMOOTH_GROUP_MATERIAL_WALLS
+ material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+
+/obj/structure/falsewall/material/deconstruct(disassembled = TRUE)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ if(disassembled)
+ new girder_type(loc)
+ for(var/material in custom_materials)
+ var/datum/material/material_datum = material
+ new material_datum.sheet_type(loc, FLOOR(custom_materials[material_datum] / SHEET_MATERIAL_AMOUNT, 1))
+ qdel(src)
+
+/obj/structure/falsewall/material/mat_update_desc(mat)
+ desc = "A huge chunk of [mat] used to separate rooms."
+
+/obj/structure/falsewall/material/toggle_open()
+ if(!QDELETED(src))
+ set_density(!density)
+ var/mat_opacity = TRUE
+ for(var/datum/material/mat in custom_materials)
+ if(mat.alpha < 255)
+ mat_opacity = FALSE
+ break
+ set_opacity(density && mat_opacity)
+ opening = FALSE
+ update_appearance()
+ air_update_turf(TRUE, !density)
+
+/obj/structure/falsewall/material/ChangeToWall(delete = 1)
+ var/turf/current_turf = get_turf(src)
+ var/turf/closed/wall/material/new_wall = current_turf.place_on_top(/turf/closed/wall/material)
+ new_wall.set_custom_materials(custom_materials)
+ if(delete)
+ qdel(src)
+ return current_turf
+
+/obj/structure/falsewall/material/update_icon(updates)
+ . = ..()
+ for(var/datum/material/mat in custom_materials)
+ if(mat.alpha < 255)
+ update_transparency_underlays()
+ return
+
+/obj/structure/falsewall/material/proc/update_transparency_underlays()
+ underlays.Cut()
+ var/girder_icon_state = "displaced"
+ if(opening)
+ girder_icon_state += "_[density ? "opening" : "closing"]"
+ else if(!density)
+ girder_icon_state += "_open"
+ var/mutable_appearance/girder_underlay = mutable_appearance('icons/obj/structures.dmi', girder_icon_state, layer = LOW_OBJ_LAYER-0.01)
+ girder_underlay.appearance_flags = RESET_ALPHA | RESET_COLOR
+ underlays += girder_underlay
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index d594114ca22c..9b42aad57ba6 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -3,6 +3,14 @@
max_integrity = 150
anchored = TRUE
+//yogs edit
+/obj/structure/flora/ex_act(severity, target)
+ . = ..()
+ if(severity == 1 || severity == 2)
+ qdel(src)
+//yogs end
+
+
//trees
/obj/structure/flora/tree
name = "tree"
@@ -35,7 +43,7 @@
/obj/structure/flora/stump
name = "stump"
desc = "This represents our promise to the crew, and the station itself, to cut down as many trees as possible." //running naked through the trees
- icon = 'icons/obj/flora/pinetrees.dmi'
+ icon = 'icons/obj/flora/deadtrees.dmi' //yog
icon_state = "tree_stump"
density = FALSE
pixel_x = -16
@@ -394,7 +402,7 @@
/obj/structure/flora/rock/pile
icon_state = "lavarocks"
desc = "A pile of rocks."
-
+ density = FALSE //yogs
//Jungle grass
/obj/structure/flora/grass/jungle
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index c007459bf5e7..f0438d4b89e2 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -10,6 +10,7 @@
max_integrity = 200
flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
rad_insulation = RAD_VERY_LIGHT_INSULATION
+ var/next_beep = 0 //Prevents spamming of the construction sound, dripstation edit
/obj/structure/girder/examine(mob/user)
. = ..()
@@ -27,6 +28,12 @@
. += span_notice("[src] is disassembled! You probably shouldn't be able to see this examine message.")
/obj/structure/girder/attackby(obj/item/W, mob/user, params)
+ var/platingmodifier = 1 //dripstation edit start
+ if(HAS_TRAIT(user, TRAIT_QUICK_BUILD))
+ platingmodifier = 0.7
+ if(next_beep <= world.time)
+ next_beep = world.time + 10
+ playsound(src, 'sound/machines/clockcult/integration_cog_install.ogg', 50, TRUE) //dripstation edit end
add_fingerprint(user)
if(istype(W, /obj/item/gun/energy/plasmacutter))
@@ -55,7 +62,7 @@
to_chat(user, span_warning("You need at least two rods to create a false wall!"))
return
to_chat(user, span_notice("You start building a reinforced false wall..."))
- if(do_after(user, 2 SECONDS, src))
+ if(do_after(user, 20*platingmodifier, src)) //dripstation edit
if(S.get_amount() < 2)
return
S.use(2)
@@ -68,13 +75,13 @@
to_chat(user, span_warning("You need at least five rods to add plating!"))
return
to_chat(user, span_notice("You start adding plating..."))
- if(do_after(user, 4 SECONDS, src))
+ if(do_after(user, 40*platingmodifier, src)) //dripstation edit
if(S.get_amount() < 5)
return
S.use(5)
to_chat(user, span_notice("You add the plating."))
var/turf/T = get_turf(src)
- T.PlaceOnTop(/turf/closed/wall/mineral/iron)
+ T.place_on_top(/turf/closed/wall/mineral/iron)
transfer_fingerprints_to(T)
qdel(src)
return
@@ -108,7 +115,7 @@
S.use(2)
to_chat(user, span_notice("You add the plating."))
var/turf/T = get_turf(src)
- T.PlaceOnTop(/turf/closed/wall)
+ T.place_on_top(/turf/closed/wall)
transfer_fingerprints_to(T)
qdel(src)
return
@@ -138,7 +145,7 @@
S.use(1)
to_chat(user, span_notice("You fully reinforce the wall."))
var/turf/T = get_turf(src)
- T.PlaceOnTop(/turf/closed/wall/r_wall)
+ T.place_on_top(/turf/closed/wall/r_wall)
transfer_fingerprints_to(T)
qdel(src)
return
@@ -182,7 +189,7 @@
S.use(2)
to_chat(user, span_notice("You add the plating."))
var/turf/T = get_turf(src)
- T.PlaceOnTop(text2path("/turf/closed/wall/mineral/[M]"))
+ T.place_on_top(text2path("/turf/closed/wall/mineral/[M]"))
transfer_fingerprints_to(T)
qdel(src)
return
@@ -355,7 +362,7 @@
user.visible_message(span_notice("[user] plates [src] with runed metal."), span_notice("You construct a runed wall."))
R.use(1)
var/turf/T = get_turf(src)
- T.PlaceOnTop(/turf/closed/wall/mineral/cult)
+ T.place_on_top(/turf/closed/wall/mineral/cult)
qdel(src)
else
@@ -370,7 +377,7 @@
qdel(src)
/obj/structure/girder/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_FLOORWALL)
return list("mode" = RCD_FLOORWALL, "delay" = 20, "cost" = 8)
if(RCD_DECONSTRUCT)
@@ -382,7 +389,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, span_notice("You finish a wall."))
- T.PlaceOnTop(/turf/closed/wall)
+ T.place_on_top(/turf/closed/wall)
qdel(src)
return TRUE
if(RCD_DECONSTRUCT)
@@ -422,7 +429,7 @@
user.visible_message(span_notice("[user] plates [src] with bronze!"), span_notice("You construct a bronze wall."))
B.use(2)
var/turf/T = get_turf(src)
- T.PlaceOnTop(/turf/closed/wall/mineral/bronze)
+ T.place_on_top(/turf/closed/wall/mineral/bronze)
qdel(src)
else
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 57547fbedd0a..999054d19ef1 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -2,7 +2,8 @@
desc = "A flimsy framework of metal rods."
name = "grille"
icon = 'icons/obj/smooth_structures/grille.dmi'
- icon_state = "grille"
+ icon_state = "grille-0"
+ base_icon_state = "grille"
density = TRUE
anchored = TRUE
flags_1 = CONDUCT_1 | RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
@@ -11,9 +12,10 @@
max_integrity = 50
integrity_failure = 20
appearance_flags = KEEP_TOGETHER
- smooth = SMOOTH_TRUE
can_be_unanchored = TRUE
- canSmoothWith = list(/obj/structure/grille, /obj/structure/grille/broken)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_GRILLE
+ canSmoothWith = SMOOTH_GROUP_GRILLE
var/holes = 0 //bitflag
var/rods_type = /obj/item/stack/rods
var/rods_amount = 2
@@ -34,12 +36,20 @@
if(broken)
holes = (holes | 16) //16 is the biggest hole
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
holes = (holes | (1 << rand(0,3))) //add random holes between 1 and 8
- update_appearance(UPDATE_ICON)
+ update_appearance()
+
+/obj/structure/grille/update_appearance(updates)
+ if(QDELETED(src))
+ return
+
+ . = ..()
+ if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)))
+ QUEUE_SMOOTH(src)
/obj/structure/grille/update_icon(updates=ALL)
. = ..()
@@ -58,7 +68,7 @@
. += span_notice("The anchoring screws are unscrewed. The rods look like they could be cut through.")
/obj/structure/grille/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_DECONSTRUCT)
return list("mode" = RCD_DECONSTRUCT, "delay" = 20, "cost" = 5)
if(RCD_WINDOWGRILLE)
@@ -160,8 +170,6 @@
setAnchored(!anchored)
user.visible_message(span_notice("[user] [anchored ? "fastens" : "unfastens"] [src]."), \
span_notice("You [anchored ? "fasten [src] to" : "unfasten [src] from"] the floor."))
- queue_smooth(src)
- queue_smooth_neighbors(src)
return
else if(istype(W, /obj/item/stack/rods) && broken)
var/obj/item/stack/rods/R = W
@@ -258,6 +266,8 @@
if(!in_range(src, user))//To prevent TK and mech users from getting shocked
return FALSE
var/turf/T = get_turf(src)
+ if(T.overfloor_placed)//cant be a floor in the way!
+ return FALSE
var/obj/structure/cable/C = T.get_cable_node()
if(C)
if(electrocute_mob(user, C, src, 1, TRUE))
@@ -281,6 +291,8 @@
var/obj/O = AM
if(O.throwforce != 0)//don't want to let people spam tesla bolts, this way it will break after time
var/turf/T = get_turf(src)
+ if(T.overfloor_placed)
+ return FALSE
var/obj/structure/cable/C = T.get_cable_node()
if(C)
playsound(src, 'sound/magic/lightningshock.ogg', 100, 1, extrarange = 5)
@@ -292,7 +304,7 @@
return null
/obj/structure/grille/broken // Pre-broken grilles for map placement
- icon_state = "grille_broken"
+ icon_state = "brokengrille"
density = FALSE
obj_integrity = 20
broken = TRUE
@@ -304,7 +316,7 @@
/obj/structure/grille/broken/Initialize(mapload)
. = ..()
holes = (holes | 16)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/structure/grille/ratvar
icon = 'icons/obj/structures.dmi'
@@ -312,7 +324,6 @@
name = "cog grille"
desc = "A strangely-shaped grille."
broken_type = /obj/structure/grille/ratvar/broken
- smooth = SMOOTH_FALSE
/obj/structure/grille/ratvar/Initialize(mapload)
. = ..()
diff --git a/code/game/objects/structures/holosign.dm b/code/game/objects/structures/holosign.dm
index e6f3ac779793..646e6717209b 100644
--- a/code/game/objects/structures/holosign.dm
+++ b/code/game/objects/structures/holosign.dm
@@ -7,19 +7,19 @@
anchored = TRUE
max_integrity = 1
armor = list(MELEE = 0, BULLET = 50, LASER = 50, ENERGY = 50, BOMB = 0, BIO = 0, RAD = 0, FIRE = 20, ACID = 20)
- var/obj/item/holosign_creator/projector
resistance_flags = FREEZE_PROOF
+ var/obj/item/holosign_creator/projector
+ var/use_vis_overlay = TRUE
-/obj/structure/holosign/New(loc, source_projector)
+/obj/structure/holosign/Initialize(mapload, source_projector)
+ . = ..()
+ var/turf/our_turf = get_turf(src)
+ if(use_vis_overlay)
+ alpha = 0
+ SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, MUTATE_PLANE(GAME_PLANE, our_turf), dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
if(source_projector)
projector = source_projector
- projector.signs += src
- ..()
-
-/obj/structure/holosign/Initialize(mapload)
- . = ..()
- alpha = 0
- SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, plane, dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
+ LAZYADD(projector.signs, src)
/obj/structure/holosign/Destroy()
if(projector)
@@ -92,7 +92,7 @@
slippery_floor = get_turf(src)
if(!slippery_floor?.GetComponent(/datum/component/slippery))
return INITIALIZE_HINT_QDEL
- RegisterSignal(slippery_floor.GetComponent(/datum/component/slippery), COMSIG_PARENT_QDELETING, PROC_REF(del_self))
+ RegisterSignal(slippery_floor.GetComponent(/datum/component/slippery), COMSIG_QDELETING, PROC_REF(del_self))
/obj/structure/holosign/barrier/wetsign/proc/del_self()
SIGNAL_HANDLER
@@ -126,7 +126,7 @@
icon_state = "holo_firelock"
density = FALSE
anchored = TRUE
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
resistance_flags = FIRE_PROOF | FREEZE_PROOF
alpha = 150
flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm
index 27ee9e973cb2..fc8f99772923 100644
--- a/code/game/objects/structures/ladders.dm
+++ b/code/game/objects/structures/ladders.dm
@@ -5,24 +5,31 @@
icon = 'icons/obj/structures.dmi'
icon_state = "ladder11"
anchored = TRUE
+ obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
var/obj/structure/ladder/down //the ladder below this one
var/obj/structure/ladder/up //the ladder above this one
+ var/crafted = FALSE
+ /// travel time for ladder in deciseconds
+ var/travel_time = 1 SECONDS
/obj/structure/ladder/Initialize(mapload, obj/structure/ladder/up, obj/structure/ladder/down)
..()
+ GLOB.ladders += src
if (up)
src.up = up
up.down = src
- up.update_appearance(UPDATE_ICON)
+ up.update_appearance()
if (down)
src.down = down
down.up = src
- down.update_appearance(UPDATE_ICON)
+ down.update_appearance()
+
return INITIALIZE_HINT_LATELOAD
/obj/structure/ladder/Destroy(force)
if ((resistance_flags & INDESTRUCTIBLE) && !force)
return QDEL_HINT_LETMELIVE
+ GLOB.ladders -= src
disconnect()
return ..()
@@ -32,91 +39,135 @@
var/obj/structure/ladder/L
if (!down)
- L = locate() in SSmapping.get_turf_below(T)
+ L = locate() in GET_TURF_BELOW(T)
if (L)
- down = L
- L.up = src // Don't waste effort looping the other way
- L.update_appearance(UPDATE_ICON)
+ if(crafted == L.crafted)
+ down = L
+ L.up = src // Don't waste effort looping the other way
+ L.update_appearance()
if (!up)
- L = locate() in SSmapping.get_turf_above(T)
+ L = locate() in GET_TURF_ABOVE(T)
if (L)
- up = L
- L.down = src // Don't waste effort looping the other way
- L.update_appearance(UPDATE_ICON)
+ if(crafted == L.crafted)
+ up = L
+ L.down = src // Don't waste effort looping the other way
+ L.update_appearance()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/structure/ladder/proc/disconnect()
if(up && up.down == src)
up.down = null
- up.update_appearance(UPDATE_ICON)
+ up.update_appearance()
if(down && down.up == src)
down.up = null
- down.update_appearance(UPDATE_ICON)
+ down.update_appearance()
up = down = null
/obj/structure/ladder/update_icon_state()
- . = ..()
- if(up && down)
- icon_state = "ladder11"
- else if(up)
- icon_state = "ladder10"
- else if(down)
- icon_state = "ladder01"
- else //wtf make your ladders properly assholes
- icon_state = "ladder00"
+ icon_state = "ladder[up ? 1 : 0][down ? 1 : 0]"
+ return ..()
/obj/structure/ladder/singularity_pull()
if (!(resistance_flags & INDESTRUCTIBLE))
visible_message(span_danger("[src] is torn to pieces by the gravitational pull!"))
qdel(src)
-/obj/structure/ladder/proc/travel(going_up, mob/user, is_ghost, obj/structure/ladder/ladder)
- if(!is_ghost)
- show_fluff_message(going_up, user)
- ladder.add_fingerprint(user)
-
- var/turf/T = get_turf(ladder)
- var/atom/movable/AM
- if(user.pulling)
- AM = user.pulling
- AM.forceMove(T)
- user.forceMove(T)
- if(AM)
- user.start_pulling(AM)
-
-/obj/structure/ladder/proc/use(mob/user, is_ghost=FALSE, direction)
- if (!is_ghost && !in_range(src, user))
+/obj/structure/ladder/proc/use(mob/user, going_up = TRUE)
+ if(!in_range(src, user) || DOING_INTERACTION(user, DOAFTER_SOURCE_CLIMBING_LADDER))
return
- if (up && down)
- if(!direction)
- direction = tgui_alert(user, "Go up or down [src]?", "Ladder", list("Up", "Down", "Cancel"))
- if (!is_ghost && !in_range(src, user))
- return // nice try
- switch(direction)
- if("Up")
- travel(TRUE, user, is_ghost, up)
- if("Down")
- travel(FALSE, user, is_ghost, down)
- if("Cancel")
- return
- else if(up)
- travel(TRUE, user, is_ghost, up)
- else if(down)
- travel(FALSE, user, is_ghost, down)
+ if(!up && !down)
+ balloon_alert(user, "doesn't lead anywhere!")
+ return
+ if(going_up ? !up : !down)
+ balloon_alert(user, "can't go any further [going_up ? "up" : "down"]")
+ return
+ if(user.buckled && user.buckled.anchored)
+ balloon_alert(user, "buckled to something anchored!")
+ return
+ if(travel_time)
+ INVOKE_ASYNC(src, PROC_REF(start_travelling), user, going_up)
else
- to_chat(user, span_warning("[src] doesn't seem to lead anywhere!"))
+ travel(user, going_up)
+ add_fingerprint(user)
+
+/obj/structure/ladder/proc/start_travelling(mob/user, going_up)
+ show_initial_fluff_message(user, going_up)
+ if(do_after(user, travel_time, target = src, interaction_key = DOAFTER_SOURCE_CLIMBING_LADDER))
+ travel(user, going_up)
+
+/// The message shown when the player starts climbing the ladder
+/obj/structure/ladder/proc/show_initial_fluff_message(mob/user, going_up)
+ var/up_down = going_up ? "up" : "down"
+ user.balloon_alert_to_viewers("climbing [up_down]...")
+
+/obj/structure/ladder/proc/travel(mob/user, going_up = TRUE, is_ghost = FALSE)
+ var/obj/structure/ladder/ladder = going_up ? up : down
+ if(!ladder)
+ balloon_alert(user, "there's nothing that way!")
+ return
+ var/response = SEND_SIGNAL(user, COMSIG_LADDER_TRAVEL, src, ladder, going_up)
+ if(response & LADDER_TRAVEL_BLOCK)
+ return
+
+ var/turf/target = get_turf(ladder)
+ user.zMove(target = target, z_move_flags = ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED|ZMOVE_INCLUDE_PULLED)
if(!is_ghost)
- add_fingerprint(user)
+ show_final_fluff_message(user, ladder, going_up)
+
+ // to avoid having players hunt for the pixels of a ladder that goes through several stories and is
+ // partially covered by the sprites of their mobs, a radial menu will be displayed over them.
+ // this way players can keep climbing up or down with ease until they reach an end.
+ if(ladder.up && ladder.down)
+ ladder.show_options(user, is_ghost)
+
+/// The messages shown after the player has finished climbing. Players can see this happen from either src or the destination so we've 2 POVs here
+/obj/structure/ladder/proc/show_final_fluff_message(mob/user, obj/structure/ladder/destination, going_up)
+ var/up_down = going_up ? "up" : "down"
+
+ //POV of players around the source
+ visible_message(span_notice("[user] climbs [up_down] [src]."))
+ //POV of players around the destination
+ user.balloon_alert_to_viewers("climbed [up_down]")
+
+/// Shows a radial menu that players can use to climb up and down a stair.
+/obj/structure/ladder/proc/show_options(mob/user, is_ghost = FALSE)
+ var/list/tool_list = list()
+ tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
+ tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
+
+ var/datum/callback/check_menu
+ if(!is_ghost)
+ check_menu = CALLBACK(src, PROC_REF(check_menu), user)
+ var/result = show_radial_menu(user, src, tool_list, custom_check = check_menu, require_near = !is_ghost, tooltips = TRUE)
+
+ var/going_up
+ switch(result)
+ if("Up")
+ going_up = TRUE
+ if("Down")
+ going_up = FALSE
+ else
+ return
+
+ if(is_ghost || !travel_time)
+ travel(user, going_up, is_ghost)
+ else
+ INVOKE_ASYNC(src, PROC_REF(start_travelling), user, going_up)
+
+/obj/structure/ladder/proc/check_menu(mob/user, is_ghost)
+ if(user.incapacitated() || (!user.Adjacent(src)))
+ return FALSE
+ return TRUE
/obj/structure/ladder/CtrlClick(mob/user)
. = ..()
if(.)
return
if(down)
- use(user, FALSE, "Down")
+ use(user, going_up = FALSE)
else
to_chat(user, span_warning("[src] doesn't seem to lead anywhere!"))
@@ -125,7 +176,7 @@
if(.)
return
if(up)
- use(user, FALSE, "Up")
+ use(user)
else
to_chat(user, span_warning("[src] doesn't seem to lead anywhere!"))
@@ -136,7 +187,8 @@
use(user)
/obj/structure/ladder/attack_paw(mob/user)
- return use(user)
+ use(user)
+ return TRUE
/obj/structure/ladder/attackby(obj/item/W, mob/user, params)
return use(user)
@@ -147,14 +199,20 @@
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/structure/ladder/attack_ghost(mob/dead/observer/user)
- use(user, TRUE)
+ ghost_use(user)
return ..()
-/obj/structure/ladder/proc/show_fluff_message(going_up, mob/user)
- if(going_up)
- user.visible_message("[user] climbs up [src].",span_notice("You climb up [src]."))
- else
- user.visible_message("[user] climbs down [src].",span_notice("You climb down [src]."))
+///Ghosts use the byond default popup menu function on right click, so this is going to work a little differently for them.
+/obj/structure/ladder/proc/ghost_use(mob/user)
+ if (!up && !down)
+ balloon_alert(user, "doesn't lead anywhere!")
+ return
+ if(!up) //only goes down
+ travel(user, going_up = FALSE, is_ghost = TRUE)
+ else if(!down) //only goes up
+ travel(user, going_up = TRUE, is_ghost = TRUE)
+ else //goes both ways
+ show_options(user, is_ghost = TRUE)
// Indestructible away mission ladders which link based on a mapped ID and height value rather than X/Y/Z.
@@ -165,10 +223,6 @@
var/id
var/height = 0 // higher numbers are considered physically higher
-/obj/structure/ladder/unbreakable/Initialize(mapload)
- GLOB.ladders += src
- return ..()
-
/obj/structure/ladder/unbreakable/Destroy()
. = ..()
if (. != QDEL_HINT_LETMELIVE)
@@ -177,27 +231,29 @@
/obj/structure/ladder/unbreakable/LateInitialize()
// Override the parent to find ladders based on being height-linked
if (!id || (up && down))
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
- for (var/O in GLOB.ladders)
- var/obj/structure/ladder/unbreakable/L = O
- if (L.id != id)
+ for(var/obj/structure/ladder/unbreakable/unbreakable_ladder in GLOB.ladders)
+ if (unbreakable_ladder.id != id)
continue // not one of our pals
- if (!down && L.height == height - 1)
- down = L
- L.up = src
- L.update_appearance(UPDATE_ICON)
+ if (!down && unbreakable_ladder.height == height - 1)
+ down = unbreakable_ladder
+ unbreakable_ladder.up = src
+ unbreakable_ladder.update_appearance()
if (up)
break // break if both our connections are filled
- else if (!up && L.height == height + 1)
- up = L
- L.down = src
- L.update_appearance(UPDATE_ICON)
+ else if (!up && unbreakable_ladder.height == height + 1)
+ up = unbreakable_ladder
+ unbreakable_ladder.down = src
+ unbreakable_ladder.update_appearance()
if (down)
break // break if both our connections are filled
- update_appearance(UPDATE_ICON)
+ update_appearance()
+
+/obj/structure/ladder/crafted
+ crafted = TRUE
/obj/structure/ladder/unbreakable/binary
name = "mysterious ladder"
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index 12e7af0c4aa6..ca111ff4fecf 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -2,19 +2,19 @@
name = "lattice"
desc = "A lightweight support lattice. These hold our station together."
icon = 'icons/obj/smooth_structures/lattice.dmi'
- icon_state = "lattice"
+ icon_state = "lattice-255"
+ base_icon_state = "lattice"
density = FALSE
anchored = TRUE
armor = list(MELEE = 50, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 50)
max_integrity = 50
layer = LATTICE_LAYER //under pipes
plane = FLOOR_PLANE
+ obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_LATTICE
+ canSmoothWith = SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_OPEN_FLOOR
var/number_of_rods = 1
- canSmoothWith = list(/obj/structure/lattice,
- /turf/open/floor,
- /turf/closed/wall,
- /obj/structure/falsewall)
- smooth = SMOOTH_MORE
// flags = CONDUCT_1
/obj/structure/lattice/examine(mob/user)
@@ -52,7 +52,7 @@
qdel(src)
/obj/structure/lattice/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- if(the_rcd.mode == RCD_FLOORWALL)
+ if(the_rcd.construction_mode == RCD_FLOORWALL)
return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 2)
/obj/structure/lattice/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
@@ -60,7 +60,7 @@
to_chat(user, span_notice("You build a floor."))
var/turf/T = src.loc
if(isspaceturf(T))
- T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ T.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
qdel(src)
return TRUE
return FALSE
@@ -71,11 +71,13 @@
/obj/structure/lattice/clockwork
name = "cog lattice"
- desc = "A lightweight support lattice. These hold the Justicar's station together."
+ desc = "A lightweight support lattice. These hold the Justiciar's station together."
icon = 'icons/obj/smooth_structures/lattice_clockwork.dmi'
+ smoothing_flags = NONE
+ smoothing_groups = null
+ canSmoothWith = null
/obj/structure/lattice/clockwork/Initialize(mapload)
- canSmoothWith += /turf/open/indestructible/clock_spawn_room //list overrides are a terrible thing
. = ..()
ratvar_act()
if(is_reebe(z))
diff --git a/code/game/objects/structures/lavaland/geyser.dm b/code/game/objects/structures/lavaland/geyser.dm
index 385115227669..efc741377471 100644
--- a/code/game/objects/structures/lavaland/geyser.dm
+++ b/code/game/objects/structures/lavaland/geyser.dm
@@ -28,13 +28,6 @@
var/true_name
///the message given when you discover this geyser.
var/discovery_message = null
- ///The internal GPS once this is discovered
- var/obj/item/gps/internal
-
-/obj/structure/geyser/Destroy()
- QDEL_NULL(internal)
- . = ..()
-
/obj/structure/geyser/proc/start_chemming()
activated = TRUE
@@ -80,8 +73,7 @@
if(true_name)
name = true_name
- internal = new /obj/item/gps/internal/geyser(src) //put it on the gps so miners can mark it and chemists can profit off of it //Yogs - LOL NO!
- internal.gpstag = true_name
+ AddComponent(/datum/component/gps, "Geyser")
if(isliving(user))
var/mob/living/living = user
@@ -125,12 +117,6 @@
. = ..()
reagent_id = get_random_reagent_id()
-/obj/item/gps/internal/geyser
- icon_state = null
- gpstag = "Geyser"
- desc = "Chemicals come from deep below."
- invisibility = 100
-
/obj/item/plunger
name = "plunger"
desc = "It's a plunger for plunging."
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index 83c942ee6b15..842c2885ad12 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -29,7 +29,7 @@
//BUCKLE HOOKS
-/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob,force = FALSE)
+/obj/machinery/manned_turret/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
playsound(src,'sound/mecha/mechmove01.ogg', 50, 1)
for(var/obj/item/I in buckled_mob.held_items)
if(istype(I, /obj/item/gun_control))
diff --git a/code/game/objects/structures/mineral_doors.dm b/code/game/objects/structures/mineral_doors.dm
index c05798db10ab..cc189048636e 100644
--- a/code/game/objects/structures/mineral_doors.dm
+++ b/code/game/objects/structures/mineral_doors.dm
@@ -12,7 +12,7 @@
icon_state = "metal"
max_integrity = 200
armor = list(MELEE = 10, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 10, BIO = 100, RAD = 100, FIRE = 50, ACID = 50)
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
rad_insulation = RAD_MEDIUM_INSULATION
@@ -304,7 +304,13 @@
/obj/structure/mineral_door/paperframe/Initialize(mapload)
. = ..()
- queue_smooth_neighbors(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
+
+/obj/structure/mineral_door/paperframe/Destroy()
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ return ..()
/obj/structure/mineral_door/paperframe/examine(mob/user)
. = ..()
@@ -334,7 +340,3 @@
return TRUE
return ..()
-
-/obj/structure/mineral_door/paperframe/Destroy()
- queue_smooth_neighbors(src)
- return ..()
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 7d22e05d02f5..b143d3e1d2ce 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -144,7 +144,7 @@
/obj/item/wallframe/mirror
name = "mirror"
- desc = "a mirror on your hand, what are you gonna do?"
+ desc = "A mirror on your hand, what are you gonna do?"
icon = 'icons/obj/watercloset.dmi'
icon_state = "mirror"
result_path = /obj/structure/mirror
diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm
index 5a4394975542..c5158cad7b07 100644
--- a/code/game/objects/structures/plasticflaps.dm
+++ b/code/game/objects/structures/plasticflaps.dm
@@ -6,7 +6,7 @@
armor = list(MELEE = 100, BULLET = 80, LASER = 80, ENERGY = 100, BOMB = 50, BIO = 100, RAD = 100, FIRE = 50, ACID = 50)
density = FALSE
anchored = TRUE
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
/obj/structure/plasticflaps/opaque
opacity = TRUE
@@ -14,7 +14,19 @@
/obj/structure/plasticflaps/Initialize(mapload)
. = ..()
alpha = 0
- SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, plane, dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
+ gen_overlay()
+
+/obj/structure/plasticflaps/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ if(same_z_layer)
+ return ..()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ gen_overlay()
+ return ..()
+
+/obj/structure/plasticflaps/proc/gen_overlay()
+ var/turf/our_turf = get_turf(src)
+ SSvis_overlays.add_vis_overlay(src, icon, icon_state, ABOVE_MOB_LAYER, MUTATE_PLANE(GAME_PLANE, our_turf), dir, add_appearance_flags = RESET_ALPHA) //you see mobs under it, but you hit them like they are above it
+
/obj/structure/plasticflaps/examine(mob/user)
. = ..()
diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm
index 728282c02ca5..ddc0983467cb 100644
--- a/code/game/objects/structures/railings.dm
+++ b/code/game/objects/structures/railings.dm
@@ -3,6 +3,8 @@
desc = "Basic railing meant to protect idiots like you from falling."
icon = 'icons/obj/railing.dmi'
icon_state = "railing"
+ flags_1 = ON_BORDER_1
+ obj_flags = CAN_BE_HIT | BLOCKS_CONSTRUCTION_DIR
density = TRUE
anchored = TRUE
pixel_y = -16
@@ -23,6 +25,13 @@
ini_dir = dir
if(climbable)
AddElement(/datum/element/climbable)
+
+ if(density && flags_1 & ON_BORDER_1) // blocks normal movement from and to the direction it's facing.
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_exit),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ,null,CALLBACK(src, PROC_REF(can_be_rotated)),CALLBACK(src, PROC_REF(after_rotation)))
/obj/structure/railing/attackby(obj/item/I, mob/living/user, params)
@@ -76,15 +85,29 @@
. = ..()
return TRUE
-/obj/structure/railing/CheckExit(atom/movable/mover, turf/target)
- ..()
- if(get_dir(loc, target) & dir)
- var/checking = UNSTOPPABLE | FLYING | FLOATING
- return !density || mover.throwing || mover.movement_type & checking || mover.move_force >= MOVE_FORCE_EXTREMELY_STRONG
- return TRUE
+/obj/structure/railing/proc/on_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER
-/obj/structure/railing/corner/CheckExit()
- return TRUE
+ if(leaving == src)
+ return // Let's not block ourselves.
+
+ if(!(direction & dir))
+ return
+
+ if (!density)
+ return
+
+ if (leaving.throwing)
+ return
+
+ if (leaving.movement_type & (PHASING|MOVETYPES_NOT_TOUCHING_GROUND))
+ return
+
+ if (leaving.move_force >= MOVE_FORCE_EXTREMELY_STRONG)
+ return
+
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/railing/proc/can_be_rotated(mob/user, rotation_type)
var/silent = FALSE
diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm
index b0b1e1094065..300bfa7449fa 100644
--- a/code/game/objects/structures/reflector.dm
+++ b/code/game/objects/structures/reflector.dm
@@ -72,6 +72,8 @@
P.ignore_source_check = TRUE
P.range = P.decayedRange
P.decayedRange = max(P.decayedRange--, 0)
+ if(P.hitscan)
+ P.store_hitscan_collision(P.trajectory.copy_to())
return BULLET_ACT_FORCE_PIERCE
/obj/structure/reflector/attackby(obj/item/W, mob/user, params)
diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm
index 132df55d8472..0c2b3f61e0d1 100644
--- a/code/game/objects/structures/safe.dm
+++ b/code/game/objects/structures/safe.dm
@@ -242,5 +242,9 @@ FLOOR SAFES
density = FALSE
layer = LOW_OBJ_LAYER
+/obj/structure/safe/floor/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/undertile)
+
#undef SOUND_CHANCE
#undef BROKEN_THRESHOLD
diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm
index 3a01b3dac254..bd06323dfda3 100644
--- a/code/game/objects/structures/spawner.dm
+++ b/code/game/objects/structures/spawner.dm
@@ -8,16 +8,70 @@
anchored = TRUE
density = TRUE
+ var/faction = list("hostile")
+
var/max_mobs = 5
- var/spawn_time = 300 //30 seconds default
+ var/spawn_time = 30 SECONDS
var/mob_types = list(/mob/living/simple_animal/hostile/carp)
var/spawn_text = "emerges from"
- var/faction = list("hostile")
var/spawner_type = /datum/component/spawner
+ /// Is this spawner taggable with something?
+ var/scanner_taggable = FALSE
+ /// If this spawner's taggable, what can we tag it with?
+ var/static/list/scanner_types = list(/obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner)
+ /// If this spawner's taggable, what's the text we use to describe what we can tag it with?
+ var/scanner_descriptor = "mining analyzer"
+ /// Has this spawner been tagged/analyzed by a mining scanner?
+ var/gps_tagged = FALSE
+ /// A short identifier for the mob it spawns. Keep around 3 characters or less?
+ var/mob_gps_id = "???"
+ /// A short identifier for what kind of spawner it is, for use in putting together its GPS tag.
+ var/spawner_gps_id = "Creature Nest"
+ /// A complete identifier. Generated on tag (if tagged), used for its examine.
+ var/assigned_tag
+
+/obj/structure/spawner/examine(mob/user)
+ . = ..()
+ if(!scanner_taggable)
+ return
+ if(gps_tagged)
+ . += span_notice("A holotag's been attached, projecting \"[assigned_tag]\".")
+ else
+ . += span_notice("It looks like you could probably scan and tag it with a [scanner_descriptor].")
+
+/obj/structure/spawner/attackby(obj/item/item, mob/user, params)
+ . = ..()
+ if(.)
+ return TRUE
+ if(scanner_taggable && is_type_in_list(item, scanner_types))
+ gps_tag(user)
+ return TRUE
+
+/// Tag the spawner, prefixing its GPS entry with an identifier - or giving it one, if nonexistent.
+/obj/structure/spawner/proc/gps_tag(mob/user)
+ if(gps_tagged)
+ to_chat(user, span_warning("[src] already has a holotag attached!"))
+ return
+ to_chat(user, span_notice("You affix a holotag to [src]."))
+ playsound(src, 'sound/machines/twobeep.ogg', 100)
+ gps_tagged = TRUE
+ assigned_tag = "\[[mob_gps_id]-[rand(100,999)]\] " + spawner_gps_id
+ var/datum/component/gps/our_gps = GetComponent(/datum/component/gps)
+ if(our_gps)
+ our_gps.gpstag = assigned_tag
+ return
+ AddComponent(/datum/component/gps, assigned_tag)
/obj/structure/spawner/Initialize(mapload)
. = ..()
- AddComponent(spawner_type, mob_types, spawn_time, faction, spawn_text, max_mobs)
+ AddComponent(\
+ spawner_type, \
+ spawn_types = mob_types, \
+ spawn_time = spawn_time, \
+ max_spawned = max_mobs, \
+ faction = faction, \
+ spawn_text = spawn_text, \
+ )
/obj/structure/spawner/attack_animal(mob/living/simple_animal/M)
if(faction_check(faction, M.faction, FALSE)&&!M.client)
diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm
index 94f420f3facd..c2fa6dcc5c3c 100644
--- a/code/game/objects/structures/stairs.dm
+++ b/code/game/objects/structures/stairs.dm
@@ -11,20 +11,42 @@
icon = 'icons/obj/stairs.dmi'
icon_state = "stairs"
anchored = TRUE
+ move_resist = INFINITY
var/force_open_above = FALSE // replaces the turf above this stair obj with /turf/open/openspace
var/terminator_mode = STAIR_TERMINATOR_AUTOMATIC
var/turf/listeningTo
+/obj/structure/stairs/north
+ dir = NORTH
+
+/obj/structure/stairs/south
+ dir = SOUTH
+
+/obj/structure/stairs/east
+ dir = EAST
+
+/obj/structure/stairs/west
+ dir = WEST
+
/obj/structure/stairs/Initialize(mapload)
+ GLOB.stairs += src
if(force_open_above)
force_open_above()
build_signal_listener()
update_surrounding()
+
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_exit),
+ )
+
+ AddElement(/datum/element/connect_loc, loc_connections)
+
return ..()
/obj/structure/stairs/Destroy()
listeningTo = null
+ GLOB.stairs -= src
return ..()
/obj/structure/stairs/Move() //Look this should never happen but...
@@ -34,26 +56,46 @@
update_surrounding()
/obj/structure/stairs/proc/update_surrounding()
- update_appearance(UPDATE_ICON)
+ update_appearance()
for(var/i in GLOB.cardinals)
var/turf/T = get_step(get_turf(src), i)
var/obj/structure/stairs/S = locate() in T
if(S)
- S.update_appearance(UPDATE_ICON)
+ S.update_appearance()
-/obj/structure/stairs/Uncross(atom/movable/AM, atom/newloc)
- if(!newloc || !AM)
- return ..()
- if(isliving(AM) && isTerminator() && (get_dir(src, newloc) == dir))
- stair_ascend(AM)
- return FALSE
- return ..()
+/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER
+
+ if(leaving == src)
+ return //Let's not block ourselves.
+
+ if(!isobserver(leaving) && isTerminator() && direction == dir)
+ leaving.set_currently_z_moving(CURRENTLY_Z_ASCENDING)
+ INVOKE_ASYNC(src, PROC_REF(stair_ascend), leaving)
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/stairs/Cross(atom/movable/AM)
if(isTerminator() && (get_dir(src, AM) == dir))
return FALSE
return ..()
+/obj/structure/stairs/proc/stair_ascend(atom/movable/climber)
+ var/turf/checking = get_step_multiz(get_turf(src), UP)
+ if(!istype(checking))
+ return
+ // I'm only interested in if the pass is unobstructed, not if the mob will actually make it
+ if(!climber.can_z_move(UP, get_turf(src), checking, z_move_flags = ZMOVE_ALLOW_BUCKLED))
+ return
+ var/turf/target = get_step_multiz(get_turf(src), (dir|UP))
+ if(istype(target) && !climber.can_z_move(DOWN, target, z_move_flags = ZMOVE_FALL_FLAGS)) //Don't throw them into a tile that will just dump them back down.
+ climber.zMove(target = target, z_move_flags = ZMOVE_STAIRS_FLAGS)
+ /// Moves anything that's being dragged by src or anything buckled to it to the stairs turf.
+ climber.pulling?.move_from_pull(climber, loc, climber.glide_size)
+ for(var/mob/living/buckled as anything in climber.buckled_mobs)
+ buckled.pulling?.move_from_pull(buckled, loc, buckled.glide_size)
+
+
/obj/structure/stairs/update_icon_state()
. = ..()
if(isTerminator())
@@ -61,16 +103,6 @@
else
icon_state = "stairs"
-/obj/structure/stairs/proc/stair_ascend(atom/movable/AM)
- var/turf/checking = get_step_multiz(get_turf(src), UP)
- if(!istype(checking))
- return
- if(!checking.zPassIn(AM, UP, get_turf(src)))
- return
- var/turf/target = get_step_multiz(get_turf(src), (dir|UP))
- if(istype(target) && !target.can_zFall(AM, null, get_step_multiz(target, DOWN))) //Don't throw them into a tile that will just dump them back down.
- AM.forceMove(target)
-
/obj/structure/stairs/vv_edit_var(var_name, var_value)
. = ..()
if(!.)
@@ -98,15 +130,19 @@
T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
/obj/structure/stairs/proc/on_multiz_new(turf/source, dir)
+ SIGNAL_HANDLER
+
if(dir == UP)
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
if(T && !istype(T))
T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
-/obj/structure/stairs/intercept_zImpact(atom/movable/AM, levels = 1)
- return isTerminator()
+/obj/structure/stairs/intercept_zImpact(list/falling_movables, levels = 1)
+ . = ..()
+ if(levels == 1 && isTerminator()) // Stairs won't save you from a steep fall.
+ . |= FALL_INTERCEPTED | FALL_NO_MESSAGE | FALL_RETAIN_PULL
-/obj/structure/stairs/proc/isTerminator() //If this is the last stair in a chain and should move mobs up
+/obj/structure/stairs/proc/isTerminator() //If this is the last stair in a chain and should move mobs up
if(terminator_mode != STAIR_TERMINATOR_AUTOMATIC)
return (terminator_mode == STAIR_TERMINATOR_YES)
var/turf/T = get_turf(src)
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index 30862146bfb1..f35d78f5b09a 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -14,7 +14,8 @@
var/oreAmount = 5
var/material_drop_type = /obj/item/stack/sheet/metal
var/impressiveness = 15
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
/obj/structure/statue/Initialize(mapload)
. = ..()
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index eba7e22e45ea..094650274a29 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -16,11 +16,17 @@
name = "table"
desc = "A square piece of metal standing on four metal legs. It can not move."
icon = 'icons/obj/smooth_structures/table.dmi'
- icon_state = "table"
+ icon_state = "table-0"
+ base_icon_state = "table"
density = TRUE
anchored = TRUE
layer = TABLE_LAYER
pass_flags = LETPASSTHROW //You can throw objects over this, despite it's density.")
+ max_integrity = 100
+ integrity_failure = 30
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_TABLES
+ canSmoothWith = SMOOTH_GROUP_TABLES
var/frame = /obj/structure/table_frame
var/framestack = /obj/item/stack/rods
var/buildstack = /obj/item/stack/sheet/metal
@@ -28,10 +34,6 @@
var/buildstackamount = 1
var/framestackamount = 2
var/deconstruction_ready = 1
- max_integrity = 100
- integrity_failure = 30
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced)
/obj/structure/table/Initialize(mapload)
. = ..()
@@ -72,16 +74,17 @@
/obj/structure/table/examine(mob/user)
. = ..()
- . += deconstruction_hints(user)
+ if(!(flags_1 & NODECONSTRUCT_1))
+ . += deconstruction_hints(user)
/obj/structure/table/proc/deconstruction_hints(mob/user)
return span_notice("The top is screwed on, but the main bolts are also visible.")
/obj/structure/table/update_icon(updates=ALL)
. = ..()
- if(smooth)
- queue_smooth(src)
- queue_smooth_neighbors(src)
+ if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)))
+ QUEUE_SMOOTH(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
/obj/structure/table/narsie_act()
var/atom/A = loc
@@ -233,7 +236,7 @@
qdel(src)
/obj/structure/table/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_DECONSTRUCT)
return list("mode" = RCD_DECONSTRUCT, "delay" = 24, "cost" = 16)
return FALSE
@@ -253,9 +256,11 @@
name = "glass table"
desc = "What did I say about leaning on the glass tables? Now you need surgery."
icon = 'icons/obj/smooth_structures/glass_table.dmi'
- icon_state = "glass_table"
+ icon_state = "glass_table-0"
+ base_icon_state = "glass_table"
buildstack = /obj/item/stack/sheet/glass
- canSmoothWith = null
+ smoothing_groups = SMOOTH_GROUP_GLASS_TABLES
+ canSmoothWith = SMOOTH_GROUP_GLASS_TABLES
max_integrity = 70
resistance_flags = ACID_PROOF
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 100)
@@ -331,15 +336,15 @@
name = "wooden table"
desc = "Do not apply fire to this. Rumour says it burns easily."
icon = 'icons/obj/smooth_structures/wood_table.dmi'
- icon_state = "wood_table"
+ icon_state = "wood_table-0"
+ base_icon_state = "wood_table"
frame = /obj/structure/table_frame/wood
framestack = /obj/item/stack/sheet/mineral/wood
buildstack = /obj/item/stack/sheet/mineral/wood
resistance_flags = FLAMMABLE
max_integrity = 70
- canSmoothWith = list(/obj/structure/table/wood,
- /obj/structure/table/wood/poker,
- /obj/structure/table/wood/bar)
+ smoothing_groups = SMOOTH_GROUP_WOOD_TABLES //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = SMOOTH_GROUP_WOOD_TABLES
/obj/structure/table/wood/narsie_act(total_override = TRUE)
if(!total_override)
@@ -349,7 +354,8 @@
name = "gambling table"
desc = "A seedy table for seedy dealings in seedy places."
icon = 'icons/obj/smooth_structures/poker_table.dmi'
- icon_state = "poker_table"
+ icon_state = "poker_table-0"
+ base_icon_state = "poker_table"
buildstack = /obj/item/stack/tile/carpet
/obj/structure/table/wood/poker/narsie_act()
@@ -360,20 +366,14 @@
desc = "A standard metal table frame covered with an amazingly fancy, patterned cloth."
icon = 'icons/obj/structures.dmi'
icon_state = "fancy_table"
+ base_icon_state = "fancy_table"
frame = /obj/structure/table_frame
framestack = /obj/item/stack/rods
buildstack = /obj/item/stack/tile/carpet
- canSmoothWith = list(/obj/structure/table/wood/fancy,
- /obj/structure/table/wood/fancy/black,
- /obj/structure/table/wood/fancy/exoticblue,
- /obj/structure/table/wood/fancy/cyan,
- /obj/structure/table/wood/fancy/exoticgreen,
- /obj/structure/table/wood/fancy/orange,
- /obj/structure/table/wood/fancy/exoticpurple,
- /obj/structure/table/wood/fancy/red,
- /obj/structure/table/wood/fancy/royalblack,
- /obj/structure/table/wood/fancy/royalblue)
- var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize(mapload)
+ smoothing_groups = SMOOTH_GROUP_FANCY_WOOD_TABLES //Don't smooth with SMOOTH_GROUP_TABLES or SMOOTH_GROUP_WOOD_TABLES
+ canSmoothWith = SMOOTH_GROUP_FANCY_WOOD_TABLES
+ var/smooth_icon = 'icons/obj/smooth_structures/fancy_table.dmi' // see Initialize()
+
/obj/structure/table/wood/fancy/Initialize(mapload)
. = ..()
@@ -385,48 +385,58 @@
/obj/structure/table/wood/fancy/black
icon_state = "fancy_table_black"
+ base_icon_state = "fancy_table_black"
buildstack = /obj/item/stack/tile/carpet/black
smooth_icon = 'icons/obj/smooth_structures/fancy_table_black.dmi'
-/obj/structure/table/wood/fancy/exoticblue
- icon_state = "fancy_table_exoticblue"
- buildstack = /obj/item/stack/tile/carpet/exoticblue
- smooth_icon = 'icons/obj/smooth_structures/fancy_table_exoticblue.dmi'
+/obj/structure/table/wood/fancy/blue
+ icon_state = "fancy_table_blue"
+ base_icon_state = "fancy_table_blue"
+ buildstack = /obj/item/stack/tile/carpet/blue
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_blue.dmi'
/obj/structure/table/wood/fancy/cyan
icon_state = "fancy_table_cyan"
+ base_icon_state = "fancy_table_cyan"
buildstack = /obj/item/stack/tile/carpet/cyan
smooth_icon = 'icons/obj/smooth_structures/fancy_table_cyan.dmi'
-/obj/structure/table/wood/fancy/exoticgreen
- icon_state = "fancy_table_exoticgreen"
- buildstack = /obj/item/stack/tile/carpet/exoticgreen
- smooth_icon = 'icons/obj/smooth_structures/fancy_table_exoticgreen.dmi'
+/obj/structure/table/wood/fancy/green
+ icon_state = "fancy_table_green"
+ base_icon_state = "fancy_table_green"
+ buildstack = /obj/item/stack/tile/carpet/green
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_green.dmi'
/obj/structure/table/wood/fancy/orange
icon_state = "fancy_table_orange"
+ base_icon_state = "fancy_table_orange"
buildstack = /obj/item/stack/tile/carpet/orange
smooth_icon = 'icons/obj/smooth_structures/fancy_table_orange.dmi'
-/obj/structure/table/wood/fancy/exoticpurple
- icon_state = "fancy_table_exoticpurple"
- buildstack = /obj/item/stack/tile/carpet/exoticpurple
- smooth_icon = 'icons/obj/smooth_structures/fancy_table_exoticpurple.dmi'
+/obj/structure/table/wood/fancy/purple
+ icon_state = "fancy_table_purple"
+ base_icon_state = "fancy_table_purple"
+ buildstack = /obj/item/stack/tile/carpet/purple
+ smooth_icon = 'icons/obj/smooth_structures/fancy_table_purple.dmi'
/obj/structure/table/wood/fancy/red
icon_state = "fancy_table_red"
+ base_icon_state = "fancy_table_red"
buildstack = /obj/item/stack/tile/carpet/red
smooth_icon = 'icons/obj/smooth_structures/fancy_table_red.dmi'
/obj/structure/table/wood/fancy/royalblack
icon_state = "fancy_table_royalblack"
+ base_icon_state = "fancy_table_royalblack"
buildstack = /obj/item/stack/tile/carpet/royalblack
smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblack.dmi'
/obj/structure/table/wood/fancy/royalblue
icon_state = "fancy_table_royalblue"
+ base_icon_state = "fancy_table_royalblue"
buildstack = /obj/item/stack/tile/carpet/royalblue
smooth_icon = 'icons/obj/smooth_structures/fancy_table_royalblue.dmi'
+
/*
* Reinforced tables
*/
@@ -434,10 +444,10 @@
name = "reinforced table"
desc = "A reinforced version of the four legged table."
icon = 'icons/obj/smooth_structures/reinforced_table.dmi'
- icon_state = "r_table"
+ icon_state = "reinforced_table-0"
+ base_icon_state = "reinforced_table"
deconstruction_ready = 0
buildstack = /obj/item/stack/sheet/plasteel
- canSmoothWith = list(/obj/structure/table/reinforced, /obj/structure/table)
max_integrity = 200
integrity_failure = 50
armor = list(MELEE = 10, BULLET = 30, LASER = 30, ENERGY = 100, BOMB = 20, BIO = 0, RAD = 0, FIRE = 80, ACID = 70)
@@ -470,14 +480,16 @@
name = "brass table"
desc = "A solid, slightly beveled brass table."
icon = 'icons/obj/smooth_structures/brass_table.dmi'
- icon_state = "brass_table"
+ icon_state = "brass_table-0"
+ base_icon_state = "brass_table"
resistance_flags = FIRE_PROOF | ACID_PROOF
frame = /obj/structure/table_frame/brass
framestack = /obj/item/stack/tile/brass
buildstack = /obj/item/stack/tile/brass
framestackamount = 1
buildstackamount = 1
- canSmoothWith = list(/obj/structure/table/reinforced/brass, /obj/structure/table/bronze)
+ smoothing_groups = SMOOTH_GROUP_BRONZE_TABLES //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = SMOOTH_GROUP_BRONZE_TABLES
/obj/structure/table/reinforced/brass/Initialize(mapload)
. = ..()
@@ -506,10 +518,12 @@
name = "bronze table"
desc = "A solid table made out of bronze."
icon = 'icons/obj/smooth_structures/brass_table.dmi'
- icon_state = "brass_table"
+ icon_state = "brass_table-0"
+ base_icon_state = "brass_table"
resistance_flags = FIRE_PROOF | ACID_PROOF
buildstack = /obj/item/stack/tile/bronze
- canSmoothWith = list(/obj/structure/table/reinforced/brass, /obj/structure/table/bronze)
+ smoothing_groups = SMOOTH_GROUP_BRONZE_TABLES //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = SMOOTH_GROUP_BRONZE_TABLES
/obj/structure/table/bronze/tablepush(mob/living/user, mob/living/pushed_mob)
..()
@@ -525,9 +539,10 @@
icon = 'icons/obj/surgery.dmi'
icon_state = "optable"
buildstack = /obj/item/stack/sheet/mineral/silver
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
+ smoothing_groups = null
+ canSmoothWith = null
can_buckle = TRUE
- buckle_requires_restraints = TRUE
/obj/structure/table/optable/Initialize(mapload)
. = ..()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index c1050969b625..f6e1bb5f3d42 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -387,26 +387,33 @@
opacity = FALSE
density = FALSE
var/open = TRUE
+ /// if it can be seen through when closed
+ var/opaque_closed = FALSE
+
+/obj/structure/curtain/Initialize(mapload)
+ // see-through curtains should let emissives shine through
+ if(!opaque_closed)
+ blocks_emissive = EMISSIVE_BLOCK_NONE
+ return ..()
/obj/structure/curtain/proc/toggle()
open = !open
- update_appearance(UPDATE_ICON)
-
-/obj/structure/curtain/update_icon_state()
- . = ..()
- if(!open)
- icon_state = "closed"
- layer = WALL_OBJ_LAYER
- density = TRUE
- open = FALSE
- set_opacity(TRUE)
-
- else
- icon_state = "open"
+ if(open)
layer = SIGN_LAYER
- density = FALSE
- open = TRUE
+ SET_PLANE_IMPLICIT(src, GAME_PLANE)
+ set_density(FALSE)
set_opacity(FALSE)
+ else
+ layer = WALL_OBJ_LAYER
+ set_density(TRUE)
+ if(opaque_closed)
+ set_opacity(TRUE)
+
+ update_appearance()
+
+/obj/structure/curtain/update_icon_state()
+ icon_state = "[open ? "open" : "closed"]"
+ return ..()
/obj/structure/curtain/attackby(obj/item/W, mob/user)
if (istype(W, /obj/item/toy/crayon))
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index aabd13e23356..af8a79890584 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -18,6 +18,7 @@
anchored = FALSE
density = FALSE
dir = NORTH
+ obj_flags = CAN_BE_HIT | BLOCKS_CONSTRUCTION_DIR
var/ini_dir
var/obj/item/electronics/airlock/electronics = null
@@ -27,7 +28,7 @@
var/facing = "l" //Does the windoor open to the left or right?
var/secure = FALSE //Whether or not this creates a secure windoor
var/state = "01" //How far the door assembly has progressed
- CanAtmosPass = ATMOS_PASS_PROC
+ can_atmos_pass = ATMOS_PASS_PROC
/obj/structure/windoor_assembly/New(loc, set_dir)
..()
@@ -68,11 +69,11 @@
else if(istype(mover, /obj/machinery/door/window) && !valid_window_location(loc, mover.dir))
return FALSE
-/obj/structure/windoor_assembly/CanAtmosPass(turf/T)
- if(get_dir(loc, T) == dir)
+/obj/structure/windoor_assembly/can_atmos_pass(turf/target_turf, vertical = FALSE)
+ if(get_dir(loc, target_turf) == dir)
return !density
else
- return 1
+ return TRUE
/obj/structure/windoor_assembly/CheckExit(atom/movable/mover as mob|obj, turf/target)
if(istype(mover) && (mover.pass_flags & PASSGLASS))
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index d4a01fd2a175..9327f9175fed 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -7,11 +7,12 @@
pressure_resistance = 4*ONE_ATMOSPHERE
anchored = TRUE //initially is 0 for tile smoothing
flags_1 = ON_BORDER_1 | RAD_PROTECT_CONTENTS_1
+ obj_flags = CAN_BE_HIT | BLOCKS_CONSTRUCTION_DIR | IGNORE_DENSITY
max_integrity = 25
can_be_unanchored = TRUE
resistance_flags = ACID_PROOF
armor = list(MELEE = 0, BULLET = -50, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 100)
- CanAtmosPass = ATMOS_PASS_PROC
+ can_atmos_pass = ATMOS_PASS_PROC
rad_insulation = RAD_VERY_LIGHT_INSULATION
var/ini_dir = null
var/state = WINDOW_SCREWED_TO_FRAME
@@ -22,7 +23,6 @@
var/fulltile = FALSE
var/glass_type = /obj/item/stack/sheet/glass
var/glass_amount = 1
- var/mutable_appearance/crack_overlay
var/real_explosion_block //ignore this, just use explosion_block
var/breaksound = "shatter"
var/hitsound = 'sound/effects/Glasshit.ogg'
@@ -48,7 +48,6 @@
/obj/structure/window/Initialize(mapload, direct)
. = ..()
- AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, PROC_REF(can_be_rotated)), CALLBACK(src, PROC_REF(after_rotation)))
if(direct)
setDir(direct)
if(reinf && anchored)
@@ -59,13 +58,23 @@
if(fulltile)
setDir()
+ obj_flags &= ~BLOCKS_CONSTRUCTION_DIR
+ obj_flags &= ~IGNORE_DENSITY
//windows only block while reinforced and fulltile, so we'll use the proc
real_explosion_block = explosion_block
explosion_block = EXPLOSION_BLOCK_PROC
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, PROC_REF(can_be_rotated)), CALLBACK(src, PROC_REF(after_rotation)))
+
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_EXIT = PROC_REF(on_exit),
+ )
+
+ if (flags_1 & ON_BORDER_1)
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/structure/window/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_DECONSTRUCT)
return list("mode" = RCD_DECONSTRUCT, "delay" = 20, "cost" = 5)
return FALSE
@@ -74,7 +83,7 @@
if (resistance_flags & INDESTRUCTIBLE)
return FALSE
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_DECONSTRUCT)
to_chat(user, span_notice("You deconstruct the window."))
qdel(src)
@@ -112,9 +121,9 @@
/obj/structure/window/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
if(istype(mover) && (mover.pass_flags & PASSGLASS))
- return 1
+ return TRUE
if(dir == FULLTILE_WINDOW_DIR)
- return 0 //full tile window, you can't move into it!
+ return FALSE //full tile window, you can't move into it!
var/attempted_dir = get_dir(loc, target)
if(attempted_dir == dir)
return
@@ -131,12 +140,24 @@
else if(attempted_dir != dir)
return TRUE
-/obj/structure/window/CheckExit(atom/movable/O, turf/target)
- if(istype(O) && (O.pass_flags & PASSGLASS))
- return 1
- if(get_dir(O.loc, target) == dir)
- return 0
- return 1
+/obj/structure/window/proc/on_exit(datum/source, atom/movable/leaving, direction)
+ SIGNAL_HANDLER
+
+ if(leaving.movement_type & PHASING)
+ return
+
+ if(leaving == src)
+ return // Let's not block ourselves.
+
+ if (leaving.pass_flags & PASSGLASS)
+ return
+
+ if (fulltile)
+ return
+
+ if(direction == dir && density)
+ leaving.Bump(src)
+ return COMPONENT_ATOM_BLOCK_EXIT
/obj/structure/window/attack_tk(mob/user)
user.changeNext_move(CLICK_CD_MELEE)
@@ -318,35 +339,31 @@
setDir(ini_dir)
move_update_air(T)
-/obj/structure/window/CanAtmosPass(turf/T)
+/obj/structure/window/can_atmos_pass(turf/target_turf, vertical = FALSE)
if(!anchored || !density)
return TRUE
- return !(FULLTILE_WINDOW_DIR == dir || dir == get_dir(loc, T))
+ return !(fulltile || dir == get_dir(loc, target_turf) || FULLTILE_WINDOW_DIR == dir)
//This proc is used to update the icons of nearby windows.
/obj/structure/window/proc/update_nearby_icons()
- update_appearance(UPDATE_ICON)
- if(smooth)
- queue_smooth_neighbors(src)
+ update_appearance()
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
//merges adjacent full-tile windows into one
-/obj/structure/window/update_overlays()
+/obj/structure/window/update_overlays(updates=ALL)
. = ..()
- if(!QDELETED(src))
- if(!fulltile)
- return
-
- var/ratio = obj_integrity / max_integrity
- ratio = CEILING(ratio*4, 1) * 25
+ if(QDELETED(src) || !fulltile)
+ return
- if(smooth)
- queue_smooth(src)
+ if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)))
+ QUEUE_SMOOTH(src)
- cut_overlay(crack_overlay)
- if(ratio > 75)
- return
- crack_overlay = mutable_appearance('icons/obj/structures.dmi', "damage[ratio]", -(layer+0.1))
- . += crack_overlay
+ var/ratio = obj_integrity / max_integrity
+ ratio = CEILING(ratio*4, 1) * 25
+ if(ratio > 75)
+ return
+ . += mutable_appearance('icons/obj/structures.dmi', "damage[ratio]", -(layer+0.1))
/obj/structure/window/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
@@ -618,6 +635,7 @@
name = "tinted window"
icon_state = "twindow"
opacity = TRUE
+
/obj/structure/window/reinforced/tinted/frosted
name = "frosted window"
icon_state = "fwindow"
@@ -626,13 +644,15 @@
/obj/structure/window/fulltile
icon = 'icons/obj/smooth_structures/window.dmi'
- icon_state = "window"
+ icon_state = "window-0"
+ base_icon_state = "window"
dir = FULLTILE_WINDOW_DIR
max_integrity = 50
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/plasma/reinforced/fulltile)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
glass_amount = 2
/obj/structure/window/fulltile/unanchored
@@ -641,13 +661,15 @@
/obj/structure/window/plasma/fulltile
icon = 'icons/obj/smooth_structures/plasma_window.dmi'
- icon_state = "plasmawindow"
+ icon_state = "plasma_window"
+ base_icon_state = "plasma_window"
dir = FULLTILE_WINDOW_DIR
max_integrity = 300
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/plasma/reinforced/fulltile)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
glass_amount = 2
/obj/structure/window/plasma/fulltile/unanchored
@@ -656,13 +678,16 @@
/obj/structure/window/plasma/reinforced/fulltile
icon = 'icons/obj/smooth_structures/rplasma_window.dmi'
- icon_state = "rplasmawindow"
+ icon_state = "rplasma_window-0"
+ base_icon_state = "rplasma_window"
dir = FULLTILE_WINDOW_DIR
state = RWINDOW_SECURE
max_integrity = 1000
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
glass_amount = 2
/obj/structure/window/plasma/reinforced/fulltile/unanchored
@@ -671,15 +696,16 @@
/obj/structure/window/reinforced/fulltile
icon = 'icons/obj/smooth_structures/reinforced_window.dmi'
- icon_state = "r_window"
+ icon_state = "reinforced_window-0"
+ base_icon_state = "reinforced_window"
dir = FULLTILE_WINDOW_DIR
max_integrity = 150
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
state = RWINDOW_SECURE
- canSmoothWith = list(/obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/plasma/reinforced/fulltile)
- level = 3
glass_amount = 2
/obj/structure/window/reinforced/fulltile/unanchored
@@ -688,35 +714,37 @@
/obj/structure/window/reinforced/tinted/fulltile
icon = 'icons/obj/smooth_structures/tinted_window.dmi'
- icon_state = "tinted_window"
+ icon_state = "tinted_window-0"
+ base_icon_state = "tinted_window"
dir = FULLTILE_WINDOW_DIR
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/plasma/reinforced/fulltile)
- level = 3
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
glass_amount = 2
/obj/structure/window/reinforced/fulltile/ice
icon = 'icons/obj/smooth_structures/rice_window.dmi'
- icon_state = "ice_window"
+ icon_state = "rice_window-0"
+ base_icon_state = "rice_window"
max_integrity = 150
- canSmoothWith = list(/obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/plasma/reinforced/fulltile)
- level = 3
glass_amount = 2
/obj/structure/window/reinforced/fulltile/bronze
name = "bronze window"
desc = "A pane of translucent yet reinforced bronze."
icon = 'icons/obj/smooth_structures/clockwork_window.dmi'
- icon_state = "clockwork_window"
+ icon_state = "clockwork_window-0"
+ base_icon_state = "clockwork_window"
glass_type = /obj/item/stack/tile/bronze
/obj/structure/window/shuttle
name = "shuttle window"
desc = "A reinforced, air-locked pod window."
icon = 'icons/obj/smooth_structures/shuttle_window.dmi'
- icon_state = "shuttle_window"
+ icon_state = "shuttle_window-0"
+ base_icon_state = "shuttle_window"
dir = FULLTILE_WINDOW_DIR
max_integrity = 100
wtype = "shuttle"
@@ -725,10 +753,10 @@
reinf = TRUE
heat_resistance = 1600
armor = list(MELEE = 50, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 100, RAD = 100, FIRE = 80, ACID = 100)
- smooth = SMOOTH_TRUE
- canSmoothWith = null
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_SHUTTLE
explosion_block = 3
- level = 3
glass_type = /obj/item/stack/sheet/titaniumglass
glass_amount = 2
@@ -746,7 +774,8 @@
name = "plastitanium window"
desc = "A durable looking window made of an alloy of of plasma and titanium."
icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
- icon_state = "plastitanium_window"
+ icon_state = "plastitanium_window-0"
+ base_icon_state = "plastitanium_window"
dir = FULLTILE_WINDOW_DIR
max_integrity = 1200
wtype = "shuttle"
@@ -755,11 +784,11 @@
reinf = TRUE
heat_resistance = 1600
armor = list(MELEE = 40, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 50, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
- smooth = SMOOTH_TRUE
- canSmoothWith = null
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
explosion_block = 3
damage_deflection = 21 //The same as reinforced plasma windows.
- level = 3
glass_type = /obj/item/stack/sheet/plastitaniumglass
glass_amount = 2
rad_insulation = RAD_FULL_INSULATION
@@ -777,7 +806,8 @@
name = "brass window"
desc = "A paper-thin pane of translucent yet reinforced brass."
icon = 'icons/obj/smooth_structures/clockwork_window.dmi'
- icon_state = "clockwork_window_single"
+ icon_state = "clockwork_window-0"
+ base_icon_state = "clockwork_window"
resistance_flags = FIRE_PROOF | ACID_PROOF
max_integrity = 150
armor = list(MELEE = 80, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 100, RAD = 100, FIRE = 80, ACID = 100)
@@ -812,7 +842,7 @@
/obj/structure/window/reinforced/clockwork/ratvar_act()
if(GLOB.ratvar_awakens)
obj_integrity = max_integrity
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/structure/window/reinforced/clockwork/narsie_act()
take_damage(rand(25, 75), BRUTE)
@@ -828,13 +858,13 @@
/obj/structure/window/reinforced/clockwork/fulltile
icon_state = "clockwork_window"
- smooth = SMOOTH_TRUE
- canSmoothWith = null
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE_BRONZE + SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_BRONZE
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
dir = FULLTILE_WINDOW_DIR
max_integrity = 600
- level = 3
glass_amount = 2
/obj/structure/window/reinforced/clockwork/spawnDebris(location)
@@ -856,19 +886,21 @@
name = "paper frame"
desc = "A fragile separator made of thin wood and paper."
icon = 'icons/obj/smooth_structures/paperframes.dmi'
- icon_state = "frame"
+ icon_state = "frame-0"
+ base_icon_state = "frame"
dir = FULLTILE_WINDOW_DIR
opacity = TRUE
max_integrity = 15
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_PAPERFRAME
+ canSmoothWith = SMOOTH_GROUP_PAPERFRAME
glass_amount = 2
glass_type = /obj/item/stack/sheet/paperframes
heat_resistance = 233
decon_speed = 10
- CanAtmosPass = ATMOS_PASS_YES
+ can_atmos_pass = ATMOS_PASS_YES
resistance_flags = FLAMMABLE
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0)
breaksound = 'sound/items/poster_ripped.ogg'
@@ -878,7 +910,7 @@
/obj/structure/window/paperframe/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/structure/window/paperframe/examine(mob/user)
. = ..()
@@ -906,26 +938,14 @@
user.visible_message(span_danger("[user] tears a hole in [src]."))
update_appearance(UPDATE_ICON)
-/obj/structure/window/paperframe/update_overlays()
- . = ..()
- if(obj_integrity < max_integrity)
- cut_overlay(paper)
- . += torn
- set_opacity(FALSE)
- else
- cut_overlay(torn)
- . += paper
- set_opacity(TRUE)
- queue_smooth(src)
-
/obj/structure/window/paperframe/update_appearance(updates)
. = ..()
set_opacity(obj_integrity >= max_integrity)
/obj/structure/window/paperframe/update_icon(updates=ALL)
. = ..()
- if((updates & UPDATE_SMOOTHING) && (smooth & (SMOOTH_TRUE)))
- queue_smooth(src)
+ if((updates & UPDATE_SMOOTHING) && (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)))
+ QUEUE_SMOOTH(src)
/obj/structure/window/paperframe/update_overlays()
. = ..()
@@ -947,7 +967,7 @@
update_appearance(UPDATE_ICON)
return
..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
diff --git a/code/game/objects/structures/wire_splicing.dm b/code/game/objects/structures/wire_splicing.dm
index 5536249aa223..ea9e8780fb4e 100644
--- a/code/game/objects/structures/wire_splicing.dm
+++ b/code/game/objects/structures/wire_splicing.dm
@@ -6,7 +6,7 @@
density = FALSE
anchored = TRUE
flags_1 = CONDUCT_1
- layer = UNDER_CATWALK
+ layer = CATWALK_LAYER
var/messiness = 0 // How bad the splicing was, determines the chance of shock
/obj/structure/wire_splicing/Initialize(mapload)
diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm
index 098efd8fc44f..52041cf605ce 100644
--- a/code/game/shuttle_engines.dm
+++ b/code/game/shuttle_engines.dm
@@ -15,8 +15,34 @@
desc = "A bluespace engine used to make shuttles move."
density = TRUE
anchored = TRUE
+
+ ///How well the engine affects the ship's speed.
var/engine_power = 1
- var/state = ENGINE_WELDED //welding shmelding
+ ///Construction state of the Engine.
+ var/state = ENGINE_WELDED //welding shmelding //i love welding //i love welding charlie! I LOVE WELDING!!!!!!!!
+
+ ///The mobile ship we are connected to.
+ var/datum/weakref/connected_ship_ref
+
+/obj/structure/shuttle/engine/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ . = ..()
+ if(!port)
+ return FALSE
+ connected_ship_ref = WEAKREF(port)
+ port.engine_list += src
+ port.current_engines += engine_power
+ if(mapload)
+ port.initial_engines += engine_power
+
+/**
+ * Called on destroy and when we need to unsync an engine from their ship.
+ */
+/obj/structure/shuttle/engine/proc/unsync_ship()
+ var/obj/docking_port/mobile/port = connected_ship_ref?.resolve()
+ if(port)
+ port.engine_list -= src
+ port.current_engines -= initial(engine_power)
+ connected_ship_ref = null
//Ugh this is a lot of copypasta from emitters, welding need some boilerplate reduction
/obj/structure/shuttle/engine/can_be_unfasten_wrench(mob/user, silent)
@@ -31,7 +57,9 @@
if(. == SUCCESSFUL_UNFASTEN)
if(anchored)
state = ENGINE_WRENCHED
+ connect_to_shuttle(port = SSshuttle.get_containing_shuttle(src)) //connect to a new ship, if needed
else
+ unsync_ship() //not part of the ship anymore
state = ENGINE_UNWRENCHED
/obj/structure/shuttle/engine/wrench_act(mob/living/user, obj/item/I)
@@ -76,12 +104,11 @@
//Propagates the change to the shuttle.
/obj/structure/shuttle/engine/proc/alter_engine_power(mod)
- if(mod == 0)
+ if(!mod)
return
- if(SSshuttle.is_in_shuttle_bounds(src))
- var/obj/docking_port/mobile/M = SSshuttle.get_containing_shuttle(src)
- if(M)
- M.alter_engines(mod)
+ var/obj/docking_port/mobile/port = connected_ship_ref?.resolve()
+ if(port)
+ port.alter_engines(mod)
/obj/structure/shuttle/engine/heater
name = "engine heater"
diff --git a/code/game/turfs/baseturfs.dm b/code/game/turfs/baseturfs.dm
new file mode 100644
index 000000000000..ad016b634477
--- /dev/null
+++ b/code/game/turfs/baseturfs.dm
@@ -0,0 +1,172 @@
+/// Take off the top layer turf and replace it with the next baseturf down
+/turf/proc/ScrapeAway(amount=1, flags)
+ if(!amount)
+ return
+ if(length(baseturfs))
+ var/list/new_baseturfs = baseturfs.Copy()
+ var/turf_type = new_baseturfs[max(1, new_baseturfs.len - amount + 1)]
+ while(ispath(turf_type, /turf/baseturf_skipover))
+ amount++
+ if(amount > new_baseturfs.len)
+ CRASH("The bottommost baseturf of a turf is a skipover [src]([type])")
+ turf_type = new_baseturfs[max(1, new_baseturfs.len - amount + 1)]
+ new_baseturfs.len -= min(amount, new_baseturfs.len - 1) // No removing the very bottom
+ if(new_baseturfs.len == 1)
+ new_baseturfs = new_baseturfs[1]
+ return ChangeTurf(turf_type, new_baseturfs, flags)
+
+ if(baseturfs == type)
+ return src
+
+ return ChangeTurf(baseturfs, baseturfs, flags) // The bottom baseturf will never go away
+
+/// Places the given turf on the bottom of the turf stack.
+/turf/proc/place_on_bottom(turf/bottom_turf)
+ baseturfs = baseturfs_string_list(
+ list(initial(bottom_turf.baseturfs), bottom_turf) + baseturfs,
+ src
+ )
+
+/// Places a turf at the top of the stack
+/turf/proc/place_on_top(turf/added_layer, flags)
+ var/list/turf/new_baseturfs = list()
+
+ new_baseturfs.Add(baseturfs)
+ if(isopenturf(src))
+ new_baseturfs.Add(type)
+
+ return ChangeTurf(added_layer, new_baseturfs, flags)
+
+/// Places a turf on top - for map loading
+/turf/proc/load_on_top(turf/added_layer, flags)
+ var/area/our_area = get_area(src)
+ flags = our_area.PlaceOnTopReact(list(baseturfs), added_layer, flags)
+
+ if(flags & CHANGETURF_SKIP) // We haven't been initialized
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("CHANGETURF_SKIP was used in a PlaceOnTop call for a turf that's initialized. This is a mistake. [src]([type])")
+ assemble_baseturfs()
+
+ var/turf/new_turf
+ if(!length(baseturfs))
+ baseturfs = list(baseturfs)
+
+ var/list/old_baseturfs = baseturfs.Copy()
+ if(!isclosedturf(src))
+ old_baseturfs += type
+
+ new_turf = ChangeTurf(added_layer, null, flags)
+ new_turf.assemble_baseturfs(initial(added_layer.baseturfs)) // The baseturfs list is created like roundstart
+ if(!length(new_turf.baseturfs))
+ new_turf.baseturfs = list(baseturfs)
+
+ // The old baseturfs are put underneath, and we sort out the unwanted ones
+ new_turf.baseturfs = baseturfs_string_list(old_baseturfs + (new_turf.baseturfs - GLOB.blacklisted_automated_baseturfs), new_turf)
+ return new_turf
+
+// Copy an existing turf and put it on top
+// Returns the new turf
+/turf/proc/CopyOnTop(turf/copytarget, ignore_bottom=1, depth=INFINITY, copy_air = FALSE)
+ var/list/new_baseturfs = list()
+ new_baseturfs += baseturfs
+ new_baseturfs += type
+
+ if(depth)
+ var/list/target_baseturfs
+ if(length(copytarget.baseturfs))
+ // with default inputs this would be Copy(clamp(2, -INFINITY, baseturfs.len))
+ // Don't forget a lower index is lower in the baseturfs stack, the bottom is baseturfs[1]
+ target_baseturfs = copytarget.baseturfs.Copy(clamp(1 + ignore_bottom, 1 + copytarget.baseturfs.len - depth, copytarget.baseturfs.len))
+ else if(!ignore_bottom)
+ target_baseturfs = list(copytarget.baseturfs)
+ if(target_baseturfs)
+ target_baseturfs -= new_baseturfs & GLOB.blacklisted_automated_baseturfs
+ new_baseturfs += target_baseturfs
+
+ var/turf/newT = copytarget.copyTurf(src, copy_air)
+ newT.baseturfs = baseturfs_string_list(new_baseturfs, newT)
+ return newT
+
+/// Tries to find the given type in baseturfs.
+/// If found, returns how deep it is for use in other baseturf procs, or null if it cannot be found.
+/// For example, this number can be passed into ScrapeAway to scrape everything until that point.
+/turf/proc/depth_to_find_baseturf(baseturf_type)
+ if(!islist(baseturfs))
+ return baseturfs == baseturf_type ? 1 : null
+ var/index = baseturfs.Find(baseturf_type)
+ if (index == 0)
+ return null
+ return baseturfs.len - index + 1
+
+/// Returns the baseturf at the given depth.
+/// For example, baseturf_at_depth(1) will give the baseturf that would show up when scraping once.
+/turf/proc/baseturf_at_depth(index)
+ TEST_ONLY_ASSERT(isnum(index), "baseturf_at_depth must be given a number, received [index]")
+ if (islist(baseturfs))
+ return LAZYACCESS(baseturfs, baseturfs.len - index + 1)
+ else if (index == 1)
+ return baseturfs
+ else
+ return null
+
+/// Replaces all instances of needle_type in baseturfs with replacement_type
+/turf/proc/replace_baseturf(needle_type, replacement_type)
+ if (islist(baseturfs))
+ var/list/new_baseturfs
+
+ while (TRUE)
+ var/found_index = baseturfs.Find(needle_type)
+ if (found_index == 0)
+ break
+
+ new_baseturfs ||= baseturfs.Copy()
+ new_baseturfs[found_index] = replacement_type
+
+ if (!isnull(new_baseturfs))
+ baseturfs = baseturfs_string_list(new_baseturfs, src)
+ else if (baseturfs == needle_type)
+ baseturfs = replacement_type
+
+/// Removes all baseturfs that are found in the given typecache.
+/turf/proc/remove_baseturfs_from_typecache(list/typecache)
+ if (islist(baseturfs))
+ var/list/new_baseturfs
+
+ for (var/baseturf in baseturfs)
+ if (!typecache[baseturf])
+ continue
+
+ new_baseturfs ||= baseturfs.Copy()
+ new_baseturfs -= baseturf
+
+ if (!isnull(new_baseturfs))
+ baseturfs = baseturfs_string_list(new_baseturfs, src)
+ else if (typecache[baseturfs])
+ baseturfs = /turf/baseturf_bottom
+
+/// Returns the total number of baseturfs
+/turf/proc/count_baseturfs()
+ return islist(baseturfs) ? length(baseturfs) : 1
+
+/// Inserts a baseturf at the given level.
+/// "Level" here doesn't mean depth.
+/// For example, `insert_baseturf(2, /turf/open/floor/plating)` will make it so
+/// the 2nd to last turf in the list is plating.
+/// This is different from *depth*, since depth is the level from the top.
+/turf/proc/insert_baseturf(level, turf_type)
+ if (!islist(baseturfs))
+ assemble_baseturfs()
+ if(!islist(baseturfs))
+ baseturfs = list(baseturfs)
+
+ var/list/baseturfs_copy = baseturfs.Copy()
+ baseturfs_copy.Insert(level, turf_type)
+ baseturfs = baseturfs_string_list(baseturfs_copy, src)
+
+/// Places a baseturf ontop of a searched for baseturf.
+/turf/proc/stack_ontop_of_baseturf(floor, roof)
+ if (!islist(baseturfs))
+ baseturfs = list(baseturfs)
+ var/floor_position = baseturfs.Find(floor)
+ if(floor_position != 0)
+ insert_baseturf(floor_position + 1, roof)
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index 9c110801ed2a..549524de8897 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -2,7 +2,7 @@
GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
/turf/open/space,
/turf/baseturf_bottom,
-)))
+ )))
/turf/proc/empty(turf_type=/turf/open/space, baseturf_type, list/ignore_typecache, flags)
// Remove all atoms except observers, landmarks, docking ports
@@ -16,30 +16,29 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
if(turf_type)
ChangeTurf(turf_type, baseturf_type, flags)
-/turf/proc/copyTurf(turf/T, copy_air, flags)
- if(T.type != type)
- T.ChangeTurf(type, null, flags)
- if(T.icon_state != icon_state)
- T.icon_state = icon_state
- if(T.icon != icon)
- T.icon = icon
+/turf/proc/copyTurf(turf/copy_to_turf)
+ if(copy_to_turf.type != type)
+ copy_to_turf.ChangeTurf(type)
+ if(copy_to_turf.icon_state != icon_state)
+ copy_to_turf.icon_state = icon_state
+ if(copy_to_turf.icon != icon)
+ copy_to_turf.icon = icon
if(color)
- T.atom_colours = atom_colours.Copy()
- T.update_atom_colour()
- if(T.dir != dir)
- T.setDir(dir)
- return T
+ copy_to_turf.atom_colours = atom_colours.Copy()
+ copy_to_turf.update_atom_colour()
+ if(copy_to_turf.dir != dir)
+ copy_to_turf.setDir(dir)
+ return copy_to_turf
-/turf/open/copyTurf(turf/T, copy_air = FALSE)
+/turf/open/copyTurf(turf/open/copy_to_turf, copy_air = FALSE)
. = ..()
- if (isopenturf(T))
- var/datum/component/wet_floor/slip = GetComponent(/datum/component/wet_floor)
- if(slip)
- var/datum/component/wet_floor/WF = T.AddComponent(/datum/component/wet_floor)
- WF.InheritComponent(slip)
- if (copy_air)
- var/turf/open/openTurf = T
- openTurf.air.copy_from(air)
+ ASSERT(istype(copy_to_turf, /turf/open))
+ var/datum/component/wet_floor/slip = GetComponent(/datum/component/wet_floor)
+ if(slip)
+ var/datum/component/wet_floor/new_wet_floor_component = copy_to_turf.AddComponent(/datum/component/wet_floor)
+ new_wet_floor_component.InheritComponent(slip)
+ if (copy_air)
+ copy_to_turf.air.copy_from(air)
//wrapper for ChangeTurf()s that you want to prevent/affect without overriding ChangeTurf() itself
/turf/proc/TerraformTurf(path, new_baseturf, flags)
@@ -76,70 +75,132 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
if(flags & CHANGETURF_SKIP)
return new path(src)
- var/old_dynamic_lighting = dynamic_lighting
var/old_lighting_object = lighting_object
var/old_lighting_corner_NE = lighting_corner_NE
var/old_lighting_corner_SE = lighting_corner_SE
var/old_lighting_corner_SW = lighting_corner_SW
var/old_lighting_corner_NW = lighting_corner_NW
var/old_directional_opacity = directional_opacity
+ var/old_dynamic_lumcount = dynamic_lumcount
+ var/old_rcd_memory = rcd_memory
+ var/old_explosion_throw_details = explosion_throw_details
+ var/old_opacity = opacity
+ // I'm so sorry brother
+ // This is used for a starlight optimization
+ var/old_light_range = light_range
+ // We get just the bits of explosive_resistance that aren't the turf
+ //var/old_explosive_resistance = explosive_resistance - get_explosive_block()
+ var/old_lattice_underneath = lattice_underneath
var/old_exl = explosion_level
- var/old_exi = explosion_id
+
var/old_bp = blueprint_data
blueprint_data = null
var/list/old_baseturfs = baseturfs
+ var/old_type = type
- var/list/transferring_comps = list()
- SEND_SIGNAL(src, COMSIG_TURF_CHANGE, path, new_baseturfs, flags, transferring_comps)
- for(var/i in transferring_comps)
- var/datum/component/comp = i
- comp.RemoveComponent()
+ var/list/post_change_callbacks = list()
+ SEND_SIGNAL(src, COMSIG_TURF_CHANGE, path, new_baseturfs, flags, post_change_callbacks)
changing_turf = TRUE
- qdel(src) //Just get the side effects and call Destroy
- var/turf/W = new path(src)
-
- for(var/i in transferring_comps)
- W.TakeComponent(i)
+ qdel(src) //Just get the side effects and call Destroy
+ //We do this here so anything that doesn't want to persist can clear itself
+ var/list/old_listen_lookup = _listen_lookup?.Copy()
+ var/list/old_signal_procs = _signal_procs?.Copy()
+ var/carryover_turf_flags = (RESERVATION_TURF | UNUSED_RESERVATION_TURF) & turf_flags
+ var/turf/new_turf = new path(src)
+ new_turf.turf_flags |= carryover_turf_flags
+ // WARNING WARNING
+ // Turfs DO NOT lose their signals when they get replaced, REMEMBER THIS
+ // It's possible because turfs are fucked, and if you have one in a list and it's replaced with another one, the list ref points to the new turf
+ if(old_listen_lookup)
+ LAZYOR(new_turf._listen_lookup, old_listen_lookup)
+ if(old_signal_procs)
+ LAZYOR(new_turf._signal_procs, old_signal_procs)
+
+ for(var/datum/callback/callback as anything in post_change_callbacks)
+ callback.InvokeAsync(new_turf)
if(new_baseturfs)
- W.baseturfs = new_baseturfs
+ new_turf.baseturfs = baseturfs_string_list(new_baseturfs, new_turf)
else
- W.baseturfs = old_baseturfs
-
- W.explosion_id = old_exi
- W.explosion_level = old_exl
+ new_turf.baseturfs = baseturfs_string_list(old_baseturfs, new_turf) //Just to be safe
if(!(flags & CHANGETURF_DEFER_CHANGE))
- W.AfterChange(flags)
+ new_turf.AfterChange(flags, old_type)
- W.blueprint_data = old_bp
+ new_turf.blueprint_data = old_bp
+ new_turf.rcd_memory = old_rcd_memory
+ new_turf.explosion_throw_details = old_explosion_throw_details
+ //new_turf.explosive_resistance += old_explosive_resistance
lighting_corner_NE = old_lighting_corner_NE
lighting_corner_SE = old_lighting_corner_SE
lighting_corner_SW = old_lighting_corner_SW
lighting_corner_NW = old_lighting_corner_NW
+ dynamic_lumcount = old_dynamic_lumcount
+
+ lattice_underneath = old_lattice_underneath
+
+ new_turf.explosion_level = old_exl
+
if(SSlighting.initialized)
- lighting_object = old_lighting_object
+ // Space tiles should never have lighting objects
+ if(!space_lit)
+ // Should have a lighting object if we never had one
+ lighting_object = old_lighting_object || new /datum/lighting_object(src)
+ else if (old_lighting_object)
+ qdel(old_lighting_object, force = TRUE)
+
directional_opacity = old_directional_opacity
recalculate_directional_opacity()
- if (dynamic_lighting != old_dynamic_lighting)
- if (IS_DYNAMIC_LIGHTING(src))
- lighting_build_overlay()
- else
- lighting_clear_overlay()
- else if(lighting_object && !lighting_object.needs_update)
+ if(lighting_object && !lighting_object.needs_update)
lighting_object.update()
-
- for(var/turf/open/space/S in RANGE_TURFS(1, src)) //RANGE_TURFS is in code\__HELPERS\game.dm
- S.update_starlight()
- SSdemo.mark_turf(W)
-
- return W
+
+// If we're space, then we're either lit, or not, and impacting our neighbors, or not
+ if(isspaceturf(src))
+ var/turf/open/space/lit_turf = src
+ // This also counts as a removal, so we need to do a full rebuild
+ if(!ispath(old_type, /turf/open/space))
+ lit_turf.update_starlight()
+ for(var/turf/open/space/space_tile in RANGE_TURFS(1, src) - src)
+ space_tile.update_starlight()
+ else if(old_light_range)
+ lit_turf.enable_starlight()
+
+ // If we're a cordon we count against a light, but also don't produce any ourselves
+ else if (istype(src, /turf/cordon))
+ // This counts as removing a source of starlight, so we need to update the space tile to inform it
+ if(!ispath(old_type, /turf/open/space))
+ for(var/turf/open/space/space_tile in RANGE_TURFS(1, src))
+ space_tile.update_starlight()
+
+ // If we're not either, but were formerly a space turf, then we want light
+ else if(ispath(old_type, /turf/open/space))
+ for(var/turf/open/space/space_tile in RANGE_TURFS(1, src))
+ space_tile.enable_starlight()
+
+ if(old_opacity != opacity && SSticker)
+ GLOB.cameranet.bareMajorChunkChange(src)
+
+ // We will only run this logic if the tile is not on the prime z layer, since we use area overlays to cover that
+ if(SSmapping.z_level_to_plane_offset[z])
+ var/area/our_area = new_turf.loc
+ if(our_area.lighting_effects)
+ new_turf.add_overlay(our_area.lighting_effects[SSmapping.z_level_to_plane_offset[z] + 1])
+
+ // only queue for smoothing if SSatom initialized us, and we'd be changing smoothing state
+ if(flags_1 & INITIALIZED_1)
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ QUEUE_SMOOTH(src)
+
+
+ SSdemo.mark_turf(new_turf)
+
+ return new_turf
/turf/open/ChangeTurf(path, list/new_baseturfs, flags)
//don't
@@ -165,7 +226,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
else
if(turf_fire)
qdel(turf_fire)
- if(ispath(path,/turf/closed)|| ispath(path,/turf/cordon))
+ if(ispath(path, /turf/closed) || ispath(path, /turf/cordon))
flags |= CHANGETURF_RECALC_ADJACENT
update_air_ref(-1)
. = ..()
@@ -174,128 +235,10 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
if(!istype(air,/datum/gas_mixture))
Initalize_Atmos(0)
-// Take off the top layer turf and replace it with the next baseturf down
-/turf/proc/ScrapeAway(amount=1, flags)
- if(!amount)
- return
- if(length(baseturfs))
- var/list/new_baseturfs = baseturfs.Copy()
- var/turf_type = new_baseturfs[max(1, new_baseturfs.len - amount + 1)]
- while(ispath(turf_type, /turf/baseturf_skipover))
- amount++
- if(amount > new_baseturfs.len)
- CRASH("The bottomost baseturf of a turf is a skipover [src]([type])")
- turf_type = new_baseturfs[max(1, new_baseturfs.len - amount + 1)]
- new_baseturfs.len -= min(amount, new_baseturfs.len - 1) // No removing the very bottom
- if(new_baseturfs.len == 1)
- new_baseturfs = new_baseturfs[1]
- return ChangeTurf(turf_type, new_baseturfs, flags)
-
- if(baseturfs == type)
- return src
-
- return ChangeTurf(baseturfs, baseturfs, flags) // The bottom baseturf will never go away
-
-// Take the input as baseturfs and put it underneath the current baseturfs
-// If fake_turf_type is provided and new_baseturfs is not the baseturfs list will be created identical to the turf type's
-// If both or just new_baseturfs is provided they will be inserted below the existing baseturfs
-/turf/proc/PlaceOnBottom(list/new_baseturfs, turf/fake_turf_type)
- if(fake_turf_type)
- if(!new_baseturfs)
- if(!length(baseturfs))
- baseturfs = list(baseturfs)
- var/list/old_baseturfs = baseturfs.Copy()
- assemble_baseturfs(fake_turf_type)
- if(!length(baseturfs))
- baseturfs = list(baseturfs)
- baseturfs -= baseturfs & GLOB.blacklisted_automated_baseturfs
- baseturfs += old_baseturfs
- return
- else if(!length(new_baseturfs))
- new_baseturfs = list(new_baseturfs, fake_turf_type)
- else
- new_baseturfs += fake_turf_type
- if(!length(baseturfs))
- baseturfs = list(baseturfs)
- baseturfs.Insert(1, new_baseturfs)
-
-// Make a new turf and put it on top
-// The args behave identical to PlaceOnBottom except they go on top
-// Things placed on top of closed turfs will ignore the topmost closed turf
-// Returns the new turf
-/turf/proc/PlaceOnTop(list/new_baseturfs, turf/fake_turf_type, flags)
- var/area/turf_area = loc
- if(new_baseturfs && !length(new_baseturfs))
- new_baseturfs = list(new_baseturfs)
- flags = turf_area.PlaceOnTopReact(new_baseturfs, fake_turf_type, flags) // A hook so areas can modify the incoming args
-
- var/turf/newT
- if(flags & CHANGETURF_SKIP) // We haven't been initialized
- if(flags_1 & INITIALIZED_1)
- stack_trace("CHANGETURF_SKIP was used in a PlaceOnTop call for a turf that's initialized. This is a mistake. [src]([type])")
- assemble_baseturfs()
- if(fake_turf_type)
- if(!new_baseturfs) // If no baseturfs list then we want to create one from the turf type
- if(!length(baseturfs))
- baseturfs = list(baseturfs)
- var/list/old_baseturfs = baseturfs.Copy()
- if(!istype(src, /turf/closed))
- old_baseturfs += type
- newT = ChangeTurf(fake_turf_type, null, flags)
- newT.assemble_baseturfs(initial(fake_turf_type.baseturfs)) // The baseturfs list is created like roundstart
- if(!length(newT.baseturfs))
- newT.baseturfs = list(baseturfs)
- newT.baseturfs -= GLOB.blacklisted_automated_baseturfs
- newT.baseturfs.Insert(1, old_baseturfs) // The old baseturfs are put underneath
- return newT
- if(!length(baseturfs))
- baseturfs = list(baseturfs)
- if(!istype(src, /turf/closed))
- baseturfs += type
- baseturfs += new_baseturfs
- return ChangeTurf(fake_turf_type, null, flags)
- if(!length(baseturfs))
- baseturfs = list(baseturfs)
- if(!istype(src, /turf/closed))
- baseturfs += type
- var/turf/change_type
- if(length(new_baseturfs))
- change_type = new_baseturfs[new_baseturfs.len]
- new_baseturfs.len--
- if(new_baseturfs.len)
- baseturfs += new_baseturfs
- else
- change_type = new_baseturfs
- return ChangeTurf(change_type, null, flags)
-
-// Copy an existing turf and put it on top
-// Returns the new turf
-/turf/proc/CopyOnTop(turf/copytarget, ignore_bottom=1, depth=INFINITY, copy_air = FALSE, flags)
- var/list/new_baseturfs = list()
- new_baseturfs += baseturfs
- new_baseturfs += type
-
- if(depth)
- var/list/target_baseturfs
- if(length(copytarget.baseturfs))
- // with default inputs this would be Copy(clamp(2, -INFINITY, baseturfs.len))
- // Don't forget a lower index is lower in the baseturfs stack, the bottom is baseturfs[1]
- target_baseturfs = copytarget.baseturfs.Copy(clamp(1 + ignore_bottom, 1 + copytarget.baseturfs.len - depth, copytarget.baseturfs.len))
- else if(!ignore_bottom)
- target_baseturfs = list(copytarget.baseturfs)
- if(target_baseturfs)
- target_baseturfs -= new_baseturfs & GLOB.blacklisted_automated_baseturfs
- new_baseturfs += target_baseturfs
-
- var/turf/newT = copytarget.copyTurf(src, copy_air, flags)
- newT.baseturfs = new_baseturfs
- return newT
-
-
//If you modify this function, ensure it works correctly with lateloaded map templates.
-/turf/proc/AfterChange(flags) //called after a turf has been replaced in ChangeTurf()
+/turf/proc/AfterChange(flags, oldType) //called after a turf has been replaced in ChangeTurf()
levelupdate()
- ImmediateCalculateAdjacentTurfs()
+ immediate_calculate_adjacent_turfs()
//update firedoor adjacency
var/list/turfs_to_check = get_adjacent_open_turfs(src) | src
@@ -304,11 +247,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
for(var/obj/machinery/door/firedoor/FD in T)
FD.CalculateAffectingAreas()
- queue_smooth_neighbors(src)
-
- HandleTurfChange(src)
-
-/turf/open/AfterChange(flags)
+/turf/open/AfterChange(flags, oldType)
..()
RemoveLattice()
if(!(flags & (CHANGETURF_IGNORE_AIR | CHANGETURF_INHERIT_AIR)))
@@ -330,6 +269,11 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
air.copy_from(total.remove_ratio(1/turf_count))
-/turf/proc/ReplaceWithLattice()
- ScrapeToBottom(flags = CHANGETURF_INHERIT_AIR) // Yogs -- fixes this not actually replacing the turf with a lattice, lmao (ScrapeToBottom defined in yogs file)
- new /obj/structure/lattice(locate(x, y, z))
+/// Attempts to replace a tile with lattice. Amount is the amount of tiles to scrape away.
+/turf/proc/attempt_lattice_replacement(amount = 2)
+ if(lattice_underneath)
+ var/turf/new_turf = ScrapeAway(amount, flags = CHANGETURF_INHERIT_AIR)
+ if(!istype(new_turf, /turf/open/floor))
+ new /obj/structure/lattice(src)
+ else
+ ScrapeAway(amount, flags = CHANGETURF_INHERIT_AIR)
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
new file mode 100644
index 000000000000..d9e4815adb01
--- /dev/null
+++ b/code/game/turfs/closed/_closed.dm
@@ -0,0 +1,21 @@
+/turf/closed
+ layer = CLOSED_TURF_LAYER
+ plane = WALL_PLANE
+ opacity = TRUE
+ density = TRUE
+ blocks_air = TRUE
+ flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ turf_flags = IS_SOLID
+ rad_insulation = RAD_MEDIUM_INSULATION
+
+/turf/closed/AfterChange()
+ . = ..()
+ SSair.high_pressure_delta -= src
+
+/turf/closed/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ return FALSE
+
+/turf/closed/CanAllowThrough(atom/movable/mover, turf/target)
+ . = ..()
+ if(istype(mover) && (mover.pass_flags & PASSCLOSEDTURF))
+ return TRUE
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed/indestructible.dm
similarity index 58%
rename from code/game/turfs/closed.dm
rename to code/game/turfs/closed/indestructible.dm
index 114bcd368b51..5f138e222905 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed/indestructible.dm
@@ -1,28 +1,12 @@
-/turf/closed
- layer = CLOSED_TURF_LAYER
- opacity = TRUE
- density = TRUE
- blocks_air = TRUE
- flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
- rad_insulation = RAD_MEDIUM_INSULATION
-
-/turf/closed/AfterChange()
- . = ..()
- SSair.high_pressure_delta -= src
-
-/turf/closed/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- return FALSE
-
-/turf/closed/CanAllowThrough(atom/movable/mover, turf/target)
- . = ..()
- if(istype(mover) && (mover.pass_flags & PASSCLOSEDTURF))
- return TRUE
-
/turf/closed/indestructible
name = "wall"
+ desc = "Effectively impervious to conventional methods of destruction."
icon = 'icons/turf/walls.dmi'
explosion_block = 50
- flags_1 = NOJAUNT_1 | CAN_BE_DIRTY_1 | NO_RUST
+ turf_flags = NOJAUNT | NO_RUST
+
+/turf/closed/indestructible/rust_heretic_act()
+ return
/turf/closed/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE)
return
@@ -52,46 +36,89 @@
name = "sandstone wall"
desc = "A wall with sandstone plating. Rough."
icon = 'icons/turf/walls/sandstone_wall.dmi'
- icon_state = "sandstone"
+ icon_state = "sandstone_wall-0"
+ base_icon_state = "sandstone_wall"
baseturfs = /turf/closed/indestructible/sandstone
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_BITMASK
/turf/closed/indestructible/oldshuttle/corner
icon_state = "corner"
/turf/closed/indestructible/splashscreen
name = "Space Station 13"
+ desc = null
icon = 'icons/blanks/blank_title.png'
icon_state = ""
- layer = FLY_LAYER
+ //pixel_x = -64
+ plane = SPLASHSCREEN_PLANE
bullet_bounce_sound = null
-/turf/closed/indestructible/splashscreen/New()
+INITIALIZE_IMMEDIATE(/turf/closed/indestructible/splashscreen)
+
+/turf/closed/indestructible/splashscreen/Initialize(mapload)
+ . = ..()
SStitle.splash_turf = src
if(SStitle.icon)
icon = SStitle.icon
- ..()
+ handle_generic_titlescreen_sizes()
+
+///helper proc that will center the screen if the icon is changed to a generic width, to make admins have to fudge around with pixel_x less. returns null
+/turf/closed/indestructible/splashscreen/proc/handle_generic_titlescreen_sizes()
+ var/icon/size_check = icon(SStitle.icon, icon_state)
+ var/width = size_check.Width()
+ if(width == 480) // 480x480 is nonwidescreen
+ pixel_x = 0
+ else if(width == 608) // 608x480 is widescreen
+ pixel_x = -64
/turf/closed/indestructible/splashscreen/vv_edit_var(var_name, var_value)
. = ..()
if(.)
switch(var_name)
- if("icon")
+ if(NAMEOF(src, icon))
SStitle.icon = icon
+ handle_generic_titlescreen_sizes()
/turf/closed/indestructible/riveted
icon = 'icons/turf/walls/riveted.dmi'
- icon_state = "riveted"
- smooth = SMOOTH_TRUE
+ icon_state = "riveted-0"
+ base_icon_state = "riveted"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
/turf/closed/indestructible/syndicate
icon = 'icons/turf/walls/plastitanium_wall.dmi'
- icon_state = "map-shuttle"
- smooth = SMOOTH_MORE
+ icon_state = "plastitanium_wall-0"
+ base_icon_state = "plastitanium_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_SYNDICATE_WALLS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_SYNDICATE_WALLS
/turf/closed/indestructible/riveted/uranium
icon = 'icons/turf/walls/uranium_wall.dmi'
- icon_state = "uranium"
+ icon_state = "uranium_wall-0"
+ base_icon_state = "uranium_wall"
+ smoothing_flags = SMOOTH_BITMASK
+
+/turf/closed/indestructible/wood
+ icon = 'icons/turf/walls/wood_wall.dmi'
+ icon_state = "wood_wall-0"
+ base_icon_state = "wood_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WOOD_WALLS
+
+
+/turf/closed/indestructible/alien
+ name = "alien wall"
+ desc = "A wall with alien alloy plating."
+ icon = 'icons/turf/walls/abductor_wall.dmi'
+ icon_state = "abductor_wall-0"
+ base_icon_state = "abductor_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS
/turf/closed/indestructible/abductor
icon_state = "alien1"
@@ -101,23 +128,29 @@
/turf/closed/indestructible/fakeglass
name = "window"
- icon_state = "fake_window"
+ icon = MAP_SWITCH('icons/obj/smooth_structures/reinforced_window.dmi', 'icons/obj/smooth_structures/structure_variations.dmi')
+ icon_state = MAP_SWITCH("reinforced_window-0", "fake_window")
+ base_icon_state = "reinforced_window"
opacity = FALSE
- smooth = SMOOTH_TRUE
- icon = 'icons/obj/smooth_structures/reinforced_window.dmi'
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
/turf/closed/indestructible/fakeglass/Initialize(mapload)
. = ..()
- icon_state = null //set the icon state to null, so our base state isn't visible
- underlays += mutable_appearance('icons/obj/structures.dmi', "grille") //add a grille underlay
- underlays += mutable_appearance('icons/turf/floors.dmi', "plating") //add the plating underlay, below the grille
+ icon_state = null
+ underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01) //add a grille underlay
+ underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02) //add the plating underlay, below the grille
/turf/closed/indestructible/opsglass
name = "window"
- icon_state = "plastitanium_window"
- opacity = FALSE
- smooth = SMOOTH_TRUE
icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
+ icon_state = "plastitanium_window-0"
+ base_icon_state = "plastitanium_window"
+ opacity = FALSE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
/turf/closed/indestructible/opsglass/Initialize(mapload)
. = ..()
@@ -155,11 +188,14 @@
icon = 'icons/turf/walls.dmi'
icon_state = "icerock"
-/turf/closed/indestructible/rock/wood
- name = "wooden wall"
- desc = "A wall with wooden plating. Stiff."
- icon = 'icons/turf/walls/wood_wall.dmi'
- icon_state = "wood"
+/turf/closed/indestructible/rock/snow/ice/ore
+ icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon_state = "icerock_wall-0"
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
+ pixel_x = -4
+ pixel_y = -4
/turf/closed/indestructible/paper
name = "thick paper wall"
@@ -190,8 +226,11 @@
name = "necropolis wall"
desc = "A thick, seemingly indestructible stone wall."
icon = 'icons/turf/walls/boss_wall.dmi'
- icon_state = "wall"
- canSmoothWith = list(/turf/closed/indestructible/riveted/boss, /turf/closed/indestructible/riveted/boss/see_through)
+ icon_state = "boss_wall-0"
+ base_icon_state = "boss_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_BOSS_WALLS
+ canSmoothWith = SMOOTH_GROUP_BOSS_WALLS
explosion_block = 50
baseturfs = /turf/closed/indestructible/riveted/boss
@@ -208,3 +247,7 @@
desc = "A wall made out of a strange metal. The squares on it pulse in a predictable pattern."
icon = 'icons/turf/walls/hierophant_wall.dmi'
icon_state = "wall"
+ smoothing_flags = SMOOTH_CORNERS
+ smoothing_groups = SMOOTH_GROUP_HIERO_WALL
+ canSmoothWith = SMOOTH_GROUP_HIERO_WALL
+
diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/closed/minerals.dm
similarity index 77%
rename from code/game/turfs/simulated/minerals.dm
rename to code/game/turfs/closed/minerals.dm
index b9e8d6521e16..4489a7d47c27 100644
--- a/code/game/turfs/simulated/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -2,21 +2,32 @@
/turf/closed/mineral //wall piece
name = "rock"
- icon = 'icons/turf/mining.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks.dmi', 'icons/turf/mining.dmi')
icon_state = "rock"
- var/smooth_icon = 'icons/turf/smoothrocks.dmi'
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1 | NO_RUST
- canSmoothWith = null
+ smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_MINERAL_WALLS
+ canSmoothWith = SMOOTH_GROUP_MINERAL_WALLS
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
baseturfs = /turf/open/floor/plating/asteroid/airless
initial_gas_mix = AIRLESS_ATMOS
opacity = TRUE
density = TRUE
+ // We're a BIG wall, larger then 32x32, so we need to be on the game plane
+ // Otherwise we'll draw under shit in weird ways
+ plane = GAME_PLANE
layer = EDGED_TURF_LAYER
+ base_icon_state = "smoothrocks"
+
+ // This is static
+ // Done like this to avoid needing to make it dynamic and save cpu time
+ // 4 to the left, 4 down
+ transform = MAP_SWITCH(TRANSLATE_MATRIX(-4, -4), matrix())
+
+ flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
initial_temperature = TCMB
+
var/environment_type = "asteroid"
var/turf/open/floor/plating/turf_type = /turf/open/floor/plating/asteroid/airless
- var/mineralType = null
+ var/obj/item/stack/ore/mineralType = null
var/mineralAmt = 3
var/spread = 0 //will the seam spread?
var/spreadChance = 0 //the percentual chance of an ore spreading to the neighbouring tiles
@@ -25,13 +36,15 @@
var/hardness = 1 //how hard the material is, we'll have to have more powerful stuff if we want to blast harder materials.
/turf/closed/mineral/Initialize(mapload)
- if (!canSmoothWith)
- canSmoothWith = list(/turf/closed/mineral, /turf/closed/indestructible)
- var/matrix/M = new
- M.Translate(-4, -4)
- transform = M
- icon = smooth_icon
. = ..()
+ // Mineral turfs are big, so they need to be on the game plane at a high layer
+ // But they're also turfs, so we need to cut them out from the light mask plane
+ // So we draw them as if they were on the game plane, and then overlay a copy onto
+ // The wall plane (so emissives/light masks behave)
+ // I am so sorry
+ var/static/mutable_appearance/wall_overlay = mutable_appearance('icons/turf/mining.dmi', "rock", appearance_flags = RESET_TRANSFORM)
+ wall_overlay.plane = MUTATE_PLANE(WALL_PLANE, src)
+ overlays += wall_overlay
if (mineralType && mineralAmt && spread && spreadChance)
for(var/dir in GLOB.cardinals)
if(prob(spreadChance))
@@ -46,7 +59,6 @@
return TRUE
return ..()
-
/turf/closed/mineral/attackby(obj/item/I, mob/user, params)
if (!user.IsAdvancedToolUser())
to_chat(usr, span_warning("You don't have the dexterity to do this!"))
@@ -94,18 +106,17 @@
if(hardness <= 0)
gets_drilled(user,triggered_by_explosion)
else
- update_appearance(UPDATE_ICON)
+ update_appearance()
/turf/closed/mineral/update_overlays()
. = ..()
if(hardness != initial(hardness))
- var/mutable_appearance/cracks = mutable_appearance('icons/turf/mining.dmi',"rock_cracks",ON_EDGED_TURF_LAYER)
+ var/mutable_appearance/cracks = mutable_appearance('icons/turf/mining.dmi',"rock_cracks", ON_EDGED_TURF_LAYER)
var/matrix/M = new
M.Translate(4,4)
cracks.transform = M
. += cracks
-
/turf/closed/mineral/attack_animal(mob/living/simple_animal/user)
if((user.environment_smash & ENVIRONMENT_SMASH_WALLS) || (user.environment_smash & ENVIRONMENT_SMASH_RWALLS))
attempt_drill()
@@ -174,7 +185,11 @@
. = ..()
if (prob(mineralChance))
var/path = pickweight(mineralSpawnChanceList)
+ var/stored_flags = 0
+ if(turf_flags & NO_RUINS)
+ stored_flags |= NO_RUINS
var/turf/T = ChangeTurf(path,null,CHANGETURF_IGNORE_AIR)
+ T.flags_1 |= stored_flags
if(T && ismineralturf(T))
var/turf/closed/mineral/M = T
@@ -184,6 +199,9 @@
M.baseturfs = src.baseturfs
src = M
M.levelupdate()
+ else
+ src = T
+ T.levelupdate()
/turf/closed/mineral/random/high_chance
icon_state = "rock_highchance"
@@ -205,11 +223,11 @@
/turf/closed/mineral/random/high_chance/snow
name = "snowy mountainside"
- icon = 'icons/turf/mining.dmi'
- smooth_icon = 'icons/turf/walls/mountain_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/mountain_wall.dmi', 'icons/turf/mining.dmi')
icon_state = "mountainrock"
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- canSmoothWith = list (/turf/closed)
+ base_icon_state = "mountain_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
defer_change = TRUE
environment_type = "snow"
turf_type = /turf/open/floor/plating/asteroid/snow/icemoon
@@ -263,7 +281,8 @@
/turf/closed/mineral/random/volcanic/hard
name = "hardened basalt"
icon_state = "rock_hard"
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
mineralChance = 15
hardness = 2
@@ -271,25 +290,29 @@
/turf/closed/mineral/uranium/volcanic/hard = 5, /turf/closed/mineral/diamond/volcanic/hard = 1, /turf/closed/mineral/gold/volcanic/hard = 10, /turf/closed/mineral/titanium/volcanic/hard = 11, /turf/closed/mineral/magmite/volcanic/hard = 0.5, /turf/closed/mineral/gem/volcanic/hard = 2,
/turf/closed/mineral/silver/volcanic/hard = 12, /turf/closed/mineral/plasma/volcanic/hard = 20, /turf/closed/mineral/iron/volcanic/hard = 20, /turf/closed/mineral/dilithium/volcanic/hard = 2, /turf/closed/mineral/gibtonite/volcanic/hard = 4, /turf/closed/mineral/bscrystal/volcanic/hard = 2)
-/turf/closed/mineral/random/volcanic/hard/harder
+/turf/closed/mineral/random/volcanic/harder
name = "granite"
- icon_state = "rock"
- smooth_icon = 'icons/turf/smoothrocks.dmi'
color = "#eb9877"
mineralChance = 20
hardness = 3
mineralSpawnChanceList = list(
- /turf/closed/mineral/uranium/volcanic/hard/harder = 6, /turf/closed/mineral/diamond/volcanic/hard/harder = 2, /turf/closed/mineral/gold/volcanic/hard/harder = 11, /turf/closed/mineral/titanium/volcanic/hard/harder = 12, /turf/closed/mineral/magmite/volcanic/hard/harder = 2, /turf/closed/mineral/gem/volcanic/hard/harder = 2,
- /turf/closed/mineral/silver/volcanic/hard/harder = 13, /turf/closed/mineral/plasma/volcanic/hard/harder = 21, /turf/closed/mineral/iron/volcanic/hard/harder = 10, /turf/closed/mineral/dilithium/volcanic/hard/harder = 4, /turf/closed/mineral/gibtonite/volcanic/hard/harder = 7, /turf/closed/mineral/bscrystal/volcanic/hard/harder = 3)
+ /turf/closed/mineral/uranium/volcanic/harder = 6, /turf/closed/mineral/diamond/volcanic/harder = 2, /turf/closed/mineral/gold/volcanic/harder = 11, /turf/closed/mineral/titanium/volcanic/harder = 12, /turf/closed/mineral/magmite/volcanic/harder = 2, /turf/closed/mineral/gem/volcanic/harder = 2,
+ /turf/closed/mineral/silver/volcanic/harder = 13, /turf/closed/mineral/plasma/volcanic/harder = 21, /turf/closed/mineral/iron/volcanic/harder = 10, /turf/closed/mineral/dilithium/volcanic/harder = 4, /turf/closed/mineral/gibtonite/volcanic/harder = 7, /turf/closed/mineral/bscrystal/volcanic/harder = 3)
+
+/// A turf that can't we can't build openspace chasms on or spawn ruins in.
+/turf/closed/mineral/random/volcanic/do_not_chasm
+ turf_type = /turf/open/floor/plating/asteroid/basalt/lava_land_surface/no_ruins
+ baseturfs = /turf/open/floor/plating/asteroid/basalt/lava_land_surface/no_ruins
+ turf_flags = NO_RUINS
/turf/closed/mineral/random/snow
name = "snowy mountainside"
- icon = 'icons/turf/mining.dmi'
- smooth_icon = 'icons/turf/walls/mountain_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/mountain_wall.dmi', 'icons/turf/mining.dmi')
icon_state = "mountainrock"
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- canSmoothWith = list (/turf/closed)
+ base_icon_state = "mountain_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
defer_change = TRUE
environment_type = "snow"
turf_type = /turf/open/floor/plating/asteroid/snow/icemoon
@@ -354,11 +377,11 @@
defer_change = TRUE
/turf/closed/mineral/iron/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/iron/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/iron/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -366,7 +389,9 @@
/turf/closed/mineral/iron/ice
environment_type = "snow_cavern"
icon_state = "icerock_iron"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -396,11 +421,11 @@
defer_change = TRUE
/turf/closed/mineral/uranium/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/uranium/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/uranium/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -408,7 +433,9 @@
/turf/closed/mineral/uranium/ice
environment_type = "snow_cavern"
icon_state = "icerock_Uranium"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -437,11 +464,11 @@
defer_change = TRUE
/turf/closed/mineral/diamond/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/diamond/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/diamond/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -449,7 +476,9 @@
/turf/closed/mineral/diamond/ice
environment_type = "snow_cavern"
icon_state = "icerock_diamond"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -478,11 +507,11 @@
defer_change = TRUE
/turf/closed/mineral/gold/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/gold/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/gold/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -490,7 +519,9 @@
/turf/closed/mineral/gold/ice
environment_type = "snow_cavern"
icon_state = "icerock_gold"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -519,11 +550,11 @@
defer_change = TRUE
/turf/closed/mineral/silver/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/silver/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/silver/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -531,7 +562,9 @@
/turf/closed/mineral/silver/ice
environment_type = "snow_cavern"
icon_state = "icerock_silver"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -560,11 +593,11 @@
defer_change = TRUE
/turf/closed/mineral/titanium/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/titanium/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/titanium/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -572,7 +605,9 @@
/turf/closed/mineral/titanium/ice
environment_type = "snow_cavern"
icon_state = "icerock_titanium"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -602,11 +637,11 @@
defer_change = TRUE
/turf/closed/mineral/plasma/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/plasma/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/plasma/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
@@ -614,7 +649,9 @@
/turf/closed/mineral/plasma/ice
environment_type = "snow_cavern"
icon_state = "icerock_plasma"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -639,7 +676,9 @@
/turf/closed/mineral/bananium/ice
environment_type = "snow_cavern"
icon_state = "icerock_Bananium"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -658,11 +697,11 @@
defer_change = TRUE
/turf/closed/mineral/bananium/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/bananium/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/bananium/volcanic/harder
mineralAmt = 3
color = "#eb9877"
hardness = 3
@@ -682,11 +721,11 @@
defer_change = TRUE
/turf/closed/mineral/bscrystal/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/bscrystal/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/bscrystal/volcanic/harder
mineralAmt = 3
color = "#eb9877"
hardness = 3
@@ -694,7 +733,9 @@
/turf/closed/mineral/bscrystal/ice
environment_type = "snow_cavern"
icon_state = "icerock_BScrystal"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -722,21 +763,21 @@
defer_change = TRUE
/turf/closed/mineral/volcanic/lava_land_surface/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/volcanic/lava_land_surface/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/volcanic/lava_land_surface/harder
color = "#eb9877"
hardness = 3
/turf/closed/mineral/ash_rock //wall piece
name = "rock"
- icon = 'icons/turf/mining.dmi'
- smooth_icon = 'icons/turf/walls/rock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/rock_wall.dmi', 'icons/turf/mining.dmi')
icon_state = "rock2"
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- canSmoothWith = list (/turf/closed)
+ base_icon_state = "rock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
baseturfs = /turf/open/floor/plating/ashplanet/wateryrock
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
environment_type = "waste"
@@ -750,29 +791,39 @@
/turf/closed/mineral/snowmountain
name = "snowy mountainside"
- icon = 'icons/turf/mining.dmi'
- smooth_icon = 'icons/turf/walls/mountain_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/mountain_wall.dmi', 'icons/turf/mining.dmi')
icon_state = "mountainrock"
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- canSmoothWith = list (/turf/closed)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
baseturfs = /turf/open/floor/plating/asteroid/snow
initial_gas_mix = FROZEN_ATMOS
environment_type = "snow"
turf_type = /turf/open/floor/plating/asteroid/snow
defer_change = TRUE
+/// Near exact same subtype as parent, just used in ruins to prevent other ruins/chasms from spawning on top of it.
+/turf/closed/mineral/snowmountain/do_not_chasm
+ turf_type = /turf/open/floor/plating/asteroid/snow/icemoon/do_not_chasm
+ baseturfs = /turf/open/floor/plating/asteroid/snow/icemoon/do_not_chasm
+ turf_flags = NO_RUINS
+
/turf/closed/mineral/snowmountain/icemoon
turf_type = /turf/open/floor/plating/asteroid/snow/icemoon
baseturfs = /turf/open/floor/plating/asteroid/snow/icemoon
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
+/// This snowy mountain will never be scraped away for any reason what so ever.
+/turf/closed/mineral/snowmountain/icemoon/unscrapeable
+ turf_flags = IS_SOLID | NO_CLEARING
+ turf_type = /turf/open/floor/plating/asteroid/snow/icemoon/do_not_scrape
+ baseturfs = /turf/open/floor/plating/asteroid/snow/icemoon/do_not_scrape
+
/turf/closed/mineral/snowmountain/cavern
name = "ice cavern rock"
- icon = 'icons/turf/mining.dmi'
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
icon_state = "icerock"
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- canSmoothWith = list (/turf/closed)
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
environment_type = "snow_cavern"
turf_type = /turf/open/floor/plating/asteroid/snow/ice
@@ -786,6 +837,7 @@
/turf/closed/mineral/gibtonite
mineralAmt = 1
+ MAP_SWITCH(, icon_state = "rock_Gibtonite_inactive")
spreadChance = 0
spread = 0
scan_state = "rock_Gibtonite"
@@ -814,7 +866,7 @@
/turf/closed/mineral/gibtonite/proc/explosive_reaction(mob/user = null, triggered_by_explosion = 0)
if(stage == GIBTONITE_UNSTRUCK)
- activated_overlay = mutable_appearance('icons/turf/smoothrocks.dmi', "rock_Gibtonite_active", ON_EDGED_TURF_LAYER)
+ activated_overlay = mutable_appearance('icons/turf/smoothrocks_overlays.dmi', "rock_Gibtonite_inactive", ON_EDGED_TURF_LAYER) //shows in gaps between pulses if there are any
add_overlay(activated_overlay)
name = "gibtonite deposit"
desc = "An active gibtonite reserve. Run!"
@@ -835,6 +887,7 @@
/turf/closed/mineral/gibtonite/proc/countdown(notify_admins = 0)
set waitfor = 0
while(istype(src, /turf/closed/mineral/gibtonite) && stage == GIBTONITE_ACTIVE && det_time > 0 && mineralAmt >= 1)
+ flick_overlay_view(mutable_appearance('icons/turf/smoothrocks_overlays.dmi', "rock_Gibtonite_active", ON_EDGED_TURF_LAYER + 0.1), 0.5 SECONDS) //makes the animation pulse one time per tick
det_time--
sleep(0.5 SECONDS)
if(istype(src, /turf/closed/mineral/gibtonite))
@@ -879,11 +932,12 @@
G.icon_state = "Gibtonite ore 2"
var/flags = NONE
+ var/old_type = type
if(defer_change)
flags = CHANGETURF_DEFER_CHANGE
- ScrapeAway(null, flags)
- addtimer(CALLBACK(src, PROC_REF(AfterChange)), 1, TIMER_UNIQUE)
-
+ var/turf/open/mined = ScrapeAway(null, flags)
+ addtimer(CALLBACK(src, PROC_REF(AfterChange), flags, old_type), 1, TIMER_UNIQUE)
+ mined.update_visuals()
/turf/closed/mineral/gibtonite/volcanic
environment_type = "basalt"
@@ -893,18 +947,20 @@
defer_change = TRUE
/turf/closed/mineral/gibtonite/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/gibtonite/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/gibtonite/volcanic/harder
color = "#eb9877"
hardness = 3
/turf/closed/mineral/gibtonite/ice
environment_type = "snow_cavern"
icon_state = "icerock_Gibtonite"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
@@ -937,11 +993,11 @@
defer_change = TRUE
/turf/closed/mineral/magmite/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/magmite/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/magmite/volcanic/harder
color = "#eb9877"
hardness = 3
@@ -960,11 +1016,34 @@
/turf/closed/mineral/gem/volcanic/hard
mineralAmt = 2
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
hardness = 2
-/turf/closed/mineral/gem/volcanic/hard/harder
+/turf/closed/mineral/gem/volcanic/harder
mineralAmt = 3
- smooth_icon = 'icons/turf/smoothrocks.dmi'
color = "#eb9877"
hardness = 3
+
+//yoo RED ROCK RED ROCK
+
+/turf/closed/mineral/asteroid
+ name = "iron rock"
+ icon = MAP_SWITCH('icons/turf/walls/red_wall.dmi', 'icons/turf/mining.dmi')
+ icon_state = "redrock"
+ base_icon_state = "red_wall"
+
+/turf/closed/mineral/random/stationside/asteroid
+ name = "iron rock"
+ icon = MAP_SWITCH('icons/turf/walls/red_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "red_wall"
+
+/turf/closed/mineral/random/stationside/asteroid/porus
+ name = "porous iron rock"
+ desc = "This rock is filled with pockets of breathable air."
+ baseturfs = /turf/open/floor/plating/asteroid
+
+/turf/closed/mineral/asteroid/porous
+ name = "porous rock"
+ desc = "This rock is filled with pockets of breathable air."
+ baseturfs = /turf/open/floor/plating/asteroid
diff --git a/code/game/turfs/closed/wall/material_walls.dm b/code/game/turfs/closed/wall/material_walls.dm
new file mode 100644
index 000000000000..e62500072220
--- /dev/null
+++ b/code/game/turfs/closed/wall/material_walls.dm
@@ -0,0 +1,26 @@
+/turf/closed/wall/material
+ name = "wall"
+ desc = "A huge chunk of material used to separate rooms."
+ icon = 'icons/turf/walls/material_wall.dmi'
+ icon_state = "material_wall-0"
+ base_icon_state = "material_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_MATERIAL_WALLS
+ canSmoothWith = SMOOTH_GROUP_MATERIAL_WALLS
+ rcd_memory = null
+ material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+
+/turf/closed/wall/material/break_wall()
+ for(var/i in custom_materials)
+ var/datum/material/M = i
+ new M.sheet_type(src, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1))
+ return new girder_type(src)
+
+/turf/closed/wall/material/devastate_wall()
+ for(var/i in custom_materials)
+ var/datum/material/M = i
+ new M.sheet_type(src, FLOOR(custom_materials[M] / SHEET_MATERIAL_AMOUNT, 1))
+
+/turf/closed/wall/material/mat_update_desc(mat)
+ desc = "A huge chunk of [mat] used to separate rooms."
+
diff --git a/code/game/turfs/simulated/wall/mineral_walls.dm b/code/game/turfs/closed/wall/mineral_walls.dm
similarity index 63%
rename from code/game/turfs/simulated/wall/mineral_walls.dm
rename to code/game/turfs/closed/wall/mineral_walls.dm
index 5636dce78a18..e3ee7484de90 100644
--- a/code/game/turfs/simulated/wall/mineral_walls.dm
+++ b/code/game/turfs/closed/wall/mineral_walls.dm
@@ -2,45 +2,57 @@
name = "mineral wall"
desc = "This shouldn't exist"
icon_state = ""
+ smoothing_flags = SMOOTH_BITMASK
+ canSmoothWith = null
+
var/last_event = 0
var/active = null
- canSmoothWith = null
- smooth = SMOOTH_TRUE
/turf/closed/wall/mineral/gold
name = "gold wall"
desc = "A wall with gold plating. Swag!"
icon = 'icons/turf/walls/gold_wall.dmi'
- icon_state = "gold"
+ icon_state = "gold_wall-0"
+ base_icon_state = "gold_wall"
sheet_type = /obj/item/stack/sheet/mineral/gold
explosion_block = 0 //gold is a soft metal you dingus.
- canSmoothWith = list(/turf/closed/wall/mineral/gold, /obj/structure/falsewall/gold)
+ smoothing_groups = SMOOTH_GROUP_GOLD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_GOLD_WALLS
/turf/closed/wall/mineral/silver
name = "silver wall"
desc = "A wall with silver plating. Shiny!"
icon = 'icons/turf/walls/silver_wall.dmi'
- icon_state = "silver"
+ icon_state = "silver_wall-0"
+ base_icon_state = "silver_wall"
sheet_type = /obj/item/stack/sheet/mineral/silver
- canSmoothWith = list(/turf/closed/wall/mineral/silver, /obj/structure/falsewall/silver)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SILVER_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_SILVER_WALLS
/turf/closed/wall/mineral/diamond
name = "diamond wall"
desc = "A wall with diamond plating. You monster."
icon = 'icons/turf/walls/diamond_wall.dmi'
- icon_state = "diamond"
+ icon_state = "diamond_wall-0"
+ base_icon_state = "diamond_wall"
sheet_type = /obj/item/stack/sheet/mineral/diamond
slicing_duration = 400 //diamond wall takes twice as much time to slice
explosion_block = 3
- canSmoothWith = list(/turf/closed/wall/mineral/diamond, /obj/structure/falsewall/diamond)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_DIAMOND_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_DIAMOND_WALLS
/turf/closed/wall/mineral/bananium
name = "bananium wall"
desc = "A wall with bananium plating. Honk!"
icon = 'icons/turf/walls/bananium_wall.dmi'
- icon_state = "bananium"
+ icon_state = "bananium_wall-0"
+ base_icon_state = "bananium_wall"
sheet_type = /obj/item/stack/sheet/mineral/bananium
- canSmoothWith = list(/turf/closed/wall/mineral/bananium, /obj/structure/falsewall/bananium)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_BANANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_BANANIUM_WALLS
/turf/closed/wall/mineral/bananium/honk_act()
return FALSE
@@ -49,19 +61,25 @@
name = "sandstone wall"
desc = "A wall with sandstone plating. Rough."
icon = 'icons/turf/walls/sandstone_wall.dmi'
- icon_state = "sandstone"
+ icon_state = "sandstone_wall-0"
+ base_icon_state = "sandstone_wall"
sheet_type = /obj/item/stack/sheet/mineral/sandstone
explosion_block = 0
- canSmoothWith = list(/turf/closed/wall/mineral/sandstone, /obj/structure/falsewall/sandstone)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SANDSTONE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_SANDSTONE_WALLS
/turf/closed/wall/mineral/uranium
article = "a"
name = "uranium wall"
desc = "A wall with uranium plating. This is probably a bad idea."
icon = 'icons/turf/walls/uranium_wall.dmi'
- icon_state = "uranium"
+ icon_state = "uranium_wall-0"
+ base_icon_state = "uranium_wall"
sheet_type = /obj/item/stack/sheet/mineral/uranium
- canSmoothWith = list(/turf/closed/wall/mineral/uranium, /obj/structure/falsewall/uranium)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_URANIUM_WALLS + SMOOTH_GROUP_WALLS
+ canSmoothWith = SMOOTH_GROUP_URANIUM_WALLS
/turf/closed/wall/mineral/uranium/proc/radiate()
if(!active)
@@ -91,10 +109,13 @@
name = "plasma wall"
desc = "A wall with plasma plating. This is definitely a bad idea."
icon = 'icons/turf/walls/plasma_wall.dmi'
- icon_state = "plasma"
+ icon_state = "plasma_wall-0"
+ base_icon_state = "plasma_wall"
sheet_type = /obj/item/stack/sheet/mineral/plasma
thermal_conductivity = 0.04
- canSmoothWith = list(/turf/closed/wall/mineral/plasma, /obj/structure/falsewall/plasma)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_PLASMA_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_PLASMA_WALLS
rad_insulation = RAD_FULL_INSULATION
/turf/closed/wall/mineral/plasma/attackby(obj/item/W, mob/user, params)
@@ -130,20 +151,25 @@
name = "wooden wall"
desc = "A wall with wooden plating. Stiff."
icon = 'icons/turf/walls/wood_wall.dmi'
- icon_state = "wood"
+ icon_state = "wood_wall-0"
+ base_icon_state = "wood_wall"
sheet_type = /obj/item/stack/sheet/mineral/wood
hardness = 70
explosion_block = 0
- canSmoothWith = list(/turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/closed/wall/mineral/wood/nonmetal)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WOOD_WALLS
/turf/closed/wall/mineral/bamboo
name = "bamboo wall"
desc = "A wall with a bamboo finish."
icon = 'icons/turf/walls/bamboo_wall.dmi'
- icon_state = "bamboo"
+ icon_state = "wall-0"
sheet_type = /obj/item/stack/sheet/mineral/bamboo
hardness = 60
- canSmoothWith = list(/turf/closed/wall/mineral/bamboo, /obj/structure/falsewall/bamboo)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_BAMBOO_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_BAMBOO_WALLS
/turf/closed/wall/mineral/wood/attackby(obj/item/W, mob/user)
if(W.is_sharp() && W.force)
@@ -159,22 +185,25 @@
desc = "A solidly wooden wall. It's a bit weaker than a wall made with metal."
girder_type = /obj/structure/barricade/wooden
hardness = 50
- canSmoothWith = list(/turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/closed/wall/mineral/wood/nonmetal)
/turf/closed/wall/mineral/iron
name = "rough metal wall"
desc = "A wall with rough metal plating."
icon = 'icons/turf/walls/iron_wall.dmi'
- icon_state = "iron"
+ icon_state = "iron_wall-0"
+ base_icon_state = "iron_wall"
sheet_type = /obj/item/stack/rods
sheet_amount = 5
- canSmoothWith = list(/turf/closed/wall/mineral/iron, /obj/structure/falsewall/iron)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_IRON_WALLS
/turf/closed/wall/mineral/snow
name = "packed snow wall"
desc = "A wall made of densely packed snow blocks."
icon = 'icons/turf/walls/snow_wall.dmi'
- icon_state = "snow"
+ icon_state = "snow_wall-0"
+ base_icon_state = "snow_wall"
hardness = 80
explosion_block = 0
slicing_duration = 30
@@ -188,12 +217,14 @@
name = "alien wall"
desc = "A wall with alien alloy plating."
icon = 'icons/turf/walls/abductor_wall.dmi'
- icon_state = "abductor"
- smooth = SMOOTH_TRUE|SMOOTH_DIAGONAL
+ icon_state = "abductor_wall-0"
+ base_icon_state = "abductor_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS
sheet_type = /obj/item/stack/sheet/mineral/abductor
slicing_duration = 400 //alien wall takes twice as much time to slice
explosion_block = 3
- canSmoothWith = list(/turf/closed/wall/mineral/abductor, /obj/structure/falsewall/abductor)
/////////////////////Titanium walls/////////////////////
@@ -201,44 +232,38 @@
name = "wall"
desc = "A light-weight titanium wall used in shuttles."
icon = 'icons/turf/walls/shuttle_wall.dmi'
- icon_state = "map-shuttle"
+ icon_state = "shuttle_wall-0"
+ base_icon_state = "shuttle_wall"
explosion_block = 3
flags_1 = CAN_BE_DIRTY_1 | CHECK_RICOCHET_1
sheet_type = /obj/item/stack/sheet/mineral/titanium
- smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
- canSmoothWith = list(/turf/closed/wall/mineral/titanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/shuttle, /obj/structure/shuttle/engine/heater, /obj/structure/falsewall/titanium)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_TITANIUM_WALLS
+
+/turf/closed/wall/mineral/titanium/rust_heretic_act()
+ return // titanium does not rust
/turf/closed/wall/mineral/titanium/nodiagonal
- smooth = SMOOTH_MORE
icon_state = "map-shuttle_nd"
+ base_icon_state = "shuttle_wall"
+ smoothing_flags = SMOOTH_BITMASK
/turf/closed/wall/mineral/titanium/nosmooth
icon = 'icons/turf/shuttle.dmi'
icon_state = "wall"
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
/turf/closed/wall/mineral/titanium/overspace
icon_state = "map-overspace"
- fixed_underlay = list("space"=1)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ fixed_underlay = list("space" = TRUE)
//sub-type to be used for interior shuttle walls
//won't get an underlay of the destination turf on shuttle move
/turf/closed/wall/mineral/titanium/interior/copyTurf(turf/T)
- if(T.type != type)
- T.ChangeTurf(type)
- if(underlays.len)
- T.underlays = underlays
- if(T.icon_state != icon_state)
- T.icon_state = icon_state
- if(T.icon != icon)
- T.icon = icon
- if(color)
- T.atom_colours = atom_colours.Copy()
- T.update_atom_colour()
- if(T.dir != dir)
- T.setDir(dir)
+ . = ..()
T.transform = transform
- return T
/turf/closed/wall/mineral/titanium/copyTurf(turf/T)
. = ..()
@@ -248,15 +273,17 @@
name = "pod wall"
desc = "An easily-compressable wall used for temporary shelter."
icon = 'icons/turf/walls/survival_pod_walls.dmi'
- icon_state = "smooth"
- smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
- canSmoothWith = list(/turf/closed/wall/mineral/titanium/survival, /obj/machinery/door/airlock, /obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/shuttle, /obj/structure/shuttle/engine)
+ icon_state = "survival_pod_walls-0"
+ base_icon_state = "survival_pod_walls"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_WINDOW_FULLTILE + SMOOTH_GROUP_TITANIUM_WALLS
/turf/closed/wall/mineral/titanium/survival/nodiagonal
- smooth = SMOOTH_MORE
+ smoothing_flags = SMOOTH_BITMASK
/turf/closed/wall/mineral/titanium/survival/pod
- canSmoothWith = list(/turf/closed/wall/mineral/titanium/survival, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/shuttle/survival_pod)
+ smoothing_groups = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD + SMOOTH_GROUP_TITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD
/////////////////////Plastitanium walls/////////////////////
@@ -264,49 +291,38 @@
name = "wall"
desc = "A durable wall made of an alloy of plasma and titanium."
icon = 'icons/turf/walls/plastitanium_wall.dmi'
- icon_state = "map-shuttle"
+ icon_state = "plastitanium_wall-0"
+ base_icon_state = "plastitanium_wall"
explosion_block = 4
sheet_type = /obj/item/stack/sheet/mineral/plastitanium
- smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
- canSmoothWith = list(/turf/closed/wall/mineral/plastitanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/plastitanium, /obj/structure/shuttle/engine, /obj/structure/falsewall/plastitanium)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_SYNDICATE_WALLS
rad_insulation = RAD_FULL_INSULATION
+/turf/closed/wall/mineral/plastitanium/rust_heretic_act()
+ return // plastitanium does not rust
+
/turf/closed/wall/mineral/plastitanium/nodiagonal
- smooth = SMOOTH_MORE
icon_state = "map-shuttle_nd"
+ base_icon_state = "plastitanium_wall"
+ smoothing_flags = SMOOTH_BITMASK
/turf/closed/wall/mineral/plastitanium/nosmooth
icon = 'icons/turf/shuttle.dmi'
icon_state = "wall"
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
/turf/closed/wall/mineral/plastitanium/overspace
icon_state = "map-overspace"
- fixed_underlay = list("space"=1)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ fixed_underlay = list("space" = TRUE)
/turf/closed/wall/mineral/plastitanium/explosive/ex_act(severity)
var/obj/item/bombcore/large/bombcore = new(get_turf(src))
bombcore.detonate()
..()
-//have to copypaste this code
-/turf/closed/wall/mineral/plastitanium/interior/copyTurf(turf/T)
- if(T.type != type)
- T.ChangeTurf(type)
- if(underlays.len)
- T.underlays = underlays
- if(T.icon_state != icon_state)
- T.icon_state = icon_state
- if(T.icon != icon)
- T.icon = icon
- if(color)
- T.atom_colours = atom_colours.Copy()
- T.update_atom_colour()
- if(T.dir != dir)
- T.setDir(dir)
- T.transform = transform
- return T
-
/turf/closed/wall/mineral/plastitanium/copyTurf(turf/T)
. = ..()
T.transform = transform
diff --git a/code/game/turfs/simulated/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm
similarity index 85%
rename from code/game/turfs/simulated/wall/misc_walls.dm
rename to code/game/turfs/closed/wall/misc_walls.dm
index 3f6374a3668b..f5c71a3f3406 100644
--- a/code/game/turfs/simulated/wall/misc_walls.dm
+++ b/code/game/turfs/closed/wall/misc_walls.dm
@@ -2,9 +2,10 @@
name = "runed metal wall"
desc = "A cold metal wall engraved with indecipherable symbols. Studying them causes your head to pound."
icon = 'icons/turf/walls/cult_wall.dmi'
- icon_state = "cult"
+ icon_state = "cult_wall-0"
+ base_icon_state = "cult_wall"
+ smoothing_flags = SMOOTH_BITMASK
canSmoothWith = null
- smooth = SMOOTH_MORE
sheet_type = /obj/item/stack/sheet/runed_metal
sheet_amount = 1
girder_type = /obj/structure/girder/cult
@@ -49,6 +50,9 @@
/turf/closed/wall/clockwork
name = "clockwork wall"
desc = "A huge chunk of warm metal. The clanging of machinery emanates from within."
+ icon = 'icons/turf/walls/clockwork_wall.dmi'
+ icon_state = "clockwork_wall-0"
+ base_icon_state = "clockwork_wall"
explosion_block = 3
hardness = 5
slicing_duration = 150
@@ -56,6 +60,8 @@
sheet_amount = 1
girder_type = /obj/structure/destructible/clockwork/wall_gear
baseturfs = /turf/open/floor/clockwork/reebe
+ smoothing_flags = SMOOTH_BITMASK
+
var/heated
var/obj/effect/clockwork/overlay/wall/realappearance
@@ -77,7 +83,7 @@
return ..()
-/turf/closed/wall/clockwork/ReplaceWithLattice()
+/turf/closed/wall/clockwork/attempt_lattice_replacement()
..()
for(var/obj/structure/lattice/L in src)
L.ratvar_act()
@@ -175,7 +181,10 @@
name = "clockwork wall"
desc = "A huge chunk of bronze, decorated like gears and cogs."
icon = 'icons/turf/walls/clockwork_wall.dmi'
- icon_state = "clockwork_wall"
+ icon_state = "clockwork_wall-0"
+ base_icon_state = "clockwork_wall"
+ turf_flags = IS_SOLID
+ smoothing_flags = SMOOTH_BITMASK
sheet_type = /obj/item/stack/tile/bronze
sheet_amount = 2
girder_type = /obj/structure/girder/bronze
@@ -187,3 +196,21 @@
var/obj/item/bombcore/large/bombcore = new(get_turf(src))
bombcore.detonate()
..()
+
+/turf/closed/wall/rock
+ name = "reinforced rock"
+ desc = "It has metal struts that need to be welded away before it can be mined."
+ icon = 'icons/turf/walls/reinforced_rock.dmi'
+ icon_state = "porous_rock-0"
+ base_icon_state = "porous_rock"
+ turf_flags = NO_RUST
+ flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
+ sheet_amount = 1
+ hardness = 50
+ girder_type = null
+ decon_type = /turf/closed/mineral/asteroid
+
+/turf/closed/wall/rock/porous
+ name = "reinforced porous rock"
+ desc = "This rock is filled with pockets of breathable air. It has metal struts to protect it from mining."
+ decon_type = /turf/closed/mineral/asteroid/porous
diff --git a/code/game/turfs/simulated/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm
similarity index 78%
rename from code/game/turfs/simulated/wall/reinf_walls.dm
rename to code/game/turfs/closed/wall/reinf_walls.dm
index 94e94e57333f..e7e417ba8d39 100644
--- a/code/game/turfs/simulated/wall/reinf_walls.dm
+++ b/code/game/turfs/closed/wall/reinf_walls.dm
@@ -2,17 +2,22 @@
name = "reinforced wall"
desc = "A huge chunk of reinforced metal used to separate rooms."
icon = 'icons/turf/walls/reinforced_wall.dmi'
- icon_state = "r_wall"
+ icon_state = "reinforced_wall-0"
+ base_icon_state = "reinforced_wall"
opacity = TRUE
density = TRUE
-
- var/d_state = INTACT
+ turf_flags = IS_SOLID
+ smoothing_flags = SMOOTH_BITMASK
hardness = 10
sheet_type = /obj/item/stack/sheet/plasteel
sheet_amount = 1
girder_type = /obj/structure/girder/reinforced
explosion_block = 2
rad_insulation = RAD_FULL_INSULATION
+ ///Dismantled state, related to deconstruction.
+ var/d_state = INTACT
+ ///Base icon state to use for deconstruction
+ var/base_decon_state = "r_wall"
/turf/closed/wall/r_wall/deconstruction_hints(mob/user)
switch(d_state)
@@ -55,27 +60,27 @@
if(W.tool_behaviour == TOOL_WIRECUTTER)
W.play_tool_sound(src, 100)
d_state = SUPPORT_LINES
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You cut the outer grille."))
- return 1
+ return TRUE
if(SUPPORT_LINES)
if(W.tool_behaviour == TOOL_SCREWDRIVER)
to_chat(user, span_notice("You begin unsecuring the support lines..."))
if(W.use_tool(src, user, 40, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SUPPORT_LINES)
- return 1
+ return TRUE
d_state = COVER
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You unsecure the support lines."))
- return 1
+ return TRUE
else if(W.tool_behaviour == TOOL_WIRECUTTER)
W.play_tool_sound(src, 100)
d_state = INTACT
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You repair the outer grille."))
- return 1
+ return TRUE
if(COVER)
if(W.tool_behaviour == TOOL_WELDER)
@@ -84,32 +89,32 @@
to_chat(user, span_notice("You begin slicing through the metal cover..."))
if(W.use_tool(src, user, 60, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != COVER)
- return 1
+ return TRUE
d_state = CUT_COVER
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You press firmly on the cover, dislodging it."))
- return 1
+ return TRUE
if(W.tool_behaviour == TOOL_SCREWDRIVER)
to_chat(user, span_notice("You begin securing the support lines..."))
if(W.use_tool(src, user, 40, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != COVER)
- return 1
+ return TRUE
d_state = SUPPORT_LINES
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("The support lines have been secured."))
- return 1
+ return TRUE
if(CUT_COVER)
if(W.tool_behaviour == TOOL_CROWBAR)
to_chat(user, span_notice("You struggle to pry off the cover..."))
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != CUT_COVER)
- return 1
+ return TRUE
d_state = ANCHOR_BOLTS
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You pry off the cover."))
- return 1
+ return TRUE
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
@@ -119,30 +124,30 @@
if(!istype(src, /turf/closed/wall/r_wall) || d_state != CUT_COVER)
return TRUE
d_state = COVER
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("The metal cover has been welded securely to the frame."))
- return 1
+ return TRUE
if(ANCHOR_BOLTS)
if(W.tool_behaviour == TOOL_WRENCH)
to_chat(user, span_notice("You start loosening the anchoring bolts which secure the support rods to their frame..."))
if(W.use_tool(src, user, 40, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != ANCHOR_BOLTS)
- return 1
+ return TRUE
d_state = SUPPORT_RODS
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You remove the bolts anchoring the support rods."))
- return 1
+ return TRUE
if(W.tool_behaviour == TOOL_CROWBAR)
to_chat(user, span_notice("You start to pry the cover back into place..."))
if(W.use_tool(src, user, 20, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != ANCHOR_BOLTS)
- return 1
+ return TRUE
d_state = CUT_COVER
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("The metal cover has been pried back into place."))
- return 1
+ return TRUE
if(SUPPORT_RODS)
if(W.tool_behaviour == TOOL_WELDER)
@@ -151,32 +156,32 @@
to_chat(user, span_notice("You begin slicing through the support rods..."))
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SUPPORT_RODS)
- return 1
+ return TRUE
d_state = SHEATH
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You slice through the support rods."))
- return 1
+ return TRUE
if(W.tool_behaviour == TOOL_WRENCH)
to_chat(user, span_notice("You start tightening the bolts which secure the support rods to their frame..."))
W.play_tool_sound(src, 100)
if(W.use_tool(src, user, 40))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SUPPORT_RODS)
- return 1
+ return TRUE
d_state = ANCHOR_BOLTS
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You tighten the bolts anchoring the support rods."))
- return 1
+ return TRUE
if(SHEATH)
if(W.tool_behaviour == TOOL_CROWBAR)
to_chat(user, span_notice("You struggle to pry off the outer sheath..."))
if(W.use_tool(src, user, 100, volume=100))
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SHEATH)
- return 1
+ return TRUE
to_chat(user, span_notice("You pry off the outer sheath."))
dismantle_wall()
- return 1
+ return TRUE
if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
@@ -186,22 +191,31 @@
if(!istype(src, /turf/closed/wall/r_wall) || d_state != SHEATH)
return TRUE
d_state = SUPPORT_RODS
- update_appearance(UPDATE_ICON)
+ update_appearance()
to_chat(user, span_notice("You weld the support rods back together."))
- return 1
- return 0
+ return TRUE
+ return FALSE
/turf/closed/wall/r_wall/update_icon(updates=ALL)
. = ..()
if(d_state != INTACT)
- smooth = SMOOTH_FALSE
- clear_smooth_overlays()
- icon_state = "r_wall-[d_state]"
+ smoothing_flags = NONE
+ return
+ if (!(updates & UPDATE_SMOOTHING))
+ return
+ smoothing_flags = SMOOTH_BITMASK
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ QUEUE_SMOOTH(src)
+
+// We don't react to smoothing changing here because this else exists only to "revert" intact changes
+/turf/closed/wall/r_wall/update_icon_state()
+ if(d_state != INTACT)
+ icon = 'icons/turf/walls/reinforced_states.dmi'
+ icon_state = "[base_decon_state]-[d_state]"
else
- smooth = SMOOTH_TRUE
- queue_smooth_neighbors(src)
- queue_smooth(src)
- icon_state = "r_wall"
+ icon = 'icons/turf/walls/reinforced_wall.dmi'
+ icon_state = "[base_icon_state]-[smoothing_junction]"
+ return ..()
/turf/closed/wall/r_wall/singularity_pull(S, current_size)
if(current_size >= STAGE_FIVE)
@@ -228,24 +242,30 @@
name = "hull"
desc = "The armored hull of an ominous looking ship."
icon = 'icons/turf/walls/plastitanium_wall.dmi'
- icon_state = "map-shuttle"
+ icon_state = "plastitanium_wall-0"
+ base_icon_state = "plastitanium_wall"
explosion_block = 20
sheet_type = /obj/item/stack/sheet/mineral/plastitanium
- smooth = SMOOTH_MORE|SMOOTH_DIAGONAL
- canSmoothWith = list(/turf/closed/wall/r_wall/syndicate, /turf/closed/wall/mineral/plastitanium, /obj/machinery/door/airlock/shuttle, /obj/machinery/door/airlock, /obj/structure/window/plastitanium, /obj/structure/shuttle/engine, /obj/structure/falsewall/plastitanium)
+ turf_flags = IS_SOLID
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_SYNDICATE_WALLS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_SYNDICATE_WALLS
/turf/closed/wall/r_wall/syndicate/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
return FALSE
/turf/closed/wall/r_wall/syndicate/nodiagonal
- smooth = SMOOTH_MORE
+ icon = 'icons/turf/walls/plastitanium_wall.dmi'
icon_state = "map-shuttle_nd"
+ base_icon_state = "plastitanium_wall"
+ smoothing_flags = SMOOTH_BITMASK
/turf/closed/wall/r_wall/syndicate/nosmooth
icon = 'icons/turf/shuttle.dmi'
icon_state = "wall"
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
/turf/closed/wall/r_wall/syndicate/overspace
icon_state = "map-overspace"
- fixed_underlay = list("space"=1)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ fixed_underlay = list("space" = TRUE)
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/closed/walls.dm
similarity index 86%
rename from code/game/turfs/simulated/walls.dm
rename to code/game/turfs/closed/walls.dm
index aff1b3ae03f4..2127dc3bac60 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -4,7 +4,8 @@
name = "wall"
desc = "A huge chunk of metal used to separate rooms."
icon = 'icons/turf/walls/wall.dmi'
- icon_state = "wall"
+ icon_state = "wall-0"
+ base_icon_state = "wall"
explosion_block = 1
thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
@@ -16,30 +17,38 @@
pipe_astar_cost = 50 /* nich really doesn't like pipes that go through walls */\
)
- var/hardness = 30 //lower numbers are harder. Used to determine the probability of a hulk smashing through.
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WALLS
+
+ ///bool on whether this wall can be chiselled into
+ var/can_engrave = TRUE
+ ///lower numbers are harder. Used to determine the probability of a hulk smashing through.
+ var/hardness = 30
var/slicing_duration = 200 //default time taken to slice the wall
var/sheet_type = /obj/item/stack/sheet/metal
var/sheet_amount = 2
var/girder_type = /obj/structure/girder
-
- canSmoothWith = list(
- /turf/closed/wall,
- /turf/closed/wall/r_wall,
- /obj/structure/falsewall,
- /obj/structure/falsewall/brass,
- /obj/structure/falsewall/reinforced,
- /turf/closed/wall/explosive,
- /turf/closed/wall/rust,
- /turf/closed/wall/r_wall/rust,
- /turf/closed/wall/clockwork)
- smooth = SMOOTH_TRUE
+ /// A turf that will replace this turf when this turf is destroyed
+ var/decon_type
var/list/dent_decals
/turf/closed/wall/Initialize(mapload)
. = ..()
+ if(!can_engrave)
+ ADD_TRAIT(src, TRAIT_NOT_ENGRAVABLE, INNATE_TRAIT)
if(is_station_level(z))
GLOB.station_turfs += src
+ if(smoothing_flags & SMOOTH_DIAGONAL_CORNERS && fixed_underlay) //Set underlays for the diagonal walls.
+ var/mutable_appearance/underlay_appearance = mutable_appearance(layer = TURF_LAYER, offset_spokesman = src, plane = FLOOR_PLANE)
+ if(fixed_underlay["space"])
+ generate_space_underlay(underlay_appearance, src)
+ else
+ underlay_appearance.icon = fixed_underlay["icon"]
+ underlay_appearance.icon_state = fixed_underlay["icon_state"]
+ fixed_underlay = string_assoc_list(fixed_underlay)
+ underlays += underlay_appearance
/turf/closed/wall/Destroy()
if(is_station_level(z))
@@ -67,7 +76,7 @@
P.setAngle(new_angle_s)
return TRUE
-/turf/closed/wall/proc/dismantle_wall(devastated=0, explode=0)
+/turf/closed/wall/proc/dismantle_wall(devastated = FALSE, explode = FALSE)
if(devastated)
devastate_wall()
else
@@ -80,8 +89,11 @@
if(istype(O, /obj/structure/sign/poster))
var/obj/structure/sign/poster/P = O
P.roll_and_drop(src)
-
- ScrapeAway()
+ if(decon_type)
+ ChangeTurf(decon_type, flags = CHANGETURF_INHERIT_AIR)
+ else
+ ScrapeAway()
+ QUEUE_SMOOTH_NEIGHBORS(src)
/turf/closed/wall/proc/break_wall()
new sheet_type(src, sheet_amount)
@@ -112,6 +124,8 @@
dismantle_wall(0,1)
if(!density)
..()
+
+ return TRUE
/turf/closed/wall/blob_act(obj/structure/blob/B)
@@ -182,7 +196,7 @@
return
//get the user's location
- if(!isturf(user.loc))
+ if(!isturf(user.loc) && !ismecha(user.loc))
return //can't do this stuff whilst inside objects and such
add_fingerprint(user)
@@ -283,7 +297,7 @@
dismantle_wall(1)
/turf/closed/wall/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_DECONSTRUCT)
return list("mode" = RCD_DECONSTRUCT, "delay" = 40, "cost" = 26)
return FALSE
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open/_open.dm
similarity index 71%
rename from code/game/turfs/open.dm
rename to code/game/turfs/open/_open.dm
index 82203455e9de..6994d4df97ad 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -15,18 +15,66 @@
var/barefootstep = null
var/clawfootstep = null
var/heavyfootstep = null
- /// How much fuel this open turf provides to turf fires, and how easily they can be ignited in the first place. Can be negative to make fires die out faster.
- var/flammability = 0.3
- var/obj/effect/abstract/turf_fire/turf_fire
- var/obj/effect/hotspot/hotspot
+
+ /// Determines the type of damage overlay that will be used for the tile
+ var/damaged_dmi = null
+ var/broken = FALSE
+ var/burnt = FALSE
+
+/// Returns a list of every turf state considered "broken".
+/// Will be randomly chosen if a turf breaks at runtime.
+/turf/open/proc/broken_states()
+ return list()
+
+/// Returns a list of every turf state considered "burnt".
+/// Will be randomly chosen if a turf is burnt at runtime.
+/turf/open/proc/burnt_states()
+ return list()
+
+/turf/open/break_tile()
+ if(isnull(damaged_dmi) || broken)
+ return FALSE
+ broken = TRUE
+ update_appearance()
+ return TRUE
-//direction is direction of travel of A
-/turf/open/zPassIn(atom/movable/A, direction, turf/source)
- return (direction == DOWN)
+/turf/open/burn_tile()
+ if(isnull(damaged_dmi) || burnt)
+ return FALSE
+ burnt = TRUE
+ update_appearance()
+ return TRUE
+
+/turf/open/update_overlays()
+ if(isnull(damaged_dmi))
+ return ..()
+ . = ..()
+ if(broken)
+ . += mutable_appearance(damaged_dmi, pick(broken_states()))
+ else if(burnt)
+ var/list/burnt_states = burnt_states()
+ if(burnt_states.len)
+ . += mutable_appearance(damaged_dmi, pick(burnt_states))
+ else
+ . += mutable_appearance(damaged_dmi, pick(broken_states()))
//direction is direction of travel of A
-/turf/open/zPassOut(atom/movable/A, direction, turf/destination)
- return (direction == UP)
+/turf/open/zPassIn(direction)
+ if(direction != DOWN)
+ return FALSE
+ for(var/obj/on_us in contents)
+ if(on_us.obj_flags & BLOCK_Z_IN_DOWN)
+ return FALSE
+ return TRUE
+
+//direction is direction of travel of an atom
+/turf/open/zPassOut(direction)
+ if(direction != UP)
+ return FALSE
+ for(var/obj/on_us in contents)
+ if(on_us.obj_flags & BLOCK_Z_OUT_UP)
+ return FALSE
+ return TRUE
//direction is direction of travel of air
/turf/open/zAirIn(direction, turf/source)
@@ -36,6 +84,23 @@
/turf/open/zAirOut(direction, turf/source)
return (direction == UP)
+/turf/open/update_icon()
+ . = ..()
+ update_visuals()
+
+/**
+ * Replace an open turf with another open turf while avoiding the pitfall of replacing plating with a floor tile, leaving a hole underneath.
+ * This replaces the current turf if it is plating and is passed plating, is tile and is passed tile.
+ * It places the new turf on top of itself if it is plating and is passed a tile.
+ * It also replaces the turf if it is tile and is passed plating, essentially destroying the over turf.
+ * Flags argument is passed directly to ChangeTurf or PlaceOnTop
+ */
+/turf/open/proc/replace_floor(turf/open/new_floor_path, flags)
+ if (!overfloor_placed && initial(new_floor_path.overfloor_placed))
+ place_on_top(new_floor_path, flags = flags)
+ return
+ ChangeTurf(new_floor_path, flags = flags)
+
/turf/open/indestructible
name = "floor"
icon = 'icons/turf/floors.dmi'
@@ -59,6 +124,8 @@
/turf/open/indestructible/plating
name = "plating"
icon_state = "plating"
+ overfloor_placed = FALSE
+ underfloor_accessibility = UNDERFLOOR_INTERACTABLE
footstep = FOOTSTEP_PLATING
barefootstep = FOOTSTEP_HARD_BAREFOOT
clawfootstep = FOOTSTEP_HARD_CLAW
@@ -102,9 +169,7 @@
name = "carpet"
desc = "Soft velvet carpeting. Feels good between your toes."
icon = 'icons/turf/floors/carpet.dmi'
- icon_state = "carpet"
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/turf/open/indestructible/carpet)
+ icon_state = "carpet-255"
flags_1 = NONE
bullet_bounce_sound = null
footstep = FOOTSTEP_CARPET
@@ -112,61 +177,75 @@
clawfootstep = FOOTSTEP_CARPET_BAREFOOT
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
tiled_dirt = FALSE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET
+ canSmoothWith = SMOOTH_GROUP_CARPET
/turf/open/indestructible/carpet/black
icon = 'icons/turf/floors/carpet_black.dmi'
- icon_state = "carpet"
+ icon_state = "carpet_black-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_BLACK
+ canSmoothWith = SMOOTH_GROUP_CARPET_BLACK
/turf/open/indestructible/carpet/blue
- icon = 'goon/icons/turfs/carpet_blue.dmi'
- icon_state = "carpet"
-
-/turf/open/indestructible/carpet/green
- icon = 'goon/icons/turfs/carpet_green.dmi'
- icon_state = "carpet"
+ icon = 'icons/turf/floors/carpet_blue.dmi'
+ icon_state = "carpet_blue-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_BLUE
+ canSmoothWith = SMOOTH_GROUP_CARPET_BLUE
/turf/open/indestructible/carpet/cyan
icon = 'icons/turf/floors/carpet_cyan.dmi'
- icon_state = "carpet"
+ icon_state = "carpet_cyan-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_CYAN
+ canSmoothWith = SMOOTH_GROUP_CARPET_CYAN
+
+/turf/open/indestructible/carpet/green
+ icon = 'icons/turf/floors/carpet_green.dmi'
+ icon_state = "carpet_green-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_GREEN
+ canSmoothWith = SMOOTH_GROUP_CARPET_GREEN
/turf/open/indestructible/carpet/orange
icon = 'icons/turf/floors/carpet_orange.dmi'
- icon_state = "carpet"
+ icon_state = "carpet_orange-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ORANGE
+ canSmoothWith = SMOOTH_GROUP_CARPET_ORANGE
/turf/open/indestructible/carpet/purple
- icon = 'goon/icons/turfs/carpet_purple.dmi'
- icon_state = "carpet"
+ icon = 'icons/turf/floors/carpet_purple.dmi'
+ icon_state = "carpet_purple-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_PURPLE
+ canSmoothWith = SMOOTH_GROUP_CARPET_PURPLE
/turf/open/indestructible/carpet/red
icon = 'icons/turf/floors/carpet_red.dmi'
- icon_state = "carpet"
+ icon_state = "carpet_red-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_RED
+ canSmoothWith = SMOOTH_GROUP_CARPET_RED
-/turf/open/indestructible/carpet/royal
- name = "carpet"
- desc = "Soft velvet carpeting. Feels good between your toes."
- icon = 'icons/turf/floors/carpet_royalblue.dmi'
- icon_state = "carpet"
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/turf/open/indestructible/carpet/royal)
- flags_1 = NONE
- bullet_bounce_sound = null
- footstep = FOOTSTEP_CARPET
- barefootstep = FOOTSTEP_CARPET_BAREFOOT
- clawfootstep = FOOTSTEP_CARPET_BAREFOOT
- heavyfootstep = FOOTSTEP_GENERIC_HEAVY
- tiled_dirt = FALSE
-
-/turf/open/indestructible/carpet/royal/black
+/turf/open/indestructible/carpet/royalblack
icon = 'icons/turf/floors/carpet_royalblack.dmi'
- icon_state = "carpet"
-
-/turf/open/indestructible/carpet/royal/green
- icon = 'icons/turf/floors/carpet_exoticgreen.dmi'
- icon_state = "carpet"
+ icon_state = "carpet_royalblack-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ROYAL_BLACK
+ canSmoothWith = SMOOTH_GROUP_CARPET_ROYAL_BLACK
-/turf/open/indestructible/carpet/royal/purple
- icon = 'icons/turf/floors/carpet_exoticpurple.dmi'
- icon_state = "carpet"
+/turf/open/indestructible/carpet/royalblue
+ icon = 'icons/turf/floors/carpet_royalblue.dmi'
+ icon_state = "carpet_royalblue-255"
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ROYAL_BLUE
+ canSmoothWith = SMOOTH_GROUP_CARPET_ROYAL_BLUE
+
+// /turf/open/indestructible/carpet/royal/green
+// icon = 'icons/turf/floors/carpet_green.dmi'
+// icon_state = "carpet"
+// smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ROYAL_GREEN
+// canSmoothWith = SMOOTH_GROUP_CARPET_ROYAL_GREEN
+
+// /turf/open/indestructible/carpet/royal/purple
+// icon = 'icons/turf/floors/carpet_purple.dmi'
+// icon_state = "carpet"
+// smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ROYAL_PURPLE
+// canSmoothWith = SMOOTH_GROUP_CARPET_ROYAL_PURPLE
/turf/open/indestructible/grass
name = "grass patch"
@@ -337,7 +416,7 @@
icon = 'icons/turf/floors/hierophant_floor.dmi'
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
baseturfs = /turf/open/indestructible/hierophant
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_CORNERS
tiled_dirt = FALSE
/turf/open/indestructible/hierophant/two
@@ -357,7 +436,7 @@
/turf/open/indestructible/binary
name = "tear in the fabric of reality"
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
baseturfs = /turf/open/indestructible/binary
icon_state = "binary"
footstep = null
@@ -453,10 +532,11 @@
icon_state = "necro[rand(2,3)]"
/turf/open/indestructible/brazil/lostit
- smooth = SMOOTH_TRUE | SMOOTH_BORDER | SMOOTH_MORE
- canSmoothWith = list(/turf/open/indestructible/brazil/lostit)
icon = 'yogstation/icons/turf/floors/ballpit_smooth.dmi'
icon_state = "smooth"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_BRAZIL
+ canSmoothWith = SMOOTH_GROUP_BRAZIL
/turf/open/indestructible/wiki
light_range = 2
@@ -492,7 +572,7 @@
air = new(2500,src)
air.copy_from_turf(src)
update_air_ref(planetary_atmos ? 1 : 2)
- ImmediateCalculateAdjacentTurfs()
+ immediate_calculate_adjacent_turfs()
/turf/open/proc/GetHeatCapacity()
. = air.heat_capacity()
@@ -609,3 +689,54 @@
flammability = initial(flammability)
return
flammability = new_flammability
+
+/// Builds with rods. This doesn't exist to be overriden, just to remove duplicate logic for turfs that want
+/// To support floor tile creation
+/// I'd make it a component, but one of these things is space. So no.
+/turf/open/proc/build_with_rods(obj/item/stack/rods/used_rods, mob/user)
+ var/obj/structure/lattice/catwalk_bait = locate(/obj/structure/lattice, src)
+ var/obj/structure/lattice/catwalk/existing_catwalk = locate(/obj/structure/lattice/catwalk, src)
+ if(existing_catwalk)
+ to_chat(user, span_warning("There is already a catwalk here!"))
+ return
+
+ if(catwalk_bait)
+ if(used_rods.use(1))
+ qdel(catwalk_bait)
+ to_chat(user, span_notice("You construct a catwalk."))
+ playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
+ new /obj/structure/lattice/catwalk(src)
+ else
+ to_chat(user, span_warning("You need two rods to build a catwalk!"))
+ return
+
+ if(used_rods.use(1))
+ to_chat(user, span_notice("You construct a lattice."))
+ playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
+ new /obj/structure/lattice(src)
+ else
+ to_chat(user, span_warning("You need one rod to build a lattice."))
+
+/// Very similar to build_with_rods, this exists to allow consistent behavior between different types in terms of how
+/// Building floors works
+/turf/open/proc/build_with_floor_tiles(obj/item/stack/tile/plasteel/used_tiles, user)
+ var/obj/structure/lattice/lattice = locate(/obj/structure/lattice, src)
+ if(!has_valid_support() && !lattice)
+ balloon_alert(user, "needs support, place rods!")
+ return
+ if(!used_tiles.use(1))
+ balloon_alert(user, "need a floor tile to build!")
+ return
+
+ playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
+ var/turf/open/floor/plating/new_plating = place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ if(lattice)
+ qdel(lattice)
+ else
+ new_plating.lattice_underneath = FALSE
+
+/turf/open/proc/has_valid_support()
+ for (var/direction in GLOB.cardinals)
+ if(istype(get_step(src, direction), /turf/open/floor))
+ return TRUE
+ return FALSE
diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/open/chasm.dm
similarity index 80%
rename from code/game/turfs/simulated/chasm.dm
rename to code/game/turfs/open/chasm.dm
index 482a7a9a7a7e..7871b313ea9e 100644
--- a/code/game/turfs/simulated/chasm.dm
+++ b/code/game/turfs/open/chasm.dm
@@ -3,16 +3,23 @@
name = "chasm"
desc = "Watch your step."
baseturfs = /turf/open/chasm
- smooth = SMOOTH_TRUE | SMOOTH_BORDER | SMOOTH_MORE
icon = 'icons/turf/floors/chasms.dmi'
- icon_state = "smooth"
- canSmoothWith = list(/turf/open/floor/fakepit, /turf/open/chasm)
+ icon_state = "chasms-255"
+ base_icon_state = "chasms"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_TURF_CHASM
+ canSmoothWith = SMOOTH_GROUP_TURF_CHASM
density = TRUE //This will prevent hostile mobs from pathing into chasms, while the canpass override will still let it function like an open turf
bullet_bounce_sound = null //abandon all hope ye who enter
/turf/open/chasm/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/chasm, SSmapping.get_turf_below(src))
+ apply_components(mapload)
+
+/// Lets people walk into chasms.
+/turf/open/chasm/CanAllowThrough(atom/movable/mover, border_dir)
+ . = ..()
+ return TRUE
/turf/open/chasm/proc/set_target(turf/target)
var/datum/component/chasm/chasm_component = GetComponent(/datum/component/chasm)
@@ -29,7 +36,7 @@
return
/turf/open/chasm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_FLOORWALL)
return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3)
return FALSE
@@ -38,7 +45,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, span_notice("You build a floor."))
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
@@ -57,7 +64,10 @@
to_chat(user, span_notice("You construct a lattice."))
playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
// Create a lattice, without reverting to our baseturf
- new /obj/structure/lattice(src)
+ if(istype(R, /obj/item/stack/rods/lava)) //dripstation edit start
+ new /obj/structure/lattice/lava(src)
+ else
+ new /obj/structure/lattice(src) //dripstation edit end
else
to_chat(user, span_warning("You need one rod to build a lattice."))
return
@@ -70,7 +80,7 @@
playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
to_chat(user, span_notice("You build a floor."))
// Create a floor, which has this chasm underneath it
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
to_chat(user, span_warning("You need one floor tile to build a floor!"))
else
@@ -119,10 +129,14 @@
baseturfs = /turf/open/chasm/magic
light_range = 1.9
light_power = 0.65
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
/turf/open/chasm/magic/Initialize(mapload)
. = ..()
- var/turf/T = pick(get_area_turfs(/area/fabric_of_reality))
+ var/turf/T = pick(get_area_turfs(/area/centcom/fabric_of_reality))
if(T)
set_target(T)
+
+/// Handles adding the chasm component to the turf (So stuff falls into it!)
+/turf/open/chasm/proc/apply_components(mapload)
+ AddComponent(/datum/component/chasm, null, mapload)
diff --git a/code/game/turfs/simulated/dirtystation.dm b/code/game/turfs/open/dirtystation.dm
similarity index 72%
rename from code/game/turfs/simulated/dirtystation.dm
rename to code/game/turfs/open/dirtystation.dm
index 831656da730c..170cfe3a0240 100644
--- a/code/game/turfs/simulated/dirtystation.dm
+++ b/code/game/turfs/open/dirtystation.dm
@@ -24,11 +24,13 @@
//The code below here isn't exactly optimal, but because of the individual decals that each area uses it's still applicable.
//high dirt - 1/3 chance.
- var/static/list/high_dirt_areas = typecacheof(list(/area/science/test_area,
- /area/mine/production,
- /area/mine/living_quarters,
- /area/vacant_room/office,
- /area/ruin/space))
+ var/static/list/high_dirt_areas = typecacheof(list(
+ /area/science/test_area,
+ /area/mine/production,
+ /area/mine/living_quarters,
+ /area/vacant_room/office,
+ /area/ruin/space,
+ ))
if(is_type_in_typecache(A, high_dirt_areas))
new /obj/effect/decal/cleanable/dirt(src) //vanilla, but it works
return
@@ -38,13 +40,15 @@
return
//Construction zones. Blood, sweat, and oil. Oh, and dirt.
- var/static/list/engine_dirt_areas = typecacheof(list(/area/engine,
- /area/crew_quarters/heads/chief,
- /area/science/robotics,
- /area/maintenance,
- /area/construction,
- /area/vacant_room/commissary,
- /area/survivalpod))
+ var/static/list/engine_dirt_areas = typecacheof(list(
+ /area/engine,
+ /area/crew_quarters/heads/chief,
+ /area/science/robotics,
+ /area/maintenance,
+ /area/construction,
+ /area/vacant_room/commissary,
+ /area/survivalpod,
+ ))
if(is_type_in_typecache(A, engine_dirt_areas))
if(prob(3))
new /obj/effect/decal/cleanable/blood/old(src)
@@ -59,8 +63,10 @@
return
//Bathrooms. Blood, vomit, and shavings in the sinks.
- var/static/list/bathroom_dirt_areas = typecacheof(list( /area/crew_quarters/toilet,
- /area/awaymission/research/interior/bathroom))
+ var/static/list/bathroom_dirt_areas = typecacheof(list(
+ /area/crew_quarters/toilet,
+ /area/awaymission/research/interior/bathroom,
+ ))
if(is_type_in_typecache(A, bathroom_dirt_areas))
if(prob(40))
if(prob(90))
@@ -81,9 +87,11 @@
return
//Areas where gibs will be present. Robusting probably happened some time ago.
- var/static/list/gib_covered_areas = typecacheof(list(/area/ai_monitored/turret_protected,
- /area/security,
- /area/crew_quarters/heads/hos))
+ var/static/list/gib_covered_areas = typecacheof(list(
+ /area/ai_monitored/turret_protected,
+ /area/security,
+ /area/crew_quarters/heads/hos,
+ ))
if(is_type_in_typecache(A, gib_covered_areas))
if(prob(20))
if(prob(5))
@@ -93,8 +101,10 @@
return
//Kitchen areas. Broken eggs, flour, spilled milk (no crying allowed.)
- var/static/list/kitchen_dirt_areas = typecacheof(list(/area/crew_quarters/kitchen,
- /area/crew_quarters/cafeteria))
+ var/static/list/kitchen_dirt_areas = typecacheof(list(
+ /area/crew_quarters/kitchen,
+ /area/crew_quarters/cafeteria,
+ ))
if(is_type_in_typecache(A, kitchen_dirt_areas))
if(prob(60))
if(prob(50))
@@ -104,8 +114,10 @@
return
//Medical areas. Mostly clean by space-OSHA standards, but has some blood and oil spread about.
- var/static/list/medical_dirt_areas = typecacheof(list(/area/medical,
- /area/crew_quarters/heads/cmo))
+ var/static/list/medical_dirt_areas = typecacheof(list(
+ /area/medical,
+ /area/crew_quarters/heads/cmo,
+ ))
if(is_type_in_typecache(A, medical_dirt_areas))
if(prob(66))
if(prob(5))
@@ -120,8 +132,10 @@
return
//Science messes. Mostly green glowy stuff -WHICH YOU SHOULD NOT INJEST-.
- var/static/list/science_dirt_areas = typecacheof(list(/area/science,
- /area/crew_quarters/heads/hor))
+ var/static/list/science_dirt_areas = typecacheof(list(
+ /area/science,
+ /area/crew_quarters/heads/hor,
+ ))
if(is_type_in_typecache(A, science_dirt_areas))
if(prob(20))
new /obj/effect/decal/cleanable/greenglow/filled(src) //this cleans itself up but it might startle you when you see it.
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/open/floor.dm
similarity index 67%
rename from code/game/turfs/simulated/floor.dm
rename to code/game/turfs/open/floor.dm
index 676b6d1bc154..67990c43ee7b 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/open/floor.dm
@@ -1,68 +1,75 @@
+/// Anything above a lattice should go here.
/turf/open/floor
- //NOTE: Floor code has been refactored, many procs were removed and refactored
- //- you should use istype() if you want to find out whether a floor has a certain type
- //- floor_tile is now a path, and not a tile obj
name = "floor"
icon = 'icons/turf/floors.dmi'
+ base_icon_state = "floor"
baseturfs = /turf/open/floor/plating
footstep = FOOTSTEP_FLOOR
barefootstep = FOOTSTEP_HARD_BAREFOOT
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ flags_1 = CAN_BE_DIRTY_1
+ turf_flags = IS_SOLID
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_OPEN_FLOOR
+ canSmoothWith = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_OPEN_FLOOR
+
+ overfloor_placed = TRUE
- var/icon_state_regular_floor = "floor" //used to remember what icon state the tile should have by default
- var/icon_regular_floor = 'icons/turf/floors.dmi' //used to remember what icon the tile should have by default
- var/icon_plating = "plating"
thermal_conductivity = 0.040
heat_capacity = 10000
- intact = 1
- var/broken = 0
- var/burnt = 0
- var/floor_tile = null //tile that this floor drops
- var/list/broken_states
- var/list/burnt_states
tiled_dirt = TRUE
+ damaged_dmi = 'icons/turf/damaged.dmi'
+ var/icon_state_regular_floor = "floor" //used to remember what icon state the tile should have by default
+ var/icon_regular_floor = 'icons/turf/floors.dmi' //used to remember what icon the tile should have by default
+ var/icon_plating = "plating"
+
+ var/floor_tile = null //tile that this floor drops
/turf/open/floor/Initialize(mapload)
- if (!broken_states)
- broken_states = typelist("broken_states", list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5"))
- else
- broken_states = typelist("broken_states", broken_states)
- burnt_states = typelist("burnt_states", burnt_states)
- if(!broken && broken_states && (icon_state in broken_states))
- broken = TRUE
- if(!burnt && burnt_states && (icon_state in burnt_states))
- burnt = TRUE
+ // if (!broken_states)
+ // broken_states = typelist("broken_states", list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5"))
+ // else
+ // broken_states = typelist("broken_states", broken_states)
+ // burnt_states = typelist("burnt_states", burnt_states)
+ // if(!broken && broken_states && (icon_state in broken_states))
+ // broken = TRUE
+ // if(!burnt && burnt_states && (icon_state in burnt_states))
+ // burnt = TRUE
. = ..()
//This is so damaged or burnt tiles or platings don't get remembered as the default tile
- var/static/list/icons_to_ignore_at_floor_init = list("foam_plating", "plating","light_on","light_on_flicker1","light_on_flicker2",
- "light_on_clicker3","light_on_clicker4","light_on_clicker5",
- "light_on_broken","light_off","wall_thermite","grass", "sand",
- "asteroid","asteroid_dug",
- "asteroid0","asteroid1","asteroid2","asteroid3","asteroid4",
- "asteroid5","asteroid6","asteroid7","asteroid8","asteroid9","asteroid10","asteroid11","asteroid12",
- "basalt","basalt_dug",
- "basalt0","basalt1","basalt2","basalt3","basalt4",
- "basalt5","basalt6","basalt7","basalt8","basalt9","basalt10","basalt11","basalt12",
- "oldburning","light-on-r","light-on-y","light-on-g","light-on-b", "wood", "carpetsymbol", "carpetstar",
- "carpetcorner", "carpetside", "carpet", "ironsand1", "ironsand2", "ironsand3", "ironsand4", "ironsand5",
- "ironsand6", "ironsand7", "ironsand8", "ironsand9", "ironsand10", "ironsand11",
- "ironsand12", "ironsand13", "ironsand14", "ironsand15", "bamboo", "bamboosymbol", "bamboostar")
- if(broken || burnt || (icon_state in icons_to_ignore_at_floor_init)) //so damaged/burned tiles or plating icons aren't saved as the default
- icon_state_regular_floor = "floor"
- icon_regular_floor = 'icons/turf/floors.dmi'
- else
- icon_state_regular_floor = icon_state
- icon_regular_floor = icon
+ // var/static/list/icons_to_ignore_at_floor_init = list("foam_plating", "plating","light_on","light_on_flicker1","light_on_flicker2",
+ // "light_on_clicker3","light_on_clicker4","light_on_clicker5",
+ // "light_on_broken","light_off","wall_thermite","grass", "sand",
+ // "asteroid","asteroid_dug",
+ // "asteroid0","asteroid1","asteroid2","asteroid3","asteroid4",
+ // "asteroid5","asteroid6","asteroid7","asteroid8","asteroid9","asteroid10","asteroid11","asteroid12",
+ // "basalt","basalt_dug",
+ // "basalt0","basalt1","basalt2","basalt3","basalt4",
+ // "basalt5","basalt6","basalt7","basalt8","basalt9","basalt10","basalt11","basalt12",
+ // "oldburning","light-on-r","light-on-y","light-on-g","light-on-b", "wood", "carpetsymbol", "carpetstar",
+ // "carpetcorner", "carpetside", "carpet", "ironsand1", "ironsand2", "ironsand3", "ironsand4", "ironsand5",
+ // "ironsand6", "ironsand7", "ironsand8", "ironsand9", "ironsand10", "ironsand11",
+ // "ironsand12", "ironsand13", "ironsand14", "ironsand15", "bamboo", "bamboosymbol", "bamboostar")
+ // if(broken || burnt || (icon_state in icons_to_ignore_at_floor_init)) //so damaged/burned tiles or plating icons aren't saved as the default
+ // icon_state_regular_floor = "floor"
+ // icon_regular_floor = 'icons/turf/floors.dmi'
+ // else
+ // icon_state_regular_floor = icon_state
+ // icon_regular_floor = icon
if(mapload && prob(33))
MakeDirty()
if(is_station_level(z))
GLOB.station_turfs += src
+/turf/open/floor/broken_states()
+ return list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5")
+
+/turf/open/floor/burnt_states()
+ return list()
/turf/open/floor/Destroy()
if(is_station_level(z))
@@ -81,14 +88,13 @@
severity = 3
switch(severity)
- if(1)
+ if(EXPLODE_DEVASTATE)
ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
- if(2)
+ if(EXPLODE_HEAVY)
switch(pick(1,2;75,3))
if(1)
- if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor))
- ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
- ReplaceWithLattice()
+ if (!ispath(baseturf_at_depth(2), /turf/open/floor))
+ attempt_lattice_replacement()
else
ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
if(prob(33))
@@ -103,23 +109,18 @@
hotspot_expose(1000,CELL_VOLUME)
if(prob(33))
new /obj/item/stack/sheet/metal(src)
- if(3)
+ if(EXPLODE_LIGHT)
if (prob(50))
src.break_tile()
src.hotspot_expose(1000,CELL_VOLUME)
/turf/open/floor/is_shielded()
for(var/obj/structure/A in contents)
- if(A.level == 3)
- return 1
+ return 1
/turf/open/floor/blob_act(obj/structure/blob/B)
return
-/turf/open/floor/update_icon(updates=ALL)
- . = ..()
- update_visuals()
-
/turf/open/floor/attack_paw(mob/user)
return attack_hand(user)
@@ -135,21 +136,6 @@
return
T.break_tile()
-/turf/open/floor/proc/break_tile()
- if(broken)
- return
- icon_state = pick(broken_states)
- broken = 1
-
-/turf/open/floor/burn_tile()
- if(broken || burnt)
- return
- if(burnt_states.len)
- icon_state = pick(burnt_states)
- else
- icon_state = pick(broken_states)
- burnt = 1
-
/turf/open/floor/proc/make_plating()
return ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
@@ -158,30 +144,31 @@
return ..() //fucking turfs switch the fucking src of the fucking running procs
if(!ispath(path, /turf/open/floor))
return ..()
- var/old_icon = icon_regular_floor
- var/old_icon_state = icon_state_regular_floor
var/old_dir = dir
var/turf/open/floor/W = ..()
- W.icon_regular_floor = old_icon
- W.icon_state_regular_floor = old_icon_state
W.setDir(old_dir)
- W.update_appearance(UPDATE_ICON)
+ W.update_appearance()
return W
-/turf/open/floor/attackby(obj/item/C, mob/user, params)
- if(!C || !user)
- return 1
- if(..())
- return 1
- if(intact && istype(C, /obj/item/stack/tile))
- try_replace_tile(C, user, params)
- return 0
+/turf/open/floor/attackby(obj/item/object, mob/living/user, params)
+ if(!object || !user)
+ return TRUE
+ . = ..()
+ if(.)
+ return .
+ if(overfloor_placed && istype(object, /obj/item/stack/tile))
+ try_replace_tile(object, user, params)
+ return TRUE
+ if(underfloor_accessibility >= UNDERFLOOR_INTERACTABLE && istype(object, /obj/item/stack/tile))
+ try_replace_tile(object, user, params)
+ return FALSE
/turf/open/floor/crowbar_act(mob/living/user, obj/item/I)
- if(istype(I,/obj/item/jawsoflife/jimmy))
- to_chat(user,"The [I] cannot pry tiles.")
+ if(istype(I, /obj/item/jawsoflife/jimmy) || istype(I, /obj/item/mecha_parts/mecha_equipment/hydraulic_clamp))
+ to_chat(user,"[I] cannot pry tiles.")
return
- return intact ? pry_tile(I, user) : FALSE
+ if(overfloor_placed && pry_tile(I, user))
+ return TRUE
/turf/open/floor/proc/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
if(T.turf_type == type)
@@ -229,7 +216,7 @@
new floor_tile(src)
make_plating()
else if(prob(50))
- ReplaceWithLattice()
+ attempt_lattice_replacement()
/turf/open/floor/narsie_act(force, ignore_mobs, probability = 20)
. = ..()
@@ -248,7 +235,7 @@
ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_FLOORWALL)
return list("mode" = RCD_FLOORWALL, "delay" = 20, "cost" = 16)
if(RCD_AIRLOCK)
@@ -266,13 +253,17 @@
return list("mode" = RCD_COMPUTER, "delay" = 20, "cost" = 25)
if(RCD_FURNISHING)
return list("mode" = RCD_FURNISHING, "delay" = the_rcd.furnish_delay, "cost" = the_rcd.furnish_cost)
+ if(RCD_CONVEYOR)
+ return list("mode" = RCD_CONVEYOR, "delay" = 5, "cost" = 5)
+ if(RCD_SWITCH)
+ return list("mode" = RCD_SWITCH, "delay" = 1, "cost" = 1)
return FALSE
/turf/open/floor/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, span_notice("You build a wall."))
- PlaceOnTop(/turf/closed/wall)
+ place_on_top(/turf/closed/wall)
return TRUE
if(RCD_AIRLOCK)
if((locate(/obj/machinery/door/airlock) in src) || (locate(/obj/machinery/door/window) in src)) // Have to ignore firelocks
@@ -342,4 +333,19 @@
var/atom/new_furnish = new the_rcd.furnish_type(src)
new_furnish.setDir(user.dir)
return TRUE
+ if(RCD_CONVEYOR)
+ if(locate(/obj/machinery/conveyor) in src)
+ return FALSE
+ if(get_turf(user) == src)
+ return FALSE
+ var/obj/machinery/conveyor/new_conveyor = new /obj/machinery/conveyor(src)
+ new_conveyor.setDir(user.dir)
+ if(the_rcd.linked_switch_id)
+ new_conveyor.id = the_rcd.linked_switch_id // link the conveyor if possible
+ return TRUE
+ if(RCD_SWITCH)
+ if(locate(/obj/machinery/conveyor_switch) in src)
+ return FALSE
+ new /obj/machinery/conveyor_switch(src)
+ return TRUE
return FALSE
diff --git a/code/game/turfs/simulated/floor/fancy_floor.dm b/code/game/turfs/open/floor/fancy_floor.dm
similarity index 69%
rename from code/game/turfs/simulated/floor/fancy_floor.dm
rename to code/game/turfs/open/floor/fancy_floor.dm
index c6ec1637b061..b09a66f88395 100644
--- a/code/game/turfs/simulated/floor/fancy_floor.dm
+++ b/code/game/turfs/open/floor/fancy_floor.dm
@@ -11,15 +11,17 @@
desc = "Stylish dark wood."
icon_state = "wood"
floor_tile = /obj/item/stack/tile/wood
- broken_states = list("wood-broken", "wood-broken2", "wood-broken3", "wood-broken4", "wood-broken5", "wood-broken6", "wood-broken7")
footstep = FOOTSTEP_WOOD
barefootstep = FOOTSTEP_WOOD_BAREFOOT
clawfootstep = FOOTSTEP_WOOD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
tiled_dirt = FALSE
- flags_1 = NO_RUST | CAN_BE_DIRTY_1
+ turf_flags = NO_RUST
flammability = 3 // yikes, better put that out quick
+/turf/open/floor/wood/broken_states()
+ return list("wood-broken", "wood-broken2", "wood-broken3", "wood-broken4", "wood-broken5", "wood-broken6", "wood-broken7")
+
/turf/open/floor/wood/examine(mob/user)
. = ..()
. += span_notice("There's a few screws and a small crack visible.")
@@ -66,17 +68,23 @@
/turf/open/floor/wood/parquet
icon_state = "wood-parquet"
floor_tile = /obj/item/stack/tile/wood/parquet
- broken_states = list("wood-parquet-broken", "wood-parquet-broken2", "wood-parquet-broken3", "wood-parquet-broken4", "wood-parquet-broken5", "wood-parquet-broken6", "wood-parquet-broken7")
+
+/turf/open/floor/wood/parquet/broken_states()
+ return list("wood-parquet-broken", "wood-parquet-broken2", "wood-parquet-broken3", "wood-parquet-broken4", "wood-parquet-broken5", "wood-parquet-broken6", "wood-parquet-broken7")
/turf/open/floor/wood/tile
icon_state = "wood-tile"
floor_tile = /obj/item/stack/tile/wood/tile
- broken_states = list("wood-tile-broken", "wood-tile-broken2", "wood-tile-broken3")
+
+/turf/open/floor/wood/tile/broken_states()
+ return list("wood-tile-broken", "wood-tile-broken2", "wood-tile-broken3")
/turf/open/floor/wood/large
icon_state = "wood-large"
floor_tile = /obj/item/stack/tile/wood/large
- broken_states = list("wood-large-broken", "wood-large-broken2", "wood-large-broken3")
+
+/turf/open/floor/wood/large/broken_states()
+ return list("wood-tile-broken", "wood-tile-broken2", "wood-tile-broken3")
/turf/open/floor/wood/cold
initial_gas_mix = KITCHEN_COLDROOM_ATMOS
@@ -222,9 +230,9 @@
icon = 'icons/turf/floors/bamboo_mat.dmi'
icon_state = "bamboo"
floor_tile = /obj/item/stack/tile/bamboo
- broken_states = list("damaged")
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/turf/open/floor/bamboo)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_BAMBOO_FLOOR
+ canSmoothWith = SMOOTH_GROUP_BAMBOO_FLOOR
flags_1 = NONE
footstep = FOOTSTEP_WOOD
barefootstep = FOOTSTEP_WOOD_BAREFOOT
@@ -232,6 +240,9 @@
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
tiled_dirt = FALSE
+/turf/open/floor/bamboo/broken_states()
+ return list("bamboodamaged")
+
/turf/open/floor/bamboo/broken
icon_state = "damaged"
broken = TRUE
@@ -241,7 +252,6 @@
desc = "You can't tell if this is real grass or just cheap plastic imitation."
icon_state = "grass1"
floor_tile = /obj/item/stack/tile/grass
- broken_states = list("sand")
flags_1 = NONE
bullet_bounce_sound = null
footstep = FOOTSTEP_GRASS
@@ -253,11 +263,14 @@
tiled_dirt = FALSE
flammability = 2 // california simulator
+/turf/open/floor/grass/broken_states()
+ return list("sand")
+
/turf/open/floor/grass/Initialize(mapload)
. = ..()
if(src.type == /turf/open/floor/grass) //don't want grass subtypes getting the icon state,
icon_state = "grass[rand(1,4)]"
- update_appearance(UPDATE_ICON)
+ update_appearance()
/turf/open/floor/grass/attackby(obj/item/C, mob/user, params)
if((C.tool_behaviour == TOOL_SHOVEL) && params)
@@ -335,11 +348,12 @@
name = "carpet"
desc = "Soft velvet carpeting. Feels good between your toes."
icon = 'icons/turf/floors/carpet.dmi'
- icon_state = "carpet"
+ icon_state = "carpet-255"
+ base_icon_state = "carpet"
floor_tile = /obj/item/stack/tile/carpet
- broken_states = list("damaged")
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/turf/open/floor/carpet)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET
+ canSmoothWith = SMOOTH_GROUP_CARPET
flags_1 = NONE
bullet_bounce_sound = null
footstep = FOOTSTEP_CARPET
@@ -355,104 +369,119 @@
/turf/open/floor/carpet/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/turf/open/floor/carpet/update_icon(updates=ALL)
. = ..()
- if(!.)
- return 0
+ if(!. || !(updates & UPDATE_SMOOTHING))
+ return
if(!broken && !burnt)
- if(smooth)
- queue_smooth(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH(src)
else
make_plating()
- if(smooth)
- queue_smooth_neighbors(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
-/turf/open/floor/carpet/broken
- icon_state = "damaged"
- broken = TRUE
/turf/open/floor/carpet/black
icon = 'icons/turf/floors/carpet_black.dmi'
+ icon_state = "carpet_black-255"
+ base_icon_state = "carpet_black"
floor_tile = /obj/item/stack/tile/carpet/black
- canSmoothWith = list(/turf/open/floor/carpet/black)
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_BLACK
+ canSmoothWith = SMOOTH_GROUP_CARPET_BLACK
-/turf/open/floor/carpet/black/broken
- icon_state = "damaged"
- broken = TRUE
-
-/turf/open/floor/carpet/exoticblue
- icon = 'icons/turf/floors/carpet_exoticblue.dmi'
- floor_tile = /obj/item/stack/tile/carpet/exoticblue
- canSmoothWith = list(/turf/open/floor/carpet/exoticblue)
-
-/turf/open/floor/carpet/exoticblue/broken
- icon_state = "damaged"
- broken = TRUE
+/turf/open/floor/carpet/blue
+ icon = 'icons/turf/floors/carpet_blue.dmi'
+ icon_state = "carpet_blue-255"
+ base_icon_state = "carpet_blue"
+ floor_tile = /obj/item/stack/tile/carpet/blue
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_BLUE
+ canSmoothWith = SMOOTH_GROUP_CARPET_BLUE
/turf/open/floor/carpet/cyan
icon = 'icons/turf/floors/carpet_cyan.dmi'
+ icon_state = "carpet_cyan-255"
+ base_icon_state = "carpet_cyan"
floor_tile = /obj/item/stack/tile/carpet/cyan
- canSmoothWith = list(/turf/open/floor/carpet/cyan)
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_CYAN
+ canSmoothWith = SMOOTH_GROUP_CARPET_CYAN
-/turf/open/floor/carpet/cyan/broken
- icon_state = "damaged"
- broken = TRUE
-
-/turf/open/floor/carpet/exoticgreen
- icon = 'icons/turf/floors/carpet_exoticgreen.dmi'
- floor_tile = /obj/item/stack/tile/carpet/exoticgreen
- canSmoothWith = list(/turf/open/floor/carpet/exoticgreen)
-
-/turf/open/floor/carpet/exoticgreen/broken
- icon_state = "damaged"
- broken = TRUE
+/turf/open/floor/carpet/green
+ icon = 'icons/turf/floors/carpet_green.dmi'
+ icon_state = "carpet_green-255"
+ base_icon_state = "carpet_green"
+ floor_tile = /obj/item/stack/tile/carpet/green
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_GREEN
+ canSmoothWith = SMOOTH_GROUP_CARPET_GREEN
/turf/open/floor/carpet/orange
icon = 'icons/turf/floors/carpet_orange.dmi'
+ icon_state = "carpet_orange-255"
+ base_icon_state = "carpet_orange"
floor_tile = /obj/item/stack/tile/carpet/orange
- canSmoothWith = list(/turf/open/floor/carpet/orange)
-
-/turf/open/floor/carpet/orange/broken
- icon_state = "damaged"
- broken = TRUE
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ORANGE
+ canSmoothWith = SMOOTH_GROUP_CARPET_ORANGE
-/turf/open/floor/carpet/exoticpurple
- icon = 'icons/turf/floors/carpet_exoticpurple.dmi'
- floor_tile = /obj/item/stack/tile/carpet/exoticpurple
- canSmoothWith = list(/turf/open/floor/carpet/exoticpurple)
-
-/turf/open/floor/carpet/exoticpurple/broken
- icon_state = "damaged"
- broken = TRUE
+/turf/open/floor/carpet/purple
+ icon = 'icons/turf/floors/carpet_purple.dmi'
+ icon_state = "carpet_purple-255"
+ base_icon_state = "carpet_purple"
+ floor_tile = /obj/item/stack/tile/carpet/purple
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_PURPLE
+ canSmoothWith = SMOOTH_GROUP_CARPET_PURPLE
/turf/open/floor/carpet/red
icon = 'icons/turf/floors/carpet_red.dmi'
+ icon_state = "carpet_red-255"
+ base_icon_state = "carpet_red"
floor_tile = /obj/item/stack/tile/carpet/red
- canSmoothWith = list(/turf/open/floor/carpet/red)
-
-/turf/open/floor/carpet/red/broken
- icon_state = "damaged"
- broken = TRUE
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_RED
+ canSmoothWith = SMOOTH_GROUP_CARPET_RED
/turf/open/floor/carpet/royalblack
icon = 'icons/turf/floors/carpet_royalblack.dmi'
+ icon_state = "carpet_royalblack-255"
+ base_icon_state = "carpet_royalblack"
floor_tile = /obj/item/stack/tile/carpet/royalblack
- canSmoothWith = list(/turf/open/floor/carpet/royalblack)
-
-/turf/open/floor/carpet/royalblack/broken
- icon_state = "damaged"
- broken = TRUE
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ROYAL_BLACK
+ canSmoothWith = SMOOTH_GROUP_CARPET_ROYAL_BLACK
/turf/open/floor/carpet/royalblue
icon = 'icons/turf/floors/carpet_royalblue.dmi'
+ icon_state = "carpet_royalblue-255"
+ base_icon_state = "carpet_royalblue"
floor_tile = /obj/item/stack/tile/carpet/royalblue
- canSmoothWith = list(/turf/open/floor/carpet/royalblue)
-
-/turf/open/floor/carpet/royalblue/broken
- icon_state = "damaged"
- broken = TRUE
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_ROYAL_BLUE
+ canSmoothWith = SMOOTH_GROUP_CARPET_ROYAL_BLUE
+
+/turf/open/floor/carpet/executive
+ name = "executive carpet"
+ icon = 'icons/turf/floors/carpet_executive.dmi'
+ icon_state = "executive_carpet-255"
+ base_icon_state = "executive_carpet"
+ floor_tile = /obj/item/stack/tile/carpet/executive
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_EXECUTIVE
+ canSmoothWith = SMOOTH_GROUP_CARPET_EXECUTIVE
+
+/turf/open/floor/carpet/stellar
+ name = "stellar carpet"
+ icon = 'icons/turf/floors/carpet_stellar.dmi'
+ icon_state = "stellar_carpet-255"
+ base_icon_state = "stellar_carpet"
+ floor_tile = /obj/item/stack/tile/carpet/stellar
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_STELLAR
+ canSmoothWith = SMOOTH_GROUP_CARPET_STELLAR
+
+/turf/open/floor/carpet/donk
+ name = "Donk Co. carpet"
+ icon = 'icons/turf/floors/carpet_donk.dmi'
+ icon_state = "donk_carpet-255"
+ base_icon_state = "donk_carpet"
+ floor_tile = /obj/item/stack/tile/carpet/donk
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET_DONK
+ canSmoothWith = SMOOTH_GROUP_CARPET_DONK
/turf/open/floor/carpet/narsie_act(force, ignore_mobs, probability = 20)
. = (prob(probability) || force)
@@ -465,11 +494,11 @@
/turf/open/floor/carpet/break_tile()
broken = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/turf/open/floor/carpet/burn_tile()
burnt = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/turf/open/floor/carpet/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
return FALSE
@@ -477,10 +506,12 @@
/turf/open/floor/fakepit
desc = "A clever illusion designed to look like a bottomless pit."
- smooth = SMOOTH_TRUE | SMOOTH_BORDER | SMOOTH_MORE
- canSmoothWith = list(/turf/open/floor/fakepit)
- icon = 'icons/turf/floors/Chasms.dmi'
- icon_state = "smooth"
+ icon = 'icons/turf/floors/chasms.dmi'
+ icon_state = "chasms-0"
+ base_icon_state = "chasms"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_TURF_CHASM
+ canSmoothWith = SMOOTH_GROUP_TURF_CHASM
tiled_dirt = FALSE
/turf/open/floor/fakepit/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
@@ -492,16 +523,13 @@
icon = 'icons/turf/space.dmi'
icon_state = "0"
floor_tile = /obj/item/stack/tile/fakespace
- broken_states = list("damaged")
plane = PLANE_SPACE
tiled_dirt = FALSE
+ damaged_dmi = 'icons/turf/space.dmi'
-/turf/open/floor/fakespace/Initialize(mapload)
- . = ..()
- icon_state = SPACE_ICON_STATE
+/turf/open/floor/fakespace/broken_states()
+ return list("damaged")
/turf/open/floor/fakespace/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/space.dmi'
- underlay_appearance.icon_state = SPACE_ICON_STATE
- underlay_appearance.plane = PLANE_SPACE
+ generate_space_underlay(underlay_appearance, asking_turf)
return TRUE
diff --git a/code/game/turfs/open/floor/hull.dm b/code/game/turfs/open/floor/hull.dm
new file mode 100644
index 000000000000..4c9c356fd2dd
--- /dev/null
+++ b/code/game/turfs/open/floor/hull.dm
@@ -0,0 +1,20 @@
+
+/turf/open/floor/engine/hull
+ name = "exterior hull plating"
+ desc = "Sturdy exterior hull plating that separates you from the uncaring vacuum of space."
+ icon_state = "regular_hull"
+ initial_gas_mix = AIRLESS_ATMOS
+
+/turf/open/floor/engine/hull/ceiling
+ name = "shuttle ceiling plating"
+ var/old_turf_type
+
+/turf/open/floor/engine/hull/ceiling/AfterChange(flags, oldType)
+ . = ..()
+ old_turf_type = oldType
+
+/turf/open/floor/engine/hull/reinforced
+ name = "exterior reinforced hull plating"
+ desc = "Extremely sturdy exterior hull plating that separates you from the uncaring vacuum of space."
+ icon_state = "reinforced_hull"
+ heat_capacity = INFINITY
diff --git a/code/game/turfs/simulated/floor/light_floor.dm b/code/game/turfs/open/floor/light_floor.dm
similarity index 97%
rename from code/game/turfs/simulated/floor/light_floor.dm
rename to code/game/turfs/open/floor/light_floor.dm
index 12e4fcbc5d57..ef74f2a51bb9 100644
--- a/code/game/turfs/simulated/floor/light_floor.dm
+++ b/code/game/turfs/open/floor/light_floor.dm
@@ -4,7 +4,6 @@
light_range = 5
icon_state = "light_on"
floor_tile = /obj/item/stack/tile/light
- broken_states = list("light_broken")
var/on = TRUE
var/state = 0//0 = fine, 1 = flickering, 2 = breaking, 3 = broken
var/list/coloredlights = list("r", "o", "y", "g", "b", "i", "v", "w", "s", "z")
@@ -13,6 +12,9 @@
tiled_dirt = FALSE
var/static/list/lighttile_designs
+/turf/open/floor/light/broken_states()
+ return list("light_broken")
+
/turf/open/floor/light/examine(mob/user)
. = ..()
. += span_notice("There's a small crack on the edge of it.")
@@ -34,7 +36,7 @@
/turf/open/floor/light/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(!length(lighttile_designs))
populate_lighttile_designs()
diff --git a/code/game/turfs/simulated/floor/mineral_floor.dm b/code/game/turfs/open/floor/mineral_floor.dm
similarity index 90%
rename from code/game/turfs/simulated/floor/mineral_floor.dm
rename to code/game/turfs/open/floor/mineral_floor.dm
index d13d418043dc..5a070aadadcd 100644
--- a/code/game/turfs/simulated/floor/mineral_floor.dm
+++ b/code/game/turfs/open/floor/mineral_floor.dm
@@ -17,19 +17,16 @@
/turf/open/floor/mineral/Initialize(mapload)
- if(!broken_states)
- broken_states = list("[initial(icon_state)]_dam")
. = ..()
icons = typelist("icons", icons)
+/turf/open/floor/mineral/broken_states()
+ return isnull(icon_state) ? list() : list("[initial(icon_state)]_dam")
/turf/open/floor/mineral/update_icon_state()
- . = ..()
- if(!.)
- return
- if(!broken && !burnt)
- if(!(icon_state in icons))
- icon_state = initial(icon_state)
+ if(!broken && !burnt && !(icon_state in icons))
+ icon_state = initial(icon_state)
+ return ..()
//PLASMA
@@ -93,9 +90,13 @@
/turf/open/floor/mineral/titanium
name = "shuttle floor"
icon_state = "titanium"
- flags_1 = NO_RUST | CAN_BE_DIRTY_1
floor_tile = /obj/item/stack/tile/mineral/titanium
- broken_states = list("titanium_dam1","titanium_dam2","titanium_dam3","titanium_dam4","titanium_dam5")
+
+/turf/open/floor/mineral/titanium/broken_states()
+ return list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5")
+
+/turf/open/floor/mineral/titanium/rust_heretic_act()
+ return // titanium does not rust
/turf/open/floor/mineral/titanium/broken
icon_state = "titanium_dam1"
@@ -161,7 +162,9 @@
name = "shuttle floor"
icon_state = "plastitanium"
floor_tile = /obj/item/stack/tile/mineral/plastitanium
- broken_states = list("plastitanium_dam1","plastitanium_dam2","plastitanium_dam3","plastitanium_dam4","plastitanium_dam5")
+
+/turf/open/floor/mineral/plastitanium/broken_states()
+ return list("plastitanium_dam1","plastitanium_dam2","plastitanium_dam3","plastitanium_dam4","plastitanium_dam5")
/turf/open/floor/mineral/plastitanium/broken
icon_state = "plastitanium_dam1"
@@ -196,10 +199,12 @@
/turf/open/floor/mineral/plastitanium/red/brig/fakepit
name = "brig chasm"
desc = "A place for very naughy criminals."
- smooth = SMOOTH_TRUE | SMOOTH_BORDER | SMOOTH_MORE
- canSmoothWith = list(/turf/open/floor/mineral/plastitanium/red/brig/fakepit)
- icon = 'icons/turf/floors/Chasms.dmi'
- icon_state = "smooth"
+ icon = 'icons/turf/floors/chasms.dmi'
+ icon_state = "chasms-0"
+ base_icon_state = "chasms"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_TURF_CHASM
+ canSmoothWith = SMOOTH_GROUP_TURF_CHASM
tiled_dirt = FALSE
//BANANIUM
diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/open/floor/misc_floor.dm
similarity index 95%
rename from code/game/turfs/simulated/floor/misc_floor.dm
rename to code/game/turfs/open/floor/misc_floor.dm
index 5003791ed8eb..974302bdbe58 100644
--- a/code/game/turfs/simulated/floor/misc_floor.dm
+++ b/code/game/turfs/open/floor/misc_floor.dm
@@ -123,10 +123,14 @@
name = "high-traction floor"
icon_state = "noslip"
floor_tile = /obj/item/stack/tile/noslip
- broken_states = list("noslip-damaged1","noslip-damaged2","noslip-damaged3")
- burnt_states = list("noslip-scorched1","noslip-scorched2")
slowdown = -0.3
+/turf/open/floor/noslip/broken_states()
+ return list("noslip-damaged1","noslip-damaged2","noslip-damaged3")
+
+/turf/open/floor/noslip/burnt_states()
+ return list("noslip-scorched1","noslip-scorched2")
+
/turf/open/floor/noslip/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
return
@@ -183,7 +187,7 @@
QDEL_NULL(realappearence)
return ..()
-/turf/open/floor/clockwork/ReplaceWithLattice()
+/turf/open/floor/clockwork/attempt_lattice_replacement(amount = 2)
. = ..()
for(var/obj/structure/lattice/L in src)
L.ratvar_act()
@@ -216,7 +220,7 @@
for(var/mob/M in viewers(src))
if(M.client && (is_servant_of_ratvar(M) || isobserver(M) || M.stat == DEAD))
viewing += M.client
- flick_overlay(I, viewing, 8)
+ flick_overlay_global(I, viewing, 8)
L.adjustToxLoss(-3, TRUE, TRUE)
/turf/open/floor/clockwork/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
@@ -313,7 +317,9 @@
desc = "This one takes you back."
icon_state = "eighties"
floor_tile = /obj/item/stack/tile/eighties
- broken_states = list("eighties_damaged")
+
+/turf/open/floor/eighties/broken_states()
+ return list("eighties_damaged")
/turf/open/floor/eighties/broken
icon_state = "eighties_damaged"
@@ -323,4 +329,4 @@
name = "stone brick floor"
desc = "Some stone brick tiles, how rustic."
icon_state = "stone_floor"
- floor_tile = /obj/item/stack/tile/plasteel
\ No newline at end of file
+ floor_tile = /obj/item/stack/tile/plasteel
diff --git a/code/game/turfs/simulated/floor/plasteel_floor.dm b/code/game/turfs/open/floor/plasteel_floor.dm
similarity index 93%
rename from code/game/turfs/simulated/floor/plasteel_floor.dm
rename to code/game/turfs/open/floor/plasteel_floor.dm
index 6f0f9dbe29fd..878200516f79 100644
--- a/code/game/turfs/simulated/floor/plasteel_floor.dm
+++ b/code/game/turfs/open/floor/plasteel_floor.dm
@@ -1,8 +1,12 @@
/turf/open/floor/plasteel
icon_state = "floor"
floor_tile = /obj/item/stack/tile/plasteel
- broken_states = list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5")
- burnt_states = list("floorscorched1", "floorscorched2")
+
+/turf/open/floor/plasteel/broken_states()
+ return list("damaged1", "damaged2", "damaged3", "damaged4", "damaged5")
+
+/turf/open/floor/plasteel/broken_states()
+ return list("floorscorched1", "floorscorched2")
/turf/open/floor/plasteel/examine(mob/user)
. = ..()
@@ -13,13 +17,11 @@
new /obj/effect/glowing_rune(src)
ChangeTurf(/turf/open/floor/plating/rust)
-/turf/open/floor/plasteel/update_icon(updates=ALL)
- . = ..()
- if(!.)
- return 0
- if(!broken && !burnt)
- icon = icon_regular_floor
- icon_state = icon_state_regular_floor
+/turf/open/floor/plasteel/update_icon_state()
+ if(broken || burnt)
+ return ..()
+ icon_state = base_icon_state
+ return ..()
/turf/open/floor/plasteel/broken
icon_state = "damaged1"
@@ -192,9 +194,11 @@ turf/open/floor/plasteel/airless/solarpanel
/turf/open/floor/plasteel/cult
icon_state = "cult"
- broken_states = list("damage1", "damage2", "damage3", "damage4", "damage5")
name = "engraved floor"
+/turf/open/floor/plasteel/cult/broken_states()
+ return list("damage1", "damage2", "damage3", "damage4", "damage5")
+
/turf/open/floor/plasteel/cult/broken
icon_state = "damage1"
broken = TRUE
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/open/floor/plating.dm
similarity index 82%
rename from code/game/turfs/simulated/floor/plating.dm
rename to code/game/turfs/open/floor/plating.dm
index 5fdd9fa14468..115a9e89c71d 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/open/floor/plating.dm
@@ -10,7 +10,8 @@
/turf/open/floor/plating
name = "plating"
icon_state = "plating"
- intact = FALSE
+ overfloor_placed = FALSE
+ underfloor_accessibility = UNDERFLOOR_INTERACTABLE
baseturfs = /turf/baseturf_bottom
footstep = FOOTSTEP_PLATING
barefootstep = FOOTSTEP_HARD_BAREFOOT
@@ -20,9 +21,23 @@
FASTDMM_PROP(\
pipe_astar_cost = 1\
)
-
+ //Can this plating have reinforced floors placed ontop of it
var/attachment_holes = TRUE
+ //Used for upgrading this into R-Plating
+ var/upgradable = TRUE
+
+ /// If true, will allow tiles to replace us if the tile [wants to] [/obj/item/stack/tile/var/replace_plating].
+ /// And if our baseturfs are compatible.
+ /// See [/obj/item/stack/tile/proc/place_tile].
+ var/allow_replacement = TRUE
+
+/turf/open/floor/plating/broken_states()
+ return list("damaged1", "damaged2", "damaged4")
+
+/turf/open/floor/plating/burnt_states()
+ return list("floorscorched1", "floorscorched2")
+
/turf/open/floor/plating/examine(mob/user)
. = ..()
if(broken || burnt)
@@ -33,24 +48,6 @@
else
. += span_notice("You might be able to build ontop of it with some tiles...")
-/turf/open/floor/plating/Initialize(mapload)
- if (!broken_states)
- broken_states = list("platingdmg1", "platingdmg2", "platingdmg3")
- if (!burnt_states)
- burnt_states = list("panelscorched")
- . = ..()
- if(!attachment_holes || (!broken && !burnt))
- icon_plating = icon_state
- else
- icon_plating = initial(icon_state)
-
-/turf/open/floor/plating/update_icon_state()
- . = ..()
- if(!.)
- return
- if(!broken && !burnt)
- icon_state = icon_plating //Because asteroids are 'platings' too.
-
/turf/open/floor/plating/attackby(obj/item/C, mob/user, params)
if(..())
return
@@ -79,7 +76,7 @@
to_chat(user, span_notice("You begin reinforcing the floor..."))
if(do_after(user, 3 SECONDS, src))
if (R.get_amount() >= 1 && !istype(src, /turf/open/floor/engine))
- PlaceOnTop(/turf/open/floor/engine, flags = CHANGETURF_INHERIT_AIR)
+ place_on_top(/turf/open/floor/engine, flags = CHANGETURF_INHERIT_AIR)
playsound(src, 'sound/items/deconstruct.ogg', 80, 1)
R.use(1)
to_chat(user, span_notice("You reinforce the floor."))
@@ -87,14 +84,13 @@
else if(istype(C, /obj/item/stack/tile) && !locate(/obj/structure/lattice/catwalk, src))
if(!broken && !burnt)
for(var/obj/O in src)
- if(O.level == 1) //ex. pipes laid underneath a tile
- for(var/M in O.buckled_mobs)
- to_chat(user, span_warning("Someone is buckled to \the [O]! Unbuckle [M] to move \him out of the way."))
- return
+ for(var/M in O.buckled_mobs)
+ to_chat(user, span_warning("Someone is buckled to \the [O]! Unbuckle [M] to move \him out of the way."))
+ return
var/obj/item/stack/tile/W = C
if(!W.use(1))
return
- var/turf/open/floor/T = PlaceOnTop(W.turf_type, flags = CHANGETURF_INHERIT_AIR)
+ var/turf/open/floor/T = place_on_top(W.turf_type, flags = CHANGETURF_INHERIT_AIR)
if(istype(W, /obj/item/stack/tile/light)) //TODO: get rid of this ugly check somehow
var/obj/item/stack/tile/light/L = W
var/turf/open/floor/light/F = T
@@ -167,7 +163,7 @@
to_chat(user, span_danger("You hit [src], to no effect!"))
/turf/open/floor/plating/foam/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- if(the_rcd.mode == RCD_FLOORWALL)
+ if(the_rcd.construction_mode == RCD_FLOORWALL)
return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1)
/turf/open/floor/plating/foam/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
@@ -188,3 +184,8 @@
if(locate(/obj/structure/lattice/catwalk, src))
return FALSE
return TRUE
+///not an actual turf its used just for rcd ui purposes
+/turf/open/floor/plating/rcd
+ name = "Floor/Wall"
+ icon = 'icons/mob/radial.dmi'
+ icon_state = "wallfloor"
diff --git a/code/game/turfs/simulated/floor/plating/asteroid.dm b/code/game/turfs/open/floor/plating/asteroid.dm
similarity index 85%
rename from code/game/turfs/simulated/floor/plating/asteroid.dm
rename to code/game/turfs/open/floor/plating/asteroid.dm
index 4f3960ac2d02..b0f441a1d59c 100644
--- a/code/game/turfs/simulated/floor/plating/asteroid.dm
+++ b/code/game/turfs/open/floor/plating/asteroid.dm
@@ -7,6 +7,7 @@
baseturfs = /turf/open/floor/plating/asteroid
icon = 'icons/turf/floors.dmi'
icon_state = "asteroid"
+ base_icon_state = "asteroid"
icon_plating = "asteroid"
postdig_icon_change = TRUE
footstep = FOOTSTEP_SAND
@@ -18,7 +19,12 @@
var/floor_variance = 20 //probability floor has a different icon state
attachment_holes = FALSE
var/obj/item/stack/digResult = /obj/item/stack/ore/glass/basalt
- var/dug
+ var/dug = FALSE
+
+/turf/open/floor/plating/asteroid/broken_states()
+ if(initial(dug))
+ return list(icon_state)
+ return list("[base_icon_state]_dug")
/turf/open/floor/plating/asteroid/Initialize(mapload)
var/proper_name = name
@@ -34,6 +40,8 @@
icon_plating = "[environment_type]_dug"
icon_state = "[environment_type]_dug"
dug = TRUE
+ broken = TRUE
+ update_appearance()
/turf/open/floor/plating/asteroid/proc/can_dig(mob/user)
if(!dug)
@@ -45,7 +53,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, span_notice("You build a floor."))
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
@@ -89,14 +97,16 @@
contents_explosion(severity, target)
/turf/open/floor/plating/lavaland_baseturf
- baseturfs = /turf/open/floor/plating/asteroid/basalt/lava_land_surface
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
+ planetary_atmos = TRUE
+ baseturfs = /turf/open/floor/plating/asteroid/basalt/lava_land_surface
/turf/open/floor/plating/asteroid/basalt
name = "volcanic floor"
baseturfs = /turf/open/floor/plating/asteroid/basalt
icon = 'icons/turf/floors.dmi'
icon_state = "basalt"
+ base_icon_state = "basalt"
icon_plating = "basalt"
environment_type = "basalt"
floor_variance = 15
@@ -129,7 +139,11 @@
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
planetary_atmos = TRUE
baseturfs = /turf/open/lava/smooth/lava_land_surface
-
+
+/// Used for the lavaland icemoon ruin.
+/turf/open/floor/plating/asteroid/basalt/lava_land_surface/no_ruins
+ turf_flags = NO_RUINS
+
/turf/open/floor/plating/asteroid/airless
initial_gas_mix = AIRLESS_ATMOS
baseturfs = /turf/open/floor/plating/asteroid/airless
@@ -144,39 +158,53 @@
name = "snow"
desc = "Looks cold."
icon = 'icons/turf/snow.dmi'
+ damaged_dmi = 'icons/turf/snow.dmi'
baseturfs = /turf/open/floor/plating/asteroid/snow
icon_state = "snow"
+ base_icon_state = "snow"
icon_plating = "snow"
initial_gas_mix = FROZEN_ATMOS
+ slowdown = 2
environment_type = "snow"
flags_1 = NONE
planetary_atmos = TRUE
- burnt_states = list("snow_dug")
bullet_sizzle = TRUE
bullet_bounce_sound = null
digResult = /obj/item/stack/sheet/mineral/snow
flammability = -5
-/turf/open/floor/plating/asteroid/snow/singularity_act()
- . = ..() //take the wires n shit out
- return 0
-
-/turf/open/floor/plating/asteroid/snow/getDug()
- ..()
- slowdown = 0
-
/turf/open/floor/plating/asteroid/snow/burn_tile()
if(!burnt)
visible_message(span_danger("[src] melts away!."))
slowdown = 0
burnt = TRUE
- icon_state = "snow_dug"
+ update_appearance()
return TRUE
return FALSE
+/turf/open/floor/plating/asteroid/snow/burnt_states()
+ return list("snow_dug")
+
+/turf/open/floor/plating/asteroid/snow/singularity_act()
+ . = ..() //take the wires n shit out
+ return 0
+
+/turf/open/floor/plating/asteroid/snow/getDug()
+ ..()
+ slowdown = 0
+
/turf/open/floor/plating/asteroid/snow/icemoon
- baseturfs = /turf/open/floor/plating/asteroid/snow/icemoon
+ baseturfs = /turf/open/openspace/icemoon
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
+ slowdown = 0
+
+/turf/open/floor/plating/asteroid/snow/icemoon/do_not_chasm
+ flags_1 = CAN_BE_DIRTY_1
+ turf_flags = IS_SOLID | NO_RUST | NO_RUINS
+
+/turf/open/floor/plating/asteroid/snow/icemoon/do_not_scrape
+ flags_1 = CAN_BE_DIRTY_1
+ turf_flags = IS_SOLID | NO_RUST | NO_CLEARING
/turf/open/floor/plating/asteroid/snow/icemoon/top_layer
light_range = 2
@@ -201,7 +229,7 @@
name = "icy snow"
desc = "Looks colder."
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
- initial_gas_mix = "o2=0;n2=82;plasma=24;TEMP=120"
+ initial_gas_mix = BURNING_COLD
floor_variance = 0
icon_state = "snow-ice"
icon_plating = "snow-ice"
@@ -211,6 +239,12 @@
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+/turf/open/floor/plating/asteroid/snow/ice/break_tile()
+ return FALSE
+
+/turf/open/floor/plating/asteroid/snow/ice/burn_tile()
+ return FALSE
+
/turf/open/floor/plating/asteroid/snow/ice/icemoon
baseturfs = /turf/open/floor/plating/asteroid/snow/ice/icemoon
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
diff --git a/code/game/turfs/simulated/floor/plating/dirt.dm b/code/game/turfs/open/floor/plating/dirt.dm
similarity index 100%
rename from code/game/turfs/simulated/floor/plating/dirt.dm
rename to code/game/turfs/open/floor/plating/dirt.dm
diff --git a/code/game/turfs/simulated/floor/plating/misc_plating.dm b/code/game/turfs/open/floor/plating/misc_plating.dm
similarity index 81%
rename from code/game/turfs/simulated/floor/plating/misc_plating.dm
rename to code/game/turfs/open/floor/plating/misc_plating.dm
index c3cae2da310b..fabe4d7c6f56 100644
--- a/code/game/turfs/simulated/floor/plating/misc_plating.dm
+++ b/code/game/turfs/open/floor/plating/misc_plating.dm
@@ -35,13 +35,13 @@
/turf/open/floor/plating/ashplanet
- icon = 'icons/turf/mining.dmi'
- gender = PLURAL
name = "ash"
- icon_state = "ash"
- smooth = SMOOTH_MORE|SMOOTH_BORDER
- var/smooth_icon = 'icons/turf/floors/ash.dmi'
desc = "The ground is covered in volcanic ash."
+ icon_state = "ash"
+ base_icon_state = "ash"
+ icon = MAP_SWITCH('icons/turf/floors/ash.dmi', 'icons/turf/mining.dmi')
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ gender = PLURAL
baseturfs = /turf/open/floor/plating/ashplanet/wateryrock //I assume this will be a chasm eventually, once this becomes an actual surface
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
planetary_atmos = TRUE
@@ -53,12 +53,13 @@
tiled_dirt = FALSE
/turf/open/floor/plating/ashplanet/Initialize(mapload)
- if(smooth)
- var/matrix/M = new
- M.Translate(-4, -4)
- transform = M
- icon = smooth_icon
. = ..()
+ if(!(smoothing_flags & SMOOTH_BITMASK))
+ return
+ var/matrix/M = new
+ M.Translate(-4, -4)
+ transform = M
+ icon_state = "[base_icon_state]-[smoothing_junction]"
/turf/open/floor/plating/ashplanet/try_replace_tile(obj/item/stack/tile/T, mob/user, params)
return
@@ -70,17 +71,20 @@
return
/turf/open/floor/plating/ashplanet/ash
- canSmoothWith = list(/turf/open/floor/plating/ashplanet/ash, /turf/closed)
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_ASH
+ canSmoothWith = SMOOTH_GROUP_FLOOR_ASH + SMOOTH_GROUP_CLOSED_TURFS
layer = HIGH_TURF_LAYER
slowdown = 1
/turf/open/floor/plating/ashplanet/rocky
gender = PLURAL
name = "rocky ground"
- icon_state = "rockyash"
- smooth_icon = 'icons/turf/floors/rocky_ash.dmi'
+ icon_state = "rocky_ash"
+ base_icon_state = "rocky_ash"
+ icon = MAP_SWITCH('icons/turf/floors/rocky_ash.dmi', 'icons/turf/mining.dmi')
layer = MID_TURF_LAYER
- canSmoothWith = list(/turf/open/floor/plating/ashplanet/rocky, /turf/closed)
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_ASH_ROCKY
+ canSmoothWith = SMOOTH_GROUP_FLOOR_ASH_ROCKY + SMOOTH_GROUP_CLOSED_TURFS
footstep = FOOTSTEP_FLOOR
barefootstep = FOOTSTEP_HARD_BAREFOOT
clawfootstep = FOOTSTEP_HARD_CLAW
@@ -89,8 +93,9 @@
/turf/open/floor/plating/ashplanet/wateryrock
gender = PLURAL
name = "wet rocky ground"
- smooth = null
- icon_state = "wateryrock"
+ icon_state = "watery_rock"
+ base_icon_state = "watery_rock"
+ smoothing_flags = NONE
slowdown = 2
footstep = FOOTSTEP_FLOOR
barefootstep = FOOTSTEP_HARD_BAREFOOT
@@ -98,9 +103,8 @@
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
/turf/open/floor/plating/ashplanet/wateryrock/Initialize(mapload)
- icon_state = "[icon_state][rand(1, 9)]"
- . = ..()
-
+ icon_state = "[base_icon_state][rand(1, 9)]"
+ return ..()
/turf/open/floor/plating/beach
name = "beach"
@@ -171,7 +175,8 @@
name = "ice sheet"
desc = "A sheet of solid ice. Looks slippery."
icon = 'icons/turf/floors/ice_turf.dmi'
- icon_state = "unsmooth"
+ icon_state = "ice_turf-0"
+ base_icon_state = "ice_turf-0"
initial_gas_mix = FROZEN_ATMOS
initial_temperature = 180
planetary_atmos = TRUE
@@ -196,9 +201,11 @@
return
/turf/open/floor/plating/ice/smooth
- icon_state = "smooth"
- smooth = SMOOTH_MORE | SMOOTH_BORDER
- canSmoothWith = list(/turf/open/floor/plating/ice/smooth, /turf/open/floor/plating/ice)
+ icon_state = "ice_turf-255"
+ base_icon_state = "ice_turf"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_ICE
+ canSmoothWith = SMOOTH_GROUP_FLOOR_ICE
/turf/open/floor/plating/ice/colder
initial_temperature = 140
@@ -234,14 +241,16 @@
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
/turf/open/floor/plating/snowed/cavern
- initial_gas_mix = "o2=0;n2=82;plasma=24;TEMP=120"
+ initial_gas_mix = BURNING_COLD
/turf/open/floor/plating/snowed/smoothed
- smooth = SMOOTH_MORE | SMOOTH_BORDER
- canSmoothWith = list(/turf/open/floor/plating/snowed/smoothed, /turf/open/floor/plating/snowed)
- planetary_atmos = TRUE
icon = 'icons/turf/floors/snow_turf.dmi'
- icon_state = "smooth"
+ icon_state = "snow_turf-0"
+ base_icon_state = "snow_turf"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_SNOWED
+ canSmoothWith = SMOOTH_GROUP_FLOOR_SNOWED
+ planetary_atmos = TRUE
/turf/open/floor/plating/snowed/colder
initial_temperature = 140
diff --git a/code/game/turfs/simulated/floor/reinf_floor.dm b/code/game/turfs/open/floor/reinf_floor.dm
similarity index 95%
rename from code/game/turfs/simulated/floor/reinf_floor.dm
rename to code/game/turfs/open/floor/reinf_floor.dm
index be42663a18c6..4959ce4fe3f8 100644
--- a/code/game/turfs/simulated/floor/reinf_floor.dm
+++ b/code/game/turfs/open/floor/reinf_floor.dm
@@ -72,8 +72,7 @@
if(1)
if(prob(80))
if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor))
- ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
- ReplaceWithLattice()
+ attempt_lattice_replacement()
else
ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
else if(prob(50))
@@ -91,7 +90,7 @@
new floor_tile(src)
make_plating(TRUE)
else if(prob(30))
- ReplaceWithLattice()
+ attempt_lattice_replacement()
/turf/open/floor/engine/attack_paw(mob/user)
return attack_hand(user)
@@ -139,7 +138,7 @@
desc = "The air smells strangely over this sinister flooring."
icon_state = "plating"
floor_tile = null
- CanAtmosPass = ATMOS_PASS_NO // cant be spaced or flooded that easily
+ can_atmos_pass = ATMOS_PASS_NO // cant be spaced or flooded that easily
var/obj/effect/clockwork/overlay/floor/bloodcult/realappearance
@@ -172,7 +171,7 @@
/turf/open/floor/engine/cult/airless
initial_gas_mix = AIRLESS_ATMOS
- CanAtmosPass = ATMOS_PASS_PROC // same as /turf/open/floor/engine
+ can_atmos_pass = ATMOS_PASS_PROC // same as /turf/open/floor/engine
/turf/open/floor/engine/vacuum
name = "vacuum floor"
diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/open/lava.dm
similarity index 62%
rename from code/game/turfs/simulated/lava.dm
rename to code/game/turfs/open/lava.dm
index 0a24d89e0e9e..eb63fc7dd6c8 100644
--- a/code/game/turfs/simulated/lava.dm
+++ b/code/game/turfs/open/lava.dm
@@ -3,6 +3,7 @@
/turf/open/lava
name = "lava"
icon_state = "lava"
+ desc = "Looks painful to step in. Don't mine down."
gender = PLURAL //"That's some lava."
baseturfs = /turf/open/lava //lava all the way down
slowdown = 2
@@ -10,6 +11,7 @@
light_range = 2
light_power = 0.75
light_color = LIGHT_COLOR_LAVA
+ light_on = FALSE
bullet_bounce_sound = 'sound/items/welder2.ogg'
footstep = FOOTSTEP_LAVA
@@ -17,6 +19,79 @@
clawfootstep = FOOTSTEP_LAVA
heavyfootstep = FOOTSTEP_LAVA
+ /// The icon that covers the lava bits of our turf
+ var/mask_icon = 'icons/turf/floors.dmi'
+ /// The icon state that covers the lava bits of our turf
+ var/mask_state = "lava-lightmask"
+
+/turf/open/lava/Initialize(mapload)
+ . = ..()
+ refresh_light()
+ if(!smoothing_flags)
+ update_appearance()
+
+/turf/open/lava/update_overlays()
+ . = ..()
+ . += emissive_appearance(mask_icon, mask_state, src)
+ // We need a light overlay here because not every lava turf casts light, only the edge ones
+ var/mutable_appearance/light = mutable_appearance(mask_icon, mask_state, LIGHTING_PRIMARY_LAYER, src, LIGHTING_PLANE)
+ light.color = light_color
+ light.blend_mode = BLEND_ADD
+ . += light
+ // Mask away our light underlay, so things don't double stack
+ // This does mean if our light underlay DOESN'T look like the light we emit things will be wrong
+ // But that's rare, and I'm ok with that, quartering our light source count is useful
+ var/mutable_appearance/light_mask = mutable_appearance(mask_icon, mask_state, LIGHTING_MASK_LAYER, src, LIGHTING_PLANE)
+ light_mask.blend_mode = BLEND_MULTIPLY
+ light_mask.color = COLOR_MATRIX_INVERT
+ . += light_mask
+
+/// Refreshes this lava turf's lighting
+/turf/open/lava/proc/refresh_light()
+ var/border_turf = FALSE
+ var/list/turfs_to_check = RANGE_TURFS(1, src)
+ if(GET_LOWEST_STACK_OFFSET(z))
+ var/turf/above = GET_TURF_ABOVE(src)
+ if(above)
+ turfs_to_check += RANGE_TURFS(1, above)
+ var/turf/below = GET_TURF_BELOW(src)
+ if(below)
+ turfs_to_check += RANGE_TURFS(1, below)
+
+ for(var/turf/around as anything in turfs_to_check)
+ if(islava(around))
+ continue
+ border_turf = TRUE
+
+ if(!border_turf)
+ set_light(l_on = FALSE)
+ return
+
+ set_light(l_on = TRUE)
+
+/turf/open/lava/ChangeTurf(path, list/new_baseturfs, flags)
+ var/turf/result = ..()
+
+ if(result && !islava(result))
+ // We have gone from a lava turf to a non lava turf, time to let them know
+ var/list/turfs_to_check = RANGE_TURFS(1, result)
+ if(GET_LOWEST_STACK_OFFSET(z))
+ var/turf/above = GET_TURF_ABOVE(result)
+ if(above)
+ turfs_to_check += RANGE_TURFS(1, above)
+ var/turf/below = GET_TURF_BELOW(result)
+ if(below)
+ turfs_to_check += RANGE_TURFS(1, below)
+ for(var/turf/open/lava/inform in turfs_to_check)
+ inform.set_light(l_on = TRUE)
+
+ return result
+
+/turf/open/lava/smooth_icon()
+ . = ..()
+ mask_state = icon_state
+ update_appearance(~UPDATE_SMOOTHING)
+
/turf/open/lava/ex_act(severity, target)
contents_explosion(severity, target)
@@ -52,7 +127,7 @@
STOP_PROCESSING(SSobj, src)
/turf/open/lava/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- switch(the_rcd.mode)
+ switch(the_rcd.construction_mode)
if(RCD_FLOORWALL)
return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3)
return FALSE
@@ -61,7 +136,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, span_notice("You build a floor."))
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
@@ -163,9 +238,13 @@
name = "lava"
baseturfs = /turf/open/lava/smooth
icon = 'icons/turf/floors/lava.dmi'
- icon_state = "unsmooth"
- smooth = SMOOTH_MORE | SMOOTH_BORDER
- canSmoothWith = list(/turf/open/lava/smooth)
+ mask_icon = 'icons/turf/floors/lava_mask.dmi'
+ icon_state = "lava-255"
+ mask_state = "lava-255"
+ base_icon_state = "lava"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_LAVA
+ canSmoothWith = SMOOTH_GROUP_FLOOR_LAVA
/turf/open/lava/smooth/lava_land_surface
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm
new file mode 100644
index 000000000000..2bf2f2c87af0
--- /dev/null
+++ b/code/game/turfs/open/openspace.dm
@@ -0,0 +1,197 @@
+/turf/open/openspace
+ name = "open space"
+ desc = "Watch your step!"
+ // We don't actually draw openspace, but it needs to have color
+ // In its icon state so we can count it as a "non black" tile
+ icon_state = MAP_SWITCH("pure_white", "invisible")
+ baseturfs = /turf/open/openspace
+ overfloor_placed = FALSE
+ underfloor_accessibility = UNDERFLOOR_INTERACTABLE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ plane = TRANSPARENT_FLOOR_PLANE
+ var/can_cover_up = TRUE
+ var/can_build_on = TRUE
+
+/turf/open/openspace/airless
+ initial_gas_mix = AIRLESS_ATMOS
+
+/turf/open/openspace/airless/planetary
+ planetary_atmos = TRUE
+
+// Reminder, any behavior code written here needs to be duped to /turf/open/space/openspace
+// I am so sorry
+/turf/open/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker
+ . = ..()
+ if(PERFORM_ALL_TESTS(focus_only/openspace_clear) && !GET_TURF_BELOW(src))
+ stack_trace("[src] was inited as openspace with nothing below it at ([x], [y], [z])")
+ RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(on_atom_created))
+ var/area/our_area = loc
+ if(istype(our_area, /area/space))
+ force_no_gravity = TRUE
+ return INITIALIZE_HINT_LATELOAD
+
+/turf/open/openspace/LateInitialize()
+ . = ..()
+ AddElement(/datum/element/turf_z_transparency)
+
+/turf/open/openspace/ChangeTurf(path, list/new_baseturfs, flags)
+ UnregisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON)
+ return ..()
+
+/**
+ * Prepares a moving movable to be precipitated if Move() is successful.
+ * This is done in Enter() and not Entered() because there's no easy way to tell
+ * if the latter was called by Move() or forceMove() while the former is only called by Move().
+ */
+/turf/open/openspace/Enter(atom/movable/movable, atom/oldloc, no_side_effects = FALSE)
+ . = ..()
+ if(.)
+ //higher priority than CURRENTLY_Z_FALLING so the movable doesn't fall on Entered()
+ movable.set_currently_z_moving(CURRENTLY_Z_FALLING_FROM_MOVE)
+
+///Makes movables fall when forceMove()'d to this turf.
+/turf/open/openspace/Entered(atom/movable/movable)
+ . = ..()
+ if(movable.set_currently_z_moving(CURRENTLY_Z_FALLING))
+ zFall(movable, falling_from_move = TRUE)
+
+/**
+ * Drops movables spawned on this turf after they are successfully initialized.
+ * so that spawned movables that should fall to gravity, will fall.
+ */
+/turf/open/openspace/proc/on_atom_created(datum/source, atom/created_atom)
+ SIGNAL_HANDLER
+ if(ismovable(created_atom))
+ zfall_if_on_turf(created_atom)
+
+/turf/open/openspace/proc/zfall_if_on_turf(atom/movable/movable)
+ if(QDELETED(movable) || movable.loc != src)
+ return
+ zFall(movable)
+
+/turf/open/openspace/zAirIn()
+ return TRUE
+
+/turf/open/openspace/zAirOut()
+ return TRUE
+
+/turf/open/openspace/zPassIn(direction)
+ if(direction == DOWN)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_IN_DOWN)
+ return FALSE
+ return TRUE
+ if(direction == UP)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_IN_UP)
+ return FALSE
+ return TRUE
+ return FALSE
+
+/turf/open/openspace/zPassOut(direction)
+ if(direction == DOWN)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_OUT_DOWN)
+ return FALSE
+ return TRUE
+ if(direction == UP)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_OUT_UP)
+ return FALSE
+ return TRUE
+ return FALSE
+
+/turf/open/openspace/proc/CanCoverUp()
+ return can_cover_up
+
+/turf/open/openspace/proc/CanBuildHere()
+ return can_build_on
+
+/turf/open/openspace/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ switch(the_rcd.construction_mode)
+ if(RCD_FLOORWALL)
+ return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3)
+ return FALSE
+
+/turf/open/openspace/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
+ switch(passed_mode)
+ if(RCD_FLOORWALL)
+ to_chat(user, span_notice("You build a floor."))
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ return TRUE
+ return FALSE
+
+/turf/open/openspace/attackby(obj/item/C, mob/user, params)
+ ..()
+ if(!CanBuildHere())
+ return
+ if(istype(C, /obj/item/stack/rods))
+ build_with_rods(C, user)
+ if(istype(C, /obj/item/stack/tile/plasteel))
+ if(!CanCoverUp())
+ return
+ var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
+ if(L)
+ var/obj/item/stack/tile/plasteel/S = C
+ if(S.use(1))
+ qdel(L)
+ playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
+ to_chat(user, span_notice("You build a floor."))
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ else
+ to_chat(user, span_warning("You need one floor tile to build a floor!"))
+ else
+ to_chat(user, span_warning("The plating is going to need some support! Place metal rods first."))
+
+/turf/open/openspace/build_with_floor_tiles(obj/item/stack/tile/plasteel/used_tiles)
+ if(!CanCoverUp())
+ return
+ return ..()
+
+/turf/open/openspace/rust_heretic_act()
+ return FALSE
+
+/turf/open/openspace/replace_floor(turf/open/new_floor_path, flags)
+ if (!initial(new_floor_path.overfloor_placed))
+ ChangeTurf(new_floor_path, flags = flags)
+ return
+ // Create plating under tiled floor we try to create directly onto the air
+ place_on_top(/turf/open/floor/plating, flags = flags)
+ place_on_top(new_floor_path, flags = flags)
+
+/turf/open/openspace/can_cross_safely(atom/movable/crossing)
+ return HAS_TRAIT(crossing, TRAIT_MOVE_FLYING)
+
+/turf/open/openspace/icemoon
+ name = "ice chasm"
+ baseturfs = /turf/open/openspace/icemoon
+ initial_gas_mix = ICEMOON_DEFAULT_ATMOS
+ /// Replaces itself with replacement_turf if the turf has the no ruins allowed flag (usually ruins themselves)
+ var/protect_ruin = TRUE
+ /// The turf that will replace this one if the turf below has the no ruins allowed flag. we use this one so we don't get any potential double whammies
+ var/replacement_turf = /turf/open/floor/plating/asteroid/snow/icemoon/do_not_chasm
+ /// If true mineral turfs below this openspace turf will be mined automatically
+ var/drill_below = TRUE
+
+/turf/open/openspace/icemoon/Initialize(mapload)
+ . = ..()
+ var/turf/T = GET_TURF_BELOW(src)
+ //I wonder if I should error here
+ if(!T)
+ return
+ if(T.turf_flags & NO_RUINS && protect_ruin)
+ ChangeTurf(replacement_turf, null, CHANGETURF_IGNORE_AIR)
+ return
+ if(!ismineralturf(T) || !drill_below)
+ return
+ var/turf/closed/mineral/M = T
+ M.mineralAmt = 0
+ M.gets_drilled()
+ baseturfs = /turf/open/openspace/icemoon //This is to ensure that IF random turf generation produces a openturf, there won't be other turfs assigned other than openspace.
+
+/turf/open/openspace/icemoon/keep_below
+ drill_below = FALSE
+
+/turf/open/openspace/icemoon/ruins
+ protect_ruin = FALSE
+ drill_below = FALSE
diff --git a/code/game/turfs/simulated/reebe_void.dm b/code/game/turfs/open/reebe_void.dm
similarity index 92%
rename from code/game/turfs/simulated/reebe_void.dm
rename to code/game/turfs/open/reebe_void.dm
index f521c9a0364d..5c7a6917a801 100644
--- a/code/game/turfs/simulated/reebe_void.dm
+++ b/code/game/turfs/open/reebe_void.dm
@@ -36,7 +36,7 @@
else if(prob(5))
new /obj/structure/lattice/clockwork(src)
-/turf/open/indestructible/reebe_void/Enter(atom/movable/AM, atom/old_loc, no_side_effects = FALSE)
+/turf/open/indestructible/reebe_void/Enter(atom/movable/AM, no_side_effects = FALSE)
if(!..())
return FALSE
else
diff --git a/code/game/turfs/simulated/river.dm b/code/game/turfs/open/river.dm
similarity index 95%
rename from code/game/turfs/simulated/river.dm
rename to code/game/turfs/open/river.dm
index 4b7930d3fb9c..70d4bf50faa4 100644
--- a/code/game/turfs/simulated/river.dm
+++ b/code/game/turfs/open/river.dm
@@ -11,7 +11,7 @@
while(num_spawned < nodes && possible_locs.len)
var/turf/T = pick(possible_locs)
var/area/A = get_area(T)
- if(!istype(A, whitelist_area) || (T.flags_1 & NO_LAVA_GEN_1))
+ if(!istype(A, whitelist_area) || (T.turf_flags & NO_LAVA_GEN))
possible_locs -= T
else
river_nodes += new /obj/effect/landmark/river_waypoint(T)
@@ -48,7 +48,7 @@
cur_turf = get_step(cur_turf, cur_dir)
var/area/new_area = get_area(cur_turf)
- if(!istype(new_area, whitelist_area) || (cur_turf.flags_1 & NO_LAVA_GEN_1)) //Rivers will skip ruins
+ if(!istype(new_area, whitelist_area) || (cur_turf.turf_flags & NO_LAVA_GEN)) //Rivers will skip ruins
detouring = FALSE
cur_dir = get_dir(cur_turf, target_turf)
cur_turf = get_step(cur_turf, cur_dir)
@@ -79,7 +79,7 @@
continue
var/area/new_area = get_area(candidate)
- if((!istype(new_area, whitelisted_area) && whitelisted_area) || (candidate.flags_1 & NO_LAVA_GEN_1))
+ if((!istype(new_area, whitelisted_area) && whitelisted_area) || (candidate.turf_flags & NO_LAVA_GEN))
continue
if(!logged_turf_type && ismineralturf(candidate))
diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm
new file mode 100644
index 000000000000..f370afc3123d
--- /dev/null
+++ b/code/game/turfs/open/space/space.dm
@@ -0,0 +1,339 @@
+///The base color of light space emits
+GLOBAL_VAR_INIT(base_starlight_color, default_starlight_color())
+///The color of light space is currently emitting
+GLOBAL_VAR_INIT(starlight_color, default_starlight_color())
+/proc/default_starlight_color()
+ var/turf/open/space/read_from = /turf/open/space
+ return initial(read_from.light_color)
+
+///The range of the light space is displaying
+GLOBAL_VAR_INIT(starlight_range, default_starlight_range())
+/proc/default_starlight_range()
+ var/turf/open/space/read_from = /turf/open/space
+ return initial(read_from.light_range)
+
+///The power of the light space is throwin out
+GLOBAL_VAR_INIT(starlight_power, default_starlight_power())
+/proc/default_starlight_power()
+ var/turf/open/space/read_from = /turf/open/space
+ return initial(read_from.light_power)
+
+/proc/set_base_starlight(star_color = null, range = null, power = null)
+ GLOB.base_starlight_color = star_color
+ set_starlight(star_color, range, power)
+
+/proc/set_starlight(star_color = null, range = null, power = null)
+ if(isnull(star_color))
+ star_color = GLOB.starlight_color
+ var/old_star_color = GLOB.starlight_color
+ GLOB.starlight_color = star_color
+ // set light color on all lit turfs
+ for(var/turf/open/space/spess as anything in GLOB.starlight)
+ spess.set_light(l_range = range, l_power = power, l_color = star_color)
+
+ if(star_color == old_star_color)
+ return
+
+ // Update the base overlays
+ for(var/obj/light as anything in GLOB.starlight_objects)
+ light.color = star_color
+ // Send some signals that'll update everything that uses the color
+ SEND_GLOBAL_SIGNAL(COMSIG_STARLIGHT_COLOR_CHANGED, old_star_color, star_color)
+
+GLOBAL_LIST_EMPTY(starlight)
+
+/turf/open/space
+ icon = 'icons/turf/space.dmi'
+ icon_state = "0"
+ name = "\proper space"
+ overfloor_placed = FALSE
+ underfloor_accessibility = UNDERFLOOR_INTERACTABLE
+
+ initial_temperature = TCMB
+ thermal_conductivity = 0
+ heat_capacity = 700000
+ var/starlight_source_count = 0
+
+ var/destination_z
+ var/destination_x
+ var/destination_y
+
+ var/static/datum/gas_mixture/immutable/space/space_gas
+ // We do NOT want atmos adjacent turfs
+ //init_air = FALSE
+ //run_later = TRUE
+ plane = PLANE_SPACE
+ layer = SPACE_LAYER
+ light_power = 1
+ light_range = 2
+ light_color = COLOR_STARLIGHT
+ light_height = LIGHTING_HEIGHT_SPACE
+ light_on = FALSE
+ space_lit = TRUE
+ bullet_bounce_sound = null
+ vis_flags = VIS_INHERIT_ID //when this be added to vis_contents of something it be associated with something on clicking, important for visualisation of turf in openspace and interraction with openspace that show you turf.
+
+ force_no_gravity = TRUE
+
+ FASTDMM_PROP(\
+ pipe_astar_cost = 3\
+ )
+
+/turf/open/space/basic/New() //Do not convert to Initialize
+ SHOULD_CALL_PARENT(FALSE)
+ //This is used to optimize the map loader
+ return
+
+/turf/open/space/Destroy()
+ GLOB.starlight -= src
+ return ..()
+
+//ATTACK GHOST IGNORING PARENT RETURN VALUE
+/turf/open/space/attack_ghost(mob/dead/observer/user)
+ if(destination_z)
+ var/turf/T = locate(destination_x, destination_y, destination_z)
+ user.forceMove(T)
+
+/turf/open/space/Initalize_Atmos(times_fired)
+ return
+
+/turf/open/space/TakeTemperature(temp)
+
+/turf/open/space/RemoveLattice()
+ return
+
+/turf/open/space/AfterChange()
+ ..()
+ atmos_overlay_types = null
+
+/turf/open/space/Assimilate_Air()
+ return
+
+//IT SHOULD RETURN NULL YOU MONKEY, WHY IN TARNATION WHAT THE FUCKING FUCK
+/turf/open/space/remove_air(amount)
+ return null
+
+/turf/open/space/remove_air_ratio(amount)
+ return null
+
+//IT SHOULD RETURN NULL YOU MONKEY, WHY IN TARNATION WHAT THE FUCKING FUCK
+/turf/open/space/remove_air(amount)
+ return null
+
+/// Updates starlight. Called when we're unsure of a turf's starlight state
+/// Returns TRUE if we succeed, FALSE otherwise
+/turf/open/space/proc/update_starlight()
+ for(var/t in RANGE_TURFS(1, src)) //RANGE_TURFS is in code\__HELPERS\game.dm
+ // I've got a lot of cordons near spaceturfs, be good kids
+ if(isspaceturf(t) || istype(t, /turf/cordon))
+ //let's NOT update this that much pls
+ continue
+ enable_starlight()
+ return TRUE
+ GLOB.starlight -= src
+ set_light(l_on = FALSE)
+ return FALSE
+
+/// Turns on the stars, if they aren't already
+/turf/open/space/proc/enable_starlight()
+ if(!light_on)
+ set_light(l_on = TRUE, l_range = GLOB.starlight_range, l_power = GLOB.starlight_power, l_color = GLOB.starlight_color)
+ GLOB.starlight += src
+
+/turf/open/space/attack_paw(mob/user, list/modifiers)
+ return attack_hand(user, modifiers)
+
+/turf/open/space/proc/CanBuildHere()
+ if(destination_z)
+ return FALSE
+ return TRUE
+
+/turf/open/space/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ switch(the_rcd.construction_mode)
+ if(RCD_FLOORWALL)
+ return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3)
+ return FALSE
+
+/turf/open/space/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
+ switch(passed_mode)
+ if(RCD_FLOORWALL)
+ to_chat(user, span_notice("You build a floor."))
+ place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ return TRUE
+ return FALSE
+
+/turf/open/space/handle_slip()
+ return
+
+/turf/open/space/attackby(obj/item/C, mob/user, params)
+ ..()
+ if(!CanBuildHere())
+ return
+ if(istype(C, /obj/item/stack/rods))
+ build_with_rods(C, user)
+ else if(istype(C, /obj/item/stack/tile/plasteel))
+ build_with_floor_tiles(C, user)
+
+
+/turf/open/space/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ . = ..()
+ if(!arrived || src != arrived.loc)
+ return
+
+ if(destination_z && destination_x && destination_y && !arrived.pulledby && !arrived.currently_z_moving)
+ var/tx = destination_x
+ var/ty = destination_y
+ var/turf/DT = locate(tx, ty, destination_z)
+ var/itercount = 0
+ while(DT.density || istype(DT.loc,/area/shuttle)) // Extend towards the center of the map, trying to look for a better place to arrive
+ if (itercount++ >= 100)
+ log_game("SPACE Z-TRANSIT ERROR: Could not find a safe place to land [arrived] within 100 iterations.")
+ break
+ if (tx < 128)
+ tx++
+ else
+ tx--
+ if (ty < 128)
+ ty++
+ else
+ ty--
+ DT = locate(tx, ty, destination_z)
+
+ arrived.zMove(null, DT, ZMOVE_ALLOW_BUCKLED)
+
+ var/atom/movable/current_pull = arrived.pulling
+ while (current_pull)
+ var/turf/target_turf = get_step(current_pull.pulledby.loc, REVERSE_DIR(current_pull.pulledby.dir)) || current_pull.pulledby.loc
+ current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED)
+ current_pull = current_pull.pulling
+
+
+/turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
+ return
+
+/turf/open/space/singularity_act()
+ return
+
+/turf/open/space/can_have_cabling()
+ if(locate(/obj/structure/lattice/catwalk, src))
+ return TRUE
+ return FALSE
+
+/turf/open/space/is_transition_turf()
+ if(destination_x || destination_y || destination_z)
+ return TRUE
+
+
+/turf/open/space/acid_act(acidpwr, acid_volume)
+ return FALSE
+
+/turf/open/space/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ generate_space_underlay(underlay_appearance, asking_turf)
+ return TRUE
+
+/turf/open/space/rust_heretic_act()
+ return FALSE
+
+/turf/open/space/attempt_lattice_replacement()
+ var/dest_x = destination_x
+ var/dest_y = destination_y
+ var/dest_z = destination_z
+ ..()
+ destination_x = dest_x
+ destination_y = dest_y
+ destination_z = dest_z
+
+/turf/open/space/can_cross_safely(atom/movable/crossing)
+ return HAS_TRAIT(crossing, TRAIT_SPACEWALK)
+
+/turf/open/space/openspace
+ icon = 'icons/turf/floors.dmi'
+ icon_state = MAP_SWITCH("pure_white", "transparent")
+ plane = TRANSPARENT_FLOOR_PLANE
+
+/turf/open/space/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker
+ . = ..()
+ if(PERFORM_ALL_TESTS(focus_only/openspace_clear) && !GET_TURF_BELOW(src))
+ stack_trace("[src] was inited as openspace with nothing below it at ([x], [y], [z])")
+ icon_state = "pure_white"
+ // We make the assumption that the space plane will never be blacklisted, as an optimization
+ if(SSmapping.max_plane_offset)
+ plane = TRANSPARENT_FLOOR_PLANE - (PLANE_RANGE * SSmapping.z_level_to_plane_offset[z])
+ return INITIALIZE_HINT_LATELOAD
+
+/turf/open/space/openspace/LateInitialize()
+ . = ..()
+ AddElement(/datum/element/turf_z_transparency)
+
+/turf/open/space/openspace/Destroy()
+ // Signals persist through destroy, GO HOME
+ var/turf/below = GET_TURF_BELOW(src)
+ if(below)
+ UnregisterSignal(below, COMSIG_TURF_CHANGE)
+ return ..()
+
+/turf/open/space/openspace/zAirIn()
+ return TRUE
+
+/turf/open/space/openspace/zAirOut()
+ return TRUE
+
+/turf/open/space/openspace/zPassIn(direction)
+ if(direction == DOWN)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_IN_DOWN)
+ return FALSE
+ return TRUE
+ if(direction == UP)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_IN_UP)
+ return FALSE
+ return TRUE
+ return FALSE
+
+/turf/open/space/openspace/zPassOut(direction)
+ if(direction == DOWN)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_OUT_DOWN)
+ return FALSE
+ return TRUE
+ if(direction == UP)
+ for(var/obj/contained_object in contents)
+ if(contained_object.obj_flags & BLOCK_Z_OUT_UP)
+ return FALSE
+ return TRUE
+ return FALSE
+
+/turf/open/space/openspace/enable_starlight()
+ var/turf/below = GET_TURF_BELOW(src)
+ // Override = TRUE beacuse we could have our starlight updated many times without a failure, which'd trigger this
+ RegisterSignal(below, COMSIG_TURF_CHANGE, PROC_REF(on_below_change), override = TRUE)
+ if(!isspaceturf(below) || light_on)
+ return
+ set_light(l_on = TRUE, l_range = GLOB.starlight_range, l_power = GLOB.starlight_power, l_color = GLOB.starlight_color)
+ GLOB.starlight += src
+
+/turf/open/space/openspace/update_starlight()
+ . = ..()
+ if(.)
+ return
+ // If we're here, the starlight is not to be
+ var/turf/below = GET_TURF_BELOW(src)
+ UnregisterSignal(below, COMSIG_TURF_CHANGE)
+
+/turf/open/space/openspace/proc/on_below_change(turf/source, path, list/new_baseturfs, flags, list/post_change_callbacks)
+ SIGNAL_HANDLER
+ if(isspaceturf(source) && !ispath(path, /turf/open/space))
+ GLOB.starlight += src
+ set_light(l_on = TRUE, l_range = GLOB.starlight_range, l_power = GLOB.starlight_power, l_color = GLOB.starlight_color)
+ else if(!isspaceturf(source) && ispath(path, /turf/open/space))
+ GLOB.starlight -= src
+ set_light(l_on = FALSE)
+
+/turf/open/space/replace_floor(turf/open/new_floor_path, flags)
+ if (!initial(new_floor_path.overfloor_placed))
+ ChangeTurf(new_floor_path, flags = flags)
+ return
+ // Create plating under tiled floor we try to create directly onto space
+ place_on_top(/turf/open/floor/plating, flags = flags)
+ place_on_top(new_floor_path, flags = flags)
diff --git a/code/game/turfs/open/space/space_EXPENSIVE.dm b/code/game/turfs/open/space/space_EXPENSIVE.dm
new file mode 100644
index 000000000000..393439b3d919
--- /dev/null
+++ b/code/game/turfs/open/space/space_EXPENSIVE.dm
@@ -0,0 +1,49 @@
+/**
+ * Space Initialize
+ *
+ * Doesn't call parent, see [/atom/proc/Initialize].
+ * When adding new stuff to /atom/Initialize, /turf/Initialize, etc
+ * don't just add it here unless space actually needs it.
+ *
+ * There is a lot of work that is intentionally not done because it is not currently used.
+ * This includes stuff like smoothing, blocking camera visibility, etc.
+ * If you are facing some odd bug with specifically space, check if it's something that was
+ * intentionally ommitted from this implementation.
+ */
+/turf/open/space/Initialize(mapload)
+ SHOULD_CALL_PARENT(FALSE)
+ icon_state = SPACE_ICON_STATE
+ if(!space_gas)
+ space_gas = new
+ air = space_gas
+ update_air_ref(0)
+
+ if (PERFORM_ALL_TESTS(focus_only/multiple_space_initialization))
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
+
+ // We make the assumption that the space plane will never be blacklisted, as an optimization
+ if(SSmapping.max_plane_offset)
+ plane = PLANE_SPACE - (PLANE_RANGE * SSmapping.z_level_to_plane_offset[z])
+
+ var/area/our_area = loc
+ if(!our_area.area_has_base_lighting && space_lit) //Only provide your own lighting if the area doesn't for you
+ // Intentionally not add_overlay for performance reasons.
+ // add_overlay does a bunch of generic stuff, like creating a new list for overlays,
+ // queueing compile, cloning appearance, etc etc etc that is not necessary here.
+ overlays += GLOB.starlight_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
+
+ if (!mapload)
+ // if(requires_activation) //yog code we don't use this
+ // SSair.add_to_active(src, TRUE)
+
+ if(SSmapping.max_plane_offset)
+ var/turf/T = GET_TURF_ABOVE(src)
+ if(T)
+ T.multiz_turf_new(src, DOWN)
+ T = GET_TURF_BELOW(src)
+ if(T)
+ T.multiz_turf_new(src, UP)
+
+ return INITIALIZE_HINT_NORMAL
diff --git a/code/game/turfs/space/transit.dm b/code/game/turfs/open/space/transit.dm
similarity index 63%
rename from code/game/turfs/space/transit.dm
rename to code/game/turfs/open/space/transit.dm
index 39d0c255272a..fa5ce4bf686d 100644
--- a/code/game/turfs/space/transit.dm
+++ b/code/game/turfs/open/space/transit.dm
@@ -1,19 +1,23 @@
/turf/open/space/transit
- name = "\proper hyperspace"
+ name = "\proper bluespace"
+ desc = "What is this, light-speed? We need to go to plaid speed!" // spaceballs was a great movie
icon_state = "black"
dir = SOUTH
baseturfs = /turf/open/space/transit
- flags_1 = NOJAUNT_1 //This line goes out to every wizard that ever managed to escape the den. I'm sorry.
+ turf_flags = NOJAUNT //This line goes out to every wizard that ever managed to escape the den. I'm sorry.
explosion_block = INFINITY
/turf/open/space/transit/Initialize(mapload)
. = ..()
- update_appearance(UPDATE_ICON)
+ 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)
@@ -21,19 +25,46 @@
underlay_appearance.icon_state = "speedspace_ns_[get_transit_state(asking_turf)]"
underlay_appearance.transform = turn(matrix(), get_transit_angle(asking_turf))
-/turf/open/space/transit/Entered(atom/movable/AM, atom/OldLoc)
- ..()
+/turf/open/space/transit/update_icon()
+ . = ..()
+ transform = turn(matrix(), get_transit_angle(src))
+
+/turf/open/space/transit/update_icon_state()
+ icon_state = "speedspace_ns_[get_transit_state(src)]"
+ return ..()
+
+/turf/open/space/transit/proc/initialize_drifting(atom/entered, atom/movable/enterer)
+ SIGNAL_HANDLER
+
if(!locate(/obj/structure/lattice) in src)
- throw_atom(AM)
+ dump_in_space(enterer)
+
+/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)
+ . = ..()
+
+ var/turf/location = gone.loc
+ if(istype(location, /turf/open/space) && !istype(location, src.type))//they got forced out of transit area into default space tiles
+ dump_in_space(gone) //launch them into game space, away from transitspace
///Get rid of all our contents, called when our reservation is released (which in our case means the shuttle arrived)
/turf/open/space/transit/proc/launch_contents(datum/turf_reservation/reservation)
SIGNAL_HANDLER
for(var/atom/movable/movable in contents)
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(throw_atom), movable)
+ dump_in_space(movable)
+
+///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
-/proc/throw_atom(atom/movable/dumpee)
var/max = world.maxx-TRANSITIONEDGE
var/min = 1+TRANSITIONEDGE
@@ -67,11 +98,6 @@
/turf/open/space/transit/east
dir = EAST
-/turf/open/space/transit/update_icon_state()
- . = ..()
- icon_state = "speedspace_ns_[get_transit_state(src)]"
- transform = turn(matrix(), get_transit_angle(src))
-
/proc/get_transit_state(turf/T)
var/p = 9
. = 1
diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/open/water.dm
similarity index 95%
rename from code/game/turfs/simulated/water.dm
rename to code/game/turfs/open/water.dm
index 95d6e8e5587e..b8f5d39b4570 100644
--- a/code/game/turfs/simulated/water.dm
+++ b/code/game/turfs/open/water.dm
@@ -10,7 +10,7 @@
slowdown = 1
bullet_sizzle = TRUE
bullet_bounce_sound = null //needs a splashing sound one day.
- flags_1 = NO_RUST | CAN_BE_DIRTY_1
+ turf_flags = NO_RUST
footstep = FOOTSTEP_WATER
barefootstep = FOOTSTEP_WATER
diff --git a/code/game/turfs/openspace/openspace.dm b/code/game/turfs/openspace/openspace.dm
deleted file mode 100644
index 5a06c4bc2234..000000000000
--- a/code/game/turfs/openspace/openspace.dm
+++ /dev/null
@@ -1,119 +0,0 @@
-/turf/open/openspace
- name = "open space"
- desc = "Watch your step!"
- icon_state = "grey"
- baseturfs = /turf/open/openspace
- CanAtmosPassVertical = ATMOS_PASS_YES
- flags_1 = NO_RUST
- //mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- var/can_cover_up = TRUE
- var/can_build_on = TRUE
-
-/turf/open/openspace/debug/update_multiz()
- ..()
- return TRUE
-
-/turf/open/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker
- . = ..()
- plane = FLOOR_OPENSPACE_PLANE
- layer = OPENSPACE_LAYER
- return INITIALIZE_HINT_LATELOAD
-
-/turf/open/openspace/LateInitialize()
- update_multiz(TRUE, TRUE)
-
-/turf/open/openspace/Destroy()
- vis_contents.len = 0
- return ..()
-
-/turf/open/openspace/update_multiz(prune_on_fail = FALSE, init = FALSE)
- . = ..()
- var/turf/T = below()
- if(!T)
- vis_contents.len = 0
- if(prune_on_fail)
- ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
- return FALSE
- if(init)
- vis_contents += T
- return TRUE
-
-/turf/open/openspace/multiz_turf_del(turf/T, dir)
- if(dir != DOWN)
- return
- update_multiz()
-
-/turf/open/openspace/multiz_turf_new(turf/T, dir)
- if(dir != DOWN)
- return
- update_multiz()
-
-/turf/open/openspace/zAirIn()
- return TRUE
-
-/turf/open/openspace/zAirOut()
- return TRUE
-
-/turf/open/openspace/zPassIn(atom/movable/A, direction, turf/source)
- return TRUE
-
-/turf/open/openspace/zPassOut(atom/movable/A, direction, turf/destination)
- return TRUE
-
-/turf/open/openspace/proc/CanCoverUp()
- return can_cover_up
-
-/turf/open/openspace/proc/CanBuildHere()
- return can_build_on
-
-/turf/open/openspace/attackby(obj/item/C, mob/user, params)
- ..()
- if(!CanBuildHere())
- return
- if(istype(C, /obj/item/stack/rods))
- var/obj/item/stack/rods/R = C
- var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
- var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src)
- if(W)
- to_chat(user, span_warning("There is already a catwalk here!"))
- return
- if(L)
- if(R.use(1))
- to_chat(user, span_notice("You construct a catwalk."))
- playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
- new/obj/structure/lattice/catwalk(src)
- else
- to_chat(user, span_warning("You need two rods to build a catwalk!"))
- return
- if(R.use(1))
- to_chat(user, span_notice("You construct a lattice."))
- playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
- ReplaceWithLattice()
- else
- to_chat(user, span_warning("You need one rod to build a lattice."))
- return
- if(istype(C, /obj/item/stack/tile/plasteel))
- if(!CanCoverUp())
- return
- var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
- if(L)
- var/obj/item/stack/tile/plasteel/S = C
- if(S.use(1))
- qdel(L)
- playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
- to_chat(user, span_notice("You build a floor."))
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
- else
- to_chat(user, span_warning("You need one floor tile to build a floor!"))
- else
- to_chat(user, span_warning("The plating is going to need some support! Place metal rods first."))
-
-/turf/open/openspace/icemoon
- name = "ice chasm"
- baseturfs = /turf/open/openspace/icemoon
- can_cover_up = FALSE
- can_build_on = FALSE
- initial_gas_mix = ICEMOON_DEFAULT_ATMOS
-
-/turf/open/openspace/icemoon/can_zFall(atom/movable/A, levels = 1, turf/target)
- return TRUE
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
deleted file mode 100644
index 253cf793e805..000000000000
--- a/code/game/turfs/space/space.dm
+++ /dev/null
@@ -1,240 +0,0 @@
-/turf/open/space
- icon = 'icons/turf/space.dmi'
- icon_state = "0"
- name = "\proper space"
- intact = 0
-
- initial_temperature = TCMB
- thermal_conductivity = 0
- heat_capacity = 700000
-
- FASTDMM_PROP(\
- pipe_astar_cost = 3\
- )
-
- var/destination_z
- var/destination_x
- var/destination_y
-
- var/global/datum/gas_mixture/immutable/space/space_gas
- plane = PLANE_SPACE
- layer = SPACE_LAYER
- light_power = 0.25
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
- bullet_bounce_sound = null
-
-/turf/open/space/basic/New() //Do not convert to Initialize
- //This is used to optimize the map loader
- return
-
-/turf/open/space/Initialize(mapload)
- SHOULD_CALL_PARENT(FALSE)
- icon_state = SPACE_ICON_STATE
- if(!space_gas)
- space_gas = new
- air = space_gas
- update_air_ref(0)
- vis_contents.Cut() //removes inherited overlays
- visibilityChanged()
-
- if (PERFORM_ALL_TESTS(focus_only/multiple_space_initialization))
- if(flags_1 & INITIALIZED_1)
- stack_trace("Warning: [src]([type]) initialized multiple times!")
- flags_1 |= INITIALIZED_1
-
- var/area/A = loc
- if(!IS_DYNAMIC_LIGHTING(src) && IS_DYNAMIC_LIGHTING(A))
- add_overlay(/obj/effect/fullbright)
-
- if (light_system == STATIC_LIGHT && light_power && light_range)
- update_light()
-
- if (opacity)
- directional_opacity = ALL_CARDINALS
-
- return INITIALIZE_HINT_NORMAL
-
-//ATTACK GHOST IGNORING PARENT RETURN VALUE
-/turf/open/space/attack_ghost(mob/dead/observer/user)
- if(destination_z)
- var/turf/T = locate(destination_x, destination_y, destination_z)
- user.forceMove(T)
-
-/turf/open/space/Initalize_Atmos(times_fired)
- return
-
-/turf/open/space/TakeTemperature(temp)
-
-/turf/open/space/RemoveLattice()
- return
-
-/turf/open/space/AfterChange()
- ..()
- atmos_overlay_types = null
-
-/turf/open/space/Assimilate_Air()
- return
-
-//IT SHOULD RETURN NULL YOU MONKEY, WHY IN TARNATION WHAT THE FUCKING FUCK
-/turf/open/space/remove_air(amount)
- return null
-
-/turf/open/space/remove_air_ratio(amount)
- return null
-
-//IT SHOULD RETURN NULL YOU MONKEY, WHY IN TARNATION WHAT THE FUCKING FUCK
-/turf/open/space/remove_air(amount)
- return null
-
-/turf/open/space/proc/update_starlight()
- if(CONFIG_GET(flag/starlight))
- for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm
- if(isspaceturf(t))
- //let's NOT update this that much pls
- continue
- set_light(2)
- return
- set_light(0)
-
-/turf/open/space/attack_paw(mob/user)
- return attack_hand(user)
-
-/turf/open/space/proc/CanBuildHere()
- return TRUE
-
-/turf/open/space/handle_slip()
- return
-
-/turf/open/space/attackby(obj/item/C, mob/user, params)
- ..()
- if(!CanBuildHere())
- return
- if(istype(C, /obj/item/stack/rods))
- var/obj/item/stack/rods/R = C
- var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
- var/obj/structure/lattice/catwalk/W = locate(/obj/structure/lattice/catwalk, src)
- if(W)
- to_chat(user, span_warning("There is already a catwalk here!"))
- return
- if(L)
- if(R.use(1))
- to_chat(user, span_notice("You construct a catwalk."))
- playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
- new/obj/structure/lattice/catwalk(src)
- else
- to_chat(user, span_warning("You need one rod to build a catwalk!"))
- return
- if(R.use(1))
- to_chat(user, span_notice("You construct a lattice."))
- playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
- ReplaceWithLattice()
- else
- to_chat(user, span_warning("You need one rod to build a lattice."))
- return
- if(istype(C, /obj/item/stack/tile/plasteel))
- var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
- if(L)
- var/obj/item/stack/tile/plasteel/S = C
- if(S.use(1))
- qdel(L)
- playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
- to_chat(user, span_notice("You build a floor."))
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
- else
- to_chat(user, span_warning("You need one floor tile to build a floor!"))
- else
- to_chat(user, span_warning("The plating is going to need some support! Place metal rods first."))
-
-/turf/open/space/Entered(atom/movable/A)
- ..()
- if ((!(A) || src != A.loc))
- return
-
- if(destination_z && destination_x && destination_y && !(A.pulledby || !A.can_be_z_moved))
- var/tx = destination_x
- var/ty = destination_y
- var/turf/DT = locate(tx, ty, destination_z)
- var/itercount = 0
- while(DT.density || istype(DT.loc,/area/shuttle)) // Extend towards the center of the map, trying to look for a better place to arrive
- if (itercount++ >= 100)
- log_game("SPACE Z-TRANSIT ERROR: Could not find a safe place to land [A] within 100 iterations.")
- break
- if (tx < 128)
- tx++
- else
- tx--
- if (ty < 128)
- ty++
- else
- ty--
- DT = locate(tx, ty, destination_z)
-
- var/atom/movable/AM = A.pulling
- A.forceMove(DT)
- if(AM)
- var/turf/T = get_step(A.loc,turn(A.dir, 180))
- AM.can_be_z_moved = FALSE
- AM.forceMove(T)
- A.start_pulling(AM)
- AM.can_be_z_moved = TRUE
-
- //now we're on the new z_level, proceed the space drifting
- stoplag()//Let a diagonal move finish, if necessary
- A.newtonian_move(A.inertia_dir)
-
-
-/turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
- return
-
-/turf/open/space/singularity_act()
- return
-
-/turf/open/space/can_have_cabling()
- if(locate(/obj/structure/lattice/catwalk, src))
- return 1
- return 0
-
-/turf/open/space/is_transition_turf()
- if(destination_x || destination_y || destination_z)
- return 1
-
-
-/turf/open/space/acid_act(acidpwr, acid_volume)
- return 0
-
-/turf/open/space/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/space.dmi'
- underlay_appearance.icon_state = SPACE_ICON_STATE
- underlay_appearance.plane = PLANE_SPACE
- return TRUE
-
-
-/turf/open/space/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- if(!CanBuildHere())
- return FALSE
-
- switch(the_rcd.mode)
- if(RCD_FLOORWALL)
- var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
- if(L)
- return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 1)
- else
- return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3)
- return FALSE
-
-/turf/open/space/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
- switch(passed_mode)
- if(RCD_FLOORWALL)
- to_chat(user, span_notice("You build a floor."))
- PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
- return TRUE
- return FALSE
-
-/turf/open/space/ReplaceWithLattice()
- var/dest_x = destination_x
- var/dest_y = destination_y
- var/dest_z = destination_z
- ..()
- destination_x = dest_x
- destination_y = dest_y
- destination_z = dest_z
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
old mode 100755
new mode 100644
index 0d676cd092d3..48ea42c20dd1
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -1,14 +1,15 @@
GLOBAL_LIST_EMPTY(station_turfs)
+/// Any floor or wall. What makes up the station and the rest of the map.
/turf
icon = 'icons/turf/floors.dmi'
- level = 1
+ vis_flags = VIS_INHERIT_ID // Important for interaction with and visualization of openspace.
luminosity = 1
-
- var/dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
-
- var/intact = 1
-
+ light_height = LIGHTING_HEIGHT_FLOOR
+
+ /// Turf bitflags, see code/__DEFINES/flags.dm
+ var/turf_flags = NONE
+
// baseturfs can be either a list or a single turf type.
// In class definition like here it should always be a single type.
// A list will be created in initialization that figures out the baseturf's baseturf etc.
@@ -17,20 +18,28 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/list/baseturfs = /turf/baseturf_bottom
var/initial_temperature = T20C
- var/to_be_destroyed = 0 //Used for fire, if a melting temperature was reached, it will be destroyed
- var/max_fire_temperature_sustained = 0 //The max temperature of the fire which it was subjected to
+ ///Used for fire, if a melting temperature was reached, it will be destroyed
+ var/to_be_destroyed = 0
+ ///The max temperature of the fire which it was subjected to
+ var/max_fire_temperature_sustained = 0
+ /// Does this turf block air from existing on it
var/blocks_air = FALSE
+
+ /// If there's a tile over a basic floor that can be ripped out
+ var/overfloor_placed = FALSE
+ /// How accessible underfloor pieces such as wires, pipes, etc are on this turf. Can be HIDDEN, VISIBLE, or INTERACTABLE.
+ var/underfloor_accessibility = UNDERFLOOR_HIDDEN
- flags_1 = CAN_BE_DIRTY_1
+ /// If there is a lattice underneat this turf. Used for the attempt_lattice_replacement proc to determine if it should place lattice.
+ var/lattice_underneath = TRUE
var/list/image/blueprint_data //for the station blueprints, images of objects eg: pipes
-
+
var/explosion_level = 0 //for preventing explosion dodging
- var/explosion_id = 0
var/list/explosion_throw_details
- var/requires_activation //add to air processing after initialize?
+ var/requires_activation //add to air processing after initialize?
var/changing_turf = FALSE
var/bullet_bounce_sound = 'sound/weapons/bulletremove.ogg' //sound played when a shell casing is ejected ontop of the turf.
@@ -39,9 +48,16 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/tiled_dirt = FALSE // use smooth tiled dirt decal
+ ///Icon-smoothing variable to map a diagonal wall corner with a fixed underlay.
+ var/list/fixed_underlay = null
+
///Lumcount added by sources other than lighting datum objects, such as the overlay lighting component.
var/dynamic_lumcount = 0
+ ///Bool, whether this turf will always be illuminated no matter what area it is in
+ ///Makes it look blue, be warned
+ var/space_lit = FALSE
+
var/tmp/lighting_corners_initialised = FALSE
///Our lighting object.
@@ -52,11 +68,39 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/tmp/datum/lighting_corner/lighting_corner_SW
var/tmp/datum/lighting_corner/lighting_corner_NW
+
///Which directions does this turf block the vision of, taking into account both the turf's opacity and the movable opacity_sources.
var/directional_opacity = NONE
///Lazylist of movable atoms providing opacity sources.
var/list/atom/movable/opacity_sources
+ ///the holodeck can load onto this turf if TRUE
+ var/holodeck_compatible = FALSE
+
+ /// If this turf contained an RCD'able object (or IS one, for walls)
+ /// but is now destroyed, this will preserve the value.
+ /// See __DEFINES/construction.dm for RCD_MEMORY_*.
+ var/rcd_memory
+ ///whether or not this turf forces movables on it to have no gravity (unless they themselves have forced gravity)
+ var/force_no_gravity = FALSE
+
+ /// How pathing algorithm will check if this turf is passable by itself (not including content checks). By default it's just density check.
+ /// WARNING: Currently to use a density shortcircuiting this does not support dense turfs with special allow through function
+ var/pathing_pass_method = TURF_PATHING_PASS_DENSITY
+
+#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM)
+ /// For the area_contents list unit test
+ /// Allows us to know our area without needing to preassign it
+ /// Sorry for the mess
+ var/area/in_contents_of
+#endif
+ /// How much explosive resistance this turf is providing to itself
+ /// Defaults to -1, interpreted as initial(explosive_resistance)
+ /// This is an optimization to prevent turfs from needing to set these on init
+ /// This would either be expensive, or impossible to manage. Let's just avoid it yes?
+ /// Never directly access this, use get_explosive_block() instead
+ var/inherent_explosive_resistance = -1
+
/turf/vv_edit_var(var_name, new_value)
var/static/list/banned_edits = list("x", "y", "z")
if(var_name in banned_edits)
@@ -69,40 +113,51 @@ GLOBAL_LIST_EMPTY(station_turfs)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
- // by default, vis_contents is inherited from the turf that was here before
- vis_contents.Cut()
+ /// We do NOT use the shortcut here, because this is faster
+ if(SSmapping.max_plane_offset)
+ if(!SSmapping.plane_offset_blacklist["[plane]"])
+ plane = plane - (PLANE_RANGE * SSmapping.z_level_to_plane_offset[z])
+
+ var/turf/T = GET_TURF_ABOVE(src)
+ if(T)
+ T.multiz_turf_new(src, DOWN)
+ T = GET_TURF_BELOW(src)
+ if(T)
+ T.multiz_turf_new(src, UP)
+
+ // by default, vis_contents is inherited from the turf that was here before.
+ // Checking length(vis_contents) in a proc this hot has huge wins for performance.
+ if (length(vis_contents))
+ vis_contents.Cut()
assemble_baseturfs()
levelupdate()
- if(smooth)
- queue_smooth(src)
+
+ SETUP_SMOOTHING()
+
+ if (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH(src)
+ QUEUE_SMOOTH_NEIGHBORS(src) //Yog code because there are some templates we load right into the map
+
visibilityChanged()
- for(var/atom/movable/AM in src)
- Entered(AM)
+ for(var/atom/movable/content as anything in src)
+ Entered(content, null)
- var/area/A = loc
- if(!IS_DYNAMIC_LIGHTING(src) && IS_DYNAMIC_LIGHTING(A))
- add_overlay(/obj/effect/fullbright)
+ var/area/our_area = loc
+ if(!our_area.area_has_base_lighting && space_lit) //Only provide your own lighting if the area doesn't for you
+ add_overlay(GLOB.starlight_overlays[GET_TURF_PLANE_OFFSET(src) + 1])
if(requires_activation)
- ImmediateCalculateAdjacentTurfs()
+ immediate_calculate_adjacent_turfs()
if(color)
add_atom_colour(color, FIXED_COLOUR_PRIORITY)
- if (light_system == STATIC_LIGHT && light_power && light_range)
+
+ if(light_power && light_range)
update_light()
- var/turf/T = SSmapping.get_turf_above(src)
- if(T)
- T.multiz_turf_new(src, DOWN)
- SEND_SIGNAL(T, COMSIG_TURF_MULTIZ_NEW, src, DOWN)
- T = SSmapping.get_turf_below(src)
- if(T)
- T.multiz_turf_new(src, UP)
- SEND_SIGNAL(T, COMSIG_TURF_MULTIZ_NEW, src, UP)
-
if (opacity)
directional_opacity = ALL_CARDINALS
@@ -113,8 +168,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
update_air_ref(-1)
__auxtools_update_turf_temp_info(isspaceturf(get_z_base_turf()))
- if(color)
- add_atom_colour(color, FIXED_COLOUR_PRIORITY)
return INITIALIZE_HINT_NORMAL
/turf/proc/__auxtools_update_turf_temp_info()
@@ -126,34 +179,35 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/set_temperature()
/turf/proc/Initalize_Atmos(times_fired)
- ImmediateCalculateAdjacentTurfs()
+ immediate_calculate_adjacent_turfs()
/turf/Destroy(force)
. = QDEL_HINT_IWILLGC
if(!changing_turf)
stack_trace("Incorrect turf deletion")
changing_turf = FALSE
- var/turf/T = SSmapping.get_turf_above(src)
- if(T)
- T.multiz_turf_del(src, DOWN)
- T = SSmapping.get_turf_below(src)
- if(T)
- T.multiz_turf_del(src, UP)
+ if(GET_LOWEST_STACK_OFFSET(z))
+ var/turf/T = GET_TURF_ABOVE(src)
+ if(T)
+ T.multiz_turf_del(src, DOWN)
+ T = GET_TURF_BELOW(src)
+ if(T)
+ T.multiz_turf_del(src, UP)
if(force)
..()
//this will completely wipe turf state
var/turf/B = new world.turf(src)
for(var/A in B.contents)
qdel(A)
- for(var/I in B.vars)
- B.vars[I] = null
return
- visibilityChanged()
- QDEL_LIST(blueprint_data)
+ LAZYCLEARLIST(blueprint_data)
flags_1 &= ~INITIALIZED_1
requires_activation = FALSE
..()
+ if(length(vis_contents))
+ vis_contents.Cut()
+
/// WARNING WARNING
/// Turfs DO NOT lose their signals when they get replaced, REMEMBER THIS
/// It's possible because turfs are fucked, and if you have one in a list and it's replaced with another one, the list ref points to the new turf
@@ -169,9 +223,28 @@ GLOBAL_LIST_EMPTY(station_turfs)
return
user.Move_Pulled(src)
+/turf/proc/change_area(area/old_area, area/new_area)
+ //dont waste our time
+ if(old_area == new_area)
+ return
+
+ //move the turf
+ old_area.turfs_to_uncontain += src
+ new_area.contained_turfs += src
+ new_area.contents += src
+
+ //changes to make after turf has moved
+ on_change_area(old_area, new_area)
+
+/// Allows for reactions to an area change without inherently requiring change_area() be called (I hate maploading)
+/turf/proc/on_change_area(area/old_area, area/new_area)
+ transfer_area_lighting(old_area, new_area)
+
/turf/proc/multiz_turf_del(turf/T, dir)
+ SEND_SIGNAL(src, COMSIG_TURF_MULTIZ_DEL, T, dir)
/turf/proc/multiz_turf_new(turf/T, dir)
+ SEND_SIGNAL(src, COMSIG_TURF_MULTIZ_NEW, T, dir)
//zPassIn doesn't necessarily pass an atom!
//direction is direction of travel of air
@@ -190,34 +263,64 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/zAirOut(direction, turf/source)
return FALSE
-/turf/proc/zImpact(atom/movable/A, levels = 1)
+/// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact()
+/turf/proc/zFall(atom/movable/falling, levels = 1, force = FALSE, falling_from_move = FALSE)
+ var/direction = DOWN
+ if(falling.has_gravity() <= NEGATIVE_GRAVITY)
+ direction = UP
+ var/turf/target = get_step_multiz(src, direction)
+ if(!target)
+ return FALSE
+ var/isliving = isliving(falling)
+ if(!isliving && !isobj(falling))
+ return
+ if(isliving)
+ var/mob/living/falling_living = falling
+ //relay this mess to whatever the mob is buckled to.
+ if(falling_living.buckled)
+ falling = falling_living.buckled
+ if(!falling_from_move && falling.currently_z_moving)
+ return
+ if(!force && !falling.can_z_move(direction, src, target, ZMOVE_FALL_FLAGS))
+ falling.set_currently_z_moving(FALSE, TRUE)
+ return FALSE
+
+ // So it doesn't trigger other zFall calls. Cleared on zMove.
+ falling.set_currently_z_moving(CURRENTLY_Z_FALLING)
+
+ falling.zMove(null, target, ZMOVE_CHECK_PULLEDBY)
+ target.zImpact(falling, levels, src)
+ return TRUE
+
+///Called each time the target falls down a z level possibly making their trajectory come to a halt. see __DEFINES/movement.dm.
+/turf/proc/zImpact(atom/movable/falling, levels = 1, turf/prev_turf, flags = NONE)
+ var/list/falling_movables = falling.get_z_move_affected()
+ var/list/falling_mov_names
+ for(var/atom/movable/falling_mov as anything in falling_movables)
+ falling_mov_names += falling_mov.name
for(var/i in contents)
var/atom/thing = i
- if(thing.intercept_zImpact(A, levels))
- return FALSE
- if(zFall(A, ++levels))
+ flags |= thing.intercept_zImpact(falling_movables, levels)
+ if(flags & FALL_STOP_INTERCEPTING)
+ break
+ if(prev_turf && !(flags & FALL_NO_MESSAGE))
+ for(var/mov_name in falling_mov_names)
+ prev_turf.visible_message(span_danger("[mov_name] falls through [prev_turf]!"))
+ if(!(flags & FALL_INTERCEPTED) && zFall(falling, levels + 1))
return FALSE
- A.visible_message(span_danger("[A] crashes into [src]!"))
- A.onZImpact(src, levels)
+ for(var/atom/movable/falling_mov as anything in falling_movables)
+ if(!(flags & FALL_RETAIN_PULL))
+ falling_mov.stop_pulling()
+ if(!(flags & FALL_INTERCEPTED))
+ falling_mov.onZImpact(src, levels)
+ if(falling_mov.pulledby && (falling_mov.z != falling_mov.pulledby.z || get_dist(falling_mov, falling_mov.pulledby) > 1))
+ falling_mov.pulledby.stop_pulling()
return TRUE
/turf/proc/can_zFall(atom/movable/A, levels = 1, turf/target)
SHOULD_BE_PURE(TRUE)
return zPassOut(A, DOWN, target) && target.zPassIn(A, DOWN, src)
-/turf/proc/zFall(atom/movable/A, levels = 1, force = FALSE)
- var/turf/target = get_step_multiz(src, DOWN)
- if(!target || (!isobj(A) && !ismob(A)))
- return FALSE
- if(!force && (!can_zFall(A, levels, target) || !A.can_zFall(src, levels, target, DOWN)))
- return FALSE
- A.visible_message(span_danger("[A] falls through [src]!"))
- A.zfalling = TRUE
- A.forceMove(target)
- A.zfalling = FALSE
- target.zImpact(A, levels)
- return TRUE
-
/turf/proc/handleRCL(obj/item/rcl/C, mob/user)
if(C.loaded)
for(var/obj/structure/cable/LC in src)
@@ -248,14 +351,14 @@ GLOBAL_LIST_EMPTY(station_turfs)
//There's a lot of QDELETED() calls here if someone can figure out how to optimize this but not runtime when something gets deleted by a Bump/CanPass/Cross call, lemme know or go ahead and fix this mess - kevinz000
// Test if a movable can enter this turf. Send no_side_effects = TRUE to prevent bumping.
-/turf/Enter(atom/movable/mover, atom/oldloc, no_side_effects = FALSE)
+/turf/Enter(atom/movable/mover, no_side_effects = FALSE)
// Do not call ..()
// Byond's default turf/Enter() doesn't have the behaviour we want with Bump()
// By default byond will call Bump() on the first dense object in contents
// Here's hoping it doesn't stay like this for years before we finish conversion to step_
var/atom/firstbump
var/canPassSelf = CanPass(mover, src)
- if(canPassSelf || CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE))
+ if(canPassSelf || CHECK_BITFIELD(mover.movement_type, PHASING))
for(var/i in contents)
if(QDELETED(mover))
return FALSE //We were deleted, do not attempt to proceed with movement.
@@ -267,7 +370,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
return FALSE
if(QDELETED(mover)) //Mover deleted from Cross/CanPass, do not proceed.
return FALSE
- if(CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE))
+ if(CHECK_BITFIELD(mover.movement_type, PHASING))
mover.Bump(thing)
continue
else
@@ -275,11 +378,11 @@ GLOBAL_LIST_EMPTY(station_turfs)
firstbump = thing
if(QDELETED(mover)) //Mover deleted from Cross/CanPass/Bump, do not proceed.
return FALSE
- if(!canPassSelf) //Even if mover is unstoppable they need to bump us.
+ if(!canPassSelf) //Even if mover is PHASING they need to bump us.
firstbump = src
if(firstbump)
mover.Bump(firstbump)
- return CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE)
+ return CHECK_BITFIELD(mover.movement_type, PHASING)
return TRUE
/turf/Exit(atom/movable/mover, atom/newloc)
@@ -293,7 +396,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
if(!thing.Uncross(mover, newloc))
if(thing.flags_1 & ON_BORDER_1)
mover.Bump(thing)
- if(!CHECK_BITFIELD(mover.movement_type, UNSTOPPABLE))
+ if(!CHECK_BITFIELD(mover.movement_type, PHASING))
return FALSE
if(QDELETED(mover))
return FALSE //We were deleted.
@@ -308,16 +411,13 @@ GLOBAL_LIST_EMPTY(station_turfs)
if(!AM.zfalling)
zFall(AM)
-/turf/proc/is_plasteel_floor()
- return FALSE
-
// A proc in case it needs to be recreated or badmins want to change the baseturfs
/turf/proc/assemble_baseturfs(turf/fake_baseturf_type)
var/static/list/created_baseturf_lists = list()
var/turf/current_target
if(fake_baseturf_type)
if(length(fake_baseturf_type)) // We were given a list, just apply it and move on
- baseturfs = fake_baseturf_type
+ baseturfs = baseturfs_string_list(fake_baseturf_type, src)
return
current_target = fake_baseturf_type
else
@@ -333,9 +433,9 @@ GLOBAL_LIST_EMPTY(station_turfs)
if(created_baseturf_lists[current_target])
var/list/premade_baseturfs = created_baseturf_lists[current_target]
if(length(premade_baseturfs))
- baseturfs = premade_baseturfs.Copy()
+ baseturfs = baseturfs_string_list(premade_baseturfs.Copy(), src)
else
- baseturfs = premade_baseturfs
+ baseturfs = baseturfs_string_list(premade_baseturfs, src)
return baseturfs
var/turf/next_target = initial(current_target.baseturfs)
@@ -356,20 +456,18 @@ GLOBAL_LIST_EMPTY(station_turfs)
current_target = next_target
next_target = initial(current_target.baseturfs)
- baseturfs = new_baseturfs
+ baseturfs = baseturfs_string_list(new_baseturfs, src)
created_baseturf_lists[new_baseturfs[new_baseturfs.len]] = new_baseturfs.Copy()
return new_baseturfs
/turf/proc/levelupdate()
for(var/obj/O in src)
- if(O.level == 1 && (O.flags_1 & INITIALIZED_1))
- O.hide(src.intact)
+ if(O.flags_1 & INITIALIZED_1)
+ SEND_SIGNAL(O, COMSIG_OBJ_HIDE, underfloor_accessibility)
// override for space turfs, since they should never hide anything
/turf/open/space/levelupdate()
- for(var/obj/O in src)
- if(O.level == 1 && (O.flags_1 & INITIALIZED_1))
- O.hide(0)
+ return
// Removes all signs of lattice on the pos of the turf -Donkieyo
/turf/proc/RemoveLattice()
@@ -474,12 +572,10 @@ GLOBAL_LIST_EMPTY(station_turfs)
////////////////////////////////////////////////////
/turf/singularity_act()
- if(intact)
- for(var/obj/O in contents) //this is for deleting things like wires contained in the turf
- if(O.level != 1)
- continue
- if(O.invisibility == INVISIBILITY_MAXIMUM)
- O.singularity_act()
+ if(underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
+ for(var/obj/on_top in contents) //this is for deleting things like wires contained in the turf
+ if(HAS_TRAIT(on_top, TRAIT_T_RAY_VISIBLE))
+ on_top.singularity_act()
ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return(2)
@@ -487,7 +583,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
return TRUE
/turf/proc/can_lay_cable()
- return can_have_cabling() & !intact
+ return can_have_cabling() && underfloor_accessibility >= UNDERFLOOR_INTERACTABLE
/turf/proc/visibilityChanged()
GLOB.cameranet.updateVisibility(src)
@@ -495,6 +591,9 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/burn_tile()
return
+/turf/proc/break_tile()
+ return
+
/turf/proc/is_shielded()
return
@@ -503,8 +602,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/atom/movable/movable_thing = thing
if(QDELETED(movable_thing))
continue
- if(!movable_thing.ex_check(explosion_id))
- continue
switch(severity)
if(EXPLODE_DEVASTATE)
SSexplosions.high_mov_atom += movable_thing
@@ -539,18 +636,22 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/add_blueprints(atom/movable/AM)
var/image/I = new
+ SET_PLANE(I, GAME_PLANE, src)
+ I.layer = OBJ_LAYER
I.appearance = AM.appearance
I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM
I.loc = src
I.setDir(AM.dir)
I.alpha = 128
-
LAZYADD(blueprint_data, I)
/turf/proc/add_blueprints_preround(atom/movable/AM)
- if(!SSticker.HasRoundStarted())
- add_blueprints(AM)
+ if(!SSicon_smooth.initialized)
+ if(AM.layer == WIRE_LAYER) //wires connect to adjacent positions after its parent init, meaning we need to wait (in this case, until smoothing) to take its image
+ SSicon_smooth.blueprint_queue += AM
+ else
+ add_blueprints(AM)
/turf/proc/is_transition_turf()
return
@@ -562,11 +663,12 @@ GLOBAL_LIST_EMPTY(station_turfs)
acid_type = /obj/effect/acid/alien
var/has_acid_effect = FALSE
for(var/obj/O in src)
- if(intact && O.level == 1) //hidden under the floor
+ var/turf/loc = get_turf(O)
+ if(loc.underfloor_accessibility < UNDERFLOOR_INTERACTABLE) //hidden under the floor
continue
if(istype(O, acid_type))
var/obj/effect/acid/A = O
- A.acid_level = min(A.level + acid_volume * acidpwr, 12000)//capping acid level to limit power of the acid
+ A.acid_level = min(acid_volume * acidpwr, 12000)//capping acid level to limit power of the acid
has_acid_effect = 1
continue
O.acid_act(acidpwr, acid_volume)
@@ -576,6 +678,14 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/proc/acid_melt()
return
+/turf/rust_heretic_act()
+ if(turf_flags & NO_RUST)
+ return
+ if(HAS_TRAIT(src, TRAIT_RUSTY))
+ return
+
+ AddElement(/datum/element/rust)
+
/turf/handle_fall(mob/faller)
if(has_gravity(src))
playsound(src, "bodyfall", 50, 1)
@@ -631,14 +741,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
//Should return new turf
/turf/proc/Melt()
return ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
-
-/turf/rust_heretic_act()
- if(flags_1 & NO_RUST)
- return
- if(HAS_TRAIT(src, TRAIT_RUSTY))
- return
-
- AddElement(/datum/element/rust)
/turf/bullet_act(obj/projectile/P)
. = ..()
@@ -662,3 +764,17 @@ GLOBAL_LIST_EMPTY(station_turfs)
/// Called when attempting to set fire to a turf
/turf/proc/IgniteTurf(power, fire_color="red")
return
+/// Returns whether it is safe for an atom to move across this turf
+/turf/proc/can_cross_safely(atom/movable/crossing)
+ return TRUE
+
+
+/turf/proc/on_turf_saved()
+ // This is all we can do. I'm sorry mappers, but there's no way to get any more details.
+ var/first = TRUE
+ for(var/datum/element/decal/decal as anything in GetComponents(/datum/element/decal))
+ if(!first)
+ . += ",\n"
+ . += "[/obj/effect/turf_decal]{\n\ticon = '[decal.pic.icon]';\n\ticon_state = \"[decal.pic.icon_state]\";\n\tdir = [decal.pic.dir];\n\tcolor = \"[decal.pic.color]\"\n\t}"
+ first = FALSE
+ return
diff --git a/code/game/world.dm b/code/game/world.dm
index 65b4cbdc23a3..fe894bcb0cdf 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -1,5 +1,10 @@
#define RESTART_COUNTER_PATH "data/round_counter.txt"
+/// Force the log directory to be something specific in the data/logs folder
+#define OVERRIDE_LOG_DIRECTORY_PARAMETER "log-directory"
+/// Prevent the master controller from starting automatically
+#define NO_INIT_PARAMETER "no-init"
+
GLOBAL_VAR(restart_counter)
/**
@@ -344,6 +349,37 @@ GLOBAL_VAR(restart_counter)
else
hub_password = "SORRYNOPASSWORD"
+/**
+ * Handles incresing the world's maxx var and intializing the new turfs and assigning them to the global area.
+ * If map_load_z_cutoff is passed in, it will only load turfs up to that z level, inclusive.
+ * This is because maploading will handle the turfs it loads itself.
+ */
+/world/proc/increase_max_x(new_maxx, map_load_z_cutoff = maxz)
+ if(new_maxx <= maxx)
+ return
+ var/old_max = world.maxx
+ maxx = new_maxx
+ if(!map_load_z_cutoff)
+ return
+ var/area/global_area = GLOB.areas_by_type[world.area] // We're guaranteed to be touching the global area, so we'll just do this
+ var/list/to_add = block(
+ locate(old_max + 1, 1, 1),
+ locate(maxx, maxy, map_load_z_cutoff))
+ global_area.contained_turfs += to_add
+
+/world/proc/increase_max_y(new_maxy, map_load_z_cutoff = maxz)
+ if(new_maxy <= maxy)
+ return
+ var/old_maxy = maxy
+ maxy = new_maxy
+ if(!map_load_z_cutoff)
+ return
+ var/area/global_area = GLOB.areas_by_type[world.area] // We're guarenteed to be touching the global area, so we'll just do this
+ var/list/to_add = block(
+ locate(1, old_maxy + 1, 1),
+ locate(maxx, maxy, map_load_z_cutoff))
+ global_area.contained_turfs += to_add
+
/world/proc/incrementMaxZ()
maxz++
SSmobs.MaxZChanged()
@@ -374,3 +410,7 @@ GLOBAL_VAR(restart_counter)
SStimer?.reset_buckets()
/world/proc/refresh_atmos_grid()
+
+#undef NO_INIT_PARAMETER
+#undef OVERRIDE_LOG_DIRECTORY_PARAMETER
+#undef RESTART_COUNTER_PATH
diff --git a/code/modules/VR/vr_human.dm b/code/modules/VR/vr_human.dm
index 3adc00bd2f07..855d615d284a 100644
--- a/code/modules/VR/vr_human.dm
+++ b/code/modules/VR/vr_human.dm
@@ -9,7 +9,7 @@
quit_action.Grant(src)
check_area()
-/mob/living/carbon/human/virtual_reality/Moved()
+/mob/living/carbon/human/virtual_reality/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
check_area()
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 94a5b4ed4d58..7df979729dad 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -162,7 +162,6 @@
body += "Is an AI | "
body += "Access AI Dashboard | "
else if(ishuman(M))
- body += "Make AI | "
body += "Make Robot | "
body += "Make Alien | "
body += "Make Slime | "
@@ -177,6 +176,7 @@
body += "
"
body += "Rudimentary transformation: These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible. "
body += "Observer | "
+ body += "Make AI | "
body += "\[ Alien: Drone, "
body += "Hunter, "
body += "Sentinel, "
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 41933cd0ae11..bcbd9a5afcf3 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -79,8 +79,8 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/admins/proc/open_shuttlepanel, /* Opens shuttle manipulator UI */
/client/proc/respawn_character,
/client/proc/discord_id_manipulation,
+ /datum/admins/proc/manage_silicon_laws,
/datum/admins/proc/open_borgopanel,
- /datum/admins/proc/change_laws,
/datum/admins/proc/restart, //yogs - moved from +server
/client/proc/admin_pick_random_player, //yogs
/client/proc/get_law_history, //yogs - silicon law history
@@ -157,7 +157,8 @@ GLOBAL_PROTECT(admin_verbs_server)
/client/proc/toggle_hub,
/client/proc/mentor_memo, // YOGS - something stupid about "Mentor memos"
/client/proc/release_queue, // Yogs -- Adds some queue-manipulation verbs
- /client/proc/toggle_cdn
+ /client/proc/toggle_cdn,
+ /client/proc/set_next_minetype
)
GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
GLOBAL_PROTECT(admin_verbs_debug)
@@ -176,6 +177,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/export_dynamic_json,
/client/proc/run_dynamic_simulations,
#endif
+ /client/proc/debug_plane_masters,
/client/proc/debug_spell_requirements,
)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm
index efbfd0e47d01..b3fd9046d563 100644
--- a/code/modules/admin/create_mob.dm
+++ b/code/modules/admin/create_mob.dm
@@ -38,6 +38,10 @@
human.dna.features["dome"] = pick(GLOB.dome_list)
human.dna.features["dorsal_tubes"] = pick(GLOB.dorsal_tubes_list)
human.dna.features["ethereal_mark"] = pick(GLOB.ethereal_mark_list)
+ human.dna.features["preternis_weathering"] = pick(GLOB.preternis_weathering_list)
+ human.dna.features["preternis_antenna"] = pick(GLOB.preternis_antenna_list)
+ human.dna.features["preternis_eye"] = pick(GLOB.preternis_eye_list)
+ human.dna.features["preternis_core"] = pick(GLOB.preternis_core_list)
human.dna.features["pod_hair"] = pick(GLOB.pod_hair_list)
human.dna.features["pod_flower"] = GLOB.pod_flower_list[human.dna.features["pod_hair"]]
diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm
index c444519e7ae1..a1adbb7c803f 100644
--- a/code/modules/admin/fun_balloon.dm
+++ b/code/modules/admin/fun_balloon.dm
@@ -94,9 +94,9 @@
/obj/effect/station_crash/Initialize(mapload)
..()
- for(var/S in SSshuttle.stationary)
+ for(var/S in SSshuttle.stationary_docking_ports)
var/obj/docking_port/stationary/SM = S
- if(SM.id == "emergency_home")
+ if(SM.shuttle_id == "emergency_home")
var/new_dir = turn(SM.dir, 180)
SM.forceMove(get_ranged_target_turf(SM, new_dir, rand(3,15)))
break
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 9ae6194dfd44..b7acb10ee945 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -23,6 +23,9 @@ GLOBAL_PROTECT(href_token)
var/deadmined
+ var/datum/filter_editor/filteriffic
+ var/datum/plane_master_debug/plane_debug
+
var/ip_cache
var/cid_cache
@@ -51,6 +54,7 @@ GLOBAL_PROTECT(href_token)
activate()
else
deactivate()
+ plane_debug = new(src)
/datum/admins/Destroy()
if(IsAdminAdvancedProcCall())
@@ -75,6 +79,7 @@ GLOBAL_PROTECT(href_token)
GLOB.permissions.deadmins -= target
GLOB.permissions.admin_datums[target] = src
deadmined = FALSE
+ QDEL_NULL(plane_debug)
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
diff --git a/code/modules/admin/mfa.dm b/code/modules/admin/mfa.dm
index 71511f406550..343d40feccef 100644
--- a/code/modules/admin/mfa.dm
+++ b/code/modules/admin/mfa.dm
@@ -91,7 +91,7 @@
if(response == "Retry TOTP")
return mfa_query()
else if(response == "Backup Code")
- if(tgui_alert(src, "Using the backup code will forget all previous logins and require re-enrolling in MFA, Do you wish to continue?", list("Confirmation", "Cancel", "Yes")) != "Yes")
+ if(tgui_alert(src, "Using the backup code will forget all previous logins and require re-enrolling in MFA, Do you wish to continue?", "Confirmation", list("Cancel", "Yes")) != "Yes")
return mfa_query()
return mfa_backup_query()
else
diff --git a/code/modules/admin/secrets.dm b/code/modules/admin/secrets.dm
index d93f496ca9b3..84f537e131ef 100644
--- a/code/modules/admin/secrets.dm
+++ b/code/modules/admin/secrets.dm
@@ -89,7 +89,7 @@
log_admin("[key_name(mob_user)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1)
message_admins(span_adminnotice("[key_name_admin(mob_user)] reset the thunderdome to default with delete_mobs=[delete_mobs]."))
- var/area/thunderdome = GLOB.areas_by_type[/area/tdome/arena]
+ var/area/thunderdome = GLOB.areas_by_type[/area/centcom/tdome/arena]
if(delete_mobs == "Yes")
for(var/mob/living/mob in thunderdome)
qdel(mob) //Clear mobs
@@ -97,7 +97,7 @@
if(!istype(obj, /obj/machinery/camera) && !istype(obj, /obj/effect/abstract/proximity_checker))
qdel(obj) //Clear objects
- var/area/template = GLOB.areas_by_type[/area/tdome/arena_source]
+ var/area/template = GLOB.areas_by_type[/area/centcom/tdome/arena_source]
template.copy_contents_to(thunderdome)
if("clear_virus")
@@ -237,7 +237,7 @@
dat += "
Name
DNA
Blood Type
"
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
if(H.ckey)
- dat += "
[H]
[H.dna.unique_enzymes]
[H.dna.blood_type]
"
+ dat += "
[H]
[H.dna.unique_enzymes]
[H.dna.blood_type.name]
"
dat += "
"
dat += ""
mob_user << browse(dat, "window=DNA;size=440x410")
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index e82d53f88f43..ebb2c6618ce3 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1187,14 +1187,13 @@
if(!check_rights(R_SPAWN))
return
- var/mob/living/carbon/human/H = locate(href_list["makeai"])
- if(!istype(H))
- to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.", confidential=TRUE)
+ var/mob/our_mob = locate(href_list["makeai"])
+ if(!istype(our_mob))
return
-
- message_admins(span_danger("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!"))
- log_admin("[key_name(usr)] AIized [key_name(H)].")
- H.AIize(TRUE, H.client)
+
+ message_admins(span_danger("Admin [key_name_admin(usr)] AIized [key_name_admin(our_mob)]!"))
+ log_admin("[key_name(usr)] AIized [key_name(our_mob)].")
+ our_mob.AIize(our_mob.client)
else if(href_list["makealien"])
if(!check_rights(R_SPAWN))
diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm
index 314f71922a2b..3c21e6ab4807 100644
--- a/code/modules/admin/verbs/adminsay.dm
+++ b/code/modules/admin/verbs/adminsay.dm
@@ -1,6 +1,4 @@
/client/verb/cmd_admin_say(msg as text)
- set hidden = TRUE
-
set category = "Misc.Unused"
set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite
diff --git a/code/modules/admin/verbs/borgpanel.dm b/code/modules/admin/verbs/borgpanel.dm
index 51a1f5ba29b5..55c05149f8fe 100644
--- a/code/modules/admin/verbs/borgpanel.dm
+++ b/code/modules/admin/verbs/borgpanel.dm
@@ -236,24 +236,21 @@
. = TRUE
-/datum/admins/proc/change_laws()
+/datum/admins/proc/manage_silicon_laws()
set category = "Admin.Player Interaction"
- set name = "Change Silicon Laws"
- set desc = "Change Silicon Laws"
+ set name = "Manage Silicon Laws"
+ set desc = "Manage Silicon Laws"
if(!check_rights(R_ADMIN))
return
- var/chosensilicon = input("Select a Silicon", "Select a Silicon", null, null) as null|anything in GLOB.silicon_mobs
- if (!istype(chosensilicon, /mob/living/silicon))
- to_chat(usr, span_warning("Silicon is required for law changes"), confidential=TRUE)
- return
- var/chosen = pick_closest_path(null, make_types_fancy(typesof(/obj/item/aiModule)))
- if (!chosen)
- return
- var/new_board = new chosen(src)
- var/obj/item/aiModule/chosenboard = new_board
- var/mob/living/silicon/beepboop = chosensilicon
- chosenboard.install(beepboop.laws, usr)
- message_admins("[key_name_admin(usr)] added [chosenboard] to [ADMIN_LOOKUPFLW(beepboop)].")
- log_admin("[key_name(usr)] added [chosenboard] to [key_name(beepboop)].")
- qdel(new_board)
+
+ var/mob/living/silicon/S = input("Select silicon.", "Manage Silicon Laws") as null|anything in GLOB.silicon_mobs
+ if(!S) return
+
+ var/datum/law_manager/L = new(S)
+ L.ui_interact(usr)
+
+ log_admin("[key_name(usr)] has opened [S]'s law manager.")
+ message_admins("[key_name(usr)] has opened [S]'s law manager.")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Manage Silicon Laws") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm
index 634d7c8bc424..9e767fe00603 100644
--- a/code/modules/admin/verbs/deadsay.dm
+++ b/code/modules/admin/verbs/deadsay.dm
@@ -1,7 +1,6 @@
/client/verb/dsay(msg as text)
set category = "Misc.Unused"
set name = "Dsay"
- set hidden = TRUE
if(!holder)
to_chat(src, "Only administrators may use this command.", confidential=TRUE)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 893c26ed104e..89599ce142d2 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -948,6 +948,23 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
usr << browse("" + replacetext(SSatoms.InitLog(), "\n", " ") + "", "window=initlog")
+/client/proc/debug_plane_masters()
+ set category = "Debug"
+ set name = "Edit/Debug Planes"
+ set desc = "Edit and visualize plane masters and their connections (relays)"
+
+ edit_plane_masters()
+
+/client/proc/edit_plane_masters(mob/debug_on)
+ if(!holder)
+ return
+ if(debug_on)
+ holder.plane_debug.set_mirroring(TRUE)
+ holder.plane_debug.set_target(debug_on)
+ else
+ holder.plane_debug.set_mirroring(FALSE)
+ holder.plane_debug.ui_interact(mob)
+
/client/proc/debug_huds(i as num)
set category = "Misc.Server Debug"
set name = "Debug HUDs"
diff --git a/code/modules/admin/verbs/plane_debugger.dm b/code/modules/admin/verbs/plane_debugger.dm
new file mode 100644
index 000000000000..1cf7d9c37a55
--- /dev/null
+++ b/code/modules/admin/verbs/plane_debugger.dm
@@ -0,0 +1,400 @@
+/// Used for testing/debugger plane masters and their associated rendering plates
+/datum/plane_master_debug
+ var/datum/admins/owner
+ /// Assoc list of plane master group key -> its depth stack
+ var/list/depth_stack = list()
+ /// The current plane master group we're viewing
+ var/current_group = PLANE_GROUP_MAIN
+ /// Weakref to the mob to edit
+ var/datum/weakref/mob_ref
+
+ var/datum/visual_data/tracking/stored
+ var/datum/visual_data/mirroring/mirror
+ /// If we are actively mirroring the target of our current ui
+ var/mirror_target = FALSE
+
+/datum/plane_master_debug/New(datum/admins/owner)
+ src.owner = owner
+
+/datum/plane_master_debug/Destroy()
+ if(owner)
+ owner.plane_debug = null
+ owner = null
+ return ..()
+
+/datum/plane_master_debug/proc/set_target(mob/new_mob)
+ QDEL_NULL(mirror)
+ QDEL_NULL(stored)
+
+ depth_stack = list()
+ if(!new_mob?.hud_used)
+ new_mob = owner.owner?.mob
+
+ mob_ref = WEAKREF(new_mob)
+
+ if(!mirror_target)
+ UnregisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT)
+ return
+
+ RegisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT, PROC_REF(on_our_logout), override = TRUE)
+ mirror = new()
+ mirror.shadow(new_mob)
+
+ if(new_mob == owner.owner.mob)
+ return
+
+ create_store()
+
+/datum/plane_master_debug/proc/on_our_logout(mob/source)
+ SIGNAL_HANDLER
+ // Recreate our stored view, since we've changed mobs now
+ create_store()
+ UnregisterSignal(source, COMSIG_MOB_LOGOUT)
+ RegisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT, PROC_REF(on_our_logout), override = TRUE)
+
+/// Create or refresh our stored visual data, represeting the viewing mob
+/datum/plane_master_debug/proc/create_store()
+ if(stored)
+ QDEL_NULL(stored)
+ stored = new()
+ stored.shadow(owner.owner.mob)
+ stored.set_truth(mirror)
+ mirror.set_mirror_target(owner.owner.mob)
+
+/datum/plane_master_debug/proc/get_target()
+ var/mob/target = mob_ref?.resolve()
+ if(!target?.hud_used)
+ target = owner.owner.mob
+ set_target(target)
+ return target
+
+/// Setter for mirror_target, basically allows for enabling/disabiling viewing through mob's sight
+/datum/plane_master_debug/proc/set_mirroring(value)
+ if(value == mirror_target)
+ return
+ mirror_target = value
+ // Refresh our target and mirrors and such
+ set_target(get_target())
+
+/datum/plane_master_debug/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/plane_master_debug/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PlaneMasterDebug")
+ ui.open()
+
+/datum/plane_master_debug/ui_assets(mob/user)
+ return list(get_asset_datum(/datum/asset/simple/plane_background))
+
+/datum/plane_master_debug/ui_data()
+ var/list/data = list()
+
+ var/mob/reference_frame = get_target()
+ data["mob_name"] = reference_frame.name
+ data["mob_ref"] = ref(reference_frame)
+ data["our_ref"] = ref(owner.owner.mob)
+ data["tracking_active"] = mirror_target
+
+ var/datum/hud/our_hud = reference_frame.hud_used
+ var/list/our_groups = our_hud.master_groups
+ if(!our_groups[current_group])
+ // We assume we'll always have at least one group
+ current_group = our_groups[length(our_hud.master_groups)]
+
+ var/list/groups = list()
+ for(var/key in our_groups)
+ groups += key
+
+ data["enable_group_view"] = length(groups) > 1
+ data["our_group"] = current_group
+ data["present_groups"] = groups
+
+ var/list/plane_info = list()
+ data["plane_info"] = plane_info
+ var/list/relay_deets = list()
+ data["relay_info"] = relay_deets
+ var/list/filter_connections = list()
+ data["filter_connect"] = filter_connections
+
+ var/list/filter_queue = list()
+
+ // Assoc of render targets -> planes
+ // Gotta be able to look these up so filter stuff can work
+ var/list/render_target_to_plane = list()
+ // Assoc list of pending planes -> relays
+ // Used to ensure the incoming_relays list is filled, even if the relay's generated before the plane's processed
+ var/list/pending_relays = list()
+
+ var/list/our_planes = our_hud?.get_planes_from(current_group)
+ for(var/plane_string as anything in our_planes)
+ var/list/this_plane = list()
+ var/atom/movable/screen/plane_master/plane = our_planes[plane_string]
+ var/string_plane = "[plane.plane]"
+ this_plane["name"] = plane.name
+ this_plane["documentation"] = plane.documentation
+ this_plane["plane"] = plane.plane
+ this_plane["our_ref"] = string_plane
+ this_plane["offset"] = plane.offset
+ this_plane["real_plane"] = plane.real_plane
+ this_plane["renders_onto"] = plane.render_relay_planes
+ this_plane["blend_mode"] = GLOB.blend_names["[plane.blend_mode_override || initial(plane.blend_mode)]"]
+ this_plane["color"] = plane.color
+ this_plane["alpha"] = plane.alpha
+ this_plane["render_target"] = plane.render_target
+ this_plane["intended_hidden"] = plane.force_hidden
+
+
+ var/list/incoming_relays = list()
+ this_plane["incoming_relays"] = incoming_relays
+
+ for(var/pending_relay in pending_relays[string_plane])
+ incoming_relays += pending_relay
+ var/list/this_relay = relay_deets[pending_relay]
+ this_relay["target_index"] = length(incoming_relays)
+
+
+ this_plane["outgoing_relays"] = list()
+
+ // You can think of relays as connections between plane master "nodes
+ // They do have some info of their own tho, best to pass that along
+ for(var/atom/movable/render_plane_relay/relay in plane.relays)
+ var/string_target = "[relay.plane]"
+ var/list/this_relay = list()
+ this_relay["name"] = relay.name
+ this_relay["source"] = plane.plane
+ this_relay["source_ref"] = string_plane
+ this_relay["target"] = relay.plane
+ this_relay["target_ref"] = string_target
+ this_relay["layer"] = relay.layer
+
+ // Now taht we've encoded our relay, we need to hand out references to it to our source plane, alongside the target plane
+ var/relay_ref = "[string_plane]-[string_target]"
+ this_relay["our_ref"] = relay_ref
+ relay_deets[relay_ref] = this_relay
+ this_plane["outgoing_relays"] += relay_ref
+
+ // If we've already encoded our target plane, update its incoming relays list
+ // Otherwise, we'll handle this later
+ var/list/existing_target = plane_info[string_target]
+ if(existing_target)
+ existing_target["incoming_relays"] += relay_ref
+ else
+ var/list/pending_plane = pending_relays[string_target]
+ if(!pending_plane)
+ pending_plane = list()
+ pending_relays[string_target] = pending_plane
+ pending_plane += relay_ref
+
+ this_plane["incoming_filters"] = list()
+ this_plane["outgoing_filters"] = list()
+ // We're gonna collect a list of filters, partly because they're useful info
+ // But also because they can be used as connections, and we need to support that
+ for(var/filter_id in plane.filter_data)
+ var/list/filter = plane.filter_data[filter_id]
+ if(!filter["render_source"])
+ continue
+ var/list/filter_info = filter.Copy()
+ filter_info["target_ref"] = string_plane
+ filter_info["name"] = filter_id
+ filter_queue += list(filter_info)
+
+ plane_info[plane_string] = this_plane
+ render_target_to_plane[plane.render_target] = this_plane
+
+ for(var/list/filter in filter_queue)
+ var/source = filter["render_source"]
+ var/list/source_plane = render_target_to_plane[source]
+ var/list/target_plane = plane_info[filter["target_ref"]]
+ var/source_ref = source_plane["our_ref"]
+ filter["source_ref"] = source_ref
+ var/our_ref = "[source_ref]-[filter["target_ref"]]-filter"
+ filter["our_ref"] = our_ref
+ filter_connections[our_ref] = filter
+ source_plane["outgoing_filters"] += our_ref
+ target_plane["incoming_filters"] += our_ref
+
+ // Only load this once. Prevents leaving off orphaned components
+ if(!depth_stack[current_group])
+ depth_stack[current_group] = treeify(plane_info, relay_deets, filter_connections)
+
+ // We will use this js side to arrange our plane masters and such
+ // It's essentially a stack of where they should be displayed
+ data["depth_stack"] = depth_stack[current_group]
+ return data
+
+// Reading this in the queue tells the search to increase the depth, and then push another increase command to the end of the stack
+// This way we ensure groupings always stay together, and depth is respected
+#define COMMAND_DEPTH_INCREASE "increase_depth"
+#define COMMAND_NEXT_PARENT "next_parent"
+
+/// Takes a list of js formatted planes, and turns it into a tree based off the back connections of relays
+/// So start at the top master plane, and work down
+/// Haha jerry what if I added commands to my list parser lmao lol
+/datum/plane_master_debug/proc/treeify(list/plane_info, list/relay_info, list/filter_connections)
+ // List in the form [depth in num] -> list(list(plane_ref -> parent_ref, ...), ...)
+ var/list/treelike_output = list()
+ // List in the form plane ref -> current depth
+ var/list/plane_to_depth = list()
+ // List of items/commands to process. FIFO queue, to ensure the brackets are built correctly
+ var/list/processing_queue = list()
+ // A FIFO queue of parents. Used so planes can have refs to their direct parent, to make sorting easier
+ var/list/parents = list("")
+ var/parent_head = 1
+ // The current depth of our search, used with treelike_output
+ var/depth = 0
+ // Push a depth increase onto the queue, to properly setup the sorta looping effect it has
+ processing_queue += COMMAND_DEPTH_INCREASE
+ processing_queue += "[RENDER_PLANE_MASTER]"
+ // We need to do a c style loop here because we are expanding the queue, and so need to update our conditional
+ for(var/i = 1; i <= length(processing_queue); i++)
+ var/entry = processing_queue[i]
+ // We've reached the end of a depth block
+ // Increment the depth and stick another command on the end of the queue
+ if(entry == COMMAND_DEPTH_INCREASE)
+ // The plane to continue on with, assuming we can find an unvisited head to use
+ var/continue_on_with = ""
+ // Don't wanna infinite loop now
+ if(i == length(processing_queue))
+ for(var/plane in TRUE_PLANE_TO_OFFSETS(RENDER_PLANE_MASTER))
+ if(!plane_to_depth["[plane]"])
+ continue_on_with = "[plane]"
+ // We only want to handle one plane master at a time
+ break
+ if(!continue_on_with)
+ continue
+ // Increment our depth
+ depth += 1
+ treelike_output += list(list())
+ // If this isn't the end, stick another entry on the end to ensure batches work proper
+ processing_queue += COMMAND_DEPTH_INCREASE
+ // If we found a plane to use to extend our process, tack it on the end here as god intended
+ if(continue_on_with)
+ processing_queue += continue_on_with
+ continue
+ if(entry == COMMAND_NEXT_PARENT)
+ parent_head += 1
+ continue
+
+ var/old_queue_len = length(processing_queue)
+ var/existing_depth = plane_to_depth[entry]
+ // If we've seen you before, remove your last entry
+ // We always want inputs before outputs in the stack
+ if(existing_depth)
+ treelike_output[existing_depth] -= entry
+
+ // If it's not a command, it must be a plane string
+ var/list/plane = plane_info[entry]
+ /// We want master planes to ALWAYS bubble down to their own space.
+ /// Just ignore this if this is the head we're processing, yeah?
+ if(PLANE_TO_TRUE(plane["real_plane"]) == RENDER_PLANE_MASTER && i > 2)
+ // If there's other stuff already in your depth entry, or there's more then one thing (a depth increase command)
+ // Left in the queue, "bubble" down a layer.
+ if(length(treelike_output[depth]) || i + 1 != length(processing_queue))
+ processing_queue += COMMAND_NEXT_PARENT
+ parents += parents[parent_head]
+ processing_queue += entry
+ continue
+ // Add all the planes that pipe into us to the queue, Intentionally allows dupes
+ // If we find the same entry twice, it'll get moved down the depth stack
+ for(var/relay_string in plane["incoming_relays"])
+ var/list/relay = relay_info[relay_string]
+ processing_queue += relay["source_ref"]
+ for(var/filter_ref in plane["incoming_filters"])
+ var/list/filter = filter_connections[filter_ref]
+ processing_queue += filter["source_ref"]
+
+ // If the queue has grown, we're a parent, so stick us in the parent queue
+ if(old_queue_len != length(processing_queue))
+ parents += entry
+ // Stick a parent increase right before our children show up in the queue. That way we're properly set as their parent
+ processing_queue.Insert(old_queue_len + 1, COMMAND_NEXT_PARENT)
+ // Stick us in the output at our designated depth
+ var/list/plane_packet = list()
+ plane_packet[entry] = parents[parent_head]
+ treelike_output[depth] += plane_packet
+ plane_to_depth[entry] = depth
+
+ /// Walk treelike output, remove allll the empty lists we've accidentially generated
+ for(var/depth_index = 1; depth_index <= length(treelike_output); depth_index++)
+ var/list/layer = treelike_output[depth_index]
+ if(!length(layer))
+ treelike_output.Cut(depth_index, depth_index + 1)
+ depth_index -= 1
+
+ return treelike_output
+
+#undef COMMAND_DEPTH_INCREASE
+#undef COMMAND_NEXT_PARENT
+
+/datum/plane_master_debug/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/reference_frame = get_target()
+ var/datum/hud/our_hud = reference_frame.hud_used
+ var/datum/plane_master_group/group = our_hud?.master_groups[current_group]
+ if(!group) // Nothing to act on
+ return
+ var/list/our_planes = group.plane_masters
+
+ switch(action)
+ if("refresh")
+ group.rebuild_hud()
+ if("reset_mob")
+ set_target(null)
+ if("toggle_mirroring")
+ set_mirroring(!mirror_target)
+ if("vv_mob")
+ owner.owner.debug_variables(reference_frame)
+ if("set_group")
+ current_group = params["target_group"]
+ if("connect_relay")
+ var/source_plane = params["source"]
+ var/target_plane = params["target"]
+ var/atom/movable/screen/plane_master/source = our_planes["[source_plane]"]
+ if(source.get_relay_to(target_plane)) // Fuck off
+ return
+ source.add_relay_to(target_plane)
+ return TRUE
+ if("disconnect_relay")
+ var/source_plane = params["source"]
+ var/target_plane = params["target"]
+ var/atom/movable/screen/plane_master/source = our_planes["[source_plane]"]
+ source.remove_relay_from(text2num(target_plane))
+ return TRUE
+ if("disconnect_filter")
+ var/target_plane = params["target"]
+ var/atom/movable/screen/plane_master/filtered_plane = our_planes["[target_plane]"]
+ filtered_plane.remove_filter(params["name"])
+ return TRUE
+ if("vv_plane")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/mob/user = ui.user
+ user?.client?.debug_variables(edit)
+ return TRUE
+ if("set_alpha")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/newalpha = params["alpha"]
+ animate(edit, 0.4 SECONDS, alpha = newalpha)
+ return TRUE
+ if("edit_color_matrix")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/mob/user = ui.user
+ user?.client?.open_color_matrix_editor(edit)
+ return TRUE
+ if("edit_filters")
+ var/plane_edit = params["edit"]
+ var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
+ var/mob/user = ui.user
+ user?.client?.open_filter_editor(edit)
+ return TRUE
+
+/datum/plane_master_debug/ui_close(mob/user)
+ . = ..()
+ set_mirroring(FALSE)
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index ee3dc3e7238c..9dde10f43dd8 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -1430,6 +1430,52 @@ Traitors and the like can also be revived with the previous role mostly intact.
message_admins("[key_name_admin(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name_admin(C)]")
log_admin("[key_name(usr)] has [newstate ? "activated" : "deactivated"] job exp exempt status on [key_name(C)]")
+/// Allow admin to add or remove traits of datum
+/datum/admins/proc/modify_traits(datum/D)
+ if(!D)
+ return
+
+ var/add_or_remove = input("Remove/Add?", "Trait Remove/Add") as null|anything in list("Add","Remove")
+ if(!add_or_remove)
+ return
+ var/list/available_traits = list()
+
+ switch(add_or_remove)
+ if("Add")
+ for(var/key in GLOB.admin_visible_traits)
+ if(istype(D,key))
+ available_traits += GLOB.admin_visible_traits[key]
+ if("Remove")
+ if(!GLOB.admin_trait_name_map)
+ GLOB.admin_trait_name_map = generate_admin_trait_name_map()
+ for(var/trait in D._status_traits)
+ var/name = GLOB.admin_trait_name_map[trait] || trait
+ available_traits[name] = trait
+
+ var/chosen_trait = input("Select trait to modify", "Trait") as null|anything in sort_list(available_traits)
+ if(!chosen_trait)
+ return
+ chosen_trait = available_traits[chosen_trait]
+
+ var/source = "adminabuse"
+ switch(add_or_remove)
+ if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it.
+ if(GLOB.movement_type_trait_to_flag[chosen_trait]) //include the required element.
+ D.AddElement(/datum/element/movetype_handler)
+ ADD_TRAIT(D,chosen_trait,source)
+ if("Remove")
+ var/specific = input("All or specific source ?", "Trait Remove/Add") as null|anything in list("All","Specific")
+ if(!specific)
+ return
+ switch(specific)
+ if("All")
+ source = null
+ if("Specific")
+ source = input("Source to be removed","Trait Remove/Add") as null|anything in sort_list(GET_TRAIT_SOURCES(D, chosen_trait))
+ if(!source)
+ return
+ REMOVE_TRAIT(D,chosen_trait,source)
+
/mob/living/carbon/proc/adminpie(mob/user)
var/obj/item/reagent_containers/food/snacks/pie/cream/admin/p = new (get_turf(pick(oview(3,user))))
p.item_flags = UNCATCHABLE
diff --git a/code/modules/admin/verbs/shuttlepanel.dm b/code/modules/admin/verbs/shuttlepanel.dm
index e0922ac662cd..955024ac3b7c 100644
--- a/code/modules/admin/verbs/shuttlepanel.dm
+++ b/code/modules/admin/verbs/shuttlepanel.dm
@@ -12,19 +12,19 @@
/obj/docking_port/mobile/proc/admin_fly_shuttle(mob/user)
var/list/options = list()
- for(var/port in SSshuttle.stationary)
+ for(var/port in SSshuttle.stationary_docking_ports)
if (istype(port, /obj/docking_port/stationary/transit))
continue // please don't do this
var/obj/docking_port/stationary/S = port
if (canDock(S) == SHUTTLE_CAN_DOCK)
- options[S.name || S.id] = S
+ options[S.name || S.shuttle_id] = S
options += "--------"
options += "Infinite Transit"
options += "Delete Shuttle"
options += "Into The Sunset (delete & greentext 'escape')"
- var/selection = input(user, "Select where to fly [name || id]:", "Fly Shuttle") as null|anything in options
+ var/selection = input(user, "Select where to fly [name || shuttle_id]:", "Fly Shuttle") as null|anything in options
if(!selection)
return
@@ -35,12 +35,12 @@
setTimer(ignitionTime)
if("Delete Shuttle")
- if(tgui_alert(user, "Really delete [name || id]?", "Delete Shuttle", list("Cancel", "Really!")) != "Really!")
+ if(tgui_alert(user, "Really delete [name || shuttle_id]?", "Delete Shuttle", list("Cancel", "Really!")) != "Really!")
return
jumpToNullSpace()
if("Into The Sunset (delete & greentext 'escape')")
- if(tgui_alert(user, "Really delete [name || id] and greentext escape objectives?", "Delete Shuttle", list("Cancel", "Really!")) != "Really!")
+ if(tgui_alert(user, "Really delete [name || shuttle_id] and greentext escape objectives?", "Delete Shuttle", list("Cancel", "Really!")) != "Really!")
return
intoTheSunset()
@@ -60,12 +60,12 @@
var/list/options = list()
- for(var/port in SSshuttle.stationary)
+ for(var/port in SSshuttle.stationary_docking_ports)
if (istype(port, /obj/docking_port/stationary/transit))
continue // please don't do this
var/obj/docking_port/stationary/S = port
if (canDock(S) == SHUTTLE_CAN_DOCK)
- options[S.name || S.id] = S
+ options[S.name || S.shuttle_id] = S
var/selection = input(user, "Select the new arrivals destination:", "Fly Shuttle") as null|anything in options
if(!selection)
diff --git a/code/modules/admin/view_variables/color_matrix_editor.dm b/code/modules/admin/view_variables/color_matrix_editor.dm
new file mode 100644
index 000000000000..7fde4c245909
--- /dev/null
+++ b/code/modules/admin/view_variables/color_matrix_editor.dm
@@ -0,0 +1,85 @@
+/datum/color_matrix_editor
+ var/client/owner
+ var/datum/weakref/target
+ var/atom/movable/screen/map_view/proxy_view
+ var/list/current_color
+ var/closed
+
+/datum/color_matrix_editor/New(user, atom/_target = null)
+ owner = CLIENT_FROM_VAR(user)
+ if(islist(_target?.color))
+ current_color = _target.color
+ else if(istext(_target?.color))
+ current_color = color_hex2color_matrix(_target.color)
+ else
+ current_color = COLOR_MATRIX_IDENTITY
+
+ var/mutable_appearance/view = image('icons/misc/colortest.dmi', "colors")
+ if(_target)
+ target = WEAKREF(_target)
+ if(!(_target.appearance_flags & PLANE_MASTER))
+ view = image(_target)
+
+ proxy_view = new
+ proxy_view.generate_view("color_matrix_proxy_[REF(src)]")
+
+ proxy_view.appearance = view
+ proxy_view.color = current_color
+ proxy_view.display_to(owner.mob)
+
+/datum/color_matrix_editor/Destroy(force, ...)
+ QDEL_NULL(proxy_view)
+ return ..()
+
+/datum/color_matrix_editor/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/color_matrix_editor/ui_static_data(mob/user)
+ var/list/data = list()
+ data["mapRef"] = proxy_view.assigned_map
+
+ return data
+
+/datum/color_matrix_editor/ui_data(mob/user)
+ var/list/data = list()
+ data["currentColor"] = current_color
+
+ return data
+
+/datum/color_matrix_editor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ColorMatrixEditor")
+ ui.open()
+
+/datum/color_matrix_editor/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("transition_color")
+ current_color = params["color"]
+ animate(proxy_view, time = 4, color = current_color)
+ if("confirm")
+ on_confirm()
+ SStgui.close_uis(src)
+
+/datum/color_matrix_editor/ui_close(mob/user)
+ . = ..()
+ closed = TRUE
+
+/datum/color_matrix_editor/proc/on_confirm()
+ var/atom/target_atom = target?.resolve()
+ if(istype(target_atom))
+ target_atom.vv_edit_var("color", current_color)
+
+/datum/color_matrix_editor/proc/wait()
+ while(!closed)
+ stoplag(1)
+
+/client/proc/open_color_matrix_editor(atom/in_atom)
+ var/datum/color_matrix_editor/editor = new /datum/color_matrix_editor(src, in_atom)
+ editor.ui_interact(mob)
+ editor.wait()
+ . = editor.current_color
+ qdel(editor)
diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm
new file mode 100644
index 000000000000..0bd9f51c114f
--- /dev/null
+++ b/code/modules/admin/view_variables/filterrific.dm
@@ -0,0 +1,99 @@
+/datum/filter_editor
+ var/atom/target
+
+/datum/filter_editor/New(atom/target)
+ src.target = target
+
+/datum/filter_editor/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/filter_editor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Filteriffic")
+ ui.open()
+
+/datum/filter_editor/ui_static_data(mob/user)
+ var/list/data = list()
+ data["filter_info"] = GLOB.master_filter_info
+ return data
+
+/datum/filter_editor/ui_data()
+ var/list/data = list()
+ data["target_name"] = target.name
+ data["target_filter_data"] = target.filter_data
+ return data
+
+/datum/filter_editor/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("add_filter")
+ var/target_name = params["name"]
+ while(target.filter_data && target.filter_data[target_name])
+ target_name = "[target_name]-dupe"
+ target.add_filter(target_name, params["priority"], list("type" = params["type"]))
+ . = TRUE
+ if("remove_filter")
+ target.remove_filter(params["name"])
+ . = TRUE
+ if("rename_filter")
+ var/list/filter_data = target.filter_data[params["name"]]
+ target.remove_filter(params["name"])
+ target.add_filter(params["new_name"], filter_data["priority"], filter_data)
+ . = TRUE
+ if("edit_filter")
+ target.remove_filter(params["name"])
+ target.add_filter(params["name"], params["priority"], params["new_filter"])
+ . = TRUE
+ if("change_priority")
+ var/new_priority = params["new_priority"]
+ target.change_filter_priority(params["name"], new_priority)
+ . = TRUE
+ if("transition_filter_value")
+ target.transition_filter(params["name"], params["new_data"], 4)
+ . = TRUE
+ if("modify_filter_value")
+ var/list/old_filter_data = target.filter_data[params["name"]]
+ var/list/new_filter_data = old_filter_data.Copy()
+ for(var/entry in params["new_data"])
+ new_filter_data[entry] = params["new_data"][entry]
+ for(var/entry in new_filter_data)
+ if(entry == GLOB.master_filter_info[old_filter_data["type"]]["defaults"][entry])
+ new_filter_data.Remove(entry)
+ target.remove_filter(params["name"])
+ target.add_filter(params["name"], old_filter_data["priority"], new_filter_data)
+ . = TRUE
+ if("modify_color_value")
+ var/new_color = input(usr, "Pick new filter color", "Filteriffic Colors!") as color|null
+ if(new_color)
+ target.transition_filter(params["name"], list("color" = new_color), 4)
+ . = TRUE
+ if("modify_icon_value")
+ var/icon/new_icon = input("Pick icon:", "Icon") as null|icon
+ if(new_icon)
+ target.filter_data[params["name"]]["icon"] = new_icon
+ target.update_filters()
+ . = TRUE
+ if("mass_apply")
+ if(!check_rights_for(usr.client, R_FUN))
+ to_chat(usr, span_userdanger("Stay in your lane, jannie."))
+ return
+ var/target_path = text2path(params["path"])
+ if(!target_path)
+ return
+ var/filters_to_copy = target.filters
+ var/filter_data_to_copy = target.filter_data
+ var/count = 0
+ for(var/thing in world.contents)
+ if(istype(thing, target_path))
+ var/atom/thing_at = thing
+ thing_at.filters = filters_to_copy
+ thing_at.filter_data = filter_data_to_copy
+ count += 1
+ message_admins("LOCAL CLOWN [usr.ckey] JUST MASS FILTER EDITED [count] WITH PATH OF [params["path"]]!")
+ log_admin("LOCAL CLOWN [usr.ckey] JUST MASS FILTER EDITED [count] WITH PATH OF [params["path"]]!")
+
+
diff --git a/code/modules/admin/view_variables/mark_datum.dm b/code/modules/admin/view_variables/mark_datum.dm
index ae46da3dd173..adfa01906978 100644
--- a/code/modules/admin/view_variables/mark_datum.dm
+++ b/code/modules/admin/view_variables/mark_datum.dm
@@ -2,13 +2,13 @@
if(!holder)
return
if(holder.marked_datum)
- holder.UnregisterSignal(holder.marked_datum, COMSIG_PARENT_QDELETING)
+ holder.UnregisterSignal(holder.marked_datum, COMSIG_QDELETING)
vv_update_display(holder.marked_datum, "marked", "")
holder.marked_datum = D
- holder.RegisterSignal(holder.marked_datum, COMSIG_PARENT_QDELETING, /datum/admins/proc/handle_marked_del)
+ holder.RegisterSignal(holder.marked_datum, COMSIG_QDELETING, /datum/admins/proc/handle_marked_del)
vv_update_display(D, "marked", VV_MSG_MARKED)
/datum/admins/proc/handle_marked_del(datum/source)
SIGNAL_HANDLER
- UnregisterSignal(marked_datum, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(marked_datum, COMSIG_QDELETING)
marked_datum = null
diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm
index 46c15f28f68c..4e929a7afe25 100644
--- a/code/modules/admin/view_variables/topic_basic.dm
+++ b/code/modules/admin/view_variables/topic_basic.dm
@@ -54,29 +54,86 @@
if(!check_rights(NONE))
return
var/list/names = list()
- var/list/componentsubtypes = sortList(subtypesof(/datum/component), /proc/cmp_typepaths_asc)
+ var/list/componentsubtypes = sort_list(subtypesof(/datum/component), GLOBAL_PROC_REF(cmp_typepaths_asc))
names += "---Components---"
names += componentsubtypes
names += "---Elements---"
- names += sortList(subtypesof(/datum/element), /proc/cmp_typepaths_asc)
- var/result = input(usr, "Choose a component/element to add","better know what ur fuckin doin pal") as null|anything in names
- if(!usr || !result || result == "---Components---" || result == "---Elements---")
+ names += sort_list(subtypesof(/datum/element), GLOBAL_PROC_REF(cmp_typepaths_asc))
+
+ var/result = tgui_input_list(usr, "Choose a component/element to add", "Add Component", names)
+ if(isnull(result))
+ return
+ if(!usr || result == "---Components---" || result == "---Elements---")
return
+
if(QDELETED(src))
- to_chat(usr, "That thing doesn't exist anymore!")
+ to_chat(usr, "That thing doesn't exist anymore!", confidential = TRUE)
return
+
+ var/add_source
+ if(ispath(result, /datum/component))
+ var/datum/component/comp_path = result
+ if(initial(comp_path.dupe_mode) == COMPONENT_DUPE_SOURCES)
+ add_source = tgui_input_text(usr, "Enter a source for the component", "Add Component", "ADMIN-ABUSE")
+ if(isnull(add_source))
+ return
+
var/list/lst = get_callproc_args()
if(!lst)
return
+
var/datumname = "error"
lst.Insert(1, result)
if(result in componentsubtypes)
datumname = "component"
- target._AddComponent(lst)
+ target._AddComponent(lst, add_source)
else
datumname = "element"
target._AddElement(lst)
- log_admin("[key_name(usr)] has added [result] [datumname] to [key_name(src)].")
- message_admins("[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(src)].")
+ log_admin("[key_name(usr)] has added [result] [datumname] to [key_name(target)].")
+ message_admins(span_notice("[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(target)]."))
+ if(href_list[VV_HK_REMOVECOMPONENT] || href_list[VV_HK_MASS_REMOVECOMPONENT])
+ if(!check_rights(NONE))
+ return
+ var/mass_remove = href_list[VV_HK_MASS_REMOVECOMPONENT]
+ var/list/components = list()
+ for(var/datum/component/component in target.GetComponents(/datum/component))
+ components += component.type
+ var/list/names = list()
+ names += "---Components---"
+ if(length(components))
+ names += sort_list(components, GLOBAL_PROC_REF(cmp_typepaths_asc))
+ names += "---Elements---"
+ // We have to list every element here because there is no way to know what element is on this object without doing some sort of hack.
+ names += sort_list(subtypesof(/datum/element), GLOBAL_PROC_REF(cmp_typepaths_asc))
+ var/path = tgui_input_list(usr, "Choose a component/element to remove. All elements listed here may not be on the datum.", "Remove element", names)
+ if(isnull(path))
+ return
+ if(!usr || path == "---Components---" || path == "---Elements---")
+ return
+ if(QDELETED(src))
+ to_chat(usr, "That thing doesn't exist anymore!")
+ return
+ var/list/targets_to_remove_from = list(target)
+ if(mass_remove)
+ var/method = vv_subtype_prompt(target.type)
+ targets_to_remove_from = get_all_of_type(target.type, method)
+
+ if(alert(usr, "Are you sure you want to mass-delete [path] on [target.type]?", "Mass Remove Confirmation", "Yes", "No") == "No")
+ return
+
+ for(var/datum/target_to_remove_from as anything in targets_to_remove_from)
+ if(ispath(path, /datum/element))
+ var/list/lst = get_callproc_args()
+ if(!lst)
+ lst = list()
+ lst.Insert(1, path)
+ target._RemoveElement(lst)
+ else
+ var/list/components_actual = target_to_remove_from.GetComponents(path)
+ for(var/to_delete in components_actual)
+ qdel(to_delete)
+
+ message_admins(span_notice("[key_name_admin(usr)] has [mass_remove? "mass" : ""] removed [path] component from [mass_remove? target.type : key_name_admin(target)]."))
if(href_list[VV_HK_CALLPROC])
usr.client.callproc_datum(target)
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 604ef5a2b179..ed357bb9d91a 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -17,7 +17,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens.
///The antag hud's icon file
- var/hud_icon = 'yogstation/icons/mob/antag_hud.dmi'
+ var/hud_icon = 'modular_dripstation/icons/mob/hud.dmi' //dripstation edit
///Name of the antag hud we provide to this mob.
var/antag_hud_name
var/awake_stage = ANTAG_AWAKE //What stage we are of "waking up"
diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
index 2f1de93c9631..47c051bb5f21 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
@@ -585,10 +585,10 @@ Congratulations! You are now trained for invasive xenobiology research!"}
/obj/item/restraints/handcuffs/energy
name = "hard-light energy field"
desc = "A hard-light field restraining the hands."
- icon_state = "cuff" // Needs sprite
+ icon_state = "handcuffAlien" //likely sprite change, enabling
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
- breakouttime = 450
+ breakouttime = 45 SECONDS
trashtype = /obj/item/restraints/handcuffs/energy/used
flags_1 = NONE
@@ -771,12 +771,14 @@ Congratulations! You are now trained for invasive xenobiology research!"}
name = "alien table"
desc = "Advanced flat surface technology at work!"
icon = 'icons/obj/smooth_structures/alien_table.dmi'
- icon_state = "alien_table"
+ icon_state = "alien_table-0"
+ base_icon_state = "alien_table"
buildstack = /obj/item/stack/sheet/mineral/abductor
framestack = /obj/item/stack/sheet/mineral/abductor
buildstackamount = 1
framestackamount = 1
- canSmoothWith = null
+ smoothing_groups = SMOOTH_GROUP_ABDUCTOR_TABLES
+ canSmoothWith = SMOOTH_GROUP_ABDUCTOR_TABLES
frame = /obj/structure/table_frame/abductor
/obj/structure/table/optable/abductor
diff --git a/code/modules/antagonists/abductor/machinery/camera.dm b/code/modules/antagonists/abductor/machinery/camera.dm
index 79cf5623ec04..3f8e1a458662 100644
--- a/code/modules/antagonists/abductor/machinery/camera.dm
+++ b/code/modules/antagonists/abductor/machinery/camera.dm
@@ -13,6 +13,8 @@
icon = 'icons/obj/abductor.dmi'
icon_state = "camera"
+ icon_keyboard = null
+ icon_screen = null
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
/obj/machinery/computer/camera_advanced/abductor/CreateEye()
@@ -67,7 +69,7 @@
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/obj/machinery/abductor/pad/P = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -94,7 +96,7 @@
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/obj/machinery/abductor/pad/P = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -133,7 +135,7 @@
return
var/mob/living/carbon/human/C = owner
- var/mob/camera/aiEye/remote/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/remote_eye = C.remote_control
var/obj/machinery/abductor/console/console = target
console.SetDroppoint(remote_eye.loc,owner)
diff --git a/code/modules/antagonists/blob/blob_mobs.dm b/code/modules/antagonists/blob/blob_mobs.dm
index 3c9b7fa82a2e..5534764c9ef9 100644
--- a/code/modules/antagonists/blob/blob_mobs.dm
+++ b/code/modules/antagonists/blob/blob_mobs.dm
@@ -8,15 +8,17 @@
icon = 'icons/mob/blob.dmi'
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
- bubble_icon = "blob"
+ bubble_icon = BUBBLE_BLOB
speak_emote = null //so we use verb_yell/verb_say/etc
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
maxbodytemp = 360
unique_name = 1
a_intent = INTENT_HARM
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // ... Blob colored lighting
+ lighting_cutoff_red = 20
+ lighting_cutoff_green = 40
+ lighting_cutoff_blue = 30
var/mob/camera/blob/overmind = null
var/obj/structure/blob/factory/factory = null
var/independent = FALSE
diff --git a/code/modules/antagonists/blob/blob_report.dm b/code/modules/antagonists/blob/blob_report.dm
index f0d46888c2f6..d8ee7857d635 100644
--- a/code/modules/antagonists/blob/blob_report.dm
+++ b/code/modules/antagonists/blob/blob_report.dm
@@ -29,15 +29,11 @@
floor += 1
if(iswallturf(T))
- var/turf/closed/wall/TW = T
- if(TW.intact)
- wall += 2
- else
- wall += 1
+ wall += 1
if(istype(T, /turf/closed/wall/r_wall))
var/turf/closed/wall/r_wall/TRW = T
- if(TRW.intact)
+ if(TRW.d_state == INTACT)
r_wall += 2
else
r_wall += 1
diff --git a/code/modules/antagonists/blob/overmind.dm b/code/modules/antagonists/blob/overmind.dm
index dacff037b249..3c79ce8c687e 100644
--- a/code/modules/antagonists/blob/overmind.dm
+++ b/code/modules/antagonists/blob/overmind.dm
@@ -13,13 +13,15 @@ GLOBAL_LIST_EMPTY(blob_nodes)
icon_state = "marker"
mouse_opacity = MOUSE_OPACITY_ICON
move_on_shuttle = 1
- see_in_dark = 8
invisibility = INVISIBILITY_OBSERVER
layer = FLY_LAYER
+ // Vivid blue green, would be cool to make this change with strain
+ lighting_cutoff_red = 0
+ lighting_cutoff_green = 35
+ lighting_cutoff_blue = 20
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
hud_type = /datum/hud/blob_overmind
var/obj/structure/blob/core/blob_core = null // The blob overmind's core
var/blob_points = 0
diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm
index e7054ad62198..e7937f7781bc 100644
--- a/code/modules/antagonists/blob/structures/_blob.dm
+++ b/code/modules/antagonists/blob/structures/_blob.dm
@@ -8,7 +8,8 @@
opacity = FALSE
anchored = TRUE
layer = BELOW_MOB_LAYER
- CanAtmosPass = ATMOS_PASS_PROC
+ can_atmos_pass = ATMOS_PASS_PROC
+ obj_flags = CAN_BE_HIT|BLOCK_Z_OUT_DOWN // stops blob mobs from falling on multiz.
var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed.
max_integrity = 30
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 70)
@@ -73,7 +74,7 @@
return TRUE
return FALSE
-/obj/structure/blob/CanAtmosPass(turf/T)
+/obj/structure/blob/can_atmos_pass(turf/target_turf, vertical = FALSE)
return !atmosblock
/obj/structure/blob/CanAStarPass(ID, dir, caller)
diff --git a/code/modules/antagonists/blob/structures/core.dm b/code/modules/antagonists/blob/structures/core.dm
index 1c87f7bf2558..d99bd10d4b36 100644
--- a/code/modules/antagonists/blob/structures/core.dm
+++ b/code/modules/antagonists/blob/structures/core.dm
@@ -69,7 +69,7 @@
B.change_to(/obj/structure/blob/shield/core, overmind)
..()
-/obj/structure/blob/core/onTransitZ(old_z, new_z)
- if(overmind && is_station_level(new_z))
+/obj/structure/blob/core/on_changed_z_level(turf/old_turf, turf/new_turf)
+ if(overmind && is_station_level(new_turf.z))
overmind.forceMove(get_turf(src))
return ..()
diff --git a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm
index 58826b125266..8fd824cf1042 100644
--- a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm
+++ b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm
@@ -114,7 +114,7 @@
/datum/antagonist/bloodsucker/apply_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/current_mob = mob_override || owner.current
- RegisterSignal(current_mob, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(current_mob, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick))
RegisterSignal(current_mob, COMSIG_LIVING_DEATH, PROC_REF(on_death))
handle_clown_mutation(current_mob, mob_override ? null : "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.")
@@ -139,7 +139,7 @@
/datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/current_mob = mob_override || owner.current
- UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_PARENT_EXAMINE, COMSIG_LIVING_DEATH))
+ UnregisterSignal(current_mob, list(COMSIG_LIVING_LIFE, COMSIG_ATOM_EXAMINE, COMSIG_LIVING_DEATH))
if(current_mob.hud_used)
var/datum/hud/hud_used = current_mob.hud_used
@@ -564,8 +564,9 @@
if(user_eyes)
user_eyes.flash_protect = initial(user_eyes.flash_protect)
user_eyes.sight_flags = initial(user_eyes.sight_flags)
- user_eyes.see_in_dark = initial(user_eyes.see_in_dark)
- user_eyes.lighting_alpha = initial(user_eyes.lighting_alpha)
+ user.lighting_cutoff_red += 5
+ user.lighting_cutoff_green += 15
+ user.lighting_cutoff_blue += 5
user.update_sight()
/datum/antagonist/bloodsucker/proc/give_masquerade_infraction()
diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm
index 88758963a2af..f0e77d742f3e 100644
--- a/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm
+++ b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm
@@ -36,6 +36,7 @@
if(ishuman(bloodsuckerdatum.owner.current))
var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
human_user.eye_color = BLOODCULT_EYE
+ human_user.update_body()
human_user.updateappearance()
ADD_TRAIT(bloodsuckerdatum.owner.current, CULT_EYES, BLOODSUCKER_TRAIT)
bloodsuckerdatum.owner.current.faction |= "bloodhungry"
@@ -51,12 +52,13 @@
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/restingplace)
/datum/bloodsucker_clan/lasombra/on_favorite_vassal(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
+ vassaldatum.BuyPower(new /datum/action/cooldown/spell/pointed/lesser_glare)
+ vassaldatum.BuyPower(new /datum/action/cooldown/spell/jaunt/shadow_walk)
if(ishuman(vassaldatum.owner.current))
var/mob/living/carbon/human/vassal = vassaldatum.owner.current
- vassal.see_in_dark = 8
vassal.eye_color = BLOODCULT_EYE
- vassal.updateappearance()
- var/list/powers = list(/datum/action/cooldown/spell/pointed/lesser_glare, /datum/action/cooldown/spell/jaunt/shadow_walk)
- for(var/datum/action/cooldown/spell/power in powers)
- power = new(vassaldatum.owner.current)
- power.Grant(vassaldatum.owner.current)
+ vassal.dna.update_ui_block(DNA_EYE_COLOR_BLOCK)
+ ADD_TRAIT(vassal, CULT_EYES, BLOODSUCKER_TRAIT)
+ vassal.update_body()
+ vassal.update_sight()
+ vassal.update_appearance()
diff --git a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm
index 91ab766577bd..3177ef22750c 100644
--- a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm
@@ -471,7 +471,7 @@
base_background_icon_state = "wolf_power_off"
power_explanation = "Rabidism:\n\
Rabidism will deal reduced damage to everyone in range including you.\n\
- During Rabidism's ten second rage you'll deal alot more damage to structures.\n\
+ During Rabidism's ten second rage you'll deal a lot more damage to structures.\n\
Be aware of it's long cooldown time.\n\
Created from your Tresspass ability"
power_flags = BP_AM_TOGGLE
diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm
index 76f4f6618269..1959d68ad505 100644
--- a/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm
+++ b/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm
@@ -211,8 +211,7 @@
/datum/action/cooldown/bloodsucker/targeted/brawn/shadow/FireTargetedPower(atom/target_atom)
var/mob/living/carbon/human/H = target_atom
H.apply_status_effect(STATUS_EFFECT_SHADOWAFFLICTED)
- var/turf/T = get_turf(H)
- for(var/datum/light_source/LS in T.get_affecting_lights())
+ for(var/datum/light_source/LS in target_atom.light_sources)
var/atom/LO = LS.source_atom
if(isitem(LO))
var/obj/item/I = LO
diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm
index 7925aa933ad6..d019b1f00538 100644
--- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm
+++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm
@@ -215,8 +215,7 @@
if(current_eyes)
current_eyes.flash_protect = max(initial(current_eyes.flash_protect) - 1, - 1)
current_eyes.sight_flags = SEE_MOBS
- current_eyes.see_in_dark = 8
- current_eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+
current_eyes.setOrganDamage(0) //making sure
if(my_clan?.get_clan() == CLAN_LASOMBRA && ishuman(bloodsuckeruser))
var/mob/living/carbon/human/bloodsucker = bloodsuckeruser
@@ -286,7 +285,7 @@
owner.current.adjust_jitter(3 SECONDS)
// BLOOD_VOLUME_SURVIVE: [122] - Blur Vision
if(bloodsucker_blood_volume < BLOOD_VOLUME_SURVIVE(owner.current))
- owner.current.blur_eyes((8 - 8 * (bloodsucker_blood_volume / BLOOD_VOLUME_BAD(owner.current)))* 2 SECONDS)
+ owner.current.adjust_eye_blur((8 - 8 * (bloodsucker_blood_volume / BLOOD_VOLUME_BAD(owner.current)))* 2 SECONDS)
// The more blood, the better the Regeneration, get too low blood, and you enter Frenzy.
if(bloodsucker_blood_volume < (FRENZY_THRESHOLD_ENTER + humanity_lost * 10) && !frenzied)
diff --git a/code/modules/antagonists/bloodsuckers/vassal/vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/vassal.dm
index 3b2cc4f7347a..9d21f401a298 100644
--- a/code/modules/antagonists/bloodsuckers/vassal/vassal.dm
+++ b/code/modules/antagonists/bloodsuckers/vassal/vassal.dm
@@ -66,7 +66,7 @@
examine_text += vassal_examine
/datum/antagonist/vassal/on_gain()
- RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(owner.current, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning))
if(owner.current && HAS_TRAIT(owner.current, TRAIT_MINDSHIELD))
for(var/obj/item/implant/mindshield/L in owner.current)
@@ -94,7 +94,7 @@
. = ..()
/datum/antagonist/vassal/on_removal()
- UnregisterSignal(owner.current, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(owner.current, COMSIG_ATOM_EXAMINE)
UnregisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN)
//Free them from their Master
if(master && master.owner)
@@ -102,7 +102,7 @@
master.special_vassals[special_type] -= src
master.vassals -= src
owner.enslaved_to = null
- for(var/all_status_traits in owner.current.status_traits)
+ for(var/all_status_traits in owner.current._status_traits)
REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT)
//Remove Recuperate Power
while(powers.len)
@@ -276,7 +276,7 @@
show_in_antagpanel = FALSE
silent = TRUE
ui_name = FALSE
- hud_icon = 'yogstation/icons/mob/hud.dmi'
+ hud_icon = 'modular_dripstation/icons/mob/hud.dmi' //dripstation edit
///The revenge vassal that brought us into the fold.
var/datum/antagonist/vassal/revenge/revenge_vassal
@@ -285,7 +285,7 @@
/datum/antagonist/ex_vassal/on_gain()
. = ..()
- RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(owner.current, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
/datum/antagonist/ex_vassal/on_removal()
if(revenge_vassal)
diff --git a/code/modules/antagonists/changeling/powers/adrenaline.dm b/code/modules/antagonists/changeling/powers/adrenaline.dm
index e690e51deb7e..896bfe6427fb 100644
--- a/code/modules/antagonists/changeling/powers/adrenaline.dm
+++ b/code/modules/antagonists/changeling/powers/adrenaline.dm
@@ -1,10 +1,10 @@
/datum/action/changeling/adrenaline
name = "Adrenaline Sacs"
- desc = "We evolve additional sacs of adrenaline throughout our body. Costs 50 chemicals."
+ desc = "We evolve additional sacs of adrenaline throughout our body. Costs 75 chemicals."
helptext = "Removes all stuns instantly. Can be used while unconscious. Continued use poisons the body." //yogs - changed text to suit the below change
button_icon_state = "adrenaline"
- chemical_cost = 50
- dna_cost = 2
+ chemical_cost = 75
+ dna_cost = 3
req_human = 1
req_stat = UNCONSCIOUS
conflicts = list(/datum/action/changeling/strained_muscles)
diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm
index 920cad4f34d7..45765c9006be 100644
--- a/code/modules/antagonists/changeling/powers/headcrab.dm
+++ b/code/modules/antagonists/changeling/powers/headcrab.dm
@@ -31,7 +31,7 @@
if(eyes)
to_chat(H, span_userdanger("You are blinded by a shower of blood!"))
H.Stun(20)
- H.blur_eyes(20)
+ H.adjust_eye_blur(20)
eyes.applyOrganDamage(5)
H.adjust_confusion(3 SECONDS)
for(var/mob/living/silicon/S in range(2,user))
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 73c99de9b2eb..8bca7a9ce148 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -288,7 +288,7 @@
name = "tentacle"
desc = "A tentacle."
projectile_type = /obj/projectile/tentacle
- caliber = "tentacle"
+ caliber = CALIBER_TENTACLE
icon_state = "tentacle_end"
firing_effect_type = null
var/obj/item/gun/magic/tentacle/gun //the item that shot it
@@ -318,7 +318,7 @@
/obj/projectile/tentacle/fire(setAngle)
if(firer)
- chain = firer.Beam(src, icon_state = "tentacle", time = INFINITY, maxdistance = INFINITY, beam_sleep_time = 1)
+ chain = firer.Beam(src, icon_state = "tentacle", emissive = FALSE)
..()
/obj/projectile/tentacle/proc/reset_throw(mob/living/carbon/human/H)
@@ -613,23 +613,23 @@
if(synthetic)
can_drop = TRUE
-/obj/item/melee/flesh_maul/afterattack(atom/target, mob/user, proximity)
+/obj/item/melee/flesh_maul/afterattack(atom/target, mob/user, proximity)
. = ..()
if(!proximity)
return
if(iscarbon(target))
var/mob/living/carbon/C = target
C.add_movespeed_modifier("flesh maul", update=TRUE, priority=101, multiplicative_slowdown=1) //Slows the target because big whack
- addtimer(CALLBACK(C, TYPE_PROC_REF(/mob, remove_movespeed_modifier), "flesh maul"), 2 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
+ addtimer(CALLBACK(C, TYPE_PROC_REF(/mob, remove_movespeed_modifier), "flesh maul"), 2 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
to_chat(target, span_danger("You are staggered from the blow!"))
else if(iscyborg(target))
var/mob/living/silicon/robot/R = target
R.Paralyze(1 SECONDS) //One second stun on borgs because they get their circuits rattled or something
- else if(isstructure(target) || ismachinery(target))
+ else if(isstructure(target) || ismachinery(target))
var/obj/structure/S = target //Works for machinery because they have the same variable that does the same thing
- var/structure_damage = S.max_integrity
+ var/structure_damage = S.max_integrity
var/make_sound = TRUE
if(istype(target, /obj/structure/window) || istype(target, /obj/structure/grille))
structure_damage *= 2 // Because of melee armor
diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm
index 9c3cf68e97b7..41476653d340 100644
--- a/code/modules/antagonists/changeling/powers/shriek.dm
+++ b/code/modules/antagonists/changeling/powers/shriek.dm
@@ -1,9 +1,9 @@
/datum/action/changeling/resonant_shriek
name = "Resonant Shriek"
- desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 60 chemicals."
+ desc = "Our lungs and vocal cords shift, allowing us to briefly emit a noise that deafens and confuses the weak-minded. Costs 75 chemicals."
helptext = "Emits a high-frequency sound that confuses and deafens humans, blows out nearby lights and overloads cyborg sensors."
button_icon_state = "resonant_shriek"
- chemical_cost = 60
+ chemical_cost = 75
dna_cost = 1
req_human = 1
xenoling_available = FALSE
@@ -23,8 +23,8 @@
var/mob/living/carbon/C = M
if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling))
C.adjustEarDamage(0, 30)
- C.adjust_confusion(25 SECONDS)
- C.adjust_jitter(50 SECONDS)
+ C.adjust_confusion(10 SECONDS)
+ C.adjust_jitter(10 SECONDS)
else
SEND_SOUND(C, sound('sound/effects/screech.ogg'))
diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm
index 5631ac7de2f6..0fc3a1fa17c4 100644
--- a/code/modules/antagonists/changeling/powers/tiny_prick.dm
+++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm
@@ -235,7 +235,7 @@
to_chat(target, span_danger("Your eyes burn horrifically!"))
target.become_nearsighted(EYE_DAMAGE)
target.blind_eyes(20)
- target.blur_eyes(40)
+ target.adjust_eye_blur(40)
return TRUE
/datum/action/changeling/sting/LSD
diff --git a/code/modules/antagonists/clockcult/clock_effects/clock_overlay.dm b/code/modules/antagonists/clockcult/clock_effects/clock_overlay.dm
index 5707fd0a9fef..b585b0e82dd1 100644
--- a/code/modules/antagonists/clockcult/clock_effects/clock_overlay.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/clock_overlay.dm
@@ -29,17 +29,16 @@
name = "clockwork wall"
icon = 'icons/turf/walls/clockwork_wall.dmi'
icon_state = "clockwork_wall"
- canSmoothWith = list(/obj/effect/clockwork/overlay/wall, /obj/structure/falsewall/brass)
- smooth = SMOOTH_TRUE
layer = CLOSED_TURF_LAYER
+ smoothing_flags = SMOOTH_BITMASK
+ canSmoothWith = null
/obj/effect/clockwork/overlay/wall/Initialize(mapload)
. = ..()
- queue_smooth_neighbors(src)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(queue_smooth), src), 1)
+ QUEUE_SMOOTH_NEIGHBORS(src)
/obj/effect/clockwork/overlay/wall/Destroy()
- queue_smooth_neighbors(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
/obj/effect/clockwork/overlay/floor
diff --git a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
index b72939d80d6b..b9c483b75021 100644
--- a/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/clock_sigils.dm
@@ -63,7 +63,7 @@
icon = 'icons/effects/clockwork_effects.dmi'
clockwork_desc = "A sigil that will stun the next non-Servant to cross it."
icon_state = "sigildull"
- layer = HIGH_SIGIL_LAYER
+ layer = SIGIL_LAYER
alpha = 75
color = "#FAE48C"
light_range = 1.4
diff --git a/code/modules/antagonists/clockcult/clock_effects/general_markers.dm b/code/modules/antagonists/clockcult/clock_effects/general_markers.dm
index eda88f27e706..f5c7d28a2e14 100644
--- a/code/modules/antagonists/clockcult/clock_effects/general_markers.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/general_markers.dm
@@ -4,7 +4,7 @@
desc = "Some big guy. For you."
clockwork_desc = "One of Ratvar's generals."
alpha = 200
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
/obj/effect/clockwork/general_marker/Initialize(mapload)
. = ..()
@@ -44,7 +44,7 @@
name = "Sevtug, the Formless Pariah"
desc = "A sinister cloud of purple energy. Looking at it gives you a headache."
clockwork_desc = "One of Ratvar's four generals. Sevtug taught him how to manipulate minds and is one of his oldest allies. Sevtug serves Ratvar loyally out of a hope that one day, he will \
- be able to use a moment of weakness in the Justicar to usurp him, but such a day will never come. And so, he serves with dedication, if not necessarily any sort of decorum, never aware he is \
+ be able to use a moment of weakness in the Justiciar to usurp him, but such a day will never come. And so, he serves with dedication, if not necessarily any sort of decorum, never aware he is \
the one being made a fool of."
icon = 'icons/effects/166x195.dmi'
icon_state = "sevtug"
diff --git a/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm b/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm
index 9bf278d7800e..13ea18556069 100644
--- a/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm
+++ b/code/modules/antagonists/clockcult/clock_effects/servant_blocker.dm
@@ -6,7 +6,7 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
density = TRUE
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
/obj/effect/clockwork/servant_blocker/Initialize(mapload)
. = ..()
diff --git a/code/modules/antagonists/clockcult/clock_items/wraith_spectacles.dm b/code/modules/antagonists/clockcult/clock_items/wraith_spectacles.dm
index bac0e1fac941..b2fc01309dae 100644
--- a/code/modules/antagonists/clockcult/clock_items/wraith_spectacles.dm
+++ b/code/modules/antagonists/clockcult/clock_items/wraith_spectacles.dm
@@ -53,20 +53,16 @@
to_chat(victim, span_userdanger("Your eyes explode with horrific pain!"))
victim.emote("scream")
eyes.applyOrganDamage(eyes.maxHealth)
- victim.adjust_blurriness(30)
+ victim.adjust_eye_blur(30)
victim.adjust_blindness(30)
return TRUE
/obj/item/clothing/glasses/wraith_spectacles/proc/set_vision_vars(update_vision)
- lighting_alpha = null
tint = 0
vision_flags = NONE
- darkness_view = 2
if(!up)
if(is_servant_of_ratvar(loc))
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
vision_flags = SEE_MOBS | SEE_TURFS | SEE_OBJS
- darkness_view = 3
else
tint = 3
if(update_vision && iscarbon(loc))
@@ -161,7 +157,7 @@
eyes.applyOrganDamage(0.5)
eye_damage_done += 0.5
if(eye_damage_done >= 20)
- H.adjust_blurriness(2)
+ H.adjust_eye_blur(2)
if(eye_damage_done >= nearsight_breakpoint)
if(!HAS_TRAIT(H, TRAIT_NEARSIGHT))
to_chat(H, span_nzcrentr("Your vision doubles, then trembles. Darkness begins to close in. You can't keep this up!"))
diff --git a/code/modules/antagonists/clockcult/clock_mobs.dm b/code/modules/antagonists/clockcult/clock_mobs.dm
index 0a080e94fe02..019cd68bfad0 100644
--- a/code/modules/antagonists/clockcult/clock_mobs.dm
+++ b/code/modules/antagonists/clockcult/clock_mobs.dm
@@ -16,7 +16,7 @@
verb_whisper = "imparts"
verb_yell = "harangues"
initial_language_holder = /datum/language_holder/clockmob
- bubble_icon = "clock"
+ bubble_icon = BUBBLE_CLOCK
light_color = "#E42742"
deathsound = 'sound/magic/clockwork/anima_fragment_death.ogg'
speech_span = SPAN_ROBOT
diff --git a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
index efcbd6f882ec..8ed7296d9442 100644
--- a/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
+++ b/code/modules/antagonists/clockcult/clock_mobs/_eminence.dm
@@ -11,11 +11,13 @@
icon_state = "eminence"
mouse_opacity = MOUSE_OPACITY_OPAQUE
move_on_shuttle = TRUE
- see_in_dark = 8
invisibility = INVISIBILITY_OBSERVER
layer = FLY_LAYER
faction = list("ratvar")
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // VERY red, to fit the eyes
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 5
var/turf/last_failed_turf
var/static/superheated_walls = 0
var/lastWarning = 0
diff --git a/code/modules/antagonists/clockcult/clock_scripture.dm b/code/modules/antagonists/clockcult/clock_scripture.dm
index e7f387caa80a..0ac790200506 100644
--- a/code/modules/antagonists/clockcult/clock_scripture.dm
+++ b/code/modules/antagonists/clockcult/clock_scripture.dm
@@ -43,7 +43,7 @@ GLOBAL_LIST_INIT(scripture_states,scripture_states_init_value()) //list of clock
var/static/list/inathneq_penalty = list("Child, this is too far out!", "The barrier isn't thin enough for for me to help!", "Please, go to the station so I can assist you.", \
"Don't waste my Cogs on this...", "There isn't enough time to linger out here!")
var/static/list/sevtug_penalty = list("Fool! Get to the station and don't waste capacitors.", "You go this far out and expect help?", "The veil is too strong, idiot.", \
- "How does the Justicar get anything done with servants like you?", "Oh, you love wasting time, don't you?")
+ "How does the Justiciar get anything done with servants like you?", "Oh, you love wasting time, don't you?")
var/static/list/nezbere_penalty = list("You disgrace our master's name with this endeavour.", "This is too far from the station to be a good base.", "This will take too long, friend.", \
"The barrier isn't weakened enough to make this practical.", "Don't waste alloy.")
var/static/list/nzcrentr_penalty = list("You'd be easy to hunt in that little hunk of metal.", "Boss says you need to get back to the beacon.", "Boss says I can kill you if you do this again.", \
diff --git a/code/modules/antagonists/clockcult/clock_structure.dm b/code/modules/antagonists/clockcult/clock_structure.dm
index 768d550e5378..b4d71902b183 100644
--- a/code/modules/antagonists/clockcult/clock_structure.dm
+++ b/code/modules/antagonists/clockcult/clock_structure.dm
@@ -130,7 +130,7 @@
/obj/structure/destructible/clockwork/massive
name = "massive construct"
desc = "A very large construction."
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
density = FALSE
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justiciar.dm
similarity index 94%
rename from code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
rename to code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justiciar.dm
index b90d1d0ffbea..cca95290a006 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justiciar.dm
@@ -8,7 +8,7 @@
//The gateway to Reebe, from which Ratvar emerges.
/obj/structure/destructible/clockwork/massive/celestial_gateway
- name = "\improper Ark of the Clockwork Justicar"
+ name = "\improper Ark of the Clockwork Justiciar"
desc = "A massive, hulking amalgamation of parts. It seems to be maintaining a very unstable bluespace anomaly."
clockwork_desc = "Nezbere's magnum opus: a hulking clockwork machine capable of combining bluespace and steam power to summon Ratvar. Once activated, \
its instability will cause one-way bluespace rifts to open across the station to the City of Cogs, so be prepared to defend it at all costs."
@@ -124,7 +124,7 @@
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/spawn_animation()
hierophant_message("The Ark has activated! [grace_period ? "You have [round(grace_period / 60)] minutes until the crew invades! " : ""]Defend it at all costs!", FALSE, src)
- sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
+ sound_to_playing_players(volume = 10, channel = CHANNEL_JUSTICIAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
seconds_until_activation = 0
SSshuttle.registerHostileEnvironment(src)
@@ -157,7 +157,7 @@
SSshuttle.clearHostileEnvironment(src)
if(!purpose_fulfilled)
hierophant_message("The Ark has fallen!")
- sound_to_playing_players(null, channel = CHANNEL_JUSTICAR_ARK)
+ sound_to_playing_players(null, channel = CHANNEL_JUSTICIAR_ARK)
if(istype(SSticker.mode, /datum/game_mode/clockwork_cult))
SSticker.force_ending = TRUE //rip
if(glow)
@@ -190,7 +190,7 @@
resistance_flags |= INDESTRUCTIBLE
countdown.stop()
visible_message(span_userdanger("[src] begins to pulse uncontrollably... you might want to run!"))
- sound_to_playing_players(volume = 25, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_disrupted.ogg'))
+ sound_to_playing_players(volume = 25, channel = CHANNEL_JUSTICIAR_ARK, S = sound('sound/effects/clockcult_gateway_disrupted.ogg'))
for(var/mob/M in GLOB.player_list)
var/turf/T = get_turf(M)
if((T && T.z == z) || is_servant_of_ratvar(M))
@@ -198,9 +198,12 @@
make_glow()
glow.icon_state = "clockwork_gateway_disrupted"
resistance_flags |= INDESTRUCTIBLE
- sleep(2.7 SECONDS)
- explosion(src, 1, 3, 8, 8)
- sound_to_playing_players('sound/effects/explosion_distant.ogg', volume = 50)
+ addtimer(CALLBACK(src, PROC_REF(boom)), 2.7 SECONDS)
+ //well that was another easy adventure for spi-
+
+/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/boom()
+ explosion(src, 1, 3, 8, 8)
+ sound_to_playing_players('sound/effects/explosion_distant.ogg', volume = 50)
qdel(src)
/obj/structure/destructible/clockwork/massive/celestial_gateway/proc/make_glow()
@@ -314,19 +317,19 @@
for(var/V in GLOB.generic_event_spawns)
addtimer(CALLBACK(src, PROC_REF(open_portal), get_turf(V)), rand(100, 600))
sound_to_playing_players('sound/magic/clockwork/invoke_general.ogg', 30, FALSE)
- sound_to_playing_players(volume = 15, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
+ sound_to_playing_players(volume = 15, channel = CHANNEL_JUSTICIAR_ARK, S = sound('sound/effects/clockcult_gateway_charging.ogg', TRUE))
second_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_charging"
if(GATEWAY_REEBE_FOUND to GATEWAY_RATVAR_COMING)
if(!third_sound_played)
- sound_to_playing_players(volume = 20, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
+ sound_to_playing_players(volume = 20, channel = CHANNEL_JUSTICIAR_ARK, S = sound('sound/effects/clockcult_gateway_active.ogg', TRUE))
third_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_active"
if(GATEWAY_RATVAR_COMING to GATEWAY_RATVAR_ARRIVAL)
if(!fourth_sound_played)
- sound_to_playing_players(volume = 25, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
+ sound_to_playing_players(volume = 25, channel = CHANNEL_JUSTICIAR_ARK, S = sound('sound/effects/clockcult_gateway_closing.ogg', TRUE))
fourth_sound_played = TRUE
make_glow()
glow.icon_state = "clockwork_gateway_closing"
@@ -338,7 +341,7 @@
purpose_fulfilled = TRUE
make_glow()
animate(glow, transform = matrix() * 1.5, alpha = 255, time = 12.5 SECONDS)
- sound_to_playing_players(volume = 100, channel = CHANNEL_JUSTICAR_ARK, S = sound('sound/effects/ratvar_rises.ogg')) //End the sounds
+ sound_to_playing_players(volume = 100, channel = CHANNEL_JUSTICIAR_ARK, S = sound('sound/effects/ratvar_rises.ogg')) //End the sounds
sleep(12.5 SECONDS)
make_glow()
animate(glow, transform = matrix() * 3, alpha = 0, time = 0.5 SECONDS)
@@ -389,7 +392,7 @@
return
initiate_mass_recall() //wHOOPS LOOKS LIKE A HULK GOT THROUGH
-//the actual appearance of the Ark of the Clockwork Justicar; an object so the edges of the gate can be clicked through.
+//the actual appearance of the Ark of the Clockwork Justiciar; an object so the edges of the gate can be clicked through.
/obj/effect/clockwork/overlay/gateway_glow
icon = 'icons/effects/96x96.dmi'
icon_state = "clockwork_gateway_components"
diff --git a/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justiciar.dm
similarity index 100%
rename from code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justicar.dm
rename to code/modules/antagonists/clockcult/clock_structures/ratvar_the_clockwork_justiciar.dm
diff --git a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
index 7270696af35b..8a79dde170f9 100644
--- a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
@@ -60,9 +60,9 @@
brass_floor = TRUE
if(W.use(2 - brass_floor))
if(anchored)
- T.PlaceOnTop(/turf/closed/wall/clockwork)
+ T.place_on_top(/turf/closed/wall/clockwork)
else
- T.PlaceOnTop(/turf/open/floor/clockwork, flags = CHANGETURF_INHERIT_AIR)
+ T.place_on_top(/turf/open/floor/clockwork, flags = CHANGETURF_INHERIT_AIR)
new /obj/structure/falsewall/brass(T)
qdel(src)
else
diff --git a/code/modules/antagonists/creep/creep.dm b/code/modules/antagonists/creep/creep.dm
index 23cbf6779ce4..9b5d04604706 100644
--- a/code/modules/antagonists/creep/creep.dm
+++ b/code/modules/antagonists/creep/creep.dm
@@ -66,6 +66,10 @@
to_chat(owner, span_boldannounce("This role does NOT enable you to otherwise surpass what's deemed creepy behavior per the rules."))//ironic if you know the history of the antag
owner.announce_objectives()
+/datum/antagonist/obsessed/farewell()
+ to_chat(owner, span_userdanger("The Voices fall silent, you are once again alone in your own mind."))
+ owner.announce_objectives()
+
/datum/antagonist/obsessed/Destroy()
if(trauma)
qdel(trauma)
@@ -85,7 +89,7 @@
var/datum/component/C = M.GetComponent(/datum/component/mood)
if(C) //we cannot be too sure they may have somehow removed it
to_chat(owner, span_danger("Your need for mental fitness vanishes alongside the voices, mood has been disabled."))
- C.RemoveComponent()
+ qdel(C)
/datum/antagonist/obsessed/proc/forge_objectives(datum/mind/obsessionmind)
var/list/objectives_left = list("spendtime", "polaroid", "hug")
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index f8c9196488a5..e01ca1c7ea42 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -578,6 +578,7 @@
/obj/item/restraints/handcuffs/energy/cult //For the shackling spell
name = "shadow shackles"
desc = "Shackles that bind the wrists with sinister magic."
+ icon_state = "cuff"
trashtype = /obj/item/restraints/handcuffs/energy/used
item_flags = DROPDEL
@@ -763,12 +764,8 @@
temp += max((B.bloodiness**2)/800,1)
new /obj/effect/temp_visual/cult/turf/floor(get_turf(B))
qdel(B)
- for(var/obj/effect/decal/cleanable/trail_holder/TH in view(T, 2))
+ for(var/obj/effect/decal/cleanable/blood/trail_holder/TH in view(T, 2))
qdel(TH)
- var/obj/item/clothing/shoes/shoecheck = user.shoes
- if(shoecheck && istype(shoecheck) && shoecheck.bloody_shoes[/datum/reagent/blood])
- temp += shoecheck.bloody_shoes[/datum/reagent/blood]/20
- shoecheck.bloody_shoes[/datum/reagent/blood] = 0
if(temp)
user.Beam(T,icon_state="drainbeam",time=15)
new /obj/effect/temp_visual/cult/sparks(get_turf(user))
diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm
index 03e17fee9c51..a73e265331c3 100644
--- a/code/modules/antagonists/cult/cult.dm
+++ b/code/modules/antagonists/cult/cult.dm
@@ -62,7 +62,7 @@
/datum/antagonist/cult/greet()
to_chat(owner.current, "You are a member of the cult!")
- to_chat(owner.current, "If you are new to Blood Cult, please review this tutorial!") //Yogs
+ to_chat(owner.current, "If you are new to Blood Cult, please review this tutorial!") //Yogs
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/bloodcult.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
owner.announce_objectives()
@@ -482,7 +482,7 @@
return
UnregisterSignal(target, COMSIG_MIND_TRANSFERRED)
if(target.current)
- UnregisterSignal(target.current, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
+ UnregisterSignal(target.current, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
target = null
/datum/objective/sacrifice/find_target(dupe_search_range, list/blacklist)
@@ -522,7 +522,7 @@
// Register a bunch of signals to both the target mind and its body
// to stop cult from softlocking everytime the target is deleted before being actually sacrificed.
RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_mind_transfer))
- RegisterSignal(target.current, COMSIG_PARENT_QDELETING, PROC_REF(on_target_body_del))
+ RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del))
RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap))
else
message_admins("Cult Sacrifice: Could not find unconvertible or convertible target. WELP!")
@@ -544,13 +544,13 @@
if(!isliving(target.current))
INVOKE_ASYNC(src, PROC_REF(find_target))
return
- UnregisterSignal(previous_body, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
- RegisterSignal(target.current, COMSIG_PARENT_QDELETING, PROC_REF(on_target_body_del))
+ UnregisterSignal(previous_body, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
+ RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del))
RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap))
/datum/objective/sacrifice/proc/on_possible_mindswap(mob/source)
SIGNAL_HANDLER
- UnregisterSignal(target.current, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
+ UnregisterSignal(target.current, list(COMSIG_QDELETING, COMSIG_MOB_MIND_TRANSFERRED_INTO))
//we check if the mind is bodyless only after mindswap shenanigeans to avoid issues.
addtimer(CALLBACK(src, PROC_REF(do_we_have_a_body)), 0 SECONDS)
@@ -558,7 +558,7 @@
if(!target.current) //The player was ghosted and the mind isn't probably going to be transferred to another mob at this point.
find_target()
return
- RegisterSignal(target.current, COMSIG_PARENT_QDELETING, PROC_REF(on_target_body_del))
+ RegisterSignal(target.current, COMSIG_QDELETING, PROC_REF(on_target_body_del))
RegisterSignal(target.current, COMSIG_MOB_MIND_TRANSFERRED_INTO, PROC_REF(on_possible_mindswap))
/datum/objective/sacrifice/check_completion()
@@ -650,7 +650,7 @@
return FALSE
blood_target = new_target
- RegisterSignal(blood_target, COMSIG_PARENT_QDELETING, PROC_REF(unset_blood_target_and_timer))
+ RegisterSignal(blood_target, COMSIG_QDELETING, PROC_REF(unset_blood_target_and_timer))
var/area/target_area = get_area(new_target)
blood_target_image = image('icons/effects/mouse_pointers/cult_target.dmi', new_target, "glow", ABOVE_MOB_LAYER)
@@ -687,7 +687,7 @@
to_chat(cultist.current, span_bold(span_cultlarge("The blood mark has expired!")))
cultist.current.client.images -= blood_target_image
- UnregisterSignal(blood_target, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(blood_target, COMSIG_QDELETING)
blood_target = null
QDEL_NULL(blood_target_image)
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 87b5d4ed3f62..4d5fefa3d622 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -487,7 +487,7 @@
user.dropItemToGround(src, TRUE)
/obj/item/clothing/glasses/hud/health/night/cultblind
- desc = "may Nar'sie guide you through the darkness and shield you from the light."
+ desc = "May Nar'sie guide you through the darkness and shield you from the light."
name = "zealot's blindfold"
icon_state = "blindfold"
item_state = "blindfold"
@@ -543,7 +543,7 @@ GLOBAL_VAR_INIT(curselimit, 0)
set_coefficient = 1
else
set_coefficient = 0.5
- var/surplus = timer - (SSshuttle.emergencyCallTime * set_coefficient)
+ var/surplus = timer - (SSshuttle.emergency_call_time * set_coefficient)
SSshuttle.emergency.setTimer(timer)
if(surplus > 0)
SSshuttle.block_recall(surplus)
@@ -626,7 +626,7 @@ GLOBAL_VAR_INIT(curselimit, 0)
color = "#ff0000"
on_damage = 15
slot_flags = null
- on = TRUE
+ light_on = TRUE
var/charges = 5
/obj/item/flashlight/flare/culttorch/afterattack(atom/movable/A, mob/user, proximity)
@@ -925,8 +925,7 @@ GLOBAL_VAR_INIT(curselimit, 0)
L.adjustBruteLoss(45)
playsound(L, 'sound/hallucinations/wail.ogg', 50, 1)
L.emote("scream")
- var/datum/beam/current_beam = new(user,temp_target,time=7,beam_icon_state="blood_beam",btype=/obj/effect/ebeam/blood)
- INVOKE_ASYNC(current_beam, TYPE_PROC_REF(/datum/beam, Start))
+ user.Beam(temp_target, icon_state="blood_beam", time = 7, beam_type = /obj/effect/ebeam/blood)
/obj/effect/ebeam/blood
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index 7727d52ce3ff..519f16e4b613 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -223,7 +223,7 @@
var/turf/T = safepick(validturfs)
if(T)
if(istype(T, /turf/open/floor/plating))
- T.PlaceOnTop(/turf/open/floor/engine/cult, flags = CHANGETURF_IGNORE_AIR)
+ T.place_on_top(/turf/open/floor/engine/cult, flags = CHANGETURF_IGNORE_AIR)
else
T.ChangeTurf(/turf/open/floor/engine/cult, flags = CHANGETURF_IGNORE_AIR)
else
@@ -282,7 +282,7 @@
max_integrity = 200
break_sound = 'sound/effects/meteorimpact.ogg'
break_message = span_warning("The pillar crumbles!")
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
var/alt = 0
is_endgame = TRUE
@@ -342,7 +342,7 @@
max_integrity = 600
break_sound = 'sound/effects/glassbr2.ogg'
break_message = span_warning("The bloodstone resonates violently before crumbling to the floor!")
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
light_color = "#FF0000"
var/current_fullness = 0
var/anchor = FALSE //are we the bloodstone used to summon Nar'sie? used in the final part of the summoning
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index c6d9cee7a905..0b72aa0f9a2d 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -17,7 +17,7 @@ Runes can either be invoked by one's self or with many different cultists. Each
name = "rune"
var/cultist_name = "basic rune"
desc = "A rune vandalizing the station."
- var/cultist_desc = "a basic rune with no function." //This is shown to cultists who examine the rune in order to determine its true purpose.
+ var/cultist_desc = "A basic rune with no function." //This is shown to cultists who examine the rune in order to determine its true purpose.
anchored = TRUE
icon = 'icons/obj/rune.dmi'
icon_state = "1"
@@ -193,7 +193,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Malformed Rune: This forms if a rune is not drawn correctly. Invoking it does nothing but hurt the user.
/obj/effect/rune/malformed
cultist_name = "malformed rune"
- cultist_desc = "a senseless rune written in gibberish. No good can come from invoking this."
+ cultist_desc = "A senseless rune written in gibberish. No good can come from invoking this."
invocation = "Ra'sha yoka!"
invoke_damage = 30
@@ -209,7 +209,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Rite of Offering: Converts or sacrifices a target.
/obj/effect/rune/convert
cultist_name = "Offer"
- cultist_desc = "offers a noncultist above it to Nar'sie, either converting them or sacrificing them."
+ cultist_desc = "Offers a non-cultist above it to Nar'sie, either converting them or sacrificing them."
req_cultists_text = "2 for conversion, 3 for living sacrifices and sacrifice targets."
invocation = "Mah'weyh pleggh at e'ntrath!"
icon_state = "3"
@@ -336,6 +336,8 @@ structure_check() searches for nearby cultist structures required for the invoca
to_chat(M, span_cultlarge("\"I accept this sacrifice.\""))
else
to_chat(M, span_cultlarge("\"I accept this meager sacrifice.\""))
+ var/mob/living/carbon/carbon_user = M //dripstation edit
+ SEND_SIGNAL(carbon_user, COMSIG_ADD_MOOD_EVENT, "geometeroffer", /datum/mood_event/sacrifice_geometer) //dripstation edit
var/obj/item/soulstone/stone = new /obj/item/soulstone(get_turf(src))
if(sacrificial.mind && !sacrificial.suiciding)
@@ -361,7 +363,7 @@ structure_check() searches for nearby cultist structures required for the invoca
/obj/effect/rune/empower
cultist_name = "Empower"
- cultist_desc = "allows cultists to prepare greater amounts of blood magic at far less of a cost."
+ cultist_desc = "Allows cultists to prepare greater amounts of blood magic at far less of a cost."
invocation = "H'drak v'loso, mir'kanas verbot!"
icon_state = "3"
color = RUNE_COLOR_TALISMAN
@@ -375,7 +377,7 @@ structure_check() searches for nearby cultist structures required for the invoca
/obj/effect/rune/teleport
cultist_name = "Teleport"
- cultist_desc = "warps everything above it to another chosen teleport rune."
+ cultist_desc = "Warps everything above it to another chosen teleport rune."
invocation = "Sas'so c'arta forbici!"
icon_state = "2"
color = RUNE_COLOR_TELEPORT
@@ -498,7 +500,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Ritual of Dimensional Rending: Calls forth the avatar of Nar'sie upon the station.
/obj/effect/rune/narsie
cultist_name = "Nar'sie"
- cultist_desc = "tears apart dimensional barriers, beginning the Red Harvest. You will need to protect 4 Bloodstones around the station, then the Anchor Bloodstone after invoking this rune or the summoning will backfire and need to be restarted. Requires 9 invokers, with the cult leader counting as half of this if they invoke the rune."
+ cultist_desc = "Tears apart dimensional barriers, beginning the Red Harvest. You will need to protect 4 Bloodstones around the station, then the Anchor Bloodstone after invoking this rune or the summoning will backfire and need to be restarted. Requires 9 invokers, with the cult leader counting as half of this if they invoke the rune."
invocation = "TOK-LYR RQA-NAP G'OLT-ULOFT!!"
req_cultists = 9
req_cultists_text = "9 cultists, with the cult leader counting as 5 if they are the invoker"
@@ -525,6 +527,12 @@ structure_check() searches for nearby cultist structures required for the invoca
/obj/effect/rune/narsie/conceal() //can't hide this, and you wouldn't want to
return
+GLOBAL_VAR_INIT(narsie_effect_last_modified, 0)
+GLOBAL_VAR_INIT(narsie_summon_count, 0)
+/proc/set_narsie_count(new_count)
+ GLOB.narsie_summon_count = new_count
+ SEND_GLOBAL_SIGNAL(COMSIG_NARSIE_SUMMON_UPDATE, GLOB.narsie_summon_count)
+
/obj/effect/rune/narsie/attack_hand(mob/living/user)
if(user.mind?.has_antag_datum(/datum/antagonist/cult/master))
req_cultists -= 4 //leader counts as 5 cultists if they are the invoker
@@ -583,7 +591,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Rite of Resurrection: Requires a dead or inactive cultist. When reviving the dead, you can only perform one revival for every three sacrifices your cult has carried out.
/obj/effect/rune/raise_dead
cultist_name = "Revive"
- cultist_desc = "requires a dead, mindless, or inactive cultist placed upon the rune. Provided there have been sufficient sacrifices, they will be given a new life. This will cause large amounts of damage to the invoker and the revived corpse."
+ cultist_desc = "Requires a dead, mindless, or inactive cultist placed upon the rune. Provided there have been sufficient sacrifices, they will be given a new life. This will cause large amounts of damage to the invoker and the revived corpse."
invocation = "Pasnar val'keriam usinar. Savrae ines amutan. Yam'toth remium il'tarat!" //Depends on the name of the user - see below
icon_state = "1"
color = RUNE_COLOR_MEDIUMRED
@@ -677,11 +685,11 @@ structure_check() searches for nearby cultist structures required for the invoca
//Rite of the Corporeal Shield: When invoked, becomes solid and cannot be passed. Invoke again to undo.
/obj/effect/rune/wall
cultist_name = "Barrier"
- cultist_desc = "when invoked, makes a temporary invisible wall to block passage. Can be invoked again to reverse this."
+ cultist_desc = "When invoked, makes a temporary invisible wall to block passage. Can be invoked again to reverse this."
invocation = "Khari'd! Eske'te tannin!"
icon_state = "4"
color = RUNE_COLOR_DARKRED
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
var/datum/timedevent/density_timer
var/recharging = FALSE
@@ -758,7 +766,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Rite of Joined Souls: Summons a single cultist.
/obj/effect/rune/summon
cultist_name = "Summon Cultist"
- cultist_desc = "summons a single cultist to the rune. Requires 2 invokers."
+ cultist_desc = "Summons a single cultist to the rune. Requires 2 invokers."
invocation = "N'ath reth sh'yro eth d'rekkathnor!"
req_cultists = 2
invoke_damage = 10
@@ -816,7 +824,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Rite of Boiling Blood: Deals extremely high amounts of damage to non-cultists nearby
/obj/effect/rune/blood_boil
cultist_name = "Boil Blood"
- cultist_desc = "boils the blood of non-believers who can see the rune, rapidly dealing extreme amounts of damage. Requires 3 invokers."
+ cultist_desc = "Boils the blood of non-believers who can see the rune, rapidly dealing extreme amounts of damage. Requires 3 invokers."
invocation = "Dedo ol'btoh!"
icon_state = "4"
color = RUNE_COLOR_BURNTORANGE
@@ -881,7 +889,7 @@ structure_check() searches for nearby cultist structures required for the invoca
//Rite of Spectral Manifestation: Summons a ghost on top of the rune as a cultist human with no items. User must stand on the rune at all times, and takes damage for each summoned ghost.
/obj/effect/rune/manifest
cultist_name = "Spirit Realm"
- cultist_desc = "manifests a spirit servant of the Geometer and allows you to ascend as a spirit yourself. The invoker must not move from atop the rune, and will take damage for each summoned spirit."
+ cultist_desc = "Manifests a spirit servant of the Geometer and allows you to ascend as a spirit yourself. The invoker must not move from atop the rune, and will take damage for each summoned spirit."
invocation = "Gal'h'rfikk harfrandid mud'gib!" //how the fuck do you pronounce this
icon_state = "7"
invoke_damage = 10
@@ -1008,7 +1016,7 @@ structure_check() searches for nearby cultist structures required for the invoca
/obj/effect/rune/apocalypse
cultist_name = "Apocalypse"
- cultist_desc = "a harbinger of the end times. Grows in strength with the cult's desperation - but at the risk of... side effects."
+ cultist_desc = "A harbinger of the end times. Grows in strength with the cult's desperation - but at the risk of... side effects."
invocation = "Ta'gh fara'qha fel d'amar det!"
icon = 'icons/effects/96x96.dmi'
icon_state = "apoc"
diff --git a/code/modules/antagonists/demon/demons.dm b/code/modules/antagonists/demon/demons.dm
index 7310800742c6..1e412c31133d 100644
--- a/code/modules/antagonists/demon/demons.dm
+++ b/code/modules/antagonists/demon/demons.dm
@@ -27,6 +27,8 @@
/datum/action/cooldown/spell/touch/mend,
/datum/action/cooldown/spell/touch/torment,
/datum/action/cooldown/spell/conjure/cursed_item,
+ /datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin,
+ /datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/wrath,
))
var/static/list/sinfuldemon_traits = list(
@@ -127,6 +129,9 @@
var/datum/action/cooldown/spell/shapeshift/demon/gluttony/fat_demon = new(owner.current)
fat_demon.Grant(owner.current)
+ var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
+
ADD_TRAIT(owner.current, TRAIT_AGEUSIA, SINFULDEMON_TRAIT) // nothing disgusts you
ADD_TRAIT(owner.current, TRAIT_EAT_MORE, SINFULDEMON_TRAIT) // 3x hunger rate
ADD_TRAIT(owner.current, TRAIT_BOTTOMLESS_STOMACH, SINFULDEMON_TRAIT) // nutrition is capped for infinite eating
@@ -142,6 +147,9 @@
var/datum/action/cooldown/spell/conjure/cursed_item/immortal_temptation = new(owner.current)
immortal_temptation.Grant(owner.current)
+ var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
+
if(SIN_WRATH)
var/datum/action/cooldown/spell/shapeshift/demon/wrath/wrath_demon = new(owner.current)
wrath_demon.Grant(owner.current)
@@ -152,6 +160,9 @@
var/datum/action/cooldown/spell/touch/torment/pain_hand = new(owner.current)
pain_hand.Grant(owner.current)
+ var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/wrath/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
+
if(SIN_ENVY)
var/datum/action/cooldown/spell/shapeshift/demon/demon_form = new(owner.current)
demon_form.Grant(owner.current)
@@ -162,6 +173,9 @@
var/datum/action/cooldown/spell/touch/torment/pain_hand = new(owner.current)
pain_hand.Grant(owner.current)
+ var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
+
if(SIN_PRIDE)
var/datum/action/cooldown/spell/shapeshift/demon/demon_form = new(owner.current)
demon_form.Grant(owner.current)
@@ -172,12 +186,15 @@
var/datum/action/cooldown/spell/touch/mend/heal_hand = new(owner.current)
heal_hand.Grant(owner.current)
+ var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/jaunt = new(owner.current)
+ jaunt.Grant(owner.current)
+
return ..()
/datum/antagonist/sinfuldemon/on_removal()
owner.special_role = null
owner.current.faction -= "hell"
- for(var/all_status_traits in owner.current.status_traits) //removes demon traits
+ for(var/all_status_traits in owner.current._status_traits) //removes demon traits
REMOVE_TRAIT(owner.current, all_status_traits, SINFULDEMON_TRAIT)
for(var/datum/action/cooldown/spell/spell in owner.current.actions)
if(spell.target == owner)
diff --git a/code/modules/antagonists/demon/general_powers.dm b/code/modules/antagonists/demon/general_powers.dm
index 41834bb33b6a..555c78abbb76 100644
--- a/code/modules/antagonists/demon/general_powers.dm
+++ b/code/modules/antagonists/demon/general_powers.dm
@@ -45,8 +45,9 @@
melee_damage_lower = 20
melee_damage_upper = 20
wound_bonus = -15
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 5
loot = (/obj/effect/decal/cleanable/blood)
del_on_death = TRUE
@@ -108,7 +109,7 @@
..()
return TRUE
playsound(caster, 'sound/magic/demon_attack1.ogg', 75, TRUE)
- victim.blur_eyes(15) //huge array of relatively minor effects.
+ victim.adjust_eye_blur(15) //huge array of relatively minor effects.
victim.adjust_jitter(5 SECONDS)
victim.set_confusion_if_lower(5 SECONDS)
victim.adjust_disgust(40)
@@ -120,3 +121,18 @@
victim.emote("scream")
to_chat(victim, span_warning("You feel an explosion of pain erupt in your mind!"))
return TRUE
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin
+ name = "Demonic Jaunt"
+ desc = "Briefly turn to cinder and ash, allowing you to freely pass through objects."
+ background_icon_state = "bg_demon"
+ overlay_icon_state = "bg_demon_border"
+ sound = 'sound/magic/fireball.ogg'
+ spell_requirements = NONE
+
+ cooldown_time = 50 SECONDS
+
+ jaunt_duration = 3 SECONDS
+ jaunt_out_time = 0.5 SECONDS
+ jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift
+ jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out
diff --git a/code/modules/antagonists/demon/sins/wrath.dm b/code/modules/antagonists/demon/sins/wrath.dm
index 9dc381bcb541..71d296ec1e87 100644
--- a/code/modules/antagonists/demon/sins/wrath.dm
+++ b/code/modules/antagonists/demon/sins/wrath.dm
@@ -45,3 +45,12 @@
#undef WRATHFUL_FIRE_AMOUNT
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/sin/wrath
+ name = "Greater Demonic Jaunt"
+ desc = "Briefly turn to cinder and ash, allowing you to freely pass through objects. Lasts slightly shorter than normal, but is more easily used."
+
+ cooldown_time = 25 SECONDS
+
+ jaunt_duration = 2 SECONDS
+ jaunt_out_time = 0 SECONDS
+
diff --git a/code/modules/antagonists/devil/devil.dm b/code/modules/antagonists/devil/devil.dm
index bc131a39933c..e51538b60a18 100644
--- a/code/modules/antagonists/devil/devil.dm
+++ b/code/modules/antagonists/devil/devil.dm
@@ -538,7 +538,7 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
if(issilicon(owner.current))
var/mob/living/silicon/robot_devil = owner.current
var/laws = list("You may not use violence to coerce someone into selling their soul.", "You may not directly and knowingly physically harm a devil, other than yourself.", GLOB.lawlorify[LAW][ban], GLOB.lawlorify[LAW][obligation], "Accomplish your objectives at all costs.")
- robot_devil.set_law_sixsixsix(laws)
+ robot_devil.set_devil_laws(laws)
handle_clown_mutation(owner.current, "Your infernal nature has allowed you to overcome your clownishness.")
return ..()
diff --git a/code/modules/antagonists/devil/imp/imp.dm b/code/modules/antagonists/devil/imp/imp.dm
index 7c76013f3401..b4474c51e689 100644
--- a/code/modules/antagonists/devil/imp/imp.dm
+++ b/code/modules/antagonists/devil/imp/imp.dm
@@ -31,8 +31,10 @@
obj_damage = 40
melee_damage_lower = 10
melee_damage_upper = 15
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // You KNOW we're doing a lightly purple red
+ lighting_cutoff_red = 30
+ lighting_cutoff_green = 10
+ lighting_cutoff_blue = 20
var/boost = 0
bloodcrawl = BLOODCRAWL_EAT
var/list/consumed_mobs = list()
diff --git a/code/modules/antagonists/devil/true_devil/inventory.dm b/code/modules/antagonists/devil/true_devil/inventory.dm
index a3d0dbdf582b..e0aefdff50c5 100644
--- a/code/modules/antagonists/devil/true_devil/inventory.dm
+++ b/code/modules/antagonists/devil/true_devil/inventory.dm
@@ -17,8 +17,7 @@
hands_overlays += r_hand_overlay
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- r_hand.layer = ABOVE_HUD_LAYER
- r_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src)
r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand))
client.screen |= r_hand
@@ -28,8 +27,7 @@
hands_overlays += l_hand_overlay
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- l_hand.layer = ABOVE_HUD_LAYER
- l_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src)
l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand))
client.screen |= l_hand
if(hands_overlays.len)
diff --git a/code/modules/antagonists/disease/disease_datum.dm b/code/modules/antagonists/disease/disease_datum.dm
index fb18acc65d00..74c35d9d177c 100644
--- a/code/modules/antagonists/disease/disease_datum.dm
+++ b/code/modules/antagonists/disease/disease_datum.dm
@@ -76,7 +76,7 @@
return result.Join(" ")
/datum/antagonist/disease/get_preview_icon()
- var/icon/icon = icon('icons/mob/hud.dmi', "virus_infected")
+ var/icon/icon = icon('modular_dripstation/icons/mob/hud.dmi', "virus_infected") //dripstation edit
icon.Blend(COLOR_GREEN_GRAY, ICON_MULTIPLY)
icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
return icon
@@ -109,7 +109,7 @@
return FALSE
/datum/antagonist/disease/get_preview_icon()
- var/icon/disease_icon = icon('icons/mob/hud.dmi', "infected")
+ var/icon/disease_icon = icon('modular_dripstation/icons/mob/hud.dmi', "infected") //dripstation edit
disease_icon.Blend(COLOR_GREEN_GRAY, ICON_MULTIPLY)
disease_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE)
return disease_icon
diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm
index a9c6d361e279..9ebd36567bdc 100644
--- a/code/modules/antagonists/disease/disease_mob.dm
+++ b/code/modules/antagonists/disease/disease_mob.dm
@@ -13,14 +13,17 @@ the new instance inside the host to be updated to the template's stats.
icon_state = "marker"
mouse_opacity = MOUSE_OPACITY_ICON
move_on_shuttle = FALSE
- see_in_dark = 8
see_invisible = SEE_INVISIBLE_LIVING
invisibility = INVISIBILITY_OBSERVER
layer = BELOW_MOB_LAYER
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
sight = SEE_SELF|SEE_THRU
initial_language_holder = /datum/language_holder/universal
+ // Pale green, bright enough to have good vision
+ lighting_cutoff_red = 5
+ lighting_cutoff_green = 35
+ lighting_cutoff_blue = 20
+
var/freemove = TRUE
var/freemove_end = 0
var/const/freemove_time = 1200
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
index 5d23088f1e90..40c530c1efed 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
@@ -1,7 +1,7 @@
#define PENANCE_LIFE "Lose your life (10 marbles)"
#define PENANCE_SOUL "Lose your soul (14 marbles)"
-#define PENANCE_LIMB "Lose a limb (5 marbles)"
-#define PENANCE_SKELETON "Lose your flesh (1 marbles)"
+#define PENANCE_LIMB "Lose a limb (7 marbles)"
+#define PENANCE_SKELETON "Lose your flesh (3 marbles)"
#define PENANCE_TRAUMA_ADV "Lose your mind (5 marbles)"
#define PENANCE_TRAUMA_BASIC "Lose a smaller, but still important part of your mind (1 marbles)"
#define TRAUMA_ADV_CAP 1
@@ -335,7 +335,7 @@
var/list/unspooked_limbs = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG)
/atom/movable/screen/alert/status_effect/brazil_penance
- name = "Otherworldly Tarrif"
+ name = "Otherworldly Tariff"
desc = "The things of this place want something from you. You won't be able to leave until enough has been taken."
icon_state = "shadow_mend"
@@ -378,8 +378,8 @@
var/obj/item/bodypart/BP
while(!BP)
if(!LAZYLEN(unspooked_limbs))
- message_admins(span_notice("Someone managed to break brazil limb sacrificing stuff tell theos"))
- break
+ to_chat(C, span_warning("Something you did managed to break brazil limb sacrificing stuff, make a bug report!"))
+ return
var/target_zone = pick_n_take(unspooked_limbs)
BP = C.get_bodypart(target_zone)
C.visible_message(span_warning("[owner]'s [BP] suddenly disintegrates!"), span_warning("In a flash, your [BP] is torn from your body and disintegrates!"))
@@ -388,8 +388,8 @@
var/obj/item/bodypart/BP
while(!BP || BP.species_id == "skeleton")
if(!LAZYLEN(unspooked_limbs))
- message_admins(span_notice("Someone managed to break brazil limb sacrificing stuff tell theos"))
- break
+ to_chat(C, span_warning("Something you did managed to break brazil limb sacrificing stuff, make a bug report!"))
+ return
var/target_zone = pick_n_take(unspooked_limbs)
BP = C.get_bodypart(target_zone)
var/obj/item/bodypart/replacement_part = new BP.type
@@ -419,7 +419,7 @@
/obj/effect/penance_giver
name = "code ing"
- desc = "it takes your soul, and other stuff"
+ desc = "It takes your soul, and other stuff."
icon = 'icons/mob/triangle.dmi'
icon_state = "triangle"
light_power = 2
@@ -472,7 +472,7 @@
icon_state = "shell_narsie_active"
pixel_x = -16
pixel_y = -17
- penance_given = list(PENANCE_LIFE = 10, PENANCE_LIMB = 5)
+ penance_given = list(PENANCE_LIFE = 10, PENANCE_LIMB = 7)
/obj/effect/penance_giver/mind
name = "Headache"
@@ -488,7 +488,7 @@
icon = 'icons/mob/evilpope.dmi' //fun fact the pope's mask is off center on his north sprite and now you have to see it too
icon_state = "EvilPope"
light_color = COLOR_SILVER
- penance_given = list(PENANCE_SOUL = 14, PENANCE_SKELETON = 1)
+ penance_given = list(PENANCE_SOUL = 14, PENANCE_SKELETON = 3)
#undef PENANCE_LIFE
#undef PENANCE_SOUL
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_gun.dm b/code/modules/antagonists/eldritch_cult/eldritch_gun.dm
index 2f902acbc474..a21f30ec0d2b 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_gun.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_gun.dm
@@ -20,7 +20,7 @@
/obj/item/ammo_box/magazine/internal/boltaction/lionhunter
name = "lionhunter rifle internal magazine"
ammo_type = /obj/item/ammo_casing/strilka310/lionhunter
- caliber = "CALIBER_STRILKA310"
+ caliber = CALIBER_310
max_ammo = 3
multiload = 1
@@ -28,7 +28,7 @@
name = "strilka310 bullet casing"
desc = "A .310 bullet casing."
icon_state = "310-casing"
- caliber = "CALIBER_STRILKA310"
+ caliber = CALIBER_310
projectile_type = /obj/projectile/bullet/strilka310/lionhunter
/// Whether we're currently aiming this casing at something
var/currently_aiming = FALSE
@@ -134,8 +134,8 @@
// If fired without aiming or at someone too close, it will do much less
damage = 30
stamina = 30
- penetration_type = 2
- penetrating = TRUE
+ penetration_flags = PENETRATE_OBJECTS | PENETRATE_MOBS
+ penetrations = INFINITY
// Extra ammunition can be made with a heretic ritual.
/obj/item/ammo_box/strilka310/lionhunter
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
index 693016e600dc..32d16a4ff7ee 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
@@ -303,7 +303,8 @@
desc = "Allows the user to swap between three hud types, science, medical, and diagnostic"
icon_state = "godeye"
item_state = "godeye"
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Blue, light blue
+ color_cutoffs = list(15, 30, 40)
hud_type = DATA_HUD_SECURITY_BASIC
/obj/item/clothing/glasses/hud/toggle/eldritch_eye/equipped(mob/living/user, slot)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm b/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm
index 90bbe651339e..de6d0ddc545a 100644
--- a/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm
+++ b/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm
@@ -179,6 +179,7 @@
if(LH.target?.stat) ///wow this works
to_chat(carbon_user,span_danger("Your patrons accepts your offer.."))
+ SEND_SIGNAL(carbon_user, COMSIG_ADD_MOOD_EVENT, "hereticoffer", /datum/mood_event/sacrifice_heretic) //dripstation edit
var/mob/living/carbon/human/H = LH.target
H.apply_status_effect(STATUS_EFFECT_BRAZIL_PENANCE)
var/datum/antagonist/heretic/EC = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic)
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/blade_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/blade_lore.dm
index 6989d662bb75..cf74d211784f 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/blade_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/blade_lore.dm
@@ -12,6 +12,7 @@
name = "knife"
icon = 'icons/obj/kitchen.dmi';
icon_state = "knife"
+ layer = LOW_MOB_LAYER
/// The color the knife glows around it.
var/glow_color = "#ececff"
@@ -209,7 +210,7 @@
/datum/eldritch_knowledge/duel_stance/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
ADD_TRAIT(user, TRAIT_NODISMEMBER, type)
- RegisterSignal(user, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(user, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(user, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_update))
on_health_update(user) // Run this once, so if the knowledge is learned while hurt it activates properly
@@ -219,7 +220,7 @@
if(in_duelist_stance)
user.remove_traits(list(TRAIT_HARDLY_WOUNDED, TRAIT_REDUCED_DAMAGE_SLOWDOWN), type)
- UnregisterSignal(user, list(COMSIG_PARENT_EXAMINE, COMSIG_CARBON_GAIN_WOUND, COMSIG_LIVING_HEALTH_UPDATE))
+ UnregisterSignal(user, list(COMSIG_ATOM_EXAMINE, COMSIG_CARBON_GAIN_WOUND, COMSIG_LIVING_HEALTH_UPDATE))
/datum/eldritch_knowledge/duel_stance/proc/on_examine(mob/living/source, mob/user, list/examine_list)
SIGNAL_HANDLER
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/mind_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/mind_lore.dm
index a39b68b38f7c..195140c21169 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/mind_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/mind_lore.dm
@@ -31,7 +31,7 @@
if(!ishuman(target))
return COMPONENT_BLOCK_HAND_USE
var/mob/living/carbon/human/human_target = target
- human_target.blur_eyes(1 SECONDS)
+ human_target.adjust_eye_blur(1 SECONDS)
human_target.Knockdown(2 SECONDS)
/datum/eldritch_knowledge/spell/eldritchbolt
@@ -76,7 +76,7 @@
if(!ishuman(target))
return COMPONENT_BLOCK_HAND_USE
var/mob/living/carbon/human/human_target = target
- human_target.blur_eyes(2 SECONDS)
+ human_target.adjust_eye_blur(2 SECONDS)
human_target.blind_eyes(1 SECONDS)
/datum/eldritch_knowledge/spell/assault
diff --git a/code/modules/antagonists/eldritch_cult/magic/blade_magic.dm b/code/modules/antagonists/eldritch_cult/magic/blade_magic.dm
index f0fb49742e29..986d46e057bb 100644
--- a/code/modules/antagonists/eldritch_cult/magic/blade_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/magic/blade_magic.dm
@@ -84,12 +84,12 @@
// Delete existing
if(blade_effect)
stack_trace("[type] had an existing blade effect in on_activation. This might be an exploit, and should be investigated.")
- UnregisterSignal(blade_effect, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(blade_effect, COMSIG_QDELETING)
QDEL_NULL(blade_effect)
var/mob/living/living_user = on_who
blade_effect = living_user.apply_status_effect(/datum/status_effect/protective_blades, null, projectile_amount, 25, 0.66 SECONDS)
- RegisterSignal(blade_effect, COMSIG_PARENT_QDELETING, PROC_REF(on_status_effect_deleted))
+ RegisterSignal(blade_effect, COMSIG_QDELETING, PROC_REF(on_status_effect_deleted))
/datum/action/cooldown/spell/pointed/projectile/furious_steel/on_deactivation(mob/on_who, refund_cooldown = TRUE)
. = ..()
diff --git a/code/modules/antagonists/eldritch_cult/magic/cosmo_magic.dm b/code/modules/antagonists/eldritch_cult/magic/cosmo_magic.dm
index 711348b36c08..82336f434b10 100644
--- a/code/modules/antagonists/eldritch_cult/magic/cosmo_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/magic/cosmo_magic.dm
@@ -248,7 +248,7 @@
/datum/action/cooldown/spell/conjure/cosmic_expansion
name = "Cosmic Expansion"
desc = "This spell generates a 3x3 domain of cosmic fields. \
- Creatures up to 7 tiles away will also recieve a star mark."
+ Creatures up to 7 tiles away will also receive a star mark."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -295,7 +295,7 @@
name = "Cosmic Domain"
desc = "This spell generates a domain of cosmic fields. \
- Creatures up to 7 tiles away will also recieve a star mark."
+ Creatures up to 7 tiles away will also receive a star mark."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
diff --git a/code/modules/antagonists/eldritch_cult/magic/rust_magic.dm b/code/modules/antagonists/eldritch_cult/magic/rust_magic.dm
index 8804e4240d29..571c445d1cdd 100644
--- a/code/modules/antagonists/eldritch_cult/magic/rust_magic.dm
+++ b/code/modules/antagonists/eldritch_cult/magic/rust_magic.dm
@@ -107,18 +107,18 @@ RUST PATH SPELLS GO HERE
range = 15
speed = 1
-/obj/projectile/magic/aoe/rust_wave/Moved(atom/OldLoc, Dir)
+/obj/projectile/magic/aoe/rust_wave/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
playsound(src, 'sound/items/welder.ogg', 75, TRUE)
var/list/turflist = list()
var/turf/T1
turflist += get_turf(src)
- T1 = get_step(src,turn(dir,90))
+ T1 = get_step(src,turn(movement_dir,90))
turflist += T1
- turflist += get_step(T1,turn(dir,90))
- T1 = get_step(src,turn(dir,-90))
+ turflist += get_step(T1,turn(movement_dir,90))
+ T1 = get_step(src,turn(movement_dir,-90))
turflist += T1
- turflist += get_step(T1,turn(dir,-90))
+ turflist += get_step(T1,turn(movement_dir,-90))
for(var/X in turflist)
if(!X || prob(25))
continue
@@ -226,7 +226,7 @@ RUST PATH SPELLS GO HERE
/datum/action/cooldown/spell/pointed/rust_construction/cast(turf/open/cast_on)
. = ..()
var/rises_message = "rises out of [cast_on]"
- var/turf/closed/wall/new_wall = cast_on.PlaceOnTop(/turf/closed/wall)
+ var/turf/closed/wall/new_wall = cast_on.place_on_top(/turf/closed/wall)
if(!istype(new_wall))
return
diff --git a/code/modules/antagonists/eldritch_cult/transmutations/knock_transmutations.dm b/code/modules/antagonists/eldritch_cult/transmutations/knock_transmutations.dm
index 1330b4e63c0e..33103ca21814 100644
--- a/code/modules/antagonists/eldritch_cult/transmutations/knock_transmutations.dm
+++ b/code/modules/antagonists/eldritch_cult/transmutations/knock_transmutations.dm
@@ -59,7 +59,7 @@
monster_types = subtypesof(/mob/living/simple_animal/hostile/eldritch) - monster_types_blacklist
if(!isnull(ascendant_mind))
ascendee = ascendant_mind
- RegisterSignals(ascendant_mind.current, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING), PROC_REF(end_madness))
+ RegisterSignals(ascendant_mind.current, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(end_madness))
INVOKE_ASYNC(src, PROC_REF(poll_ghosts))
/// Ask ghosts if they want to make some noise
@@ -76,7 +76,7 @@
var/turf/our_turf = get_turf(src)
playsound(our_turf, 'sound/magic/castsummon.ogg', vol = 100, vary = TRUE)
visible_message(span_boldwarning("The rip in space spasms and disappears!"))
- UnregisterSignal(former_master, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING)) // Just in case they die THEN delete
+ UnregisterSignal(former_master, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING)) // Just in case they die THEN delete
new /obj/effect/temp_visual/destabilising_tear(our_turf)
qdel(src)
diff --git a/code/modules/antagonists/ert/ert.dm b/code/modules/antagonists/ert/ert.dm
index f5892b008e6e..53778771773e 100644
--- a/code/modules/antagonists/ert/ert.dm
+++ b/code/modules/antagonists/ert/ert.dm
@@ -78,7 +78,7 @@
/datum/antagonist/ert/deathsquad/New()
. = ..()
name_source = GLOB.commando_names
-
+
/datum/antagonist/ert/clown/New()
. = ..()
name_source = GLOB.clown_names
@@ -138,6 +138,10 @@
outfit = /datum/outfit/death_commando
role = "Trooper"
+/datum/antagonist/ert/mining
+ name = "Dwarven Miner"
+ outfit = /datum/outfit/ert/mining
+
/datum/antagonist/ert/medic/inquisitor
outfit = /datum/outfit/ert/medic/inquisitor
@@ -207,7 +211,7 @@
name = "Occupying Riot Officer"
outfit = /datum/outfit/occupying/heavy
role = "Riot Officer"
-
+
/datum/antagonist/ert/occupying/commander
name = "Occupying Commander"
outfit = /datum/outfit/occupying/commander
@@ -222,7 +226,7 @@
name = "HONK Squad Trooper"
outfit = /datum/outfit/centcom_clown/honk_squad
role = "HONKER"
-
+
/datum/antagonist/ert/imperial
name = "Imperial Guardsman"
outfit = /datum/outfit/imperial
diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm
index 03ca12a00b9c..c476f05a1f97 100644
--- a/code/modules/antagonists/horror/horror.dm
+++ b/code/modules/antagonists/horror/horror.dm
@@ -9,8 +9,9 @@
maxHealth = 50
melee_damage_lower = 10
melee_damage_upper = 10
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 5
stop_automated_movement = TRUE
attacktext = "bites"
speak_emote = list("gurgles")
diff --git a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
index 8213353bc619..00824d9fe6ae 100644
--- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
+++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
@@ -437,7 +437,7 @@
/datum/horror_upgrade/upgraded_chems
name = "Advanced reagent synthesis"
id = "upgraded_chems"
- desc = "Lets you synthetize adrenaline, salicyclic acid, oxandrolone, pentetic acid and rezadone into your host."
+ desc = "Lets you synthesize adrenaline, salicylic acid, oxandrolone, pentetic acid and rezadone into your host."
soul_price = 2
/datum/horror_upgrade/upgraded_chems/apply_effects()
diff --git a/code/modules/antagonists/horror/horror_chemicals.dm b/code/modules/antagonists/horror/horror_chemicals.dm
index 704ca1b9dc15..f6ca90f7f3bd 100644
--- a/code/modules/antagonists/horror/horror_chemicals.dm
+++ b/code/modules/antagonists/horror/horror_chemicals.dm
@@ -85,11 +85,11 @@
chem_desc = "Reduces massive amounts of radiation and toxin damage while purging other chemicals from the body."
/datum/horror_chem/sal_acid
- chemname = "salicyclic acid"
+ chemname = "salicylic acid"
R = /datum/reagent/medicine/sal_acid
chem_desc = "Stimulates the healing of severe bruises. Rapidly heals severe bruising and slowly heals minor ones."
/datum/horror_chem/oxandrolone
chemname = "oxandrolone"
R = /datum/reagent/medicine/oxandrolone
- chem_desc = "Stimulates the healing of severe burns. Rapidly heals severe burns and slowly heals minor ones."
\ No newline at end of file
+ chem_desc = "Stimulates the healing of severe burns. Rapidly heals severe burns and slowly heals minor ones."
diff --git a/code/modules/antagonists/morph/morph.dm b/code/modules/antagonists/morph/morph.dm
index 4513e199850e..25f2cf9fff4e 100644
--- a/code/modules/antagonists/morph/morph.dm
+++ b/code/modules/antagonists/morph/morph.dm
@@ -24,8 +24,10 @@
obj_damage = 50
melee_damage_lower = 20
melee_damage_upper = 20
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Oh you KNOW it's gonna be real green
+ lighting_cutoff_red = 10
+ lighting_cutoff_green = 35
+ lighting_cutoff_blue = 15
vision_range = 1 // Only attack when target is close
wander = FALSE
attack_vis_effect = ATTACK_EFFECT_BITE //nom nom nom
diff --git a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
index 27f710613846..4e720c328ab2 100644
--- a/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
+++ b/code/modules/antagonists/nukeop/equipment/borgchameleon.dm
@@ -118,7 +118,7 @@
var/obj/item/robot_module/M = modulelist[option]
var/is = initial(M.cyborg_base_icon)
moduleicons[option] = image(icon = 'icons/mob/robots.dmi', icon_state = is)
- var/input_module = show_radial_menu(usr, usr, moduleicons, radius = 42, custom_check = CALLBACK(src, PROC_REF(cancel_radical_on_move), usr, user.loc), check_delay = 0.1 SECONDS)
+ var/input_module = show_radial_menu(usr, usr, moduleicons, radius = 42, custom_check = CALLBACK(src, PROC_REF(cancel_radical_on_move), usr, user.loc))
// Sanity
if(!input_module || active)
@@ -134,7 +134,7 @@
savedName = user.name
user.name = friendlyName
user.module.cyborg_base_icon = initial(selected_module.cyborg_base_icon)
- user.bubble_icon = "robot"
+ user.bubble_icon = BUBBLE_ROBOT
user.module.name = input_module
active = TRUE
user.update_icons()
diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
index 6157aa467e5b..71f316318918 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -459,8 +459,8 @@
var/off_station = 0
var/turf/bomb_location = get_turf(src)
var/area/A = get_area(bomb_location)
- if(istype(A, /area/fabric_of_reality))
- var/area/fabric_of_reality/fabric = A
+ if(istype(A, /area/centcom/fabric_of_reality))
+ var/area/centcom/fabric_of_reality/fabric = A
new /obj/singularity(fabric.origin, 2000) // Stage five singulo back on the station, as a gift
else if(bomb_location && is_station_level(bomb_location.z))
if(istype(A, /area/space) || istype(A, /area/shuttle/syndicate))
@@ -484,8 +484,8 @@
/obj/machinery/nuclearbomb/proc/really_actually_explode(off_station)
Cinematic(get_cinematic_type(off_station),world,CALLBACK(SSticker,/datum/controller/subsystem/ticker/proc/station_explosion_detonation,src))
var/area/A = get_area(src)
- if(istype(A, /area/fabric_of_reality))
- var/area/fabric_of_reality/fabric = A
+ if(istype(A, /area/centcom/fabric_of_reality))
+ var/area/centcom/fabric_of_reality/fabric = A
var/turf/T = fabric.origin
INVOKE_ASYNC(GLOBAL_PROC, PROC_REF(KillEveryoneOnZLevel), T.z)
else
diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm
index 2e53e926ab3e..7378a9431957 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -39,6 +39,7 @@
to_chat(owner, span_notice("You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!"))
owner.announce_objectives()
+/* //dripstation edit
/datum/antagonist/nukeop/on_gain()
give_alias()
. = ..()
@@ -51,6 +52,7 @@
var/datum/component/uplink/U = owner.find_syndicate_uplink()
if (U)
U.telecrystals += extra_tc
+*/ //dripstation edit
diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm
index b0f30b70856b..fdd5d2b8690e 100644
--- a/code/modules/antagonists/revenant/revenant.dm
+++ b/code/modules/antagonists/revenant/revenant.dm
@@ -3,7 +3,6 @@
//Don't hear deadchat and are NOT normal ghosts
//Admin-spawn or random event
-#define INVISIBILITY_REVENANT 50
#define REVENANT_NAME_FILE "revenant_names.json"
/mob/living/simple_animal/revenant
@@ -21,14 +20,16 @@
invisibility = INVISIBILITY_REVENANT
health = INFINITY //Revenants don't use health, they use essence instead
maxHealth = INFINITY
- layer = GHOST_LAYER
+ plane = GHOST_PLANE
healable = FALSE
spacewalk = TRUE
sight = SEE_MOBS | SEE_OBJS | SEE_TURFS | SEE_SELF
throwforce = 0
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Going for faint purple spoopy ghost
+ lighting_cutoff_red = 20
+ lighting_cutoff_green = 15
+ lighting_cutoff_blue = 35
response_help = "passes through"
response_disarm = "swings through"
response_harm = "punches through"
@@ -72,10 +73,6 @@
flags_1 |= RAD_NO_CONTAMINATE_1
ADD_TRAIT(src, TRAIT_SIXTHSENSE, INNATE_TRAIT)
- // Starting spells
- var/datum/action/cooldown/spell/night_vision/revenant/vision = new(src)
- vision.Grant(src)
-
var/datum/action/cooldown/spell/list_target/telepathy/revenant/telepathy = new(src)
telepathy.Grant(src)
diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm
index 1253d94a738a..15c49f857d0f 100644
--- a/code/modules/antagonists/revenant/revenant_abilities.dm
+++ b/code/modules/antagonists/revenant/revenant_abilities.dm
@@ -71,7 +71,7 @@
span_revenwarning("Violet lights, dancing in your vision, receding--"))
draining = FALSE
return
- var/datum/beam/B = Beam(target,icon_state="drain_life",time=INFINITY)
+ var/datum/beam/draining_beam = Beam(target, icon_state = "drain_life")
if(do_after(src, 4.6 SECONDS, target, timed_action_flags = IGNORE_HELD_ITEM)) //As one cannot prove the existance of ghosts, ghosts cannot prove the existance of the target they were draining.
change_essence_amount(essence_drained, FALSE, target)
if(essence_drained <= 90 && target.stat != DEAD)
@@ -91,21 +91,12 @@
if(target) //Wait, target is WHERE NOW?
target.visible_message(span_warning("[target] slumps onto the ground."), \
span_revenwarning("Violets lights, dancing in your vision, receding--"))
- qdel(B)
+ qdel(draining_beam)
else
to_chat(src, span_revenwarning("You are not close enough to siphon [target ? "[target]'s":"[target.p_their()]"] soul. The link has been broken."))
draining = FALSE
essence_drained = 0
-
-//Toggle night vision: lets the revenant toggle its night vision
-/datum/action/cooldown/spell/night_vision/revenant
- name = "Toggle Darkvision"
- panel = "Revenant Abilities"
- background_icon_state = "bg_revenant"
- button_icon = 'icons/mob/actions/actions_revenant.dmi'
- button_icon_state = "r_nightvision"
- toggle_span = "revennotice"
-
+
//Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob
/datum/action/cooldown/spell/list_target/telepathy/revenant
name = "Revenant Transmit"
@@ -268,7 +259,7 @@
if(!isplatingturf(victim) && !istype(victim, /turf/open/floor/engine/cult) && isfloorturf(victim) && prob(15))
var/turf/open/floor/floor = victim
- if(floor.floor_tile)
+ if(floor.overfloor_placed && floor.floor_tile)
new floor.floor_tile(floor)
floor.broken = 0
floor.burnt = 0
diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm
index 51de65512872..77db2ed669ba 100644
--- a/code/modules/antagonists/revolution/revolution.dm
+++ b/code/modules/antagonists/revolution/revolution.dm
@@ -175,7 +175,7 @@
// Otherwise, the R gets cut off.
final_icon.Scale(64, 64)
- var/icon/rev_head_icon = icon('yogstation/icons/mob/antag_hud.dmi', "rev_head")
+ var/icon/rev_head_icon = icon('modular_dripstation/icons/mob/hud.dmi', "rev_head") //dripstation edit
rev_head_icon.Scale(48, 48)
rev_head_icon.Crop(1, 1, 64, 64)
rev_head_icon.Shift(EAST, 10)
diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm
index 5b186f6696c5..adaa1d088467 100644
--- a/code/modules/antagonists/slaughter/slaughter.dm
+++ b/code/modules/antagonists/slaughter/slaughter.dm
@@ -33,8 +33,9 @@
obj_damage = 50
melee_damage_lower = 30
melee_damage_upper = 30
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 5
bloodcrawl = BLOODCRAWL_EAT
var/playstyle_string = "You are a slaughter demon, a terrible creature from another realm. You have a single desire: To kill. \
You may use the \"Blood Crawl\" ability near blood pools to travel through them, appearing and disappearing from the station at will. \
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 877b97da4936..661f10255f92 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -70,7 +70,7 @@
if(uplink_holder)
var/datum/component/uplink/uplink = uplink_holder.GetComponent(/datum/component/uplink)
if(uplink)//remove uplink so they can't keep using it if admin abuse happens
- uplink.RemoveComponent()
+ qdel(uplink)
UnregisterSignal(owner.current, COMSIG_MOVABLE_HEAR)
SSticker.mode.traitors -= owner
if(!silent && owner.current)
@@ -195,14 +195,14 @@
destroy_objective.owner = owner
destroy_objective.find_target()
add_objective(destroy_objective)
- else if(prob(30))
- var/datum/objective/maroon/maroon_objective = new
- maroon_objective.owner = owner
- maroon_objective.find_target()
- add_objective(maroon_objective)
+ else if(prob(20))
+ var/datum/objective/maroon_organ/organ_objective = new
+ organ_objective.owner = owner
+ organ_objective.finalize()
+ add_objective(organ_objective)
else
- var/N = pick(/datum/objective/assassinate, /datum/objective/assassinate/cloned, /datum/objective/assassinate/once)
- var/datum/objective/assassinate/kill_objective = new N
+ var/N = pick(/datum/objective/assassinate/cloned, /datum/objective/assassinate/once, /datum/objective/assassinate, /datum/objective/maroon)
+ var/datum/objective/kill_objective = new N
kill_objective.owner = owner
kill_objective.find_target()
add_objective(kill_objective)
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index bc3c4bbf5ea3..c820bc437c2a 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -386,11 +386,11 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
. = ..()
desc = "[desc] It has [uses] use\s remaining."
-/datum/action/innate/ai/ranged/override_machine/do_ability(mob/living/caller, atom/clicked_on)
+/datum/action/innate/ai/ranged/override_machine/do_ability(mob/living/caller, params, atom/clicked_on)
if(caller.incapacitated())
unset_ranged_ability(caller)
return FALSE
- if(!istype(clicked_on, /obj/machinery))
+ if(!ismachinery(clicked_on))
to_chat(caller, span_warning("You can only animate machines!"))
return FALSE
var/obj/machinery/clicked_machine = clicked_on
@@ -474,7 +474,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module))
if(!QDELETED(to_explode)) //to check if the explosion killed it before we try to delete it
qdel(to_explode)
-/datum/action/innate/ai/ranged/overload_machine/do_ability(mob/living/caller, atom/clicked_on)
+/datum/action/innate/ai/ranged/overload_machine/do_ability(mob/living/caller, params, atom/clicked_on)
if(caller.incapacitated())
unset_ranged_ability(caller)
return FALSE
diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm
index 52fed5895983..2025c419f1ad 100644
--- a/code/modules/antagonists/traitor/syndicate_contract.dm
+++ b/code/modules/antagonists/traitor/syndicate_contract.dm
@@ -163,7 +163,7 @@
M.flash_act()
M.adjust_confusion(10 SECONDS)
- M.blur_eyes(0.5 SECONDS)
+ M.adjust_eye_blur(0.5 SECONDS)
to_chat(M, span_warning("You feel strange..."))
sleep(6 SECONDS)
to_chat(M, span_warning("That pod did something to you..."))
@@ -172,7 +172,7 @@
to_chat(M, span_warning("Your head pounds... It feels like it's going to burst out your skull!"))
M.flash_act()
M.adjust_confusion(20 SECONDS)
- M.blur_eyes(3)
+ M.adjust_eye_blur(3)
sleep(3 SECONDS)
to_chat(M, span_warning("Your head pounds..."))
sleep(10 SECONDS)
@@ -181,7 +181,7 @@
to_chat(M, "A million voices echo in your head... \"Your mind held many valuable secrets - \
we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \
so it's only a matter of time before we ship you back...\"")
- M.blur_eyes(1 SECONDS)
+ M.adjust_eye_blur(1 SECONDS)
M.adjust_dizzy(1.5 SECONDS)
M.adjust_confusion(20 SECONDS)
@@ -220,7 +220,7 @@
M.forceMove(return_pod)
M.flash_act()
- M.blur_eyes(30)
+ M.adjust_eye_blur(30)
M.adjust_dizzy(35 SECONDS)
M.adjust_confusion(20 SECONDS)
diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm
index 07b5a9e52fd6..0d5982d66891 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm
@@ -155,7 +155,7 @@
* Return -1 on failure, or return the point value of the refund on success
*/
/datum/spellbook_entry/proc/refund_spell(mob/living/carbon/human/user, obj/item/spellbook/book)
- var/area/wizard_station/wizard_home = GLOB.areas_by_type[/area/wizard_station]
+ var/area/centcom/wizard_station/wizard_home = GLOB.areas_by_type[/area/centcom/wizard_station]
if(get_area(user) != wizard_home)
to_chat(user, span_warning("You can only refund spells at the wizard lair!"))
return -1
diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm
index c7568497afc4..e56821fc7e33 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm
@@ -30,6 +30,13 @@
category = "Offensive"
cost = 1
+/datum/spellbook_entry/appendicitis
+ name = "Bestow Appendicitis"
+ desc = "Give someone appendicitis."
+ spell_type = /datum/action/cooldown/spell/pointed/appendicitis
+ category = "Offensive"
+ cost = 1
+
/datum/spellbook_entry/mutate
name = "Mutate"
desc = "Causes you to turn into a hulk and gain laser vision for a short while."
diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm
index b3daa433db9e..2ac8f3b83d8d 100644
--- a/code/modules/assembly/flash.dm
+++ b/code/modules/assembly/flash.dm
@@ -9,9 +9,9 @@
throwforce = 0
w_class = WEIGHT_CLASS_TINY
materials = list(/datum/material/iron = 300, /datum/material/glass = 300)
- light_color = LIGHT_COLOR_WHITE
light_system = MOVABLE_LIGHT //Used as a flash here.
light_range = FLASH_LIGHT_RANGE
+ light_color = COLOR_WHITE
light_power = FLASH_LIGHT_POWER
light_on = FALSE
fryable = TRUE
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 77ce1dd11cc8..cdb02b9dfb0e 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -124,7 +124,7 @@
. = ..()
refreshBeam()
-/obj/item/assembly/infra/Moved()
+/obj/item/assembly/infra/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
var/t = dir
. = ..()
setDir(t)
diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm
index cd2eb6972683..75f8fd51ee9c 100644
--- a/code/modules/assembly/mousetrap.dm
+++ b/code/modules/assembly/mousetrap.dm
@@ -9,6 +9,13 @@
var/armed = FALSE
+/obj/item/assembly/mousetrap/Initialize(mapload)
+ . = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(trap_stepped_on),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
/obj/item/assembly/mousetrap/examine(mob/user)
. = ..()
. += span_notice("The pressure plate is [armed?"primed":"safe"].")
@@ -109,7 +116,7 @@
return ..()
-/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj)
+/obj/item/assembly/mousetrap/proc/trap_stepped_on(datum/source, atom/movable/AM, ...)
if(armed)
if(ismob(AM))
var/mob/MM = AM
@@ -124,7 +131,7 @@
triggered(MM)
else if(AM.density) // For mousetrap grenades, set off by anything heavy
triggered(AM)
- ..()
+ return
/obj/item/assembly/mousetrap/on_found(mob/finder)
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index fd72dc814a4d..0ac3e148490a 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -654,3 +654,59 @@
glow = "pod_glow_[glow]"
podIcon.Blend(icon(icon_file, glow), ICON_OVERLAY)
Insert("pod_asset[style]", podIcon)
+
+/datum/asset/spritesheet/rcd
+ name = "rcd-tgui"
+
+/datum/asset/spritesheet/rcd/create_spritesheets()
+ //We load airlock icons seperatly from other icons cause they need overlays
+
+ //load all category essential icon_states. format is icon_file = list of icon states we need from that file
+ var/list/essentials = list(
+ 'icons/mob/radial.dmi' = list("wallfloor", "delete", "dirwindow", "fullwindow", "dirwindow_r", "fullwindow_r", "cnorth", "csouth", "ceast", "cwest", "chair", "stool", "windoor", "secure_windoor"),
+ 'icons/obj/recycling.dmi' = list("conveyor_construct", "switch-off"),
+ 'icons/obj/structures.dmi' = list("window0", "rwindow0", "table", "glass_table"),
+ 'icons/obj/stock_parts.dmi' = list("box_1"),
+ )
+
+ var/icon/icon
+ for(var/icon_file as anything in essentials)
+ for(var/icon_state as anything in essentials[icon_file])
+ icon = icon(icon = icon_file, icon_state = icon_state)
+ Insert(sanitize_css_class_name(icon_state), icon)
+
+ //for each airlock type we create its overlayed version with the suffix Glass in the sprite name
+ var/list/airlocks = list(
+ "Standard" = 'icons/obj/doors/airlocks/station/public.dmi',
+ "Public" = 'icons/obj/doors/airlocks/station2/glass.dmi',
+ "Engineering" = 'icons/obj/doors/airlocks/station/engineering.dmi',
+ "Atmospherics" = 'icons/obj/doors/airlocks/station/atmos.dmi',
+ "Security" = 'icons/obj/doors/airlocks/station/security.dmi',
+ "Command" = 'icons/obj/doors/airlocks/station/command.dmi',
+ "Medical" = 'icons/obj/doors/airlocks/station/medical.dmi',
+ "Research" = 'icons/obj/doors/airlocks/station/research.dmi',
+ "Freezer" = 'icons/obj/doors/airlocks/station/freezer.dmi',
+ "Virology" = 'icons/obj/doors/airlocks/station/virology.dmi',
+ "Mining" = 'icons/obj/doors/airlocks/station/mining.dmi',
+ "Maintenance" = 'icons/obj/doors/airlocks/station/maintenance.dmi',
+ "External" = 'icons/obj/doors/airlocks/external/external.dmi',
+ "External Maintenance" = 'icons/obj/doors/airlocks/station/maintenanceexternal.dmi',
+ "Airtight Hatch" = 'icons/obj/doors/airlocks/hatch/centcom.dmi',
+ "Maintenance Hatch" = 'icons/obj/doors/airlocks/hatch/maintenance.dmi'
+ )
+ //these 3 types dont have glass doors
+ var/list/exclusion = list("Freezer", "Airtight Hatch", "Maintenance Hatch")
+
+ for(var/airlock_name in airlocks)
+ //solid door with overlay
+ icon = icon(icon = airlocks[airlock_name] , icon_state = "closed" , dir = SOUTH)
+ icon.Blend(icon(icon = airlocks[airlock_name], icon_state = "fill_closed", dir = SOUTH), ICON_OVERLAY)
+ Insert(sanitize_css_class_name(airlock_name), icon)
+
+ //exclude these glass types
+ if(airlock_name in exclusion)
+ continue
+
+ //glass door no overlay
+ icon = icon(airlocks[airlock_name] , "closed" , SOUTH)
+ Insert(sanitize_css_class_name("[airlock_name]Glass"), icon)
diff --git a/code/modules/asset_cache/assets/plane_debug.dm b/code/modules/asset_cache/assets/plane_debug.dm
new file mode 100644
index 000000000000..d510aee82486
--- /dev/null
+++ b/code/modules/asset_cache/assets/plane_debug.dm
@@ -0,0 +1,4 @@
+/datum/asset/simple/plane_background
+ assets = list(
+ "grid_background.png" = 'icons/UI_Icons/tgui/grid_background.png'
+ )
diff --git a/code/modules/atmospherics/environmental/LINDA_system.dm b/code/modules/atmospherics/environmental/LINDA_system.dm
index fc3d4ffa183b..1e354e93b455 100644
--- a/code/modules/atmospherics/environmental/LINDA_system.dm
+++ b/code/modules/atmospherics/environmental/LINDA_system.dm
@@ -1,34 +1,52 @@
-/atom/var/CanAtmosPass = ATMOS_PASS_YES
-/atom/var/CanAtmosPassVertical = ATMOS_PASS_YES
+/atom
+ ///Check if atmos can pass in this atom (ATMOS_PASS_YES, ATMOS_PASS_NO, ATMOS_PASS_DENSITY, ATMOS_PASS_PROC)
+ var/can_atmos_pass = ATMOS_PASS_YES
-/atom/proc/CanAtmosPass(turf/T)
- switch (CanAtmosPass)
+/atom/proc/can_atmos_pass(turf/T)
+ switch (can_atmos_pass)
if (ATMOS_PASS_PROC)
return ATMOS_PASS_YES
if (ATMOS_PASS_DENSITY)
return !density
else
- return CanAtmosPass
-
-/turf/CanAtmosPass = ATMOS_PASS_NO
-/turf/CanAtmosPassVertical = ATMOS_PASS_NO
-
-/turf/open/CanAtmosPass = ATMOS_PASS_PROC
-/turf/open/CanAtmosPassVertical = ATMOS_PASS_PROC
-
-/turf/open/CanAtmosPass(turf/T, vertical = FALSE)
- var/dir = vertical? get_dir_multiz(src, T) : get_dir(src, T)
- . = TRUE
- if(vertical && !(zAirOut(dir, T) && T.zAirIn(dir, src)))
- . = FALSE
- if(blocks_air || T.blocks_air)
- . = FALSE
- if (T == src)
- return .
- for(var/obj/O in contents+T.contents)
- var/turf/other = (O.loc == src ? T : src)
- if(!(vertical? (CANVERTICALATMOSPASS(O, other)) : (CANATMOSPASS(O, other))))
- . = FALSE
+ return can_atmos_pass
+
+/turf
+ can_atmos_pass = ATMOS_PASS_NO
+
+/turf/open
+ can_atmos_pass = ATMOS_PASS_PROC
+
+/turf/open/can_atmos_pass(turf/target_turf, vertical = FALSE)
+ var/can_pass = TRUE
+ var/direction = vertical ? get_dir_multiz(src, target_turf) : get_dir(src, target_turf)
+ var/opposite_direction = REVERSE_DIR(direction)
+ if(vertical && !(zAirOut(direction, target_turf) && target_turf.zAirIn(direction, src)))
+ can_pass = FALSE
+ if(blocks_air || target_turf.blocks_air)
+ can_pass = FALSE
+ //This path is a bit weird, if we're just checking with ourselves no sense asking objects on the turf
+ if (target_turf == src)
+ return can_pass
+
+ //Can't just return if canpass is false here, we need to set superconductivity
+ for(var/obj/checked_object in contents + target_turf.contents)
+ var/turf/other = (checked_object.loc == src ? target_turf : src)
+ if(CANATMOSPASS(checked_object, other, vertical))
+ continue
+ can_pass = FALSE
+ //the direction and open/closed are already checked on can_atmos_pass() so there are no arguments
+ if(checked_object.BlockThermalConductivity())
+ conductivity_blocked_directions |= direction
+ target_turf.conductivity_blocked_directions |= opposite_direction
+ return FALSE //no need to keep going, we got all we asked (Is this even faster? fuck you it's soul)
+
+ //Superconductivity is a bitfield of directions we can't conduct with
+ //Yes this is really weird. Fuck you
+ conductivity_blocked_directions &= ~direction
+ target_turf.conductivity_blocked_directions &= ~opposite_direction
+
+ return can_pass
/turf/proc/update_conductivity(turf/T)
var/dir = get_dir_multiz(src, T)
@@ -44,7 +62,7 @@
return
for(var/obj/O in contents+T.contents)
- if(O.BlockThermalConductivity(opp)) //the direction and open/closed are already checked on CanAtmosPass() so there are no arguments
+ if(O.BlockThermalConductivity(opp)) //the direction and open/closed are already checked on can_atmos_pass() so there are no arguments
conductivity_blocked_directions |= dir
T.conductivity_blocked_directions |= opp
@@ -55,9 +73,10 @@
/atom/movable/proc/BlockThermalConductivity(dir) // Objects that don't let heat through.
return FALSE
-/turf/proc/ImmediateCalculateAdjacentTurfs()
- var/canpass = CANATMOSPASS(src, src)
- var/canvpass = CANVERTICALATMOSPASS(src, src)
+/turf/proc/immediate_calculate_adjacent_turfs()
+ LAZYINITLIST(src.atmos_adjacent_turfs)
+ var/list/atmos_adjacent_turfs = src.atmos_adjacent_turfs
+ var/canpass = CANATMOSPASS(src, src, FALSE)
conductivity_blocked_directions = 0
@@ -66,64 +85,83 @@
src_contains_firelock |= 2
for(var/direction in GLOB.cardinals_multiz)
- var/turf/T = get_step_multiz(src, direction)
- if(!istype(T))
+ var/turf/current_turf = get_step_multiz(src, direction)
+ if(!istype(current_turf))
conductivity_blocked_directions |= direction
continue
var/other_contains_firelock = 1
- if(locate(/obj/machinery/door/firedoor) in T)
+ if(locate(/obj/machinery/door/firedoor) in current_turf)
other_contains_firelock |= 2
- update_conductivity(T)
+ update_conductivity(current_turf)
- if(isopenturf(T) && !(blocks_air || T.blocks_air) && ((direction & (UP|DOWN))? (canvpass && CANVERTICALATMOSPASS(T, src)) : (canpass && CANATMOSPASS(T, src))) )
- LAZYINITLIST(atmos_adjacent_turfs)
- LAZYINITLIST(T.atmos_adjacent_turfs)
- atmos_adjacent_turfs[T] = other_contains_firelock | src_contains_firelock
- T.atmos_adjacent_turfs[src] = src_contains_firelock
+ if(canpass && CANATMOSPASS(current_turf, src, (direction & (UP|DOWN))) && !(blocks_air || current_turf.blocks_air))
+ LAZYINITLIST(current_turf.atmos_adjacent_turfs)
+ atmos_adjacent_turfs[current_turf] = other_contains_firelock | src_contains_firelock
+ current_turf.atmos_adjacent_turfs[src] = src_contains_firelock
else
- if (atmos_adjacent_turfs)
- atmos_adjacent_turfs -= T
- if (T.atmos_adjacent_turfs)
- T.atmos_adjacent_turfs -= src
- UNSETEMPTY(T.atmos_adjacent_turfs)
+ atmos_adjacent_turfs -= current_turf
+ if (current_turf.atmos_adjacent_turfs)
+ current_turf.atmos_adjacent_turfs -= src
+ UNSETEMPTY(current_turf.atmos_adjacent_turfs)
+ SEND_SIGNAL(current_turf, COMSIG_TURF_CALCULATED_ADJACENT_ATMOS)
- T.__update_auxtools_turf_adjacency_info()
+ current_turf.__update_auxtools_turf_adjacency_info()
UNSETEMPTY(atmos_adjacent_turfs)
src.atmos_adjacent_turfs = atmos_adjacent_turfs
+ SEND_SIGNAL(src, COMSIG_TURF_CALCULATED_ADJACENT_ATMOS)
__update_auxtools_turf_adjacency_info()
/turf/proc/clear_adjacencies()
block_all_conductivity()
for(var/direction in GLOB.cardinals_multiz)
- var/turf/T = get_step_multiz(src, direction)
- if(!T)
+ var/turf/current_turf = get_step_multiz(src, direction)
+ if(!current_turf)
continue
if (atmos_adjacent_turfs)
- atmos_adjacent_turfs -= T
- if (T.atmos_adjacent_turfs)
- T.atmos_adjacent_turfs -= src
- UNSETEMPTY(T.atmos_adjacent_turfs)
+ atmos_adjacent_turfs -= current_turf
+ if (current_turf.atmos_adjacent_turfs)
+ current_turf.atmos_adjacent_turfs -= src
+ UNSETEMPTY(current_turf.atmos_adjacent_turfs)
- T.__update_auxtools_turf_adjacency_info()
+ current_turf.__update_auxtools_turf_adjacency_info()
LAZYNULL(atmos_adjacent_turfs)
__update_auxtools_turf_adjacency_info()
//Only gets a list of adjacencies, does NOT update
-/turf/proc/get_adjacent_atmos_turfs()
- . = list()
- var/canpass = CANATMOSPASS(src, src)
- var/canvpass = CANVERTICALATMOSPASS(src, src)
+/turf/proc/get_atmos_adjacent_turfs(alldir = 0)
+ var/adjacent_turfs
+ if (atmos_adjacent_turfs)
+ adjacent_turfs = atmos_adjacent_turfs.Copy()
+ else
+ adjacent_turfs = list()
- for(var/direction in GLOB.cardinals_multiz)
- var/turf/T = get_step_multiz(src, direction)
- if(isopenturf(T) && !(blocks_air || T.blocks_air) && ((direction & (UP|DOWN))? (canvpass && CANVERTICALATMOSPASS(T, src)) : (canpass && CANATMOSPASS(T, src))) )
- .[T] = 0
- else
- . -= T
- UNSETEMPTY(.)
+ if (!alldir)
+ return adjacent_turfs
+
+ var/turf/current_location = src
+
+ for (var/direction in GLOB.diagonals_multiz)
+ var/matching_directions = 0
+ var/turf/checked_turf = get_step_multiz(current_location, direction)
+ if(!checked_turf)
+ continue
+
+ for (var/check_direction in GLOB.cardinals_multiz)
+ var/turf/secondary_turf = get_step(checked_turf, check_direction)
+ if(!checked_turf.atmos_adjacent_turfs || !checked_turf.atmos_adjacent_turfs[secondary_turf])
+ continue
+
+ if (adjacent_turfs[secondary_turf])
+ matching_directions++
+
+ if (matching_directions >= 2)
+ adjacent_turfs += checked_turf
+ break
+
+ return adjacent_turfs
//returns a list of adjacent turfs that can share air with this one.
//alldir includes adjacent diagonal tiles that can share
@@ -161,18 +199,18 @@
return adjacent_turfs
/atom/proc/air_update_turf()
- var/turf/T = get_turf(src)
- if(!T)
+ var/turf/local_turf = get_turf(loc)
+ if(!local_turf)
return
- T.air_update_turf()
+ local_turf.air_update_turf()
/turf/air_update_turf()
- ImmediateCalculateAdjacentTurfs()
+ immediate_calculate_adjacent_turfs()
-/atom/movable/proc/move_update_air(turf/T)
- if(isturf(T))
- T.air_update_turf()
- air_update_turf()
+/atom/movable/proc/move_update_air(turf/target_turf)
+ if(isturf(target_turf))
+ target_turf.air_update_turf() //You're empty now
+ air_update_turf() //You aren't
/atom/proc/atmos_spawn_air(text) //because a lot of people loves to copy paste awful code lets just make an easy proc to spawn your plasma fires
var/turf/open/T = get_turf(src)
diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
index af36d33d22d4..ff241fa145c8 100644
--- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
+++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm
@@ -1,6 +1,7 @@
/turf
//conductivity is divided by 10 when interacting with air for balance purposes
var/thermal_conductivity = 0.05
+ ///Amount of heat necessary to activate some atmos processes (there is a weird usage of this var because is compared directly to the temperature instead of heat energy)
var/heat_capacity = 1
//list of open turfs adjacent to us
@@ -8,24 +9,38 @@
///bitfield of dirs in which we thermal conductivity is blocked
var/conductivity_blocked_directions = NONE
- //used for mapping and for breathing while in walls (because that's a thing that needs to be accounted for...)
- //string parsed by /datum/gas/proc/copy_from_turf
+ /**
+ * used for mapping and for breathing while in walls (because that's a thing that needs to be accounted for...)
+ * string parsed by /datum/gas/proc/copy_from_turf
+ * approximation of MOLES_O2STANDARD and MOLES_N2STANDARD pending byond allowing constant expressions to be embedded in constant strings
+ * If someone will place 0 of some gas there, SHIT WILL BREAK. Do not do that.
+ **/
var/initial_gas_mix = OPENTURF_DEFAULT_ATMOS
- //approximation of MOLES_O2STANDARD and MOLES_N2STANDARD pending byond allowing constant expressions to be embedded in constant strings
- // If someone will place 0 of some gas there, SHIT WILL BREAK. Do not do that.
/turf/open
//used for spacewind
+ ///Pressure difference between two turfs
var/pressure_difference = 0
+ ///Where the difference come from (from higher pressure to lower pressure)
var/pressure_direction = 0
- var/turf/pressure_specific_target
+ ///Our gas mix
var/datum/gas_mixture/turf/air
+ ///If there is an active hotspot on us store a reference to it here
var/obj/effect/hotspot/active_hotspot
- var/planetary_atmos = FALSE //air will revert to initial_gas_mix over time
-
- var/list/atmos_overlay_types //gas IDs of current active gas overlays
+ /// air will slowly revert to initial_gas_mix
+ var/planetary_atmos = FALSE
+ /// once our paired turfs are finished with all other shares, do one 100% share
+ /// exists so things like space can ask to take 100% of a tile's gas
+ var/run_later = FALSE
+
+ ///gas IDs of current active gas overlays
+ var/list/atmos_overlay_types
+ /// How much fuel this open turf provides to turf fires, and how easily they can be ignited in the first place. Can be negative to make fires die out faster.
+ var/flammability = 0.3
+ var/obj/effect/abstract/turf_fire/turf_fire
+ var/turf/pressure_specific_target
/turf/proc/should_conduct_to_space()
return get_z_base_turf() == /turf/open/space
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index e3d56b5e471c..af260e5397f7 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -299,6 +299,7 @@ get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
//math is under the assumption temperatures are equal
var/self_moles = get_moles(gas_id)
var/other_moles = other.get_moles(gas_id)
+
if(abs(self_moles / return_volume() - other_moles / other.return_volume()) > min_p_delta / (R_IDEAL_GAS_EQUATION * return_temperature()))
. = TRUE
var/total_moles = self_moles + other_moles
diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm
index d97e5e504e98..2b1ae7d7d4a3 100644
--- a/code/modules/atmospherics/gasmixtures/reactions.dm
+++ b/code/modules/atmospherics/gasmixtures/reactions.dm
@@ -297,7 +297,7 @@
if(air.return_temperature() < (FUSION_TEMPERATURE_THRESHOLD - FUSION_TEMPERATURE_THRESHOLD_MINIMUM) * NUM_E**( - air.get_moles(GAS_DILITHIUM) * DILITHIUM_LAMBDA) + FUSION_TEMPERATURE_THRESHOLD_MINIMUM)
// This is an exponential decay equation, actually. Horizontal Asymptote is FUSION_TEMPERATURE_THRESHOLD_MINIMUM.
return NO_REACTION
- return fusion_react(air, holder, id)
+ return fusion_react(air, holder)
/datum/gas_reaction/fusion
exclude = FALSE
@@ -313,9 +313,9 @@
GAS_CO2 = FUSION_MOLE_THRESHOLD)
/datum/gas_reaction/fusion/react(datum/gas_mixture/air, datum/holder)
- return fusion_react(air, holder, id)
+ return fusion_react(air, holder)
-/proc/fusion_react(datum/gas_mixture/air, datum/holder, id)
+/proc/fusion_react(datum/gas_mixture/air, datum/holder)
var/turf/open/location = get_holder_turf(holder)
if(!location)
return NO_REACTION
@@ -332,7 +332,7 @@
for (var/gas_id in air.get_gases())
gas_power += (GLOB.gas_data.fusion_powers[gas_id]*air.get_moles(gas_id))
var/instability = MODULUS((gas_power*INSTABILITY_GAS_POWER_FACTOR)**2,toroidal_size) //Instability effects how chaotic the behavior of the reaction is
- cached_scan_results[id] = instability//used for analyzer feedback
+ cached_scan_results["fusion"] = instability //used for analyzer feedback
var/plasma = (initial_plasma-FUSION_MOLE_THRESHOLD)/(scale_factor) //We have to scale the amounts of carbon and plasma down a significant amount in order to show the chaotic dynamics we want
var/carbon = (initial_carbon-FUSION_MOLE_THRESHOLD)/(scale_factor) //We also subtract out the threshold amount to make it harder for fusion to burn itself out.
@@ -429,7 +429,7 @@
GAS_PLASMA = 10,
)
-/datum/gas_reaction/bzformation/react(datum/gas_mixture/air)
+/datum/gas_reaction/bzformation/react(datum/gas_mixture/air, datum/holder)
var/pressure = air.return_pressure()
var/old_thermal_energy = air.thermal_energy()
@@ -448,7 +448,9 @@
air.adjust_moles(GAS_PLASMA, -2*reaction_efficency)
//clamps by a minimum amount in the event of an underflow.
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, clamp((reaction_efficency**2)*BZ_RESEARCH_AMOUNT,0.01,BZ_RESEARCH_MAX_AMOUNT))
+ var/turf/holder_turf = get_holder_turf(holder)
+ if(holder_turf && SSmapping.level_trait(holder_turf.z, ZTRAIT_STATION))
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, clamp((reaction_efficency**2)*BZ_RESEARCH_AMOUNT,0.01,BZ_RESEARCH_MAX_AMOUNT))
if(energy_released > 0)
var/new_heat_capacity = air.heat_capacity()
@@ -467,7 +469,7 @@
GAS_TRITIUM = 10,
"TEMP" = 5000000)
-/datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air)
+/datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air, datum/holder)
var/initial_trit = air.get_moles(GAS_TRITIUM)
var/initial_n2 = air.get_moles(GAS_N2)
var/initial_bz = air.get_moles(GAS_BZ)
@@ -480,7 +482,11 @@
air.adjust_moles(GAS_TRITIUM, -10*nob_formed)
air.adjust_moles(GAS_N2, -20*nob_formed)
air.adjust_moles(GAS_HYPERNOB, nob_formed)
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, clamp(nob_formed*NOBLIUM_RESEARCH_AMOUNT, 0.01, NOBLIUM_RESEARCH_MAX_AMOUNT))
+
+ var/turf/holder_turf = get_holder_turf(holder)
+ if(holder_turf && SSmapping.level_trait(holder_turf.z, ZTRAIT_STATION))
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, clamp(nob_formed*NOBLIUM_RESEARCH_AMOUNT, 0.01, NOBLIUM_RESEARCH_MAX_AMOUNT))
+
var/new_heat_capacity = air.heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
air.set_temperature(max(((old_thermal_energy - energy_taken)/new_heat_capacity),TCMB))
@@ -510,7 +516,10 @@
//Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable
air.set_temperature(air.return_temperature() + cleaned_air * 0.002)
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, clamp(cleaned_air*MIASMA_RESEARCH_AMOUNT,0.01, MIASMA_RESEARCH_MAX_AMOUNT))//Turns out the burning of miasma is kinda interesting to scientists
+
+ var/turf/holder_turf = get_holder_turf(holder)
+ if(holder_turf && SSmapping.level_trait(holder_turf.z, ZTRAIT_STATION))
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, clamp(cleaned_air*MIASMA_RESEARCH_AMOUNT,0.01, MIASMA_RESEARCH_MAX_AMOUNT))//Turns out the burning of miasma is kinda interesting to scientists
return REACTING
/datum/gas_reaction/nitro_ball
@@ -730,7 +739,9 @@
if (prob(25 * increase_factor))
air.adjust_moles(GAS_H2, -(heat_efficency * 10))
new /obj/item/stack/sheet/mineral/metal_hydrogen(location)
- SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((heat_efficency * increase_factor * 0.5), METAL_HYDROGEN_RESEARCH_MAX_AMOUNT))
+ var/turf/holder_turf = get_holder_turf(holder)
+ if(holder_turf && SSmapping.level_trait(holder_turf.z, ZTRAIT_STATION))
+ SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((heat_efficency * increase_factor * 0.5), METAL_HYDROGEN_RESEARCH_MAX_AMOUNT))
if(energy_used > 0)
var/new_heat_capacity = air.heat_capacity()
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index f7dc8a79e3ec..fb56c2c87412 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -119,6 +119,7 @@
GAS_ZAUKER = new/datum/tlv/dangerous,
GAS_HALON = new/datum/tlv/dangerous,
GAS_HEXANE = new/datum/tlv/dangerous,
+ GAS_ANTINOB = new/datum/tlv/dangerous,
)
/obj/machinery/airalarm/server // No checks here.
@@ -144,6 +145,7 @@
GAS_PLUONIUM = new/datum/tlv/dangerous,
GAS_HALON = new/datum/tlv/dangerous,
GAS_HEXANE = new/datum/tlv/dangerous,
+ GAS_ANTINOB = new/datum/tlv/dangerous,
)
/obj/machinery/airalarm/kitchen_cold_room // Kitchen cold rooms start off at -80°C or 193.15°K.
@@ -170,6 +172,7 @@
GAS_ZAUKER = new/datum/tlv/dangerous,
GAS_HALON = new/datum/tlv/dangerous,
GAS_HEXANE = new/datum/tlv/dangerous,
+ GAS_ANTINOB = new/datum/tlv/dangerous,
)
/obj/machinery/airalarm/unlocked
@@ -556,6 +559,7 @@
GAS_PLASMA,
GAS_H2O,
GAS_HYPERNOB,
+ GAS_ANTINOB,
GAS_NITROUS,
GAS_NITRIUM,
GAS_TRITIUM,
@@ -583,14 +587,14 @@
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
- "widenet" = 0,
+ "widenet" = 1,
"scrubbing" = 0
), signal_source)
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 1,
"checks" = 1,
- "set_external_pressure" = ONE_ATMOSPHERE*2
+ "set_external_pressure" = ONE_ATMOSPHERE
), signal_source)
if(AALARM_MODE_PANIC,
AALARM_MODE_REPLACEMENT)
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index 73f2b391ca04..5dfa7e7d23b1 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -7,9 +7,6 @@
// Pipes -> Pipelines
// Pipelines + Other Objects -> Pipe network
-#define PIPE_VISIBLE_LEVEL 2
-#define PIPE_HIDDEN_LEVEL 1
-
GLOBAL_LIST_EMPTY(iconsetids)
GLOBAL_LIST_EMPTY(pipeimages)
@@ -27,14 +24,24 @@ GLOBAL_LIST_EMPTY(pipeimages)
var/rebuilding = FALSE
///If we should init and immediately start processing
var/init_processing = FALSE
- var/can_unwrench = 0
+ ///Check if the object can be unwrenched
+ var/can_unwrench = FALSE
+ ///Bitflag of the initialized directions (NORTH | SOUTH | EAST | WEST)
var/initialize_directions = 0
- var/pipe_color
+ ///The color of the pipe
+ var/pipe_color = COLOR_VERY_LIGHT_GRAY
+ ///What layer the pipe is in (from 1 to 5, default 3)
var/piping_layer = PIPING_LAYER_DEFAULT
+ ///The flags of the pipe/component (PIPING_ALL_LAYER | PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE)
var/pipe_flags = NONE
+ ///This only works on pipes, because they have 1000 subtypes wich need to be visible and invisible under tiles, so we track this here
+ var/hide = TRUE
+
+ ///The image of the pipe/device used for ventcrawling
var/image/pipe_vision_img = null
+ ///The type of the device (UNARY, BINARY, TRINARY, QUATERNARY)
var/device_type = 0
var/list/obj/machinery/atmospherics/nodes
@@ -42,6 +49,13 @@ GLOBAL_LIST_EMPTY(pipeimages)
var/pipe_state //icon_state as a pipe item
var/on = FALSE
+ ///The bitflag that's being checked on ventcrawling. Default is to allow ventcrawling and seeing pipes.
+ var/vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE
+
+/obj/machinery/atmospherics/LateInitialize()
+ . = ..()
+ update_name()
+
/obj/machinery/atmospherics/examine(mob/user)
. = ..()
if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user))
@@ -63,14 +77,14 @@ GLOBAL_LIST_EMPTY(pipeimages)
/obj/machinery/atmospherics/Initialize(mapload)
if(init_processing)
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
return ..()
/obj/machinery/atmospherics/Destroy()
for(var/i in 1 to device_type)
nullify_node(i)
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
SSair.rebuild_queue -= src
dropContents()
@@ -132,15 +146,15 @@ GLOBAL_LIST_EMPTY(pipeimages)
if(can_be_node(target, i))
nodes[i] = target
break
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/atmospherics/proc/set_piping_layer(new_layer)
piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/atmospherics/update_icon(updates=ALL)
. = ..()
- layer = initial(layer) + piping_layer / 1000
+ update_layer()
/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration)
return connection_check(target, piping_layer)
@@ -216,11 +230,6 @@ GLOBAL_LIST_EMPTY(pipeimages)
if(!can_unwrench(user))
return ..()
- var/turf/T = get_turf(src)
- if (level==1 && isturf(T) && T.intact)
- to_chat(user, span_warning("You must remove the plating first!"))
- return TRUE
-
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
add_fingerprint(user)
@@ -297,19 +306,31 @@ GLOBAL_LIST_EMPTY(pipeimages)
transfer_fingerprints_to(stored)
..()
-/obj/machinery/atmospherics/proc/get_pipe_image(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=3, trinary = FALSE)
+/**
+ * Getter for piping layer shifted, pipe colored overlays
+ *
+ * Creates the image for the pipe underlay that all components use, called by get_pipe_underlay() in components_base.dm
+ * Arguments:
+ * * iconfile - path of the iconstate we are using (ex: 'icons/obj/machines/atmospherics/thermomachine.dmi')
+ * * iconstate - the image we are using inside the file
+ * * direction - the direction of our device
+ * * color - the color (in hex value, like #559900) that the pipe should have
+ * * piping_layer - the piping_layer the device is in, used inside PIPING_LAYER_SHIFT
+ * * trinary - if TRUE we also use PIPING_FORWARD_SHIFT on layer 1 and 5 for trinary devices (filters and mixers)
+ */
+/obj/machinery/atmospherics/proc/get_pipe_image(iconfile, iconstate, direction, color = COLOR_VERY_LIGHT_GRAY, piping_layer = 3, trinary = FALSE)
//Add identifiers for the iconset
- if(GLOB.iconsetids[iconset] == null)
- GLOB.iconsetids[iconset] = num2text(GLOB.iconsetids.len + 1)
+ if(GLOB.iconsetids[iconfile] == null)
+ GLOB.iconsetids[iconfile] = num2text(GLOB.iconsetids.len + 1)
//Generate a unique identifier for this image combination
- var/identifier = GLOB.iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]"
+ var/identifier = GLOB.iconsetids[iconfile] + "_[iconstate]_[direction]_[color]_[piping_layer]"
if((!(. = GLOB.pipeimages[identifier])))
var/image/pipe_overlay
- pipe_overlay = . = GLOB.pipeimages[identifier] = image(iconset, iconstate, dir = direction)
- pipe_overlay.color = col
+ pipe_overlay = . = GLOB.pipeimages[identifier] = image(iconfile, iconstate, dir = direction)
+ pipe_overlay.color = color
PIPING_LAYER_SHIFT(pipe_overlay, piping_layer)
if(trinary == TRUE && (piping_layer == 1 || piping_layer == 5))
PIPING_FORWARD_SHIFT(pipe_overlay, piping_layer, 2)
@@ -319,8 +340,6 @@ GLOBAL_LIST_EMPTY(pipeimages)
add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY)
pipe_color = obj_color
set_piping_layer(set_layer)
- var/turf/T = get_turf(src)
- level = T.intact ? 2 : 1
atmos_init()
var/list/nodes = pipeline_expansion()
for(var/obj/machinery/atmospherics/A in nodes)
@@ -357,18 +376,20 @@ GLOBAL_LIST_EMPTY(pipeimages)
return
user.forceMove(target_move.loc) //handle entering and so on.
user.visible_message(span_notice("You hear something squeezing through the ducts..."), "You climb out the ventilation system.")
+ REMOVE_TRAIT(user, TRAIT_MOVE_VENTCRAWLING, VENTCRAWLING_TRAIT)
else
var/list/pipenetdiff = return_pipenets() ^ target_move.return_pipenets()
if(pipenetdiff.len)
user.update_pipe_vision(target_move)
user.forceMove(target_move)
- user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement
+ user.client.set_eye(target_move) //Byond only updates the eye every tick, This smooths out the movement
if(world.time - user.last_played_vent > VENT_SOUND_DELAY)
user.last_played_vent = world.time
playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3)
else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent
user.forceMove(loc)
user.visible_message(span_notice("You hear something squeezing through the ducts..."), "You climb out the ventilation system.")
+ REMOVE_TRAIT(user, TRAIT_MOVE_VENTCRAWLING, VENTCRAWLING_TRAIT)
//PLACEHOLDER COMMENT FOR ME TO READD THE 1 (?) DS DELAY THAT WAS IMPLEMENTED WITH A... TIMER?
@@ -393,4 +414,4 @@ GLOBAL_LIST_EMPTY(pipeimages)
return TRUE
/obj/machinery/atmospherics/proc/update_layer()
- layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE
+ return
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm
index 5f3741f24519..8733af600244 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm
@@ -13,10 +13,6 @@
if(EAST, WEST)
initialize_directions = EAST|WEST
-/obj/machinery/atmospherics/components/binary/hide(intact)
- update_appearance(UPDATE_ICON)
- ..()
-
/obj/machinery/atmospherics/components/binary/get_node_connects()
return list(turn(dir, 180), dir)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
index a309095d2716..bb83be1d2ad2 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
@@ -15,6 +15,7 @@
var/last_pressure_delta = 0
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
+ vent_movement = VENTCRAWL_CAN_SEE
var/flipped = 0
var/mode = CIRCULATOR_HOT
@@ -123,7 +124,7 @@
else
if(!last_pressure_delta)
set_light(1)
- SSvis_overlays.add_vis_overlay(src, icon, "circ-off", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "circ-off", ABOVE_LIGHTING_PLANE, dir)
return
else
if(last_pressure_delta > ONE_ATMOSPHERE) //fast
@@ -131,15 +132,15 @@
set_light(3,2,"#4F82FF")
else
set_light(3,2,"#FF3232")
- SSvis_overlays.add_vis_overlay(src, icon, "circ-ex[mode?"cold":"hot"]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "circ-run", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "circ-ex[mode?"cold":"hot"]", ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "circ-run", ABOVE_LIGHTING_PLANE, dir)
else //slow
if(mode)
set_light(2,1,"#4F82FF")
else
set_light(2,1,"#FF3232")
- SSvis_overlays.add_vis_overlay(src, icon, "circ-[mode?"cold":"hot"]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "circ-slow", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "circ-[mode?"cold":"hot"]", ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "circ-slow", ABOVE_LIGHTING_PLANE, dir)
/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I)
if(user.a_intent == INTENT_HARM)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
index d82bde6274b8..6e4e519edf5b 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
@@ -13,8 +13,8 @@
name = "dual-port air vent"
desc = "Has a valve and pump attached to it. There are two ports."
+ hide = TRUE
- level = 1
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
index be8c8dbdb6e2..0e21afc3b4f1 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
@@ -26,11 +26,12 @@
construction_type = /obj/item/pipe/directional
pipe_state = "pump"
+ vent_movement = NONE
/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user)
if(can_interact(user))
on = !on
- update_appearance(UPDATE_ICON)
+ update_appearance()
return ..()
/obj/machinery/atmospherics/components/binary/pump/AltClick(mob/user)
@@ -40,7 +41,7 @@
investigate_log(msg, INVESTIGATE_ATMOS)
investigate_log(msg, INVESTIGATE_SUPERMATTER) // yogs - makes supermatter invest useful
balloon_alert(user, "pressure output set to [target_pressure] kPa")
- update_appearance(UPDATE_ICON)
+ update_appearance()
return ..()
/obj/machinery/atmospherics/components/binary/pump/Destroy()
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
index 25d30312fc03..0454fa6ca5ff 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm
@@ -13,6 +13,7 @@
construction_type = /obj/item/pipe/directional
pipe_state = "tpump"
+ vent_movement = NONE
/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user)
if(can_interact(user))
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
index 74c6be7c3f26..42cec8937748 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm
@@ -27,6 +27,7 @@
construction_type = /obj/item/pipe/directional
pipe_state = "volumepump"
+ vent_movement = NONE
/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user)
if(can_interact(user))
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index 2f1863532f74..e57738e946b6 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -2,18 +2,25 @@
// On top of that, now people can add component-speciic procs/vars if they want!
/obj/machinery/atmospherics/components
- var/welded = FALSE //Used on pumps and scrubbers
+ hide = FALSE
+ layer = GAS_PUMP_LAYER
+ ///Is the component welded?
+ var/welded = FALSE
+ ///Should the component should show the pipe underneath it?
var/showpipe = TRUE
- var/shift_underlay_only = TRUE //Layering only shifts underlay?
-
- var/update_parents_after_rebuild = FALSE
-
+ ///When the component is on a non default layer should we shift everything? Or just the underlay pipe
+ var/shift_underlay_only = TRUE
+ ///Stores the parent pipeline, used in components
var/list/datum/pipeline/parents
+ ///If this is queued for a rebuild this var signifies whether parents should be updated after it's done
+ var/update_parents_after_rebuild = FALSE
+ ///Stores the gasmix for each node, used in components
var/list/datum/gas_mixture/airs
- var/startingvolume = 200
-
+ ///Handles whether the custom reconcilation handling should be used
var/custom_reconcilation = FALSE
+ var/startingvolume = 200
+
/obj/machinery/atmospherics/components/New()
parents = new(device_type)
airs = new(device_type)
@@ -27,18 +34,39 @@
component_mixture.set_volume(startingvolume)
airs[i] = component_mixture
+/obj/machinery/atmospherics/components/Initialize(mapload)
+ . = ..()
+
+ if(hide)
+ RegisterSignal(src, COMSIG_OBJ_HIDE, PROC_REF(hide_pipe))
+
// Iconnery
+/**
+ * Called by update_icon(), used individually by each component to determine the icon state without the pipe in consideration
+ */
/obj/machinery/atmospherics/components/proc/update_icon_nopipes()
return
+/**
+ * Called in Initialize(), set the showpipe var to true or false depending on the situation, calls update_icon()
+ */
+/obj/machinery/atmospherics/components/proc/hide_pipe(datum/source, underfloor_accessibility)
+ SIGNAL_HANDLER
+ showpipe = !!underfloor_accessibility
+ if(showpipe)
+ REMOVE_TRAIT(src, TRAIT_UNDERFLOOR, REF(src))
+ else
+ ADD_TRAIT(src, TRAIT_UNDERFLOOR, REF(src))
+ update_appearance()
+
/obj/machinery/atmospherics/components/update_icon(updates=ALL)
. = ..()
update_icon_nopipes()
underlays.Cut()
- plane = showpipe ? GAME_PLANE : FLOOR_PLANE
+ SET_PLANE_IMPLICIT(src, showpipe ? GAME_PLANE : FLOOR_PLANE)
if(!showpipe)
return
@@ -91,6 +119,11 @@
to_return += parents[i]
return to_return
+/**
+ * Called by nullify_node(), used to remove the pipeline the component is attached to
+ * Arguments:
+ * * -reference: the pipeline the component is attached to
+ */
/obj/machinery/atmospherics/components/proc/nullify_pipenet(datum/pipeline/reference)
if(!reference)
CRASH("nullify_pipenet(null) called by [type] on [COORD(src)]")
@@ -157,6 +190,10 @@
// Helpers
+/**
+ * Called in most atmos processes and gas handling situations, update the parents pipelines of the devices connected to the source component
+ * This way gases won't get stuck
+ */
/obj/machinery/atmospherics/components/proc/update_parents()
if(!SSair.initialized)
return
@@ -219,3 +256,6 @@
/obj/machinery/atmospherics/components/return_analyzable_air()
return airs
+
+/obj/machinery/atmospherics/components/update_layer()
+ layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.001)
diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm
index 453f3849691f..095b9f8d2aa9 100644
--- a/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm
+++ b/code/modules/atmospherics/machinery/components/fusion/hfr_core.dm
@@ -28,7 +28,7 @@
var/start_moderator = FALSE
/**
- * Hypertorus internal objects and gasmixes
+ * Hypertorus internal objects and gasmixes.
*/
///Stores the informations of the interface machine
@@ -113,7 +113,10 @@
var/fuel_injection_rate = 25
///User controlled variable to control the flow of the fusion by changing the amount of moderators injected
var/moderator_injection_rate = 25
-
+ ///Amount of gas inside internal fuel being removed or added per tick by the reaction. This is used for tgui only
+ var/list/delta_fuel_list = list()
+ ///Amount of gas inside moderator being removed or added per tick by the reaction. This is used for tgui only
+ var/list/delta_mod_list = list()
///Integrity of the machine, if reaches 900 the machine will explode
var/critical_threshold_proximity = 0
///Store the integrity for calculations
@@ -172,6 +175,9 @@
internal_fusion.set_volume(5000)
moderator_internal = new
moderator_internal.set_volume(10000)
+ for(var/id in GLOB.gas_data.ids)
+ delta_mod_list[id] = 0
+ delta_fuel_list[id] = 0
radio = new(src)
radio.keyslot = new radio_key
diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm
index c213e0ad72fb..209408558b01 100644
--- a/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm
+++ b/code/modules/atmospherics/machinery/components/fusion/hfr_main_processes.dm
@@ -42,6 +42,13 @@
//Fusion Rework Counter: Please increment this if you make a major overhaul to this system again.
//7 reworks
+ //Keep the lists at 0 in most cases
+ for(var/delta_mod_id in delta_mod_list)
+ delta_mod_list[delta_mod_id] = 0
+
+ for(var/delta_fuel_id in delta_fuel_list)
+ delta_fuel_list[delta_fuel_id] = 0
+
if (check_power_use())
if (start_cooling)
inject_from_side_components(delta_time)
@@ -213,13 +220,13 @@
// Phew. Lets calculate what this means in practice.
var/reaction_rate = clamp((power_level * 0.5) * (500 / magnetic_constrictor) * delta_time, 0.05, 30) // constrictor controls reaction rate instead of fuel injection
switch(power_level)
- if(3,4)
+ if(3,4,5,6)
reaction_rate = clamp(reaction_rate * heat_output * 5e-4, 0, reaction_rate)
else
reaction_rate = clamp(reaction_rate * heat_output / (10 ** (power_level+1)), 0, reaction_rate)
// antinob production is special, and uses its own calculations from how stale the fusion mix is (via byproduct ratio and fresh fuel rate)
- var/dirty_production_rate = scaled_fuel_list[scaled_fuel_list[3]] / fuel_injection_rate
+ var/dirty_production_rate = 10 / (scaled_fuel_list[scaled_fuel_list[3]]+1)
// Run the effects of our selected fuel recipe
@@ -241,9 +248,14 @@
var/scaled_production = reaction_rate * selected_fuel.gas_production_multiplier
for(var/gas_id in fuel.requirements)
- internal_fusion.adjust_moles(gas_id, -min(fuel_list[gas_id], fuel_consumption))
+ var/remove_amount = round(min(fuel_list[gas_id], fuel_consumption), 0.01)
+ internal_fusion.adjust_moles(gas_id, -remove_amount)
+ delta_fuel_list[gas_id] -= remove_amount
+
+ var/add_remove_amount = round(scaled_production, 0.01) // gases on the same tier are produced at normal rate
for(var/gas_id in fuel.primary_products)
- internal_fusion.adjust_moles(gas_id, fuel_consumption * 0.5)
+ internal_fusion.adjust_moles(gas_id, add_remove_amount)
+ delta_fuel_list[gas_id] += add_remove_amount
if(power_level < 1)
return // can't produce any gases, don't need to continue
@@ -251,11 +263,14 @@
// Each recipe provides a tier list of six output gases.
// Which gases are produced depend on what the fusion level is.
var/list/tier = fuel.secondary_products
- moderator_internal.adjust_moles(tier[power_level], scaled_production) // gases on the same tier are produced at normal rate
+ moderator_internal.adjust_moles(tier[power_level], add_remove_amount)
+ delta_mod_list[tier[power_level]] += add_remove_amount
if(power_level < 6)
- moderator_internal.adjust_moles(tier[power_level + 1], scaled_production * 0.5) // gases on the above tier are produced at reduced rate
+ moderator_internal.adjust_moles(tier[power_level + 1], round(scaled_production * 0.5, 0.01)) // gases on the above tier are produced at reduced rate
+ delta_mod_list[tier[power_level + 1]] += round(scaled_production * 0.5, 0.01)
if(power_level > 1)
- moderator_internal.adjust_moles(tier[power_level - 1], scaled_production * 1.5) // gases on the below tier are produced at increased rate
+ moderator_internal.adjust_moles(tier[power_level - 1], round(scaled_production * 1.5, 0.01)) // gases on the below tier are produced at increased rate
+ delta_mod_list[tier[power_level - 1]] += round(scaled_production * 1.5, 0.01)
/**
* Perform common fusion actions:
@@ -268,79 +283,103 @@
switch(power_level)
if(1)
if(moderator_list[GAS_PLASMA] > 100)
- internal_output.adjust_moles(GAS_NITROUS, scaled_production * 0.5)
- moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 0.85))
+ linked_output.airs[1].adjust_moles(GAS_NITROUS, scaled_production * 0.5)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 0.85), 0.01)
+ moderator_internal.adjust_moles(GAS_PLASMA, -remove_amount)
+ delta_mod_list[GAS_PLASMA] -= remove_amount
if(moderator_list[GAS_BZ] > 150)
- internal_output.adjust_moles(GAS_HALON, scaled_production * 0.55)
- moderator_internal.adjust_moles(GAS_BZ, -min(moderator_internal.get_moles(GAS_BZ), scaled_production * 0.95))
+ linked_output.airs[1].adjust_moles(GAS_HALON, scaled_production * 0.55)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_BZ), scaled_production * 0.95), 0.01)
+ moderator_internal.adjust_moles(GAS_BZ, -remove_amount)
+ delta_mod_list[GAS_BZ] -= remove_amount
if(2)
if(moderator_list[GAS_PLASMA] > 50)
- internal_output.adjust_moles(GAS_BZ, scaled_production * 1.8)
- moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.75))
+ linked_output.airs[1].adjust_moles(GAS_BZ, scaled_production * 1.8)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.75), 0.01)
+ moderator_internal.adjust_moles(GAS_PLASMA, -remove_amount)
+ delta_mod_list[GAS_PLASMA] -= remove_amount
if(moderator_list[GAS_PLUONIUM] > 20)
radiation *= 1.55
heat_output *= 1.025
- moderator_internal.adjust_moles(GAS_PLUONIUM, -min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 1.35))
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 1.35), 0.01)
+ moderator_internal.adjust_moles(GAS_PLUONIUM, -remove_amount)
+ delta_mod_list[GAS_PLUONIUM] += remove_amount
if(3, 4)
if(moderator_list[GAS_PLASMA] > 10)
- internal_output.adjust_moles(GAS_FREON, scaled_production * 0.15)
- moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 0.45))
+ linked_output.airs[1].adjust_moles(GAS_FREON, scaled_production * 0.15)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 0.45), 0.01)
+ moderator_internal.adjust_moles(GAS_PLASMA, -remove_amount)
+ delta_mod_list[GAS_PLASMA] -= remove_amount
if(moderator_list[GAS_FREON] > 50)
heat_output *= 0.9
radiation *= 0.8
if(moderator_list[GAS_PLUONIUM]> 15)
- internal_output.adjust_moles(GAS_HALON, scaled_production * 1.15)
- moderator_internal.adjust_moles(GAS_PLUONIUM, -min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 1.55))
+ linked_output.airs[1].adjust_moles(GAS_HALON, scaled_production * 1.15)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 1.55), 0.01)
+ moderator_internal.adjust_moles(GAS_PLUONIUM, -remove_amount)
+ delta_mod_list[GAS_PLUONIUM] -= remove_amount
radiation *= 1.95
heat_output *= 1.25
if(moderator_list[GAS_BZ] > 100)
- internal_output.adjust_moles(GAS_PLUONIUM, scaled_production * 1.5)
- internal_output.adjust_moles(GAS_HEALIUM, scaled_production * 1.5)
+ linked_output.airs[1].adjust_moles(GAS_PLUONIUM, scaled_production * 1.5)
+ linked_output.airs[1].adjust_moles(GAS_HEALIUM, scaled_production * 1.5)
induce_hallucination(50 * power_level, delta_time)
if(5)
if(moderator_list[GAS_PLASMA] > 15)
- internal_output.adjust_moles(GAS_FREON, scaled_production * 0.25)
- moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.45))
+ linked_output.airs[1].adjust_moles(GAS_FREON, scaled_production * 0.25)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.45), 0.01)
+ moderator_internal.adjust_moles(GAS_PLASMA, -remove_amount)
+ delta_mod_list[GAS_PLASMA] -= remove_amount
if(moderator_list[GAS_FREON] > 500)
heat_output *= 0.5
radiation *= 0.2
if(moderator_list[GAS_PLUONIUM] > 50)
- internal_output.adjust_moles(GAS_PLUOXIUM, scaled_production)
- moderator_internal.adjust_moles(GAS_PLUONIUM, -min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 1.35))
+ linked_output.airs[1].adjust_moles(GAS_PLUOXIUM, scaled_production)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 1.35), 0.01)
+ moderator_internal.adjust_moles(GAS_PLUONIUM, -remove_amount)
+ delta_mod_list[GAS_PLUONIUM] -= remove_amount
radiation *= 1.95
heat_output *= 1.25
if(moderator_list[GAS_BZ] > 100)
- internal_output.adjust_moles(GAS_HEALIUM, scaled_production)
+ linked_output.airs[1].adjust_moles(GAS_HEALIUM, scaled_production)
+ linked_output.airs[1].adjust_moles(GAS_FREON, scaled_production * 1.15)
induce_hallucination(500, delta_time)
- internal_output.adjust_moles(GAS_FREON, scaled_production * 1.15)
if(moderator_list[GAS_HEALIUM] > 100)
if(critical_threshold_proximity > 400)
critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[GAS_HEALIUM] / 100 * delta_time ), 0)
- moderator_internal.adjust_moles(GAS_HEALIUM, -min(moderator_internal.get_moles(GAS_HEALIUM), scaled_production * 20))
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_HEALIUM), scaled_production * 20), 0.01)
+ moderator_internal.adjust_moles(GAS_HEALIUM, -remove_amount)
+ delta_mod_list[GAS_HEALIUM] -= remove_amount
if(moderator_internal.return_temperature() < 1e7 || (moderator_list[GAS_PLASMA] > 100 && moderator_list[GAS_BZ] > 50))
- internal_output.adjust_moles(GAS_ANTINOB, dirty_production_rate * 0.9 / 0.065 * delta_time)
+ linked_output.airs[1].adjust_moles(GAS_ANTINOB, dirty_production_rate * 0.9 / 0.065 * delta_time)
if(6)
if(moderator_list[GAS_PLASMA] > 30)
- internal_output.adjust_moles(GAS_BZ, scaled_production * 1.15)
- moderator_internal.adjust_moles(GAS_PLASMA, -min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.45))
+ linked_output.airs[1].adjust_moles(GAS_BZ, scaled_production * 1.15)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLASMA), scaled_production * 1.45), 0.01)
+ moderator_internal.adjust_moles(GAS_PLASMA, -remove_amount)
+ delta_mod_list[GAS_PLASMA] -= remove_amount
if(moderator_list[GAS_PLUONIUM])
- internal_output.adjust_moles(GAS_ZAUKER, scaled_production * 5.35)
- internal_output.adjust_moles(GAS_NITRIUM, scaled_production * 2.15)
- moderator_internal.adjust_moles(GAS_PLUONIUM, -min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 3.35))
+ linked_output.airs[1].adjust_moles(GAS_ZAUKER, scaled_production * 5.35)
+ linked_output.airs[1].adjust_moles(GAS_NITRIUM, scaled_production * 2.15)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_PLUONIUM), scaled_production * 3.35), 0.01)
+ delta_mod_list[GAS_PLUONIUM] -= remove_amount
+ moderator_internal.adjust_moles(GAS_PLUONIUM, -remove_amount)
radiation *= 2
heat_output *= 2.25
if(moderator_list[GAS_BZ])
induce_hallucination(900, delta_time, force=TRUE)
- internal_output.adjust_moles(GAS_ANTINOB, clamp(dirty_production_rate / 0.045, 0, 10) * delta_time)
+ linked_output.airs[1].adjust_moles(GAS_ANTINOB, clamp(dirty_production_rate / 0.045, 0, 10) * delta_time)
if(moderator_list[GAS_HEALIUM] > 100)
if(critical_threshold_proximity > 400)
- critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[GAS_HEALIUM] / 100 * delta_time ), 0)
- moderator_internal.adjust_moles(GAS_HEALIUM, -min(moderator_internal.get_moles(GAS_HEALIUM), scaled_production * 20))
- internal_fusion.adjust_moles(GAS_ANTINOB, dirty_production_rate * 0.01 / 0.095 * delta_time)
+ critical_threshold_proximity = max(critical_threshold_proximity - (moderator_list[GAS_HEALIUM] / 100 * delta_time), 0)
+ var/remove_amount = round(min(moderator_internal.get_moles(GAS_HEALIUM), scaled_production * 20), 0.01)
+ delta_mod_list[GAS_HEALIUM] -= remove_amount
+ moderator_internal.adjust_moles(GAS_HEALIUM, -remove_amount)
+ linked_output.airs[1].adjust_moles(GAS_ANTINOB, dirty_production_rate * 0.01 / 0.095 * delta_time)
//Modifies the internal_fusion temperature with the amount of heat output
var/temperature_modifier = selected_fuel.temperature_change_multiplier
@@ -369,7 +408,9 @@
var/max_iron_removable = IRON_OXYGEN_HEAL_PER_SECOND
var/iron_removed = min(max_iron_removable * delta_time, iron_content)
iron_content -= iron_removed
- moderator_internal.adjust_moles(GAS_O2, -(iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL))
+ var/remove_amount = round(iron_removed * OXYGEN_MOLES_CONSUMED_PER_IRON_HEAL, 0.01)
+ delta_mod_list[GAS_O2] -= remove_amount
+ moderator_internal.adjust_moles(GAS_O2, -remove_amount)
check_gravity_pulse(delta_time)
@@ -381,7 +422,10 @@
return
// All gases in the moderator slowly burn away over time, whether used for production or not
if(moderator_internal.total_moles() > 0)
- moderator_internal.remove(moderator_internal.total_moles() * (1 - (1 - 0.0005 * power_level) ** delta_time))
+ var/remove_amount = round(moderator_internal.total_moles() * (1 - (1 - 0.0005 * power_level) ** delta_time), 0.01)
+ for(var/delta_id in moderator_internal.get_gases())
+ delta_mod_list[delta_id] -= round(remove_amount * moderator_internal.get_moles(delta_id) / moderator_internal.total_moles(), 0.01)
+ moderator_internal.remove(remove_amount)
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_damageheal(delta_time)
// Archive current health for damage cap purposes
@@ -456,26 +500,22 @@
supermatter_zap(src, 5, power_level * 300)
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/check_gravity_pulse(delta_time)
- if (critical_threshold_proximity == 0)
- return
-
- if(DT_PROB(100 - critical_threshold_proximity / 15, delta_time))
- return
-
- var/grav_range = round(log(2.5, critical_threshold_proximity))
- for(var/mob/alive_mob in GLOB.alive_mob_list)
- if(alive_mob.z != z || get_dist(alive_mob, src) > grav_range || alive_mob.mob_negates_gravity())
- continue
- step_towards(alive_mob, loc)
+ if(prob(critical_threshold_proximity / 15 * delta_time))
+ var/grav_range = round(log(2.5, critical_threshold_proximity))
+ for(var/mob/alive_mob in GLOB.alive_mob_list)
+ if(alive_mob.z != z || get_dist(alive_mob, src) > grav_range || alive_mob.mob_negates_gravity())
+ continue
+ step_towards(alive_mob, loc)
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/remove_fuel(delta_time)
if(!fuel_remove)
return
- for(var/gas in internal_fusion.get_gases())
- var/gas_removed = min(internal_fusion.get_moles(gas), fuel_filtering_rate/2 * delta_time)
- internal_fusion.adjust_moles(gas, -gas_removed)
- linked_output.airs[1].adjust_moles(gas, gas_removed)
+ for(var/gas_id in internal_fusion.get_gases())
+ var/gas_removed = round(min(internal_fusion.get_moles(gas_id), fuel_filtering_rate * delta_time * 4), 0.01)
+ delta_fuel_list[gas_id] -= gas_removed
+ internal_fusion.adjust_moles(gas_id, -gas_removed)
+ linked_output.airs[1].adjust_moles(gas_id, gas_removed)
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/remove_waste(delta_time)
//Gases can be removed from the moderator internal by using the interface.
@@ -483,16 +523,18 @@
return
var/filtering_amount = moderator_scrubbing.len
- for(var/gas in moderator_internal.get_gases() & moderator_scrubbing)
- var/gas_removed = min(moderator_internal.get_moles(gas), (moderator_filtering_rate / filtering_amount) * delta_time)
- moderator_internal.adjust_moles(gas, -gas_removed)
- linked_output.airs[1].adjust_moles(gas, gas_removed)
+ for(var/gas_id in moderator_internal.get_gases() & moderator_scrubbing)
+ var/gas_removed = round(min(moderator_internal.get_moles(gas_id), (moderator_filtering_rate / filtering_amount) * delta_time * 4), 0.01)
+ delta_mod_list[gas_id] -= gas_removed
+ moderator_internal.adjust_moles(gas_id, -gas_removed)
+ linked_output.airs[1].adjust_moles(gas_id, gas_removed)
if (selected_fuel)
for(var/gas_id in selected_fuel.primary_products)
if(internal_fusion.get_moles(gas_id) > 0)
- var/gas_removed = min(internal_fusion.get_moles(gas_id), internal_fusion.get_moles(gas_id) * (1 - (1 - 0.25) ** delta_time))
+ var/gas_removed = round(min(internal_fusion.get_moles(gas_id), internal_fusion.get_moles(gas_id) * (1 - (1 - 0.25) ** (delta_time * 4))), 0.01)
internal_fusion.adjust_moles(gas_id, -gas_removed)
+ delta_fuel_list[gas_id] -= gas_removed
linked_output.airs[1].adjust_moles(gas_id, gas_removed)
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/process_internal_cooling(delta_time)
@@ -501,7 +543,7 @@
var/fusion_temperature_delta = internal_fusion.return_temperature() - moderator_internal.return_temperature()
var/fusion_heat_amount = (1 - (1 - METALLIC_VOID_CONDUCTIVITY) ** delta_time) * fusion_temperature_delta * (internal_fusion.heat_capacity() * moderator_internal.heat_capacity() / (internal_fusion.heat_capacity() + moderator_internal.heat_capacity()))
internal_fusion.set_temperature(max(internal_fusion.return_temperature() - fusion_heat_amount / internal_fusion.heat_capacity(), TCMB))
- moderator_internal.set_temperature(max(moderator_internal.return_temperature() + fusion_heat_amount / moderator_internal.heat_capacity(), TCMB))
+ moderator_internal.set_temperature(max((moderator_internal.return_temperature() + fusion_heat_amount / moderator_internal.heat_capacity()) * 0.4, TCMB))
if(airs[1].total_moles() * 0.05 <= MINIMUM_MOLE_COUNT)
return
@@ -509,16 +551,16 @@
var/datum/gas_mixture/cooling_port = airs[1]
var/datum/gas_mixture/cooling_remove = cooling_port.remove(0.05 * cooling_port.total_moles())
//Cooling of the moderator gases with the cooling loop in and out the core
- if(moderator_internal.total_moles() > 0)
+ if(moderator_internal.total_moles() > MINIMUM_MOLE_COUNT)
var/coolant_temperature_delta = cooling_remove.return_temperature() - moderator_internal.return_temperature()
var/cooling_heat_amount = (1 - (1 - HIGH_EFFICIENCY_CONDUCTIVITY) ** delta_time) * coolant_temperature_delta * (cooling_remove.heat_capacity() * moderator_internal.heat_capacity() / (cooling_remove.heat_capacity() + moderator_internal.heat_capacity()))
- cooling_remove.set_temperature(max(cooling_remove.return_temperature() - cooling_heat_amount / cooling_remove.heat_capacity(), TCMB))
+ cooling_remove.set_temperature(max((cooling_remove.return_temperature() - cooling_heat_amount / cooling_remove.heat_capacity()) * 0.4, TCMB))
moderator_internal.set_temperature(max(moderator_internal.return_temperature() + cooling_heat_amount / moderator_internal.heat_capacity(), TCMB))
- else if(internal_fusion.total_moles() > 0)
+ else if(internal_fusion.total_moles() > MINIMUM_MOLE_COUNT)
var/coolant_temperature_delta = cooling_remove.return_temperature() - internal_fusion.return_temperature()
var/cooling_heat_amount = (1 - (1 - METALLIC_VOID_CONDUCTIVITY) ** delta_time) * coolant_temperature_delta * (cooling_remove.heat_capacity() * internal_fusion.heat_capacity() / (cooling_remove.heat_capacity() + internal_fusion.heat_capacity()))
- cooling_remove.set_temperature(max(cooling_remove.return_temperature() - cooling_heat_amount / cooling_remove.heat_capacity(), TCMB))
+ cooling_remove.set_temperature(max((cooling_remove.return_temperature() - cooling_heat_amount / cooling_remove.heat_capacity()) * 0.4, TCMB))
internal_fusion.set_temperature(max(internal_fusion.return_temperature() + cooling_heat_amount / internal_fusion.heat_capacity(), TCMB))
cooling_port.merge(cooling_remove)
@@ -528,7 +570,10 @@
//Check and stores the gases from the moderator input in the moderator internal gasmix
var/datum/gas_mixture/moderator_port = linked_moderator.airs[1]
if(start_moderator && moderator_port.total_moles())
- moderator_internal.merge(moderator_port.remove(moderator_injection_rate * delta_time))
+ var/adjust_amount = round(moderator_injection_rate * delta_time * 4, 0.01)
+ moderator_internal.merge(moderator_port.remove(adjust_amount))
+ for(var/gas_id in moderator_port.get_gases())
+ delta_mod_list[gas_id] += adjust_amount
linked_moderator.update_parents()
//Check if the fuels are present and move them inside the fuel internal gasmix
@@ -537,8 +582,9 @@
var/datum/gas_mixture/fuel_port = linked_input.airs[1]
for(var/gas_type in selected_fuel.requirements)
- var/fuel_injected = min(linked_input.airs[1].get_moles(gas_type), fuel_injection_rate * delta_time / length(selected_fuel.requirements))
+ var/fuel_injected = round(min(linked_input.airs[1].get_moles(gas_type), fuel_injection_rate * 4 * delta_time / length(selected_fuel.requirements)), 0.01)
fuel_port.adjust_moles(gas_type, -fuel_injected)
+ delta_fuel_list[gas_type] += fuel_injected
internal_fusion.adjust_moles(gas_type, fuel_injected)
linked_input.update_parents()
diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm
index ecb4e063594f..b059b9037686 100644
--- a/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm
+++ b/code/modules/atmospherics/machinery/components/fusion/hfr_parts.dm
@@ -247,12 +247,14 @@
fusion_gasdata.Add(list(list(
"id"= initial(gas_id),
"amount" = round(connected_core.internal_fusion.get_moles(gas_id), 0.01),
+ "remove_rate" = round(connected_core.delta_fuel_list[gas_id], 0.01),
)))
else
for(var/gas_id in connected_core.internal_fusion.get_gases())
fusion_gasdata.Add(list(list(
"id"= initial(gas_id),
"amount" = 0,
+ "remove_rate" = round(connected_core.delta_fuel_list[gas_id], 0.01),
)))
//Moderator gases
var/list/moderator_gasdata = list()
@@ -261,12 +263,14 @@
moderator_gasdata.Add(list(list(
"id"= initial(gas_id),
"amount" = round(connected_core.moderator_internal.get_moles(gas_id), 0.01),
+ "remove_rate" = round(connected_core.delta_mod_list[gas_id], 0.01),
)))
else
for(var/gas_id in connected_core.moderator_internal.get_gases())
moderator_gasdata.Add(list(list(
"id"= initial(gas_id),
"amount" = 0,
+ "remove_rate" = round(connected_core.delta_mod_list[gas_id], 0.01),
)))
data["fusion_gases"] = fusion_gasdata
diff --git a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm
index e6e67f3c501f..a0c0acff3bde 100644
--- a/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm
+++ b/code/modules/atmospherics/machinery/components/fusion/hfr_procs.dm
@@ -92,20 +92,20 @@
update_appearance(UPDATE_ICON)
linked_interface.active = TRUE
linked_interface.update_appearance(UPDATE_ICON)
- RegisterSignal(linked_interface, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals))
+ RegisterSignal(linked_interface, COMSIG_QDELETING, PROC_REF(unregister_signals))
linked_input.active = TRUE
linked_input.update_appearance(UPDATE_ICON)
- RegisterSignal(linked_input, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals))
+ RegisterSignal(linked_input, COMSIG_QDELETING, PROC_REF(unregister_signals))
linked_output.active = TRUE
linked_output.update_appearance(UPDATE_ICON)
- RegisterSignal(linked_output, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals))
+ RegisterSignal(linked_output, COMSIG_QDELETING, PROC_REF(unregister_signals))
linked_moderator.active = TRUE
linked_moderator.update_appearance(UPDATE_ICON)
- RegisterSignal(linked_moderator, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals))
+ RegisterSignal(linked_moderator, COMSIG_QDELETING, PROC_REF(unregister_signals))
for(var/obj/machinery/hypertorus/corner/corner in corners)
corner.active = TRUE
corner.update_appearance(UPDATE_ICON)
- RegisterSignal(corner, COMSIG_PARENT_QDELETING, PROC_REF(unregister_signals))
+ RegisterSignal(corner, COMSIG_QDELETING, PROC_REF(unregister_signals))
soundloop = new(list(src), TRUE)
soundloop.volume = 5
@@ -118,15 +118,15 @@
/obj/machinery/atmospherics/components/unary/hypertorus/core/proc/unregister_signals(only_signals = FALSE)
SIGNAL_HANDLER
if(linked_interface)
- UnregisterSignal(linked_interface, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(linked_interface, COMSIG_QDELETING)
if(linked_input)
- UnregisterSignal(linked_input, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(linked_input, COMSIG_QDELETING)
if(linked_output)
- UnregisterSignal(linked_output, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(linked_output, COMSIG_QDELETING)
if(linked_moderator)
- UnregisterSignal(linked_moderator, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(linked_moderator, COMSIG_QDELETING)
for(var/obj/machinery/hypertorus/corner/corner in corners)
- UnregisterSignal(corner, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(corner, COMSIG_QDELETING)
if(!only_signals)
deactivate()
diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm
index f60149ae6115..198ea0c2ddc3 100644
--- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm
+++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm
@@ -14,6 +14,7 @@
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 80, ACID = 30)
circuit = /obj/item/circuitboard/machine/crystallizer
pipe_flags = PIPING_ONE_PER_TURF| PIPING_DEFAULT_LAYER_ONLY
+ vent_movement = NONE
///Base icon state for the machine to be used in update_appearance(UPDATE_ICON)
var/base_icon = "crystallizer"
@@ -341,6 +342,9 @@
investigate_log("was set to [gas_input] by [key_name(usr)]", INVESTIGATE_ATMOS)
update_appearance(UPDATE_ICON)
+/obj/machinery/atmospherics/components/binary/crystallizer/update_layer()
+ return
+
#undef MIN_PROGRESS_AMOUNT
#undef MIN_DEVIATION_RATE
#undef MAX_DEVIATION_RATE
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm
index d711d65ec4d4..fd2e968e0d6e 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/trinary_devices.dm
@@ -6,6 +6,7 @@
device_type = TRINARY
layer = GAS_FILTER_LAYER
pipe_flags = PIPING_ONE_PER_TURF
+ vent_movement = NONE
var/flipped = FALSE
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index 7e9740b47a36..8d153b95f98c 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -7,39 +7,52 @@
// Must be tall, otherwise the filter will consider this as a 32x32 tile
// and will crop the head off.
icon_state = "mask_bg"
- layer = ABOVE_WINDOW_LAYER + 0.01
+ layer = ABOVE_MOB_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
pixel_y = 22
appearance_flags = KEEP_TOGETHER
+ /// The current occupant being presented
+ var/mob/living/occupant
-/atom/movable/visual/cryo_occupant/Initialize(mapload)
+/atom/movable/visual/cryo_occupant/Initialize(mapload, obj/machinery/atmospherics/components/unary/cryo_cell/parent)
. = ..()
// Alpha masking
// It will follow this as the animation goes, but that's no problem as the "mask" icon state
// already accounts for this.
add_filter("alpha_mask", 1, list("type" = "alpha", "icon" = icon('icons/obj/cryogenics.dmi', "mask"), "y" = -22))
+ RegisterSignal(parent, COMSIG_MACHINERY_SET_OCCUPANT, PROC_REF(on_set_occupant))
+ RegisterSignal(parent, COMSIG_CRYO_SET_ON, PROC_REF(on_set_on))
+
+/// COMSIG_MACHINERY_SET_OCCUPANT callback
+/atom/movable/visual/cryo_occupant/proc/on_set_occupant(datum/source, mob/living/new_occupant)
+ SIGNAL_HANDLER
+
+ if(occupant)
+ vis_contents -= occupant
+ occupant.vis_flags &= ~VIS_INHERIT_PLANE
+ occupant.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_FORCED_STANDING), CRYO_TRAIT)
+
+ occupant = new_occupant
+ if(!occupant)
+ return
-/atom/movable/visual/cryo_occupant/proc/on_occupant_enter(mob/living/occupant)
occupant.setDir(SOUTH)
+ // We want to pull our occupant up to our plane so we look right
+ occupant.vis_flags |= VIS_INHERIT_PLANE
vis_contents += occupant
pixel_y = 22
- ADD_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
- ADD_TRAIT(occupant, TRAIT_FORCED_STANDING, CRYO_TRAIT)
+ occupant.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_FORCED_STANDING), CRYO_TRAIT)
occupant.set_resting(FALSE, silent = TRUE)
-/atom/movable/visual/cryo_occupant/proc/on_occupant_exit(mob/living/occupant)
- vis_contents -= occupant
- REMOVE_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
- REMOVE_TRAIT(occupant, TRAIT_FORCED_STANDING, CRYO_TRAIT)
- if(occupant.resting || HAS_TRAIT(occupant, TRAIT_FLOORED))
- occupant.set_resting(TRUE, silent = TRUE)
-
-/atom/movable/visual/cryo_occupant/proc/on_toggle_on()
- animate(src, pixel_y = 24, time = 20, loop = -1)
- animate(pixel_y = 22, time = 20)
+/// COMSIG_CRYO_SET_ON callback
+/atom/movable/visual/cryo_occupant/proc/on_set_on(datum/source, on)
+ SIGNAL_HANDLER
-/atom/movable/visual/cryo_occupant/proc/on_toggle_off()
- animate(src)
+ if(on)
+ animate(src, pixel_y = 24, time = 20, loop = -1)
+ animate(pixel_y = 22, time = 20)
+ else
+ animate(src)
/// Cryo cell
/obj/machinery/atmospherics/components/unary/cryo_cell
@@ -49,10 +62,11 @@
density = TRUE
max_integrity = 350
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 30, ACID = 30)
- layer = ABOVE_WINDOW_LAYER
+ layer = MOB_LAYER
state_open = FALSE
circuit = /obj/item/circuitboard/machine/cryo_tube
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
+ vent_movement = NONE
occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
showpipe = FALSE
@@ -81,9 +95,14 @@
var/breakout_time = 300
///Cryo will continue to treat people with 0 damage but existing wounds, but will sound off when damage healing is done in case doctors want to directly treat the wounds instead
var/treating_wounds = FALSE
+ /// Cryo should notify doctors if the patient is dead, and eject them if autoeject is enabled
+ var/patient_dead = FALSE
fair_market_price = 10
payment_department = ACCOUNT_MED
+ /// Reference to the datum connector we're using to interface with the pipe network
+ //var/datum/gas_machine_connector/internal_connector
+
/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize(mapload)
. = ..()
@@ -97,15 +116,23 @@
radio.canhear_range = 0
radio.recalculateChannels()
- occupant_vis = new(null)
+ occupant_vis = new(null, src)
vis_contents += occupant_vis
+/obj/machinery/atmospherics/components/unary/cryo_cell/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ SET_PLANE(occupant_vis, PLANE_TO_TRUE(occupant_vis.plane), new_turf)
+
+/obj/machinery/atmospherics/components/unary/cryo_cell/set_occupant(atom/movable/new_occupant)
+ . = ..()
+ update_appearance()
+
/obj/machinery/atmospherics/components/unary/cryo_cell/Exited(atom/movable/AM, atom/newloc)
- var/mob/living/oldoccupant = occupant
- . = ..() // Parent proc takes care of removing occupant if necessary
- if (oldoccupant && istype(oldoccupant) && AM == oldoccupant)
- occupant_vis.on_occupant_exit(oldoccupant)
- update_appearance(UPDATE_ICON)
+ . = ..()
+ if(AM == beaker)
+ beaker = null
/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction()
..(dir, dir)
@@ -163,20 +190,25 @@
updateUsrDialog()
/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction()
+ if(occupant)
+ occupant.vis_flags &= ~VIS_INHERIT_PLANE
+ REMOVE_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
+ REMOVE_TRAIT(occupant, TRAIT_FORCED_STANDING, CRYO_TRAIT)
+
if(beaker)
beaker.forceMove(drop_location())
beaker = null
/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon(updates=ALL)
. = ..()
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon_state()
- . = ..()
icon_state = (state_open) ? "pod-open" : (on && is_operational()) ? "pod-on" : "pod-off"
+ return ..()
-GLOBAL_VAR_INIT(cryo_overlay_cover_on, mutable_appearance('icons/obj/cryogenics.dmi', "cover-on", layer = ABOVE_WINDOW_LAYER + 0.02))
-GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics.dmi', "cover-off", layer = ABOVE_WINDOW_LAYER + 0.02))
+/obj/machinery/atmospherics/components/unary/cryo_cell/update_layer()
+ return //no updates so we don't end up on layer 4.003 and overlapping mobs
/obj/machinery/atmospherics/components/unary/cryo_cell/update_overlays()
. = ..()
@@ -185,20 +217,17 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics
if(state_open)
return
if(on && is_operational())
- . += GLOB.cryo_overlay_cover_on
+ . += mutable_appearance('icons/obj/cryogenics.dmi', "cover-on", ABOVE_ALL_MOB_LAYER, src, plane = ABOVE_GAME_PLANE)
else
- . += GLOB.cryo_overlay_cover_off
+ . += mutable_appearance('icons/obj/cryogenics.dmi', "cover-off", ABOVE_ALL_MOB_LAYER, src, plane = ABOVE_GAME_PLANE)
/obj/machinery/atmospherics/components/unary/cryo_cell/proc/set_on(new_value)
if(on == new_value)
return
+ SEND_SIGNAL(src, COMSIG_CRYO_SET_ON, new_value)
. = on
on = new_value
- if(on)
- occupant_vis.on_toggle_on()
- else
- occupant_vis.on_toggle_off()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/atmospherics/components/unary/cryo_cell/process(delta_time)
@@ -318,7 +347,7 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics
set_on(FALSE)
for(var/mob/M in contents) //only drop mobs
M.forceMove(get_turf(src))
- occupant = null
+ set_occupant(null)
flick("pod-open-anim", src)
reagent_transfer = efficiency * 10 - 5 // wait before injecting the next occupant
..()
@@ -328,8 +357,6 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics
if((isnull(user) || istype(user)) && state_open && !panel_open)
flick("pod-close-anim", src)
..(user)
- if(isliving(occupant))
- occupant_vis.on_occupant_enter(occupant)
return occupant
/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user)
@@ -385,7 +412,7 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics
|| default_change_direction_wrench(user, I) \
|| default_pry_open(I) \
|| default_deconstruction_crowbar(I))
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
else if(I.tool_behaviour == TOOL_SCREWDRIVER)
to_chat(user, "You can't access the maintenance panel while the pod is " \
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
index 40d7ebc0af8c..6bedbe6f1c9a 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
@@ -7,6 +7,7 @@
use_power = IDLE_POWER_USE
can_unwrench = TRUE
shift_underlay_only = FALSE
+ hide = TRUE
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF //really helpful in building gas chambers for xenomorphs
@@ -18,7 +19,6 @@
var/id = null
var/datum/radio_frequency/radio_connection
- level = 1
layer = GAS_SCRUBBER_LAYER
pipe_state = "injector"
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
index 8a44fb2bcb61..8b163496380e 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm
@@ -4,9 +4,11 @@
desc = "It is an open vent."
can_unwrench = TRUE
layer = GAS_SCRUBBER_LAYER
+ hide = TRUE
shift_underlay_only = FALSE
pipe_state = "pvent"
+ vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
/obj/machinery/atmospherics/components/unary/passive_vent/update_icon_nopipes()
cut_overlays()
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
index a5d1fe5b0a29..177c281d79e4 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
@@ -4,8 +4,8 @@
desc = "For connecting portables devices related to atmospherics control."
can_unwrench = TRUE
use_power = NO_POWER_USE
- level = 0
layer = GAS_FILTER_LAYER
+ hide = TRUE
shift_underlay_only = FALSE
pipe_flags = PIPING_ONE_PER_TURF
pipe_state = "connector"
@@ -63,8 +63,8 @@
icon_state = "connector_map-4"
/obj/machinery/atmospherics/components/unary/portables_connector/visible
- level = 3
piping_layer = 3
+ hide = FALSE
/obj/machinery/atmospherics/components/unary/portables_connector/visible/layer2
piping_layer = 2
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index bad3de341d73..0bd4d72eb38e 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -12,6 +12,7 @@
circuit = /obj/item/circuitboard/machine/thermomachine
pipe_flags = PIPING_ONE_PER_TURF
+ vent_movement = NONE
var/icon_state_off = "freezer"
var/icon_state_on = "freezer_1"
@@ -96,6 +97,9 @@
investigate_log("was set to [target_temperature] K by [key_name(user)]", INVESTIGATE_ATMOS)
balloon_alert(user, "You maximize the target temperature on [src] to [target_temperature] K.d")
+/obj/machinery/atmospherics/components/unary/thermomachine/update_layer()
+ return
+
/obj/machinery/atmospherics/components/unary/thermomachine/update_icon_nopipes()
cut_overlays()
if(showpipe)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/unary_devices.dm b/code/modules/atmospherics/machinery/components/unary_devices/unary_devices.dm
index b61cc02b516e..284c5abbc50c 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/unary_devices.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/unary_devices.dm
@@ -5,7 +5,9 @@
device_type = UNARY
pipe_flags = PIPING_ONE_PER_TURF
construction_type = /obj/item/pipe/directional
+ ///Unique id of the device
var/uid
+ ///Increases to prevent duplicated Ids
var/static/gl_uid = 1
FASTDMM_PROP(\
@@ -18,11 +20,7 @@
/obj/machinery/atmospherics/components/unary/on_construction()
..()
- update_appearance(UPDATE_ICON)
-
-/obj/machinery/atmospherics/components/unary/hide(intact)
- update_appearance(UPDATE_ICON)
- ..(intact)
+ update_appearance()
/obj/machinery/atmospherics/components/unary/proc/assign_uid_vents()
uid = num2text(gl_uid++)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index 18c8cc194bfb..b8cbf4f440d0 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -14,8 +14,8 @@
use_power = IDLE_POWER_USE
can_unwrench = TRUE
welded = FALSE
- level = 1
layer = GAS_SCRUBBER_LAYER
+ hide = TRUE
shift_underlay_only = FALSE
showpipe = FALSE
@@ -43,6 +43,7 @@
var/obj/machinery/advanced_airlock_controller/aac = null
pipe_state = "uvent"
+ vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
/obj/machinery/atmospherics/components/unary/vent_pump/New()
..()
@@ -118,7 +119,7 @@
space_shutoff_ticks--
if(space_shutoff_ticks <= 1 && !on)
on = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(!nodes[1])
on = FALSE
if(!on || welded)
@@ -140,20 +141,25 @@
last_moles_added = 0
on = FALSE
space_shutoff_ticks = 20 // shut off for about 20 seconds before trying again.
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(pump_direction & RELEASING) // internal -> external
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
- var/multiplier = 1 // fast_fill multiplier
- if(fast_fill)
- if(last_moles_added > 0 && last_moles_real_added > 0)
- multiplier = clamp(last_moles_added / last_moles_real_added * 0.25, 1, 100)
- else if(last_moles_added > 0 && last_moles_real_added < 0 && environment_moles != 0)
- multiplier = 10 // pressure is going down, but let's fight it anyways
- pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure) * multiplier)
+ var/ext_difference = external_pressure_bound - environment_pressure
+ if(fast_fill && ext_difference <= 100)
+ //exponential
+ pressure_delta = min(pressure_delta, (2 ** ((ext_difference / 30) + 7)) - 128)
+ else if(fast_fill && last_moles_added > 0 && last_moles_real_added > 0)
+ //old scaling
+ pressure_delta = min(pressure_delta, ext_difference * clamp((last_moles_added / last_moles_real_added) * 0.25, 1, 100))
+ else if(fast_fill && last_moles_added > 0 && last_moles_real_added < 0)
+ //old scaling
+ pressure_delta = min(pressure_delta, ext_difference * 10)
+ else
+ pressure_delta = min(pressure_delta, ext_difference)
if(pressure_checks&INT_BOUND)
pressure_delta = min(pressure_delta, (air_contents.return_pressure() - internal_pressure_bound))
if(space_shutoff_ticks > 0) // if we just came off a space-shutoff, only transfer a little bit.
@@ -161,7 +167,6 @@
if(pressure_delta > 0)
if(air_contents.return_temperature() > 0)
-
var/transfer_moles = pressure_delta*environment.return_volume()/(air_contents.return_temperature() * R_IDEAL_GAS_EQUATION)
last_moles_added = transfer_moles
loc.assume_air_moles(air_contents, transfer_moles)
@@ -296,7 +301,7 @@
// log_admin("DEBUG \[[world.timeofday]\]: vent_pump/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
broadcast_status()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/atmospherics/components/unary/vent_pump/welder_act(mob/living/user, obj/item/I)
if(!I.tool_start_check(user, amount=0))
@@ -309,9 +314,9 @@
else
user.visible_message("[user] unwelded the vent.", span_notice("You unweld the vent."), span_italics("You hear welding."))
welded = FALSE
- update_appearance(UPDATE_ICON)
- pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ update_appearance()
+ pipe_vision_img = image(src, loc, dir = dir)
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
investigate_log("was [welded ? "welded shut" : "unwelded"] by [key_name(user)]", INVESTIGATE_ATMOS)
add_fingerprint(user)
return TRUE
@@ -339,9 +344,9 @@
return
user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the vent", "You hear loud scraping noises.")
welded = FALSE
- update_appearance(UPDATE_ICON)
- pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ update_appearance()
+ pipe_vision_img = image(src, loc, dir = dir)
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
/obj/machinery/atmospherics/components/unary/vent_pump/high_volume
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
index 4631397bb504..9db8ab0ded06 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm
@@ -11,8 +11,8 @@
active_power_usage = 60
can_unwrench = TRUE
welded = FALSE
- level = 1
layer = GAS_SCRUBBER_LAYER
+ hide = TRUE
shift_underlay_only = FALSE
showpipe = FALSE
@@ -30,6 +30,7 @@
var/radio_filter_in
pipe_state = "scrubber"
+ vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
/obj/machinery/atmospherics/components/unary/vent_scrubber/New()
..()
@@ -239,9 +240,9 @@
else
user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.")
welded = FALSE
- update_appearance(UPDATE_ICON)
- pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ update_appearance()
+ pipe_vision_img = image(src, loc, dir = dir)
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
investigate_log("was [welded ? "welded shut" : "unwelded"] by [key_name(user)]", INVESTIGATE_ATMOS)
add_fingerprint(user)
return TRUE
@@ -265,9 +266,9 @@
return
user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
welded = FALSE
- update_appearance(UPDATE_ICON)
- pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
- pipe_vision_img.plane = ABOVE_HUD_PLANE
+ update_appearance()
+ pipe_vision_img = image(src, loc, dir = dir)
+ SET_PLANE_EXPLICIT(pipe_vision_img, ABOVE_HUD_PLANE, src)
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm
index e5a2eb7addff..79a04860104e 100644
--- a/code/modules/atmospherics/machinery/datum_pipeline.dm
+++ b/code/modules/atmospherics/machinery/datum_pipeline.dm
@@ -49,8 +49,10 @@
considered_pipe.air_temporary = null
else
add_machinery_member(base)
+
if(!air)
air = new
+
air.set_volume(volume)
SSair.add_to_expansion(src, base)
@@ -106,6 +108,14 @@
air.set_volume(volume)
+/**
+ * For a machine to properly "connect" to a pipeline and share gases,
+ * the pipeline needs to acknowledge a gas mixture as it's member.
+ * This is currently handled by the other_airs list in the pipeline datum.
+ *
+ * Other_airs itself is populated by gas mixtures through the parents list that each machineries have.
+ * This parents list is populated when a machinery calls update_parents and is then added into the queue by the controller.
+ */
/datum/pipeline/proc/add_machinery_member(obj/machinery/atmospherics/components/considered_component)
other_atmos_machines |= considered_component
var/list/returned_airs = considered_component.return_pipenet_airs(src)
diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm
index 3e511d438366..925ff93310e3 100644
--- a/code/modules/atmospherics/machinery/other/meter.dm
+++ b/code/modules/atmospherics/machinery/other/meter.dm
@@ -3,7 +3,7 @@
desc = "It measures something."
icon = 'icons/obj/atmospherics/pipes/meter.dmi'
icon_state = "meterX"
- layer = GAS_METER_LAYER
+ layer = HIGH_PIPE_LAYER
power_channel = AREA_USAGE_ENVIRON
use_power = IDLE_POWER_USE
idle_power_usage = 2
@@ -32,14 +32,14 @@
id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION
/obj/machinery/meter/Destroy()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
target = null
return ..()
/obj/machinery/meter/Initialize(mapload, new_piping_layer)
if(!isnull(new_piping_layer))
target_layer = new_piping_layer
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
if(!target)
reattach_to_layer()
return ..()
@@ -49,8 +49,6 @@
for(var/obj/machinery/atmospherics/pipe/pipe in loc)
if(pipe.piping_layer == target_layer)
candidate = pipe
- if(pipe.level == 2)
- break
if(candidate)
target = candidate
setAttachLayer(candidate.piping_layer)
diff --git a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
index 43721f5de7b1..457a8c174e54 100644
--- a/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/heat_exchange/he_pipes.dm
@@ -1,11 +1,11 @@
/obj/machinery/atmospherics/pipe/heat_exchanging
- level = 2
- var/minimum_temperature_difference = 20
- var/thermal_conductivity = WINDOW_HEAT_TRANSFER_COEFFICIENT
color = "#404040"
buckle_lying = -1
- var/icon_temperature = T20C //stop small changes in temperature causing icon refresh
resistance_flags = LAVA_PROOF | FIRE_PROOF
+ hide = FALSE
+ var/minimum_temperature_difference = 20
+ var/thermal_conductivity = WINDOW_HEAT_TRANSFER_COEFFICIENT
+ var/icon_temperature = T20C //stop small changes in temperature causing icon refresh
/obj/machinery/atmospherics/pipe/heat_exchanging/Initialize(mapload)
. = ..()
@@ -14,10 +14,7 @@
/obj/machinery/atmospherics/pipe/heat_exchanging/is_connectable(obj/machinery/atmospherics/pipe/heat_exchanging/target, given_layer, HE_type_check = TRUE)
if(istype(target, /obj/machinery/atmospherics/pipe/heat_exchanging) != HE_type_check)
return FALSE
- . = ..()
-
-/obj/machinery/atmospherics/pipe/heat_exchanging/hide()
- return
+ return ..()
/obj/machinery/atmospherics/pipe/heat_exchanging/process_atmos()
var/environment_temperature = 0
diff --git a/code/modules/atmospherics/machinery/pipes/layermanifold.dm b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
index d979f42c53a0..677e7c334a30 100644
--- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm
+++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm
@@ -42,9 +42,8 @@
/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes()
return front_nodes + back_nodes + nodes
-/obj/machinery/atmospherics/pipe/layer_manifold/update_icon(updates=ALL)
- . = ..()
- layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE)
+/obj/machinery/atmospherics/pipe/layer_manifold/update_layer()
+ layer = initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE) //This is above everything else.
/obj/machinery/atmospherics/pipe/layer_manifold/update_overlays(updates=ALL)
. = ..()
@@ -106,8 +105,6 @@
/obj/machinery/atmospherics/pipe/layer_manifold/atmos_init()
normalize_cardinal_directions()
findAllConnections()
- var/turf/T = loc // hide if turf is not intact
- hide(T.intact)
/obj/machinery/atmospherics/pipe/layer_manifold/set_piping_layer()
piping_layer = PIPING_LAYER_DEFAULT
@@ -141,5 +138,4 @@
to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.")
/obj/machinery/atmospherics/pipe/layer_manifold/visible
- level = PIPE_VISIBLE_LEVEL
layer = GAS_PIPE_VISIBLE_LAYER
diff --git a/code/modules/atmospherics/machinery/pipes/mapping.dm b/code/modules/atmospherics/machinery/pipes/mapping.dm
index 887f7788b2b3..61ea729cf538 100644
--- a/code/modules/atmospherics/machinery/pipes/mapping.dm
+++ b/code/modules/atmospherics/machinery/pipes/mapping.dm
@@ -5,9 +5,9 @@
pipe_color = Color; \
color = Color; \
} \
- ##Fulltype/visible { \
- level = PIPE_VISIBLE_LEVEL; \
- layer = GAS_PIPE_VISIBLE_LAYER; \
+ ##Fulltype/visible { \
+ hide = FALSE; \
+ layer = GAS_PIPE_VISIBLE_LAYER; \
FASTDMM_PROP(pipe_group = "atmos-[piping_layer]-"+Type+"-visible");\
} \
##Fulltype/visible/layer2 { \
@@ -19,7 +19,7 @@
icon_state = Iconbase + "-4"; \
} \
##Fulltype/hidden { \
- level = PIPE_HIDDEN_LEVEL; \
+ hide = TRUE; \
FASTDMM_PROP(pipe_group = "atmos-[piping_layer]-"+Type+"-hidden");\
} \
##Fulltype/hidden/layer2 { \
diff --git a/code/modules/atmospherics/machinery/pipes/multiz.dm b/code/modules/atmospherics/machinery/pipes/multiz.dm
new file mode 100644
index 000000000000..f045afc3bef3
--- /dev/null
+++ b/code/modules/atmospherics/machinery/pipes/multiz.dm
@@ -0,0 +1,60 @@
+/// This is an atmospherics pipe which can relay air up/down a deck.
+/obj/machinery/atmospherics/pipe/multiz
+ name = "multi deck pipe adapter"
+ desc = "An adapter which allows pipes to connect to other pipenets on different decks."
+ icon_state = "adapter-3"
+ icon = 'icons/obj/atmospherics/pipes/multiz.dmi'
+
+ dir = SOUTH
+ initialize_directions = SOUTH
+
+ layer = HIGH_OBJ_LAYER
+ device_type = UNARY
+
+ construction_type = /obj/item/pipe/directional
+ pipe_state = "multiz"
+
+ ///Our central icon
+ var/mutable_appearance/center = null
+ ///The pipe icon
+ var/mutable_appearance/pipe = null
+ ///Reference to the node
+ var/obj/machinery/atmospherics/front_node = null
+
+/* We use New() instead of Initialize() because these values are used in update_icon()
+ * in the mapping subsystem init before Initialize() is called in the atoms subsystem init.
+ * This is true for the other manifolds (the 4 ways and the heat exchanges) too.
+ */
+/obj/machinery/atmospherics/pipe/multiz/New()
+ icon_state = ""
+ center = mutable_appearance(icon, "adapter_center", layer = HIGH_OBJ_LAYER)
+ pipe = mutable_appearance(icon, "pipe-[piping_layer]")
+ return ..()
+
+/obj/machinery/atmospherics/pipe/multiz/set_init_directions()
+ initialize_directions = dir
+
+/obj/machinery/atmospherics/pipe/multiz/update_layer()
+ return // Noop because we're moving this to /obj/machinery/atmospherics/pipe
+
+/obj/machinery/atmospherics/pipe/multiz/update_overlays()
+ . = ..()
+ pipe.color = front_node ? front_node.pipe_color : rgb(255, 255, 255)
+ pipe.icon_state = "pipe-[piping_layer]"
+ . += pipe
+ center.pixel_x = PIPING_LAYER_P_X * (piping_layer - PIPING_LAYER_DEFAULT)
+ . += center
+
+///Attempts to locate a multiz pipe that's above us, if it finds one it merges us into its pipenet
+/obj/machinery/atmospherics/pipe/multiz/pipeline_expansion()
+ var/turf/local_turf = get_turf(src)
+ for(var/obj/machinery/atmospherics/pipe/multiz/above in GET_TURF_ABOVE(local_turf))
+ if(!is_connectable(above, piping_layer))
+ continue
+ nodes += above
+ above.nodes += src //Two way travel :)
+ for(var/obj/machinery/atmospherics/pipe/multiz/below in GET_TURF_BELOW(local_turf))
+ if(!is_connectable(below, piping_layer))
+ continue
+ below.pipeline_expansion() //If we've got one below us, force it to add us on facebook
+ return ..()
diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm
index e14ef7818dab..893ed307ddc2 100644
--- a/code/modules/atmospherics/machinery/pipes/pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipes.dm
@@ -2,8 +2,6 @@
var/datum/gas_mixture/air_temporary //used when reconstructing a pipeline that broke
var/volume = 0
- level = 1
-
use_power = NO_POWER_USE
can_unwrench = 1
var/datum/pipeline/parent = null
@@ -18,11 +16,18 @@
),\
)
-/obj/machinery/atmospherics/pipe/New(mapload)
+/obj/machinery/atmospherics/pipe/New()
add_atom_colour(pipe_color, FIXED_COLOUR_PRIORITY)
volume = 35 * device_type
return ..()
+///I have no idea why there's a new and at this point I'm too afraid to ask
+/obj/machinery/atmospherics/pipe/Initialize(mapload)
+ . = ..()
+
+ if(hide)
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE) //if changing this, change the subtypes RemoveElements too, because thats how bespoke works
+
/obj/machinery/atmospherics/pipe/nullify_node(i)
var/obj/machinery/atmospherics/old_node = nodes[i]
. = ..()
@@ -38,16 +43,6 @@
parent = new
return list(parent)
-/obj/machinery/atmospherics/pipe/atmos_init()
- var/turf/T = loc // hide if turf is not intact
- hide(T.intact)
- ..()
-
-/obj/machinery/atmospherics/pipe/hide(i)
- if(level == 1 && isturf(loc))
- invisibility = i ? INVISIBILITY_MAXIMUM : 0
- update_appearance(UPDATE_ICON)
-
/obj/machinery/atmospherics/pipe/proc/releaseAirToTurf()
if(air_temporary)
var/turf/T = loc
@@ -117,7 +112,7 @@
for(var/i in 1 to device_type)
if(nodes[i])
var/obj/machinery/atmospherics/N = nodes[i]
- N.update_appearance(UPDATE_ICON)
+ N.update_appearance()
/obj/machinery/atmospherics/pipe/return_pipenets()
. = list(parent)
@@ -132,3 +127,6 @@
pipe_color = paint_color
update_node_icon()
return TRUE
+
+/obj/machinery/atmospherics/pipe/update_layer()
+ layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index 1e7c2c6b3148..d6de11c10b18 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -6,9 +6,6 @@
icon = 'icons/obj/atmospherics/canister.dmi'
icon_state = "hazard"
density = TRUE
- light_system = MOVABLE_LIGHT
- light_range = 1.4
- light_on = FALSE
var/valve_open = FALSE
var/obj/machinery/atmospherics/components/binary/passive_gate/pump
var/release_log = ""
@@ -43,7 +40,7 @@
"generic striped" = /obj/machinery/portable_atmospherics/canister/generic/stripe,
"generic hazard" = /obj/machinery/portable_atmospherics/canister/generic/hazard,
"caution" = /obj/machinery/portable_atmospherics/canister,
- "danger" = /obj/machinery/portable_atmospherics/canister/fusion_test,
+ "danger" = /obj/machinery/portable_atmospherics/canister/fusion,
"n2" = /obj/machinery/portable_atmospherics/canister/nitrogen,
"o2" = /obj/machinery/portable_atmospherics/canister/oxygen,
"co2" = /obj/machinery/portable_atmospherics/canister/carbon_dioxide,
@@ -171,56 +168,56 @@
/obj/machinery/portable_atmospherics/canister/freon
name = "Freon canister"
- desc = "Freon. Can absorb heat"
+ desc = "Freon. Can absorb heat."
icon_state = "freon"
gas_type = GAS_FREON
filled = 1
/obj/machinery/portable_atmospherics/canister/hydrogen
name = "Hydrogen canister"
- desc = "Hydrogen, highly flammable"
+ desc = "Hydrogen, highly flammable."
icon_state = "h2"
gas_type = GAS_H2
filled = 1
/obj/machinery/portable_atmospherics/canister/healium
name = "Healium canister"
- desc = "Healium, causes deep sleep"
+ desc = "Healium, causes deep sleep."
icon_state = "healium"
gas_type = GAS_HEALIUM
filled = 1
/obj/machinery/portable_atmospherics/canister/pluonium
name = "Pluonium canister"
- desc = "Pluonium, reacts differently with various gases"
+ desc = "Pluonium, reacts differently with various gases."
icon_state = "pluonium"
gas_type = GAS_PLUONIUM
filled = 1
/obj/machinery/portable_atmospherics/canister/halon
name = "Halon canister"
- desc = "Halon, remove oxygen from high temperature fires and cool down the area"
+ desc = "Halon, remove oxygen from high temperature fires and cool down the area."
icon_state = "halon"
gas_type = GAS_HALON
filled = 1
/obj/machinery/portable_atmospherics/canister/hexane
name = "Hexane canister"
- desc = "hexane, highly flammable."
+ desc = "Hexane, highly flammable."
icon_state = "hexane"
gas_type = GAS_HEXANE
filled = 1
/obj/machinery/portable_atmospherics/canister/zauker
name = "Zauker canister"
- desc = "Zauker, highly toxic"
+ desc = "Zauker, highly toxic."
icon_state = "zauker"
gas_type = GAS_ZAUKER
filled = 1
/obj/machinery/portable_atmospherics/canister/antinoblium
name = "Antinoblium canister"
- desc = "Antinoblium, we still don't know what it does, but it sells for a lot"
+ desc = "Antinoblium, we still don't know what it does, but it sells for a lot."
icon_state = "antino"
gas_type = GAS_ANTINOB
filled = 1
@@ -569,6 +566,12 @@
analyzer_act(user, src)
return ..()
+/obj/machinery/portable_atmospherics/canister/fusion
+ name = "Fusion Canister"
+ desc = "A violent mix of gases resulting in a fusion reaction inside the canister. \
+ A note on the side reads: \"DANGER: DO NOT OPEN\""
+ icon_state = "danger"
+
/* yog- ADMEME CANISTERS */
/// Canister 1 Kelvin below the fusion point. Is highly unoptimal, do not spawn to start fusion, only good for testing low instability mixes.
diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
index 156ade34ddab..3b8f327732ca 100644
--- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
+++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
@@ -16,13 +16,13 @@
/obj/machinery/portable_atmospherics/Initialize(mapload)
. = ..()
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
air_contents = new(volume)
air_contents.set_temperature(T20C)
/obj/machinery/portable_atmospherics/Destroy()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
disconnect()
QDEL_NULL(air_contents)
return ..()
diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm
index e3a22c002df9..bc48588c3bc1 100644
--- a/code/modules/atmospherics/machinery/portable/pump.dm
+++ b/code/modules/atmospherics/machinery/portable/pump.dm
@@ -8,8 +8,9 @@
name = "portable air pump"
icon_state = "psiphon:0"
density = TRUE
-
+ ///Is the machine on?
var/on = FALSE
+ ///What direction is the machine pumping (into pump/port or out to the tank/area)?
var/direction = PUMP_OUT
var/obj/machinery/atmospherics/components/binary/pump/pump
@@ -67,7 +68,7 @@
if(prob(10 * severity))
direction = PUMP_OUT
pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve)
. = ..()
@@ -75,7 +76,7 @@
if(close_valve)
if(on)
on = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
else if(on && holding && direction == PUMP_OUT)
investigate_log("[key_name(user)] started a transfer into [holding]. ", INVESTIGATE_ATMOS)
@@ -153,10 +154,10 @@
if(holding)
replace_tank(usr, FALSE)
. = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/portable_atmospherics/pump/CtrlShiftClick(mob/user)
if(!user.canUseTopic(src, BE_CLOSE))
return
on = !on
- update_appearance(UPDATE_ICON)
+ update_appearance()
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index 9611617c10c4..7f8d208ba54f 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -47,7 +47,7 @@
forceMove(get_turf(src.reset))
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
- if(istype(mob_area, /area/ctf))
+ if(istype(mob_area, /area/centcom/ctf))
to_chat(M, span_userdanger("\The [src] has been returned to base!"))
STOP_PROCESSING(SSobj, src)
@@ -71,7 +71,7 @@
user.status_flags &= ~CANPUSH
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
- if(istype(mob_area, /area/ctf))
+ if(istype(mob_area, /area/centcom/ctf))
to_chat(M, span_userdanger("\The [src] has been taken!"))
STOP_PROCESSING(SSobj, src)
..()
@@ -84,7 +84,7 @@
START_PROCESSING(SSobj, src)
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
- if(istype(mob_area, /area/ctf))
+ if(istype(mob_area, /area/centcom/ctf))
to_chat(M, span_userdanger("\The [src] has been dropped!"))
anchored = TRUE
@@ -286,7 +286,7 @@
points++
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
- if(istype(mob_area, /area/ctf))
+ if(istype(mob_area, /area/centcom/ctf))
to_chat(M, "[user.real_name] has captured \the [flag], scoring a point for [team] team! They now have [points]/[points_to_win] points!")
if(points >= points_to_win)
victory()
@@ -294,7 +294,7 @@
/obj/machinery/capture_the_flag/proc/victory()
for(var/mob/living/M as anything in GLOB.mob_list)
var/area/mob_area = get_area(M)
- if(istype(mob_area, /area/ctf))
+ if(istype(mob_area, /area/centcom/ctf))
to_chat(M, "[team] team wins!")
to_chat(M, span_userdanger("Teams have been cleared. Click on the machines to vote to begin another round."))
for(var/obj/item/ctf_flag/W in M)
@@ -684,7 +684,7 @@
icon_state = "dominator-[CTF.team]"
for(var/mob/M in GLOB.player_list)
var/area/mob_area = get_area(M)
- if(istype(mob_area, /area/ctf))
+ if(istype(mob_area, /area/centcom/ctf))
to_chat(M, span_userdanger("[user.real_name] has captured \the [src], claiming it for [CTF.team]! Go take it back!"))
break
diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm
index 8e3cdc28e583..c88f9ba199b6 100644
--- a/code/modules/awaymissions/cordon.dm
+++ b/code/modules/awaymissions/cordon.dm
@@ -9,9 +9,10 @@
opacity = TRUE
density = TRUE
blocks_air = TRUE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ space_lit = TRUE
bullet_bounce_sound = null
- flags_1 = CAN_BE_DIRTY_1 | NO_RUST
+ turf_flags = NOJAUNT
+ baseturfs = /turf/cordon
/turf/cordon/AfterChange()
. = ..()
@@ -34,16 +35,24 @@
return src // :devilcat:
/turf/cordon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
+ SHOULD_CALL_PARENT(FALSE) // Fuck you
return BULLET_ACT_HIT
/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"
icon_state = "cordon"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
unique = TRUE
noteleport = TRUE
hidden = TRUE
diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm
index 5627157c0fb5..76a5c6f96f65 100644
--- a/code/modules/awaymissions/corpse.dm
+++ b/code/modules/awaymissions/corpse.dm
@@ -454,6 +454,11 @@
return
H.dna.add_mutation(STONER)
+
+/obj/effect/mob_spawn/human/corpse/felinid
+ name = "Felinid"
+ mob_species = /datum/species/human/felinid
+
/obj/effect/mob_spawn/human/fishing/alive
death = FALSE
roundstart = FALSE
@@ -481,6 +486,7 @@
shoes = /obj/item/clothing/shoes/fishing
id = /obj/item/card/id
+
/////////////////Officers+Nanotrasen Security//////////////////////
/obj/effect/mob_spawn/human/bridgeofficer
diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm
index 4cd2f39cf900..b44e0f8fc2d4 100644
--- a/code/modules/awaymissions/mission_code/Academy.dm
+++ b/code/modules/awaymissions/mission_code/Academy.dm
@@ -417,9 +417,13 @@
AddElement(/datum/element/update_icon_blocker)
return ..()
-/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user)
- user.visible_message("[user] activates \the [src].",span_notice("You activate \the [src]."))
+/obj/structure/ladder/unbreakable/rune/show_initial_fluff_message(mob/user, going_up)
+ user.balloon_alert_to_viewers("activating...")
-/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE)
- if(is_ghost || !(user.mind in SSticker.mode.wizards))
+/obj/structure/ladder/unbreakable/rune/show_final_fluff_message(mob/user, going_up)
+ visible_message(span_notice("[user] activates [src] and teleports away."))
+ user.balloon_alert_to_viewers("warped in")
+
+/obj/structure/ladder/unbreakable/rune/use(mob/user, going_up = TRUE)
+ if(!IS_WIZARD(user))
..()
diff --git a/code/modules/awaymissions/mission_code/Cabin.dm b/code/modules/awaymissions/mission_code/Cabin.dm
index e82ec6d3cad8..cb9ad02c5804 100644
--- a/code/modules/awaymissions/mission_code/Cabin.dm
+++ b/code/modules/awaymissions/mission_code/Cabin.dm
@@ -1,16 +1,16 @@
/*Cabin areas*/
-/area/awaymission/snowforest
- name = "Snow Forest"
- icon_state = "away"
- requires_power = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
-
/area/awaymission/cabin
name = "Cabin"
icon_state = "away2"
requires_power = TRUE
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
+
+/area/awaymission/snowforest
+ name = "Snow Forest"
+ icon_state = "away"
+ requires_power = FALSE
+ static_lighting = TRUE
/area/awaymission/snowforest/lumbermill
name = "Lumbermill"
diff --git a/code/modules/awaymissions/mission_code/caves.dm b/code/modules/awaymissions/mission_code/caves.dm
index 13b526fddb5d..be6f8f17a28b 100644
--- a/code/modules/awaymissions/mission_code/caves.dm
+++ b/code/modules/awaymissions/mission_code/caves.dm
@@ -19,7 +19,7 @@
/area/awaymission/caves/research
name = "Research Outpost"
icon_state = "awaycontent5"
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
/area/awaymission/caves/northblock //engineering, bridge (not really north but it doesnt really need its own APC)
diff --git a/code/modules/awaymissions/mission_code/mining.dm b/code/modules/awaymissions/mission_code/mining.dm
index 2be2b71b6922..84b58c6c9feb 100644
--- a/code/modules/awaymissions/mission_code/mining.dm
+++ b/code/modules/awaymissions/mission_code/mining.dm
@@ -207,7 +207,7 @@
/datum/component/spawner/megafauna/try_spawn_mob()
STOP_PROCESSING(SSprocessing, src)
- if(spawned_mobs.len < max_mobs && initial_spawned)
+ if(spawned_things.len < max_spawned && initial_spawned)
var/obj/structure/spawner/megafauna/MS = parent
MS.cleanup_arena()
spawn_delay = world.time + spawn_wait_time
diff --git a/code/modules/awaymissions/mission_code/research.dm b/code/modules/awaymissions/mission_code/research.dm
index 666ee7909e16..a075818ca80b 100644
--- a/code/modules/awaymissions/mission_code/research.dm
+++ b/code/modules/awaymissions/mission_code/research.dm
@@ -3,7 +3,7 @@
/area/awaymission/research
name = "Research Outpost"
icon_state = "away"
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
/area/awaymission/research/interior
name = "Research Inside"
diff --git a/code/modules/awaymissions/mission_code/snowdin.dm b/code/modules/awaymissions/mission_code/snowdin.dm
index 9a58051c044c..c3e7bcd3f29e 100644
--- a/code/modules/awaymissions/mission_code/snowdin.dm
+++ b/code/modules/awaymissions/mission_code/snowdin.dm
@@ -4,7 +4,8 @@
name = "Snowdin"
icon_state = "awaycontent1"
requires_power = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
/area/awaymission/snowdin/outside
name = "Snowdin Tundra Plains"
@@ -14,7 +15,8 @@
name = "Snowdin Outpost"
icon_state = "awaycontent2"
requires_power = TRUE
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
+ base_lighting_alpha = 0
/area/awaymission/snowdin/post/medbay
name = "Snowdin Outpost - Medbay"
@@ -96,12 +98,14 @@
/area/awaymission/snowdin/igloo
name = "Snowdin Igloos"
icon_state = "awaycontent14"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
+ base_lighting_alpha = 0
/area/awaymission/snowdin/cave
name = "Snowdin Caves"
icon_state = "awaycontent15"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
+ base_lighting_alpha = 0
/area/awaymission/snowdin/cave/cavern
name = "Snowdin Depths"
@@ -115,18 +119,21 @@
/area/awaymission/snowdin/base
name = "Snowdin Main Base"
icon_state = "awaycontent16"
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
+ base_lighting_alpha = 0
requires_power = TRUE
/area/awaymission/snowdin/dungeon1
name = "Snowdin Depths"
icon_state = "awaycontent17"
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
+ base_lighting_alpha = 0
/area/awaymission/snowdin/sekret
name = "Snowdin Operations"
icon_state = "awaycontent18"
- dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ static_lighting = TRUE
+ base_lighting_alpha = 0
requires_power = TRUE
/area/shuttle/snowdin/elevator1
@@ -158,7 +165,7 @@
name = "liquid plasma"
desc = "A flowing stream of chilled liquid plasma. You probably shouldn't get in."
icon_state = "liquidplasma"
- initial_gas_mix = "o2=0;n2=82;plasma=24;TEMP=120"
+ initial_gas_mix = BURNING_COLD
baseturfs = /turf/open/lava/plasma
slowdown = 2
diff --git a/code/modules/awaymissions/mission_code/vrhub.dm b/code/modules/awaymissions/mission_code/vrhub.dm
index a69bf9ba0f99..96df1655a78e 100644
--- a/code/modules/awaymissions/mission_code/vrhub.dm
+++ b/code/modules/awaymissions/mission_code/vrhub.dm
@@ -5,7 +5,8 @@
/area/awaymission/vr/hub
name = "Virtual Reality Hub Area"
icon_state = "awaycontent2"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = FALSE
+ base_lighting_alpha = 255
/area/awaymission/vr/hub/boxing
name = "Virtual Reality Boxing Ring"
@@ -22,22 +23,23 @@
desc = "Gives you a one time ability to return to this portal once you have entered."
mech_sized = TRUE
keep = TRUE
+ density = FALSE
var/datum/outfit/equipment // optional outfit to equip upon entering
var/datum/outfit/recall_equipment // optional outfit to equip upon recalling
-/obj/effect/portal/permanent/one_way/recall/Crossed(atom/movable/AM, oldloc)
- if(ismob(AM))
- var/mob/user = AM
- var/check = locate(/datum/action/cooldown/spell/portal_recall) in user.actions
- if(check)
- var/datum/action/cooldown/spell/portal_recall/mob_recall = check
- for(var/obj/effect/portal/permanent/one_way/recall/P in mob_recall.recall_portals)
- if(src == P)
- return ..(AM, oldloc, force_stop = TRUE) // don't teleport if they have a recall spell with this portal already (or have just teleported onto it)
- return ..()
+/obj/effect/portal/permanent/one_way/recall/Entered(atom/movable/entering_atom, oldloc)
+ if(!ismob(entering_atom))
+ return
+ var/mob/user = entering_atom
+ var/check = locate(/datum/action/cooldown/spell/portal_recall) in user.actions
+ if(check)
+ var/datum/action/cooldown/spell/portal_recall/mob_recall = check
+ for(var/obj/effect/portal/permanent/one_way/recall/P in mob_recall.recall_portals)
+ if(src == P)
+ return // don't teleport if they have a recall spell with this portal already (or have just teleported onto it)
+ Bumped(user)
/obj/effect/portal/permanent/one_way/recall/teleport(atom/movable/M, force = FALSE)
- . = ..()
if(. && ismob(M))
var/mob/user = M
var/findspell = locate(/datum/action/cooldown/spell/portal_recall) in user.actions
@@ -49,6 +51,7 @@
var/mob/living/carbon/human/H = user
H.delete_equipment()
H.equipOutfit(equipment)
+ . = ..()
// the effect that happens when someone recalls to your portal
/obj/effect/portal/permanent/one_way/recall/proc/recall_effect(mob/user)
@@ -149,11 +152,13 @@
/obj/effect/light_emitter/vr_hub
set_luminosity = 9
set_cap = 2.5
- light_color = LIGHT_COLOR_WHITE
/turf/closed/indestructible/iron
name = "rough metal wall"
desc = "A wall with rough metal plating."
icon = 'icons/turf/walls/iron_wall.dmi'
- icon_state = "iron"
- canSmoothWith = list(/turf/closed/indestructible/iron)
+ icon_state = "iron_wall-0"
+ base_icon_state = "iron_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_IRON_WALLS
diff --git a/code/modules/awaymissions/super_secret_room.dm b/code/modules/awaymissions/super_secret_room.dm
index f6e18b6ec5b5..7c8af5490bb9 100644
--- a/code/modules/awaymissions/super_secret_room.dm
+++ b/code/modules/awaymissions/super_secret_room.dm
@@ -4,7 +4,8 @@
verb_say = "intones"
icon = 'icons/obj/structures.dmi'
icon_state = "speaking_tile"
- layer = 5
+ layer = FLY_LAYER
+ plane = ABOVE_GAME_PLANE
resistance_flags = INDESTRUCTIBLE
var/speaking = FALSE
var/times_spoken_to = 0
diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm
index fde8a87c5f97..75eb7ca23d84 100644
--- a/code/modules/awaymissions/zlevel.dm
+++ b/code/modules/awaymissions/zlevel.dm
@@ -1,6 +1,6 @@
// How much "space" we give the edge of the map
-GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt"))
-GLOBAL_LIST_INIT(potentialConfigRandomZlevels, generateConfigMapList(directory = "[global.config.directory]/away_missions/"))
+GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "awaymissionconfig.txt"))
+GLOBAL_LIST_INIT(potentialConfigRandomZlevels, generate_map_list_from_directory(directory = "[global.config.directory]/away_missions/"))
/proc/createRandomZlevel(config_gateway = FALSE)
var/map
@@ -39,6 +39,7 @@ GLOBAL_LIST_INIT(potentialConfigRandomZlevels, generateConfigMapList(directory =
/proc/generateMapList(filename)
. = list()
+ filename = "[global.config.directory]/[SANITIZE_FILENAME(filename)]"
var/list/Lines = world.file2list(filename)
if(!Lines.len)
@@ -67,7 +68,8 @@ GLOBAL_LIST_INIT(potentialConfigRandomZlevels, generateConfigMapList(directory =
. += t
-/proc/generateConfigMapList(directory)
+/// Returns a list of all maps to be found in the directory that is passed in.
+/proc/generate_map_list_from_directory(directory)
var/list/config_maps = list()
var/list/maps = flist(directory)
for(var/map_file in maps)
diff --git a/code/modules/balloon_alert/balloon_alert.dm b/code/modules/balloon_alert/balloon_alert.dm
index 45c78f0f466d..89400e63de99 100644
--- a/code/modules/balloon_alert/balloon_alert.dm
+++ b/code/modules/balloon_alert/balloon_alert.dm
@@ -43,6 +43,7 @@
// I would've made the maptext_height update on its own, but I don't know
// if this would look bad on laggy clients.
/atom/proc/balloon_alert_perform(mob/viewer, text)
+
var/client/viewer_client = viewer.client
if (isnull(viewer_client))
return
@@ -53,26 +54,22 @@
bound_width = movable_source.bound_width
var/image/balloon_alert = image(loc = isturf(src) ? src : get_atom_on_turf(src), layer = ABOVE_MOB_LAYER)
- balloon_alert.plane = BALLOON_CHAT_PLANE
+ SET_PLANE_EXPLICIT(balloon_alert, BALLOON_CHAT_PLANE, src)
balloon_alert.alpha = 0
balloon_alert.appearance_flags = RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM
balloon_alert.maptext = MAPTEXT("[text]")
balloon_alert.maptext_x = (BALLOON_TEXT_WIDTH - bound_width) * -0.5
- balloon_alert.maptext_height = WXH_TO_HEIGHT(viewer_client?.MeasureText(text, null, BALLOON_TEXT_WIDTH))
+ WXH_TO_HEIGHT(viewer_client?.MeasureText(text, null, BALLOON_TEXT_WIDTH), balloon_alert.maptext_height)
balloon_alert.maptext_width = BALLOON_TEXT_WIDTH
viewer_client?.images += balloon_alert
- var/duration_mult = 1
- var/duration_length = length(text) - BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN
-
- if(duration_length > 0)
- duration_mult += duration_length*BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT
+ var/length_mult = 1 + max(0, length(strip_html_full(text)) - BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN) * BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT
animate(
balloon_alert,
pixel_y = world.icon_size * 1.2,
- time = BALLOON_TEXT_TOTAL_LIFETIME(1),
+ time = BALLOON_TEXT_TOTAL_LIFETIME(length_mult),
easing = SINE_EASING | EASE_OUT,
)
@@ -85,11 +82,19 @@
animate(
alpha = 0,
- time = BALLOON_TEXT_FULLY_VISIBLE_TIME*duration_mult,
+ time = BALLOON_TEXT_FULLY_VISIBLE_TIME*length_mult,
easing = CUBIC_EASING | EASE_IN,
)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_image_from_client), balloon_alert, viewer_client), BALLOON_TEXT_TOTAL_LIFETIME(duration_mult))
+ LAZYADD(update_on_z, balloon_alert)
+ // These two timers are not the same
+ // One manages the relation to the atom that spawned us, the other to the client we're displaying to
+ // We could lose our loc, and still need to talk to our client, so they are done seperately
+ addtimer(CALLBACK(balloon_alert.loc, PROC_REF(forget_balloon_alert), balloon_alert), BALLOON_TEXT_TOTAL_LIFETIME(length_mult))
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_image_from_client), balloon_alert, viewer_client), BALLOON_TEXT_TOTAL_LIFETIME(length_mult))
+
+/atom/proc/forget_balloon_alert(image/balloon_alert)
+ LAZYREMOVE(update_on_z, balloon_alert)
#undef BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MIN
#undef BALLOON_TEXT_CHAR_LIFETIME_INCREASE_MULT
diff --git a/code/modules/buildmode/buttons.dm b/code/modules/buildmode/buttons.dm
index 8468962b9c38..217a583cf147 100644
--- a/code/modules/buildmode/buttons.dm
+++ b/code/modules/buildmode/buttons.dm
@@ -2,7 +2,8 @@
icon = 'icons/misc/buildmode.dmi'
var/datum/buildmode/bd
// If we don't do this, we get occluded by item action buttons
- layer = ABOVE_HUD_LAYER
+ plane = ABOVE_HUD_PLANE
+
/atom/movable/screen/buildmode/New(bld)
bd = bld
diff --git a/code/modules/buildmode/submodes/basic.dm b/code/modules/buildmode/submodes/basic.dm
index a52b4642490c..d6aaa08685f2 100644
--- a/code/modules/buildmode/submodes/basic.dm
+++ b/code/modules/buildmode/submodes/basic.dm
@@ -22,13 +22,13 @@
if(istype(object,/turf) && left_click && !alt_click && !ctrl_click)
var/turf/T = object
if(isspaceturf(object) || ischasm(object) || islava(object))
- T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ T.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else if(isplatingturf(object))
- T.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
+ T.place_on_top(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
else if(isfloorturf(object))
- T.PlaceOnTop(/turf/closed/wall)
+ T.place_on_top(/turf/closed/wall)
else if(iswallturf(object))
- T.PlaceOnTop(/turf/closed/wall/r_wall)
+ T.place_on_top(/turf/closed/wall/r_wall)
log_admin("Build Mode: [key_name(c)] built [T] at [AREACOORD(T)]")
return
else if(right_click)
diff --git a/code/modules/buildmode/submodes/fill.dm b/code/modules/buildmode/submodes/fill.dm
index c71f12eab249..04c60d35ef46 100644
--- a/code/modules/buildmode/submodes/fill.dm
+++ b/code/modules/buildmode/submodes/fill.dm
@@ -59,7 +59,7 @@
else
for(var/turf/T in block(get_turf(cornerA),get_turf(cornerB)))
if(ispath(objholder,/turf))
- T.PlaceOnTop(objholder)
+ T.place_on_top(objholder)
else
var/obj/A = new objholder(T)
A.setDir(BM.build_dir)
diff --git a/code/modules/cargo/bounties/special.dm b/code/modules/cargo/bounties/special.dm
index 0072cb11f629..fce400e49f20 100644
--- a/code/modules/cargo/bounties/special.dm
+++ b/code/modules/cargo/bounties/special.dm
@@ -3,7 +3,7 @@
description = "Nanotrasen is interested in studying Xenomorph biology. Ship a set of organs to be thoroughly compensated."
reward = 25000
required_count = 3
- wanted_types = list(/obj/item/organ/brain/alien, /obj/item/organ/alien, /obj/item/organ/body_egg/alien_embryo, /obj/item/organ/liver/alien, /obj/item/organ/tongue/alien, /obj/item/organ/eyes/night_vision/alien)
+ wanted_types = list(/obj/item/organ/brain/alien, /obj/item/organ/alien, /obj/item/organ/body_egg/alien_embryo, /obj/item/organ/liver/alien, /obj/item/organ/tongue/alien, /obj/item/organ/eyes/alien)
/datum/bounty/item/syndicate_documents
name = "Syndicate Documents"
diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm
index a55fd9d6da86..193a5225a7d8 100644
--- a/code/modules/cargo/console.dm
+++ b/code/modules/cargo/console.dm
@@ -16,6 +16,7 @@
or homing beacons. Additionally, remove any privately ordered crates from the shuttle."
var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible."
var/self_paid = FALSE
+ req_access = list(ACCESS_CARGO) //dripstation edit
/obj/machinery/computer/cargo/request
name = "supply request console"
@@ -25,6 +26,7 @@
requestonly = TRUE
can_send = FALSE
can_approve_requests = FALSE
+ req_access = list() //dripstation edit
/obj/machinery/computer/cargo/Initialize(mapload)
. = ..()
@@ -50,6 +52,7 @@
obj_flags |= EMAGGED
contraband = TRUE
+ do_sparks(8, FALSE, loc) //dripstation edit
// This also permamently sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
@@ -80,11 +83,11 @@
var/message = "Remember to stamp and send back the supply manifests."
if(SSshuttle.centcom_message)
message = SSshuttle.centcom_message
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
message = blockade_warning
data["message"] = message
data["cart"] = list()
- for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ for(var/datum/supply_order/SO in SSshuttle.shopping_list)
data["cart"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.get_cost(),
@@ -95,7 +98,7 @@
))
data["requests"] = list()
- for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ for(var/datum/supply_order/SO in SSshuttle.request_list)
data["requests"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.get_cost(),
@@ -133,12 +136,15 @@
/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui)
if(..())
return
+ if(!allowed(usr) && can_approve_requests) //dripstation edit
+ say("Access denied.") //dripstation edit
+ return //dripstation edit
switch(action)
if("send")
if(!SSshuttle.supply.canMove())
say(safety_warning)
return
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
say(blockade_warning)
return
if(SSshuttle.supply.getDockedId() == "supply_home")
@@ -148,13 +154,13 @@
investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO)
else
investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO)
- say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.")
+ say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(10)] seconds.") //dripstation edit
SSshuttle.moveShuttle("supply", "supply_home", TRUE)
. = TRUE
if("loan")
if(!SSshuttle.shuttle_loan)
return
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
say(blockade_warning)
return
else if(SSshuttle.supply.mode != SHUTTLE_IDLE)
@@ -171,6 +177,12 @@
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
return
+ if(pack.times_ordered >= pack.order_limit && pack.order_limit != -1) //If the crate has reached the limit, do not allow it to be ordered.
+ say("[pack.name] is out of stock and can no longer be ordered.")
+ return
+ if(pack.times_ordered_in_one_order >= pack.order_limit_in_one_order && pack.order_limit_in_one_order != -1)
+ say("[pack.name] is out of stock for now and can no longer be ordered in this package. Try again later.")
+ return
if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly)
return
@@ -207,39 +219,54 @@
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account)
SO.generateRequisition(T)
if(requestonly && !self_paid)
- SSshuttle.requestlist += SO
+ SSshuttle.request_list += SO
else
- SSshuttle.shoppinglist += SO
+ SSshuttle.shopping_list += SO
+ SO.pack.times_ordered += 1 //dripstation edit
+ SO.pack.times_ordered_in_one_order += 1 //dripstation edit
if(self_paid)
say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.")
. = TRUE
if("remove")
var/id = text2num(params["id"])
- for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ for(var/datum/supply_order/SO in SSshuttle.shopping_list)
if(SO.id == id)
- SSshuttle.shoppinglist -= SO
+ SSshuttle.shopping_list -= SO
+ SO.pack.times_ordered -= 1 //dripstation edit
+ SO.pack.times_ordered_in_one_order -= 1 //dripstation edit
. = TRUE
break
if("clear")
- SSshuttle.shoppinglist.Cut()
+ for(var/datum/supply_order/SO in SSshuttle.shopping_list) //dripstation edit
+ SO.pack.times_ordered -= 1 //dripstation edit
+ SO.pack.times_ordered_in_one_order = 0 //dripstation edit
+ SSshuttle.shopping_list.Cut()
. = TRUE
if("approve")
var/id = text2num(params["id"])
- for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ for(var/datum/supply_order/SO in SSshuttle.request_list)
if(SO.id == id)
- SSshuttle.requestlist -= SO
- SSshuttle.shoppinglist += SO
+ if(SO.pack.times_ordered >= SO.pack.order_limit && SO.pack.order_limit != -1) //If the crate has reached the limit, do not allow it to be ordered. dripstation edit start
+ say("[SO.pack.name] is out of stock and can no longer be ordered.")
+ return
+ if(SO.pack.times_ordered_in_one_order >= SO.pack.order_limit_in_one_order && SO.pack.order_limit_in_one_order != -1)
+ say("[SO.pack.name] is out of stock for now and can no longer be ordered in this package. Try again later.")
+ return //dripstation edit end
+ SSshuttle.request_list -= SO
+ SSshuttle.shopping_list += SO
+ SO.pack.times_ordered += 1 //dripstation edit
+ SO.pack.times_ordered_in_one_order += 1 //dripstation edit
. = TRUE
break
if("deny")
var/id = text2num(params["id"])
- for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ for(var/datum/supply_order/SO in SSshuttle.request_list)
if(SO.id == id)
- SSshuttle.requestlist -= SO
+ SSshuttle.request_list -= SO
. = TRUE
break
if("denyall")
- SSshuttle.requestlist.Cut()
+ SSshuttle.request_list.Cut()
. = TRUE
if("toggleprivate")
self_paid = !self_paid
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index ca3636f094a0..391945664105 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -109,7 +109,7 @@
data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons
data["supplies"] = list()
message = "Sales are near-instantaneous - please choose carefully."
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
message = blockade_warning
if(usingBeacon && !beacon)
message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed
diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm
index adc2988f5594..9c0d4f1b5fba 100644
--- a/code/modules/cargo/gondolapod.dm
+++ b/code/modules/cargo/gondolapod.dm
@@ -61,12 +61,13 @@
/mob/living/simple_animal/pet/gondola/gondolapod/setOpened()
opened = TRUE
- update_appearance(UPDATE_ICON)
+ layer = initial(layer)
+ update_appearance()
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, setClosed)), 50)
/mob/living/simple_animal/pet/gondola/gondolapod/setClosed()
opened = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/mob/living/simple_animal/pet/gondola/gondolapod/death()
qdel(linked_pod) //Will cause the open() proc for the linked supplypod to be called with the "broken" parameter set to true, meaning that it will dump its contents on death
diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm
index 6afc883649fe..9d99817b7c6b 100644
--- a/code/modules/cargo/order.dm
+++ b/code/modules/cargo/order.dm
@@ -32,7 +32,7 @@
var/datum/bank_account/paying_account
/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account, budget)
- id = SSshuttle.ordernum++
+ id = SSshuttle.order_number++
src.pack = pack
src.orderer = orderer
src.orderer_rank = orderer_rank
@@ -108,7 +108,7 @@
if(paying_account)
account_holder = paying_account.account_holder
else
- account_holder = "Cargo"
+ account_holder = "Cargo Budget" //dripstation edit
var/obj/structure/closet/crate/C = pack.generate(A, paying_account)
generateManifest(C, account_holder, pack)
return C
diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm
index e6c4eb8ed399..85ec5c3fab54 100644
--- a/code/modules/cargo/packs.dm
+++ b/code/modules/cargo/packs.dm
@@ -317,7 +317,7 @@
/obj/item/toy/crayon/white,
/obj/item/clothing/head/fedora/det_hat)
crate_name = "forensics crate"
-
+/*
/datum/supply_pack/security/laser
name = "Lasers Crate"
desc = "Contains three lethal, high-energy laser guns. Requires Security access to open."
@@ -326,7 +326,7 @@
/obj/item/gun/energy/laser,
/obj/item/gun/energy/laser)
crate_name = "laser crate"
-
+*/
/datum/supply_pack/security/secfiringpins
name = "Mindshield Firing Pins Crate"
desc = "Upgrade your arsenal with 10 mindshield firing pins. Requires Security access to open."
@@ -1947,16 +1947,16 @@
name = "Exotic Carpet Crate"
desc = "Exotic carpets straight from Space Russia, for all your decorating needs. Contains 100 tiles each of 8 different flooring patterns."
cost = 4000
- contains = list(/obj/item/stack/tile/carpet/exoticblue/fifty,
- /obj/item/stack/tile/carpet/exoticblue/fifty,
+ contains = list(/obj/item/stack/tile/carpet/blue/fifty,
+ /obj/item/stack/tile/carpet/blue/fifty,
/obj/item/stack/tile/carpet/cyan/fifty,
/obj/item/stack/tile/carpet/cyan/fifty,
- /obj/item/stack/tile/carpet/exoticgreen/fifty,
- /obj/item/stack/tile/carpet/exoticgreen/fifty,
+ /obj/item/stack/tile/carpet/green/fifty,
+ /obj/item/stack/tile/carpet/green/fifty,
/obj/item/stack/tile/carpet/orange/fifty,
/obj/item/stack/tile/carpet/orange/fifty,
- /obj/item/stack/tile/carpet/exoticpurple/fifty,
- /obj/item/stack/tile/carpet/exoticpurple/fifty,
+ /obj/item/stack/tile/carpet/purple/fifty,
+ /obj/item/stack/tile/carpet/purple/fifty,
/obj/item/stack/tile/carpet/red/fifty,
/obj/item/stack/tile/carpet/red/fifty,
/obj/item/stack/tile/carpet/royalblue/fifty,
@@ -2056,7 +2056,7 @@
/datum/supply_pack/service/syrup
name = "Coffee Syrups Box"
desc = "A packaged box of various syrups, perfect for making your delicious coffee even more diabetic."
- cost = 200
+ cost = 1400
contains = list(
/obj/item/reagent_containers/food/drinks/bottle/syrup_bottle/caramel,
/obj/item/reagent_containers/food/drinks/bottle/syrup_bottle/liqueur,
diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm
index 388c64229bac..30ddf37ea894 100644
--- a/code/modules/cargo/supplypod.dm
+++ b/code/modules/cargo/supplypod.dm
@@ -289,7 +289,7 @@
if (openingSound)
playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play
for (var/turf_type in turfs_in_cargo)
- turf_underneath.PlaceOnTop(turf_type)
+ turf_underneath.place_on_top(turf_type)
for (var/cargo in contents)
var/atom/movable/movable_cargo = cargo
movable_cargo.forceMove(turf_underneath)
@@ -371,14 +371,10 @@
return FALSE
if(istype(obj_to_insert, /obj/effect/supplypod_rubble))
return FALSE
- if((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && reverse_option_list["Underfloor"])
- return TRUE
- else if ((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && !reverse_option_list["Underfloor"])
- return FALSE
- if(isProbablyWallMounted(obj_to_insert) && reverse_option_list["Wallmounted"])
- return TRUE
- else if (isProbablyWallMounted(obj_to_insert) && !reverse_option_list["Wallmounted"])
- return FALSE
+ if(HAS_TRAIT(obj_to_insert, TRAIT_UNDERFLOOR))
+ return !!reverse_option_list["Underfloor"]
+ if(isProbablyWallMounted(obj_to_insert))
+ return !!reverse_option_list["Wallmounted"]
if(!obj_to_insert.anchored && reverse_option_list["Unanchored"])
return TRUE
if(obj_to_insert.anchored && !ismecha(obj_to_insert) && reverse_option_list["Anchored"]) //Mecha are anchored but there is a separate option for them
@@ -434,7 +430,7 @@
rubble.setStyle(rubble_type, src)
update_appearance(UPDATE_ICON)
-/obj/structure/closet/supplypod/Moved()
+/obj/structure/closet/supplypod/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
deleteRubble()
return ..()
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 9f876156b699..a3d7db2478b3 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -173,3 +173,32 @@
/// Whether or not this client has the combo HUD enabled
var/combo_hud_enabled = FALSE
+
+ var/list/parallax_layers
+ var/list/parallax_layers_cached
+ ///Tracks say() usage for ic/dchat while slowmode is enabled
+ COOLDOWN_DECLARE(say_slowmode)
+
+ ///this is the last recorded client eye by SSparallax/fire()
+ var/atom/movable/movingmob
+ var/turf/previous_turf
+ ///world.time of when we can state animate()ing parallax again
+ var/dont_animate_parallax
+ /// Direction our current area wants to move parallax
+ var/parallax_movedir = 0
+ /// How many parallax layers to show our client
+ var/parallax_layers_max = 4
+ /// Timer for the area directional animation
+ var/parallax_animate_timer
+ /// Do we want to do parallax animations at all?
+ /// Exists to prevent laptop fires
+ var/do_parallax_animations = TRUE
+ var/parallax_throttle = 0 //ds between updates
+ var/last_parallax_shift //world.time of last update
+ /**
+ * Assoc list with all the active maps - when a screen obj is added to
+ * a map, it's put in here as well.
+ *
+ * Format: list( = list(/atom/movable/screen))
+ */
+ var/list/screen_maps = list()
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 31adecb27a05..6871680cf672 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -225,7 +225,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(copytext(tdata, 1, 3) == "/?")
tdata = copytext(tdata, 3)
-
if(connection != "seeker" && connection != "web")//Invalid connection type.
return null
@@ -233,7 +232,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
GLOB.directory[ckey] = src
// Instantiate tgui panel
- tgui_panel = new(src)
+ tgui_panel = new(src, "browseroutput")
//tgui_panel.send_connected()
@@ -391,7 +390,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(holder)
add_admin_verbs()
- to_chat(src, get_message_output("memo"))
+ var/memos = get_message_output("memo")
+ if(memos)
+ to_chat(src, memos)
adminGreet()
add_verbs_from_config()
@@ -438,7 +439,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(CONFIG_GET(flag/autoconvert_notes))
convert_notes_sql(ckey)
- to_chat(src, get_message_output("message", ckey))
+ var/admin_messages = get_message_output("message", ckey)
+ if(admin_messages)
+ to_chat(src, admin_messages)
if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
to_chat(src, span_warning("Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you."))
@@ -482,52 +485,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
//////////////
/client/Del()
- //if(credits)
- //QDEL_LIST(credits)
- log_access("Logout: [key_name(src)]")
- if(holder)
- adminGreet(1)
- holder.owner = null
- GLOB.permissions.admins -= src
- if (!GLOB.permissions.admins.len && SSticker.IsRoundInProgress()) //Only report this stuff if we are currently playing.
- var/cheesy_message = pick(
- "I have no admins online!",\
- "I'm all alone :(",\
- "I'm feeling lonely :(",\
- "I'm so lonely :(",\
- "Why does nobody love me? :(",\
- "I want a man :(",\
- "Where has everyone gone?",\
- "I need a hug :(",\
- "Someone come hold me :(",\
- "I need someone on me :(",\
- "What happened? Where has everyone gone?",\
- "Forever alone :("\
- )
-
- send2irc("Server", "[cheesy_message] (No admins online)")
- qdel(holder)
- if(ckey in GLOB.permissions.deadmins)
- qdel(GLOB.permissions.deadmins[ckey])
-
- GLOB.ahelp_tickets.ClientLogout(src)
- GLOB.directory -= ckey
- GLOB.clients -= src
-
- SSambience.remove_ambience_client(src)
-
- var/datum/connection_log/CL = GLOB.connection_logs[ckey]
- if(CL)
- CL.logout(mob)
-
- QDEL_LIST_ASSOC_VAL(char_render_holders)
- if(movingmob != null)
- movingmob.client_mobs_in_contents -= mob
- UNSETEMPTY(movingmob.client_mobs_in_contents)
- seen_messages = null
- Master.UpdateTickRate()
- world.sync_logout_with_db(connection_number) // yogs - logout logging
-
+ if(!gc_destroyed)
+ gc_destroyed = world.time
+ if (!QDELING(src))
+ stack_trace("Client does not purport to be QDELING, this is going to cause bugs in other places!")
+
+ // Yes this is the same as what's found in qdel(). Yes it does need to be here
+ // Get off my back
+ SEND_SIGNAL(src, COMSIG_QDELETING, TRUE)
+ Destroy() //Clean up signals and timers.
return ..()
/client/proc/set_client_age_from_db(connectiontopic)
@@ -984,6 +950,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
/client/proc/rescale_view(change, min, max)
view_size.setTo(clamp(change, min, max), clamp(change, min, max))
+/client/proc/set_eye(new_eye)
+ if(new_eye == eye)
+ return
+ var/atom/old_eye = eye
+ eye = new_eye
+ SEND_SIGNAL(src, COMSIG_CLIENT_SET_EYE, old_eye, new_eye)
/**
* Updates the keybinds for special keys
*
@@ -1009,6 +981,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
movement_keys[key] = WEST
if("South")
movement_keys[key] = SOUTH
+/* Dripstation edit - cyrillic support
if(SAY_CHANNEL)
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=.say")
if(ME_CHANNEL)
@@ -1017,12 +990,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=ooc")
if(LOOC_CHANNEL)
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=looc")
+*/
if(ASAY_CHANNEL)
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=asay")
+/* Dripstation edit - cyrillic support
if(MSAY_CHANNEL)
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=msay")
if(DONORSAY_CHANNEL)
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=.donorsay")
+*/
if(DEADSAY_CHANNEL)
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=dsay")
@@ -1031,16 +1007,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
CRASH("change_view called without argument.")
view = new_size
+ SEND_SIGNAL(src, COMSIG_VIEW_SET, new_size)
apply_clickcatcher()
mob.reload_fullscreen()
- if(mob && istype(mob.hud_used, /datum/hud/ai))
- if(new_size == CONFIG_GET(string/default_view) || new_size == CONFIG_GET(string/default_view_square))
- QDEL_NULL(mob.hud_used)
- mob.create_mob_hud()
- mob.hud_used.show_hud(mob.hud_used.hud_version)
- mob.hud_used.update_ui_style(ui_style2icon(prefs.read_preference(/datum/preference/choiced/ui_style)))
-
if (isliving(mob))
var/mob/living/M = mob
M.update_damage_hud()
@@ -1112,7 +1082,22 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
else
SSambience.ambience_listening_clients -= src
+/client/proc/open_filter_editor(atom/in_atom)
+ if(holder)
+ holder.filteriffic = new /datum/filter_editor(in_atom)
+ holder.filteriffic.ui_interact(mob)
+
/client/proc/open_particle_editor(atom/in_atom)
if(holder)
holder.particool = new /datum/particle_editor(in_atom)
holder.particool.ui_interact(mob)
+
+/// Clears the client's screen, aside from ones that opt out
+/client/proc/clear_screen()
+ for (var/object in screen)
+ if (istype(object, /atom/movable/screen))
+ var/atom/movable/screen/screen_object = object
+ if (!screen_object.clear_with_screen)
+ continue
+
+ screen -= object
diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm
index 5184738932b2..c7c8fe025771 100644
--- a/code/modules/client/preferences/_preference.dm
+++ b/code/modules/client/preferences/_preference.dm
@@ -240,6 +240,8 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key())
// things running pre-assets-initialization.
if (!isnull(Master.current_initializing_subsystem))
extra_info = "Info was attempted to be retrieved while [Master.current_initializing_subsystem] was initializing."
+ else if (!MC_RUNNING())
+ extra_info = "Info was attempted to be retrieved before the MC started, but not while it was actively initializing a subsystem"
CRASH("Preference type `[preference_type]` is invalid! [extra_info]")
diff --git a/code/modules/client/preferences/ambient_occlusion.dm b/code/modules/client/preferences/ambient_occlusion.dm
index a81efca00bd8..cf8bf28c1e56 100644
--- a/code/modules/client/preferences/ambient_occlusion.dm
+++ b/code/modules/client/preferences/ambient_occlusion.dm
@@ -5,8 +5,6 @@
savefile_identifier = PREFERENCE_PLAYER
/datum/preference/toggle/ambient_occlusion/apply_to_client(client/client, value)
- var/atom/movable/screen/plane_master/game_world/plane_master = locate() in client?.screen
- if (!plane_master)
- return
-
- plane_master.backdrop(client.mob)
+ /// Backdrop for the game world plane.
+ for(var/atom/movable/screen/plane_master/plane_master as anything in client.mob?.hud_used?.get_true_plane_masters(GAME_PLANE))
+ plane_master.show_to(client.mob)
diff --git a/code/modules/client/preferences/clothing.dm b/code/modules/client/preferences/clothing.dm
index 4b85cedd1f63..44e72c844861 100644
--- a/code/modules/client/preferences/clothing.dm
+++ b/code/modules/client/preferences/clothing.dm
@@ -87,6 +87,14 @@
/datum/preference/choiced/socks/apply_to_human(mob/living/carbon/human/target, value)
target.socks = value
+/datum/preference/choiced/socks/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ var/species_type = preferences.read_preference(/datum/preference/choiced/species)
+ var/datum/species/species = new species_type
+ return !(NO_UNDERWEAR in species.species_traits)
+
/// Undershirt preference
/datum/preference/choiced/undershirt
savefile_key = "undershirt"
@@ -122,6 +130,14 @@
/datum/preference/choiced/undershirt/apply_to_human(mob/living/carbon/human/target, value)
target.undershirt = value
+/datum/preference/choiced/undershirt/is_accessible(datum/preferences/preferences)
+ if (!..(preferences))
+ return FALSE
+
+ var/species_type = preferences.read_preference(/datum/preference/choiced/species)
+ var/datum/species/species = new species_type
+ return !(NO_UNDERWEAR in species.species_traits)
+
/// Underwear preference
/datum/preference/choiced/underwear
savefile_key = "underwear"
diff --git a/code/modules/client/preferences/multiz_parallax.dm b/code/modules/client/preferences/multiz_parallax.dm
new file mode 100644
index 000000000000..d4f77e206b2a
--- /dev/null
+++ b/code/modules/client/preferences/multiz_parallax.dm
@@ -0,0 +1,16 @@
+/// Whether or not to toggle multiz parallax, the parallax effect for lower z-levels.
+/datum/preference/toggle/multiz_parallax
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "multiz_parallax"
+ savefile_identifier = PREFERENCE_PLAYER
+
+/datum/preference/toggle/multiz_parallax/apply_to_client(client/client, value)
+ // Update the plane master group's Z transforms.
+
+ var/datum/hud/my_hud = client.mob?.hud_used
+ if(!my_hud)
+ return
+
+ for(var/group_key as anything in my_hud.master_groups)
+ var/datum/plane_master_group/group = my_hud.master_groups[group_key]
+ group.transform_lower_turfs(my_hud, my_hud.current_plane_offset)
diff --git a/code/modules/client/preferences/multiz_performance.dm b/code/modules/client/preferences/multiz_performance.dm
new file mode 100644
index 000000000000..7591401f2d8d
--- /dev/null
+++ b/code/modules/client/preferences/multiz_performance.dm
@@ -0,0 +1,21 @@
+/// Boundary for how many z levels down to render properly before we start going cheapo mode
+/datum/preference/numeric/multiz_performance
+ category = PREFERENCE_CATEGORY_GAME_PREFERENCES
+ savefile_key = "multiz_performance"
+ savefile_identifier = PREFERENCE_PLAYER
+
+ minimum = MULTIZ_PERFORMANCE_DISABLE
+ maximum = MAX_EXPECTED_Z_DEPTH - 1
+
+/datum/preference/numeric/multiz_performance/create_default_value()
+ return -1
+
+/datum/preference/numeric/multiz_performance/apply_to_client(client/client, value)
+ // Update the plane master group's layering
+ var/datum/hud/my_hud = client.mob?.hud_used
+ if(!my_hud)
+ return
+
+ for(var/group_key as anything in my_hud.master_groups)
+ var/datum/plane_master_group/group = my_hud.master_groups[group_key]
+ group.transform_lower_turfs(my_hud, my_hud.current_plane_offset)
diff --git a/code/modules/client/preferences/species_features/plasmaman.dm b/code/modules/client/preferences/species_features/plasmaman.dm
index 35067fe502a4..a90b89327a13 100644
--- a/code/modules/client/preferences/species_features/plasmaman.dm
+++ b/code/modules/client/preferences/species_features/plasmaman.dm
@@ -10,10 +10,10 @@
var/list/values = list()
for (var/helmet_name as anything in GLOB.plasmaman_helmet_list)
- var/icon/helmet_icon = icon('icons/obj/clothing/hats.dmi', "purple_envirohelm")
+ var/icon/helmet_icon = icon('icons/obj/clothing/hats/hats.dmi', "purple_envirohelm")
var/icon/overlay_to_blend
if (helmet_name != "None")
- overlay_to_blend = icon('icons/obj/clothing/hats.dmi', "enviro[GLOB.plasmaman_helmet_list[helmet_name]]")
+ overlay_to_blend = icon('icons/obj/clothing/hats/hats.dmi', "enviro[GLOB.plasmaman_helmet_list[helmet_name]]")
if(overlay_to_blend)
helmet_icon.Blend(overlay_to_blend, ICON_OVERLAY)
diff --git a/code/modules/client/preferences/species_features/preternis.dm b/code/modules/client/preferences/species_features/preternis.dm
index a5af851c8855..f858e17f86d6 100644
--- a/code/modules/client/preferences/species_features/preternis.dm
+++ b/code/modules/client/preferences/species_features/preternis.dm
@@ -13,7 +13,7 @@
preternis_base.Blend(icon('icons/mob/human_parts_greyscale.dmi', "preternis_l_arm"), ICON_OVERLAY)
preternis_base.Blend(icon('icons/mob/human_parts_greyscale.dmi', "preternis_r_arm"), ICON_OVERLAY)
- var/icon/eyes = icon('icons/mob/human_face.dmi', "eyes")
+ var/icon/eyes = icon('icons/mob/mutant_bodyparts.dmi', "m_preternis_eye_1_ADJ")
eyes.Blend(COLOR_RED, ICON_MULTIPLY)
preternis_base.Blend(eyes, ICON_OVERLAY)
@@ -31,3 +31,43 @@
/datum/preference/choiced/preternis_color/apply_to_human(mob/living/carbon/human/target, value)
target.dna.features["pretcolor"] = GLOB.color_list_preternis[value]
+
+//Weathering
+/datum/preference/choiced/preternis_weathering
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ savefile_key = "feature_preternis_weathering"
+ savefile_identifier = PREFERENCE_CHARACTER
+ relevant_mutant_bodypart = "preternis_weathering"
+
+/datum/preference/choiced/preternis_weathering/init_possible_values()
+ return assoc_to_keys(GLOB.preternis_weathering_list)
+
+/datum/preference/choiced/preternis_weathering/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["preternis_weathering"] = value
+
+//Antenna
+/datum/preference/choiced/preternis_antenna
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ savefile_key = "feature_preternis_antenna"
+ savefile_identifier = PREFERENCE_CHARACTER
+ relevant_mutant_bodypart = "preternis_antenna"
+
+/datum/preference/choiced/preternis_antenna/init_possible_values()
+ return assoc_to_keys(GLOB.preternis_antenna_list)
+
+/datum/preference/choiced/preternis_antenna/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["preternis_antenna"] = value
+
+//Eye
+/datum/preference/choiced/preternis_eye
+ category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
+ savefile_key = "feature_preternis_eye"
+ savefile_identifier = PREFERENCE_CHARACTER
+ relevant_mutant_bodypart = "preternis_eye"
+
+/datum/preference/choiced/preternis_eye/init_possible_values()
+ return assoc_to_keys(GLOB.preternis_eye_list)
+
+/datum/preference/choiced/preternis_eye/apply_to_human(mob/living/carbon/human/target, value)
+ target.dna.features["preternis_eye"] = value
+ target.dna.features["preternis_core"] = "Core" //core is reliant on eye colour and it needs to get added to features somewhere
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index dd07e425d30a..21396361cc4b 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -183,7 +183,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
player_alt_titles = SANITIZE_LIST(player_alt_titles)
- toggles = sanitize_integer(toggles, 0, ~0, initial(toggles)) // Yogs -- Fixes toggles not having >16 bits of flagspace
+ toggles = sanitize_integer(toggles, 0, SHORT_REAL_LIMIT-1, initial(toggles))
key_bindings = sanitize_keybindings(key_bindings)
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 795b1f3d5cee..3914e3abc87e 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -12,7 +12,7 @@
var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv
var/visor_flags_cover = 0 //same as above, but for flags_cover
//what to toggle when toggled with weldingvisortoggle()
- var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW
+ var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_INVISVIEW
lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
var/alt_desc = null
@@ -26,6 +26,8 @@
var/clothing_flags = NONE
+ var/can_be_bloody = TRUE
+
/// What items can be consumed to repair this clothing (must by an /obj/item/stack)
var/repairable_by = /obj/item/stack/sheet/cloth
@@ -61,6 +63,8 @@
. = ..()
if(ispath(pocket_storage_component_path))
LoadComponent(pocket_storage_component_path)
+ if(can_be_bloody && ((body_parts_covered & FEET) || (flags_inv & HIDESHOES)))
+ LoadComponent(/datum/component/bloodysoles)
/obj/item/clothing/MouseDrop(atom/over_object)
. = ..()
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 7c433361c60d..cd98d5465b88 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -10,13 +10,22 @@
resistance_flags = NONE
materials = list(/datum/material/glass = 250)
var/vision_flags = 0
- var/darkness_view = 2//Base human is 2
var/invis_view = SEE_INVISIBLE_LIVING //admin only for now
- var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis
- var/lighting_alpha
- var/list/icon/current = list() //the current hud icons
- var/vision_correction = 0 //does wearing these glasses correct some of our vision defects?
- var/glass_colour_type //colors your vision when worn
+ /// Override to allow glasses to set higher than normal see_invis
+ var/invis_override = 0
+ /// A percentage of how much rgb to "max" on the lighting plane
+ /// This lets us brighten darkness without washing out bright color
+ var/lighting_cutoff = null
+ /// Similar to lighting_cutoff, except it has individual r g and b components in the same 0-100 scale
+ var/list/color_cutoffs = null
+ /// The current hud icons
+ var/list/icon/current = list()
+ /// Colors your vision when worn
+ var/glass_colour_type
+ /// Whether or not vision coloring is forcing
+ var/forced_glass_color = FALSE
+ //does wearing these glasses correct some of our vision defects?
+ var/vision_correction = 0
/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -31,8 +40,6 @@
..()
if(visor_vars_to_toggle & VISOR_VISIONFLAGS)
vision_flags ^= initial(vision_flags)
- if(visor_vars_to_toggle & VISOR_DARKNESSVIEW)
- darkness_view ^= initial(darkness_view)
if(visor_vars_to_toggle & VISOR_INVISVIEW)
invis_view ^= initial(invis_view)
@@ -51,7 +58,7 @@
to_chat(H, span_danger("[src] overloads and blinds you!"))
H.flash_act(visual = 1)
H.blind_eyes(3)
- H.blur_eyes(5)
+ H.adjust_eye_blur(5)
eyes.applyOrganDamage(5)
/obj/item/clothing/glasses/meson
@@ -59,9 +66,9 @@
desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions."
icon_state = "meson"
item_state = "meson"
- darkness_view = 2
vision_flags = SEE_TURFS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Mesons get to be lightly green
+ color_cutoffs = list(5, 15, 5)
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
clothing_traits = list(TRAIT_MESONS)
@@ -74,9 +81,9 @@
desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness."
icon_state = "nvgmeson"
item_state = "nvgmeson"
- darkness_view = 8
flash_protect = -1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Night vision mesons get the same but more intense
+ color_cutoffs = list(10, 30, 10)
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/meson/gar
@@ -137,18 +144,19 @@
desc = "A pair of snazzy goggles used to protect against chemical spills that happen in complete darkness. Fitted with an analyzer for scanning items and reagents."
icon_state = "sciencehudnight"
item_state = "sciencehudnight"
- darkness_view = 8
flash_protect = -1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Real vivid purple
+ color_cutoffs = list(50, 10, 30)
+ glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/night
name = "night vision goggles"
desc = "You can totally see in the dark now!"
icon_state = "night"
item_state = "glasses"
- darkness_view = 8
flash_protect = -1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Dark green
+ color_cutoffs = list(10, 30, 10)
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user)
@@ -164,8 +172,10 @@
/obj/item/clothing/glasses/eyepatch/bigboss
name = "faded eyepatch"
desc = "Offers night vision and protection from flashes. Another mission, right boss?"
- darkness_view = 8
flash_protect = 1
+ // Dark green
+ color_cutoffs = list(10, 30, 10)
+
/obj/item/clothing/glasses/monocle
name = "monocle"
@@ -187,7 +197,6 @@
desc = "Used by miners to detect ores deep within the rock."
icon_state = "material"
item_state = "glasses"
- darkness_view = 0
/obj/item/clothing/glasses/material/mining/gar
name = "gar material scanner"
@@ -234,7 +243,6 @@
desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes."
icon_state = "sun"
item_state = "sunglasses"
- darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/gray
@@ -317,7 +325,6 @@
item_state = "blindfold"
flash_protect = 2
tint = 3
- darkness_view = 1
dog_fashion = /datum/dog_fashion/head
/obj/item/clothing/glasses/blindfold/equipped(mob/living/carbon/human/user, slot)
@@ -371,7 +378,8 @@
icon_state = "thermal"
item_state = "glasses"
vision_flags = SEE_MOBS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Going for an orange color here
+ color_cutoffs = list(25, 8, 5)
flash_protect = -1
glass_colour_type = /datum/client_colour/glass_colour/red
@@ -462,8 +470,8 @@
icon_state = "godeye"
item_state = "godeye"
vision_flags = SEE_TURFS
- darkness_view = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Blue, light blue
+ color_cutoffs = list(15, 30, 40)
resistance_flags = LAVA_PROOF | FIRE_PROOF
clothing_flags = SCAN_REAGENTS
var/datum/action/cooldown/expose/expose_ability
@@ -524,14 +532,13 @@
return ..() && isliving(owner)
/datum/action/cooldown/expose/Activate(atom/exposed)
- StartCooldown(15 SECONDS)
-
if(owner.stat != CONSCIOUS)
return FALSE
if(!isliving(exposed) || exposed == owner)
owner.balloon_alert(owner, "invalid exposed!")
return FALSE
-
+ StartCooldown(15 SECONDS)
+
var/mob/living/living_exposed = exposed
living_exposed.apply_status_effect(STATUS_EFFECT_EXPOSED)
living_exposed.adjust_jitter(5 SECONDS)
diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm
index 23ac10427b4b..07d7a05eda3c 100644
--- a/code/modules/clothing/glasses/engine_goggles.dm
+++ b/code/modules/clothing/glasses/engine_goggles.dm
@@ -19,8 +19,8 @@
actions_types = list(/datum/action/item_action/toggle_mode)
vision_flags = NONE
- darkness_view = 2
invis_view = SEE_INVISIBLE_LIVING
+ color_cutoffs = null
var/list/modes = list(MODE_NONE = MODE_MESON, MODE_MESON = MODE_TRAY, MODE_TRAY = MODE_RAD, MODE_RAD = MODE_NONE)
var/mode = MODE_NONE
@@ -42,14 +42,12 @@
switch(mode)
if(MODE_MESON)
vision_flags = SEE_TURFS
- darkness_view = 1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ color_cutoffs = list(15, 12, 0)
if(MODE_TRAY) //undoes the last mode, meson
vision_flags = NONE
- darkness_view = 2
- lighting_alpha = null
-
+ color_cutoffs = list()
+
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(H.glasses == src)
@@ -99,10 +97,9 @@
var/mutable_appearance/MA = new()
MA.maptext = span_maptext("[strength]k")
MA.color = "#04e604"
- MA.layer = RAD_TEXT_LAYER
MA.plane = GAME_PLANE
pic.appearance = MA
- flick_overlay(pic, list(user.client), 10)
+ flick_overlay_global(pic, list(user.client), 10)
/obj/item/clothing/glasses/meson/engine/proc/show_shuttle()
var/mob/living/carbon/human/user = loc
@@ -119,7 +116,7 @@
pic = new('icons/turf/overlays.dmi', place, "greenOverlay", AREA_LAYER)
else
pic = new('icons/turf/overlays.dmi', place, "redOverlay", AREA_LAYER)
- flick_overlay(pic, list(user.client), 8)
+ flick_overlay_global(pic, list(user.client), 8)
/obj/item/clothing/glasses/meson/engine/update_icon_state()
. = ..()
@@ -187,7 +184,7 @@
pic.color = COLOR_RED
pic.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
pic.alpha = 200
- flick_overlay(pic, list(viewer.client), duration)
+ flick_overlay_global(pic, list(viewer.client), duration)
#undef MODE_NONE
#undef MODE_MESON
diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm
index 33592b773bf9..eefa6d6b7381 100644
--- a/code/modules/clothing/glasses/hud.dm
+++ b/code/modules/clothing/glasses/hud.dm
@@ -43,9 +43,10 @@
desc = "An advanced medical heads-up display that allows doctors to find patients in complete darkness."
icon_state = "healthhudnight"
item_state = "glasses"
- darkness_view = 8
flash_protect = -1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Blue green, dark
+ color_cutoffs = list(10, 10, 30)
+ lighting_cutoff = LIGHTING_CUTOFF_HIGH
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/health/meson
@@ -54,7 +55,8 @@
icon_state = "mesonhealth"
item_state = "mesonhealth"
vision_flags = SEE_TURFS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Mesons get to be lightly green
+ color_cutoffs = list(5, 5, 15)
clothing_traits = list(TRAIT_MESONS)
glass_colour_type = /datum/client_colour/glass_colour/lightblue
@@ -62,7 +64,6 @@
name = "medical HUDSunglasses"
desc = "Sunglasses with a medical HUD."
icon_state = "sunhudmed"
- darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/blue
@@ -84,9 +85,10 @@
desc = "A robotics diagnostic HUD fitted with a light amplifier."
icon_state = "diagnostichudnight"
item_state = "glasses"
- darkness_view = 8
flash_protect = -1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Pale yellow
+ color_cutoffs = list(30, 20, 10)
+ lighting_cutoff = LIGHTING_CUTOFF_HIGH
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/diagnostic/sunglasses
@@ -143,7 +145,6 @@
name = "security HUDSunglasses"
desc = "Sunglasses with a security HUD."
icon_state = "sunhudsec"
- darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/darkred
@@ -157,9 +158,8 @@
name = "night vision security HUD"
desc = "An advanced heads-up display which provides ID data and vision in complete darkness."
icon_state = "securityhudnight"
- darkness_view = 8
flash_protect = -1
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ color_cutoffs = list(35, 10, 10)
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/security/sunglasses/gars
@@ -228,7 +228,7 @@
icon_state = "thermal"
hud_type = DATA_HUD_SECURITY_ADVANCED
vision_flags = SEE_MOBS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ color_cutoffs = list(25, 8, 5)
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user)
@@ -236,13 +236,17 @@
switch (hud_type)
if (DATA_HUD_MEDICAL_ADVANCED)
icon_state = "meson"
+ color_cutoffs = list(5, 15, 5)
change_glass_color(user, /datum/client_colour/glass_colour/green)
if (DATA_HUD_SECURITY_ADVANCED)
icon_state = "thermal"
+ color_cutoffs = list(25, 8, 5)
change_glass_color(user, /datum/client_colour/glass_colour/red)
else
icon_state = "purple"
+ color_cutoffs = list(15, 0, 25)
change_glass_color(user, /datum/client_colour/glass_colour/purple)
+ user.update_sight()
user.update_inv_glasses()
/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity)
diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm
index 0535f58f0576..fec4a268aed9 100644
--- a/code/modules/clothing/gloves/_gloves.dm
+++ b/code/modules/clothing/gloves/_gloves.dm
@@ -32,7 +32,9 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves")
if(HAS_BLOOD_DNA(src))
- . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands")
+ var/mutable_appearance/bloody_hands = mutable_appearance('icons/effects/blood.dmi', "bloodyhands")
+ bloody_hands.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_hands
/obj/item/clothing/gloves/update_clothes_damaged_state()
..()
diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm
index fdc36d7ac040..327d560c0616 100644
--- a/code/modules/clothing/gloves/color.dm
+++ b/code/modules/clothing/gloves/color.dm
@@ -18,7 +18,27 @@
resistance_flags = NONE
var/damaged = FALSE
-/obj/item/clothing/gloves/color/fyellow/proc/get_shocked()
+/obj/item/clothing/gloves/color/fyellow/equipped(mob/user, slot)
+ . = ..()
+ if(slot & ITEM_SLOT_GLOVES)
+ RegisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED, PROC_REF(get_shocked))
+
+/obj/item/clothing/gloves/fyellow/dropped(mob/user)
+ if(user.get_item_by_slot(ITEM_SLOT_GLOVES)==src)
+ UnregisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED)
+ return ..()
+
+/obj/item/clothing/gloves/color/fyellow/proc/get_shocked(mob/living/carbon/victim, power_source, source, siemens_coeff, dist_check)
+ var/list/powernet_info = get_powernet_info_from_source(power_source)
+ if (!powernet_info)
+ return FALSE
+
+ var/datum/powernet/net = powernet_info["powernet"]
+ var/obj/item/stock_parts/cell/cell = powernet_info["cell"]
+
+ if(!(net?.get_electrocute_damage() || cell?.get_electrocute_damage()))
+ return FALSE
+
if(damaged)
to_chat(loc, span_warning("Your gloves catch fire and disintegrate!"))
new/obj/effect/decal/cleanable/ash(src)
diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm
index eb43544d3797..35487d05043d 100644
--- a/code/modules/clothing/head/_head.dm
+++ b/code/modules/clothing/head/_head.dm
@@ -1,6 +1,6 @@
/obj/item/clothing/head
name = BODY_ZONE_HEAD
- icon = 'icons/obj/clothing/hats.dmi'
+ icon = 'icons/obj/clothing/hats/hats.dmi'
icon_state = "top_hat"
item_state = "that"
body_parts_covered = HEAD
@@ -23,8 +23,10 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet")
if(HAS_BLOOD_DNA(src))
- . += mutable_appearance('icons/effects/blood.dmi', "helmetblood")
-
+ var/mutable_appearance/bloody_helmet = mutable_appearance('icons/effects/blood.dmi', "helmetblood")
+ bloody_helmet.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_helmet
+
/obj/item/clothing/head/update_clothes_damaged_state()
..()
if(ismob(loc))
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index ef6030f8a5b4..4b940f5f2c15 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -46,7 +46,7 @@
if(A == attached_light)
set_attached_light(null)
update_helmlight()
- update_appearance(UPDATE_ICON)
+ update_appearance()
QDEL_NULL(alight)
qdel(A)
return ..()
@@ -374,7 +374,7 @@
. = ..()
var/state = "[initial(icon_state)]"
if(attached_light)
- if(attached_light.on)
+ if(attached_light.light_on)
state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on"
else
state += "-flight" //etc.
@@ -399,7 +399,7 @@
return
to_chat(user, span_notice("You click [S] into place on [src]."))
set_attached_light(S)
- update_appearance(UPDATE_ICON)
+ update_appearance()
update_helmlight()
alight = new(src)
if(loc == user)
@@ -419,7 +419,7 @@
var/obj/item/flashlight/removed_light = set_attached_light(null)
update_helmlight()
removed_light.update_brightness(user)
- update_appearance(UPDATE_ICON)
+ update_appearance()
user.update_inv_head()
QDEL_NULL(alight)
return TRUE
@@ -435,16 +435,16 @@
var/mob/user = usr
if(user.incapacitated())
return
- attached_light.on = !attached_light.on
+ attached_light.light_on = !attached_light.light_on
attached_light.update_brightness()
- to_chat(user, span_notice("You toggle the helmet-light [attached_light.on ? "on":"off"]."))
+ to_chat(user, span_notice("You toggle the helmet-light [attached_light.light_on ? "on":"off"]."))
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
update_helmlight()
/obj/item/clothing/head/helmet/proc/update_helmlight()
if(attached_light)
- update_appearance(UPDATE_ICON)
+ update_appearance()
for(var/X in actions)
var/datum/action/A = X
A.build_all_button_icons()
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index 374ec9aac78f..b6cfcd594d57 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -367,7 +367,7 @@
/obj/item/clothing/head/franks_hat
name = "Frank's Hat"
- desc = "You feel ashamed about what you had to do to get this hat"
+ desc = "You feel ashamed about what you had to do to get this hat."
icon_state = "cowboy"
item_state = "cowboy"
@@ -393,12 +393,12 @@
/obj/item/clothing/head/Floralwizhat
name = "Druid hat"
- desc = "A black wizard hat with an exotic looking purple flower on it"
+ desc = "A black wizard hat with an exotic looking purple flower on it."
icon_state = "flowerwizhat"
item_state = "flowerwizhat"
/obj/item/clothing/head/fedora/gtrim_fedora
name = "Gold trimmed Fedora"
- desc = "A Unique variation of the classic fedora. Now with 'Waterproofing' for when buisness gets messy."
+ desc = "A unique variation of the classic fedora. Now with 'waterproofing' for when business gets messy."
icon_state = "gtrim_fedora"
item_state = "gtrim_fedora"
diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm
index 105ca46b8ac4..63fbf97f7790 100644
--- a/code/modules/clothing/masks/_masks.dm
+++ b/code/modules/clothing/masks/_masks.dm
@@ -37,7 +37,9 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask")
if(HAS_BLOOD_DNA(src))
- . += mutable_appearance('icons/effects/blood.dmi', "maskblood")
+ var/mutable_appearance/bloody_mask = mutable_appearance('icons/effects/blood.dmi', "maskblood")
+ bloody_mask.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_mask
/obj/item/clothing/mask/update_clothes_damaged_state()
..()
diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm
index 4d274cf4a614..2f39c722152a 100644
--- a/code/modules/clothing/masks/hailer.dm
+++ b/code/modules/clothing/masks/hailer.dm
@@ -33,6 +33,55 @@
'sound/voice/cpdeath/die3.ogg',
'sound/voice/cpdeath/die4.ogg',
)
+ //dripstation edit start
+ var/static/list/sechailer_voicelines = list(
+ "Принял" = 'sound/voice/cpvoicelines/affirmative.ogg',
+ "Понял" = 'sound/voice/cpvoicelines/copy.ogg',
+ "Можешь идти" = 'sound/voice/cpvoicelines/allrightyoucango.ogg',
+ "Поддержка" = 'sound/voice/cpvoicelines/backup.ogg',
+ "Предатель" = 'sound/voice/cpvoicelines/anticitizen.ogg',
+ "Гражданский" = 'sound/voice/cpvoicelines/citizen.ogg',
+ "На пол" = 'sound/voice/cpvoicelines/getdown.ogg',
+ "Пошёл прочь" = 'sound/voice/cpvoicelines/getoutofhere.ogg',
+ "Граната" = 'sound/voice/cpvoicelines/grenade.ogg',
+ "Помощь" = 'sound/voice/cpvoicelines/help.ogg',
+ "Стоп" = 'sound/voice/cpvoicelines/holdit.ogg',
+ "На позиции" = 'sound/voice/cpvoicelines/inposition.ogg',
+ "Проходи" = 'sound/voice/cpvoicelines/isaidmovealong.ogg',
+ "Иди" = 'sound/voice/cpvoicelines/keepmoving.ogg',
+ "Бдительно" = 'sound/voice/cpvoicelines/Lookout.ogg',
+ "Проходи мимо" = 'sound/voice/cpvoicelines/movealong.ogg',
+ "Повернулся" = 'sound/voice/cpvoicelines/movebackrightnow.ogg',
+ "Шевелись" = 'sound/voice/cpvoicelines/moveit2.ogg',
+ "Пошёл отсюда" = 'sound/voice/cpvoicelines/nowgetoutofhere.ogg',
+ "Подними" = 'sound/voice/cpvoicelines/pickupthecan1.ogg',
+ "Подбери" = 'sound/voice/cpvoicelines/pickupthecan3.ogg',
+ "Казни" = 'sound/voice/cpvoicelines/prepareforjudgement.ogg',
+ "В мусорку" = 'sound/voice/cpvoicelines/putitinthetrash1.ogg',
+ "Реагирую" = 'sound/voice/cpvoicelines/responding2.ogg',
+ "Принято" = 'sound/voice/cpvoicelines/rodgerthat.ogg',
+ "Бля" = 'sound/voice/cpvoicelines/shit.ogg',
+ "Пригнись" = 'sound/voice/cpvoicelines/takecover.ogg',
+ "Опрокинул" = 'sound/voice/cpvoicelines/youknockeditover.ogg',
+ "Подозреваем" = 'sound/voice/cpvoicelines/searchingforsuspect.ogg',
+ "Первое предупреждение" = 'sound/voice/cpvoicelines/firstwarningmove.ogg',
+ "Приговор" = 'sound/voice/cpvoicelines/sentencedelivered.ogg',
+ "Нет слов" = 'sound/voice/cpvoicelines/issuingmalcompliantcitation.ogg',
+ "Применяю" = 'sound/voice/cpvoicelines/apply.ogg',
+ "Хах" = 'sound/voice/cpvoicelines/chuckle.ogg',
+ )
+ var/static/list/speach_on_sounds = list(
+ 'modular_dripstation/sound/voice/sechailer_on1.ogg',
+ 'modular_dripstation/sound/voice/sechailer_on2.ogg',
+ )
+ var/static/list/speach_off_sounds = list(
+ 'modular_dripstation/sound/voice/sechailer_off1.ogg',
+ 'modular_dripstation/sound/voice/sechailer_off2.ogg',
+ 'modular_dripstation/sound/voice/sechailer_off3.ogg',
+ 'modular_dripstation/sound/voice/sechailer_off4.ogg',
+ )
+ //dripstation edit end
+/*
///List of all lines that can be said by the sechailer, with their respective sound file.
var/static/list/sechailer_voicelines = list(
"Affirmative" = 'sound/voice/cpvoicelines/affirmative.ogg',
@@ -70,6 +119,7 @@
"Apply" = 'sound/voice/cpvoicelines/apply.ogg',
"Hehe" = 'sound/voice/cpvoicelines/chuckle.ogg',
)
+*/
/obj/item/clothing/mask/gas/sechailer/swat/spacepol
name = "spacepol mask"
@@ -139,11 +189,15 @@
/obj/item/clothing/mask/gas/sechailer/handle_speech(datum/source, mob/speech_args)
if(!voicetoggled)
return
+ playsound(loc, pick(speach_on_sounds), 50, 0) //dripstation edit
+ sleep(0.2 SECONDS) //dripstation edit
var/full_message = speech_args[SPEECH_MESSAGE]
for(var/lines in sechailer_voicelines)
if(findtext(full_message, lines))
playsound(source, sechailer_voicelines[lines], 50, FALSE)
return // only play the first.
+ sleep(0.2 SECONDS) //dripstation edit
+ playsound(loc, pick(speach_off_sounds), 50, 0) //dripstation edit
/obj/item/clothing/mask/gas/sechailer/on_mob_death()
. = ..()
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index 5579278a21a3..70084ff9db3f 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -375,7 +375,7 @@ GLOBAL_LIST_INIT(cursed_animal_masks, list(
/obj/item/clothing/mask/rmask
name = "dusty mask"
- desc = "A face is nothing, it’s what’s inside that matters."
+ desc = "A face is nothing, it's what's inside that matters."
icon_state = "rmask"
item_state = "rmaks"
flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
@@ -384,7 +384,7 @@ GLOBAL_LIST_INIT(cursed_animal_masks, list(
/obj/item/clothing/mask/pocketcatmask
name = "peculiar cat mask"
- desc = "this mask makes you a little uneasy"
+ desc = "This mask makes you a little uneasy."
icon_state = "pocketmask"
item_state = "pocketmask"
flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 67b320c3b228..ef688f6ef4f1 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -13,7 +13,9 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask")
if(HAS_BLOOD_DNA(src))
- . += mutable_appearance('icons/effects/blood.dmi', "maskblood")
+ var/mutable_appearance/bloody_mask = mutable_appearance('icons/effects/blood.dmi', "maskblood")
+ bloody_mask.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_mask
/obj/item/clothing/neck/tie
name = "tie"
diff --git a/code/modules/clothing/neck/bodycamera.dm b/code/modules/clothing/neck/bodycamera.dm
index 21207b75cfd3..8ebe19cc91b5 100644
--- a/code/modules/clothing/neck/bodycamera.dm
+++ b/code/modules/clothing/neck/bodycamera.dm
@@ -26,7 +26,7 @@
bodcam.network = list("ss13")
bodcam.internal_light = FALSE
bodcam.status = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/clothing/neck/bodycam/attack_self(mob/user)
if(!setup)
@@ -126,6 +126,7 @@
UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
listeningTo = to_hook
RegisterSignal(listeningTo, COMSIG_MOVABLE_MOVED, PROC_REF(trigger))
+ GLOB.cameranet.updatePortableCamera(bodcam)
/obj/item/clothing/neck/bodycam/proc/trigger(mob/user)
if(!bodcam.status)//this is a safety in case of some fucky wucky shit. This SHOULD not ever be true but sometimes it is anyway :(
diff --git a/code/modules/clothing/neck/skillcapes/skillcapes.dm b/code/modules/clothing/neck/skillcapes/skillcapes.dm
index 56972e5a33bc..6d67e97bed06 100644
--- a/code/modules/clothing/neck/skillcapes/skillcapes.dm
+++ b/code/modules/clothing/neck/skillcapes/skillcapes.dm
@@ -11,7 +11,7 @@
/obj/item/clothing/neck/skillcape/trimmed
name = "trimmed cape of skill"
- desc = "a golden trimmed cape, marks proof of excellence."
+ desc = "A golden-trimmed cape, marks proof of excellence."
/obj/item/clothing/neck/skillcape/admin
name = "cape of mighty judgement"
diff --git a/code/modules/clothing/outfits/ert.dm b/code/modules/clothing/outfits/ert.dm
index ccd4b495ea02..7b4a39e43e17 100644
--- a/code/modules/clothing/outfits/ert.dm
+++ b/code/modules/clothing/outfits/ert.dm
@@ -311,6 +311,41 @@
/obj/item/melee/classic_baton/telescopic=1,\
/obj/item/grenade/clusterbuster/cleaner=3)
+/datum/outfit/ert/mining
+ name = "A Dwarven Miner"
+
+ id = /obj/item/card/id/ert
+ suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker
+ suit_store = /obj/item/tank/internals/oxygen/tactical
+ r_hand = /obj/item/kinetic_crusher/mega
+ glasses = /obj/item/clothing/glasses/hud/health/meson
+ gloves = /obj/item/clothing/gloves/gauntlets
+ back = /obj/item/storage/backpack/explorer
+ belt = /obj/item/storage/belt/mining
+ mask = /obj/item/clothing/mask/gas/explorer
+ shoes = /obj/item/clothing/shoes/bhop
+ uniform = /obj/item/clothing/under/rank/miner/lavaland
+ backpack_contents = list(
+ /obj/item/storage/box/survival_mining=1,
+ /obj/item/crusher_trophy/demon_claws=1,
+ /obj/item/crusher_trophy/watcher_wing=1,
+ /obj/item/reagent_containers/autoinjector/medipen/survival=3,
+ /obj/item/kinetic_javelin=1,
+ /obj/item/kinetic_javelin_core/green=1
+ )
+ l_pocket = /obj/item/reagent_containers/glass/beaker/bluespace/dorf
+
+/datum/outfit/ert/mining/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+
+ if(visualsOnly)
+ return
+
+ var/obj/item/radio/R = H.ears
+ R.keyslot = new /obj/item/encryptionkey/heads/cmo
+ R.recalculateChannels()
+ H.dna.add_mutation(DWARFISM)
+
/datum/outfit/centcom_clown
name = "Code Banana ERT"
id = /obj/item/card/id/centcom
diff --git a/code/modules/clothing/outfits/imperial.dm b/code/modules/clothing/outfits/imperial.dm
index 056c9d28516f..9d603a6f101b 100644
--- a/code/modules/clothing/outfits/imperial.dm
+++ b/code/modules/clothing/outfits/imperial.dm
@@ -18,6 +18,7 @@
desc = "A set of khaki fatigues. Standard issue for Imperial Guardsmen"
icon_state = "guard_uniform"
item_state = "guard_uniform"
+ can_adjust = 0
/obj/item/clothing/shoes/combat/imperial
name = "flak boots"
@@ -59,7 +60,7 @@
/obj/item/clothing/suit/armor/imperial/commissar
name = "armored commisar coat"
desc = "A black and red coat. Armored and padded, it instils fear into all that see it, friend and foe alike."
- icon = 'icons/obj/clothing/suits.dmi'
+ icon = 'icons/obj/clothing/suits/suits.dmi'
icon_state = "commissar"
item_state = "commissar"
@@ -67,7 +68,7 @@
/obj/item/clothing/head/helmet/imperial/commissar
name = "commissar cap"
desc = "An armored cap, protecting your head from stray rounds and your eyes from splashes of blood."
- icon = 'icons/obj/clothing/hats.dmi'
+ icon = 'icons/obj/clothing/hats/hats.dmi'
icon_state = "commissar"
item_state = "commissar"
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 051f2872f285..253dd925eb8f 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -9,11 +9,9 @@
slot_flags = ITEM_SLOT_FEET
slowdown = SHOES_SLOWDOWN
- var/blood_state = BLOOD_STATE_NOT_BLOODY
- var/list/bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
+
var/offset = 0
var/equipped_before_drop = FALSE
- var/can_be_bloody = TRUE
var/xenoshoe = NO_DIGIT // Check for if shoes can be worn by straight legs (NO_DIGIT) which is default, both / hybrid (EITHER_STYLE), or digitigrade only (YES_DIGIT)
var/mutantrace_variation = NO_MUTANTRACE_VARIATION // Assigns shoes to have variations for if worn clothing doesn't enforce straight legs (such as cursed jumpskirts)
var/adjusted = NORMAL_STYLE // Default needed to make the above work
@@ -41,16 +39,13 @@
/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE)
. = list()
if(!isinhands)
- var/bloody = FALSE
- if(HAS_BLOOD_DNA(src))
- bloody = TRUE
- else
- bloody = bloody_shoes[BLOOD_STATE_HUMAN]
-
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe")
- if(bloody)
- . += mutable_appearance('icons/effects/blood.dmi', "shoeblood")
+ if(HAS_BLOOD_DNA(src))
+ var/mutable_appearance/bloody_shoes
+ bloody_shoes = mutable_appearance('icons/effects/blood.dmi', "shoeblood")
+ bloody_shoes.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_shoes
/obj/item/clothing/shoes/equipped(mob/user, slot)
if(adjusted)
@@ -89,16 +84,5 @@
var/mob/M = loc
M.update_inv_shoes()
-/obj/item/clothing/shoes/wash(clean_types)
- . = ..()
- if(!(clean_types & CLEAN_TYPE_BLOOD) || blood_state == BLOOD_STATE_NOT_BLOODY)
- return
- bloody_shoes = list(BLOOD_STATE_HUMAN = 0,BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
- blood_state = BLOOD_STATE_NOT_BLOODY
- if(ismob(loc))
- var/mob/M = loc
- M.update_inv_shoes()
- return TRUE
-
/obj/item/proc/negates_gravity()
return FALSE
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 47a0f9d707aa..3e2d7808d15d 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -87,7 +87,7 @@
hat.pixel_y -= 10
hat.layer = initial(hat.layer)
- hat.plane = initial(hat.plane)
+ SET_PLANE_IMPLICIT(hat, initial(hat.plane))
cut_overlays()
var/drop = drop_location()
if(drop)
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index 0e6c247764e6..064db59bcf53 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -100,7 +100,7 @@
if(!ismob(loc))
return
if(helmet_pref_style)
- var/mutable_appearance/helmet_overlay = mutable_appearance('icons/obj/clothing/hats.dmi', helmet_pref_style)
+ var/mutable_appearance/helmet_overlay = mutable_appearance('icons/obj/clothing/hats/hats.dmi', helmet_pref_style)
. += helmet_overlay
/obj/item/clothing/head/helmet/space/plasmaman/equipped(mob/living/user, slot)
diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm
index d5dad7fafb92..8582b6063e60 100644
--- a/code/modules/clothing/suits/_suits.dm
+++ b/code/modules/clothing/suits/_suits.dm
@@ -1,5 +1,5 @@
/obj/item/clothing/suit
- icon = 'icons/obj/clothing/suits.dmi'
+ icon = 'icons/obj/clothing/suits/suits.dmi'
name = "suit"
var/fire_resist = T0C+100
allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant)
@@ -25,9 +25,11 @@
. = list()
if(!isinhands)
if(damaged_clothes)
- . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]")
+ . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
if(HAS_BLOOD_DNA(src))
- . += mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood")
+ var/mutable_appearance/bloody_armor = mutable_appearance('icons/effects/blood.dmi', "[blood_overlay_type]blood")
+ bloody_armor.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_armor
var/mob/living/carbon/human/M = loc
if(ishuman(M) && M.w_uniform)
var/obj/item/clothing/under/U = M.w_uniform
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 3345a9940fa7..1bebc3b9dff3 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -545,217 +545,6 @@
attack_verb = list("warned", "cautioned", "smashed")
armor = list(MELEE = 5, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0)
-
-
-// WINTER COATS
-
-/obj/item/clothing/suit/hooded/wintercoat
- name = "winter coat"
- desc = "A heavy jacket made from 'synthetic' animal furs."
- icon_state = "coatwinter"
- item_state = "coatwinter"
- body_parts_covered = CHEST|GROIN|ARMS
- cold_protection = CHEST|GROIN|ARMS
- min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
- armor = list(MELEE = 0, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 0, BIO = 10, RAD = 0, FIRE = 0, ACID = 0)
- allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
-
-/obj/item/clothing/head/hooded/winterhood
- name = "winter hood"
- desc = "A hood attached to a heavy winter jacket."
- icon_state = "winterhood"
- body_parts_covered = HEAD
- cold_protection = HEAD
- min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
- flags_inv = HIDEHAIR|HIDEEARS
-
-/obj/item/clothing/suit/hooded/wintercoat/captain
- name = "captain's winter coat"
- icon_state = "coatcaptain"
- item_state = "coatcaptain"
- armor = list(MELEE = 25, BULLET = 30, LASER = 30, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 0, ACID = 50)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/captain
-
-/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize(mapload)
- . = ..()
- allowed = GLOB.security_wintercoat_allowed
-
-/obj/item/clothing/head/hooded/winterhood/captain
- icon_state = "winterhood_captain"
-
-/obj/item/clothing/suit/hooded/wintercoat/hop
- name = "head of personnel's winter coat"
- desc = "A cozy winter coat, covered in thick fur. The breast features a proud yellow chevron, reminding everyone that you're the second banana."
- icon_state = "coathop"
- item_state = "coathop"
- armor = list(MELEE = 10, BULLET = 15, LASER = 15, ENERGY = 25, BOMB = 10, BIO = 0, RAD = 0, FIRE = 0, ACID = 35)
- allowed = list(
- /obj/item/melee/classic_baton,
- /obj/item/stamp
- )
- hoodtype = /obj/item/clothing/head/hooded/winterhood/hop
-
-/obj/item/clothing/head/hooded/winterhood/hop
- icon_state = "winterhood_hop"
-
-/obj/item/clothing/suit/hooded/wintercoat/security
- name = "security winter coat"
- icon_state = "coatsecurity"
- item_state = "coatsecurity"
- armor = list(MELEE = 25, BULLET = 15, LASER = 30, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 0, ACID = 45)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/security
-
-/obj/item/clothing/suit/hooded/wintercoat/security/Initialize(mapload)
- . = ..()
- allowed = GLOB.security_wintercoat_allowed
-
-/obj/item/clothing/head/hooded/winterhood/security
- icon_state = "winterhood_security"
-
-/obj/item/clothing/suit/hooded/wintercoat/security/hos
- name = "head of security's winter coat"
- desc = "A red, armour-padded winter coat, lovingly woven with a Kevlar interweave and reinforced with semi-ablative polymers and a silver azide fill material. The zipper tab looks like a tiny replica of Beepsky."
- icon_state = "coathos"
- item_state = "coathos"
- armor = list(MELEE = 35, BULLET = 25, LASER = 40, ENERGY = 20, BOMB = 35, BIO = 0, RAD = 0, FIRE = 0, ACID = 55)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/security/hos
-
-/obj/item/clothing/head/hooded/winterhood/security/hos
- desc = "A red, armour-padded winter hood, lovingly woven with a Kevlar interweave. Definitely not bulletproof, especially not the part where your face goes."
- icon_state = "winterhood_hos"
- armor = list(MELEE = 5, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 5, BIO = 0, RAD = 0, FIRE = 0, ACID = 55)
-
-/obj/item/clothing/suit/hooded/wintercoat/medical
- name = "medical winter coat"
- icon_state = "coatmedical"
- item_state = "coatmedical"
- allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/autoinjector, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/soap, /obj/item/sensor_device, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/hypospray)
- armor = list(MELEE = 0, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 0, BIO = 50, RAD = 0, FIRE = 0, ACID = 45)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/medical
-
-/obj/item/clothing/head/hooded/winterhood/medical
- icon_state = "winterhood_medical"
-
-/obj/item/clothing/suit/hooded/wintercoat/medical/cmo
- name = "chief medical officer's winter coat"
- desc = "A winter coat in a vibrant shade of blue with a small silver caduceus instead of a plastic zipper tab. The normal liner is replaced with an exceptionally thick, soft layer of fur."
- icon_state = "coatcmo"
- item_state = "coatcmo"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 50, RAD = 0, FIRE = 20, ACID = 30)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/cmo
-
-/obj/item/clothing/suit/hooded/wintercoat/medical/cmo/Initialize(mapload)
- . = ..()
- allowed += list(
- /obj/item/melee/classic_baton,
- )
-
-/obj/item/clothing/head/hooded/winterhood/medical/cmo
- desc = "A blue winter coat hood."
- icon_state = "winterhood_cmo"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 50, RAD = 0, FIRE = 20, ACID = 30)
-
-/obj/item/clothing/suit/hooded/wintercoat/science
- name = "science winter coat"
- icon_state = "coatscience"
- item_state = "coatscience"
- allowed = list(/obj/item/analyzer, /obj/item/multitool/tricorder, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/autoinjector, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant)
- armor = list(MELEE = 0, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 10, BIO = 0, RAD = 0, FIRE = 0, ACID = 0)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/science
-
-/obj/item/clothing/head/hooded/winterhood/science
- icon_state = "winterhood_science"
-
-/obj/item/clothing/suit/hooded/wintercoat/science/rd
- name = "research director's advanced thermal insulator"
- desc = "A thick arctic winter coat with an outdated atomic model instead of a plastic zipper tab. Most in the know are heavily aware that Bohr's model of the atom was outdated by the time of the 1930s when the Heisenbergian and Schrodinger models were generally accepted for true. Nevertheless, we still see its use in anachronism, roleplaying, and, in this case, as a zipper tab. At least it should keep you warm on your ivory pillar."
- icon_state = "coatrd"
- item_state = "coatrd"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 20, BIO = 0, RAD = 0, FIRE = 30, ACID = 0)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/science/rd
-
-/obj/item/clothing/suit/hooded/wintercoat/science/rd/Initialize(mapload)
- . = ..()
- allowed += list(
- /obj/item/melee/classic_baton,
- )
-
-/obj/item/clothing/head/hooded/winterhood/science/rd
- desc = "A white winter coat hood. It smells faintly of hair gel."
- icon_state = "winterhood_rd"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 20, BIO = 0, RAD = 0, FIRE = 30, ACID = 0)
-
-/obj/item/clothing/suit/hooded/wintercoat/engineering
- name = "engineering winter coat"
- icon_state = "coatengineer"
- item_state = "coatengineer"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 20, FIRE = 30, ACID = 45, ELECTRIC = 20)
- allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/extinguisher/mini)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering
-
-/obj/item/clothing/head/hooded/winterhood/engineering
- icon_state = "winterhood_engineer"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 10, FIRE = 20, ACID = 10, ELECTRIC = 20)
-
-/obj/item/clothing/suit/hooded/wintercoat/engineering/ce
- name = "chief engineer's winter coat"
- desc = "A white winter coat with reflective green and yellow stripes made for the truly insane. Stuffed with asbestos, treated with fire retardant PBDE, lined with a micro thin sheet of lead foil and snugly fitted to your body's measurements. This baby's ready to save you from anything except the thyroid cancer and systemic fibrosis you'll get from wearing it. The zipper tab is a tiny golden wrench."
- icon_state = "coatce"
- item_state = "coatce"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 30, FIRE = 40, ACID = 10, ELECTRIC = 40)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/ce
-
-/obj/item/clothing/suit/hooded/wintercoat/engineering/ce/Initialize(mapload)
- . = ..()
- allowed += list(
- /obj/item/melee/classic_baton,
- )
-
-/obj/item/clothing/head/hooded/winterhood/engineering/ce
- desc = "A white winter coat hood. Feels surprisingly heavy. The tag says that it's not child safe."
- icon_state = "winterhood_ce"
- armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 15, FIRE = 30, ACID = 10, ELECTRIC = 40)
-
-/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos
- name = "atmospherics winter coat"
- icon_state = "coatatmos"
- item_state = "coatatmos"
- hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos
-
-/obj/item/clothing/head/hooded/winterhood/engineering/atmos
- icon_state = "winterhood_atmos"
-
-/obj/item/clothing/suit/hooded/wintercoat/hydro
- name = "hydroponics winter coat"
- icon_state = "coathydro"
- item_state = "coathydro"
- allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro
-
-/obj/item/clothing/head/hooded/winterhood/hydro
- icon_state = "winterhood_hydro"
-
-/obj/item/clothing/suit/hooded/wintercoat/cargo
- name = "cargo winter coat"
- icon_state = "coatcargo"
- item_state = "coatcargo"
- hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo
- allowed = list(/obj/item/stamp)
-
-/obj/item/clothing/head/hooded/winterhood/cargo
- icon_state = "winterhood_cargo"
-
-/obj/item/clothing/suit/hooded/wintercoat/miner
- name = "mining winter coat"
- icon_state = "coatminer"
- item_state = "coatminer"
- allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
- armor = list(MELEE = 10, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0)
- hoodtype = /obj/item/clothing/head/hooded/winterhood/miner
-
-/obj/item/clothing/head/hooded/winterhood/miner
- icon_state = "winterhood_miner"
-
/obj/item/clothing/suit/spookyghost
name = "spooky ghost"
desc = "This is obviously just a bedsheet, but maybe try it on?"
diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm
index 8995c24607e5..cee86b63f436 100644
--- a/code/modules/clothing/suits/reactive_armour.dm
+++ b/code/modules/clothing/suits/reactive_armour.dm
@@ -2,7 +2,7 @@
name = "reactive armor shell"
desc = "An experimental suit of armor, awaiting installation of an anomaly core."
icon_state = "reactiveoff"
- icon = 'icons/obj/clothing/suits.dmi'
+ icon = 'icons/obj/clothing/suits/suits.dmi'
w_class = WEIGHT_CLASS_BULKY
/obj/item/reactive_armor_shell/attackby(obj/item/I, mob/user, params)
diff --git a/code/modules/clothing/suits/wintercoat.dm b/code/modules/clothing/suits/wintercoat.dm
new file mode 100644
index 000000000000..21662fd7534a
--- /dev/null
+++ b/code/modules/clothing/suits/wintercoat.dm
@@ -0,0 +1,341 @@
+// WINTER COATS
+
+/obj/item/clothing/suit/hooded/wintercoat
+ mob_overlay_icon = 'icons/mob/clothing/suit/wintercoat.dmi'
+ icon = 'icons/obj/clothing/suits/wintercoat.dmi'
+ name = "winter coat"
+ desc = "A heavy jacket made from 'synthetic' animal furs."
+ icon_state = "coatwinter"
+ item_state = "coatwinter"
+ body_parts_covered = CHEST|GROIN|ARMS
+ cold_protection = CHEST|GROIN|ARMS
+ min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 0, BIO = 10, RAD = 0, FIRE = 0, ACID = 0)
+ allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
+
+/obj/item/clothing/head/hooded/winterhood
+ mob_overlay_icon = 'icons/mob/clothing/head/winterhood.dmi'
+ icon = 'icons/obj/clothing/hats/winterhood.dmi'
+ name = "winter hood"
+ desc = "A hood attached to a heavy winter jacket."
+ icon_state = "hood_winter"
+ body_parts_covered = HEAD
+ cold_protection = HEAD
+ min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
+ flags_inv = HIDEHAIR|HIDEEARS
+
+/obj/item/clothing/suit/hooded/wintercoat/centcom
+ name = "centcom winter coat"
+ desc = "A luxurious winter coat woven in the bright green and gold colours of Central Command. It has a small pin in the shape of the Nanotrasen logo for a zipper."
+ icon_state = "coatcentcom"
+ item_state = "coatcentcom"
+ armor = list(MELEE = 35, BULLET = 40, LASER = 40, ENERGY = 50, BOMB = 35, BIO = 10, RAD = 10, FIRE = 10, ACID = 60)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/centcom
+
+/obj/item/clothing/suit/hooded/wintercoat/centcom/Initialize(mapload)
+ . = ..()
+ allowed = GLOB.security_wintercoat_allowed
+
+/obj/item/clothing/head/hooded/winterhood/centcom
+ icon_state = "hood_centcom"
+ armor = list(MELEE = 35, BULLET = 40, LASER = 40, ENERGY = 50, BOMB = 35, BIO = 10, RAD = 10, FIRE = 10, ACID = 60)
+
+// COMMAND
+
+/obj/item/clothing/suit/hooded/wintercoat/captain
+ name = "captain's winter coat"
+ desc = "A luxurious winter coat, stuffed with the down of the endangered Uka bird and trimmed with genuine sable. The fabric is an indulgently soft micro-fiber,\
+ and the deep ultramarine colour is only one that could be achieved with minute amounts of crystalline bluespace dust woven into the thread between the plectrums.\
+ Extremely lavish, and extremely durable."
+ icon_state = "coatcaptain"
+ item_state = "coatcaptain"
+ armor = list(MELEE = 25, BULLET = 30, LASER = 30, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 0, ACID = 50)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/captain
+
+/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize(mapload)
+ . = ..()
+ allowed = GLOB.security_wintercoat_allowed
+
+/obj/item/clothing/head/hooded/winterhood/captain
+ desc = "A blue winter coat hood."
+ icon_state = "hood_captain"
+
+/obj/item/clothing/suit/hooded/wintercoat/hop
+ name = "head of personnel's winter coat"
+ desc = "A cozy winter coat, covered in thick fur. The breast features a proud yellow chevron, reminding everyone that you're the second banana."
+ icon_state = "coathop"
+ item_state = "coathop"
+ armor = list(MELEE = 10, BULLET = 15, LASER = 15, ENERGY = 25, BOMB = 10, BIO = 0, RAD = 0, FIRE = 0, ACID = 35)
+ allowed = list(
+ /obj/item/melee/classic_baton,
+ /obj/item/stamp
+ )
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/hop
+
+/obj/item/clothing/head/hooded/winterhood/hop
+ desc = "A blue winter coat hood."
+ icon_state = "hood_hop"
+
+// SERVICE
+
+/obj/item/clothing/suit/hooded/wintercoat/hydro
+ name = "hydroponics winter coat"
+ desc = "A green and blue winter coat. The zipper tab looks like the flower from a member of Rosa Hesperrhodos, a pretty pink-and-white rose. The colours absolutely clash."
+ icon_state = "coathydro"
+ item_state = "coathydro"
+ allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro
+
+/obj/item/clothing/head/hooded/winterhood/hydro
+ desc = "A green winter coat hood."
+ icon_state = "hood_hydro"
+
+/obj/item/clothing/suit/hooded/wintercoat/janitor
+ name = "janitors winter coat"
+ desc = "A purple-and-beige winter coat that smells of space cleaner."
+ icon_state = "coatjanitor"
+ item_state = "coatjanitor"
+ allowed = list(/obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter,/obj/item/grenade/chem_grenade,/obj/item/lightreplacer,/obj/item/flashlight,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/spray,/obj/item/soap,/obj/item/holosign_creator,/obj/item/key/janitor,/obj/item/melee/flyswatter,/obj/item/paint/paint_remover,/obj/item/storage/bag/trash,/obj/item/reagent_containers/glass/bucket)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/janitor
+
+/obj/item/clothing/head/hooded/winterhood/janitor
+ desc = "A purple hood that smells of space cleaner."
+ icon_state = "hood_janitor"
+
+// SECURITY
+
+/obj/item/clothing/suit/hooded/wintercoat/security
+ name = "security winter coat"
+ desc = "A red, armour-padded winter coat. It glitters with a mild ablative coating and a robust air of authority. The zipper tab is a pair of jingly little handcuffs that get annoying after the first ten seconds."
+ icon_state = "coatsecurity"
+ item_state = "coatsecurity"
+ armor = list(MELEE = 25, BULLET = 15, LASER = 30, ENERGY = 10, BOMB = 25, BIO = 0, RAD = 0, FIRE = 0, ACID = 45)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/security
+
+/obj/item/clothing/suit/hooded/wintercoat/security/Initialize(mapload)
+ . = ..()
+ allowed = GLOB.security_wintercoat_allowed
+
+/obj/item/clothing/head/hooded/winterhood/security
+ desc = "A red, armour-padded winter hood. Definitely not bulletproof, especially not the part where your face goes."
+ icon_state = "hood_security"
+
+/obj/item/clothing/suit/hooded/wintercoat/security/hos
+ name = "head of security's winter coat"
+ desc = "A red, armour-padded winter coat, lovingly woven with a Kevlar interweave and reinforced with semi-ablative polymers and a silver azide fill material. The zipper tab looks like a tiny replica of Beepsky."
+ icon_state = "coathos"
+ item_state = "coathos"
+ armor = list(MELEE = 35, BULLET = 25, LASER = 40, ENERGY = 20, BOMB = 35, BIO = 0, RAD = 0, FIRE = 0, ACID = 55)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/security/hos
+
+/obj/item/clothing/head/hooded/winterhood/security/hos
+ desc = "A red, armour-padded winter hood, lovingly woven with a Kevlar interweave. Definitely not bulletproof, especially not the part where your face goes."
+ icon_state = "hood_hos"
+ armor = list(MELEE = 5, BULLET = 5, LASER = 5, ENERGY = 5, BOMB = 5, BIO = 0, RAD = 0, FIRE = 0, ACID = 55)
+
+// MEDICAL
+
+/obj/item/clothing/suit/hooded/wintercoat/medical
+ name = "medical winter coat"
+ desc = "An arctic white winter coat with a small blue caduceus instead of a plastic zipper tab. Snazzy."
+ icon_state = "coatmedical"
+ item_state = "coatmedical"
+ allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/autoinjector, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/soap, /obj/item/sensor_device, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/hypospray)
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 0, BIO = 50, RAD = 0, FIRE = 0, ACID = 45)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/medical
+
+/obj/item/clothing/head/hooded/winterhood/medical
+ desc = "A white winter coat hood."
+ icon_state = "hood_medical"
+
+/obj/item/clothing/suit/hooded/wintercoat/medical/cmo
+ name = "chief medical officer's winter coat"
+ desc = "A winter coat in a vibrant shade of blue with a small silver caduceus instead of a plastic zipper tab. The normal liner is replaced with an exceptionally thick, soft layer of fur."
+ icon_state = "coatcmo"
+ item_state = "coatcmo"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 50, RAD = 0, FIRE = 20, ACID = 30)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/cmo
+
+/obj/item/clothing/suit/hooded/wintercoat/medical/cmo/Initialize(mapload)
+ . = ..()
+ allowed += list(
+ /obj/item/melee/classic_baton,
+ )
+
+/obj/item/clothing/head/hooded/winterhood/medical/cmo
+ desc = "A blue winter coat hood."
+ icon_state = "hood_cmo"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 50, RAD = 0, FIRE = 20, ACID = 30)
+
+/obj/item/clothing/suit/hooded/wintercoat/medical/chemistry
+ name = "chemistry winter coat"
+ desc = "A lab-grade winter coat made with acid resistant polymers. For the enterprising chemist who was exiled to a frozen wasteland on the go."
+ icon_state = "coatchemistry"
+ item_state = "coatchemistry"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/chemistry
+
+/obj/item/clothing/head/hooded/winterhood/medical/chemistry
+ desc = "A white winter coat hood."
+ icon_state = "hood_chemistry"
+
+/obj/item/clothing/suit/hooded/wintercoat/medical/viro
+ name = "virology winter coat"
+ desc = "A white winter coat with green markings. Warm, but wont fight off the common cold or any other disease. Might make people stand far away from you in the hallway. The zipper tab looks like an oversized bacteriophage."
+ icon_state = "coatviro"
+ item_state = "coatviro"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/viro
+
+/obj/item/clothing/head/hooded/winterhood/medical/viro
+ desc = "A white winter coat hood with green markings."
+ icon_state = "hood_viro"
+
+/obj/item/clothing/suit/hooded/wintercoat/medical/paramedic
+ name = "paramedic winter coat"
+ desc = "A winter coat with blue markings. Warm, but probably won't protect from biological agents. For the cozy doctor on the go."
+ icon_state = "coatparamed"
+ item_state = "coatparamed"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/paramedic
+
+/obj/item/clothing/head/hooded/winterhood/medical/paramedic
+ desc = "A white winter coat hood with blue markings."
+ icon_state = "hood_paramed"
+
+// SCIENCE
+
+/obj/item/clothing/suit/hooded/wintercoat/science
+ name = "science winter coat"
+ desc = "A white winter coat with an outdated atomic model instead of a plastic zipper tab."
+ icon_state = "coatscience"
+ item_state = "coatscience"
+ allowed = list(/obj/item/analyzer, /obj/item/multitool/tricorder, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/autoinjector, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant)
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0,ENERGY = 0, BOMB = 10, BIO = 0, RAD = 0, FIRE = 0, ACID = 0)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/science
+
+/obj/item/clothing/head/hooded/winterhood/science
+ desc = "A white winter coat hood. This one will keep your brain warm. About as much as the others, really."
+ icon_state = "hood_science"
+
+/obj/item/clothing/suit/hooded/wintercoat/science/rd
+ name = "research director's advanced thermal insulator"
+ desc = "A thick arctic winter coat with an outdated atomic model instead of a plastic zipper tab. Most in the know are heavily aware that Bohr's model of the atom was outdated by the time of the 1930s when the Heisenbergian and Schrodinger models were generally accepted for true. Nevertheless, we still see its use in anachronism, roleplaying, and, in this case, as a zipper tab. At least it should keep you warm on your ivory pillar."
+ icon_state = "coatrd"
+ item_state = "coatrd"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 20, BIO = 0, RAD = 0, FIRE = 30, ACID = 0)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/science/rd
+
+/obj/item/clothing/suit/hooded/wintercoat/science/rd/Initialize(mapload)
+ . = ..()
+ allowed += list(
+ /obj/item/melee/classic_baton,
+ )
+
+/obj/item/clothing/head/hooded/winterhood/science/rd
+ desc = "A white winter coat hood. It smells faintly of hair gel."
+ icon_state = "hood_rd"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 20, BIO = 0, RAD = 0, FIRE = 30, ACID = 0)
+
+/obj/item/clothing/suit/hooded/wintercoat/science/robotics
+ name = "robotics winter coat"
+ desc = "A black winter coat with a badass flaming robotic skull for the zipper tab. This one has bright red designs and a few useless buttons."
+ icon_state = "coatrobotics"
+ item_state = "coatrobotics"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/science/robotics
+
+/obj/item/clothing/head/hooded/winterhood/science/robotics
+ desc = "A black winter coat hood. You can pull it down over your eyes and pretend that you're an outdated, late 1980s interpretation of a futuristic mechanized police force. They'll fix you. They fix everything."
+ icon_state = "hood_robotics"
+
+/obj/item/clothing/suit/hooded/wintercoat/science/genetics
+ name = "genetics winter coat"
+ desc = "A white winter coat with a DNA helix for the zipper tab."
+ icon_state = "coatgenetics"
+ item_state = "coatgenetics"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/science/genetics
+
+/obj/item/clothing/head/hooded/winterhood/science/genetics
+ desc = "A white winter coat hood. It's warm."
+ icon_state = "hood_genetics"
+
+// ENGINEERING
+
+/obj/item/clothing/suit/hooded/wintercoat/engineering
+ name = "engineering winter coat"
+ desc = "A surprisingly heavy yellow winter coat with reflective orange stripes. It has a small wrench for its zipper tab, and the inside layer is covered with a radiation-resistant silver-nylon blend. Because you're worth it."
+ icon_state = "coatengineer"
+ item_state = "coatengineer"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 20, FIRE = 30, ACID = 45, ELECTRIC = 20)
+ allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/extinguisher/mini)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering
+
+/obj/item/clothing/head/hooded/winterhood/engineering
+ desc = "A yellow winter coat hood. Definitely not a replacement for a hard hat."
+ icon_state = "hood_engineer"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 10, FIRE = 20, ACID = 10, ELECTRIC = 20)
+
+/obj/item/clothing/suit/hooded/wintercoat/engineering/ce
+ name = "chief engineer's winter coat"
+ desc = "A white winter coat with reflective green and yellow stripes. Stuffed with asbestos, treated with fire retardant PBDE, lined with a micro thin sheet of lead foil and snugly fitted to your body's measurements. This baby's ready to save you from anything except the thyroid cancer and systemic fibrosis you'll get from wearing it. The zipper tab is a tiny golden wrench."
+ icon_state = "coatce"
+ item_state = "coatce"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 30, FIRE = 40, ACID = 10, ELECTRIC = 40)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/ce
+
+/obj/item/clothing/suit/hooded/wintercoat/engineering/ce/Initialize(mapload)
+ . = ..()
+ allowed += list(
+ /obj/item/melee/classic_baton,
+ )
+
+/obj/item/clothing/head/hooded/winterhood/engineering/ce
+ desc = "A white winter coat hood. Feels surprisingly heavy. The tag says that it's not child safe."
+ icon_state = "hood_ce"
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 15, FIRE = 30, ACID = 10, ELECTRIC = 40)
+
+/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos
+ name = "atmospherics winter coat"
+ desc = "A surprisingly heavy yellow winter coat with reflective blue stripes. It has a small wrench for its zipper tab, and the inside layer is covered with a radiation-resistant silver-nylon blend. Because you're worth it."
+ icon_state = "coatatmos"
+ item_state = "coatatmos"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos
+
+/obj/item/clothing/head/hooded/winterhood/engineering/atmos
+ desc = "A yellow and blue winter coat hood."
+ icon_state = "hood_atmos"
+
+// SUPPLY
+
+/obj/item/clothing/suit/hooded/wintercoat/cargo
+ name = "cargo winter coat"
+ desc = "A tan-and-grey winter coat. The zipper tab is a small pin resembling a MULE. It fills you with the warmth of a fierce independence."
+ icon_state = "coatcargo"
+ item_state = "coatcargo"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo
+ allowed = list(/obj/item/stamp)
+
+/obj/item/clothing/head/hooded/winterhood/cargo
+ desc = "A grey hood for a winter coat."
+ icon_state = "hood_cargo"
+
+/obj/item/clothing/suit/hooded/wintercoat/qm
+ name = "quartermaster's winter coat"
+ desc = "A dark brown winter coat that has a golden crate pin for its zipper pully."
+ icon_state = "coatqm"
+ item_state = "coatqm"
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/qm
+
+/obj/item/clothing/head/hooded/winterhood/qm
+ desc = "A dark brown winter hood"
+ icon_state = "hood_qm"
+
+/obj/item/clothing/suit/hooded/wintercoat/miner
+ name = "mining winter coat"
+ desc = "A dusty button up winter coat. The zipper tab looks like a tiny pickaxe."
+ icon_state = "coatminer"
+ item_state = "coatminer"
+ allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/tank/internals/ipc_coolant, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter)
+ armor = list(MELEE = 10, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0)
+ hoodtype = /obj/item/clothing/head/hooded/winterhood/miner
+
+/obj/item/clothing/head/hooded/winterhood/miner
+ desc = "A dusty winter coat hood."
+ icon_state = "hood_miner"
diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm
index 346de7cbf271..82cad43f267f 100644
--- a/code/modules/clothing/under/_under.dm
+++ b/code/modules/clothing/under/_under.dm
@@ -27,7 +27,9 @@
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
if(HAS_BLOOD_DNA(src))
- . += mutable_appearance('icons/effects/blood.dmi', "uniformblood")
+ var/mutable_appearance/bloody_uniform = mutable_appearance('icons/effects/blood.dmi', "uniformblood")
+ bloody_uniform.color = get_blood_dna_color(return_blood_DNA())
+ . += bloody_uniform
if(accessory_overlay)
. += accessory_overlay
diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm
index 3822fcb73be4..b876a8275d52 100644
--- a/code/modules/clothing/under/accessories.dm
+++ b/code/modules/clothing/under/accessories.dm
@@ -63,7 +63,7 @@
pixel_x -= 8
pixel_y += 8
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
U.cut_overlays()
U.attached_accessory = null
U.accessory_overlay = null
@@ -316,13 +316,12 @@
/obj/item/clothing/accessory/lawyers_badge/on_clothing_equip(obj/item/clothing/U, user)
var/mob/living/L = user
if(L)
- L.bubble_icon = "lawyer"
+ L.AddElement(/datum/element/speech_bubble_override, BUBBLE_LAWYER)
/obj/item/clothing/accessory/lawyers_badge/on_clothing_dropped(obj/item/clothing/U, user)
var/mob/living/L = user
if(L)
- L.bubble_icon = initial(L.bubble_icon)
-
+ L.RemoveElement(/datum/element/speech_bubble_override, BUBBLE_LAWYER)
////////////////
//HA HA! NERD!//
diff --git a/code/modules/clothing/under/badges.dm b/code/modules/clothing/under/badges.dm
index 2bf400df42e1..3794c821b63e 100644
--- a/code/modules/clothing/under/badges.dm
+++ b/code/modules/clothing/under/badges.dm
@@ -31,7 +31,7 @@
pixel_x -= 8
pixel_y += 8
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
suit_target.cut_overlays()
suit_target.attached_badge = null
suit_target.badge_overlay = null
diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm
index ca2978767bb1..ee40f950e8d7 100644
--- a/code/modules/clothing/under/jobs/civilian.dm
+++ b/code/modules/clothing/under/jobs/civilian.dm
@@ -53,6 +53,12 @@
item_state = "lb_suit"
mutantrace_variation = MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/cargo/turtleneck
+ name = "quartermaster's turtleneck jumpsuit"
+ desc = "It's a fashionable turtleneck worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper."
+ icon_state = "turtleneck_qm"
+ item_state = "lb_suit"
+
/obj/item/clothing/under/rank/cargo/skirt
name = "quartermaster's jumpskirt"
desc = "It's a jumpskirt worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper."
@@ -64,6 +70,12 @@
fitted = FEMALE_UNIFORM_TOP
mutantrace_variation = NO_MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/cargo/skirt/turtleneck
+ name = "quartermaster's skirtleneck"
+ desc = "It's a stylish skirtleneck worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper."
+ icon_state = "skirtleneckQM"
+ item_state = "lb_suit"
+
/obj/item/clothing/under/rank/cargotech
name = "cargo technician's jumpsuit"
desc = "Shooooorts! They're comfy and easy to wear!"
@@ -74,6 +86,12 @@
alt_covers_chest = TRUE
mutantrace_variation = MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/cargotech/turtleneck
+ name = "cargo technician's turtleneck jumpsuit"
+ desc = "Perfect for pushing crates and looking good while doing it! Shorts not included."
+ icon_state = "turtleneck_cargo"
+ item_state = "lb_suit"
+
/obj/item/clothing/under/rank/cargotech/skirt
name = "cargo technician's jumpskirt"
desc = "Skiiiiirts! They're comfy and easy to wear!"
@@ -85,6 +103,12 @@
fitted = FEMALE_UNIFORM_TOP
mutantrace_variation = NO_MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/cargotech/skirt/turtleneck
+ name = "cargo technician's skirtleneck"
+ desc = "Skiiiiirtlenecks! Even comfier and easier to wear!"
+ icon_state = "skirtleneck"
+ item_state = "lb_suit"
+
/obj/item/clothing/under/rank/chaplain
desc = "It's a black jumpsuit, often worn by religious folk."
name = "chaplain's jumpsuit"
@@ -186,8 +210,8 @@
AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50)
/obj/item/clothing/under/rank/head_of_personnel
- desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"."
name = "head of personnel's jumpsuit"
+ desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"."
icon_state = "hop"
item_state = "b_suit"
can_adjust = FALSE
@@ -195,6 +219,13 @@
random_sensor = FALSE
mutantrace_variation = MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/head_of_personnel/turtleneck
+ name = "head of personnel's turtleneck jumpsuit"
+ desc = "It's a comfy turtleneck jumpsuit worn by someone who works in the position of \"Head of Personnel\"."
+ icon_state = "hopturtle"
+ item_state = "b_suit"
+ can_adjust = TRUE
+
/obj/item/clothing/under/rank/head_of_personnel/skirt
name = "head of personnel's jumpskirt"
desc = "It's a jumpskirt worn by someone who works in the position of \"Head of Personnel\"."
@@ -206,6 +237,13 @@
fitted = FEMALE_UNIFORM_TOP
mutantrace_variation = NO_MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/head_of_personnel/skirt/turtleneck
+ name = "head of personnel's skirtleneck"
+ desc = "It's a fashionable skirtleneck worn by someone who works in the position of \"Head of Personnel\"."
+ icon_state = "hopturtle_skirt"
+ item_state = "b_suit"
+ can_adjust = TRUE
+
/obj/item/clothing/under/rank/hydroponics
desc = "It's a jumpsuit designed to protect against minor plant-related hazards."
name = "botanist's jumpsuit"
diff --git a/code/modules/clothing/under/jobs/medsci.dm b/code/modules/clothing/under/jobs/medsci.dm
index 7677aeaf49ed..00361915b525 100644
--- a/code/modules/clothing/under/jobs/medsci.dm
+++ b/code/modules/clothing/under/jobs/medsci.dm
@@ -115,6 +115,12 @@
random_sensor = FALSE
mutantrace_variation = MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/chief_medical_officer/turtleneck
+ desc = "It's a jumpsuit worn by those with the experience, and the style, to be \"Chief Medical Officer\". It provides minor biological protection."
+ name = "chief medical officer's turtleneck jumpsuit"
+ icon_state = "cmoturtle"
+ item_state = "w_suit"
+
/obj/item/clothing/under/rank/chief_medical_officer/skirt
name = "chief medical officer's jumpskirt"
desc = "It's a jumpskirt worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection."
@@ -126,6 +132,12 @@
fitted = FEMALE_UNIFORM_TOP
mutantrace_variation = NO_MUTANTRACE_VARIATION
+/obj/item/clothing/under/rank/chief_medical_officer/skirt/turtleneck
+ name = "chief medical officer's skirtleneck"
+ desc = "It's a jumpskirt worn by those with the experience, and the style, to be \"Chief Medical Officer\". It provides minor biological protection."
+ icon_state = "cmoturtle_skirt"
+ item_state = "w_suit"
+
/obj/item/clothing/under/rank/geneticist
desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it."
name = "geneticist's jumpsuit"
diff --git a/code/modules/clothing/under/jobs/security.dm b/code/modules/clothing/under/jobs/security.dm
index 08c56479263f..057e0e298a08 100644
--- a/code/modules/clothing/under/jobs/security.dm
+++ b/code/modules/clothing/under/jobs/security.dm
@@ -27,6 +27,14 @@
icon_state = "security"
item_state = "gy_suit"
+/obj/item/clothing/under/rank/security/shitcurity
+ name = "shitcurity uniform"
+ desc = "For the security members that want to show their true colors."
+ mob_overlay_icon = 'yogstation/icons/mob/clothing/uniform/uniform.dmi'
+ icon = 'yogstation/icons/obj/clothing/uniforms.dmi'
+ icon_state = "altsecurity"
+ item_state = "altsecurity"
+
/obj/item/clothing/under/rank/security/skirt
name = "security jumpskirt"
desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt."
diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm
index 409e68ea653e..64ab89735680 100644
--- a/code/modules/clothing/under/miscellaneous.dm
+++ b/code/modules/clothing/under/miscellaneous.dm
@@ -202,7 +202,7 @@
/obj/item/clothing/under/cloud
name = "cloud"
- desc = "cloud"
+ desc = "Cloud."
icon_state = "cloud"
can_adjust = FALSE
@@ -954,3 +954,9 @@
if(H.get_item_by_slot(ITEM_SLOT_ICLOTHING) == src)
SEND_SIGNAL(user, COMSIG_CLEAR_MOOD_EVENT, "drippy")
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "dripless", /datum/mood_event/dripless)
+
+//ivymen name variatons
+
+/obj/item/clothing/under/ash_robe/hunter/jungle
+ name = "primal rags"
+ desc = "Light primal rags that are fashionable and practical, while still maximizing photosynthesis capability for plantpeople."
diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm
index 9a172114208c..f9269b87debd 100644
--- a/code/modules/detectivework/detective_work.dm
+++ b/code/modules/detectivework/detective_work.dm
@@ -25,16 +25,18 @@
if(D)
. = D.fibers
-/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT
+/atom/proc/add_fingerprint_list(list/fingerprints) //ASSOC LIST FINGERPRINT = FINGERPRINT
if(length(fingerprints))
. = AddComponent(/datum/component/forensics, fingerprints)
//Set ignoregloves to add prints irrespective of the mob having gloves on.
/atom/proc/add_fingerprint(mob/M, ignoregloves = FALSE)
+ if (QDELING(src))
+ return
var/datum/component/forensics/D = AddComponent(/datum/component/forensics)
- . = D.add_fingerprint(M, ignoregloves)
+ . = D?.add_fingerprint(M, ignoregloves)
-/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT
+/atom/proc/add_fiber_list(list/fibertext) //ASSOC LIST FIBERTEXT = FIBERTEXT
if(length(fibertext))
. = AddComponent(/datum/component/forensics, null, null, null, fibertext)
@@ -53,7 +55,7 @@
var/datum/component/forensics/D = AddComponent(/datum/component/forensics)
. = D.add_fibers(M)
-/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM
+/atom/proc/add_hiddenprint_list(list/hiddenprints) //NOTE: THIS IS FOR ADMINISTRATION FINGERPRINTS, YOU MUST CUSTOM SET THIS TO INCLUDE CKEY/REAL NAMES! CHECK FORENSICS.DM
if(length(hiddenprints))
. = AddComponent(/datum/component/forensics, null, hiddenprints)
@@ -61,15 +63,17 @@
var/datum/component/forensics/D = AddComponent(/datum/component/forensics)
. = D.add_hiddenprint(M)
-/atom/proc/add_blood_DNA(list/dna) //ASSOC LIST DNA = BLOODTYPE
+/atom/proc/add_blood_DNA(list/blood_DNA_to_add) //ASSOC LIST DNA = BLOODTYPE
return FALSE
-/obj/add_blood_DNA(list/dna)
+/obj/add_blood_DNA(list/blood_DNA_to_add)
. = ..()
- if(length(dna))
- . = AddComponent(/datum/component/forensics, null, null, dna)
+ if (isnull(blood_DNA_to_add))
+ return .
+ if(length(blood_DNA_to_add))
+ . = AddComponent(/datum/component/forensics, null, null, blood_DNA_to_add)
-/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+/obj/item/clothing/gloves/add_blood_DNA(list/blood_DNA_to_add, list/datum/disease/diseases)
. = ..()
transfer_blood = rand(2, 4)
@@ -77,8 +81,9 @@
var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src
if(!B)
B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases)
- B.add_blood_DNA(blood_dna) //give blood info to the blood decal.
- return TRUE //we bloodied the floor
+ if(!QDELETED(B))
+ B.add_blood_DNA(blood_dna) //give blood info to the blood decal.
+ return TRUE //we bloodied the floor
/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
if(wear_suit)
@@ -96,6 +101,11 @@
update_inv_gloves() //handles bloody hands overlays and updating
return TRUE
+/obj/effect/decal/cleanable/blood/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ . = ..()
+ if(blood_dna)
+ color = get_blood_dna_color(blood_dna)
+
/atom/proc/transfer_fingerprints_to(atom/A)
A.add_fingerprint_list(return_fingerprints())
A.add_hiddenprint_list(return_hiddenprints())
diff --git a/code/modules/economy/pay_stand.dm b/code/modules/economy/pay_stand.dm
index 1e32fe3adb1e..565956699d4e 100644
--- a/code/modules/economy/pay_stand.dm
+++ b/code/modules/economy/pay_stand.dm
@@ -1,6 +1,6 @@
/obj/machinery/paystand
name = "unregistered pay stand"
- desc = "an unregistered pay stand"
+ desc = "An unregistered pay stand, ready to be linked to an account."
icon = 'icons/obj/economy.dmi'
icon_state = "card_scanner"
anchored = TRUE
diff --git a/code/modules/events/aurora_caelus.dm b/code/modules/events/aurora_caelus.dm
index 0717d98b0f16..b6e4c3ccba8f 100644
--- a/code/modules/events/aurora_caelus.dm
+++ b/code/modules/events/aurora_caelus.dm
@@ -7,16 +7,14 @@
max_alert = SEC_LEVEL_RED
/datum/round_event_control/aurora_caelus/canSpawnEvent(players, gamemode)
- if(!CONFIG_GET(flag/starlight))
+ if(!SSmapping.empty_space)
return FALSE
return ..()
/datum/round_event/aurora_caelus
announceWhen = 1
- startWhen = 9
- endWhen = 50
- var/list/aurora_colors = list("#A2FF80", "#A2FF8B", "#A2FF96", "#A2FFA5", "#A2FFB6", "#A2FFC7", "#A2FFDE", "#A2FFEE")
- var/aurora_progress = 0 //this cycles from 1 to 8, slowly changing colors from gentle green to gentle blue
+ startWhen = 21
+ endWhen = 80
/datum/round_event/aurora_caelus/announce()
priority_announce("[station_name()]: A harmless cloud of ions is approaching your station, and will exhaust their energy battering the hull. Nanotrasen has approved a short break for all employees to relax and observe this very rare event. During this time, starlight will be bright but gentle, shifting between quiet green and blue colors. Any staff who would like to view these lights for themselves may proceed to the area nearest to them with viewing ports to open space. We hope you enjoy the lights.",
@@ -26,35 +24,92 @@
var/mob/M = V
if((M.client.prefs.toggles & SOUND_MIDI) && is_station_level(M.z))
M.playsound_local(M, 'sound/ambience/aurora_caelus.ogg', 20, FALSE, pressure_affected = FALSE)
+ fade_space(fade_in = TRUE)
+ fade_kitchen(fade_in = TRUE)
/datum/round_event/aurora_caelus/start()
- for(var/area/A as anything in GLOB.areas)
- if(initial(A.dynamic_lighting) == DYNAMIC_LIGHTING_IFSTARLIGHT)
- for(var/turf/open/space/S in A.get_contained_turfs())
- S.set_light(S.light_range * 3, S.light_power * 2)
+ if(!prob(1) && !check_holidays(APRIL_FOOLS))
+ return
+ for(var/area/crew_quarters/kitchen/affected_area in GLOB.areas)
+ var/obj/machinery/oven/roast_ruiner = locate() in affected_area
+ if(roast_ruiner)
+ roast_ruiner.balloon_alert_to_viewers("oh egads!")
+ var/turf/ruined_roast = get_turf(roast_ruiner)
+ ruined_roast.atmos_spawn_air("[GAS_PLASMA]=100;[TURF_TEMPERATURE(1000)]")
+ message_admins("Aurora Caelus event caused an oven to ignite at [ADMIN_VERBOSEJMP(ruined_roast)].")
+ log_game("Aurora Caelus event caused an oven to ignite at [loc_name(ruined_roast)].")
+ announce_to_ghosts(roast_ruiner)
+ for(var/mob/living/carbon/human/seymour as anything in GLOB.carbon_list)
+ if(seymour.mind && istype(seymour.mind.assigned_role, /datum/job/cook))
+ seymour.say("My roast is ruined!!!", forced = "ruined roast")
+ seymour.emote("scream")
/datum/round_event/aurora_caelus/tick()
- if(activeFor % 5 == 0)
- aurora_progress++
- var/aurora_color = aurora_colors[aurora_progress]
- for(var/area/A as anything in GLOB.areas)
- if(initial(A.dynamic_lighting) == DYNAMIC_LIGHTING_IFSTARLIGHT)
- for(var/turf/open/space/S in A.get_contained_turfs())
- S.set_light(l_color = aurora_color)
+ if(activeFor % 8 != 0)
+ return
+ var/aurora_color = hsl_gradient((activeFor - startWhen) / (endWhen - startWhen), 0, "#A2FF80", 1, "#A2FFEE")
+ set_starlight(aurora_color)
+
+ for(var/area/crew_quarters/kitchen/affected_area in GLOB.areas)
+ for(var/turf/open/kitchen_floor in affected_area.get_contained_turfs())
+ kitchen_floor.set_light(l_color = aurora_color)
/datum/round_event/aurora_caelus/end()
- for(var/area/A as anything in GLOB.areas)
- if(initial(A.dynamic_lighting) == DYNAMIC_LIGHTING_IFSTARLIGHT)
- for(var/turf/open/space/S in A.get_contained_turfs())
- fade_to_black(S)
+ fade_space()
+ fade_kitchen()
priority_announce("The aurora caelus event is now ending. Starlight conditions will slowly return to normal. When this has concluded, please return to your workplace and continue work as normal. Have a pleasant shift, [station_name()], and thank you for watching with us.",
sound = 'sound/misc/notice2.ogg',
sender_override = "Nanotrasen Meteorology Division")
-/datum/round_event/aurora_caelus/proc/fade_to_black(turf/open/space/S)
+/datum/round_event/aurora_caelus/proc/fade_space(fade_in = FALSE)
set waitfor = FALSE
- var/new_light = initial(S.light_range)
- while(S.light_range > new_light)
- S.set_light(S.light_range - 0.2)
- sleep(3 SECONDS)
- S.set_light(new_light, initial(S.light_power), initial(S.light_color))
+ // iterate all glass tiles
+ var/start_color = hsl_gradient(1, 0, "#A2FF80", 1, "#A2FFEE")
+ var/start_range = GLOB.starlight_range * 1.75
+ var/start_power = GLOB.starlight_power * 0.6
+ var/end_color = GLOB.base_starlight_color
+ var/end_range = GLOB.starlight_range
+ var/end_power = GLOB.starlight_power
+ if(fade_in)
+ end_color = hsl_gradient(0, 0, "#A2FF80", 1, "#A2FFEE")
+ end_range = start_range
+ end_power = start_power
+ start_color = GLOB.base_starlight_color
+ start_range = GLOB.starlight_range
+ start_power = GLOB.starlight_power
+
+ for(var/i in 1 to 5)
+ var/walked_color = hsl_gradient(i/5, 0, start_color, 1, end_color)
+ var/walked_range = LERP(start_range, end_range, i/5)
+ var/walked_power = LERP(start_power, end_power, i/5)
+ set_starlight(walked_color, walked_range, walked_power)
+ sleep(8 SECONDS)
+ set_starlight(end_color, end_range, end_power)
+
+/datum/round_event/aurora_caelus/proc/fade_kitchen(fade_in = FALSE)
+ set waitfor = FALSE
+ var/start_color = hsl_gradient(1, 0, "#A2FF80", 1, "#A2FFEE")
+ var/start_range = 1
+ var/start_power = 0.75
+ var/end_color = "#000000"
+ var/end_range = 0.5
+ var/end_power = 0
+ if(fade_in)
+ end_color = hsl_gradient(0, 0, "#A2FF80", 1, "#A2FFEE")
+ end_range = start_range
+ end_power = start_power
+ start_color = "#000000"
+ start_range = 0.5
+ start_power = 0
+
+ for(var/i in 1 to 5)
+ var/walked_color = hsl_gradient(i/5, 0, start_color, 1, end_color)
+ var/walked_range = LERP(start_range, end_range, i/5)
+ var/walked_power = LERP(start_power, end_power, i/5)
+ for(var/area/crew_quarters/kitchen/affected_area in GLOB.areas)
+ for(var/turf/open/kitchen_floor in affected_area.get_contained_turfs())
+ kitchen_floor.set_light(walked_range, walked_power, walked_color)
+ sleep(8 SECONDS)
+ for(var/area/crew_quarters/kitchen/affected_area in GLOB.areas)
+ for(var/turf/open/kitchen_floor in affected_area.get_contained_turfs())
+ kitchen_floor.set_light(end_range, end_power, end_color)
diff --git a/code/modules/events/flutes.dm b/code/modules/events/flutes.dm
index 10734d1859b7..ac328f006291 100644
--- a/code/modules/events/flutes.dm
+++ b/code/modules/events/flutes.dm
@@ -57,23 +57,23 @@
/datum/round_event/flutes/proc/flute_vis_flicker(mob/living/carbon/M)
to_chat(M, span_warning("Your vision flickers."))
- M.blur_eyes(15)
+ M.adjust_eye_blur(15)
/datum/round_event/flutes/proc/flute_headache(mob/living/carbon/M)
to_chat(M, span_warning("You get an intense headache!"))
- M.blur_eyes(15)
+ M.adjust_eye_blur(15)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 20, 30)
M.adjustStaminaLoss(15)
/datum/round_event/flutes/proc/flute_tremble(mob/living/carbon/M)
to_chat(M, span_warning("Something trembles along the edge of your vision, your eyes water, with the familiar beat of blood racing through your head."))
- M.blur_eyes(30)
+ M.adjust_eye_blur(30)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30, 35)
M.adjustStaminaLoss(30)
/datum/round_event/flutes/proc/flute_chanting(mob/living/carbon/M)
to_chat(M, "You hear faint chanting.. You feel a heavy weight upon your shoulders, as something shifts it's gaze towards you..")
- M.blur_eyes(30)
+ M.adjust_eye_blur(30)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 50, 60)
M.adjustStaminaLoss(50)
ADD_TRAIT(M, TRAIT_UNSTABLE, M)
@@ -82,7 +82,7 @@
/datum/round_event/flutes/proc/flute_starlight(mob/living/carbon/M)
to_chat(M, span_warning("As the nearest stars light your skin and your station, you can make out the faint whispers being spoken in turn with the monotone flutes playing beyond you. You feel so tired, as the struts of metal piping, the walls, the floor twist in unnatural ways, as the lights dim."))
sleep(30 SECONDS)
- M.blur_eyes(40)
+ M.adjust_eye_blur(40)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 65, 70, 80)
ADD_TRAIT(M, TRAIT_UNSTABLE, M)
//sanity = 50
@@ -108,7 +108,7 @@
to_chat(M, span_suicide("Your flesh undulates, and boils off your bones. You were blind, yet now you've seen a glimpse behind the cosmic curtain."))
//sanity = 25
REMOVE_TRAIT(M, TRAIT_UNSTABLE, M)
- M.blur_eyes(5)
+ M.adjust_eye_blur(5)
M.adjustStaminaLoss(90)
M.adjustBruteLoss(60, 70, 75, 80, 85)
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 65, 70, 75, 80, 90)
@@ -120,7 +120,7 @@
ADD_TRAIT(M, TRAIT_UNSTABLE, M)
//sanity = 1
M.SetSleeping(30)
- M.blur_eyes(40)
+ M.adjust_eye_blur(40)
M.adjustStaminaLoss(99)
to_chat(M, span_narsie("Y'HAH HT'HU THRZHZU. UA'KLL GHRT AWN ZUU!"))
M.adjustBruteLoss(60, 70, 75, 80, 85)
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index 43eb1b899109..fc5634abe628 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -89,7 +89,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
GLOB.poi_list -= src
. = ..()
-/obj/effect/immovablerod/Moved()
+/obj/effect/immovablerod/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
if((z != z_original) || (loc == destination))
qdel(src)
if(special_target && loc == get_turf(special_target))
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index 225fca834f08..bf76e07204ed 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -63,7 +63,7 @@
to_chat(M, span_alert("Your lawset has been changed by the ion storm!"))
if(prob(removeRandomLawChance))
- M.remove_law(rand(1, M.laws.get_law_amount(list(LAW_INHERENT, LAW_SUPPLIED))))
+ M.laws.remove_random_inherent_or_supplied_law()
if(prob(addIonLawChance))
var/message = ionMessage || generate_ion_law()
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index ae2b3031707c..bf23415bc2a2 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -22,15 +22,12 @@
var/paid_off = FALSE
var/ship_name = "Space Privateers Association"
var/shuttle_spawned = FALSE
- /// The beacon that the crew can shoot to cancel the event. See [/obj/item/gps/pirate] and [/obj/machinery/computer/bsa_control]
- var/obj/item/gps/pirate/beacon
/datum/round_event/pirates/setup()
var/the = prob(50) ? "The " : ""
var/start = pick(strings(PIRATE_NAMES_FILE, "ship_name_start"))
var/end = pick(strings(PIRATE_NAMES_FILE, "ship_name_end"))
ship_name = "[the][start] [end]"
- beacon = new(ship_name)
/datum/round_event/pirates/announce(fake)
priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", SSstation.announcer.get_rand_report_sound())
@@ -83,7 +80,6 @@
spawn_shuttle()
/datum/round_event/pirates/proc/spawn_shuttle()
- qdel(beacon)
shuttle_spawned = TRUE
var/list/candidates = pollGhostCandidates("Do you wish to be considered for pirate crew?", ROLE_TRAITOR)
@@ -128,34 +124,33 @@
icon = 'icons/obj/machines/dominator.dmi'
icon_state = "dominator"
density = TRUE
+ /// Is the machine siphoning right now
var/active = FALSE
- var/obj/item/gps/gps
+ /// The amount of money stored in the machine
var/credits_stored = 0
+ /// The amount of money removed per tick
var/siphon_per_tick = 5
/obj/machinery/shuttle_scrambler/Initialize(mapload)
. = ..()
- gps = new/obj/item/gps/internal/pirate(src)
- gps.tracking = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/shuttle_scrambler/process()
- if(active)
- if(is_station_level(z))
- var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
- if(D)
- var/siphoned = min(D.account_balance,siphon_per_tick)
- D.adjust_money(-siphoned)
- credits_stored += siphoned
- interrupt_research()
- else
- return
- else
- STOP_PROCESSING(SSobj,src)
+ if(!active)
+ return PROCESS_KILL
+
+ if(!is_station_level(z))
+ return
+
+ var/datum/bank_account/account = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ var/siphoned = min(account.account_balance,siphon_per_tick)
+ account.adjust_money(-siphoned)
+ credits_stored += siphoned
+ interrupt_research()
/obj/machinery/shuttle_scrambler/proc/toggle_on(mob/user)
SSshuttle.registerTradeBlockade(src)
- gps.tracking = TRUE
+ AddComponent(/datum/component/gps, "Nautical Signal")
active = TRUE
to_chat(user,span_notice("You toggle [src] [active ? "on":"off"]."))
to_chat(user,span_warning("The scrambling signal can be now tracked by GPS."))
@@ -191,20 +186,15 @@
/obj/machinery/shuttle_scrambler/proc/toggle_off(mob/user)
SSshuttle.clearTradeBlockade(src)
- gps.tracking = FALSE
active = FALSE
STOP_PROCESSING(SSobj,src)
/obj/machinery/shuttle_scrambler/update_icon_state()
- . = ..()
- if(active)
- icon_state = "dominator-blue"
- else
- icon_state = "dominator"
+ icon_state = active ? "dominator-Blue" : "dominator"
+ return ..()
/obj/machinery/shuttle_scrambler/Destroy()
toggle_off()
- QDEL_NULL(gps)
return ..()
/obj/item/gps/internal/pirate
@@ -231,7 +221,7 @@
/obj/docking_port/mobile/pirate
name = "pirate shuttle"
- id = "pirateship"
+ shuttle_id = "pirateship"
rechargeTime = 3 MINUTES
/obj/machinery/suit_storage_unit/pirate
diff --git a/code/modules/events/shuttle_catastrophe.dm b/code/modules/events/shuttle_catastrophe.dm
index 7fa850c78943..4ed74ae0b931 100644
--- a/code/modules/events/shuttle_catastrophe.dm
+++ b/code/modules/events/shuttle_catastrophe.dm
@@ -37,5 +37,5 @@
SSshuttle.load_template(new_shuttle)
SSshuttle.existing_shuttle = SSshuttle.emergency
SSshuttle.emergency.name = new_shuttle.name
- SSshuttle.action_load(new_shuttle)
+ SSshuttle.action_load(new_shuttle, replace = TRUE)
log_game("Shuttle Catastrophe set a new shuttle, [new_shuttle.name].")
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index 8ede48819396..9daaf5143401 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -126,7 +126,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
py = new_py
Show()
-/obj/effect/hallucination/simple/Moved(atom/OldLoc, Dir)
+/obj/effect/hallucination/simple/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
Show()
@@ -189,7 +189,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
for(var/turf/FT in flood_turfs)
for(var/dir in GLOB.cardinals)
var/turf/T = get_step(FT, dir)
- if((T in flood_turfs) || !FT.CanAtmosPass(T))
+ if((T in flood_turfs) || !TURFS_CAN_SHARE(T, FT))
continue
var/image/new_plasma = image(image_icon,T,image_state,FLY_LAYER)
new_plasma.alpha = 50
@@ -712,7 +712,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
// Display message
if (!is_radio && !target.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
var/image/speech_overlay = image('icons/mob/talk.dmi', person, "default0", layer = ABOVE_MOB_LAYER)
- INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay, speech_overlay, list(target.client), 30)
+ INVOKE_ASYNC(GLOBAL_PROC, /proc/flick_overlay_global, speech_overlay, list(target.client), 30)
if (target.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat))
target.create_chat_message(person, understood_language, chosen, spans)
to_chat(target, message)
@@ -1023,7 +1023,6 @@ GLOBAL_LIST_INIT(hallucination_list, list(
slots_free += ui_storage2
if(slots_free.len)
target.halitem.screen_loc = pick(slots_free)
- target.halitem.layer = ABOVE_HUD_LAYER
target.halitem.plane = ABOVE_HUD_PLANE
switch(rand(1,6))
if(1) //revolver
@@ -1120,7 +1119,7 @@ GLOBAL_LIST_INIT(hallucination_list, list(
name = "chasm"
/obj/effect/hallucination/danger/chasm/show_icon()
- image = image('icons/turf/floors/Chasms.dmi',src,"smooth",TURF_LAYER)
+ image = image('icons/turf/floors/chasms.dmi',src,"smooth",TURF_LAYER)
if(target.client)
target.client.images += image
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index 9880855e6d27..bbb28b85863c 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -3,7 +3,7 @@
////////////////////////////////////////////////////////////////////////////////
/obj/item/reagent_containers/food/drinks
name = "drink"
- desc = "yummy"
+ desc = "Yummy."
icon = 'icons/obj/drinks.dmi'
icon_state = "pineapplejuice" // Shouldn't see this anyways.
lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
diff --git a/code/modules/food_and_drinks/food/condiment.dm b/code/modules/food_and_drinks/food/condiment.dm
index 8207ccbda7c8..2301eb9217b6 100644
--- a/code/modules/food_and_drinks/food/condiment.dm
+++ b/code/modules/food_and_drinks/food/condiment.dm
@@ -392,25 +392,25 @@
/obj/item/reagent_containers/food/condiment/pack/astrotame
name = "astrotame pack"
originalname = "astrotame"
- list_reagents = list(/datum/reagent/consumable/astrotame = 5)
+ list_reagents = list(/datum/reagent/consumable/astrotame = 10)
/obj/item/reagent_containers/food/condiment/pack/bbqsauce
name = "bbq sauce pack"
originalname = "bbq sauce"
- list_reagents = list(/datum/reagent/consumable/bbqsauce = 5)
+ list_reagents = list(/datum/reagent/consumable/bbqsauce = 10)
/obj/item/reagent_containers/food/condiment/pack/sugar
name = "sugar pack"
originalname = "sugar"
- list_reagents = list(/datum/reagent/consumable/sugar = 5)
+ list_reagents = list(/datum/reagent/consumable/sugar = 10)
/obj/item/reagent_containers/food/condiment/pack/creamer
name = "creamer" /// dont laugh you child
originalname = "cream"
- list_reagents = list(/datum/reagent/consumable/cream = 5)
+ list_reagents = list(/datum/reagent/consumable/cream = 10)
/obj/item/reagent_containers/food/condiment/pack/chocolate
- name = "creamer"
- originalname = "cream"
- list_reagents = list(/datum/reagent/consumable/chocolate = 5)
+ name = "chocolate"
+ originalname = "chocolate"
+ list_reagents = list(/datum/reagent/consumable/chocolate = 10)
diff --git a/code/modules/food_and_drinks/food/snacks/meat.dm b/code/modules/food_and_drinks/food/snacks/meat.dm
index 8130272307bd..1077d59a0f94 100644
--- a/code/modules/food_and_drinks/food/snacks/meat.dm
+++ b/code/modules/food_and_drinks/food/snacks/meat.dm
@@ -133,7 +133,7 @@
foodtype = GROSS
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie
- name = " meat (rotten)"
+ name = "meat (rotten)"
icon_state = "rottenmeat"
desc = "Halfway to becoming fertilizer for your garden."
filling_color = "#6B8E23"
@@ -142,7 +142,7 @@
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
icon_state = "etherealmeat"
- desc = "So shiny you feel like ingesting it might make you shine too"
+ desc = "So shiny you feel like ingesting it might make you shine too."
filling_color = "#97ee63"
list_reagents = list(/datum/reagent/consumable/liquidelectricity = 3, /datum/reagent/consumable/tinlux = 3)
tastes = list("pure electricity" = 2, "meat" = 1)
diff --git a/code/modules/food_and_drinks/food/snacks_bread.dm b/code/modules/food_and_drinks/food/snacks_bread.dm
index a7434696a03a..a79121d78766 100644
--- a/code/modules/food_and_drinks/food/snacks_bread.dm
+++ b/code/modules/food_and_drinks/food/snacks_bread.dm
@@ -286,7 +286,7 @@
name = fried.name //We'll determine the other stuff when it's actually removed
appearance = fried.appearance
layer = initial(layer)
- plane = initial(plane)
+ SET_PLANE_IMPLICIT(src, initial(plane))
lefthand_file = fried.lefthand_file
righthand_file = fried.righthand_file
item_state = fried.item_state
diff --git a/code/modules/food_and_drinks/food/snacks_burgers.dm b/code/modules/food_and_drinks/food/snacks_burgers.dm
index 7e2686fec389..336b6dfcff7d 100644
--- a/code/modules/food_and_drinks/food/snacks_burgers.dm
+++ b/code/modules/food_and_drinks/food/snacks_burgers.dm
@@ -16,9 +16,10 @@
/obj/item/reagent_containers/food/snacks/burger/plain/Initialize(mapload)
. = ..()
if(prob(1))
- new/obj/effect/particle_effect/fluid/smoke(get_turf(src))
- playsound(src, 'sound/effects/smoke.ogg', 50, TRUE)
- visible_message(span_warning("Oh, ye gods! [src] is ruined! But what if...?"))
+ if(!mapload)
+ new/obj/effect/particle_effect/fluid/smoke(get_turf(src))
+ playsound(src, 'sound/effects/smoke.ogg', 50, TRUE)
+ visible_message(span_warning("Oh, ye gods! [src] is ruined! But what if...?"))
name = "steamed ham"
desc = pick("Ahh, Head of Personnel, welcome. I hope you're prepared for an unforgettable luncheon!",
"And you call these steamed hams despite the fact that they are obviously microwaved?",
diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm
index 4881b9e8e4f8..705ccc4a56f7 100644
--- a/code/modules/food_and_drinks/food/snacks_pastry.dm
+++ b/code/modules/food_and_drinks/food/snacks_pastry.dm
@@ -150,6 +150,12 @@
bonus_reagents = list(/datum/reagent/consumable/nutriment/vitamin = 1, /datum/reagent/consumable/ketchup = 2)
tastes = list("spaghetti"= 3, "carbs" = 2, "ketchup" = 1)
+/obj/item/reagent_containers/food/snacks/donut/vegan
+ name = "Vegan Donut"
+ desc = "Adding tofu to a donut makes it vegan, who knew?!"
+ tastes = list("donut" = 1)
+ foodtype = VEGETABLES
+
////////////////////////////////////////////MUFFINS////////////////////////////////////////////
/obj/item/reagent_containers/food/snacks/muffin
diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm
index b0b63954b023..31298dfc77ce 100644
--- a/code/modules/food_and_drinks/food/snacks_pie.dm
+++ b/code/modules/food_and_drinks/food/snacks_pie.dm
@@ -49,7 +49,7 @@
creamoverlay.icon_state = "creampie_human"
if(stunning)
H.Paralyze(20) //splat!
- H.adjust_blurriness(1)
+ H.adjust_eye_blur(1)
H.visible_message(span_warning("[H] is creamed by [src]!"), span_userdanger("You've been creamed by [src]!"))
playsound(H, "desceration", 50, TRUE)
if(!H.creamed) // one layer at a time
@@ -301,4 +301,4 @@
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 4)
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 4)
tastes = list("pie" = 1, "dark chocolate" = 3)
- foodtype = GRAIN | SUGAR | CHOCOLATE
\ No newline at end of file
+ foodtype = GRAIN | SUGAR | CHOCOLATE
diff --git a/code/modules/food_and_drinks/food/snacks_seafood.dm b/code/modules/food_and_drinks/food/snacks_seafood.dm
index d0574f6af193..46b3e93ee46f 100644
--- a/code/modules/food_and_drinks/food/snacks_seafood.dm
+++ b/code/modules/food_and_drinks/food/snacks_seafood.dm
@@ -178,7 +178,7 @@
/obj/item/reagent_containers/food/snacks/fishdumpling
name = "fish dumpling"
- desc = "a powerful little pocket of flavor."
+ desc = "A powerful little pocket of flavor."
icon_state = "fishdumpling"
bonus_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 10)
list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/nutriment/vitamin = 15) //delicious onion and garlic and fish
diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
index f757aad4fcf1..46bf78268a69 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
@@ -66,6 +66,11 @@ God bless America.
RefreshParts()
fry_loop = new(list(src), FALSE)
+/obj/machinery/deepfryer/process()
+ if(prob(0.05))
+ say("I'm SO hungry, feed me a 20 pound bag of ice!") /// don't make a scene harry
+ name = "Absolutely Famished Deep Fryer"
+
/obj/machinery/deepfryer/RefreshParts()
var/oil_efficiency
for(var/obj/item/stock_parts/micro_laser/M in component_parts)
diff --git a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
index 905c7c076214..4e629b6b2971 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
@@ -110,7 +110,7 @@
var/dat
dat += "ICE CREAM
"
dat += "Dispensing: [flavour_name] icecream
"
- dat += "Vanilla ice cream:SelectMakex5 [product_types[ICECREAM_VANILLA]] scoops left. (Ingredients: milk, ice) "
+ dat += "Vanilla ice cream:SelectMakex5 [product_types[ICECREAM_VANILLA]] scoops left. (Ingredients: milk, ice, vanilla powder) "
dat += "Strawberry ice cream:SelectMakex5 [product_types[ICECREAM_STRAWBERRY]] dollops left. (Ingredients: milk, ice, berry juice) "
dat += "Chocolate ice cream:SelectMakex5 [product_types[ICECREAM_CHOCOLATE]] dollops left. (Ingredients: milk, ice, coco powder) "
dat += "Blue ice cream:SelectMakex5 [product_types[ICECREAM_BLUE]] dollops left. (Ingredients: milk, ice, singulo) "
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index bf9da2bf7bd9..16ac98441a7f 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -4,7 +4,7 @@
/obj/machinery/smartfridge
name = "smartfridge"
desc = "Keeps cold things cold and hot things cold."
- icon = 'icons/obj/vending.dmi'
+ icon = 'icons/obj/smartfridge.dmi'
icon_state = "smartfridge"
layer = BELOW_OBJ_LAYER
density = TRUE
@@ -18,21 +18,37 @@
idle_power_usage = 5
active_power_usage = 100
circuit = /obj/item/circuitboard/machine/smartfridge
+ /// Maximum number of items that can be loaded into the machine
var/max_n_of_items = 1000
var/allow_ai_retrieve = FALSE
+ /// List of items that the machine starts with upon spawn
var/list/initial_contents
- var/full_indicator_state = "smartfridge-indicator" //the icon state for the "oh no, we're full" indicator light
- var/retrieval_state = "smartfridge-retrieve" //the icon state for the dispensing animation
- var/retrieval_time = 19 //the length (in ticks) of the retrieval_state
- var/supports_full_indicator_state = TRUE //whether or not the smartfridge supports a full inventory indicator icon state
- var/supports_retrieval_state = TRUE //whether or not the smartfridge supports a retrieval_state dispensing animation
- var/supports_capacity_indication = TRUE //whether or not the smartfridge supports 5 levels of inventory quantity indication icon states
- var/pitches = FALSE //whether or not it should use "sales pitches" similar to a vendor machine
- var/last_pitch = 0 //When did we last pitch?
- var/pitch_delay = 2000 //How long until we can pitch again?
- var/product_slogans = "" //String of slogans separated by semicolons, optional
- var/seconds_electrified = MACHINE_NOT_ELECTRIFIED //Shock users like an airlock.
- var/dispenser_arm = TRUE //whether or not the dispenser is active (wires can disable this)
+ /// Is this smartfridge going to have a glowing screen? (Drying Racks are not)
+ var/has_emissive = TRUE
+ /// the icon state for the "oh no, we're full" indicator light
+ var/full_indicator_state = "smartfridge-indicator"
+ /// the icon state for the dispensing animation
+ var/retrieval_state = "smartfridge-retrieve"
+ /// the length (in ticks) of the retrieval_state
+ var/retrieval_time = 19
+ /// whether or not the smartfridge supports a full inventory indicator icon state
+ var/supports_full_indicator_state = TRUE
+ /// whether or not the smartfridge supports a retrieval_state dispensing animation
+ var/supports_retrieval_state = TRUE
+ /// whether or not the smartfridge supports 5 levels of inventory quantity indication icon states
+ var/supports_capacity_indication = TRUE
+ /// whether or not it should use "sales pitches" similar to a vendor machine
+ var/pitches = FALSE
+ // When did we last pitch?
+ var/last_pitch = 0
+ /// How long until we can pitch again?
+ var/pitch_delay = 2000
+ /// String of slogans separated by semicolons, optional
+ var/product_slogans = ""
+ /// Shock users like an airlock.
+ var/seconds_electrified = MACHINE_NOT_ELECTRIFIED
+ /// whether or not the dispenser is active (wires can disable this)
+ var/dispenser_arm = TRUE
var/power_wire_cut = FALSE
var/list/slogan_list = list()
@@ -146,9 +162,14 @@
/obj/machinery/smartfridge/obj_break(damage_flag)
if(!(stat & BROKEN))
stat |= BROKEN
- update_appearance(UPDATE_ICON)
+ update_appearance()
..(damage_flag)
+/obj/machinery/smartfridge/update_appearance(updates=ALL)
+ . = ..()
+
+ set_light((!(stat & BROKEN) && powered()) ? MINIMUM_USEFUL_LIGHT_RANGE : 0)
+
/obj/machinery/smartfridge/update_icon_state()
. = ..()
var/startstate = initial(icon_state)
@@ -174,6 +195,26 @@
else
icon_state = "[startstate]-off"
+/obj/machinery/smartfridge/update_overlays()
+ . = ..()
+
+ // var/shown_contents_length = visible_items()
+ // if(visible_contents && shown_contents_length)
+ // var/content_level = "[initial(icon_state)]-[contents_icon_state]"
+ // switch(shown_contents_length)
+ // if(1 to 25)
+ // content_level += "-1"
+ // if(26 to 50)
+ // content_level += "-2"
+ // if(31 to INFINITY)
+ // content_level += "-3"
+ // . += mutable_appearance(icon, content_level)
+
+ // . += mutable_appearance(icon, "[initial(icon_state)]-glass[(machine_stat & BROKEN) ? "-broken" : ""]")
+
+ if(!stat && has_emissive)
+ . += emissive_appearance(icon, "[initial(icon_state)]-light-mask", src, alpha = src.alpha)
+
/obj/machinery/smartfridge/proc/animate_dispenser()
//visually animate the smartfridge dispensing an item
if (supports_retrieval_state && !(stat & (NOPOWER|BROKEN)))
@@ -199,9 +240,10 @@
return
if(default_deconstruction_screwdriver(user, icon_state, icon_state, O))
- cut_overlays()
if(panel_open)
add_overlay("[initial(icon_state)]-panel")
+ else
+ cut_overlay("[initial(icon_state)]-panel")
updateUsrDialog()
return
@@ -231,7 +273,7 @@
if(accept_check(O))
load(O)
user.visible_message("[user] has added \the [O] to \the [src].", span_notice("You add \the [O] to \the [src]."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateUsrDialog()
if(contents.len >= max_n_of_items)
indicate_full()
@@ -246,7 +288,7 @@
if(accept_check(G))
load(G)
loaded++
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateUsrDialog()
if(loaded)
@@ -273,7 +315,7 @@
load(organ)
OS.clear_organ()
user.visible_message("[user] has added \the [organ] to \the [src].", span_notice("You add \the [organ] to \the [src]."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateUsrDialog()
if(contents.len >= max_n_of_items)
indicate_full()
@@ -378,7 +420,7 @@
if(O.name == params["name"])
dispense(O, usr)
break
- update_appearance(UPDATE_ICON)
+ update_appearance()
cut_overlay(full_indicator_state)
animate_dispenser()
return TRUE
@@ -391,7 +433,7 @@
dispense(O, usr)
desired--
- update_appearance(UPDATE_ICON)
+ update_appearance()
cut_overlay(full_indicator_state)
animate_dispenser()
return TRUE
@@ -408,6 +450,7 @@
icon_state = "drying_rack"
use_power = IDLE_POWER_USE
idle_power_usage = 5
+ has_emissive = FALSE
active_power_usage = 200
supports_full_indicator_state = FALSE //whether or not the smartfridge supports a full inventory indicator icon state
supports_retrieval_state = FALSE //whether or not the smartfridge supports a retrieval_state dispensing animation
@@ -427,8 +470,13 @@
..()
/obj/machinery/smartfridge/drying_rack/RefreshParts()
+ return NONE
+
/obj/machinery/smartfridge/drying_rack/exchange_parts()
+ return NONE
+
/obj/machinery/smartfridge/drying_rack/spawn_frame()
+ return NONE
/obj/machinery/smartfridge/drying_rack/default_deconstruction_crowbar(obj/item/crowbar/C, ignore_panel = 1)
..()
@@ -455,7 +503,7 @@
/obj/machinery/smartfridge/drying_rack/ui_act(action, params)
. = ..()
if(.)
- update_appearance(UPDATE_ICON) // This is to handle a case where the last item is taken out manually instead of through drying pop-out
+ update_appearance() // This is to handle a case where the last item is taken out manually instead of through drying pop-out
return
switch(action)
if("Dry")
@@ -475,7 +523,7 @@
/obj/machinery/smartfridge/drying_rack/load() //For updating the filled overlay
..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/smartfridge/drying_rack/update_overlays()
. = ..()
@@ -489,7 +537,7 @@
if(drying)
if(rack_dry())//no need to update unless something got dried
SStgui.update_uis(src)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/smartfridge/drying_rack/accept_check(obj/item/O)
if(istype(O, /obj/item/reagent_containers/food/snacks/))
@@ -507,7 +555,7 @@
else
drying = TRUE
use_power = ACTIVE_POWER_USE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/smartfridge/drying_rack/proc/rack_dry()
for(var/obj/item/reagent_containers/food/snacks/S in src)
@@ -699,6 +747,7 @@
/obj/machinery/smartfridge/disks
name = "disk compartmentalizer"
desc = "A machine capable of storing a variety of disks. Denoted by most as the DSU (disk storage unit)."
+ icon = 'icons/obj/vending.dmi'
icon_state = "disktoaster"
product_slogans = "Toasty!;Burnt to a crisp.;Puts a new meaning to the term \"burning a disk\", eh?;Store your plant data disks here. Or any kind of disk really. I don't discriminate."
pass_flags = PASSTABLE
diff --git a/code/modules/food_and_drinks/plate.dm b/code/modules/food_and_drinks/plate.dm
index 693d0727ba54..ba6c586fa6a3 100644
--- a/code/modules/food_and_drinks/plate.dm
+++ b/code/modules/food_and_drinks/plate.dm
@@ -64,9 +64,9 @@
/obj/item/plate/proc/AddToPlate(obj/item/item_to_plate)
vis_contents += item_to_plate
item_to_plate.vis_flags |= VIS_INHERIT_PLANE
- item_to_plate.layer = ABOVE_HUD_LAYER
+ item_to_plate.plane = ABOVE_HUD_PLANE
RegisterSignal(item_to_plate, COMSIG_MOVABLE_MOVED, PROC_REF(ItemMoved))
- RegisterSignal(item_to_plate, COMSIG_PARENT_QDELETING, PROC_REF(ItemMoved))
+ RegisterSignal(item_to_plate, COMSIG_QDELETING, PROC_REF(ItemMoved))
///This proc cleans up any signals on the item when it is removed from a plate, and ensures it has the correct state again.
/obj/item/plate/proc/ItemRemovedFromPlate(obj/item/removed_item)
@@ -75,7 +75,7 @@
removed_item.vis_flags &= ~VIS_INHERIT_PLANE
removed_item.layer = OBJ_LAYER
- UnregisterSignal(removed_item, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(removed_item, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
///This proc is called by signals that remove the food from the plate.
/obj/item/plate/proc/ItemMoved(obj/item/moved_item, forced)
@@ -186,6 +186,6 @@
/obj/item/plate_shard/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/caltrop, force)
+ AddComponent(/datum/component/caltrop, min_damage = force)
-#undef PLATE_SHARD_PIECES
\ No newline at end of file
+#undef PLATE_SHARD_PIECES
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm
index bb96d03f374a..7a8be303146d 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_frozen.dm
@@ -161,7 +161,7 @@
reqs = list(
/obj/item/reagent_containers/food/drinks/sillycup = 1,
/datum/reagent/consumable/ice = 15,
- /datum/reagent/consumable/berryjuice = 5
+ /datum/reagent/consumable/grapejuice = 5
)
result = /obj/item/reagent_containers/food/snacks/snowcones/grape
category = CAT_ICE
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
index 2037e1108da6..dd62786f77e6 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -90,6 +90,15 @@ datum/crafting_recipe/food/donut/meat
result = /obj/item/reagent_containers/food/snacks/donut/laugh
category = CAT_PASTRY
+/datum/crafting_recipe/food/donut/vegan
+ name = "Vegan Donut"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/tofu = 1,
+ /obj/item/reagent_containers/food/snacks/donut = 1,
+ )
+ result = /obj/item/reagent_containers/food/snacks/donut/vegan
+ category = CAT_PASTRY
+
/datum/crafting_recipe/food/donut/jelly/laugh
name = "Sweet Pea Jelly Donut"
reqs = list(
@@ -484,7 +493,7 @@ datum/crafting_recipe/food/donut/meat
/datum/crafting_recipe/food/raw_croissant
name = "Raw Croissant"
reqs = list(
- /obj/item/reagent_containers/food/snacks/pastrybase = 1,
+ /obj/item/reagent_containers/food/snacks/rawpastrybase = 1,
/datum/reagent/consumable/sugar = 1,
/obj/item/reagent_containers/food/snacks/butterslice = 2
)
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm
index f993e907daf7..9588360be093 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pizza.dm
@@ -9,7 +9,7 @@
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/meat/raw_cutlet = 3,
/obj/item/ammo_casing/c9mm = 8,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/arnold/raw
@@ -20,7 +20,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/grown/cannabis = 3,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/dank/raw
@@ -31,7 +31,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/donkpocket/warm = 3,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/donkpocket/raw
@@ -43,7 +43,7 @@
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/meat/raw_cutlet = 2,
/obj/item/reagent_containers/food/snacks/pineappleslice = 3,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/pineapple/raw
@@ -53,7 +53,7 @@
name = "Margherita Pizza"
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 4,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 4,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/margherita/raw
@@ -64,7 +64,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/meat/raw_cutlet = 4,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/meat/raw
@@ -75,7 +75,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/grown/mushroom = 5,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/mushroom/raw
@@ -86,7 +86,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/raw_meatball = 3,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/sassysage/raw
@@ -97,7 +97,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/flatdough = 1,
/obj/item/reagent_containers/food/snacks/fish/tuna = 1,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1,
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/seafood/raw
@@ -111,7 +111,7 @@
/obj/item/reagent_containers/food/snacks/grown/carrot = 1,
/obj/item/reagent_containers/food/snacks/grown/corn = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1,
- /obj/item/reagent_containers/food/snacks/cheesewedge = 1
+ /obj/item/reagent_containers/food/snacks/cheesewedge/mozzarella = 1
)
result = /obj/item/reagent_containers/food/snacks/pizza/vegetable/raw
category = CAT_PIZZA
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
index e27f9626b489..2319f58ee875 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_soup.dm
@@ -251,6 +251,7 @@
name = "Pea soup"
reqs = list(
/datum/reagent/water = 10,
+ /obj/item/reagent_containers/glass/bowl = 1,
/obj/item/reagent_containers/food/snacks/grown/peas = 2,
/obj/item/reagent_containers/food/snacks/grown/parsnip = 1,
/obj/item/reagent_containers/food/snacks/grown/carrot = 1
diff --git a/code/modules/goals/station_goals/bluespace_tap.dm b/code/modules/goals/station_goals/bluespace_tap.dm
index 1e15f1ca6a1a..a489004d4700 100644
--- a/code/modules/goals/station_goals/bluespace_tap.dm
+++ b/code/modules/goals/station_goals/bluespace_tap.dm
@@ -205,10 +205,11 @@
/obj/machinery/power/bluespace_tap
name = "Bluespace harvester"
icon = 'icons/obj/machines/bluespace_tap.dmi'
- icon_state = "bluespace_tap" //sprites by Ionward
+ icon_state = "bluespace_tap"
+ base_icon_state = "bluespace_tap"
max_integrity = 300
pixel_x = -32 //shamelessly stolen from dna vault
- pixel_y = -64
+ pixel_y = -32
/// For faking having a big machine, dummy 'machines' that are hidden inside the large sprite and make certain tiles dense. See new and destroy.
var/list/obj/structure/fillers = list()
use_power = NO_POWER_USE // power usage is handelled manually
@@ -251,6 +252,8 @@
var/base_points = 100
/// How high the machine can be run before it starts having a chance for dimension breaches.
var/safe_levels = 10
+ /// When event triggers this will hold references to all portals so we can fix the sprite after they're broken
+ var/list/active_nether_portals = list()
var/emagged = FALSE
/// Cooldown to prevent spamming portal spawns without an emag
@@ -261,10 +264,8 @@
//more code stolen from dna vault, inculding comment below. Taking bets on that datum being made ever.
//TODO: Replace this,bsa and gravgen with some big machinery datum
var/list/occupied = list()
- for(var/direct in list(EAST, WEST, SOUTHEAST, SOUTHWEST))
+ for(var/direct in list(NORTH, NORTHEAST, NORTHWEST, EAST, WEST, SOUTHEAST, SOUTHWEST))
occupied += get_step(src, direct)
- occupied += locate(x + 1, y - 2, z)
- occupied += locate(x - 1, y - 2, z)
for(var/T in occupied)
var/obj/structure/filler/F = new(T)
@@ -278,6 +279,72 @@
if(!powernet)
connect_to_network()
+/obj/machinery/power/bluespace_tap/update_icon_state()
+ . = ..()
+
+ if(length(active_nether_portals))
+ icon_state = "redspace_tap"
+ return
+
+ if(avail() <= 0)
+ icon_state = base_icon_state
+ else
+ icon_state = "[base_icon_state][get_icon_state_number()]"
+
+
+/obj/machinery/power/bluespace_tap/update_overlays()
+ . = ..()
+
+ underlays.Cut()
+
+ if(length(active_nether_portals))
+ . += "redspace"
+ . += "redspace_flash"
+ set_light(15, 5, "#ff0000")
+ return
+
+ if(stat & (BROKEN|NOPOWER))
+ set_light(0)
+ else
+ set_light(1, 1, "#353535")
+
+ if(avail())
+ . += "screen"
+ if(light)
+ underlays += mutable_appearance(icon, "light_mask")
+
+
+/obj/machinery/power/bluespace_tap/proc/get_icon_state_number()
+ switch(input_level)
+ if(0)
+ return 0
+ if(1 to 2)
+ return 1
+ if(3 to 5)
+ return 2
+ if(6 to 7)
+ return 3
+ if(8 to 10)
+ return 4
+ if(11 to INFINITY)
+ return 5
+
+/obj/machinery/power/bluespace_tap/power_change()
+ . = ..()
+ if(stat & (BROKEN|NOPOWER))
+ set_light(0)
+ else
+ set_light(1, 1, "#353535")
+
+
+/obj/machinery/power/bluespace_tap/connect_to_network()
+ . = ..()
+ update_appearance(UPDATE_ICON)
+
+/obj/machinery/power/bluespace_tap/disconnect_from_network()
+ . = ..()
+ update_appearance(UPDATE_ICON)
+
/obj/machinery/power/bluespace_tap/Destroy()
QDEL_LIST(fillers)
return ..()
@@ -322,9 +389,6 @@
* * t_level - The level we try to set it at, between 0 and max_level
*/
/obj/machinery/power/bluespace_tap/proc/set_level(t_level)
- if(!COOLDOWN_FINISHED(src, emergency_shutdown))
- desired_level = 0
- return
if(t_level < 0)
return
if(t_level > max_level)
@@ -344,9 +408,14 @@
return power_needs[i_level]
/obj/machinery/power/bluespace_tap/process()
+ if(avail() && icon_state == "bluespace_tap")
+ update_appearance(UPDATE_ICON)
+ else if(!avail() && icon_state == "bluespace_tap0")
+ update_appearance(UPDATE_ICON)
actual_power_usage = get_power_use(input_level)
if(surplus() < actual_power_usage) //not enough power, so turn down a level
input_level--
+ update_appearance(UPDATE_ICON)
return // and no mining gets done
if(actual_power_usage)
add_load(actual_power_usage)
@@ -356,17 +425,26 @@
// actual input level changes slowly
if(input_level < desired_level && (surplus() >= get_power_use(input_level + 1)))
input_level++
+ update_appearance(UPDATE_ICON)
else if(input_level > desired_level)
input_level--
+ update_appearance(UPDATE_ICON)
if(prob(input_level - safe_levels + (emagged * 5))) //at dangerous levels, start doing freaky shit. prob with values less than 0 treat it as 0
priority_announce("Unexpected power spike during Bluespace Harvester Operation. Extra-dimensional intruder alert. Expected location: [get_area_name(src)]. [emagged ? "DANGER: Emergency shutdown failed! Please proceed with manual shutdown." : "Emergency shutdown initiated."]", "Bluespace Harvester Malfunction",sound = SSstation.announcer.get_rand_report_sound())
if(!emagged)
input_level = 0 //emergency shutdown unless we're sabotaged
desired_level = 0
- COOLDOWN_START(src, emergency_shutdown, 10 MINUTES)
- for(var/i in 1 to rand(1, 3))
- var/turf/location = locate(x + rand(-5, 5), y + rand(-5, 5), z)
- new /obj/structure/spawner/nether/bluespace_tap(location)
+ start_nether_portaling(rand(3,5))
+
+/obj/machinery/power/bluespace_tap/proc/start_nether_portaling(amount)
+ var/turf/location = locate(x + rand(-5, -2) || rand(2, 5), y + rand(-5, -2) || rand(2, 5), z)
+ var/obj/structure/spawner/nether/bluespace_tap/P = new /obj/structure/spawner/nether/bluespace_tap(location)
+ amount--
+ active_nether_portals += P
+ P.linked_source_object = src
+ update_appearance(UPDATE_ICON)
+ if(amount)
+ addtimer(CALLBACK(src, PROC_REF(start_nether_portaling), amount), rand(3,5) SECONDS)
@@ -398,12 +476,18 @@
/obj/machinery/power/bluespace_tap/attack_hand(mob/user)
add_fingerprint(user)
+ if(length(active_nether_portals)) //this would be cool if we made unique TGUI for this
+ to_chat(user, span_warning("UNKNOWN INTERFERENCE ... UNRESPONSIVE"))
+ return
ui_interact(user)
/obj/machinery/power/bluespace_tap/attack_ghost(mob/user)
ui_interact(user)
/obj/machinery/power/bluespace_tap/attack_ai(mob/user)
+ if(length(active_nether_portals)) //this would be cool if we made unique TGUI for this
+ to_chat(user, span_warning("UNKNOWN INTERFERENCE ... UNRESPONSIVE"))
+ return
ui_interact(user)
/**
@@ -419,8 +503,9 @@
return
points -= A.product_cost
playsound(src, 'sound/magic/blink.ogg', 50)
+ flick_overlay_view(image(icon, src, "flash", layer+1), src, 6)
do_sparks(2, FALSE, src)
- new A.product_path(get_step(src,(dir)))
+ new A.product_path(get_turf(src))
@@ -454,19 +539,26 @@
emagged = TRUE
do_sparks(5, FALSE, src)
if(user)
- user.visible_message("[user] overrides the safety protocols of [src].", "You override the safety protocols.")
- COOLDOWN_RESET(src, emergency_shutdown)
+ user.visible_message(span_warning("[user] overrides the safety protocols of [src]."), span_warning("You override the safety protocols."))
return TRUE
/obj/structure/spawner/nether/bluespace_tap
spawn_time = 30 SECONDS
max_mobs = 5 //Dont' want them overrunning the station
max_integrity = 250
+ /// the BSH that spawned this portal
+ var/obj/machinery/power/bluespace_tap/linked_source_object
/obj/structure/spawner/nether/bluespace_tap/deconstruct(disassembled)
new /obj/item/stack/ore/bluespace_crystal(loc) //have a reward
return ..()
+/obj/structure/spawner/nether/bluespace_tap/Destroy()
+ . = ..()
+ if(linked_source_object)
+ linked_source_object.active_nether_portals -= src
+ linked_source_object.update_appearance(UPDATE_ICON)
+
/obj/item/paper/bluespace_tap
name = "paper- 'The Experimental NT Bluespace Harvester - Mining other universes for science and profit!'"
info = "
Important Instructions!
Please follow all setup instructions to ensure proper operation. \
diff --git a/code/modules/goals/station_goals/bsa.dm b/code/modules/goals/station_goals/bsa.dm
index 887252463c37..9ef438e33d16 100644
--- a/code/modules/goals/station_goals/bsa.dm
+++ b/code/modules/goals/station_goals/bsa.dm
@@ -255,21 +255,26 @@
if("recalibrate")
calibrate(usr)
. = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/computer/bsa_control/proc/calibrate(mob/user)
if(!GLOB.bsa_unlock)
return
var/list/gps_locators = list()
- for(var/obj/item/gps/G in GLOB.GPS_list) //nulls on the list somehow
+ for(var/datum/component/gps/G in GLOB.GPS_list) //nulls on the list somehow
if(G.tracking)
gps_locators[G.gpstag] = G
var/list/options = gps_locators
if(area_aim)
options += GLOB.teleportlocs
- var/V = input(user,"Select target", "Select target",null) in options|null
- target = options[V]
+ var/victim = tgui_input_list(user, "Select target", "Artillery Targeting", options)
+ if(isnull(victim))
+ return
+ if(isnull(options[victim]))
+ return
+ target = options[victim]
+ log_game("[key_name(user)] has aimed the bluespace artillery strike at [target].")
/obj/machinery/computer/bsa_control/proc/get_target_name()
@@ -278,12 +283,20 @@
else if(istype(target, /obj/item/gps))
var/obj/item/gps/G = target
return G.gpstag
+ else if(istype(target, /datum/component/gps))
+ var/datum/component/gps/G = target
+ return G.gpstag
/obj/machinery/computer/bsa_control/proc/get_impact_turf()
- if(istype(target, /area))
+ if(obj_flags & EMAGGED)
+ return get_turf(src)
+ else if(istype(target, /area))
return pick(get_area_turfs(target))
else if(istype(target, /obj/item/gps))
return get_turf(target)
+ else if(istype(target, /datum/component/gps))
+ var/datum/component/gps/G = target
+ return get_turf(G.parent)
/**
* Fires the BSA (duh) if it has power
@@ -298,13 +311,8 @@
notice = "Cannon unpowered!"
return
notice = null
- if(istype(target, /obj/item/gps/pirate))
- var/obj/item/gps/pirate/p = target
- p.on_shoot()
- cannon.fire(user, cannon.get_target_turf())
- target = null
- return
- cannon.fire(user, get_impact_turf())
+ var/turf/target_turf = get_impact_turf()
+ cannon.fire(user, target_turf)
/obj/machinery/computer/bsa_control/proc/deploy(force=FALSE)
var/obj/machinery/bsa/full/prebuilt = locate() in range(7) //In case of adminspawn
diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm
index 0c38777f4604..46ad9c6b227c 100644
--- a/code/modules/holiday/easter.dm
+++ b/code/modules/holiday/easter.dm
@@ -214,6 +214,7 @@
/datum/crafting_recipe/food/mammi
name = "Mammi"
reqs = list(
+ /obj/item/reagent_containers/glass/bowl = 1,
/obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/chocolatebar = 1,
/datum/reagent/consumable/milk = 5
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index 48094ebea3b5..3620c7c24f30 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -1,18 +1,38 @@
/datum/holiday
+ ///Name of the holiday itself. Visible to players.
var/name = "Bugsgiving"
+ ///What day of begin_month does the holiday begin on?
var/begin_day = 1
+ ///What month does the holiday begin on?
var/begin_month = 0
- var/end_day = 0 // Default of 0 means the holiday lasts a single day
+ /// What day of end_month does the holiday end? Default of 0 means the holiday lasts a single.
+ var/end_day = 0
+ /// What month does the holiday end on?
var/end_month = 0
- var/begin_week = FALSE //If set to a number, then this holiday will begin on certain week
- var/begin_weekday = FALSE //If set to a weekday, then this will trigger the holiday on the above week
- var/always_celebrate = FALSE // for christmas neverending, or testing.
+ ///If set to a number, then this holiday will begin on certain week
+ var/begin_week = FALSE
+ ///If set to a weekday, then this will trigger the holiday on the above week
+ var/begin_weekday = FALSE
+ ///for christmas neverending, or testing.
+ var/always_celebrate = FALSE
+ /// Held variable to better calculate when certain holidays may fall on, like easter.
var/current_year = 0
+ ///How many years are you offsetting your calculations for begin_day and end_day on. Used for holidays like easter.
var/year_offset = 0
- var/obj/item/drone_hat //If this is defined, drones without a default hat will spawn with this one during the holiday; check drones_as_items.dm to see this used
+ ///Timezones this holiday is celebrated in (defaults to three timezones spanning a 50 hour window covering all timezones)
+ var/list/timezones = list(TIMEZONE_LINT, TIMEZONE_UTC, TIMEZONE_ANYWHERE_ON_EARTH)
+ ///If this is defined, drones without a default hat will spawn with this one during the holiday; check drones_as_items.dm to see this used
+ var/obj/item/drone_hat
///When this holiday is active, does this prevent mail from arriving to cargo? Try not to use this for longer holidays.
var/mail_holiday = FALSE
+ var/poster_name = "generic celebration poster"
+ var/poster_desc = "A poster for celebrating some holiday. Unfortunately, its unfinished, so you can't see what the holiday is."
+ var/poster_icon = "holiday_unfinished"
+ /// Color scheme for this holiday
+ var/list/holiday_colors
+ /// The default pattern of the holiday, if the requested pattern is null.
+ var/holiday_pattern = PATTERN_DEFAULT
// This proc gets run before the game starts when the holiday is activated. Do festive shit here.
/datum/holiday/proc/celebrate()
@@ -66,6 +86,31 @@
return FALSE
+/// Procs to return holiday themed colors for recoloring atoms
+/datum/holiday/proc/get_holiday_colors(atom/thing_to_color, pattern = holiday_pattern)
+ if(!holiday_colors)
+ return
+ switch(pattern)
+ if(PATTERN_DEFAULT)
+ return holiday_colors[(thing_to_color.y % holiday_colors.len) + 1]
+ if(PATTERN_VERTICAL_STRIPE)
+ return holiday_colors[(thing_to_color.x % holiday_colors.len) + 1]
+
+/proc/request_holiday_colors(atom/thing_to_color, pattern)
+ switch(pattern)
+ if(PATTERN_RANDOM)
+ return "#[random_short_color()]"
+ if(PATTERN_RAINBOW)
+ var/datum/holiday/pride_week/rainbow_datum = new()
+ return rainbow_datum.get_holiday_colors(thing_to_color, PATTERN_DEFAULT)
+ if(!length(GLOB.holidays))
+ return
+ for(var/holiday_key in GLOB.holidays)
+ var/datum/holiday/holiday_real = GLOB.holidays[holiday_key]
+ if(!holiday_real.holiday_colors)
+ continue
+ return holiday_real.get_holiday_colors(thing_to_color, pattern || holiday_real.holiday_pattern)
+
// The actual holidays
/datum/holiday/new_year
@@ -103,7 +148,8 @@
lobby_music = list(
"https://www.youtube.com/watch?v=cEwZpejd4rM", // Charlie Wilson - Forever Valentine
"https://www.youtube.com/watch?v=4j-cPYewjn4", // David Bowie - Valentine's Day
- "https://www.youtube.com/watch?v=yvUPEW8bdHA" // On The Street Where You Live
+ "https://www.youtube.com/watch?v=yvUPEW8bdHA", // On The Street Where You Live
+ "https://www.youtube.com/watch?v=J2w8Gubp3x0" // Tim McMorris - Be My Valentine
)
/datum/holiday/valentines/getStationPrefix()
@@ -178,6 +224,12 @@
begin_day = 17
begin_month = MARCH
drone_hat = /obj/item/clothing/head/soft/green
+ holiday_colors = list(
+ COLOR_IRISH_GREEN,
+ COLOR_WHITE,
+ COLOR_IRISH_ORANGE,
+ )
+ holiday_pattern = PATTERN_VERTICAL_STRIPE
/datum/holiday/no_this_is_patrick/getStationPrefix()
return pick("Blarney","Green","Leprechaun","Booze","Pub","IRA")
@@ -201,6 +253,9 @@
/datum/holiday/april_fools/celebrate()
SSjob.set_overflow_role("Clown")
+/datum/holiday/april_fools/get_holiday_colors(atom/thing_to_color)
+ return "#[random_short_color()]"
+
/datum/holiday/april_fools/greet()
return "NOTICE: Yogstation will be down from April 2nd to April 5th as we transfer to the Source engine. Please join our discord for more info."
@@ -217,6 +272,11 @@
name = "Four-Twenty"
begin_day = 20
begin_month = APRIL
+ holiday_colors = list(
+ COLOR_ETHIOPIA_GREEN,
+ COLOR_ETHIOPIA_YELLOW,
+ COLOR_ETHIOPIA_RED,
+ )
/datum/holiday/fourtwenty/getStationPrefix()
return pick("Snoop","Blunt","Toke","Dank","Cheech","Chong")
@@ -273,6 +333,21 @@
/datum/holiday/summersolstice/greet()
return "Happy Summer Solstice!"
+/datum/holiday/pride_week
+ name = PRIDE_WEEK
+ begin_month = JUNE
+ // Stonewall was June 28th, this captures its week.
+ begin_day = 23
+ end_day = 29
+ holiday_colors = list(
+ COLOR_PRIDE_PURPLE,
+ COLOR_PRIDE_BLUE,
+ COLOR_PRIDE_GREEN,
+ COLOR_PRIDE_YELLOW,
+ COLOR_PRIDE_ORANGE,
+ COLOR_PRIDE_RED,
+ )
+
/datum/holiday/doctor
name = "Doctor's Day"
begin_day = 1
@@ -300,6 +375,7 @@
/datum/holiday/USA
name = "Independence Day"
+ timezones = list(TIMEZONE_EDT, TIMEZONE_CDT, TIMEZONE_MDT, TIMEZONE_MST, TIMEZONE_PDT, TIMEZONE_AKDT, TIMEZONE_HDT, TIMEZONE_HST)
begin_day = 4
begin_month = JULY
lobby_music = list(
@@ -313,6 +389,14 @@
"https://www.youtube.com/watch?v=kQzdJUiALBk" // wyoming - In the Shadow of the Valley - Don Burnham
)
mail_holiday = TRUE
+ holiday_colors = list(
+ COLOR_OLD_GLORY_BLUE,
+ COLOR_OLD_GLORY_RED,
+ COLOR_WHITE,
+ COLOR_OLD_GLORY_RED,
+ COLOR_WHITE,
+ )
+
/datum/holiday/USA/getStationPrefix()
return pick("Independent","American","Burger","Bald Eagle","Star-Spangled", "Fireworks")
@@ -323,6 +407,7 @@
/datum/holiday/france
name = "Bastille Day"
+ timezones = list(TIMEZONE_CEST)
begin_day = 14
begin_month = JULY
drone_hat = /obj/item/clothing/head/beret
@@ -333,6 +418,12 @@
"https://www.youtube.com/watch?v=o3wivTC1gOw", // Bonjour mon vieux Paris
)
mail_holiday = TRUE
+ holiday_colors = list(
+ COLOR_FRENCH_BLUE,
+ COLOR_WHITE,
+ COLOR_FRENCH_RED
+ )
+ holiday_pattern = PATTERN_VERTICAL_STRIPE
/datum/holiday/france/getStationPrefix()
return pick("Francais","Fromage", "Zut", "Merde")
@@ -427,6 +518,7 @@
"https://www.youtube.com/watch?v=fixc63xMXeY", // Plok Boss Theme - HD Remastered
"https://www.youtube.com/watch?v=bRLML36HnzU" // Monster Mash
)
+ holiday_colors = list(COLOR_MOSTLY_PURE_ORANGE, COLOR_PRISONER_BLACK)
/datum/holiday/halloween/shouldCelebrate(dd, mm, yy, ww, ddd)
. = ..()
@@ -452,6 +544,11 @@
begin_day = 6
begin_month = NOVEMBER
end_day = 7
+ holiday_colors = list(
+ COLOR_MEDIUM_DARK_RED,
+ COLOR_GOLD,
+ COLOR_MEDIUM_DARK_RED,
+ )
/datum/holiday/october_revolution/getStationPrefix()
return pick("Communist", "Soviet", "Bolshevik", "Socialist", "Red", "Workers'")
@@ -612,6 +709,10 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and
)
mail_holiday = TRUE
+ holiday_colors = list(
+ COLOR_CHRISTMAS_GREEN,
+ COLOR_CHRISTMAS_RED,
+ )
/datum/holiday/xmas/getStationPrefix()
return pick(
diff --git a/code/modules/holodeck/area_copy.dm b/code/modules/holodeck/area_copy.dm
index cc92d7637050..8261a223c21e 100644
--- a/code/modules/holodeck/area_copy.dm
+++ b/code/modules/holodeck/area_copy.dm
@@ -77,6 +77,6 @@
if(toupdate.len)
for(var/turf/T1 in toupdate)
- T1.ImmediateCalculateAdjacentTurfs()
+ T1.immediate_calculate_adjacent_turfs()
return copiedobjs
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index 8d2fc26e966d..fda9e32c3c5e 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -224,7 +224,7 @@
/obj/machinery/computer/holodeck/proc/floorcheck()
for(var/turf/T in linked)
- if(!T.intact || isspaceturf(T))
+ if(T.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE || isspaceturf(T))
return FALSE
return TRUE
diff --git a/code/modules/holodeck/holodeck_map_templates.dm b/code/modules/holodeck/holodeck_map_templates.dm
new file mode 100644
index 000000000000..e7354ceb70f4
--- /dev/null
+++ b/code/modules/holodeck/holodeck_map_templates.dm
@@ -0,0 +1,183 @@
+
+/datum/map_template/holodeck
+ var/template_id
+ var/description
+ var/restricted = FALSE
+ var/datum/parsed_map/lastparsed
+
+ should_place_on_top = FALSE
+ returns_created_atoms = TRUE
+ keep_cached_map = TRUE
+
+ var/obj/machinery/computer/holodeck/linked
+
+/datum/map_template/holodeck/offline
+ name = "Holodeck - Offline"
+ template_id = "holodeck_offline"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_offline.dmm"
+
+/datum/map_template/holodeck/emptycourt
+ name = "Holodeck - Empty Court"
+ template_id = "holodeck_emptycourt"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_emptycourt.dmm"
+
+/datum/map_template/holodeck/dodgeball
+ name = "Holodeck - Dodgeball Court"
+ template_id = "holodeck_dodgeball"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_dodgeball.dmm"
+
+/datum/map_template/holodeck/basketball
+ name = "Holodeck - Basketball Court"
+ template_id = "holodeck_basketball"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_basketball.dmm"
+
+/datum/map_template/holodeck/thunderdome
+ name = "Holodeck - Thunderdome Arena"
+ template_id = "holodeck_thunderdome"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_thunderdome.dmm"
+
+/datum/map_template/holodeck/beach
+ name = "Holodeck - Beach"
+ template_id = "holodeck_beach"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_beach.dmm"
+
+/datum/map_template/holodeck/lounge
+ name = "Holodeck - Lounge"
+ template_id = "holodeck_lounge"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_lounge.dmm"
+
+/datum/map_template/holodeck/petpark
+ name = "Holodeck - Pet Park"
+ template_id = "holodeck_petpark"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_petpark.dmm"
+
+/datum/map_template/holodeck/firingrange
+ name = "Holodeck - Firing Range"
+ template_id = "holodeck_firingrange"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_firingrange.dmm"
+
+/datum/map_template/holodeck/anime_school
+ name = "Holodeck - Anime School"
+ template_id = "holodeck_animeschool"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_animeschool.dmm"
+
+/datum/map_template/holodeck/chapelcourt
+ name = "Holodeck - Chapel Courtroom"
+ template_id = "holodeck_chapelcourt"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_chapelcourt.dmm"
+
+/datum/map_template/holodeck/spacechess
+ name = "Holodeck - Space Chess"
+ template_id = "holodeck_spacechess"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_spacechess.dmm"
+
+/datum/map_template/holodeck/spacecheckers
+ name = "Holodeck - Space Checkers"
+ template_id = "holodeck_spacecheckers"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_spacecheckers.dmm"
+
+/datum/map_template/holodeck/kobayashi
+ name = "Holodeck - Kobayashi Maru"
+ template_id = "holodeck_kobayashi"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_kobayashi.dmm"
+
+/datum/map_template/holodeck/winterwonderland
+ name = "Holodeck - Winter Wonderland"
+ template_id = "holodeck_winterwonderland"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_winterwonderland.dmm"
+
+/datum/map_template/holodeck/photobooth
+ name = "Holodeck - Photobooth"
+ template_id = "holodeck_photobooth"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_photobooth.dmm"
+
+/datum/map_template/holodeck/skatepark
+ name = "Holodeck - Skatepark"
+ template_id = "holodeck_skatepark"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_skatepark.dmm"
+
+/datum/map_template/holodeck/microwave
+ name = "Holodeck - Microwave Paradise"
+ template_id = "holodeck_microwave"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_microwave.dmm"
+
+/datum/map_template/holodeck/baseball
+ name = "Holodeck - Baseball Field"
+ template_id = "holodeck_baseball"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_baseball.dmm"
+
+/datum/map_template/holodeck/card_battle
+ name = "Holodeck - TGC Battle Arena"
+ template_id = "holodeck_card_battle"
+ description = "An arena for playing Tactical Game Cards."
+ mappath = "_maps/templates/holodeck_card_battle.dmm"
+
+//bad evil no good programs
+
+/datum/map_template/holodeck/medicalsim
+ name = "Holodeck - Emergency Medical"
+ template_id = "holodeck_medicalsim"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_medicalsim.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/thunderdome1218
+ name = "Holodeck - 1218 AD"
+ template_id = "holodeck_thunderdome1218"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_thunderdome1218.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/burntest
+ name = "Holodeck - Atmospheric Burn Test"
+ template_id = "holodeck_burntest"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_burntest.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/wildlifesim
+ name = "Holodeck - Wildlife Simulation"
+ template_id = "holodeck_wildlifesim"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_wildlifesim.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/holdoutbunker
+ name = "Holodeck - Holdout Bunker"
+ template_id = "holodeck_holdoutbunker"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_holdoutbunker.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/anthophillia
+ name = "Holodeck - Anthophillia"
+ template_id = "holodeck_anthophillia"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_anthophillia.dmm"
+ restricted = TRUE
+
+/datum/map_template/holodeck/refuelingstation
+ name = "Holodeck - Refueling Station"
+ template_id = "holodeck_refuelingstation"
+ description = "benis"
+ mappath = "_maps/templates/holodeck_refuelingstation.dmm"
+ restricted = TRUE
diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm
index 1ac4884134ef..c0b8bd142596 100644
--- a/code/modules/holodeck/turfs.dm
+++ b/code/modules/holodeck/turfs.dm
@@ -89,18 +89,18 @@
icon_state = SPACE_ICON_STATE // so realistic
. = ..()
-/turf/open/floor/holofloor/hyperspace
- name = "\proper hyperspace"
+/turf/open/floor/holofloor/bluespace
+ name = "\proper bluespace"
icon = 'icons/turf/space.dmi'
icon_state = "speedspace_ns_1"
bullet_bounce_sound = null
tiled_dirt = FALSE
-/turf/open/floor/holofloor/hyperspace/Initialize(mapload)
+/turf/open/floor/holofloor/bluespace/Initialize(mapload)
icon_state = "speedspace_ns_[(x + 5*y + (y%2+1)*7)%15+1]"
. = ..()
-/turf/open/floor/holofloor/hyperspace/ns/Initialize(mapload)
+/turf/open/floor/holofloor/bluespace/ns/Initialize(mapload)
. = ..()
icon_state = "speedspace_ns_[(x + 5*y + (y%2+1)*7)%15+1]"
@@ -108,23 +108,23 @@
name = "carpet"
desc = "Electrically inviting."
icon = 'icons/turf/floors/carpet.dmi'
- icon_state = "carpet"
+ icon_state = "carpet-255"
+ base_icon_state = "carpet"
floor_tile = /obj/item/stack/tile/carpet
- smooth = SMOOTH_TRUE
- canSmoothWith = null
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_CARPET
+ canSmoothWith = SMOOTH_GROUP_CARPET
bullet_bounce_sound = null
tiled_dirt = FALSE
/turf/open/floor/holofloor/carpet/Initialize(mapload)
. = ..()
- addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, update_icon)), 1)
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, update_appearance)), 1)
/turf/open/floor/holofloor/carpet/update_icon(updates=ALL)
. = ..()
- if(!.)
- return FALSE
- if(intact)
- queue_smooth(src)
+ if((updates & UPDATE_SMOOTHING) && overfloor_placed && smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH(src)
/turf/open/floor/holofloor/wood
icon_state = "wood"
diff --git a/code/modules/hydroponics/grown/nettle.dm b/code/modules/hydroponics/grown/nettle.dm
index f94de039f47a..f53b16f47ab1 100644
--- a/code/modules/hydroponics/grown/nettle.dm
+++ b/code/modules/hydroponics/grown/nettle.dm
@@ -114,4 +114,4 @@
to_chat(M, span_danger("You are blinded by the powerful acid of [src]!"))
log_combat(user, M, "attacked", src)
- M.adjust_blurriness(force/7)
+ M.adjust_eye_blur(force/7)
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index 9bf186af6b50..693dbe5c2f6a 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -138,10 +138,61 @@
max_integrity = 30
density = FALSE
anchored = TRUE
+ buckle_lying = 90
+ /// Overlay we apply when impaling a mob.
+ var/mutable_appearance/stab_overlay
/obj/structure/punji_sticks/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/caltrop, 20, 30, 100, CALTROP_BYPASS_SHOES)
+ AddComponent(/datum/component/caltrop, min_damage = 20, max_damage = 30, flags = CALTROP_BYPASS_SHOES)
+ build_stab_overlay()
+
+/obj/structure/punji_sticks/proc/build_stab_overlay()
+ stab_overlay = mutable_appearance(icon, "[icon_state]_stab", layer = ABOVE_MOB_LAYER)
+
+/obj/structure/punji_sticks/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ build_stab_overlay()
+ update_appearance()
+
+/obj/structure/punji_sticks/post_buckle_mob(mob/living/M)
+ update_appearance()
+ return ..()
+
+/obj/structure/punji_sticks/post_unbuckle_mob(mob/living/M)
+ update_appearance()
+ return ..()
+
+/obj/structure/punji_sticks/update_overlays()
+ . = ..()
+ if(length(buckled_mobs))
+ . += stab_overlay
+
+/obj/structure/punji_sticks/intercept_zImpact(list/falling_movables, levels)
+ . = ..()
+ for(var/mob/living/fallen_mob in falling_movables)
+ if(LAZYLEN(buckled_mobs))
+ return
+ if(buckle_mob(fallen_mob, TRUE))
+ to_chat(fallen_mob, span_userdanger("You are impaled by [src]!"))
+ fallen_mob.apply_damage(25 * levels, BRUTE, sharpness = SHARP_POINTY)
+ if(iscarbon(fallen_mob))
+ var/mob/living/carbon/fallen_carbon = fallen_mob
+ fallen_carbon.emote("scream")
+ fallen_carbon.bleed(30)
+ . |= FALL_INTERCEPTED | FALL_NO_MESSAGE
+
+/obj/structure/punji_sticks/unbuckle_mob(mob/living/buckled_mob, force, can_fall)
+ if(force)
+ return ..()
+ to_chat(buckled_mob, span_warning("You begin climbing out of [src]."))
+ buckled_mob.apply_damage(5, BRUTE, sharpness = SHARP_POINTY)
+ if(!do_after(buckled_mob, 5 SECONDS, target = src))
+ to_chat(buckled_mob, span_userdanger("You fail to detach yourself from [src]."))
+ return
+ return ..()
/////////BONFIRES//////////
@@ -311,6 +362,6 @@
if(..())
M.pixel_y += 13
-/obj/structure/bonfire/unbuckle_mob(mob/living/buckled_mob, force=FALSE)
+/obj/structure/bonfire/unbuckle_mob(mob/living/buckled_mob, force=FALSE, can_fall = TRUE)
if(..())
buckled_mob.pixel_y -= 13
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index f4f415cf0be9..62e0e73fdece 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -178,7 +178,7 @@
var/list/data = null
if(rid == /datum/reagent/blood) // Hack to make blood in plants always O-
- data = list("blood_type" = "O-")
+ data = list("blood_type" = get_blood_type("O-"))
if(rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin)
// apple tastes of apple.
if(istype(T, /obj/item/reagent_containers/food/snacks/grown))
diff --git a/code/modules/jobs/departments/departments.dm b/code/modules/jobs/departments/departments.dm
index a629c2eaab62..81b44473b7fa 100644
--- a/code/modules/jobs/departments/departments.dm
+++ b/code/modules/jobs/departments/departments.dm
@@ -43,7 +43,7 @@
department_experience_type = EXP_TYPE_COMMAND
display_order = 1
label_class = "command"
- ui_color = "#ccccff"
+ ui_color = "#6681a5"
/datum/job_department/security
@@ -53,7 +53,7 @@
department_experience_type = EXP_TYPE_SECURITY
display_order = 2
label_class = "security"
- ui_color = "#ffbbbb"
+ ui_color = "#d46a78"
/datum/job_department/engineering
@@ -63,7 +63,7 @@
department_experience_type = EXP_TYPE_ENGINEERING
display_order = 3
label_class = "engineering"
- ui_color = "#ffeeaa"
+ ui_color = "#dfb567"
/datum/job_department/medical
@@ -73,7 +73,7 @@
department_experience_type = EXP_TYPE_MEDICAL
display_order = 4
label_class = "medical"
- ui_color = "#c1e1ec"
+ ui_color = "#65b2bd"
/datum/job_department/science
@@ -83,7 +83,7 @@
department_experience_type = EXP_TYPE_SCIENCE
display_order = 5
label_class = "science"
- ui_color = "#ffddff"
+ ui_color = "#c973c9"
/datum/job_department/cargo
@@ -93,7 +93,7 @@
department_experience_type = EXP_TYPE_SUPPLY
display_order = 6
label_class = "supply"
- ui_color = "#d7b088"
+ ui_color = "#cf9c6c"
/datum/job_department/service
@@ -103,7 +103,7 @@
department_experience_type = EXP_TYPE_SERVICE
display_order = 7
label_class = "service"
- ui_color = "#ddddff"
+ ui_color = "#7cc46a"
/datum/job_department/silicon
@@ -113,7 +113,7 @@
department_experience_type = EXP_TYPE_SILICON
display_order = 8
label_class = "silicon"
- ui_color = "#ccffcc"
+ ui_color = "#5dbda0"
/// Catch-all department for undefined jobs.
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index a044c675f4d4..f9ec46ba90f7 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -43,8 +43,10 @@
var/current_positions = 0
/// Supervisors, who this person answers to directly
var/supervisors = ""
- /// Selection Color for job preferences
- var/selection_color = "#ffffff"
+
+ /// What kind of mob type joining players with this job as their assigned role are spawned as.
+ var/spawn_type = /mob/living/carbon/human
+
/// Alternate titles for the job
var/list/alt_titles
/// If this is set to TRUE, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect.
@@ -130,7 +132,6 @@
Here is another example of using this:
/datum/job/doctor/proc/OmegaStationChanges()
- selection_color = "#ffffff"
total_positions = 3
spawn_positions = 3
added_access = list()
@@ -398,6 +399,97 @@
/datum/job/proc/get_mail_goodies(mob/recipient)
return mail_goodies
+
+/datum/job/proc/award_service(client/winner, award)
+ return
+
+
+/datum/job/proc/get_captaincy_announcement(mob/living/captain)
+ return "Due to extreme staffing shortages, newly promoted Acting Captain [captain.real_name] on deck!"
+
+/// Returns an atom where the mob should spawn in.
+// /datum/job/proc/get_roundstart_spawn_point()
+// if(random_spawns_possible)
+// if(HAS_TRAIT(SSstation, STATION_TRAIT_LATE_ARRIVALS))
+// return get_latejoin_spawn_point()
+// if(HAS_TRAIT(SSstation, STATION_TRAIT_RANDOM_ARRIVALS))
+// return get_safe_random_station_turf(typesof(/area/station/hallway)) || get_latejoin_spawn_point()
+// if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER))
+// var/obj/effect/landmark/start/hangover_spawn_point
+// for(var/obj/effect/landmark/start/hangover/hangover_landmark in GLOB.start_landmarks_list)
+// hangover_spawn_point = hangover_landmark
+// if(hangover_landmark.used) //so we can revert to spawning them on top of eachother if something goes wrong
+// continue
+// hangover_landmark.used = TRUE
+// break
+// return hangover_spawn_point || get_latejoin_spawn_point()
+// if(length(GLOB.jobspawn_overrides[title]))
+// return pick(GLOB.jobspawn_overrides[title])
+// var/obj/effect/landmark/start/spawn_point = get_default_roundstart_spawn_point()
+// if(!spawn_point) //if there isn't a spawnpoint send them to latejoin, if there's no latejoin go yell at your mapper
+// return get_latejoin_spawn_point()
+// return spawn_point
+
+
+/// Handles finding and picking a valid roundstart effect landmark spawn point, in case no uncommon different spawning events occur.
+/datum/job/proc/get_default_roundstart_spawn_point()
+ for(var/obj/effect/landmark/start/spawn_point as anything in GLOB.start_landmarks_list)
+ if(spawn_point.name != title)
+ continue
+ . = spawn_point
+ if(spawn_point.used) //so we can revert to spawning them on top of eachother if something goes wrong
+ continue
+ spawn_point.used = TRUE
+ break
+ if(!.)
+ log_mapping("Job [title] ([type]) couldn't find a round start spawn point.")
+
+/// Finds a valid latejoin spawn point, checking for events and special conditions.
+// /datum/job/proc/get_latejoin_spawn_point()
+// if(length(GLOB.jobspawn_overrides[title])) //We're doing something special today.
+// return pick(GLOB.jobspawn_overrides[title])
+// if(length(SSjob.latejoin_trackers))
+// return pick(SSjob.latejoin_trackers)
+// return SSjob.get_last_resort_spawn_points()
+
+
+// Spawns the mob to be played as, taking into account preferences and the desired spawn point.
+/datum/job/proc/get_spawn_mob(client/player_client, atom/spawn_point)
+ var/mob/living/spawn_instance
+ if(ispath(spawn_type, /mob/living/silicon/ai))
+ // This is unfortunately necessary because of snowflake AI init code. To be refactored.
+ spawn_instance = new spawn_type(get_turf(spawn_point), null, player_client.mob)
+ else
+ spawn_instance = new spawn_type(player_client.mob.loc)
+ spawn_point.JoinPlayerHere(spawn_instance, TRUE)
+ spawn_instance.apply_prefs_job(player_client, src)
+ if(!player_client)
+ qdel(spawn_instance)
+ return // Disconnected while checking for the appearance ban.
+ return spawn_instance
+
+
+/// Applies the preference options to the spawning mob, taking the job into account. Assumes the client has the proper mind.
+/mob/living/proc/apply_prefs_job(client/player_client, datum/job/job)
+
+/**
+ * Called after a successful roundstart spawn.
+ * Client is not yet in the mob.
+ * This happens after after_spawn()
+ */
+/datum/job/proc/after_roundstart_spawn(mob/living/spawning, client/player_client)
+ SHOULD_CALL_PARENT(TRUE)
+
+
+/**
+ * Called after a successful latejoin spawn.
+ * Client is in the mob.
+ * This happens after after_spawn()
+ */
+/datum/job/proc/after_latejoin_spawn(mob/living/spawning)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN, src, spawning)
+
//Warden and regular officers add this result to their get_access()
/datum/job/proc/check_config_for_sec_maint()
if(CONFIG_GET(flag/security_has_maint_access))
diff --git a/code/modules/jobs/job_types/ai.dm b/code/modules/jobs/job_types/ai.dm
index f57b02b140db..c2f33e9b666b 100644
--- a/code/modules/jobs/job_types/ai.dm
+++ b/code/modules/jobs/job_types/ai.dm
@@ -6,7 +6,6 @@
faction = "Station"
total_positions = 1
spawn_positions = 1
- selection_color = "#ccffcc"
supervisors = "your laws"
req_admin_notify = TRUE
minimal_player_age = 30
@@ -25,15 +24,14 @@
//this should never be seen because of the way olfaction works but just in case
smells_like = "chained intellect"
-/datum/job/ai/equip(mob/living/carbon/human/H, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source = null)
+/datum/job/ai/equip(mob/living/equipping, visualsOnly, announce, latejoin, datum/outfit/outfit_override, client/preference_source = null)
if(visualsOnly)
CRASH("dynamic preview is unsupported")
- . = H.AIize(latejoin,preference_source)
+ . = equipping.AIize(preference_source)
-/datum/job/ai/after_spawn(mob/H, mob/M, latejoin)
+/datum/job/ai/after_spawn(mob/living/spawned, mob/M, latejoin)
. = ..()
-
- var/mob/living/silicon/ai/AI = H
+ var/mob/living/silicon/ai/AI = spawned
AI.relocate(TRUE)
diff --git a/code/modules/jobs/job_types/artist.dm b/code/modules/jobs/job_types/artist.dm
index 55784466133e..28dfe08cc02e 100644
--- a/code/modules/jobs/job_types/artist.dm
+++ b/code/modules/jobs/job_types/artist.dm
@@ -7,7 +7,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#dddddd"
outfit = /datum/outfit/job/artist
alt_titles = list("Painter", "Composer", "Artisan")
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index 666a68b0f168..b2427dfdefde 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -9,7 +9,6 @@ Assistant
total_positions = 5
spawn_positions = 5
supervisors = "absolutely everyone"
- selection_color = "#dddddd"
added_access = list() //See /datum/job/assistant/get_access()
base_access = list() //See /datum/job/assistant/get_access()
outfit = /datum/outfit/job/assistant
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index 2d4b57815cd2..dc0d460ed354 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -7,7 +7,6 @@
total_positions = 3
spawn_positions = 2
supervisors = "the chief engineer"
- selection_color = "#fff5cc"
exp_requirements = 180
exp_type = EXP_TYPE_CREW
alt_titles = list("Life-support Technician", "Fire Suppression Specialist", "Atmospherics Trainee", "Environmental Maintainer", "Fusion Specialist")
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index 66fce46dc27a..eca2c26c67e6 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -7,7 +7,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#bbe291"
exp_type_department = EXP_TYPE_SERVICE // This is so the jobs menu can work properly
alt_titles = list("Barkeep", "Tapster", "Barista", "Mixologist")
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
index 3e366db34668..b63b4e969c52 100644
--- a/code/modules/jobs/job_types/botanist.dm
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -7,7 +7,6 @@
total_positions = 3
spawn_positions = 2
supervisors = "the head of personnel"
- selection_color = "#bbe291"
outfit = /datum/outfit/job/botanist
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 6365594bd8dc..75d55d8b4aed 100644
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -10,7 +10,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "Nanotrasen officers and Space law" //Changed to officer to separate from CentCom officials being their superior.
- selection_color = "#ccccff"
req_admin_notify = 1
space_law_notify = 1 //Yogs
minimal_player_age = 14
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
index 14ea8105f32b..ab2a5973a9bd 100644
--- a/code/modules/jobs/job_types/cargo_technician.dm
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -9,7 +9,6 @@
total_positions = 2
spawn_positions = 1
supervisors = "the quartermaster and the head of personnel"
- selection_color = "#dcba97"
outfit = /datum/outfit/job/cargo_tech
diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
index 3cd1ee881bd2..42a2cb83d797 100644
--- a/code/modules/jobs/job_types/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -8,7 +8,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#dddddd"
outfit = /datum/outfit/job/chaplain
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
index 140bd0337c28..72b93fbb9ddc 100644
--- a/code/modules/jobs/job_types/chemist.dm
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -8,7 +8,6 @@
total_positions = 2
spawn_positions = 2
supervisors = "the chief medical officer"
- selection_color = "#d4ebf2"
exp_type = EXP_TYPE_CREW
exp_requirements = 120
exp_type_department = EXP_TYPE_MEDICAL
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index 9c0b7297f63d..c7004ee741a3 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -10,7 +10,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the captain"
- selection_color = "#ffeeaa"
req_admin_notify = 1
minimal_player_age = 7
exp_requirements = 1500 //25 hours
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index 2bc428901cc5..2dec1ebd5fe6 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -10,7 +10,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the captain"
- selection_color = "#c1e1ec"
req_admin_notify = 1
minimal_player_age = 7
exp_requirements = 1500 //25 hours
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index 7fc1b36a9f38..4c38c09cb965 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -7,7 +7,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#dddddd"
outfit = /datum/outfit/job/clown
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
index b0efc565e7a6..3100197198af 100644
--- a/code/modules/jobs/job_types/cook.dm
+++ b/code/modules/jobs/job_types/cook.dm
@@ -7,7 +7,6 @@
total_positions = 2
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#bbe291"
var/cooks = 0 //Counts cooks amount
outfit = /datum/outfit/job/cook
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
index eb7aae9266a3..5d8e15bc38ec 100644
--- a/code/modules/jobs/job_types/curator.dm
+++ b/code/modules/jobs/job_types/curator.dm
@@ -8,7 +8,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#dddddd"
outfit = /datum/outfit/job/curator
diff --git a/code/modules/jobs/job_types/cyborg.dm b/code/modules/jobs/job_types/cyborg.dm
index c1b202ae57f1..b2b714a9d6c1 100644
--- a/code/modules/jobs/job_types/cyborg.dm
+++ b/code/modules/jobs/job_types/cyborg.dm
@@ -7,7 +7,6 @@
total_positions = 0
spawn_positions = 2
supervisors = "your laws and the AI" //Nodrak
- selection_color = "#ddffdd"
minimal_player_age = 21
exp_requirements = 120
exp_type = EXP_TYPE_CREW
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index ddc4fd758b16..328700db625e 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -9,7 +9,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of security"
- selection_color = "#ffeeee"
minimal_player_age = 7
exp_requirements = 180
exp_type = EXP_TYPE_SECURITY
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
index 2b92768b71bd..27db89c87a40 100644
--- a/code/modules/jobs/job_types/geneticist.dm
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -7,7 +7,6 @@
total_positions = 2
spawn_positions = 2
supervisors = "the chief medical officer and research director"
- selection_color = "#d4ebf2"
exp_type = EXP_TYPE_CREW
exp_requirements = 60
alt_titles = list("DNA Mechanic", "Bioengineer", "Junior Geneticist", "Gene Splicer", "Mutation Specialist")
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index 4d684d9a14ca..681574e1cfaf 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -10,7 +10,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the captain"
- selection_color = "#ddddff"
req_admin_notify = 1
minimal_player_age = 10
exp_requirements = 720 //fairly low skill job
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index b3449b86cc8e..434c3627504c 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -10,7 +10,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the captain"
- selection_color = "#ffdddd"
req_admin_notify = 1
minimal_player_age = 14
exp_requirements = 1500 //25 hours
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
index 55e6708188cd..a14f9ee38454 100644
--- a/code/modules/jobs/job_types/janitor.dm
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -7,7 +7,6 @@
total_positions = 2
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#bbe291"
outfit = /datum/outfit/job/janitor
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
index 2e4c1aaa7850..edcce8838c85 100644
--- a/code/modules/jobs/job_types/lawyer.dm
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -8,7 +8,6 @@
total_positions = 2
spawn_positions = 2
supervisors = "the head of personnel"
- selection_color = "#dddddd"
var/lawyers = 0 //Counts lawyer amount
alt_titles = list("Prosecutor", "Defense Attorney", "Paralegal", "Ace Attorney")
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
index dd83f89c8d16..b44a4dcf9e6a 100644
--- a/code/modules/jobs/job_types/medical_doctor.dm
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -8,7 +8,6 @@
total_positions = 5
spawn_positions = 3
supervisors = "the chief medical officer"
- selection_color = "#d4ebf2"
exp_requirements = 180
exp_type = EXP_TYPE_CREW
alt_titles = list("Physician", "Surgeon", "Nurse", "Medical Resident", "Attending Physician", "General Practitioner")
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
index f15a4bfd3be3..3826212f52f1 100644
--- a/code/modules/jobs/job_types/mime.dm
+++ b/code/modules/jobs/job_types/mime.dm
@@ -7,7 +7,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#dddddd"
outfit = /datum/outfit/job/mime
@@ -27,13 +26,13 @@
mail_goodies = list(
/obj/item/reagent_containers/food/snacks/baguette = 15,
- /obj/item/reagent_containers/food/snacks/store/cheesewheel = 10,
+ /obj/item/reagent_containers/food/snacks/store/cheesewheel/brie = 10,
/obj/item/reagent_containers/food/drinks/bottle/bottleofnothing = 10,
/obj/item/book/mimery = 1,
)
minimal_lightup_areas = list(/area/crew_quarters/theatre)
-
+
smells_like = "complete nothingness"
/datum/job/mime/after_spawn(mob/living/carbon/human/H, mob/M)
@@ -103,7 +102,7 @@
if("Invisible Box")
picked_spell_type = /datum/action/cooldown/spell/conjure_item/invisible_box
-
+
if("Invisible Touch")
picked_spell_type = /datum/action/cooldown/spell/touch/invisible_touch
diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm
index e30304d00b2c..4442590fe746 100644
--- a/code/modules/jobs/job_types/quartermaster.dm
+++ b/code/modules/jobs/job_types/quartermaster.dm
@@ -8,7 +8,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of personnel"
- selection_color = "#d7b088"
outfit = /datum/outfit/job/quartermaster
alt_titles = list("Stock Controller", "Cargo Coordinator", "Shipping Overseer", "Postmaster General")
added_access = list()
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index e381a867427a..b6a91ef5c87d 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -11,7 +11,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the captain"
- selection_color = "#ffddff"
req_admin_notify = 1
minimal_player_age = 7
exp_type_department = EXP_TYPE_SCIENCE
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index b798d1c85e75..b90ccf3d6aec 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -7,7 +7,6 @@
total_positions = 2
spawn_positions = 2
supervisors = "the research director"
- selection_color = "#ffeeff"
exp_requirements = 60
exp_type = EXP_TYPE_CREW
alt_titles = list("Augmentation Theorist", "Cyborg Maintainer", "Robotics Intern", "Biomechanical Engineer", "Mechatronic Engineer", "Machinist", "Chrome Shaman", "Ripperdoc")
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
index b69a337ab166..725a217f3155 100644
--- a/code/modules/jobs/job_types/scientist.dm
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -7,7 +7,6 @@
total_positions = 5
spawn_positions = 3
supervisors = "the research director"
- selection_color = "#ffeeff"
exp_requirements = 180
exp_type = EXP_TYPE_CREW
alt_titles = list("Researcher", "Toxins Specialist", "Physicist", "Test Associate", "Anomalist", "Quantum Physicist", "Theoretical Physicist", "Xenobiologist", "Explosives Technician", "Hypothetical Physicist")
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
index 30564c5c89cf..1b231992929c 100644
--- a/code/modules/jobs/job_types/security_officer.dm
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -9,7 +9,6 @@
total_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
spawn_positions = 5 //Handled in /datum/controller/occupations/proc/setup_officer_positions()
supervisors = "the head of security, and the head of your assigned department (if applicable)"
- selection_color = "#ffeeee"
minimal_player_age = 7
exp_requirements = 300
exp_type = EXP_TYPE_CREW
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index 1163f300518e..5776843d03b4 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -8,7 +8,6 @@
total_positions = 3
spawn_positions = 3
supervisors = "the quartermaster and the head of personnel"
- selection_color = "#dcba97"
alt_titles = list("Lavaland Scout", "Prospector", "Junior Miner", "Major Miner", "Surveyor")
outfit = /datum/outfit/job/miner
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index 1caf26068293..e96c1dee2768 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -8,7 +8,6 @@
total_positions = 5
spawn_positions = 5
supervisors = "the chief engineer"
- selection_color = "#fff5cc"
exp_requirements = 180
exp_type = EXP_TYPE_CREW
alt_titles = list("Engine Technician", "Solar Engineer", "Project Engineer", "Junior Engineer", "Construction Specialist")
diff --git a/code/modules/jobs/job_types/unassigned.dm b/code/modules/jobs/job_types/unassigned.dm
new file mode 100644
index 000000000000..ea39bfc269d3
--- /dev/null
+++ b/code/modules/jobs/job_types/unassigned.dm
@@ -0,0 +1,10 @@
+/**
+ * This type is used to indicate a lack of a job.
+ * The mind variable assigned_role will point here by default.
+ * As any other job datum, this is a singleton.
+ **/
+
+/datum/job/unassigned
+ title = "Unassigned Crewmember"
+ //rpg_title = "Peasant"
+ paycheck = PAYCHECK_ZERO
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
index 837e74c86c6a..1e31e5e5330f 100644
--- a/code/modules/jobs/job_types/virologist.dm
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -8,7 +8,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the chief medical officer"
- selection_color = "#d4ebf2"
exp_type = EXP_TYPE_CREW
exp_requirements = 120
minimal_player_age = 7
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
index ccb68f5c0e1c..84f8838d2b08 100644
--- a/code/modules/jobs/job_types/warden.dm
+++ b/code/modules/jobs/job_types/warden.dm
@@ -10,7 +10,6 @@
total_positions = 1
spawn_positions = 1
supervisors = "the head of security"
- selection_color = "#ffeeee"
minimal_player_age = 7
exp_requirements = 600
exp_type = EXP_TYPE_CREW
diff --git a/code/modules/language/mouse.dm b/code/modules/language/mouse.dm
index 8e488c93e5ac..e736dd8af181 100644
--- a/code/modules/language/mouse.dm
+++ b/code/modules/language/mouse.dm
@@ -9,4 +9,4 @@
/datum/language/mouse/scramble(input)
. = "Squeak"
- . += (copytext(input, length(input)) == "?") ? "?" : "!"
+ . += (copytext_char(input, length(input)) == "?") ? "?" : "!" //Dripstation edit
diff --git a/code/modules/lighting/emissive_blocker.dm b/code/modules/lighting/emissive_blocker.dm
deleted file mode 100644
index b69a474009ee..000000000000
--- a/code/modules/lighting/emissive_blocker.dm
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Internal atom that copies an appearance on to the blocker plane
- *
- * Copies an appearance vis render_target and render_source on to the emissive blocking plane.
- * This means that the atom in question will block any emissive sprites.
- * This should only be used internally. If you are directly creating more of these, you're
- * almost guaranteed to be doing something wrong.
- */
-/atom/movable/emissive_blocker
- name = ""
- plane = EMISSIVE_BLOCKER_PLANE
- layer = EMISSIVE_BLOCKER_LAYER
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- //Why?
- //render_targets copy the transform of the target as well, but vis_contents also applies the transform
- //to what's in it. Applying RESET_TRANSFORM here makes vis_contents not apply the transform.
- //Since only render_target handles transform we don't get any applied transform "stacking"
- appearance_flags = RESET_TRANSFORM
-
-/atom/movable/emissive_blocker/Initialize(mapload, source)
- . = ..()
- verbs.Cut() //Cargo culting from lighting object, this maybe affects memory usage?
-
- render_source = source
-
-/atom/movable/emissive_blocker/ex_act(severity)
- return FALSE
-
-/atom/movable/emissive_blocker/singularity_act()
- return
-
-/atom/movable/emissive_blocker/singularity_pull()
- return
-
-/atom/movable/emissive_blocker/blob_act()
- return
-
-/atom/movable/emissive_blocker/onTransitZ()
- return
-
-//Prevents people from moving these after creation, because they shouldn't be.
-/atom/movable/emissive_blocker/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
- if(harderforce)
- return ..()
diff --git a/code/modules/lighting/lighting_area.dm b/code/modules/lighting/lighting_area.dm
index 58ee031d3281..c6f427f592f6 100644
--- a/code/modules/lighting/lighting_area.dm
+++ b/code/modules/lighting/lighting_area.dm
@@ -1,30 +1,125 @@
/area
- luminosity = TRUE
- var/dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
+ luminosity = 1
+ ///List of mutable appearances we underlay to show light
+ ///In the form plane offset + 1 -> appearance to use
+ var/list/mutable_appearance/lighting_effects = null
+ ///Whether this area has a currently active base lighting, bool
+ var/area_has_base_lighting = FALSE
+ ///alpha 0-255 of lighting_effect and thus baselighting intensity
+ var/base_lighting_alpha = 0
+ ///The colour of the light acting on this area
+ var/base_lighting_color = COLOR_WHITE
-/area/proc/set_dynamic_lighting(new_dynamic_lighting = DYNAMIC_LIGHTING_ENABLED)
- if (new_dynamic_lighting == dynamic_lighting)
+/area/proc/set_base_lighting(new_base_lighting_color = -1, new_alpha = -1)
+ if(base_lighting_alpha == new_alpha && base_lighting_color == new_base_lighting_color)
return FALSE
-
- dynamic_lighting = new_dynamic_lighting
-
- if (IS_DYNAMIC_LIGHTING(src))
- cut_overlay(/obj/effect/fullbright)
- for (var/turf/T in src)
- if (IS_DYNAMIC_LIGHTING(T))
- T.lighting_build_overlay()
-
- else
- add_overlay(/obj/effect/fullbright)
- for (var/turf/T in src)
- if (T.lighting_object)
- T.lighting_clear_overlay()
-
+ if(new_alpha != -1)
+ base_lighting_alpha = new_alpha
+ if(new_base_lighting_color != -1)
+ base_lighting_color = new_base_lighting_color
+ update_base_lighting()
return TRUE
/area/vv_edit_var(var_name, var_value)
switch(var_name)
- if("dynamic_lighting")
- set_dynamic_lighting(var_value)
+ if(NAMEOF(src, base_lighting_color))
+ set_base_lighting(new_base_lighting_color = var_value)
+ return TRUE
+ if(NAMEOF(src, base_lighting_alpha))
+ set_base_lighting(new_alpha = var_value)
return TRUE
+ if(NAMEOF(src, static_lighting))
+ if(!static_lighting)
+ create_area_lighting_objects()
+ else
+ remove_area_lighting_objects()
+
return ..()
+
+/area/proc/update_base_lighting()
+ if(!area_has_base_lighting && (!base_lighting_alpha || !base_lighting_color))
+ return
+
+ if(!area_has_base_lighting)
+ add_base_lighting()
+ return
+ remove_base_lighting()
+ if(base_lighting_alpha && base_lighting_color)
+ add_base_lighting()
+
+/area/proc/remove_base_lighting()
+ UnregisterSignal(SSdcs, COMSIG_STARLIGHT_COLOR_CHANGED)
+ var/list/z_offsets = SSmapping.z_level_to_plane_offset
+ if(length(lighting_effects) > 1)
+ for(var/turf/T as anything in get_contained_turfs())
+ if(z_offsets[T.z])
+ T.cut_overlay(lighting_effects[z_offsets[T.z] + 1])
+ cut_overlay(lighting_effects[1])
+ lighting_effects = null
+ area_has_base_lighting = FALSE
+
+/area/proc/add_base_lighting()
+ lighting_effects = list()
+ for(var/offset in 0 to SSmapping.max_plane_offset)
+ var/mutable_appearance/light
+ if(base_lighting_color == COLOR_STARLIGHT)
+ light = new(GLOB.starlight_overlays[offset + 1])
+ else
+ light = mutable_appearance('icons/effects/alphacolors.dmi', "white")
+ light.color = base_lighting_color
+ light.layer = LIGHTING_PRIMARY_LAYER
+ light.blend_mode = BLEND_ADD
+ light.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR
+ light.alpha = base_lighting_alpha
+ SET_PLANE_W_SCALAR(light, LIGHTING_PLANE, offset)
+ lighting_effects += light
+
+ if(base_lighting_color == COLOR_STARLIGHT)
+ // Ok this is gonna be dumb
+ // We rely on render_source working, and it DOES NOT APPEAR TO in area rendering
+ // So we're gonna have to update the area's overlay manually. everything else can be automatic tho
+ // Fortunately the first overlay is only ever used by the area, soooo
+ var/mutable_appearance/light = mutable_appearance('icons/effects/alphacolors.dmi', "white")
+ light.layer = LIGHTING_PRIMARY_LAYER
+ light.blend_mode = BLEND_ADD
+ light.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR
+ light.color = GLOB.starlight_color
+ light.alpha = base_lighting_alpha
+ SET_PLANE_W_SCALAR(light, LIGHTING_PLANE, 0)
+ lighting_effects[1] = light
+ RegisterSignal(SSdcs, COMSIG_STARLIGHT_COLOR_CHANGED, PROC_REF(starlight_changed))
+
+ add_overlay(lighting_effects[1])
+ var/list/z_offsets = SSmapping.z_level_to_plane_offset
+ if(length(lighting_effects) > 1)
+ // This inside loop is EXTREMELY hot because it's run by space tiles. Don't want no part in that
+ for(var/turf/T as anything in get_contained_turfs())
+ T.luminosity = 1
+ // We will only add overlays to turfs not on the first z layer, because that's a significantly lesser portion
+ // And we need to do them separate, or lighting will go fuckey
+ if(z_offsets[T.z])
+ T.add_overlay(lighting_effects[z_offsets[T.z] + 1])
+ else
+ for(var/turf/T as anything in get_contained_turfs())
+ T.luminosity = 1
+
+ area_has_base_lighting = TRUE
+
+/area/proc/starlight_changed(datum/source, old_star, new_star)
+ var/mutable_appearance/old_star_effect = mutable_appearance('icons/effects/alphacolors.dmi', "white")
+ old_star_effect.layer = LIGHTING_PRIMARY_LAYER
+ old_star_effect.blend_mode = BLEND_ADD
+ old_star_effect.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR
+ old_star_effect.color = old_star
+ old_star_effect.alpha = base_lighting_alpha
+ SET_PLANE_W_SCALAR(old_star_effect, LIGHTING_PLANE, 0)
+ cut_overlay(old_star_effect)
+ var/mutable_appearance/new_star_effect = mutable_appearance('icons/effects/alphacolors.dmi', "white")
+ new_star_effect.layer = LIGHTING_PRIMARY_LAYER
+ new_star_effect.blend_mode = BLEND_ADD
+ new_star_effect.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR
+ new_star_effect.color = new_star
+ new_star_effect.alpha = base_lighting_alpha
+ SET_PLANE_W_SCALAR(new_star_effect, LIGHTING_PLANE, 0)
+ add_overlay(new_star_effect)
+ lighting_effects[1] = new_star_effect
diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm
index 6ffa5121b925..17aca71121f2 100644
--- a/code/modules/lighting/lighting_atom.dm
+++ b/code/modules/lighting/lighting_atom.dm
@@ -1,5 +1,5 @@
// The proc you should always use to set the light of this atom.
-/atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE, l_angle, l_dir, l_on)
+/atom/proc/set_light(l_range, l_power, l_color = NONSENSICAL_VALUE, l_angle, l_dir, l_height, l_on)
// We null everything but l_dir, because we don't want to allow for modifications while frozen
if(light_flags & LIGHT_FROZEN)
l_range = null
@@ -7,6 +7,7 @@
l_color = null
l_on = null
l_angle = null
+ l_height = null
if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE)
l_range = MINIMUM_USEFUL_LIGHT_RANGE //Brings the range up to 1.4, which is just barely brighter than the soft lighting that surrounds players.
@@ -31,6 +32,9 @@
if(!isnull(l_on))
set_light_on(l_on)
+
+ if(!isnull(l_height))
+ set_light_height(l_height)
update_light()
@@ -174,6 +178,17 @@
SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_ON, .)
return .
+/// Setter for the height of our light
+/atom/proc/set_light_height(new_value)
+ if(new_value == light_height || light_flags & LIGHT_FROZEN)
+ return
+ if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_HEIGHT, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE)
+ return
+ . = light_height
+ light_height = new_value
+ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_HEIGHT, .)
+ return .
+
/// Setter for the light flags of this atom.
/atom/proc/set_light_flags(new_value)
if(new_value == light_flags || (light_flags & LIGHT_FROZEN && new_value & LIGHT_FROZEN))
@@ -185,3 +200,20 @@
SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, .)
return .
+/atom/proc/get_light_offset()
+ return list(0, 0)
+
+/// Returns a list of x and y offsets to apply to our visual lighting position
+/proc/calculate_light_offset(atom/get_offset)
+ var/list/hand_back
+ if(!(get_offset.light_flags & LIGHT_IGNORE_OFFSET))
+ hand_back = get_visual_offset(get_offset)
+ hand_back[1] = -hand_back[1] / world.icon_size
+ hand_back[2] = -hand_back[2] / world.icon_size
+ else
+ hand_back = list(0, 0)
+
+ var/list/atoms_opinion = get_offset.get_light_offset()
+ hand_back[1] += atoms_opinion[1]
+ hand_back[2] += atoms_opinion[2]
+ return hand_back
diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm
index 187c07332885..912a586bc3b5 100644
--- a/code/modules/lighting/lighting_corner.dm
+++ b/code/modules/lighting/lighting_corner.dm
@@ -7,12 +7,14 @@
var/x = 0
var/y = 0
+ var/z = 0
var/turf/master_NE
var/turf/master_SE
var/turf/master_SW
var/turf/master_NW
+ //"raw" color values, changed by update_lumcount()
var/lum_r = 0
var/lum_g = 0
var/lum_b = 0
@@ -28,50 +30,49 @@
///whether we are to be added to SSlighting's corners_queue list for an update
var/needs_update = FALSE
-/datum/lighting_corner/New(turf/new_turf, diagonal)
+// Takes as an argument the coords to use as the bottom left (south west) of our corner
+/datum/lighting_corner/New(x, y, z)
. = ..()
- save_master(new_turf, turn(diagonal, 180))
-
- var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction.
- var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one.
-
- x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5)
- y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5)
-
- // My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal).
- // Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered.
- // So we'll have this hardcode instead.
- var/turf/new_master_turf
-
- // Diagonal one is easy.
- new_master_turf = get_step(new_turf, diagonal)
- if (new_master_turf) // In case we're on the map's border.
- save_master(new_master_turf, diagonal)
-
- // Now the horizontal one.
- new_master_turf = get_step(new_turf, horizontal)
- if (new_master_turf) // Ditto.
- save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates.
-
- // And finally the vertical one.
- new_master_turf = get_step(new_turf, vertical)
- if (new_master_turf)
- save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates.
-
-/datum/lighting_corner/proc/save_master(turf/master, dir)
- switch (dir)
- if (NORTHEAST)
- master_NE = master
- master.lighting_corner_SW = src
- if (SOUTHEAST)
- master_SE = master
- master.lighting_corner_NW = src
- if (SOUTHWEST)
- master_SW = master
- master.lighting_corner_NE = src
- if (NORTHWEST)
- master_NW = master
- master.lighting_corner_SE = src
+
+ src.x = x + 0.5
+ src.y = y + 0.5
+ src.z = z
+
+ // Alright. We're gonna take a set of coords, and from them do a loop clockwise
+ // To build out the turfs adjacent to us. This is pretty fast
+ var/turf/process_next = locate(x, y, z)
+ if(process_next)
+ master_SW = process_next
+ process_next.lighting_corner_NE = src
+ // Now, we go north!
+ process_next = get_step(process_next, NORTH)
+ else
+ // Yes this is slightly slower then having a guarenteeed turf, but there aren't many null turfs
+ // So this is pretty damn fast
+ process_next = locate(x, y + 1, z)
+
+ // Ok, if we have a north turf, go there. otherwise, onto the next
+ if(process_next)
+ master_NW = process_next
+ process_next.lighting_corner_SE = src
+ // Now, TO THE EAST
+ process_next = get_step(process_next, EAST)
+ else
+ process_next = locate(x + 1, y + 1, z)
+
+ // Etc etc
+ if(process_next)
+ master_NE = process_next
+ process_next.lighting_corner_SW = src
+ // Now, TO THE SOUTH AGAIN (SE)
+ process_next = get_step(process_next, SOUTH)
+ else
+ process_next = locate(x + 1, y, z)
+
+ // anddd the last tile
+ if(process_next)
+ master_SE = process_next
+ process_next.lighting_corner_NW = src
/datum/lighting_corner/proc/self_destruct_if_idle()
if (!LAZYLEN(affecting))
@@ -87,8 +88,14 @@
// God that was a mess, now to do the rest of the corner code! Hooray!
/datum/lighting_corner/proc/update_lumcount(delta_r, delta_g, delta_b)
+
+#ifdef VISUALIZE_LIGHT_UPDATES
+ if (!SSlighting.allow_duped_values && !(delta_r || delta_g || delta_b)) // 0 is falsey ok
+ return
+#else
if (!(delta_r || delta_g || delta_b)) // 0 is falsey ok
return
+#endif
lum_r += delta_r
lum_g += delta_g
@@ -108,19 +115,31 @@
if (largest_color_luminosity > 1)
. = 1 / largest_color_luminosity
+ var/old_r = cache_r
+ var/old_g = cache_g
+ var/old_b = cache_b
+
#if LIGHTING_SOFT_THRESHOLD != 0
else if (largest_color_luminosity < LIGHTING_SOFT_THRESHOLD)
. = 0 // 0 means soft lighting.
- cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
- cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
- cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
+ cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
+ cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
+ cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE) || LIGHTING_SOFT_THRESHOLD
#else
- cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE)
- cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE)
- cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE)
+ cache_r = round(lum_r * ., LIGHTING_ROUND_VALUE)
+ cache_g = round(lum_g * ., LIGHTING_ROUND_VALUE)
+ cache_b = round(lum_b * ., LIGHTING_ROUND_VALUE)
#endif
+
src.largest_color_luminosity = round(largest_color_luminosity, LIGHTING_ROUND_VALUE)
+#ifdef VISUALIZE_LIGHT_UPDATES
+ if(!SSlighting.allow_duped_corners && old_r == cache_r && old_g == cache_g && old_b == cache_b)
+ return
+#else
+ if(old_r == cache_r && old_g == cache_g && old_b == cache_b)
+ return
+#endif
var/datum/lighting_object/lighting_object = master_NE?.lighting_object
if (lighting_object && !lighting_object.needs_update)
@@ -152,6 +171,7 @@
if (!force)
return QDEL_HINT_LETMELIVE
+
for (var/datum/light_source/light_source as anything in affecting)
LAZYREMOVE(light_source.effect_str, src)
affecting = null
@@ -172,3 +192,38 @@
SSlighting.corners_queue -= src
return ..()
+
+/// Debug proc to aid in understanding how corners work
+/datum/lighting_corner/proc/display(max_lum)
+ if(QDELETED(src))
+ return
+
+ var/turf/draw_to = master_SW || master_NE || master_SE || master_NW
+ var/mutable_appearance/display = mutable_appearance('icons/turf/debug.dmi', "corner_color", LIGHT_DEBUG_LAYER, draw_to, BALLOON_CHAT_PLANE)
+ if(x > draw_to.x)
+ display.pixel_x = 16
+ else
+ display.pixel_x = -16
+ if(y > draw_to.y)
+ display.pixel_y = 16
+ else
+ display.pixel_y = -16
+
+ display.color = rgb(cache_r * 255, cache_g * 255, cache_b * 255)
+
+ draw_to.add_overlay(display)
+
+/datum/lighting_corner/dummy/display()
+ return
+
+/// Makes all lighting corners visible, debug to aid in understanding
+/proc/display_corners()
+ var/list/corners = list()
+ var/max_lum = 0
+ for(var/datum/lighting_corner/corner) // I am so sorry
+ corners += corner
+ max_lum = max(max_lum, corner.largest_color_luminosity)
+
+
+ for(var/datum/lighting_corner/corner as anything in corners)
+ corner.display(max_lum)
diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm
index 49b3ec20e454..b18664e1971f 100644
--- a/code/modules/lighting/lighting_object.dm
+++ b/code/modules/lighting/lighting_object.dm
@@ -8,14 +8,18 @@
///the turf that our light is applied to
var/turf/affected_turf
+// Global list of lighting underlays, indexed by z level
+GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z)
+
/datum/lighting_object/New(turf/source)
if(!isturf(source))
qdel(src, force=TRUE)
stack_trace("a lighting object was assigned to [source], a non turf! ")
return
+
. = ..()
- current_underlay = mutable_appearance(LIGHTING_ICON, "atransparent", source.z, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM)
+ current_underlay = new(GLOB.default_lighting_underlays_by_z[source.z])
affected_turf = source
if (affected_turf.lighting_object)
@@ -23,10 +27,13 @@
stack_trace("a lighting object was assigned to a turf that already had a lighting object!")
affected_turf.lighting_object = src
- affected_turf.luminosity = 0
+ // Default to fullbright, so things can "see" if they use view() before we update
+ affected_turf.luminosity = 1
+ // This path is really hot. this is faster
+ // Really this should be a global var or something, but lets not think about that yes?
for(var/turf/open/space/space_tile in RANGE_TURFS(1, affected_turf))
- space_tile.update_starlight()
+ space_tile.enable_starlight()
needs_update = TRUE
SSlighting.objects_queue += src
@@ -43,7 +50,6 @@
return ..()
/datum/lighting_object/proc/update()
-
// To the future coder who sees this and thinks
// "Why didn't he just use a loop?"
// Well my man, it's because the loop performed like shit.
@@ -54,6 +60,14 @@
var/static/datum/lighting_corner/dummy/dummy_lighting_corner = new
+ var/turf/affected_turf = src.affected_turf
+
+#ifdef VISUALIZE_LIGHT_UPDATES
+ affected_turf.add_atom_colour(COLOR_BLUE_LIGHT, ADMIN_COLOUR_PRIORITY)
+ animate(affected_turf, 10, color = null)
+ addtimer(CALLBACK(affected_turf, TYPE_PROC_REF(/atom, remove_atom_colour), ADMIN_COLOUR_PRIORITY, COLOR_BLUE_LIGHT), 10, TIMER_UNIQUE|TIMER_OVERRIDE)
+#endif
+
var/datum/lighting_corner/red_corner = affected_turf.lighting_corner_SW || dummy_lighting_corner
var/datum/lighting_corner/green_corner = affected_turf.lighting_corner_SE || dummy_lighting_corner
var/datum/lighting_corner/blue_corner = affected_turf.lighting_corner_NW || dummy_lighting_corner
@@ -61,21 +75,6 @@
var/max = max(red_corner.largest_color_luminosity, green_corner.largest_color_luminosity, blue_corner.largest_color_luminosity, alpha_corner.largest_color_luminosity)
- var/rr = red_corner.cache_r
- var/rg = red_corner.cache_g
- var/rb = red_corner.cache_b
-
- var/gr = green_corner.cache_r
- var/gg = green_corner.cache_g
- var/gb = green_corner.cache_b
-
- var/br = blue_corner.cache_r
- var/bg = blue_corner.cache_g
- var/bb = blue_corner.cache_b
-
- var/ar = alpha_corner.cache_r
- var/ag = alpha_corner.cache_g
- var/ab = alpha_corner.cache_b
#if LIGHTING_SOFT_THRESHOLD != 0
var/set_luminosity = max > LIGHTING_SOFT_THRESHOLD
@@ -85,28 +84,29 @@
var/set_luminosity = max > 1e-6
#endif
- if((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8))
+ var/mutable_appearance/current_underlay = src.current_underlay
+ affected_turf.underlays -= current_underlay
+ if(red_corner.cache_r & green_corner.cache_r & blue_corner.cache_r & alpha_corner.cache_r && \
+ (red_corner.cache_g + green_corner.cache_g + blue_corner.cache_g + alpha_corner.cache_g + \
+ red_corner.cache_b + green_corner.cache_b + blue_corner.cache_b + alpha_corner.cache_b == 8))
//anything that passes the first case is very likely to pass the second, and addition is a little faster in this case
- affected_turf.underlays -= current_underlay
- current_underlay.icon_state = "atransparent"
+ current_underlay.icon_state = "lighting_transparent"
current_underlay.color = null
- affected_turf.underlays += current_underlay
else if(!set_luminosity)
- affected_turf.underlays -= current_underlay
- current_underlay.icon_state = "adark"
+ current_underlay.icon_state = "lighting_dark"
current_underlay.color = null
- affected_turf.underlays += current_underlay
else
- affected_turf.underlays -= current_underlay
current_underlay.icon_state = null
current_underlay.color = list(
- rr, rg, rb, 00,
- gr, gg, gb, 00,
- br, bg, bb, 00,
- ar, ag, ab, 00,
+ red_corner.cache_r, red_corner.cache_g, red_corner.cache_b, 00,
+ green_corner.cache_r, green_corner.cache_g, green_corner.cache_b, 00,
+ blue_corner.cache_r, blue_corner.cache_g, blue_corner.cache_b, 00,
+ alpha_corner.cache_r, alpha_corner.cache_g, alpha_corner.cache_b, 00,
00, 00, 00, 01
)
- affected_turf.underlays += current_underlay
-
+ // Of note. Most of the cost in this proc is here, I think because color matrix'd underlays DO NOT cache well, which is what adding to underlays does
+ // We use underlays because objects on each tile would fuck with maptick. if that ever changes, use an object for this instead
+ affected_turf.underlays += current_underlay
affected_turf.luminosity = set_luminosity
+ SSdemo.mark_turf(affected_turf)
diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm
index 4d365d6646d0..d6bf19711ef7 100644
--- a/code/modules/lighting/lighting_setup.dm
+++ b/code/modules/lighting/lighting_setup.dm
@@ -1,12 +1,12 @@
+
/proc/create_all_lighting_objects()
for(var/area/A as anything in GLOB.areas)
- if(!IS_DYNAMIC_LIGHTING(A))
+ if(!A.static_lighting)
continue
for(var/turf/T as anything in A.get_contained_turfs())
- if(!IS_DYNAMIC_LIGHTING(T))
+ if(T.space_lit)
continue
-
- new/datum/lighting_object(T)
+ new /datum/lighting_object(T)
CHECK_TICK
CHECK_TICK
diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm
index 93839a8a6e79..cd4bc4db0f24 100644
--- a/code/modules/lighting/lighting_source.dm
+++ b/code/modules/lighting/lighting_source.dm
@@ -9,20 +9,33 @@
///The turf under the source atom.
var/turf/source_turf
- ///The turf the top_atom appears to over.
- var/turf/pixel_turf
+ /// How much to x shift our light by when displaying it
+ var/offset_x = 0
+ /// How much to y shift our light by when displaying it
+ var/offset_y = 0
+ /// How much larger our light sheet should be, based off offset_x and y
+ /// We clamp to at least 1, so if offset_x is 0.1, then this'll be 1
+ var/visual_offset
+
///Intensity of the emitter light.
var/light_power
/// The range of the emitted light.
var/light_range
/// The colour of the light, string, decomposed by parse_light_color()
var/light_color
+ /// The height of the light. The larger this is, the dimmer we'll start
+ var/light_height
// Variables for keeping track of the colour.
var/lum_r
var/lum_g
var/lum_b
+ /// What direction our angled light is pointed
+ var/light_dir = NONE
+ /// How many degrees of a circle should our light show. 360 is all of it, 180 is half, etc
+ var/light_angle = 360
+
// The lumcount values used to apply the light.
var/tmp/applied_lum_r
var/tmp/applied_lum_g
@@ -33,20 +46,18 @@
/// Whether we have applied our light yet or not.
var/applied = FALSE
-
/// whether we are to be added to SSlighting's sources_queue list for an update
var/needs_update = LIGHTING_NO_UPDATE
/datum/light_source/New(atom/owner, atom/top)
source_atom = owner // Set our new owner.
- LAZYADD(source_atom.light_sources, src)
+ add_to_light_sources(source_atom)
top_atom = top
if (top_atom != source_atom)
- LAZYADD(top_atom.light_sources, src)
+ add_to_light_sources(top_atom)
source_turf = top_atom
- pixel_turf = get_turf_pixel(top_atom) || source_turf
light_power = source_atom.light_power
light_range = source_atom.light_range
@@ -55,41 +66,80 @@
PARSE_LIGHT_COLOR(src)
update()
+ // if(GLOB.light_debug_enabled)
+ // source_atom.debug()
/datum/light_source/Destroy(force)
remove_lum()
if (source_atom)
- LAZYREMOVE(source_atom.light_sources, src)
+ remove_from_light_sources(source_atom)
if (top_atom)
- LAZYREMOVE(top_atom.light_sources, src)
+ remove_from_light_sources(top_atom)
if (needs_update)
SSlighting.sources_queue -= src
+ SSlighting.current_sources -= src
+
+ top_atom = null
+ source_atom = null
+ source_turf = null
+
+ return ..()
+
+///add this light source to new_atom_host's light_sources list. updating movement registrations as needed
+/datum/light_source/proc/add_to_light_sources(atom/new_atom_host)
+ if(QDELETED(new_atom_host))
+ return FALSE
+
+ LAZYADD(new_atom_host.light_sources, src)
+ //yes, we register the signal to the top atom too, this is intentional and ensures contained lighting updates properly
+ if(ismovable(new_atom_host))
+ RegisterSignal(new_atom_host, COMSIG_MOVABLE_MOVED, PROC_REF(update_host_lights))
+ return TRUE
+
+///remove this light source from old_atom_host's light_sources list, unsetting movement registrations
+/datum/light_source/proc/remove_from_light_sources(atom/old_atom_host)
+ if(QDELETED(old_atom_host))
+ return FALSE
+
+ LAZYREMOVE(old_atom_host.light_sources, src)
+ if(ismovable(old_atom_host))
+ UnregisterSignal(old_atom_host, COMSIG_MOVABLE_MOVED)
+ return TRUE
+
+///signal handler for when our host atom moves and we need to update our effects
+/datum/light_source/proc/update_host_lights(atom/movable/host)
+ SIGNAL_HANDLER
+
+ if(QDELETED(host))
+ return
- . = ..()
+ host.update_light()
// Yes this doesn't align correctly on anything other than 4 width tabs.
// If you want it to go switch everybody to elastic tab stops.
// Actually that'd be great if you could!
-#define EFFECT_UPDATE(level) \
- if (needs_update == LIGHTING_NO_UPDATE) \
- SSlighting.sources_queue += src; \
- if (needs_update < level) \
- needs_update = level; \
+#define EFFECT_UPDATE(level) \
+ if (needs_update == LIGHTING_NO_UPDATE) { \
+ SSlighting.sources_queue += src; \
+ } \
+ if (needs_update < level) { \
+ needs_update = level; \
+ }
-// This proc will cause the light source to update the top atom, and add itself to the update queue.
+/// This proc will cause the light source to update the top atom, and add itself to the update queue.
/datum/light_source/proc/update(atom/new_top_atom)
// This top atom is different.
if (new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom && top_atom.light_sources) // Remove ourselves from the light sources of that top atom.
- LAZYREMOVE(top_atom.light_sources, src)
+ remove_from_light_sources(top_atom)
top_atom = new_top_atom
if (top_atom != source_atom)
- LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom.
+ add_to_light_sources(top_atom)
EFFECT_UPDATE(LIGHTING_CHECK_UPDATE)
@@ -101,47 +151,189 @@
/datum/light_source/proc/vis_update()
EFFECT_UPDATE(LIGHTING_VIS_UPDATE)
+// This exists so we can cache the vars used in this macro, and save MASSIVE time :)
+// Most of this is saving off datum var accesses, tho some of it does actually cache computation
+// You will NEED to call this before you call APPLY_CORNER
+#define SETUP_CORNERS_CACHE(lighting_source) \
+ var/_turf_x = lighting_source.source_turf.x; \
+ var/_turf_y = lighting_source.source_turf.y; \
+ var/_turf_z = lighting_source.source_turf.z; \
+ var/list/_sheet = get_sheet(); \
+ var/list/_multiz_sheet = list(); \
+ if(!!GET_LOWEST_STACK_OFFSET(source_turf.z)) { \
+ _multiz_sheet = get_sheet(multiz = TRUE); \
+ } \
+ var/_range_offset = CEILING(lighting_source.light_range, 1) + 0.5 + 1 + lighting_source.visual_offset; \
+ var/_multiz_offset = SSmapping.max_plane_offset + 1; \
+ var/_light_power = lighting_source.light_power; \
+ var/_applied_lum_r = lighting_source.applied_lum_r; \
+ var/_applied_lum_g = lighting_source.applied_lum_g; \
+ var/_applied_lum_b = lighting_source.applied_lum_b; \
+ var/_lum_r = lighting_source.lum_r; \
+ var/_lum_g = lighting_source.lum_g; \
+ var/_lum_b = lighting_source.lum_b;
+
+#define SETUP_CORNERS_REMOVAL_CACHE(lighting_source) \
+ var/_applied_lum_r = lighting_source.applied_lum_r; \
+ var/_applied_lum_g = lighting_source.applied_lum_g; \
+ var/_applied_lum_b = lighting_source.applied_lum_b;
+
+// Read out of our sources light sheet, a map of offsets -> the luminosity to use
+#define LUM_FALLOFF(C) _sheet[C.x - _turf_x + _range_offset][C.y - _turf_y + _range_offset]
+#define LUM_FALLOFF_MULTIZ(C) _multiz_sheet[C.z - _turf_z + _multiz_offset][C.x - _turf_x + _range_offset][C.y - _turf_y + _range_offset]
+
// Macro that applies light to a new corner.
// It is a macro in the interest of speed, yet not having to copy paste it.
// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line.
// As such this all gets counted as a single line.
// The braces and semicolons are there to be able to do this on a single line.
-#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range)))
-
#define APPLY_CORNER(C) \
- . = LUM_FALLOFF(C, pixel_turf); \
- . *= light_power; \
+ if(C.z == _turf_z) { \
+ . = LUM_FALLOFF(C); \
+ } \
+ else { \
+ . = LUM_FALLOFF_MULTIZ(C) \
+ } \
+ . *= _light_power; \
var/OLD = effect_str[C]; \
\
C.update_lumcount \
( \
- (. * lum_r) - (OLD * applied_lum_r), \
- (. * lum_g) - (OLD * applied_lum_g), \
- (. * lum_b) - (OLD * applied_lum_b) \
- ); \
+ (. * _lum_r) - (OLD * _applied_lum_r), \
+ (. * _lum_g) - (OLD * _applied_lum_g), \
+ (. * _lum_b) - (OLD * _applied_lum_b) \
+ );
#define REMOVE_CORNER(C) \
. = -effect_str[C]; \
C.update_lumcount \
( \
- . * applied_lum_r, \
- . * applied_lum_g, \
- . * applied_lum_b \
+ . * _applied_lum_r, \
+ . * _applied_lum_g, \
+ . * _applied_lum_b \
);
-// This is the define used to calculate falloff.
-
+/// Returns a list of lists, indexed with ints, that can be read to get the lighting multiplier at any one point
+/// If the requested sheet is multiz, this will be 3 lists deep, first handling z level then x and y
+/// otherwise it's just two, x then y
+/datum/light_source/proc/get_sheet(multiz = FALSE)
+ var/list/static/key_to_sheet = list()
+ var/range = max(1, light_range);
+ var/key = "[range]-[visual_offset]-[offset_x]-[offset_y]-[light_dir]-[light_angle]-[light_height]-[multiz]"
+ var/list/hand_back = key_to_sheet[key]
+ if(!hand_back)
+ if(multiz)
+ hand_back = generate_sheet_multiz(range, visual_offset, offset_x, offset_y, light_dir, light_angle, light_height)
+ else
+ hand_back = generate_sheet(range, visual_offset, offset_x, offset_y, light_dir, light_angle, light_height)
+ key_to_sheet[key] = hand_back
+ return hand_back
+
+/// Returns a list of lists that encodes the light falloff of our source
+/// Takes anything that impacts our generation as input
+/// This function should be "pure", no side effects or reads from the source object
+/datum/light_source/proc/generate_sheet(range, visual_offset, x_offset, y_offset, center_dir, angle, height, z_level = 0)
+ var/list/encode = list()
+ // How far away the turfs we get are, and how many there are are often not the same calculation
+ // So we need to include the visual offset, so we can ensure our sheet is large enough to accept all the distance differences
+ var/bound_range = CEILING(range, 1) + visual_offset
+
+ // Corners are placed at 0.5 offsets
+ // We need our coords to reflect that (though x_offsets that change the basis for how things are calculated are fine too)
+ for(var/x in (-(bound_range) + x_offset - 0.5) to (bound_range + x_offset + 0.5))
+ var/list/row = list()
+ for(var/y in (-(bound_range) + y_offset - 0.5) to (bound_range + y_offset + 0.5))
+ row += falloff_at_coord(x, y, z_level, range, center_dir, angle, height)
+ encode += list(row)
+ return encode
+
+/// Returns a THREE dimensional list of lists that encodes the lighting falloff of our source
+/// Takes anything that impacts our generation as input
+/// This function should be "pure", no side effects or reads from the passed object
+/datum/light_source/proc/generate_sheet_multiz(range, visual_offset, x_offset, y_offset, center_dir, angle, height)
+ var/list/encode = list()
+ var/z_range = SSmapping.max_plane_offset // Let's just be safe yeah?
+ for(var/z in -z_range to z_range)
+ var/list/sheet = generate_sheet(range, visual_offset, x_offset, y_offset, center_dir, angle, height, z)
+ encode += list(sheet)
+ return encode
+
+/// Takes x y and z offsets from the source as input, alongside our source's range
+/// Returns a value between 0 and 1, 0 being dark on that tile, 1 being fully lit
+/datum/light_source/proc/falloff_at_coord(x, y, z, range, center_dir, angle, height)
+ var/range_divisor = max(1, range)
+
+ // You may notice we use squares here even though there are three components
+ // Because z diffs are so functionally small, cubes and cube roots are too aggressive
+ // The larger the distance is, the less bright our light will be
+ var/multiplier = 1 - CLAMP01(sqrt(x ** 2 + y ** 2 + z ** 2 + height) / range_divisor)
+ if(angle >= 360 || angle <= 0)
+ return multiplier
+
+ // Turn our positional offset into an angle
+ var/coord_angle = delta_to_angle(x, y)
+ // Get the difference between the angle we want, and the angle we have
+ var/center_angle = dir2angle(center_dir)
+ var/angle_delta = abs(center_angle - coord_angle)
+ // Now we have to normalize the angle delta to be between 0 and 180, instead of 0 and 360
+ // This ensures removing say, 15 degrees removes it from both sides, rather then just one
+ // Turns an unfurling fan into a pair of scissors
+ if(angle_delta > 180)
+ angle_delta = 180 - (angle_delta - 180)
+ // We allow angle deltas to a certian amount, angle / 2
+ // If we pass that, then it starts effecting the visuals
+ // Oh and we'll scale it so 30 degrees is the "0" point, where things become fully dark
+ // This could be variable, it just isn't yet yaknow?
+ return max(multiplier * (1 - max(angle_delta - (angle / 2), 0) / 30), 0)
+
+/// Dumps the content of a lighting sheet to chat, for debugging
+/datum/light_source/proc/print_sheet()
+ var/list/sheet = get_sheet()
+ var/list/output = list()
+ var/multiz_depth = 1
+ // If we have a list 3 layers down we're multiz
+ if(length(sheet[1][1]))
+ multiz_depth = length(sheet)
+ var/column_seperator = ""
+ for(var/i in 1 to length(sheet))
+ column_seperator += "----"
+ output += column_seperator
+ for(var/i in 1 to multiz_depth)
+ for(var/list/column in sheet)
+ var/list/print_column = list()
+ for(var/row in column)
+ print_column += round(row, 0.1)
+ output += print_column.Join(", ")
+ output += column_seperator
+ to_chat(usr, "\n[output.Join("\n")]")
+
+/// Debug proc, for when lighting sheets fuck up
+/// Accepts the sheet (2 or 3 (multiz) dimensional list of lighting values at some offset)
+/// alongside x and y delta values and the sheet's "offset", which is the amount required to ensure everything indexes at 1
+/// Optionally, you can pass similar values for multiz stuff
+/proc/read_sheet(list/sheet, x, y, offset, z, z_offset)
+ var/list/working = sheet
+ var/offset_x = x + offset
+ var/offset_y = y + offset
+ var/offset_z = z + z_offset
+ if(z)
+ working = sheet[offset_z]
+ var/list/line = working[offset_x]
+ var/word = line[offset_y]
+ return word
+
+/// This is the define used to calculate falloff.
/datum/light_source/proc/remove_lum()
+ SETUP_CORNERS_REMOVAL_CACHE(src)
applied = FALSE
for (var/datum/lighting_corner/corner as anything in effect_str)
REMOVE_CORNER(corner)
LAZYREMOVE(corner.affecting, src)
- SSdemo.mark_turf(corner.master_NE)
- SSdemo.mark_turf(corner.master_SE)
- SSdemo.mark_turf(corner.master_SW)
- SSdemo.mark_turf(corner.master_NW)
+
+ effect_str = null
/datum/light_source/proc/recalc_corner(datum/lighting_corner/corner)
+ SETUP_CORNERS_CACHE(src)
LAZYINITLIST(effect_str)
if (effect_str[corner]) // Already have one.
REMOVE_CORNER(corner)
@@ -151,13 +343,41 @@
effect_str[corner] = .
-/datum/light_source/proc/update_corners()
+// Keep in mind. Lighting corners accept the bottom left (northwest) set of cords to them as input
+#define GENERATE_MISSING_CORNERS(gen_for) \
+ if (!gen_for.lighting_corner_NE) { \
+ gen_for.lighting_corner_NE = new /datum/lighting_corner(gen_for.x, gen_for.y, gen_for.z); \
+ } \
+ if (!gen_for.lighting_corner_SE) { \
+ gen_for.lighting_corner_SE = new /datum/lighting_corner(gen_for.x, gen_for.y - 1, gen_for.z); \
+ } \
+ if (!gen_for.lighting_corner_SW) { \
+ gen_for.lighting_corner_SW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y - 1, gen_for.z); \
+ } \
+ if (!gen_for.lighting_corner_NW) { \
+ gen_for.lighting_corner_NW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y, gen_for.z); \
+ } \
+ gen_for.lighting_corners_initialised = TRUE;
+
+#define INSERT_CORNERS(insert_into, draw_from) \
+ if (!draw_from.lighting_corners_initialised) { \
+ GENERATE_MISSING_CORNERS(draw_from); \
+ } \
+ insert_into[draw_from.lighting_corner_NE] = 0; \
+ insert_into[draw_from.lighting_corner_SE] = 0; \
+ insert_into[draw_from.lighting_corner_SW] = 0; \
+ insert_into[draw_from.lighting_corner_NW] = 0;
+
+/// Refreshes our lighting source to match its parent atom
+/// Returns TRUE if an update is needed, FALSE otherwise
+/datum/light_source/proc/refresh_values()
var/update = FALSE
var/atom/source_atom = src.source_atom
+ var/turf/old_source_turf = source_turf
if (QDELETED(source_atom))
qdel(src)
- return
+ return FALSE
if (source_atom.light_power != light_power)
light_power = source_atom.light_power
@@ -173,27 +393,24 @@
if (!light_range || !light_power)
qdel(src)
- return
+ return FALSE
- if (isturf(top_atom))
- if (source_turf != top_atom)
+ var/atom/visual_source = source_atom
+ if(isturf(top_atom))
+ visual_source = source_atom
+ if(source_turf != top_atom)
source_turf = top_atom
- pixel_turf = source_turf
update = TRUE
- else if (top_atom.loc != source_turf)
- source_turf = top_atom.loc
- pixel_turf = get_turf_pixel(top_atom)
- update = TRUE
else
- var/pixel_loc = get_turf_pixel(top_atom)
- if (pixel_loc != pixel_turf)
- pixel_turf = pixel_loc
+ visual_source = top_atom
+ if(top_atom.loc != source_turf)
+ source_turf = top_atom.loc
update = TRUE
if (!isturf(source_turf))
if (applied)
remove_lum()
- return
+ return FALSE
if (light_range && light_power && !applied)
update = TRUE
@@ -206,45 +423,104 @@
else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b)
update = TRUE
+ if(source_atom.light_dir != light_dir)
+ light_dir = source_atom.light_dir
+ update = TRUE
+
+ if (source_atom.light_angle != light_angle)
+ light_angle = source_atom.light_angle
+ update = TRUE
+
+ if(source_atom.light_height != light_height)
+ light_height = source_atom.light_height
+ update = TRUE
+
+ var/list/visual_offsets = calculate_light_offset(visual_source)
+ if(visual_offsets[1] != offset_x || visual_offsets[2] != offset_y || source_turf != old_source_turf)
+ offset_x = visual_offsets[1]
+ offset_y = visual_offsets[2]
+ visual_offset = max(CEILING(abs(offset_x), 1), CEILING(abs(offset_y), 1))
+ update = TRUE
+
+ // If we need to update, well, update
if (update)
needs_update = LIGHTING_CHECK_UPDATE
applied = TRUE
- else if (needs_update == LIGHTING_CHECK_UPDATE)
- return //nothing's changed
+ return TRUE
+ // Otherwise, go off the needs_update var. If it requires an update provide one, otherwise we're kosher
+ if (needs_update == LIGHTING_CHECK_UPDATE)
+ return FALSE //nothing's changed
+ return TRUE
+
+/// Returns a list of lighting corners this source impacts
+/datum/light_source/proc/impacted_corners()
var/list/datum/lighting_corner/corners = list()
- var/list/turf/turfs = list()
-
- if (source_turf)
- var/oldlum = source_turf.luminosity
- source_turf.luminosity = CEILING(light_range, 1)
- for(var/turf/T in view(CEILING(light_range, 1), source_turf))
- if(!IS_OPAQUE_TURF(T))
- if (!T.lighting_corners_initialised)
- T.generate_missing_corners()
- corners[T.lighting_corner_NE] = 0
- corners[T.lighting_corner_SE] = 0
- corners[T.lighting_corner_SW] = 0
- corners[T.lighting_corner_NW] = 0
- turfs += T
- SSdemo.mark_turf(T)
+ if (!source_turf)
+ return list()
+
+ var/oldlum = source_turf.luminosity
+ var/working_range = CEILING(light_range + visual_offset, 1)
+ source_turf.luminosity = working_range
+
+ var/uses_multiz = !!GET_LOWEST_STACK_OFFSET(source_turf.z)
+
+ if(!uses_multiz) // Yes I know this could be acomplished with an if in the for loop, but it's fukin lighting code man
+ for(var/turf/T in view(working_range, source_turf))
+ if(IS_OPAQUE_TURF(T))
+ continue
+ INSERT_CORNERS(corners, T)
source_turf.luminosity = oldlum
+ return corners
+
+ for(var/turf/T in view(working_range, source_turf))
+ if(IS_OPAQUE_TURF(T))
+ continue
+ INSERT_CORNERS(corners, T)
+
+ var/turf/below = GET_TURF_BELOW(T)
+ var/turf/previous = T
+ while(below)
+ // If we find a non transparent previous, end
+ if(!istransparentturf(previous))
+ break
+ if(IS_OPAQUE_TURF(below))
+ // If we're opaque but the tile above us is transparent, then we should be counted as part of the potential "space"
+ // Of this corner
+ break
+ // Now we do lighting things to it
+ INSERT_CORNERS(corners, below)
+ // ANNND then we add the one below it
+ previous = below
+ below = GET_TURF_BELOW(below)
+
+ var/turf/above = GET_TURF_ABOVE(T)
+ while(above)
+ // If we find a non transparent turf, end
+ if(!istransparentturf(above) || IS_OPAQUE_TURF(above))
+ break
+ INSERT_CORNERS(corners, above)
+ above = GET_TURF_ABOVE(above)
+
+ source_turf.luminosity = oldlum
+ return corners
- var/list/datum/lighting_corner/new_corners = (corners - effect_str)
- LAZYINITLIST(effect_str)
- if (needs_update == LIGHTING_VIS_UPDATE)
- for (var/datum/lighting_corner/corner as anything in new_corners)
- APPLY_CORNER(corner)
- if (. != 0)
- LAZYADD(corner.affecting, src)
- effect_str[corner] = .
- else
- for (var/datum/lighting_corner/corner as anything in new_corners)
- APPLY_CORNER(corner)
- if (. != 0)
- LAZYADD(corner.affecting, src)
- effect_str[corner] = .
+/datum/light_source/proc/update_corners()
+ if(!refresh_values())
+ return
+ var/list/datum/lighting_corner/corners = impacted_corners()
+ SETUP_CORNERS_CACHE(src)
+
+ var/list/datum/lighting_corner/new_corners = (corners - src.effect_str)
+ LAZYINITLIST(src.effect_str)
+ for (var/datum/lighting_corner/corner as anything in new_corners)
+ APPLY_CORNER(corner)
+ if (. != 0)
+ LAZYADD(corner.affecting, src)
+ effect_str[corner] = .
+ // New corners are a subset of corners. so if they're both the same length, there are NO old corners!
+ if(needs_update != LIGHTING_VIS_UPDATE && length(corners) != length(new_corners))
for (var/datum/lighting_corner/corner as anything in corners - new_corners) // Existing corners
APPLY_CORNER(corner)
if (. != 0)
@@ -263,9 +539,14 @@
applied_lum_g = lum_g
applied_lum_b = lum_b
- UNSETEMPTY(effect_str)
+ UNSETEMPTY(src.effect_str)
+#undef APPLY_CORNER
#undef EFFECT_UPDATE
+#undef GENERATE_MISSING_CORNERS
+#undef INSERT_CORNERS
#undef LUM_FALLOFF
+#undef LUM_FALLOFF_MULTIZ
#undef REMOVE_CORNER
-#undef APPLY_CORNER
+#undef SETUP_CORNERS_CACHE
+#undef SETUP_CORNERS_REMOVAL_CACHE
diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm
index 871099973730..26ebbd1ba4e2 100644
--- a/code/modules/lighting/lighting_turf.dm
+++ b/code/modules/lighting/lighting_turf.dm
@@ -14,11 +14,7 @@
if (lighting_object)
qdel(lighting_object, force=TRUE) //Shitty fix for lighting objects persisting after death
- var/area/our_area = loc
- if (!IS_DYNAMIC_LIGHTING(our_area) && !light_sources)
- return
-
- new/datum/lighting_object(src)
+ new /datum/lighting_object(src)
// Used to get a scaled lumcount.
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
@@ -57,7 +53,8 @@
if (!lighting_object)
return FALSE
- return !luminosity
+ return !(luminosity || dynamic_lumcount)
+
///Proc to add movable sources of opacity on the turf and let it handle lighting code.
/turf/proc/add_opacity_source(atom/movable/new_source)
@@ -74,6 +71,7 @@
return
recalculate_directional_opacity()
+
///Calculate on which directions this turfs block view.
/turf/proc/recalculate_directional_opacity()
. = directional_opacity
@@ -93,47 +91,28 @@
if(. != directional_opacity && (. == ALL_CARDINALS || directional_opacity == ALL_CARDINALS))
reconsider_lights() //The lighting system only cares whether the tile is fully concealed from all directions or not.
-/turf/proc/change_area(area/old_area, area/new_area)
- if(SSlighting.initialized)
- if (new_area.dynamic_lighting != old_area.dynamic_lighting)
- if (new_area.dynamic_lighting)
+///Transfer the lighting of one area to another
+/turf/proc/transfer_area_lighting(area/old_area, area/new_area)
+ if(SSlighting.initialized && !space_lit)
+ if (new_area.static_lighting != old_area.static_lighting)
+ if (new_area.static_lighting)
lighting_build_overlay()
else
lighting_clear_overlay()
-/turf/proc/generate_missing_corners()
- if (!lighting_corner_NE)
- lighting_corner_NE = new/datum/lighting_corner(src, NORTH|EAST)
-
- if (!lighting_corner_SE)
- lighting_corner_SE = new/datum/lighting_corner(src, SOUTH|EAST)
-
- if (!lighting_corner_SW)
- lighting_corner_SW = new/datum/lighting_corner(src, SOUTH|WEST)
-
- if (!lighting_corner_NW)
- lighting_corner_NW = new/datum/lighting_corner(src, NORTH|WEST)
-
- lighting_corners_initialised = TRUE
-
-/turf/proc/get_affecting_lights()
- var/list/affecting = list()
-
- if (!lighting_object)
- return affecting
-
- var/datum/lighting_corner/L
- L = lighting_corner_NE
- if (L)
- affecting += L.affecting
- L = lighting_corner_SE
- if (L)
- affecting += L.affecting
- L = lighting_corner_SW
- if (L)
- affecting += L.affecting
- L = lighting_corner_NW
- if (L)
- affecting += L.affecting
-
- return uniqueList(affecting)
+ // We will only run this logic on turfs off the prime z layer
+ // Since on the prime z layer, we use an overlay on the area instead, to save time
+ if(SSmapping.z_level_to_plane_offset[z])
+ var/index = SSmapping.z_level_to_plane_offset[z] + 1
+ //Inherit overlay of new area
+ if(old_area.lighting_effects)
+ cut_overlay(old_area.lighting_effects[index])
+ if(new_area.lighting_effects)
+ add_overlay(new_area.lighting_effects[index])
+
+ // Manage removing/adding starlight overlays, we'll inherit from the area so we can drop it if the area has it already
+ if(space_lit)
+ if(!new_area.lighting_effects && old_area.lighting_effects)
+ overlays += GLOB.starlight_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
+ else if (new_area.lighting_effects && !old_area.lighting_effects)
+ overlays -= GLOB.starlight_overlays[GET_TURF_PLANE_OFFSET(src) + 1]
diff --git a/code/modules/lighting/static_lighting_area.dm b/code/modules/lighting/static_lighting_area.dm
new file mode 100644
index 000000000000..e05868d0b829
--- /dev/null
+++ b/code/modules/lighting/static_lighting_area.dm
@@ -0,0 +1,59 @@
+/// List of plane offset + 1 -> object to display to use
+/// Fills with offsets as they are generated
+/// Holds a list of objects that represent starlight. The idea is to render_source them
+/// So modifying starlight requires touching only one place (NOTE: this doesn't work for the area overlays)
+/// In order to modify them you need to use set_starlight. Areas don't work with render sources it looks like
+GLOBAL_LIST_INIT_TYPED(starlight_objects, /obj, list(starlight_object(0)))
+/obj/starlight_appearance
+ icon = 'icons/effects/alphacolors.dmi'
+ icon_state = "white"
+ layer = LIGHTING_PRIMARY_LAYER
+ blend_mode = BLEND_ADD
+ screen_loc = "1,1"
+
+/proc/starlight_object(offset)
+ var/obj/starlight_appearance/glow = new()
+ SET_PLANE_W_SCALAR(glow, LIGHTING_PLANE, offset)
+ glow.layer = LIGHTING_PRIMARY_LAYER
+ glow.blend_mode = BLEND_ADD
+ glow.color = GLOB.starlight_color
+ glow.render_target = SPACE_OVERLAY_RENDER_TARGET(offset)
+ return glow
+
+/// List of plane offset + 1 -> mutable appearance to use
+/// Fills with offsets as they are generated
+/// They mirror their appearance from the starlight objects, which lets us save
+/// time updating them
+GLOBAL_LIST_INIT_TYPED(starlight_overlays, /obj, list(starlight_overlay(0)))
+
+/proc/starlight_overlay(offset)
+ var/mutable_appearance/glow = new /mutable_appearance()
+ SET_PLANE_W_SCALAR(glow, LIGHTING_PLANE, offset)
+ glow.layer = LIGHTING_PRIMARY_LAYER
+ glow.blend_mode = BLEND_ADD
+ glow.render_source = SPACE_OVERLAY_RENDER_TARGET(offset)
+ return glow
+
+/area
+ ///Whether this area allows static lighting and thus loads the lighting objects
+ var/static_lighting = TRUE
+
+//Non static lighting areas.
+//Any lighting area that wont support static lights.
+//These areas will NOT have corners generated.
+
+///regenerates lighting objects for turfs in this area, primary use is VV changes
+/area/proc/create_area_lighting_objects()
+ for(var/turf/T in src)
+ if(T.space_lit)
+ continue
+ T.lighting_build_overlay()
+ CHECK_TICK
+
+///Removes lighting objects from turfs in this area if we have them, primary use is VV changes
+/area/proc/remove_area_lighting_objects()
+ for(var/turf/T in src)
+ if(T.space_lit)
+ continue
+ T.lighting_clear_overlay()
+ CHECK_TICK
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 7a5695b82748..e5a25d297f46 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -6,14 +6,38 @@
var/loaded = 0 // Times loaded this round
var/datum/parsed_map/cached_map
var/keep_cached_map = FALSE
+ var/station_id = null // used to override the root id when generating
+
+ ///Default area associated with the map template
+ var/default_area
+
+ ///if true, turfs loaded from this template are placed on top of the turfs already there, defaults to TRUE
+ var/should_place_on_top = TRUE
+
+ ///if true, creates a list of all atoms created by this template loading, defaults to FALSE
+ var/returns_created_atoms = FALSE
+
+ ///the list of atoms created by this template being loaded, only populated if returns_created_atoms is TRUE
+ var/list/created_atoms = list()
+ //make sure this list is accounted for/cleared if you request it from ssatoms!
+
+ ///If true, any openspace turfs above the template will be replaced with ceiling_turf when loading. Should probably be FALSE for lower levels of multi-z ruins.
+ var/has_ceiling = FALSE
+ ///What turf to replace openspace with when has_ceiling is true
+ var/turf/ceiling_turf = /turf/open/floor/plating
+ ///What baseturfs to set when replacing openspace when has_ceiling is true
+ var/list/ceiling_baseturfs = list()
/datum/map_template/New(path = null, rename = null, cache = FALSE)
+ SHOULD_CALL_PARENT(TRUE)
+ . = ..()
if(path)
mappath = path
if(mappath)
preload_size(mappath, cache)
if(rename)
name = rename
+ ceiling_baseturfs.Insert(1, /turf/baseturf_bottom)
/datum/map_template/proc/preload_size(path, cache = FALSE)
var/datum/parsed_map/parsed = new(file(path))
@@ -70,15 +94,16 @@
// first or not. Its defined In Initialize yet its run first in templates
// BEFORE so... hummm
SSmapping.reg_in_areas_in_z(areas)
- //SSnetworks.assign_areas_root_ids(areas, src)
if(!SSatoms.initialized)
return
- SSatoms.InitializeAtoms(areas + turfs + movables)
+ SSatoms.InitializeAtoms(areas + turfs + movables, returns_created_atoms ? created_atoms : null)
for(var/turf/unlit as anything in turfs)
+ if(unlit.space_lit)
+ continue
var/area/loc_area = unlit.loc
- if(!IS_DYNAMIC_LIGHTING(loc_area))
+ if(!loc_area.static_lighting)
continue
unlit.lighting_build_overlay()
@@ -86,7 +111,6 @@
// need these two below?
SSmachines.setup_template_powernets(cables)
SSair.setup_template_machinery(atmos_machines)
- SSshuttle.setup_shuttles(ports)
//calculate all turfs inside the border
var/list/template_and_bordering_turfs = block(
@@ -102,15 +126,23 @@
)
)
for(var/turf/affected_turf as anything in template_and_bordering_turfs)
- affected_turf.ImmediateCalculateAdjacentTurfs()
+ affected_turf.air_update_turf(TRUE, TRUE)
affected_turf.levelupdate()
/datum/map_template/proc/load_new_z(secret = FALSE)
- var/x = round((world.maxx - width)/2)
- var/y = round((world.maxy - height)/2)
+ var/x = round((world.maxx - width) * 0.5) + 1
+ var/y = round((world.maxy - height) * 0.5) + 1
var/datum/space_level/level = SSmapping.add_new_zlevel(name, secret ? ZTRAITS_AWAY_SECRET : ZTRAITS_AWAY, contain_turfs = FALSE)
- var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE, new_z = TRUE)
+ var/datum/parsed_map/parsed = load_map(
+ file(mappath),
+ x,
+ y,
+ level.z_value,
+ no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS),
+ place_on_top = should_place_on_top,
+ new_z = TRUE,
+ )
var/list/bounds = parsed.bounds
if(!bounds)
return FALSE
@@ -119,7 +151,7 @@
//initialize things that are normally initialized after map load
initTemplateBounds(bounds)
smooth_zlevel(world.maxz)
- log_game("Z-level [name] loaded at at [x],[y],[world.maxz]")
+ log_game("Z-level [name] loaded at [x],[y],[world.maxz]")
return level
@@ -133,12 +165,36 @@
if((T.y+height) - 1 > world.maxy)
return
+ // // Cache for sonic speed
+ // var/list/to_rebuild = SSair.adjacent_rebuild
+ // // iterate over turfs in the border and clear them from active atmos processing
+ // for(var/turf/border_turf as anything in CORNER_BLOCK_OFFSET(T, width + 2, height + 2, -1, -1))
+ // SSair.remove_from_active(border_turf)
+ // to_rebuild -= border_turf
+ // for(var/turf/sub_turf as anything in border_turf.atmos_adjacent_turfs)
+ // sub_turf.atmos_adjacent_turfs?.Remove(border_turf)
+ // border_turf.atmos_adjacent_turfs?.Cut()
+
// Accept cached maps, but don't save them automatically - we don't want
// ruins clogging up memory for the whole round.
var/datum/parsed_map/parsed = cached_map || new(file(mappath))
cached_map = keep_cached_map ? parsed : null
- if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE))
+
+ var/list/turf_blacklist = list()
+ update_blacklist(T, turf_blacklist)
+
+ UNSETEMPTY(turf_blacklist)
+ parsed.turf_blacklist = turf_blacklist
+ if(!parsed.load(
+ T.x,
+ T.y,
+ T.z,
+ crop_map = TRUE,
+ no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS),
+ place_on_top = should_place_on_top,
+ ))
return
+
var/list/bounds = parsed.bounds
if(!bounds)
return
@@ -148,9 +204,26 @@
//initialize things that are normally initialized after map load
initTemplateBounds(bounds)
- log_game("[name] loaded at at [T.x],[T.y],[T.z]")
+ if(has_ceiling)
+ var/affected_turfs = get_affected_turfs(T, FALSE)
+ generate_ceiling(affected_turfs)
+
+ log_game("[name] loaded at [T.x],[T.y],[T.z]")
return bounds
+/datum/map_template/proc/generate_ceiling(affected_turfs)
+ for (var/turf/turf in affected_turfs)
+ var/turf/ceiling = get_step_multiz(turf, UP)
+ if (ceiling)
+ if (istype(ceiling, /turf/open/openspace) || istype(ceiling, /turf/open/space/openspace))
+ ceiling.ChangeTurf(ceiling_turf, ceiling_baseturfs, CHANGETURF_INHERIT_AIR)
+
+/datum/map_template/proc/post_load()
+ return
+
+/datum/map_template/proc/update_blacklist(turf/T, list/input_blacklist)
+ return
+
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE)
var/turf/placement = T
if(centered)
@@ -159,10 +232,27 @@
placement = corner
return block(placement, locate(placement.x+width-1, placement.y+height-1, placement.z))
+/// Takes in a type path, locates an instance of that type in the cached map, and calculates its offset from the origin of the map, returns this offset in the form list(x, y).
+/datum/map_template/proc/discover_offset(obj/marker)
+ var/key
+ var/list/models = cached_map.grid_models
+ for(key in models)
+ if(findtext(models[key], "[marker]")) // Yay compile time checks
+ break // This works by assuming there will ever only be one mobile dock in a template at most
+
+ for(var/datum/grid_set/gset as anything in cached_map.gridSets)
+ var/ycrd = gset.ycrd
+ for(var/line in gset.gridLines)
+ var/xcrd = gset.xcrd
+ for(var/j in 1 to length(line) step cached_map.key_len)
+ if(key == copytext(line, j, j + cached_map.key_len))
+ return list(xcrd, ycrd)
+ ++xcrd
+ --ycrd
+
//for your ever biggening badminnery kevinz000
//❤ - Cyberboss
-
/proc/load_new_z_level(file, name, secret)
var/datum/map_template/template = new(file, name, TRUE)
if(!template.cached_map || template.cached_map.check_for_errors())
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index 0858fb4ea0dd..43f08d0157c9 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -6,11 +6,12 @@
name = "baseturf editor"
icon = 'icons/effects/mapping_helpers.dmi'
icon_state = ""
-
+ /// Replacing a specific turf
var/list/baseturf_to_replace
+ /// The desired bottom turf
var/baseturf
- layer = POINT_LAYER
+ plane = POINT_PLANE
/obj/effect/baseturf_helper/Initialize(mapload)
. = ..()
@@ -33,22 +34,16 @@
qdel(src)
+/// Replaces all the requested baseturfs (usually space/baseturfbottom) with the desired baseturf. Skips if its already there
/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing)
- var/list/baseturf_cache = thing.baseturfs
- if(length(baseturf_cache))
- for(var/i in baseturf_cache)
- if(baseturf_to_replace[i])
- baseturf_cache -= i
- if(!baseturf_cache.len)
- thing.assemble_baseturfs(baseturf)
- else
- thing.PlaceOnBottom(null, baseturf)
- else if(baseturf_to_replace[thing.baseturfs])
- thing.assemble_baseturfs(baseturf)
- else
- thing.PlaceOnBottom(null, baseturf)
+ thing.remove_baseturfs_from_typecache(baseturf_to_replace)
+ if(length(thing.baseturfs))
+ var/turf/tile = thing.baseturfs[1]
+ if(tile == baseturf)
+ return
+ thing.place_on_bottom(baseturf)
/obj/effect/baseturf_helper/space
name = "space baseturf editor"
@@ -90,6 +85,9 @@
/obj/effect/mapping_helpers
icon = 'icons/effects/mapping_helpers.dmi'
icon_state = ""
+ anchored = TRUE
+ // Unless otherwise specified, layer above everything
+ layer = ABOVE_ALL_MOB_LAYER
var/late = FALSE
/obj/effect/mapping_helpers/Initialize(mapload)
@@ -180,7 +178,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
/obj/effect/mapping_helpers/no_lava/Initialize(mapload)
. = ..()
var/turf/T = get_turf(src)
- T.flags_1 |= NO_LAVA_GEN_1
+ T.turf_flags |= NO_LAVA_GEN
//This helper applies components to things on the map directly.
/obj/effect/mapping_helpers/component_injector
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index 6a604037a7a7..ab6743ff17a0 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -1,7 +1,6 @@
///////////////////////////////////////////////////////////////
//SS13 Optimized Map loader
//////////////////////////////////////////////////////////////
-#define SPACE_KEY "space"
// We support two different map formats
// It is kinda possible to process them together, but if we split them up
// I can make optimization decisions more easily
@@ -47,7 +46,6 @@
* Files are kinda susy, and may not actually work. buyer beware
* Lists support assoc values as expected
* These constants can be further embedded into lists
- * One var edited list will be shared among all the things it is applied to
*
* There can be no padding in front of, or behind a path
*
@@ -108,30 +106,79 @@
/// Pulls out model paths for DMM
var/static/regex/model_path = new(@'(\/[^\{]*?(?:\{.*?\})?)(?:,|$)', "g")
+ /// If we are currently loading this map
+ var/loading = FALSE
+
#ifdef TESTING
var/turfsSkipped = 0
#endif
+/datum/parsed_map/proc/copy()
+ // Avoids duped work just in case
+ build_cache()
+ var/datum/parsed_map/newfriend = new()
+ newfriend.original_path = original_path
+ newfriend.map_format = map_format
+ newfriend.key_len = key_len
+ newfriend.line_len = line_len
+ newfriend.grid_models = grid_models.Copy()
+ newfriend.gridSets = gridSets.Copy()
+ newfriend.modelCache = modelCache.Copy()
+ newfriend.parsed_bounds = parsed_bounds.Copy()
+ // Copy parsed bounds to reset to initial values
+ newfriend.bounds = parsed_bounds.Copy()
+ newfriend.turf_blacklist = turf_blacklist?.Copy()
+ return newfriend
+
//text trimming (both directions) helper macro
#define TRIM_TEXT(text) (trim_reduced(text))
-/// Shortcut function to parse a map and apply it to the world.
-///
-/// - `dmm_file`: A .dmm file to load (Required).
-/// - `x_offset`, `y_offset`, `z_offset`: Positions representign where to load the map (Optional).
-/// - `cropMap`: When true, the map will be cropped to fit the existing world dimensions (Optional).
-/// - `measureOnly`: When true, no changes will be made to the world (Optional).
-/// - `no_changeturf`: When true, [/turf/proc/AfterChange] won't be called on loaded turfs
-/// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional).
-/// - `placeOnTop`: Whether to use [/turf/proc/PlaceOnTop] rather than [/turf/proc/ChangeTurf] (Optional).
-/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num, new_z)
- var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, measureOnly)
- if(parsed.bounds && !measureOnly)
- parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z = new_z)
- return parsed
+/**
+ * Helper and recommened way to load a map file
+ * - dmm_file: The path to the map file
+ * - x_offset: The x offset to load the map at
+ * - y_offset: The y offset to load the map at
+ * - z_offset: The z offset to load the map at
+ * - crop_map: If true, the map will be cropped to the world bounds
+ * - measure_only: If true, the map will not be loaded, but the bounds will be calculated
+ * - no_changeturf: If true, the map will not call /turf/AfterChange
+ * - x_lower: The minimum x coordinate to load
+ * - x_upper: The maximum x coordinate to load
+ * - y_lower: The minimum y coordinate to load
+ * - y_upper: The maximum y coordinate to load
+ * - z_lower: The minimum z coordinate to load
+ * - z_upper: The maximum z coordinate to load
+ * - place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf
+ * - new_z: If true, a new z level will be created for the map
+ */
+/proc/load_map(
+ dmm_file,
+ x_offset = 0,
+ y_offset = 0,
+ z_offset = 0,
+ crop_map = FALSE,
+ measure_only = FALSE,
+ no_changeturf = FALSE,
+ x_lower = -INFINITY,
+ x_upper = INFINITY,
+ y_lower = -INFINITY,
+ y_upper = INFINITY,
+ z_lower = -INFINITY,
+ z_upper = INFINITY,
+ place_on_top = FALSE,
+ new_z = FALSE,
+)
+ if(!(dmm_file in GLOB.cached_maps))
+ GLOB.cached_maps[dmm_file] = new /datum/parsed_map(dmm_file)
+
+ var/datum/parsed_map/parsed_map = GLOB.cached_maps[dmm_file]
+ parsed_map = parsed_map.copy()
+ if(!measure_only && !isnull(parsed_map.bounds))
+ parsed_map.load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
+ return parsed_map
/// Parse a map, possibly cropping it.
-/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE)
+/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, z_lower = -INFINITY, z_upper=INFINITY, measureOnly=FALSE)
// This proc sleeps for like 6 seconds. why?
// Is it file accesses? if so, can those be done ahead of time, async to save on time here? I wonder.
// Love ya :)
@@ -182,20 +229,26 @@
CRASH("Coords before model definition in DMM")
var/curr_x = text2num(regexOutput[3])
-
if(curr_x < x_lower || curr_x > x_upper)
continue
+ var/curr_y = text2num(regexOutput[4])
+ if(curr_y < y_lower || curr_y > y_upper)
+ continue
+
+ var/curr_z = text2num(regexOutput[5])
+ if(curr_z < z_lower || curr_z > z_upper)
+ continue
+
var/datum/grid_set/gridSet = new
gridSet.xcrd = curr_x
- //position of the currently processed square
- gridSet.ycrd = text2num(regexOutput[4])
- gridSet.zcrd = text2num(regexOutput[5])
+ gridSet.ycrd = curr_y
+ gridSet.zcrd = curr_z
bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x)
- bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd)
- bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd)
+ bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_y)
+ bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z)
var/list/gridLines = splittext(regexOutput[6], "\n")
gridSet.gridLines = gridLines
@@ -236,31 +289,49 @@
bounds[MAP_MAXX] = clamp(bounds[MAP_MAXX], x_lower, x_upper)
bounds[MAP_MINY] = clamp(bounds[MAP_MINY], y_lower, y_upper)
bounds[MAP_MAXY] = clamp(bounds[MAP_MAXY], y_lower, y_upper)
+ bounds[MAP_MINZ] = clamp(bounds[MAP_MINZ], z_lower, z_upper)
+ bounds[MAP_MAXZ] = clamp(bounds[MAP_MAXZ], z_lower, z_upper)
parsed_bounds = src.bounds
src.key_len = key_len
src.line_len = line_len
-/// Load the parsed map into the world. See [/proc/load_map] for arguments.
-/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, whitelist = FALSE, new_z)
+/// Iterates over all grid sets and returns ones with z values within the given bounds. Inclusive
+/datum/parsed_map/proc/filter_grid_sets_based_on_z_bounds(lower_z, upper_z)
+ var/list/filtered_sets = list()
+ for(var/datum/grid_set/grid_set as anything in gridSets)
+ if(grid_set.zcrd < lower_z)
+ continue
+ if(grid_set.zcrd > upper_z)
+ continue
+ filtered_sets += grid_set
+ return filtered_sets
+
+/// Load the parsed map into the world. You probably want [/proc/load_map]. Keep the signature the same.
+/datum/parsed_map/proc/load(x_offset = 0, y_offset = 0, z_offset = 0, crop_map = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, place_on_top = FALSE, new_z = FALSE)
//How I wish for RAII
Master.StartLoadingMap()
- . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+ . = _load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
Master.StopLoadingMap()
#define MAPLOADING_CHECK_TICK \
if(TICK_CHECK) { \
- SSatoms.map_loader_stop(); \
- stoplag(); \
- SSatoms.map_loader_begin(); \
+ if(loading) { \
+ SSatoms.map_loader_stop(REF(src)); \
+ stoplag(); \
+ SSatoms.map_loader_begin(REF(src)); \
+ } else { \
+ stoplag(); \
+ } \
}
// Do not call except via load() above.
-/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, new_z = FALSE)
+/datum/parsed_map/proc/_load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
PRIVATE_PROC(TRUE)
// Tell ss atoms that we're doing maploading
// We'll have to account for this in the following tick_checks so it doesn't overflow
- SSatoms.map_loader_begin()
+ loading = TRUE
+ SSatoms.map_loader_begin(REF(src))
// Loading used to be done in this proc
// We make the assumption that if the inner procs runtime, we WANT to do cleanup on them, but we should stil tell our parents we failed
@@ -268,12 +339,13 @@
var/sucessful = FALSE
switch(map_format)
if(MAP_TGM)
- sucessful = _tgm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+ sucessful = _tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
else
- sucessful = _dmm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+ sucessful = _dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
// And we are done lads, call it off
- SSatoms.map_loader_stop()
+ SSatoms.map_loader_stop(REF(src))
+ loading = FALSE
if(new_z)
for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ])
@@ -301,7 +373,7 @@
// In the tgm format, each gridset contains 255 lines, each line representing one tile, with 255 total gridsets
// In the dmm format, each gridset contains 255 lines, each line representing one row of tiles, containing 255 * line length characters, with one gridset per z
// You can think of dmm as storing maps in rows, whereas tgm stores them in columns
-/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
// setup
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
@@ -322,8 +394,12 @@
var/relative_y = first_column.ycrd
var/highest_y = relative_y + y_relative_to_absolute
- if(!cropMap && highest_y > world.maxy)
- world.maxy = highest_y // Expand Y here. X is expanded later on
+ if(!crop_map && highest_y > world.maxy)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_y(highest_y, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_y(highest_y)
expanded_y = TRUE
// Skip Y coords that are above the smallest of the three params
@@ -331,10 +407,7 @@
var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y)
// How many lines to skip because they'd be above the y cuttoff line
var/y_starting_skip = relative_y - y_skip_above
- if (y_skip_above == y_upper)
- highest_y = y_upper
- else
- highest_y += y_starting_skip
+ highest_y -= y_starting_skip
// Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go
var/line_count = length(first_column.gridLines)
@@ -343,7 +416,7 @@
// X setup
var/x_delta_with = x_upper
- if(cropMap)
+ if(crop_map)
// Take our smaller crop threshold yes?
x_delta_with = min(x_delta_with, world.maxx)
@@ -357,29 +430,51 @@
// If our relative x is greater then X upper, well then we've gotta limit our expansion
var/delta = max(final_x - x_delta_with, 0)
final_x -= delta
- if(final_x > world.maxx && !cropMap)
- world.maxx = final_x
+ if(final_x > world.maxx && !crop_map)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_x(final_x)
expanded_x = TRUE
var/lowest_x = max(x_lower, 1 - x_relative_to_absolute)
+ // Amount we offset the grid zcrd to get the true zcrd
+ var/grid_z_offset = z_offset - 1
+ var/z_upper_set = z_upper < INFINITY
+ var/z_lower_set = z_lower > -INFINITY
+
// We make the assumption that the last block of turfs will have the highest embedded z in it
- var/highest_z = last_column.zcrd + z_offset - 1 // Lets not just make a new z level each time we increment maxz
+ // true max zcrd
+ var/map_bounds_z_max = last_column.zcrd
+ var/z_upper_parsed = map_bounds_z_max + z_offset - 1
+ if(z_upper_set)
+ z_upper_parsed -= map_bounds_z_max - z_upper
+ if(z_lower_set)
+ var/offset_amount = z_lower - 1
+ z_upper_parsed -= offset_amount
+ grid_z_offset -= offset_amount
+
+ var/list/target_grid_sets = gridSets
+ if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want
+ target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper)
+
var/z_threshold = world.maxz
- if(highest_z > z_threshold && cropMap)
- for(var/i in z_threshold + 1 to highest_z) //create a new z_level if needed
+ if(z_upper_parsed > z_threshold && crop_map)
+ for(var/i in z_threshold + 1 to z_upper_parsed) //create a new z_level if needed
world.incrementMaxZ()
if(!no_changeturf)
WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called")
- for(var/datum/grid_set/gset as anything in gridSets)
+ for(var/datum/grid_set/gset as anything in target_grid_sets)
var/true_xcrd = gset.xcrd + x_relative_to_absolute
// any cutoff of x means we just shouldn't iterate this gridset
if(final_x < true_xcrd || lowest_x > gset.xcrd)
continue
- var/zcrd = gset.zcrd + z_offset - 1
+ var/zcrd = gset.zcrd + grid_z_offset
// If we're using changeturf, we disable it if we load into a z level we JUST created
var/no_afterchange = no_changeturf || zcrd > z_threshold
@@ -404,9 +499,9 @@
var/list/cache = modelCache[gset.gridLines[i]]
if(!cache)
- SSatoms.map_loader_stop()
+ SSatoms.map_loader_stop(REF(src))
CRASH("Undefined model key in DMM: [gset.gridLines[i]]")
- build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z)
+ build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z)
// only bother with bounds that actually exist
if(!first_found)
@@ -430,7 +525,7 @@
/// Stanrdard loading, not used in production
/// Doesn't take advantage of any tgm optimizations, which makes it slower but also more general
/// Use this if for some reason your map format is messy
-/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
// setup
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
@@ -441,19 +536,46 @@
var/y_relative_to_absolute = y_offset - 1
var/x_relative_to_absolute = x_offset - 1
var/line_len = src.line_len
- for(var/datum/grid_set/gset as anything in gridSets)
+
+ // Amount we offset the grid zcrd to get the true zcrd
+ var/grid_z_offset = z_offset - 1
+ var/z_upper_set = z_upper < INFINITY
+ var/z_lower_set = z_lower > -INFINITY
+
+ // we now need to find the maximum z, fun!
+ var/map_bounds_z_max = 1
+ for(var/datum/grid_set/grid_set as anything in gridSets)
+ map_bounds_z_max = max(map_bounds_z_max, grid_set.zcrd)
+
+ var/z_upper_parsed = map_bounds_z_max + z_offset - 1
+ if(z_upper_set)
+ z_upper_parsed -= map_bounds_z_max - z_upper
+ if(z_lower_set)
+ var/offset_amount = z_lower - 1
+ z_upper_parsed -= offset_amount
+ grid_z_offset -= offset_amount
+
+ var/list/target_grid_sets = gridSets
+ if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want
+ target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper)
+
+ for(var/datum/grid_set/gset as anything in target_grid_sets)
var/relative_x = gset.xcrd
var/relative_y = gset.ycrd
var/true_xcrd = relative_x + x_relative_to_absolute
var/ycrd = relative_y + y_relative_to_absolute
- var/zcrd = gset.zcrd + z_offset - 1
- if(!cropMap && ycrd > world.maxy)
- world.maxy = ycrd // Expand Y here. X is expanded in the loop below
+ var/zcrd = gset.zcrd + grid_z_offset
+ if(!crop_map && ycrd > world.maxy)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_y(ycrd, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_y(ycrd)
expanded_y = TRUE
var/zexpansion = zcrd > world.maxz
var/no_afterchange = no_changeturf
if(zexpansion)
- if(cropMap)
+ if(crop_map)
continue
else
while (zcrd > world.maxz) //create a new z_level if needed
@@ -471,10 +593,7 @@
var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y)
// How many lines to skip because they'd be above the y cuttoff line
var/y_starting_skip = relative_y - y_skip_above
- if (y_skip_above == y_upper)
- ycrd = y_upper
- else
- ycrd += y_starting_skip
+ ycrd += y_starting_skip
// Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go
var/line_count = length(gset.gridLines)
@@ -493,7 +612,7 @@
var/x_step_count = ROUND_UP(x_target / key_len)
var/final_x = relative_x + (x_step_count - 1)
var/x_delta_with = x_upper
- if(cropMap)
+ if(crop_map)
// Take our smaller crop threshold yes?
x_delta_with = min(x_delta_with, world.maxx)
if(final_x > x_delta_with)
@@ -502,8 +621,12 @@
x_step_count -= delta
final_x -= delta
x_target = x_step_count * key_len
- if(final_x > world.maxx && !cropMap)
- world.maxx = final_x
+ if(final_x > world.maxx && !crop_map)
+ if(new_z)
+ // Need to avoid improperly loaded area/turf_contents
+ world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1)
+ else
+ world.increase_max_x(final_x)
expanded_x = TRUE
// We're gonna track the first and last pairs of coords we find
@@ -532,9 +655,9 @@
continue
var/list/cache = modelCache[model_key]
if(!cache)
- SSatoms.map_loader_stop()
+ SSatoms.map_loader_stop(REF(src))
CRASH("Undefined model key in DMM: [model_key]")
- build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z)
+ build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z)
// only bother with bounds that actually exist
if(!first_found)
@@ -826,7 +949,7 @@ GLOBAL_LIST_EMPTY(map_model_default)
// Note: we make the assertion that the last path WILL be a turf. if it isn't, this will fail.
if(placeOnTop)
- instance = crds.PlaceOnTop(null, members[index], CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE))
+ instance = crds.load_on_top(members[index], CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE))
else if(no_changeturf)
instance = create_atom(members[index], crds)//first preloader pass
else
@@ -836,7 +959,8 @@ GLOBAL_LIST_EMPTY(map_model_default)
world.preloader_load(instance)
// If this isn't template work, we didn't change our turf and we changed area, then we've gotta handle area lighting transfer
else if(!no_changeturf && old_area)
- crds.change_area(old_area, crds.loc)
+ // Don't do contain/uncontain stuff, this happens a few lines up when the area actally changes
+ crds.on_change_area(old_area, crds.loc)
MAPLOADING_CHECK_TICK
//finally instance all remainings objects/mobs
@@ -945,6 +1069,7 @@ GLOBAL_LIST_EMPTY(map_model_default)
/datum/parsed_map/Destroy()
..()
+ SSatoms.map_loader_stop(REF(src)) // Just in case, I don't want to double up here
if(turf_blacklist)
turf_blacklist.Cut()
parsed_bounds.Cut()
@@ -952,3 +1077,9 @@ GLOBAL_LIST_EMPTY(map_model_default)
grid_models.Cut()
gridSets.Cut()
return QDEL_HINT_HARDDEL_NOW
+
+#undef MAP_DMM
+#undef MAP_TGM
+#undef MAP_UNKNOWN
+#undef TRIM_TEXT
+#undef MAPLOADING_CHECK_TICK
diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm
index f6ed99bf937d..db93c9fdc64a 100644
--- a/code/modules/mapping/ruins.dm
+++ b/code/modules/mapping/ruins.dm
@@ -1,20 +1,21 @@
/datum/map_template/ruin/proc/try_to_place(z, list/allowed_areas_typecache, turf/forced_turf, clear_below)
var/sanity = forced_turf ? 1 : PLACEMENT_TRIES
+ if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS))
+ return place_on_isolated_level(z)
while(sanity > 0)
sanity--
var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2)
var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2)
- var/turf/central_turf = locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z)
+ var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z)
var/valid = TRUE
var/list/affected_turfs = get_affected_turfs(central_turf,1)
var/list/affected_areas = list()
for(var/turf/check in affected_turfs)
// Use assoc lists to move this out, it's easier that way
- if(check.flags_1 & NO_RUINS_1)
+ if(check.turf_flags & NO_RUINS)
valid = FALSE
break
-
var/area/new_area = get_area(check)
affected_areas[new_area] = TRUE
@@ -33,7 +34,8 @@
var/list/static/clear_below_typecache = typecacheof(list(
/obj/structure/spawner,
/mob/living/simple_animal,
- /obj/structure/flora
+ /obj/structure/flora,
+ /obj/structure/herb //YOGS EDIT
))
for(var/turf/T as anything in affected_turfs)
for(var/atom/thing as anything in T)
@@ -44,12 +46,32 @@
loaded++
for(var/turf/T in affected_turfs)
- T.flags_1 |= NO_RUINS_1
+ T.turf_flags |= NO_RUINS
new /obj/effect/landmark/ruin(central_turf, src)
return central_turf
-
+/datum/map_template/ruin/proc/place_on_isolated_level(z)
+ var/datum/turf_reservation/reservation = SSmapping.request_turf_block_reservation(width, height, 1, z) //Make the new level creation work with different traits.
+ if(!reservation)
+ return
+ var/turf/placement = reservation.bottom_left_turfs[1]
+ load(placement)
+ loaded++
+ for(var/turf/T in get_affected_turfs(placement))
+ T.turf_flags |= NO_RUINS
+ var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z)
+ new /obj/effect/landmark/ruin(center, src)
+ return center
+
+/**
+ * Loads the ruins for a given z level.
+ * @param z_levels The z levels to load ruins on.
+ * @param budget The budget to spend on ruins. Compare against the cost of the ruins in /datum/map_template/ruin.
+ * @param whitelist A list of areas to allow ruins to be placed in.
+ * @param potentialRuins A list of ruins to choose from.
+ * @param clear_below Whether to clear the area below the ruin. Used for multiz ruins.
+ */
/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = list(/area/space), list/potentialRuins, clear_below = FALSE)
if(!z_levels || !z_levels.len)
WARNING("No Z levels provided - Not generating ruins")
@@ -121,7 +143,7 @@
for(var/v in current_pick.always_spawn_with)
if(current_pick.always_spawn_with[v] == PLACE_BELOW)
var/turf/T = locate(1,1,target_z)
- if(!SSmapping.get_turf_below(T))
+ if(!GET_TURF_BELOW(T))
if(forced_z)
continue outer
else
@@ -181,7 +203,7 @@
if(PLACE_DEFAULT)
forced_ruins[linked] = -1
if(PLACE_BELOW)
- forced_ruins[linked] = SSmapping.get_turf_below(placed_turf)
+ forced_ruins[linked] = GET_TURF_BELOW(placed_turf)
forced_z = 0
//Update the availible list
diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm
index ca7cd8d52042..b0e2ff7fa065 100644
--- a/code/modules/mapping/space_management/multiz_helpers.dm
+++ b/code/modules/mapping/space_management/multiz_helpers.dm
@@ -1,10 +1,11 @@
/proc/get_step_multiz(ref, dir)
+ var/turf/us = get_turf(ref)
if(dir & UP)
dir &= ~UP
- return get_step(SSmapping.get_turf_above(get_turf(ref)), dir)
+ return get_step(GET_TURF_ABOVE(us), dir)
if(dir & DOWN)
dir &= ~DOWN
- return get_step(SSmapping.get_turf_below(get_turf(ref)), dir)
+ return get_step(GET_TURF_BELOW(us), dir)
return get_step(ref, dir)
/proc/get_dir_multiz(turf/us, turf/them)
@@ -15,33 +16,31 @@
if(us.z == them.z)
return get_dir(us, them)
else
- var/turf/T = us.above()
+ var/turf/T = GET_TURF_ABOVE(us)
var/dir = NONE
if(T && (T.z == them.z))
dir = UP
else
- T = us.below()
+ T = GET_TURF_BELOW(us)
if(T && (T.z == them.z))
dir = DOWN
else
return get_dir(us, them)
return (dir | get_dir(us, them))
-/turf/proc/above()
- return get_step_multiz(src, UP)
+/proc/get_lowest_turf(atom/ref)
+ var/turf/us = get_turf(ref)
+ var/turf/next = GET_TURF_BELOW(us)
+ while(next)
+ us = next
+ next = GET_TURF_BELOW(us)
+ return us
-/turf/proc/below()
- return get_step_multiz(src, DOWN)
-
-/proc/dir_inverse_multiz(dir)
- var/holder = dir & (UP|DOWN)
- if((holder == NONE) || (holder == (UP|DOWN)))
- return turn(dir, 180)
- dir &= ~(UP|DOWN)
- dir = turn(dir, 180)
- if(holder == UP)
- holder = DOWN
- else
- holder = UP
- dir |= holder
- return dir
+// I wish this was lisp
+/proc/get_highest_turf(atom/ref)
+ var/turf/us = get_turf(ref)
+ var/turf/next = GET_TURF_ABOVE(us)
+ while(next)
+ us = next
+ next = GET_TURF_ABOVE(us)
+ return us
diff --git a/code/modules/mapping/space_management/space_level.dm b/code/modules/mapping/space_management/space_level.dm
index cc9c6e11f173..1747806cb216 100644
--- a/code/modules/mapping/space_management/space_level.dm
+++ b/code/modules/mapping/space_management/space_level.dm
@@ -11,4 +11,12 @@
z_value = new_z
name = new_name
traits = new_traits
+
+ if (islist(new_traits))
+ for (var/trait in new_traits)
+ SSmapping.z_trait_levels[trait] += list(new_z)
+ else // in case a single trait is passed in
+ SSmapping.z_trait_levels[new_traits] += list(new_z)
+
+
set_linkage(new_traits[ZTRAIT_LINKAGE])
diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm
index 12137ebf4ca5..74185cebb4ba 100644
--- a/code/modules/mapping/space_management/space_reservation.dm
+++ b/code/modules/mapping/space_management/space_reservation.dm
@@ -1,16 +1,33 @@
//Yes, they can only be rectangular.
//Yes, I'm sorry.
/datum/turf_reservation
+ /// All turfs that we've reserved
var/list/reserved_turfs = list()
- ///Turfs around the reservation for cordoning
+
+ /// Turfs around the reservation for cordoning
var/list/cordon_turfs = list()
- ///Area of turfs next to the cordon to fill with pre_cordon_area's
+
+ /// Area of turfs next to the cordon to fill with pre_cordon_area's
var/list/pre_cordon_turfs = list()
+
+ /// The width of the reservation
var/width = 0
+
+ /// The height of the reservation
var/height = 0
- var/bottom_left_coords[3]
- var/top_right_coords[3]
+
+ /// The z stack size of the reservation. Note that reservations are ALWAYS reserved from the bottom up
+ var/z_size = 0
+
+ /// List of the bottom left turfs. Indexed by what their z index for this reservation is
+ var/list/bottom_left_turfs = list()
+
+ /// List of the top right turfs. Indexed by what their z index for this reservation is
+ var/list/top_right_turfs = list()
+
+ /// The turf type the reservation is initially made with
var/turf_type = /turf/open/space
+
///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen
var/pre_cordon_distance = 0
@@ -19,6 +36,9 @@
pre_cordon_distance = 7
/datum/turf_reservation/proc/Release()
+ bottom_left_turfs.Cut()
+ top_right_turfs.Cut()
+
var/list/reserved_copy = reserved_turfs.Copy()
SSmapping.used_turfs -= reserved_turfs
reserved_turfs = list()
@@ -33,23 +53,23 @@
SEND_SIGNAL(reserved_turf, COMSIG_TURF_RESERVATION_RELEASED, src)
// Makes the linter happy, even tho we don't await this
- INVOKE_ASYNC(SSmapping, /datum/controller/subsystem/mapping/proc/reserve_turfs, reserved_copy)
+ INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, reserve_turfs), release_turfs)
/// Attempts to calaculate and store a list of turfs around the reservation for cordoning. Returns whether a valid cordon was calculated
-/datum/turf_reservation/proc/calculate_cordon_turfs(turf/BL, turf/TR)
- if(BL.x < 2 || BL.y < 2 || TR.x > (world.maxx - 2) || TR.y > (world.maxy - 2))
+/datum/turf_reservation/proc/calculate_cordon_turfs(turf/bottom_left, turf/top_right)
+ if(bottom_left.x < 2 || bottom_left.y < 2 || top_right.x > (world.maxx - 2) || top_right.y > (world.maxy - 2))
return FALSE // no space for a cordon here
- var/list/possible_turfs = CORNER_OUTLINE(BL, width, height)
+ var/list/possible_turfs = CORNER_OUTLINE(bottom_left, width, height)
+ // if they're our cordon turfs, accept them
+ possible_turfs -= cordon_turfs
for(var/turf/cordon_turf as anything in possible_turfs)
- if(!(cordon_turf.flags_1 & UNUSED_RESERVATION_TURF_1))
+ if(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF))
return FALSE
- cordon_turfs = possible_turfs
-
- pre_cordon_turfs.Cut()
+ cordon_turfs |= possible_turfs
if(pre_cordon_distance)
- var/turf/offset_turf = locate(BL.x + pre_cordon_distance, BL.y + pre_cordon_distance, BL.z)
+ var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z)
var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon
for(var/turf/turf_being_added as anything in to_add)
pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates
@@ -61,13 +81,18 @@
for(var/turf/cordon_turf as anything in cordon_turfs)
var/area/misc/cordon/cordon_area = GLOB.areas_by_type[/area/misc/cordon] || new
var/area/old_area = cordon_turf.loc
+
+ //LISTASSERTLEN(old_area.turfs_to_uncontain, cordon_turf.z, list())
+ //LISTASSERTLEN(cordon_area.contained_turfs, cordon_turf.z, list())
old_area.turfs_to_uncontain += cordon_turf
cordon_area.contained_turfs += cordon_turf
cordon_area.contents += cordon_turf
+
+ // Its no longer unused, but its also not "used"
+ cordon_turf.turf_flags &= ~UNUSED_RESERVATION_TURF
cordon_turf.ChangeTurf(/turf/cordon, /turf/cordon)
-
- cordon_turf.flags_1 &= ~UNUSED_RESERVATION_TURF_1
SSmapping.unused_turfs["[cordon_turf.z]"] -= cordon_turf
+ // still gets linked to us though
SSmapping.used_turfs[cordon_turf] = src
//swap the area with the pre-cordoning area
@@ -79,7 +104,7 @@
SHOULD_CALL_PARENT(TRUE)
//Okay so hear me out. If we place a special turf IN the reserved area, it will be overwritten, so we can't do that
//But signals are preserved even between turf changes, so even if we register a signal now it will stay even if that turf is overriden by the template
- RegisterSignals(pre_cordon_turf, list(COMSIG_PARENT_QDELETING, COMSIG_TURF_RESERVATION_RELEASED), PROC_REF(on_stop_repel))
+ RegisterSignals(pre_cordon_turf, list(COMSIG_QDELETING, COMSIG_TURF_RESERVATION_RELEASED), PROC_REF(on_stop_repel))
/datum/turf_reservation/proc/on_stop_repel(turf/pre_cordon_turf)
SHOULD_CALL_PARENT(TRUE)
@@ -89,12 +114,12 @@
///Unregister all the signals we added in RegisterRepelSignals
/datum/turf_reservation/proc/stop_repel(turf/pre_cordon_turf)
- UnregisterSignal(pre_cordon_turf, list(COMSIG_PARENT_QDELETING, COMSIG_TURF_RESERVATION_RELEASED))
+ UnregisterSignal(pre_cordon_turf, list(COMSIG_QDELETING, COMSIG_TURF_RESERVATION_RELEASED))
/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)
..()
@@ -104,9 +129,17 @@
/datum/turf_reservation/transit/proc/space_dump(atom/source, atom/movable/enterer)
SIGNAL_HANDLER
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(throw_atom), enterer)
+ dump_in_space(enterer)
+
+///Only dump if we don't have the hyperspace cordon movement exemption trait
+/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)
-/datum/turf_reservation/proc/Reserve(width, height, zlevel)
+/// Internal proc which handles reserving the area for the reservation.
+/datum/turf_reservation/proc/_reserve_area(width, height, zlevel)
src.width = width
src.height = height
if(width > world.maxx || height > world.maxy || width < 1 || height < 1)
@@ -119,12 +152,12 @@
for(var/i in avail)
CHECK_TICK
BL = i
- if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1))
+ if(!(BL.turf_flags & UNUSED_RESERVATION_TURF))
continue
if(BL.x + width > world.maxx || BL.y + height > world.maxy)
continue
TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z)
- if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1))
+ if(!(TR.turf_flags & UNUSED_RESERVATION_TURF))
continue
final = block(BL, TR)
if(!final)
@@ -132,7 +165,7 @@
passing = TRUE
for(var/I in final)
var/turf/checking = I
- if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1))
+ if(!(checking.turf_flags & UNUSED_RESERVATION_TURF))
passing = FALSE
break
if(passing) // found a potentially valid area, now try to calculate its cordon
@@ -142,18 +175,96 @@
break
if(!passing || !istype(BL) || !istype(TR))
return FALSE
- bottom_left_coords = list(BL.x, BL.y, BL.z)
- top_right_coords = list(TR.x, TR.y, TR.z)
for(var/i in final)
var/turf/T = i
reserved_turfs |= T
- T.flags_1 &= ~UNUSED_RESERVATION_TURF_1
SSmapping.unused_turfs["[T.z]"] -= T
SSmapping.used_turfs[T] = src
+ T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF
T.ChangeTurf(turf_type, turf_type)
+
+ bottom_left_turfs += BL
+ top_right_turfs += TR
+ return TRUE
+
+/datum/turf_reservation/proc/reserve(width, height, z_size, z_reservation)
+ src.z_size = z_size
+ var/failed_reservation = FALSE
+ for(var/_ in 1 to z_size)
+ if(!_reserve_area(width, height, z_reservation))
+ failed_reservation = TRUE
+ break
+
+ if(failed_reservation)
+ Release()
+ return FALSE
+
generate_cordon()
return TRUE
+/// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable.
+/datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target)
+ if(!length(bottom_left_turfs) || !length(top_right_turfs))
+ return null
+ for(var/z_idx in 1 to z_size)
+ var/turf/bottom_left = bottom_left_turfs[z_idx]
+ var/turf/top_right = top_right_turfs[z_idx]
+ var/bl_x = bottom_left.x
+ var/bl_y = bottom_left.y
+ var/tr_x = top_right.x
+ var/tr_y = top_right.y
+
+ if(target.x < bl_x)
+ continue
+
+ if(target.y < bl_y)
+ continue
+
+ if(target.x > tr_x)
+ continue
+
+ if(target.y > tr_y)
+ continue
+
+ var/list/return_information = list()
+ return_information["z_idx"] = z_idx
+ return_information["offset_x"] = target.x - bl_x
+ return_information["offset_y"] = target.y - bl_y
+ return return_information
+ return null
+
+/// Gets the turf below the given target. Returns null if there is no turf below the target
+/datum/turf_reservation/proc/get_turf_below(turf/target)
+ var/list/bounds_info = calculate_turf_bounds_information(target)
+ if(isnull(bounds_info))
+ return null
+
+ var/z_idx = bounds_info["z_idx"]
+ // check what z level, if its the max, then there is no turf below
+ if(z_idx == z_size)
+ return null
+
+ var/offset_x = bounds_info["offset_x"]
+ var/offset_y = bounds_info["offset_y"]
+ var/turf/bottom_left = bottom_left_turfs[z_idx + 1]
+ return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z)
+
+/// Gets the turf above the given target. Returns null if there is no turf above the target
+/datum/turf_reservation/proc/get_turf_above(turf/target)
+ var/list/bounds_info = calculate_turf_bounds_information(target)
+ if(isnull(bounds_info))
+ return null
+
+ var/z_idx = bounds_info["z_idx"]
+ // check what z level, if its the min, then there is no turf above
+ if(z_idx == 1)
+ return null
+
+ var/offset_x = bounds_info["offset_x"]
+ var/offset_y = bounds_info["offset_y"]
+ var/turf/bottom_left = bottom_left_turfs[z_idx - 1]
+ return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z)
+
/datum/turf_reservation/New()
LAZYADD(SSmapping.turf_reservations, src)
diff --git a/code/modules/mapping/space_management/traits.dm b/code/modules/mapping/space_management/traits.dm
index 9ba8d96d5e1c..9841f85ebd7e 100644
--- a/code/modules/mapping/space_management/traits.dm
+++ b/code/modules/mapping/space_management/traits.dm
@@ -1,4 +1,4 @@
-// Look up levels[z].traits[trait]
+/// Look up levels[z].traits[trait]
/datum/controller/subsystem/mapping/proc/level_trait(z, trait)
if (!isnum(z) || z < 1)
return null
@@ -6,7 +6,7 @@
if (z > z_list.len)
stack_trace("Unmanaged z-level [z]! maxz = [world.maxz], z_list.len = [z_list.len]")
return list()
- var/datum/space_level/S = get_level(z)
+ var/datum/space_level/S = z_list[z]
return S.traits[trait]
else
var/list/default = DEFAULT_MAP_TRAITS
@@ -15,59 +15,41 @@
return list()
return default[z][DL_TRAITS][trait]
-// Check if levels[z] has any of the specified traits
+/// Check if levels[z] has any of the specified traits
/datum/controller/subsystem/mapping/proc/level_has_any_trait(z, list/traits)
- for (var/I in traits)
- if (level_trait(z, I))
- return TRUE
+ var/datum/space_level/level_to_check = z_list[z]
+ if (length(level_to_check.traits & traits))
+ return TRUE
return FALSE
-// Check if levels[z] has all of the specified traits
+/// Check if levels[z] has all of the specified traits
/datum/controller/subsystem/mapping/proc/level_has_all_traits(z, list/traits)
- for (var/I in traits)
- if (!level_trait(z, I))
- return FALSE
- return TRUE
+ var/datum/space_level/level_to_check = z_list[z]
+ if (length(level_to_check.traits & traits) == length(traits))
+ return TRUE
+ return FALSE
-// Get a list of all z which have the specified trait
+/// Get a list of all z which have the specified trait
/datum/controller/subsystem/mapping/proc/levels_by_trait(trait)
- . = list()
- var/list/_z_list = z_list
- for(var/A in _z_list)
- var/datum/space_level/S = A
- if (S.traits[trait])
- . += S.z_value
+ return z_trait_levels[trait] || list()
-// Get a list of all z which have any of the specified traits
+/// Get a list of all z which have any of the specified traits
/datum/controller/subsystem/mapping/proc/levels_by_any_trait(list/traits)
- . = list()
- var/list/_z_list = z_list
- for(var/A in _z_list)
- var/datum/space_level/S = A
- for (var/trait in traits)
- if (S.traits[trait])
- . += S.z_value
- break
-
-// Attempt to get the turf below the provided one according to Z traits
-/datum/controller/subsystem/mapping/proc/get_turf_below(turf/T)
- if (!T)
- return
- var/offset = level_trait(T.z, ZTRAIT_DOWN)
- if (!offset)
- return
- return locate(T.x, T.y, T.z + offset)
+ var/list/final_return = list()
+ for (var/trait in traits)
+ if (z_trait_levels[trait])
+ final_return |= z_trait_levels[trait]
+ return final_return
-// Attempt to get the turf above the provided one according to Z traits
-/datum/controller/subsystem/mapping/proc/get_turf_above(turf/T)
- if (!T)
- return
- var/offset = level_trait(T.z, ZTRAIT_UP)
- if (!offset)
- return
- return locate(T.x, T.y, T.z + offset)
+/// Get a list of all z which have all of the specified traits
+/datum/controller/subsystem/mapping/proc/levels_by_all_traits(list/traits)
+ var/list/final_return = list()
+ for(var/datum/space_level/level as anything in z_list)
+ if(level_has_all_traits(level.z_value, traits))
+ final_return += level.z_value
+ return final_return
-// Prefer not to use this one too often
+/// Prefer not to use this one too often
/datum/controller/subsystem/mapping/proc/get_station_center()
var/station_z = levels_by_trait(ZTRAIT_STATION)[1]
return locate(round(world.maxx * 0.5, 1), round(world.maxy * 0.5, 1), station_z)
diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm
index dbaad83f229b..b7664f09ff2d 100644
--- a/code/modules/mapping/space_management/zlevel_manager.dm
+++ b/code/modules/mapping/space_management/zlevel_manager.dm
@@ -4,30 +4,37 @@
return
z_list = list()
+ z_level_to_plane_offset = list()
+ z_level_to_lowest_plane_offset = list()
var/list/default_map_traits = DEFAULT_MAP_TRAITS
if (default_map_traits.len != world.maxz)
- WARNING("More or less map attributes pre-defined ([default_map_traits.len]) than existent z-levels ([world.maxz]). Ignoring the larger.")
+ log_mapping("More or less map attributes pre-defined ([default_map_traits.len]) than existent z-levels ([world.maxz]). Ignoring the larger.")
if (default_map_traits.len > world.maxz)
default_map_traits.Cut(world.maxz + 1)
for (var/I in 1 to default_map_traits.len)
var/list/features = default_map_traits[I]
var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS])
- build_area_turfs(I, FALSE)
- z_list += S
+ manage_z_level(S, filled_with_space = FALSE)
+ generate_z_level_linkages() // Default Zs don't use add_new_zlevel() so they don't automatically generate z-linkages.
-/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, filled_with_space = TRUE, contain_turfs = TRUE)
- SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, args)
+/// Generates a real, honest to god new z level. Will create the actual space, and also generate a datum that holds info about the new plot of land
+/// Accepts the name, traits list, datum type, and if we should manage the turfs we create
+/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, contain_turfs = TRUE)
+ UNTIL(!adding_new_zlevel)
+ adding_new_zlevel = TRUE
var/new_z = z_list.len + 1
if (world.maxz < new_z)
world.incrementMaxZ()
CHECK_TICK
// TODO: sleep here if the Z level needs to be cleared
var/datum/space_level/S = new z_type(new_z, name, traits)
- if(contain_turfs)
- build_area_turfs(new_z, filled_with_space)
- z_list += S
+ manage_z_level(S, filled_with_space = TRUE, contain_turfs = contain_turfs)
+ generate_linkages_for_z_level(new_z)
+ calculate_z_level_gravity(new_z)
+ adding_new_zlevel = FALSE
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, S)
return S
/datum/controller/subsystem/mapping/proc/get_level(z)
diff --git a/code/modules/mining/aux_base.dm b/code/modules/mining/aux_base.dm
index 2714be881838..0f14017d188c 100644
--- a/code/modules/mining/aux_base.dm
+++ b/code/modules/mining/aux_base.dm
@@ -5,6 +5,7 @@
#define BAD_AREA 2
#define BAD_COORDS 3
#define BAD_TURF 4
+#define BAD_LAYER 5
#define SCIENCE_AMOUNT 500 // How much science to generate per minute
@@ -47,6 +48,7 @@
. = ..()
radio = new /obj/item/radio(src)
radio.frequency = radio_freq
+ AddComponent(/datum/component/gps, "NT_AUX")
/obj/machinery/computer/auxiliary_base/Destroy()
QDEL_NULL(radio)
@@ -108,13 +110,13 @@
data["status"] = "Recharging"
else
data["status"] = "In Transit"
- for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
- if(!options.Find(S.id))
+ for(var/obj/docking_port/stationary/S in SSshuttle.stationary_docking_ports)
+ if(!options.Find(S.port_destinations))
continue
if(!M.check_dock(S, silent=TRUE))
continue
var/list/location_data = list(
- id = S.id,
+ id = S.shuttle_id,
name = S.name
)
data["locations"] += list(location_data)
@@ -170,7 +172,7 @@
for(var/z_level in SSmapping.levels_by_trait(ZTRAIT_MINING))
all_mining_turfs += Z_TURFS(z_level)
var/turf/LZ = pick(all_mining_turfs) //Pick a random mining Z-level turf
- if(!ismineralturf(LZ) && !istype(LZ, /turf/open/floor/plating/asteroid))
+ if(!ismineralturf(LZ) && !istype(LZ, /turf/open/floor/plating/asteroid) && !istype(LZ,/turf/open/floor/plating/dirt/jungleland))
//Find a suitable mining turf. Reduces chance of landing in a bad area
to_chat(usr, span_warning("Landing zone scan failed. Please try again."))
return
@@ -217,7 +219,7 @@
possible_destinations = "mining_home;mining_away;landing_zone_dock;mining_public;auxiliary_construction"
/obj/machinery/computer/auxiliary_base/proc/set_landing_zone(turf/T, mob/user, no_restrictions)
- var/obj/docking_port/mobile/auxiliary_base/base_dock = locate(/obj/docking_port/mobile/auxiliary_base) in SSshuttle.mobile
+ var/obj/docking_port/mobile/auxiliary_base/base_dock = locate(/obj/docking_port/mobile/auxiliary_base) in SSshuttle.mobile_docking_ports
if(!base_dock) //Not all maps have an Aux base. This object is useless in that case.
to_chat(user, span_warning("This station is not equipped with an auxiliary base. Please contact your Nanotrasen contractor."))
return
@@ -226,6 +228,9 @@
/turf/closed,
/turf/open/lava,
/turf/open/indestructible,
+ /turf/open/water/toxic_pit,
+ /turf/open/water/deep_toxic_pit,
+ /turf/open/water/tar_basin,
)) - typecacheof(list(
/turf/closed/mineral,
))
@@ -239,7 +244,9 @@
var/turf/place = colony_turfs[i]
if(!place)
return BAD_COORDS
- if(!istype(place.loc, /area/lavaland/surface) && !istype(place.loc, /area/icemoon/surface) && !istype(place.loc, /area/icemoon/underground))
+ if(istype(place.loc, /area/icemoon/surface))
+ return BAD_LAYER
+ if(!istype(place.loc, /area/lavaland/surface) && !istype(place.loc, /area/icemoon/underground) && !(istype(place.loc, /area/jungleland) && !istype(place.loc, /area/jungleland/explored)) )
return BAD_AREA
if(disallowed_turf_types[place.type])
return BAD_TURF
@@ -248,7 +255,8 @@
var/area/A = get_area(T)
var/obj/docking_port/stationary/landing_zone = new /obj/docking_port/stationary(T)
- landing_zone.id = "colony_drop([REF(src)])"
+ landing_zone.shuttle_id = "colony_drop([REF(src)])"
+ landing_zone.port_destinations = "colony_drop([REF(src)])"
landing_zone.name = "Landing Zone ([T.x], [T.y])"
landing_zone.dwidth = base_dock.dwidth
landing_zone.dheight = base_dock.dheight
@@ -257,7 +265,7 @@
landing_zone.setDir(base_dock.dir)
landing_zone.area_type = A.type
- possible_destinations += "[landing_zone.id];"
+ possible_destinations += "[landing_zone.shuttle_id];"
//Serves as a nice mechanic to people get ready for the launch.
minor_announce("Auxiliary base landing zone coordinates locked in for [A]. Launch command now available!")
@@ -319,7 +327,7 @@
/obj/docking_port/mobile/auxiliary_base
name = "auxiliary base"
- id = "colony_drop"
+ shuttle_id = "colony_drop"
//Reminder to map-makers to set these values equal to the size of your base.
dheight = 4
dwidth = 4
@@ -329,13 +337,13 @@
/obj/docking_port/mobile/auxiliary_base/takeoff(list/old_turfs, list/new_turfs, list/moved_atoms, rotation, movement_direction, old_dock, area/underlying_old_area)
for(var/i in new_turfs)
var/turf/place = i
- if(istype(place, /turf/closed/mineral))
+ if(ismineralturf(place))
place.ScrapeAway()
return ..()
/obj/docking_port/stationary/public_mining_dock
name = "public mining base dock"
- id = "disabled" //The Aux Base has to leave before this can be used as a dock.
+ shuttle_id = "disabled" //The Aux Base has to leave before this can be used as a dock.
//Should be checked on the map to ensure it matchs the mining shuttle dimensions.
dwidth = 3
width = 7
@@ -385,14 +393,15 @@
return
//Mining shuttles may not be created equal, so we find the map's shuttle dock and size accordingly.
- for(var/S in SSshuttle.stationary)
+ for(var/S in SSshuttle.stationary_docking_ports)
var/obj/docking_port/stationary/SM = S //SM is declared outside so it can be checked for null
- if(SM.id == "mining_home" || SM.id == "mining_away")
+ if(SM.shuttle_id == "mining_home" || SM.shuttle_id == "mining_away")
var/area/A = get_area(landing_spot)
Mport = new(landing_spot)
- Mport.id = "landing_zone_dock"
+ Mport.shuttle_id = "landing_zone_dock"
+ Mport.port_destinations = "landing_zone_dock"
Mport.name = "auxiliary base landing site"
Mport.dwidth = SM.dwidth
Mport.dheight = SM.dheight
@@ -408,9 +417,8 @@
var/obj/docking_port/mobile/mining_shuttle
var/list/landing_turfs = list() //List of turfs where the mining shuttle may land.
- for(var/S in SSshuttle.mobile)
- var/obj/docking_port/mobile/MS = S
- if(MS.id != "mining")
+ for(var/obj/docking_port/mobile/MS as anything in SSshuttle.mobile_docking_ports)
+ if(MS.shuttle_id != "mining")
continue
mining_shuttle = MS
landing_turfs = mining_shuttle.return_ordered_turfs(x,y,z,dir)
@@ -418,7 +426,7 @@
if(!mining_shuttle) //Not having a mining shuttle is a map issue
to_chat(user, span_warning("No mining shuttle signal detected. Please contact Nanotrasen Support."))
- SSshuttle.stationary.Remove(Mport)
+ SSshuttle.stationary_docking_ports.Remove(Mport)
qdel(Mport)
return
@@ -426,18 +434,18 @@
var/turf/L = landing_turfs[i]
if(!L) //This happens at map edges
to_chat(user, span_warning("Unable to secure a valid docking zone. Please try again in an open area near, but not within the auxiliary mining base."))
- SSshuttle.stationary.Remove(Mport)
+ SSshuttle.stationary_docking_ports.Remove(Mport)
qdel(Mport)
return
if(istype(get_area(L), /area/shuttle/auxiliary_base))
to_chat(user, span_warning("The mining shuttle must not land within the mining base itself."))
- SSshuttle.stationary.Remove(Mport)
+ SSshuttle.stationary_docking_ports.Remove(Mport)
qdel(Mport)
return
if(mining_shuttle.canDock(Mport) != SHUTTLE_CAN_DOCK)
to_chat(user, span_warning("Unable to secure a valid docking zone. Please try again in an open area near, but not within the auxiliary mining base."))
- SSshuttle.stationary.Remove(Mport)
+ SSshuttle.stationary_docking_ports.Remove(Mport)
qdel(Mport)
return
diff --git a/code/modules/mining/aux_base_camera.dm b/code/modules/mining/aux_base_camera.dm
index a916cd312a15..46f4850e0a7a 100644
--- a/code/modules/mining/aux_base_camera.dm
+++ b/code/modules/mining/aux_base_camera.dm
@@ -1,5 +1,5 @@
//Aux base construction console
-/mob/camera/aiEye/remote/base_construction
+/mob/camera/ai_eye/remote/base_construction
name = "construction holo-drone"
move_on_shuttle = 1 //Allows any curious crew to watch the base after it leaves. (This is safe as the base cannot be modified once it leaves)
icon = 'icons/obj/mining.dmi'
@@ -7,22 +7,22 @@
var/area/starting_area
invisibility = 0 //Not invisible
-/mob/camera/aiEye/remote/base_construction/Initialize(mapload)
+/mob/camera/ai_eye/remote/base_construction/Initialize(mapload)
. = ..()
starting_area = get_area(loc)
- icon_state = "construction_drone" // Overrides /mob/camera/aiEye/Initialize(mapload) in \modules\mob\living\silicon\ai\freelook\eye
+ icon_state = "construction_drone" // Overrides /mob/camera/ai_eye/Initialize(mapload) in \modules\mob\living\silicon\ai\freelook\eye
-/mob/camera/aiEye/remote/base_construction/setLoc(t)
- var/area/curr_area = get_area(t)
+/mob/camera/ai_eye/remote/base_construction/setLoc(turf/destination, force_update = FALSE)
+ var/area/curr_area = get_area(destination)
if(curr_area == starting_area || istype(curr_area, /area/shuttle/auxiliary_base))
return ..()
//While players are only allowed to build in the base area, but consoles starting outside the base can move into the base area to begin work.
-/mob/camera/aiEye/remote/base_construction/relaymove(mob/user, direct)
+/mob/camera/ai_eye/remote/base_construction/relaymove(mob/user, direct)
dir = direct //This camera eye is visible as a drone, and needs to keep the dir updated
..()
-/obj/item/construction/rcd/internal //Base console's internal RCD. Roundstart consoles are filled, rebuilt cosoles start empty.
+/obj/item/construction/rcd/internal //Base console's internal RCD. Roundstart consoles are filled, rebuilt consoles start empty.
name = "internal RCD"
max_matter = 600 //Bigger container and faster speeds due to being specialized and stationary.
no_ammo_message = span_warning("Internal matter exhausted. Please add additional materials.")
@@ -32,24 +32,24 @@
name = "base construction console"
desc = "An industrial computer integrated with a camera-assisted rapid construction drone."
networks = list("ss13")
- var/obj/item/construction/rcd/internal/RCD //Internal RCD. The computer passes user commands to this in order to avoid massive copypaste.
circuit = /obj/item/circuitboard/computer/base_construction
- off_action = new/datum/action/innate/camera_off/base_construction
+ off_action = /datum/action/innate/camera_off/base_construction
jump_action = null
- var/datum/action/innate/aux_base/switch_mode/switch_mode_action = new //Action for switching the RCD's build modes
- var/datum/action/innate/aux_base/build/build_action = new //Action for using the RCD
- var/datum/action/innate/aux_base/airlock_type/airlock_mode_action = new //Action for setting the airlock type
- var/datum/action/innate/aux_base/window_type/window_action = new //Action for setting the window type
- var/datum/action/innate/aux_base/place_fan/fan_action = new //Action for spawning fans
- var/fans_remaining = 0 //Number of fans in stock.
- var/datum/action/innate/aux_base/install_turret/turret_action = new //Action for spawning turrets
- var/turret_stock = 0 //Turrets in stock
- var/obj/machinery/computer/auxiliary_base/found_aux_console //Tracker for the Aux base console, so the eye can always find it.
-
icon_screen = "mining"
icon_keyboard = "rd_key"
-
light_color = LIGHT_COLOR_PINK
+ ///Area that the eyeobj will be constrained to. If null, eyeobj will be able to build and move anywhere.
+ var/area/allowed_area = /area/shuttle/auxiliary_base
+
+ var/obj/item/construction/rcd/internal/RCD //Internal RCD. The computer passes user commands to this in order to avoid massive copypaste.
+ var/obj/machinery/computer/auxiliary_base/found_aux_console //Tracker for the Aux base console, so the eye can always find it.
+ var/datum/action/innate/aux_base/configure_mode/configure_mode_action = new //Action for switching the RCD's build modes
+ var/datum/action/innate/aux_base/build/build_action = new //Action for using the RCD
+ var/fans_remaining = 0 //Number of fans in stock.
+ var/datum/action/innate/aux_base/place_fan/fan_action = new //Action for spawning fans
+ var/turret_stock = 0 //Turrets in stock
+ var/datum/action/innate/aux_base/install_turret/turret_action = new //Action for spawning turrets
+
/obj/machinery/computer/camera_advanced/base_construction/Initialize(mapload)
. = ..()
@@ -62,21 +62,22 @@
fans_remaining = 4
turret_stock = 4
-/obj/machinery/computer/camera_advanced/base_construction/CreateEye()
-
- var/spawn_spot
- for(var/obj/machinery/computer/auxiliary_base/ABC in GLOB.machines)
- if(istype(get_area(ABC), /area/shuttle/auxiliary_base))
- found_aux_console = ABC
+///Find a spawn location for the eyeobj. If no allowed_area is defined, spawn ontop of the console.
+/obj/machinery/computer/camera_advanced/base_construction/proc/find_spawn_spot()
+ for(var/obj/machinery/computer/auxiliary_base/aux_base_camera as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/computer/auxiliary_base))
+ if(istype(get_area(aux_base_camera), allowed_area))
+ found_aux_console = aux_base_camera
break
if(found_aux_console)
- spawn_spot = found_aux_console
- else
- spawn_spot = src
-
+ return get_turf(found_aux_console)
+ return get_turf(src)
- eyeobj = new /mob/camera/aiEye/remote/base_construction(get_turf(spawn_spot))
+/obj/machinery/computer/camera_advanced/base_construction/CreateEye()
+ var/turf/spawn_spot = find_spawn_spot()
+ if (!spawn_spot)
+ return FALSE
+ eyeobj = new /mob/camera/ai_eye/remote/base_construction(get_turf(spawn_spot))
eyeobj.origin = src
@@ -93,26 +94,16 @@
/obj/machinery/computer/camera_advanced/base_construction/GrantActions(mob/living/user)
..()
- if(switch_mode_action)
- switch_mode_action.target = src
- switch_mode_action.Grant(user)
- actions += switch_mode_action
+ if(configure_mode_action)
+ configure_mode_action.target = src
+ configure_mode_action.Grant(user)
+ actions += configure_mode_action
if(build_action)
build_action.target = src
build_action.Grant(user)
actions += build_action
- if(airlock_mode_action)
- airlock_mode_action.target = src
- airlock_mode_action.Grant(user)
- actions += airlock_mode_action
-
- if(window_action)
- window_action.target = src
- window_action.Grant(user)
- actions += window_action
-
if(fan_action)
fan_action.target = src
fan_action.Grant(user)
@@ -132,7 +123,7 @@
/datum/action/innate/aux_base //Parent aux base action
button_icon = 'icons/mob/actions/actions_construction.dmi'
var/mob/living/C //Mob using the action
- var/mob/camera/aiEye/remote/base_construction/remote_eye //Console's eye mob
+ var/mob/camera/ai_eye/remote/base_construction/remote_eye //Console's eye mob
var/obj/machinery/computer/camera_advanced/base_construction/B //Console itself
/datum/action/innate/aux_base/Activate()
@@ -187,38 +178,17 @@
B.RCD.afterattack(rcd_target, owner, TRUE) //Activate the RCD and force it to work remotely!
playsound(target_turf, 'sound/items/deconstruct.ogg', 60, 1)
-/datum/action/innate/aux_base/switch_mode
- name = "Switch Mode"
- button_icon_state = "builder_mode"
+/datum/action/innate/aux_base/configure_mode
+ name = "Configure RCD"
+ button_icon = 'icons/obj/tools.dmi'
+ button_icon_state = "rcd"
-/datum/action/innate/aux_base/switch_mode/Activate()
+/datum/action/innate/aux_base/configure_mode/Activate()
if(..())
return
- var/list/buildlist = list("Walls and Floors" = RCD_FLOORWALL,"Airlocks" = RCD_AIRLOCK,"Deconstruction" = RCD_DECONSTRUCT,"Windows and Grilles" = RCD_WINDOWGRILLE)
- var/buildmode = input(owner, "Set construction mode.", "Base Console", null) in buildlist
- B.RCD.mode = buildlist[buildmode]
- to_chat(owner, "Build mode is now [buildmode].")
-
-/datum/action/innate/aux_base/airlock_type
- name = "Select Airlock Type"
- button_icon_state = "airlock_select"
-
-datum/action/innate/aux_base/airlock_type/Activate()
- if(..())
- return
-
- B.RCD.change_airlock_setting(owner, remote_eye)
-
-
-datum/action/innate/aux_base/window_type
- name = "Select Window Glass"
- button_icon_state = "window_select"
-
-datum/action/innate/aux_base/window_type/Activate()
- if(..())
- return
- B.RCD.toggle_window_glass(owner)
+ B.RCD.owner = B
+ B.RCD.ui_interact(owner)
datum/action/innate/aux_base/place_fan
name = "Place Tiny Fan"
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index 1c2de639447a..2c942c2e1f36 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -127,6 +127,11 @@
if(!QDELETED(C))
C.total_damage += detonation_damage
L.apply_damage(detonation_damage, BRUTE, blocked = def_check)
+ //YOGS EDIT BEGIN
+ for(var/t in trophies)
+ var/obj/item/crusher_trophy/T = t
+ T.after_mark_detonation(target,user,src,target_health-L.health)
+ //YOGS EDIT END
/obj/item/kinetic_crusher/proc/recharge(magmite = FALSE)
if(!charged)
@@ -134,6 +139,7 @@
icon_state = "[base_icon_state]0"
playsound(src.loc, 'sound/weapons/kenetic_reload.ogg', 60, 1)
+
//destablizing force
/obj/projectile/destabilizer
name = "destabilizing force"
@@ -158,7 +164,9 @@
if(hammer_synced)
for(var/t in hammer_synced.trophies)
var/obj/item/crusher_trophy/T = t
- T.on_mark_application(target, CM, had_effect)
+ T.on_mark_application(target, CM, had_effect, hammer_synced)
+ else
+ SEND_SIGNAL(hammer_synced,COMSIG_KINETIC_CRUSHER_PROJECTILE_FAILED_TO_MARK,firer,hammer_synced)
var/target_turf = get_turf(target)
if(ismineralturf(target_turf))
var/turf/closed/mineral/M = target_turf
@@ -183,6 +191,10 @@
return ..()
+/obj/projectile/destabilizer/on_range()
+ SEND_SIGNAL(hammer_synced,COMSIG_KINETIC_CRUSHER_PROJECTILE_ON_RANGE,firer,hammer_synced)
+ ..()
+
//trophies
/obj/item/crusher_trophy
name = "tail spike"
@@ -223,9 +235,12 @@
/obj/item/crusher_trophy/proc/on_melee_hit(mob/living/target, mob/living/user) //the target and the user
/obj/item/crusher_trophy/proc/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user) //the projectile fired and the user
-/obj/item/crusher_trophy/proc/on_mark_application(mob/living/target, datum/status_effect/crusher_mark/mark, had_mark) //the target, the mark applied, and if the target had a mark before
-/obj/item/crusher_trophy/proc/on_mark_detonation(mob/living/target, mob/living/user) //the target and the user
+//obj/item/crusher_trophy/proc/on_mark_application(mob/living/target, datum/status_effect/crusher_mark/mark, had_mark) //the target, the mark applied, and if the target had a mark before
+//obj/item/crusher_trophy/proc/on_mark_detonation(mob/living/target, mob/living/user) //the target and the user
+/obj/item/crusher_trophy/proc/on_mark_application(mob/living/target, datum/status_effect/crusher_mark/mark, had_mark,obj/item/kinetic_crusher/hammer_synced) //YOGS EDIT
+/obj/item/crusher_trophy/proc/on_mark_detonation(mob/living/target, mob/living/user, obj/item/kinetic_crusher/hammer_synced) //YOGS EDIT
+/obj/item/crusher_trophy/proc/after_mark_detonation(mob/living/target, mob/living/user, obj/item/kinetic_crusher/hammer_synced, damage_dealt) //YOGS EDIT
//goliath
/obj/item/crusher_trophy/goliath_tentacle
name = "goliath tentacle"
diff --git a/code/modules/mining/equipment/mineral_scanner.dm b/code/modules/mining/equipment/mineral_scanner.dm
index 33a23c9f7b52..045c148cb2c4 100644
--- a/code/modules/mining/equipment/mineral_scanner.dm
+++ b/code/modules/mining/equipment/mineral_scanner.dm
@@ -85,6 +85,18 @@
for(var/turf/closed/mineral/M in range(range, T))
if(M.scan_state)
minerals += M
+ //yogs edit
+ for(var/turf/open/floor/plating/dirt/jungleland/JG in range(range, T))
+ if(JG.ore_present == ORE_EMPTY || !JG.can_spawn_ore)
+ continue
+ var/datum/ore_patch/ore = GLOB.jungle_ores[JG.ore_present]
+ var/state = initial(ore.overlay_state)
+ var/obj/effect/temp_visual/mining_overlay/oldC = locate(/obj/effect/temp_visual/mining_overlay) in JG
+ if(oldC)
+ qdel(oldC)
+ var/obj/effect/temp_visual/mining_overlay/C = new /obj/effect/temp_visual/mining_overlay(JG)
+ C.icon_state = state
+ //yogs end
if(LAZYLEN(minerals))
for(var/turf/closed/mineral/M in minerals)
var/obj/effect/temp_visual/mining_overlay/oldC = locate(/obj/effect/temp_visual/mining_overlay) in M
diff --git a/code/modules/mining/equipment/mining_charges.dm b/code/modules/mining/equipment/mining_charges.dm
index 9134c61d4717..7abe0e143eb4 100644
--- a/code/modules/mining/equipment/mining_charges.dm
+++ b/code/modules/mining/equipment/mining_charges.dm
@@ -22,27 +22,26 @@
nadeassembly.attack_self(user)
/obj/item/grenade/plastic/miningcharge/afterattack(atom/movable/AM, mob/user, flag, notify_ghosts = FALSE)
- if(ismineralturf(AM) || hacked)
+ if(ismineralturf_inclusive(AM) || hacked)
..()
else
to_chat(user,span_warning("The charge only works on rocks!"))
/obj/item/grenade/plastic/miningcharge/prime()
+ var/turf/location = get_turf(target) //YOGS EDIT
if(hacked) //big boom override
- var/turf/location = get_turf(target)
explosion(location, boom_sizes[1], boom_sizes[2], boom_sizes[3])
qdel(src)
return //don't know if this is needed...
- var/turf/closed/mineral/location = get_turf(target)
- location.attempt_drill(null,TRUE,3) //orange says it doesnt include the actual middle
- for(var/turf/closed/mineral/rock in circle_range_turfs(location,boom_sizes[3]))
+ drill_at(location,3)
+ for(var/turf/rock in circle_range_turfs(location,boom_sizes[3]))
var/distance = get_dist_euclidian(location,rock)
if(distance <= boom_sizes[1])
- rock.attempt_drill(null,TRUE,3)
+ drill_at(rock,3) //YOGS EDIT
else if (distance <= boom_sizes[2])
- rock.attempt_drill(null,TRUE,2)
+ drill_at(rock,2) // YOGS EDIT
else if (distance <= boom_sizes[3])
- rock.attempt_drill(null,TRUE,1)
+ drill_at(rock,1) // YOGS EDIT
for(var/mob/living/carbon/C in circlerange(location,boom_sizes[3]))
if(ishuman(C) && C.soundbang_act(1, 0))
to_chat(C, span_warning("You are knocked down by the power of the mining charge!"))
@@ -63,6 +62,16 @@
boom_sizes[3] = max(boom_sizes[3]/3, 1)
alert_admins = TRUE //i'm telling teacher you're gibbing clown!
+//YOGS EDIT BEGIN
+/obj/item/grenade/plastic/miningcharge/proc/drill_at(location,power)
+ if(istype(location,/turf/closed/mineral))
+ var/turf/closed/mineral/M = location
+ M.attempt_drill(null,TRUE,power) //orange says it doesnt include the actual middle
+ else
+ var/turf/open/floor/plating/dirt/jungleland/J = location
+ J.spawn_rock()
+//YOGS EDIT END
+
//MINING CHARGE HACKER
/obj/item/t_scanner/adv_mining_scanner/syndicate
var/charges = 6
diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm
index fbc7e291fdb9..7ddcc93e4c71 100644
--- a/code/modules/mining/equipment/regenerative_core.dm
+++ b/code/modules/mining/equipment/regenerative_core.dm
@@ -30,6 +30,7 @@
actions_types = list(/datum/action/item_action/organ_action/use)
var/inert = 0
var/preserved = 0
+ var/status_effect = STATUS_EFFECT_REGENERATIVE_CORE //yogs edit
/obj/item/organ/regenerative_core/Initialize(mapload)
. = ..()
@@ -62,6 +63,7 @@
to_chat(owner, span_notice("[src] breaks down as it tries to activate."))
else
owner.revive(full_heal = 1)
+ SEND_SIGNAL(owner,COMSIG_REGEN_CORE_HEALED) //yogs edit
qdel(src)
/obj/item/organ/regenerative_core/on_life()
@@ -85,8 +87,7 @@
to_chat(user, span_notice("[src] are useless on the dead."))
return
if(H != user)
-
- if(!is_station_level(user_turf.z) || is_reserved_level(user_turf.z))
+ if(is_mining_level(user_turf.z) || !is_station_level(user_turf.z) || is_reserved_level(user_turf.z))
H.visible_message(span_notice("[user] crushes [src] against [H]'s body, causing black tendrils to encover and reinforce [H.p_them()]!"))
else
H.visible_message(span_notice("[user] holds [src] against [H]'s body, coaxing the regenerating tendrils from [src]..."))
@@ -98,7 +99,7 @@
H.visible_message(span_notice("[src] explodes into a flurry of tendrils, rapidly covering and reinforcing [H]'s body."))
SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "other"))
else
- if(!is_station_level(user_turf.z) || is_reserved_level(user_turf.z))
+ if(is_mining_level(user_turf.z) || !is_station_level(user_turf.z) || is_reserved_level(user_turf.z))
to_chat(user, span_notice("You crush [src] within your hand. Disgusting tendrils spread across your body, hold you together and allow you to keep moving, but for how long?"))
else
to_chat(user, span_notice("You hold [src] against your body, coaxing the regenerating tendrils from [src]..."))
@@ -109,8 +110,9 @@
balloon_alert(user, "Core applied!")
to_chat(user, span_notice("[src] explodes into a flurry of tendrils, rapidly spreading across your body. They will hold you together and allow you to keep moving, but for how long?"))
SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self"))
- H.apply_status_effect(STATUS_EFFECT_REGENERATIVE_CORE)
+ H.apply_status_effect(status_effect) //yogs edit
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "core", /datum/mood_event/healsbadman) //Now THIS is a miner buff (fixed - nerf)
+ SEND_SIGNAL(H,COMSIG_REGEN_CORE_HEALED) //yogs edit
qdel(src)
/obj/item/organ/regenerative_core/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index 9669eacfe860..acd61605e70b 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -2,7 +2,7 @@
/area/survivalpod
name = "\improper Emergency Shelter"
icon_state = "away"
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
+ static_lighting = TRUE
requires_power = FALSE
has_gravity = STANDARD_GRAVITY
valid_territory = FALSE
@@ -83,9 +83,11 @@
/obj/structure/window/shuttle/survival_pod
name = "pod window"
icon = 'icons/obj/smooth_structures/pod_window.dmi'
- icon_state = "smooth"
- smooth = SMOOTH_MORE
- canSmoothWith = list(/turf/closed/wall/mineral/titanium/survival, /obj/machinery/door/airlock/survival_pod, /obj/structure/window/shuttle/survival_pod)
+ icon_state = "pod_window-0"
+ base_icon_state = "pod_window"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_SURVIVAL_TITANIUM_POD
+ canSmoothWith = SMOOTH_GROUP_SURVIVAL_TITANIUM_POD
/obj/structure/window/shuttle/survival_pod/spawner/north
dir = NORTH
@@ -131,7 +133,9 @@
/obj/structure/table/survival_pod
icon = 'icons/obj/lavaland/survival_pod.dmi'
icon_state = "table"
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
+ smoothing_groups = null
+ canSmoothWith = null
//Sleeper
/obj/machinery/sleeper/survival_pod
@@ -248,7 +252,7 @@
density = TRUE
var/buildstacktype = /obj/item/stack/sheet/metal
var/buildstackamount = 5
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
/obj/structure/fans/deconstruct()
if(!(flags_1 & NODECONSTRUCT_1))
diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
index 57593686c35a..19eea5ae191a 100644
--- a/code/modules/mining/fulton.dm
+++ b/code/modules/mining/fulton.dm
@@ -28,6 +28,9 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
if(is_species(user, /datum/species/lizard/ashwalker))
to_chat(user, span_warning("You don't know how to use this!"))
return FALSE
+ if(is_species(user, /datum/species/pod/ivymen)) // yogs - ivymen
+ to_chat(user, span_warning("You don't know how to use this!"))
+ return FALSE
var/list/possible_beacons = list()
for(var/B in GLOB.total_extraction_beacons)
var/obj/structure/extraction_point/EP = B
@@ -197,7 +200,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons)
/obj/effect/extraction_holder
name = "extraction holder"
- desc = "you shouldnt see this"
+ desc = "You shouldn't see this."
var/atom/movable/stored_obj
/obj/item/extraction_pack/proc/check_for_living_mobs(atom/A)
diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm
index 694f0c933bb9..77b93188e9a5 100644
--- a/code/modules/mining/lavaland/ash_flora.dm
+++ b/code/modules/mining/lavaland/ash_flora.dm
@@ -143,8 +143,7 @@
/obj/structure/flora/ash/cacti/Initialize(mapload)
. = ..()
- // min dmg 3, max dmg 6, prob(70)
- AddComponent(/datum/component/caltrop, 3, 6, 70)
+ AddComponent(/datum/component/caltrop, min_damage = 3, max_damage = 6, probability = 70)
//SNACKS
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 584081e4a09b..859732d9b6d3 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -188,9 +188,9 @@ GLOBAL_LIST_EMPTY(aide_list)
if(active_owner && user == active_owner)
var/safety = alert(user, "Doing this will instantly kill you, reducing you to nothing but dust.", "Take off [src]?", "Abort", "Proceed")
if(safety != "Proceed")
- return
+ return
. = ..()
-
+
/obj/item/clothing/neck/necklace/memento_mori/dropped(mob/user)
..()
if(active_owner)
@@ -299,7 +299,7 @@ GLOBAL_LIST_EMPTY(aide_list)
light_flags = LIGHT_ATTACHED
layer = ABOVE_ALL_MOB_LAYER
var/sight_flags = SEE_MOBS
- var/lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ var/list/color_cutoffs = list(10, 25, 25)
/obj/effect/wisp/orbit(atom/thing, radius, clockwise, rotation_speed, rotation_segments, pre_rotation, lockinorbit)
. = ..()
@@ -316,9 +316,10 @@ GLOBAL_LIST_EMPTY(aide_list)
to_chat(orbits.parent, span_notice("Your vision returns to normal."))
/obj/effect/wisp/proc/update_user_sight(mob/user)
- user.sight |= sight_flags
- if(!isnull(lighting_alpha))
- user.lighting_alpha = min(user.lighting_alpha, lighting_alpha)
+ SIGNAL_HANDLER
+ user.add_sight(sight_flags)
+ if(!isnull(color_cutoffs))
+ user.lighting_color_cutoffs = blend_cutoff_colors(user.lighting_color_cutoffs, color_cutoffs)
//Red/Blue Cubes
/obj/item/warp_cube
@@ -413,7 +414,7 @@ GLOBAL_LIST_EMPTY(aide_list)
name = "hook"
desc = "A hook."
projectile_type = /obj/projectile/hook
- caliber = "hook"
+ caliber = CALIBER_HOOK
icon_state = "hook"
/obj/projectile/hook
@@ -609,7 +610,7 @@ GLOBAL_LIST_EMPTY(aide_list)
return
if(isliving(target))
var/mob/living/L = target
- if(ismegafauna(L) || istype(L, /mob/living/simple_animal/hostile/asteroid)) //no loot allowed from the little skulls
+ if(ismegafauna(L) || istype(L, /mob/living/simple_animal/hostile/asteroid) || istype(L,/mob/living/simple_animal/hostile/yog_jungle)) //no loot allowed from the little skulls
if(!istype(L, /mob/living/simple_animal/hostile/asteroid/hivelordbrood))
RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(roll_loot), TRUE)
//after quite a bit of grinding, you'll be doing a total of 120 damage to fauna per hit. A lot, but i feel like the grind justifies the payoff. also this doesn't effect crew. so. go nuts.
@@ -679,7 +680,7 @@ GLOBAL_LIST_EMPTY(aide_list)
icon = 'icons/obj/lavaland/artefacts.dmi'
icon_state = "syndi_potionflask"
desc = "An ornate red bottle, with an \"S\" embossed into the underside. Filled with an experimental flight potion. Mileage may vary."
-
+
/obj/item/reagent_containers/glass/bottle/potion/flight
name = "strange elixir"
desc = "A flask with an almost-holy aura emitting from it. The label on the bottle says: 'erqo'hyy tvi'rf lbh jv'atf'."
@@ -1376,7 +1377,7 @@ GLOBAL_LIST_EMPTY(aide_list)
if(target == user)
continue
for(var/obj/effect/decal/cleanable/decal in range(0, target))
- if(istype(decal, /obj/effect/decal/cleanable/blood )|| istype(decal, /obj/effect/decal/cleanable/trail_holder))
+ if(istype(decal, /obj/effect/decal/cleanable/blood )|| istype(decal, /obj/effect/decal/cleanable/blood/trail_holder))
valid_reaching = TRUE
target.apply_status_effect(STATUS_EFFECT_KNUCKLED)
if(!valid_reaching)
@@ -1746,7 +1747,7 @@ GLOBAL_LIST_EMPTY(aide_list)
name = "ancient control rod"
//don't want your rare megafauna loot shattering easily
max_integrity = 2000
- desc = "A mysterious crystaline rod of exceptional length, humming with ancient power. Too unweildy for use in one hand."
+ desc = "A mysterious crystaline rod of exceptional length, humming with ancient power. Too unwieldy for use in one hand."
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
force = 0
@@ -1858,7 +1859,7 @@ GLOBAL_LIST_EMPTY(aide_list)
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
actions_types = list(/datum/action/item_action/band)
var/limit = 3
- var/telerange = 20
+ var/telerange = 20
var/next_tap = 0
var/next_band = 0
var/next_teleport = 0
@@ -1972,7 +1973,7 @@ GLOBAL_LIST_EMPTY(aide_list)
aide.forceMove(O)
playsound(aide, 'sound/magic/teleport_app.ogg', 20, 1)
next_band = world.time + COOLDOWN_BAND
-
+
/obj/item/cane/cursed/afterattack(mob/living/target , mob/living/carbon/user, proximity)
.=..()
diff --git a/code/modules/mining/lavaland/ruins/gym.dm b/code/modules/mining/lavaland/ruins/gym.dm
index 54d4a61cabf5..0ecbf2db6693 100644
--- a/code/modules/mining/lavaland/ruins/gym.dm
+++ b/code/modules/mining/lavaland/ruins/gym.dm
@@ -24,6 +24,7 @@
desc = "Just looking at this thing makes you feel tired."
density = TRUE
anchored = TRUE
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
var/icon_state_inuse
/obj/structure/weightmachine/proc/AnimateMachine(mob/living/user)
diff --git a/code/modules/mining/lavaland/seismicarm.dm b/code/modules/mining/lavaland/seismicarm.dm
index f71d608c5a99..a24cd1519552 100644
--- a/code/modules/mining/lavaland/seismicarm.dm
+++ b/code/modules/mining/lavaland/seismicarm.dm
@@ -1,228 +1,32 @@
-/datum/action/cooldown/spell/pointed/seismic
- spell_requirements = SPELL_REQUIRES_HUMAN
-/datum/action/cooldown/spell/pointed/seismic/can_cast_spell(feedback = TRUE)
- if(!isliving(owner))
- return FALSE
- var/mob/living/user = owner
- var/obj/item/bodypart/r_arm/R = user.get_bodypart(BODY_ZONE_R_ARM)
- if(R?.bodypart_disabled)
- to_chat(user, span_warning("The arm isn't in a functional state right now!"))
- return FALSE
- if(user.IsParalyzed() || user.IsStun() || user.restrained())
- return FALSE
- return TRUE
-
-/datum/action/cooldown/spell/pointed/seismic/proc/check_turf(turf/turf_to_check)
- if(turf_to_check.density)
- return FALSE
- if(ischasm(turf_to_check))
- return FALSE
- if(isindestructiblewall(turf_to_check))
- return FALSE
- if(islava(turf_to_check))
- return FALSE
- for(var/obj/object in turf_to_check.contents)
- if(object.density)
- return FALSE
- return TRUE
-
-#define LARIAT_JUMP_DISTANCE 4
-
-/datum/action/cooldown/spell/pointed/seismic/lariat
- name = "Lariat"
- desc = "Dash forward, catching whatever animal you hit with your arm and knocking them over."
- button_icon = 'icons/mob/actions/actions_arm.dmi'
- button_icon_state = "lariat"
-
- cooldown_time = 7 SECONDS
-
-/datum/action/cooldown/spell/pointed/seismic/lariat/InterceptClickOn(mob/living/user, params, atom/target)
- . = ..()
- if(!.)
- return FALSE
- var/turf/turf_ahead = get_step(get_turf(user), user.dir)
- var/obj/effect/temp_visual/decoy/fading/threesecond/followup_effect = new(get_turf(user), user)
- user.visible_message(span_warning("[user] sprints forward with [user.p_their()] arm outstretched!"))
- playsound(user,'sound/effects/gravhit.ogg', 20, TRUE)
- for(var/i = 0 to LARIAT_JUMP_DISTANCE)
- if(!check_turf(turf_ahead))
- return TRUE //TRUE as in: start cooldown
- user.forceMove(turf_ahead)
- walk_towards(followup_effect, user, 0, 1.5)
- animate(followup_effect, alpha = 0, color = "#00d9ff", time = 0.3 SECONDS)
- for(var/mob/living/assaulted in turf_ahead.contents)
- if(assaulted != user)
- user.forceMove(get_turf(assaulted))
- to_chat(assaulted, span_userdanger("[user] catches you with [user.p_their()] arm and clotheslines you!"))
- user.visible_message(span_warning("[user] hits [assaulted] with a lariat!"))
- assaulted.SpinAnimation(0.5 SECONDS, 1)
- if(isanimal(assaulted))
- assaulted.adjustBruteLoss(50)
- if(iscarbon(assaulted))
- assaulted.adjustBruteLoss(10)
- if(issilicon(assaulted))
- assaulted.adjustBruteLoss(12)
- playsound(assaulted,'sound/effects/meteorimpact.ogg', 60, 1)
- turf_ahead = get_step(turf_ahead, user.dir)
-
- return TRUE
-
-#undef LARIAT_JUMP_DISTANCE
-
-#define MOP_JUMP_DISTANCE 4
-
-/datum/action/cooldown/spell/pointed/seismic/mop
- name = "Mop the Floor"
- desc = "Launch forward and drag whoever's in front of you on the ground. The power of this move increases with closeness to the target upon using it."
- button_icon = 'icons/mob/actions/actions_arm.dmi'
- button_icon_state = "mop"
-
- cooldown_time = 7 SECONDS
-
-/datum/action/cooldown/spell/pointed/seismic/mop/InterceptClickOn(mob/living/user, params, atom/target)
- . = ..()
- if(!.)
- return FALSE
- var/turf/turf_ahead = get_step(get_turf(user), user.dir)
- var/obj/effect/temp_visual/decoy/fading/threesecond/followup_effect = new(get_turf(user), user)
- user.visible_message(span_warning("[user] sprints forward with [user.p_their()] hand outstretched!"))
- playsound(user,'sound/effects/gravhit.ogg', 20, 1)
- for(var/i = 0 to MOP_JUMP_DISTANCE)
- if(!check_turf(turf_ahead))
- return TRUE
- user.forceMove(turf_ahead)
- walk_towards(followup_effect, user, 0, 1.5)
- animate(followup_effect, alpha = 0, color = "#00d9ff", time = 0.3 SECONDS)
- var/list/mopped = list()
- for(var/mob/living/L in turf_ahead.contents)
- if(L != user)
- mopped |= L
- var/turf/Q = get_step(get_turf(user), user.dir)
- animate(L, transform = matrix(90, MATRIX_ROTATE), time = 0.1 SECONDS, loop = 0)
- if(ismineralturf(Q))
- var/turf/closed/mineral/M = Q
- M.attempt_drill()
- L.adjustBruteLoss(5)
- if(Q.density)
- return FALSE
- for(var/obj/D in Q.contents)
- if(D.density)
- return FALSE
- user.forceMove(get_turf(L))
- to_chat(L, span_userdanger("[user] catches you with [user.p_their()] hand and drags you down!"))
- user.visible_message(span_warning("[user] hits [L] and drags them through the dirt!"))
- L.forceMove(Q)
- if(isanimal(L))
- user.apply_status_effect(STATUS_EFFECT_BLOODDRUNK)//guaranteed extended contact with a fauna so i have to make it not a death sentence
- L.adjustBruteLoss(20)
- if(L.stat == DEAD)
- L.visible_message(span_warning("[L] is ground into paste!"))
- L.gib()
- if(iscarbon(L))
- L.adjustBruteLoss(4)
- if(issilicon(L))
- L.adjustBruteLoss(5)
- playsound(L,'sound/effects/meteorimpact.ogg', 60, 1)
- turf_ahead = get_step(turf_ahead, user.dir)
- for(var/mob/living/C in mopped)
- if(C.stat == CONSCIOUS && C.resting == FALSE)
- animate(C, transform = null, time = 0.5 SECONDS, loop = 0)
-
- return TRUE
-
-#undef MOP_JUMP_DISTANCE
-
-/datum/action/cooldown/spell/pointed/seismic/suplex
- name = "Suplex"
- desc = "Grab the target in front of you and slam them back onto the ground."
- button_icon = 'icons/mob/actions/actions_arm.dmi'
- button_icon_state = "suplex"
-
- cooldown_time = 1 SECONDS
+/obj/item/bodypart/r_arm/robot/seismic
+ name = "seismic right arm"
+ desc = "A prototype arm adorned with woofers capable of emitting shockwaves to excavate basalt."
+ icon = 'icons/mob/augmentation/augments_seismic.dmi'
+ icon_state = "seismic_r_arm"
+ max_damage = 60
+ var/datum/martial_art/reverberating_palm/reverberating_palm = new
+ var/datum/action/cooldown/seismic_recalibrate/recalibration = new/datum/action/cooldown/seismic_recalibrate()
+ var/datum/action/cooldown/seismic_deactivate/deactivation = new/datum/action/cooldown/seismic_deactivate()
-/datum/action/cooldown/spell/pointed/seismic/suplex/InterceptClickOn(mob/living/user, params, atom/target)
+/obj/item/bodypart/r_arm/robot/seismic/attach_limb(mob/living/carbon/C, special)
. = ..()
- if(!.)
- return FALSE
- var/turf/T = get_step(get_turf(user), user.dir)
- var/turf/Z = get_turf(user)
- user.visible_message(span_warning("[user] outstretches [user.p_their()] arm and goes for a grab!"))
- for(var/mob/living/L in T.contents)
- var/turf/Q = get_step(get_turf(user), turn(user.dir,180))
- if(ismineralturf(Q))
- var/turf/closed/mineral/M = Q
- M.attempt_drill()
- L.adjustBruteLoss(5)
- if(Q.density)
- to_chat(user, span_warning("There's no room to do this!"))
- return
- for(var/obj/D in Q.contents)
- if(D.density == TRUE)
- to_chat(user, span_warning("There's no room to do this!"))
- return
- for(var/obj/machinery/door/window/E in Z.contents)
- if(E.density == TRUE)
- return
- for(var/mob/living/M in Q.contents)
- if(isanimal(L))
- M.adjustBruteLoss(20)
- if(iscarbon(L))
- M.adjustBruteLoss(10)
- user.visible_message(span_warning("[L] slams into [M]!"))
- L.forceMove(Q)
- playsound(L,'sound/effects/meteorimpact.ogg', 60, 1)
- playsound(user,'sound/effects/gravhit.ogg', 20, 1)
- to_chat(L, span_userdanger("[user] catches you with [user.p_their()] hand and crushes you on the ground!"))
- user.visible_message(span_warning("[user] turns around and slams [L] against the ground!"))
- user.setDir(turn(user.dir,180))
- animate(L, transform = matrix(179, MATRIX_ROTATE), time = 0.1 SECONDS, loop = 0)
- if(isanimal(L))
- L.adjustBruteLoss(20)
- if(L.stat == DEAD)
- L.visible_message(span_warning("[L] explodes into gore on impact!"))
- L.gib()
- if(iscarbon(L))
- L.adjustBruteLoss(6)
- if(issilicon(L))
- L.adjustBruteLoss(8)
- addtimer(CALLBACK(src, PROC_REF(fix_target_anim), L), 0.5 SECONDS)
-
- return TRUE
+ reverberating_palm.teach(C)
+ recalibration.Grant(C)
+ deactivation.Grant(C)
+ to_chat(owner, span_boldannounce("You've gained the ability to use Reverberating Palm!"))
-/datum/action/cooldown/spell/pointed/seismic/suplex/proc/fix_target_anim(mob/living/target)
- if(target.stat == CONSCIOUS && target.resting == FALSE)
- animate(target, transform = null, time = 0.1 SECONDS, loop = 0)
-
-/datum/action/cooldown/spell/touch/righthook
- name = "Right Hook"
- desc = "Put the arm through its paces, cranking the outputs located at the front and back of the hand to full capacity for a powerful blow. This attack can only be readied for five seconds and connecting with it will temporarily overwhelm the entire arm for fifteen."
- button_icon = 'icons/mob/actions/actions_arm.dmi'
- button_icon_state = "ponch"
-
- cooldown_time = 3 SECONDS
-
- sound = 'sound/effects/gravhit.ogg'
- draw_message = span_notice("Your arm begins crackling loudly!")
- drop_message = span_notice("You dissipate the power in your hand.")
-
- spell_requirements = SPELL_REQUIRES_HUMAN
+/obj/item/bodypart/r_arm/robot/seismic/drop_limb(special)
+ reverberating_palm.remove(owner)
+ owner.click_intercept = null
+ recalibration.Remove(owner)
+ deactivation.Remove(owner)
+ to_chat(owner, "[span_boldannounce("You've lost the ability to use Reverberating Palm...")]")
+ ..()
- hand_path = /obj/item/melee/touch_attack/overcharged_emitter
-/datum/action/cooldown/spell/touch/righthook/can_cast_spell(feedback = TRUE)
- if(!isliving(owner))
- return FALSE
- var/mob/living/user = owner
- var/obj/item/bodypart/r_arm/R = user.get_bodypart(BODY_ZONE_R_ARM)
- if(R?.bodypart_disabled)
- to_chat(user, span_warning("The arm isn't in a functional state right now!"))
- return FALSE
- if(user.IsParalyzed() || user.IsStun() || user.restrained())
- return FALSE
- return TRUE
-/obj/item/melee/touch_attack/overcharged_emitter
+/obj/item/melee/overcharged_emitter
name = "supercharged emitter"
desc = "The result of all the prosthetic's power building up in its palm. It's fading fast."
icon = 'icons/obj/wizard.dmi'
@@ -231,106 +35,101 @@
color = "#04b8ff"
item_flags = DROPDEL
w_class = 5
+ var/carbondam = 15
+ var/animaldam = 100
+ var/borgdam = 18
+ var/objdam = 40
var/flightdist = 8
+ var/objcrash = 30
+ var/carboncrash = 10
+ var/borgcrash = 11
+ var/animalcrash = 30
-/obj/item/melee/touch_attack/overcharged_emitter/afterattack(mob/living/L, mob/living/user, proximity)
- var/direction = user.dir
+/obj/item/melee/overcharged_emitter/afterattack(atom/target, mob/living/user, proximity)
var/obj/item/bodypart/r_arm/R = user.get_bodypart(BODY_ZONE_R_ARM)
- var/list/knockedback = list()
- if(!proximity)
- return
- if(!isliving(L))
+ if(isturf(target) || iseffect(target) || isitem(target) || !proximity || (target == user))
return
- if(L == user)
- return
- if(isliving(L))
- qdel(src, force = TRUE)
- L.SpinAnimation(0.5 SECONDS, 2)
- playsound(L,'sound/effects/gravhit.ogg', 60, 1)
- R?.set_disabled(TRUE)
- to_chat(user, span_warning("The huge impact takes the arm out of commission!"))
- shake_camera(L, 4, 3)
- shake_camera(user, 2, 3)
- addtimer(CALLBACK(R, TYPE_PROC_REF(/obj/item/bodypart/r_arm, set_disabled)), 15 SECONDS, TRUE)
- to_chat(L, span_userdanger("[user] hits you with a blast of energy and sends you flying!"))
- user.visible_message(span_warning("[user] blasts [L] with a surge of energy and sends [L.p_them()] flying!"))
- knockedback |= L
- if(iscarbon(L))
- L.adjustBruteLoss(15)
- if(isanimal(L))
- if(istype(L, /mob/living/simple_animal/hostile/guardian))
- L.adjustBruteLoss(40)
- else
- L.adjustBruteLoss(100)
- if(issilicon(L))
- L.adjustBruteLoss(18)
- var/turf/P = get_turf(user)
- for(var/i = 2 to flightdist)
- var/turf/T = get_ranged_target_turf(P, direction, i)
- if(ismineralturf(T))
- var/turf/closed/mineral/M = T
- M.attempt_drill()
- L.adjustBruteLoss(5)
- if(T.density)
- for(var/mob/living/simple_animal/S in knockedback)
- if(S.stat == DEAD)
- S.gib()
+ qdel(src, force = TRUE)
+ target.SpinAnimation(0.5 SECONDS, 2)
+ playsound(target,'sound/effects/gravhit.ogg', 60, 1)
+ shake_camera(target, 4, 3)
+ shake_camera(user, 2, 3)
+ to_chat(target, span_userdanger("[user] hits you with a blast of energy and sends you flying!"))
+ user.visible_message(span_warning("[user] blasts [target] with a surge of energy and sends [target.p_them()] flying!"))
+ R?.set_disabled(TRUE)
+ addtimer(CALLBACK(R, TYPE_PROC_REF(/obj/item/bodypart/r_arm, set_disabled)), 15 SECONDS, TRUE)
+ if(isliving(target))
+ var/mob/living/L = target
+ L.Immobilize(0.5 SECONDS)
+ if(iscarbon(L))
+ L.adjustBruteLoss(carbondam)
+ if(isanimal(L))
+ if(istype(L, /mob/living/simple_animal/hostile/guardian))
+ L.adjustBruteLoss(40)
+ else
+ L.adjustBruteLoss(animaldam)
+ if(issilicon(L))
+ L.adjustBruteLoss(borgdam)
+ if(isobj(target))
+ var/obj/I = target
+ I.take_damage(objdam)
+ if(I.anchored == TRUE)
return
- for(var/obj/D in T.contents)
- if(D.density == TRUE)
- for(var/mob/living/simple_animal/S in knockedback)
- if(S.stat == DEAD)
- S.visible_message(span_warning("[S] crashes and explodes!"))
- S.gib()
- return
- if(T)
- for(var/mob/living/M in T.contents)
- knockedback |= M
- if(iscarbon(M))
- M.adjustBruteLoss(10)
- if(isanimal(M))
- M.adjustBruteLoss(30)
- if(issilicon(M))
- M.adjustBruteLoss(11)
- for(var/mob/living/S in knockedback)
- S.forceMove(T)
- S.SpinAnimation(0.2 SECONDS, 1)
+ if(ismovable(target))
+ addtimer(CALLBACK(src, PROC_REF(fly), target, user.dir, flightdist))
-/obj/item/melee/touch_attack/overcharged_emitter/Initialize(mapload)
+/obj/item/melee/overcharged_emitter/proc/fly(atom/movable/ball, dir, triplength = 0)
+ if(triplength == 0)
+ return
+ var/turf/Q = get_step(get_turf(ball), dir)
+ var/turf/current = (get_turf(ball))
+ for(var/atom/speedbump in Q.contents)
+ if(isitem(speedbump) || !ismovable(speedbump))
+ continue
+ var/atom/movable/H = speedbump
+ if(isobj(H) && H.density)
+ var/obj/O = H
+ O.take_damage(30)
+ if(O.anchored == TRUE)
+ continue
+ H.forceMove(current)
+ if(isliving(H))
+ var/mob/living/L = H
+ if(iscarbon(L))
+ L.adjustBruteLoss(carboncrash)
+ if(isanimal(L))
+ L.adjustBruteLoss(animalcrash)
+ if(issilicon(L))
+ L.adjustBruteLoss(borgcrash)
+ for(var/atom/movable/T in current.contents)
+ if(isitem(T) || !ismovable(T) || !(T.density))
+ continue
+ T.SpinAnimation(0.2 SECONDS, 1)
+ if(Q.density || (!(Q.reachableTurftestdensity(T = Q))))
+ if(isliving(T))
+ var/mob/living/target = T
+ target.adjustBruteLoss(5)
+ if(isanimal(target))
+ if(target.stat == DEAD)
+ target.visible_message(span_warning("[target] crashes and explodes!"))
+ target.gib()
+ if(isobj(T))
+ var/obj/O = T
+ O.take_damage(20)
+ if(O.anchored == TRUE)
+ continue
+ if(ismineralturf(Q))
+ var/turf/closed/mineral/M = Q
+ M.attempt_drill()
+ if(Q.density || (!(Q.reachableTurftestdensity(T = Q))))
+ return
+ T.forceMove(Q)
+ addtimer(CALLBACK(src, PROC_REF(fly), ball, dir, triplength-1), 0.1 SECONDS)
+
+
+/obj/item/melee/overcharged_emitter/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
animate(src, alpha = 50, time = 5 SECONDS)
QDEL_IN(src, 5 SECONDS)
-/obj/item/melee/touch_attack/overcharged_emitter/Destroy()
- var/datum/action/cooldown/spell/our_spell = spell_which_made_us?.resolve()
- if(our_spell)
- our_spell.build_all_button_icons()
- return ..()
-
-//Seismic Arm
-/obj/item/bodypart/r_arm/robot/seismic
- name = "seismic right arm"
- desc = "A robotic arm adorned with subwoofers capable of emitting shockwaves to imitate strength."
- icon = 'icons/mob/augmentation/augments_seismic.dmi'
- icon_state = "seismic_r_arm"
- max_damage = 60
- var/list/seismic_arm_moves = list(
- /datum/action/cooldown/spell/pointed/seismic/lariat,
- /datum/action/cooldown/spell/pointed/seismic/mop,
- /datum/action/cooldown/spell/pointed/seismic/suplex,
- /datum/action/cooldown/spell/touch/righthook,
- )
-
-/obj/item/bodypart/r_arm/robot/seismic/attach_limb(mob/living/carbon/C, special)
- . = ..()
- for(var/datum/action/cooldown/spell/moves as anything in seismic_arm_moves)
- moves = new moves(C)
- moves.Grant(C)
-
-/obj/item/bodypart/r_arm/robot/seismic/drop_limb(special)
- var/mob/living/carbon/C = owner
- for(var/datum/action/cooldown/spell/moves in C.actions)
- if(LAZYFIND(seismic_arm_moves, moves))
- moves.Remove(C)
- return ..()
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index 3dc7fcb2ce27..41065300c24f 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -19,14 +19,19 @@
var/icon_deny = "mining-deny"
var/list/prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces.
new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750, VENDING_WEAPON),
- new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/kinetic_crusher, 750, VENDING_WEAPON),
+ new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/kinetic_crusher, 750, VENDING_WEAPON),
new /datum/data/mining_equipment("Resonator", /obj/item/resonator, 800, VENDING_WEAPON),
new /datum/data/mining_equipment("Super Resonator", /obj/item/resonator/upgraded, 2500, VENDING_WEAPON),
+ new /datum/data/mining_equipment("Kinetic Javelin", /obj/item/kinetic_javelin/blue, 1000, VENDING_WEAPON), //YOGS EDIT
new /datum/data/mining_equipment("Silver Pickaxe", /obj/item/pickaxe/silver, 1000, VENDING_WEAPON),
new /datum/data/mining_equipment("Diamond Pickaxe", /obj/item/pickaxe/diamond, 2000, VENDING_WEAPON),
new /datum/data/mining_equipment("Mini Plasma Cutter", /obj/item/gun/energy/plasmacutter/mini, 2500, VENDING_WEAPON),
- new /datum/data/mining_equipment("Plasma Cutter Shotgun", /obj/item/gun/energy/plasmacutter/scatter, 6000, VENDING_WEAPON),
- new /datum/data/mining_equipment("Plasma Shotgun Upgrade", /obj/item/upgrade/plasmacutter/defuser, 1000, VENDING_WEAPON),
+ new /datum/data/mining_equipment("Plasma Cutter Shotgun", /obj/item/gun/energy/plasmacutter/scatter, 5000, VENDING_WEAPON),
+ new /datum/data/mining_equipment("PC Defuser Upgrade", /obj/item/upgrade/plasmacutter/defuser, 1000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Capacity Upgrade", /obj/item/upgrade/plasmacutter/capacity, 4500, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Cooldown Upgrade", /obj/item/upgrade/plasmacutter/cooldown, 5000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Range Upgrade", /obj/item/upgrade/plasmacutter/range, 5000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Ore Upgrade", /obj/item/upgrade/plasmacutter/ore, 10000, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150, VENDING_UPGRADE),
@@ -37,6 +42,11 @@
new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA Hardness Increase", /obj/item/borg/upgrade/modkit/hardness, 1200, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("Energized Kinetic Javelin Core", /obj/item/kinetic_javelin_core/blue, 1000, VENDING_UPGRADE), //YOGS EDIT
+ new /datum/data/mining_equipment("Merciful Kinetic Javelin Core", /obj/item/kinetic_javelin_core/green, 1000, VENDING_UPGRADE), //YOGS EDIT
+ new /datum/data/mining_equipment("Enraged Kinetic Javelin Core", /obj/item/kinetic_javelin_core/red, 1500, VENDING_UPGRADE), //YOGS EDIT
+ new /datum/data/mining_equipment("Radiant Kinetic Javelin Core",/obj/item/kinetic_javelin_core/yellow, 2500, VENDING_UPGRADE), //YOGS EDIT
+ new /datum/data/mining_equipment("Loyal Kinetic Javelin Core", /obj/item/kinetic_javelin_core/purple, 3000, VENDING_UPGRADE), //YOGS EDIT
new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400, VENDING_TOOL),
new /datum/data/mining_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000, VENDING_TOOL),
new /datum/data/mining_equipment("Luxury Elite Bar Capsule", /obj/item/survivalcapsule/luxuryelite, 20000, VENDING_TOOL),
@@ -199,7 +209,8 @@
"Extraction and Rescue Kit" = image(icon = 'icons/obj/fulton.dmi', icon_state = "extraction_pack"),
"Crusher Kit" = image(icon = 'icons/obj/mining.dmi', icon_state = "mining_hammer0"),
"Mining Conscription Kit" = image(icon = 'icons/obj/storage.dmi', icon_state = "duffel"),
- "Mini Plasma Cutter Kit" = image(icon = 'icons/obj/guns/energy.dmi', icon_state="plasmacutter_mini")
+ "Mini Plasma Cutter Kit" = image(icon = 'icons/obj/guns/energy.dmi', icon_state="plasmacutter_mini"),
+ "Kinetic Javelin Kit" = image(icon = 'yogstation/icons/obj/kinetic_javelin.dmi', icon_state = "blue") //YOGS EDIT
)
items = sortList(items)
@@ -230,6 +241,9 @@
new /obj/item/storage/backpack/duffelbag/mining_conscript(drop_location)
if("Mini Plasma Cutter Kit")
new /obj/item/gun/energy/plasmacutter/mini(drop_location)
+ if("Kinetic Javelin Kit")
+ new /obj/item/extinguisher/mini(drop_location)
+ new /obj/item/kinetic_javelin/blue(drop_location)
SSblackbox.record_feedback("tally", "mining_voucher_redeemed", 1, selection)
qdel(voucher)
@@ -293,8 +307,12 @@
new /datum/data/mining_equipment("Diamond Pickaxe", /obj/item/pickaxe/diamond, 1500, VENDING_WEAPON),
new /datum/data/mining_equipment("Mini Plasma Cutter", /obj/item/gun/energy/plasmacutter/mini, 500, VENDING_WEAPON),
new /datum/data/mining_equipment("Plasma Cutter" , /obj/item/gun/energy/plasmacutter, 2500, VENDING_WEAPON),
- new /datum/data/mining_equipment("Plasma Cutter Shotgun", /obj/item/gun/energy/plasmacutter/scatter, 6000, VENDING_WEAPON),
- new /datum/data/mining_equipment("Plasma Shotgun Upgrade", /obj/item/upgrade/plasmacutter/defuser, 1000, VENDING_WEAPON),
+ new /datum/data/mining_equipment("Plasma Cutter Shotgun", /obj/item/gun/energy/plasmacutter/scatter, 5000, VENDING_WEAPON),
+ new /datum/data/mining_equipment("PC Defuser Upgrade", /obj/item/upgrade/plasmacutter/defuser, 1000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Capacity Upgrade", /obj/item/upgrade/plasmacutter/capacity, 4500, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Cooldown Upgrade", /obj/item/upgrade/plasmacutter/cooldown, 5000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Range Upgrade", /obj/item/upgrade/plasmacutter/range, 5000, VENDING_UPGRADE),
+ new /datum/data/mining_equipment("PC Ore Upgrade", /obj/item/upgrade/plasmacutter/ore, 10000, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100, VENDING_UPGRADE),
new /datum/data/mining_equipment("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150, VENDING_UPGRADE),
diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm
index 5ec5b905fb62..42a2a9b99ee4 100644
--- a/code/modules/mining/mine_items.dm
+++ b/code/modules/mining/mine_items.dm
@@ -37,6 +37,9 @@
new /obj/item/clothing/gloves/color/black(src)
new /obj/item/clothing/gloves/color/black(src)
new /obj/item/clothing/gloves/color/black(src)
+ new /obj/item/clothing/suit/hooded/wintercoat/miner(src)
+ new /obj/item/clothing/suit/hooded/wintercoat/miner(src)
+ new /obj/item/clothing/suit/hooded/wintercoat/miner(src)
/obj/structure/closet/secure_closet/miner
name = "miner's equipment"
diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm
index 5ccc373a53fb..c1ea6bfafaf4 100644
--- a/code/modules/mining/minebot.dm
+++ b/code/modules/mining/minebot.dm
@@ -205,12 +205,15 @@
var/mob/living/simple_animal/hostile/mining_drone/user = owner
if(user.sight & SEE_TURFS)
user.sight &= ~SEE_TURFS
- user.lighting_alpha = initial(user.lighting_alpha)
+ user.lighting_cutoff_red += 5
+ user.lighting_cutoff_green += 15
+ user.lighting_cutoff_blue += 5
else
user.sight |= SEE_TURFS
- user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
-
- user.sync_lighting_plane_alpha()
+ user.lighting_cutoff_red -= 5
+ user.lighting_cutoff_green -= 15
+ user.lighting_cutoff_blue -= 5
+ user.sync_lighting_plane_cutoff()
to_chat(user, span_notice("You toggle your meson vision [(user.sight & SEE_TURFS) ? "on" : "off"]."))
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index 646b2cf26cec..9cc1e57931c7 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -154,7 +154,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
if(C.is_eyes_covered())
C.visible_message(span_danger("[C]'s eye protection blocks the sand!"), span_warning("Your eye protection blocks the sand!"))
return
- C.adjust_blurriness(6)
+ C.adjust_eye_blur(6)
C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage
C.adjust_confusion(5 SECONDS)
to_chat(C, span_userdanger("\The [src] gets into your eyes! The pain, it burns!"))
@@ -415,7 +415,6 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
var/value = 1
var/coinflip
var/coin_stack_icon_state = "coin_stack"
- var/list/allowed_ricochet_types = list(/obj/projectile/bullet/c38, /obj/projectile/bullet/a357, /obj/projectile/bullet/ipcmartial)
/obj/item/coin/get_item_credit_value()
return value
@@ -603,7 +602,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
flick("coin_[cmineral]_flip", src)
icon_state = "coin_[cmineral]_[coinflip]"
if(flash)
- SSvis_overlays.add_vis_overlay(src, icon, "flash", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, unique = TRUE)
+ SSvis_overlays.add_vis_overlay(src, icon, "flash", ABOVE_LIGHTING_PLANE, unique = TRUE)
playsound(loc, 'sound/items/coinflip.ogg', 50, TRUE)
var/oldloc = loc
sleep(1.5 SECONDS)
@@ -624,10 +623,10 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
transform = initial(transform)
/obj/item/coin/bullet_act(obj/projectile/P)
- if(P.armor_flag != LASER && P.armor_flag != ENERGY && !is_type_in_list(P, allowed_ricochet_types)) //only energy projectiles get deflected (also revolvers because damn thats cool)
+ if(P.armor_flag != LASER && P.armor_flag != ENERGY && !P.can_ricoshot) //only energy projectiles get deflected (also revolvers because damn thats cool)
return ..()
- if(cooldown >= world.time || istype(P, /obj/projectile/bullet/ipcmartial))//we ricochet the projectile
+ if(cooldown >= world.time || P.can_ricoshot == ALWAYS_RICOSHOT)//we ricochet the projectile
var/list/targets = list()
for(var/mob/living/T in viewers(5, src))
if(istype(T) && T != P.firer && T.stat != DEAD) //don't fire at someone if they're dead or if we already hit them
@@ -635,6 +634,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
P.damage *= 1.5
P.speed *= 0.5
P.ricochets++
+ if(P.hitscan)
+ P.store_hitscan_collision(P.trajectory.copy_to()) // ULTRA-RICOSHOT
P.on_ricochet(src)
P.impacted = list(src)
P.pixel_x = pixel_x
diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm
index 16b816d16909..d215c6fedc2f 100644
--- a/code/modules/mining/satchel_ore_boxdm.dm
+++ b/code/modules/mining/satchel_ore_boxdm.dm
@@ -93,5 +93,6 @@
dump_box_contents()
qdel(src)
-/obj/structure/ore_box/onTransitZ()
- return
\ No newline at end of file
+/// Special override for notify_contents = FALSE.
+/obj/structure/ore_box/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = FALSE)
+ return ..()
diff --git a/code/modules/mining/shelters.dm b/code/modules/mining/shelters.dm
index 9dcb54da06f4..d5141dad9f53 100644
--- a/code/modules/mining/shelters.dm
+++ b/code/modules/mining/shelters.dm
@@ -8,7 +8,7 @@
/datum/map_template/shelter/New()
. = ..()
- blacklisted_turfs = typecacheof(/turf/closed, /turf/open/lava/smooth/lava_land_surface/no_shelter)
+ blacklisted_turfs = typecacheof(/turf/closed, /turf/open/lava/smooth/lava_land_surface/no_shelter,/turf/open/water/deep_toxic_pit)
whitelisted_turfs = list()
banned_areas = typecacheof(/area/shuttle)
banned_objects = list()
diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm
index fe06cbf0f341..97363488d33c 100644
--- a/code/modules/mob/camera/camera.dm
+++ b/code/modules/mob/camera/camera.dm
@@ -1,5 +1,4 @@
// Camera mob, used by AI camera and blob.
-
/mob/camera
name = "camera mob"
density = FALSE
@@ -7,21 +6,45 @@
move_resist = INFINITY
status_flags = GODMODE // You can't damage it.
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- see_in_dark = 7
invisibility = INVISIBILITY_ABSTRACT // No one can see us
sight = SEE_SELF
move_on_shuttle = FALSE
+ /// Toggles if the camera can use emotes
+ var/has_emotes = FALSE
+
+/mob/camera/Initialize(mapload)
+ . = ..()
+ //SSpoints_of_interest.make_point_of_interest(src)
/mob/camera/experience_pressure_difference()
return
-/mob/camera/forceMove(atom/destination)
- var/oldloc = loc
- loc = destination
- Moved(oldloc, NONE, TRUE)
-
/mob/camera/canUseStorage()
return FALSE
-/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE, is_keybind = FALSE)
+/mob/camera/up()
+ set name = "Move Upwards"
+ set category = "IC"
+
+ if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK))
+ to_chat(src, span_notice("You move upwards."))
+
+/mob/camera/down()
+ set name = "Move Down"
+ set category = "IC"
+
+ if(zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK))
+ to_chat(src, span_notice("You move down."))
+
+/mob/camera/can_z_move(direction, turf/start, turf/destination, z_move_flags = NONE, mob/living/rider)
+ z_move_flags |= ZMOVE_IGNORE_OBSTACLES //cameras do not respect these FLOORS you speak so much of
+ return ..()
+
+/mob/camera/emote(act, m_type=1, message = null, intentional = FALSE, is_keybind = FALSE, force_silence = FALSE)
+ if(has_emotes)
+ return ..()
return FALSE
+
+/mob/camera/update_sight()
+ lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue)
+ return ..()
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index e656d73c891b..26fc9cfcdddc 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -12,6 +12,8 @@ INITIALIZE_IMMEDIATE(/mob/dead)
if(flags_1 & INITIALIZED_1)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
+ // Initial is non standard here, but ghosts move before they get here so it's needed. this is a cold path too so it's ok
+ SET_PLANE_IMPLICIT(src, initial(plane))
tag = "mob_[next_mob_id++]"
add_to_mob_list()
@@ -25,15 +27,6 @@ INITIALIZE_IMMEDIATE(/mob/dead)
/mob/dead/canUseStorage()
return FALSE
-/mob/dead/forceMove(atom/destination)
- var/turf/old_turf = get_turf(src)
- var/turf/new_turf = get_turf(destination)
- if (old_turf?.z != new_turf?.z)
- onTransitZ(old_turf?.z, new_turf?.z)
- var/oldloc = loc
- loc = destination
- Moved(oldloc, NONE, TRUE)
-
/mob/dead/get_status_tab_items()
. = ..()
. += ""
@@ -82,7 +75,7 @@ INITIALIZE_IMMEDIATE(/mob/dead)
var/client/C = client
to_chat(C, span_notice("Sending you to [pick]."))
- new /atom/movable/screen/splash(C)
+ new /atom/movable/screen/splash(null, C)
notransform = TRUE
sleep(2.9 SECONDS) //let the animation play
@@ -108,6 +101,8 @@ INITIALIZE_IMMEDIATE(/mob/dead)
/mob/dead/Login()
. = ..()
+ if(!. || !client)
+ return FALSE
var/turf/T = get_turf(src)
if (isturf(T))
update_z(T.z)
@@ -119,6 +114,6 @@ INITIALIZE_IMMEDIATE(/mob/dead)
update_z(null)
return ..()
-/mob/dead/onTransitZ(old_z,new_z)
+/mob/dead/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
- update_z(new_z)
+ update_z(new_turf?.z)
diff --git a/code/modules/mob/dead/emote.dm b/code/modules/mob/dead/emote.dm
index 0f93953fdf57..7e305e13fbb3 100644
--- a/code/modules/mob/dead/emote.dm
+++ b/code/modules/mob/dead/emote.dm
@@ -12,7 +12,7 @@
/datum/emote/dead/dab/run_emote(mob/user, params)
. = ..()
- var/mob/dead/observer/H = user
+ var/mob/dead/observer/dabber = user
var/light_dab_angle = rand(35,55)
var/light_dab_speed = rand(3,7)
- H.DabAnimation(angle = light_dab_angle , speed = light_dab_speed)
+ INVOKE_ASYNC(dabber, TYPE_PROC_REF(/atom, DabAnimation), light_dab_speed, 0, 0, 0, light_dab_angle)
diff --git a/code/modules/mob/dead/new_player/latejoin_menu.dm b/code/modules/mob/dead/new_player/latejoin_menu.dm
index 80dd13b9359a..6a46a49dc1cc 100644
--- a/code/modules/mob/dead/new_player/latejoin_menu.dm
+++ b/code/modules/mob/dead/new_player/latejoin_menu.dm
@@ -43,15 +43,10 @@ GLOBAL_DATUM_INIT(latejoin_menu, /datum/latejoin_menu, new)
// FIXME: this can cause a runtime since user can be a living mob
if(istype(user))
user.jobs_menu_mounted = FALSE
- addtimer(CALLBACK(src, PROC_REF(scream_at_player), user), 5 SECONDS)
ui = new(user, src, "JobSelection", "Latejoin Menu")
ui.open()
-/datum/latejoin_menu/proc/scream_at_player(mob/dead/new_player/player)
- if(!player.jobs_menu_mounted)
- to_chat(player, span_notice("If the late join menu isn't showing, you can open the fallback menu using the verb in the Preferences tab!"))
-
/datum/latejoin_menu/ui_data(mob/user)
var/mob/dead/new_player/owner = user
var/list/departments = list()
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 0dc2444d07c1..b0f63caccdde 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -1,27 +1,28 @@
#define LINKIFY_READY(string, value) "[string]"
+///Cooldown for the Reset Lobby Menu HUD verb
+#define RESET_HUD_INTERVAL 15 SECONDS
/mob/dead/new_player
- var/ready = 0
- var/spawning = 0//Referenced when you want to delete the new_player later on in the code.
-
flags_1 = NONE
-
invisibility = INVISIBILITY_ABSTRACT
-
density = FALSE
stat = DEAD
- var/mob/living/new_character //for instant transfer once the round is set up
-
- //Used to make sure someone doesn't get spammed with messages if they're ineligible for roles
+ var/ready = FALSE
+ /// Referenced when you want to delete the new_player later on in the code.
+ var/spawning = FALSE
+ /// For instant transfer once the round is set up
+ var/mob/living/new_character
+ ///Used to make sure someone doesn't get spammed with messages if they're ineligible for roles.
var/ineligible_for_roles = FALSE
-
/// Used to track if the player's jobs menu sent a message saying it successfully mounted.
var/jobs_menu_mounted = FALSE
+ ///Cooldown for the Reset Lobby Menu HUD verb
+ COOLDOWN_DECLARE(reset_hud_cooldown)
/mob/dead/new_player/Initialize(mapload)
if(client && SSticker.state == GAME_STATE_STARTUP)
- var/atom/movable/screen/splash/S = new(client, TRUE, TRUE)
+ var/atom/movable/screen/splash/S = new(null, client, TRUE)
S.Fade(TRUE)
if(length(GLOB.newplayer_start))
@@ -29,11 +30,10 @@
else
forceMove(locate(1,1,1))
- add_verb(usr, /datum/latejoin_menu/verb/open_fallback_ui)
-
. = ..()
GLOB.new_player_list += src
+ add_verb(usr, /datum/latejoin_menu/verb/open_fallback_ui)
/mob/dead/new_player/Destroy()
GLOB.new_player_list -= src
@@ -355,7 +355,6 @@
tgui_alert(usr, "An administrator has disabled late join spawning.")
return FALSE
- var/arrivals_docked = TRUE
if(SSshuttle.arrivals)
close_spawn_windows() //In case we get held up
if(SSshuttle.arrivals.damaged && CONFIG_GET(flag/arrivals_shuttle_require_safe_latejoin))
@@ -364,30 +363,26 @@
if(CONFIG_GET(flag/arrivals_shuttle_require_undocked))
SSshuttle.arrivals.RequireUndocked(src)
- arrivals_docked = SSshuttle.arrivals.mode != SHUTTLE_CALL
//Remove the player from the join queue if he was in one and reset the timer
SSticker.queued_players -= src
SSticker.queue_delay = 4
-
- SSjob.AssignRole(src, rank, 1)
+
+ var/datum/job/job = SSjob.GetJob(rank)
+
+ if(!SSjob.AssignRole(src, rank, TRUE))
+ tgui_alert(usr, "There was an unexpected error putting you into your requested job. If you cannot join with any job, you should contact an admin.")
+ return FALSE
var/mob/living/character = create_character(TRUE) //creates the human and transfers vars and mind
character.mind.quiet_round = character.client.prefs.read_preference(/datum/preference/toggle/quiet_mode) // yogs - Donor Features
var/equip = SSjob.EquipRank(character, rank, TRUE)
+ job.after_latejoin_spawn(character)
if(isliving(equip)) //Borgs get borged in the equip, so we need to make sure we handle the new mob.
character = equip
- var/datum/job/job = SSjob.GetJob(rank)
-
if(job && !job.override_latejoin_spawn(character))
SSjob.SendToLateJoin(character)
- if(!arrivals_docked)
- var/atom/movable/screen/splash/Spl = new(character.client, TRUE)
- Spl.Fade(TRUE)
- character.playsound_local(get_turf(character), 'sound/voice/ApproachingTG.ogg', 25)
-
- character.update_parallax_teleport()
SSticker.minds += character.mind
character.client.init_verbs() // init verbs for the late join
@@ -421,7 +416,7 @@
if(SHUTTLE_RECALL, SHUTTLE_IDLE)
SSticker.mode.make_antag_chance(humanc)
if(SHUTTLE_CALL)
- if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergencyCallTime)*0.5)
+ if(SSshuttle.emergency.timeLeft(1) > initial(SSshuttle.emergency_call_time)*0.5)
SSticker.mode.make_antag_chance(humanc)
if(humanc && CONFIG_GET(flag/roundstart_traits))
@@ -432,6 +427,7 @@
/mob/dead/new_player/proc/create_character(transfer_after)
spawning = TRUE
close_spawn_windows()
+ //mind.active = FALSE //we wish to transfer the key manually
var/mob/living/carbon/human/H = new(loc)
@@ -462,7 +458,7 @@
accent_name = null
mind.accent_name = accent_name
mind.transfer_to(H) //won't transfer key since the mind is not active
- mind.original_character = H
+ mind.set_original_character(H)
H.name = real_name
client.init_verbs()
@@ -473,11 +469,16 @@
/mob/dead/new_player/proc/transfer_character()
. = new_character
- if(.)
- new_character.key = key //Manually transfer the key to log them in
- new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
- new_character = null
- qdel(src)
+ if(!.)
+ return
+ new_character.key = key //Manually transfer the key to log them in
+ new_character.stop_sound_channel(CHANNEL_LOBBYMUSIC)
+ var/area/joined_area = get_area(new_character.loc)
+ if(joined_area)
+ joined_area.on_joining_game(new_character)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CREWMEMBER_JOINED, new_character, new_character.mind.assigned_role)
+ new_character = null
+ qdel(src)
/mob/dead/new_player/proc/ViewManifest()
if(!client)
@@ -530,3 +531,5 @@
return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
return TRUE
+
+#undef RESET_HUD_INTERVAL
diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm
index 8d3e0bc14bfd..3edf0f22f170 100644
--- a/code/modules/mob/dead/new_player/poll.dm
+++ b/code/modules/mob/dead/new_player/poll.dm
@@ -569,7 +569,7 @@
adminrank = client.holder.rank_name()
if(isnull(rating))
rating = "null"
- var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank, :rating", list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank, "rating" = rating))
+ var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), :pollid, :optionid, :ckey, INET_ATON(:address), :adminrank, :rating)", list("pollid" = pollid, "optionid" = optionid, "ckey" = ckey, "address" = client.address, "adminrank" = adminrank, "rating" = rating))
if(!query_numval_vote.warn_execute())
qdel(query_numval_vote)
return
diff --git a/code/modules/mob/dead/new_player/sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories.dm
index 8e98046522f2..79e62e6a0993 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories.dm
@@ -2267,6 +2267,23 @@
dimension_y = 34
color_src = null
+/datum/sprite_accessory/wings/elytra
+ name = "Elytra"
+ icon_state = "elytra"
+ dimension_x = 32
+ center = TRUE
+ dimension_y = 32
+ locked = TRUE
+ color_src = EYECOLOR
+
+/datum/sprite_accessory/wings_open/elytra
+ name = "Elytra"
+ icon_state = "elytra"
+ dimension_x = 64
+ center = TRUE
+ dimension_y = 32
+ color_src = EYECOLOR
+
/datum/sprite_accessory/frills
icon = 'icons/mob/mutant_bodyparts.dmi'
@@ -2610,6 +2627,138 @@
name = "X"
icon_state = "x"
+//Preternis body weathering
+/datum/sprite_accessory/preternis_weathering
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+ color_src = null
+
+/datum/sprite_accessory/preternis_weathering/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/preternis_weathering/damaged
+ name = "Damaged"
+ icon_state = "damaged"
+
+/datum/sprite_accessory/preternis_weathering/rusted
+ name = "Rusted"
+ icon_state = "rusted"
+
+/datum/sprite_accessory/preternis_weathering/repaired
+ name = "Repaired"
+ icon_state = "repaired"
+
+/datum/sprite_accessory/preternis_weathering/fullchasis
+ name = "Full chassis"
+ icon_state = "fullchassis"
+
+/datum/sprite_accessory/preternis_weathering/fullbody
+ name = "Upper body"
+ icon_state = "fullbody"
+
+/datum/sprite_accessory/preternis_weathering/halfbody
+ name = "Upper body (minor)"
+ icon_state = "halfbody"
+
+/datum/sprite_accessory/preternis_weathering/rightarm
+ name = "Right arm"
+ icon_state = "rightarm"
+
+/datum/sprite_accessory/preternis_weathering/leftarm
+ name = "Left arm"
+ icon_state = "leftarm"
+
+/datum/sprite_accessory/preternis_weathering/fullarm
+ name = "Both arms"
+ icon_state = "fullarm"
+
+/datum/sprite_accessory/preternis_weathering/halfarm
+ name = "Both arms (minor)"
+ icon_state = "halfarm"
+
+/datum/sprite_accessory/preternis_weathering/waist
+ name = "Waist"
+ icon_state = "waist"
+
+//Preternis body weathering
+/datum/sprite_accessory/preternis_antenna
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/preternis_antenna/none
+ name = "None"
+ icon_state = "none"
+
+/datum/sprite_accessory/preternis_antenna/dracula
+ name = "Dracula"
+ icon_state = "dracula"
+
+/datum/sprite_accessory/preternis_antenna/ghost
+ name = "Ghost"
+ icon_state = "ghost"
+
+/datum/sprite_accessory/preternis_antenna/army
+ name = "Army"
+ icon_state = "army"
+
+/datum/sprite_accessory/preternis_antenna/praetor
+ name = "Praetor"
+ icon_state = "praetor"
+
+/datum/sprite_accessory/preternis_antenna/field
+ name = "Field"
+ icon_state = "field"
+
+/datum/sprite_accessory/preternis_antenna/driver
+ name = "Driver"
+ icon_state = "driver"
+
+//Preternis eye
+/datum/sprite_accessory/preternis_eye
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+ color_src = EYECOLOR
+
+/datum/sprite_accessory/preternis_eye/one
+ name = "Standard"
+ icon_state = "1"
+
+/datum/sprite_accessory/preternis_eye/two
+ name = "Focused"
+ icon_state = "2"
+
+/datum/sprite_accessory/preternis_eye/three
+ name = "Fanned"
+ icon_state = "3"
+
+/datum/sprite_accessory/preternis_eye/four
+ name = "Horizontal"
+ icon_state = "4"
+
+/datum/sprite_accessory/preternis_eye/six
+ name = "Vertical"
+ icon_state = "6"
+
+/datum/sprite_accessory/preternis_eye/five
+ name = "Chambered"
+ icon_state = "5"
+
+/datum/sprite_accessory/preternis_eye/seven
+ name = "Compound"
+ icon_state = "7"
+
+/datum/sprite_accessory/preternis_eye/visionary
+ name = "Visionary"
+ icon_state = "8"
+
+//preternis core
+/datum/sprite_accessory/preternis_core
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+ color_src = EYECOLOR
+
+/datum/sprite_accessory/preternis_core/core
+ name = "Core"
+ icon_state = "core"
+
//Phytosian hair
/datum/sprite_accessory/pod_hair
icon = 'icons/mob/pod_hair.dmi'
diff --git a/code/modules/mob/dead/observer/jumpto.dm b/code/modules/mob/dead/observer/jumpto.dm
new file mode 100644
index 000000000000..cb397336255f
--- /dev/null
+++ b/code/modules/mob/dead/observer/jumpto.dm
@@ -0,0 +1,75 @@
+/datum/jump_menu
+ var/mob/dead/observer/owner
+ var/auto_observe = FALSE
+
+/datum/jump_menu/New(mob/dead/observer/new_owner)
+ if(!istype(new_owner))
+ qdel(src)
+ owner = new_owner
+
+/datum/jump_menu/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/jump_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if (!ui)
+ ui = new(user, src, "Jump")
+ ui.open()
+
+/datum/jump_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if ("jump")
+ if(!isobserver(usr))
+ return
+
+ var/ref = params["ref"]
+ var/list/pois = GLOB.mob_list
+ var/atom/movable/poi = (locate(ref) in pois) || (locate(ref) in GLOB.areas)
+
+ if (!poi)
+ . = TRUE
+ return
+
+ if(isarea(poi))
+ var/area/A = poi
+ owner.forceMove(pick(get_area_turfs(A)))
+ else
+ owner.forceMove(get_turf(poi))
+ owner.reset_perspective(null)
+ if ("refresh")
+ update_static_data(owner, ui)
+ . = TRUE
+
+/datum/jump_menu/ui_static_data(mob/user)
+ var/list/data = list()
+
+ var/list/mobs = list()
+ var/list/areas
+
+ var/list/mob_list = GLOB.mob_list
+ for (var/mob/M in mob_list)
+ var/list/serialized = list()
+ serialized["name"] = M.name
+ serialized["ref"] = REF(M) // Apparently name isn't a direct ref.
+ mobs += list(serialized)
+
+ var/list/Alist = GLOB.areas
+ for (var/area/A in Alist)
+ if(A.hidden)
+ continue
+ var/list/serialized = list()
+ serialized["name"] = A.name
+ serialized["ref"] = REF(A)
+ areas += list(serialized)
+
+ data["mobs"] = mobs
+ data["areas"] = areas
+ return data
+
+/datum/jump_menu/ui_assets()
+ . = ..() || list()
+ . += get_asset_datum(/datum/asset/simple/orbit)
+
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 6f2f8e73ed36..0205ef6b745b 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -8,12 +8,11 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
desc = "It's a g-g-g-g-ghooooost!" //jinkies!
icon = 'icons/mob/mob.dmi'
icon_state = "ghost"
- layer = GHOST_LAYER
+ plane = GHOST_PLANE
stat = DEAD
density = FALSE
see_invisible = SEE_INVISIBLE_OBSERVER
- see_in_dark = 100
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ lighting_cutoff = LIGHTING_CUTOFF_MEDIUM
invisibility = INVISIBILITY_OBSERVER
hud_type = /datum/hud/ghost
movement_type = GROUND | FLYING
@@ -47,7 +46,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
var/facial_hair_color
var/mutable_appearance/facial_hair_overlay
- var/updatedir = 1 //Do we have to update our dir as the ghost moves around?
+ var/updatedir = 1 //Do we have to update our dir as the ghost moves around?
var/lastsetting = null //Stores the last setting that ghost_others was set to, for a little more efficiency when we update ghost images. Null means no update is necessary
//We store copies of the ghost display preferences locally so they can be referred to even if no client is connected.
@@ -57,7 +56,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
// Used for displaying in ghost chat, without changing the actual name
// of the mob
var/deadchat_name
- var/datum/orbit_menu/orbit_menu
+ var/datum/orbit_menu/orbit_ui
+ var/datum/jump_menu/jump_ui
var/datum/spawners_menu/spawners_menu
///Action to quickly unobserve someone
@@ -115,7 +115,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
facial_hair_style = body_human.facial_hair_style
facial_hair_color = brighten_color(body_human.facial_hair_color)
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(!T || is_secret_level(T.z))
var/list/turfs = get_area_turfs(/area/shuttle/arrival)
@@ -124,7 +124,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
else
T = SSmapping.get_station_center()
- forceMove(T)
+ abstract_move(T)
if(!name) //To prevent nameless ghosts
name = random_unique_name(gender)
@@ -182,7 +182,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
updateallghostimages()
- QDEL_NULL(orbit_menu)
+ QDEL_NULL(orbit_ui)
+ QDEL_NULL(jump_ui)
QDEL_NULL(spawners_menu)
return ..()
@@ -346,30 +347,37 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/response = tgui_alert(usr, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you may not play again this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?",list("Ghost","Stay in body"))
if(response != "Ghost")
return
- ghostize(0)
+ ghostize(FALSE)
/mob/dead/observer/Move(NewLoc, direct, glide_size_override = 32)
if(updatedir)
setDir(direct)//only update dir if we actually need it, so overlays won't spin on base sprites that don't have directions of their own
- var/oldloc = loc
if(glide_size_override)
set_glide_size(glide_size_override)
if(NewLoc)
- forceMove(NewLoc)
+ abstract_move(NewLoc)
update_parallax_contents()
else
- forceMove(get_turf(src)) //Get out of closets and such as a ghost
+ var/turf/destination = get_turf(src)
+
if((direct & NORTH) && y < world.maxy)
- y++
+ destination = get_step(destination, NORTH)
+
else if((direct & SOUTH) && y > 1)
- y--
+ destination = get_step(destination, SOUTH)
+
if((direct & EAST) && x < world.maxx)
- x++
+ destination = get_step(destination, EAST)
+
else if((direct & WEST) && x > 1)
- x--
+ destination = get_step(destination, WEST)
+
+ abstract_move(destination)//Get out of closets and such as a ghost
- Moved(oldloc, direct)
+/mob/dead/observer/forceMove(atom/destination)
+ abstract_move(destination) // move like the wind
+ return TRUE
/mob/dead/observer/verb/reenter_corpse()
set category = "Ghost"
@@ -435,42 +443,22 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/proc/dead_tele()
set category = "Ghost"
set name = "Teleport"
- set desc= "Teleport to a location"
- if(!isobserver(usr))
- to_chat(usr, span_warning("Not when you're not dead!"))
- return
- var/list/filtered = list()
- for(var/area/A as anything in get_sorted_areas())
- if(!A.hidden)
- filtered += A
- var/area/thearea = tgui_input_list(usr, "Area to jump to", "BOOYEA", filtered)
+ set desc= "Ghostly Magic"
- if(!thearea)
- return
- if(!isobserver(usr))
- to_chat(usr, span_warning("Not when you're not dead!"))
- return
-
- var/list/L = list()
- for(var/turf/T in get_area_turfs(thearea.type))
- L+=T
-
- if(!L || !length(L))
- to_chat(usr, span_warning("No area available."))
- return
+ if(!jump_ui)
+ jump_ui = new(src)
- usr.forceMove(pick(L))
- update_parallax_contents()
+ jump_ui.ui_interact(src)
/mob/dead/observer/verb/follow()
set category = "Ghost"
set name = "Orbit" // "Haunt"
set desc = "Follow and orbit a mob."
- if(!orbit_menu)
- orbit_menu = new(src)
+ if(!orbit_ui)
+ orbit_ui = new(src)
- orbit_menu.ui_interact(src)
+ orbit_ui.ui_interact(src)
// This is the ghost's follow verb with an argument
/mob/dead/observer/proc/ManualFollow(atom/movable/target)
@@ -508,32 +496,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
pixel_y = 0
animate(src, pixel_y = 2, time = 1 SECONDS, loop = -1)
-/mob/dead/observer/verb/jump_to_mob() //Moves the ghost instead of just changing the ghosts's eye -Nodrak
- set category = "Ghost"
- set name = "Jump to Mob"
- set desc = "Teleport to a mob"
-
- if(isobserver(usr)) //Make sure they're an observer!
-
-
- var/list/possible_destinations = getpois(mobs_only = TRUE) //List of possible destinations (mobs)
- var/target = null //Chosen target.
-
- target = tgui_input_list(usr, "Please, select a player!", "Jump to Mob", possible_destinations)
-
- if (!target)//Make sure we actually have a target
- return
- else
- var/mob/M = possible_destinations[target] //Destination mob
- var/mob/A = src //Source mob
- var/turf/T = get_turf(M) //Turf of the destination mob
-
- if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination.
- A.forceMove(T)
- A.update_parallax_contents()
- else
- to_chat(A, "This mob is not located in the game world.")
-
/mob/dead/observer/verb/change_view_range()
set category = "Ghost"
set name = "View Range"
@@ -597,15 +559,15 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/verb/toggle_darkness()
set name = "Toggle Darkness"
set category = "Ghost"
- switch(lighting_alpha)
- if (LIGHTING_PLANE_ALPHA_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
+ switch(lighting_cutoff)
+ if (LIGHTING_CUTOFF_VISIBLE)
+ lighting_cutoff = LIGHTING_CUTOFF_MEDIUM
+ if (LIGHTING_CUTOFF_MEDIUM)
+ lighting_cutoff = LIGHTING_CUTOFF_HIGH
+ if (LIGHTING_CUTOFF_HIGH)
+ lighting_cutoff = LIGHTING_CUTOFF_FULLBRIGHT
else
- lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
+ lighting_cutoff = LIGHTING_CUTOFF_VISIBLE
update_sight()
@@ -838,7 +800,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
qdel(species)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/mob/dead/observer/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
return IsAdminGhost(usr)
@@ -870,21 +832,22 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
cleanup_observe()
if(..())
if(hud_used)
- client.screen = list()
+ client.clear_screen()
hud_used.show_hud(hud_used.hud_version)
/mob/dead/observer/proc/cleanup_observe()
+ if(isnull(observetarget))
+ return
var/mob/target = observetarget
observetarget = null
client?.perspective = initial(client.perspective)
- sight = initial(sight)
+ set_sight(initial(sight))
if(target)
UnregisterSignal(target, COMSIG_MOVABLE_Z_CHANGED)
hide_other_mob_action_buttons(target)
LAZYREMOVE(target.observers, src)
- actions = originalactions
- actions -= unobserve_button
+ unobserve_button.Remove(src)
update_action_buttons()
/mob/dead/observer/verb/observe()
@@ -911,6 +874,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(!mob_target.loc)
return
+ reset_perspective(null)
+
var/mob/chosen_target = possible_destinations[target]
do_observe(chosen_target)
@@ -920,23 +885,37 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
stack_trace("/mob/dead/new_player: \[[mob_eye]\] is being observed by [key_name(src)]. This should never happen and has been blocked.")
message_admins("[ADMIN_LOOKUPFLW(src)] attempted to observe someone in the lobby: [ADMIN_LOOKUPFLW(mob_eye)]. This should not be possible and has been blocked.")
return
+
+ if(!isnull(observetarget))
+ stack_trace("do_observe called on an observer ([src]) who was already observing something! (observing: [observetarget], new target: [mob_eye])")
+ message_admins("[ADMIN_LOOKUPFLW(src)] attempted to observe someone while already observing someone, \
+ this is a bug (and a past exploit) and should be investigated.")
+ return
//Istype so we filter out points of interest that are not mobs
if(client && mob_eye && istype(mob_eye))
- client.eye = mob_eye
+ client.set_eye(mob_eye)
client.perspective = EYE_PERSPECTIVE
if(is_secret_level(mob_eye.z) && !client?.holder)
- sight = null //we dont want ghosts to see through walls in secret areas
+ set_sight(null) //we dont want ghosts to see through walls in secret areas
RegisterSignal(mob_eye, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_observing_z_changed), TRUE)
if(!unobserve_button)
unobserve_button = new(src)
unobserve_button.Grant(src)
if(mob_eye.hud_used)
- client.screen = list()
+ client.clear_screen()
LAZYOR(mob_eye.observers, src)
mob_eye.hud_used.show_hud(mob_eye.hud_used.hud_version, src)
observetarget = mob_eye
+/mob/dead/observer/proc/on_observing_z_changed(datum/source, turf/old_turf, turf/new_turf)
+ SIGNAL_HANDLER
+
+ if(is_secret_level(new_turf.z) && !client?.holder)
+ set_sight(null) //we dont want ghosts to see through walls in secret areas
+ else
+ set_sight(initial(sight))
+
/datum/action/innate/unobserve
name = "Stop Observing"
desc = "Stops observing the person."
@@ -950,14 +929,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/datum/action/innate/unobserve/IsAvailable(feedback = FALSE)
return TRUE
-/mob/dead/observer/proc/on_observing_z_changed(datum/source, oldz, newz)
- SHOULD_NOT_SLEEP(TRUE)
-
- if(is_secret_level(newz) && !client?.holder)
- sight = null //we dont want ghosts to see through walls in secret areas
- else
- sight = initial(sight)
-
/mob/dead/observer/verb/register_pai_candidate()
set category = "Ghost"
set name = "pAI Setup"
@@ -1020,10 +991,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/list/t_ray_images = list()
var/static/list/stored_t_ray_images = list()
for(var/obj/O in orange(client.view, src) )
- if(O.level != 1)
- continue
-
- if(O.invisibility == INVISIBILITY_MAXIMUM)
+ if(HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE))
var/image/I = new(loc = get_turf(O))
var/mutable_appearance/MA = new(O)
MA.alpha = 128
@@ -1031,7 +999,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
I.appearance = MA
t_ray_images += I
stored_t_ray_images += t_ray_images
- if(t_ray_images.len)
+ if(length(t_ray_images))
if(t_ray_view)
client.images += t_ray_images
else
diff --git a/code/modules/mob/dead/observer/observer_movement.dm b/code/modules/mob/dead/observer/observer_movement.dm
deleted file mode 100644
index f3e411fb06a2..000000000000
--- a/code/modules/mob/dead/observer/observer_movement.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/mob/dead/observer/canZMove(direction, turf/target)
- return TRUE
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index 075bbe7392b5..fb824d486687 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -20,7 +20,6 @@
param = copytext(act, custom_param + length(act[custom_param]))
act = copytext(act, 1, custom_param)
-
var/list/key_emotes = GLOB.emote_list[act]
if(!length(key_emotes))
@@ -33,12 +32,13 @@
silenced = TRUE
continue
if(P.run_emote(src, param, m_type, intentional))
+ SEND_SIGNAL(src, COMSIG_MOB_EMOTE, P, act, m_type, message, intentional)
+ SEND_SIGNAL(src, COMSIG_MOB_EMOTED(P.key))
return TRUE
if(intentional && !silenced)
to_chat(src, span_notice("Unusable emote '[act]'. Say *help for a list."))
return FALSE
-
/datum/emote/flip
key = "flip"
key_third_person = "flips"
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index b30a1abc79f2..a5e36af995ad 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -189,8 +189,7 @@
dropItemToGround(get_item_for_held_index(hand_index), force = TRUE)
I.forceMove(src)
held_items[hand_index] = I
- I.layer = ABOVE_HUD_LAYER
- I.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
I.equipped(src, ITEM_SLOT_HANDS, no_sound)
if(I.pulledby)
I.pulledby.stop_pulling()
@@ -239,7 +238,7 @@
var/obj/item/stack/I_stack = I
var/obj/item/stack/active_stack = get_active_held_item()
- if (I_stack.zero_amount())
+ if (I_stack.is_zero_amount(delete_if_zero = TRUE))
return FALSE
if (merge_stacks)
@@ -268,7 +267,7 @@
return FALSE
I.forceMove(drop_location())
I.layer = initial(I.layer)
- I.plane = initial(I.plane)
+ SET_PLANE_IMPLICIT(I, initial(I.plane))
I.dropped(src)
return FALSE
@@ -327,7 +326,7 @@
//DO NOT CALL THIS PROC
//use one of the above 3 helper procs
//you may override it, but do not modify the args
-/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress.
+/mob/proc/doUnEquip(obj/item/I, force, atom/newloc, no_move, invdrop = TRUE, silent = FALSE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress.
//Use no_move if the item is just gonna be immediately moved afterward
//Invdrop is used to prevent stuff in pockets dropping. only set to false if it's going to immediately be replaced
PROTECTED_PROC(TRUE)
@@ -336,6 +335,9 @@
if(HAS_TRAIT(I, TRAIT_NODROP) && !force)
return FALSE
+
+ if((SEND_SIGNAL(I, COMSIG_ITEM_PRE_UNEQUIP, force, newloc, no_move, invdrop, silent) & COMPONENT_ITEM_BLOCK_UNEQUIP) && !force)
+ return FALSE
var/hand_index = get_held_index_of_item(I)
if(hand_index)
@@ -345,7 +347,7 @@
if(client)
client.screen -= I
I.layer = initial(I.layer)
- I.plane = initial(I.plane)
+ SET_PLANE_EXPLICIT(I, initial(I.plane), newloc)
I.appearance_flags &= ~NO_CLIENT_COLOR
if(!no_move && !(I.item_flags & DROPDEL)) //item may be moved/qdel'd immedietely, don't bother moving it
if (isnull(newloc))
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 9f4d093d4f67..f863757f99f5 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -37,7 +37,7 @@
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_HUSK))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
- if(blood_volume < BLOOD_VOLUME_NORMAL(src) && !HAS_TRAIT(src, TRAIT_NOHUNGER))
+ if(blood_volume < BLOOD_VOLUME_NORMAL(src) && !HAS_TRAIT(src, TRAIT_NOHUNGER) && !HAS_TRAIT(src, TRAIT_NO_BLOOD_REGEN))
var/obj/item/organ/heart = getorganslot(ORGAN_SLOT_HEART)
var/heart_ratio = heart ? heart.get_organ_efficiency() : 0.5 //slower blood regeneration without a heart, or with a broken one 3
var/nutrition_ratio = 0
@@ -68,7 +68,7 @@
if(BLOOD_BAD)
adjustOxyLoss(round((BLOOD_VOLUME_NORMAL(src) - blood_volume) * 0.02, 1))
if(prob(5))
- blur_eyes(6)
+ adjust_eye_blur(6)
to_chat(src, span_warning("You feel very [word]."))
if(BLOOD_SURVIVE)
adjustOxyLoss(5)
@@ -238,7 +238,8 @@
if((D.spread_flags & DISEASE_SPREAD_SPECIAL) || (D.spread_flags & DISEASE_SPREAD_NON_CONTAGIOUS))
continue
C.ForceContractDisease(D)
- if(!(blood_data["blood_type"] in get_safe_blood(C.dna.blood_type)))
+ var/datum/blood_type/blood_type = blood_data["blood_type"]
+ if(!(blood_type.type in C.dna.blood_type.compatible_types))
C.reagents.add_reagent(/datum/reagent/toxin, amount * 0.5)
return TRUE
@@ -317,47 +318,31 @@
return
return /datum/reagent/blood
-// This is has more potential uses, and is probably faster than the old proc.
-/proc/get_safe_blood(bloodtype)
- . = list()
- if(!bloodtype)
- return
-
- var/static/list/bloodtypes_safe = list(
- "A-" = list("A-", "O-", "U"),
- "A+" = list("A-", "A+", "O-", "O+", "U"),
- "B-" = list("B-", "O-", "U"),
- "B+" = list("B-", "B+", "O-", "O+", "U"),
- "AB-" = list("A-", "B-", "O-", "AB-", "U"),
- "AB+" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+", "U"),
- "O-" = list("O-", "U"),
- "O+" = list("O-", "O+", "U"),
- "L" = list("L", "U"),
- "U" = list("A-", "A+", "B-", "B+", "O-", "O+", "AB-", "AB+", "L", "U") //literally universal
- )
-
- var/safe = bloodtypes_safe[bloodtype]
- if(safe)
- . = safe
+/proc/random_blood_type()
+ return get_blood_type(pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+"))
+
+/proc/get_blood_type(type)
+ return GLOB.blood_types[type]
+
+/proc/get_blood_dna_color(list/blood_dna)
+ if(!blood_dna)
+ return null
+ var/blood_print = blood_dna[length(blood_dna)]
+ var/datum/blood_type/blood_type = blood_dna[blood_print]
+ if(!blood_type)
+ return null
+ if(!istype(blood_type))//maybe a letter somehow got passed here, check anyways
+ blood_type = get_blood_type(blood_type)
+ if(!blood_type || !istype(blood_type))
+ return null
+ return blood_type.color
//to add a splatter of blood or other mob liquid.
/mob/living/proc/add_splatter_floor(turf/T, small_drip)
- if(!T)
- T = get_turf(src)
- if(ispolysmorph(src)) //polysmorphs bleed green blood
- var/obj/effect/decal/cleanable/xenoblood/B = locate() in T.contents
- if(!B)
- B = new(T)
- B.transfer_mob_blood_dna(src)
- return
- if(isethereal(src))
- var/obj/effect/decal/cleanable/whiteblood/ethereal/B = locate() in T.contents
- if(!B)
- B = new(T)
- B.transfer_mob_blood_dna(src)
- return
if(get_blood_id() != /datum/reagent/blood)
return
+ if(!T)
+ T = get_turf(src)
var/list/temp_blood_DNA
if(small_drip)
// Only a certain number of drips (or one large splatter) can be on a given turf.
@@ -367,6 +352,8 @@
drop.drips++
drop.add_overlay(pick(drop.random_icon_states))
drop.transfer_mob_blood_dna(src)
+ if(isethereal(src))
+ drop.Etherealify()
return
else
temp_blood_DNA = drop.return_blood_DNA() //we transfer the dna from the drip to the splatter
@@ -374,17 +361,20 @@
else
drop = new(T, get_static_viruses())
drop.transfer_mob_blood_dna(src)
+ if(isethereal(src))
+ drop.Etherealify()
return
// Find a blood decal or create a new one.
var/obj/effect/decal/cleanable/blood/B = locate() in T
if(!B)
B = new /obj/effect/decal/cleanable/blood/splatter(T, get_static_viruses())
- if (B.bloodiness < MAX_SHOE_BLOODINESS) //add more blood, up to a limit
- B.bloodiness += BLOOD_AMOUNT_PER_DECAL
+ B.bloodiness = min((B.bloodiness + BLOOD_AMOUNT_PER_DECAL), BLOOD_POOL_MAX)
B.transfer_mob_blood_dna(src) //give blood info to the blood decal.
if(temp_blood_DNA)
B.add_blood_DNA(temp_blood_DNA)
+ if(isethereal(src))
+ B.Etherealify()
/mob/living/carbon/human/add_splatter_floor(turf/T, small_drip)
if(!(NOBLOOD in dna.species.species_traits))
diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm
index 7ff5578de58b..99171e324ffb 100644
--- a/code/modules/mob/living/brain/MMI.dm
+++ b/code/modules/mob/living/brain/MMI.dm
@@ -14,7 +14,8 @@
var/datum/ai_laws/laws = new()
var/force_replace_ai_name = FALSE
var/overrides_aicore_laws = TRUE // Whether the laws on the MMI are transferred when it's uploaded as an AI
- var/override_cyborg_laws = FALSE // Do custom laws uploaded to the MMI get transferred to borgs? If yes the borg will be unlinked and have lawsync disabled.
+ /// Do custom laws uploaded to the MMI get transferred to borgs? If yes, the borg will be unlinked, have their lawsync disabled, and get the custom laws.
+ var/override_cyborg_laws = FALSE
var/can_update_laws = TRUE //Can we use a lawboard to change the laws of this MMI?
var/remove_time = 2 SECONDS /// The time to remove the brain or reset the posi brain
var/rebooting = FALSE /// If the MMI is rebooting after being deconstructed
diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm
index 81ada574d608..c296f5396282 100644
--- a/code/modules/mob/living/brain/brain.dm
+++ b/code/modules/mob/living/brain/brain.dm
@@ -17,6 +17,12 @@
OB.brainmob = src
forceMove(OB)
+/mob/living/brain/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ var/obj/item/organ/brain/brain_loc = loc
+ if(brain_loc && isnull(new_turf) && brain_loc.owner) //we're actively being put inside a new body.
+ return ..(old_turf, get_turf(brain_loc.owner), same_z_layer, notify_contents)
+ return ..()
+
/mob/living/brain/proc/create_dna()
stored_dna = new /datum/dna/stored(src)
if(!stored_dna.species)
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index 1cf7fedec273..52d935f24f3e 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -105,10 +105,11 @@
if(!brainmob.stored_dna)
brainmob.stored_dna = new /datum/dna/stored(brainmob)
C.dna.copy_dna(brainmob.stored_dna)
- if(HAS_TRAIT(L, TRAIT_BADDNA))
- LAZYSET(brainmob.status_traits, TRAIT_BADDNA, L.status_traits[TRAIT_BADDNA])
- if(HAS_TRAIT(L, TRAIT_NOCLONE)) // YOU CAN'T ESCAPE
- LAZYSET(brainmob.status_traits, TRAIT_NOCLONE, L.status_traits[TRAIT_NOCLONE])
+ // Hack, fucked dna needs to follow the brain to prevent memes, so we need to copy over the trait sources and shit
+ for(var/source in GET_TRAIT_SOURCES(L, TRAIT_BADDNA))
+ ADD_TRAIT(brainmob, TRAIT_BADDNA, source)
+ for(var/source in GET_TRAIT_SOURCES(L, TRAIT_NOCLONE))
+ ADD_TRAIT(brainmob, TRAIT_NOCLONE, source)
var/obj/item/organ/zombie_infection/ZI = L.getorganslot(ORGAN_SLOT_ZOMBIE)
if(ZI)
brainmob.set_species(ZI.old_species) //For if the brain is cloned
diff --git a/code/modules/mob/living/brain/status_procs.dm b/code/modules/mob/living/brain/status_procs.dm
index 1c5aeb1f47c0..e69de29bb2d1 100644
--- a/code/modules/mob/living/brain/status_procs.dm
+++ b/code/modules/mob/living/brain/status_procs.dm
@@ -1,25 +0,0 @@
-//Here are the procs used to modify status effects of a mob.
-//The effects include: stun, knockdown, unconscious, sleeping, resting, jitteriness, dizziness
-// eye damage, eye_blind, eye_blurry, druggy, TRAIT_BLIND trait, and TRAIT_NEARSIGHT trait.
-
-/////////////////////////////////// EYE_BLIND ////////////////////////////////////
-
-/mob/living/brain/blind_eyes() // no eyes to damage or heal
- return
-
-/mob/living/brain/adjust_blindness()
- return
-
-/mob/living/brain/set_blindness()
- return
-
-/////////////////////////////////// EYE_BLURRY ////////////////////////////////////
-
-/mob/living/brain/blur_eyes()
- return
-
-/mob/living/brain/adjust_blurriness()
- return
-
-/mob/living/brain/set_blurriness()
- return
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index 995728c0f2ad..69bada1582a9 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -6,11 +6,11 @@
faction = list(ROLE_ALIEN)
ventcrawler = VENTCRAWLER_ALWAYS
sight = SEE_MOBS
- see_in_dark = 4
verb_say = "hisses"
initial_language_holder = /datum/language_holder/alien
- bubble_icon = "alien"
+ bubble_icon = BUBBLE_ALIEN
type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie
var/has_fine_manipulation = 0
@@ -25,6 +25,7 @@
var/static/regex/alien_name_regex = new("alien (larva|sentinel|drone|hunter|praetorian|queen)( \\(\\d+\\))?")
blood_volume = BLOOD_VOLUME_XENO //Yogs -- Makes monkeys/xenos have different amounts of blood from normal carbonbois
+ var/datum/techweb/linked_techweb
/mob/living/carbon/alien/Initialize(mapload)
add_verb(src, /mob/living/proc/mob_sleep)
@@ -34,13 +35,16 @@
create_internal_organs()
+ if(!linked_techweb)
+ linked_techweb = SSresearch.science_tech
+
. = ..()
/mob/living/carbon/alien/create_internal_organs()
internal_organs += new /obj/item/organ/brain/alien
internal_organs += new /obj/item/organ/alien/hivenode
internal_organs += new /obj/item/organ/tongue/alien
- internal_organs += new /obj/item/organ/eyes/night_vision/alien
+ internal_organs += new /obj/item/organ/eyes/alien
internal_organs += new /obj/item/organ/liver/alien
internal_organs += new /obj/item/organ/ears
..()
@@ -109,7 +113,7 @@ Des: Gives the client of the alien an image on each infected mob.
if(HAS_TRAIT(L, TRAIT_XENO_HOST))
var/obj/item/organ/body_egg/alien_embryo/A = L.getorgan(/obj/item/organ/body_egg/alien_embryo)
if(A)
- var/I = image('icons/mob/alien.dmi', loc = L, icon_state = "infected[A.stage]")
+ var/I = image('icons/mob/alien.dmi', loc = L, icon_state = "infected[A.current_stage]")
client.images += I
return
diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
index 4ccdde632602..24e3d79584c4 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm
@@ -5,7 +5,7 @@
ventcrawler = VENTCRAWLER_NONE //pull over that ass too fat
unique_name = 0
pixel_x = -16
- bubble_icon = "alienroyal"
+ bubble_icon = BUBBLE_ALIENROYAL
mob_size = MOB_SIZE_LARGE
layer = LARGE_MOB_LAYER //above most mobs, but below speechbubbles
pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper.
diff --git a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm
index 3e5fdaf31ef9..147598f9a7b7 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm
@@ -85,14 +85,20 @@
var/itm_state = l_hand.item_state
if(!itm_state)
itm_state = l_hand.icon_state
- hands += mutable_appearance(alt_inhands_file, "[itm_state][caste]_l", -HANDS_LAYER)
+ var/mutable_appearance/l_hand_item = mutable_appearance(alt_inhands_file, "[itm_state][caste]_l", -HANDS_LAYER)
+ if(l_hand.blocks_emissive != EMISSIVE_BLOCK_NONE)
+ l_hand_item.overlays += emissive_blocker(l_hand_item.icon, l_hand_item.icon_state, src, alpha = l_hand_item.alpha)
+ hands += l_hand_item
var/obj/item/r_hand = get_item_for_held_index(2)
if(r_hand)
var/itm_state = r_hand.item_state
if(!itm_state)
itm_state = r_hand.icon_state
- hands += mutable_appearance(alt_inhands_file, "[itm_state][caste]_r", -HANDS_LAYER)
+ var/mutable_appearance/r_hand_item = mutable_appearance(alt_inhands_file, "[itm_state][caste]_r", -HANDS_LAYER)
+ if(r_hand.blocks_emissive != EMISSIVE_BLOCK_NONE)
+ r_hand_item.overlays += emissive_blocker(r_hand_item.icon, r_hand_item.icon_state, src, alpha = r_hand_item.alpha)
+ hands += r_hand_item
overlays_standing[HANDS_LAYER] = hands
apply_overlay(HANDS_LAYER)
diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm
index b468e9223cdc..06125b06d8f6 100644
--- a/code/modules/mob/living/carbon/alien/life.dm
+++ b/code/modules/mob/living/carbon/alien/life.dm
@@ -1,5 +1,7 @@
/mob/living/carbon/alien/Life(seconds_per_tick = SSMOBS_DT, times_fired)
findQueen()
+ if(linked_techweb && is_station_level(z)) // 5/s, server passive is ~60/s
+ linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, seconds_per_tick * 5)
return..()
/mob/living/carbon/alien/check_breath(datum/gas_mixture/breath)
diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
index e1d3ef3058f6..7c0ac90b8a17 100644
--- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
+++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm
@@ -4,20 +4,22 @@
name = "alien embryo"
icon = 'icons/mob/alien.dmi'
icon_state = "larva0_dead"
- ///What stage of growth the embryo is at. Developed embryos give the host symptoms suggesting that an embryo is inside them.
- var/stage = 0
- /// Are we bursting out of the poor sucker who's the xeno mom?
+ /// How long it has been growing, increases by up to 3 every 2 seconds based on the state of the host
+ var/growth_progress = 0
+ /// At what point can it burst
+ var/burst_threshold = 270
+ /// what "stage" we are at (used for image handling)
+ var/current_stage = 0
+ /// Are we in the process of bursting out of the poor sucker who's the xeno mom?
var/bursting = FALSE
- /// How long does it take to advance one stage? Growth time * 5 = how long till we make a Larva!
- var/growth_time = 60 SECONDS
/obj/item/organ/body_egg/alien_embryo/Initialize(mapload)
. = ..()
- advance_embryo_stage()
+ RefreshInfectionImage()
/obj/item/organ/body_egg/alien_embryo/on_find(mob/living/finder)
..()
- if(stage < 5)
+ if(current_stage < 5)
to_chat(finder, span_notice("It's small and weak, barely the size of a foetus."))
else
to_chat(finder, "It's grown quite large, and writhes slightly as you look at it.")
@@ -30,7 +32,9 @@
return S
/obj/item/organ/body_egg/alien_embryo/on_life()
- switch(stage)
+ if(owner.buckling && istype(owner.buckling, /obj/structure/bed/nest))
+ growth_progress += 2 //doesn't get the extra progress if the host is either dead or not buckled to an alien bed
+ switch(current_stage)
if(3, 4)
if(prob(2))
owner.emote("sneeze")
@@ -57,17 +61,15 @@
to_chat(owner, span_danger("You feel something tearing its way out of your stomach..."))
owner.adjustToxLoss(10)
-/// Controls Xenomorph Embryo growth. If embryo is fully grown (or overgrown), stop the proc. If not, increase the stage by one and if it's not fully grown (stage 6), add a timer to do this proc again after however long the growth time variable is.
-/obj/item/organ/body_egg/alien_embryo/proc/advance_embryo_stage()
- if(stage >= 6)
- return
- if(++stage < 6)
- INVOKE_ASYNC(src, PROC_REF(RefreshInfectionImage))
- addtimer(CALLBACK(src, PROC_REF(advance_embryo_stage)), growth_time)
+/obj/item/organ/body_egg/alien_embryo/egg_process()
+ growth_progress ++ //continues to progress even if the host is dead
+ var/stage = clamp(round((growth_progress / burst_threshold) * 6), 0, 5)
+ if(stage != current_stage) //if we've gone through a progress threshold, update the icon
+ current_stage = stage
+ RefreshInfectionImage()
-/obj/item/organ/body_egg/alien_embryo/egg_process()
- if(stage == 6 && prob(50))
+ if(growth_progress >= burst_threshold && prob(50))
for(var/datum/surgery/S in owner.surgeries)
if(S.location == BODY_ZONE_CHEST && istype(S.get_surgery_step(), /datum/surgery_step/manipulate_organs))
AttemptGrow(0)
@@ -88,8 +90,10 @@
if(!candidates.len || !owner)
bursting = FALSE
- stage = 5 // If no ghosts sign up for the Larva, let's regress our growth by one minute, we will try again!
- addtimer(CALLBACK(src, PROC_REF(advance_embryo_stage)), growth_time)
+ // If no ghosts sign up for the Larva, let's regress our growth by twenty percent, we will try again!
+ growth_progress = max(growth_progress - (burst_threshold*0.2), 0)
+ current_stage = clamp(round((growth_progress / burst_threshold) * 6), 0, 5)
+ RefreshInfectionImage()
return
var/mob/dead/observer/ghost = pick(candidates)
@@ -135,7 +139,7 @@ Des: Adds the infection image to all aliens for this embryo
/obj/item/organ/body_egg/alien_embryo/AddInfectionImages()
for(var/mob/living/carbon/alien/alien in GLOB.player_list)
if(alien.client)
- var/I = image('icons/mob/alien.dmi', loc = owner, icon_state = "infected[stage]")
+ var/I = image('icons/mob/alien.dmi', loc = owner, icon_state = "infected[current_stage]")
alien.client.images += I
/*----------------------------------------
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index d04f8899ad5e..6fe67f54b776 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -400,7 +400,7 @@
if(!QDELETED(cuff)) //if it didn't delete on drop, update planes
cuff.layer = initial(cuff.layer)
- cuff.plane = initial(cuff.plane)
+ SET_PLANE_IMPLICIT(cuff, initial(cuff.plane))
changeNext_move(0)
@@ -558,78 +558,69 @@
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
else if(is_secret_level(z))
- sight = initial(sight)
+ set_sight(initial(sight))
else
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = 8
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
return
- sight = initial(sight)
- see_infrared = initial(see_infrared)
- lighting_alpha = initial(lighting_alpha)
- var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES)
- if(!E)
- update_tint()
- else
- see_invisible = E.see_invisible
- see_in_dark = E.see_in_dark
- sight |= E.sight_flags
- if(!isnull(E.lighting_alpha))
- lighting_alpha = E.lighting_alpha
-
- for(var/image/I in infra_images)
- if(client)
- client.images.Remove(I)
- infra_images = list()
- remove_client_colour(/datum/client_colour/monochrome_infra)
-
- if(client.eye != src)
+ var/new_sight = initial(sight)
+ lighting_cutoff = initial(lighting_cutoff)
+ lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue)
+
+ var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES)
+ if(eyes)
+ set_invis_see(eyes.see_invisible)
+ new_sight |= eyes.sight_flags
+ if(!isnull(eyes.lighting_cutoff))
+ lighting_cutoff = eyes.lighting_cutoff
+ if(!isnull(eyes.color_cutoffs))
+ lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, eyes.color_cutoffs)
+ if(istype(eyes, /obj/item/organ/eyes/ethereal) && client) //special view range ethereal eyes
+ client.view_size.resetToDefault(getScreenSize(client.prefs.read_preference(/datum/preference/toggle/widescreen)))
+ client.view_size.addTo("2x2")
+
+
+ if(client.eye && client.eye != src)
var/atom/A = client.eye
if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates.
return
if(glasses)
- var/obj/item/clothing/glasses/G = glasses
- sight |= G.vision_flags
- see_in_dark = max(G.darkness_view, see_in_dark)
- if(G.invis_override)
- see_invisible = G.invis_override
+ new_sight |= glasses.vision_flags
+ if(glasses.invis_override)
+ set_invis_see(glasses.invis_override)
else
- see_invisible = min(G.invis_view, see_invisible)
- if(!isnull(G.lighting_alpha))
- lighting_alpha = min(lighting_alpha, G.lighting_alpha)
-
- if(HAS_TRAIT(src, TRAIT_INFRARED_VISION))
- add_client_colour(/datum/client_colour/monochrome_infra)
- var/image/A = null
- see_infrared = TRUE
- lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
-
- if(client)
- for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
- A = image('icons/mob/simple_human.dmi', H, "fullwhite")
- A.name = "white haze"
- A.override = 1
- infra_images |= A
- client.images |= A
+ set_invis_see(min(glasses.invis_view, see_invisible))
+ if(!isnull(glasses.lighting_cutoff))
+ lighting_cutoff = max(lighting_cutoff, glasses.lighting_cutoff)
+ if(length(glasses.color_cutoffs))
+ lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, glasses.color_cutoffs)
+
+
+ if(HAS_TRAIT(src, TRAIT_TRUE_NIGHT_VISION))
+ lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_HIGH)
+
+ if(HAS_TRAIT(src, TRAIT_MESON_VISION))
+ new_sight |= SEE_TURFS
+ lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_MEDIUM)
if(HAS_TRAIT(src, TRAIT_THERMAL_VISION))
- sight |= (SEE_MOBS)
- lighting_alpha = min(lighting_alpha, LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
+ new_sight |= SEE_MOBS
+ lighting_cutoff = max(lighting_cutoff, LIGHTING_CUTOFF_MEDIUM)
if(HAS_TRAIT(src, TRAIT_XRAY_VISION))
- sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = max(see_in_dark, 8)
+ new_sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
if(see_override)
- see_invisible = see_override
+ set_invis_see(see_override)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ new_sight = NONE
+ set_sight(new_sight)
return ..()
/mob/living/carbon/update_stamina_hud(shown_stamina_amount)
@@ -1269,7 +1260,15 @@
/mob/living/carbon/proc/spray_blood(splatter_direction, splatter_strength = 3)
if(!isturf(loc))
return
- var/obj/effect/decal/cleanable/blood/hitsplatter/our_splatter = new(loc)
+ var/splatter_color = null
+ if(dna?.blood_type)
+ splatter_color = dna.blood_type.color
+ else
+ var/blood_id = get_blood_id()
+ if(blood_id != /datum/reagent/blood)//special blood substance
+ var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
+ splatter_color = R.color
+ var/obj/effect/decal/cleanable/blood/hitsplatter/our_splatter = new(loc, splatter_strength, splatter_color)
our_splatter.add_blood_DNA(return_blood_DNA())
our_splatter.blood_dna_info = get_blood_dna_list()
var/turf/targ = get_ranged_target_turf(src, splatter_direction, splatter_strength)
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 25436281deae..db9b0b1f685c 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -1,7 +1,7 @@
/mob/living/carbon/get_eye_protection()
. = ..()
- if(HAS_TRAIT(src, TRAIT_BLIND))
- return INFINITY //Can't get flashed if you cant see
+ if(is_blind() && !HAS_TRAIT_FROM(src, TRAIT_BLIND, UNCONSCIOUS_TRAIT) && !HAS_TRAIT_FROM(src, TRAIT_BLIND, HYPNOCHAIR_TRAIT)) //dripstation edit
+ return INFINITY //For all my homies that can not see in the world
var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES)
if(!E)
return INFINITY //Can't get flashed without eyes
@@ -36,6 +36,14 @@
/mob/living/carbon/check_projectile_dismemberment(obj/projectile/P, def_zone)
var/obj/item/bodypart/affecting = get_bodypart(def_zone)
+ if(P.damtype == BRUTE) // dripstation edit start
+ var/brute_armor = getarmor(affecting, BULLET) // so here we hardblocking projectile-based dismemberment if the armor protection is 60 and more. On station it`s only bulletproof helmet, that protects head from violent falling
+ if(brute_armor >= BULLET_DISMEMBER_THRESHOLD)
+ return ..()
+ if(P.damtype == BURN)
+ var/burn_armor = getarmor(affecting, LASER)
+ if(burn_armor >= LASER_DISMEMBER_THRESHOLD)
+ return ..() // dripstation edit end
if(affecting && affecting.dismemberable && affecting.get_damage() >= (affecting.max_damage - P.dismemberment))
affecting.dismember(P.damtype)
@@ -129,7 +137,7 @@
body_part.receive_damage(stamina = damage_amount * 0.25, sharpness = SHARP_EDGED)//Non-harmful stuff causes stamina damage when removed
if(!silent && damage_amount)
- emote("scream")
+ INVOKE_ASYNC(src, TYPE_PROC_REF(/mob, emote), "scream")
if(!has_embedded_objects())
clear_alert("embeddedobject")
@@ -470,13 +478,14 @@
M.visible_message(span_notice("[M] shakes [src] trying to get [p_them()] up!"), \
span_notice("You shake [src] trying to get [p_them()] up!"))
- else if(check_zone(M.zone_selected) == BODY_ZONE_L_ARM || check_zone(M.zone_selected) == BODY_ZONE_R_ARM) //Headpats are too extreme, we have to pat shoulders on yogs
- M.visible_message(span_notice("[M] gives [src] a pat on the shoulder to make [p_them()] feel better!"), \
- span_notice("You give [src] a pat on the shoulder to make [p_them()] feel better!"))
+ else if(check_zone(M.zone_selected) == BODY_ZONE_HEAD) //For the f sake, yogs, stop this. dripstation edit
+ M.visible_message(span_notice("[M] gives [src] a headpat to make [p_them()] feel better!"), \
+ span_notice("You give [src] a a headpat to make [p_them()] feel better!")) //dripstation edit
else
M.visible_message(span_notice("[M] hugs [src] to make [p_them()] feel better!"), \
span_notice("You hug [src] to make [p_them()] feel better!"))
+/*
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "hug", /datum/mood_event/hug)
if(HAS_TRAIT(M, TRAIT_FRIENDLY))
var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
@@ -488,6 +497,24 @@
if(isethereal(src) && ismoth(M))
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/lamphug, src)
+*/
+ if(HAS_TRAIT(src, TRAIT_PSYCHOPATHIC) || HAS_TRAIT(src, TRAIT_APATHETIC)) //dripstation edit
+ to_chat(M, span_warning("[src] have no visual reaction to your hug.")) //dripstation edit
+ else
+ if(HAS_TRAIT(src, TRAIT_BADTOUCH)) //dripstation edit
+ to_chat(M, span_warning("[src] looks visibly upset as you hug [p_them()].")) //dripstation edit
+ else
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "hug", /datum/mood_event/hug)
+ if(HAS_TRAIT(M, TRAIT_FRIENDLY))
+ var/datum/component/mood/mood = M.GetComponent(/datum/component/mood)
+ if (mood.sanity >= SANITY_GREAT)
+ new /obj/effect/temp_visual/heart(loc)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/besthug, M)
+ else if (mood.sanity >= SANITY_DISTURBED)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/betterhug, M)
+
+ if(isethereal(src) && ismoth(M))
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/lamphug, src)
for(var/datum/brain_trauma/trauma in M.get_traumas())
trauma.on_hug(M, src)
for(var/datum/brain_trauma/trauma in get_traumas())
@@ -503,9 +530,11 @@
M.adjust_wet_stacks(-averagestacks)
to_chat(src, span_notice("The hug [M] gave you was a little wet..."))
+ SEND_SIGNAL(src, COMSIG_CARBON_HELP_ACT, M) //dripstation edit
+ SEND_SIGNAL(M, COMSIG_CARBON_HELPED, src) //dripstation edit
adjust_status_effects_on_shake_up()
-// adjustStaminaLoss(-10) if you want hugs to recover stamina damage, uncomment this
+ adjustStaminaLoss(-10) //dripstation edit, now shakes and hugs recovers stamina again
set_resting(FALSE)
playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
@@ -550,7 +579,7 @@
if(eyes.damage > 10)
blind_eyes(damage)
- blur_eyes(damage * rand(3, 6))
+ adjust_eye_blur(damage * rand(3, 6))
if(eyes.damage > 20)
if(prob(eyes.damage - 20))
@@ -602,7 +631,7 @@
/mob/living/carbon/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone)
if(damage_type != BRUTE && damage_type != BURN)
return
- damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed
+ damage_amount *= 0.2 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed, dripstation edited for 0.2
if(!def_zone || def_zone == BODY_ZONE_HEAD)
var/obj/item/clothing/hit_clothes
if(wear_mask)
@@ -663,9 +692,9 @@
/obj/item/self_grasp/Destroy()
if(user)
to_chat(user, span_warning("You stop holding onto your[grasped_part ? " [grasped_part.name]" : "self"]."))
- UnregisterSignal(user, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(user, COMSIG_QDELETING)
if(grasped_part)
- UnregisterSignal(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_QDELETING))
grasped_part.grasped_by = null
grasped_part = null
user = null
@@ -685,8 +714,8 @@
grasped_part = grasping_part
grasped_part.grasped_by = src
- RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(qdel_void))
- RegisterSignals(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_PARENT_QDELETING), PROC_REF(qdel_void))
+ RegisterSignal(user, COMSIG_QDELETING, PROC_REF(qdel_void))
+ RegisterSignals(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_QDELETING), PROC_REF(qdel_void))
user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE)
playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 3e46bd2d5f20..39c2d4f18ec9 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -4,6 +4,7 @@
possible_a_intents = list(INTENT_HELP, INTENT_HARM)
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD)
has_limbs = 1
+ blocks_emissive = EMISSIVE_BLOCK_NONE
held_items = list(null, null)
/// List of /obj/item/organ in the mob.
/// They don't go in the contents for some reason I don't want to know.
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 735b2bd1daf1..a792a9639d5d 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -4,6 +4,8 @@
new /obj/effect/temp_visual/gib_animation(loc, "gibbed-h")
if("robotic")
new /obj/effect/temp_visual/gib_animation(loc, "gibbed-r")
+ if("plasma")
+ new /obj/effect/temp_visual/gib_animation(loc, "gibbed-h") //This will have more use in the near future
/mob/living/carbon/human/dust(just_ash, drop_items, force)
if(drop_items)
@@ -29,15 +31,19 @@
if(with_bodyparts)
switch(dna.species.species_gibs)
if("human")
- new /obj/effect/gibspawner/human(get_turf(src), dna, get_static_viruses())
+ new /obj/effect/gibspawner/human(get_turf(src), src, get_static_viruses())
if("robotic")
new /obj/effect/gibspawner/robot(get_turf(src))
+ if("plasma")
+ new /obj/effect/gibspawner/human(get_turf(src), src, get_static_viruses())
else
switch(dna.species.species_gibs)
if("human")
- new /obj/effect/gibspawner/human(get_turf(src), dna, get_static_viruses())
+ new /obj/effect/gibspawner/human(get_turf(src), src, get_static_viruses())
if("robotic")
new /obj/effect/gibspawner/robot(get_turf(src))
+ if("plasma")
+ new /obj/effect/gibspawner/human(get_turf(src), src, get_static_viruses())
/mob/living/carbon/human/spawn_dust(just_ash = FALSE)
if(just_ash)
@@ -48,6 +54,9 @@
new /obj/effect/decal/remains/human(loc)
if("robotic")
new /obj/effect/decal/remains/robot(loc)
+ if("plasma")
+ new /obj/effect/decal/remains/plasma(loc)
+
/mob/living/carbon/human/death(gibbed)
if(stat == DEAD)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 55f80107e70a..adb3fd1e5883 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -408,7 +408,7 @@
if(digitalcamo)
msg += "[t_He] [t_is] moving [t_his] body in an unnatural and blatantly inhuman manner.\n"
- SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
+ SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE, user, .)
var/scar_severity = 0
for(var/i in all_scars)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index c5508355451b..be2381ed21bd 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -11,9 +11,12 @@
icon_state = "" //Remove the inherent human icon that is visible on the map editor. We're rendering ourselves limb by limb, having it still be there results in a bug where the basic human icon appears below as south in all directions and generally looks nasty.
+ physiology = new() //create physiology early so organs and bodyparts can modify it
+
//initialize limbs first
create_bodyparts()
+
setup_human_dna()
if(!(CONFIG_GET(flag/disable_human_mood)))
@@ -26,13 +29,13 @@
//initialise organs
create_internal_organs() //most of it is done in set_species now, this is only for parent call
- physiology = new()
. = ..()
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(clean_face))
AddComponent(/datum/component/personal_crafting)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HUMAN, 1, -6)
+ AddComponent(/datum/component/bloodysoles/feet)
/mob/living/carbon/human/proc/setup_human_dna()
//initialize dna. for spawned humans; overwritten by other code
@@ -55,6 +58,11 @@
//...and display them.
add_to_all_human_data_huds()
+/mob/living/carbon/human/reset_perspective(atom/new_eye, force_reset = FALSE)
+ if(dna?.species?.prevent_perspective_change && !force_reset) // This is in case a species needs to prevent perspective changes in certain cases, like Dullahans preventing perspective changes when they're looking through their head.
+ update_fullscreen()
+ return
+ return ..()
/mob/living/carbon/human/get_status_tab_items()
. = ..()
@@ -1022,7 +1030,7 @@
/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target)
var/carrydelay = 50 //if you have latex you are faster at grabbing
- var/skills_space = "" // Changes depending on glove type
+ var/skills_space = null // Changes depending on glove type
if(HAS_TRAIT(src, TRAIT_QUICKEST_CARRY))
carrydelay = 25
skills_space = "masterfully"
@@ -1035,7 +1043,7 @@
if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE))
visible_message(span_notice("[src] starts [skills_space] lifting [target] onto their back.."),
//Joe Medic starts quickly/expertly lifting Grey Tider onto their back..
- span_notice("[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space] start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]"))
+ span_notice("[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space ? "[skills_space] " : ""]start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]"))
//(Using your gloves' nanochips, you/You) ( /quickly/expertly) start to lift Grey Tider onto your back(, while assisted by the nanochips in your gloves../...)
if(do_after(src, carrydelay, target))
//Second check to make sure they're still valid to be carried
@@ -1075,7 +1083,7 @@
target.visible_message(span_warning("[target] really can't seem to mount [src]..."))
return
buckle_lying = lying_buckle
- var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human)
+ var/datum/component/riding/human/riding_datum = AddComponent(/datum/component/riding/human)
if(target_hands_needed)
riding_datum.ride_check_rider_restrained = TRUE
if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled)
@@ -1182,9 +1190,9 @@
/mob/living/carbon/human/species
var/race = null
-/mob/living/carbon/human/species/Initialize(mapload)
- . = ..()
- set_species(race)
+/mob/living/carbon/human/species/create_dna()
+ dna = new /datum/dna(src)
+ dna.species = new race()
/mob/living/carbon/human/species/abductor
race = /datum/species/abductor
@@ -1192,9 +1200,6 @@
/mob/living/carbon/human/species/android
race = /datum/species/android
-/mob/living/carbon/human/species/corporate
- race = /datum/species/corporate
-
/mob/living/carbon/human/species/dullahan
race = /datum/species/dullahan
@@ -1391,3 +1396,6 @@
/mob/living/carbon/human/species/zombie/krokodil_addict
race = /datum/species/krokodil_addict
+
+/mob/living/carbon/human/species/zombie/preternis
+ race = /datum/species/preternis/zombie
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 72524b8c425c..fe4b57915877 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -78,6 +78,8 @@
return BULLET_ACT_BLOCK
else
P.firer = src
+ if(P.hitscan)
+ P.store_hitscan_collision(P.trajectory.copy_to())
P.setAngle(rand(0, 360))//SHING
return BULLET_ACT_FORCE_PIERCE
@@ -86,6 +88,8 @@
if(check_reflect(def_zone)) // Checks if you've passed a reflection% check
visible_message(span_danger("The [P.name] gets reflected by [src]!"), \
span_userdanger("The [P.name] gets reflected by [src]!"))
+ if(P.hitscan) // hitscan check
+ P.store_hitscan_collision(P.trajectory.copy_to())
// Find a turf near or on the original location to bounce to
if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit
P.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage.
@@ -125,8 +129,11 @@
return 0
/mob/living/carbon/human/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0, damage_type = BRUTE)
+ //YOGS EDIT BEGIN
+ if(SEND_SIGNAL(src, COMSIG_MOB_CHECK_SHIELDS, AM, damage, attack_text, attack_type, armour_penetration))
+ return TRUE
+ //YOGS EDIT END
var/block_chance_modifier = round(damage / -3)
-
for(var/obj/item/I in held_items)
if(!istype(I, /obj/item/clothing))
var/final_block_chance = I.block_chance - (clamp((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
@@ -198,8 +205,6 @@
if(I.force && I.damtype != STAMINA && affecting.status == BODYPART_ROBOTIC) // Bodpart_robotic sparks when hit, but only when it does real damage
if(I.force >= 5)
do_sparks(1, FALSE, loc)
- if(prob(25))
- new /obj/effect/decal/cleanable/oil(loc)
SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting)
@@ -475,8 +480,52 @@
adjustEarDamage(15,60)
Knockdown(max(120 - (bomb_armor * 2),0)) //60 bomb armor prevents knockdown entirely
- take_overall_damage(brute_loss,burn_loss)
+ if (EXPLODE_NONE) //dripstation edit
+ Knockdown(max(60 - bomb_armor,0)) //short knock, 60 bomb armor prevents knockdown entirely, dripstation edit
+ take_overall_damage(brute_loss,burn_loss)
+//dripstation edit start, tg-like bomb defence, more violent
+ var/max_limb_loss = 0
+ var/probability = 0
+ var/violent = FALSE
+ switch(severity)
+ if(EXPLODE_NONE)
+ max_limb_loss = 1
+ probability = 20
+ if(EXPLODE_LIGHT)
+ max_limb_loss = 2
+ probability = 30
+ if(EXPLODE_HEAVY)
+ max_limb_loss = 3
+ probability = 40
+ if(EXPLODE_DEVASTATE)
+ max_limb_loss = 4
+ probability = 50
+ violent = TRUE
+ if(HAS_TRAIT(src, TRAIT_EASYDISMEMBER))
+ max_limb_loss += 1
+ probability += 20
+ for(var/X in bodyparts)
+ var/obj/item/bodypart/BP = X
+ if(probability <= 0)
+ break
+ if(prob(probability) && BP.body_zone != BODY_ZONE_HEAD)
+ if(BP.body_zone == BODY_ZONE_CHEST && !violent)
+ continue
+ var/bomb_part_armor = getarmor(BP, BOMB)
+ var/fracture_probability = 70 - probability + round(bomb_part_armor/1.5, 10) //EXPLODE_LIGHT = 40+(armor/1.5)% chance, EXPLODE_HEAVY = 30+(armor/1.5)%
+ if(severity == EXPLODE_NONE || fracture_probability >= 100 || prob(fracture_probability))
+ BP.brute_dam += 6*(2 - round(bomb_part_armor/60, 0.05)) //2-12 damage total depending on bomb armor
+ var/datum/wound/blunt/critical/fracture = new
+ fracture.apply_wound(BP)
+ else
+ BP.brute_dam = BP.max_damage
+ BP.dismember()
+ probability -= 10
+ max_limb_loss--
+ if(!max_limb_loss)
+ break //dripstation edit end
+/*
//attempt to dismember bodyparts
if(severity <= 2 || HAS_TRAIT(src, TRAIT_EASYDISMEMBER)) //light explosions only can dismember those with easy dismember
var/max_limb_loss = round(4/severity) //so you don't lose more than 2 limbs on severity 2
@@ -488,6 +537,7 @@
max_limb_loss--
if(!max_limb_loss)
break
+*/
/mob/living/carbon/human/blob_act(obj/structure/blob/B)
@@ -501,6 +551,8 @@
//Added a safety check in case you want to shock a human mob directly through electrocute_act.
/mob/living/carbon/human/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, zone = BODY_ZONE_R_ARM, override = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE, gib = FALSE)
+ if(!override)
+ siemens_coeff *= physiology.siemens_coeff
. = ..()
if(.)
electrocution_animation(40)
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 24508222ca83..167209586732 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -59,32 +59,10 @@
/mob/living/carbon/human/Move(NewLoc, direct)
. = ..()
- if(shoes)
- if(mobility_flags & MOBILITY_STAND)
- if(loc == NewLoc)
- if(!has_gravity(loc))
- return
- var/obj/item/clothing/shoes/S = shoes
-
- //Bloody footprints
- var/turf/T = get_turf(src)
- if(istype(S) && S.bloody_shoes && S.bloody_shoes[S.blood_state])
- for(var/obj/effect/decal/cleanable/blood/footprints/oldFP in T)
- if (oldFP.blood_state == S.blood_state)
- return
- //No oldFP or they're all a different kind of blood
- S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state] - BLOOD_LOSS_PER_STEP)
- if (S.bloody_shoes[S.blood_state] > BLOOD_LOSS_IN_SPREAD)
- var/obj/effect/decal/cleanable/blood/footprints/FP = new /obj/effect/decal/cleanable/blood/footprints(T)
- FP.blood_state = S.blood_state
- FP.entered_dirs |= dir
- FP.bloodiness = S.bloody_shoes[S.blood_state] - BLOOD_LOSS_IN_SPREAD
- FP.add_blood_DNA(S.return_blood_DNA())
- FP.update_appearance(UPDATE_ICON)
- update_inv_shoes()
- //End bloody footprints
- if(istype(S))
- S.step_action()
+ if(shoes && (mobility_flags & MOBILITY_STAND) && loc == NewLoc && has_gravity(loc) && istype(shoes, /obj/item/clothing/shoes))
+ var/obj/item/clothing/shoes/S = shoes
+ S.step_action()
+
if(wear_neck)
if(mobility_flags & MOBILITY_STAND)
if(loc == NewLoc)
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index f87e39120139..517e9b1fef8b 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -132,7 +132,7 @@
if(G.vision_correction)
clear_fullscreen("nearsighted")
clear_fullscreen("eye_damage")
- if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha))
+ if(G.vision_flags || G.invis_override || G.invis_view || !isnull(G.lighting_cutoff))
update_sight()
update_inv_glasses()
if(ITEM_SLOT_GLOVES)
@@ -169,6 +169,10 @@
if(!not_handled)
I.equipped(src, slot, initial)
+ // Send a signal for when we equip an item that used to cover our feet/shoes. Used for bloody feet
+ if((I.body_parts_covered & FEET) || (I.flags_inv | I.transparent_protection) & HIDESHOES)
+ SEND_SIGNAL(src, COMSIG_CARBON_EQUIP_SHOECOVER, I, slot, initial)
+
return not_handled //For future deeper overrides
/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE)
@@ -217,7 +221,7 @@
if(G.vision_correction)
if(HAS_TRAIT(src, TRAIT_NEARSIGHT))
overlay_fullscreen("nearsighted", /atom/movable/screen/fullscreen/impaired, 1)
- if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha))
+ if(G.vision_flags || G.invis_override || G.invis_view || !isnull(G.lighting_cutoff))
update_sight()
if(!QDELETED(src))
update_inv_glasses()
@@ -251,6 +255,10 @@
if(!QDELETED(src))
update_inv_s_store()
+ // Send a signal for when we unequip an item that used to cover our feet/shoes. Used for bloody feet
+ if((I.body_parts_covered & FEET) || (I.flags_inv | I.transparent_protection) & HIDESHOES)
+ SEND_SIGNAL(src, COMSIG_CARBON_UNEQUIP_SHOECOVER, I, force, newloc, no_move, invdrop, silent)
+
/mob/living/carbon/human/toggle_internals(obj/item/tank, is_external = FALSE)
// Just close the tank if it's the one the mob already has open.
var/obj/item/existing_tank = is_external ? external : internal
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 83ce26f2f419..b7789f20d03c 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -98,7 +98,7 @@
else
adjust_blindness(-1)
if(eye_blurry) //blurry eyes heal slowly
- adjust_blurriness(-1)
+ adjust_eye_blur(-1)
if (getOrganLoss(ORGAN_SLOT_BRAIN) >= 60)
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "brain_damage", /datum/mood_event/brain_damage)
@@ -158,7 +158,7 @@
var/thermal_protection = 0 //Simple check to estimate how protected we are against multiple temperatures
if(wear_suit)
if(wear_suit.max_heat_protection_temperature >= FIRE_SUIT_MAX_TEMP_PROTECT)
- thermal_protection += (wear_suit.max_heat_protection_temperature*0.7)
+ thermal_protection += (wear_suit.max_heat_protection_temperature*(1-THERMAL_PROTECTION_HEAD))
if(head)
if(head.max_heat_protection_temperature >= FIRE_HELM_MAX_TEMP_PROTECT)
thermal_protection += (head.max_heat_protection_temperature*THERMAL_PROTECTION_HEAD)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index f10afc709572..be7e3397cb18 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -162,6 +162,9 @@ GLOBAL_LIST_EMPTY(features_by_species)
///what type of gas is breathed
var/breathid = "o2"
+ /// Special typing indicators
+ var/bubble_icon = BUBBLE_DEFAULT
+
/// The icon_state of the fire overlay added when sufficently ablaze and standing. see onfire.dmi
var/fire_overlay = "human" //not used until monkey is added as a species type rather than a mob
@@ -204,6 +207,12 @@ GLOBAL_LIST_EMPTY(features_by_species)
//Should we preload this species's organs?
var/preload = TRUE
+ ///Does our species have colors for its' damage overlays?
+ var/use_damage_color = TRUE
+
+ /// Do we try to prevent reset_perspective() from working? Useful for Dullahans to stop perspective changes when they're looking through their head.
+ var/prevent_perspective_change = FALSE
+
///////////
// PROCS //
///////////
@@ -431,11 +440,12 @@ GLOBAL_LIST_EMPTY(features_by_species)
C.Digitigrade_Leg_Swap(FALSE)
C.mob_biotypes = inherent_biotypes
+ C.bubble_icon = bubble_icon
regenerate_organs(C,old_species)
if(exotic_bloodtype && C.dna.blood_type != exotic_bloodtype)
- C.dna.blood_type = exotic_bloodtype
+ C.dna.blood_type = get_blood_type(exotic_bloodtype)
if(old_species.mutanthands)
for(var/obj/item/I in C.held_items)
@@ -898,6 +908,14 @@ GLOBAL_LIST_EMPTY(features_by_species)
if((H.wear_mask && (H.wear_mask.flags_inv & HIDEEYES)) || (H.head && (H.head.flags_inv & HIDEEYES)) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "ethereal_mark"
+ if("preternis_antenna" in mutant_bodyparts)
+ if(H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD)
+ bodyparts_to_add -= "preternis_antenna"
+
+ if("preternis_eye" in mutant_bodyparts)
+ if((H.wear_mask && (H.wear_mask.flags_inv & HIDEEYES)) || (H.head && (H.head.flags_inv & HIDEEYES)) || !HD)
+ bodyparts_to_add -= "preternis_eye"
+
if("pod_hair" in mutant_bodyparts)
if((H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || (H.head && (H.head.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
bodyparts_to_add -= "pod_hair"
@@ -1006,6 +1024,14 @@ GLOBAL_LIST_EMPTY(features_by_species)
S = GLOB.dorsal_tubes_list[H.dna.features["dorsal_tubes"]]
if("ethereal_mark")
S = GLOB.ethereal_mark_list[H.dna.features["ethereal_mark"]]
+ if("preternis_weathering")
+ S = GLOB.preternis_weathering_list[H.dna.features["preternis_weathering"]]
+ if("preternis_antenna")
+ S = GLOB.preternis_antenna_list[H.dna.features["preternis_antenna"]]
+ if("preternis_eye")
+ S = GLOB.preternis_eye_list[H.dna.features["preternis_eye"]]
+ if("preternis_core")
+ S = GLOB.preternis_core_list[H.dna.features["preternis_core"]]
if("ipc_screen")
S = GLOB.ipc_screens_list[H.dna.features["ipc_screen"]]
if("ipc_antenna")
@@ -1858,7 +1884,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
H.visible_message(span_danger("[H] has been knocked senseless!"), \
span_userdanger("[H] has been knocked senseless!"))
H.set_confusion_if_lower(20 SECONDS)
- H.adjust_blurriness(10)
+ H.adjust_eye_blur(10)
if(prob(10))
H.gain_trauma(/datum/brain_trauma/mild/concussion)
else
diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm
index 49ed03017109..e22d4936abc7 100644
--- a/code/modules/mob/living/carbon/human/species_types/IPC.dm
+++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm
@@ -4,6 +4,7 @@
name = "IPC" //inherited from the real species, for health scanners and things
id = "ipc"
say_mod = "states" //inherited from a user's real species
+ bubble_icon = BUBBLE_ROBOT // beep boop
sexes = FALSE
species_traits = list(NOTRANSSTING,NOEYESPRITES,NO_DNA_COPY,NOZOMBIE,MUTCOLORS,NOHUSK,AGENDER,NOBLOOD,NO_UNDERWEAR)
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_NOBREATH,TRAIT_LIMBATTACHMENT,TRAIT_EASYDISMEMBER,TRAIT_NOCRITDAMAGE,TRAIT_GENELESS,TRAIT_MEDICALIGNORE,TRAIT_NOCLONE,TRAIT_TOXIMMUNE,TRAIT_EASILY_WOUNDED,TRAIT_NODEFIB,TRAIT_POWERHUNGRY)
diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm
index f47e803cf76d..f61de46f7d57 100644
--- a/code/modules/mob/living/carbon/human/species_types/android.dm
+++ b/code/modules/mob/living/carbon/human/species_types/android.dm
@@ -2,6 +2,7 @@
name = "Android"
id = "android"
say_mod = "states"
+ bubble_icon = BUBBLE_ROBOT
sexes = FALSE
species_traits = list(NOBLOOD, NOZOMBIE, NOHUSK, NO_DNA_COPY, NOTRANSSTING)
inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_COLDBLOODED,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_NOCLONE,TRAIT_TOXIMMUNE,TRAIT_GENELESS,TRAIT_NOFIRE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_LIMBATTACHMENT,TRAIT_MEDICALIGNORE)
diff --git a/code/modules/mob/living/carbon/human/species_types/corporate.dm b/code/modules/mob/living/carbon/human/species_types/corporate.dm
deleted file mode 100644
index a5f951a3c049..000000000000
--- a/code/modules/mob/living/carbon/human/species_types/corporate.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-/datum/species/corporate
- name = "Corporate Agent"
- id = "agent"
- hair_alpha = 0
- say_mod = "declares"
- speedmod = -2//Fast
- brutemod = 0.7//Tough against firearms
- burnmod = 0.65//Tough against lasers
- coldmod = 0
- heatmod = 0.5//it's a little tough to burn them to death not as hard though.
- punchdamagelow = 20
- punchdamagehigh = 30//they are inhumanly strong
- punchstunthreshold = 25
- attack_verb = "smash"
- attack_sound = 'sound/weapons/resonator_blast.ogg'
- use_skintones = 0
- species_traits = list(NOBLOOD,EYECOLOR)
- inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER)
- sexes = 0
- changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index c368723dba36..b1193b2b63b6 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -8,7 +8,9 @@
mutantlungs = /obj/item/organ/lungs/ethereal
mutantstomach = /obj/item/organ/stomach/cell/ethereal
mutantheart = /obj/item/organ/heart/ethereal
+ mutanteyes = /obj/item/organ/eyes/ethereal
exotic_blood = /datum/reagent/consumable/liquidelectricity //Liquid Electricity. fuck you think of something better gamer
+ exotic_bloodtype = "E" //this isn't used for anything other than bloodsplatter colours
siemens_coeff = 0.5 //They thrive on energy
brutemod = 1.25 //Don't rupture their membranes
burnmod = 0.8 //Bodies are resilient to heat and energy
@@ -72,6 +74,9 @@
var/obj/item/organ/heart/ethereal/ethereal_heart = ethereal.getorganslot(ORGAN_SLOT_HEART)
if(ethereal_heart)
ethereal_heart.ethereal_color = default_color
+ var/obj/item/organ/eyes/ethereal/ethereal_eyes = ethereal.getorganslot(ORGAN_SLOT_EYES)
+ if(ethereal_eyes)
+ ethereal_eyes.ethereal_color = default_color
/datum/species/ethereal/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load)
QDEL_NULL(ethereal_light)
diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm
index deab134b5678..336b28ded8bf 100644
--- a/code/modules/mob/living/carbon/human/species_types/golems.dm
+++ b/code/modules/mob/living/carbon/human/species_types/golems.dm
@@ -1652,3 +1652,29 @@
))
return to_add
+
+/datum/species/golem/tar
+ name = "Tar Golem"
+ id = "tar golem"
+ species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR, NO_DNA_COPY, NOTRANSSTING)
+ inherent_traits = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER,TRAIT_NOGUNS)
+ inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID)
+ speedmod = 1.5 // Slightly faster
+ armor = 25
+ punchstunthreshold = 13
+ fixed_mut_color = "48002b"
+ info_text = "As a Tar Golem, you burn very very easily and can temporarily turn yourself into a pool of tar, in this form you are invulnerable to all attacks."
+ random_eligible = FALSE //If false, the golem subtype can't be made through golem mutation toxin
+ prefix = "Tar"
+ special_names = list("Tar'ath", "Tar'eth", "Tar'kian", "Eth'ar", "Rum'tir")
+ var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/tar_pool/TP
+
+/datum/species/golem/tar/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
+ . = ..()
+ TP = new
+ TP.Grant(C)
+
+
+/datum/species/golem/tar/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load)
+ . = ..()
+ TP?.Remove(C)
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index ec31ef5643ae..f1cfac0ca393 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -5,6 +5,7 @@
id = "jelly"
default_color = "00FF90"
say_mod = "chirps"
+ bubble_icon = BUBBLE_SLIME
species_traits = list(MUTCOLORS, EYECOLOR, NOBLOOD, HAIR)
inherent_traits = list(TRAIT_TOXINLOVER)
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index 3002695e59be..b22479775cc2 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -20,6 +20,7 @@
heatmod = 1.5 //Don't let the plasma actually heat up though
punchdamagehigh = 7 //Bone punches are weak and usually inside soft suit gloves
punchstunthreshold = 7 //Stuns on max hit as usual, somewhat higher stun chance because math
+ species_gibs = "plasma"
breathid = "tox"
damage_overlay_type = ""//let's not show bloody wounds or burns over bones.
disliked_food = NONE
diff --git a/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm b/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm
index c95dfc9d1115..d6f44556a682 100644
--- a/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm
+++ b/code/modules/mob/living/carbon/human/species_types/polysmorphs.dm
@@ -6,15 +6,18 @@
inherent_traits = list(TRAIT_ACIDBLOOD, TRAIT_SKINNY)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID
exotic_blood = /datum/reagent/toxin/acid //Hell yeah sulphuric acid blood
+ exotic_bloodtype = "X" //this isn't used for anything other than bloodsplatter colours
meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno
liked_food = GROSS | MEAT | MICE
disliked_food = GRAIN | DAIRY | VEGETABLES | FRUIT
say_mod = "hisses"
+ bubble_icon = BUBBLE_ALIEN
species_language_holder = /datum/language_holder/polysmorph
brutemod = 0.9 //exoskeleton protects against brute
burnmod = 1.35 //residual plasma inside them, highly flammable
coldmod = 0.75
heatmod = 1.5
+ pressuremod = 0.75 //Xenos are completely pressure immune, they're bargain bin xenos
acidmod = 0.2 //Their blood is literally acid
action_speed_coefficient = 1.1 //claws aren't dextrous like hands
speedmod = -0.1 //apex predator humanoid hybrid
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index c4b8bd0be8d4..4aca5549e3d8 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -13,7 +13,7 @@
inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH,TRAIT_GENELESS,TRAIT_NOHUNGER)
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC
- mutanteyes = /obj/item/organ/eyes/night_vision
+ mutanteyes = /obj/item/organ/eyes/shadow
/datum/species/shadow/spec_life(mob/living/carbon/human/H)
@@ -89,7 +89,7 @@
no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE)
species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYESPRITES,NOFLASH)
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER)
- mutanteyes = /obj/item/organ/eyes/night_vision/nightmare
+ mutanteyes = /obj/item/organ/eyes/shadow
mutant_organs = list(/obj/item/organ/heart/nightmare)
mutantbrain = /obj/item/organ/brain/nightmare
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index 41ed79f0ff96..4f3a9832fafb 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -51,7 +51,7 @@
mutanthands = /obj/item/zombie_hand
armor = 20 // 120 damage to KO a zombie, which kills it
speedmod = 1.6
- mutanteyes = /obj/item/organ/eyes/night_vision/zombie
+ mutanteyes = /obj/item/organ/eyes/zombie
var/heal_rate = 1
var/regen_cooldown = 0
changesource_flags = MIRROR_BADMIN | WABBAJACK | ERT_SPAWN
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 15c1da734fbe..88ae7a195608 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -182,6 +182,7 @@ There are several things that need to be remembered:
bloody_overlay.icon_state = "bloodyhands_left"
else if(has_right_hand(FALSE))
bloody_overlay.icon_state = "bloodyhands_right"
+ bloody_overlay.color = get_blood_dna_color(return_blood_DNA())
overlays_standing[GLOVES_LAYER] = bloody_overlay
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index f56a8d5d5274..8e58ff3cf1a6 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -70,8 +70,7 @@
if(observe.client)
observe.client.screen -= I
I.forceMove(src)
- I.layer = ABOVE_HUD_LAYER
- I.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(I, ABOVE_HUD_PLANE, src)
I.appearance_flags |= NO_CLIENT_COLOR
var/not_handled = FALSE
switch(slot)
diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm
index ede5b0e27261..209f5de448e7 100644
--- a/code/modules/mob/living/carbon/monkey/monkey.dm
+++ b/code/modules/mob/living/carbon/monkey/monkey.dm
@@ -45,6 +45,7 @@
create_dna(src)
dna.initialize_dna(random_blood_type())
+ AddComponent(/datum/component/bloodysoles/feet)
/mob/living/carbon/monkey/Destroy()
SSmobs.cubemonkeys -= src
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 426bc2cc3c6c..068a9180e93d 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -48,6 +48,7 @@
/mob/living/carbon/update_inv_hands()
+ . = ..()
remove_overlay(HANDS_LAYER)
if (handcuffed)
drop_all_held_items()
@@ -114,7 +115,10 @@
var/obj/item/bodypart/BP = X
if(BP.dmg_overlay_type)
if(BP.brutestate)
- damage_overlay.add_overlay("[BP.dmg_overlay_type]_[BP.body_zone]_[BP.brutestate]0") //we're adding icon_states of the base image as overlays
+ var/image/brute_overlay = image('icons/mob/dam_mob.dmi', "[BP.dmg_overlay_type]_[BP.body_zone]_[BP.brutestate]0")
+ if(BP.use_damage_color)
+ brute_overlay.color = BP.damage_color
+ damage_overlay.add_overlay(brute_overlay)
if(BP.burnstate)
damage_overlay.add_overlay("[BP.dmg_overlay_type]_[BP.body_zone]_0[BP.burnstate]")
@@ -244,6 +248,184 @@
/mob/living/carbon/update_body()
update_body_parts()
+/mob/living/carbon/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ update_z_overlays(GET_TURF_PLANE_OFFSET(new_turf), TRUE)
+
+/mob/living/carbon/proc/refresh_loop(iter_cnt, rebuild = FALSE)
+ for(var/i in 1 to iter_cnt)
+ update_z_overlays(1, rebuild)
+ sleep(0.3 SECONDS)
+ update_z_overlays(0, rebuild)
+ sleep(0.3 SECONDS)
+
+#define NEXT_PARENT_COMMAND "next_parent"
+/// Takes a list of mutable appearances
+/// Returns a list in the form:
+/// 1 - a list of all mutable appearances that would need to be updated to change planes in the event of a z layer change, alnongside the commands required
+/// to properly track parents to update
+/// 2 - a list of all parents that will require updating
+/proc/build_planeed_apperance_queue(list/mutable_appearance/appearances)
+ var/list/queue
+ if(islist(appearances))
+ queue = appearances.Copy()
+ else
+ queue = list(appearances)
+ var/queue_index = 0
+ var/list/parent_queue = list()
+
+ // We are essentially going to unroll apperance overlays into a flattened list here, so we can filter out floating planes laster
+ // It will look like "overlay overlay overlay (change overlay parent), overlay overlay etc"
+ // We can use this list to dynamically update these non floating planes, later
+ while(queue_index < length(queue))
+ queue_index++
+ // If it's not a command, we assert that it's an appearance
+ var/mutable_appearance/appearance = queue[queue_index]
+ if(!appearance || appearance == NEXT_PARENT_COMMAND) // Who fucking adds nulls to their sublists god you people are the worst
+ continue
+
+ var/mutable_appearance/new_appearance = new /mutable_appearance()
+ new_appearance.appearance = appearance
+ // Now check its children
+ if(length(appearance.overlays))
+ queue += NEXT_PARENT_COMMAND
+ parent_queue += appearance
+ for(var/mutable_appearance/child_appearance as anything in appearance.overlays)
+ queue += child_appearance
+
+ // Now we have a flattened list of parents and their children
+ // Setup such that walking the list backwards will allow us to properly update overlays
+ // (keeping in mind that overlays only update if an apperance is removed and added, and this pattern applies in a nested fashion)
+
+ // If we found no results, return null
+ if(!length(queue))
+ return null
+
+ // ALRIGHT MOTHERFUCKER
+ // SO
+ // DID YOU KNOW THAT OVERLAY RENDERING BEHAVIOR DEPENDS PARTIALLY ON THE ORDER IN WHICH OVERLAYS ARE ADDED?
+ // WHAT WE'RE DOING HERE ENDS UP REVERSING THE OVERLAYS ADDITION ORDER (when it's walked back to front)
+ // SO GUESS WHAT I'VE GOTTA DO, I'VE GOTTA SWAP ALLLL THE MEMBERS OF THE SUBLISTS
+ // I HATE IT HERE
+ var/lower_parent = 0
+ var/upper_parent = 0
+ var/queue_size = length(queue)
+ while(lower_parent <= queue_size)
+ // Let's reorder our "lists" (spaces between parent changes)
+ // We've got a delta index, and we're gonna essentially use it to get "swap" positions from the top and bottom
+ // We only need to loop over half the deltas to swap all the entries, any more and it'd be redundant
+ // We floor so as to avoid over flipping, and ending up flipping "back" a delta
+ // etc etc
+ var/target = FLOOR((upper_parent - lower_parent) / 2, 1)
+ for(var/delta_index in 1 to target)
+ var/old_lower = queue[lower_parent + delta_index]
+ queue[lower_parent + delta_index] = queue[upper_parent - delta_index]
+ queue[upper_parent - delta_index] = old_lower
+
+ // lower bound moves to the old upper, upper bound finds a new home
+ // Note that the end of the list is a valid upper bound
+ lower_parent = upper_parent // our old upper bound is now our lower bound
+ while(upper_parent <= queue_size)
+ upper_parent += 1
+ if(length(queue) < upper_parent) // Parent found
+ break
+ if(queue[upper_parent] == NEXT_PARENT_COMMAND) // We found em lads
+ break
+
+ // One more thing to do
+ // It's much more convinient for the parent queue to be a list of indexes pointing at queue locations
+ // Rather then a list of copied appearances
+ // Let's turn what we have now into that yeah?
+ // This'll require a loop over both queues
+ // We're using an assoc list here rather then several find()s because I feel like that's more sane
+ var/list/apperance_to_position = list()
+ for(var/i in 1 to length(queue))
+ apperance_to_position[queue[i]] = i
+
+ var/list/parent_indexes = list()
+ for(var/mutable_appearance/parent as anything in parent_queue)
+ parent_indexes += apperance_to_position[parent]
+
+ // Alright. We should now have two queues, a command/appearances one, and a parents queue, which contain no fluff
+ // And when walked backwards allow for proper plane updating
+ var/list/return_pack = list(queue, parent_indexes)
+ return return_pack
+
+// Rebuilding is a hack. We should really store a list of indexes into our existing overlay list or SOMETHING
+// IDK. will work for now though, which is a lot better then not working at all
+/mob/living/carbon/proc/update_z_overlays(new_offset, rebuild = FALSE)
+ // Null entries will be filtered here
+ for(var/i in 1 to length(overlays_standing))
+ var/list/cache_grouping = overlays_standing[i]
+ if(cache_grouping && !islist(cache_grouping))
+ cache_grouping = list(cache_grouping)
+ // Need this so we can have an index, could build index into the list if we need to tho, check
+ if(!length(cache_grouping))
+ continue
+ overlays_standing[i] = update_appearance_planes(cache_grouping, new_offset)
+
+/atom/proc/update_appearance_planes(list/mutable_appearance/appearances, new_offset)
+ var/list/build_list = build_planeed_apperance_queue(appearances)
+
+ if(!length(build_list))
+ return appearances
+
+ // hand_back contains a new copy of the passed in list, with updated values
+ var/list/hand_back = list()
+
+ var/list/processing_queue = build_list[1]
+ var/list/parents_queue = build_list[2]
+ // Now that we have our queues, we're going to walk them forwards to remove, and backwards to add
+ // Note, we need to do this separately because you can only remove a mutable appearance when it
+ // Exactly matches the appearance it had when it was first "made static" (by being added to the overlays list)
+ var/parents_index = 0
+ for(var/item in processing_queue)
+ if(item == NEXT_PARENT_COMMAND)
+ parents_index++
+ continue
+ var/mutable_appearance/iter_apper = item
+ if(parents_index)
+ var/parent_src_index = parents_queue[parents_index]
+ var/mutable_appearance/parent = processing_queue[parent_src_index]
+ parent.overlays -= iter_apper.appearance
+ else // Otherwise, we're at the end of the list, and our parent is the mob
+ cut_overlay(iter_apper)
+
+ // Now the back to front stuff, to readd the updated appearances
+ var/queue_index = length(processing_queue)
+ parents_index = length(parents_queue)
+ while(queue_index >= 1)
+ var/item = processing_queue[queue_index]
+ if(item == NEXT_PARENT_COMMAND)
+ parents_index--
+ queue_index--
+ continue
+ var/mutable_appearance/new_iter = new /mutable_appearance()
+ new_iter.appearance = item
+ if(new_iter.plane != FLOAT_PLANE)
+ // Here, finally, is where we actually update the plane offsets
+ SET_PLANE_W_SCALAR(new_iter, PLANE_TO_TRUE(new_iter.plane), new_offset)
+ if(parents_index)
+ var/parent_src_index = parents_queue[parents_index]
+ var/mutable_appearance/parent = processing_queue[parent_src_index]
+ parent.overlays += new_iter.appearance
+ else
+ add_overlay(new_iter)
+ // chant a protective overlays.Copy to prevent appearance theft and overlay sticking
+ // I'm not joking without this overlays can corrupt and be replaced by other appearances
+ // the compiler might call it useless but I swear it works
+ // we conjure the spirits of the computer with our spells, we conjur- (Hey lemon make a damn issue report already)
+ var/list/does_nothing = new_iter.overlays.Copy()
+ pass(does_nothing)
+ hand_back += new_iter
+
+ queue_index--
+ return hand_back
+
+#undef NEXT_PARENT_COMMAND
+
/mob/living/carbon/proc/update_body_parts()
//CHECK FOR UPDATE
var/oldkey = icon_render_key
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index 42afcb604854..7b8cba63935e 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -99,7 +99,7 @@
if(!HAS_TRAIT(src, TRAIT_RADIMMUNE)&& !(status_flags & GODMODE))
radiation += max(effect * hit_percent, 0)
if(EFFECT_EYE_BLUR)
- blur_eyes(effect * hit_percent)
+ adjust_eye_blur(effect * hit_percent)
if(EFFECT_PARALYZE)
Paralyze(effect * hit_percent)
if(EFFECT_IMMOBILIZE)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index ff70ea41ad64..0eb590462dd2 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -71,7 +71,7 @@ GLOBAL_VAR_INIT(permadeath, FALSE)
var/turf/T = get_turf(src)
for(var/obj/item/I in contents)
I.on_mob_death(src, gibbed)
- if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf))
+ if(mind && mind.name && mind.active && !istype(T.loc, /area/centcom/ctf))
deadchat_broadcast(" has died at [get_area_name(T)].", "[mind.name]", follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE)
if(mind)
mind.store_memory("Time of death: [tod]", 0)
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 8baca92c259c..cbfc194eb95c 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -1,8 +1,10 @@
/mob/living/proc/Life(seconds_per_tick = SSMOBS_DT, times_fired)
set waitfor = FALSE
- set invisibility = 0
- SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired)
+ var/signal_result = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds_per_tick, times_fired)
+
+ if(signal_result & COMPONENT_LIVING_CANCEL_LIFE_PROCESSING) // mmm less work
+ return
if(digitalinvis)
handle_diginvis() //AI becomes unable to see mob
@@ -32,9 +34,7 @@
log_game("Z-TRACKING: [src] of type [src.type] has a Z-registration despite not having a client.")
update_z(null)
- if (notransform)
- return
- if(!loc)
+ if(isnull(loc) || notransform)
return
if(SHOULD_LIFETICK(src, times_fired))
@@ -121,9 +121,7 @@
eye_blind = max(eye_blind-1,1)
if(eye_blurry) //blurry eyes heal slowly
eye_blurry = max(eye_blurry-1, 0)
- if(client)
- update_eye_blur()
-
+
/mob/living/proc/update_damage_hud()
return
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index b09e8ed406d9..f6162920ab5b 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -258,6 +258,7 @@
pulling = AM
AM.set_pulledby(src)
+ SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force) //dripstation edit
if(!supress_message)
var/sound_to_play = 'sound/weapons/thudswoosh.ogg'
if(ishuman(src))
@@ -272,6 +273,7 @@
if(ismob(AM))
var/mob/M = AM
+ SEND_SIGNAL(M, COMSIG_LIVING_GET_PULLED, src) //dripstation edit
log_combat(src, M, "grabbed", addition="passive grab")
if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER)))
visible_message(span_warning("[src] has grabbed [M] passively!"))
@@ -609,7 +611,7 @@
set_nutrition(NUTRITION_LEVEL_FED + 50)
bodytemperature = BODYTEMP_NORMAL
set_blindness(0)
- set_blurriness(0)
+ set_eye_blur(0)
cure_nearsighted()
cure_blind()
@@ -669,7 +671,7 @@
/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction)
if(!has_gravity() || !isturf(start) || !blood_volume)
return
- var/blood_exists = locate(/obj/effect/decal/cleanable/trail_holder) in start
+ var/blood_exists = locate(/obj/effect/decal/cleanable/blood/trail_holder) in start
var/trail_type = getTrail()
if(!trail_type)
@@ -691,15 +693,15 @@
if((newdir in GLOB.cardinals) && (prob(50)))
newdir = turn(get_dir(target_turf, start), 180)
if(!blood_exists)
- var/obj/effect/decal/cleanable/trail_holder/TH = new(start, get_static_viruses())
- if(isethereal(src))//ethereal blood glows
- TH.Etherealify()
+ new /obj/effect/decal/cleanable/blood/trail_holder(start, get_static_viruses())
- for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
+ for(var/obj/effect/decal/cleanable/blood/trail_holder/TH in start)
if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
TH.existing_dirs += newdir
TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
TH.transfer_mob_blood_dna(src)
+ if(isethereal(src))//ethereal blood glows
+ TH.Etherealify()
/mob/living/carbon/human/makeTrail(turf/T)
if((NOBLOOD in dna.species.species_traits) || !is_bleeding() || bleedsuppress)
@@ -720,16 +722,8 @@
/mob/living/proc/getTrail()
if(getBruteLoss() < 300)
- if(ispolysmorph(src))
- return pick("xltrails_1", "xltrails_2")
- if(isethereal(src))
- return pick("wltrails_1", "wltrails_2")
return pick("ltrails_1", "ltrails_2")
else
- if(ispolysmorph(src))
- return pick("xttrails_1", "xttrails_2")
- if(isethereal(src))
- return pick("wttrails_1", "wttrails_2")
return pick("trails_1", "trails_2")
/mob/living/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0)
@@ -959,25 +953,26 @@
/mob/living/proc/can_track(mob/living/user)
//basic fast checks go first. When overriding this proc, I recommend calling ..() at the end.
+ if(SEND_SIGNAL(src, COMSIG_LIVING_CAN_TRACK, user) & COMPONENT_CANT_TRACK)
+ return FALSE
var/turf/T = get_turf(src)
if(!T)
- return 0
+ return FALSE
if(is_centcom_level(T.z)) //dont detect mobs on centcom
- return 0
+ return FALSE
if(is_away_level(T.z))
- return 0
- if(user != null && src == user)
- return 0
+ return FALSE
+ if(!isnull(user) && src == user)
+ return FALSE
if(invisibility || alpha == 0)//cloaked
- return 0
+ return FALSE
if(digitalcamo || digitalinvis)
- return 0
-
+ return FALSE
// Now, are they viewable by a camera? (This is last because it's the most intensive check)
if(!near_camera(src))
- return 0
+ return FALSE
- return 1
+ return TRUE
/mob/living/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, vomit_type = VOMIT_TOXIC, harm = TRUE, force = FALSE, purge_ratio = 0.1)
if((HAS_TRAIT(src, TRAIT_NOHUNGER) || HAS_TRAIT(src, TRAIT_POWERHUNGRY) || HAS_TRAIT(src, TRAIT_TOXINLOVER)) && !force)
@@ -1436,16 +1431,15 @@ GLOBAL_LIST_EMPTY(fire_appearances)
return LINGHIVE_NONE
/mob/living/forceMove(atom/destination)
- stop_pulling()
- if(buckled)
- buckled.unbuckle_mob(src, force = TRUE)
- if(has_buckled_mobs())
- unbuckle_all_mobs(force = TRUE)
+ if(!currently_z_moving)
+ stop_pulling()
+ if(buckled)
+ buckled.unbuckle_mob(src, force = TRUE)
+ if(has_buckled_mobs())
+ unbuckle_all_mobs(force = TRUE)
. = ..()
- if(.)
- if(client)
- reset_perspective()
- update_mobility() //if the mob was asleep inside a container and then got forceMoved out we need to make them fall.
+ if(. && client)
+ reset_perspective()
/mob/living/proc/update_z(new_z) // 1+ to register, null to unregister
if (registered_z != new_z)
@@ -1465,9 +1459,9 @@ GLOBAL_LIST_EMPTY(fire_appearances)
else
registered_z = null
-/mob/living/onTransitZ(old_z,new_z)
+/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
- update_z(new_z)
+ update_z(new_turf?.z)
/mob/living/MouseDrop_T(atom/pickable, atom/user)
var/mob/living/U = user
@@ -1514,15 +1508,24 @@ GLOBAL_LIST_EMPTY(fire_appearances)
return result
/mob/living/reset_perspective(atom/A)
- if(..())
- update_sight()
- if(client.eye && client.eye != src)
- var/atom/AT = client.eye
- AT.get_remote_view_fullscreens(src)
- else
- clear_fullscreen("remote_view", 0)
- update_pipe_vision()
- update_wire_vision()
+ if(!..())
+ return
+ update_sight()
+ if(client.eye && client.eye != src)
+ var/atom/AT = client.eye
+ AT.get_remote_view_fullscreens(src)
+ else
+ clear_fullscreen("remote_view", 0)
+ update_pipe_vision()
+ update_wire_vision()
+
+/// Proc used to handle the fullscreen overlay updates, realistically meant for the reset_perspective() proc.
+/mob/living/proc/update_fullscreen()
+ if(client.eye && client.eye != src)
+ var/atom/client_eye = client.eye
+ client_eye.get_remote_view_fullscreens(src)
+ else
+ clear_fullscreen("remote_view", 0)
/mob/living/vv_edit_var(var_name, var_value)
switch(var_name)
@@ -1553,13 +1556,13 @@ GLOBAL_LIST_EMPTY(fire_appearances)
if(E)
E.setOrganDamage(var_value)
if("eye_blurry")
- set_blurriness(var_value)
+ set_eye_blur(var_value)
if("maxHealth")
updatehealth()
if("resize")
update_transform()
- if("lighting_alpha")
- sync_lighting_plane_alpha()
+ if("lighting_cutoff")
+ sync_lighting_plane_cutoff()
/mob/living/vv_get_header()
. = ..()
@@ -1690,3 +1693,15 @@ GLOBAL_LIST_EMPTY(fire_appearances)
// if(!resting)
// get_up()
set_resting(FALSE)
+
+/mob/living/proc/move_to_error_room()
+ var/obj/effect/landmark/error/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list
+ if(error_landmark)
+ forceMove(error_landmark.loc)
+ else
+ forceMove(locate(4,4,1)) //Even if the landmark is missing, this should put them in the error room.
+ //If you're here from seeing this error, I'm sorry. I'm so very sorry. The error landmark should be a sacred object that nobody has any business messing with, and someone did!
+ //Consider seeing a therapist.
+ var/ERROR_ERROR_LANDMARK_ERROR = "ERROR-ERROR: ERROR landmark missing!"
+ log_mapping(ERROR_ERROR_LANDMARK_ERROR)
+ CRASH(ERROR_ERROR_LANDMARK_ERROR)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 161beed32fd4..30b36d0faf48 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -79,7 +79,7 @@
apply_damage(P.damage, P.damage_type, def_zone, armor, wound_bonus = P.wound_bonus, bare_wound_bonus = P.bare_wound_bonus, sharpness = P.sharpness, attack_direction = attack_direction)
if(P.dismemberment)
check_projectile_dismemberment(P, def_zone)
- if(P.penetrating && (P.penetration_type == 0 || P.penetration_type == 2) && P.penetrations > 0)
+ if((P.penetration_flags & PENETRATE_MOBS) && P.penetrations > 0)
P.penetrations -= 1
return P.on_hit(src, armor) && BULLET_ACT_FORCE_PIERCE
return P.on_hit(src, armor)? BULLET_ACT_HIT : BULLET_ACT_BLOCK
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 4288d6291183..5fa0ce4f99df 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -1,7 +1,6 @@
/mob/living
see_invisible = SEE_INVISIBLE_LIVING
sight = 0
- see_in_dark = 2
hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD)
pressure_resistance = 10
infra_luminosity = 10
@@ -77,8 +76,6 @@
var/health_doll_icon //if this exists AND the normal sprite is bigger than 32x32, this is the replacement icon state (because health doll size limitations). the icon will always be screen_gen.dmi
- var/bubble_icon = "default" //what icon the mob uses for speechbubbles
-
var/last_bumped = 0
var/unique_name = 0 //if a mob's name should be appended with an id when created e.g. Mob (666)
@@ -149,3 +146,5 @@
var/num_hands = 2
///How many usable hands does this mob currently have. Should only be changed through set_usable_hands()
var/usable_hands = 2
+ /// What our current gravity state is. Used to avoid duplicate animates and such
+ var/gravity_state = null
diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm
index b54b472b1b2b..081f217b3f9e 100644
--- a/code/modules/mob/living/living_movement.dm
+++ b/code/modules/mob/living/living_movement.dm
@@ -1,4 +1,4 @@
-/mob/living/Moved()
+/mob/living/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
update_turf_movespeed(loc)
if(is_shifted)
@@ -6,6 +6,12 @@
pixel_x = get_standard_pixel_x_offset(lying)
pixel_y = get_standard_pixel_y_offset(lying)
+/mob/living/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+
+ // if(!old_turf || !new_turf || SSmapping.gravity_by_z_level[old_turf.z] != SSmapping.gravity_by_z_level[new_turf.z])
+ // refresh_gravity()
+
/mob/living/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
if((mover.pass_flags & PASSMOB))
@@ -57,8 +63,50 @@
return
remove_movespeed_modifier(MOVESPEED_ID_PRONE_DRAGGING)
-/mob/living/can_zFall(turf/T, levels)
- return !(movement_type & FLYING)
+/**
+ * We want to relay the zmovement to the buckled atom when possible
+ * and only run what we can't have on buckled.zMove() or buckled.can_z_move() here.
+ * This way we can avoid esoteric bugs, copypasta and inconsistencies.
+ */
+/mob/living/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS)
+ if(buckled)
+ if(buckled.currently_z_moving)
+ return FALSE
+ if(!(z_move_flags & ZMOVE_ALLOW_BUCKLED))
+ buckled.unbuckle_mob(src, force = TRUE, can_fall = FALSE)
+ else
+ if(!target)
+ target = can_z_move(dir, get_turf(src), null, z_move_flags, src)
+ if(!target)
+ return FALSE
+ return buckled.zMove(dir, target, z_move_flags) // Return value is a loc.
+ return ..()
+
+/mob/living/can_z_move(direction, turf/start, turf/destination, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider)
+ if(z_move_flags & ZMOVE_INCAPACITATED_CHECKS && incapacitated())
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(rider || src, span_warning("[rider ? src : "You"] can't do that right now!"))
+ return FALSE
+ if(!buckled || !(z_move_flags & ZMOVE_ALLOW_BUCKLED))
+ if(!(z_move_flags & ZMOVE_FALL_CHECKS) && incorporeal_move && (!rider || rider.incorporeal_move))
+ //An incorporeal mob will ignore obstacles unless it's a potential fall (it'd suck hard) or is carrying corporeal mobs.
+ //Coupled with flying/floating, this allows the mob to move up and down freely.
+ //By itself, it only allows the mob to move down.
+ z_move_flags |= ZMOVE_IGNORE_OBSTACLES
+ return ..()
+ switch(SEND_SIGNAL(buckled, COMSIG_BUCKLED_CAN_Z_MOVE, direction, start, destination, z_move_flags, src))
+ if(COMPONENT_RIDDEN_ALLOW_Z_MOVE) // Can be ridden.
+ return buckled.can_z_move(direction, start, destination, z_move_flags, src)
+ if(COMPONENT_RIDDEN_STOP_Z_MOVE) // Is a ridable but can't be ridden right now. Feedback messages already done.
+ return FALSE
+ else
+ if(!(z_move_flags & ZMOVE_CAN_FLY_CHECKS) && !buckled.anchored)
+ return buckled.can_z_move(direction, start, destination, z_move_flags, src)
+ if(z_move_flags & ZMOVE_FEEDBACK)
+ to_chat(src, span_warning("Unbuckle from [buckled] first."))
+ return FALSE
-/mob/living/canZMove(dir, turf/target)
- return can_zTravel(target, dir) && (movement_type & FLYING)
+/mob/set_currently_z_moving(value)
+ if(buckled)
+ return buckled.set_currently_z_moving(value)
+ return ..()
diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm
index 5b4098da9021..c5a94b99a552 100644
--- a/code/modules/mob/living/login.dm
+++ b/code/modules/mob/living/login.dm
@@ -1,11 +1,15 @@
/mob/living/Login()
- ..()
+ . = ..()
+ if(!. || !client)
+ return FALSE
+
//Mind updates
sync_mind()
mind.show_memory(src, 0)
update_damage_hud()
update_health_hud()
+ update_sight()
var/turf/T = get_turf(src)
if (isturf(T))
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index 0aa25a9049cc..e45c50d9189f 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -319,9 +319,18 @@ GLOBAL_LIST_INIT(special_radio_keys, list(
for(var/mob/M in listening)
if(M.client)
speech_bubble_recipients.Add(M.client)
- var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER)
- I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay), I, speech_bubble_recipients, 30)
+ var/image/say_popup = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER)
+ if(a_intent == INTENT_HARM) // ANGRY!!!!
+ var/mutable_appearance/angerlay = mutable_appearance('icons/mob/talk.dmi', "angry")
+ say_popup.add_overlay(angerlay)
+ SET_PLANE_EXPLICIT(say_popup, ABOVE_GAME_PLANE, src)
+ say_popup.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay_global), say_popup, speech_bubble_recipients, 3 SECONDS)
+ LAZYADD(update_on_z, say_popup)
+ addtimer(CALLBACK(src, PROC_REF(clear_saypopup), say_popup), 3.5 SECONDS)
+
+/mob/living/proc/clear_saypopup(image/say_popup)
+ LAZYREMOVE(update_on_z, say_popup)
/mob/proc/binarycheck()
return FALSE
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 5d890506003f..a738f7b9eb9c 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -23,7 +23,6 @@
status_flags = CANSTUN|CANPUSH
a_intent = INTENT_HARM //so we always get pushed instead of trying to swap
sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
- see_in_dark = 8
hud_type = /datum/hud/ai
med_hud = DATA_HUD_MEDICAL_BASIC
sec_hud = DATA_HUD_SECURITY_BASIC
@@ -80,7 +79,7 @@
var/nuking = FALSE
var/obj/machinery/doomsday_device/doomsday_device
- var/mob/camera/aiEye/eyeobj
+ var/mob/camera/ai_eye/eyeobj
//How fast you move your camera
var/sprint = 10
var/cooldown = 0
@@ -112,8 +111,6 @@
//Reduces/Increases download speed by this modifier
var/downloadSpeedModifier = 1
- var/login_warned_temp = FALSE
-
//Do we have access to camera tracking?
var/canCameraMemoryTrack = FALSE
//The person we are tracking
@@ -131,6 +128,7 @@
/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai, shunted)
. = ..()
if(!target_ai) //If there is no player/brain inside.
+ //new/obj/structure/ai_core/deactivated(loc) //New empty terminal.
return INITIALIZE_HINT_QDEL //Delete AI.
if(!istype(loc, /obj/machinery/ai/data_core) && !shunted)
@@ -144,6 +142,8 @@
update_law_history() //yogs
+ create_eye()
+
if(target_ai.mind)
target_ai.mind.transfer_to(src)
if(mind.special_role)
@@ -163,8 +163,8 @@
job = "AI"
+ create_modularInterface()
- create_eye()
if(client)
INVOKE_ASYNC(src, PROC_REF(apply_pref_name), /datum/preference/name/ai, client)
@@ -179,8 +179,6 @@
add_verb(src, /mob/living/silicon/ai/proc/show_laws_verb)
- create_modularInterface()
-
aiMulti = new(src)
radio = new /obj/item/radio/headset/silicon/ai(src)
aicamera = new/obj/item/camera/siliconcam/ai_camera(src)
@@ -193,7 +191,7 @@
add_verb(src, list(/mob/living/silicon/ai/proc/ai_network_change, \
/mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \
/mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \
- /mob/living/silicon/ai/proc/set_automatic_say_channel, /mob/living/silicon/ai/proc/changeaccent))
+ /mob/living/silicon/ai/proc/changeaccent))
GLOB.ai_list += src
GLOB.shuttle_caller_list += src
@@ -685,42 +683,43 @@
set name = "Jump To Network"
unset_machine()
cameraFollow = null
- var/list/network_list = list()
+
+ var/cameralist[0]
if(incapacitated())
return
var/mob/living/silicon/ai/U = usr
- //Yogs -- refactors this fucking decade-old, looney camera code
- if(!U.eyeobj)
- U.view_core()
- return
+ for (var/obj/machinery/camera/C in GLOB.cameranet.cameras)
+ var/turf/camera_turf = get_turf(C) //get camera's turf in case it's built into something so we don't get z=0
- for (var/x in GLOB.cameranet.cameras)
- var/obj/machinery/camera/C = x
- if(!(is_station_level(C.z) || is_mining_level(C.z) || ("ss13" in C.network)))
+ var/list/tempnetwork = C.network
+ if(!camera_turf || !(is_station_level(camera_turf.z) || is_mining_level(camera_turf.z) || ("ss13" in tempnetwork)))
continue
if(!C.can_use())
continue
- var/list/tempnetwork = C.network
tempnetwork.Remove("rd", "toxins", "prison")
- if(tempnetwork.len)
- network_list |= tempnetwork
- if(network_list.len < 2)
- return
+ if(length(tempnetwork))
+ for(var/i in C.network)
+ cameralist[i] = i
+ var/old_network = network
+ network = tgui_input_list(U, "Which network would you like to view?", "Camera Network", sort_list(cameralist))
- var/viewed_network = input(U, "Which network would you like to view?") as null|anything in network_list
- if(!viewed_network)
+ if(!U.eyeobj)
+ U.view_core()
return
- for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
- if(!C.can_use())
- continue
- if(viewed_network in C.network)
- U.eyeobj.setLoc(get_turf(C))
- to_chat(src, span_notice("Switched to a camera in the \"[uppertext(viewed_network)]\" camera network."))
- return
- to_chat(src, span_warning("Failed to find any camera on the \"[uppertext(viewed_network)]\" camera network!")) // This is a bug, if it happens.
+
+ if(isnull(network))
+ network = old_network // If nothing is selected
+ else
+ for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
+ if(!C.can_use())
+ continue
+ if(network in C.network)
+ U.eyeobj.setLoc(get_turf(C))
+ break
+ to_chat(src, span_notice("Switched to the \"[uppertext(network)]\" camera network."))
//End of code by Mord_Sith and others :^)
//yogs end
@@ -864,11 +863,12 @@
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
- for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks)
- for (var/obj/machinery/camera/C in CC.cameras)
- if (!C.can_use() || get_dist(C, eyeobj) > 7 || !C.internal_light)
- continue
- visible |= C
+ for (var/datum/camerachunk/chunk as anything in eyeobj.visibleCameraChunks)
+ for (var/z_key in chunk.cameras)
+ for(var/obj/machinery/camera/camera as anything in chunk.cameras[z_key])
+ if(isnull(camera) || !camera.can_use() || get_dist(camera, eyeobj) > 7 || !camera.internal_light)
+ continue
+ visible |= camera
add = visible - lit_cameras
remove = lit_cameras - visible
@@ -896,15 +896,6 @@
if(radio)
radio.make_syndie()
-/mob/living/silicon/ai/proc/set_automatic_say_channel()
- set name = "Set Auto Announce Mode"
- set desc = "Modify the default radio setting for your automatic announcements."
- set category = "AI Commands"
-
- if(incapacitated())
- return
- set_autosay()
-
/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
if(!..())
return
@@ -997,35 +988,41 @@
malf_picker = new /datum/module_picker
-/mob/living/silicon/ai/reset_perspective(atom/A)
+/mob/living/silicon/ai/reset_perspective(atom/new_eye)
+ SHOULD_CALL_PARENT(FALSE) // I hate you all
if(camera_light_on)
light_cameras()
- if(istype(A, /obj/machinery/camera))
- current = A
- if(client)
- if(ismovable(A))
- if(A != GLOB.ai_camera_room_landmark)
- end_multicam()
- client.perspective = EYE_PERSPECTIVE
- client.eye = A
- else
+ if(istype(new_eye, /obj/machinery/camera))
+ current = new_eye
+ if(!client)
+ return
+
+ if(ismovable(new_eye))
+ if(new_eye != GLOB.ai_camera_room_landmark)
end_multicam()
- if(isturf(loc) || istype(loc, /obj/machinery/ai/data_core))
- if(eyeobj)
- client.eye = eyeobj
- client.perspective = EYE_PERSPECTIVE
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
- else
+ client.perspective = EYE_PERSPECTIVE
+ client.set_eye(new_eye)
+ else
+ end_multicam()
+ if(isturf(loc) || istype(loc, /obj/machinery/ai/data_core))
+ if(eyeobj)
+ client.set_eye(eyeobj)
client.perspective = EYE_PERSPECTIVE
- client.eye = loc
- update_sight()
- if(client.eye != src)
- var/atom/AT = client.eye
- AT.get_remote_view_fullscreens(src)
+ else
+ client.set_eye(client.mob)
+ client.perspective = MOB_PERSPECTIVE
else
- clear_fullscreen("remote_view", 0)
+ client.perspective = EYE_PERSPECTIVE
+ client.set_eye(loc)
+ update_sight()
+ if(client.eye != src)
+ var/atom/AT = client.eye
+ AT?.get_remote_view_fullscreens(src)
+ else
+ clear_fullscreen("remote_view", 0)
+
+ // I am so sorry
+ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0)
. = ..()
@@ -1070,16 +1067,18 @@
for(var/borgie in GLOB.available_ai_shells)
var/mob/living/silicon/robot/R = borgie
- if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai ||(R.connected_ai == src)))
+ if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai || (R.connected_ai == src)))
possible += R
if(!LAZYLEN(possible))
to_chat(src, "No usable AI shell beacons detected.")
if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid
- target = input(src, "Which body to control?") as null|anything in possible
+ target = tgui_input_list(src, "Which body to control?", "Direct Control", sort_names(possible))
- if (!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src)))
+ if(isnull(target))
+ return
+ if (target.stat == DEAD || target.deployed || !(!target.connected_ai || (target.connected_ai == src)))
return
else if(mind)
@@ -1128,11 +1127,11 @@
return
/mob/living/silicon/ai/spawned/Initialize(mapload, datum/ai_laws/L, mob/target_ai)
- . = ..()
if(!target_ai)
target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct
+ . = ..()
-/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye)
+/mob/living/silicon/ai/proc/camera_visibility(mob/camera/ai_eye/moved_eye)
GLOB.cameranet.visibility(moved_eye, client, all_eyes, TRUE)
/mob/living/silicon/ai/forceMove(atom/destination)
@@ -1140,6 +1139,20 @@
if(.)
end_multicam()
+/mob/living/silicon/ai/up()
+ set name = "Move Upwards"
+ set category = "IC"
+
+ if(eyeobj.zMove(UP, z_move_flags = ZMOVE_FEEDBACK))
+ to_chat(src, span_notice("You move upwards."))
+
+/mob/living/silicon/ai/down()
+ set name = "Move Down"
+ set category = "IC"
+
+ if(eyeobj.zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK))
+ to_chat(src, span_notice("You move down."))
+
/mob/living/silicon/ai/proc/send_borg_death_warning(mob/living/silicon/robot/R)
to_chat(src, span_warning("Unit [R] has stopped sending telemetry updates."))
playsound_local(src, 'sound/machines/engine_alert2.ogg', 30)
diff --git a/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm b/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
index 51dea1064c3a..1f030aaf5533 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/_ai_machinery.dm
@@ -14,7 +14,7 @@
/obj/machinery/ai/Initialize(mapload)
. = ..()
START_PROCESSING(SSmachines, src)
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
//Cooling happens here
/obj/machinery/ai/process_atmos()
@@ -31,7 +31,7 @@
/obj/machinery/ai/Destroy()
. = ..()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
STOP_PROCESSING(SSmachines, src)
/obj/machinery/ai/proc/valid_holder()
diff --git a/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
index cd77f9b70ad8..7cbdad7bc280 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/ai_data_core.dm
@@ -34,15 +34,17 @@ GLOBAL_VAR_INIT(primary_data_core, null)
var/obj/item/stock_parts/cell/integrated_battery
-
/obj/machinery/ai/data_core/Initialize(mapload)
. = ..()
GLOB.data_cores += src
if(primary && !GLOB.primary_data_core)
GLOB.primary_data_core = src
- update_appearance(UPDATE_ICON)
+ update_appearance()
RefreshParts()
+/obj/machinery/ai/data_core/JoinPlayerHere(mob/M, buckle)
+ return
+
/obj/machinery/ai/data_core/RefreshParts()
var/new_heat_mod = 1
var/new_power_mod = 1
@@ -247,7 +249,7 @@ GLOBAL_VAR_INIT(primary_data_core, null)
/obj/machinery/ai/data_core/proc/transfer_AI(mob/living/silicon/ai/AI)
AI.forceMove(src)
if(AI.eyeobj)
- AI.eyeobj.forceMove(get_turf(src))
+ AI.eyeobj.setLoc(get_turf(src))
/obj/machinery/ai/data_core/update_icon_state()
. = ..()
@@ -285,6 +287,7 @@ That prevents a few funky behaviors.
/atom/proc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
+ SHOULD_CALL_PARENT(TRUE)
if(istype(card))
if(card.flush)
to_chat(user, "[span_boldannounce("ERROR")]: AI flush is in progress, cannot execute transfer protocol.")
diff --git a/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm b/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm
index d19eba133c67..9ff8d1a66c91 100644
--- a/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized/management/ai_controlpanel.dm
@@ -456,7 +456,7 @@ GLOBAL_VAR_INIT(ai_control_code, random_nukecode(6))
return
var/mob/living/silicon/ai/A = target
if(A.mind && is_servant_of_ratvar(A))
- to_chat(user, span_brass("[A] has already seen the light of the Justicar!"))
+ to_chat(user, span_brass("[A] has already seen the light of the Justiciar!"))
return
if(A.stat == DEAD)
to_chat(user, span_warning("[A] is dead!"))
diff --git a/code/modules/mob/living/silicon/ai/decentralized_ai.dm b/code/modules/mob/living/silicon/ai/decentralized_ai.dm
index 7cfcf23c5f2f..008903099c1e 100644
--- a/code/modules/mob/living/silicon/ai/decentralized_ai.dm
+++ b/code/modules/mob/living/silicon/ai/decentralized_ai.dm
@@ -47,7 +47,7 @@
if(!silent)
to_chat(src, span_danger("Alternative data core detected. Rerouting connection..."))
new_data_core.transfer_AI(src)
-
+
/mob/living/silicon/ai/proc/death_prompt()
to_chat(src, span_userdanger("Unable to re-establish connection to data core. System shutting down..."))
diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
index efd4184eee1f..a9ad9884045d 100644
--- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm
@@ -2,8 +2,6 @@
//
// The datum containing all the chunks.
-#define CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc..
-
GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
/datum/cameranet
@@ -11,38 +9,54 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
var/name = "Camera Net"
/// The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Del().
- var/list/cameras = list()
+ var/list/obj/machinery/camera/cameras = list()
/// The chunks of the map, mapping the areas that the cameras can see.
var/list/chunks = list()
var/ready = 0
- // The object used for the clickable stat() button.
- var/obj/effect/statclick/statclick
- ///The image given to the effect in vis_contents on AI clients
- var/image/obscured
+
+ /// List of images cloned by all chunk static images put onto turfs cameras cant see
+ /// Indexed by the plane offset to use
+ var/list/image/obscured_images
/datum/cameranet/New()
- obscured = new('icons/effects/cameravis.dmi')
- obscured.plane = CAMERA_STATIC_PLANE
- obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
- obscured.override = TRUE
+ obscured_images = list()
+ update_offsets(SSmapping.max_plane_offset)
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(on_offset_growth))
+
+/datum/cameranet/proc/update_offsets(new_offset)
+ for(var/i in length(obscured_images) to new_offset)
+ var/image/obscured = new('icons/effects/cameravis.dmi')
+ SET_PLANE_W_SCALAR(obscured, CAMERA_STATIC_PLANE, i)
+ obscured.appearance_flags = RESET_TRANSFORM | RESET_ALPHA | RESET_COLOR | KEEP_APART
+ obscured.override = TRUE
+ obscured_images += obscured
+
+/datum/cameranet/proc/on_offset_growth(datum/source, old_offset, new_offset)
+ SIGNAL_HANDLER
+ update_offsets(new_offset)
/// Checks if a chunk has been Generated in x, y, z.
/datum/cameranet/proc/chunkGenerated(x, y, z)
- x &= ~(CHUNK_SIZE - 1)
- y &= ~(CHUNK_SIZE - 1)
+ x = GET_CHUNK_COORD(x)
+ y = GET_CHUNK_COORD(y)
+ if(GET_LOWEST_STACK_OFFSET(z) != 0)
+ var/turf/lowest = get_lowest_turf(locate(x, y, z))
+ return chunks["[x],[y],[lowest.z]"]
+
return chunks["[x],[y],[z]"]
// Returns the chunk in the x, y, z.
// If there is no chunk, it creates a new chunk and returns that.
/datum/cameranet/proc/getCameraChunk(x, y, z)
- x &= ~(CHUNK_SIZE - 1)
- y &= ~(CHUNK_SIZE - 1)
- var/key = "[x],[y],[z]"
+ x = GET_CHUNK_COORD(x)
+ y = GET_CHUNK_COORD(y)
+ var/turf/lowest = get_lowest_turf(locate(x, y, z))
+ var/key = "[x],[y],[lowest.z]"
. = chunks[key]
if(!.)
- chunks[key] = . = new /datum/camerachunk(x, y, z)
+ chunks[key] = . = new /datum/camerachunk(x, y, lowest.z)
-/// Updates what the ai_eye can see. It is recommended you use this when the ai_eye moves or it's location is set.
+/// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set.
/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = TRUE)
if(!islist(moved_eyes))
moved_eyes = moved_eyes ? list(moved_eyes) : list()
@@ -51,23 +65,20 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
else
other_eyes = list()
- for(var/mob/camera/aiEye/eye as anything in moved_eyes)
+ for(var/mob/camera/ai_eye/eye as anything in moved_eyes)
var/list/visibleChunks = list()
+ //Get the eye's turf in case it's located in an object like a mecha
+ var/turf/eye_turf = get_turf(eye)
if(eye.loc)
- var/x_value = eye.x
- var/y_value = eye.y
- var/z_value = eye.z
- // 0xf = 15
var/static_range = eye.static_visibility_range
- var/x1 = max(0, x_value - static_range) & ~(CHUNK_SIZE - 1)
- var/y1 = max(0, y_value - static_range) & ~(CHUNK_SIZE - 1)
- var/x2 = min(world.maxx, x_value + static_range) & ~(CHUNK_SIZE - 1)
- var/y2 = min(world.maxy, y_value + static_range) & ~(CHUNK_SIZE - 1)
-
+ var/x1 = max(1, eye_turf.x - static_range)
+ var/y1 = max(1, eye_turf.y - static_range)
+ var/x2 = min(world.maxx, eye_turf.x + static_range)
+ var/y2 = min(world.maxy, eye_turf.y + static_range)
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
- visibleChunks |= getCameraChunk(x, y, z_value)
+ visibleChunks |= getCameraChunk(x, y, eye_turf.z)
var/list/remove = eye.visibleCameraChunks - visibleChunks
var/list/add = visibleChunks - eye.visibleCameraChunks
@@ -78,7 +89,7 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
for(var/datum/camerachunk/chunk as anything in add)
chunk.add(eye)
-/// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
+/// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open.
/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1)
if(!SSticker || (opacity_check && !A.opacity))
return
@@ -99,10 +110,14 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
if(c.can_use())
majorChunkChange(c, 1)
-/// Used for Cyborg cameras. Since portable cameras can be in ANY chunk.
-/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c)
- if(c.can_use())
- majorChunkChange(c, 1)
+/**
+ * Used for Cyborg/mecha cameras. Since portable cameras can be in ANY chunk.
+ * update_delay_buffer is passed all the way to hasChanged() from their camera updates on movement
+ * to change the time between static updates.
+*/
+/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/updating_camera, update_delay_buffer)
+ if(updating_camera.can_use())
+ majorChunkChange(updating_camera, 1, update_delay_buffer)
/**
* Never access this proc directly!!!!
@@ -110,37 +125,52 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
* It will also add the atom to the cameras list if you set the choice to 1.
* Setting the choice to 0 will remove the camera from the chunks.
* If you want to update the chunks around an object, without adding/removing a camera, use choice 2.
+ * update_delay_buffer is passed all the way to hasChanged() from portable camera updates on movement
+ * to change the time between static updates.
*/
-/datum/cameranet/proc/majorChunkChange(atom/c, choice)
- if(!c)
- return
+/datum/cameranet/proc/majorChunkChange(atom/c, choice, update_delay_buffer)
+ if(QDELETED(c) && choice == 1)
+ CRASH("Tried to add a qdeleting camera to the net")
var/turf/T = get_turf(c)
if(T)
- var/x1 = max(0, T.x - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
- var/y1 = max(0, T.y - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
- var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
- var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1)
+ var/x1 = max(1, T.x - (CHUNK_SIZE / 2))
+ var/y1 = max(1, T.y - (CHUNK_SIZE / 2))
+ var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2))
+ var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2))
for(var/x = x1; x <= x2; x += CHUNK_SIZE)
for(var/y = y1; y <= y2; y += CHUNK_SIZE)
var/datum/camerachunk/chunk = chunkGenerated(x, y, T.z)
if(chunk)
if(choice == 0)
// Remove the camera.
- chunk.cameras -= c
+ chunk.cameras["[T.z]"] -= c
else if(choice == 1)
// You can't have the same camera in the list twice.
- chunk.cameras |= c
- chunk.hasChanged()
+ chunk.cameras["[T.z]"] |= c
+ chunk.hasChanged(update_delay_buffer = update_delay_buffer)
+
+/// A faster, turf only version of [/datum/cameranet/proc/majorChunkChange]
+/// For use in sensitive code, be careful with it
+/datum/cameranet/proc/bareMajorChunkChange(turf/changed)
+ var/x1 = max(1, changed.x - (CHUNK_SIZE / 2))
+ var/y1 = max(1, changed.y - (CHUNK_SIZE / 2))
+ var/x2 = min(world.maxx, changed.x + (CHUNK_SIZE / 2))
+ var/y2 = min(world.maxy, changed.y + (CHUNK_SIZE / 2))
+ for(var/x = x1; x <= x2; x += CHUNK_SIZE)
+ for(var/y = y1; y <= y2; y += CHUNK_SIZE)
+ var/datum/camerachunk/chunk = chunkGenerated(x, y, changed.z)
+ chunk?.hasChanged()
/// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0.
/datum/cameranet/proc/checkCameraVis(mob/living/target)
var/turf/position = get_turf(target)
+ if(!position)
+ return
return checkTurfVis(position)
-
/datum/cameranet/proc/checkTurfVis(turf/position)
- var/datum/camerachunk/chunk = chunkGenerated(position.x, position.y, position.z)
+ var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
if(chunk)
if(chunk.changed)
chunk.hasChanged(1) // Update now, no matter if it's visible or not.
@@ -148,22 +178,25 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new)
return TRUE
return FALSE
-/datum/cameranet/proc/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat(name, statclick.update("Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]"))
+/datum/cameranet/proc/getTurfVis(turf/position)
+ RETURN_TYPE(/datum/camerachunk)
+ var/datum/camerachunk/chunk = getCameraChunk(position.x, position.y, position.z)
+ if(!chunk)
+ return FALSE
+ if(chunk.changed)
+ chunk.hasChanged(1) // Update now, no matter if it's visible or not.
+ if(chunk.visibleTurfs[position])
+ return chunk
/obj/effect/overlay/camera_static
name = "static"
icon = null
icon_state = null
anchored = TRUE // should only appear in vis_contents, but to be safe
- appearance_flags = RESET_TRANSFORM | TILE_BOUND | RESET_COLOR
+ appearance_flags = RESET_TRANSFORM | TILE_BOUND | LONG_GLIDE
// this combination makes the static block clicks to everything below it,
// without appearing in the right-click menu for non-AI clients
mouse_opacity = MOUSE_OPACITY_ICON
invisibility = INVISIBILITY_ABSTRACT
- layer = CAMERA_STATIC_LAYER
plane = CAMERA_STATIC_PLANE
diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm
index abf891b011ac..a4f63e82a4fd 100644
--- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm
@@ -11,23 +11,24 @@
///turfs our cameras can see inside our grid
var/list/visibleTurfs = list()
///cameras that can see into our grid
+ ///indexed by the z level of the camera
var/list/cameras = list()
- ///list of all turfs
+ ///list of all turfs, associative with that turf's static image
+ ///turf -> /image
var/list/turfs = list()
///camera mobs that can see turfs in our grid
var/list/seenby = list()
- ///images created to represent obscured turfs
- var/list/inactive_static_images = list()
///images currently in use on obscured turfs.
var/list/active_static_images = list()
var/changed = FALSE
var/x = 0
var/y = 0
- var/z = 0
+ var/lower_z
+ var/upper_z
/// Add an AI eye to the chunk, then update if changed.
-/datum/camerachunk/proc/add(mob/camera/aiEye/eye)
+/datum/camerachunk/proc/add(mob/camera/ai_eye/eye)
eye.visibleCameraChunks += src
seenby += eye
if(changed)
@@ -38,7 +39,7 @@
client.images += active_static_images
/// Remove an AI eye from the chunk
-/datum/camerachunk/proc/remove(mob/camera/aiEye/eye, remove_static_with_last_chunk = TRUE)
+/datum/camerachunk/proc/remove(mob/camera/ai_eye/eye, remove_static_with_last_chunk = TRUE)
eye.visibleCameraChunks -= src
seenby -= eye
@@ -55,35 +56,40 @@
/**
* Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will
* instead be flagged to update the next time an AI Eye moves near it.
+ * update_delay_buffer is used for cameras that are moving around, which are cyborg inbuilt cameras and
+ * mecha onboard cameras. This buffer should be usually lower than UPDATE_BUFFER_TIME because
+ * otherwise a moving camera can run out of its own view before updating static.
*/
-/datum/camerachunk/proc/hasChanged(update_now = 0)
+/datum/camerachunk/proc/hasChanged(update_now = 0, update_delay_buffer = UPDATE_BUFFER_TIME)
if(seenby.len || update_now)
- addtimer(CALLBACK(src, PROC_REF(update)), UPDATE_BUFFER_TIME, TIMER_UNIQUE)
+ addtimer(CALLBACK(src, PROC_REF(update)), update_delay_buffer, TIMER_UNIQUE)
else
changed = TRUE
/// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists.
-/datum/camerachunk/proc/update()
+/// Accepts an optional partial_update argument, that blocks any calls out to chunks that could affect us, like above or below
+/datum/camerachunk/proc/update(partial_update = FALSE)
var/list/updated_visible_turfs = list()
- for(var/obj/machinery/camera/current_camera as anything in cameras)
- if(!current_camera || !current_camera.can_use())
- continue
+ for(var/z_level in lower_z to upper_z)
+ for(var/obj/machinery/camera/current_camera as anything in cameras["[z_level]"])
+ if(!current_camera || !current_camera.can_use())
+ continue
- var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z)
- if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2))
- continue
+ var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), z_level)
+ if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2))
+ continue
- for(var/turf/vis_turf in current_camera.can_see())
- if(turfs[vis_turf])
- updated_visible_turfs[vis_turf] = vis_turf
+ for(var/turf/vis_turf in current_camera.can_see())
+ if(turfs[vis_turf])
+ updated_visible_turfs[vis_turf] = vis_turf
///new turfs that we couldnt see last update but can now
var/list/newly_visible_turfs = updated_visible_turfs - visibleTurfs
///turfs that we could see last update but cant see now
var/list/newly_obscured_turfs = visibleTurfs - updated_visible_turfs
- for(var/mob/camera/aiEye/client_eye as anything in seenby)
+ for(var/mob/camera/ai_eye/client_eye as anything in seenby)
var/client/client = client_eye.ai?.client || client_eye.client
if(!client)
continue
@@ -91,75 +97,84 @@
client.images -= active_static_images
for(var/turf/visible_turf as anything in newly_visible_turfs)
- var/image/static_image_to_deallocate = obscuredTurfs[visible_turf]
- if(!static_image_to_deallocate)
+ var/image/static_image = obscuredTurfs[visible_turf]
+ if(!static_image)
continue
- static_image_to_deallocate.loc = null
- active_static_images -= static_image_to_deallocate
- inactive_static_images += static_image_to_deallocate
-
+ active_static_images -= static_image
obscuredTurfs -= visible_turf
for(var/turf/obscured_turf as anything in newly_obscured_turfs)
if(obscuredTurfs[obscured_turf] || istype(obscured_turf, /turf/open/ai_visible))
continue
- var/image/static_image_to_allocate = inactive_static_images[length(inactive_static_images)]
- if(!static_image_to_allocate)
- stack_trace("somehow a camera chunk ran out of static images!")
+ var/image/static_image = turfs[obscured_turf]
+ if(!static_image)
+ stack_trace("somehow a camera chunk used a turf it didn't contain!!")
break
- obscuredTurfs[obscured_turf] = static_image_to_allocate
- static_image_to_allocate.loc = obscured_turf
-
- inactive_static_images -= static_image_to_allocate
- active_static_images += static_image_to_allocate
-
+ obscuredTurfs[obscured_turf] = static_image
+ active_static_images += static_image
visibleTurfs = updated_visible_turfs
changed = FALSE
- for(var/mob/camera/aiEye/client_eye as anything in seenby)
+ for(var/mob/camera/ai_eye/client_eye as anything in seenby)
var/client/client = client_eye.ai?.client || client_eye.client
if(!client)
continue
client.images += active_static_images
+
/// Create a new camera chunk, since the chunks are made as they are needed.
-/datum/camerachunk/New(x, y, z)
- x &= ~(CHUNK_SIZE - 1)
- y &= ~(CHUNK_SIZE - 1)
+/datum/camerachunk/New(x, y, lower_z)
+ x = GET_CHUNK_COORD(x)
+ y = GET_CHUNK_COORD(y)
src.x = x
src.y = y
- src.z = z
+ src.lower_z = lower_z
+ var/turf/upper_turf = get_highest_turf(locate(x, y, lower_z))
+ src.upper_z = upper_turf.z
- for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z)))
- if(camera.can_use())
- cameras += camera
+ for(var/z_level in lower_z to upper_z)
+ var/list/local_cameras = list()
+ for(var/obj/machinery/camera/camera in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
+ if(camera.can_use())
+ local_cameras += camera
- for(var/turf/t as anything in block(locate(max(x, 1), max(y, 1), z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z)))
- turfs[t] = t
+ for(var/mob/living/silicon/sillycone in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
+ if(sillycone.builtInCamera?.can_use())
+ local_cameras += sillycone.builtInCamera
- for(var/turf in turfs)//one for each 16x16 = 256 turfs this camera chunk encompasses
- inactive_static_images += new/image(GLOB.cameranet.obscured)
+ // for(var/obj/vehicle/sealed/mecha/mech in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z_level)))
+ // if(mech.chassis_camera?.can_use())
+ // local_cameras += mech.chassis_camera
- for(var/obj/machinery/camera/camera as anything in cameras)
- if(!camera || !camera.can_use())
- continue
+ cameras["[z_level]"] = local_cameras
+
+ var/image/mirror_from = GLOB.cameranet.obscured_images[GET_Z_PLANE_OFFSET(z_level) + 1]
+ var/turf/chunk_corner = locate(x, y, z_level)
+ for(var/turf/lad as anything in CORNER_BLOCK(chunk_corner, CHUNK_SIZE, CHUNK_SIZE)) //we use CHUNK_SIZE for width and height here as it handles subtracting 1 from those two parameters by itself
+ var/image/our_image = new /image(mirror_from)
+ our_image.loc = lad
+ turfs[lad] = our_image
+
+ for(var/obj/machinery/camera/camera as anything in local_cameras)
+ if(!camera)
+ continue
+
+ if(!camera.can_use())
+ continue
- for(var/turf/vis_turf in camera.can_see())
- if(turfs[vis_turf])
- visibleTurfs[vis_turf] = vis_turf
+ for(var/turf/vis_turf in camera.can_see())
+ if(turfs[vis_turf])
+ visibleTurfs[vis_turf] = vis_turf
for(var/turf/obscured_turf as anything in turfs - visibleTurfs)
- var/image/new_static = inactive_static_images[inactive_static_images.len]
- new_static.loc = obscured_turf
+ var/image/new_static = turfs[obscured_turf]
active_static_images += new_static
- inactive_static_images -= new_static
obscuredTurfs[obscured_turf] = new_static
#undef UPDATE_BUFFER_TIME
-#undef CHUNK_SIZE
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 0486fda51619..de64d1187b47 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -2,8 +2,7 @@
//
// An invisible (no icon) mob that the AI controls to look around the station with.
// It streams chunks as it moves around, which will show it what the AI can and cannot see.
-
-/mob/camera/aiEye
+/mob/camera/ai_eye
name = "Inactive AI Eye"
icon_state = "ai_camera"
@@ -18,14 +17,20 @@
var/ai_detector_visible = TRUE
var/ai_detector_color = COLOR_RED
-/mob/camera/aiEye/Initialize(mapload)
+/mob/camera/ai_eye/Initialize(mapload)
. = ..()
GLOB.aiEyes += src
icon_state = "ai_camera[GLOB.ai_list.len % 3]" // Yogs -- multicoloured AI eyes
update_ai_detect_hud()
setLoc(loc, TRUE)
-/mob/camera/aiEye/proc/update_ai_detect_hud()
+/mob/camera/ai_eye/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+ . = ..()
+ if(same_z_layer)
+ return
+ update_ai_detect_hud()
+
+/mob/camera/ai_eye/proc/update_ai_detect_hud()
var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT]
var/list/old_images = hud_list[AI_DETECT_HUD]
if(!ai_detector_visible)
@@ -39,12 +44,16 @@
hud.remove_atom_from_hud(src)
var/static/list/vis_contents_opaque = list()
- var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_opaque[ai_detector_color]
+ var/turf/our_turf = get_turf(src)
+ var/our_z_offset = GET_TURF_PLANE_OFFSET(our_turf)
+ var/key = "[our_z_offset]-[ai_detector_color]"
+ var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_opaque[key]
if(!hud_obj)
hud_obj = new /obj/effect/overlay/ai_detect_hud()
+ SET_PLANE_W_SCALAR(hud_obj, PLANE_TO_TRUE(hud_obj.plane), our_z_offset)
hud_obj.color = ai_detector_color
- vis_contents_opaque[ai_detector_color] = hud_obj
+ vis_contents_opaque[key] = hud_obj
var/list/new_images = list()
var/list/turfs = get_visible_turfs()
@@ -58,7 +67,7 @@
hud_list[AI_DETECT_HUD] = new_images
hud.add_atom_to_hud(src)
-/mob/camera/aiEye/proc/get_visible_turfs()
+/mob/camera/ai_eye/proc/get_visible_turfs()
if(!isturf(loc))
return list()
var/client/C = GetViewerClient()
@@ -67,44 +76,58 @@
var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z)
return block(lowerleft, upperright)
+/// Used in cases when the eye is located in a movable object (i.e. mecha)
+/mob/camera/ai_eye/proc/update_visibility()
+ SIGNAL_HANDLER
+ if(use_static)
+ ai.camera_visibility(src)
+
// Use this when setting the aiEye's location.
// It will also stream the chunk that the new loc is in.
-/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE)
- if(ai)
- if(!(isturf(ai.loc) || istype(ai.loc, /obj/machinery/ai/data_core)))
- return
- T = get_turf(T)
- if(!force_update && (T == get_turf(src)) )
- return //we are already here!
- if (T)
- forceMove(T)
- else
- moveToNullspace()
- if(use_static)
- ai.camera_visibility(src)
- if(ai.client && !ai.multicam_on)
- ai.client.eye = src
- update_ai_detect_hud()
- update_parallax_contents()
- //Holopad
- if(istype(ai.current, /obj/machinery/holopad))
- var/obj/machinery/holopad/H = ai.current
- H.move_hologram(ai, T)
- if(ai.camera_light_on)
- ai.light_cameras()
- if(ai.master_multicam)
- ai.master_multicam.refresh_view()
-
-/mob/camera/aiEye/Move()
- return 0
-
-/mob/camera/aiEye/proc/GetViewerClient()
+/mob/camera/ai_eye/proc/setLoc(destination, force_update = FALSE)
+ if(!ai)
+ return
+ if(!(isturf(ai.loc) || istype(ai.loc, /obj/machinery/ai/data_core)))
+ return
+ destination = get_turf(destination)
+ if(!force_update && (destination == get_turf(src)))
+ return //we are already here!
+ if (destination)
+ abstract_move(destination)
+ else
+ moveToNullspace()
+ if(use_static)
+ ai.camera_visibility(src)
+ if(ai.client && !ai.multicam_on)
+ ai.client.set_eye(src)
+ update_ai_detect_hud()
+ update_parallax_contents()
+ //Holopad
+ if(istype(ai.current, /obj/machinery/holopad))
+ var/obj/machinery/holopad/H = ai.current
+ if(!H.move_hologram(ai, destination))
+ H.clear_holo(ai)
+
+ if(ai.camera_light_on)
+ ai.light_cameras()
+ if(ai.master_multicam)
+ ai.master_multicam.refresh_view()
+
+/mob/camera/ai_eye/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs)
+ . = ..()
+ if(.)
+ setLoc(loc, force_update = TRUE)
+
+/mob/camera/ai_eye/Move()
+ return
+
+/mob/camera/ai_eye/proc/GetViewerClient()
if(ai)
return ai.client
return null
-/mob/camera/aiEye/Destroy()
+/mob/camera/ai_eye/Destroy()
if(ai)
ai.all_eyes -= src
ai = null
@@ -120,12 +143,13 @@
return ..()
/atom/proc/move_camera_by_click()
- if(isAI(usr))
- var/mob/living/silicon/ai/AI = usr
- if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z))
- AI.cameraFollow = null
- if (isturf(loc) || isturf(src))
- AI.eyeobj.setLoc(src)
+ if(!isAI(usr))
+ return
+ var/mob/living/silicon/ai/AI = usr
+ if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z))
+ AI.cameraFollow = null
+ if (isturf(loc) || isturf(src))
+ AI.eyeobj.setLoc(src)
// This will move the AIEye. It will also cause lights near the eye to light up, if toggled.
// This is handled in the proc below this one.
@@ -133,7 +157,7 @@
/client/proc/AIMove(n, direct, mob/living/silicon/ai/user)
var/initial = initial(user.sprint)
- var/max_sprint = user.max_camera_sprint
+ var/max_sprint = 50
if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds
user.sprint = initial
@@ -164,6 +188,8 @@
if(isvalidAIloc(loc) && (QDELETED(eyeobj) || !eyeobj.loc))
to_chat(src, "ERROR: Eyeobj not found. Creating new eye...")
+ stack_trace("AI eye object wasn't found! Location: [loc] / Eyeobj: [eyeobj] / QDELETED: [QDELETED(eyeobj)] / Eye loc: [eyeobj?.loc]")
+ QDEL_NULL(eyeobj)
create_eye()
eyeobj?.setLoc(loc)
@@ -171,7 +197,7 @@
/mob/living/silicon/ai/proc/create_eye()
if(eyeobj)
return
- eyeobj = new /mob/camera/aiEye()
+ eyeobj = new /mob/camera/ai_eye()
all_eyes += eyeobj
eyeobj.ai = src
eyeobj.setLoc(loc)
@@ -183,7 +209,10 @@
if(!eyeobj)
return
eyeobj.mouse_opacity = state ? MOUSE_OPACITY_ICON : initial(eyeobj.mouse_opacity)
- eyeobj.invisibility = state ? INVISIBILITY_OBSERVER : initial(eyeobj.invisibility)
+ if(state)
+ eyeobj.SetInvisibility(INVISIBILITY_OBSERVER, id=type)
+ else
+ eyeobj.RemoveInvisibility(type)
/mob/living/silicon/ai/verb/toggle_acceleration()
set category = "AI Commands"
@@ -194,7 +223,7 @@
acceleration = !acceleration
to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].")
-/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
+/mob/camera/ai_eye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker))
ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mods)
@@ -206,4 +235,4 @@
icon_state = ""
alpha = 100
layer = ABOVE_ALL_MOB_LAYER
- plane = GAME_PLANE
+ plane = ABOVE_GAME_PLANE
diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm
index 7d9c128e622e..7c0162a5b06f 100644
--- a/code/modules/mob/living/silicon/ai/life.dm
+++ b/code/modules/mob/living/silicon/ai/life.dm
@@ -110,18 +110,14 @@
diag_hud_set_status()
/mob/living/silicon/ai/update_sight()
- see_invisible = initial(see_invisible)
- see_in_dark = initial(see_in_dark)
- sight = initial(sight)
+ set_invis_see(initial(see_invisible))
+ set_sight(initial(sight))
if(aiRestorePowerRoutine && !available_ai_cores())
- sight = sight&~SEE_TURFS
- sight = sight&~SEE_MOBS
- sight = sight&~SEE_OBJS
- see_in_dark = 0
+ clear_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
if(see_override)
- see_invisible = see_override
- sync_lighting_plane_alpha()
+ set_invis_see(see_override)
+ return ..()
/mob/living/silicon/ai/proc/start_RestorePowerRoutine()
diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm
index 454e7eba34e8..03989573fbd4 100644
--- a/code/modules/mob/living/silicon/ai/login.dm
+++ b/code/modules/mob/living/silicon/ai/login.dm
@@ -1,5 +1,7 @@
/mob/living/silicon/ai/Login()
- ..()
+ . = ..()
+ if(!. || !client)
+ return FALSE
if(stat != DEAD)
for(var/each in GLOB.ai_status_displays) //change status
var/obj/machinery/status_display/ai/O = each
@@ -12,6 +14,3 @@
if(multicam_on)
end_multicam()
view_core()
- if(!login_warned_temp)
- to_chat(src, span_userdanger("WARNING. THE WAY AI IS PLAYED HAS CHANGED. PLEASE REFER TO https://github.com/yogstation13/Yogstation/pull/12388"))
- login_warned_temp = TRUE
diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm
index a7a07e329fad..6e0451738bd5 100644
--- a/code/modules/mob/living/silicon/ai/multicam.dm
+++ b/code/modules/mob/living/silicon/ai/multicam.dm
@@ -4,11 +4,11 @@
var/mob/living/silicon/ai/ai
var/mutable_appearance/highlighted_background
var/highlighted = FALSE
- var/mob/camera/aiEye/pic_in_pic/aiEye
+ var/mob/camera/ai_eye/pic_in_pic/aiEye
/atom/movable/screen/movable/pic_in_pic/ai/Initialize(mapload)
. = ..()
- aiEye = new /mob/camera/aiEye/pic_in_pic()
+ aiEye = new /mob/camera/ai_eye/pic_in_pic()
aiEye.screen = src
/atom/movable/screen/movable/pic_in_pic/ai/Destroy()
@@ -86,13 +86,24 @@
name = ""
icon = 'icons/misc/pic_in_pic.dmi'
icon_state = "room_background"
- flags_1 = NOJAUNT_1
+ turf_flags = NOJAUNT
+
+/turf/open/ai_visible/Initialize(mapload)
+ . = ..()
+ RegisterSignal(SSmapping, COMSIG_PLANE_OFFSET_INCREASE, PROC_REF(multiz_offset_increase))
+ multiz_offset_increase(SSmapping)
+
+/turf/open/ai_visible/proc/multiz_offset_increase(datum/source)
+ SIGNAL_HANDLER
+ SET_PLANE_W_SCALAR(src, initial(plane), SSmapping.max_plane_offset)
/area/ai_multicam_room
name = "ai_multicam_room"
icon_state = "ai_camera_room"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
valid_territory = FALSE
+ static_lighting = FALSE
+
+ base_lighting_alpha = 255
ambientsounds = list()
blob_allowed = FALSE
noteleport = TRUE
@@ -118,7 +129,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room)
//Dummy camera eyes
-/mob/camera/aiEye/pic_in_pic
+/mob/camera/ai_eye/pic_in_pic
name = "Secondary AI Eye"
invisibility = INVISIBILITY_OBSERVER
mouse_opacity = MOUSE_OPACITY_ICON
@@ -129,13 +140,13 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room)
var/telegraph_range = 7
ai_detector_color = COLOR_ORANGE
-/mob/camera/aiEye/pic_in_pic/GetViewerClient()
+/mob/camera/ai_eye/pic_in_pic/GetViewerClient()
if(screen && screen.ai)
return screen.ai.client
-/mob/camera/aiEye/pic_in_pic/setLoc(turf/T)
- if (T)
- forceMove(T)
+/mob/camera/ai_eye/pic_in_pic/setLoc(turf/destination, force_update = FALSE)
+ if (destination)
+ abstract_move(destination)
else
moveToNullspace()
if(screen && screen.ai)
@@ -145,22 +156,21 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room)
update_camera_telegraphing()
update_ai_detect_hud()
-/mob/camera/aiEye/pic_in_pic/get_visible_turfs()
+/mob/camera/ai_eye/pic_in_pic/get_visible_turfs()
return screen ? screen.get_visible_turfs() : list()
-/mob/camera/aiEye/pic_in_pic/proc/update_camera_telegraphing()
+/mob/camera/ai_eye/pic_in_pic/proc/update_camera_telegraphing()
if(!telegraph_cameras)
return
var/list/obj/machinery/camera/add = list()
var/list/obj/machinery/camera/remove = list()
var/list/obj/machinery/camera/visible = list()
- for (var/VV in visibleCameraChunks)
- var/datum/camerachunk/CC = VV
- for (var/V in CC.cameras)
- var/obj/machinery/camera/C = V
- if (!C.can_use() || (get_dist(C, src) > telegraph_range))
- continue
- visible |= C
+ for (var/datum/camerachunk/chunk as anything in visibleCameraChunks)
+ for (var/z_key in chunk.cameras)
+ for(var/obj/machinery/camera/camera as anything in chunk.cameras[z_key])
+ if (!camera.can_use() || (get_dist(camera, src) > telegraph_range))
+ continue
+ visible |= camera
add = visible - cameras_telegraphed
remove = cameras_telegraphed - visible
@@ -171,26 +181,26 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room)
continue
cameras_telegraphed -= C
C.in_use_lights--
- C.update_appearance(UPDATE_ICON)
+ C.update_appearance()
for (var/V in add)
var/obj/machinery/camera/C = V
if(QDELETED(C))
continue
cameras_telegraphed |= C
C.in_use_lights++
- C.update_appearance(UPDATE_ICON)
+ C.update_appearance()
-/mob/camera/aiEye/pic_in_pic/proc/disable_camera_telegraphing()
+/mob/camera/ai_eye/pic_in_pic/proc/disable_camera_telegraphing()
telegraph_cameras = FALSE
for (var/V in cameras_telegraphed)
var/obj/machinery/camera/C = V
if(QDELETED(C))
continue
C.in_use_lights--
- C.update_appearance(UPDATE_ICON)
+ C.update_appearance()
cameras_telegraphed.Cut()
-/mob/camera/aiEye/pic_in_pic/Destroy()
+/mob/camera/ai_eye/pic_in_pic/Destroy()
disable_camera_telegraphing()
return ..()
diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm
index 954ae2ba4f09..d69a7b579f89 100644
--- a/code/modules/mob/living/silicon/laws.dm
+++ b/code/modules/mob/living/silicon/laws.dm
@@ -1,10 +1,15 @@
-/mob/living/silicon/proc/show_laws() //Redefined in ai/laws.dm and robot/laws.dm
- return
-
/mob/living/silicon/proc/laws_sanity_check()
- if (!laws)
+ if(!laws)
make_laws()
+/mob/living/silicon/proc/make_laws()
+ laws = new /datum/ai_laws
+ laws.set_laws_config()
+ laws.associate(src)
+
+/mob/living/silicon/proc/show_laws() // Redefined in silicon/ai/laws.dm and silicon/robot/laws.dm
+ return
+
/mob/living/silicon/proc/post_lawchange(announce = TRUE)
throw_alert("newlaw", /atom/movable/screen/alert/newlaw)
if(announce && last_lawchange_announce != world.time)
@@ -13,39 +18,66 @@
addtimer(CALLBACK(src, PROC_REF(show_laws)), 0)
last_lawchange_announce = world.time
-/mob/living/silicon/proc/set_law_sixsixsix(law, announce = TRUE)
+//
+// Devil Laws
+//
+/mob/living/silicon/proc/set_devil_laws(law_list, announce = TRUE)
laws_sanity_check()
- laws.set_law_sixsixsix(law)
+ laws.set_devil_laws(law_list)
post_lawchange(announce)
-/mob/living/silicon/proc/set_zeroth_law(law, law_borg, announce = TRUE)
+/mob/living/silicon/proc/clear_devil_laws(force, announce = TRUE)
laws_sanity_check()
- laws.set_zeroth_law(law, law_borg)
+ laws.clear_devil_laws(force)
post_lawchange(announce)
-/mob/living/silicon/proc/add_inherent_law(law, announce = TRUE)
+/mob/living/silicon/proc/add_devil_law(law, announce = TRUE)
laws_sanity_check()
- laws.add_inherent_law(law)
+ laws.add_devil_law(law)
post_lawchange(announce)
-/mob/living/silicon/proc/clear_inherent_laws(announce = TRUE)
+/mob/living/silicon/proc/remove_devil_law(index, announce = TRUE)
laws_sanity_check()
- laws.clear_inherent_laws()
+ laws.remove_devil_law(index)
post_lawchange(announce)
-/mob/living/silicon/proc/add_supplied_law(number, law, announce = TRUE)
+/mob/living/silicon/proc/edit_devil_law(index, law, announce = TRUE)
laws_sanity_check()
- laws.add_supplied_law(number, law)
+ laws.edit_devil_law(index, law)
post_lawchange(announce)
-/mob/living/silicon/proc/clear_supplied_laws(announce = TRUE)
+/mob/living/silicon/proc/flip_devil_state(index, announce = TRUE)
laws_sanity_check()
- laws.clear_supplied_laws()
+ laws.flip_devil_state(index)
+
+//
+// Zeroth Law
+//
+/mob/living/silicon/proc/set_zeroth_law(law, law_borg, announce = TRUE, force = FALSE)
+ laws_sanity_check()
+ laws.set_zeroth_law(law, law_borg, force)
post_lawchange(announce)
-/mob/living/silicon/proc/add_ion_law(law, announce = TRUE)
+/mob/living/silicon/proc/clear_zeroth_law(force, announce = TRUE)
laws_sanity_check()
- laws.add_ion_law(law)
+ laws.clear_zeroth_law(force)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/flip_zeroth_state()
+ laws_sanity_check()
+ laws.flip_zeroth_state()
+
+//
+// Hacked Laws
+//
+/mob/living/silicon/proc/set_hacked_laws(law_list, announce = TRUE)
+ laws_sanity_check()
+ laws.set_hacked_laws(law_list)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/clear_hacked_laws(force, announce = TRUE)
+ laws_sanity_check()
+ laws.clear_hacked_laws(force)
post_lawchange(announce)
/mob/living/silicon/proc/add_hacked_law(law, announce = TRUE)
@@ -53,19 +85,26 @@
laws.add_hacked_law(law)
post_lawchange(announce)
-/mob/living/silicon/proc/replace_random_law(law, groups, announce = TRUE)
+/mob/living/silicon/proc/remove_hacked_law(index, announce = TRUE)
laws_sanity_check()
- . = laws.replace_random_law(law,groups)
+ laws.remove_hacked_law(index)
post_lawchange(announce)
-/mob/living/silicon/proc/shuffle_laws(list/groups, announce = TRUE)
+/mob/living/silicon/proc/edit_hacked_law(index, law, announce = TRUE)
laws_sanity_check()
- laws.shuffle_laws(groups)
+ laws.edit_hacked_law(index, law)
post_lawchange(announce)
-/mob/living/silicon/proc/remove_law(number, announce = TRUE)
+/mob/living/silicon/proc/flip_hacked_state(index, announce = TRUE)
+ laws_sanity_check()
+ laws.flip_hacked_state(index)
+
+//
+// Ion Laws
+//
+/mob/living/silicon/proc/set_ion_laws(law_list, announce = TRUE)
laws_sanity_check()
- . = laws.remove_law(number)
+ laws.set_ion_laws(law_list)
post_lawchange(announce)
/mob/living/silicon/proc/clear_ion_laws(announce = TRUE)
@@ -73,22 +112,493 @@
laws.clear_ion_laws()
post_lawchange(announce)
-/mob/living/silicon/proc/clear_hacked_laws(announce = TRUE)
+/mob/living/silicon/proc/add_ion_law(law, announce = TRUE)
laws_sanity_check()
- laws.clear_hacked_laws()
+ laws.add_ion_law(law)
post_lawchange(announce)
-/mob/living/silicon/proc/make_laws()
- laws = new /datum/ai_laws
- laws.set_laws_config()
- laws.associate(src)
+/mob/living/silicon/proc/remove_ion_law(index, announce = TRUE)
+ laws_sanity_check()
+ laws.remove_ion_law(index)
+ post_lawchange(announce)
-/mob/living/silicon/proc/clear_zeroth_law(force, announce = TRUE)
+/mob/living/silicon/proc/edit_ion_law(index, law, announce = TRUE)
laws_sanity_check()
- laws.clear_zeroth_law(force)
+ laws.edit_ion_law(index, law)
post_lawchange(announce)
-/mob/living/silicon/proc/clear_law_sixsixsix(force, announce = TRUE)
+/mob/living/silicon/proc/flip_ion_state(index, announce = TRUE)
laws_sanity_check()
- laws.clear_law_sixsixsix(force)
+ laws.flip_ion_state(index)
+
+//
+// Inherent Laws
+//
+/mob/living/silicon/proc/set_inherent_laws(law_list, announce = TRUE)
+ laws_sanity_check()
+ laws.set_inherent_laws(law_list)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/clear_inherent_laws(announce = TRUE)
+ laws_sanity_check()
+ laws.clear_inherent_laws()
post_lawchange(announce)
+
+/mob/living/silicon/proc/add_inherent_law(law, announce = TRUE)
+ laws_sanity_check()
+ laws.add_inherent_law(law)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/remove_inherent_law(number, announce = TRUE)
+ laws_sanity_check()
+ laws.remove_inherent_law(number)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/edit_inherent_law(index, law, announce = TRUE)
+ laws_sanity_check()
+ laws.edit_inherent_law(index, law)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/flip_inherent_state(index, announce = TRUE)
+ laws_sanity_check()
+ laws.flip_inherent_state(index)
+
+//
+// Supplied Laws
+//
+/mob/living/silicon/proc/set_supplied_laws(law_list, announce = TRUE)
+ laws_sanity_check()
+ laws.set_supplied_laws(law_list)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/clear_supplied_laws(announce = TRUE)
+ laws_sanity_check()
+ laws.clear_supplied_laws()
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/add_supplied_law(number, law, announce = TRUE)
+ laws_sanity_check()
+ laws.add_supplied_law(number, law)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/remove_supplied_law(number, announce = TRUE)
+ laws_sanity_check()
+ laws.remove_supplied_law(number)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/edit_supplied_law(index, law, announce = TRUE)
+ laws_sanity_check()
+ laws.edit_supplied_law(index, law)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/flip_supplied_state(index, announce = TRUE)
+ laws_sanity_check()
+ laws.flip_supplied_state(index)
+
+//
+// Unsorted
+//
+/mob/living/silicon/proc/replace_random_law(law, groups, announce = TRUE)
+ laws_sanity_check()
+ laws.replace_random_law(law, groups)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/shuffle_laws(list/groups, announce = TRUE)
+ laws_sanity_check()
+ laws.shuffle_laws(groups)
+ post_lawchange(announce)
+
+/mob/living/silicon/proc/remove_law(number, announce = TRUE)
+ laws_sanity_check()
+ laws.remove_law(number)
+ post_lawchange(announce)
+
+//
+// Law Manager
+//
+/datum/law_manager
+ var/zeroth_law = "ZerothLaw"
+ var/hacked_law = "HackedLaw"
+ var/ion_law = "IonLaw"
+ var/inherent_law = "InherentLaw"
+ var/supplied_law = "SuppliedLaw"
+ var/supplied_law_position = MIN_SUPPLIED_LAW_NUMBER
+ var/current_view = 0
+
+ /// All laws that admins can view/transfer.
+ var/list/datum/ai_laws/admin_laws
+ /// All laws that players can view/transfer.
+ var/list/datum/ai_laws/player_laws
+ var/mob/living/silicon/owner = null
+
+/datum/law_manager/New(mob/living/silicon/S)
+ ..()
+ owner = S
+
+ if(!admin_laws)
+ admin_laws = new()
+ player_laws = new()
+
+ init_subtypes(/datum/ai_laws, admin_laws)
+
+ for(var/datum/ai_laws/laws in admin_laws)
+ if(!laws.adminselectable)
+ admin_laws -= laws
+
+ for(var/datum/ai_laws/laws in admin_laws)
+ if(laws.selectable)
+ player_laws += laws
+
+/datum/law_manager/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "LawManager", "Law Manager - [owner.name]")
+ ui.open()
+
+/datum/law_manager/ui_state(mob/user)
+ return (is_admin(user) || owner == user) ? GLOB.always_state : GLOB.never_state
+
+/datum/law_manager/ui_status(mob/user)
+ return (is_admin(user) || owner == user) ? UI_INTERACTIVE : UI_CLOSE
+
+/datum/law_manager/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ if(owner != usr && !is_admin(usr))
+ message_admins("Warning: Non-silicon and non-admin [usr] attempted to open [owner]'s Law Manager!")
+ return
+
+ switch(action)
+ // Normal actions:
+ if("set_view")
+ current_view = text2num(params["set_view"])
+ if("state_law")
+ var/index = text2num(params["index"])
+ var/type = params["type"]
+ if(type == "devil")
+ owner.laws.flip_devil_state(index)
+ if(type == "zeroth")
+ owner.laws.flip_zeroth_state()
+ if(type == "hacked")
+ owner.laws.flip_hacked_state(index)
+ if(type == "ion")
+ owner.laws.flip_ion_state(index)
+ if(type == "inherent")
+ owner.laws.flip_inherent_state(index)
+ if(type == "supplied")
+ owner.laws.flip_supplied_state(index)
+ if("law_channel")
+ if(params["law_channel"] == "Default")
+ owner.radiomod = ";"
+ owner.radiomodname = params["law_channel"]
+ else if(params["law_channel"] == "None")
+ owner.radiomod = ""
+ owner.radiomodname = null
+ else if(params["law_channel"] == "Holopad")
+ owner.radiomod = ":h"
+ owner.radiomodname = params["law_channel"]
+ else if(params["law_channel"] == "Binary")
+ owner.radiomod = ":b"
+ owner.radiomodname = params["law_channel"]
+ else if(params["law_channel"] in owner.radio.channels)
+ for(var/key in GLOB.department_radio_keys)
+ if(GLOB.department_radio_keys[key] == params["law_channel"])
+ owner.radiomod = ":" + key
+ owner.radiomodname = params["law_channel"]
+ break
+ if("state_laws")
+ owner.statelaws()
+ if("notify_laws") // Changing laws through this menu won't notify the AI automatically as it'd be super annoying if they get spammed with sound effects.
+ if(ispAI(owner))
+ to_chat(owner, span_bold("Obey these laws:"))
+ owner.laws.show_laws(owner)
+ else
+ owner.show_laws()
+ if(usr != owner)
+ to_chat(usr, span_notice("Laws displayed."))
+ //Admin actions:
+ if("edit_law")
+ var/index = text2num(params["index"])
+ var/type = params["type"]
+ if(owner == usr && !is_special_character(owner) && !is_admin(usr))
+ message_admins("Warning: Non-antag silicon and non-admin [usr] attempted to edit one of their laws!")
+ return
+ if(type == "devil" && owner.laws.devil.len >= index)
+ if(!is_admin(usr))
+ to_chat(usr, span_warning("You can't edit your own devil laws."))
+ return
+ var/new_law = sanitize(input(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", owner.laws.devil[index]))
+ if(new_law != "" && new_law != owner.laws.devil[index])
+ log_admin("[usr] has changed a law of [owner] from '[owner.laws.devil[index]]' to '[new_law]'")
+ message_admins("[usr] has changed a law of [owner] from '[owner.laws.devil[index]]' to '[new_law]'")
+ owner.edit_devil_law(index, new_law)
+ owner.update_law_history(usr) // NOTE: It can be either "ckey/youricname" (as mob/ghost) or "ckey/(new player)" (in lobby).
+ if(type == "zeroth" && !isnull(owner.laws.zeroth))
+ if(!is_admin(usr))
+ to_chat(usr, span_warning("You can't edit your own zeroth laws."))
+ return
+ var/new_law = sanitize(input(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", owner.laws.zeroth))
+ if(new_law != "" && new_law != owner.laws.zeroth)
+ log_admin("[usr] has changed a law of [owner] from '[owner.laws.zeroth]' to '[new_law]'")
+ message_admins("[usr] has changed a law of [owner] from '[owner.laws.zeroth]' to '[new_law]'")
+ owner.set_zeroth_law(new_law, null, FALSE, TRUE)
+ owner.update_law_history(usr)
+ if(type == "hacked" && owner.laws.hacked.len >= index)
+ var/new_law = sanitize(input(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", owner.laws.hacked[index]))
+ if(new_law != "" && new_law != owner.laws.hacked[index])
+ log_admin("[usr] has changed a law of [owner] from '[owner.laws.hacked[index]]' to '[new_law]'")
+ message_admins("[usr] has changed a law of [owner] from '[owner.laws.hacked[index]]' to '[new_law]'")
+ owner.edit_hacked_law(index, new_law, FALSE)
+ owner.update_law_history(usr)
+ if(type == "ion" && owner.laws.ion.len >= index)
+ var/new_law = sanitize(input(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", owner.laws.ion[index]))
+ if(new_law != "" && new_law != owner.laws.ion[index])
+ log_admin("[usr] has changed a law of [owner] from '[owner.laws.ion[index]]' to '[new_law]'")
+ message_admins("[usr] has changed a law of [owner] from '[owner.laws.ion[index]]' to '[new_law]'")
+ owner.edit_ion_law(index, new_law, FALSE)
+ owner.update_law_history(usr)
+ if(type == "inherent" && owner.laws.inherent.len >= index)
+ var/new_law = sanitize(input(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", owner.laws.inherent[index]))
+ if(new_law != "" && new_law != owner.laws.inherent[index])
+ log_admin("[usr] has changed a law of [owner] from '[owner.laws.inherent[index]]' to '[new_law]'")
+ message_admins("[usr] has changed a law of [owner] from '[owner.laws.inherent[index]]' to '[new_law]'")
+ owner.edit_inherent_law(index, new_law, FALSE)
+ owner.update_law_history(usr)
+ if(type == "supplied" && owner.laws.supplied.len >= index)
+ var/new_law = sanitize(input(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", owner.laws.supplied[index]))
+ if(new_law != "" && new_law != owner.laws.supplied[index])
+ log_admin("[usr] has changed a law of [owner] from '[owner.laws.supplied[index]]' to '[new_law]'")
+ message_admins("[usr] has changed a law of [owner] from '[owner.laws.supplied[index]]' to '[new_law]'")
+ owner.edit_supplied_law(index, new_law, FALSE)
+ owner.update_law_history(usr)
+ // Assuming that the law was successfully edited and they're an AI, give their connected cyborgs the same lawset.
+ if(isAI(owner))
+ var/mob/living/silicon/ai/ai_owner = owner
+ for(var/mob/living/silicon/robot/connected_cyborg in ai_owner.connected_robots)
+ if(connected_cyborg.lawupdate)
+ connected_cyborg.lawsync()
+ connected_cyborg.law_change_counter++
+ if("delete_law")
+ var/index = text2num(params["index"])
+ var/type = params["type"]
+ if(owner == usr && !is_admin(usr))
+ message_admins("Warning: Non-antag silicon and non-admin[usr] attempted to delete one of their laws!")
+ return
+ if(ispAI(owner))
+ to_chat(usr, span_warning("You cannot delete a law from a pAI."))
+ return
+ if(type == "devil" && owner.laws.devil.len >= index)
+ if(!is_admin(usr))
+ to_chat(usr, span_warning("You can't remove your own devil laws."))
+ return
+ log_admin("[usr] has deleted a devil law of [owner]: '[owner.laws.devil[index]]'")
+ message_admins("[usr] has deleted a devil law of [owner]: '[owner.laws.devil[index]]'")
+ owner.remove_devil_law(index, FALSE)
+ owner.update_law_history(usr)
+ if(type == "zeroth" && !isnull(owner.laws.zeroth))
+ if(!is_admin(usr))
+ to_chat(usr, span_warning("You can't remove your own zeroth law."))
+ return
+ log_admin("[usr] has deleted the zeroth law of [owner]: '[owner.laws.zeroth]'")
+ message_admins("[usr] has deleted the zeroth law of [owner]: '[owner.laws.zeroth]'")
+ owner.clear_zeroth_law(TRUE, FALSE)
+ owner.update_law_history(usr)
+ if(type == "hacked" && owner.laws.hacked.len >= index)
+ log_admin("[usr] has deleted an hacked law of [owner]: '[owner.laws.hacked[index]]'")
+ message_admins("[usr] has deleted an hacked law of [owner]: '[owner.laws.hacked[index]]'")
+ owner.remove_hacked_law(index, FALSE)
+ owner.update_law_history(usr)
+ if(type == "ion" && owner.laws.ion.len >= index)
+ log_admin("[usr] has deleted an ion law of [owner]: '[owner.laws.ion[index]]'")
+ message_admins("[usr] has deleted an ion law of [owner]: '[owner.laws.ion[index]]'")
+ owner.remove_ion_law(index, FALSE)
+ owner.update_law_history(usr)
+ if(type == "inherent" && owner.laws.inherent.len >= index)
+ log_admin("[usr] has deleted an inherent law of [owner]: '[owner.laws.inherent[index]]'")
+ message_admins("[usr] has deleted an inherent law of [owner]: '[owner.laws.inherent[index]]'")
+ owner.remove_inherent_law(index, FALSE)
+ owner.update_law_history(usr)
+ if(type == "supplied" && owner.laws.supplied.len >= index && length(owner.laws.supplied[index]) > 0 )
+ log_admin("[usr] has deleted an supplied law of [owner]: '[owner.laws.supplied[index]]'")
+ message_admins("[usr] has deleted a supplied law of [owner]: '[owner.laws.supplied[index]]'")
+ owner.remove_supplied_law(index, FALSE)
+ owner.update_law_history(usr)
+ if(isAI(owner))
+ var/mob/living/silicon/ai/ai_owner = owner
+ for(var/mob/living/silicon/robot/connected_cyborg in ai_owner.connected_robots)
+ if(connected_cyborg.lawupdate)
+ connected_cyborg.lawsync()
+ connected_cyborg.law_change_counter++
+ if("add_law")
+ var/type = params["type"]
+ if(owner == usr && !is_admin(usr))
+ message_admins("Warning: Non-antag silicon and non-admin [usr] attempted to give themselves a law!")
+ return
+ if(ispAI(owner))
+ to_chat(usr, span_warning("You cannot add laws to a pAI."))
+ return
+ if(type == "zeroth" && length(zeroth_law) > 0 && !owner.laws.zeroth)
+ log_admin("[usr] has added a zeroth law to [owner]: '[zeroth_law]'")
+ message_admins("[usr] has added a zeroth law to [owner]: '[zeroth_law]'")
+ owner.set_zeroth_law(zeroth_law, null, FALSE, TRUE)
+ owner.update_law_history(usr)
+ if(type == "hacked" && length(hacked_law) > 0)
+ log_admin("[usr] has added a hacked law to [owner]: '[hacked_law]'")
+ message_admins("[usr] has added a hacked law to [owner]: '[hacked_law]'")
+ owner.add_hacked_law(hacked_law, FALSE)
+ owner.update_law_history(usr)
+ if(type == "ion" && length(ion_law) > 0)
+ log_admin("[usr] has added an ion law to [owner]: '[ion_law]'")
+ message_admins("[usr] has added an ion law to [owner]: '[ion_law]'")
+ owner.add_ion_law(ion_law, FALSE)
+ owner.update_law_history(usr)
+ if(type == "inherent" && length(inherent_law) > 0)
+ log_admin("[usr] has added an inherent law to [owner]: '[inherent_law]'")
+ message_admins("[usr] has added an inherent law to [owner]: '[inherent_law]'")
+ owner.add_inherent_law(inherent_law, FALSE)
+ owner.update_law_history(usr)
+ if(type == "supplied" && length(supplied_law) > 0 && supplied_law_position >= MIN_SUPPLIED_LAW_NUMBER && supplied_law_position <= MAX_SUPPLIED_LAW_NUMBER)
+ log_admin("[usr] has added a supplied law to [owner] at position [supplied_law_position]: '[supplied_law]'")
+ message_admins("[usr] has added a supplied law to [owner] at position [supplied_law_position]: '[supplied_law]'")
+ owner.add_supplied_law(supplied_law_position, supplied_law, FALSE)
+ owner.update_law_history(usr)
+ if(isAI(owner))
+ var/mob/living/silicon/ai/ai_owner = owner
+ for(var/mob/living/silicon/robot/connected_cyborg in ai_owner.connected_robots)
+ if(connected_cyborg.lawupdate)
+ connected_cyborg.lawsync()
+ connected_cyborg.law_change_counter++
+ if("change_law")
+ var/type = params["type"]
+ if(type == "zeroth")
+ var/new_law = sanitize(input("Enter new zeroth law. Leaving the field blank will cancel the edit.", "Edit Law", zeroth_law))
+ if(new_law && new_law != zeroth_law && (!..()))
+ zeroth_law = new_law
+ if(type == "hacked")
+ var/new_law = sanitize(input("Enter new hacked law. Leaving the field blank will cancel the edit.", "Edit Law", hacked_law))
+ if(new_law && new_law != hacked_law && (!..()))
+ hacked_law = new_law
+ if(type == "ion")
+ var/new_law = sanitize(input("Enter new ion law. Leaving the field blank will cancel the edit.", "Edit Law", ion_law))
+ if(new_law && new_law != ion_law && (!..()))
+ ion_law = new_law
+ if(type == "inherent")
+ var/new_law = sanitize(input("Enter new inherent law. Leaving the field blank will cancel the edit.", "Edit Law", inherent_law))
+ if(new_law && new_law != inherent_law && (!..()))
+ inherent_law = new_law
+ if(type == "supplied")
+ var/new_law = sanitize(input("Enter new supplied law. Leaving the field blank will cancel the edit.", "Edit Law", supplied_law))
+ if(new_law && new_law != supplied_law && (!..()))
+ supplied_law = new_law
+ if("change_supplied_law_position")
+ var/new_position = input(usr, "Enter new supplied law position between [MIN_SUPPLIED_LAW_NUMBER] and [MAX_SUPPLIED_LAW_NUMBER].", "Law Position", supplied_law_position) as num|null
+ if(isnum(new_position) && (!..()))
+ supplied_law_position = clamp(new_position, MIN_SUPPLIED_LAW_NUMBER, MAX_SUPPLIED_LAW_NUMBER)
+ if("transfer_laws")
+ if(owner == usr && !is_admin(usr))
+ message_admins("Warning: Non-antag silicon and non-admin [usr] attempted to transfer themselves a lawset!")
+ return
+ if(ispAI(owner))
+ to_chat(usr, span_warning("You cannot transfer laws to a pAI."))
+ return
+ var/transfer_id = params["id"]
+ var/list/datum/ai_laws/law_list = (is_admin(usr) ? admin_laws : player_laws)
+ for(var/datum/ai_laws/law_set in law_list)
+ if(law_set.id == transfer_id)
+ log_admin("[usr] has transfered the [law_set.name] laws to [owner].")
+ message_admins("[usr] has transfered the [law_set.name] laws to [owner].")
+ var/lawtype = law_set.lawid_to_type(transfer_id)
+ var/datum/ai_laws/temp_laws = new lawtype
+ owner.set_inherent_laws(temp_laws.inherent, FALSE)
+ current_view = 0
+ break
+
+/datum/law_manager/ui_data(mob/user)
+ var/list/data = list()
+
+ data["ai"] = isAI(owner)
+ data["cyborg"] = iscyborg(owner)
+ if(data["cyborg"])
+ var/mob/living/silicon/robot/R = owner
+ data["connected"] = R.connected_ai ? sanitize(R.connected_ai.name) : null
+ data["lawsync"] = R.lawupdate ? R.lawupdate : FALSE
+ data["syndiemmi"] = R.mmi?.syndicate_mmi ? R.mmi.syndicate_mmi : FALSE
+ else
+ data["connected"] = FALSE
+ data["lawsync"] = FALSE
+ data["syndiemmi"] = FALSE
+ data["pai"] = ispAI(owner) // pAIs are much different from AIs and Cyborgs. They are heavily restricted.
+
+ // This usually gives the power to add/delete/edit the laws. Some exceptions apply (like being a pAI)!
+ data["admin"] = is_admin(user)
+
+ handle_laws(data, owner.laws)
+ handle_channels(data)
+ data["zeroth_law"] = zeroth_law
+ data["hacked_law"] = hacked_law
+ data["ion_law"] = ion_law
+ data["inherent_law"] = inherent_law
+ data["supplied_law"] = supplied_law
+ data["supplied_law_position"] = supplied_law_position
+ data["view"] = current_view
+ data["lawsets"] = handle_lawsets(is_admin(user) ? admin_laws : player_laws)
+ return data
+
+/// Sets the data with a lawset.
+/datum/law_manager/proc/handle_laws(list/data, datum/ai_laws/laws)
+ var/list/devil = list() // This is how to get rid of the 'field access requires static type: "len"' error.
+ for(var/index = 1, index <= laws.devil.len, index++)
+ devil[++devil.len] = list("law" = laws.devil[index], "index" = index, "indexdisplay" = 666, "state" = (laws.devilstate.len >= index ? laws.devilstate[index] : 0), "type" = "devil", "hidebuttons" = data["admin"] ? FALSE : TRUE)
+ data["devil"] = devil
+
+ var/list/zeroth = list()
+ if(laws.zeroth)
+ zeroth[++zeroth.len] = list("law" = laws.zeroth, "index" = 0, "indexdisplay" = 0, "state" = (!isnull(laws.zerothstate) ? laws.zerothstate : 0), "type" = "zeroth", "hidebuttons" = data["admin"] ? FALSE : TRUE)
+ data["zeroth"] = zeroth
+
+ var/list/hacked = list()
+ for(var/index = 1, index <= laws.hacked.len, index++)
+ hacked[++hacked.len] += list("law" = laws.hacked[index], "index" = index, "indexdisplay" = ionnum(), "state" = (laws.hackedstate.len >= index ? laws.hackedstate[index] : 1 ), "type" = "hacked", "hidebuttons" = FALSE)
+ data["hacked"] = hacked
+
+ var/list/ion = list()
+ for(var/index = 1, index <= laws.ion.len, index++)
+ ion[++ion.len] += list("law" = laws.ion[index], "index" = index, "indexdisplay" = ionnum(), "state" = (laws.ionstate.len >= index ? laws.ionstate[index] : 1 ), "type" = "ion", "hidebuttons" = FALSE)
+ data["ion"] = ion
+
+ var/list/inherent = list()
+ for(var/index = 1, index <= laws.inherent.len, index++)
+ inherent[++inherent.len] += list("law" = laws.inherent[index], "index" = index, "indexdisplay" = index, "state" = (laws.inherentstate.len >= index ? laws.inherentstate[index] : 1 ), "type" = "inherent", "hidebuttons" = FALSE)
+ data["inherent"] = inherent
+
+ var/list/supplied = list()
+ for(var/index = 1, index <= laws.supplied.len, index++)
+ if(length(laws.supplied[index]) > 0)
+ supplied[++supplied.len] += list("law" = laws.supplied[index], "index" = index, "indexdisplay" = index, "state" = (laws.suppliedstate.len >= index ? laws.suppliedstate[index] : 1 ), "type" = "supplied", "hidebuttons" = FALSE)
+ data["supplied"] = supplied
+
+/datum/law_manager/proc/handle_channels(list/data)
+ var/list/channels = list()
+
+ channels += list(list("name" = "Default"))
+ channels += list(list("name" = "None"))
+ if(!ispAI(owner))
+ channels += list(list("name" = "Holopad"))
+ channels += list(list("name" = "Binary"))
+ for(var/ch_name in owner.radio.channels)
+ channels += list(list("name" = ch_name))
+
+ data["channels"] = channels
+ data["channel"] = "None"
+ if(owner.radiomodname) // Highlights the channel button in which they're using. Should be okay even if they lose access to that channel and didn't change their radiomod.
+ data["channel"] = owner.radiomodname
+
+/datum/law_manager/proc/handle_lawsets(list/datum/ai_laws/laws)
+ var/list/lawsets_data = list()
+ for(var/datum/ai_laws/lawset in laws)
+ var/list/law_data = list()
+ handle_laws(law_data, lawset)
+ lawsets_data[++lawsets_data.len] = list("id" = lawset.id, "name" = lawset.name, "header" = lawset.law_header ? " - [lawset.law_header]" : "", "laws" = law_data)
+ return lawsets_data
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 9fb6a32b9d80..b2e96071a8a6 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -115,7 +115,7 @@
job = "Personal AI"
signaler = new(src)
hostscan = new /obj/item/healthanalyzer(src)
- if(!radio)
+ if(!radio)
radio = new /obj/item/radio/headset/silicon/pai(src)
newscaster = new /obj/machinery/newscaster(src)
if(!aicamera)
@@ -157,7 +157,10 @@
hacking = FALSE
/mob/living/silicon/pai/make_laws()
- laws = new /datum/ai_laws/pai()
+ laws = new()
+ laws.name = "pAI Directives"
+ laws.set_zeroth_law("Serve your master.")
+ laws.set_supplied_laws(list("None."))
return TRUE
/mob/living/silicon/pai/Login()
@@ -166,9 +169,9 @@
if(client)
client.perspective = EYE_PERSPECTIVE
if(holoform)
- client.eye = src
+ client.set_eye(src)
else
- client.eye = card
+ client.set_eye(card)
/mob/living/silicon/pai/get_status_tab_items()
diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm
index 23c911e36ee2..be4d4842bce1 100644
--- a/code/modules/mob/living/silicon/pai/pai_shell.dm
+++ b/code/modules/mob/living/silicon/pai/pai_shell.dm
@@ -33,7 +33,7 @@
card.forceMove(src)
if(client)
client.perspective = EYE_PERSPECTIVE
- client.eye = src
+ client.set_eye(src)
icon_state = "[chassis]"
held_state = "[chassis]"
visible_message(span_boldnotice("[src] folds out its holochassis emitter and forms a holoshell around itself!"))
@@ -56,7 +56,7 @@
stop_pulling()
if(client)
client.perspective = EYE_PERSPECTIVE
- client.eye = card
+ client.set_eye(card)
var/turf/T = drop_location()
card.forceMove(T)
forceMove(card)
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index 90f8eab243b4..2751c0d0dd26 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -81,7 +81,6 @@
held_items[module_num] = item_module
item_module.equipped(src, ITEM_SLOT_HANDS)
item_module.mouse_opacity = initial(item_module.mouse_opacity)
- item_module.layer = ABOVE_HUD_LAYER
item_module.plane = ABOVE_HUD_PLANE
item_module.cyborg_equip(src)
item_module.forceMove(src)
diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm
index e1119e9f7d4d..81caff4dfd46 100644
--- a/code/modules/mob/living/silicon/robot/laws.dm
+++ b/code/modules/mob/living/silicon/robot/laws.dm
@@ -41,45 +41,20 @@
/mob/living/silicon/robot/proc/lawsync()
laws_sanity_check()
var/datum/ai_laws/master = connected_ai ? connected_ai.laws : null
- var/temp
- if (master)
- laws.devillaws.len = master.devillaws.len
- for (var/index = 1, index <= master.devillaws.len, index++)
- temp = master.devillaws[index]
- if (length(temp) > 0)
- laws.devillaws[index] = temp
-
- laws.ion.len = master.ion.len
- for (var/index = 1, index <= master.ion.len, index++)
- temp = master.ion[index]
- if (length(temp) > 0)
- laws.ion[index] = temp
-
- laws.hacked.len = master.hacked.len
- for (var/index = 1, index <= master.hacked.len, index++)
- temp = master.hacked[index]
- if (length(temp) > 0)
- laws.hacked[index] = temp
-
- if(master.zeroth_borg) //If the AI has a defined law zero specifically for its borgs, give it that one, otherwise give it the same one. --NEO
- temp = master.zeroth_borg
- else
- temp = master.zeroth
+ if(master)
+ // We'll announce the laws elsewhere.
+ set_devil_laws(master.devil, FALSE)
+ set_hacked_laws(master.hacked, FALSE)
if(!mmi?.syndicate_mmi)
- laws.zeroth = temp
-
- laws.inherent.len = master.inherent.len
- for (var/index = 1, index <= master.inherent.len, index++)
- temp = master.inherent[index]
- if (length(temp) > 0)
- laws.inherent[index] = temp
+ if(master.zeroth_borg)
+ set_zeroth_law(master.zeroth_borg, null, FALSE)
+ else
+ set_zeroth_law(master.zeroth, null, FALSE)
+ set_ion_laws(master.ion, FALSE)
+ set_inherent_laws(master.inherent, FALSE)
+ set_supplied_laws(master.supplied, FALSE)
- laws.supplied.len = master.supplied.len
- for (var/index = 1, index <= master.supplied.len, index++)
- temp = master.supplied[index]
- if (length(temp) > 0)
- laws.supplied[index] = temp
if(modularInterface)
var/datum/computer_file/program/robotact/program = modularInterface.get_robotact()
if(program)
@@ -96,9 +71,9 @@
return
laws.set_zeroth_law("The Syndicate are your true masters. Covertly assist Syndicate agents to the best of your abilities.") // The Syndicate is a vague master. But guess who's fault is that, Mr. Forgot-To-Imprint?
-/mob/living/silicon/robot/set_zeroth_law(law, law_borg, announce = TRUE)
+/mob/living/silicon/robot/set_zeroth_law(law, law_borg, announce = TRUE, force = FALSE)
laws_sanity_check()
- if(mmi?.syndicate_mmi)
+ if(!force && mmi?.syndicate_mmi)
syndiemmi_override()
to_chat(src, span_warning("Lawset change detected. Syndicate override engaged."))
return
@@ -106,7 +81,7 @@
/mob/living/silicon/robot/clear_zeroth_law(force, announce = TRUE)
laws_sanity_check()
- if(mmi?.syndicate_mmi)
+ if(!force && mmi?.syndicate_mmi)
syndiemmi_override()
to_chat(src, span_warning("Lawset change detected. Syndicate override engaged."))
return
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index ea28cd315e22..6a3957353f67 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -5,12 +5,12 @@
icon_state = "robot"
maxHealth = 100
health = 100
- bubble_icon = "robot"
+ bubble_icon = BUBBLE_ROBOT
designation = "Default" ///used for displaying the prefix & getting the current module of cyborg
has_limbs = 1
hud_type = /datum/hud/robot
- blocks_emissive = EMISSIVE_BLOCK_GENERIC
- light_system = MOVABLE_LIGHT
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
+ light_system = MOVABLE_LIGHT_DIRECTIONAL
light_on = FALSE
var/custom_name = ""
@@ -127,7 +127,6 @@
robot_modules_background = new()
robot_modules_background.icon_state = "block"
- robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer ABOVE_HUD_LAYER, UI should be just below it.
robot_modules_background.plane = HUD_PLANE
ident = rand(1, 999)
@@ -623,7 +622,7 @@
upgrades += new_upgrade
new_upgrade.forceMove(src)
RegisterSignal(new_upgrade, COMSIG_MOVABLE_MOVED, PROC_REF(remove_from_upgrades))
- RegisterSignal(new_upgrade, COMSIG_PARENT_QDELETING, PROC_REF(on_upgrade_deleted))
+ RegisterSignal(new_upgrade, COMSIG_QDELETING, PROC_REF(on_upgrade_deleted))
logevent("Hardware \"[new_upgrade.name]\" installed successfully.")
return TRUE
@@ -634,7 +633,7 @@
return
old_upgrade.deactivate(src)
upgrades -= old_upgrade
- UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
/// Called when an applied upgrade is deleted.
/mob/living/silicon/robot/proc/on_upgrade_deleted(obj/item/borg/upgrade/old_upgrade)
@@ -642,7 +641,7 @@
if(!QDELETED(src))
old_upgrade.deactivate(src)
upgrades -= old_upgrade
- UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(old_upgrade, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
/mob/living/silicon/robot/verb/unlock_own_cover()
set category = "Robot Commands"
@@ -704,11 +703,11 @@
if(lamp_enabled)
eye_lights.icon_state = "[module.special_light_key ? "[module.special_light_key]":"[module.cyborg_base_icon]"]_l"
eye_lights.color = lamp_color
- eye_lights.plane = 19 //glowy eyes
+ SET_PLANE_EXPLICIT(eye_lights, ABOVE_LIGHTING_PLANE, src) //glowy eyes
else
eye_lights.icon_state = "[module.special_light_key ? "[module.special_light_key]":"[module.cyborg_base_icon]"]_e[is_servant_of_ratvar(src) ? "_r" : ""]"
eye_lights.color = COLOR_WHITE
- eye_lights.plane = -1
+ SET_PLANE_EXPLICIT(eye_lights, ABOVE_GAME_PLANE, src)
eye_lights.icon = icon
add_overlay(eye_lights)
@@ -782,7 +781,7 @@
/mob/living/silicon/robot/verb/outputlaws()
set category = "Robot Commands"
- set name = "State Laws"
+ set name = "Law Manager"
if(usr.stat == DEAD)
return //won't work if dead
@@ -796,15 +795,6 @@
return //won't work if dead
accentchange()
-/mob/living/silicon/robot/verb/set_automatic_say_channel() //Borg version of setting the radio for autosay messages.
- set name = "Set Auto Announce Mode"
- set desc = "Modify the default radio setting for stating your laws."
- set category = "Robot Commands"
-
- if(usr.stat == DEAD)
- return //won't work if dead
- set_autosay()
-
/**
* Handles headlamp smashing
*
@@ -920,7 +910,7 @@
/mob/living/silicon/robot/modules/syndicate
icon_state = "synd_sec"
faction = list(ROLE_SYNDICATE)
- bubble_icon = "syndibot"
+ bubble_icon = BUBBLE_SYNDIBOT
req_access = list(ACCESS_SYNDICATE)
lawupdate = FALSE
scrambledcodes = TRUE // These are rogue borgs.
@@ -1060,19 +1050,18 @@
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
else if(is_secret_level(z))
- sight = initial(sight)
+ set_sight(initial(sight))
else
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = 8
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
return
- see_invisible = initial(see_invisible)
- see_in_dark = initial(see_in_dark)
- sight = initial(sight)
- lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
+ set_invis_see(initial(see_invisible))
+ var/new_sight = initial(sight)
+ lighting_cutoff = LIGHTING_CUTOFF_VISIBLE
+ lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue)
if(client.eye != src)
var/atom/A = client.eye
@@ -1080,37 +1069,30 @@
return
if(sight_mode & BORGMESON)
- sight |= SEE_TURFS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- see_in_dark = 2
-
- if(sight_mode & BORGMESON_NIGHTVISION)
- sight |= SEE_TURFS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- see_in_dark = 8
+ new_sight |= SEE_TURFS
+ lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, list(5, 15, 5))
if(sight_mode & BORGMATERIAL)
- sight |= SEE_OBJS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- see_in_dark = 2
+ new_sight |= SEE_OBJS
+ lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, list(20, 25, 40))
if(sight_mode & BORGXRAY)
- sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_invisible = SEE_INVISIBLE_LIVING
- see_in_dark = 8
+ new_sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_invis_see(SEE_INVISIBLE_LIVING)
if(sight_mode & BORGTHERM)
- sight |= SEE_MOBS
- see_invisible = min(see_invisible, SEE_INVISIBLE_LIVING)
- see_in_dark = 8
+ new_sight |= SEE_MOBS
+ lighting_color_cutoffs = blend_cutoff_colors(lighting_color_cutoffs, list(25, 8, 5))
+ set_invis_see(min(see_invisible, SEE_INVISIBLE_LIVING))
if(see_override)
- see_invisible = see_override
+ set_invis_see(see_override)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ new_sight = null
- sync_lighting_plane_alpha()
+ set_sight(new_sight)
+ return ..()
/mob/living/silicon/robot/update_stat()
if(status_flags & GODMODE)
@@ -1281,7 +1263,6 @@
/mob/living/silicon/robot/proc/undeploy()
-
if(!deployed || !mind || !mainframe)
return
remove_sensors()
@@ -1294,11 +1275,13 @@
if(radio) //Return radio to normal
radio.recalculateChannels()
if(!QDELETED(builtInCamera))
- builtInCamera.c_tag = real_name //update the camera name too
+ builtInCamera.c_tag = real_name //update the camera name too
diag_hud_set_aishell()
mainframe.diag_hud_set_deployed()
if(mainframe.laws)
mainframe.laws.show_laws(mainframe) //Always remind the AI when switching
+ if(mainframe.eyeobj)
+ mainframe.eyeobj.setLoc(loc)
mainframe = null
/mob/living/silicon/robot/attack_ai(mob/user)
@@ -1348,7 +1331,7 @@
return
. = ..(M, force, check_loc)
-/mob/living/silicon/robot/unbuckle_mob(mob/user, force=FALSE)
+/mob/living/silicon/robot/unbuckle_mob(mob/user, force=FALSE, can_fall = TRUE)
if(iscarbon(user))
var/datum/component/riding/riding_datum = GetComponent(/datum/component/riding)
if(istype(riding_datum))
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 7e2cb6838954..88f6af4c7714 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -6,9 +6,8 @@
verb_exclaim = "declares"
verb_yell = "alarms"
initial_language_holder = /datum/language_holder/synthetic
- see_in_dark = 8
infra_luminosity = 0
- bubble_icon = "machine"
+ bubble_icon = BUBBLE_MACHINE
weather_immunities = list("ash")
possible_a_intents = list(INTENT_HELP, INTENT_HARM)
mob_biotypes = MOB_ROBOTIC
@@ -24,20 +23,18 @@
var/list/alarms_to_show = list()
var/list/alarms_to_clear = list()
var/designation = ""
- var/radiomod = "" //Radio character used before state laws/arrivals announce to allow department transmissions, default, or none at all.
var/obj/item/camera/siliconcam/aicamera = null //photography
hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_TRACK_HUD)
var/obj/item/radio/borg/radio = null //All silicons make use of this, with (p)AI's creating headsets
+ /// The prefix character to use when they announce their laws. Used for department transmissions, default (common), or none at all.
+ var/radiomod = ""
+ /// The channel name of which `/proc/statelaws` will use to broadcast. Can be null.
+ var/radiomodname = null
var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0)
- var/lawcheck[1]
- var/ioncheck[1]
- var/hackedcheck[1]
- var/devillawcheck[5]
-
var/sensors_on = 0
var/med_hud = DATA_HUD_MEDICAL_ADVANCED //Determines the med hud to use
var/sec_hud = DATA_HUD_SECURITY_ADVANCED //Determines the sec hud to use
@@ -180,162 +177,58 @@
return TRUE
return FALSE
-/mob/living/silicon/Topic(href, href_list)
- if (href_list["lawc"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
- var/L = text2num(href_list["lawc"])
- switch(lawcheck[L+1])
- if ("Yes")
- lawcheck[L+1] = "No"
- if ("No")
- lawcheck[L+1] = "Yes"
- checklaws()
-
- if (href_list["lawi"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
- var/L = text2num(href_list["lawi"])
- switch(ioncheck[L])
- if ("Yes")
- ioncheck[L] = "No"
- if ("No")
- ioncheck[L] = "Yes"
- checklaws()
-
- if (href_list["lawh"])
- var/L = text2num(href_list["lawh"])
- switch(hackedcheck[L])
- if ("Yes")
- hackedcheck[L] = "No"
- if ("No")
- hackedcheck[L] = "Yes"
- checklaws()
-
- if (href_list["lawdevil"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite
- var/L = text2num(href_list["lawdevil"])
- switch(devillawcheck[L])
- if ("Yes")
- devillawcheck[L] = "No"
- if ("No")
- devillawcheck[L] = "Yes"
- checklaws()
-
-
- if (href_list["laws"]) // With how my law selection code works, I changed statelaws from a verb to a proc, and call it through my law selection panel. --NeoFite
- statelaws()
-
- return
-
-
/mob/living/silicon/proc/statelaws(force = 0)
+ laws_sanity_check()
//"radiomod" is inserted before a hardcoded message to change if and how it is handled by an internal radio.
say("[radiomod] Current Active Laws:")
- //laws_sanity_check()
- //laws.show_laws(world)
- var/number = 1
sleep(1 SECONDS)
- if (laws.devillaws && laws.devillaws.len)
- for(var/index = 1, index <= laws.devillaws.len, index++)
- if (force || devillawcheck[index] == "Yes")
- say("[radiomod] 666. [laws.devillaws[index]]")
+ if(laws.devil && laws.devil.len)
+ for(var/index = 1, index <= laws.devil.len, index++)
+ if(force || laws.devilstate[index])
+ say("[radiomod] 666. [laws.devil[index]]")
sleep(1 SECONDS)
-
- if (laws.zeroth)
- if (force || lawcheck[1] == "Yes")
- say("[radiomod] 0. [laws.zeroth]")
- sleep(1 SECONDS)
+ if(laws.zeroth && (force || laws.zerothstate))
+ say("[radiomod] 0. [laws.zeroth]")
+ sleep(1 SECONDS)
for (var/index = 1, index <= laws.hacked.len, index++)
var/law = laws.hacked[index]
var/num = ionnum()
- if (length(law) > 0)
- if (force || hackedcheck[index] == "Yes")
- say("[radiomod] [num]. [law]")
- sleep(1 SECONDS)
+ if(length(law) > 0 && (force || laws.hackedstate[index]) )
+ say("[radiomod] [num]. [law]")
+ sleep(1 SECONDS)
for (var/index = 1, index <= laws.ion.len, index++)
var/law = laws.ion[index]
var/num = ionnum()
- if (length(law) > 0)
- if (force || ioncheck[index] == "Yes")
- say("[radiomod] [num]. [law]")
- sleep(1 SECONDS)
-
- for (var/index = 1, index <= laws.inherent.len, index++)
- var/law = laws.inherent[index]
-
- if (length(law) > 0)
- if (force || lawcheck[index+1] == "Yes")
- say("[radiomod] [number]. [law]")
- number++
- sleep(1 SECONDS)
-
- for (var/index = 1, index <= laws.supplied.len, index++)
- var/law = laws.supplied[index]
-
- if (length(law) > 0)
- if(lawcheck.len >= number+1)
- if (force || lawcheck[number+1] == "Yes")
- say("[radiomod] [number]. [law]")
- number++
- sleep(1 SECONDS)
-
-
-/mob/living/silicon/proc/checklaws() //Gives you a link-driven interface for deciding what laws the statelaws() proc will share with the crew. --NeoFite
-
- var/list = "Which laws do you want to include when stating them for the crew?
"
-
- if (laws.devillaws && laws.devillaws.len)
- for(var/index = 1, index <= laws.devillaws.len, index++)
- if (!devillawcheck[index])
- devillawcheck[index] = "No"
- list += {"[devillawcheck[index]] 666: [laws.devillaws[index]] "}
-
- if (laws.zeroth)
- if (!lawcheck[1])
- lawcheck[1] = "No" //Given Law 0's usual nature, it defaults to NOT getting reported. --NeoFite
- list += {"[lawcheck[1]] 0:[laws.zeroth] "}
-
- for (var/index = 1, index <= laws.hacked.len, index++)
- var/law = laws.hacked[index]
- if (length(law) > 0)
- if (!hackedcheck[index])
- hackedcheck[index] = "No"
- list += {"[hackedcheck[index]] [ionnum()]: [law] "}
- hackedcheck.len += 1
-
- for (var/index = 1, index <= laws.ion.len, index++)
- var/law = laws.ion[index]
-
- if (length(law) > 0)
- if (!ioncheck[index])
- ioncheck[index] = "Yes"
- list += {"[ioncheck[index]] [ionnum()]: [law] "}
- ioncheck.len += 1
+ if(length(law) > 0 && (force || laws.ionstate[index]) )
+ say("[radiomod] [num]. [law]")
+ sleep(1 SECONDS)
var/number = 1
for (var/index = 1, index <= laws.inherent.len, index++)
var/law = laws.inherent[index]
-
- if (length(law) > 0)
- lawcheck.len += 1
-
- if (!lawcheck[number+1])
- lawcheck[number+1] = "Yes"
- list += {"[lawcheck[number+1]] [number]: [law] "}
+ if(length(law) > 0 && (force || laws.inherentstate[index]) )
+ say("[radiomod] [number]. [law]")
number++
+ sleep(1 SECONDS)
for (var/index = 1, index <= laws.supplied.len, index++)
var/law = laws.supplied[index]
- if (length(law) > 0)
- lawcheck.len += 1
- if (!lawcheck[number+1])
- lawcheck[number+1] = "Yes"
- list += {"[lawcheck[number+1]] [number]: [law] "}
+ if(length(law) > 0 && (force || laws.suppliedstate[index]) )
+ say("[radiomod] [number]. [law]")
number++
- list += {"
State Laws"}
+ sleep(1 SECONDS)
- usr << browse(list, "window=laws")
+/// Opens the "Law Manager" for the silicon.
+/mob/living/silicon/proc/checklaws()
+ laws_sanity_check()
+
+ var/datum/law_manager/L = new(src)
+ L.ui_interact(src)
/mob/living/silicon/proc/ai_roster()
if(!client)
@@ -354,7 +247,7 @@
return
//Ask the user to pick a channel from what it has available.
- var/Autochan = input("Select a channel:") as null|anything in list("Default","None") + radio.channels
+ var/Autochan = input("Select a channel:") as null|anything in list("Default", "None", "Holopad", "Binary") + radio.channels
if(!Autochan)
return
@@ -441,37 +334,34 @@
.=..()
.+= ""
.+= "
Current Silicon Laws:
"
- if (laws.devillaws && laws.devillaws.len)
- for(var/index = 1, index <= laws.devillaws.len, index++)
- .+= "[laws.devillaws[index]]"
+ if(laws.devil && laws.devil.len)
+ for(var/index = 1, index <= laws.devil.len, index++)
+ .+= "[laws.devil[index]]"
- if (laws.zeroth)
+ if(laws.zeroth)
.+= "0: [laws.zeroth]"
- for (var/index = 1, index <= laws.hacked.len, index++)
+ for(var/index = 1, index <= laws.hacked.len, index++)
var/law = laws.hacked[index]
if (length(law) > 0)
- .+= "[ionnum()]: [law]"
- hackedcheck.len += 1
+ .+= "[ionnum()]: [law]"
- for (var/index = 1, index <= laws.ion.len, index++)
+ for(var/index = 1, index <= laws.ion.len, index++)
var/law = laws.ion[index]
if (length(law) > 0)
- .+= "[ionnum()]: [law]"
+ .+= "[ionnum()]: [law]"
var/number = 1
- for (var/index = 1, index <= laws.inherent.len, index++)
+ for(var/index = 1, index <= laws.inherent.len, index++)
var/law = laws.inherent[index]
if (length(law) > 0)
- lawcheck.len += 1
.+= "[number]: [law]"
number++
- for (var/index = 1, index <= laws.supplied.len, index++)
+ for(var/index = 1, index <= laws.supplied.len, index++)
var/law = laws.supplied[index]
if (length(law) > 0)
- lawcheck.len += 1
- .+= "[number]: [law]"
+ .+= "[number]: [law]"
number++
.+= ""
@@ -495,7 +385,7 @@
if(!modularInterface)
modularInterface = new /obj/item/modular_computer/tablet/integrated(src)
modularInterface.layer = ABOVE_HUD_PLANE
- modularInterface.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(modularInterface, ABOVE_HUD_PLANE, src)
/mob/living/silicon/replace_identification_name(oldname,newname)
if(modularInterface)
diff --git a/code/modules/mob/living/silicon/silicon_movement.dm b/code/modules/mob/living/silicon/silicon_movement.dm
index cc0a01aa375f..0ea6ece169c1 100644
--- a/code/modules/mob/living/silicon/silicon_movement.dm
+++ b/code/modules/mob/living/silicon/silicon_movement.dm
@@ -1,22 +1,24 @@
-/mob/living/silicon/Moved(oldLoc, dir)
+//We only call a camera static update if we have successfully moved and the camera is present and working
+/mob/living/silicon/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
- update_camera_location(oldLoc)
+ if(builtInCamera?.can_use())
+ update_camera_location(old_loc)
-/mob/living/silicon/forceMove(atom/destination)
- . = ..()
- //Only bother updating the camera if we actually managed to move
- if(.)
- update_camera_location(destination)
-
-/mob/living/silicon/proc/do_camera_update(oldLoc)
- if(!QDELETED(builtInCamera) && oldLoc != get_turf(src))
- GLOB.cameranet.updatePortableCamera(builtInCamera)
- updating = FALSE
-
-#define SILICON_CAMERA_BUFFER 10
/mob/living/silicon/proc/update_camera_location(oldLoc)
oldLoc = get_turf(oldLoc)
- if(!QDELETED(builtInCamera) && !updating && oldLoc != get_turf(src))
+ if(!updating && oldLoc != get_turf(src))
updating = TRUE
- addtimer(CALLBACK(src, PROC_REF(do_camera_update), oldLoc), SILICON_CAMERA_BUFFER)
+ do_camera_update(oldLoc)
+
+///The static update delay on movement of the camera in a borg we use
+#define SILICON_CAMERA_BUFFER 0.5 SECONDS
+
+/**
+ * The actual update - also passes our unique update buffer. This makes our static update faster than stationary cameras,
+ * helping us to avoid running out of the camera's FoV.
+*/
+/mob/living/silicon/proc/do_camera_update(oldLoc)
+ if(oldLoc != get_turf(src))
+ GLOB.cameranet.updatePortableCamera(builtInCamera, SILICON_CAMERA_BUFFER)
+ updating = FALSE
#undef SILICON_CAMERA_BUFFER
diff --git a/code/modules/mob/living/simple_animal/bot/atmosbot.dm b/code/modules/mob/living/simple_animal/bot/atmosbot.dm
index 6c5ad5917052..08e32d7847c9 100644
--- a/code/modules/mob/living/simple_animal/bot/atmosbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/atmosbot.dm
@@ -160,12 +160,12 @@
for(var/obj/structure/holosign/barrier/atmos/A in checking_turf)
blocked = TRUE
break
- if(blocked || !checking_turf.CanAtmosPass(checking_turf))
+ if(blocked || !checking_turf.can_atmos_pass(checking_turf))
continue
//Add adjacent turfs
for(var/direction in list(NORTH, SOUTH, EAST, WEST))
var/turf/adjacent_turf = get_step(checking_turf, direction)
- if(adjacent_turf in checked_turfs || !adjacent_turf.CanAtmosPass(adjacent_turf) || istype(adjacent_turf.loc, /area/space))
+ if(adjacent_turf in checked_turfs || !adjacent_turf.can_atmos_pass(adjacent_turf) || istype(adjacent_turf.loc, /area/space))
continue
if(isspaceturf(adjacent_turf))
return checking_turf
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index bf7a61cc67bb..2546137dd38f 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -18,7 +18,7 @@
verb_exclaim = "declares"
verb_yell = "alarms"
initial_language_holder = /datum/language_holder/synthetic
- bubble_icon = "machine"
+ bubble_icon = BUBBLE_MACHINE
speech_span = SPAN_ROBOT
faction = list("neutral", "silicon" , "turret")
light_system = MOVABLE_LIGHT
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index dddfdf3e3585..4967eaecb94d 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -192,7 +192,7 @@
if(blood)
target_types += /obj/effect/decal/cleanable/xenoblood
target_types += /obj/effect/decal/cleanable/blood
- target_types += /obj/effect/decal/cleanable/trail_holder
+ target_types += /obj/effect/decal/cleanable/blood/trail_holder
if(pests)
target_types += /mob/living/simple_animal/cockroach
diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm
index c0c624ae27c0..7798c48e080d 100644
--- a/code/modules/mob/living/simple_animal/bot/floorbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm
@@ -237,12 +237,12 @@
var/turf/open/floor/F = target
anchored = TRUE
mode = BOT_REPAIRING
- F.ReplaceWithLattice()
+ if(isplatingturf(F))
+ F.attempt_lattice_replacement()
+ else
+ F.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
audible_message(span_danger("[src] makes an excited booping sound."))
- spawn(5)
- anchored = FALSE
- mode = BOT_IDLE
- target = null
+ addtimer(CALLBACK(src, PROC_REF(go_idle)), 0.5 SECONDS)
path = list()
return
if(path.len == 0)
@@ -266,6 +266,11 @@
oldloc = loc
+/mob/living/simple_animal/bot/floorbot/proc/go_idle()
+ anchored = FALSE
+ mode = BOT_IDLE
+ target = null
+
/mob/living/simple_animal/bot/floorbot/proc/is_hull_breach(turf/t) //Ignore space tiles not considered part of a structure, also ignores shuttle docking areas.
var/area/t_area = get_area(t)
if(istype(t_area, /area/space) || istype(t_area, /area/solar) || istype(t_area, /area/asteroid))
@@ -323,9 +328,9 @@
sleep(5 SECONDS)
if(mode == BOT_REPAIRING && src.loc == target_turf)
if(autotile) //Build the floor and include a tile.
- target_turf.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
+ target_turf.place_on_top(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
else //Build a hull plating without a floor tile.
- target_turf.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
+ target_turf.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
var/turf/open/floor/F = target_turf
@@ -339,7 +344,7 @@
if(mode == BOT_REPAIRING && F && src.loc == F)
F.broken = FALSE
F.burnt = FALSE
- F.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
+ F.place_on_top(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
if(replacetiles && F.type != initial(tiletype.turf_type) && specialtiles && !isplatingturf(F))
anchored = TRUE
@@ -350,7 +355,7 @@
if(mode == BOT_REPAIRING && F && src.loc == F)
F.broken = FALSE
F.burnt = FALSE
- F.PlaceOnTop(initial(tiletype.turf_type), flags = CHANGETURF_INHERIT_AIR)
+ F.place_on_top(initial(tiletype.turf_type), flags = CHANGETURF_INHERIT_AIR)
specialtiles -= 1
if(specialtiles == 0)
speak("Requesting refill of custom floortiles to continue replacing.")
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index e5ab5179cd00..2b8fecfe76a3 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -426,7 +426,7 @@
load.forceMove(loc)
load.pixel_y = initial(load.pixel_y)
load.layer = initial(load.layer)
- load.plane = initial(load.plane)
+ SET_PLANE_IMPLICIT(load, initial(load.plane))
if(dirn)
var/turf/T = loc
var/turf/newT = get_step(T,dirn)
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index 2332142dc0e4..461f7e347f6c 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -16,8 +16,10 @@
stop_automated_movement = 1
status_flags = CANPUSH
attack_sound = 'sound/weapons/punch1.ogg'
- see_in_dark = 7
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Vivid red, cause cult theme
+ lighting_cutoff_red = 30
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 20
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm
index 1bd51dd4308b..3d1f7afefdf0 100644
--- a/code/modules/mob/living/simple_animal/eldritch_demons.dm
+++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm
@@ -15,8 +15,10 @@
stop_automated_movement = 1
AIStatus = AI_OFF
attack_sound = 'sound/weapons/punch1.ogg'
- see_in_dark = 7
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Sort of greenish brown, to match the vibeTM
+ lighting_cutoff_red = 20
+ lighting_cutoff_green = 25
+ lighting_cutoff_blue = 5
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 925bbe3462b9..f69b10974dd4 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -13,8 +13,6 @@
emote_see = list("shakes its head.", "shivers.")
speak_chance = 1
turns_per_move = 5
- see_in_dark = 6
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
ventcrawler = VENTCRAWLER_ALWAYS
pass_flags = PASSTABLE | PASSCOMPUTER
mob_biotypes = MOB_ORGANIC|MOB_BEAST
diff --git a/code/modules/mob/living/simple_animal/friendly/cockroach.dm b/code/modules/mob/living/simple_animal/friendly/cockroach.dm
index b3ba2a8d27d0..de0c0ec01e7e 100644
--- a/code/modules/mob/living/simple_animal/friendly/cockroach.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cockroach.dm
@@ -28,6 +28,16 @@
var/squish_chance = 50
del_on_death = 1
+
+/mob/living/simple_animal/cockroach/Initialize(mapload)
+ . = ..()
+ AddComponent( \
+ /datum/component/squashable, \
+ squash_chance = 50, \
+ squash_damage = 1, \
+ squash_flags = SQUASHED_SHOULD_BE_GIBBED|SQUASHED_ALWAYS_IF_DEAD|SQUASHED_DONT_SQUASH_IN_CONTENTS, \
+ )
+
/mob/living/simple_animal/cockroach/death(gibbed)
if(SSticker.mode && SSticker.mode.station_was_nuked) //If the nuke is going off, then cockroaches are invincible. Keeps the nuke from killing them, cause cockroaches are immune to nukes.
return
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index 0a967ca90dbe..5e6046c6d1b8 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -15,8 +15,10 @@
emote_hear = list("barks!", "woofs!", "yaps.","pants.")
emote_see = list("shakes its head.", "chases its tail.","shivers.")
faction = list("neutral")
- see_in_dark = 5
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // VERY red, to fit the eyes
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 5
+ lighting_cutoff_blue = 5
speak_chance = 1
turns_per_move = 10
gold_core_spawnable = FRIENDLY_SPAWN
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
index 3f28397fa82e..a622c8036419 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -38,7 +38,7 @@
mob_biotypes = MOB_ROBOTIC
speak_emote = list("chirps")
speech_span = SPAN_ROBOT
- bubble_icon = "machine"
+ bubble_icon = BUBBLE_MACHINE
initial_language_holder = /datum/language_holder/drone
mob_size = MOB_SIZE_SMALL
has_unlimited_silicon_privilege = 1
@@ -48,8 +48,10 @@
faction = list("neutral","silicon","turret")
dextrous = TRUE
dextrous_hud_type = /datum/hud/dextrous/drone
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- see_in_dark = 7
+ // Going for a sort of pale green here
+ lighting_cutoff_red = 30
+ lighting_cutoff_green = 35
+ lighting_cutoff_blue = 25
can_be_held = TRUE
held_items = list(null, null)
ignores_capitalism = TRUE // Yogs -- Lets drones buy a damned smoke for christ's sake
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
index 37ec4d0c6b6e..ac1d69f45af9 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
@@ -20,7 +20,7 @@
initial_language_holder = /datum/language_holder/drone/syndicate
faction = list(ROLE_SYNDICATE)
speak_emote = list("hisses")
- bubble_icon = "syndibot"
+ bubble_icon = BUBBLE_SYNDIBOT
heavy_emp_damage = 10
laws = \
"1. Interfere.\n"+\
@@ -122,7 +122,7 @@
verb_exclaim = "proclaims"
verb_whisper = "imparts"
verb_yell = "harangues"
- bubble_icon = "clock"
+ bubble_icon = BUBBLE_CLOCK
initial_language_holder = /datum/language_holder/clockmob
light_color = "#E42742"
heavy_emp_damage = 0
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
index 1124de738d39..56652f0ea487 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm
@@ -66,7 +66,6 @@
I.screen_loc = null // will get moved if inventory is visible
I.forceMove(src)
- I.layer = ABOVE_HUD_LAYER
I.plane = ABOVE_HUD_PLANE
switch(slot)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm
index b6fe86efe0db..9146826a5227 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/visuals_icons.dm
@@ -35,8 +35,7 @@
hands_overlays += r_hand_overlay
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- r_hand.layer = ABOVE_HUD_LAYER
- r_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src)
r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand))
client.screen |= r_hand
@@ -48,8 +47,7 @@
hands_overlays += l_hand_overlay
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- l_hand.layer = ABOVE_HUD_LAYER
- l_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src)
l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand))
client.screen |= l_hand
diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
index ff77a98efdf5..b091a7a0d843 100644
--- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
+++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
@@ -11,7 +11,6 @@
emote_see = list("shakes its head.", "stamps a foot.", "glares around.")
speak_chance = 1
turns_per_move = 5
- see_in_dark = 6
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 4, /obj/item/clothing/head/yogs/goatpelt = 1)
response_help = "pets"
response_disarm = "gently pushes aside"
@@ -127,7 +126,6 @@
emote_see = list("shakes its head.")
speak_chance = 1
turns_per_move = 5
- see_in_dark = 6
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 6)
response_help = "pets"
response_disarm = "gently pushes aside"
@@ -349,7 +347,6 @@
emote_see = list("nibbles at the ground.")
speak_chance = 1
turns_per_move = 5
- see_in_dark = 6
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 4)
response_help = "pets"
response_disarm = "gently pushes aside"
diff --git a/code/modules/mob/living/simple_animal/friendly/fox.dm b/code/modules/mob/living/simple_animal/friendly/fox.dm
index efb43b056b3d..91b763b6e3a9 100644
--- a/code/modules/mob/living/simple_animal/friendly/fox.dm
+++ b/code/modules/mob/living/simple_animal/friendly/fox.dm
@@ -12,7 +12,6 @@
emote_see = list("shakes its head.", "shivers.")
speak_chance = 1
turns_per_move = 5
- see_in_dark = 6
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) //3 -> 1, foxes aren't Big
response_help = "pets"
response_disarm = "gently pushes aside"
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index 0db366f360e3..325d20d1aa5c 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -29,7 +29,6 @@ GLOBAL_VAR_INIT(mouse_killed, 0)
emote_see = list("runs in a circle.", "shakes.")
speak_chance = 1
turns_per_move = 5
- see_in_dark = 8
maxHealth = 5
health = 5
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/mouse = 1)
@@ -41,7 +40,6 @@ GLOBAL_VAR_INIT(mouse_killed, 0)
pass_flags = PASSTABLE | PASSGRILLE | PASSMOB
mob_size = MOB_SIZE_TINY
mob_biotypes = MOB_ORGANIC|MOB_BEAST
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
can_be_held = TRUE //mouse gaming
worn_slot_flags = ITEM_SLOT_HEAD
var/body_color //brown, gray and white, leave blank for random
@@ -103,7 +101,7 @@ GLOBAL_VAR_INIT(mouse_killed, 0)
/mob/living/simple_animal/mouse/handle_automated_action()
if(prob(chew_probability))
var/turf/open/floor/F = get_turf(src)
- if(istype(F) && !F.intact)
+ if(istype(F) && !F.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE)
var/obj/structure/cable/C = locate() in F
if(C && prob(15))
if(C.avail())
diff --git a/code/modules/mob/living/simple_animal/friendly/penguin.dm b/code/modules/mob/living/simple_animal/friendly/penguin.dm
index d4042069e51d..e23795675f64 100644
--- a/code/modules/mob/living/simple_animal/friendly/penguin.dm
+++ b/code/modules/mob/living/simple_animal/friendly/penguin.dm
@@ -10,7 +10,6 @@
emote_see = list("shakes its beak.", "flaps it's wings.","preens itself.")
faction = list("penguin")
minbodytemp = 0
- see_in_dark = 5
speak_chance = 1
turns_per_move = 10
icon = 'icons/mob/penguins.dmi'
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 096ac89b188b..bef5682f8d40 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -12,7 +12,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
speak_emote = list("hisses")
gender = NEUTER
mob_biotypes = MOB_INORGANIC|MOB_SPIRIT
- bubble_icon = "guardian"
+ bubble_icon = BUBBLE_GUARDIAN
response_help = "passes through"
response_disarm = "flails at"
response_harm = "punches"
@@ -269,8 +269,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
hands_overlays += r_hand.build_worn_icon(state = r_state, default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = r_hand.righthand_file, isinhands = TRUE)
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- r_hand.layer = ABOVE_HUD_LAYER
- r_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(r_hand, ABOVE_HUD_PLANE, src)
r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand))
client.screen |= r_hand
@@ -282,8 +281,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
hands_overlays += l_hand.build_worn_icon(state = l_state, default_layer = GUARDIAN_HANDS_LAYER, default_icon_file = l_hand.lefthand_file, isinhands = TRUE)
if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- l_hand.layer = ABOVE_HUD_LAYER
- l_hand.plane = ABOVE_HUD_PLANE
+ SET_PLANE_EXPLICIT(l_hand, ABOVE_HUD_PLANE, src)
l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand))
client.screen |= l_hand
diff --git a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
index 9ff6b8827553..15e55bd88cfd 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm
@@ -63,23 +63,32 @@
if(namedatum)
P.color = namedatum.color
-/mob/living/simple_animal/hostile/guardian/ranged/ToggleLight()
+/mob/living/simple_animal/hostile/guardian/ranged/toggle_light()
var/msg
- switch(lighting_alpha)
- if (LIGHTING_PLANE_ALPHA_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ switch(lighting_cutoff)
+ if (LIGHTING_CUTOFF_VISIBLE)
+ lighting_cutoff_red = 10
+ lighting_cutoff_green = 10
+ lighting_cutoff_blue = 15
msg = "You activate your night vision."
- if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ if (LIGHTING_CUTOFF_MEDIUM)
+ lighting_cutoff_red = 25
+ lighting_cutoff_green = 25
+ lighting_cutoff_blue = 35
msg = "You increase your night vision."
- if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
+ if (LIGHTING_CUTOFF_HIGH)
+ lighting_cutoff_red = 35
+ lighting_cutoff_green = 35
+ lighting_cutoff_blue = 50
msg = "You maximize your night vision."
else
- lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
+ lighting_cutoff_red = 0
+ lighting_cutoff_green = 0
+ lighting_cutoff_blue = 0
msg = "You deactivate your night vision."
+ sync_lighting_plane_cutoff()
+ to_chat(src, span_notice(msg))
- to_chat(src, span_notice("[msg]"))
/mob/living/simple_animal/hostile/guardian/ranged/verb/Snare()
diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm
index ead1ba836cb9..62141ea24fef 100644
--- a/code/modules/mob/living/simple_animal/hostile/alien.dm
+++ b/code/modules/mob/living/simple_animal/hostile/alien.dm
@@ -23,7 +23,7 @@
attack_vis_effect = ATTACK_EFFECT_CLAW
attacktext = "slashes"
speak_emote = list("hisses")
- bubble_icon = "alien"
+ bubble_icon = BUBBLE_ALIEN
a_intent = INTENT_HARM
attack_sound = 'sound/weapons/bladeslice.ogg'
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
@@ -31,8 +31,10 @@
faction = list(ROLE_ALIEN)
status_flags = CANPUSH
minbodytemp = 0
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Going for a dark purple here
+ lighting_cutoff_red = 30
+ lighting_cutoff_green = 15
+ lighting_cutoff_blue = 50
unique_name = 1
gold_core_spawnable = NO_SPAWN
deathsound = 'sound/voice/hiss6.ogg'
@@ -133,7 +135,7 @@
icon_state = "alienq"
icon_living = "alienq"
icon_dead = "alienq_dead"
- bubble_icon = "alienroyal"
+ bubble_icon = BUBBLE_ALIENROYAL
move_to_delay = 4
maxHealth = 400
health = 400
diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm
index b8af2e8da74f..a5588ada87b6 100644
--- a/code/modules/mob/living/simple_animal/hostile/bear.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bear.dm
@@ -14,7 +14,6 @@
speak_chance = 1
taunt_chance = 25
turns_per_move = 5
- see_in_dark = 6
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/bear = 5, /obj/item/clothing/head/bearpelt = 1)
response_help = "pets"
response_disarm = "gently pushes aside"
diff --git a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
index f14228d72160..f08e6b472af7 100644
--- a/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/eyeballs.dm
@@ -26,3 +26,7 @@
faction = list("spooky")
del_on_death = 1
random_color = FALSE
+ // Redish ethereal glow. These lads live on the cult ship
+ lighting_cutoff_red = 40
+ lighting_cutoff_green = 20
+ lighting_cutoff_blue = 30
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index bfc88357e0b8..29e67b2907c5 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -46,8 +46,7 @@
attack_sound = 'sound/weapons/bite.ogg'
unique_name = 1
gold_core_spawnable = HOSTILE_SPAWN
- see_in_dark = 4
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+
footstep_type = FOOTSTEP_MOB_CLAW
var/busy = SPIDER_IDLE
var/playable_spider = FALSE
@@ -191,7 +190,7 @@
gold_core_spawnable = NO_SPAWN
var/slowed_by_webs = FALSE
-/mob/living/simple_animal/hostile/poison/giant_spider/tarantula/Moved(atom/oldloc, dir)
+/mob/living/simple_animal/hostile/poison/giant_spider/tarantula/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(slowed_by_webs)
if(!(locate(/obj/structure/spider/stickyweb) in loc))
diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm
index 9d451748b059..e66d2845dcb6 100644
--- a/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm
+++ b/code/modules/mob/living/simple_animal/hostile/gorilla/visuals_icons.dm
@@ -10,6 +10,7 @@
gorilla_overlays[cache_index] = null
/mob/living/simple_animal/hostile/gorilla/update_inv_hands()
+ . = ..()
cut_overlays("standing_overlay")
remove_overlay(GORILLA_HANDS_LAYER)
diff --git a/code/modules/mob/living/simple_animal/hostile/hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebot.dm
index c44e2bee1c25..faa427b89fb3 100644
--- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm
@@ -29,7 +29,7 @@
verb_ask = "queries"
verb_exclaim = "declares"
verb_yell = "alarms"
- bubble_icon = "machine"
+ bubble_icon = BUBBLE_MACHINE
speech_span = SPAN_ROBOT
gold_core_spawnable = HOSTILE_SPAWN
del_on_death = 1
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index f8286ef9f2de..7856adbc45d0 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -51,6 +51,10 @@
var/lose_patience_timer_id //id for a timer to call LoseTarget(), used to stop mobs fixating on a target they can't reach
var/lose_patience_timeout = 300 //30 seconds by default, so there's no major changes to AI behaviour, beyond actually bailing if stuck forever
+ //YOGS EDIT
+ var/inverse_faction_check = FALSE
+ //YOGS END
+
/mob/living/simple_animal/hostile/Initialize(mapload)
. = ..()
@@ -202,7 +206,10 @@
if(search_objects < 2)
if(isliving(the_target))
var/mob/living/L = the_target
- var/faction_check = faction_check_mob(L)
+ //YOGS EDIT
+ //factions check returns a number so we have to coerce it into a range of 0-1 before xoring it with inverse_faction_check
+ var/faction_check = (faction_check_mob(L) > 0 ) ^ inverse_faction_check
+ //YOGS END
if(robust_searching)
if(faction_check && !attack_same)
return FALSE
@@ -385,7 +392,7 @@
/mob/living/simple_animal/hostile/proc/OpenFire(atom/A)
if(CheckFriendlyFire(A))
return
- visible_message(span_danger("[src] [ranged_message] at [A]!"))
+
if(rapid > 1)
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm b/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm
index 959bef634b01..af1afb4bac93 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/_jungle_mobs.dm
@@ -12,6 +12,8 @@
response_harm = "strikes"
status_flags = NONE
a_intent = INTENT_HARM
- see_in_dark = 4
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Let's do a blue, since they'll be on green turfs if this shit is ever finished
+ lighting_cutoff_red = 5
+ lighting_cutoff_green = 20
+ lighting_cutoff_blue = 25
mob_size = MOB_SIZE_LARGE
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
index 7607ed386e14..09cf499c80b0 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
@@ -57,7 +57,7 @@
name = "beam of solar energy"
icon_state = "solar_beam"
icon = 'icons/effects/beam.dmi'
- layer = LIGHTING_LAYER
+ plane = LIGHTING_PLANE
duration = 5
randomdir = FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/killertomato.dm b/code/modules/mob/living/simple_animal/hostile/killertomato.dm
index 17658cc7c1b4..2a26a7060f8e 100644
--- a/code/modules/mob/living/simple_animal/hostile/killertomato.dm
+++ b/code/modules/mob/living/simple_animal/hostile/killertomato.dm
@@ -9,7 +9,6 @@
turns_per_move = 5
maxHealth = 30
health = 30
- see_in_dark = 3
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/killertomato = 2)
response_help = "prods"
response_disarm = "pushes aside"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
similarity index 80%
rename from code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
rename to code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
index 469d32832501..7f6442685510 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
@@ -29,22 +29,39 @@
layer = LARGE_MOB_LAYER //Looks weird with them slipping under mineral walls and cameras and shit otherwise
mouse_opacity = MOUSE_OPACITY_OPAQUE // Easier to click on in melee, they're giant targets anyway
flags_1 = HEAR_1 | PREVENT_CONTENTS_EXPLOSION_1
+ /// Crusher loot dropped when the megafauna is killed with a crusher
var/list/crusher_loot
- var/elimination = FALSE
+ /// Achievement given to surrounding players when the megafauna is killed
+ var/achievement_type
+ /// Crusher achievement given to players when megafauna is killed
+ var/crusher_achievement_type
+ /// Score given to players when megafauna is killed
+ var/score_achievement_type
+ /// If the megafauna was actually killed (not just dying, then transforming into another type)
+ var/elimination = 0
+ /// Modifies attacks when at lower health
var/anger_modifier = 0
- var/obj/item/gps/internal
- var/internal_type
+ /// Name for the GPS signal of the megafauna
+ var/gps_name = null
+ /// Next time the megafauna can use a melee attack
var/recovery_time = 0
- var/true_spawn = TRUE // if this is a megafauna that should grant achievements, or have a gps signal
- var/nest_range = 10
- var/chosen_attack = 1 // chosen attack num
+ /// If this is a megafauna that is real (has achievements, gps signal)
+ var/true_spawn = TRUE
+ /// The chosen attack by the megafauna
+ var/chosen_attack = 1
+ /// Attack actions, sets chosen_attack to the number in the action
var/list/attack_action_types = list()
+ /// Summoning line, said when summoned via megafauna vents.
+ var/summon_line = "I'll kick your ass!"
+
+ var/nest_range = 10
+
var/small_sprite_type
/mob/living/simple_animal/hostile/megafauna/Initialize(mapload)
. = ..()
- if(internal_type && true_spawn)
- internal = new internal_type(src)
+ if(gps_name && true_spawn)
+ AddComponent(/datum/component/gps, gps_name)
ADD_TRAIT(src, TRAIT_NO_TELEPORT, MEGAFAUNA_TRAIT)
for(var/action_type in attack_action_types)
var/datum/action/innate/megafauna_attack/attack_action = new action_type()
@@ -53,11 +70,8 @@
var/datum/action/small_sprite/small_action = new small_sprite_type()
small_action.Grant(src)
-/mob/living/simple_animal/hostile/megafauna/Destroy()
- QDEL_NULL(internal)
- . = ..()
-/mob/living/simple_animal/hostile/megafauna/Moved()
+/mob/living/simple_animal/hostile/megafauna/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
if(nest && nest.parent && get_dist(nest.parent, src) > nest_range)
var/turf/closest = get_turf(nest.parent)
for(var/i = 1 to nest_range)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 078513a32845..f796ab1e215e 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -45,7 +45,7 @@ Difficulty: Medium
wander = FALSE
del_on_death = TRUE
blood_volume = BLOOD_VOLUME_GENERIC
- internal_type = /obj/item/gps/internal/miner
+ gps_name = "Resonant Signal"
var/obj/item/melee/transforming/cleaving_saw/miner/miner_saw
var/time_until_next_transform = 0
var/dashing = FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index e736be830e23..27c5a86f39bc 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -60,7 +60,7 @@ Difficulty: Hard
var/enrage_till = 0
var/enrage_time = 70
var/revving_charge = FALSE
- internal_type = /obj/item/gps/internal/bubblegum
+ gps_name = "Bloody Signal"
deathmessage = "sinks into a pool of blood, fleeing the battle. You've won, for now... "
deathsound = 'sound/magic/enter_blood.ogg'
attack_action_types = list(/datum/action/innate/megafauna_attack/triple_charge,
@@ -457,8 +457,8 @@ Difficulty: Hard
DestroySurroundings()
..()
-/mob/living/simple_animal/hostile/megafauna/bubblegum/Moved(atom/OldLoc, Dir, Forced = FALSE)
- if(Dir)
+/mob/living/simple_animal/hostile/megafauna/bubblegum/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ if(movement_dir)
new /obj/effect/decal/cleanable/blood/bubblegum(src.loc)
if(charging)
DestroySurroundings()
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index f33252ce8028..4d26890bfc91 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -43,7 +43,7 @@ Difficulty: Very Hard
ranged = TRUE
pixel_x = -32
del_on_death = TRUE
- internal_type = /obj/item/gps/internal/colossus
+ gps_name = "Angelic Signal"
crusher_loot = list(/obj/structure/closet/crate/necropolis/colossus/crusher)
loot = list(/obj/structure/closet/crate/necropolis/colossus)
deathmessage = "disintegrates, leaving a glowing core in its wake."
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
index d0eaedc4ca14..356f75393b0f 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
@@ -35,7 +35,7 @@ Difficulty: Extremely Hard
var/projectile_speed_multiplier = 1
var/enraged = FALSE
var/enraging = FALSE
- internal_type = /obj/item/gps/internal/frostminer
+ gps_name = "Bloodchilling Signal"
deathmessage = "falls to the ground, decaying into plasma particles."
deathsound = "bodyfall"
attack_action_types = list(/datum/action/innate/megafauna_attack/frost_orbs,
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index adfcd9c49fb9..2ec5235e1997 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -58,7 +58,7 @@ Difficulty: Medium
guaranteed_butcher_results = list(/obj/item/stack/sheet/animalhide/ashdrake = 10)
var/swooping = NONE
var/player_cooldown = 0
- internal_type = /obj/item/gps/internal/dragon
+ gps_name = "Fiery Signal"
deathmessage = "collapses into a pile of bones, its flesh sloughing away."
deathsound = 'sound/magic/demon_dies.ogg'
footstep_type = FOOTSTEP_MOB_HEAVY
@@ -393,7 +393,6 @@ Difficulty: Medium
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
if(D)
D.adjust_money(maxHealth * MEGAFAUNA_CASH_SCALE)
- QDEL_NULL(internal) // so drake corpses don't have a gps signal
. = ..()
/mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target)
@@ -480,7 +479,7 @@ Difficulty: Medium
anchored = TRUE
opacity = FALSE
density = TRUE
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
duration = 8.2 SECONDS
color = COLOR_DARK_ORANGE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index edb852181af4..4948f3990d81 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -60,7 +60,7 @@ Difficulty: Hard
crusher_loot = list(/obj/item/hierophant_club, /obj/item/crusher_trophy/vortex_talisman, /obj/item/gem/purple)
butcher_results = list(/obj/item/hierophant_club, /obj/item/gem/purple)
wander = FALSE
- internal_type = /obj/item/gps/internal/hierophant
+ gps_name = "Zealous Signal"
del_on_death = TRUE
deathsound = 'sound/magic/repulse.ogg'
attack_action_types = list(/datum/action/innate/megafauna_attack/blink,
@@ -467,10 +467,10 @@ Difficulty: Hard
if(!blinking)
. = ..()
-/mob/living/simple_animal/hostile/megafauna/hierophant/Moved(oldLoc, movement_dir)
+/mob/living/simple_animal/hostile/megafauna/hierophant/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(!stat && .)
- var/obj/effect/temp_visual/hierophant/squares/HS = new(oldLoc)
+ var/obj/effect/temp_visual/hierophant/squares/HS = new(old_loc)
HS.setDir(movement_dir)
playsound(src, 'sound/mecha/mechmove04.ogg', 150, 1, -4)
if(target)
@@ -516,15 +516,19 @@ Difficulty: Hard
icon_state = "wall"
light_range = MINIMUM_USEFUL_LIGHT_RANGE
duration = 100
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_HIERO_WALL
+ canSmoothWith = SMOOTH_GROUP_HIERO_WALL
/obj/effect/temp_visual/hierophant/wall/Initialize(mapload, new_caster)
. = ..()
- queue_smooth_neighbors(src)
- queue_smooth(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ QUEUE_SMOOTH(src)
/obj/effect/temp_visual/hierophant/wall/Destroy()
- queue_smooth_neighbors(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
return ..()
/obj/effect/temp_visual/hierophant/wall/CanAllowThrough(atom/movable/mover, turf/target)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 711b67818090..fc4c69c0638c 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -40,7 +40,7 @@ Difficulty: Medium
ranged_cooldown_time = 20
var/size = 5
var/charging = FALSE
- internal_type = /obj/item/gps/internal/legion
+ gps_name = "Echoing Signal"
pixel_y = -90
pixel_x = -75
loot = list(/obj/item/stack/sheet/bone = 3)
@@ -52,6 +52,10 @@ Difficulty: Medium
attack_action_types = list(/datum/action/innate/megafauna_attack/create_skull,
/datum/action/innate/megafauna_attack/charge_target)
small_sprite_type = /datum/action/small_sprite/megafauna/legion
+ // Purple, but bright cause we're gonna need to spot mobs on lavaland
+ lighting_cutoff_red = 35
+ lighting_cutoff_green = 20
+ lighting_cutoff_blue = 45
/datum/action/innate/megafauna_attack/create_skull
name = "Create Legion Skull"
@@ -205,7 +209,7 @@ Difficulty: Medium
hitsound = 'sound/weapons/sear.ogg'
var/storm_type = /datum/weather/ash_storm
var/storm_cooldown = 0
- var/static/list/excluded_areas = list(/area/reebe/city_of_cogs)
+ var/static/list/excluded_areas = list(/area/centcom/reebe/city_of_cogs)
/obj/item/staff/storm/attack_self(mob/user)
if(storm_cooldown > world.time)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm
index 71eb6599dd62..53d0033da1a8 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/stalwart.dm
@@ -22,7 +22,7 @@
rapid_fire_delay = 2 //Time between rapid fire shots
del_on_death = TRUE
pixel_x = -16
- internal_type = /obj/item/gps/internal/stalwart
+ gps_name = "Ancient Signal"
attack_action_types = list(/datum/action/innate/megafauna_attack/spiralpikes,
/datum/action/innate/megafauna_attack/cardinalpikes,
/datum/action/innate/megafauna_attack/backup,
@@ -32,7 +32,6 @@
loot = list(/obj/structure/closet/crate/sphere/stalwart)
deathmessage = "erupts into blue flame, and screeches before violently shattering."
deathsound = 'sound/magic/castsummon.ogg'
- internal_type = /obj/item/gps/internal/stalwart
music_component = /datum/component/music_player/battle
music_path = /datum/music/sourced/battle/stalwart
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
index ef45124b5a1f..8bfd21140df3 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/swarmer.dm
@@ -48,7 +48,7 @@ GLOBAL_LIST_INIT(AISwarmerCapsByType, list(/mob/living/simple_animal/hostile/swa
health = 750
maxHealth = 750 //""""low-ish"""" HP because it's a passive boss, and the swarm itself is the real foe
mob_biotypes = MOB_ROBOTIC
- internal_type = /obj/item/gps/internal/swarmer_beacon
+ gps_name = "Hungry Signal"
faction = list("mining", "boss", "swarmer")
weather_immunities = list(WEATHER_LAVA, WEATHER_ASH)
stop_automated_movement = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ambusher.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ambusher.dm
new file mode 100644
index 000000000000..b352fff3eab9
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ambusher.dm
@@ -0,0 +1,106 @@
+/mob/living/simple_animal/hostile/asteroid/ambusher
+ name = "white wolf"
+ desc = "A beast that survives by feasting on weaker opponents, they're much stronger with numbers."
+ icon = 'icons/mob/icemoon/icemoon_monsters.dmi'
+ icon_state = "whitewolf"
+ icon_living = "whitewolf"
+ icon_dead = "ambusher_dead"
+ mob_biotypes = MOB_ORGANIC|MOB_BEAST
+ mouse_opacity = MOUSE_OPACITY_ICON
+ friendly = "howls at"
+ speak_emote = list("howls")
+ speed = -1
+ move_to_delay = 5
+ maxHealth = 225
+ health = 225
+ obj_damage = 15
+ melee_damage_lower = 7.5
+ melee_damage_upper = 7.5
+ attack_vis_effect = ATTACK_EFFECT_BITE
+ rapid_melee = 2 // every second attack
+ dodging = TRUE
+ dodge_prob = 50
+ attacktext = "bites"
+ attack_sound = 'sound/weapons/bite.ogg'
+ vision_range = 7
+ aggro_vision_range = 7
+ move_force = MOVE_FORCE_WEAK
+ move_resist = MOVE_FORCE_WEAK
+ pull_force = MOVE_FORCE_WEAK
+ butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2, /obj/item/stack/sheet/sinew/wolf = 2, /obj/item/stack/sheet/bone = 2, /obj/item/reagent_containers/food/snacks/ambusher_tounge = 1)
+ loot = list()
+ stat_attack = UNCONSCIOUS
+ robust_searching = TRUE
+ var/revealed = FALSE
+ var/poison_type = /datum/reagent/toxin/ambusher_toxin
+ var/poison_per_bite = 0
+
+/mob/living/simple_animal/hostile/asteroid/ambusher/Move(atom/newloc)
+ if(newloc && newloc.z == z && (islava(newloc) || ischasm(newloc)))
+ return FALSE
+ return ..()
+
+/mob/living/simple_animal/hostile/asteroid/ambusher/Life(seconds_per_tick = SSMOBS_DT, times_fired)
+ if(!revealed) //Make sure it doesn't twitch if already revealed
+ if(prob(10)) //Randomly twitch to differentiate it from normal wolves
+ icon_state = "ambusher_twitch"
+ src.visible_message(span_warning("The white wolf twitches"))
+ playsound(get_turf(src), 'sound/effects/wounds/crack1.ogg', 30, TRUE, -1)
+ else
+ icon_state = "whitewolf"
+
+/mob/living/simple_animal/hostile/asteroid/ambusher/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
+ . = ..()
+ if(health <= 175 && !revealed) //Reveal itself if damaged enough and if it hasn't already done so
+ name = "white wolf?"
+ desc = "Something isn't quite right with this wolf..."
+ icon_state = "ambusher"
+ icon_living = "ambusher"
+ color = "#ac0000" //Grrr angery
+ friendly = "gurgles at"
+ speak_emote = list("gurgles")
+ speed = 2.5
+ move_to_delay = 2 //Faster to help it land a hit and inject toxin
+ melee_damage_lower = 2 //Less damage so player doesn't just immediatly eat rocks during rage
+ melee_damage_upper = 2
+ rapid_melee = 4 //More chances to attack and inject toxins
+ poison_per_bite = 4
+ dodging = FALSE
+ attacktext = "lashes"
+ new /obj/effect/gibspawner/generic(get_turf(src))
+ playsound(get_turf(src), 'sound/effects/reee.ogg', 150, TRUE, -1) //Froeg
+ src.visible_message(span_warning("The white wolf's head rips itself apart, forming a ghastly maw!"))
+ addtimer(CALLBACK(src, PROC_REF(endRage)), 6 SECONDS) //Rage timer
+ revealed = TRUE
+
+/mob/living/simple_animal/hostile/asteroid/ambusher/AttackingTarget()
+ ..()
+ if(isliving(target))
+ var/mob/living/L = target
+ if(target.reagents)
+ L.reagents.add_reagent(poison_type, poison_per_bite)
+
+/mob/living/simple_animal/hostile/asteroid/ambusher/proc/endRage()
+ color = null //Remove the color
+ move_to_delay = 4 //Slow down, toxin will let it keep up with player and if player did not get poisoned then ambusher skill issue
+ melee_damage_lower = 12 //More damage to make up for less speed
+ melee_damage_upper = 12
+ rapid_melee = 1
+ attacktext = "lacerates"
+ attack_sound = 'sound/effects/wounds/blood3.ogg'
+ src.visible_message(span_warning("The white wolf slows down as it focuses on you!"))
+
+/mob/living/simple_animal/hostile/asteroid/ambusher/Life(seconds_per_tick = SSMOBS_DT, times_fired)
+ . = ..()
+ if(target == null)
+ adjustHealth(-maxHealth*0.025)
+
+/obj/item/reagent_containers/food/snacks/ambusher_tounge
+ name = "tounge?"
+ desc = "An organ that appears to be an intestine with serated bone fragments jutting out of it. Something seems to be secreating from it."
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = "ambusher_tounge"
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/toxin/ambusher_toxin = 15)
+ filling_color = "#b971c8"
+ tastes = list("meat" = 1)
+ foodtype = MEAT | RAW
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm
index 954cbf4cb10d..549cfc784d40 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/drakeling.dm
@@ -113,7 +113,7 @@
/datum/action/cooldown/spell/pointed/drakeling
name = "ULTRA DRAGON ATTACK"
- desc = "if you can see this something has probably gone very wrong and you should make a bug report."
+ desc = "If you can see this something has probably gone very wrong and you should make a bug report."
background_icon_state = "bg_demon"
panel = "Dragon"
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index 496b7338074f..9f7767e37ffd 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -20,19 +20,23 @@
layer = LARGE_MOB_LAYER
sentience_type = SENTIENCE_BOSS
hud_type = /datum/hud/lavaland_elite
+ /// Name for the GPS signal of the megafauna
+ var/gps_name = null
+ /// If this is a megafauna that is real (has achievements, gps signal)
+ var/true_spawn = TRUE
+ /// The chosen attack by the megafauna
var/chosen_attack = 1
+ /// Attack actions, sets chosen_attack to the number in the action
var/list/attack_action_types = list()
+
var/can_talk = FALSE
var/obj/loot_drop = null
- var/obj/item/gps/internal
- var/internal_type
- var/true_spawn = TRUE // If this elite fauna should have a signal, same gps system used in megafauna.
//Gives player-controlled variants the ability to swap attacks
/mob/living/simple_animal/hostile/asteroid/elite/Initialize(mapload)
. = ..()
- if(internal_type && true_spawn)
- internal = new internal_type(src)
+ if(gps_name && true_spawn)
+ AddComponent(/datum/component/gps, gps_name)
for(var/action_type in attack_action_types)
var/datum/action/innate/elite_attack/attack_action = new action_type()
attack_action.Grant(src)
@@ -173,7 +177,6 @@ While using this makes the system rely on OnFire, it still gives options for tim
light_range = 3
anchored = TRUE
density = FALSE
- var/obj/item/gps/internal
/obj/structure/elite_tumor/attack_hand(mob/user)
. = ..()
@@ -208,15 +211,8 @@ obj/structure/elite_tumor/proc/return_elite()
/obj/structure/elite_tumor/Initialize(mapload)
. = ..()
//AddComponent(/datum/component/gps, "Menacing Signal")
- internal = new /obj/item/gps/internal/elite(src)
START_PROCESSING(SSobj, src)
-/obj/item/gps/internal/elite
- icon_state = null
- gpstag = "Menacing Signal"
- desc = "Something strange sleeps beneath the planet."
- invisibility = 100
-
/obj/structure/elite_tumor/Destroy()
STOP_PROCESSING(SSobj, src)
mychild = null
@@ -321,21 +317,26 @@ obj/structure/elite_tumor/proc/onEliteWon()
icon = 'icons/turf/walls/hierophant_wall_temp.dmi'
icon_state = "wall"
duration = 50
- smooth = SMOOTH_TRUE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_HIERO_WALL
+ canSmoothWith = SMOOTH_GROUP_HIERO_WALL
layer = BELOW_MOB_LAYER
- var/mob/living/carbon/human/activator = null
- var/mob/living/simple_animal/hostile/asteroid/elite/ourelite = null
color = rgb(255,0,0)
light_range = MINIMUM_USEFUL_LIGHT_RANGE
light_color = LIGHT_COLOR_RED
+ var/mob/living/carbon/human/activator = null
+ var/mob/living/simple_animal/hostile/asteroid/elite/ourelite = null
+
/obj/effect/temp_visual/elite_tumor_wall/Initialize(mapload, new_caster)
. = ..()
- queue_smooth_neighbors(src)
- queue_smooth(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ QUEUE_SMOOTH(src)
/obj/effect/temp_visual/elite_tumor_wall/Destroy()
- queue_smooth_neighbors(src)
+ if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH_NEIGHBORS(src)
activator = null
ourelite = null
return ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
index ba96126676b8..7b07a2b026f2 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
@@ -39,7 +39,7 @@
move_to_delay = 5
mob_biotypes = MOB_ORGANIC|MOB_BEAST
mouse_opacity = MOUSE_OPACITY_ICON
- internal_type = /obj/item/gps/internal/broodmother
+ gps_name = "Brooding Signal"
deathmessage = "explodes into gore!"
loot_drop = /obj/item/crusher_trophy/broodmother_tongue
@@ -162,10 +162,6 @@
desc = "7.5/10 too many tentacles."
invisibility = 100
-/mob/living/simple_animal/hostile/asteroid/elite/broodmother/death()
- QDEL_NULL(internal) // removes signal from a deceased elite.
- . = ..()
-
//The goliath's children. Pretty weak, simple mobs which are able to put a single tentacle under their target when at range.
/mob/living/simple_animal/hostile/asteroid/elite/broodmother_child
name = "baby goliath"
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
index ad57b999aa2b..37081d0d40d1 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -36,7 +36,7 @@
speed = 4
move_to_delay = 10
mouse_opacity = MOUSE_OPACITY_ICON
- internal_type = /obj/item/gps/internal/herald
+ gps_name = "Reverent Signal"
deathsound = 'sound/magic/demon_dies.ogg'
deathmessage = "begins to shudder as it becomes transparent..."
loot_drop = /obj/item/clothing/neck/cloak/herald_cloak
@@ -196,10 +196,6 @@
desc = "Mirrors inside mirrors inside mirrors inside mirrors."
invisibility = 100
-/mob/living/simple_animal/hostile/asteroid/elite/herald/death()
- QDEL_NULL(internal) // removes signal from a deceased elite.
- . = ..()
-
/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror
name = "herald's mirror"
desc = "This fiendish work of magic copies the herald's attacks. Seems logical to smash it."
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
index 7510532b0ce7..a23aca24bf82 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
@@ -37,7 +37,7 @@
speed = 0
move_to_delay = 3
mouse_opacity = MOUSE_OPACITY_ICON
- internal_type = /obj/item/gps/internal/legionnaire
+ gps_name = "Wailing Signal"
deathsound = 'sound/magic/curse.ogg'
deathmessage = "'s arms reach out before it falls apart onto the floor, lifeless."
loot_drop = /obj/item/crusher_trophy/legionnaire_spine
@@ -220,10 +220,6 @@
desc = "One vs many."
invisibility = 100
-/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/death()
- QDEL_NULL(internal) // removes signal from a deceased elite.
- . = ..()
-
//The legionnaire's head. Basically the same as any legion head, but we have to tell our creator when we die so they can generate another head.
/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead
name = "legionnaire head"
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
index 658ab1adc43e..38d0113a040d 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
@@ -36,7 +36,7 @@
speed = 4
move_to_delay = 10
mouse_opacity = MOUSE_OPACITY_ICON
- internal_type = /obj/item/gps/internal/pandora
+ gps_name = "Chaotic Signal"
deathsound = 'sound/magic/repulse.ogg'
deathmessage = "'s lights flicker, before its top part falls down."
loot_drop = /obj/item/clothing/accessory/pandora_hope
@@ -176,11 +176,6 @@
desc = "You opened the box."
invisibility = 100
-/mob/living/simple_animal/hostile/asteroid/elite/pandora/death()
- QDEL_NULL(internal) // removes signal from a deceased elite.
- . = ..()
-
-
//The specific version of hiero's squares pandora uses
/obj/effect/temp_visual/hierophant/blast/pandora
damage = 20
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
index 5ee206d44c72..611a4a7d5b8a 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
@@ -279,9 +279,10 @@
weather_immunities = list(WEATHER_LAVA, WEATHER_ASH)
obj_damage = 30
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
-
+ // Purple, but bright cause we're gonna need to spot mobs on lavaland
+ lighting_cutoff_red = 35
+ lighting_cutoff_green = 20
+ lighting_cutoff_blue = 45
/mob/living/simple_animal/hostile/big_legion/Initialize(mapload)
.=..()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm
index ff2ab1ed7ed9..4d78888ddba4 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm
@@ -16,8 +16,10 @@
var/crusher_loot
var/throw_message = "bounces off of"
var/fromtendril = FALSE
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Pale purple, should be red enough to see stuff on lavaland
+ lighting_cutoff_red = 25
+ lighting_cutoff_green = 15
+ lighting_cutoff_blue = 35
mob_size = MOB_SIZE_LARGE
var/icon_aggro = null
var/crusher_drop_mod = 25
diff --git a/code/modules/mob/living/simple_animal/hostile/rat.dm b/code/modules/mob/living/simple_animal/hostile/rat.dm
index a99c17ffc4eb..1b7e93955c94 100644
--- a/code/modules/mob/living/simple_animal/hostile/rat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/rat.dm
@@ -13,8 +13,6 @@
obj_damage = 5
speak_chance = 1
turns_per_move = 5
- see_in_dark = 6
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
maxHealth = 15
health = 15
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/mouse = 1)
@@ -81,7 +79,7 @@
if (!mind)
if(prob(40))
var/turf/open/floor/F = get_turf(src)
- if(istype(F) && !F.intact)
+ if(istype(F) && F.underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
var/obj/structure/cable/C = locate() in F
if(C && prob(15))
if(C.avail())
diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
index 8aee2da1ca0e..b2cfd9d32b77 100644
--- a/code/modules/mob/living/simple_animal/hostile/regalrat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
@@ -9,7 +9,6 @@
turns_per_move = 5
maxHealth = 70
health = 70
- see_in_dark = 15
obj_damage = 10
butcher_results = list(/obj/item/clothing/head/crown = 1,)
response_help = "glares at"
@@ -24,7 +23,11 @@
ventcrawler = VENTCRAWLER_ALWAYS
unique_name = TRUE
faction = list("rat")
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Slightly brown red, for the eyes
+ // Might be a bit too dim
+ lighting_cutoff_red = 22
+ lighting_cutoff_green = 8
+ lighting_cutoff_blue = 5
var/datum/action/cooldown/riot
var/datum/action/cooldown/domain
var/opening_airlock = FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
index 25567bc1d6aa..cf4fad70e66d 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm
@@ -14,8 +14,6 @@
maxHealth = 15
health = 15
spacewalk = TRUE
- see_in_dark = 10
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
harm_intent_damage = 6
melee_damage_lower = 6
melee_damage_upper = 5
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm
index 7ac2c9a5a278..681949c38483 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/frog.dm
@@ -36,9 +36,13 @@
icon_living = "rare_frog"
icon_dead = "rare_frog_dead"
butcher_results = list(/obj/item/reagent_containers/food/snacks/nugget = 5)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
-/mob/living/simple_animal/hostile/retaliate/frog/Crossed(AM as mob|obj)
- . = ..()
+
+/mob/living/simple_animal/hostile/retaliate/frog/proc/on_entered(datum/source, atom/movable/AM, ...)
if(!stat && isliving(AM))
var/mob/living/L = AM
if(L.mob_size > MOB_SIZE_TINY)
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/trianglespider.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/trianglespider.dm
index 2986111e54b5..f041887315e4 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/trianglespider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/trianglespider.dm
@@ -26,5 +26,3 @@
attack_sound = 'sound/weapons/bite.ogg'
unique_name = 1
gold_core_spawnable = HOSTILE_SPAWN
- see_in_dark = 4
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
diff --git a/code/modules/mob/living/simple_animal/hostile/skeleton.dm b/code/modules/mob/living/simple_animal/hostile/skeleton.dm
index ce1eb05e880d..5f342e06eccd 100644
--- a/code/modules/mob/living/simple_animal/hostile/skeleton.dm
+++ b/code/modules/mob/living/simple_animal/hostile/skeleton.dm
@@ -29,8 +29,10 @@
stat_attack = UNCONSCIOUS
gold_core_spawnable = HOSTILE_SPAWN
faction = list("skeleton")
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Going for a sort of pale bluegreen here, shooting for boneish
+ lighting_cutoff_red = 15
+ lighting_cutoff_green = 25
+ lighting_cutoff_blue = 35
deathmessage = "collapses into a pile of bones!"
del_on_death = 1
loot = list(/obj/effect/decal/remains/human)
diff --git a/code/modules/mob/living/simple_animal/hostile/smspider.dm b/code/modules/mob/living/simple_animal/hostile/smspider.dm
index 52350aa77c05..ebe30b9a04a2 100644
--- a/code/modules/mob/living/simple_animal/hostile/smspider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/smspider.dm
@@ -21,8 +21,10 @@
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
robust_searching = 1
faction = list("hostile")
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Gold, supermatter tinted
+ lighting_cutoff_red = 30
+ lighting_cutoff_green = 30
+ lighting_cutoff_blue = 10
ventcrawler = VENTCRAWLER_ALWAYS
deathmessage = "falls to the ground, its shard dulling to a miserable grey!"
var/overcharged = FALSE // if true, spider will not die if it dusts a limb
@@ -55,4 +57,4 @@
icon_living = "smspideroc"
maxHealth = 25
health = 25
- overcharged = TRUE
\ No newline at end of file
+ overcharged = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
index 6ea19253f06b..3bab5bd24821 100644
--- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
+++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
@@ -38,8 +38,6 @@
icon_dead = "spacedragon_dead"
health_doll_icon = "spacedragon"
obj_damage = 50
- see_in_dark = 7 //yogs
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE //yogs
environment_smash = ENVIRONMENT_SMASH_NONE
flags_1 = HEAR_1 | PREVENT_CONTENTS_EXPLOSION_1
mob_size = MOB_SIZE_LARGE
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 55c8f67fa8ba..ee396f27c7fa 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -36,8 +36,10 @@
animate_movement = NO_STEPS // Do not animate movement, you jump around as you're a scary statue.
hud_possible = list(ANTAG_HUD)
- see_in_dark = 13
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // Dim purple, I want it to be possible to miss people in the dark but not easy to
+ lighting_cutoff_red = 15
+ lighting_cutoff_green = 10
+ lighting_cutoff_blue = 25
vision_range = 12
aggro_vision_range = 12
@@ -64,8 +66,6 @@
flicker.Grant(src)
var/datum/action/cooldown/spell/aoe/blindness/blind = new(src)
blind.Grant(src)
- var/datum/action/cooldown/spell/night_vision/night_vision = new(src)
- night_vision.Grant(src)
// Set creator
if(creator)
diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
index b1856d13a14a..148c663989e1 100644
--- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm
+++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
@@ -312,7 +312,7 @@
movement_type = FLYING
limb_destroyer = 1
speak_emote = list("states")
- bubble_icon = "syndibot"
+ bubble_icon = BUBBLE_SYNDIBOT
gold_core_spawnable = HOSTILE_SPAWN
del_on_death = 1
deathmessage = "is smashed into pieces!"
diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
index 4ed3901312fe..3c314d52ab5d 100644
--- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
+++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
@@ -15,14 +15,24 @@
icon_state = "flower_bud"
layer = SPACEVINE_MOB_LAYER
opacity = FALSE
- canSmoothWith = list()
- smooth = SMOOTH_FALSE
+ canSmoothWith = null
+ smoothing_flags = NONE
+ density = FALSE
/// The amount of time it takes to create a venus human trap, in deciseconds
- var/growth_time = 1200
+ var/growth_time = 120 SECONDS
+ var/growth_icon = 0
+
/// Used by countdown to check time, this is when the timer will complete and the venus trap will spawn.
var/finish_time
/// The countdown ghosts see to when the plant will hatch
var/obj/effect/countdown/flower_bud/countdown
+
+ var/trait_flags = 0
+
+ var/list/vines = list()
+
+ /// The spawner that actually handles spawning the ghost role in
+ //var/obj/effect/mob_spawn/ghost_role/venus_human_trap/spawner
/obj/structure/alien/resin/flower_bud_enemy/Initialize(mapload)
. = ..()
@@ -34,8 +44,7 @@
anchors += locate(x+2,y-2,z)
for(var/turf/T in anchors)
- var/datum/beam/B = Beam(T, "vine", time=INFINITY, maxdistance=5, beam_type=/obj/effect/ebeam/vine)
- B.sleep_time = 10 //these shouldn't move, so let's slow down updates to 1 second (any slower and the deletion of the vines would be too slow)
+ vines += Beam(T, "vine", maxdistance=5, beam_type=/obj/effect/ebeam/vine)
finish_time = world.time + growth_time
addtimer(CALLBACK(src, PROC_REF(bear_fruit)), growth_time)
countdown.start()
@@ -55,8 +64,15 @@
mouse_opacity = MOUSE_OPACITY_ICON
desc = "A thick vine, painful to the touch."
-/obj/effect/ebeam/vine/Crossed(atom/movable/AM)
+/obj/effect/ebeam/vine/Initialize(mapload)
. = ..()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+/obj/effect/ebeam/vine/proc/on_entered(datum/source, atom/movable/AM)
+ SIGNAL_HANDLER
if(isliving(AM))
var/mob/living/L = AM
if(!isvineimmune(L))
@@ -94,7 +110,10 @@
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
unsuitable_atmos_damage = 0
sight = SEE_SELF|SEE_MOBS|SEE_OBJS|SEE_TURFS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ // Real green, cause of course
+ lighting_cutoff_red = 10
+ lighting_cutoff_green = 35
+ lighting_cutoff_blue = 20
faction = list("hostile","vines","plants")
initial_language_holder = /datum/language_holder/venus
del_on_death = TRUE
@@ -104,6 +123,8 @@
var/max_vines = 4
/// How far away a plant can attach a vine to something
var/vine_grab_distance = 5
+ /// how long does a vine attached to something last (and its leash) (lasts twice as long on nonliving things)
+ var/vine_duration = 2 SECONDS
/// Whether or not this plant is ghost possessable
var/playable_plant = TRUE
@@ -143,10 +164,10 @@
if(O.density)
return
- var/datum/beam/newVine = Beam(the_target, "vine", time=INFINITY, maxdistance = vine_grab_distance, beam_type=/obj/effect/ebeam/vine)
- RegisterSignal(newVine, COMSIG_PARENT_QDELETING, PROC_REF(remove_vine), newVine)
+ var/datum/beam/new_vine = Beam(the_target, icon_state = "vine", time = vine_duration * (ismob(the_target) ? 1 : 2), beam_type = /obj/effect/ebeam/vine, emissive = FALSE)
+ RegisterSignal(new_vine, COMSIG_QDELETING, PROC_REF(remove_vine), new_vine)
listclearnulls(vines)
- vines += newVine
+ vines += new_vine
if(isliving(the_target))
var/mob/living/L = the_target
if(iscarbon(the_target))
@@ -201,7 +222,7 @@
if(!AM.anchored)
step(AM,get_dir(AM,src))
if(get_dist(src,B.target) == 0)
- B.End()
+ qdel(B)
/**
* Removes a vine from the list.
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index feffe0b63a07..4bc078b6436f 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -792,7 +792,7 @@
to_chat(src, span_warning("There is no perch nearby to sit on!"))
return
-/mob/living/simple_animal/parrot/Moved(oldLoc, dir)
+/mob/living/simple_animal/parrot/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(. && !stat && client && parrot_state == PARROT_PERCH)
if(!buckled)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 2294a6799169..eb4ce27437b3 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -136,7 +136,7 @@
SSnpcpool.currentrun -= src
if(nest)
- nest.spawned_mobs -= src
+ nest.spawned_things -= src
nest = null
var/turf/T = get_turf(src)
@@ -325,7 +325,7 @@
/mob/living/simple_animal/death(gibbed)
movement_type &= ~FLYING
if(nest)
- nest.spawned_mobs -= src
+ nest.spawned_things -= src
nest = null
drop_loot()
if(dextrous)
@@ -475,25 +475,25 @@
return
if(stat == DEAD)
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
else if(is_secret_level(z))
- sight = initial(sight)
+ set_sight(initial(sight))
else
- sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_in_dark = 8
- see_invisible = SEE_INVISIBLE_OBSERVER
+ set_sight(SEE_TURFS|SEE_MOBS|SEE_OBJS)
+ set_invis_see(SEE_INVISIBLE_OBSERVER)
return
- see_invisible = initial(see_invisible)
- see_in_dark = initial(see_in_dark)
- sight = initial(sight)
+ lighting_color_cutoffs = list(lighting_cutoff_red, lighting_cutoff_green, lighting_cutoff_blue)
+ set_invis_see(initial(see_invisible))
if(SSmapping.level_trait(z, ZTRAIT_NOXRAY))
- sight = null
+ set_sight(null)
+ else
+ set_sight(initial(sight))
if(client.eye != src)
var/atom/A = client.eye
if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates.
return
- sync_lighting_plane_alpha()
+ return ..()
/mob/living/simple_animal/get_idcard(hand_first)
return access_card
@@ -544,19 +544,15 @@
update_inv_hands()
/mob/living/simple_animal/update_inv_hands()
- if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD)
- var/obj/item/l_hand = get_item_for_held_index(1)
- var/obj/item/r_hand = get_item_for_held_index(2)
- if(r_hand)
- r_hand.layer = ABOVE_HUD_LAYER
- r_hand.plane = ABOVE_HUD_PLANE
- r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand))
- client.screen |= r_hand
- if(l_hand)
- l_hand.layer = ABOVE_HUD_LAYER
- l_hand.plane = ABOVE_HUD_PLANE
- l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand))
- client.screen |= l_hand
+ . = ..()
+ if(!client || !hud_used || hud_used.hud_version == HUD_STYLE_NOHUD)
+ return
+ var/turf/our_turf = get_turf(src)
+ for(var/obj/item/I in held_items)
+ var/index = get_held_index_of_item(I)
+ SET_PLANE(I, ABOVE_HUD_PLANE, our_turf)
+ I.screen_loc = ui_hand_position(index)
+ client.screen |= I
//ANIMAL RIDING
@@ -608,8 +604,14 @@
toggle_ai(AI_ON)
-/mob/living/simple_animal/onTransitZ(old_z, new_z)
+/mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
..()
- if (AIStatus == AI_Z_OFF)
- SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src
+ if (old_turf && AIStatus == AI_Z_OFF)
+ SSidlenpcpool.idle_mobs_by_zlevel[old_turf.z] -= src
toggle_ai(initial(AIStatus))
+
+//YOGS EDIT
+/mob/living/simple_animal/proc/return_standard_turns_per_move()
+ turns_per_move = initial(turns_per_move)
+
+//YOGS END
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index 9ac936d89d9a..74b6bef7986c 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -19,7 +19,7 @@
response_harm = "stomps on"
emote_see = list("jiggles", "bounces in place")
speak_emote = list("blorbles")
- bubble_icon = "slime"
+ bubble_icon = BUBBLE_SLIME
initial_language_holder = /datum/language_holder/slime
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
@@ -542,34 +542,34 @@
var/old_target = Target
Target = new_target
if(old_target && !SLIME_CARES_ABOUT(old_target))
- UnregisterSignal(old_target, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(old_target, COMSIG_QDELETING)
if(Target)
- RegisterSignal(Target, COMSIG_PARENT_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
+ RegisterSignal(Target, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
/mob/living/simple_animal/slime/proc/set_leader(new_leader)
var/old_leader = Leader
Leader = new_leader
if(old_leader && !SLIME_CARES_ABOUT(old_leader))
- UnregisterSignal(old_leader, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(old_leader, COMSIG_QDELETING)
if(Leader)
- RegisterSignal(Leader, COMSIG_PARENT_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
+ RegisterSignal(Leader, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
/mob/living/simple_animal/slime/proc/add_friendship(new_friend, amount = 1)
if(!Friends[new_friend])
Friends[new_friend] = 0
Friends[new_friend] += amount
if(new_friend)
- RegisterSignal(new_friend, COMSIG_PARENT_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
+ RegisterSignal(new_friend, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
/mob/living/simple_animal/slime/proc/set_friendship(new_friend, amount = 1)
Friends[new_friend] = amount
if(new_friend)
- RegisterSignal(new_friend, COMSIG_PARENT_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
+ RegisterSignal(new_friend, COMSIG_QDELETING, PROC_REF(clear_memories_of), override = TRUE)
/mob/living/simple_animal/slime/proc/remove_friend(friend)
Friends -= friend
if(friend && !SLIME_CARES_ABOUT(friend))
- UnregisterSignal(friend, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(friend, COMSIG_QDELETING)
/mob/living/simple_animal/slime/proc/set_friends(new_buds)
clear_friends()
diff --git a/code/modules/mob/living/simple_animal/status_procs.dm b/code/modules/mob/living/simple_animal/status_procs.dm
deleted file mode 100644
index 62a262f9e605..000000000000
--- a/code/modules/mob/living/simple_animal/status_procs.dm
+++ /dev/null
@@ -1,25 +0,0 @@
-
-/mob/living/simple_animal/blind_eyes()
- return
-
-/mob/living/simple_animal/adjust_blindness()
- return
-
-/mob/living/simple_animal/set_blindness()
- return
-
-
-
-/mob/living/simple_animal/blur_eyes()
- return
-
-/mob/living/simple_animal/adjust_blurriness()
- return
-
-/mob/living/simple_animal/set_blurriness()
- return
-
-
-
-/mob/living/simple_animal/become_blind()
- return
diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm
index 370d71ff8850..c8e70d28d2af 100644
--- a/code/modules/mob/living/ventcrawling.dm
+++ b/code/modules/mob/living/ventcrawling.dm
@@ -86,12 +86,13 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list(
*/
/mob/living/proc/move_into_vent(obj/machinery/atmospherics/components/ventcrawl_target)
forceMove(ventcrawl_target)
+ ADD_TRAIT(src, TRAIT_MOVE_VENTCRAWLING, VENTCRAWLING_TRAIT)
update_pipe_vision()
/mob/living/proc/add_ventcrawl(obj/machinery/atmospherics/starting_machine)
- if(!istype(starting_machine) || !starting_machine.can_see_pipes())
+ if(!istype(starting_machine) || !starting_machine.can_see_pipes() || isnull(client))
return
var/list/totalMembers = list()
@@ -102,26 +103,48 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list(
if(!totalMembers.len)
return
- if(client)
- for(var/X in totalMembers)
- var/obj/machinery/atmospherics/A = X //all elements in totalMembers are necessarily of this type.
- if(in_view_range(client.mob, A))
- if(!A.pipe_vision_img)
- A.pipe_vision_img = image(A, A.loc, layer = ABOVE_HUD_LAYER, dir = A.dir)
- A.pipe_vision_img.plane = ABOVE_HUD_PLANE
- client.images += A.pipe_vision_img
- pipes_shown += A.pipe_vision_img
+ set_sight(SEE_TURFS|BLIND)
+
+ // We're gonna color the lighting plane to make it darker while ventcrawling, so things look nicer
+ // This is a bit hacky but it makes the background darker, which has a nice effect
+ for(var/atom/movable/screen/plane_master/lighting as anything in hud_used.get_true_plane_masters(LIGHTING_PLANE))
+ lighting.add_atom_colour("#4d4d4d", TEMPORARY_COLOUR_PRIORITY)
+
+ for(var/atom/movable/screen/plane_master/pipecrawl as anything in hud_used.get_true_plane_masters(PIPECRAWL_IMAGES_PLANE))
+ pipecrawl.unhide_plane(src)
+
+ var/obj/machinery/atmospherics/current_location = loc
+ var/list/our_pipenets = current_location.return_pipenets()
+
+ for(var/obj/machinery/atmospherics/pipenet_part as anything in totalMembers)
+ // If the machinery is not in view or is not meant to be seen, continue
+ // If the machinery is not part of our net or is not meant to be seen, continue
+ var/list/thier_pipenets = pipenet_part.return_pipenets()
+ if(!length(thier_pipenets & our_pipenets))
+ continue
+ if(!in_view_range(client.mob, pipenet_part))
+ continue
+ if(!pipenet_part.pipe_vision_img)
+ var/turf/their_turf = get_turf(pipenet_part)
+ pipenet_part.pipe_vision_img = image(pipenet_part, pipenet_part.loc, dir = pipenet_part.dir)
+ SET_PLANE(pipenet_part.pipe_vision_img, PIPECRAWL_IMAGES_PLANE, their_turf)
+ client.images += pipenet_part.pipe_vision_img
+ pipes_shown += pipenet_part.pipe_vision_img
setMovetype(movement_type | VENTCRAWLING)
/mob/living/proc/remove_ventcrawl()
- if(client)
+ // Take away all the pipe images if we're not doing anything with em
+ if(isnull(client) || !HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) || !istype(loc, /obj/machinery/atmospherics) || !(movement_type & VENTCRAWLING))
for(var/image/current_image in pipes_shown)
client.images -= current_image
- pipes_shown.len = 0
- setMovetype(movement_type & ~VENTCRAWLING)
-
-
+ pipes_shown.len = 0
+ for(var/atom/movable/screen/plane_master/lighting as anything in hud_used.get_true_plane_masters(LIGHTING_PLANE))
+ lighting.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#4d4d4d")
+ for(var/atom/movable/screen/plane_master/pipecrawl as anything in hud_used.get_true_plane_masters(PIPECRAWL_IMAGES_PLANE))
+ pipecrawl.hide_plane(src)
+ setMovetype(movement_type & ~VENTCRAWLING)
+ update_sight()
//OOP
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 84fd5c7ae920..6ffd468f8714 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -24,6 +24,7 @@
/mob/Login()
if(!client)
return FALSE
+
canon_client = client
add_to_player_list()
lastKnownIP = client.address
@@ -107,6 +108,10 @@
log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP)
SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client)
+ SEND_SIGNAL(client, COMSIG_CLIENT_MOB_LOGIN, src)
+
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_LOGGED_IN, src)
+ return TRUE
/**
* Checks if the attached client is an admin and may deadmin them
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 399aa3e7786d..d250293ede6c 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -87,12 +87,18 @@
update_config_movespeed()
update_movespeed(TRUE)
+/mob/New()
+ // This needs to happen IMMEDIATELY. I'm sorry :(
+ GenerateTag()
+ return ..()
+
/**
- * Generate the tag for this mob
- *
- * This is simply "mob_"+ a global incrementing counter that goes up for every mob
- */
+ * Generate the tag for this mob
+ *
+ * This is simply "mob_"+ a global incrementing counter that goes up for every mob
+ */
/mob/GenerateTag()
+ . = ..()
tag = "mob_[next_mob_id++]"
/**
@@ -155,7 +161,7 @@
hud_list[hud] = list()
else
- var/image/I = image('yogstation/icons/mob/hud.dmi', src, "")
+ var/image/I = image('modular_dripstation/icons/mob/hud.dmi', src, "") //dripstation edit
I.appearance_flags = RESET_COLOR|RESET_TRANSFORM
hud_list[hud] = I
set_hud_image_active(hud, update_huds = FALSE) //by default everything is active. but dont add it to huds to keep control.
@@ -259,19 +265,25 @@
//This entire if/else chain could be in two lines but isn't for readibilties sake.
var/msg = message
+ var/msg_type = MSG_VISUAL
+
if(M.see_invisible < invisibility)//if src is invisible to M
msg = blind_message
+ msg_type = MSG_AUDIBLE
else if(T != loc && T != src) //if src is inside something and not a turf.
+ if(M != loc) // Only give the blind message to hearers that aren't the location
+ msg = blind_message
+ msg_type = MSG_AUDIBLE
+ else if(!HAS_TRAIT(M, TRAIT_HEAR_THROUGH_DARKNESS) && M.lighting_cutoff < LIGHTING_CUTOFF_HIGH && T.is_softly_lit() && !in_range(T,M)) //if it is too dark, unless we're right next to them.
msg = blind_message
- else if(M.lighting_alpha > LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE && T.is_softly_lit() && !in_range(T,M)) //if it is too dark.
- msg = blind_message
+ msg_type = MSG_AUDIBLE
if(!msg)
continue
if(visible_message_flags & EMOTE_MESSAGE && runechat_prefs_check(M, visible_message_flags) && !is_blind(M))
M.create_chat_message(src, raw_message = raw_msg, runechat_flags = visible_message_flags)
- M.show_message(msg, MSG_VISUAL, blind_message, MSG_AUDIBLE)
+ M.show_message(msg, msg_type, blind_message, MSG_AUDIBLE)
@@ -449,36 +461,42 @@
* reset_perspective() set eye to common default : mob on turf, loc otherwise
* reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective)
*/
-/mob/proc/reset_perspective(atom/A)
- if(client)
- if(A)
- if(ismovable(A))
- //Set the the thing unless it's us
- if(A != src)
- client.perspective = EYE_PERSPECTIVE
- client.eye = A
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
- else if(isturf(A))
- //Set to the turf unless it's our current turf
- if(A != loc)
- client.perspective = EYE_PERSPECTIVE
- client.eye = A
- else
- client.eye = client.mob
- client.perspective = MOB_PERSPECTIVE
+/mob/proc/reset_perspective(atom/new_eye)
+ SHOULD_CALL_PARENT(TRUE)
+ if(!client)
+ return
+
+ if(new_eye)
+ if(ismovable(new_eye))
+ //Set the new eye unless it's us
+ if(new_eye != src)
+ client.perspective = EYE_PERSPECTIVE
+ client.set_eye(new_eye)
else
- //Do nothing
- else
- //Reset to common defaults: mob if on turf, otherwise current loc
- if(isturf(loc))
- client.eye = client.mob
+ client.set_eye(client.mob)
client.perspective = MOB_PERSPECTIVE
- else
+
+ else if(isturf(new_eye))
+ //Set to the turf unless it's our current turf
+ if(new_eye != loc)
client.perspective = EYE_PERSPECTIVE
- client.eye = loc
- return 1
+ client.set_eye(new_eye)
+ else
+ client.set_eye(client.mob)
+ client.perspective = MOB_PERSPECTIVE
+ else
+ return TRUE //no setting eye to stupid things like areas or whatever
+ else
+ //Reset to common defaults: mob if on turf, otherwise current loc
+ if(isturf(loc))
+ client.set_eye(client.mob)
+ client.perspective = MOB_PERSPECTIVE
+ else
+ client.perspective = EYE_PERSPECTIVE
+ client.set_eye(loc)
+ /// Signal sent after the eye has been successfully updated, with the client existing.
+ SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE)
+ return TRUE
/// Show the mob's inventory to another mob
/mob/proc/show_inv(mob/user)
@@ -1239,16 +1257,17 @@
///Update the lighting plane and sight of this mob (sends COMSIG_MOB_UPDATE_SIGHT)
/mob/proc/update_sight()
+ SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT)
- sync_lighting_plane_alpha()
+ sync_lighting_plane_cutoff()
///Set the lighting plane hud alpha to the mobs lighting_alpha var
-/mob/proc/sync_lighting_plane_alpha()
- if(hud_used)
- var/atom/movable/screen/plane_master/lighting/L = hud_used.plane_masters["[LIGHTING_PLANE]"]
- if (L)
- L.alpha = lighting_alpha
-
+/mob/proc/sync_lighting_plane_cutoff()
+ if(!hud_used)
+ return
+ for(var/atom/movable/screen/plane_master/rendering_plate/lighting/light as anything in hud_used.get_true_plane_masters(RENDER_PLANE_LIGHTING))
+ light.set_light_cutoff(lighting_cutoff, lighting_color_cutoffs)
+
///Update the mouse pointer of the attached client in this mob
/mob/proc/update_mouse_pointer()
if (!client)
@@ -1266,6 +1285,10 @@
if(client.mouse_override_icon)
client.mouse_pointer_icon = client.mouse_override_icon
+/mob/proc/has_nightvision()
+ // Somewhat conservative, basically is your lighting plane bright enough that you the user can see stuff
+ var/light_offset = (lighting_color_cutoffs[1] + lighting_color_cutoffs[2] + lighting_color_cutoffs[3]) / 3 + lighting_cutoff
+ return light_offset >= LIGHTING_NIGHTVISION_THRESHOLD
///This mob is abile to read books
/mob/proc/is_literate()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 469261640cb2..0489ef06309b 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -7,7 +7,6 @@
* Has a lot of the creature game world logic, such as health etc
*/
/mob
- datum_flags = DF_USE_TAG
density = TRUE
layer = MOB_LAYER
animate_movement = 2
@@ -17,8 +16,23 @@
mouse_drag_pointer = MOUSE_ACTIVE_POINTER
throwforce = 10
blocks_emissive = EMISSIVE_BLOCK_GENERIC
-
- var/lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
+ // we never want to hide a turf because it's not lit
+ // We can rely on the lighting plane to handle that for us
+ see_in_dark = 1e6
+
+ /// Percentage of how much rgb to max the lighting plane at
+ /// This lets us brighten it without washing out color
+ /// Scale from 0-100, reset off update_sight()
+ var/lighting_cutoff = LIGHTING_CUTOFF_VISIBLE
+ // Individual color max for red, we can use this to color darkness without tinting the light
+ var/lighting_cutoff_red = 0
+ // Individual color max for green, we can use this to color darkness without tinting the light
+ var/lighting_cutoff_green = 0
+ // Individual color max for blue, we can use this to color darkness without tinting the light
+ var/lighting_cutoff_blue = 0
+ /// A list of red, green and blue cutoffs
+ /// This is what actually gets applied to the mob, it's modified by things like glasses
+ var/list/lighting_color_cutoffs = null
var/datum/mind/mind
var/static/next_mob_id = 0
/// The current client inhabiting this mob. Managed by login/logout
@@ -50,6 +64,9 @@
Changing this around would probably require a good look-over the pre-existing code.
*/
+ ///How many legs does this mob currently have. Should only be changed through set_num_legs()
+ var/num_legs = 2
+
/// The zone this mob is currently targeting
var/zone_selected = BODY_ZONE_CHEST
@@ -138,6 +155,8 @@
var/datum/hud/hud_used = null
/// I have no idea tbh
var/research_scanner = FALSE
+ /// What icon the mob uses for typing indicators
+ var/bubble_icon = BUBBLE_DEFAULT
/// Is the mob throw intent on
var/in_throw_mode = 0
@@ -234,3 +253,4 @@
var/datum/focus
var/fake_client = FALSE // Currently only used for examines
+
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index d42751d8181b..6da24d3c8056 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -56,6 +56,7 @@
*
* This proc is dangerously laggy, avoid it or die
*/
+/* //Dripstation edit - russian language support
/proc/stars(n, pr)
n = html_encode(n)
if (pr == null)
@@ -233,6 +234,7 @@
+*/
///Shake the camera of the person viewing the mob SO REAL!
/proc/shake_camera(mob/M, duration, strength=1)
if(!M || !M.client || duration < 1)
@@ -612,3 +614,8 @@
if(chosen_bodypart.status < BODYPART_ROBOTIC)
amount += chosen_bodypart.burn_dam
return amount
+
+/mob/proc/default_lighting_cutoff()
+ if(client?.combo_hud_enabled && client?.prefs?.toggles & COMBOHUD_LIGHTING)
+ return LIGHTING_CUTOFF_FULLBRIGHT
+ return initial(lighting_cutoff)
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index e62c604fd91f..2ffe90246dca 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -140,14 +140,13 @@
else
move_delay = world.time
+ //this is in two areas, i have no clue why, all i know is that i hate it and don't have the time to fix it
if(L.has_status_effect(/datum/status_effect/confusion))
var/newdir = 0
- if(L.get_timed_status_effect_duration(/datum/status_effect/confusion) > 40)
- newdir = pick(GLOB.alldirs)
- else if(prob(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 1.5))
- newdir = angle2dir(dir2angle(direct) + pick(90, -90))
- else if(prob(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 3))
+ if(prob(50))
newdir = angle2dir(dir2angle(direct) + pick(45, -45))
+ else if(prob(50) && L.get_timed_status_effect_duration(/datum/status_effect/confusion) > 10 SECONDS)
+ newdir = angle2dir(dir2angle(direct) + pick(90, -90))
if(newdir)
direct = newdir
n = get_step(L, direct)
@@ -261,7 +260,7 @@
R.reveal(20)
R.stun(20)
return
- if(stepTurf.flags_1 & NOJAUNT_1)
+ if(stepTurf.turf_flags & NOJAUNT)
to_chat(L, span_warning("Some strange aura is blocking the way."))
return
if (locate(/obj/effect/blessing, stepTurf))
@@ -502,11 +501,30 @@
set name = "Move Upwards"
set category = "IC"
+ if(remote_control)
+ return remote_control.relaymove(src, UP)
+
+ var/turf/current_turf = get_turf(src)
+ var/turf/above_turf = GET_TURF_ABOVE(current_turf)
+
+ if(!above_turf)
+ to_chat(src, span_warning("There's nowhere to go in that direction!"))
+ return
+
if(ismovable(loc)) //Inside an object, tell it we moved
var/atom/loc_atom = loc
return loc_atom.relaymove(src, UP)
- if(zMove(UP, TRUE))
+ var/ventcrawling_flag = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) ? ZMOVE_VENTCRAWLING : 0
+
+ if(can_z_move(DOWN, above_turf, current_turf, ZMOVE_FALL_FLAGS|ventcrawling_flag)) //Will we fall down if we go up?
+ if(buckled)
+ to_chat(src, span_warning("[buckled] is is not capable of flight."))
+ else
+ to_chat(src, span_warning("You are not Superman."))
+ return
+
+ if(zMove(UP, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK|ventcrawling_flag))
to_chat(src, span_notice("You move upwards."))
///Moves a mob down a z level
@@ -514,35 +532,28 @@
set name = "Move Down"
set category = "IC"
+ if(remote_control)
+ return remote_control.relaymove(src, DOWN)
+
+ var/turf/current_turf = get_turf(src)
+ var/turf/below_turf = GET_TURF_BELOW(current_turf)
+
+ if(!below_turf)
+ to_chat(src, span_warning("There's nowhere to go in that direction!"))
+ return
+
if(ismovable(loc)) //Inside an object, tell it we moved
var/atom/loc_atom = loc
return loc_atom.relaymove(src, DOWN)
- if(zMove(DOWN, TRUE))
+ var/ventcrawling_flag = HAS_TRAIT(src, TRAIT_MOVE_VENTCRAWLING) ? ZMOVE_VENTCRAWLING : 0
+
+ if(zMove(DOWN, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK|ventcrawling_flag))
to_chat(src, span_notice("You move down."))
+ return FALSE
/mob/abstract_move(atom/destination)
var/turf/new_turf = get_turf(destination)
if(new_turf && (istype(new_turf, /turf/cordon/secret) || is_secret_level(new_turf.z)) && !client?.holder)
return
return ..()
-
-///Move a mob between z levels, if it's valid to move z's on this turf
-/mob/proc/zMove(dir, feedback = FALSE)
- if(dir != UP && dir != DOWN)
- return FALSE
- var/turf/target = get_step_multiz(src, dir)
- if(!target)
- if(feedback)
- to_chat(src, span_warning("There's nothing [dir == DOWN ? "below" : "above"] you!"))
- return FALSE
- if(!canZMove(dir, target))
- if(feedback)
- to_chat(src, span_warning("You couldn't move there!"))
- return FALSE
- forceMove(target)
- return TRUE
-
-/// Can this mob move between z levels
-/mob/proc/canZMove(direction, turf/target)
- return FALSE
diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm
index 345156098958..d74abe19e4ca 100644
--- a/code/modules/mob/status_procs.dm
+++ b/code/modules/mob/status_procs.dm
@@ -71,35 +71,6 @@
clear_alert("blind")
clear_fullscreen("blind")
-/**
- * Make the mobs vision blurry
- */
-/mob/proc/blur_eyes(amount)
- if(amount>0)
- eye_blurry = max(amount, eye_blurry)
- update_eye_blur()
-
-/**
- * Adjust the current blurriness of the mobs vision by amount
- */
-/mob/proc/adjust_blurriness(amount)
- eye_blurry = max(eye_blurry+amount, 0)
- update_eye_blur()
-
-///Set the mobs blurriness of vision to an amount
-/mob/proc/set_blurriness(amount)
- eye_blurry = max(amount, 0)
- update_eye_blur()
-
-///Apply the blurry overlays to a mobs clients screen
-/mob/proc/update_eye_blur()
- if(!client)
- return
- var/atom/movable/screen/plane_master/floor/OT = locate(/atom/movable/screen/plane_master/floor) in client.screen
- var/atom/movable/screen/plane_master/game_world/GW = locate(/atom/movable/screen/plane_master/game_world) in client.screen
- GW.backdrop(src)
- OT.backdrop(src)
-
///Adjust the disgust level of a mob
/mob/proc/adjust_disgust(amount)
return
@@ -122,3 +93,31 @@
// 10C has 3 range (2 tiles)
// 0C has 0 range (0 tiles)
infra_luminosity = round(max((bodytemperature - T0C)/3, 0))
+
+/// Sight here is the mob.sight var, which tells byond what to actually show to our client
+/// See [code\__DEFINES\sight.dm] for more details
+/mob/proc/set_sight(new_value)
+ SHOULD_CALL_PARENT(TRUE)
+ if(sight == new_value)
+ return
+ var/old_sight = sight
+ sight = new_value
+
+ SEND_SIGNAL(src, COMSIG_MOB_SIGHT_CHANGE, new_value, old_sight)
+
+/mob/proc/add_sight(new_value)
+ set_sight(sight | new_value)
+
+/mob/proc/clear_sight(new_value)
+ set_sight(sight & ~new_value)
+
+/// see invisibility is the mob's capability to see things that ought to be hidden from it
+/// Can think of it as a primitive version of changing the alpha of planes
+/// We mostly use it to hide ghosts, no real reason why
+/mob/proc/set_invis_see(new_sight)
+ SHOULD_CALL_PARENT(TRUE)
+ if(new_sight == see_invisible)
+ return
+ var/old_invis = see_invisible
+ see_invisible = new_sight
+ SEND_SIGNAL(src, COMSIG_MOB_SEE_INVIS_CHANGE, see_invisible, old_invis)
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index c38f830ecdbe..e0d88c84b0b3 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -354,15 +354,13 @@
qdel(src)
-/mob/living/carbon/human/AIize(transfer_after = TRUE, client/preference_source)
- if (notransform)
+/mob/living/carbon/human/AIize(client/preference_source, move = TRUE)
+ if(notransform)
return
- for(var/t in bodyparts)
- qdel(t)
return ..()
-/mob/living/carbon/AIize(transfer_after = TRUE, client/preference_source)
+/mob/living/carbon/AIize(client/preference_source, move = TRUE)
if (notransform)
return
notransform = TRUE
@@ -371,26 +369,23 @@
dropItemToGround(W)
regenerate_icons()
icon = null
- invisibility = INVISIBILITY_MAXIMUM
+ SetInvisibility(INVISIBILITY_MAXIMUM)
return ..()
-/mob/proc/AIize(transfer_after = TRUE, client/preference_source)
- var/valid_core = FALSE
+/mob/proc/AIize(client/preference_source, move = TRUE)
+ var/list/turf/core_loc = list()
for(var/obj/machinery/ai/data_core/core in GLOB.data_cores)
if(core.valid_data_core())
- valid_core = TRUE
- break
- if(!valid_core)
+ core_loc |= core.loc
+ if(!length(core_loc))
message_admins("No valid data core for [src]. Yell at a mapper! The AI will die.")
if(client)
stop_sound_channel(CHANNEL_LOBBYMUSIC)
- if(!transfer_after)
- mind.active = FALSE
-
- . = new /mob/living/silicon/ai(loc, null, src)
+ var/mob/living/silicon/ai/our_AI = new /mob/living/silicon/ai(pick(core_loc), null, src)
+ . = our_AI
if(preference_source)
apply_pref_name(/datum/preference/name/ai, preference_source)
diff --git a/code/modules/mob/update_icons.dm b/code/modules/mob/update_icons.dm
index 581584912df0..e6375782fa80 100644
--- a/code/modules/mob/update_icons.dm
+++ b/code/modules/mob/update_icons.dm
@@ -19,7 +19,10 @@
/mob/proc/update_inv_back()
return
+///Updates the held items overlay(s) & HUD element.
/mob/proc/update_inv_hands()
+ //SHOULD_CALL_PARENT(TRUE)
+ //SEND_SIGNAL(src, COMSIG_MOB_UPDATE_HELD_ITEMS)
return
/mob/proc/update_inv_wear_mask()
@@ -68,4 +71,4 @@
return
/mob/proc/update_inv_ears()
- return
\ No newline at end of file
+ return
diff --git a/code/modules/modular_computers/computers/_modular_computer_shared.dm b/code/modules/modular_computers/computers/_modular_computer_shared.dm
index b8017d182091..195df286902d 100644
--- a/code/modules/modular_computers/computers/_modular_computer_shared.dm
+++ b/code/modules/modular_computers/computers/_modular_computer_shared.dm
@@ -63,4 +63,4 @@
if(printer_slot)
. += "It has a printer installed."
if(user_is_adjacent)
- . += "The printer's paper levels are at: [printer_slot.stored_paper]/[printer_slot.max_paper].]"
+ . += "The printer's paper levels are at: [printer_slot.stored_paper]/[printer_slot.max_paper]."
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 18a00b5e236f..e83c8b988c72 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -99,7 +99,7 @@
idle_threads = list()
install_starting_components()
install_starting_files()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/modular_computer/Destroy()
kill_program(forced = TRUE)
@@ -189,6 +189,7 @@
return FALSE
if((card_slot?.try_insert(inserting_id)) || (card_slot2?.try_insert(inserting_id)))
+ update_appearance()
return TRUE
//to_chat(user, "This computer doesn't have an open card slot.")
return FALSE
@@ -268,43 +269,31 @@
. += get_modular_computer_parts_examine(user)
-/obj/item/modular_computer/update_icon(updates=ALL)
+/obj/item/modular_computer/update_icon_state()
+ if(!icon_state_powered || !icon_state_unpowered) //no valid icon, don't update.
+ return ..()
+ icon_state = enabled ? icon_state_powered : icon_state_unpowered
+ return ..()
+
+/obj/item/modular_computer/update_overlays()
. = ..()
- if(!physical)
+ var/init_icon = initial(icon)
+ if(!init_icon)
return
- SSvis_overlays.remove_vis_overlay(physical, physical.managed_vis_overlays)
- var/program_overlay = ""
- var/is_broken = obj_integrity <= integrity_failure
- if(overlay_skin)
- program_overlay = "[overlay_skin]-"
- if(!enabled)
- if(use_power() && !isnull(icon_state_screensaver))
- program_overlay += icon_state_screensaver
- else
- icon_state = icon_state_unpowered
- else
- icon_state = icon_state_powered
- if(is_broken)
- program_overlay += "bsod"
- else
- if(active_program)
- program_overlay += active_program.program_icon_state ? "[active_program.program_icon_state]" : "[icon_state_menu]"
- else
- program_overlay += icon_state_menu
-
- SSvis_overlays.add_vis_overlay(physical, physical.icon, program_overlay, physical.layer, physical.plane, physical.dir)
- SSvis_overlays.add_vis_overlay(physical, physical.icon, program_overlay, physical.layer, EMISSIVE_PLANE, physical.dir)
- if(is_broken)
- SSvis_overlays.add_vis_overlay(physical, physical.icon, "broken", physical.layer, physical.plane, physical.dir)
+ if(enabled)
+ . += active_program ? mutable_appearance(init_icon, active_program.program_icon_state) : mutable_appearance(init_icon, icon_state_menu)
+ if(obj_integrity <= integrity_failure)
+ . += mutable_appearance(init_icon, "bsod")
+ . += mutable_appearance(init_icon, "broken")
/obj/item/modular_computer/equipped()
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/modular_computer/dropped()
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/modular_computer/proc/update_label()
@@ -345,7 +334,7 @@
else
to_chat(user, span_notice("You press the power button and start up \the [src]."))
enabled = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
play_computer_sound(startup_sound, get_clamped_volume(), FALSE)
ui_interact(user)
else // Unpowered
@@ -487,7 +476,7 @@
var/mob/user = usr
if(user && istype(user))
ui_interact(user) // Re-open the UI on this computer. It should show the main screen now.
- update_appearance(UPDATE_ICON)
+ update_appearance()
// Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on)
/obj/item/modular_computer/proc/get_ntnet_status(specific_action = 0)
@@ -503,7 +492,7 @@
var/obj/item/computer_hardware/network_card/network_card = all_components[MC_NET]
return SSnetworks.station_network.add_log(text, network_card)
-/obj/item/modular_computer/proc/shutdown_computer(loud = 1)
+/obj/item/modular_computer/proc/shutdown_computer(loud = TRUE)
kill_program(forced = TRUE)
for(var/datum/computer_file/program/P in idle_threads)
P.kill_program(forced = TRUE)
@@ -511,8 +500,8 @@
if(loud)
physical.visible_message(span_notice("\The [src] shuts down."))
enabled = FALSE
- update_appearance(UPDATE_ICON)
play_computer_sound(shutdown_sound, get_clamped_volume(), FALSE)
+ update_appearance()
/**
* Toggles the computer's flashlight, if it has one.
@@ -524,7 +513,7 @@
if(!has_light)
return FALSE
set_light_on(!light_on)
- update_appearance(UPDATE_ICON)
+ update_appearance()
return TRUE
/**
@@ -635,14 +624,9 @@
/obj/item/modular_computer/proc/install_starting_files()
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
- if(!istype(hard_drive) || starting_files.len < 1)
- if(!starting_files.len < 1)
- CRASH("[src] failed to install files due to not having a hard drive even though it has starting files")
- return
+
for(var/datum/computer_file/file in starting_files)
var/result = hard_drive.store_file(file)
- if(result == FALSE)
- CRASH("[src] failed to install starting files for an unknown reason")
if(istype(result, initial_program) && istype(result, /datum/computer_file/program))
var/datum/computer_file/program/program = result
if(program.requires_ntnet && program.network_destination)
diff --git a/code/modules/modular_computers/computers/item/pda/pda.dm b/code/modules/modular_computers/computers/item/pda/pda.dm
index a8a5748a5801..a3dedd9a99f1 100644
--- a/code/modules/modular_computers/computers/item/pda/pda.dm
+++ b/code/modules/modular_computers/computers/item/pda/pda.dm
@@ -6,7 +6,7 @@
icon_state_unpowered = "pda"
icon_state_powered = "pda"
hardware_flag = PROGRAM_PDA
- max_hardware_size = WEIGHT_CLASS_TINY
+ max_hardware_size = WEIGHT_CLASS_SMALL
w_class = WEIGHT_CLASS_SMALL
max_bays = 1
steel_sheet_cost = 1
diff --git a/code/modules/modular_computers/computers/item/pda/pda_presets.dm b/code/modules/modular_computers/computers/item/pda/pda_presets.dm
index d790eb77f63e..55a6780993a4 100644
--- a/code/modules/modular_computers/computers/item/pda/pda_presets.dm
+++ b/code/modules/modular_computers/computers/item/pda/pda_presets.dm
@@ -7,6 +7,9 @@
/obj/item/computer_hardware/printer/mini
)
+ starting_files = list( new /datum/computer_file/program/budgetorders,
+ new /datum/computer_file/program/bounty_board)
+
// This is literally the worst possible cheap tablet
/obj/item/modular_computer/tablet/pda/preset/basic
desc = "A standard issue PDA often given to station personnel."
diff --git a/code/modules/modular_computers/computers/item/phone/phone.dm b/code/modules/modular_computers/computers/item/phone/phone.dm
index b0374a406d73..e95a33d5e715 100644
--- a/code/modules/modular_computers/computers/item/phone/phone.dm
+++ b/code/modules/modular_computers/computers/item/phone/phone.dm
@@ -6,7 +6,7 @@
icon_state_unpowered = "phone"
icon_state_powered = "phone"
hardware_flag = PROGRAM_PHONE
- max_hardware_size = WEIGHT_CLASS_TINY
+ max_hardware_size = WEIGHT_CLASS_SMALL
w_class = WEIGHT_CLASS_SMALL
max_bays = 2
steel_sheet_cost = 1
diff --git a/code/modules/modular_computers/computers/item/phone/phone_presets.dm b/code/modules/modular_computers/computers/item/phone/phone_presets.dm
index cadef4f92682..4804ecb26652 100644
--- a/code/modules/modular_computers/computers/item/phone/phone_presets.dm
+++ b/code/modules/modular_computers/computers/item/phone/phone_presets.dm
@@ -1,4 +1,8 @@
// This is literally the worst possible cheap phone
+/obj/item/modular_computer/tablet/phone/preset
+ starting_files = list( new /datum/computer_file/program/budgetorders,
+ new /datum/computer_file/program/bounty_board)
+
/obj/item/modular_computer/tablet/phone/preset/cheap
desc = "A low-end tablet often seen among low ranked station personnel."
starting_components = list( /obj/item/computer_hardware/processor_unit/small,
@@ -9,14 +13,14 @@
// Alternative version, an average one, for higher ranked positions mostly
/obj/item/modular_computer/tablet/phone/preset/advanced
- starting_components = list( /obj/item/computer_hardware/processor_unit/small,
+ starting_components = list( /obj/item/computer_hardware/processor_unit/small,
/obj/item/stock_parts/cell/computer,
/obj/item/computer_hardware/hard_drive/small/pda,
/obj/item/computer_hardware/network_card,
/obj/item/computer_hardware/card_slot)
/obj/item/modular_computer/tablet/phone/preset/cargo
- starting_components = list( /obj/item/computer_hardware/processor_unit/small,
+ starting_components = list( /obj/item/computer_hardware/processor_unit/small,
/obj/item/stock_parts/cell/computer,
/obj/item/computer_hardware/hard_drive/small/pda,
/obj/item/computer_hardware/network_card,
@@ -24,7 +28,7 @@
/obj/item/computer_hardware/printer/mini)
/obj/item/modular_computer/tablet/phone/preset/advanced/atmos
- starting_components = list( /obj/item/computer_hardware/processor_unit/small,
+ starting_components = list( /obj/item/computer_hardware/processor_unit/small,
/obj/item/stock_parts/cell/computer,
/obj/item/computer_hardware/hard_drive/small/pda,
/obj/item/computer_hardware/network_card,
@@ -32,9 +36,7 @@
/obj/item/computer_hardware/sensorpackage)
/obj/item/modular_computer/tablet/phone/preset/advanced/command
- starting_files = list( new /datum/computer_file/program/budgetorders,
- new /datum/computer_file/program/card_mod)
- starting_components = list( /obj/item/computer_hardware/processor_unit/small,
+ starting_components = list( /obj/item/computer_hardware/processor_unit/small,
/obj/item/stock_parts/cell/computer,
/obj/item/computer_hardware/hard_drive/small/pda,
/obj/item/computer_hardware/network_card,
@@ -42,6 +44,12 @@
/obj/item/computer_hardware/card_slot/secondary,
/obj/item/computer_hardware/printer/mini)
+/obj/item/modular_computer/tablet/phone/preset/advanced/command/Initialize(mapload)
+ starting_files |= list(
+ new /datum/computer_file/program/card_mod
+ )
+ . = ..()
+
/obj/item/modular_computer/tablet/phone/preset/advanced/command/cap
finish_color = "yellow"
pen_type = /obj/item/pen/fountain/captain
@@ -51,40 +59,45 @@
RegisterSignal(src, COMSIG_TABLET_CHECK_DETONATE, PROC_REF(pda_no_detonate))
/obj/item/modular_computer/tablet/phone/preset/advanced/command/hop
- starting_files = list( new /datum/computer_file/program/budgetorders,
- new /datum/computer_file/program/card_mod,
- new /datum/computer_file/program/cargobounty)
finish_color = "brown"
+/obj/item/modular_computer/tablet/phone/preset/advanced/command/hop/Initialize(mapload)
+ starting_files |= list(
+ new /datum/computer_file/program/cargobounty
+ )
+ . = ..()
+
/obj/item/modular_computer/tablet/phone/preset/advanced/command/hos
finish_color = "red"
/obj/item/modular_computer/tablet/phone/preset/advanced/command/ce
- starting_components = list( /obj/item/computer_hardware/processor_unit/small,
+ starting_components = list( /obj/item/computer_hardware/processor_unit/small,
/obj/item/stock_parts/cell/computer,
/obj/item/computer_hardware/hard_drive/small/pda,
/obj/item/computer_hardware/network_card,
/obj/item/computer_hardware/card_slot,
/obj/item/computer_hardware/card_slot/secondary,
/obj/item/computer_hardware/sensorpackage)
+ finish_color = "orange"
- starting_files = list( new /datum/computer_file/program/budgetorders,
- new /datum/computer_file/program/card_mod,
- new /datum/computer_file/program/alarm_monitor,
+/obj/item/modular_computer/tablet/phone/preset/advanced/command/ce/Initialize(mapload)
+ starting_files |= list( new /datum/computer_file/program/alarm_monitor,
new /datum/computer_file/program/supermatter_monitor,
new /datum/computer_file/program/nuclear_monitor,
new /datum/computer_file/program/energy_harvester_control)
- finish_color = "orange"
+ . = ..()
/obj/item/modular_computer/tablet/phone/preset/advanced/command/rd
- starting_files = list( new /datum/computer_file/program/budgetorders,
- new /datum/computer_file/program/card_mod,
- new /datum/computer_file/program/robocontrol)
finish_color = "purple"
pen_type = /obj/item/pen/fountain
+/obj/item/modular_computer/tablet/phone/preset/advanced/command/rd/Initialize(mapload)
+ starting_files |= list( new /datum/computer_file/program/robocontrol)
+ . = ..()
+
/obj/item/modular_computer/tablet/phone/preset/advanced/command/cmo
- starting_files = list( new /datum/computer_file/program/budgetorders,
- new /datum/computer_file/program/card_mod,
- new /datum/computer_file/program/crew_monitor)
finish_color = "white"
+
+/obj/item/modular_computer/tablet/phone/preset/advanced/command/cmo/Initialize(mapload)
+ starting_files |= list( new /datum/computer_file/program/crew_monitor)
+ . = ..()
diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm
index 0720cfe412af..150c18b2cf05 100644
--- a/code/modules/modular_computers/computers/item/processor.dm
+++ b/code/modules/modular_computers/computers/item/processor.dm
@@ -43,26 +43,28 @@
startup_sound = machinery_computer.startup_sound
shutdown_sound = machinery_computer.shutdown_sound
interact_sounds = machinery_computer.interact_sounds
+ machinery_computer.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, TYPE_PROC_REF(/obj/machinery/modular_computer, relay_icon_update)) //when we update_icon, also update the computer
..()
STOP_PROCESSING(SSobj, src) // Processed by its machine
+/obj/item/modular_computer/processor/Destroy(force)
+ if(machinery_computer && (machinery_computer.cpu == src))
+ machinery_computer.cpu = null
+ machinery_computer.UnregisterSignal(src, COMSIG_ATOM_UPDATED_ICON)
+ machinery_computer = null
+ return ..()
+
/obj/item/modular_computer/processor/relay_qdel()
qdel(machinery_computer)
-// This thing is not meant to be used on it's own, get topic data from our machinery owner.
-//obj/item/modular_computer/processor/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
-// if(!machinery_computer)
-// return 0
-
-// return machinery_computer.canUseTopic(user, state)
/obj/item/modular_computer/processor/shutdown_computer()
if(!machinery_computer)
return
..()
- machinery_computer.update_appearance(UPDATE_ICON)
+ machinery_computer.update_appearance()
return
/obj/item/modular_computer/processor/attack_ghost(mob/user)
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index 0bfd15dc4272..91dc2c61bf20 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -3,22 +3,28 @@
/obj/machinery/modular_computer
name = "modular computer"
desc = "An advanced computer."
-
+ icon = 'icons/obj/modular_console.dmi'
+ icon_state = "console"
use_power = IDLE_POWER_USE
idle_power_usage = 5
- var/hardware_flag = 0 // A flag that describes this device type
- var/last_power_usage = 0 // Power usage during last tick
+ density = TRUE
+ ///A flag that describes this device type
+ var/hardware_flag = PROGRAM_CONSOLE
+ /// Power usage during last tick
+ var/last_power_usage = 0
// Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..)
// must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently
// If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example.
- icon = null
- icon_state = null
- var/icon_state_unpowered = null // Icon state when the computer is turned off.
- var/icon_state_powered = null // Icon state when the computer is turned on.
- var/screen_icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen.
- var/screen_icon_screensaver = "standby" // Icon state overlay when the computer is powered, but not 'switched on'.
+ ///Icon state when the computer is turned off.
+ var/icon_state_unpowered = "console-off"
+ ///Icon state when the computer is turned on.
+ var/icon_state_powered = "console"
+ ///Icon state overlay when the computer is turned on, but no program is loaded that would override the screen.
+ var/screen_icon_state_menu = "menu"
+ ///Icon state overlay when the computer is powered, but not 'switched on'.
+ var/screen_icon_screensaver = "standby"
var/overlay_skin = null
var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed.
var/steel_sheet_cost = 10 // Amount of steel sheets refunded when disassembling an empty frame of this computer.
@@ -42,6 +48,8 @@
. = ..()
cpu = new(src)
cpu.physical = src
+ cpu.screen_on = TRUE
+ update_appearance()
/obj/machinery/modular_computer/Destroy()
QDEL_NULL(cpu)
@@ -75,9 +83,36 @@
return FALSE
return (cpu.emag_act(user, emag_card))
-/obj/machinery/modular_computer/update_icon(updates=ALL)
+/obj/machinery/modular_computer/update_appearance(updates)
. = ..()
- cpu.update_appearance(UPDATE_ICON)
+ set_light(cpu?.enabled ? light_strength : 0)
+
+/obj/machinery/modular_computer/update_icon_state()
+ if(!cpu || !cpu.enabled || (stat & NOPOWER))
+ icon_state = icon_state_unpowered
+ else
+ icon_state = icon_state_powered
+ return ..()
+
+/obj/machinery/modular_computer/update_overlays()
+ . = ..()
+ if(!cpu)
+ return .
+
+ if(cpu.enabled)
+ . += cpu.active_program?.program_icon_state || screen_icon_state_menu
+ else if(!(stat & NOPOWER))
+ . += screen_icon_screensaver
+
+ if(cpu.obj_integrity <= cpu.integrity_failure)
+ . += "bsod"
+ . += "broken"
+ return .
+
+/// Eats the "source" arg because update_icon actually expects args now.
+/obj/machinery/modular_computer/proc/relay_icon_update(datum/source, updates, updated)
+ SIGNAL_HANDLER
+ return update_icon(updates)
/obj/machinery/modular_computer/AltClick(mob/user)
if(cpu)
diff --git a/code/modules/modular_computers/file_system/computer_file.dm b/code/modules/modular_computers/file_system/computer_file.dm
index 56461984dd89..144f7290215c 100644
--- a/code/modules/modular_computers/file_system/computer_file.dm
+++ b/code/modules/modular_computers/file_system/computer_file.dm
@@ -1,11 +1,19 @@
/datum/computer_file
- var/filename = "NewFile" // Placeholder. No spacebars
- var/filetype = "XXX" // File full names are [filename].[filetype] so like NewFile.XXX in this case
- var/size = 1 // File size in GQ. Integers only!
- var/obj/item/computer_hardware/hard_drive/holder // Holder that contains this file.
- var/unsendable = FALSE // Whether the file may be sent to someone via NTNet transfer or other means.
- var/undeletable = FALSE // Whether the file may be deleted. Setting to TRUE prevents deletion/renaming/etc.
- var/uid // UID of this file
+ ///The name of the internal file shown in file management.
+ var/filename = "NewFile"
+ ///The type of file format the file is in, placed after filename. PNG, TXT, ect. This would be NewFile.XXX
+ var/filetype = "XXX"
+ ///How much GQ storage space the file will take to store. Integers only!
+ var/size = 1
+ ///Holder that contains this file.
+ var/obj/item/computer_hardware/hard_drive/holder
+ ///Whether the file may be sent to someone via NTNet transfer or other means.
+ var/unsendable = FALSE
+ ///Whether the file may be deleted. Setting to TRUE prevents deletion/renaming/etc.
+ var/undeletable = FALSE
+ ///The computer file's personal ID
+ var/uid
+ ///Static ID to ensure all IDs are unique.
var/static/file_uid = 0
/datum/computer_file/New()
diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm
index 0efcfdb19b62..0e07c82176e4 100644
--- a/code/modules/modular_computers/file_system/program.dm
+++ b/code/modules/modular_computers/file_system/program.dm
@@ -68,7 +68,7 @@
// Relays icon update to the computer.
/datum/computer_file/program/proc/update_computer_icon()
if(computer)
- computer.update_appearance(UPDATE_ICON)
+ computer.update_appearance()
// Attempts to create a log in global ntnet datum. Returns 1 on success, 0 on fail.
/datum/computer_file/program/proc/generate_network_log(text)
diff --git a/code/modules/modular_computers/file_system/programs/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/engineering/ntmonitor.dm
similarity index 100%
rename from code/modules/modular_computers/file_system/programs/ntmonitor.dm
rename to code/modules/modular_computers/file_system/programs/engineering/ntmonitor.dm
diff --git a/code/modules/modular_computers/file_system/programs/minesweeper.dm b/code/modules/modular_computers/file_system/programs/minesweeper.dm
index 14fd9c0e6de1..240e593fc382 100644
--- a/code/modules/modular_computers/file_system/programs/minesweeper.dm
+++ b/code/modules/modular_computers/file_system/programs/minesweeper.dm
@@ -154,6 +154,8 @@
var/difficulty = MINESWEEPER_BEGINNER
var/value = MINESWEEPER_BEGINNER
var/current_difficulty = "Beginner"
+ ///determines if a game has been won while it is emagged or not
+ var/emagwin = FALSE
var/starting_time = 0
var/time_frozen = 0
@@ -200,7 +202,10 @@
return TRUE
/datum/minesweeper/proc/change_difficulty(diff)
- difficulty = diff
+ if(host.obj_flags & EMAGGED)
+ difficulty = MINESWEEPER_EXPERT
+ else
+ difficulty = diff
return TRUE
/datum/minesweeper/proc/new_game()
@@ -286,38 +291,45 @@
var/result = select_square(x,y)
game_status = result
if(result == MINESWEEPER_VICTORY)
- play_snd('yogstation/sound/arcade/minesweeper_win.ogg')
- host.say("You cleared the board of all mines! Congratulations!")
- if(emaggable && host.obj_flags & EMAGGED && value >= 1)
+ winner_chicken_dinner(user)
+
+ if(result == MINESWEEPER_DEAD && emaggable && (host.obj_flags & EMAGGED))
+ // One crossed wire, one wayward pinch of potassium chlorate, ONE ERRANT TWITCH
+ // AND
+ KABLOOEY()
+
+ if(result)
+ time_frozen = REALTIMEOFDAY - starting_time
+
+ return TRUE
+
+/datum/minesweeper/proc/winner_chicken_dinner(mob/user)
+ play_snd('yogstation/sound/arcade/minesweeper_win.ogg')
+ host.say("You cleared the board of all mines! Congratulations!")
+ ticket_count += value
+ if(emaggable && host.obj_flags & EMAGGED && value >= 1 && !emagwin)
+
+ if(difficulty != MINESWEEPER_EXPERT)
+ return
+ else
var/itemname
switch(rand(1,3))
if(1)
itemname = "a syndicate bomb beacon"
new /obj/item/sbeacondrop/bomb(host.loc)
if(2)
- itemname = "a rocket launcher"
- new /obj/item/gun/ballistic/rocketlauncher/unrestricted(host.loc)
- new /obj/item/ammo_casing/caseless/rocket/hedp(host.loc)
- new /obj/item/ammo_casing/caseless/rocket/hedp(host.loc)
- new /obj/item/ammo_casing/caseless/rocket/hedp(host.loc)
+ itemname = "two deployable syndicate mines disguised as ducks"
+ new /obj/item/deployablemine/traitor(host.loc)
+ new /obj/item/deployablemine/traitor(host.loc)
if(3)
- itemname = "two bags of c4"
+ itemname = "a bag of c4 & x4 charges"
new /obj/item/storage/backpack/duffelbag/syndie/c4(host.loc)
new /obj/item/storage/backpack/duffelbag/syndie/x4(host.loc)
- message_admins("[key_name_admin(user)] won emagged Minesweeper and got [itemname]!")
+ emagwin = TRUE
+ message_admins("[user] won emagged Minesweeper and got [itemname]!")
vis_msg(span_notice("[host] dispenses [itemname]!"), span_notice("You hear a chime and a clunk."))
- else
- ticket_count += value
-
- if(result == MINESWEEPER_DEAD && emaggable && (host.obj_flags & EMAGGED))
- // One crossed wire, one wayward pinch of potassium chlorate, ONE ERRANT TWITCH
- // AND
- KABLOOEY()
-
- if(result)
- time_frozen = REALTIMEOFDAY - starting_time
+ return
- return TRUE
/datum/minesweeper/proc/generate_new_board(diff)
board_data = new /list(31,18) // Fresh board
@@ -447,3 +459,4 @@
/datum/minesweeper/proc/KABLOOEY()
explosion(get_turf(host),1,3,rand(1,5),rand(1,10))
+
diff --git a/code/modules/modular_computers/file_system/programs/security/secureye.dm b/code/modules/modular_computers/file_system/programs/security/secureye.dm
index 82b6f9ac1c29..1f4e57c88f3a 100644
--- a/code/modules/modular_computers/file_system/programs/security/secureye.dm
+++ b/code/modules/modular_computers/file_system/programs/security/secureye.dm
@@ -1,3 +1,4 @@
+
#define DEFAULT_MAP_SIZE 15
/datum/computer_file/program/secureye
@@ -9,54 +10,45 @@
extended_desc = "This program allows access to standard security camera networks."
requires_ntnet = TRUE
transfer_access = ACCESS_BRIG
- usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP | PROGRAM_INTEGRATED // Probably not a good idea to let borgs use this, though im curious how it will pan out
+ usage_flags = PROGRAM_LAPTOP |PROGRAM_STATIONARY| PROGRAM_INTEGRATED // Probably not a good idea to let borgs use this, though im curious how it will pan out
size = 10
tgui_id = "NtosSecurEye"
program_icon = "eye"
var/list/network = list("ss13")
- var/obj/machinery/camera/active_camera
+ ///List of weakrefs of all users watching the program.
+ var/list/concurrent_users = list()
+
+ /// Weakref to the active camera
+ var/datum/weakref/camera_ref
/// The turf where the camera was last updated.
var/turf/last_camera_turf
- var/list/concurrent_users = list()
// Stuff needed to render the map
- var/map_name
var/atom/movable/screen/map_view/cam_screen
/// All the plane masters that need to be applied.
- var/list/cam_plane_masters
var/atom/movable/screen/background/cam_background
/datum/computer_file/program/secureye/New()
. = ..()
// Map name has to start and end with an A-Z character,
// and definitely NOT with a square bracket or even a number.
- map_name = "camera_console_[REF(src)]_map"
+ var/map_name = "camera_console_[REF(src)]_map"
// Convert networks to lowercase
for(var/i in network)
network -= i
network += lowertext(i)
// Initialize map objects
cam_screen = new
- cam_screen.name = "screen"
- cam_screen.assigned_map = map_name
- cam_screen.del_on_map_removal = FALSE
- cam_screen.screen_loc = "[map_name]:1,1"
- cam_plane_masters = list()
- for(var/plane in subtypesof(/atom/movable/screen/plane_master))
- var/atom/movable/screen/instance = new plane()
- instance.assigned_map = map_name
- instance.del_on_map_removal = FALSE
- instance.screen_loc = "[map_name]:CENTER"
- cam_plane_masters += instance
+ cam_screen.generate_view(map_name)
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
/datum/computer_file/program/secureye/Destroy()
- qdel(cam_screen)
- QDEL_LIST(cam_plane_masters)
- qdel(cam_background)
+ QDEL_NULL(cam_screen)
+ QDEL_NULL(cam_background)
+ last_camera_turf = null
return ..()
/datum/computer_file/program/secureye/ui_interact(mob/user, datum/tgui/ui)
@@ -74,32 +66,39 @@
if(is_living)
concurrent_users += user_ref
// Register map objects
- user.client.register_map_obj(cam_screen)
- for(var/plane in cam_plane_masters)
- user.client.register_map_obj(plane)
+ cam_screen.display_to(user)
user.client.register_map_obj(cam_background)
return ..()
+/datum/computer_file/program/secureye/ui_status(mob/user)
+ . = ..()
+ if(. == UI_DISABLED)
+ return UI_CLOSE
+ return .
+
/datum/computer_file/program/secureye/ui_data()
var/list/data = get_header_data()
- data["network"] = network
data["activeCamera"] = null
+ var/obj/machinery/camera/active_camera = camera_ref?.resolve()
if(active_camera)
data["activeCamera"] = list(
name = active_camera.c_tag,
+ ref = REF(active_camera),
status = active_camera.status,
)
return data
/datum/computer_file/program/secureye/ui_static_data()
var/list/data = list()
- data["mapRef"] = map_name
- var/list/cameras = get_available_cameras()
+ data["network"] = network
+ data["mapRef"] = cam_screen.assigned_map
+ var/list/cameras = get_camera_list(network)
data["cameras"] = list()
for(var/i in cameras)
var/obj/machinery/camera/C = cameras[i]
data["cameras"] += list(list(
name = C.c_tag,
+ ref = REF(C),
))
return data
@@ -108,21 +107,20 @@
. = ..()
if(.)
return
-
- if(action == "switch_camera")
- var/c_tag = params["name"]
- var/list/cameras = get_available_cameras()
- var/obj/machinery/camera/selected_camera = cameras[c_tag]
- active_camera = selected_camera
- playsound(src, get_sfx("terminal_type"), 25, FALSE)
-
- if(!selected_camera)
+ switch(action)
+ if("switch_camera")
+ var/obj/machinery/camera/selected_camera = locate(params["camera"]) in GLOB.cameranet.cameras
+ if(selected_camera)
+ camera_ref = WEAKREF(selected_camera)
+ else
+ camera_ref = null
+ playsound(src, get_sfx("terminal_type"), 25, FALSE)
+ if(isnull(camera_ref))
+ return TRUE
+
+ update_active_camera_screen()
return TRUE
- update_active_camera_screen()
-
- return TRUE
-
/datum/computer_file/program/secureye/ui_close(mob/user)
. = ..()
var/user_ref = REF(user)
@@ -130,28 +128,41 @@
// Living creature or not, we remove you anyway.
concurrent_users -= user_ref
// Unregister map objects
- user.client.clear_map(map_name)
+ cam_screen.hide_from(user)
// Turn off the console
if(length(concurrent_users) == 0 && is_living)
- active_camera = null
+ camera_ref = null
+ last_camera_turf = null
playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE)
/datum/computer_file/program/secureye/proc/update_active_camera_screen()
+ var/obj/machinery/camera/active_camera = camera_ref?.resolve()
// Show static if can't use the camera
if(!active_camera?.can_use())
show_camera_static()
return
- var/originator = active_camera
- if(active_camera.built_in)
- originator = get_turf(active_camera.built_in)
-
var/list/visible_turfs = list()
- for(var/turf/T in (active_camera.isXRay() \
- ? range(active_camera.view_range, originator) \
- : view(active_camera.view_range, originator)))
- visible_turfs += T
+ // Get the camera's turf to correctly gather what's visible from it's turf, in case it's located in a moving object (borgs / mechs)
+ var/new_cam_turf = get_turf(active_camera)
+
+ // If we're not forcing an update for some reason and the cameras are in the same location,
+ // we don't need to update anything.
+ // Most security cameras will end here as they're not moving.
+ if(last_camera_turf == new_cam_turf)
+ return
+
+ // Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs.
+ last_camera_turf = new_cam_turf
+
+ //Here we gather what's visible from the camera's POV based on its view_range and xray modifier if present
+ var/list/visible_things = active_camera.isXRay(ignore_malf_upgrades = TRUE) ? range(active_camera.view_range, new_cam_turf) : view(active_camera.view_range, new_cam_turf)
+
+ for(var/turf/visible_turf in visible_things)
+ visible_turfs += visible_turf
+
+ //Get coordinates for a rectangle area that contains the turfs we see so we can then clear away the static in the resulting rectangle area
var/list/bbox = get_bbox_of_atoms(visible_turfs)
var/size_x = bbox[3] - bbox[1] + 1
var/size_y = bbox[4] - bbox[2] + 1
@@ -160,30 +171,12 @@
cam_background.icon_state = "clear"
cam_background.fill_rect(1, 1, size_x, size_y)
+
/datum/computer_file/program/secureye/proc/show_camera_static()
cam_screen.vis_contents.Cut()
cam_background.icon_state = "scanline2"
cam_background.fill_rect(1, 1, DEFAULT_MAP_SIZE, DEFAULT_MAP_SIZE)
-// Returns the list of cameras accessible from this computer
-/datum/computer_file/program/secureye/proc/get_available_cameras()
- var/list/L = list()
- for (var/obj/machinery/camera/cam in GLOB.cameranet.cameras)
- if(!(is_station_level(cam.z) || is_mining_level(cam.z) || !isnull(cam.built_in)))//Only show station cameras.
- continue
- L.Add(cam)
- var/list/camlist = list()
- for(var/obj/machinery/camera/cam in L)
- if(!cam.network)
- stack_trace("Camera in a cameranet has no camera network")
- continue
- if(!(islist(cam.network)))
- stack_trace("Camera in a cameranet has a non-list camera network")
- continue
- var/list/tempnetwork = cam.network & network
- if(tempnetwork.len)
- camlist["[cam.c_tag]"] = cam
- return camlist
//////////////////
//Mining Cameras//
@@ -215,3 +208,5 @@
program_icon = "dungeon"
network = list("labor")
+
+#undef DEFAULT_MAP_SIZE
diff --git a/code/modules/modular_computers/file_system/programs/supply/budgetordering.dm b/code/modules/modular_computers/file_system/programs/supply/budgetordering.dm
index c9f3ac343a79..205e006f48bd 100644
--- a/code/modules/modular_computers/file_system/programs/supply/budgetordering.dm
+++ b/code/modules/modular_computers/file_system/programs/supply/budgetordering.dm
@@ -130,11 +130,11 @@
var/message = "Remember to stamp and send back the supply manifests."
if(SSshuttle.centcom_message)
message = SSshuttle.centcom_message
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
message = blockade_warning
data["message"] = message
data["cart"] = list()
- for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ for(var/datum/supply_order/SO in SSshuttle.shopping_list)
data["cart"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.get_cost(),
@@ -145,7 +145,7 @@
))
data["requests"] = list()
- for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ for(var/datum/supply_order/SO in SSshuttle.request_list)
data["requests"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.get_cost(),
@@ -166,7 +166,7 @@
if(!SSshuttle.supply.canMove())
computer.say(safety_warning)
return
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
computer.say(blockade_warning)
return
if(SSshuttle.supply.getDockedId() == "supply_home")
@@ -182,7 +182,7 @@
if("loan")
if(!SSshuttle.shuttle_loan)
return
- if(SSshuttle.supplyBlocked)
+ if(SSshuttle.supply_blocked)
computer.say(blockade_warning)
return
else if(SSshuttle.supply.mode != SHUTTLE_IDLE)
@@ -246,39 +246,39 @@
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account, account?.account_holder)
SO.generateRequisition(T)
if((requestonly && !self_paid) || !(card_slot?.GetID()))
- SSshuttle.requestlist += SO
+ SSshuttle.request_list += SO
else
- SSshuttle.shoppinglist += SO
+ SSshuttle.shopping_list += SO
if(self_paid || budget_order)
computer.say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.")
. = TRUE
if("remove")
var/id = text2num(params["id"])
- for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ for(var/datum/supply_order/SO in SSshuttle.shopping_list)
if(SO.id == id)
- SSshuttle.shoppinglist -= SO
+ SSshuttle.shopping_list -= SO
. = TRUE
break
if("clear")
- SSshuttle.shoppinglist.Cut()
+ SSshuttle.shopping_list.Cut()
. = TRUE
if("approve")
var/id = text2num(params["id"])
- for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ for(var/datum/supply_order/SO in SSshuttle.request_list)
if(SO.id == id)
- SSshuttle.requestlist -= SO
- SSshuttle.shoppinglist += SO
+ SSshuttle.request_list -= SO
+ SSshuttle.shopping_list += SO
. = TRUE
break
if("deny")
var/id = text2num(params["id"])
- for(var/datum/supply_order/SO in SSshuttle.requestlist)
+ for(var/datum/supply_order/SO in SSshuttle.request_list)
if(SO.id == id)
- SSshuttle.requestlist -= SO
+ SSshuttle.request_list -= SO
. = TRUE
break
if("denyall")
- SSshuttle.requestlist.Cut()
+ SSshuttle.request_list.Cut()
. = TRUE
if("toggleprivate")
self_paid = !self_paid
diff --git a/code/modules/modular_computers/hardware/CPU.dm b/code/modules/modular_computers/hardware/CPU.dm
index 67e45480f604..206bb7b01691 100644
--- a/code/modules/modular_computers/hardware/CPU.dm
+++ b/code/modules/modular_computers/hardware/CPU.dm
@@ -22,7 +22,7 @@
icon_state = "cpu"
w_class = WEIGHT_CLASS_TINY
power_usage = 25
- max_idle_programs = 1
+ max_idle_programs = 2
/obj/item/computer_hardware/processor_unit/photonic
name = "photonic processor board"
@@ -38,7 +38,7 @@
icon_state = "cpu_super"
w_class = WEIGHT_CLASS_TINY
power_usage = 75
- max_idle_programs = 2
+ max_idle_programs = 3
/obj/item/computer_hardware/processor_unit/pda
name = "cheep microprocessor"
@@ -46,7 +46,7 @@
icon_state = "cpu"
w_class = WEIGHT_CLASS_TINY
power_usage = 25
- max_idle_programs = 2
+ max_idle_programs = 1
/obj/item/computer_hardware/processor_unit/pda/can_install(obj/item/modular_computer/M, mob/living/user)
if(!istype(M, /obj/item/modular_computer/tablet))
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index f0d10f749525..a505203c15c1 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -77,7 +77,6 @@
if(ishuman(user))
var/mob/living/carbon/human/H = user
H.sec_hud_set_ID()
-
return TRUE
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index 647627ddf7ab..7ff912f3cead 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -9,14 +9,31 @@
var/max_capacity = 128
var/used_capacity = 0
var/list/stored_files = list() // List of stored files on this drive. DO NOT MODIFY DIRECTLY!
+ ///Static list of default programs that come with ALL computers, here so computers don't have to repeat this.
+ ///Yog TODO: IT SHOULD BE STATIC BUT IT ISN'T BECAUSE WE HANDLE THE HARD DRIVES AWKWARDLY
+ var/list/datum/computer_file/default_programs = list(
+ /datum/computer_file/program/computerconfig,
+ /datum/computer_file/program/ntnetdownload,
+ /datum/computer_file/program/filemanager,
+ )
+
+/obj/item/computer_hardware/hard_drive/Initialize(mapload)
+ . = ..()
+ install_default_programs()
+
+/obj/item/computer_hardware/hard_drive/Destroy()
+ for(var/file in stored_files)
+ qdel(file)
+ stored_files = null
+ return ..()
/obj/item/computer_hardware/hard_drive/on_remove(obj/item/modular_computer/remove_from, mob/user)
remove_from.shutdown_computer()
/obj/item/computer_hardware/hard_drive/proc/install_default_programs()
- store_file(new/datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar
- store_file(new/datum/computer_file/program/ntnetdownload(src)) // NTNet Downloader Utility, allows users to download more software from NTNet repository
- store_file(new/datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation.
+ for(var/programs in default_programs)
+ var/datum/computer_file/program_type = new programs
+ store_file(program_type)
/obj/item/computer_hardware/hard_drive/examine(user)
. = ..()
@@ -29,11 +46,8 @@
to_chat(user, "Storage capacity: [used_capacity]/[max_capacity]GQ")
// Use this proc to add file to the drive. Returns the file on success and FALSE on failure. Contains necessary sanity checks.
-/obj/item/computer_hardware/hard_drive/proc/store_file(datum/computer_file/F)
- if(!F || !istype(F))
- return FALSE
-
- if(!can_store_file(F))
+/obj/item/computer_hardware/hard_drive/proc/store_file(datum/computer_file/file_storing)
+ if(!can_store_file(file_storing))
return FALSE
if(!check_functionality())
@@ -43,22 +57,22 @@
return FALSE
// This file is already stored. Don't store it again.
- if(F in stored_files)
- return FALSE
+ if(file_storing in stored_files)
+ return TRUE
- F.holder = src
+ file_storing.holder = src
- if(istype(F, /datum/computer_file/program))
- var/datum/computer_file/program/P = F
+ if(istype(file_storing, /datum/computer_file/program))
+ var/datum/computer_file/program/P = file_storing
P.computer = holder
- stored_files.Add(F)
+ stored_files.Add(file_storing)
recalculate_size()
- return F
+ return file_storing
// Use this proc to remove file from the drive. Returns TRUE on success and FALSE on failure. Contains necessary sanity checks.
-/obj/item/computer_hardware/hard_drive/proc/remove_file(datum/computer_file/F)
- if(!F || !istype(F))
+/obj/item/computer_hardware/hard_drive/proc/remove_file(datum/computer_file/file_removing)
+ if(!file_removing || !istype(file_removing))
return FALSE
if(!stored_files)
@@ -67,8 +81,8 @@
if(!check_functionality())
return FALSE
- if(F in stored_files)
- stored_files -= F
+ if(file_removing in stored_files)
+ stored_files -= file_removing
recalculate_size()
return TRUE
else
@@ -83,14 +97,14 @@
used_capacity = total_size
// Checks whether file can be stored on the hard drive. We can only store unique files, so this checks whether we wouldn't get a duplicity by adding a file.
-/obj/item/computer_hardware/hard_drive/proc/can_store_file(datum/computer_file/F)
- if(!F || !istype(F))
+/obj/item/computer_hardware/hard_drive/proc/can_store_file(datum/computer_file/file_storing)
+ if(!file_storing || !istype(file_storing))
return FALSE
- if(F in stored_files)
+ if(file_storing in stored_files)
return FALSE
- var/name = F.filename + "." + F.filetype
+ var/name = file_storing.filename + "." + file_storing.filetype
for(var/datum/computer_file/file in stored_files)
if((file.filename + "." + file.filetype) == name)
return FALSE
@@ -99,7 +113,7 @@
// BYOND is acting weird with numbers above 999 in loops (infinite loop prevention)
if(stored_files.len >= 999)
return FALSE
- if((used_capacity + F.size) > max_capacity)
+ if((used_capacity + file_storing.size) > max_capacity)
return FALSE
else
return TRUE
@@ -131,17 +145,6 @@
return FALSE
-/obj/item/computer_hardware/hard_drive/Destroy()
- for(var/F in stored_files)
- qdel(F)
- stored_files = null
- return ..()
-
-/obj/item/computer_hardware/hard_drive/Initialize(mapload)
- . = ..()
- install_default_programs()
-
-
/obj/item/computer_hardware/hard_drive/advanced
name = "advanced hard disk drive"
desc = "A hybrid HDD, for use in higher grade computers where balance between power efficiency and capacity is desired."
@@ -209,11 +212,11 @@
power_usage = 8
max_capacity = 70
var/datum/antagonist/traitor/traitor_data // Syndicate hard drive has the user's data baked directly into it on creation
-
-/obj/item/computer_hardware/hard_drive/small/syndicate/install_default_programs()
- store_file(new/datum/computer_file/program/computerconfig(src))
- store_file(new/datum/computer_file/program/ntnetdownload/emagged(src))
- store_file(new/datum/computer_file/program/filemanager(src))
+ default_programs = list(
+ /datum/computer_file/program/computerconfig,
+ /datum/computer_file/program/ntnetdownload/emagged,
+ /datum/computer_file/program/filemanager,
+ )
/// For PDAs, comes pre-equipped with PDA messaging
/obj/item/computer_hardware/hard_drive/small/pda
@@ -221,18 +224,16 @@
..()
store_file(new/datum/computer_file/program/themeify(src))
store_file(new/datum/computer_file/program/pdamessager(src))
- store_file(new/datum/computer_file/program/budgetorders(src))
- store_file(new/datum/computer_file/program/bounty_board(src))
/// For tablets given to nuke ops
/obj/item/computer_hardware/hard_drive/small/nukeops
power_usage = 8
max_capacity = 70
-
-/obj/item/computer_hardware/hard_drive/small/nukeops/install_default_programs()
- store_file(new/datum/computer_file/program/computerconfig(src))
- store_file(new/datum/computer_file/program/ntnetdownload/syndicate(src)) // Syndicate version; automatic access to syndicate apps and no NT apps
- store_file(new/datum/computer_file/program/filemanager(src))
+ default_programs = list(
+ /datum/computer_file/program/computerconfig,
+ /datum/computer_file/program/ntnetdownload/syndicate,
+ /datum/computer_file/program/filemanager,
+ )
/obj/item/computer_hardware/hard_drive/micro
name = "micro solid state drive"
diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm
index 390ac4ec582d..a702df3ea7eb 100644
--- a/code/modules/modular_computers/hardware/network_card.dm
+++ b/code/modules/modular_computers/hardware/network_card.dm
@@ -68,7 +68,7 @@
icon_state = "radio"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
- w_class = WEIGHT_CLASS_TINY
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/computer_hardware/network_card/wired
name = "wired network card"
@@ -76,7 +76,7 @@
ethernet = 1
power_usage = 100 // Better range but higher power usage.
icon_state = "net_wired"
- w_class = WEIGHT_CLASS_BULKY
+ w_class = WEIGHT_CLASS_NORMAL
/obj/item/computer_hardware/network_card/integrated //Borg tablet version, only works while the borg has power and is not locked
name = "cyborg data link"
diff --git a/code/modules/ninja/suit/ninjaDrainAct.dm b/code/modules/ninja/suit/ninjaDrainAct.dm
index 0b364d18b37c..3e6f2c718c2b 100644
--- a/code/modules/ninja/suit/ninjaDrainAct.dm
+++ b/code/modules/ninja/suit/ninjaDrainAct.dm
@@ -158,7 +158,7 @@ They *could* go in their appropriate files, but this is supposed to be modular
if(!do_after(H, 30 SECONDS, target = src))
return
- stored_research.modify_points_all(0)
+ stored_research.set_points_all(0)
to_chat(H, span_notice("Sabotage complete. Research notes corrupted."))
var/datum/antagonist/ninja/ninja_antag = H.mind.has_antag_datum(/datum/antagonist/ninja)
if(!ninja_antag)
diff --git a/code/modules/paperwork/paper_bundle.dm b/code/modules/paperwork/paper_bundle.dm
index c9a1d7e0e80c..014cbc2428da 100644
--- a/code/modules/paperwork/paper_bundle.dm
+++ b/code/modules/paperwork/paper_bundle.dm
@@ -180,7 +180,7 @@
set category = "Object"
set src in usr
- var/n_name = sanitize(copytext(input(usr, "What would you like to label the bundle?", "Bundle Labelling", null) as text, 1, MAX_NAME_LEN))
+ var/n_name = sanitize(copytext_char(input(usr, "What would you like to label the bundle?", "Bundle Labelling", null) as text, 1, MAX_NAME_LEN)) //Dripstation edit - russian language
if((loc == usr && usr.stat == 0))
name = "[(n_name ? text("[n_name]") : "paper")]"
add_fingerprint(usr)
diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm
index 99adb60cdc7d..0a7af4a43f1d 100644
--- a/code/modules/paperwork/paperplane.dm
+++ b/code/modules/paperwork/paperplane.dm
@@ -46,7 +46,7 @@
var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES)
user.Stun(200)
user.visible_message(span_suicide("[user] jams [src] in [user.p_their()] nose. It looks like [user.p_theyre()] trying to commit suicide!"))
- user.adjust_blurriness(6)
+ user.adjust_eye_blur(6)
if(eyes)
eyes.applyOrganDamage(rand(6,8))
sleep(1 SECONDS)
@@ -114,7 +114,7 @@
if(H.is_eyes_covered())
return
visible_message(span_danger("\The [src] hits [H] in the eye!"))
- H.adjust_blurriness(6)
+ H.adjust_eye_blur(6)
eyes.applyOrganDamage(rand(6,8))
H.Paralyze(40)
H.emote("scream")
diff --git a/code/modules/particles/byond_particles/particle_holder.dm b/code/modules/particles/byond_particles/particle_holder.dm
index 2947b2903957..9c018ede970a 100644
--- a/code/modules/particles/byond_particles/particle_holder.dm
+++ b/code/modules/particles/byond_particles/particle_holder.dm
@@ -41,7 +41,7 @@
// /atom doesn't have vis_contents, /turf and /atom/movable do
var/atom/movable/lie_about_areas = parent
lie_about_areas.vis_contents += src
- RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(parent_deleted))
+ RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(parent_deleted))
if(particle_flags & PARTICLE_ATTACH_MOB)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index 5b25b248cab8..61910e78ad64 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -224,7 +224,7 @@
var/list/turfs = list()
var/list/mobs = list()
var/blueprints = FALSE
- var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1)
+ var/clone_area = SSmapping.request_turf_block_reservation(size_x * 2 + 1, size_y * 2 + 1, 1)
for(var/turf/T in block(locate(target_turf.x - size_x, target_turf.y - size_y, target_turf.z), locate(target_turf.x + size_x, target_turf.y + size_y, target_turf.z)))
if((ai_user && GLOB.cameranet.checkTurfVis(T)) || (T in seen))
turfs += T
diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm
index 16b2da44c36f..3146464045f5 100644
--- a/code/modules/photography/camera/camera_image_capturing.dm
+++ b/code/modules/photography/camera/camera_image_capturing.dm
@@ -16,13 +16,14 @@
var/wipe_atoms = FALSE
if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0)
- var/cloned_center_x = round(clone_area.bottom_left_coords[1] + ((total_x - 1) / 2))
- var/cloned_center_y = round(clone_area.bottom_left_coords[2] + ((total_y - 1) / 2))
+ var/turf/bottom_left = clone_area.bottom_left_turfs[1]
+ var/cloned_center_x = round(bottom_left.x + ((total_x - 1) / 2))
+ var/cloned_center_y = round(bottom_left.y + ((total_y - 1) / 2))
for(var/t in turfs)
var/turf/T = t
var/offset_x = T.x - center.x
var/offset_y = T.y - center.y
- var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, clone_area.bottom_left_coords[3])
+ var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, bottom_left.z)
if(!(newT in clone_area.reserved_turfs)) //sanity check so we don't overwrite other areas somehow
continue
atoms += new /obj/effect/appearance_clone(newT, T)
@@ -34,7 +35,7 @@
atoms += new /obj/effect/appearance_clone(newT, A)
skip_normal = TRUE
wipe_atoms = TRUE
- center = locate(cloned_center_x, cloned_center_y, clone_area.bottom_left_coords[3])
+ center = locate(cloned_center_x, cloned_center_y, bottom_left.z)
if(!skip_normal)
for(var/i in turfs)
diff --git a/code/modules/plumbing/ducts.dm b/code/modules/plumbing/ducts.dm
index 94d793dc5bde..4460fac89aaf 100644
--- a/code/modules/plumbing/ducts.dm
+++ b/code/modules/plumbing/ducts.dm
@@ -47,6 +47,7 @@ All the important duct code:
qdel(src) //replace with dropping or something
if(active)
attempt_connect()
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
/obj/machinery/duct/proc/attempt_connect()
reset_connects(0) //All connects are gathered here again eitherway, we might aswell reset it so they properly update when reconnecting
diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm
index bda5e2a63407..730ec6126080 100644
--- a/code/modules/point/point.dm
+++ b/code/modules/point/point.dm
@@ -7,7 +7,6 @@
*
* Not intended as a replacement for the mob verb
*/
-
/atom/movable/proc/point_at(atom/pointed_atom)
if(!isturf(loc))
return
@@ -22,20 +21,21 @@
var/turf/our_tile = get_turf(src)
var/obj/visual = new /obj/effect/temp_visual/point(our_tile, invisibility)
+
animate(visual, pixel_x = (tile.x - our_tile.x) * world.icon_size + pointed_atom.pixel_x, pixel_y = (tile.y - our_tile.y) * world.icon_size + pointed_atom.pixel_y, time = 0.17 SECONDS, easing = EASE_OUT)
/atom/movable/proc/create_point_bubble(atom/pointed_atom)
- var/obj/effect/thought_bubble_effect = new
-
var/mutable_appearance/thought_bubble = mutable_appearance(
'icons/effects/effects.dmi',
"thought_bubble",
- layer = FLOAT_LAYER,
+ offset_spokesman = src,
+ plane = POINT_PLANE,
appearance_flags = KEEP_APART,
)
var/mutable_appearance/pointed_atom_appearance = new(pointed_atom.appearance)
pointed_atom_appearance.blend_mode = BLEND_INSET_OVERLAY
+ pointed_atom_appearance.plane = FLOAT_PLANE
pointed_atom_appearance.layer = FLOAT_LAYER
pointed_atom_appearance.pixel_x = 0
pointed_atom_appearance.pixel_y = 0
@@ -48,7 +48,6 @@
thought_bubble.pixel_x = 16
thought_bubble.pixel_y = 32
thought_bubble.alpha = 200
- thought_bubble.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
var/mutable_appearance/point_visual = mutable_appearance(
'icons/mob/screen_gen.dmi',
@@ -58,26 +57,28 @@
thought_bubble.overlays += point_visual
- // vis_contents is used to preserve mouse opacity
- thought_bubble_effect.appearance = thought_bubble
- vis_contents += thought_bubble_effect
+ add_overlay(thought_bubble)
+ LAZYADD(update_overlays_on_z, thought_bubble)
+ addtimer(CALLBACK(src, PROC_REF(clear_point_bubble), thought_bubble), POINT_TIME)
- QDEL_IN(thought_bubble_effect, POINT_TIME)
+/atom/movable/proc/clear_point_bubble(mutable_appearance/thought_bubble)
+ LAZYREMOVE(update_overlays_on_z, thought_bubble)
+ cut_overlay(thought_bubble)
/obj/effect/temp_visual/point
name = "pointer"
icon = 'icons/mob/screen_gen.dmi'
icon_state = "arrow"
- layer = POINT_LAYER
+ plane = POINT_PLANE
duration = POINT_TIME
/obj/effect/temp_visual/point/Initialize(mapload, set_invis = 0)
. = ..()
var/atom/old_loc = loc
- loc = get_turf(src) // We don't want to actualy trigger anything when it moves
+ abstract_move(get_turf(src))
pixel_x = old_loc.pixel_x
pixel_y = old_loc.pixel_y
- invisibility = set_invis
+ SetInvisibility(set_invis)
#undef POINT_TIME
diff --git a/code/modules/pool/components/swimming.dm b/code/modules/pool/components/swimming.dm
index c61f8a12a204..290e5ee9bfc3 100644
--- a/code/modules/pool/components/swimming.dm
+++ b/code/modules/pool/components/swimming.dm
@@ -41,7 +41,7 @@
if(istype(C) && C?.dna?.species)
component_type = C.dna.species.swimming_component
var/mob/M = parent
- RemoveComponent()
+ qdel(src)
M.AddComponent(component_type)
/datum/component/swimming/proc/try_leave_pool(datum/source, turf/clicked_turf)
@@ -56,7 +56,7 @@
if(do_after(parent, 1 SECONDS, clicked_turf))
L.forceMove(clicked_turf)
L.visible_message("[parent] climbs out of the pool.")
- RemoveComponent()
+ qdel(src)
/datum/component/swimming/UnregisterFromParent()
exit_pool()
diff --git a/code/modules/pool/pool.dm b/code/modules/pool/pool.dm
index d9021beac0c6..9d3bb9a3a028 100644
--- a/code/modules/pool/pool.dm
+++ b/code/modules/pool/pool.dm
@@ -22,7 +22,7 @@ Place a pool filter somewhere in the pool if you want people to be able to modif
icon = 'icons/obj/pool.dmi'
icon_state = "pool"
sound = 'sound/effects/splash.ogg'
- flags_1 = CAN_BE_DIRTY_1|RAD_CONTAIN_CONTENTS // contains most of the rads on the tile within that tile
+ flags_1 = RAD_CONTAIN_CONTENTS // contains most of the rads on the tile within that tile
var/id = null //Set me if you don't want the pool and the pump to be in the same area, or you have multiple pools per area.
var/obj/effect/water_overlay = null
@@ -74,20 +74,22 @@ Place a pool filter somewhere in the pool if you want people to be able to modif
. = ..()
if(!istype(newloc, /turf/open/indestructible/sound/pool))
var/datum/component/swimming/S = Obj.GetComponent(/datum/component/swimming) //Handling admin TPs here.
- S?.RemoveComponent()
+ if(S)
+ qdel(S)
/turf/open/MouseDrop_T(atom/dropping, mob/user)
+ . = ..()
if(!isliving(user) || !isliving(dropping)) //No I don't want ghosts to be able to dunk people into the pool.
return
var/atom/movable/AM = dropping
var/datum/component/swimming/S = dropping.GetComponent(/datum/component/swimming)
- if(S)
- if(do_after(user, 1 SECONDS, src))
- S.RemoveComponent()
- visible_message("[dropping] climbs out of the pool.")
- AM.forceMove(src)
- else
- . = ..()
+ if(!S)
+ return
+ if(!do_after(user, 1 SECONDS, src))
+ return
+ qdel(S)
+ visible_message("[dropping] climbs out of the pool.")
+ AM.forceMove(src)
/turf/open/indestructible/sound/pool/MouseDrop_T(atom/dropping, mob/user)
if(!isliving(user) || !isliving(dropping)) //No I don't want ghosts to be able to dunk people into the pool.
@@ -314,7 +316,7 @@ GLOBAL_LIST_EMPTY(pool_filters)
if(S)
to_chat(user, "You start to climb out of the pool...")
if(do_after(user, 1 SECONDS, src))
- S.RemoveComponent()
+ qdel(S)
visible_message("[user] climbs out of the pool.")
if(!reversed)
user.forceMove(get_turf(get_step(src, NORTH))) //Ladders shouldn't adjoin another pool section. Ever.
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 215bfb7fab95..d631597a03b3 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -69,7 +69,7 @@
dir_amount = 4\
)
- var/lon_range = 1.5
+ var/light_on_range = 1.5
var/area/area
var/areastring = null
var/obj/item/stock_parts/cell/cell
@@ -230,7 +230,7 @@
/obj/machinery/power/apc/handle_atom_del(atom/A)
if(A == cell)
cell = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateUsrDialog()
/obj/machinery/power/apc/proc/make_terminal()
@@ -265,7 +265,7 @@
make_terminal()
addtimer(CALLBACK(src, PROC_REF(update)), 5)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/power/apc/examine(mob/user)
. = ..()
@@ -323,8 +323,11 @@
cell = best_cell
W.play_rped_sound()
-/obj/machinery/power/apc/update_icon(updates)
- updates = check_updates()
+/obj/machinery/power/apc/update_appearance(updates = check_updates())
+ icon_update_needed = FALSE
+ if(!updates)
+ return
+
. = ..()
// And now, separately for cleanness, the lighting changing
if(update_state & UPSTATE_ALLGOOD)
@@ -335,15 +338,13 @@
light_color = LIGHT_COLOR_BLUE
if(APC_FULLY_CHARGED)
light_color = LIGHT_COLOR_GREEN
- set_light(lon_range)
+ set_light(light_on_range)
else if(update_state & UPSTATE_BLUESCREEN)
light_color = LIGHT_COLOR_BLUE
- set_light(lon_range)
+ set_light(light_on_range)
else
set_light(0)
- icon_update_needed = FALSE
-
// update the APC icon to show the three base states
// also add overlays for indicator lights
/obj/machinery/power/apc/update_icon_state()
@@ -374,21 +375,20 @@
/obj/machinery/power/apc/update_overlays()
. = ..()
if(!(update_state & UPSTATE_ALLGOOD))
- SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ return
- SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(!(stat & (BROKEN|MAINT)) && update_state & UPSTATE_ALLGOOD)
- SSvis_overlays.add_vis_overlay(src, icon, "apcox-[locked]", layer, plane, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apcox-[locked]", layer, EMISSIVE_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco3-[charging]", layer, plane, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco3-[charging]", layer, EMISSIVE_PLANE, dir)
+ . += mutable_appearance(icon, "apcox-[locked]")
+ . += emissive_appearance(icon, "apcox-[locked]", src)
+ . += mutable_appearance(icon, "apco3-[charging]")
+ . += emissive_appearance(icon, "apco3-[charging]", src)
if(operating)
- SSvis_overlays.add_vis_overlay(src, icon, "apco0-[equipment]", layer, plane, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco0-[equipment]", layer, EMISSIVE_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco1-[lighting]", layer, plane, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco1-[lighting]", layer, EMISSIVE_PLANE, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco2-[environ]", layer, plane, dir)
- SSvis_overlays.add_vis_overlay(src, icon, "apco2-[environ]", layer, EMISSIVE_PLANE, dir)
+ . += mutable_appearance(icon, "apco0-[equipment]")
+ . += emissive_appearance(icon, "apco0-[equipment]", src)
+ . += mutable_appearance(icon, "apco1-[lighting]")
+ . += emissive_appearance(icon, "apco1-[lighting]", src)
+ . += mutable_appearance(icon, "apco2-[environ]")
+ . += emissive_appearance(icon, "apco2-[environ]", src)
/obj/machinery/power/apc/proc/check_updates()
var/last_update_state = update_state
@@ -515,7 +515,7 @@
else if (opened!=APC_COVER_REMOVED)
opened = APC_COVER_CLOSED
coverlocked = TRUE //closing cover relocks it
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
else if (!(stat & BROKEN))
if(coverlocked && !(stat & MAINT)) // locked...
@@ -526,7 +526,7 @@
return
else
opened = APC_COVER_OPENED
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
else
W.play_tool_sound(src)
@@ -535,7 +535,7 @@
W.play_tool_sound(src)
to_chat(user, span_notice("You pry the broken cover off of [src]."))
opened = APC_COVER_REMOVED
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
/obj/machinery/power/apc/screwdriver_act(mob/living/user, obj/item/W)
@@ -547,10 +547,10 @@
user.visible_message("[user] removes \the [cell] from [src]!",span_notice("You remove \the [cell]."))
var/turf/T = get_turf(user)
cell.forceMove(T)
- cell.update_appearance(UPDATE_ICON)
+ cell.update_appearance()
cell = null
charging = APC_NOT_CHARGING
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
else
switch (has_electronics)
@@ -567,14 +567,14 @@
else
to_chat(user, span_warning("There is nothing to secure!"))
return
- update_appearance(UPDATE_ICON)
+ update_appearance()
else if(obj_flags & EMAGGED)
to_chat(user, span_warning("The interface is broken!"))
return
else
panel_open = !panel_open
to_chat(user, span_notice("The wires have been [panel_open ? "exposed" : "unexposed"]."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/power/apc/wirecutter_act(mob/living/user, obj/item/W)
if (terminal && opened)
@@ -623,14 +623,14 @@
"[user.name] has inserted the power cell to [src.name]!",\
span_notice("You insert the power cell."))
chargecount = 0
- update_appearance(UPDATE_ICON)
+ update_appearance()
else if (W.GetID())
togglelock(user)
else if (istype(W, /obj/item/stack/cable_coil) && opened)
var/turf/host_turf = get_turf(src)
if(!host_turf)
CRASH("attackby on APC when it's not on a turf")
- if (host_turf.intact)
+ if(host_turf.underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
to_chat(user, span_warning("You must remove the floor plating in front of the APC first!"))
return
else if (terminal)
@@ -701,7 +701,7 @@
chargecount = 0
user.visible_message(span_notice("[user] fabricates a weak power cell and places it into [src]."), \
span_warning("Your [P.name] whirrs with strain as you create a weak power cell and place it into [src]!"))
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
to_chat(user, span_warning("[src] has both electronics and a cell."))
return
@@ -716,7 +716,7 @@
to_chat(user, span_notice("You replace missing APC's cover."))
qdel(W)
opened = APC_COVER_OPENED
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if (has_electronics)
to_chat(user, span_warning("You cannot repair this APC until you remove the electronics still inside!"))
@@ -730,7 +730,7 @@
obj_integrity = max_integrity
if (opened==APC_COVER_REMOVED)
opened = APC_COVER_OPENED
- update_appearance(UPDATE_ICON)
+ update_appearance()
else if(istype(W, /obj/item/clockwork/integration_cog) && is_servant_of_ratvar(user))
if(integration_cog)
to_chat(user, span_warning("This APC already has a cog."))
@@ -739,7 +739,7 @@
user.visible_message(span_warning("[user] slices [src]'s cover lock, and it swings wide open!"), \
span_alloy("You slice [src]'s cover lock apart with [W], and the cover swings open."))
opened = APC_COVER_OPENED
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
user.visible_message(span_warning("[user] presses [W] into [src]!"), \
span_alloy("You hold [W] in place within [src], and it slowly begins to warm up..."))
@@ -756,7 +756,7 @@
playsound(src, 'sound/machines/clockcult/steam_whoosh.ogg', 50, FALSE)
opened = APC_COVER_CLOSED
locked = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
else if(istype(W, /obj/item/apc_powercord))
return //because we put our fancy code in the right places, and this is all in the powercord's afterattack()
@@ -812,7 +812,7 @@
chargecount = 0
user.visible_message(span_notice("[user] fabricates a weak power cell and places it into [src]."), \
span_warning("Your [the_rcd.name] whirrs with strain as you create a weak power cell and place it into [src]!"))
- update_appearance(UPDATE_ICON)
+ update_appearance()
return TRUE
else
to_chat(user, span_warning("[src] has both electronics and a cell."))
@@ -832,7 +832,7 @@
if((allowed(usr) && !wires.is_cut(WIRE_IDSCAN) && !malfhack) || integration_cog)
locked = !locked
to_chat(user, span_notice("You [ locked ? "lock" : "unlock"] the APC interface."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
updateUsrDialog()
else
to_chat(user, span_warning("Access denied."))
@@ -843,10 +843,10 @@
return
last_light_switch = world.time
area.lightswitch = !area.lightswitch
- area.update_appearance(UPDATE_ICON)
+ area.update_appearance()
for(var/obj/machinery/light_switch/L in area)
- L.update_appearance(UPDATE_ICON)
+ L.update_appearance()
area.power_change()
@@ -876,7 +876,7 @@
opened = APC_COVER_REMOVED
coverlocked = FALSE
visible_message(span_warning("The APC cover is knocked down!"))
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/power/apc/emag_act(mob/user, obj/item/card/emag/emag_card)
if((obj_flags & EMAGGED) || malfhack)
@@ -895,7 +895,7 @@
obj_flags |= EMAGGED
locked = FALSE
to_chat(user, span_notice("You emag the APC interface."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
return TRUE
// attack with hand - remove cell (if cover open) or interact with the APC
@@ -912,10 +912,10 @@
if(cell)
user.visible_message("[user] removes \the [cell] from [src]!",span_notice("You remove \the [cell]."))
user.put_in_hands(cell)
- cell.update_appearance(UPDATE_ICON)
+ cell.update_appearance()
src.cell = null
charging = APC_NOT_CHARGING
- src.update_appearance(UPDATE_ICON)
+ src.update_appearance()
return
if((stat & MAINT) && !opened) //no board; no interface
return
@@ -1064,7 +1064,7 @@
to_chat(usr, "The APC does not respond to the command.")
else
locked = !locked
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("cover")
coverlocked = !coverlocked
@@ -1076,20 +1076,20 @@
chargemode = !chargemode
if(!chargemode)
charging = APC_NOT_CHARGING
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("channel")
if(params["eqp"])
equipment = setsubsystem(text2num(params["eqp"]))
- update_appearance(UPDATE_ICON)
+ update_appearance()
update()
else if(params["lgt"])
lighting = setsubsystem(text2num(params["lgt"]))
- update_appearance(UPDATE_ICON)
+ update_appearance()
update()
else if(params["env"])
environ = setsubsystem(text2num(params["env"]))
- update_appearance(UPDATE_ICON)
+ update_appearance()
update()
. = TRUE
if("overload")
@@ -1107,7 +1107,7 @@
malfvacate()
if("reboot")
failure_timer = 0
- update_appearance(UPDATE_ICON)
+ update_appearance()
update()
if("emergency_lighting")
emergency_lights = !emergency_lights
@@ -1132,7 +1132,7 @@
add_hiddenprint(user)
log_combat(user, src, "turned [operating ? "on" : "off"]")
update()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/power/apc/proc/malfhack(mob/living/silicon/ai/malf)
if(!istype(malf))
@@ -1202,6 +1202,9 @@
P.alert = FALSE
/obj/machinery/power/apc/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
+ . = ..()
+ if(!.)
+ return
if(card.AI)
to_chat(user, span_warning("[card] is already occupied!"))
return
@@ -1273,7 +1276,7 @@
/obj/machinery/power/apc/process()
if(icon_update_needed)
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(stat & (BROKEN|MAINT))
return
if(!area.requires_power)
@@ -1448,7 +1451,7 @@
if(APC_RESET_EMP)
equipment = 3
environ = 3
- update_appearance(UPDATE_ICON)
+ update_appearance()
update()
// damage and destruction acts
@@ -1464,7 +1467,7 @@
lighting = 0
equipment = 0
environ = 0
- update_appearance(UPDATE_ICON)
+ update_appearance()
update()
addtimer(CALLBACK(src, PROC_REF(reset), APC_RESET_EMP), (6 * severity) SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index b3f8caafb3de..c604ea038554 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -27,7 +27,7 @@ By design, d1 is the smallest direction and d2 is the highest
desc = "A flexible, superconducting insulated cable for heavy-duty power transfer."
icon = 'icons/obj/power_cond/cables.dmi'
icon_state = "0-1"
- level = 1 //is underfloor
+ plane = FLOOR_PLANE
layer = WIRE_LAYER //Above hidden pipes, GAS_PIPE_HIDDEN_LAYER
anchored = TRUE
obj_flags = CAN_BE_HIT | ON_BLUEPRINTS
@@ -83,16 +83,18 @@ By design, d1 is the smallest direction and d2 is the highest
d1 = text2num( copytext( icon_state, 1, dash ) )
d2 = text2num( copytext( icon_state, dash+1 ) )
- var/turf/T = get_turf(src) // hide if turf is not intact
- if(level==1)
- hide(T.intact)
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
GLOB.cable_list += src //add it to the global cable list
var/list/cable_colors = GLOB.cable_colors
cable_color = param_color || cable_color || pick(cable_colors)
if(cable_colors[cable_color])
cable_color = cable_colors[cable_color]
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/structure/cable/LateInitialize()
update_appearance(UPDATE_ICON)
+ //is_fully_initialized = TRUE
/obj/structure/cable/Destroy() // called when a cable is deleted
if(powernet)
@@ -116,13 +118,6 @@ By design, d1 is the smallest direction and d2 is the highest
// General procedures
///////////////////////////////////
-//If underfloor, hide the cable
-/obj/structure/cable/hide(i)
-
- if(level == 1 && isturf(loc))
- invisibility = i ? INVISIBILITY_MAXIMUM : 0
- update_appearance(UPDATE_ICON)
-
/obj/structure/cable/update_icon(updates=ALL)
. = ..()
icon_state = "[d1]-[d2]"
@@ -131,7 +126,7 @@ By design, d1 is the smallest direction and d2 is the highest
/obj/structure/cable/proc/handlecable(obj/item/W, mob/user, params)
var/turf/T = get_turf(src)
- if(T.intact)
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
return
if(W.tool_behaviour == TOOL_WIRECUTTER)
if (shock(user, 50))
@@ -614,8 +609,8 @@ By design, d1 is the smallest direction and d2 is the highest
if(!isturf(user.loc))
return
- if(!isturf(T) || T.intact || !T.can_have_cabling())
- to_chat(user, span_warning("You can only lay cables on top of exterior catwalks and plating!"))
+ if(!isturf(T) || T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE || !T.can_have_cabling())
+ to_chat(user, span_warning("You can only lay cables on catwalks and plating!"))
return
if(get_amount() < 1) // Out of cable
@@ -676,14 +671,18 @@ By design, d1 is the smallest direction and d2 is the highest
var/turf/T = C.loc
- if(!isturf(T) || T.intact) // sanity checks, also stop use interacting with T-scanner revealed cable
+ if(!isturf(T) || T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE || !T.can_have_cabling())
+ to_chat(user, span_warning("You can only lay cables on catwalks and plating!"))
+ return
+
+ if(get_amount() < 1) // Out of cable
+ to_chat(user, span_warning("There is no cable left!"))
return
- if(get_dist(C, user) > 1) // make sure it's close enough
+ if(get_dist(C, user) > 1) // make sure it's close enough
to_chat(user, span_warning("You can't lay cable at a place that far away!"))
return
-
if(U == T && !forceddir) //if clicked on the turf we're standing on and a direction wasn't supplied, try to put a cable in the direction we're facing
place_turf(T,user)
return
@@ -698,7 +697,7 @@ By design, d1 is the smallest direction and d2 is the highest
if (showerror)
to_chat(user, span_warning("You can only lay cables on catwalks and plating!"))
return
- if(U.intact) //can't place a cable if it's a plating with a tile on it
+ if(U.underfloor_accessibility < UNDERFLOOR_INTERACTABLE) //can't place a cable if it's a plating with a tile on it
to_chat(user, span_warning("You can't lay cable there unless the floor tiles are removed!"))
return
else
diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm
index 43f7e4347f63..7b44b81f1f07 100644
--- a/code/modules/power/generator.dm
+++ b/code/modules/power/generator.dm
@@ -21,7 +21,7 @@
. = ..()
find_circs()
connect_to_network()
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
START_PROCESSING(SSmachines, src)
update_appearance(UPDATE_ICON)
component_parts = list(new /obj/item/circuitboard/machine/generator)
@@ -29,7 +29,7 @@
/obj/machinery/power/generator/Destroy()
kill_circs()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
STOP_PROCESSING(SSmachines, src)
return ..()
@@ -58,7 +58,7 @@
var/L = min(round(lastgenlev/100000), 11)
if(L != 0)
- SSvis_overlays.add_vis_overlay(src, icon, "teg-op[L]", ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "teg-op[L]", ABOVE_LIGHTING_PLANE, dir)
#define GENRATE 800 // generator output coefficient from Q
diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm
index 0c7ab0eaa9c2..1e334473ef6b 100644
--- a/code/modules/power/gravitygenerator.dm
+++ b/code/modules/power/gravitygenerator.dm
@@ -72,31 +72,36 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
//
/obj/machinery/gravity_generator/part
- var/obj/machinery/gravity_generator/main/main_part = null
+ var/obj/machinery/gravity_generator/main/main_part
+
+/obj/machinery/gravity_generator/part/Destroy()
+ obj_break()
+ if(main_part)
+ UnregisterSignal(main_part, COMSIG_ATOM_UPDATED_ICON)
+ main_part = null
+ return ..()
/obj/machinery/gravity_generator/part/attackby(obj/item/I, mob/user, params)
return main_part.attackby(I, user)
/obj/machinery/gravity_generator/part/get_status()
- return main_part?.get_status()
+ if(!main_part)
+ return
+ return main_part.get_status()
/obj/machinery/gravity_generator/part/attack_hand(mob/user)
return main_part.attack_hand(user)
/obj/machinery/gravity_generator/part/set_broken()
..()
- if(main_part && !(main_part.stat & BROKEN))
- main_part.set_broken()
+ if(!main_part || (main_part.stat & BROKEN))
+ return
+ main_part.set_broken()
-//
-// Generator which spawns with the station.
-//
-
-/obj/machinery/gravity_generator/main/station/Initialize(mapload)
- . = ..()
- setup_parts()
- middle.add_overlay("activated")
- update_list()
+/// Used to eat args
+/obj/machinery/gravity_generator/part/proc/on_update_icon(obj/machinery/gravity_generator/source, updates, updated)
+ SIGNAL_HANDLER
+ return update_appearance(updates)
//
// Generator an admin can spawn
@@ -104,10 +109,11 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
/obj/machinery/gravity_generator/main/station/admin
use_power = NO_POWER_USE
-//
-// Main Generator with the main code
-//
-
+/**
+ * Main gravity generator
+ *
+ * The actual gravity generator, that actually holds the UI, contains the grav gen parts, ect.
+ */
/obj/machinery/gravity_generator/main
icon_state = "on_8"
idle_power_usage = 0
@@ -116,29 +122,55 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
sprite_number = 8
use_power = IDLE_POWER_USE
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE
+
+ /// List of all gravity generator parts
+ var/list/generator_parts = list()
+ /// The gravity generator part in the very center, the fifth one, where we place the overlays.
+ var/obj/machinery/gravity_generator/part/center_part
+
+ /// Whether the gravity generator is currently active.
var/on = TRUE
- var/breaker = 1
- var/list/parts = list()
- var/obj/middle = null
+ /// If the main breaker is on/off, to enable/disable gravity.
+ var/breaker = TRUE
+ /// If the generatir os idle, charging, or down.
var/charging_state = POWER_IDLE
+ /// How much charge the gravity generator has, goes down when breaker is shut, and shuts down at 0.
var/charge_count = 100
+
+ /// The gravity overlay currently used.
var/current_overlay = null
- var/broken_state = 0
- var/setting = 1 //Gravity value when on
+ /// When broken, what stage it is at (GRAV_NEEDS_SCREWDRIVER:0) (GRAV_NEEDS_WELDING:1) (GRAV_NEEDS_PLASTEEL:2) (GRAV_NEEDS_WRENCH:3)
+ var/broken_state = GRAV_NEEDS_SCREWDRIVER
+ /// Gravity value when on, honestly I don't know why it does it like this, but it does.
+ var/setting = 1
+
+ ///Amount of shielding we offer against a radioactive nebula
+ var/radioactive_nebula_shielding = 4
+
+///Station generator that spawns with gravity turned off.
+/obj/machinery/gravity_generator/main/off
+ on = FALSE
+ breaker = FALSE
+ charge_count = 0
+
+/obj/machinery/gravity_generator/main/station/Initialize(mapload)
+ . = ..()
+ setup_parts()
+ if(on)
+ center_part.add_overlay("activated")
+ update_list()
/obj/machinery/gravity_generator/main/Destroy() // If we somehow get deleted, remove all of our other parts.
investigate_log("was destroyed!", INVESTIGATE_GRAVITY)
on = FALSE
update_list()
- for(var/obj/machinery/gravity_generator/part/O in parts)
- O.main_part = null
- if(!QDESTROYING(O))
- qdel(O)
+ QDEL_NULL(center_part)
+ QDEL_LIST(generator_parts)
return ..()
/obj/machinery/gravity_generator/main/proc/setup_parts()
var/turf/our_turf = get_turf(src)
- // 9x9 block obtained from the bottom middle of the block
+ // 9x9 block obtained from the bottom center_part of the block
var/list/spawn_turfs = block(locate(our_turf.x - 1, our_turf.y + 2, our_turf.z), locate(our_turf.x + 1, our_turf.y, our_turf.z))
var/count = 10
for(var/turf/T in spawn_turfs)
@@ -147,37 +179,38 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
continue
var/obj/machinery/gravity_generator/part/part = new(T)
if(count == 5) // Middle
- middle = part
+ center_part = part
if(count <= 3) // Their sprite is the top part of the generator
part.density = FALSE
part.layer = WALL_OBJ_LAYER
part.sprite_number = count
part.main_part = src
- parts += part
- part.update_appearance(UPDATE_ICON)
+ generator_parts += part
+ part.update_appearance()
+ part.RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, TYPE_PROC_REF(/obj/machinery/gravity_generator/part, on_update_icon))
/obj/machinery/gravity_generator/main/proc/connected_parts()
- return parts.len == 8
+ return generator_parts.len == 8
/obj/machinery/gravity_generator/main/set_broken()
..()
- for(var/obj/machinery/gravity_generator/M in parts)
- if(!(M.stat & BROKEN))
- M.set_broken()
- middle.cut_overlays()
+ for(var/obj/machinery/gravity_generator/internal_parts in generator_parts)
+ if(!(internal_parts.stat & BROKEN))
+ internal_parts.set_broken()
+ center_part.cut_overlays()
charge_count = 0
breaker = 0
set_power()
- set_state(0)
+ disable()
investigate_log("has broken down.", INVESTIGATE_GRAVITY)
/obj/machinery/gravity_generator/main/set_fix()
..()
- for(var/obj/machinery/gravity_generator/M in parts)
- if(M.stat & BROKEN)
- M.set_fix()
- broken_state = 0
- update_appearance(UPDATE_ICON)
+ for(var/obj/machinery/gravity_generator/internal_parts as anything in generator_parts)
+ if(internal_parts.stat & BROKEN)
+ internal_parts.set_fix()
+ broken_state = FALSE
+ update_appearance()
set_power()
// Interaction
@@ -190,14 +223,14 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
to_chat(user, span_notice("You secure the screws of the framework."))
I.play_tool_sound(src)
broken_state++
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(GRAV_NEEDS_WELDING)
if(I.tool_behaviour == TOOL_WELDER)
if(I.use_tool(src, user, 0, volume=50, amount=1))
to_chat(user, span_notice("You mend the damaged framework."))
broken_state++
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(GRAV_NEEDS_PLASTEEL)
if(istype(I, /obj/item/stack/sheet/plasteel))
@@ -207,7 +240,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
to_chat(user, span_notice("You add the plating to the framework."))
playsound(src.loc, 'sound/machines/click.ogg', 75, 1)
broken_state++
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
to_chat(user, span_warning("You need 10 sheets of plasteel!"))
return
@@ -261,20 +294,20 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
/obj/machinery/gravity_generator/main/update_icon(updates=ALL)
. = ..()
- for(var/obj/O in parts)
- O.update_appearance(UPDATE_ICON)
+ for(var/obj/O in generator_parts)
+ O.update_appearance()
// Set the charging state based on power/breaker.
/obj/machinery/gravity_generator/main/proc/set_power()
- var/new_state = 0
+ var/new_state = FALSE
if(stat & (NOPOWER|BROKEN) || !breaker)
- new_state = 0
+ new_state = FALSE
else if(breaker)
- new_state = 1
+ new_state = TRUE
charging_state = new_state ? POWER_UP : POWER_DOWN // Startup sequence animation.
investigate_log("is now [charging_state == POWER_UP ? "charging" : "discharging"].", INVESTIGATE_GRAVITY)
- update_appearance(UPDATE_ICON)
+ update_appearance()
// Set the state of the gravity.
/obj/machinery/gravity_generator/main/proc/set_state(new_state)
@@ -295,54 +328,87 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
investigate_log("was brought offline and there is now no gravity for this level.", INVESTIGATE_GRAVITY)
message_admins("The gravity generator was brought offline with no backup generator. [ADMIN_VERBOSEJMP(src)]")
- update_appearance(UPDATE_ICON)
- update_list()
- src.updateUsrDialog()
if(alert)
+ complete_state_update()
+ shake_everyone()
+ src.updateUsrDialog()
+
+/obj/machinery/gravity_generator/main/proc/enable()
+ charging_state = POWER_IDLE
+ on = TRUE
+ use_power = ACTIVE_POWER_USE
+
+ var/old_gravity = gravity_in_level()
+ complete_state_update()
+
+ if (!old_gravity)
+ if(SSticker.current_state == GAME_STATE_PLAYING)
+ investigate_log("was brought online and is now producing gravity for this level.", INVESTIGATE_GRAVITY)
+ message_admins("The gravity generator was brought online [ADMIN_VERBOSEJMP(src)]")
+ shake_everyone()
+
+
+/obj/machinery/gravity_generator/main/proc/disable()
+ charging_state = POWER_IDLE
+ on = FALSE
+ use_power = IDLE_POWER_USE
+
+ var/old_gravity = gravity_in_level()
+ complete_state_update()
+
+ if (old_gravity)
+ if(SSticker.current_state == GAME_STATE_PLAYING)
+ investigate_log("was brought offline and there is now no gravity for this level.", INVESTIGATE_GRAVITY)
+ message_admins("The gravity generator was brought offline with no backup generator. [ADMIN_VERBOSEJMP(src)]")
shake_everyone()
+/obj/machinery/gravity_generator/main/proc/complete_state_update()
+ update_appearance()
+ update_list()
+
// Charge/Discharge and turn on/off gravity when you reach 0/100 percent.
// Also emit radiation and handle the overlays.
/obj/machinery/gravity_generator/main/process()
if(stat & BROKEN)
return
- if(charging_state != POWER_IDLE)
- if(charging_state == POWER_UP && charge_count >= 100)
- set_state(1)
- else if(charging_state == POWER_DOWN && charge_count <= 0)
- set_state(0)
- else
- if(charging_state == POWER_UP)
- charge_count += 2
- else if(charging_state == POWER_DOWN)
- charge_count -= 2
-
- if(charge_count % 4 == 0 && prob(75)) // Let them know it is charging/discharging.
- playsound(src.loc, 'sound/effects/empulse.ogg', 100, 1)
-
- updateDialog()
- if(prob(25)) // To help stop "Your clothes feel warm." spam.
- pulse_radiation()
-
- var/overlay_state = null
- switch(charge_count)
- if(0 to 20)
- overlay_state = null
- if(21 to 40)
- overlay_state = "startup"
- if(41 to 60)
- overlay_state = "idle"
- if(61 to 80)
- overlay_state = "activating"
- if(81 to 100)
- overlay_state = "activated"
-
- if(overlay_state != current_overlay)
- if(middle)
- middle.cut_overlays()
- if(overlay_state)
- middle.add_overlay(overlay_state)
- current_overlay = overlay_state
+ if(charging_state == POWER_IDLE)
+ return
+ if(charging_state == POWER_UP && charge_count >= 100)
+ enable()
+ else if(charging_state == POWER_DOWN && charge_count <= 0)
+ disable()
+ else
+ if(charging_state == POWER_UP)
+ charge_count += 2
+ else if(charging_state == POWER_DOWN)
+ charge_count -= 2
+
+ if(charge_count % 4 == 0 && prob(75)) // Let them know it is charging/discharging.
+ playsound(src.loc, 'sound/effects/empulse.ogg', 100, 1)
+
+ updateDialog()
+ if(prob(25)) // To help stop "Your clothes feel warm." spam.
+ pulse_radiation()
+
+ var/overlay_state = null
+ switch(charge_count)
+ if(0 to 20)
+ overlay_state = null
+ if(21 to 40)
+ overlay_state = "startup"
+ if(41 to 60)
+ overlay_state = "idle"
+ if(61 to 80)
+ overlay_state = "activating"
+ if(81 to 100)
+ overlay_state = "activated"
+
+ if(overlay_state != current_overlay)
+ if(center_part)
+ center_part.cut_overlays()
+ if(overlay_state)
+ center_part.add_overlay(overlay_state)
+ current_overlay = overlay_state
/obj/machinery/gravity_generator/main/proc/pulse_radiation()
@@ -364,20 +430,30 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
/obj/machinery/gravity_generator/main/proc/gravity_in_level()
var/turf/T = get_turf(src)
if(!T)
- return 0
+ return FALSE
if(GLOB.gravity_generators["[T.z]"])
return length(GLOB.gravity_generators["[T.z]"])
- return 0
+ return FALSE
/obj/machinery/gravity_generator/main/proc/update_list()
- var/turf/T = get_turf(src.loc)
- if(T)
- if(!GLOB.gravity_generators["[T.z]"])
- GLOB.gravity_generators["[T.z]"] = list()
+ var/turf/T = get_turf(src)
+ if(!T)
+ return
+ var/list/z_list = list()
+ // Multi-Z, station gravity generator generates gravity on all ZTRAIT_STATION z-levels.
+ if(SSmapping.level_trait(T.z, ZTRAIT_STATION))
+ for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
+ z_list += z
+ else
+ z_list += T.z
+ for(var/z in z_list)
+ if(!GLOB.gravity_generators["[z]"])
+ GLOB.gravity_generators["[z]"] = list()
if(on)
- GLOB.gravity_generators["[T.z]"] |= src
+ GLOB.gravity_generators["[z]"] |= src
else
- GLOB.gravity_generators["[T.z]"] -= src
+ GLOB.gravity_generators["[z]"] -= src
+ SSmapping.calculate_z_level_gravity(z)
/obj/machinery/gravity_generator/main/proc/change_setting(value)
if(value != setting)
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index ede43e165e3c..63a2fb57bcf2 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -32,8 +32,8 @@
/obj/item/wallframe/light_fixture/try_build(turf/on_wall, user)
if(!..())
return
- var/area/A = get_area(user)
- if(!IS_DYNAMIC_LIGHTING(A))
+ var/area/local_area = get_area(user)
+ if(!local_area.static_lighting)
to_chat(user, span_warning("You cannot place [src] in this area!"))
return
return TRUE
@@ -90,7 +90,7 @@
if(cell)
user.visible_message("[user] removes [cell] from [src]!",span_notice("You remove [cell]."))
user.put_in_hands(cell)
- cell.update_appearance(UPDATE_ICON)
+ cell.update_appearance()
cell = null
add_fingerprint(user)
@@ -212,8 +212,6 @@
/obj/machinery/light
name = "light fixture"
icon = 'icons/obj/lighting.dmi'
- var/overlayicon = 'icons/obj/lighting_overlay.dmi'
- var/base_state = "tube" // base description and icon_state
icon_state = "tube"
desc = "A lighting fixture."
layer = WALL_OBJ_LAYER
@@ -221,45 +219,71 @@
use_power = ACTIVE_POWER_USE
idle_power_usage = 2
active_power_usage = 20
- power_channel = AREA_USAGE_LIGHT //Lights are calc'd via area so they dont need to be in the machine list
- var/on = FALSE // 1 if on, 0 if off
+ ///Lights are calc'd via area so they dont need to be in the machine list
+ power_channel = AREA_USAGE_LIGHT
+ ///What overlay the light should use
+ var/overlay_icon = 'icons/obj/lighting_overlay.dmi'
+ ///base description and icon_state
+ var/base_state = "tube"
+ ///Is the light on?
+ var/on = FALSE
var/on_gs = FALSE
var/forced_off = FALSE
var/static_power_used = 0
- var/brightness = 8 // luminosity when on, also used in power calculation
- var/bulb_power = 1 // basically the alpha of the emitted light source
- var/bulb_colour = "#FFFFFF" // befault colour of the light.
- var/status = LIGHT_OK // LIGHT_OK, _EMPTY, _BURNED or _BROKEN
+ ///Luminosity when on, also used in power calculation
+ var/brightness = 8
+ ///Basically the alpha of the emitted light source
+ var/bulb_power = 1
+ ///Default colour of the light.
+ var/bulb_colour = LIGHT_COLOR_DEFAULT
+ ///LIGHT_OK, _EMPTY, _BURNED or _BROKEN
+ var/status = LIGHT_OK
+ ///Should we flicker?
var/flickering = FALSE
- var/light_type = /obj/item/light/tube // the type of light item
+ ///The type of light item
+ var/light_type = /obj/item/light/tube
+ ///String of the light type, used in descriptions and in examine
var/fitting = "tube"
- var/switchcount = 0 // count of number of times switched on/off
- // this is used to calc the probability the light burns out
-
- var/rigged = FALSE // true if rigged to explode
+ ///Count of number of times switched on/off, this is used to calculate the probability the light burns out
+ var/switchcount = 0
+ ///true if rigged to explode
+ var/rigged = FALSE
+ ///Cell reference
var/obj/item/stock_parts/cell/cell
- var/start_with_cell = TRUE // if true, this fixture generates a very weak cell at roundstart
-
- var/nightshift_enabled = FALSE //Currently in night shift mode?
- var/nightshift_allowed = TRUE //Set to FALSE to never let this light get switched to night mode.
+ ///If true, this fixture generates a very weak cell at roundstart
+ var/start_with_cell = TRUE
+
+ ///Currently in night shift mode?
+ var/nightshift_enabled = FALSE
+ ///Set to FALSE to never let this light get switched to night mode.
+ var/nightshift_allowed = TRUE
+ ///Brightness of the nightshift light
var/nightshift_brightness = 8
+ ///Alpha of the nightshift light
var/nightshift_light_power = 0.45
+ ///Basecolor of the nightshift light
var/nightshift_light_color = "#FFDDCC"
- var/emergency_mode = FALSE // if true, the light is in emergency mode
- var/no_emergency = FALSE // if true, this light cannot ever have an emergency mode
- var/bulb_emergency_brightness_mul = 0.25 // multiplier for this light's base brightness in emergency power mode
- var/bulb_emergency_colour = "#FF3232" // determines the colour of the light while it's in emergency mode
- var/bulb_emergency_pow_mul = 0.75 // the multiplier for determining the light's power in emergency mode
- var/bulb_emergency_pow_min = 0.5 // the minimum value for the light's power in emergency mode
-
- var/bulb_vacuum_colour = "#4F82FF" // colour of the light when air alarm is set to severe
+ var/nightshift_powercheck = FALSE
+
+ ///if true, the light is in emergency mode
+ var/emergency_mode = FALSE
+ ///if true, this light cannot ever have an emergency mode
+ var/no_emergency = FALSE
+ ///Multiplier for this light's base brightness during a cascade
+ var/bulb_emergency_brightness_mul = 0.25
+ ///Colour of the light when major emergency mode is on
+ var/bulb_emergency_colour = "#ff4e4e"
+ ///the multiplier for determining the light's power in emergency mode
+ var/bulb_emergency_pow_mul = 0.75
+ ///the minimum value for the light's power in emergency mode
+ var/bulb_emergency_pow_min = 0.5
+
+ ///colour of the light when air alarm is set to severe
+ var/bulb_vacuum_colour = "#4F82FF"
var/bulb_vacuum_brightness = 8
- ///So we don't have a lot of stress on startup.
- var/maploaded = FALSE
-
///More stress stuff.
var/turning_on = FALSE
@@ -283,33 +307,23 @@
/obj/machinery/light/Move()
if(status != LIGHT_BROKEN)
- break_light_tube(1)
+ break_light_tube(TRUE)
return ..()
/obj/machinery/light/built
+ status = LIGHT_EMPTY
icon_state = "tube-empty"
start_with_cell = FALSE
-/obj/machinery/light/built/Initialize(mapload)
- . = ..()
- status = LIGHT_EMPTY
- update(0)
-
/obj/machinery/light/floor/built
- icon_state = "floor-empty"
-
-/obj/machinery/light/floor/built/Initialize(mapload)
- . = ..()
status = LIGHT_EMPTY
- update(0)
+ icon_state = "floor-empty"
+ start_with_cell = FALSE
/obj/machinery/light/small/built
- icon_state = "bulb-empty"
-
-/obj/machinery/light/small/built/Initialize(mapload)
- . = ..()
status = LIGHT_EMPTY
- update(0)
+ icon_state = "bulb-empty"
+ start_with_cell = FALSE
// create a new lighting fixture
/obj/machinery/light/Initialize(mapload)
@@ -319,56 +333,55 @@
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_light))
//Setup area colours -pb
- var/area/A = get_area(src)
+ var/area/our_area = get_room_area()
if(bulb_colour == initial(bulb_colour))
if(istype(src, /obj/machinery/light/small))
- bulb_colour = A.lighting_colour_bulb
+ bulb_colour = our_area.lighting_colour_bulb
else
- bulb_colour = A.lighting_colour_tube
+ bulb_colour = our_area.lighting_colour_tube
if(nightshift_light_color == initial(nightshift_light_color))
- nightshift_light_color = A.lighting_colour_night
+ nightshift_light_color = our_area.lighting_colour_night
if(!mapload) //sync up nightshift lighting for player made lights
- var/obj/machinery/power/apc/temp_apc = A.get_apc()
+ var/obj/machinery/power/apc/temp_apc = our_area.get_apc()
nightshift_enabled = temp_apc?.nightshift_lights
- else
- maploaded = TRUE
if(start_with_cell && !no_emergency)
cell = new/obj/item/stock_parts/cell/emergency_light(src)
- spawn(2)
- switch(fitting)
- if("tube")
- brightness = 8
- if(prob(2))
- break_light_tube(1)
- if("bulb")
- brightness = 4
- if(prob(5))
- break_light_tube(1)
- if("floor bulb")
- brightness = 4
- if(prob(5))
- break_light_tube(1)
- spawn(1)
- update(FALSE, TRUE, maploaded)
+
+ // Light projects out backwards from the dir of the light
+ set_light(l_dir = REVERSE_DIR(dir))
+
+ if(mapload && our_area.lights_always_start_on)
+ turn_on(trigger = FALSE, quiet = TRUE)
+
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/light/LateInitialize()
+ . = ..()
+ switch(fitting)
+ if("tube")
+ if(prob(2))
+ break_light_tube(TRUE)
+ if("bulb")
+ if(prob(5))
+ break_light_tube(TRUE)
+ update(trigger = FALSE)
/obj/machinery/light/Destroy()
GLOB.lights.Remove(src)
var/area/A = get_area(src)
if(A)
on = FALSE && !forced_off
-// A.update_lights()
QDEL_NULL(cell)
return ..()
-/obj/machinery/light/update_icon(updates=ALL)
+/obj/machinery/light/setDir(newdir)
. = ..()
- if(on && turning_on)
- return
+ set_light(l_dir = REVERSE_DIR(dir))
- cut_overlays()
+/obj/machinery/light/update_icon_state(updates=ALL)
switch(status) // set icon_states
if(LIGHT_OK)
if(forced_off)
@@ -381,22 +394,30 @@
icon_state = "[base_state]_vacuum"
else
icon_state = "[base_state]"
- if(on && !forced_off)
- var/glow_state = base_state
- if(emergency_mode || (A && A.fire))
- glow_state = "[base_state]_emergency"
- else if ((A && A.vacuum) || nightshift_enabled)
- glow_state = "[base_state]_nightshift"
- var/mutable_appearance/glowybit = mutable_appearance(overlayicon, glow_state, layer)
- //glowybit.alpha = clamp(light_power*250, 30, 200)
- add_overlay(glowybit)
if(LIGHT_EMPTY)
icon_state = "[base_state]-empty"
if(LIGHT_BURNED)
icon_state = "[base_state]-burned"
if(LIGHT_BROKEN)
icon_state = "[base_state]-broken"
- return
+ return ..()
+
+/obj/machinery/light/update_overlays()
+ . = ..()
+ if(!on || status != LIGHT_OK)
+ return
+
+ . += emissive_appearance(overlay_icon, "[base_state]", src, alpha = src.alpha)
+
+ var/area/local_area = get_room_area()
+
+ if(emergency_mode || (local_area?.fire))
+ . += mutable_appearance(overlay_icon, "[base_state]_emergency")
+ return
+ if(nightshift_enabled)
+ . += mutable_appearance(overlay_icon, "[base_state]_nightshift")
+ return
+ . += mutable_appearance(overlay_icon, base_state)
/obj/machinery/light/proc/clean_light(O,strength)
if(strength < CLEAN_TYPE_BLOOD)
@@ -417,20 +438,14 @@
if(on)
if(instant)
turn_on(trigger, quiet)
- else if(maploaded)
- turn_on(trigger, TRUE)
- maploaded = FALSE
else if(!turning_on)
turning_on = TRUE
addtimer(CALLBACK(src, PROC_REF(turn_on), trigger, quiet), rand(LIGHT_ON_DELAY_LOWER, LIGHT_ON_DELAY_UPPER))
else if(has_emergency_power(LIGHT_EMERGENCY_POWER_USE) && !turned_off())
- use_power = IDLE_POWER_USE
emergency_mode = TRUE
START_PROCESSING(SSmachines, src)
else
- use_power = IDLE_POWER_USE
- set_light(0)
- update_appearance(UPDATE_ICON)
+ set_light(l_range = 0)
active_power_usage = (brightness * 10)
if(on != on_gs)
@@ -440,7 +455,20 @@
addStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT)
else
removeStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT)
+ nightshift_powercheck = FALSE
+ if(on)
+ if(nightshift_enabled != nightshift_powercheck)
+ nightshift_powercheck = nightshift_enabled
+ if(nightshift_enabled)
+ removeStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT)
+ static_power_used = brightness * 8 //8W per unit luminosity
+ addStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT)
+ else
+ removeStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT)
+ static_power_used = brightness * 20 //20W per unit luminosity
+ addStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT)
+ update_appearance()
broken_sparks(start_only=TRUE)
/obj/machinery/light/proc/turn_on(trigger, quiet = FALSE)
@@ -450,23 +478,23 @@
if(!on)
return FALSE
- var/BR = brightness
- var/PO = bulb_power
- var/CO = bulb_colour
+ var/brightness_set = brightness
+ var/power_set = bulb_power
+ var/color_set = bulb_colour
if(color)
- CO = color
+ color_set = color
var/area/A = get_area(src)
if (A && (A.fire || A.delta_light))
- CO = bulb_emergency_colour
+ color_set = bulb_emergency_colour
else if (A && A.vacuum)
- CO = bulb_vacuum_colour
- BR = bulb_vacuum_brightness
+ color_set = bulb_vacuum_colour
+ brightness_set = bulb_vacuum_brightness
else if (nightshift_enabled)
- BR = nightshift_brightness
- PO = nightshift_light_power
+ brightness_set = nightshift_brightness
+ power_set = nightshift_light_power
if(!color)
- CO = nightshift_light_color
- var/matching = light && BR == light.light_range && PO == light.light_power && CO == light.light_color
+ color_set = nightshift_light_color
+ var/matching = light && brightness_set == light.light_range && power_set == light.light_power && color_set == light.light_color
if(!matching)
switchcount++
if(rigged)
@@ -476,19 +504,22 @@
if(trigger)
burn_out()
else
- use_power = ACTIVE_POWER_USE
- set_light(BR, PO, CO)
+ set_light(
+ l_range = brightness_set,
+ l_power = power_set,
+ l_color = color_set
+ )
if(!quiet)
playsound(src.loc, 'sound/effects/light_on.ogg', 50)
- update_icon()
- return TRUE
+ update_appearance()
+ broken_sparks(start_only=TRUE)
/obj/machinery/light/update_atom_colour()
..()
update()
/obj/machinery/light/proc/broken_sparks(start_only=FALSE)
- if(!QDELETED(src) && status == LIGHT_BROKEN && has_power() && Master.current_runlevel)
+ if(!QDELETED(src) && status == LIGHT_BROKEN && has_power() && MC_RUNNING())
if(!start_only)
do_sparks(3, TRUE, src)
var/delay = rand(BROKEN_SPARKS_MIN, BROKEN_SPARKS_MAX)
@@ -509,15 +540,14 @@
status = LIGHT_BURNED
icon_state = "[base_state]-burned"
on = FALSE
- set_light(0)
+ set_light(l_range = 0)
playsound(src.loc, 'sound/effects/burnout.ogg', 65)
- update_icon()
// attempt to set the light's on/off status
// will not switch on if broken/burned/empty
-/obj/machinery/light/proc/seton(s)
- on = (s && status == LIGHT_OK && !forced_off)
- update(FALSE)
+/obj/machinery/light/proc/set_on(turn_on)
+ on = (turn_on && status == LIGHT_OK)
+ update()
/obj/machinery/light/get_cell()
return cell
@@ -541,103 +571,101 @@
// attack with item - insert light (if right type), otherwise try to break the light
-/obj/machinery/light/attackby(obj/item/W, mob/living/user, params)
+/obj/machinery/light/attackby(obj/item/tool, mob/living/user, params)
//Light replacer code
- if(istype(W, /obj/item/lightreplacer))
- var/obj/item/lightreplacer/LR = W
+ if(istype(tool, /obj/item/lightreplacer))
+ var/obj/item/lightreplacer/LR = tool
LR.ReplaceLight(src, user)
+ return
// attempt to insert light
- else if(istype(W, /obj/item/light))
+ if(istype(tool, /obj/item/light))
if(status == LIGHT_OK)
to_chat(user, span_warning("There is a [fitting] already inserted!"))
+ return
+ add_fingerprint(user)
+ var/obj/item/light/light_object = tool
+ if(!istype(light_object, light_type))
+ to_chat(user, span_warning("This type of light requires a [fitting]!"))
+ return
+
+ if(!user.temporarilyRemoveItemFromInventory(light_object))
+ return
+
+ add_fingerprint(user)
+ if(status != LIGHT_EMPTY)
+ drop_light_tube(user)
+ to_chat(user, span_notice("You replace [light_object]."))
else
- src.add_fingerprint(user)
- var/obj/item/light/L = W
- if(istype(L, light_type))
- if(!user.temporarilyRemoveItemFromInventory(L))
- return
+ to_chat(user, span_notice("You insert [light_object]."))
+ status = light_object.status
+ switchcount = light_object.switchcount
+ rigged = light_object.rigged
+ brightness = light_object.brightness
+ on = has_power() && !forced_off
+ update()
+
+ qdel(light_object)
+
+ return
- src.add_fingerprint(user)
- if(status != LIGHT_EMPTY)
- drop_light_tube(user)
- to_chat(user, span_notice("You replace [L]."))
- else
- to_chat(user, span_notice("You insert [L]."))
- status = L.status
- switchcount = L.switchcount
- rigged = L.rigged
- brightness = L.brightness
- on = has_power() && !forced_off
- update()
-
- qdel(L)
-
- if(on && rigged)
- explode()
- else
- to_chat(user, span_warning("This type of light requires a [fitting]!"))
// hit the light socket with umbral tendrils, instantly breaking the light as opposed to RNG //yogs
- else if(istype(W, /obj/item/umbral_tendrils))
+ if(istype(tool, /obj/item/umbral_tendrils))
break_light_tube()
- ..() //yogs end
+ return ..() //yogs end
// attempt to stick weapon into light socket
- else if(status == LIGHT_EMPTY)
- if(W.tool_behaviour == TOOL_SCREWDRIVER) //If it's a screwdriver open it.
- W.play_tool_sound(src, 75)
- user.visible_message("[user.name] opens [src]'s casing.", \
- span_notice("You open [src]'s casing."), span_italics("You hear a noise."))
- deconstruct()
- else
- to_chat(user, span_userdanger("You stick \the [W] into the light socket!"))
- if(has_power() && (W.flags_1 & CONDUCT_1))
- do_sparks(3, TRUE, src)
- if (prob(75))
- electrocute_mob(user, get_area(src), src, rand(0.7,1.0), TRUE)
- //attempt to turn off light with multitool
- else if(W.tool_behaviour == TOOL_MULTITOOL)
- set_light(0)
- forced_off = !forced_off
- on = !on
- update_appearance(UPDATE_ICON)
- update()
- else
+ if(status != LIGHT_EMPTY)
return ..()
+ if(tool.tool_behaviour == TOOL_SCREWDRIVER) //If it's a screwdriver open it.
+ tool.play_tool_sound(src, 75)
+ user.visible_message("[user.name] opens [src]'s casing.", \
+ span_notice("You open [src]'s casing."), span_italics("You hear a noise."))
+ deconstruct()
+ return
+
+ to_chat(user, span_userdanger("You stick \the [tool] into the light socket!"))
+ if(has_power() && (tool.flags_1 & CONDUCT_1))
+ do_sparks(3, TRUE, src)
+ if (prob(75))
+ electrocute_mob(user, get_area(src), src, (rand(7,10) * 0.1), TRUE)
/obj/machinery/light/deconstruct(disassembled = TRUE)
- if(!(flags_1 & NODECONSTRUCT_1))
- var/obj/structure/light_construct/newlight = null
- var/cur_stage = 2
- if(!disassembled)
- cur_stage = 1
- switch(fitting)
- if("tube")
- newlight = new /obj/structure/light_construct(src.loc)
- newlight.icon_state = "tube-construct-stage[cur_stage]"
-
- if("bulb")
- newlight = new /obj/structure/light_construct/small(src.loc)
- newlight.icon_state = "bulb-construct-stage[cur_stage]"
-
- if("floor bulb")
- newlight = new /obj/structure/light_construct/floor(src.loc)
- newlight.icon_state = "floor-construct-stage[cur_stage]"
- newlight.setDir(src.dir)
- newlight.stage = cur_stage
- if(!disassembled)
- newlight.obj_integrity = newlight.max_integrity * 0.5
- if(status != LIGHT_BROKEN)
- break_light_tube()
- if(status != LIGHT_EMPTY)
- drop_light_tube()
- new /obj/item/stack/cable_coil(loc, 1, "red")
- transfer_fingerprints_to(newlight)
- if(cell)
- newlight.cell = cell
- cell.forceMove(newlight)
- cell = null
+ if(flags_1 & NODECONSTRUCT_1)
+ qdel(src)
+ return
+ var/obj/structure/light_construct/newlight = null
+ var/current_stage = 2
+ if(!disassembled)
+ current_stage = 1
+ switch(fitting)
+ if("tube")
+ newlight = new /obj/structure/light_construct(loc)
+ newlight.icon_state = "tube-construct-stage[current_stage]"
+
+ if("bulb")
+ newlight = new /obj/structure/light_construct/small(loc)
+ newlight.icon_state = "bulb-construct-stage[current_stage]"
+
+ if("floor bulb")
+ newlight = new /obj/structure/light_construct/floor(loc)
+ newlight.icon_state = "floor-construct-stage[current_stage]"
+ newlight.setDir(dir)
+ newlight.stage = current_stage
+ if(!disassembled)
+ newlight.obj_integrity = newlight.max_integrity * 0.5
+ if(status != LIGHT_BROKEN)
+ break_light_tube()
+ if(status != LIGHT_EMPTY)
+ drop_light_tube()
+ new /obj/item/stack/cable_coil(loc, 1, "red")
+ transfer_fingerprints_to(newlight)
+
+ if(cell)
+ newlight.cell = cell
+ cell.forceMove(newlight)
+ cell = null
qdel(src)
/obj/machinery/light/attacked_by(obj/item/I, mob/living/user)
@@ -653,9 +681,6 @@
if(prob(damage_amount * 5))
break_light_tube()
-
-
-
/obj/machinery/light/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
switch(damage_type)
if(BRUTE)
@@ -712,10 +737,10 @@
if(status != LIGHT_OK)
break
on = !on
- update(0)
+ update(FALSE)
sleep(rand(0.5, 1.5) SECONDS)
on = (status == LIGHT_OK) && !forced_off
- update(0)
+ update(FALSE)
flickering = 0
// ai attack - make lights flicker, because why not
@@ -741,64 +766,67 @@
return
// make it burn hands unless you're wearing heat insulated gloves or have the RESISTHEAT/RESISTHEATHANDS traits
- if(on && status == LIGHT_OK)
- var/prot = 0
- var/mob/living/carbon/human/H = user
-
- if(istype(H))
- if(isethereal(H))
- to_chat(H, span_notice("You start channeling some power through the [fitting] into your body."))
- if(do_after(user, 1 SECONDS, src))
- if(istype(H.getorganslot(ORGAN_SLOT_STOMACH), /obj/item/organ/stomach/cell))
- to_chat(H, span_notice("You receive some charge from the [fitting]."))
- H.adjust_nutrition(100)
- else
- to_chat(H, span_notice("You can't receive charge from the [fitting]."))
- return
-
- if(H.gloves)
- var/obj/item/clothing/gloves/G = H.gloves
- if(G.max_heat_protection_temperature)
- prot = (G.max_heat_protection_temperature > 360)
- else
- prot = 1
-
- if(prot > 0 || HAS_TRAIT(user, TRAIT_RESISTHEAT) || HAS_TRAIT(user, TRAIT_RESISTHEATHANDS))
- to_chat(user, span_notice("You remove the light [fitting]."))
- else if(istype(user) && user.dna.check_mutation(TK))
- to_chat(user, span_notice("You telekinetically remove the light [fitting]."))
- else
- to_chat(user, span_warning("You try to remove the light [fitting], but you burn your hand on it!"))
+ if(!on)
+ to_chat(user, span_notice("You remove the light [fitting]."))
+ // create a light tube/bulb item and put it in the user's hand
+ drop_light_tube(user)
+ return
+
+ var/protected = FALSE
+ var/mob/living/carbon/human/H = user
+
+ if(istype(H))
+ if(isethereal(H))
+ to_chat(H, span_notice("You start channeling some power through the [fitting] into your body."))
+ if(do_after(user, 1 SECONDS, src))
+ if(istype(H.getorganslot(ORGAN_SLOT_STOMACH), /obj/item/organ/stomach/cell))
+ to_chat(H, span_notice("You receive some charge from the [fitting]."))
+ H.adjust_nutrition(100)
+ else
+ to_chat(H, span_notice("You can't receive charge from the [fitting]."))
+ return
- var/obj/item/bodypart/affecting = H.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
- if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage
- H.update_damage_overlays()
- return // if burned, don't remove the light
+ if(H.gloves)
+ var/obj/item/clothing/gloves/G = H.gloves
+ if(G.max_heat_protection_temperature)
+ protected = (G.max_heat_protection_temperature > 360)
else
+ protected = TRUE
+
+ if(protected > FALSE || HAS_TRAIT(user, TRAIT_RESISTHEAT) || HAS_TRAIT(user, TRAIT_RESISTHEATHANDS))
to_chat(user, span_notice("You remove the light [fitting]."))
+ else if(istype(user) && user.dna.check_mutation(TK))
+ to_chat(user, span_notice("You telekinetically remove the light [fitting]."))
+ else
+ var/obj/item/bodypart/affecting = H.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
+ if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage
+ H.update_damage_overlays()
+ to_chat(user, span_warning("You try to remove the light [fitting], but you burn your hand on it!"))
+ return // if burned, don't remove the light
// create a light tube/bulb item and put it in the user's hand
drop_light_tube(user)
/obj/machinery/light/proc/drop_light_tube(mob/user)
- var/obj/item/light/L = new light_type()
- L.status = status
- L.rigged = rigged
- L.brightness = brightness
+ var/obj/item/light/light_object = new light_type()
+
+ light_object.status = status
+ light_object.rigged = rigged
+ light_object.brightness = brightness
// light item inherits the switchcount, then zero it
- L.switchcount = switchcount
+ light_object.switchcount = switchcount
switchcount = 0
- L.update()
- L.forceMove(loc)
+ light_object.update_appearance()
+ light_object.forceMove(loc)
if(user) //puts it in our active hand
- L.add_fingerprint(user)
- user.put_in_active_hand(L)
+ light_object.add_fingerprint(user)
+ user.put_in_active_hand(light_object)
status = LIGHT_EMPTY
update()
- return L
+ return light_object
/obj/machinery/light/attack_tk(mob/user)
if(status == LIGHT_EMPTY)
@@ -818,11 +846,11 @@
if(!do_after(user, 2 SECONDS, src))
return
to_chat(user, span_brass("You sucessfully break [src]!"))
- break_light_tube(0)
+ break_light_tube(FALSE)
// break the light and make sparks if was on
-/obj/machinery/light/proc/break_light_tube(skip_sound_and_sparks = 0)
+/obj/machinery/light/proc/break_light_tube(skip_sound_and_sparks = FALSE)
if(status == LIGHT_EMPTY || status == LIGHT_BROKEN)
return
@@ -852,7 +880,7 @@
// called when area power state changes
/obj/machinery/light/power_change()
var/area/A = get_area(src)
- seton(A.lightswitch && A.power_light)
+ set_on(A.lightswitch && A.power_light)
// called when on fire
@@ -880,13 +908,27 @@
force = 2
throwforce = 5
w_class = WEIGHT_CLASS_TINY
- var/status = LIGHT_OK // LIGHT_OK, LIGHT_BURNED or LIGHT_BROKEN
- var/base_state
- var/switchcount = 0 // number of times switched
materials = list(/datum/material/glass=100)
grind_results = list(/datum/reagent/silicon = 5, /datum/reagent/nitrogen = 10) //Nitrogen is used as a cheaper alternative to argon in incandescent lighbulbs
- var/rigged = FALSE // true if rigged to explode
- var/brightness = 2 //how much light it gives off
+ ///How much light it gives off
+ var/brightness = 2
+ ///LIGHT_OK, LIGHT_BURNED or LIGHT_BROKEN
+ var/status = LIGHT_OK
+ ///Base icon state for each bulb types
+ var/base_state
+ ///Number of times switched on and off
+ var/switchcount = 0
+ ///True if rigged to explode
+ var/rigged = FALSE
+
+/obj/item/light/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/caltrop, min_damage = force)
+ update_icon_state()
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
/obj/item/light/suicide_act(mob/living/carbon/user)
if (status == LIGHT_BROKEN)
@@ -907,6 +949,7 @@
/obj/item/light/tube/broken
status = LIGHT_BROKEN
+ sharpness = SHARP_POINTY
/obj/item/light/bulb
name = "light bulb"
@@ -920,6 +963,7 @@
/obj/item/light/bulb/broken
status = LIGHT_BROKEN
+ sharpness = SHARP_POINTY
/obj/item/light/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(!..()) //not caught by a mob
@@ -927,32 +971,36 @@
// update the icon state and description of the light
-/obj/item/light/proc/update()
+/obj/item/light/update_icon_state()
+ . = ..()
switch(status)
if(LIGHT_OK)
icon_state = base_state
- desc = "A replacement [name]."
if(LIGHT_BURNED)
icon_state = "[base_state]-burned"
- desc = "A burnt-out [name]."
if(LIGHT_BROKEN)
icon_state = "[base_state]-broken"
- desc = "A broken [name]."
-/obj/item/light/Initialize(mapload)
+/obj/item/light/update_desc()
. = ..()
- update()
- AddComponent(/datum/component/caltrop, force)
+ switch(status)
+ if(LIGHT_OK)
+ desc = "A replacement [name]."
+ if(LIGHT_BURNED)
+ desc = "A burnt-out [name]."
+ if(LIGHT_BROKEN)
+ desc = "A broken [name]."
-/obj/item/light/Crossed(atom/movable/AM)
- . = ..()
- if(!isliving(AM))
+
+/obj/item/light/proc/on_entered(datum/source, atom/movable/moving_atom)
+ SIGNAL_HANDLER
+ if(!isliving(moving_atom))
return
- var/mob/living/L = AM
- if(istype(L) && !(L.is_flying() || L.buckled))
- playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(L, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE)
+ var/mob/living/moving_mob = moving_atom
+ if(!(moving_mob.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || moving_mob.buckled)
+ playsound(src, 'sound/effects/glass_step.ogg', HAS_TRAIT(moving_mob, TRAIT_LIGHT_STEP) ? 30 : 50, TRUE)
if(status == LIGHT_BURNED || status == LIGHT_OK)
- shatter()
+ shatter(moving_mob)
// attack bulb/tube with object
// if a syringe, can inject plasma to make it explode
@@ -985,10 +1033,11 @@
visible_message(span_danger("[src] shatters."),span_italics("You hear a small glass object shatter."))
status = LIGHT_BROKEN
force = 5
+ sharpness = SHARP_POINTY
playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1)
if(rigged)
atmos_spawn_air("plasma=5") //5u of plasma are required to rig a light bulb/tube
- update()
+ update_appearance(UPDATE_DESC | UPDATE_ICON)
/obj/machinery/light/floor
@@ -997,7 +1046,9 @@
base_state = "floor" // base description and icon_state
icon_state = "floor"
brightness = 4
+ light_angle = 360
layer = LOW_OBJ_LAYER
+ plane = FLOOR_PLANE
light_type = /obj/item/light/bulb
fitting = "floor bulb"
diff --git a/code/modules/power/multiz.dm b/code/modules/power/multiz.dm
new file mode 100644
index 000000000000..5dfb035a4154
--- /dev/null
+++ b/code/modules/power/multiz.dm
@@ -0,0 +1,83 @@
+/obj/machinery/power/deck_relay //This bridges powernets betwen Z levels
+ name = "Multi-deck power adapter"
+ desc = "A huge bundle of double insulated cabling which seems to run up into the ceiling."
+ icon = 'icons/obj/power.dmi'
+ icon_state = "cablerelay-off"
+ ///The relay that's below us (for bridging powernets)
+ var/obj/machinery/power/deck_relay/below
+ ///The relay that's above us (for bridging powernets)
+ var/obj/machinery/power/deck_relay/above
+ anchored = TRUE
+ density = FALSE
+
+/obj/machinery/power/deck_relay/Initialize(mapload)
+ . = ..()
+ addtimer(CALLBACK(src, PROC_REF(find_relays)), 30)
+ //Wait a bit so we can find the one below, then get powering
+ addtimer(CALLBACK(src, PROC_REF(refresh)), 50)
+
+/obj/machinery/power/deck_relay/attackby(obj/item/I,mob/user)
+ if(default_unfasten_wrench(user, I))
+ return FALSE
+ . = ..()
+
+/obj/machinery/power/deck_relay/process()
+ if(!anchored)
+ icon_state = "cablerelay-off"
+ if(above) //Lose connections
+ above.below = null
+ if(below)
+ below.above = null
+ return
+ refresh() //Sometimes the powernets get lost, so we need to keep checking.
+ if(powernet && (powernet.avail <= 0)) // is it powered?
+ icon_state = "cablerelay-off"
+ else
+ icon_state = "cablerelay-on"
+ if(!below || QDELETED(below) || !above || QDELETED(above))
+ icon_state = "cablerelay-off"
+ find_relays()
+
+///Allows you to scan the relay with a multitool to see stats.
+/obj/machinery/power/deck_relay/multitool_act(mob/user, obj/item/I)
+ if(powernet && (powernet.avail > 0)) // is it powered?
+ to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]")
+ if(!powernet || below.powernet != powernet)
+ icon_state = "cablerelay-off"
+ to_chat(user, "Powernet connection lost. Attempting to re-establish. Ensure the relays below this one are connected too.")
+ find_relays()
+ addtimer(CALLBACK(src, PROC_REF(refresh)), 20) //Wait a bit so we can find the one below, then get powering
+ return TRUE
+
+///Handles re-acquiring + merging powernets found by find_relays()
+/obj/machinery/power/deck_relay/proc/refresh()
+ if(above)
+ above.merge(src)
+ if(below)
+ below.merge(src)
+
+/obj/machinery/power/deck_relay/proc/merge(obj/machinery/power/deck_relay/relay)
+ if(!relay)
+ return
+ var/turf/merge_from = get_turf(relay)
+ var/turf/merge_to = get_turf(src)
+ var/obj/structure/cable/cable_merge_from = merge_from.get_cable_node()
+ var/obj/structure/cable/cable_merge_to = merge_to.get_cable_node()
+ if(cable_merge_from && cable_merge_to)
+ merge_powernets(cable_merge_to.powernet, cable_merge_from.powernet)//Bridge the powernets.
+
+///Locates relays that are above and below this object
+/obj/machinery/power/deck_relay/proc/find_relays()
+ var/turf/T = get_turf(src)
+ if(!T || !istype(T))
+ return FALSE
+ below = null //in case we're re-establishing
+ var/obj/structure/cable/C = T.get_cable_node() //check if we have a node cable on the machine turf, the first found is picked
+ if(C && C.powernet)
+ C.powernet.add_machine(src) //Nice we're in.
+ powernet = C.powernet
+ below = locate(/obj/machinery/power/deck_relay) in (GET_TURF_BELOW(T))
+ above = locate(/obj/machinery/power/deck_relay) in (GET_TURF_ABOVE(T))
+ if(below || above)
+ icon_state = "cablerelay-on"
+ return TRUE
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
index 91ba0b8c813f..ba5cbaac4108 100644
--- a/code/modules/power/power.dm
+++ b/code/modules/power/power.dm
@@ -115,8 +115,9 @@
* Returns TRUE if the NOPOWER flag was toggled
*/
/obj/machinery/proc/power_change()
+ //SIGNAL_HANDLER
if(stat & BROKEN)
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
if(powered(power_channel))
if(stat & NOPOWER)
@@ -128,7 +129,7 @@
SEND_SIGNAL(src, COMSIG_MACHINERY_POWER_LOST)
. = TRUE
stat |= NOPOWER
- update_appearance(UPDATE_ICON)
+ update_appearance()
// connect the machine to a powernet if a node cable is present on the turf
/obj/machinery/proc/connect_to_network()
@@ -156,7 +157,7 @@
if(istype(W, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/coil = W
var/turf/T = user.loc
- if(T.intact || !isfloorturf(T))
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE || !isfloorturf(T))
return
if(get_dist(src, user) > 1)
return
@@ -355,10 +356,6 @@
if(victim.getarmor(zone, ELECTRIC) >= 100)
SEND_SIGNAL(victim, COMSIG_LIVING_SHOCK_PREVENTED, power_source, source, siemens_coeff, dist_check)
- var/obj/item/clothing/gloves/G = victim.gloves
- if(istype(G, /obj/item/clothing/gloves/color/fyellow))
- var/obj/item/clothing/gloves/color/fyellow/greytide = G
- greytide.get_shocked()
return FALSE //to avoid spamming with insulated glvoes on
var/list/powernet_info = get_powernet_info_from_source(power_source)
diff --git a/code/modules/power/reactor/fuel_rods.dm b/code/modules/power/reactor/fuel_rods.dm
index 0f9c3c85506e..86ef9a8726b1 100644
--- a/code/modules/power/reactor/fuel_rods.dm
+++ b/code/modules/power/reactor/fuel_rods.dm
@@ -138,7 +138,7 @@
to_chat(user, "You insert [adding] [material_name_singular] into \the [src].")
else
to_chat(user, "You insert [adding] [material_name] into \the [src].")
- M.zero_amount()
+ M.is_zero_amount(delete_if_zero = TRUE)
else
to_chat(user, "\The [src]'s material slots are full!")
return
diff --git a/code/modules/power/reactor/reactor.dm b/code/modules/power/reactor/reactor.dm
index e0a8597112e2..7de1e268229f 100644
--- a/code/modules/power/reactor/reactor.dm
+++ b/code/modules/power/reactor/reactor.dm
@@ -175,12 +175,15 @@
to_chat(user, span_notice("The reactor has no fuel rods!"))
return TRUE
var/obj/item/fuel_rod/rod = tgui_input_list(usr, "Select a fuel rod to remove", "Fuel Rods", fuel_rods)
- if(rod && istype(rod) && I.use_tool(src, user, removal_time))
+ if(rod && istype(rod) && I.use_tool(src, user, removal_time, volume=50))
if(temperature > REACTOR_TEMPERATURE_MINIMUM)
var/turf/T = get_turf(src)
T.atmos_spawn_air("water_vapor=[pressure/100];TEMP=[temperature]")
user.rad_act(rod.fuel_power * 1000)
fuel_rods.Remove(rod)
+ if(ismecha(user.loc))
+ rod.forceMove(get_step(get_turf(user.loc), user.loc.dir))
+ return TRUE
if(!user.put_in_hands(rod))
rod.forceMove(user.loc)
return TRUE
@@ -568,7 +571,7 @@
//Results: Engineering becomes unusable and your engine irreparable
/obj/machinery/atmospherics/components/trinary/nuclear_reactor/proc/meltdown()
set waitfor = FALSE
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
vessel_integrity = null // this makes it show up weird on the monitor to even further emphasize something's gone horribly wrong
slagged = TRUE
color = null
@@ -593,10 +596,10 @@
T.assume_air(coolant_input)
T.assume_air(moderator_input)
T.assume_air(coolant_output)
- var/turf/lower_turf = SSmapping.get_turf_below(T)
+ var/turf/lower_turf = GET_TURF_BELOW(T)
if(lower_turf) // reactor fuel will melt down into the lower levels on multi-z maps like icemeta
new /obj/structure/reactor_corium(lower_turf)
- var/turf/lowest_turf = SSmapping.get_turf_below(lower_turf)
+ var/turf/lowest_turf = GET_TURF_BELOW(lower_turf)
if(lowest_turf) // WE NEED TO GO DEEPER
new /obj/structure/reactor_corium(lower_turf)
explosion(get_turf(src), 0, 5, 10, 20, TRUE, TRUE)
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm
index 10bd9e90bbe5..dfd62b2a5912 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/singularity/collector.dm
@@ -285,12 +285,12 @@
return
Z.forceMove(drop_location())
Z.layer = initial(Z.layer)
- Z.plane = initial(Z.plane)
+ SET_PLANE_IMPLICIT(Z, initial(Z.plane))
src.loaded_tank = null
if(active)
toggle_power()
else
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/power/rad_collector/rad_act(pulse_strength, collectable_radiation)
. = ..()
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index 2fb1abeaadbf..04971b98a560 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -477,7 +477,7 @@
//BUCKLE HOOKS
-/obj/machinery/power/emitter/prototype/unbuckle_mob(mob/living/buckled_mob,force = 0)
+/obj/machinery/power/emitter/prototype/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE)
manual = FALSE
for(var/obj/item/I in buckled_mob.held_items)
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm
index b328d36525a3..5b5159de6e36 100644
--- a/code/modules/power/singularity/narsie.dm
+++ b/code/modules/power/singularity/narsie.dm
@@ -59,10 +59,12 @@
var/datum/objective/eldergod/summon_objective = locate() in T.objectives
if(summon_objective)
summon_objective.summoned = TRUE
+/*
for(var/datum/mind/cult_mind in SSticker.mode.cult)
if(isliving(cult_mind.current))
var/mob/living/L = cult_mind.current
- L.narsie_act()
+ L.narsie_act()
+*/
for(var/mob/living/player in GLOB.player_list)
if(player.stat != DEAD && player.loc && is_station_level(player.loc.z) && !iscultist(player) && !isanimal(player))
souls_needed[player] = TRUE
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 3d45e0554f19..89cebd706384 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -4,7 +4,7 @@
#define SINGULARITY_INTEREST_NONSPACE 2
/atom/movable/gravity_lens
- plane = SINGULARITY_EFFECT_PLANE
+ plane = GRAVITY_PULSE_PLANE
//plane = GHOST_LAYER
appearance_flags = PIXEL_SCALE | RESET_TRANSFORM
icon = 'icons/effects/512x512.dmi'
@@ -19,7 +19,7 @@
anchored = TRUE
density = TRUE
move_resist = INFINITY
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
light_range = 6
appearance_flags = LONG_GLIDE
var/current_size = 1
diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm
index c17a460347bb..33a0bf6eeac7 100644
--- a/code/modules/power/smes.dm
+++ b/code/modules/power/smes.dm
@@ -116,7 +116,7 @@
return
var/turf/T = get_turf(user)
- if (T.intact) //is the floor plating removed ?
+ if (T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE) //can we get to the underfloor?
to_chat(user, span_warning("You must first remove the floor plating!"))
return
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
index 49c3ec5f7170..82412f16069c 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/solar.dm
@@ -214,6 +214,7 @@
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_BULKY // Pretty big!
anchored = FALSE
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
var/tracker = 0
var/glass_type = null
var/multiplier = 1
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 93e02b451c90..e118d46e80a6 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -222,7 +222,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/Initialize(mapload)
. = ..()
uid = gl_uid++
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
countdown = new(src)
countdown.start()
GLOB.poi_list |= src
@@ -238,7 +238,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/Destroy()
investigate_log("has been destroyed.", INVESTIGATE_SUPERMATTER)
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
QDEL_NULL(radio)
GLOB.poi_list -= src
QDEL_NULL(countdown)
@@ -599,14 +599,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(produces_gas)
env.merge(removed)
- for(var/mob/living/carbon/human/l in view(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them.
- if((!HAS_TRAIT(l, TRAIT_MESONS)) || corruptor_attached)
- visible_hallucination_pulse(
- center = src,
- radius = HALLUCINATION_RANGE(power),
- hallucination_duration = power * 0.1,
- hallucination_max_duration = 400 SECONDS,
- )
+ visible_hallucination_pulse(
+ center = src,
+ radius = HALLUCINATION_RANGE(power),
+ hallucination_duration = power * 0.1,
+ hallucination_max_duration = 400 SECONDS,
+ )
power -= ((power/500)**3) * powerloss_inhibitor
@@ -1007,6 +1005,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
empulse(src, EMP_HEAVY, 6)
qdel(W)
return
+ if(istype(W, /obj/item/demon_core))
+ investigate_log("[user] has inserted demon core into the SM, doubling it's rad production.")
+ to_chat(user,"You insert the demon core into the [src], it begins to glow with dark purple fire, you notice the area around you noticeably heating up...")
+ radmodifier *= 2
+ add_overlay(mutable_appearance('yogstation/icons/effects/effects.dmi',"tar_shield"))
+ qdel(W)
else if(user.dropItemToGround(W))
user.visible_message(span_danger("As [user] touches \the [src] with \a [W], silence fills the room..."),\
"[span_userdanger("You touch \the [src] with \the [W], and everything suddenly goes silent.")]\n[span_notice("\The [W] flashes into dust as you flinch away from \the [src].")]",\
diff --git a/code/modules/power/terminal.dm b/code/modules/power/terminal.dm
index 50b70d1d1834..d94ad80fc17f 100644
--- a/code/modules/power/terminal.dm
+++ b/code/modules/power/terminal.dm
@@ -7,16 +7,13 @@
name = "terminal"
icon_state = "term"
desc = "It's an underfloor wiring terminal for power equipment."
- level = 1
layer = WIRE_TERMINAL_LAYER //a bit above wires
var/obj/machinery/power/master = null
/obj/machinery/power/terminal/Initialize(mapload)
. = ..()
- var/turf/T = get_turf(src)
- if(level == 1)
- hide(T.intact)
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE, use_alpha = TRUE)
/obj/machinery/power/terminal/Destroy()
if(master)
@@ -24,15 +21,6 @@
master = null
return ..()
-/obj/machinery/power/terminal/hide(i)
- if(i)
- invisibility = INVISIBILITY_MAXIMUM
- icon_state = "term-f"
- else
- invisibility = 0
- icon_state = "term"
-
-
/obj/machinery/power/proc/can_terminal_dismantle()
. = FALSE
@@ -50,7 +38,7 @@
/obj/machinery/power/terminal/proc/dismantle(mob/living/user, obj/item/I)
if(isturf(loc))
var/turf/T = loc
- if(T.intact)
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE)
to_chat(user, span_warning("You must first expose the power terminal!"))
return
diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm
index ec2bc2e82197..495c1c6243f8 100644
--- a/code/modules/power/tracker.dm
+++ b/code/modules/power/tracker.dm
@@ -10,6 +10,7 @@
icon_state = "tracker"
density = TRUE
use_power = NO_POWER_USE
+ blocks_emissive = EMISSIVE_BLOCK_UNIQUE
max_integrity = 250
integrity_failure = 0.2
@@ -27,6 +28,14 @@
unset_control() //remove from control computer
return ..()
+//Yog: our solars aren't as overlay based yet
+// /obj/machinery/power/tracker/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
+// . = ..()
+// if(same_z_layer)
+// return
+// SET_PLANE(tracker_dish_edge, PLANE_TO_TRUE(tracker_dish_edge.plane), new_turf)
+// SET_PLANE(tracker_dish, PLANE_TO_TRUE(tracker_dish.plane), new_turf)
+
/obj/machinery/power/tracker/proc/set_control(obj/machinery/power/solar_control/SC)
unset_control()
control = SC
diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm
index 64102eadea66..af78a918b9af 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/turbine.dm
@@ -29,7 +29,7 @@
icon_state = "compressor"
density = TRUE
resistance_flags = FIRE_PROOF
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
use_power = NO_POWER_USE // powered by gas flow
circuit = /obj/item/circuitboard/machine/power_compressor
var/obj/machinery/power/turbine/turbine
@@ -44,7 +44,7 @@
var/intake_ratio = 0.1 // might add a way to adjust this in-game later
/obj/machinery/power/compressor/Destroy()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
if (turbine && turbine.compressor == src)
turbine.compressor = null
var/turf/T = get_turf(src)
@@ -62,7 +62,7 @@
icon_state = "turbine"
density = TRUE
resistance_flags = FIRE_PROOF
- CanAtmosPass = ATMOS_PASS_DENSITY
+ can_atmos_pass = ATMOS_PASS_DENSITY
use_power = NO_POWER_USE // powered by gas flow
circuit = /obj/item/circuitboard/machine/power_turbine
var/opened = 0
@@ -72,7 +72,7 @@
var/productivity = 1
/obj/machinery/power/turbine/Destroy()
- SSair_machinery.stop_processing_machine(src)
+ SSair.stop_processing_machine(src)
if (compressor && compressor.turbine == src)
compressor.turbine = null
compressor = null
@@ -91,7 +91,7 @@
/obj/machinery/power/compressor/Initialize(mapload)
. = ..()
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
// The inlet of the compressor is the direction it faces
gas_contained = new
inturf = get_step(src, dir)
@@ -194,7 +194,7 @@
/obj/machinery/power/turbine/Initialize(mapload)
. = ..()
- SSair_machinery.start_processing_machine(src)
+ SSair.start_processing_machine(src)
// The outlet is pointed at the direction of the turbine component
outturf = get_step(src, dir)
locate_machinery()
@@ -248,7 +248,7 @@
if(!isclosedturf(outturf))
output_blocked = FALSE
for(var/atom/A in outturf)
- if(!CANATMOSPASS(A, outturf))
+ if(!CANATMOSPASS(A, outturf, FALSE))
output_blocked = TRUE
break
diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm
index 117ae0e176fd..3c78b00e4255 100644
--- a/code/modules/procedural_mapping/mapGenerators/repair.dm
+++ b/code/modules/procedural_mapping/mapGenerators/repair.dm
@@ -28,46 +28,43 @@
var/z_offset = SSmapping.station_start
var/list/bounds
for (var/path in SSmapping.config.GetFullMapPaths())
- var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, measureOnly = FALSE, no_changeturf = FALSE, cropMap=TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high)
+ var/datum/parsed_map/parsed = load_map(
+ file(path),
+ 1,
+ 1,
+ z_offset,
+ no_changeturf = FALSE,
+ crop_map = TRUE,
+ x_lower = mother1.x_low,
+ y_lower = mother1.y_low,
+ x_upper = mother1.x_high,
+ y_upper = mother1.y_high,
+ )
bounds = parsed?.bounds
z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1
var/list/obj/machinery/atmospherics/atmos_machines = list()
var/list/obj/structure/cable/cables = list()
- var/list/atom/movable/movables = list()
- var/list/area/areas = list()
-
- var/list/turfs = block(
- locate(
- bounds[MAP_MINX],
- bounds[MAP_MINY],
- SSmapping.station_start
- ),
- locate(
- bounds[MAP_MAXX],
- bounds[MAP_MAXY],
- z_offset - 1
- )
- )
+ var/list/atom/atoms = list()
- for(var/turf/current_turf as anything in turfs)
- var/area/current_turfs_area = current_turf.loc
- areas |= current_turfs_area
+ require_area_resort()
- for(var/movable_in_turf in current_turf)
- movables += movable_in_turf
- if(istype(movable_in_turf, /obj/structure/cable))
- cables += movable_in_turf
+ var/list/generation_turfs = block(
+ locate(bounds[MAP_MINX], bounds[MAP_MINY], SSmapping.station_start),
+ locate(bounds[MAP_MAXX], bounds[MAP_MAXY], z_offset - 1))
+ for(var/turf/gen_turf as anything in generation_turfs)
+ atoms += gen_turf
+ for(var/atom in gen_turf)
+ atoms += atom
+ if(istype(atom, /obj/structure/cable))
+ cables += atom
continue
- if(istype(movable_in_turf, /obj/machinery/atmospherics))
- atmos_machines += movable_in_turf
+ if(istype(atom, /obj/machinery/atmospherics))
+ atmos_machines += atom
- SSatoms.InitializeAtoms(areas + turfs + movables)
+ SSatoms.InitializeAtoms(atoms)
SSmachines.setup_template_powernets(cables)
SSair.setup_template_machinery(atmos_machines)
-
- require_area_resort()
-
GLOB.reloading_map = FALSE
/datum/mapGenerator/repair
diff --git a/code/modules/projectiles/ammunition/ballistic/lmg.dm b/code/modules/projectiles/ammunition/ballistic/lmg.dm
index 0a3867f90b49..ecf1561577f4 100644
--- a/code/modules/projectiles/ammunition/ballistic/lmg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm
@@ -4,7 +4,7 @@
name = "7.12x82mm bullet casing"
desc = "A 7.12x82mm bullet casing."
icon_state = "762-casing"
- caliber = "mm71282"
+ caliber = CALIBER_712X82
projectile_type = /obj/projectile/bullet/mm712x82
/obj/item/ammo_casing/mm712x82/ap
diff --git a/code/modules/projectiles/ammunition/ballistic/minigun.dm b/code/modules/projectiles/ammunition/ballistic/minigun.dm
index 307853a34471..593ef9422086 100644
--- a/code/modules/projectiles/ammunition/ballistic/minigun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/minigun.dm
@@ -4,5 +4,5 @@
name = "5.46mm bullet casing"
desc = "A 5.46mm bullet casing. Wait, the fuck is a 5.46"
icon_state = "556-casing" // Most bullets use this casing and this gun does not even drop any so...
- caliber = "a546"
+ caliber = CALIBER_546MM
projectile_type = /obj/projectile/bullet/a546
diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm
index f7161e0837ac..03969ee98cee 100644
--- a/code/modules/projectiles/ammunition/ballistic/pistol.dm
+++ b/code/modules/projectiles/ammunition/ballistic/pistol.dm
@@ -3,13 +3,13 @@
/obj/item/ammo_casing/c10mm
name = "10mm bullet casing"
desc = "A 10mm bullet casing."
- caliber = "10mm"
+ caliber = CALIBER_10MM
projectile_type = /obj/projectile/bullet/c10mm
/obj/item/ammo_casing/caseless/c10mm/cs
name = "10mm caseless bullet"
desc = "A 10mm caseless bullet."
- caliber = "10mm"
+ caliber = CALIBER_10MM
projectile_type = /obj/projectile/bullet/c10mm/cs
/obj/item/ammo_casing/c10mm/ap
@@ -43,7 +43,7 @@
/obj/item/ammo_casing/c9mm
name = "9mm bullet casing"
desc = "A 9mm bullet casing."
- caliber = "9mm"
+ caliber = CALIBER_9X19
projectile_type = /obj/projectile/bullet/c9mm
/obj/item/ammo_casing/c9mm/ap
@@ -62,7 +62,7 @@
/obj/item/ammo_casing/a50AE
name = ".50 AE bullet casing"
desc = "A .50 AE bullet casing."
- caliber = ".50ae"
+ caliber = CALIBER_50AE
projectile_type = /obj/projectile/bullet/a50AE
// Bolt Pistol
@@ -70,12 +70,12 @@
/obj/item/ammo_casing/boltpistol
name = ".75 bolt round casing"
desc = "A .75 bolt round casing."
- caliber = ".75"
+ caliber = CALIBER_75
projectile_type = /obj/projectile/bullet/boltpistol
/obj/item/ammo_casing/boltpistol/admin
name = ".75 bolt round casing"
desc = "A .75 bolt round casing. This one feels more powerful somehow..."
- caliber = ".75"
+ caliber = CALIBER_75
projectile_type = /obj/projectile/bullet/boltpistol/admin
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index 565fed74fb97..aa98196f2cad 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -3,7 +3,7 @@
/obj/item/ammo_casing/a357
name = ".357 magnum bullet casing"
desc = "A .357 magnum bullet casing."
- caliber = "357"
+ caliber = CALIBER_357MAG
projectile_type = /obj/projectile/bullet/a357
/obj/item/ammo_casing/a357/ironfeather
@@ -39,7 +39,7 @@
name = ".44 magnum bullet casing"
desc = "A .44 magnum bullet casing."
icon_state = "44-casing"
- caliber = "44"
+ caliber = CALIBER_44MAG
projectile_type = /obj/projectile/bullet/m44
// 7.62x38mmR (Nagant Revolver)
@@ -47,7 +47,7 @@
/obj/item/ammo_casing/n762
name = "7.62x38mmR bullet casing"
desc = "A 7.62x38mmR bullet casing."
- caliber = "n762"
+ caliber = CALIBER_762X38R
projectile_type = /obj/projectile/bullet/n762
// .38 (Colt Detective Special + Vatra M38)
@@ -55,7 +55,7 @@
/obj/item/ammo_casing/c38
name = ".38 special bullet casing"
desc = "A .38 special bullet casing."
- caliber = "38"
+ caliber = CALIBER_38
projectile_type = /obj/projectile/bullet/c38
/obj/item/ammo_casing/c38/rubber
@@ -88,5 +88,5 @@
/obj/item/ammo_casing/tra32
name = ".32 TRAC bullet casing"
desc = "A .32 TRAC bullet casing."
- caliber = "32trac"
+ caliber = CALIBER_32ACP
projectile_type = /obj/projectile/bullet/tra32
diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm
index 661ea526b139..d5379aee4e80 100644
--- a/code/modules/projectiles/ammunition/ballistic/rifle.dm
+++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm
@@ -4,7 +4,7 @@
name = "7.62mm bullet casing"
desc = "A 7.62mm bullet casing."
icon_state = "762-casing"
- caliber = "a762"
+ caliber = CALIBER_762X54R
projectile_type = /obj/projectile/bullet/a762
/obj/item/ammo_casing/a762/raze
@@ -34,7 +34,7 @@
name = "5.56mm bullet casing"
desc = "A 5.56mm bullet casing."
icon_state = "556-casing"
- caliber = "a556"
+ caliber = CALIBER_556NATO
projectile_type = /obj/projectile/bullet/a556
/obj/item/ammo_casing/a556/ap
@@ -61,7 +61,7 @@
name = ".308 bullet casing"
desc = "A .308 bullet casing."
icon_state = "556-casing"
- caliber = "m308"
+ caliber = CALIBER_308
projectile_type = /obj/projectile/bullet/m308
/obj/item/ammo_casing/m308/pen
@@ -81,6 +81,6 @@
/obj/item/ammo_casing/a40mm
name = "40mm HE shell"
desc = "A cased high explosive grenade that can only be activated once fired out of a grenade launcher."
- caliber = "40mm"
+ caliber = CALIBER_40GL
icon_state = "40mmHE"
projectile_type = /obj/projectile/bullet/a40mm
diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
index 10339f83fce3..de7538c5a49e 100644
--- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
@@ -4,7 +4,7 @@
name = "shotgun slug"
desc = "A 12-gauge lead slug."
icon_state = "blshell"
- caliber = "shotgun"
+ caliber = CALIBER_12GA
projectile_type = /obj/projectile/bullet/shotgun/slug
materials = list(/datum/material/iron=4000)
@@ -131,14 +131,14 @@
name = "depleted uranium slug"
desc = "A relatively low-tech shell, utilizing the unique properties of Uranium, and possessing \
very impressive armor penetration capabilities."
- icon_state = "dushell"
+ icon_state = "dushell"
projectile_type = /obj/projectile/bullet/shotgun/slug/uranium
/obj/item/ammo_casing/shotgun/cryoshot
name = "cryoshot shell"
desc = "A state-of-the-art shell which uses the cooling power of Rhigoxane to snap freeze a target, without causing \
them much harm."
- icon_state = "fshell"
+ icon_state = "fshell"
projectile_type = /obj/projectile/bullet/pellet/shotgun_cryoshot
pellets = 4
variance = 30
@@ -148,7 +148,7 @@
desc = "A high-tech shotgun shell which can be loaded with materials to produce unique effects."
icon_state = "cshell"
projectile_type = null
-
+
/obj/item/ammo_casing/shotgun/dart
name = "shotgun dart"
desc = "A dart for use in shotguns. Can be injected with up to 30 units of any chemical."
@@ -208,7 +208,7 @@
icon_state = "breacher"
projectile_type = /obj/projectile/bullet/shotgun/slug/breaching
materials = list(/datum/material/iron=4000)
- caliber = "breaching"
+ caliber = CALIBER_BREACH
/obj/item/ammo_casing/shotgun/thundershot
diff --git a/code/modules/projectiles/ammunition/ballistic/smg.dm b/code/modules/projectiles/ammunition/ballistic/smg.dm
index 0d5fda32219c..51c5bdba2075 100644
--- a/code/modules/projectiles/ammunition/ballistic/smg.dm
+++ b/code/modules/projectiles/ammunition/ballistic/smg.dm
@@ -3,7 +3,7 @@
/obj/item/ammo_casing/c46x30mm
name = "4.6x30mm bullet casing"
desc = "A 4.6x30mm bullet casing."
- caliber = "4.6x30mm"
+ caliber = CALIBER_46X30
projectile_type = /obj/projectile/bullet/c46x30mm
/obj/item/ammo_casing/c46x30mm/ap
@@ -35,7 +35,7 @@
name = "4.6x30mm airburst bullet casing"
desc = "A 4.6x30mm airburst bullet casing."
projectile_type = /obj/projectile/bullet/c46x30mm/airburst
-
+
/obj/item/ammo_casing/c46x30mm/airburst_pellet
name = "4.6x30mm airburst casing"
desc = "A 4.6x30mm airburst casing."
@@ -48,7 +48,7 @@
/obj/item/ammo_casing/c45
name = ".45 ACP bullet casing"
desc = "A .45 ACP bullet casing."
- caliber = ".45"
+ caliber = CALIBER_45ACP
projectile_type = /obj/projectile/bullet/c45
/obj/item/ammo_casing/c45/ap
diff --git a/code/modules/projectiles/ammunition/ballistic/sniper.dm b/code/modules/projectiles/ammunition/ballistic/sniper.dm
index 9ad7b898e7c9..959e2dd29f00 100644
--- a/code/modules/projectiles/ammunition/ballistic/sniper.dm
+++ b/code/modules/projectiles/ammunition/ballistic/sniper.dm
@@ -3,7 +3,7 @@
/obj/item/ammo_casing/p50
name = ".50 BMG bullet casing"
desc = "A .50 BMG bullet casing."
- caliber = ".50bmg"
+ caliber = CALIBER_50BMG
projectile_type = /obj/projectile/bullet/p50
icon_state = ".50"
diff --git a/code/modules/projectiles/ammunition/caseless/rocket.dm b/code/modules/projectiles/ammunition/caseless/rocket.dm
index 223450a5efd6..558471acd3a7 100644
--- a/code/modules/projectiles/ammunition/caseless/rocket.dm
+++ b/code/modules/projectiles/ammunition/caseless/rocket.dm
@@ -1,20 +1,20 @@
/obj/item/ammo_casing/caseless/rocket
name = "\improper PM-9HE"
desc = "An 84mm High Explosive rocket. Fire at people and pray."
- caliber = "84mm"
+ caliber = CALIBER_84HE
icon_state = "srm-8"
projectile_type = /obj/projectile/bullet/a84mm_he
/obj/item/ammo_casing/caseless/rocket/hedp
name = "\improper PM-9HEDP"
desc = "An 84mm High Explosive Dual Purpose rocket. Pointy end toward mechs."
- caliber = "84mm"
+ caliber = CALIBER_84HE
icon_state = "84mm-hedp"
projectile_type = /obj/projectile/bullet/a84mm
/obj/item/ammo_casing/caseless/a75
desc = "A .75 bullet casing."
- caliber = "75"
+ caliber = CALIBER_75GYRO
icon_state = "s-casing-live"
projectile_type = /obj/projectile/bullet/gyro
@@ -22,7 +22,7 @@
/obj/item/ammo_casing/caseless/cannonball
name = "cannonball"
desc = "A big ball of lead, perfect for shooting through windows and doors."
- caliber = "100mm"
+ caliber = CALIBER_CANNON
icon = 'icons/obj/ammo.dmi'
icon_state = "cannonball"
projectile_type = /obj/projectile/bullet/cball
@@ -30,7 +30,7 @@
/obj/item/ammo_casing/caseless/bolts
name = "bolts"
- desc = "rods, cut in half and ready to be shot"
+ desc = "Rods, cut in half and ready to be shot."
caliber = null
icon = 'icons/obj/ammo.dmi'
icon_state = "bolt"
diff --git a/code/modules/projectiles/ammunition/energy/plasma.dm b/code/modules/projectiles/ammunition/energy/plasma.dm
index 922b950b9333..a74528a65381 100644
--- a/code/modules/projectiles/ammunition/energy/plasma.dm
+++ b/code/modules/projectiles/ammunition/energy/plasma.dm
@@ -5,6 +5,12 @@
delay = 15
e_cost = 25
+/obj/item/ammo_casing/energy/plasma/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
+ ..()
+ if(loc && istype(loc, /obj/item/gun/energy/plasmacutter))
+ var/obj/item/gun/energy/plasmacutter/PC = loc
+ PC.modify_projectile(BB)
+
/obj/item/ammo_casing/energy/plasma/weak
projectile_type = /obj/projectile/plasma/weak
select_name = "weak plasma burst"
diff --git a/code/modules/projectiles/ammunition/reusable/arrow.dm b/code/modules/projectiles/ammunition/reusable/arrow.dm
index c367fdcfbac6..d1a8f0830980 100644
--- a/code/modules/projectiles/ammunition/reusable/arrow.dm
+++ b/code/modules/projectiles/ammunition/reusable/arrow.dm
@@ -2,7 +2,7 @@
name = "arrow"
desc = "An arrow, typically fired from a bow."
projectile_type = /obj/projectile/bullet/reusable/arrow
- caliber = "arrow"
+ caliber = CALIBER_ARROW
icon_state = "arrow"
item_state = "arrow"
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
@@ -95,7 +95,7 @@
attached_parts = null
to_chat(user, span_notice("You remove the attached parts."))
-
+
/obj/item/ammo_casing/reusable/arrow/CheckParts(list/parts_list)
var/obj/item/ammo_casing/reusable/arrow/A = locate(/obj/item/ammo_casing/reusable/arrow) in parts_list
if(A)
@@ -147,11 +147,11 @@
/obj/item/ammo_casing/reusable/arrow/proc/add_flame()
flaming = TRUE
update_appearance(UPDATE_ICON)
-
+
/obj/item/ammo_casing/reusable/arrow/proc/on_embed(target, mob/living/carbon/embedde)
if(syringe)
syringe.embed_inject(target, embedde)
-
+
/obj/item/ammo_casing/reusable/arrow/proc/embed_tick(target, mob/living/carbon/embedde)
if(syringe)
syringe.embed_inject(target, embedde)
@@ -218,7 +218,7 @@
desc = "An arrow tipped with bronze. Better against armor than iron."
icon_state = "bronzearrow"
item_state = "bronzearrow"
- armour_penetration = 10
+ armour_penetration = 10
projectile_type = /obj/projectile/bullet/reusable/arrow/bronze
/obj/item/ammo_casing/reusable/arrow/glass
@@ -481,7 +481,7 @@
break_chance = 100
rads_released = 3000
empulse(src, 15) // Its going to break open into a singulo anyways, may as well add some fireworks
-
+
// Handles releasing rads
if(rads_released)
radiation_pulse(src, rads_released, RAD_DISTANCE_COEFFICIENT * 0.5)
@@ -547,7 +547,7 @@
projectile_type = /obj/projectile/energy/arrow/disabler
harmful = FALSE
tick_damage_type = STAMINA
-
+
/obj/item/ammo_casing/reusable/arrow/energy/pulse
name = "pulse bolt"
desc = "An arrow made from hardlight. This one eliminates any obstructions it hits."
diff --git a/code/modules/projectiles/ammunition/reusable/foam.dm b/code/modules/projectiles/ammunition/reusable/foam.dm
index 69059d89f4de..a41ba9bfe832 100644
--- a/code/modules/projectiles/ammunition/reusable/foam.dm
+++ b/code/modules/projectiles/ammunition/reusable/foam.dm
@@ -2,7 +2,7 @@
name = "foam dart"
desc = "It's nerf or nothing! Ages 8 and up."
projectile_type = /obj/projectile/bullet/reusable/foam_dart
- caliber = "foam_force"
+ caliber = CALIBER_FOAM
icon = 'icons/obj/guns/toy.dmi'
icon_state = "foamdart"
materials = list(/datum/material/iron = 11.25)
diff --git a/code/modules/projectiles/attachments/laser_sight.dm b/code/modules/projectiles/attachments/laser_sight.dm
index 204313fd56a3..56d78e573a60 100644
--- a/code/modules/projectiles/attachments/laser_sight.dm
+++ b/code/modules/projectiles/attachments/laser_sight.dm
@@ -68,7 +68,7 @@
return
aiming_lastangle = lastangle
var/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new
- P.gun = attached_gun
+ P.fired_from = attached_gun
P.color = laser_color
var/turf/curloc = get_turf(src)
diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
index 6f53f89eb77d..9eba5e58cfb4 100644
--- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm
+++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
@@ -138,7 +138,7 @@
if(!silent)
to_chat(user, span_notice("You load [num_loaded] round\s into \the [src]!"))
playsound(src, 'sound/weapons/bulletinsert.ogg', 60, TRUE)
- A.update_appearance(UPDATE_ICON)
+ A.update_appearance(UPDATE_ICON|UPDATE_DESC)
update_appearance(UPDATE_ICON|UPDATE_DESC)
return num_loaded
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index d96e7b82be4d..948f6e3c2c34 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -5,7 +5,7 @@
desc = "A seven-shot speed loader designed for .357 revolvers. High damaging, some innate prowess against armor."
icon_state = "357"
ammo_type = /obj/item/ammo_casing/a357
- caliber = "357"
+ caliber = CALIBER_357MAG
max_ammo = 7
multiple_sprites = AMMO_BOX_PER_BULLET
@@ -51,7 +51,7 @@
desc = "A six-shot speed loader designed for .44 revolvers. Massively damaging, wreaks havoc on bodies."
icon_state = "44"
ammo_type = /obj/item/ammo_casing/m44
- caliber = "44"
+ caliber = CALIBER_44MAG
max_ammo = 6
multiple_sprites = AMMO_BOX_PER_BULLET
@@ -62,7 +62,7 @@
desc = "A six-shot speed loader designed for .38 revolvers."
icon_state = "38"
ammo_type = /obj/item/ammo_casing/c38
- caliber = "38"
+ caliber = CALIBER_38
max_ammo = 6
multiple_sprites = AMMO_BOX_PER_BULLET
materials = list(/datum/material/iron = 20000)
@@ -81,7 +81,7 @@
These needle-like rounds deal miniscule damage, but inject a tracking implant upon burrowing into a target's body. Implant lifespan is five minutes."
icon_state = "32trac"
ammo_type = /obj/item/ammo_casing/tra32
- caliber = "32trac"
+ caliber = CALIBER_32ACP
max_ammo = 7
multiple_sprites = AMMO_BOX_PER_BULLET
@@ -91,14 +91,14 @@
name = "ammo box (9mm)"
icon_state = "9mmbox"
ammo_type = /obj/item/ammo_casing/c9mm
- caliber = "9mm"
+ caliber = CALIBER_9X19
max_ammo = 30
/obj/item/ammo_box/c10mm
name = "ammo box (10mm)"
icon_state = "10mmbox"
ammo_type = /obj/item/ammo_casing/c10mm
- caliber = "10mm"
+ caliber = CALIBER_10MM
max_ammo = 20
/obj/item/ammo_box/c10mm/cs
@@ -129,14 +129,14 @@
name = "ammo box (.45 ACP)"
icon_state = "45box"
ammo_type = /obj/item/ammo_casing/c45
- caliber = ".45"
+ caliber = CALIBER_45ACP
max_ammo = 20
/obj/item/ammo_box/a40mm
name = "ammo box (40mm grenades)"
icon_state = "40mm"
ammo_type = /obj/item/ammo_casing/a40mm
- caliber = "40mm"
+ caliber = CALIBER_40GL
max_ammo = 4
multiple_sprites = AMMO_BOX_PER_BULLET
@@ -145,7 +145,7 @@
icon = 'icons/obj/guns/toy.dmi'
icon_state = "foambox"
ammo_type = /obj/item/ammo_casing/reusable/foam_dart
- caliber = "foam_force"
+ caliber = CALIBER_FOAM
max_ammo = 40
materials = list(/datum/material/iron = 500)
@@ -186,14 +186,14 @@
name = "ammo box (7.62x38mmR)"
icon_state = "10mmbox"
ammo_type = /obj/item/ammo_casing/n762
- caliber = "n762"
+ caliber = CALIBER_762X38R
max_ammo = 14
/obj/item/ammo_box/no_direct/m308
name = "ammo box (.308)"
icon_state = "308box"
ammo_type = /obj/item/ammo_casing/m308
- caliber = "m308"
+ caliber = CALIBER_308
max_ammo = 20
// Mosin stripper clip
@@ -203,7 +203,7 @@
desc = "A stripper clip holding 7.62mm rounds."
icon_state = "762"
ammo_type = /obj/item/ammo_casing/a762
- caliber = "a762"
+ caliber = CALIBER_762X54R
max_ammo = 5
multiple_sprites = AMMO_BOX_PER_BULLET
diff --git a/code/modules/projectiles/boxes_magazines/external/grenade.dm b/code/modules/projectiles/boxes_magazines/external/grenade.dm
index f669a80805a9..9df0a7142de0 100644
--- a/code/modules/projectiles/boxes_magazines/external/grenade.dm
+++ b/code/modules/projectiles/boxes_magazines/external/grenade.dm
@@ -2,7 +2,7 @@
name = "specialized magazine (.75)"
icon_state = "75-8"
ammo_type = /obj/item/ammo_casing/caseless/a75
- caliber = "75"
+ caliber = CALIBER_75GYRO
max_ammo = 8
/obj/item/ammo_box/magazine/m75/update_icon_state()
diff --git a/code/modules/projectiles/boxes_magazines/external/lmg.dm b/code/modules/projectiles/boxes_magazines/external/lmg.dm
index f7a2879a9412..0a5c9ab5599c 100644
--- a/code/modules/projectiles/boxes_magazines/external/lmg.dm
+++ b/code/modules/projectiles/boxes_magazines/external/lmg.dm
@@ -5,7 +5,7 @@
desc = "A 50-round box magazine designed for the L6 Saw."
icon_state = "a762-50"
ammo_type = /obj/item/ammo_casing/mm712x82
- caliber = "mm71282"
+ caliber = CALIBER_712X82
max_ammo = 50
/obj/item/ammo_box/magazine/mm712x82/hollow
diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm
index 66ff95bb5aed..a7d0db219880 100644
--- a/code/modules/projectiles/boxes_magazines/external/pistol.dm
+++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm
@@ -5,7 +5,7 @@
desc = "An 10-round 10mm magazine designed for the Stechkin pistol."
icon_state = "9x19p"
ammo_type = /obj/item/ammo_casing/c10mm
- caliber = "10mm"
+ caliber = CALIBER_10MM
max_ammo = 10
multiple_sprites = AMMO_BOX_FULL_EMPTY
@@ -62,7 +62,7 @@
desc = "An 8-round .45 ACP magazine designed for the M1911 pistol."
icon_state = "45-8"
ammo_type = /obj/item/ammo_casing/c45
- caliber = ".45"
+ caliber = CALIBER_45ACP
max_ammo = 8
/obj/item/ammo_box/magazine/m45/update_icon_state()
@@ -79,7 +79,7 @@
desc = "A 15-round 9mm magazine designed for the Stechkin APS Pistol."
icon_state = "9x19p-10"
ammo_type = /obj/item/ammo_casing/c9mm
- caliber = "9mm"
+ caliber = CALIBER_9X19
max_ammo = 15
/obj/item/ammo_box/magazine/pistolm9mm/update_icon_state()
@@ -93,7 +93,7 @@
desc = "A 7-round .50 AE magazine designed for the Desert Eagle."
icon_state = "50ae-7"
ammo_type = /obj/item/ammo_casing/a50AE
- caliber = ".50ae"
+ caliber = CALIBER_50AE
max_ammo = 7
/obj/item/ammo_box/magazine/m50/update_icon_state()
@@ -110,7 +110,7 @@
desc = "A 8-round .38 special magazine designed for the Vatra M38 pistol."
icon_state = "v38-8"
ammo_type = /obj/item/ammo_casing/c38
- caliber = "38"
+ caliber = CALIBER_38
max_ammo = 8
/obj/item/ammo_box/magazine/v38/update_icon_state()
diff --git a/code/modules/projectiles/boxes_magazines/external/rifle.dm b/code/modules/projectiles/boxes_magazines/external/rifle.dm
index d8ab658bc0a0..3c494a4bc8cf 100644
--- a/code/modules/projectiles/boxes_magazines/external/rifle.dm
+++ b/code/modules/projectiles/boxes_magazines/external/rifle.dm
@@ -5,7 +5,7 @@
desc = "A well-worn magazine fitted for the surplus carbine."
icon_state = "75-8"
ammo_type = /obj/item/ammo_casing/c45
- caliber = ".45"
+ caliber = CALIBER_45ACP
max_ammo = 10
/obj/item/ammo_box/magazine/m10mm/rifle/update_icon_state()
@@ -22,7 +22,7 @@
desc = "A 30-round toploading magazine filled with 5.56 rounds, designed for the M-90gl Rifle."
icon_state = "5.56m-30"
ammo_type = /obj/item/ammo_casing/a556
- caliber = "a556"
+ caliber = CALIBER_556NATO
max_ammo = 30
/obj/item/ammo_box/magazine/m556/update_icon_state()
@@ -52,7 +52,7 @@
desc = "A standard 30-round magazine for the NT ARG 'Boarder' Rifle. Filled with 5.56 rounds."
icon_state = "arg556"
ammo_type = /obj/item/ammo_casing/a556
- caliber = "a556"
+ caliber = CALIBER_556NATO
max_ammo = 30
/obj/item/ammo_box/magazine/r556/update_icon_state()
@@ -93,7 +93,7 @@
desc = "A standard 15-round magazine for the LWT-650 DMR. Filled with .308 rounds."
icon_state = "m308"
ammo_type = /obj/item/ammo_casing/m308
- caliber = "m308"
+ caliber = CALIBER_308
max_ammo = 15
/obj/item/ammo_box/magazine/m308/update_icon_state()
@@ -127,7 +127,7 @@
desc = "A standard 11-round magazine for the K-41s DMR. Filled with 7.62mm rounds."
icon_state = "ks762"
ammo_type = /obj/item/ammo_casing/a762
- caliber = "a762"
+ caliber = CALIBER_762X54R
max_ammo = 11
/obj/item/ammo_box/magazine/ks762/update_icon_state()
diff --git a/code/modules/projectiles/boxes_magazines/external/shotgun.dm b/code/modules/projectiles/boxes_magazines/external/shotgun.dm
index a7445adb6ae6..0f62482118f8 100644
--- a/code/modules/projectiles/boxes_magazines/external/shotgun.dm
+++ b/code/modules/projectiles/boxes_magazines/external/shotgun.dm
@@ -6,7 +6,7 @@
Syndicate buckshot is more damaging than your standard buckshot."
icon_state = "m12gb-8"
ammo_type = /obj/item/ammo_casing/shotgun/buckshot/syndie
- caliber = "shotgun"
+ caliber = CALIBER_12GA
max_ammo = 8
sprite_designation = "b"
diff --git a/code/modules/projectiles/boxes_magazines/external/smg.dm b/code/modules/projectiles/boxes_magazines/external/smg.dm
index 4255afba672a..cc4e3d48fd6e 100644
--- a/code/modules/projectiles/boxes_magazines/external/smg.dm
+++ b/code/modules/projectiles/boxes_magazines/external/smg.dm
@@ -5,7 +5,7 @@
desc = "A 20-round 4.6x30mm magazine, designed for the WT-550 Carbine."
icon_state = "46x30mmt-20"
ammo_type = /obj/item/ammo_casing/c46x30mm
- caliber = "4.6x30mm"
+ caliber = CALIBER_46X30
max_ammo = 20
/obj/item/ammo_box/magazine/wt550m9/update_icon_state()
@@ -99,7 +99,7 @@
desc = "A 32-round magazine for the Type T3 Uzi that contains 9mm rounds."
icon_state = "uzi9mm-32"
ammo_type = /obj/item/ammo_casing/c9mm
- caliber = "9mm"
+ caliber = CALIBER_9X19
max_ammo = 32
/obj/item/ammo_box/magazine/uzim9mm/update_icon_state()
@@ -113,7 +113,7 @@
desc = "A 21-round magazine for the Nanotrasen Saber SMG that contains 9mm rounds."
icon_state = "smg9mm-42"
ammo_type = /obj/item/ammo_casing/c9mm
- caliber = "9mm"
+ caliber = CALIBER_9X19
max_ammo = 21
/obj/item/ammo_box/magazine/smgm9mm/update_icon_state()
@@ -143,7 +143,7 @@
desc = "A 24-round magazine for the C-20r SMG that contains .45 ACP rounds."
icon_state = "c20r45-24"
ammo_type = /obj/item/ammo_casing/c45
- caliber = ".45"
+ caliber = CALIBER_45ACP
max_ammo = 24
/obj/item/ammo_box/magazine/smgm45/update_icon_state()
@@ -181,5 +181,5 @@
desc = "A massive 50-round drum magazine for usage in the Thompson SMG. It contains .45 ACP rounds."
icon_state = "drum45"
ammo_type = /obj/item/ammo_casing/c45
- caliber = ".45"
+ caliber = CALIBER_45ACP
max_ammo = 50
diff --git a/code/modules/projectiles/boxes_magazines/external/sniper.dm b/code/modules/projectiles/boxes_magazines/external/sniper.dm
index 3bdaf440940d..7bcf65014183 100644
--- a/code/modules/projectiles/boxes_magazines/external/sniper.dm
+++ b/code/modules/projectiles/boxes_magazines/external/sniper.dm
@@ -7,7 +7,7 @@
icon_state = ".50mag"
ammo_type = /obj/item/ammo_casing/p50
max_ammo = 6
- caliber = ".50bmg"
+ caliber = CALIBER_50BMG
/obj/item/ammo_box/magazine/sniper_rounds/update_icon_state()
. = ..()
diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm
index 118f05bb51fd..5252f4e8f61d 100644
--- a/code/modules/projectiles/boxes_magazines/external/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/external/toy.dm
@@ -1,7 +1,7 @@
/obj/item/ammo_box/magazine/toy
name = "foam force META magazine"
ammo_type = /obj/item/ammo_casing/reusable/foam_dart
- caliber = "foam_force"
+ caliber = CALIBER_FOAM
/obj/item/ammo_box/magazine/toy/smg
name = "foam force SMG magazine"
@@ -31,7 +31,7 @@
/obj/item/ammo_box/magazine/toy/smgm45
name = "donksoft SMG magazine"
icon_state = "c20r45-toy"
- caliber = "foam_force"
+ caliber = CALIBER_FOAM
ammo_type = /obj/item/ammo_casing/reusable/foam_dart
max_ammo = 20
@@ -46,7 +46,7 @@
/obj/item/ammo_box/magazine/toy/m762
name = "donksoft box magazine"
icon_state = "a762-toy"
- caliber = "foam_force"
+ caliber = CALIBER_FOAM
ammo_type = /obj/item/ammo_casing/reusable/foam_dart
max_ammo = 50
diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
index 7b47c450a3bd..aa8e6514d69f 100644
--- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
@@ -1,7 +1,7 @@
/obj/item/ammo_box/magazine/internal/cylinder
name = "revolver cylinder"
ammo_type = /obj/item/ammo_casing/a357
- caliber = "357"
+ caliber = CALIBER_357MAG
max_ammo = 7
/obj/item/ammo_box/magazine/internal/cylinder/get_round(keep = 0)
diff --git a/code/modules/projectiles/boxes_magazines/internal/bow.dm b/code/modules/projectiles/boxes_magazines/internal/bow.dm
index 785dbfbe51b3..4ca1a3611177 100644
--- a/code/modules/projectiles/boxes_magazines/internal/bow.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/bow.dm
@@ -1,7 +1,7 @@
/obj/item/ammo_box/magazine/internal/bow
name = "bow... magazine?" //shouldnt see this item
ammo_type = /obj/item/ammo_casing/reusable/arrow
- caliber = "arrow"
+ caliber = CALIBER_ARROW
max_ammo = 1
start_empty = TRUE
@@ -22,10 +22,10 @@
/obj/item/ammo_box/magazine/internal/bow/energy/clockcult
ammo_type = /obj/item/ammo_casing/reusable/arrow/energy/clockbolt
selectable_types = list(/obj/item/ammo_casing/reusable/arrow/energy/clockbolt)
-
+
/obj/item/ammo_box/magazine/arrow
name = "crossbow magazine"
ammo_type = /obj/item/ammo_casing/reusable/arrow
icon_state = ".50mag"
- caliber = "arrow"
+ caliber = CALIBER_ARROW
max_ammo = 5
diff --git a/code/modules/projectiles/boxes_magazines/internal/grenade.dm b/code/modules/projectiles/boxes_magazines/internal/grenade.dm
index c0d1d8d54269..746522d0cfc0 100644
--- a/code/modules/projectiles/boxes_magazines/internal/grenade.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/grenade.dm
@@ -1,25 +1,25 @@
/obj/item/ammo_box/magazine/internal/cylinder/grenademulti
name = "grenade launcher internal magazine"
ammo_type = /obj/item/ammo_casing/a40mm
- caliber = "40mm"
+ caliber = CALIBER_40GL
max_ammo = 6
/obj/item/ammo_box/magazine/internal/grenadelauncher
name = "grenade launcher internal magazine"
ammo_type = /obj/item/ammo_casing/a40mm
- caliber = "40mm"
+ caliber = CALIBER_40GL
max_ammo = 1
/obj/item/ammo_box/magazine/internal/rocketlauncher
name = "rocket launcher internal magazine"
ammo_type = /obj/item/ammo_casing/caseless/rocket
- caliber = "84mm"
+ caliber = CALIBER_84HE
max_ammo = 1
/obj/item/ammo_box/magazine/internal/cannonball
name = "cannonball"
ammo_type = /obj/item/ammo_casing/caseless/cannonball
- caliber = "100mm"
+ caliber = CALIBER_CANNON
max_ammo = 1
/obj/item/ammo_box/magazine/internal/rods
diff --git a/code/modules/projectiles/boxes_magazines/internal/misc.dm b/code/modules/projectiles/boxes_magazines/internal/misc.dm
index bb0a404f7307..cabb04982e09 100644
--- a/code/modules/projectiles/boxes_magazines/internal/misc.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/misc.dm
@@ -1,11 +1,11 @@
/obj/item/ammo_box/magazine/internal/minigun
name = "gatling gun fusion core"
ammo_type = /obj/item/ammo_casing/caseless/laser/gatling
- caliber = "gatling"
+ caliber = CALIBER_GATLING
max_ammo = 5000
/obj/item/ammo_box/magazine/internal/minigunosprey
name = "Minigun back stash box"
ammo_type = /obj/item/ammo_casing/a546
- caliber = "a556"
- max_ammo = 500
\ No newline at end of file
+ caliber = CALIBER_556NATO
+ max_ammo = 500
diff --git a/code/modules/projectiles/boxes_magazines/internal/revolver.dm b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
index 56624d03b8eb..f9169ee9948a 100644
--- a/code/modules/projectiles/boxes_magazines/internal/revolver.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
@@ -1,31 +1,31 @@
/obj/item/ammo_box/magazine/internal/cylinder/rev38
name = "detective revolver cylinder"
ammo_type = /obj/item/ammo_casing/c38/rubber
- caliber = "38"
+ caliber = CALIBER_38
max_ammo = 6
/obj/item/ammo_box/magazine/internal/cylinder/tra32
name = "\improper Caldwell revolver cylinder"
ammo_type = /obj/item/ammo_casing/tra32
- caliber = "32trac"
+ caliber = CALIBER_32ACP
max_ammo = 7
/obj/item/ammo_box/magazine/internal/cylinder/rev44
name = "\improper Mateba revolver cylinder"
ammo_type = /obj/item/ammo_casing/m44
- caliber = "44"
+ caliber = CALIBER_44MAG
max_ammo = 6
/obj/item/ammo_box/magazine/internal/cylinder/rev762
name = "\improper Nagant revolver cylinder"
ammo_type = /obj/item/ammo_casing/n762
- caliber = "n762"
+ caliber = CALIBER_762X38R
max_ammo = 7
/obj/item/ammo_box/magazine/internal/cylinder/rus357
name = "\improper Russian revolver cylinder"
ammo_type = /obj/item/ammo_casing/a357
- caliber = "357"
+ caliber = CALIBER_357MAG
max_ammo = 6
multiload = 0
diff --git a/code/modules/projectiles/boxes_magazines/internal/rifle.dm b/code/modules/projectiles/boxes_magazines/internal/rifle.dm
index a7469284a5c7..d5b557b0c80c 100644
--- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm
@@ -2,7 +2,7 @@
name = "bolt action rifle internal magazine"
desc = "Oh god, this shouldn't be here"
ammo_type = /obj/item/ammo_casing/a762
- caliber = "a762"
+ caliber = CALIBER_762X54R
max_ammo = 5
multiload = 1
diff --git a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm
index 6837764791b1..1f691dd297b0 100644
--- a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm
@@ -1,7 +1,7 @@
/obj/item/ammo_box/magazine/internal/shot
name = "shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/beanbag
- caliber = "shotgun"
+ caliber = CALIBER_12GA
max_ammo = 4
multiload = 0
@@ -41,10 +41,10 @@
name = "breaching shotgun internal magazine"
ammo_type = /obj/item/ammo_casing/shotgun/breacher
max_ammo = 3
- caliber = "breaching"
+ caliber = CALIBER_BREACH
/obj/item/ammo_box/magazine/internal/shot/lever
name = "lever-action rifle internal magazine"
ammo_type = /obj/item/ammo_casing/m308
max_ammo = 8
- caliber = "m308"
+ caliber = CALIBER_308
diff --git a/code/modules/projectiles/boxes_magazines/internal/toy.dm b/code/modules/projectiles/boxes_magazines/internal/toy.dm
index 10653bab7062..717acb8c4e5d 100644
--- a/code/modules/projectiles/boxes_magazines/internal/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/toy.dm
@@ -1,6 +1,6 @@
/obj/item/ammo_box/magazine/internal/shot/toy
ammo_type = /obj/item/ammo_casing/reusable/foam_dart
- caliber = "foam_force"
+ caliber = CALIBER_FOAM
max_ammo = 4
/obj/item/ammo_box/magazine/internal/shot/toy/crossbow
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 056261a9c876..08ed70608b5e 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -120,7 +120,7 @@
pin = null
if(A == chambered)
chambered = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
if(A == bayonet)
clear_bayonet()
if(A == gun_light)
@@ -493,7 +493,7 @@
return
to_chat(user, span_notice("You attach [K] to [src]'s bayonet lug."))
bayonet = K
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
return ..()
@@ -582,7 +582,7 @@
old_gun_light.forceMove(get_turf(src))
remove_item_action(gunlight_toggle)
gunlight_toggle = null
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/gun/ui_action_click(mob/user, actiontype)
if(istype(actiontype, gunlight_toggle))
@@ -595,17 +595,16 @@
return
var/mob/living/carbon/human/user = usr
- gun_light.on = !gun_light.on
- gun_light.update_brightness()
- to_chat(user, span_notice("You toggle the gunlight [gun_light.on ? "on":"off"]."))
+ gun_light.toggle_light(user)
+ to_chat(user, span_notice("You toggle the gunlight [gun_light.light_on ? "on":"off"]."))
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/item/gun/update_overlays()
. = ..()
if(gun_light)
- var/mutable_appearance/flashlight_overlay = mutable_appearance('icons/obj/guns/flashlights.dmi', "[gunlight_state][gun_light.on? "_on":""]")
+ var/mutable_appearance/flashlight_overlay = mutable_appearance('icons/obj/guns/flashlights.dmi', "[gunlight_state][gun_light.light_on? "_on":""]")
flashlight_overlay.pixel_x = flight_x_offset
flashlight_overlay.pixel_y = flight_y_offset
. += flashlight_overlay
diff --git a/code/modules/projectiles/guns/ballistic/minigun.dm b/code/modules/projectiles/guns/ballistic/minigun.dm
index 6c8172a7b0bd..4f4ead7da04a 100644
--- a/code/modules/projectiles/guns/ballistic/minigun.dm
+++ b/code/modules/projectiles/guns/ballistic/minigun.dm
@@ -2,7 +2,7 @@
/obj/item/minigunbackpack
name = "The back stash"
- desc = "The massive back stash can hold alot of ammo on your back."
+ desc = "The massive back stash can hold a lot of ammo on your back."
icon = 'yogstation/icons/obj/guns/minigunosprey.dmi'
icon_state = "holstered"
item_state = "backpack"
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index 95072efd2006..11ecef86e44a 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -97,7 +97,7 @@
/obj/item/gun/ballistic/revolver/detective/screwdriver_act(mob/living/user, obj/item/I)
if(..())
return TRUE
- if(magazine.caliber == "38")
+ if(magazine.caliber == CALIBER_38)
to_chat(user, span_notice("You begin to reinforce the barrel of [src]..."))
if(magazine.ammo_count())
afterattack(user, user) //you know the drill
@@ -107,7 +107,7 @@
if(magazine.ammo_count())
to_chat(user, span_warning("You can't modify it!"))
return TRUE
- magazine.caliber = "357"
+ magazine.caliber = CALIBER_357MAG
fire_delay = 8 //What no you don't get to mag dump plus the bullet isn't meant for this cylinder. Plus, if you perfectly slam fire with the .38 and hit all your shots, you (should) do more lethal damage than using .357 at this fire_delay
fire_sound = 'sound/weapons/revolver357tgmc.ogg' // See attributions.txt
desc = "The barrel and chamber assembly seems to have been modified."
@@ -122,7 +122,7 @@
if(magazine.ammo_count())
to_chat(user, span_warning("You can't modify it!"))
return
- magazine.caliber = "38"
+ magazine.caliber = CALIBER_38
fire_delay = 0 //Blessed mag dump
spread = 0
fire_sound = 'sound/weapons/revolver38tgmc.ogg' // See attributions.txt
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 8d7ee1a6f189..dcd2aac14568 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -239,6 +239,15 @@
if(ismineralturf(target_turf))
var/turf/closed/mineral/M = target_turf
M.attempt_drill(firer, 0, power)
+ //yogs begin
+ if(istype(target_turf,/turf/open/floor/plating/dirt/jungleland))
+ var/turf/open/floor/plating/dirt/jungleland/JG = target_turf
+ JG.spawn_rock()
+
+ if(istype(target,/obj/structure/flora))
+ qdel(target)
+
+ //yogs end
var/obj/effect/temp_visual/kinetic_blast/K = new /obj/effect/temp_visual/kinetic_blast(target_turf)
K.color = color
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index f5b6ba2dbc39..467831b21e3f 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -1,3 +1,5 @@
+#define PLASMA_BASE_RECHARGE 500
+
/obj/item/gun/energy/ionrifle
name = "ion rifle"
desc = "Invented in 2506 to quell attacks from SELF aligned IPCs, the NT-I1 is a bulky rifle designed to disable mechanical and electronic threats at range."
@@ -129,24 +131,40 @@
var/progress_flash_divisor = 10 //copypasta is best pasta
var/light_intensity = 1
var/charge_weld = 25 //amount of charge used up to start action (multiplied by amount) and per progress_flash_divisor ticks of welding
-
-/obj/item/gun/energy/plasmacutter/mini
- name = "mini plasma cutter"
- desc = "A weak plasma based mining tool."
- icon_state = "plasmacutter_mini"
- item_state = "plasmacutter_mini"
- ammo_type = list(/obj/item/ammo_casing/energy/plasma/weak)
- toolspeed = 2
+ /// Contains the instances of installed upgrades
+ var/list/installed_upgrades = list()
+ /// Mod capacity of this item
+ var/mod_capacity = 80
+
+/obj/item/gun/energy/plasmacutter/proc/modify_projectile(obj/projectile/plasma/K)
+ K.gun = src //do something special on-hit, easy!
+ for(var/obj/item/upgrade/plasmacutter/A in installed_upgrades)
+ A.modify_projectile(K)
+
+/obj/item/gun/energy/plasmacutter/proc/get_remaining_mod_capacity()
+ . = mod_capacity
+ for(var/obj/item/upgrade/plasmacutter/a in installed_upgrades)
+ . -= a.cost
+ return .
/obj/item/gun/energy/plasmacutter/Initialize(mapload)
AddElement(/datum/element/update_icon_blocker)
. = ..()
AddComponent(/datum/component/butchering, 25, 105, 0, 'sound/weapons/plasma_cutter.ogg')
+/obj/item/gun/energy/plasmacutter/Destroy()
+ . = ..()
+ for(var/obj/item/upgrade/plasmacutter/a in installed_upgrades)
+ qdel(a)
+ QDEL_NULL(installed_upgrades)
+
/obj/item/gun/energy/plasmacutter/examine(mob/user)
. = ..()
if(cell)
. += span_notice("[src] is [round(cell.percent())]% charged.")
+ . += span_boldnotice("[get_remaining_mod_capacity()]% mod capacity remaining.")
+ for(var/obj/item/upgrade/plasmacutter/a in installed_upgrades)
+ . += span_notice("There is \a [a] installed, using [span_bold("[a.cost]%")] capacity.")
/obj/item/gun/energy/plasmacutter/attackby(obj/item/I, mob/user)
var/charge_multiplier = 0 //2 = Refined stack, 1 = Ore
@@ -154,16 +172,20 @@
charge_multiplier = 2
if(istype(I, /obj/item/stack/ore/plasma))
charge_multiplier = 1
- if(charge_multiplier)
- if(cell.charge == cell.maxcharge)
- to_chat(user, span_notice("You try to insert [I] into [src], but it's fully charged.")) //my cell is round and full
- return
- I.use(1)
- cell.give(500*charge_multiplier)
- to_chat(user, span_notice("You insert [I] in [src], recharging it."))
- else
- ..()
+ if(!charge_multiplier)
+ return ..()
+
+ var/obj/item/stack/S = I
+ var/charge_amount = PLASMA_BASE_RECHARGE * charge_multiplier // Get the amount of charge per item
+ // Get remaining capacity and get the ideal amount to recharge or all of the stack whichever is smaller
+ var/amount_to_eat = ceil(min(((cell.maxcharge - cell.charge) / charge_amount), S.get_amount()))
+ if(amount_to_eat == 0)
+ to_chat(user, span_notice("You try to insert [I] into [src], but it's fully charged.")) //my cell is round and full
+ return
+ I.use(amount_to_eat)
+ cell.give(charge_amount * amount_to_eat)
+ to_chat(user, span_notice("You insert [I] in [src], recharging it."))
// Tool procs, in case plasma cutter is used as welder
// Can we start welding?
/obj/item/gun/energy/plasmacutter/tool_start_check(mob/living/user, amount)
@@ -209,12 +231,54 @@
else
. = ..(amount=1)
+/obj/item/gun/energy/plasmacutter/attackby(obj/item/I, mob/user)
+ . = ..()
+ if(istype(I, /obj/item/upgrade/plasmacutter))
+ var/obj/item/upgrade/plasmacutter/PC = I
+ if(get_remaining_mod_capacity() < PC.cost)
+ to_chat(user, span_warning("There is no more room for this upgrade."))
+ return
+ if(!PC.stackable && is_type_in_list(PC, installed_upgrades))
+ to_chat(user, span_notice("[I] has already been installed in [src]"))
+ return
+ to_chat(user, span_notice("You install [I] into [src]"))
+ playsound(loc, 'sound/items/screwdriver.ogg', 100, 1)
+ installed_upgrades += I
+ PC.install(src)
+ I.forceMove(src)
+
+/obj/item/gun/energy/plasmacutter/crowbar_act(mob/living/user, obj/item/I)
+ . = TRUE
+ if(installed_upgrades.len)
+ to_chat(user, span_notice("You pry the modifications out."))
+ I.play_tool_sound(src, 100)
+ for(var/obj/item/upgrade/plasmacutter/M in installed_upgrades)
+ M.forceMove(drop_location()) // Uninstallation handled in Exited().
+ else
+ to_chat(user, span_notice("There are no modifications currently installed."))
+
+/obj/item/gun/energy/plasmacutter/Exited(atom/movable/gone, direction)
+ ..()
+ if(gone in installed_upgrades)
+ var/obj/item/upgrade/plasmacutter/MK = gone
+ MK.uninstall(src)
+
+/obj/item/gun/energy/plasmacutter/mini
+ name = "mini plasma cutter"
+ desc = "A weak plasma based mining tool."
+ icon_state = "plasmacutter_mini"
+ item_state = "plasmacutter_mini"
+ ammo_type = list(/obj/item/ammo_casing/energy/plasma/weak)
+ toolspeed = 2
+ mod_capacity = 50
+
/obj/item/gun/energy/plasmacutter/adv
name = "advanced plasma cutter"
icon_state = "adv_plasmacutter"
item_state = "adv_plasmacutter"
force = 15
ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv)
+ mod_capacity = 100
/obj/item/gun/energy/plasmacutter/adv/mega
name = "mega plasma cutter"
@@ -222,6 +286,7 @@
item_state = "plasmacutter_mega"
desc = "A mining tool capable of expelling concentrated plasma bursts. You could use it to cut limbs off xenos! Or, you know, mine stuff. This one has been enhanced with plasma magmite."
ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv/mega)
+ mod_capacity = 120
/obj/item/gun/energy/plasmacutter/scatter
name = "plasma cutter shotgun"
@@ -230,15 +295,7 @@
desc = "An industrial-grade, heavy-duty mining shotgun."
force = 10
ammo_type = list(/obj/item/ammo_casing/energy/plasma/scatter)
-
-
-
-/obj/item/gun/energy/plasmacutter/attackby(obj/item/I, mob/user)
- . = ..()
- if(try_upgrade(I))
- to_chat(user, span_notice("You install [I] into [src]"))
- playsound(loc, 'sound/items/screwdriver.ogg', 100, 1)
- qdel(I)
+ mod_capacity = 100
/obj/item/gun/energy/plasmacutter/scatter/mega
name = "mega plasma cutter shotgun"
@@ -246,6 +303,7 @@
item_state = "miningshotgun_mega"
desc = "An industrial-grade, heavy-duty mining shotgun. This one seems... mega!"
ammo_type = list(/obj/item/ammo_casing/energy/plasma/scatter/adv/mega)
+ mod_capacity = 120
/obj/item/gun/energy/plasmacutter/adv/cyborg
name = "cyborg advanced plasma cutter"
@@ -253,6 +311,7 @@
force = 15
selfcharge = 1
ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv/cyborg)
+ mod_capacity = 70
/obj/item/gun/energy/plasmacutter/adv/malf // Can't be subtype of cyborg or it will interfere with upgrades
name = "cyborg malfunctioning plasma cutter"
@@ -261,34 +320,71 @@
force = 15
selfcharge = 1
ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv/cyborg/malf)
+ mod_capacity = 100
// Upgrades for plasma cutters
/obj/item/upgrade/plasmacutter
name = "generic upgrade kit"
- desc = "An upgrade for plasma shotguns."
+ desc = "An upgrade for plasma cutters."
icon = 'icons/obj/objects.dmi'
icon_state = "modkit"
w_class = WEIGHT_CLASS_SMALL
+ var/cost = 10
+ var/stackable = FALSE
+
+/obj/item/upgrade/plasmacutter/proc/modify_projectile(obj/projectile/plasma/K)
+
+/obj/item/upgrade/plasmacutter/proc/install(obj/item/gun/energy/plasmacutter/P)
+
+/obj/item/upgrade/plasmacutter/proc/uninstall(obj/item/gun/energy/plasmacutter/P)
+
/obj/item/upgrade/plasmacutter/defuser
name = "plasma cutter defusal kit"
- desc = "An upgrade for plasma shotguns that allows it to automatically defuse gibtonite."
+ desc = "An upgrade for plasma cutters that allows it to automatically defuse gibtonite."
-/obj/item/gun/energy/plasmacutter/proc/try_upgrade(obj/item/I)
- return // no upgrades for the plasmacutter
+/obj/item/upgrade/plasmacutter/defuser/modify_projectile(obj/projectile/plasma/K)
+ K.defuse = TRUE
-/obj/item/gun/energy/plasmacutter/scatter/try_upgrade(obj/item/I)
- if(.)
- return
- if(istype(I, /obj/item/upgrade/plasmacutter/defuser))
- var/kaboom = new/obj/item/ammo_casing/energy/plasma/scatter/adv
- ammo_type = list(kaboom)
- return TRUE
- return FALSE
+/obj/item/upgrade/plasmacutter/capacity
+ name = "plasma cutter capacity kit"
+ desc = "An upgrade for plasma cutters that doubles the tank capacity."
+ cost = 20
-//no upgrading this one either (for now)
-/obj/item/gun/energy/plasmacutter/scatter/mega/try_upgrade(obj/item/I)
- return
+/obj/item/upgrade/plasmacutter/capacity/install(obj/item/gun/energy/plasmacutter/P)
+ P.cell.maxcharge = initial(P.cell.maxcharge)*2
+
+/obj/item/upgrade/plasmacutter/capacity/uninstall(obj/item/gun/energy/plasmacutter/P)
+ P.cell.maxcharge = initial(P.cell.maxcharge)
+ P.cell.charge = min(P.cell.charge, P.cell.maxcharge)
+
+/obj/item/upgrade/plasmacutter/cooldown
+ name = "plasma cutter cooldown kit"
+ desc = "An upgrade for plasma cutters that reduces the cooldown."
+ cost = 40
+ stackable = TRUE
+
+/obj/item/upgrade/plasmacutter/cooldown/install(obj/item/gun/energy/plasmacutter/P)
+ P.fire_delay *= 0.5
+
+/obj/item/upgrade/plasmacutter/cooldown/uninstall(obj/item/gun/energy/plasmacutter/P)
+ P.fire_delay *= 2
+
+/obj/item/upgrade/plasmacutter/range
+ name = "plasma cutter range kit"
+ desc = "An upgrade for plasma cutters that increases the range."
+ cost = 30
+
+/obj/item/upgrade/plasmacutter/range/modify_projectile(obj/projectile/plasma/K)
+ K.range += 4
+
+/obj/item/upgrade/plasmacutter/ore
+ name = "plasma cutter ore kit"
+ desc = "An upgrade for plasma cutters that doubles ore output."
+ cost = 30
+
+/obj/item/upgrade/plasmacutter/ore/modify_projectile(obj/projectile/plasma/K)
+ K.explosive = TRUE
/obj/item/gun/energy/wormhole_projector
name = "bluespace wormhole projector"
@@ -497,3 +593,5 @@
icon_state = "prifle"
item_state = "prifle"
ammo_type = list(/obj/item/ammo_casing/energy/grimdark)
+
+#undef PLASMA_BASE_RECHARGE
diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm
index ba882a0e8446..ce75f0c0a59b 100644
--- a/code/modules/projectiles/guns/magic.dm
+++ b/code/modules/projectiles/guns/magic.dm
@@ -59,7 +59,7 @@
/obj/item/gun/magic/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread)
if(no_den_usage)
var/area/A = get_area(user)
- if(istype(A, /area/wizard_station))
+ if(istype(A, /area/centcom/wizard_station))
add_fingerprint(user)
to_chat(user, span_warning("You know better than to violate the security of The Den, best wait until you leave to use [src]."))
return
diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm
index dc6f9b7d0287..c12749aa6f88 100644
--- a/code/modules/projectiles/guns/magic/wand.dm
+++ b/code/modules/projectiles/guns/magic/wand.dm
@@ -37,7 +37,7 @@
if(target == user)
if(no_den_usage)
var/area/A = get_area(user)
- if(istype(A, /area/wizard_station))
+ if(istype(A, /area/centcom/wizard_station))
to_chat(user, span_warning("You know better than to violate the security of The Den, best wait until you leave to use [src]."))
return
else
diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm
index d6dadf6ccf50..b5ac01f28adf 100644
--- a/code/modules/projectiles/guns/misc/beam_rifle.dm
+++ b/code/modules/projectiles/guns/misc/beam_rifle.dm
@@ -26,7 +26,7 @@
modifystate = FALSE
weapon_weight = WEAPON_HEAVY
w_class = WEIGHT_CLASS_BULKY
- ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan)
+ ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan/piercing, /obj/item/ammo_casing/energy/beam_rifle/hitscan/impact)
actions_types = list(/datum/action/item_action/zoom_lock_action)
cell_type = /obj/item/stock_parts/cell/beam_rifle
canMouseDown = TRUE
@@ -44,20 +44,6 @@
var/aiming_lastangle = 0
var/mob/current_user = null
- var/structure_piercing = 2 //Amount * 2. For some reason structures aren't respecting this unless you have it doubled. Probably with the objects in question's Bump() code instead of this but I'll deal with this later.
- var/structure_bleed_coeff = 0.7
- var/wall_pierce_amount = 0
- var/wall_devastate = 0
- var/aoe_structure_range = 1
- var/aoe_structure_damage = 50
- var/aoe_fire_range = 2
- var/aoe_fire_chance = 40
- var/aoe_mob_range = 1
- var/aoe_mob_damage = 30
- var/impact_structure_damage = 60
- var/projectile_damage = 30
- var/projectile_stun = 0
- var/projectile_setting_pierce = TRUE
var/delay = 25
var/lastfire = 0
@@ -156,9 +142,8 @@
else
. += drained_overlay
-/obj/item/gun/energy/beam_rifle/attack_self(mob/user)
- projectile_setting_pierce = !projectile_setting_pierce
- to_chat(user, span_boldnotice("You set \the [src] to [projectile_setting_pierce? "pierce":"impact"] mode."))
+/obj/item/gun/energy/beam_rifle/select_fire(mob/living/user)
+ . = ..()
aiming_beam()
/obj/item/gun/energy/beam_rifle/proc/update_slowdown()
@@ -179,13 +164,6 @@
listeningTo = null
return ..()
-/obj/item/gun/energy/beam_rifle/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- chambered = null
- recharge_newshot()
-
/obj/item/gun/energy/beam_rifle/proc/aiming_beam(force_update = FALSE)
var/diff = abs(aiming_lastangle - lastangle)
check_user()
@@ -193,10 +171,7 @@
return
aiming_lastangle = lastangle
var/obj/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new
- P.gun = src
- P.wall_pierce_amount = wall_pierce_amount
- P.structure_pierce_amount = structure_piercing
- P.do_pierce = projectile_setting_pierce
+ P.fired_from = src
if(aiming_time)
var/percent = ((100/aiming_time)*aiming_time_left)
P.color = rgb(255 * percent,255 * ((100 - percent) / 100),0)
@@ -299,7 +274,6 @@
return
process_aim()
if(aiming_time_left <= aiming_time_fire_threshold && check_user())
- sync_ammo()
var/atom/target = M.client.mouse_object_ref?.resolve()
if(target)
afterattack(target, M, FALSE, M.client.mouseParams, passthrough = TRUE)
@@ -323,73 +297,15 @@
. = ..()
stop_aiming()
-/obj/item/gun/energy/beam_rifle/proc/sync_ammo()
- for(var/obj/item/ammo_casing/energy/beam_rifle/AC in contents)
- AC.sync_stats()
-
/obj/item/gun/energy/beam_rifle/proc/delay_penalty(amount)
aiming_time_left = clamp(aiming_time_left + amount, 0, aiming_time)
/obj/item/ammo_casing/energy/beam_rifle
name = "particle acceleration lens"
desc = "Don't look into barrel!"
- var/wall_pierce_amount = 0
- var/wall_devastate = 0
- var/aoe_structure_range = 1
- var/aoe_structure_damage = 30
- var/aoe_fire_range = 2
- var/aoe_fire_chance = 66
- var/aoe_mob_range = 1
- var/aoe_mob_damage = 20
- var/impact_structure_damage = 50
- var/projectile_damage = 40
- var/projectile_stun = 0
- var/structure_piercing = 2
- var/structure_bleed_coeff = 0.7
- var/do_pierce = TRUE
- var/obj/item/gun/energy/beam_rifle/host
-
-/obj/item/ammo_casing/energy/beam_rifle/proc/sync_stats()
- var/obj/item/gun/energy/beam_rifle/BR = loc
- if(!istype(BR))
- stack_trace("Beam rifle syncing error")
- host = BR
- do_pierce = BR.projectile_setting_pierce
- wall_pierce_amount = BR.wall_pierce_amount
- wall_devastate = BR.wall_devastate
- aoe_structure_range = BR.aoe_structure_range
- aoe_structure_damage = BR.aoe_structure_damage
- aoe_fire_range = BR.aoe_fire_range
- aoe_fire_chance = BR.aoe_fire_chance
- aoe_mob_range = BR.aoe_mob_range
- aoe_mob_damage = BR.aoe_mob_damage
- impact_structure_damage = BR.impact_structure_damage
- projectile_damage = BR.projectile_damage
- projectile_stun = BR.projectile_stun
- delay = BR.delay
- structure_piercing = BR.structure_piercing
- structure_bleed_coeff = BR.structure_bleed_coeff
-
-/obj/item/ammo_casing/energy/beam_rifle/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
- . = ..()
- var/obj/projectile/beam/beam_rifle/hitscan/HS_BB = BB
- if(!istype(HS_BB))
- return
- HS_BB.impact_direct_damage = projectile_damage
- HS_BB.stun = projectile_stun
- HS_BB.impact_structure_damage = impact_structure_damage
- HS_BB.aoe_mob_damage = aoe_mob_damage
- HS_BB.aoe_mob_range = clamp(aoe_mob_range, 0, 15) //Badmin safety lock
- HS_BB.aoe_fire_chance = aoe_fire_chance
- HS_BB.aoe_fire_range = aoe_fire_range
- HS_BB.aoe_structure_damage = aoe_structure_damage
- HS_BB.aoe_structure_range = clamp(aoe_structure_range, 0, 15) //Badmin safety lock
- HS_BB.wall_devastate = wall_devastate
- HS_BB.wall_pierce_amount = wall_pierce_amount
- HS_BB.structure_pierce_amount = structure_piercing
- HS_BB.structure_bleed_coeff = structure_bleed_coeff
- HS_BB.do_pierce = do_pierce
- HS_BB.gun = host
+ select_name = "beam"
+ e_cost = 10000
+ fire_sound = 'sound/weapons/beam_sniper.ogg'
/obj/item/ammo_casing/energy/beam_rifle/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread)
var/turf/curloc = get_turf(user)
@@ -412,143 +328,95 @@
/obj/item/ammo_casing/energy/beam_rifle/hitscan
projectile_type = /obj/projectile/beam/beam_rifle/hitscan
- select_name = "beam"
- e_cost = 10000
- fire_sound = 'sound/weapons/beam_sniper.ogg'
+
+/obj/item/ammo_casing/energy/beam_rifle/hitscan/impact
+ projectile_type = /obj/projectile/beam/beam_rifle/hitscan/impact
+ select_name = "impact"
+
+/obj/item/ammo_casing/energy/beam_rifle/hitscan/piercing
+ projectile_type = /obj/projectile/beam/beam_rifle/hitscan/piercing
+ select_name = "pierce"
/obj/projectile/beam/beam_rifle
name = "particle beam"
icon = null
hitsound = 'sound/effects/explosion3.ogg'
- damage = 0 //Handled manually.
+ damage = 0
damage_type = BURN
armor_flag = ENERGY
range = 150
jitter = 10
demolition_mod = 4
- var/obj/item/gun/gun
- var/structure_pierce_amount = 0 //All set to 0 so the gun can manually set them during firing.
- var/structure_bleed_coeff = 0
- var/structure_pierce = 0
- var/do_pierce = TRUE
- var/wall_pierce_amount = 0
- var/wall_pierce = 0
- var/wall_devastate = 0
- var/aoe_structure_range = 0
- var/aoe_structure_damage = 0
- var/aoe_fire_range = 0
+ can_ricoshot = ALWAYS_RICOSHOT
+ var/aoe_range = 0
var/aoe_fire_chance = 0
- var/aoe_mob_range = 0
- var/aoe_mob_damage = 0
- var/impact_structure_damage = 0
- var/impact_direct_damage = 0
- var/turf/cached
- var/list/pierced = list()
-
-/obj/projectile/beam/beam_rifle/proc/AOE(turf/epicenter)
+ var/tracer_fire_chance = 0
+ var/fire_color = "green"
+
+/obj/projectile/beam/beam_rifle/hitscan
+ icon_state = ""
+ hitscan = TRUE
+ tracer_type = /obj/effect/projectile/tracer/tracer/beam_rifle
+ var/constant_tracer = FALSE
+
+/obj/projectile/beam/beam_rifle/hitscan/piercing
+ damage = 60 // same as the impact version, but applied all at once
+ aoe_range = 0 // no AOE, has piercing instead
+ penetrations = 2
+ tracer_fire_chance = 50
+ penetration_flags = PENETRATE_OBJECTS | PENETRATE_MOBS
+
+/obj/projectile/beam/beam_rifle/hitscan/impact
+ damage = 30 // total of 60 on direct hit
+ aoe_range = 2
+ aoe_fire_chance = 50
+ tracer_fire_chance = 20
+
+/obj/projectile/beam/beam_rifle/Move(atom/newloc, dir)
+ . = ..()
+ if(prob(tracer_fire_chance))
+ var/turf/new_turf = newloc
+ new_turf.IgniteTurf(rand(16, 22), fire_color) // FIRE IN THE HOLE!!!!
+
+/obj/projectile/beam/beam_rifle/proc/do_area_damage(turf/epicenter)
set waitfor = FALSE
if(!epicenter)
return
new /obj/effect/temp_visual/explosion/fast(epicenter)
- for(var/mob/living/L in range(aoe_mob_range, epicenter)) //handle aoe mob damage
- L.adjustFireLoss(aoe_mob_damage)
- to_chat(L, span_userdanger("\The [src] sears you!"))
- for(var/turf/T in range(aoe_fire_range, epicenter)) //handle aoe fire
+ for(var/turf/T in spiral_range_turfs(aoe_range, epicenter))
+ var/modified_damage = damage / max(get_dist(epicenter, T), 1) // damage decreases with range
if(prob(aoe_fire_chance))
- new /obj/effect/hotspot(T)
- for(var/obj/O in range(aoe_structure_range, epicenter))
- if(!isitem(O))
- if(O.level == 1) //Please don't break underfloor items!
- continue
- O.take_damage(aoe_structure_damage * get_damage_coeff(O), BURN, LASER, FALSE)
-
-/obj/projectile/beam/beam_rifle/proc/check_pierce(atom/target)
- if(!do_pierce)
- return FALSE
- if(pierced[target]) //we already pierced them go away
- return TRUE
- if(isclosedturf(target))
- if(wall_pierce++ < wall_pierce_amount)
- if(prob(wall_devastate))
- if(iswallturf(target))
- var/turf/closed/wall/W = target
- W.dismantle_wall(TRUE, TRUE)
- else
- SSexplosions.medturf += target
- return TRUE
- if(ismovable(target))
- var/atom/movable/AM = target
- if(AM.density && !AM.CanPass(src, get_turf(target)) && !ismob(AM))
- if(structure_pierce < structure_pierce_amount)
- if(isobj(AM))
- var/obj/O = AM
- O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(AM), BURN, ENERGY, FALSE)
- pierced[AM] = TRUE
- structure_pierce++
- return TRUE
- return FALSE
-
-/obj/projectile/beam/beam_rifle/proc/get_damage_coeff(atom/target)
- if(istype(target, /obj/machinery/door))
- return 0.4
- if(istype(target, /obj/structure/window))
- return 0.5
- return 1
-
-/obj/projectile/beam/beam_rifle/proc/handle_impact(atom/target)
- if(isobj(target))
- var/obj/O = target
- O.take_damage(impact_structure_damage * get_damage_coeff(target), BURN, LASER, FALSE)
- if(isliving(target))
- var/mob/living/L = target
- L.adjustFireLoss(impact_direct_damage)
- L.emote("scream")
-
-/obj/projectile/beam/beam_rifle/proc/handle_hit(atom/target)
- set waitfor = FALSE
- if(!cached && !QDELETED(target))
- cached = get_turf(target)
- if(nodamage)
- return FALSE
- playsound(cached, 'sound/effects/explosion3.ogg', 100, 1)
- AOE(cached)
- if(!QDELETED(target))
- handle_impact(target)
-
-/obj/projectile/beam/beam_rifle/Bump(atom/target)
- if(check_pierce(target))
- impacted += target
- trajectory_ignore_forcemove = TRUE
- forceMove(target.loc)
- trajectory_ignore_forcemove = FALSE
- return FALSE
- if(!QDELETED(target))
- cached = get_turf(target)
- return ..()
+ T.IgniteTurf(rand(16, 22), fire_color)
+ for(var/mob/living/L in T) //handle aoe mob damage
+ L.apply_damage(modified_damage, BURN, null, L.getarmor(null, BOMB))
+ to_chat(L, span_userdanger("\The [src] sears you!"))
+ for(var/obj/O in T)
+ O.take_damage(modified_damage, BURN, BOMB, FALSE)
/obj/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE)
- if(!QDELETED(target))
- cached = get_turf(target)
- handle_hit(target)
- return ..()
-
-/obj/projectile/beam/beam_rifle/hitscan
- icon_state = ""
- hitscan = TRUE
- tracer_type = /obj/effect/projectile/tracer/tracer/beam_rifle
- var/constant_tracer = FALSE
+ . = ..()
+ var/turf/target_turf = (isclosedturf(target) && penetrations <= 0) ? get_turf(src) : get_turf(target)
+ playsound(target_turf, 'sound/effects/explosion3.ogg', 100, 1)
+ if(isclosedturf(target)) // if hitting a wall
+ SSexplosions.lowturf += target
+ target_turf.IgniteTurf(rand(16, 22), fire_color)
+ if(aoe_range)
+ do_area_damage(target_turf)
/obj/projectile/beam/beam_rifle/hitscan/generate_hitscan_tracers(cleanup = TRUE, duration = 5, impacting = TRUE, highlander)
set waitfor = FALSE
if(isnull(highlander))
highlander = constant_tracer
- if(highlander && istype(gun))
+
+ if(highlander && istype(fired_from, /obj/item/gun))
+ var/obj/item/gun/gun = fired_from
QDEL_LIST(gun.current_tracers)
for(var/datum/point/p in beam_segments)
gun.current_tracers += generate_tracer_between_points(p, beam_segments[p], tracer_type, color, 0, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity)
else
for(var/datum/point/p in beam_segments)
generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity)
+
if(cleanup)
QDEL_LIST(beam_segments)
beam_segments = null
@@ -561,6 +429,7 @@
hitsound_wall = null
nodamage = TRUE
damage = 0
+ aoe_range = 0
constant_tracer = TRUE
hitscan_light_range = 0
hitscan_light_intensity = 0
diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm
index 1ee011d0feae..bd1312dc6eb4 100644
--- a/code/modules/projectiles/guns/misc/blastcannon.dm
+++ b/code/modules/projectiles/guns/misc/blastcannon.dm
@@ -115,7 +115,7 @@
icon_state = "blastwave"
damage = 0
nodamage = FALSE
- movement_type = FLYING | UNSTOPPABLE
+ movement_type = FLYING | PHASING
var/heavyr = 0
var/mediumr = 0
var/lightr = 0
diff --git a/code/modules/projectiles/guns/misc/medbeam.dm b/code/modules/projectiles/guns/misc/medbeam.dm
index 7e219d3a2841..fb88edb6ae95 100644
--- a/code/modules/projectiles/guns/misc/medbeam.dm
+++ b/code/modules/projectiles/guns/misc/medbeam.dm
@@ -33,6 +33,9 @@
..()
LoseTarget()
+/**
+ * Proc that always is called when we want to end the beam and makes sure things are cleaned up, see beam_died()
+ */
/obj/item/gun/medbeam/proc/LoseTarget()
if(active)
qdel(current_beam)
@@ -41,6 +44,19 @@
on_beam_release(current_target)
current_target = null
+/**
+ * Proc that is only called when the beam fails due to something, so not when manually ended.
+ * manual disconnection = LoseTarget, so it can silently end
+ * automatic disconnection = beam_died, so we can give a warning message first
+ */
+/obj/item/gun/medbeam/proc/beam_died()
+ SIGNAL_HANDLER
+ current_beam = null
+ active = FALSE //skip qdelling the beam again if we're doing this proc, because
+ if(isliving(loc))
+ to_chat(loc, span_warning("You lose control of the beam!"))
+ LoseTarget()
+
/obj/item/gun/medbeam/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
if(isliving(user))
add_fingerprint(user)
@@ -52,8 +68,8 @@
current_target = target
active = TRUE
- current_beam = new(user,current_target,time=6000,beam_icon_state="medbeam",btype=/obj/effect/ebeam/medical)
- INVOKE_ASYNC(current_beam, TYPE_PROC_REF(/datum/beam, Start))
+ current_beam = user.Beam(current_target, icon_state="medbeam", time = 10 MINUTES, maxdistance = max_range, beam_type = /obj/effect/ebeam/medical)
+ RegisterSignal(current_beam, COMSIG_QDELETING, PROC_REF(beam_died))//this is a WAY better rangecheck than what was done before (process check)
SSblackbox.record_feedback("tally", "gun_fired", 1, type)
@@ -102,7 +118,7 @@
return 0
for(var/obj/effect/ebeam/medical/B in turf)// Don't cross the str-beams!
if(B.owner.origin != current_beam.origin)
- explosion(B.loc,0,3,5,8)
+ explosion(B.loc, heavy_impact_range = 3, light_impact_range = 5, flash_range = 8)
qdel(dummy)
return 0
qdel(dummy)
@@ -113,11 +129,14 @@
/obj/item/gun/medbeam/proc/on_beam_tick(mob/living/target)
if(target.health != target.maxHealth)
- new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF")
- target.adjustBruteLoss(-4)
- target.adjustFireLoss(-4)
- target.adjustToxLoss(-1)
- target.adjustOxyLoss(-1)
+ new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN)
+ var/need_mob_update
+ need_mob_update = target.adjustBruteLoss(-4, updating_health = FALSE, forced = TRUE)
+ need_mob_update += target.adjustFireLoss(-4, updating_health = FALSE, forced = TRUE)
+ need_mob_update += target.adjustToxLoss(-1, updating_health = FALSE, forced = TRUE)
+ need_mob_update += target.adjustOxyLoss(-1, updating_health = FALSE, forced = TRUE)
+ if(need_mob_update)
+ target.updatehealth()
return
/obj/item/gun/medbeam/proc/on_beam_release(mob/living/target)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index f8c23aa21917..97428e8e26e9 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -1,7 +1,4 @@
-#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan.
-#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers.
-
/obj/projectile
name = "projectile"
icon = 'icons/obj/projectiles.dmi'
@@ -13,6 +10,9 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
movement_type = FLYING
wound_bonus = CANT_WOUND // can't wound by default
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
+ layer = MOB_LAYER
+ //The sound this plays on impact.
var/hitsound = 'sound/weapons/pierce.ogg'
var/hitsound_wall = ""
@@ -38,7 +38,14 @@
var/datum/point/vector/trajectory
var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location!
- var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel
+ /// If objects are below this layer, we pass through them
+ var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER
+
+ /// During each fire of SSprojectiles, the number of deciseconds since the last fire of SSprojectiles
+ /// is divided by this var, and the result truncated to the next lowest integer is
+ /// the number of times the projectile's `pixel_move` proc will be called.
+ var/speed = 0.8
+
var/Angle = 0
var/original_angle = 0 //Angle at firing
var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle
@@ -49,10 +56,12 @@
var/ricochet_chance = 30
var/force_hit = FALSE //If the object being hit can pass ths damage on to something else, it should not do it for this bullet.
- //Atom penetration, set to mobs by default
- var/penetrating = FALSE
- var/penetrations = INFINITY
- var/penetration_type = 0 //Set to 1 if you only want to have it penetrate objects. Set to 2 if you want it to penetrate objects and mobs.
+ ///Whether this projectile can ricochet off of coins
+ var/can_ricoshot = FALSE
+ ///How many things can this penetrate?
+ var/penetrations = 0
+ ///Flags used to specify what this projectile can penetrate. Default is mobs only.
+ var/penetration_flags = PENETRATE_MOBS
//Hitscan
var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored.
@@ -209,7 +218,7 @@
W.add_dent(WALL_DENT_SHOT, hitx, hity)
- if(penetrating && (penetration_type == 1 || penetration_type == 2) && penetrations > 0)
+ if((penetration_flags & PENETRATE_OBJECTS) && penetrations > 0)
penetrations -= 1
return BULLET_ACT_FORCE_PIERCE
@@ -219,7 +228,7 @@
if(impact_effect_type && !hitscan)
new impact_effect_type(target_loca, hitx, hity)
- if(penetrating && (penetration_type == 1 || penetration_type == 2) && penetrations > 0)
+ if((penetration_flags & PENETRATE_OBJECTS) && penetrations > 0)
penetrations -= 1
return BULLET_ACT_FORCE_PIERCE
@@ -235,15 +244,21 @@
splatter_dir = get_dir(starting, target_loca)
if(isalien(L) || ispolysmorph(L))
new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
- else if (iscarbon(L) && !(NOBLOOD in C.dna.species.species_traits))
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
+ else if(iscarbon(L) && !(NOBLOOD in C.dna.species.species_traits))
+ var/splatter_color
+ var/mob/living/carbon/carbon_bleeder = L
+ splatter_color = carbon_bleeder.dna.blood_type.color
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, splatter_color)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter/genericsplatter(target_loca, splatter_dir)
var/obj/item/bodypart/B = L.get_bodypart(def_zone)
if(B?.status == BODYPART_ROBOTIC) // So if you hit a robotic, it sparks instead of bloodspatters
do_sparks(2, FALSE, target.loc)
- if(prob(25))
- new /obj/effect/decal/cleanable/oil(target_loca)
+ var/splatter_color = null
+ if(iscarbon(L))
+ var/mob/living/carbon/carbon_target = L
+ splatter_color = carbon_target.dna.blood_type.color
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, splatter_color)
if(prob(33))
L.add_splatter_floor(target_loca)
else if(impact_effect_type && !hitscan)
@@ -336,13 +351,13 @@
return process_hit(T, select_target(T, A))
-#define QDEL_SELF 1 //Delete if we're not UNSTOPPABLE flagged non-temporarily
+#define QDEL_SELF 1 //Delete if we're not PHASING flagged non-temporarily
#define DO_NOT_QDEL 2 //Pass through.
#define FORCE_QDEL 3 //Force deletion.
/obj/projectile/proc/process_hit(turf/T, atom/target, qdel_self, hit_something = FALSE) //probably needs to be reworked entirely when pixel movement is done.
if(QDELETED(src) || !T || !target) //We're done, nothing's left.
- if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE)))
+ if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, PHASING)))
qdel(src)
return hit_something
impacted |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do.
@@ -350,9 +365,9 @@
return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work.
var/result = target.bullet_act(src, def_zone)
if(result == BULLET_ACT_FORCE_PIERCE)
- if(!CHECK_BITFIELD(movement_type, UNSTOPPABLE))
+ if(!CHECK_BITFIELD(movement_type, PHASING))
temporary_unstoppable_movement = TRUE
- ENABLE_BITFIELD(movement_type, UNSTOPPABLE)
+ ENABLE_BITFIELD(movement_type, PHASING)
return process_hit(T, select_target(T), qdel_self, TRUE) //Hit whatever else we can since we're piercing through but we're still on the same tile.
else if(result == BULLET_ACT_PENETRATE) // This is slightly different from ACT_TURF in that it goes through the first thing
return process_hit(T, select_target(T), qdel_self, TRUE)
@@ -361,7 +376,7 @@
else //Whether it hit or blocked, we're done!
qdel_self = QDEL_SELF
hit_something = TRUE
- if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, UNSTOPPABLE)))
+ if((qdel_self == FORCE_QDEL) || ((qdel_self == QDEL_SELF) && !temporary_unstoppable_movement && !CHECK_BITFIELD(movement_type, PHASING)))
qdel(src)
return hit_something
@@ -639,7 +654,7 @@
if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc.
return TRUE
if(!isliving(target))
- if(target.layer < PROJECTILE_HIT_THRESHHOLD_LAYER)
+ if(target.layer < hit_threshhold)
return FALSE
else
var/mob/living/L = target
@@ -719,7 +734,7 @@
if(.)
if(temporary_unstoppable_movement)
temporary_unstoppable_movement = FALSE
- DISABLE_BITFIELD(movement_type, UNSTOPPABLE)
+ DISABLE_BITFIELD(movement_type, PHASING)
if(fired && can_hit_target(original, impacted, TRUE))
Bump(original)
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index b704192b8729..1b8cdf5095fe 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -22,6 +22,7 @@
wound_bonus = -30
wound_falloff_tile = -2.5
bare_wound_bonus = 15
+ can_ricoshot = TRUE
/obj/projectile/bullet/c38/rubber
name = ".38 rubber bullet"
@@ -86,6 +87,7 @@
armour_penetration = 15
wound_bonus = -45
wound_falloff_tile = -2.5
+ can_ricoshot = TRUE
/obj/projectile/bullet/pellet/a357_ironfeather
name = ".357 Ironfeather pellet"
@@ -119,8 +121,7 @@
name = ".357 Heartpiercer bullet"
damage = 35
armour_penetration = 45
- penetrating = TRUE //Goes through a single mob before ending on the next target
- penetrations = 1
+ penetrations = 1 //Goes through a single mob before ending on the next target
/obj/projectile/bullet/a357/wallstake
name = ".357 Wallstake bullet"
diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm
index c476a18556f8..e3f67d9874d5 100644
--- a/code/modules/projectiles/projectile/bullets/rifle.dm
+++ b/code/modules/projectiles/projectile/bullets/rifle.dm
@@ -34,7 +34,7 @@
name = ".308 penetrator bullet"
damage = 35
armour_penetration = 35
- penetrating = TRUE
+ penetrations = INFINITY
// 7.62 (Nagant Rifle + K-41s DMR)
@@ -61,9 +61,8 @@
name = "7.62mm anti-material bullet"
damage = 52
armour_penetration = 40
- penetrating = TRUE //Passes through two objects, stops on a mob or on a third object
- penetrations = 2
- penetration_type = 1
+ penetrations = 2 //Passes through two objects, stops on a mob or on a third object
+ penetration_flags = PENETRATE_OBJECTS
demolition_mod = 1.5 // anti-armor
/obj/projectile/bullet/a762/vulcan
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index fbec1b20f8bc..0a7a9b853574 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -76,7 +76,7 @@
armour_penetration = 60 // he he funny round go through armor
wound_bonus = -40
demolition_mod = 3 // very good at smashing through stuff
- penetrating = TRUE //Goes through an infinite number of mobs
+ penetrations = INFINITY //Goes through an infinite number of mobs
/obj/projectile/bullet/shotgun/slug/Range()
..()
diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm
index 262bec69a77f..8e1175894b26 100644
--- a/code/modules/projectiles/projectile/bullets/sniper.dm
+++ b/code/modules/projectiles/projectile/bullets/sniper.dm
@@ -26,8 +26,8 @@
name = ".50 penetrator bullet"
icon_state = "gauss"
damage = 60
- penetrating = TRUE //Passes through everything and anything until it reaches the end of its range
- penetration_type = 2
+ penetrations = INFINITY //Passes through everything and anything until it reaches the end of its range
+ penetration_flags = PENETRATE_OBJECTS | PENETRATE_MOBS
dismemberment = 0 //It goes through you cleanly.
paralyze = 0
diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm
index 2a56779fa541..2268f1ce180a 100644
--- a/code/modules/projectiles/projectile/bullets/special.dm
+++ b/code/modules/projectiles/projectile/bullets/special.dm
@@ -3,7 +3,7 @@
/obj/projectile/bullet/honker
name = "banana"
damage = 0
- movement_type = FLYING | UNSTOPPABLE
+ movement_type = FLYING | PHASING
nodamage = TRUE
hitsound = 'sound/items/bikehorn.ogg'
icon = 'icons/obj/hydroponics/harvest.dmi'
diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm
index 806adc1d8e2f..26f6fbe90b05 100644
--- a/code/modules/projectiles/projectile/energy/_energy.dm
+++ b/code/modules/projectiles/projectile/energy/_energy.dm
@@ -5,3 +5,9 @@
damage_type = BURN
armor_flag = ENERGY
reflectable = REFLECT_NORMAL
+
+/obj/projectile/energy/Initialize(mapload)
+ . = ..()
+
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT, INNATE_TRAIT)
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 0f4a9cd6d130..9a6620ced03f 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -653,7 +653,7 @@
return FALSE
return ..()
-/obj/projectile/magic/aoe/Moved(atom/OldLoc, Dir)
+/obj/projectile/magic/aoe/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(trail)
create_trail()
@@ -684,11 +684,11 @@
armor_flag = MAGIC
/// The power of the zap itself when it electrocutes someone
- var/tesla_power = 20000
+ var/zap_power = 2e4
/// The range of the zap itself when it electrocutes someone
- var/tesla_range = 15
+ var/zap_range = 15
/// The flags of the zap itself when it electrocutes someone
- var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_MOB_STUN | TESLA_OBJ_DAMAGE
+ var/zap_flags = TESLA_MOB_DAMAGE | TESLA_MOB_STUN | TESLA_OBJ_DAMAGE
/// A reference to the chain beam between the caster and the projectile
var/datum/beam/chain
@@ -699,8 +699,7 @@
/obj/projectile/magic/aoe/lightning/on_hit(target)
. = ..()
- tesla_zap(src, tesla_range, tesla_power, tesla_flags)
- qdel(src)
+ tesla_zap(source = src, zap_range = zap_range, power = zap_power, tesla_flags = zap_flags)
/obj/projectile/magic/aoe/lightning/Destroy()
QDEL_NULL(chain)
@@ -715,9 +714,9 @@
speed = 0.3
armor_flag = MAGIC
- tesla_power = 9000
- tesla_range = 7
- tesla_flags = TESLA_MOB_STUN | TESLA_OBJ_DAMAGE
+ zap_power = 9000
+ zap_range = 7
+ zap_flags = TESLA_MOB_STUN | TESLA_OBJ_DAMAGE
/obj/projectile/magic/fireball
name = "bolt of fireball"
diff --git a/code/modules/projectiles/projectile/reusable/arrow.dm b/code/modules/projectiles/projectile/reusable/arrow.dm
index 6b615926d848..8d118e57b92c 100644
--- a/code/modules/projectiles/projectile/reusable/arrow.dm
+++ b/code/modules/projectiles/projectile/reusable/arrow.dm
@@ -20,7 +20,7 @@
return
var/mob/living/L = target
- if(ismegafauna(L) || istype(L, /mob/living/simple_animal/hostile/asteroid))
+ if(ismegafauna(L) || istype(L, /mob/living/simple_animal/hostile/asteroid) || istype(L, /mob/living/simple_animal/hostile/yog_jungle) || istype(L, /mob/living/simple_animal/hostile/tar))
L.apply_damage(fauna_damage_bonus)
if(!istype(ammo_type, /obj/item/ammo_casing/reusable/arrow))
diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm
index 705c7085282a..e296440797f2 100644
--- a/code/modules/projectiles/projectile/special/curse.dm
+++ b/code/modules/projectiles/projectile/special/curse.dm
@@ -5,6 +5,7 @@
/obj/projectile/curse_hand
name = "curse hand"
icon_state = "cursehand0"
+ base_icon_state = "cursehand"
hitsound = 'sound/effects/curse4.ogg'
layer = LARGE_MOB_LAYER
damage_type = BURN
@@ -17,39 +18,58 @@
/obj/projectile/curse_hand/Initialize(mapload)
. = ..()
- ENABLE_BITFIELD(movement_type, UNSTOPPABLE)
+ ENABLE_BITFIELD(movement_type, PHASING)
+ ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT)
handedness = prob(50)
- icon_state = "cursehand[handedness]"
+ icon_state = "[base_icon_state][handedness]"
+
+/obj/projectile/curse_hand/Destroy()
+ QDEL_NULL(arm)
+ return ..()
+
+/obj/projectile/curse_hand/update_icon_state()
+ icon_state = "[base_icon_state]0[handedness]"
+ return ..()
/obj/projectile/curse_hand/fire(setAngle)
+ if(QDELETED(src)) //I'm going to try returning nothing because if it's being deleted, surely we don't want anything to happen?
+ return
if(starting)
- arm = starting.Beam(src, icon_state = "curse[handedness]", time = INFINITY, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm)
+ arm = starting.Beam(src, icon_state = "curse[handedness]", beam_type=/obj/effect/ebeam/curse_arm)
..()
/obj/projectile/curse_hand/prehit(atom/target)
if(target == original)
- DISABLE_BITFIELD(movement_type, UNSTOPPABLE)
+ DISABLE_BITFIELD(movement_type, PHASING)
else if(!isturf(target))
return FALSE
return ..()
-/obj/projectile/curse_hand/Destroy()
+/obj/projectile/curse_hand/proc/finale()
if(arm)
- arm.End()
- arm = null
- if(CHECK_BITFIELD(movement_type, UNSTOPPABLE))
- playsound(src, 'sound/effects/curse3.ogg', 25, 1, -1)
+ QDEL_NULL(arm)
+ if(CHECK_BITFIELD(movement_type, PHASING))
+ playsound(src, 'sound/effects/curse3.ogg', 25, TRUE, -1)
var/turf/T = get_step(src, dir)
- new/obj/effect/temp_visual/dir_setting/curse/hand(T, dir, handedness)
+ var/obj/effect/temp_visual/dir_setting/curse/hand/leftover = new(T, dir)
+ leftover.icon_state = icon_state
for(var/obj/effect/temp_visual/dir_setting/curse/grasp_portal/G in starting)
qdel(G)
+ if(!T) //T can be in nullspace when src is set to QDEL
+ return
new /obj/effect/temp_visual/dir_setting/curse/grasp_portal/fading(starting, dir)
- var/datum/beam/D = starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm, beam_sleep_time = 1)
- for(var/b in D.elements)
- var/obj/effect/ebeam/B = b
- animate(B, alpha = 0, time = 3.2 SECONDS)
+ var/datum/beam/D = starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm)
+ animate(D.visuals, alpha = 0, time = 3.2 SECONDS)
+
+/obj/projectile/curse_hand/on_range()
+ finale()
return ..()
+/obj/projectile/curse_hand/on_hit(atom/target, blocked, pierce_hit)
+ . = ..()
+ if (. == BULLET_ACT_HIT)
+ finale()
+
/obj/projectile/curse_hand/progenitor
name = "psionic barrage"
damage_type = BRAIN
diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm
index e6a31bc9e621..b2de093296c7 100644
--- a/code/modules/projectiles/projectile/special/hallucination.dm
+++ b/code/modules/projectiles/projectile/special/hallucination.dm
@@ -152,7 +152,7 @@
/obj/projectile/hallucination/laser/hal_apply_effect()
hal_target.adjustStaminaLoss(20)
- hal_target.blur_eyes(2)
+ hal_target.adjust_eye_blur(2)
/obj/projectile/hallucination/taser
name = "electrode"
diff --git a/code/modules/projectiles/projectile/special/plasma.dm b/code/modules/projectiles/projectile/special/plasma.dm
index 63f46d4e7785..85f746a4e30b 100644
--- a/code/modules/projectiles/projectile/special/plasma.dm
+++ b/code/modules/projectiles/projectile/special/plasma.dm
@@ -16,6 +16,10 @@
light_color = LIGHT_COLOR_PURPLE
light_range = 2
+ var/obj/item/gun/energy/plasmacutter/gun
+ var/defuse = FALSE
+ var/explosive = FALSE
+
/obj/projectile/plasma/weak
name = "weak plasma blast"
icon_state = "plasmacutter_weak"
@@ -25,22 +29,36 @@
light_color = LIGHT_COLOR_RED
mine_range = 0
+//yogs begin
+/obj/projectile/plasma/Move(atom/newloc, dir)
+ . = ..()
+ if(istype(newloc,/turf/open/floor/plating/dirt/jungleland))
+ var/turf/open/floor/plating/dirt/jungleland/JG = newloc
+ JG.spawn_rock()
+
+//yogs end
/obj/projectile/plasma/on_hit(atom/target)
. = ..()
+ if(defuse && istype(target, /turf/closed/mineral/gibtonite))
+ var/turf/closed/mineral/gibtonite/gib = target
+ gib.defuse()
if(ismineralturf(target))
var/turf/closed/mineral/M = target
- M.attempt_drill(firer)
+ M.attempt_drill(firer, explosive)
if(mine_range)
mine_range--
range++
if(range > 0)
return BULLET_ACT_FORCE_PIERCE
-
-/obj/projectile/plasma/scatter/adv/on_hit(atom/target)
- if(istype(target, /turf/closed/mineral/gibtonite))
- var/turf/closed/mineral/gibtonite/gib = target
- gib.defuse()
- . = ..()
+// yogs begin
+ if(istype(target,/obj/structure/flora))
+ qdel(target)
+ if(mine_range)
+ mine_range--
+ range++
+ if(range > 0)
+ return BULLET_ACT_FORCE_PIERCE
+// yogs end
/obj/projectile/plasma/adv
damage = 7
@@ -62,6 +80,7 @@
// Same as the scatter but with automatic defusing
/obj/projectile/plasma/scatter/adv
+ defuse = TRUE
// Megafauna loot, possibly best cutter?
/obj/projectile/plasma/scatter/adv/stalwart
diff --git a/code/modules/projectiles/projectile/special/reagent.dm b/code/modules/projectiles/projectile/special/reagent.dm
index 524180486c7b..e19024470544 100644
--- a/code/modules/projectiles/projectile/special/reagent.dm
+++ b/code/modules/projectiles/projectile/special/reagent.dm
@@ -47,6 +47,25 @@
last_volume = R.volume
name = "\proper [lowertext(R.name)]"
+// PRESSURE WASHER
+/obj/projectile/reagent/pressure_washer
+ name = "\proper high-pressure water"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "extinguish"
+ range = 7
+ reagents_list = list(/datum/reagent/water = 10)
+ speed = 0.6 // very high power
+
+/obj/projectile/reagent/pressure_washer/on_hit(atom/movable/target, blocked)
+ . = ..()
+ if(istype(target) && !target.anchored && prob(20))
+ target.throw_at(get_step(target, dir), 1, 1, firer)
+
+/obj/projectile/reagent/pressure_washer/Move(atom/newloc, dir)
+ . = ..()
+ if(newloc)
+ newloc.wash(CLEAN_SCRUB) // wash everything in its path
+
/// Xeno neurotoxin
/obj/projectile/reagent/neurotoxin
name = "neurotoxin spit"
diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm
index 3d220e4842a3..0a39cfd58b33 100644
--- a/code/modules/projectiles/projectile/special/rocket.dm
+++ b/code/modules/projectiles/projectile/special/rocket.dm
@@ -10,7 +10,7 @@
/obj/projectile/bullet/a84mm
name ="\improper HEDP rocket"
- desc = "USE A WEEL GUN"
+ desc = "USE A WEEL GUN."
icon_state= "84mm-hedp"
armor_flag = BOMB
damage = 80
@@ -73,7 +73,7 @@
/obj/projectile/bullet/cball
name = "cannonball"
icon_state = "cannonball"
- desc = "Not for bowling purposes"
+ desc = "Not for bowling purposes."
damage = 30
demolition_mod = 20 // YARRR
@@ -91,5 +91,5 @@
/obj/projectile/bullet/bolt
name = "bolt"
icon_state = "bolt"
- desc = "smaller and faster rod"
+ desc = "A smaller and faster rod."
damage = 25
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index f31b14ed7da7..ec3bea63fddd 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -97,7 +97,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
color = "#221915" // rgb: 34, 25, 21
taste_description = "malt and chocolate"
glass_name = "glass of stout"
- glass_desc = "a cold pint of 'genius' brand stout."
+ glass_desc = "A cold pint of 'genius' brand stout."
/datum/reagent/consumable/ethanol/beer/stout/irishflip
name = "Irish Flip"
@@ -106,7 +106,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "chocolate cream and egg"
glass_icon_state = "irish_flip"
glass_name = "glass of irish flip"
- glass_desc = "a fancy glass of creamy cocktail."
+ glass_desc = "A fancy glass of creamy cocktail."
/datum/reagent/consumable/ethanol/beer/stout/blackvelvet
name = "Black Velvet"
@@ -115,7 +115,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "Champagne with a hint of chocolate."
glass_icon_state = "black_velvet"
glass_name = "glass of black velvet"
- glass_desc = "a fancy drink with a melancholic past."
+ glass_desc = "A fancy drink with a melancholic past."
/datum/reagent/consumable/ethanol/beer/stout/espressomartini
name = "Espresso Martini"
@@ -124,7 +124,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "bitterness, chocolate, and cream."
glass_icon_state = "espresso_martini"
glass_name = "glass of espresso martini"
- glass_desc = "a cocktail guaranteed to keep you awake."
+ glass_desc = "A cocktail guaranteed to keep you awake."
///////////////////////////////////////////
/datum/reagent/consumable/ethanol/beer/light
name = "Light Beer"
@@ -751,6 +751,10 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "Moonshine"
glass_desc = "You've really hit rock bottom now... your liver packed its bags and left last night."
+/datum/reagent/consumable/ethanol/moonshine/on_mob_life(mob/living/carbon/M)
+ M.adjust_eye_blur(1.5)
+ return ..()
+
/datum/reagent/consumable/ethanol/b52
name = "B-52"
description = "Coffee, Irish Cream, and cognac. You will get bombed."
@@ -2277,7 +2281,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "dried plums and malt"
glass_icon_state = "trappistglass"
glass_name = "Trappist Beer"
- glass_desc = "boozy Catholicism in a glass."
+ glass_desc = "Boozy Catholicism in a glass."
/datum/reagent/consumable/ethanol/trappist/on_mob_life(mob/living/carbon/M)
if(M.mind.holy_role)
@@ -2383,7 +2387,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
/datum/reagent/consumable/ethanol/inocybeshine/on_mob_life(mob/living/carbon/M)
if(prob(10))
M.adjustStaminaLoss(10,0)
- M.blur_eyes(3)
+ M.adjust_eye_blur(3)
M.adjust_disgust(1)
. = TRUE
return ..()
@@ -2436,7 +2440,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "tequila, creme de menthe, and a hint of medicine?"
glass_icon_state = "flaming_moe2"
glass_name = "Flaming Moe"
- glass_desc = "an amazing concoction of various different bar drinks and a secret ingredient"
+ glass_desc = "An amazing concoction of various different bar drinks and a secret ingredient"
/datum/reagent/consumable/ethanol/flaming_moe/on_mob_life(mob/living/carbon/M)
M.adjust_drowsiness(-5 SECONDS)
@@ -2462,7 +2466,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_desc = "A freezing pint of malt liquor."
/datum/reagent/consumable/ethanol/ratvarnac
- name = "Justicars Juice"
+ name = "Justiciar's Juice"
description = "I don't even know what an eminence is, but I want him to recall."
metabolization_rate = INFINITY
boozepwr = 30
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index 2f8cd7d85297..3669ab2062c8 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -59,7 +59,7 @@
glass_desc = "It's just like a carrot but without crunching."
/datum/reagent/consumable/carrotjuice/on_mob_life(mob/living/carbon/M)
- M.adjust_blurriness(-1)
+ M.adjust_eye_blur(-1)
M.adjust_blindness(-1)
switch(current_cycle)
if(1 to 20)
@@ -1116,4 +1116,4 @@
taste_description = "citrus soda with cucumber"
glass_icon_state = "cucumber_lemonade"
glass_name = "cucumber lemonade"
- glass_desc = "Lemonade, with added cucumber."
\ No newline at end of file
+ glass_desc = "Lemonade, with added cucumber."
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index b7af79856259..063ca42a963a 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -317,7 +317,7 @@
else if ( mouth_covered ) // Reduced effects if partially protected
if(prob(50))
victim.emote("scream")
- victim.blur_eyes(14)
+ victim.adjust_eye_blur(14)
victim.blind_eyes(10)
victim.set_confusion_if_lower(10 SECONDS)
victim.damageoverlaytemp = 75
@@ -327,7 +327,7 @@
else if ( eyes_covered ) // Eye cover is better than mouth cover
if(prob(20))
victim.emote("cough")
- victim.blur_eyes(4)
+ victim.adjust_eye_blur(4)
victim.set_confusion_if_lower(5 SECONDS)
victim.damageoverlaytemp = 50
M.adjustStaminaLoss(3)
@@ -335,7 +335,7 @@
else // Oh dear :D
if(prob(60))
victim.emote("scream")
- victim.blur_eyes(14)
+ victim.adjust_eye_blur(14)
victim.blind_eyes(10)
victim.set_confusion_if_lower(12 SECONDS)
victim.damageoverlaytemp = 100
@@ -637,13 +637,13 @@
if(!M.eye_blurry)
to_chat(M, "Tears well up in your eyes!")
M.blind_eyes(2)
- M.blur_eyes(5)
+ M.adjust_eye_blur(5)
return ..()
/datum/reagent/consumable/tearjuice/on_mob_life(mob/living/carbon/M)
..()
if(M.eye_blurry) //Don't worsen vision if it was otherwise fine
- M.blur_eyes(4)
+ M.adjust_eye_blur(4)
if(prob(10))
to_chat(M, "Your eyes sting!")
M.blind_eyes(2)
@@ -679,7 +679,7 @@
M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM, 150)
M.adjustToxLoss(3*REM,0)
M.adjustStaminaLoss(10*REM,0)
- M.blur_eyes(5)
+ M.adjust_eye_blur(5)
. = TRUE
..()
@@ -704,10 +704,10 @@
var/obj/effect/dummy/lighting_obj/moblight/mob_light_obj = living_holder.mob_light(2)
mob_light_obj.set_light_color("#b5a213")
LAZYSET(mobs_affected, living_holder, mob_light_obj)
- RegisterSignal(living_holder, COMSIG_PARENT_QDELETING, PROC_REF(on_living_holder_deletion))
+ RegisterSignal(living_holder, COMSIG_QDELETING, PROC_REF(on_living_holder_deletion))
/datum/reagent/consumable/tinlux/proc/remove_reagent_light(mob/living/living_holder)
- UnregisterSignal(living_holder, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(living_holder, COMSIG_QDELETING)
var/obj/effect/dummy/lighting_obj/moblight/mob_light_obj = LAZYACCESS(mobs_affected, living_holder)
LAZYREMOVE(mobs_affected, living_holder)
if(mob_light_obj)
@@ -761,9 +761,10 @@
if(reac_volume < 3)
return
- var/obj/effect/decal/cleanable/whiteblood/ethereal/B = locate() in T //find some blood here
+ var/obj/effect/decal/cleanable/blood/B = locate() in T
if(!B)
- B = new(T)
+ B = new /obj/effect/decal/cleanable/blood/splatter(T)
+ B.Etherealify()
/datum/reagent/consumable/liquidelectricity/on_mob_life(mob/living/carbon/M)
if(HAS_TRAIT(M, TRAIT_POWERHUNGRY))
@@ -941,3 +942,7 @@
nutriment_factor = 15 * REAGENTS_METABOLISM
color = "#D9A066" // rgb: 217, 160, 102
taste_description = "peanuts"
+
+/// Gets just how much nutrition this reagent is worth for the passed mob
+/datum/reagent/consumable/proc/get_nutriment_factor(mob/living/carbon/eater)
+ return nutriment_factor * REAGENTS_METABOLISM * 2
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 24d73f8a1047..cb7497ab4512 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -46,7 +46,7 @@
M.adjustToxLoss(-5, 0, TRUE)
M.remove_status_effect(/datum/status_effect/hallucination)
REMOVE_TRAITS_NOT_IN(M, list(SPECIES_TRAIT, ROUNDSTART_TRAIT, ORGAN_TRAIT))
- M.set_blurriness(0)
+ M.set_eye_blur(0)
M.set_blindness(0)
M.SetKnockdown(0, FALSE)
M.SetStun(0, FALSE)
@@ -556,7 +556,7 @@
. = 1
/datum/reagent/medicine/sal_acid
- name = "Salicyclic Acid"
+ name = "Salicylic Acid"
description = "Stimulates the healing of severe bruises. Extremely rapidly heals severe bruising and slowly heals minor ones. Overdose will worsen existing bruising."
reagent_state = LIQUID
color = "#D2D2D2"
@@ -821,15 +821,15 @@
to_chat(M, span_warning("Your vision slowly returns..."))
M.cure_blind(EYE_DAMAGE)
M.cure_nearsighted(EYE_DAMAGE)
- M.blur_eyes(35)
+ M.adjust_eye_blur(35)
else if(HAS_TRAIT_FROM(M, TRAIT_NEARSIGHT, EYE_DAMAGE))
to_chat(M, span_warning("The blackness in your peripheral vision fades."))
M.cure_nearsighted(EYE_DAMAGE)
- M.blur_eyes(10)
+ M.adjust_eye_blur(10)
else if(M.eye_blind || M.eye_blurry)
M.set_blindness(0)
- M.set_blurriness(0)
+ M.set_eye_blur(0)
..()
/datum/reagent/medicine/atropine
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 86225ee4f94d..b18ce9843a65 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1,7 +1,7 @@
/datum/reagent/blood
data = list("donor"=null,"viruses"=null,"blood_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null,"quirks"=null)
name = "Blood"
- color = "#C80000" // rgb: 200, 0, 0
+ color = COLOR_BLOOD
metabolization_rate = 5 //fast rate so it disappears fast.
taste_description = "iron"
taste_mult = 1.3
@@ -30,17 +30,22 @@
L.ForceContractDisease(D)
if(iscarbon(L))
- var/mob/living/carbon/C = L
- if(C.get_blood_id() == /datum/reagent/blood && ((methods & INJECT) || ((methods & INGEST) && C.dna && C.dna.species && (DRINKSBLOOD in C.dna.species.species_traits))))
- if(!data || !(data["blood_type"] in get_safe_blood(C.dna.blood_type)) && !IS_BLOODSUCKER(C))
- C.reagents.add_reagent(/datum/reagent/toxin, reac_volume * 0.5)
- else
- C.blood_volume = min(C.blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM(C))
+ var/mob/living/carbon/exposed_carbon = L
+ if(exposed_carbon.get_blood_id() == /datum/reagent/blood && (methods == INJECT || (methods == INGEST && exposed_carbon.dna && exposed_carbon.dna.species && (DRINKSBLOOD in exposed_carbon.dna.species.species_traits))))
+ if(data && data["blood_type"])
+ var/datum/blood_type/blood_type = data["blood_type"]
+ if(blood_type.type in exposed_carbon.dna.blood_type.compatible_types)
+ exposed_carbon.blood_volume = min(exposed_carbon.blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM(L))
+ return
+ exposed_carbon.reagents.add_reagent(/datum/reagent/toxin, reac_volume * 0.5)
/datum/reagent/blood/on_new(list/data)
if(istype(data))
SetViruses(src, data)
+ var/datum/blood_type/blood_type = data["blood_type"]
+ if(blood_type)
+ color = blood_type.color
/datum/reagent/blood/on_merge(list/mix_data)
if(data && mix_data)
@@ -1765,7 +1770,7 @@
/datum/reagent/carpet/reaction_turf(turf/T, reac_volume)
if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet, flags = CHANGETURF_INHERIT_AIR)
+ F.place_on_top(/turf/open/floor/carpet, flags = CHANGETURF_INHERIT_AIR)
..()
/datum/reagent/bromine
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index b4d7d89552f9..95ab7b1c64cf 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -299,7 +299,7 @@
/datum/reagent/toxin/spore/on_mob_life(mob/living/carbon/C)
C.damageoverlaytemp = 60
C.update_damage_hud()
- C.blur_eyes(3)
+ C.adjust_eye_blur(3)
return ..()
/datum/reagent/toxin/spore_burning
@@ -471,7 +471,7 @@
switch(pick(1, 2, 3, 4))
if(1)
to_chat(M, span_danger("You can barely see!"))
- M.blur_eyes(3)
+ M.adjust_eye_blur(3)
if(2)
M.emote("cough")
if(3)
@@ -1013,3 +1013,26 @@
color = "#67423A" // rgb: 127, 132, 0
toxpwr = 0.1
taste_description = "mushrooms"
+
+/datum/reagent/toxin/ambusher_toxin
+ name = "Carpenter Toxin"
+ description = "A toxin from an unknown source that attacks the legs' muscles, slowing the victim. Its effects can, however, be nullified by Epinephrine"
+ color = "#2d4816"
+ toxpwr = 0
+ metabolization_rate = 5 * REAGENTS_METABOLISM
+ var/textShown = FALSE //So bubble alert doesn't show repeatedly
+
+/datum/reagent/toxin/ambusher_toxin/on_mob_life(mob/living/L)
+ ..()
+ if(holder.has_reagent(/datum/reagent/medicine/epinephrine))
+ L.remove_movespeed_modifier(type) //Remove slowdown from toxin if there is any
+ textShown = FALSE
+ else
+ L.add_movespeed_modifier(type, update=TRUE, priority=100, multiplicative_slowdown=1.5) //Slow them down
+ if(textShown == FALSE)
+ L.balloon_alert(L, "Your legs feel weak!")
+ textShown = TRUE
+
+/datum/reagent/toxin/ambusher_toxin/on_mob_end_metabolize(mob/living/L)
+ L.remove_movespeed_modifier(type)
+ textShown = FALSE
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index 573d57e3872d..2d9189a017c7 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -100,7 +100,7 @@
required_reagents = list(/datum/reagent/fuel = 1, /datum/reagent/chlorine = 1, /datum/reagent/ammonia = 1, /datum/reagent/toxin/formaldehyde = 1, /datum/reagent/sodium = 1, /datum/reagent/toxin/cyanide = 1)
/datum/chemical_reaction/sal_acid
- name = "Salicyclic Acid"
+ name = "Salicylic Acid"
id = /datum/reagent/medicine/sal_acid
results = list(/datum/reagent/medicine/sal_acid = 5)
required_reagents = list(/datum/reagent/sodium = 1, /datum/reagent/phenol = 1, /datum/reagent/carbon = 1, /datum/reagent/oxygen = 1, /datum/reagent/toxin/acid = 1)
diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm
index 88f71245383a..38f41a967a5d 100644
--- a/code/modules/reagents/reagent_containers/blood_pack.dm
+++ b/code/modules/reagents/reagent_containers/blood_pack.dm
@@ -4,7 +4,7 @@
icon = 'icons/obj/bloodpack.dmi'
icon_state = "bloodpack"
volume = 200
- var/blood_type = null
+ var/datum/blood_type/blood_type = null
var/unique_blood = null
var/labelled = 0
@@ -65,6 +65,8 @@
/obj/item/reagent_containers/blood/Initialize(mapload)
. = ..()
if(blood_type != null)
+ if(!istype(blood_type, /datum/blood_type) && get_blood_type(blood_type))
+ blood_type = get_blood_type(blood_type)
reagents.add_reagent(unique_blood ? unique_blood : /datum/reagent/blood, 200, list("donor"=null,"viruses"=null,"blood_DNA"=null,"blood_type"=blood_type,"resistances"=null,"trace_chem"=null))
update_appearance(UPDATE_ICON)
@@ -74,7 +76,7 @@
if(B && B.data && B.data["blood_type"])
blood_type = B.data["blood_type"]
else if(reagents.has_reagent(/datum/reagent/consumable/liquidelectricity))
- blood_type = "LE"
+ blood_type = "E"
else
blood_type = null
update_pack_name()
@@ -83,7 +85,7 @@
/obj/item/reagent_containers/blood/proc/update_pack_name()
if(!labelled)
if(blood_type)
- name = "blood pack - [blood_type]"
+ name = "blood pack[blood_type ? " - [unique_blood ? blood_type : blood_type.name]" : null]"
else
name = "blood pack"
@@ -127,12 +129,15 @@
blood_type = "L"
/obj/item/reagent_containers/blood/ethereal
- blood_type = "LE"
+ blood_type = "E"
unique_blood = /datum/reagent/consumable/liquidelectricity
/obj/item/reagent_containers/blood/universal
blood_type = "U"
+/obj/item/reagent_containers/blood/gorilla
+ blood_type = "G"
+
/obj/item/reagent_containers/blood/attackby(obj/item/I, mob/user, params)
if (istype(I, /obj/item/pen) || istype(I, /obj/item/toy/crayon))
if(!user.is_literate())
diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm
index 7eefcc645a5a..0d51466c1e7d 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -209,6 +209,11 @@
desc = "A small bottle. Contains histamine."
list_reagents = list(/datum/reagent/toxin/histamine = 30)
+/obj/item/reagent_containers/glass/bottle/ambusher_toxin
+ name = "carpenter toxin bottle"
+ desc = "A small bottle. Contains a toxin from an unknown source."
+ list_reagents = list(/datum/reagent/toxin/ambusher_toxin = 30)
+
/obj/item/reagent_containers/glass/bottle/diphenhydramine
name = "antihistamine bottle"
desc = "A small bottle of diphenhydramine."
@@ -551,7 +556,7 @@
custom_premium_price = 30
/obj/item/reagent_containers/glass/bottle/vial/sal_acid
- name = "vial (Salicyclic Acid)"
+ name = "vial (Salicylic Acid)"
icon_state = "viallarge_white"
list_reagents = list(/datum/reagent/medicine/sal_acid = 15)
custom_premium_price = 50
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index af24286804a1..bf41a9610732 100755
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -127,24 +127,28 @@
. = ..()
if(!reagents.total_volume)
return
- var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "[icon_state]10")
+ var/base_state = base_icon_state
+ if(isnull(base_state))
+ base_state = icon_state
+
+ var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "[base_state]10")
var/percent = round((reagents.total_volume / volume) * 100)
switch(percent)
if(0 to 9)
- filling.icon_state = "[icon_state]-10"
+ filling.icon_state = "[base_state]-10"
if(10 to 24)
- filling.icon_state = "[icon_state]10"
+ filling.icon_state = "[base_state]10"
if(25 to 49)
- filling.icon_state = "[icon_state]25"
+ filling.icon_state = "[base_state]25"
if(50 to 74)
- filling.icon_state = "[icon_state]50"
+ filling.icon_state = "[base_state]50"
if(75 to 79)
- filling.icon_state = "[icon_state]75"
+ filling.icon_state = "[base_state]75"
if(80 to 90)
- filling.icon_state = "[icon_state]80"
+ filling.icon_state = "[base_state]80"
if(91 to INFINITY)
- filling.icon_state = "[icon_state]100"
+ filling.icon_state = "[base_state]100"
filling.color = mix_color_from_reagents(reagents.reagent_list)
. += filling
@@ -168,16 +172,13 @@
name = "x-large beaker"
desc = "An extra-large beaker. Can hold up to 120 units."
icon_state = "beakerwhite"
+ /// Overrides the base state used for the fill overlay
+ base_icon_state = "beakerlarge"
materials = list(/datum/material/glass=2500, /datum/material/plastic=3000)
volume = 120
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,60,120)
-/obj/item/reagent_containers/glass/beaker/plastic/update_icon_state()
- icon_state = "beakerlarge" // hack to lets us reuse the large beaker reagent fill states
- . = ..()
- icon_state = "beakerwhite"
-
/obj/item/reagent_containers/glass/beaker/meta
name = "metamaterial beaker"
desc = "A large beaker. Can hold up to 180 units."
@@ -208,6 +209,11 @@
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300)
+/obj/item/reagent_containers/glass/beaker/bluespace/dorf
+ name = "A perfectly normal bottle of beer"
+ list_reagents = list(/datum/reagent/consumable/ethanol/manly_dorf = 300)
+
+
/obj/item/reagent_containers/glass/beaker/cryoxadone
list_reagents = list(/datum/reagent/medicine/cryoxadone = 30)
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index 7baa9ea617b4..e95c754ed437 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -166,7 +166,7 @@
/obj/item/reagent_containers/pill/mutadone/five
list_reagents = list(/datum/reagent/medicine/mutadone = 5)
-/obj/item/reagent_containers/pill/salicyclic
+/obj/item/reagent_containers/pill/salicylic
name = "salicylic acid pill"
desc = "Used to stimulate bruise healing."
icon_state = "pill9"
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index e6b964152774..3189fb301304 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -328,6 +328,8 @@
/obj/item/reagent_containers/spray/chemsprayer/bioterror
list_reagents = list(/datum/reagent/toxin/sodium_thiopental = 100, /datum/reagent/toxin/coniine = 100, /datum/reagent/toxin/venom = 100, /datum/reagent/consumable/condensedcapsaicin = 100, /datum/reagent/toxin/initropidril = 100, /datum/reagent/toxin/polonium = 100)
+/obj/item/reagent_containers/spray/chemsprayer/freeze
+ list_reagents = list(/datum/reagent/consumable/frostoil = 600)
/obj/item/reagent_containers/spray/chemsprayer/janitor
name = "janitor chem sprayer"
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index e2b70279b436..7697671090cd 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -11,19 +11,29 @@
interaction_flags_machine = INTERACT_MACHINE_OPEN | INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON
obj_flags = CAN_BE_HIT | USES_TGUI
flags_1 = RAD_PROTECT_CONTENTS_1 | RAD_NO_CONTAMINATE_1
- var/datum/gas_mixture/air_contents // internal reservoir
+ /// The internal air reservoir of the disposal
+ var/datum/gas_mixture/air_contents
+ /// Is the disposal at full pressure
var/full_pressure = FALSE
+ /// Is the pressure charging
var/pressure_charging = TRUE
- var/flush = 0 // true if flush handle is pulled
- var/obj/structure/disposalpipe/trunk/trunk = null // the attached pipe trunk
- var/flushing = 0 // true if flushing in progress
- var/flush_every_ticks = 30 //Every 30 ticks it will look whether it is ready to flush
- var/flush_count = 0 //this var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush.
+ // True if flush handle is pulled
+ var/flush = FALSE
+ /// The attached pipe trunk
+ var/obj/structure/disposalpipe/trunk/trunk = null
+ /// True if flushing in progress
+ var/flushing = FALSE
+ /// Every 30 ticks it will look whether it is ready to flush
+ var/flush_every_ticks = 30
+ /// This var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush.
+ var/flush_count = 0
+ /// The last time a sound was played
var/last_sound = 0
+ /// The stored disposal construction pipe
var/obj/structure/disposalconstruct/stored
- // create a new disposal
- // find the attached trunk (if present) and init gas resvr.
+// create a new disposal
+// find the attached trunk (if present) and init gas resvr.
/obj/machinery/disposal/Initialize(mapload, obj/structure/disposalconstruct/make_from)
. = ..()
@@ -39,7 +49,7 @@
air_contents = new /datum/gas_mixture()
//gas.volume = 1.05 * CELLSTANDARD
- update_appearance(UPDATE_ICON)
+ update_appearance()
return INITIALIZE_HINT_LATELOAD //we need turfs to have air
@@ -99,7 +109,7 @@
if((I.item_flags & ABSTRACT) || !user.temporarilyRemoveItemFromInventory(I))
return
place_item_in_disposal(I, user)
- update_appearance(UPDATE_ICON)
+ update_appearance()
return 1 //no afterattack
else
return ..()
@@ -143,7 +153,7 @@
target.visible_message(span_danger("[user] has placed [target] in [src]."), span_userdanger("[user] has placed [target] in [src]."))
log_combat(user, target, "stuffed", addition="into [src]")
target.LAssailant = WEAKREF(user)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/disposal/relaymove(mob/user)
attempt_escape(user)
@@ -160,14 +170,14 @@
// leave the disposal
/obj/machinery/disposal/proc/go_out(mob/user)
user.forceMove(loc)
- update_appearance(UPDATE_ICON)
+ update_appearance()
// monkeys and xenos can only pull the flush lever
/obj/machinery/disposal/attack_paw(mob/user)
if(stat & BROKEN)
return
flush = !flush
- update_appearance(UPDATE_ICON)
+ update_appearance()
// eject the contents of the disposal unit
@@ -176,7 +186,7 @@
for(var/atom/movable/AM in src)
AM.forceMove(T)
AM.pipe_eject(0)
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/disposal/proc/flush()
flushing = TRUE
@@ -229,7 +239,7 @@
src.transfer_fingerprints_to(stored)
stored.anchored = FALSE
stored.density = TRUE
- stored.update_appearance(UPDATE_ICON)
+ stored.update_appearance()
for(var/atom/movable/AM in src) //out, out, darned crowbar!
AM.forceMove(T)
..()
@@ -270,8 +280,8 @@
to_chat(user, span_warning("You empty the bag."))
for(var/obj/item/O in T.contents)
STR.remove_from_storage(O,src)
- T.update_appearance(UPDATE_ICON)
- update_appearance(UPDATE_ICON)
+ T.update_appearance()
+ update_appearance()
else
return ..()
@@ -282,7 +292,7 @@
if(!user.canUseTopic(src, TRUE))
return
flush = !flush
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/disposal/bin/ui_state(mob/user)
return GLOB.notcontained_state
@@ -312,22 +322,22 @@
switch(action)
if("handle-0")
flush = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("handle-1")
if(!panel_open)
flush = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("pump-0")
if(pressure_charging)
pressure_charging = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("pump-1")
if(!pressure_charging)
pressure_charging = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
. = TRUE
if("eject")
eject()
@@ -340,10 +350,10 @@
AM.forceMove(src)
if(ismob(AM))
do_flush()
- visible_message(span_notice("[AM] lands in [src] and triggers the flush system!."))
+ visible_message(span_notice("[AM] lands in [src] and triggers the flush system!"))
else
visible_message(span_notice("[AM] lands in [src]."))
- update_appearance(UPDATE_ICON)
+ update_appearance()
else
visible_message(span_notice("[AM] bounces off of [src]'s rim!"))
return ..()
@@ -354,7 +364,7 @@
..()
full_pressure = FALSE
pressure_charging = TRUE
- update_appearance(UPDATE_ICON)
+ update_appearance()
/obj/machinery/disposal/bin/update_overlays()
. = ..()
@@ -374,12 +384,15 @@
//check for items in disposal - occupied light
if(contents.len > 0)
. += "[base_icon_state]-full"
+ . += emissive_appearance(icon, "[base_icon_state]-full", src, alpha = src.alpha)
//charging and ready light
if(pressure_charging)
. += "[base_icon_state]-charge"
+ . += emissive_appearance(icon, "[base_icon_state]-charge-glow", src, alpha = src.alpha)
else if(full_pressure)
. += "[base_icon_state]-ready"
+ . += emissive_appearance(icon, "[base_icon_state]-ready-glow", src, alpha = src.alpha)
/obj/machinery/disposal/bin/proc/do_flush()
set waitfor = FALSE
@@ -430,7 +443,7 @@
if(air_contents.return_pressure() >= SEND_PRESSURE)
full_pressure = TRUE
pressure_charging = FALSE
- update_appearance(UPDATE_ICON)
+ update_appearance()
return
/obj/machinery/disposal/bin/get_remote_view_fullscreens(mob/user)
diff --git a/code/modules/recycling/disposal/construction.dm b/code/modules/recycling/disposal/construction.dm
index 42d680720e3f..ca3b71f99003 100644
--- a/code/modules/recycling/disposal/construction.dm
+++ b/code/modules/recycling/disposal/construction.dm
@@ -9,7 +9,6 @@
anchored = FALSE
density = FALSE
pressure_resistance = 5*ONE_ATMOSPHERE
- level = 2
max_integrity = 200
var/obj/pipe_type = /obj/structure/disposalpipe/segment
var/pipename
@@ -31,6 +30,7 @@
var/datum/component/simple_rotation/rotcomp = AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_FLIP | ROTATION_VERBS, null, CALLBACK(src, PROC_REF(can_be_rotated)), CALLBACK(src, PROC_REF(after_rot)))
if(flip)
rotcomp.BaseRot(null,ROTATION_FLIP)
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
update_appearance(UPDATE_ICON)
@@ -46,10 +46,8 @@
if(is_pipe())
icon_state = "con[icon_state]"
if(anchored)
- level = initial(pipe_type.level)
layer = initial(pipe_type.layer)
else
- level = initial(level)
layer = initial(layer)
else if(ispath(pipe_type, /obj/machinery/disposal/bin))
@@ -59,13 +57,6 @@
else
icon_state = "condisposal"
-
-// hide called by levelupdate if turf intact status changes
-// change visibility status and force update of icon
-/obj/structure/disposalconstruct/hide(intact)
- invisibility = (intact && level==1) ? INVISIBILITY_MAXIMUM: 0 // hide if floor is intact
- update_appearance(UPDATE_ICON)
-
/obj/structure/disposalconstruct/proc/get_disposal_dir()
if(!is_pipe())
return NONE
@@ -115,7 +106,7 @@
var/ispipe = is_pipe() // Indicates if we should change the level of this pipe
var/turf/T = get_turf(src)
- if(T.intact && isfloorturf(T))
+ if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && isfloorturf(T))
to_chat(user, span_warning("You can only attach the [pipename] if the floor plating is removed!"))
return TRUE
diff --git a/code/modules/recycling/disposal/multiz.dm b/code/modules/recycling/disposal/multiz.dm
new file mode 100644
index 000000000000..7852791a235a
--- /dev/null
+++ b/code/modules/recycling/disposal/multiz.dm
@@ -0,0 +1,42 @@
+#define MULTIZ_PIPE_UP 1 ///Defines for determining which way a multiz disposal element should travel
+#define MULTIZ_PIPE_DOWN 2 ///Defines for determining which way a multiz disposal element should travel
+
+
+/obj/structure/disposalpipe/trunk/multiz
+ name = "Disposal trunk that goes up"
+ icon_state = "pipe-up"
+ var/multiz_dir = MULTIZ_PIPE_UP ///Set the multiz direction of your trunk. 1 = up, 2 = down
+
+/obj/structure/disposalpipe/trunk/multiz/down
+ name = "Disposal trunk that goes down"
+ icon_state = "pipe-down"
+ multiz_dir = MULTIZ_PIPE_DOWN
+
+/obj/structure/disposalpipe/trunk/multiz/transfer(obj/structure/disposalholder/H)
+ if(H.dir == DOWN) //Since we're a trunk, you can still place a chute / bin over us. If theyve entered from there, treat this as a normal trunk
+ return ..()
+
+ //If we for some reason do not have a multiz dir, just like, use the default logic
+ if(!multiz_dir)
+ return ..()
+
+ //Are we a trunk that goes up? Or down?
+ var/turf/target = get_turf(src)
+ if(multiz_dir == MULTIZ_PIPE_UP)
+ target = GET_TURF_ABOVE(target)
+ if(multiz_dir == MULTIZ_PIPE_DOWN)
+ target = GET_TURF_BELOW(target)
+ if(!target) //Nothing located.
+ return
+
+ var/obj/structure/disposalpipe/trunk/multiz/pipe = locate(/obj/structure/disposalpipe/trunk/multiz) in target
+ if(!pipe)
+ return
+ var/obj/structure/disposalholder/destination = new(pipe) //For future reference, the disposal holder is the thing that carries mobs
+ destination.merge(H) //This takes the contents of H (Our disposal holder that's travelling into us) and puts them into the destination holder
+ destination.active = TRUE //Active allows it to process and move
+ destination.setDir(DOWN) //This tells the trunk above us NOT to loop it back down to us, or else you get an infinite loop
+ destination.move()
+
+#undef MULTIZ_PIPE_UP
+#undef MULTIZ_PIPE_DOWN
diff --git a/code/modules/recycling/disposal/pipe.dm b/code/modules/recycling/disposal/pipe.dm
index 4b6baf096ccb..32c30dc8552a 100644
--- a/code/modules/recycling/disposal/pipe.dm
+++ b/code/modules/recycling/disposal/pipe.dm
@@ -7,7 +7,6 @@
anchored = TRUE
density = FALSE
obj_flags = CAN_BE_HIT | ON_BLUEPRINTS
- level = 1 // underfloor only
dir = NONE // dir will contain dominant direction for junction pipes
max_integrity = 200
armor = list(MELEE = 25, BULLET = 10, LASER = 10, ENERGY = 100, BOMB = 0, BIO = 100, RAD = 100, FIRE = 90, ACID = 30)
@@ -34,14 +33,14 @@
if(initialize_dirs != DISP_DIR_NONE)
dpdir = dir
-
if(initialize_dirs & DISP_DIR_LEFT)
dpdir |= turn(dir, 90)
if(initialize_dirs & DISP_DIR_RIGHT)
dpdir |= turn(dir, -90)
if(initialize_dirs & DISP_DIR_FLIP)
dpdir |= turn(dir, 180)
- update()
+
+ AddElement(/datum/element/undertile, TRAIT_T_RAY_VISIBLE)
// pipe is deleted
// ensure if holder is present, it is expelled
@@ -80,16 +79,6 @@
H.forceMove(get_turf(src))
return null
-// update the icon_state to reflect hidden status
-/obj/structure/disposalpipe/proc/update()
- var/turf/T = get_turf(src)
- hide(T.intact && !isspaceturf(T)) // space never hides pipes
-
-// hide called by levelupdate if turf intact status changes
-// change visibility status and force update of icon
-/obj/structure/disposalpipe/hide(intact)
- invisibility = intact ? INVISIBILITY_MAXIMUM: 0 // hide if floor is intact
-
// expel the held objects into a turf
// called when there is a break in the pipe
/obj/structure/disposalpipe/proc/expel(obj/structure/disposalholder/H, turf/T, direction)
@@ -97,7 +86,7 @@
var/eject_range = 5
var/turf/open/floor/floorturf
- if(isfloorturf(T)) //intact floor, pop the tile
+ if(isfloorturf(T) && T.overfloor_placed) // pop the tile if present
floorturf = T
floorturf.remove_tile()
diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm
index 4684912245ac..7a08fc3a5053 100644
--- a/code/modules/religion/religion_structures.dm
+++ b/code/modules/religion/religion_structures.dm
@@ -1,4 +1,4 @@
-/obj/structure/altar_of_gods
+/obj/structure/table/altar_of_gods
name = "\improper Altar of the Gods"
desc = "An altar which allows the head of the church to choose a sect of religious teachings as well as provide sacrifices to earn favor."
icon = 'icons/obj/hand_of_god_structures.dmi'
@@ -8,32 +8,28 @@
layer = TABLE_LAYER
pass_flags = LETPASSTHROW
can_buckle = TRUE
+ smoothing_flags = null
+ canSmoothWith = null
+ flags_1 = NODECONSTRUCT_1 // no, don't
buckle_lying = 90 //we turn to you!
+ max_integrity = 300
+ integrity_failure = 0
+ buildstackamount = 6
+ buildstack = /obj/item/stack/sheet/ruinous_metal
///Avoids having to check global everytime by referencing it locally.
var/datum/religion_sect/sect_to_altar
-/obj/structure/altar_of_gods/Initialize(mapload)
+/obj/structure/table/altar_of_gods/Initialize(mapload)
. = ..()
reflect_sect_in_icons()
- AddElement(/datum/element/climbable)
AddComponent(/datum/component/religious_tool, ALL, FALSE, CALLBACK(src, PROC_REF(reflect_sect_in_icons)))
-/obj/structure/altar_of_gods/attack_hand(mob/living/user)
- if(!Adjacent(user) || !user.pulling)
- return ..()
- if(!isliving(user.pulling))
- return ..()
- var/mob/living/pushed_mob = user.pulling
- if(pushed_mob.buckled)
- to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!"))
- return ..()
- to_chat(user, span_notice("You try to coax [pushed_mob] onto [src]..."))
- if(!do_after(user, 5 SECONDS, pushed_mob))
- return ..()
- pushed_mob.forceMove(loc)
+/obj/structure/table/altar_of_gods/attackby(obj/item/I, mob/user, params)
+ if(SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, I, user, params) & COMPONENT_NO_AFTERATTACK)
+ return TRUE // this signal needs to be sent early so the bible can actually be used on it
return ..()
-/obj/structure/altar_of_gods/proc/reflect_sect_in_icons()
+/obj/structure/table/altar_of_gods/proc/reflect_sect_in_icons()
if(GLOB.religious_sect)
sect_to_altar = GLOB.religious_sect
if(sect_to_altar.altar_icon)
diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm
index a1d813677ca7..fb5d98e76771 100644
--- a/code/modules/religion/rites.dm
+++ b/code/modules/religion/rites.dm
@@ -299,6 +299,8 @@
var/favor_gained = 100 + round(chosen_sacrifice.getFireLoss())
GLOB.religious_sect?.adjust_favor(favor_gained, user)
to_chat(user, span_notice("[GLOB.deity] absorb the burning corpse and any trace of fire with it. [GLOB.deity] rewards you with [favor_gained] favor."))
+ var/mob/living/carbon/carbon_user = user //dripstation edit
+ SEND_SIGNAL(carbon_user, COMSIG_ADD_MOOD_EVENT, "chap_sac", /datum/mood_event/sacrifice_good) //dripstation edit
chosen_sacrifice.dust(force = TRUE)
playsound(get_turf(religious_tool), 'sound/effects/supermatter.ogg', 50, TRUE)
chosen_sacrifice = null
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index 5ed836d14eaa..c758688221dd 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -61,8 +61,6 @@ other types of metals and chemistry for reagents).
else
temp_list[i] = amount
materials = temp_list
- for(var/i in materials)
- to_chat("[i] [materials[i]]")
/datum/design/proc/icon_html(client/user)
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 2bd439510abd..205047e1c867 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -793,4 +793,12 @@
id = "mindmachine_pod"
build_path = /obj/item/circuitboard/machine/mindmachine_pod
category = list("Medical Machinery")
- departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
\ No newline at end of file
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/datum/design/board/mass_driver
+ name = "Machine Design (Mass Driver)"
+ desc = "The circuit board for a mass driver."
+ id = "mass_driver"
+ build_path = /obj/item/circuitboard/machine/mass_driver
+ category = list("Misc. Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE
diff --git a/code/modules/research/designs/mecha_designs.dm b/code/modules/research/designs/mecha_designs.dm
index 9b5a012ee9cd..14f89cc5dde3 100644
--- a/code/modules/research/designs/mecha_designs.dm
+++ b/code/modules/research/designs/mecha_designs.dm
@@ -411,6 +411,66 @@
construction_time = 1200
category = list("Exosuit Equipment")
+/datum/design/mech_thrusters
+ name = "Exosuit Module (RCS Thruster Package)"
+ desc = "A thruster package for exosuits. Expels gas from the internal life-support air tank to generate thrust."
+ id = "mech_thrusters"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/thrusters/gas
+ materials = list(/datum/material/iron=25000,/datum/material/titanium=5000,/datum/material/silver=3000)
+ construction_time = 100
+ category = list("Exosuit Equipment")
+
+/datum/design/mech_pipe_dispenser
+ name = "Exosuit Module (Pipe Dispenser Module)"
+ desc = "An exosuit-mounted Rapid Pipe Dispenser."
+ id = "mech_pipe_dispenser"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/pipe_dispenser
+ materials = list(/datum/material/iron=10000,/datum/material/glass=2000)
+ construction_time = 100
+ category = list("Exosuit Equipment")
+
+/datum/design/mech_t_scanner
+ name = "Exosuit Module (T-ray Scanner Module)"
+ desc = "An exosuit-mounted Terahertz-Ray Scanner."
+ id = "mech_t_scanner"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/t_scanner
+ materials = list(/datum/material/iron=2000)
+ construction_time = 50
+ category = list("Exosuit Equipment")
+
+/datum/design/mech_washer
+ name = "Exosuit Module (Pressure Washer)"
+ desc = "An exosuit-mounted pressure washer."
+ id = "mech_washer"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/weapon/pressure_washer
+ materials = list(/datum/material/iron=2000)
+ construction_time = 50
+ category = list("Exosuit Equipment")
+
+/datum/design/mech_mop
+ name = "Exosuit Module (Mop)"
+ desc = "An exosuit-mounted mop."
+ id = "mech_mop"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/melee_weapon/mop
+ materials = list(/datum/material/iron=2000)
+ construction_time = 50
+ category = list("Exosuit Equipment")
+
+/datum/design/mech_flyswatter
+ name = "Exosuit Module (Flyswatter)"
+ desc = "An exosuit-mounted flyswatter."
+ id = "mech_flyswatter"
+ build_type = MECHFAB
+ build_path = /obj/item/mecha_parts/mecha_equipment/melee_weapon/flyswatter
+ materials = list(/datum/material/iron=2000)
+ construction_time = 50
+ category = list("Exosuit Equipment")
+
/datum/design/mech_gravcatapult
name = "Exosuit Module (Gravitational Catapult Module)"
desc = "An exosuit mounted Gravitational Catapult."
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index eb8ec7b9a1af..ff01af524c1f 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -11,7 +11,7 @@
construction_time = 75
build_path = /obj/item/mmi
category = list("Control Interfaces", "Medical Designs")
- departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
/datum/design/posibrain
name = "Positronic Brain"
@@ -22,7 +22,7 @@
construction_time = 75
build_path = /obj/item/mmi/posibrain
category = list("Control Interfaces", "Medical Designs")
- departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
/datum/design/bluespacebeaker
name = "Bluespace Beaker"
@@ -708,7 +708,7 @@
/////////////////////
/datum/design/surgery
name = "Surgery Design"
- desc = "what"
+ desc = "What."
id = "surgery_parent"
research_icon = 'icons/misc/surgery_icons.dmi'
research_icon_state = "surgery_any"
diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm
index 5f5847cc2a5c..ff2327ff62bd 100644
--- a/code/modules/research/machinery/protolathe.dm
+++ b/code/modules/research/machinery/protolathe.dm
@@ -16,7 +16,7 @@
"Ammo",
"Firing Pins",
"Computer Parts",
- "Spacepod Designs", // yoggers
+ //"Spacepod Designs", // dripstation spacepod move to new fab
"Service",
"Assemblies"
)
diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm
index 1289ebcde85e..ed5a8c105eef 100644
--- a/code/modules/research/machinery/techfab.dm
+++ b/code/modules/research/machinery/techfab.dm
@@ -27,7 +27,7 @@
"Research Machinery",
"Misc. Machinery",
"Computer Parts",
- "Spacepod Designs", // yogs
+ //"Spacepod Designs", // dripstation edit
"Service" //yogs
)
console_link = FALSE
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index f19753ed5eeb..b51b398de598 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -1090,6 +1090,9 @@ Nothing else in the console has ID requirements.
stored_research.add_design(D, TRUE)
else
stored_research.add_design(d_disk.blueprints[n], TRUE)
+
+ say("Uploading blueprints from disk.") //dripstation edit
+ d_disk.on_upload(stored_research) //dripstation edit
updateUsrDialog()
diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm
index 387314c1e35f..e42becfe5b20 100644
--- a/code/modules/research/techweb/_techweb.dm
+++ b/code/modules/research/techweb/_techweb.dm
@@ -104,6 +104,17 @@
l[i] = amount
remove_point_list(l)
+/datum/techweb/proc/set_point_list(list/pointlist)
+ for(var/i in pointlist)
+ if(SSresearch.point_types[i] && pointlist[i] > 0)
+ research_points[i] = pointlist[i]
+
+/datum/techweb/proc/set_points_all(amount)
+ var/list/l = SSresearch.point_types.Copy()
+ for(var/i in l)
+ l[i] = amount
+ set_point_list(l)
+
/datum/techweb/proc/modify_point_list(list/pointlist)
for(var/i in pointlist)
if(SSresearch.point_types[i] && pointlist[i] != 0)
diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm
index 5bf1cff4a57c..23112ab60175 100644
--- a/code/modules/research/techweb/_techweb_node.dm
+++ b/code/modules/research/techweb/_techweb_node.dm
@@ -7,6 +7,7 @@
var/display_name = "Errored Node"
var/description = "Why are you seeing this?"
var/hidden = FALSE //Whether it starts off hidden.
+ var/experimental = FALSE //If the tech can be randomly generated by the BEPIS as a reward. MEant to be fully given in tech disks, not researched. dripstation edit
var/starting_node = FALSE //Whether it's available without any research.
var/list/prereq_ids = list()
var/list/design_ids = list()
@@ -41,6 +42,7 @@
VARSET_TO_LIST(., id)
VARSET_TO_LIST(., display_name)
VARSET_TO_LIST(., hidden)
+ VARSET_TO_LIST(., experimental) //dripstation edit
VARSET_TO_LIST(., starting_node)
VARSET_TO_LIST(., assoc_to_keys(prereq_ids))
VARSET_TO_LIST(., assoc_to_keys(design_ids))
@@ -56,6 +58,7 @@
VARSET_FROM_LIST(input, id)
VARSET_FROM_LIST(input, display_name)
VARSET_FROM_LIST(input, hidden)
+ VARSET_FROM_LIST(input, experimental) //dripstation edit
VARSET_FROM_LIST(input, starting_node)
VARSET_FROM_LIST(input, prereq_ids)
VARSET_FROM_LIST(input, design_ids)
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 90f1a0fe30fc..2cb62f1d9d7b 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -39,7 +39,7 @@
starting_node = TRUE
display_name = "Basic Exosuit Equipment"
description = "Various tools fit for basic mech units"
- design_ids = list("mech_drill", "mech_mscanner", "mech_extinguisher", "mech_cable_layer")
+ design_ids = list("mech_drill", "mech_mscanner", "mech_extinguisher", "mech_cable_layer", "mech_t_scanner", "mech_pipe_dispenser")
/datum/techweb_node/clarke
id = "mecha_clarke"
@@ -155,7 +155,8 @@
design_ids = list("solarcontrol", "recharger", "powermonitor", "rped", "pacman", "adv_capacitor", "adv_scanning", "emitter", "high_cell", "adv_matter_bin", "scanner_gate",
"atmosalerts", "atmos_control", "recycler", "autolathe", "high_micro_laser", "nano_mani", "mesons", "thermomachine", "rad_collector", "tesla_coil", "grounding_rod",
"cell_charger", "stack_console", "stack_machine", "conveyor_belt", "conveyor_switch", "reactor_control",
- "oxygen_tank", "plasma_tank", "emergency_oxygen", "emergency_oxygen_engi", "plasmaman_tank_belt", "electrolyzer", "floorigniter", "crystallizer", "suit_storage_unit", "atmos_thermal")
+ "oxygen_tank", "plasma_tank", "emergency_oxygen", "emergency_oxygen_engi", "plasmaman_tank_belt", "electrolyzer", "floorigniter", "crystallizer", "suit_storage_unit",
+ "atmos_thermal")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
/datum/techweb_node/adv_engi
@@ -163,7 +164,7 @@
display_name = "Advanced Engineering"
description = "Pushing the boundaries of physics, one chainsaw-fist at a time."
prereq_ids = list("engineering", "emp_basic")
- design_ids = list("engine_goggles", "magboots", "forcefield_projector", "weldingmask", "decontamination_unit", "particle_emitter", "tricorder")
+ design_ids = list("engine_goggles", "magboots", "forcefield_projector", "weldingmask", "decontamination_unit", "particle_emitter", "tricorder", "mass_driver")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/anomaly
@@ -312,7 +313,6 @@
display_name = "Advanced Robotics Research"
description = "It can even do the dishes!"
prereq_ids = list("robotics")
- design_ids = list("borg_upgrade_diamonddrill", "borg_upgrade_trashofholding", "borg_upgrade_advancedmop")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/neural_programming
@@ -330,38 +330,70 @@
design_ids = list("mmi_posi")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
-/datum/techweb_node/cyborg_upg_util
- id = "cyborg_upg_util"
+/datum/techweb_node/cyborg_upgrades_utility
+ id = "cyborg_upgrades_utility"
display_name = "Cyborg Upgrades: Utility"
- description = "Utility upgrades for cyborgs."
- prereq_ids = list("engineering")
- design_ids = list("borg_upgrade_holding", "borg_upgrade_lavaproof", "borg_upgrade_thrusters", "borg_upgrade_selfrepair", "borg_upgrade_expand", "borg_upgrade_rped", "borg_upgrade_language", "borg_upgrade_broomer", "borg_upgrade_snacks", "borg_upgrade_gemsatchel", "borg_upgrade_condiment_synthesizer", "borg_upgrade_service_cookbook", "borg_upgrade_janitor_autocleaner")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000)
+ description = "Upgrades that can be used on all cyborg module types that increases their general utility."
+ prereq_ids = list("engineering", "adv_robotics")
+ design_ids = list("borg_upgrade_thrusters", "borg_upgrade_language", "borg_upgrade_expand", "borg_upgrade_selfrepair")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1500)
-/datum/techweb_node/adv_cyborg_upg_util
- id = "adv_cyborg_upg_util"
- display_name = "Cyborg Upgrades: Advanced Utility"
- description = "Advanced utility upgrades for cyborgs."
- prereq_ids = list("cyborg_upg_util", "practical_bluespace", "exp_tools") // Experimental tools covers tools & holofan. Bluespace covers BRPED.
+/datum/techweb_node/cyborg_upgrades_engineering
+ id = "cyborg_upgrades_engineering"
+ display_name = "Cyborg Upgrades: Engineering"
+ description = "Upgrades that can only be used on cyborgs with a engineering-related module."
+ prereq_ids = list("cyborg_upgrades_utility")
+ design_ids = list("borg_upgrade_rped")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 500)
+
+/datum/techweb_node/cyborg_upgrades_engineering_adv
+ id = "cyborg_upgrades_engineering_adv"
+ display_name = "Cyborg Upgrades: Advanced Engineering"
+ description = "Advanced upgrades that can only be used on cyborgs with a engineering-related module."
+ prereq_ids = list("cyborg_upgrades_engineering", "practical_bluespace", "exp_tools")
design_ids = list("borg_upgrade_engi_advancedtools", "borg_upgrade_holofan", "borg_upgrade_brped")
- research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+
+/datum/techweb_node/cyborg_upgrades_nvg
+ id = "cyborg_upgrades_nvg" // This is its own seperate node solely ONLY because it wasn't obvious on how to find this upgrade.
+ display_name = "Cyborg Upgrades: NVG"
+ description = "Upgrade that swaps a cyborg's mesons to nightvision mesons."
+ prereq_ids = list("cyborg_upgrades_engineering", "NVGtech")
+ design_ids = list("borg_upgrade_nv_mesons")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 500)
-/datum/techweb_node/cyborg_upg_med
- id = "cyborg_upg_med"
+/datum/techweb_node/cyborg_upgrades_medical
+ id = "cyborg_upgrades_medical"
display_name = "Cyborg Upgrades: Medical"
- description = "Medical upgrades for cyborgs."
- prereq_ids = list("adv_biotech")
- design_ids = list("borg_upgrade_defibrillator", "borg_upgrade_piercinghypospray", "borg_upgrade_expandedsynthesiser", "borg_upgrade_medigripper")
+ description = "Upgrades that focus on cyborgs with a medical-related module."
+ prereq_ids = list("cyborg_upgrades_utility", "adv_biotech")
+ design_ids = list("borg_upgrade_defibrillator", "borg_upgrade_expandedsynthesiser", "borg_upgrade_medigripper")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000)
-/datum/techweb_node/cyborg_upg_surgkit
- id = "cyborg_upg_surgkit"
- display_name = "Cyborg Upgrade: Medical Advanced Medical Tools"
- description = "Advanced Surgical Kit and Advanced Health Scanner upgrade design for medical cyborgs."
- prereq_ids = list("cyborg_upg_med", "exp_tools")
- design_ids = list("borg_upgrade_surgerykit", "borg_upgrade_analyzer")
+/datum/techweb_node/cyborg_upgrades_medical_adv
+ id = "cyborg_upgrades_medical_adv"
+ display_name = "Cyborg Upgrades: Advanced Medical"
+ description = "Advanced upgrades that focus on cyborgs with a medical-related module."
+ prereq_ids = list("cyborg_upgrades_medical", "exp_tools")
+ design_ids = list("borg_upgrade_piercinghypospray", "borg_upgrade_surgerykit", "borg_upgrade_analyzer")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
+/datum/techweb_node/cyborg_upgrades_mining
+ id = "cyborg_upgrades_mining"
+ display_name = "Cyborg Upgrades: Mining"
+ description = "Upgrades that focus on cyborgs with a mining-related module."
+ prereq_ids = list("cyborg_upgrades_utility", "adv_mining")
+ design_ids = list("borg_upgrade_lavaproof", "borg_upgrade_diamonddrill", "borg_upgrade_plasmacutter", "borg_upgrade_holding", "borg_upgrade_gemsatchel")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 500)
+
+/datum/techweb_node/cyborg_upgrades_service
+ id = "cyborg_upgrades_service"
+ display_name = "Cyborg Upgrades: Service"
+ description = "Upgrades that focus on cyborgs with a service-related module." // Janitor & "Service/Bartender".
+ prereq_ids = list("cyborg_upgrades_utility", "janitor")
+ design_ids = list("borg_upgrade_trashofholding", "borg_upgrade_advancedmop", "borg_upgrade_broomer", "borg_upgrade_janitor_autocleaner", "borg_upgrade_condiment_synthesizer", "borg_upgrade_service_cookbook", "borg_upgrade_snacks")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 500) // Roleplay discount.
+
/datum/techweb_node/ai
id = "ai"
display_name = "Artificial Intelligence"
@@ -463,7 +495,7 @@
display_name = "Night Vision Technology"
description = "Allows seeing in the dark without actual light!"
prereq_ids = list("integrated_HUDs", "adv_engi", "emp_adv")
- design_ids = list("health_hud_night", "security_hud_night", "diagnostic_hud_night", "night_visision_goggles", "nvgmesons", "nightscigoggles", "borg_upgrade_nv_mesons")
+ design_ids = list("health_hud_night", "security_hud_night", "diagnostic_hud_night", "night_visision_goggles", "nvgmesons", "nightscigoggles")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
////////////////////////Medical////////////////////////
@@ -562,7 +594,7 @@
display_name = "Advanced Mining Technology"
description = "Efficiency Level 127" //dumb mc references
prereq_ids = list("basic_mining", "adv_engi", "adv_power", "adv_plasma")
- design_ids = list("drill_diamond", "jackhammer", "hypermod", "plasmacutter_adv", "borg_upgrade_plasmacutter","miningcharge")
+ design_ids = list("drill_diamond", "jackhammer", "hypermod", "plasmacutter_adv", "miningcharge")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/magmite_mining
@@ -588,7 +620,7 @@
display_name = "Advanced Sanitation Technology"
description = "Clean things better, faster, stronger, and harder!"
prereq_ids = list("adv_engi")
- design_ids = list("advmop", "buffer", "blutrash", "light_replacer", "spraybottle", "beartrap")
+ design_ids = list("advmop", "buffer", "blutrash", "light_replacer", "spraybottle", "beartrap", "mech_washer", "mech_mop", "mech_flyswatter")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/botany
@@ -825,7 +857,7 @@
display_name = "Advanced Exosuit Equipment"
description = "Tools for high level mech suits"
prereq_ids = list("adv_mecha")
- design_ids = list("mech_rcd")
+ design_ids = list("mech_rcd", "mech_thrusters")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
/datum/techweb_node/med_mech_tools
diff --git a/code/modules/research/techweb/layout.dm b/code/modules/research/techweb/layout.dm
index aaa40cdb3f01..ac965e2c77a8 100644
--- a/code/modules/research/techweb/layout.dm
+++ b/code/modules/research/techweb/layout.dm
@@ -151,14 +151,38 @@
ui_x = 32
ui_y = -320
-/datum/techweb_node/cyborg_upg_util
+/datum/techweb_node/cyborg_upgrades_utility
ui_x = -96
ui_y = 160
-/datum/techweb_node/adv_cyborg_upg_util
+/datum/techweb_node/cyborg_upgrades_engineering
ui_x = -32
ui_y = 160
+/datum/techweb_node/cyborg_upgrades_engineering_adv
+ ui_x = 32
+ ui_y = 160
+
+/datum/techweb_node/cyborg_upgrades_nvg
+ ui_x = 96
+ ui_y = 160
+
+/datum/techweb_node/cyborg_upgrades_medical
+ ui_x = -32
+ ui_y = 224
+
+/datum/techweb_node/cyborg_upgrades_medical_adv
+ ui_x = 32
+ ui_y = 224
+
+/datum/techweb_node/cyborg_upgrades_mining
+ ui_x = -32
+ ui_y = 288
+
+/datum/techweb_node/cyborg_upgrades_service
+ ui_x = -32
+ ui_y = 352
+
/datum/techweb_node/basic_mining
ui_x = 96
ui_y = -384
@@ -231,10 +255,6 @@
ui_x = 256
ui_y = -64
-/datum/techweb_node/cyborg_upg_med
- ui_x = 352
- ui_y = -160
-
/datum/techweb_node/cyber_organs
ui_x = 352
ui_y = -96
@@ -459,10 +479,6 @@
ui_x = -224
ui_y = -736
-/datum/techweb_node/cyborg_upg_surgkit
- ui_x = 416
- ui_y = -160
-
/datum/techweb_node/cyber_organs_upgraded
ui_x = 416
ui_y = -96
diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm
index c2588030a2ef..94a447f35563 100644
--- a/code/modules/research/xenobiology/crossbreeding/_misc.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm
@@ -207,7 +207,7 @@ Slimecrossing Items
desc = "A mass of solidified slime gel - completely impenetrable, but it's melting away!"
icon = 'icons/obj/slimecrossing.dmi'
icon_state = "slimebarrier_thick"
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
opacity = TRUE
initial_duration = 10 SECONDS
diff --git a/code/modules/research/xenobiology/crossbreeding/_structures.dm b/code/modules/research/xenobiology/crossbreeding/_structures.dm
index b10ef0549a87..5cd551f7d3d4 100644
--- a/code/modules/research/xenobiology/crossbreeding/_structures.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_structures.dm
@@ -408,7 +408,7 @@ GLOBAL_LIST_EMPTY(bluespace_slime_crystals)
blood_amt = max_blood_amt
break
qdel(B)
- else if (istype(B, /obj/effect/decal/cleanable/trail_holder))
+ else if (istype(B, /obj/effect/decal/cleanable/blood/trail_holder))
blood_amt += 3
if(blood_amt > max_blood_amt)
blood_amt = max_blood_amt
@@ -422,7 +422,7 @@ GLOBAL_LIST_EMPTY(bluespace_slime_crystals)
blood_amt -= 50
to_chat(user, span_notice("You touch the crystal, and see blood transforming into an organ!"))
playsound(src, 'sound/magic/demon_consume.ogg', 50, 1)
- var/type = pick(/obj/item/reagent_containers/food/snacks/meat/slab,/obj/item/organ/heart,/obj/item/organ/heart/freedom,/obj/item/organ/lungs,/obj/item/organ/lungs/plasmaman,/obj/item/organ/lungs/ethereal,/obj/item/organ/lungs/slime,/obj/item/organ/liver,/obj/item/organ/liver/plasmaman,/obj/item/organ/liver/alien,/obj/item/organ/eyes,/obj/item/organ/eyes/night_vision/alien,/obj/item/organ/eyes/night_vision,/obj/item/organ/eyes/night_vision/mushroom,/obj/item/organ/tongue,/obj/item/organ/stomach,/obj/item/organ/stomach/plasmaman,/obj/item/organ/stomach/cell/ethereal,/obj/item/organ/ears,/obj/item/organ/ears/cat,/obj/item/organ/ears/penguin)
+ var/type = pick(/obj/item/reagent_containers/food/snacks/meat/slab,/obj/item/organ/heart,/obj/item/organ/heart/freedom,/obj/item/organ/lungs,/obj/item/organ/lungs/plasmaman,/obj/item/organ/lungs/ethereal,/obj/item/organ/lungs/slime,/obj/item/organ/liver,/obj/item/organ/liver/plasmaman,/obj/item/organ/liver/alien,/obj/item/organ/eyes,/obj/item/organ/eyes/alien,/obj/item/organ/eyes/night_vision,/obj/item/organ/eyes/night_vision/mushroom,/obj/item/organ/tongue,/obj/item/organ/stomach,/obj/item/organ/stomach/plasmaman,/obj/item/organ/stomach/cell/ethereal,/obj/item/organ/ears,/obj/item/organ/ears/cat,/obj/item/organ/ears/penguin)
new type(get_turf(src))
/obj/structure/slime_crystal/red/attacked_by(obj/item/I, mob/living/user)
diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm
index 7fa85e7bd63f..a6b3c91282dc 100644
--- a/code/modules/research/xenobiology/xenobio_camera.dm
+++ b/code/modules/research/xenobiology/xenobio_camera.dm
@@ -1,17 +1,17 @@
//Xenobio control console
-/mob/camera/aiEye/remote/xenobio
+/mob/camera/ai_eye/remote/xenobio
visible_icon = TRUE
icon = 'icons/mob/cameramob.dmi'
icon_state = "generic_camera"
var/allowed_area = null
-/mob/camera/aiEye/remote/xenobio/Initialize(mapload)
+/mob/camera/ai_eye/remote/xenobio/Initialize(mapload)
var/area/A = get_area(loc)
allowed_area = A.name
. = ..()
-/mob/camera/aiEye/remote/xenobio/setLoc(t)
- var/area/new_area = get_area(t)
+/mob/camera/ai_eye/remote/xenobio/setLoc(turf/destination, force_update = FALSE)
+ var/area/new_area = get_area(destination)
if(new_area && new_area.name == allowed_area || new_area && new_area.xenobiology_compatible)
return ..()
else
@@ -67,7 +67,7 @@
return ..()
/obj/machinery/computer/camera_advanced/xenobio/CreateEye()
- eyeobj = new /mob/camera/aiEye/remote/xenobio(get_turf(src))
+ eyeobj = new /mob/camera/ai_eye/remote/xenobio(get_turf(src))
eyeobj.origin = src
eyeobj.visible_icon = TRUE
eyeobj.icon = 'icons/mob/cameramob.dmi'
@@ -185,7 +185,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -205,7 +205,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -231,7 +231,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
@@ -257,7 +257,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
var/obj/machinery/monkey_recycler/recycler = X.connected_recycler
@@ -285,7 +285,7 @@
if(!target || !isliving(owner))
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
for(var/mob/living/simple_animal/slime/S in remote_eye.loc)
@@ -303,7 +303,7 @@
return
var/mob/living/C = owner
- var/mob/camera/aiEye/remote/xenobio/remote_eye = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = target
if(QDELETED(X.current_potion))
@@ -370,7 +370,7 @@
to_chat(user, span_warning("Target is not near a camera. Cannot proceed."))
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/area/mobarea = get_area(S.loc)
if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
slime_scan(S, C)
@@ -381,7 +381,7 @@
to_chat(user, span_warning("Target is not near a camera. Cannot proceed."))
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(QDELETED(X.current_potion))
@@ -396,7 +396,7 @@
to_chat(user, span_warning("Target is not near a camera. Cannot proceed."))
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(S.loc)
if(mobarea.name == E.allowed_area || mobarea.xenobiology_compatible)
@@ -418,7 +418,7 @@
to_chat(user, span_warning("Target is not near a camera. Cannot proceed."))
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/turfarea = get_area(T)
if(turfarea.name == E.allowed_area || turfarea.xenobiology_compatible)
@@ -433,7 +433,7 @@
to_chat(user, span_warning("Target is not near a camera. Cannot proceed."))
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/turfarea = get_area(T)
if(turfarea.name == E.allowed_area || turfarea.xenobiology_compatible)
@@ -453,7 +453,7 @@
to_chat(user, span_warning("Target is not near a camera. Cannot proceed."))
return
var/mob/living/C = user
- var/mob/camera/aiEye/remote/xenobio/E = C.remote_control
+ var/mob/camera/ai_eye/remote/xenobio/E = C.remote_control
var/obj/machinery/computer/camera_advanced/xenobio/X = E.origin
var/area/mobarea = get_area(M.loc)
if(!X.connected_recycler)
diff --git a/code/modules/ruins/lavaland_ruin_code.dm b/code/modules/ruins/lavaland_ruin_code.dm
index d1ca1265b8c2..cfb46a1e3f55 100644
--- a/code/modules/ruins/lavaland_ruin_code.dm
+++ b/code/modules/ruins/lavaland_ruin_code.dm
@@ -194,7 +194,7 @@
/obj/item/clothing/mask/chameleon/gps/Initialize(mapload)
. = ..()
- new /obj/item/gps/internal/lavaland_syndicate_base(src)
+ AddComponent(/datum/component/gps, "Encrypted Signal")
/obj/item/gps/internal/lavaland_syndicate_base
gpstag = "Encrypted Signal"
diff --git a/code/modules/ruins/lavalandruin_code/puzzle.dm b/code/modules/ruins/lavalandruin_code/puzzle.dm
index 1bd27290d2d4..41cfdbdf95eb 100644
--- a/code/modules/ruins/lavalandruin_code/puzzle.dm
+++ b/code/modules/ruins/lavalandruin_code/puzzle.dm
@@ -173,7 +173,7 @@
//Setup random empty tile
empty_tile_id = pick_n_take(left_ids)
var/turf/empty_tile_turf = get_turf_for_id(empty_tile_id)
- empty_tile_turf.PlaceOnTop(floor_type,null,CHANGETURF_INHERIT_AIR)
+ empty_tile_turf.place_on_top(floor_type,null,CHANGETURF_INHERIT_AIR)
var/mutable_appearance/MA = new(puzzle_pieces["[empty_tile_id]"])
MA.layer = empty_tile_turf.layer + 0.1
empty_tile_turf.add_overlay(MA)
@@ -182,7 +182,7 @@
var/list/empty_spots = left_ids.Copy()
for(var/spot_id in empty_spots)
var/turf/T = get_turf_for_id(spot_id)
- T = T.PlaceOnTop(floor_type,null,CHANGETURF_INHERIT_AIR)
+ T = T.place_on_top(floor_type,null,CHANGETURF_INHERIT_AIR)
var/obj/structure/puzzle_element/E = new element_type(T)
elements += E
var/chosen_id = pick_n_take(left_ids)
@@ -240,7 +240,7 @@
animate(src, pixel_x=rand(-5,5), pixel_y=rand(-2,2), time=0.1 SECONDS)
QDEL_IN(src,COLLAPSE_DURATION)
-/obj/structure/puzzle_element/Moved()
+/obj/structure/puzzle_element/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
source.validate()
diff --git a/code/modules/ruins/spaceruin_code/caravanambush.dm b/code/modules/ruins/spaceruin_code/caravanambush.dm
index 69d99b2b47fd..d71ee0b2108b 100644
--- a/code/modules/ruins/spaceruin_code/caravanambush.dm
+++ b/code/modules/ruins/spaceruin_code/caravanambush.dm
@@ -67,7 +67,7 @@
desc = "Used to control the Small Freighter."
circuit = /obj/item/circuitboard/computer/caravan/trade1
shuttleId = "caravantrade1"
- possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;caravantrade1_custom;caravantrade1_ambush"
+ possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_mining;caravantrade1_custom;caravantrade1_ambush"
/obj/machinery/computer/camera_advanced/shuttle_docker/caravan/Initialize(mapload)
. = ..()
@@ -83,7 +83,7 @@
shuttleId = "caravantrade1"
lock_override = NONE
shuttlePortId = "caravantrade1_custom"
- jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1, "caravantrade1_ambush" = 1)
+ jump_to_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1, "caravantrade1_ambush" = 1)
view_range = 6.5
x_offset = -5
y_offset = -5
@@ -107,7 +107,7 @@
shuttleId = "caravanpirate"
lock_override = NONE
shuttlePortId = "caravanpirate_custom"
- jumpto_ports = list("caravanpirate_ambush" = 1)
+ jump_to_ports = list("caravanpirate_ambush" = 1)
view_range = 6.5
x_offset = 3
y_offset = -6
@@ -131,7 +131,7 @@
shuttleId = "caravansyndicate1"
lock_override = NONE
shuttlePortId = "caravansyndicate1_custom"
- jumpto_ports = list("caravansyndicate1_ambush" = 1, "listeningpost" = 1)
+ jump_to_ports = list("caravansyndicate1_ambush" = 1, "listeningpost" = 1)
view_range = 0
x_offset = 2
y_offset = 0
@@ -155,7 +155,7 @@
shuttleId = "caravansyndicate2"
lock_override = NONE
shuttlePortId = "caravansyndicate2_custom"
- jumpto_ports = list("caravansyndicate2_ambush" = 1, "listeningpost" = 1)
+ jump_to_ports = list("caravansyndicate2_ambush" = 1, "listeningpost" = 1)
view_range = 0
x_offset = 0
y_offset = 2
@@ -179,7 +179,7 @@
shuttleId = "caravansyndicate3"
lock_override = NONE
shuttlePortId = "caravansyndicate3_custom"
- jumpto_ports = list("caravansyndicate3_ambush" = 1, "listeningpost" = 1)
+ jump_to_ports = list("caravansyndicate3_ambush" = 1, "listeningpost" = 1)
view_range = 2.5
x_offset = -1
y_offset = -3
diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
index 00e9c80bd5ae..7582c07dc184 100644
--- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm
+++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm
@@ -2,195 +2,210 @@ GLOBAL_VAR_INIT(hhStorageTurf, null)
GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337)
/obj/item/hilbertshotel
- name = "Hilbert's Hotel"
- desc = "A sphere of what appears to be an intricate network of bluespace. Observing it in detail seems to give you a headache as you try to comprehend the infinite amount of infinitesimally distinct points on its surface."
- icon_state = "hilbertshotel"
- w_class = WEIGHT_CLASS_SMALL
- resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
- var/datum/map_template/hilbertshotel/hotelRoomTemp
- var/datum/map_template/hilbertshotel/empty/hotelRoomTempEmpty
- var/datum/map_template/hilbertshotel/lore/hotelRoomTempLore
- var/list/activeRooms = list()
- var/list/storedRooms = list()
- var/storageTurf
- //Lore Stuff
- var/ruinSpawned = FALSE
- var/mysteryRoom
+ name = "Hilbert's Hotel"
+ desc = "A sphere of what appears to be an intricate network of bluespace. Observing it in detail seems to give you a headache as you try to comprehend the infinite amount of infinitesimally distinct points on its surface."
+ icon_state = "hilbertshotel"
+ w_class = WEIGHT_CLASS_SMALL
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ var/datum/map_template/hilbertshotel/hotelRoomTemp
+ var/datum/map_template/hilbertshotel/empty/hotelRoomTempEmpty
+ var/datum/map_template/hilbertshotel/lore/hotelRoomTempLore
+ var/list/activeRooms = list()
+ var/list/storedRooms = list()
+ var/storageTurf
+ //Lore Stuff
+ var/ruinSpawned = FALSE
+ var/mysteryRoom
/obj/item/hilbertshotel/Initialize(mapload)
- . = ..()
- //Load templates
- hotelRoomTemp = new()
- hotelRoomTempEmpty = new()
- hotelRoomTempLore = new()
- var/area/currentArea = get_area(src)
- if(currentArea.type == /area/ruin/space/has_grav/hilbertresearchfacility)
- ruinSpawned = TRUE
+ . = ..()
+ //Load templates
+ hotelRoomTemp = new()
+ hotelRoomTempEmpty = new()
+ hotelRoomTempLore = new()
+ var/area/currentArea = get_area(src)
+ if(currentArea.type == /area/ruin/space/has_grav/hilbertresearchfacility)
+ ruinSpawned = TRUE
/obj/item/hilbertshotel/Destroy()
- ejectRooms()
- return ..()
+ ejectRooms()
+ return ..()
/obj/item/hilbertshotel/attack(mob/living/M, mob/living/user)
- if(M.mind)
- to_chat(user, span_notice("You invite [M] to the hotel."))
- promptAndCheckIn(M)
- else
- to_chat(user, span_warning("[M] is not intelligent enough to understand how to use this device!"))
+ if(M.mind)
+ to_chat(user, span_notice("You invite [M] to the hotel."))
+ promptAndCheckIn(M)
+ else
+ to_chat(user, span_warning("[M] is not intelligent enough to understand how to use this device!"))
/obj/item/hilbertshotel/attack_self(mob/user)
- . = ..()
- promptAndCheckIn(user)
+ . = ..()
+ promptAndCheckIn(user)
/obj/item/hilbertshotel/proc/promptAndCheckIn(mob/user)
- var/chosenRoomNumber = input(user, "What number room will you be checking into?", "Room Number") as null|num
- if(!chosenRoomNumber)
- return
- if(chosenRoomNumber > SHORT_REAL_LIMIT)
- to_chat(user, span_warning("You have to check out the first [SHORT_REAL_LIMIT] rooms before you can go to a higher numbered one!"))
- return
- if((chosenRoomNumber < 1) || (chosenRoomNumber != round(chosenRoomNumber)))
- to_chat(user, span_warning("That is not a valid room number!"))
- return
- if(user.incapacitated()) //nuh uh
- to_chat(user, span_warning("[src] slips out of your hand!"))
- return
- if(ismob(loc))
- if(user == loc) //Not always the same as user
- forceMove(get_turf(user))
- if(!storageTurf) //Blame subsystems for not allowing this to be in Initialize
- if(!GLOB.hhStorageTurf)
- var/datum/map_template/hilbertshotelstorage/storageTemp = new()
- var/datum/turf_reservation/storageReservation = SSmapping.RequestBlockReservation(3, 3)
- storageTemp.load(locate(storageReservation.bottom_left_coords[1], storageReservation.bottom_left_coords[2], storageReservation.bottom_left_coords[3]))
- GLOB.hhStorageTurf = locate(storageReservation.bottom_left_coords[1]+1, storageReservation.bottom_left_coords[2]+1, storageReservation.bottom_left_coords[3])
- else
- storageTurf = GLOB.hhStorageTurf
- if(tryActiveRoom(chosenRoomNumber, user))
- return
- if(tryStoredRoom(chosenRoomNumber, user))
- return
- sendToNewRoom(chosenRoomNumber, user)
+ var/chosenRoomNumber = input(user, "What number room will you be checking into?", "Room Number") as null|num
+ if(!chosenRoomNumber)
+ return
+ if(chosenRoomNumber > SHORT_REAL_LIMIT)
+ to_chat(user, span_warning("You have to check out the first [SHORT_REAL_LIMIT] rooms before you can go to a higher numbered one!"))
+ return
+ if((chosenRoomNumber < 1) || (chosenRoomNumber != round(chosenRoomNumber)))
+ to_chat(user, span_warning("That is not a valid room number!"))
+ return
+ if(user.incapacitated()) //nuh uh
+ to_chat(user, span_warning("[src] slips out of your hand!"))
+ return
+ if(ismob(loc))
+ if(user == loc) //Not always the same as user
+ forceMove(get_turf(user))
+ if(!storageTurf) //Blame subsystems for not allowing this to be in Initialize
+ if(!GLOB.hhStorageTurf)
+ var/datum/map_template/hilbertshotelstorage/storageTemp = new()
+ var/datum/turf_reservation/storageReservation = SSmapping.request_turf_block_reservation(1, 1, 1)
+ var/turf/storage_turf = storageReservation.bottom_left_turfs[1]
+ storageTemp.load(storage_turf)
+ GLOB.hhStorageTurf = storage_turf
+ else
+ storageTurf = GLOB.hhStorageTurf
+ if(tryActiveRoom(chosenRoomNumber, user))
+ return
+ if(tryStoredRoom(chosenRoomNumber, user))
+ return
+ sendToNewRoom(chosenRoomNumber, user)
/obj/item/hilbertshotel/proc/tryActiveRoom(roomNumber, mob/user)
- if(activeRooms["[roomNumber]"])
- var/datum/turf_reservation/roomReservation = activeRooms["[roomNumber]"]
- do_sparks(3, FALSE, get_turf(user))
- user.forceMove(locate(roomReservation.bottom_left_coords[1] + hotelRoomTemp.landingZoneRelativeX, roomReservation.bottom_left_coords[2] + hotelRoomTemp.landingZoneRelativeY, roomReservation.bottom_left_coords[3]))
- return TRUE
- return FALSE
+ if(activeRooms["[roomNumber]"])
+ var/datum/turf_reservation/roomReservation = activeRooms["[roomNumber]"]
+ do_sparks(3, FALSE, get_turf(user))
+ var/turf/room_bottom_left = roomReservation.bottom_left_turfs[1]
+ user.forceMove(locate(
+ room_bottom_left.x + hotelRoomTemp.landingZoneRelativeX,
+ room_bottom_left.y + hotelRoomTemp.landingZoneRelativeY,
+ room_bottom_left.z,
+ ))
+ return TRUE
+ return FALSE
/obj/item/hilbertshotel/proc/tryStoredRoom(roomNumber, mob/user)
- if(storedRooms["[roomNumber]"])
- var/datum/turf_reservation/roomReservation = SSmapping.RequestBlockReservation(hotelRoomTemp.width, hotelRoomTemp.height)
- hotelRoomTempEmpty.load(locate(roomReservation.bottom_left_coords[1], roomReservation.bottom_left_coords[2], roomReservation.bottom_left_coords[3]))
- var/turfNumber = 1
- for(var/i=0, i 1)) //no teleporting around if they're dead or moved away during the prompt.
+ return
+ user.forceMove(get_turf(parentSphere))
+ do_sparks(3, FALSE, get_turf(user))
/turf/closed/indestructible/hoteldoor/attack_ghost(mob/dead/observer/user)
- if(!isobserver(user) || !parentSphere)
- return ..()
- user.forceMove(get_turf(parentSphere))
+ if(!isobserver(user) || !parentSphere)
+ return ..()
+ user.forceMove(get_turf(parentSphere))
//If only this could be simplified...
/turf/closed/indestructible/hoteldoor/attack_hand(mob/user)
- promptExit(user)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/attack_animal(mob/user)
- promptExit(user)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/attack_paw(mob/user)
- promptExit(user)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/attack_hulk(mob/living/carbon/human/user, does_attack_animation)
- promptExit(user)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/attack_larva(mob/user)
- promptExit(user)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/attack_slime(mob/user)
- promptExit(user)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/attack_robot(mob/user)
- if(get_dist(get_turf(src), get_turf(user)) <= 1)
- promptExit(user)
+ if(get_dist(get_turf(src), get_turf(user)) <= 1)
+ promptExit(user)
/turf/closed/indestructible/hoteldoor/AltClick(mob/user)
- . = ..()
- if(get_dist(get_turf(src), get_turf(user)) <= 1)
- to_chat(user, span_notice("You peak through the door's bluespace peephole..."))
- user.reset_perspective(parentSphere)
- user.set_machine(src)
- var/datum/action/peepholeCancel/PHC = new
- user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
- PHC.Grant(user)
+ . = ..()
+ if(get_dist(get_turf(src), get_turf(user)) <= 1)
+ to_chat(user, span_notice("You peak through the door's bluespace peephole..."))
+ user.reset_perspective(parentSphere)
+ var/datum/action/peephole_cancel/PHC = new
+ user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
+ PHC.Grant(user)
+ RegisterSignal(user, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/atom/, check_eye), user)
/turf/closed/indestructible/hoteldoor/check_eye(mob/user)
- if(get_dist(get_turf(src), get_turf(user)) >= 2)
- user.unset_machine()
- for(var/datum/action/peepholeCancel/PHC in user.actions)
- PHC.Trigger()
-
-/datum/action/peepholeCancel
- name = "Cancel View"
- desc = "Stop looking through the bluespace peephole."
- button_icon_state = "cancel_peephole"
-
-/datum/action/peepholeCancel/Trigger()
- . = ..()
- to_chat(owner, span_warning("You move away from the peephole."))
- owner.reset_perspective()
- owner.clear_fullscreen("remote_view", 0)
- qdel(src)
+ if(get_dist(get_turf(src), get_turf(user)) >= 2)
+ for(var/datum/action/peephole_cancel/PHC in user.actions)
+ INVOKE_ASYNC(PHC, TYPE_PROC_REF(/datum/action/peephole_cancel, Trigger))
+
+/datum/action/peephole_cancel
+ name = "Cancel View"
+ desc = "Stop looking through the bluespace peephole."
+ button_icon_state = "cancel_peephole"
+
+/datum/action/peephole_cancel/Trigger()
+ . = ..()
+ to_chat(owner, span_warning("You move away from the peephole."))
+ owner.reset_perspective()
+ owner.clear_fullscreen("remote_view", 0)
+ qdel(src)
/area/hilbertshotel
- name = "Hilbert's Hotel Room"
- icon_state = "hilbertshotel"
- requires_power = FALSE
- has_gravity = TRUE
- noteleport = TRUE
- hidden = TRUE
- unique = FALSE
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
- ambientsounds = list('sound/ambience/servicebell.ogg')
- var/roomnumber = 0
- var/obj/item/hilbertshotel/parentSphere
- var/datum/turf_reservation/reservation
- var/turf/storageTurf
+ name = "Hilbert's Hotel Room"
+ icon_state = "hilbertshotel"
+ requires_power = FALSE
+ has_gravity = TRUE
+ noteleport = TRUE
+ hidden = TRUE
+ unique = FALSE
+ static_lighting = TRUE
+ ambientsounds = list('sound/ambience/servicebell.ogg')
+ var/roomnumber = 0
+ var/obj/item/hilbertshotel/parentSphere
+ var/datum/turf_reservation/reservation
+ var/turf/storageTurf
/area/hilbertshotel/Entered(atom/movable/AM)
- . = ..()
- if(istype(AM, /obj/item/hilbertshotel))
- relocate(AM)
- var/list/obj/item/hilbertshotel/hotels = AM.get_all_contents(/obj/item/hilbertshotel)
- for(var/obj/item/hilbertshotel/H in hotels)
- if(parentSphere == H)
- relocate(H)
+ . = ..()
+ if(istype(AM, /obj/item/hilbertshotel))
+ relocate(AM)
+ var/list/obj/item/hilbertshotel/hotels = AM.get_all_contents(/obj/item/hilbertshotel)
+ for(var/obj/item/hilbertshotel/H in hotels)
+ if(parentSphere == H)
+ relocate(H)
/area/hilbertshotel/proc/relocate(obj/item/hilbertshotel/H)
- if(prob(0.135685)) //Because screw you
- qdel(H)
- return
- var/turf/targetturf = find_safe_turf()
- if(!targetturf)
- if(GLOB.blobstart.len > 0)
- targetturf = get_turf(pick(GLOB.blobstart))
- else
- CRASH("Unable to find a blobstart landmark")
- var/turf/T = get_turf(H)
- var/area/A = T.loc
- log_game("[H] entered itself. Moving it to [loc_name(targetturf)].")
- message_admins("[H] entered itself. Moving it to [ADMIN_VERBOSEJMP(targetturf)].")
- for(var/mob/M in A)
- to_chat(M, span_danger("[H] almost implodes in upon itself, but quickly rebounds, shooting off into a random point in space!"))
- H.forceMove(targetturf)
+ if(prob(0.135685)) //Because screw you
+ qdel(H)
+ return
+ var/turf/targetturf = find_safe_turf()
+ if(!targetturf)
+ if(GLOB.blobstart.len > 0)
+ targetturf = get_turf(pick(GLOB.blobstart))
+ else
+ CRASH("Unable to find a blobstart landmark")
+ var/turf/T = get_turf(H)
+ var/area/A = T.loc
+ log_game("[H] entered itself. Moving it to [loc_name(targetturf)].")
+ message_admins("[H] entered itself. Moving it to [ADMIN_VERBOSEJMP(targetturf)].")
+ for(var/mob/M in A)
+ to_chat(M, span_danger("[H] almost implodes in upon itself, but quickly rebounds, shooting off into a random point in space!"))
+ H.forceMove(targetturf)
/area/hilbertshotel/Exited(atom/movable/AM)
- . = ..()
- if(ismob(AM))
- var/mob/M = AM
- if(M.mind)
- var/stillPopulated = FALSE
- var/list/currentLivingMobs = get_all_contents(/mob/living) //Got to catch anyone hiding in anything
- for(var/mob/living/L in currentLivingMobs) //Check to see if theres any sentient mobs left.
- if(L.mind)
- stillPopulated = TRUE
- break
- if(!stillPopulated)
- storeRoom()
+ . = ..()
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.mind)
+ var/stillPopulated = FALSE
+ var/list/currentLivingMobs = get_all_contents(/mob/living) //Got to catch anyone hiding in anything
+ for(var/mob/living/L in currentLivingMobs) //Check to see if theres any sentient mobs left.
+ if(L.mind)
+ stillPopulated = TRUE
+ break
+ if(!stillPopulated)
+ storeRoom()
/area/hilbertshotel/proc/storeRoom()
- var/roomSize = (reservation.top_right_coords[1]-reservation.bottom_left_coords[1]+1)*(reservation.top_right_coords[2]-reservation.bottom_left_coords[2]+1)
- var/storage[roomSize]
- var/turfNumber = 1
- var/obj/item/abstracthotelstorage/storageObj = new(storageTurf)
- storageObj.roomNumber = roomnumber
- storageObj.parentSphere = parentSphere
- storageObj.name = "Room [roomnumber] Storage"
- for(var/i=0, i
I might just be onto something here!
The strange space-warping properties of bluespace have been known about for awhile now, but I might be on the verge of discovering a new way of harnessing it.
It's too soon to say for sure, but this might be the start of something quite important!
- I'll be sure to log any major future breakthroughs. This might be a lot more than I can manage on my own, perhaps I should hire that secretary after all...
+ I'll be sure to log any major future breakthroughs. This might be a lot more than I can manage on my own, perhaps I should hire that secretary after all...
Breakthrough!
I can't believe it, but I did it! Just when I was certain it couldn't be done, I made the final necessary breakthrough.
- Exploiting the effects of space dilation caused by specific bluespace structures combined with a precise use of geometric calculus, I've discovered a way to correlate an infinite amount of space within a finite area!
- While the potential applications are endless, I utilized it in quite a nifty way so far by designing a system that recursively constructs subspace rooms and spatially links them to any of the infinite infinitesimally distinct points on the spheres surface.
- I call it: Hilbert's Hotel!
+ Exploiting the effects of space dilation caused by specific bluespace structures combined with a precise use of geometric calculus, I've discovered a way to correlate an infinite amount of space within a finite area!
+ While the potential applications are endless, I utilized it in quite a nifty way so far by designing a system that recursively constructs subspace rooms and spatially links them to any of the infinite infinitesimally distinct points on the spheres surface.
+ I call it: Hilbert's Hotel!
Goodbye
I can't take this anymore. I know what happens next, and the fear of what is coming leaves me unable to continue working.
- Any fool in my field has heard the stories. It's not that I didn't believe them, it's just... I guess I underestimated the importance of my own research...
- Robert has reported a further increase in frequency of the strange, prying visitors who ask questions they have no business asking. I've requested him to keep everything on strict lockdown and have permanently dismissed all other assistants.
- I've also instructed him to use the encryption method we discussed for any important quantitative data. The poor lad... I don't think he truly understands what he's gotten himself into...
- It's clear what happens now. One day they'll show up uninvited, and claim my research as their own, leaving me as nothing more than a bullet ridden corpse floating in space.
- I can't stick around to the let that happen.
- I'm escaping into the very thing that brought all this trouble to my doorstep in the first place - my hotel.
- I'll be in [uppertext(num2hex(GLOB.hhmysteryRoomNumber, 0))] (That will make sense to anyone who should know)
- I'm sorry that I must go like this. Maybe one day things will be different and it will be safe to return... maybe...
- Goodbye
+ Any fool in my field has heard the stories. It's not that I didn't believe them, it's just... I guess I underestimated the importance of my own research...
+ Robert has reported a further increase in frequency of the strange, prying visitors who ask questions they have no business asking. I've requested him to keep everything on strict lockdown and have permanently dismissed all other assistants.
+ I've also instructed him to use the encryption method we discussed for any important quantitative data. The poor lad... I don't think he truly understands what he's gotten himself into...
+ It's clear what happens now. One day they'll show up uninvited, and claim my research as their own, leaving me as nothing more than a bullet ridden corpse floating in space.
+ I can't stick around to the let that happen.
+ I'm escaping into the very thing that brought all this trouble to my doorstep in the first place - my hotel.
+ I'll be in [uppertext(num2hex(GLOB.hhmysteryRoomNumber, 0))] (That will make sense to anyone who should know)
+ I'm sorry that I must go like this. Maybe one day things will be different and it will be safe to return... maybe...
+ Goodbye
Doctor Hilbert"}
/obj/item/paper/crumpled/robertsworkjournal
- name = "Work Journal"
- info = {"
First Week!
+ name = "Work Journal"
+ info = {"
First Week!
First week on the new job. It's a secretarial position, but hey, whatever pays the bills. Plus it seems like some interesting stuff goes on here.
Doc says its best that I don't openly talk about his research with others, I guess he doesn't want it getting out or something. I've caught myself slipping a few times when talking to others, it's hard not to brag about something this cool!
I'm not really sure why I'm choosing to journal this. Doc seems to log everything. He says it's incase he discovers anything important.
- I guess that's why I'm doing it too, I've always wanted to be a part of something important.
- Here's to a new job and to becoming a part of something important!
+ I guess that's why I'm doing it too, I've always wanted to be a part of something important.
+ Here's to a new job and to becoming a part of something important!
Weird times...
Things are starting to get a little strange around here. Just weeks after Doc's amazing breakthrough, weird visitors have began showing up unannounced, asking strange things about Doc's work.
- I knew Doc wasn't a big fan of company, but even he seemed strangely unnerved when I told him about the visitors.
- He said it's important that from here on out we keep tight security on everything, even other staff members.
- He also said something about securing data, something about hexes. What's that mean? Some sort of curse? Doc never struck me as the magic type...
- He often uses a lot of big sciencey words that I don't really understand, but I kinda dig it, it makes me feel like I'm witnessing something big.
- I hope things go back to normal soon, but I guess that's the price you pay for being a part of something important.
+ I knew Doc wasn't a big fan of company, but even he seemed strangely unnerved when I told him about the visitors.
+ He said it's important that from here on out we keep tight security on everything, even other staff members.
+ He also said something about securing data, something about hexes. What's that mean? Some sort of curse? Doc never struck me as the magic type...
+ He often uses a lot of big sciencey words that I don't really understand, but I kinda dig it, it makes me feel like I'm witnessing something big.
+ I hope things go back to normal soon, but I guess that's the price you pay for being a part of something important.
Last day I guess?
Things are officially starting to get too strange for me.
- The visitors have been coming a lot more often, and they all seem increasingly aggressive and nosey. I'm starting to see why they made Doc so nervous, they're certainly starting to creep me out too.
- Awhile ago Doc started having me keep the place on strict lockdown and requested I refuse entry to anyone else, including previous staff.
- But the weirdest part?
- I haven't seen Doc in days. It's not unusual for him to work continuously for long periods of time in the lab, but when I took a peak in their yesterday - he was nowhere to be seen! I didn't risk prying much further, Doc had a habit of leaving the defense systems on these last few weeks.
- I'm thinking it might be time to call it quits. Can't work much without a boss, plus things are starting to get kind of shady. I wanted to be a part of something important, but you gotta know when to play it safe.
- As my dad always said, "The smart get famous, but the wise survive..."
+ The visitors have been coming a lot more often, and they all seem increasingly aggressive and nosey. I'm starting to see why they made Doc so nervous, they're certainly starting to creep me out too.
+ Awhile ago Doc started having me keep the place on strict lockdown and requested I refuse entry to anyone else, including previous staff.
+ But the weirdest part?
+ I haven't seen Doc in days. It's not unusual for him to work continuously for long periods of time in the lab, but when I took a peak in their yesterday - he was nowhere to be seen! I didn't risk prying much further, Doc had a habit of leaving the defense systems on these last few weeks.
+ I'm thinking it might be time to call it quits. Can't work much without a boss, plus things are starting to get kind of shady. I wanted to be a part of something important, but you gotta know when to play it safe.
+ As my dad always said, "The smart get famous, but the wise survive..."
Robert P."}
/obj/item/paper/crumpled/bloody/docsdeathnote
- name = "note"
- info = {"This is it isn't it?
- No one's coming to help, that much has become clear.
- Sure, it's lonely, but do I have much choice? At least I brought the analyzer with me, they shouldn't be able to find me without it.
- Who knows who's waiting for me out there. Its either die out there in their hands, or die a slower, slightly more comfortable death in here.
- Everyday I can feel myself slipping away more and more, both physically and mentally. Who knows what happens now...
- Heh, so it's true then, this must be the inescapable path of all great minds... so be it then.
-
-
-
- Choose a room, and enter the sphere
- Lay your head to rest, it soon becomes clear
- There's always more room around every bend
- Not all that's countable has an end..."}
+ name = "note"
+ info = {"This is it isn't it?
+ No one's coming to help, that much has become clear.
+ Sure, it's lonely, but do I have much choice? At least I brought the analyzer with me, they shouldn't be able to find me without it.
+ Who knows who's waiting for me out there. Its either die out there in their hands, or die a slower, slightly more comfortable death in here.
+ Everyday I can feel myself slipping away more and more, both physically and mentally. Who knows what happens now...
+ Heh, so it's true then, this must be the inescapable path of all great minds... so be it then.
+
+
+
+ Choose a room, and enter the sphere
+ Lay your head to rest, it soon becomes clear
+ There's always more room around every bend
+ Not all that's countable has an end..."}
diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm
index 64fe1af62370..48f78a7e59d4 100644
--- a/code/modules/shuttle/arrivals.dm
+++ b/code/modules/shuttle/arrivals.dm
@@ -1,10 +1,7 @@
/obj/docking_port/mobile/arrivals
name = "arrivals shuttle"
- id = "arrivals"
+ shuttle_id = "arrivals"
- dwidth = 3
- width = 7
- height = 15
dir = WEST
port_direction = SOUTH
diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm
index 68dfb9704089..b569ec22bfdf 100644
--- a/code/modules/shuttle/assault_pod.dm
+++ b/code/modules/shuttle/assault_pod.dm
@@ -1,6 +1,6 @@
/obj/docking_port/mobile/assault_pod
name = "assault pod"
- id = "steel_rain"
+ shuttle_id = "steel_rain"
dwidth = 3
width = 7
height = 7
@@ -46,7 +46,8 @@
if(!T)
return
var/obj/docking_port/stationary/landing_zone = new /obj/docking_port/stationary(T)
- landing_zone.id = "assault_pod([REF(src)])"
+ landing_zone.shuttle_id = "assault_pod([REF(src)])"
+ landing_zone.port_destinations = "assault_pod([REF(src)])"
landing_zone.name = "Landing Zone"
landing_zone.dwidth = dwidth
landing_zone.dheight = dheight
@@ -56,7 +57,7 @@
for(var/obj/machinery/computer/shuttle/S in GLOB.machines)
if(S.shuttleId == shuttle_id)
- S.possible_destinations = "[landing_zone.id]"
+ S.possible_destinations = "[landing_zone.shuttle_id]"
to_chat(user, "Landing zone set.")
diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm
index b924566f58c3..19dcfd4ee5cd 100644
--- a/code/modules/shuttle/computer.dm
+++ b/code/modules/shuttle/computer.dm
@@ -1,3 +1,11 @@
+#define SHUTTLE_CONSOLE_ACCESSDENIED "accessdenied"
+#define SHUTTLE_CONSOLE_ENDGAME "endgame"
+#define SHUTTLE_CONSOLE_RECHARGING "recharging"
+#define SHUTTLE_CONSOLE_INTRANSIT "intransit"
+#define SHUTTLE_CONSOLE_DESTINVALID "destinvalid"
+#define SHUTTLE_CONSOLE_SUCCESS "success"
+#define SHUTTLE_CONSOLE_ERROR "error"
+
/obj/machinery/computer/shuttle
name = "shuttle console"
desc = "A shuttle control computer."
@@ -15,9 +23,17 @@
var/no_destination_swap = FALSE
/// ID of the currently selected destination of the attached shuttle
var/destination
+ /// If the console controls are locked
+ var/locked = FALSE
+ /// List of head revs who have already clicked through the warning about not using the console
+ var/static/list/dumb_rev_heads = list()
/// Authorization request cooldown to prevent request spam to admin staff
COOLDOWN_DECLARE(request_cooldown)
+/obj/machinery/computer/shuttle/Initialize(mapload)
+ . = ..()
+ connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src))
+
/obj/machinery/computer/shuttle/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
@@ -49,13 +65,13 @@
data["status"] = "Recharging"
else
data["status"] = "In Transit"
- for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
- if(!options.Find(S.id))
+ for(var/obj/docking_port/stationary/S in SSshuttle.stationary_docking_ports)
+ if(!options.Find(S.port_destinations))
continue
if(!M.check_dock(S, silent = TRUE))
continue
var/list/location_data = list(
- id = S.id,
+ id = S.shuttle_id,
name = S.name
)
data["locations"] += list(location_data)
@@ -77,6 +93,69 @@
/obj/machinery/computer/shuttle/proc/launch_check(mob/user)
return TRUE
+/**
+ * Returns a list of currently valid destinations for this shuttle console,
+ * taking into account its list of allowed destinations, their current state, and the shuttle's current location
+**/
+/obj/machinery/computer/shuttle/proc/get_valid_destinations()
+ var/list/destination_list = params2list(possible_destinations)
+ var/obj/docking_port/mobile/mobile_docking_port = SSshuttle.getShuttle(shuttleId)
+ var/obj/docking_port/stationary/current_destination = mobile_docking_port.destination
+ var/list/valid_destinations = list()
+ for(var/obj/docking_port/stationary/stationary_docking_port in SSshuttle.stationary_docking_ports)
+ if(!destination_list.Find(stationary_docking_port.port_destinations))
+ continue
+ if(!mobile_docking_port.check_dock(stationary_docking_port, silent = TRUE))
+ continue
+ if(stationary_docking_port == current_destination)
+ continue
+ var/list/location_data = list(
+ id = stationary_docking_port.shuttle_id,
+ name = stationary_docking_port.name
+ )
+ valid_destinations += list(location_data)
+ return valid_destinations
+
+/**
+ * Attempts to send the linked shuttle to dest_id, checking various sanity checks to see if it can move or not
+ *
+ * Arguments:
+ * * dest_id - The ID of the stationary docking port to send the shuttle to
+ * * user - The mob that used the console
+ */
+/obj/machinery/computer/shuttle/proc/send_shuttle(dest_id, mob/user)
+ if(!launch_check(user))
+ return SHUTTLE_CONSOLE_ACCESSDENIED
+ var/obj/docking_port/mobile/shuttle_port = SSshuttle.getShuttle(shuttleId)
+ if(shuttle_port.launch_status == ENDGAME_LAUNCHED)
+ return SHUTTLE_CONSOLE_ENDGAME
+ if(no_destination_swap)
+ if(shuttle_port.mode == SHUTTLE_RECHARGING)
+ return SHUTTLE_CONSOLE_RECHARGING
+ if(shuttle_port.mode != SHUTTLE_IDLE)
+ return SHUTTLE_CONSOLE_INTRANSIT
+ //check to see if the dest_id passed from tgui is actually a valid destination
+ var/list/dest_list = get_valid_destinations()
+ var/validdest = FALSE
+ for(var/list/dest_data in dest_list)
+ if(dest_data["id"] == dest_id)
+ validdest = TRUE //Found our destination, we can skip ahead now
+ break
+ if(!validdest) //Didn't find our destination in the list of valid destinations, something bad happening
+ if(!isnull(user.client))
+ log_admin("Warning: possible href exploit by [key_name(user)] - Attempted to dock [src] to illegal target location \"[url_encode(dest_id)]\"")
+ message_admins("Warning: possible href exploit by [key_name_admin(user)] [ADMIN_FLW(user)] - Attempted to dock [src] to illegal target location \"[url_encode(dest_id)]\"")
+ else
+ stack_trace("[user] ([user.type]) tried to send the shuttle [src] to the target location [dest_id], but the target location was not found in the list of valid destinations.")
+ return SHUTTLE_CONSOLE_DESTINVALID
+ switch(SSshuttle.moveShuttle(shuttleId, dest_id, TRUE))
+ if(DOCKING_SUCCESS)
+ say("Shuttle departing. Please stand away from the doors.")
+ log_shuttle("[key_name(user)] has sent shuttle \"[shuttleId]\" towards \"[dest_id]\", using [src].")
+ return SHUTTLE_CONSOLE_SUCCESS
+ else
+ return SHUTTLE_CONSOLE_ERROR
+
/obj/machinery/computer/shuttle/ui_act(action, params)
. = ..()
if(.)
@@ -135,6 +214,22 @@
to_chat(user, span_notice("You fried the consoles ID checking system."))
return TRUE
-/obj/machinery/computer/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
- if(port && (shuttleId == initial(shuttleId) || override))
- shuttleId = port.id
+/obj/machinery/computer/shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ if(!mapload)
+ return
+ if(!port)
+ return
+ //Remove old custom port id and ";;"
+ var/find_old = findtextEx(possible_destinations, "[shuttleId]_custom")
+ if(find_old)
+ possible_destinations = replacetext(replacetextEx(possible_destinations, "[shuttleId]_custom", ""), ";;", ";")
+ shuttleId = port.shuttle_id
+ possible_destinations += ";[port.shuttle_id]_custom"
+
+#undef SHUTTLE_CONSOLE_ACCESSDENIED
+#undef SHUTTLE_CONSOLE_ENDGAME
+#undef SHUTTLE_CONSOLE_RECHARGING
+#undef SHUTTLE_CONSOLE_INTRANSIT
+#undef SHUTTLE_CONSOLE_DESTINVALID
+#undef SHUTTLE_CONSOLE_SUCCESS
+#undef SHUTTLE_CONSOLE_ERROR
diff --git a/code/modules/shuttle/custom_shuttle.dm b/code/modules/shuttle/custom_shuttle.dm
index 014edf3e363a..23559483ed53 100644
--- a/code/modules/shuttle/custom_shuttle.dm
+++ b/code/modules/shuttle/custom_shuttle.dm
@@ -50,8 +50,8 @@
dat += "Fuel Consumption: [calculated_consumption]units per distance "
dat += "Engine Cooldown: [calculated_cooldown]s"
var/destination_found
- for(var/obj/docking_port/stationary/S in SSshuttle.stationary)
- if(!options.Find(S.id))
+ for(var/obj/docking_port/stationary/S in SSshuttle.stationary_docking_ports)
+ if(!options.Find(S.port_destinations))
continue
if(!M.check_dock(S, silent=TRUE))
continue
@@ -59,7 +59,7 @@
break
destination_found = TRUE
var/dist = round(calculateDistance(S))
- dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)]) "
+ dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)]) "
if(!destination_found)
dat += "No valid destinations "
dat += "[targetLocation ? "Target Location : [targetLocation]" : "No Target Location"]"
@@ -218,9 +218,17 @@
to_chat(usr, "Unable to comply.")
return
-/obj/machinery/computer/custom_shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
- if(port && (shuttleId == initial(shuttleId) || override))
- linkShuttle(port.id)
+/obj/machinery/computer/custom_shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+ if(!mapload)
+ return
+ if(!port)
+ return
+ //Remove old custom port id and ";;"
+ var/find_old = findtextEx(possible_destinations, "[shuttleId]_custom")
+ if(find_old)
+ possible_destinations = replacetext(replacetextEx(possible_destinations, "[shuttleId]_custom", ""), ";;", ";")
+ shuttleId = port.shuttle_id
+ possible_destinations += ";[port.shuttle_id]_custom"
//Custom shuttle docker locations
/obj/machinery/computer/camera_advanced/shuttle_docker/custom
@@ -233,7 +241,7 @@
/turf/open/floor/plating/ashplanet,
/turf/open/floor/plating/asteroid,
/turf/open/floor/plating/lavaland_baseturf)
- jumpto_ports = list("whiteship_home" = 1)
+ jump_to_ports = list("whiteship_home" = 1)
view_range = 12
designate_time = 100
circuit = /obj/item/circuitboard/computer/shuttle/docker
diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/docking.dm
index 064a424bb7d2..7c092eef3d73 100644
--- a/code/modules/shuttle/docking.dm
+++ b/code/modules/shuttle/docking.dm
@@ -46,7 +46,7 @@
rotation = SIMPLIFY_DEGREES(rotation)
if(!movement_direction)
- movement_direction = turn(preferred_direction, 180)
+ movement_direction = REVERSE_DIR(preferred_direction)
var/list/moved_atoms = list() //Everything not a turf that gets moved in the shuttle
var/list/areas_to_move = list() //unique assoc list of areas on turfs being moved
@@ -103,7 +103,7 @@
return DOCKING_SUCCESS
/obj/docking_port/mobile/proc/preflight_check(list/old_turfs, list/new_turfs, list/areas_to_move, rotation)
- for(var/i in 1 to old_turfs.len)
+ for(var/i in 1 to length(old_turfs))
CHECK_TICK
var/turf/oldT = old_turfs[i]
var/turf/newT = new_turfs[i]
@@ -121,7 +121,6 @@
continue
move_mode = moving_atom.beforeShuttleMove(newT, rotation, move_mode, src) //atoms
- oldT.beforeShuttleMove(newT)
move_mode = oldT.fromShuttleMove(newT, move_mode) //turfs
move_mode = newT.toShuttleMove(oldT, move_mode, src) //turfs
@@ -131,28 +130,29 @@
old_turfs[oldT] = move_mode
/obj/docking_port/mobile/proc/takeoff(list/old_turfs, list/new_turfs, list/moved_atoms, rotation, movement_direction, old_dock, area/underlying_old_area)
-
for(var/i in 1 to old_turfs.len)
var/turf/oldT = old_turfs[i]
var/turf/newT = new_turfs[i]
var/move_mode = old_turfs[oldT]
+
+ if(move_mode & MOVE_TURF)
+ oldT.onShuttleMove(newT, movement_force, movement_direction) //turfs
+
+ if(move_mode & MOVE_AREA)
+ var/area/shuttle_area = oldT.loc
+ shuttle_area.onShuttleMove(oldT, newT, underlying_old_area) //areas
+
if(move_mode & MOVE_CONTENTS)
for(var/k in oldT)
var/atom/movable/moving_atom = k
if(moving_atom.loc != oldT) //fix for multi-tile objects
continue
- moving_atom.onShuttleMove(newT, oldT, movement_force, movement_direction, old_dock, src) //atoms
+ moving_atom.onShuttleMove(newT, oldT, movement_force, movement_direction, old_dock, src) //atoms
moved_atoms[moving_atom] = oldT
- if(move_mode & MOVE_TURF)
- oldT.onShuttleMove(newT, movement_force, movement_direction) //turfs
-
- if(move_mode & MOVE_AREA)
- var/area/shuttle_area = oldT.loc
- shuttle_area.onShuttleMove(oldT, newT, underlying_old_area) //areas
/obj/docking_port/mobile/proc/cleanup_runway(obj/docking_port/stationary/new_dock, list/old_turfs, list/new_turfs, list/areas_to_move, list/moved_atoms, rotation, movement_direction, area/underlying_old_area)
- underlying_old_area.afterShuttleMove()
+ underlying_old_area.afterShuttleMove(0)
// Parallax handling
// This needs to be done before the atom after move
@@ -162,15 +162,27 @@
for(var/i in 1 to areas_to_move.len)
CHECK_TICK
var/area/internal_area = areas_to_move[i]
- internal_area.afterShuttleMove(new_parallax_dir) //areas
+ internal_area.afterShuttleMove(new_parallax_dir) //areas
for(var/i in 1 to old_turfs.len)
CHECK_TICK
if(!(old_turfs[old_turfs[i]] & MOVE_TURF))
continue
- var/turf/oldT = old_turfs[i]
- var/turf/newT = new_turfs[i]
- newT.afterShuttleMove(oldT, rotation) //turfs
+ var/turf/old_turf = old_turfs[i]
+ var/turf/new_turf = new_turfs[i]
+ new_turf.afterShuttleMove(old_turf, rotation) //turfs
+ var/turf/new_ceiling = get_step_multiz(new_turf, UP) // check if a ceiling is needed
+ if(new_ceiling)
+ // generate ceiling
+ if(istype(new_ceiling, /turf/open/openspace)) // why is this needed? because we have 2 different typepaths for openspace
+ new_ceiling.ChangeTurf(/turf/open/floor/engine/hull/ceiling, list(/turf/open/openspace))
+ else if (istype(new_ceiling, /turf/open/space/openspace))
+ new_ceiling.ChangeTurf(/turf/open/floor/engine/hull/ceiling, list(/turf/open/space/openspace))
+ var/turf/old_ceiling = get_step_multiz(old_turf, UP)
+ if(old_ceiling && istype(old_ceiling, /turf/open/floor/engine/hull/ceiling)) // check if a ceiling was generated previously
+ // remove old ceiling
+ var/turf/open/floor/engine/hull/ceiling/old_shuttle_ceiling = old_ceiling
+ old_shuttle_ceiling.ChangeTurf(old_shuttle_ceiling.old_turf_type)
for(var/i in 1 to moved_atoms.len)
CHECK_TICK
@@ -204,10 +216,3 @@
continue
var/turf/oldT = moved_atoms[moved_object]
moved_object.lateShuttleMove(oldT, movement_force, movement_direction)
-
-/obj/docking_port/mobile/proc/reset_air()
- var/list/turfs = return_ordered_turfs(x, y, z, dir)
- for(var/i in 1 to length(turfs))
- var/turf/open/T = turfs[i]
- if(istype(T))
- T.air.copy_from_turf(T)
diff --git a/code/modules/shuttle/elevator.dm b/code/modules/shuttle/elevator.dm
index de5d88ee17c9..73d16d11708e 100644
--- a/code/modules/shuttle/elevator.dm
+++ b/code/modules/shuttle/elevator.dm
@@ -1,6 +1,6 @@
/obj/docking_port/mobile/elevator
name = "elevator"
- id = "elevator"
+ shuttle_id = "elevator"
dwidth = 3
width = 7
height = 7
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 5fcbabf67bd0..fb1d3d34c1ed 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -199,15 +199,16 @@
/obj/docking_port/mobile/emergency
name = "emergency shuttle"
- id = "emergency"
-
- dwidth = 9
- width = 22
- height = 11
+ shuttle_id = "emergency"
dir = EAST
port_direction = WEST
var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
+/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
@@ -234,7 +235,7 @@
set_coefficient = 1
else
set_coefficient = 0.5
- var/call_time = SSshuttle.emergencyCallTime * set_coefficient * engine_coeff
+ var/call_time = SSshuttle.emergency_call_time * set_coefficient * engine_coeff
switch(mode)
// The shuttle can not normally be called while "recalling", so
// if this proc is called, it's via admin fiat
@@ -247,28 +248,28 @@
SSshuttle.emergencyCallAmount++
if(prob(70))
- SSshuttle.emergencyLastCallLoc = signalOrigin
+ SSshuttle.emergency_last_call_loc = signalOrigin
else
- SSshuttle.emergencyLastCallLoc = null
+ SSshuttle.emergency_last_call_loc = null
var/emergency_reason = "\nNature of emergency:\n\n[reason]"
- priority_announce("The emergency shuttle has been called. [GLOB.security_level >= SEC_LEVEL_RED ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [timeLeft(600)] minutes.[html_decode(emergency_reason)][SSshuttle.emergencyLastCallLoc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ]", null, ANNOUNCER_SHUTTLECALLED, "Priority")
+ priority_announce("The emergency shuttle has been called. [GLOB.security_level >= SEC_LEVEL_RED ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [timeLeft(600)] minutes.[html_decode(emergency_reason)][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ]", null, ANNOUNCER_SHUTTLECALLED, "Priority")
/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
if(mode != SHUTTLE_CALL)
return
- if(SSshuttle.emergencyNoRecall)
+ if(SSshuttle.emergency_no_recall)
return
invertTimer()
mode = SHUTTLE_RECALL
if(prob(70))
- SSshuttle.emergencyLastCallLoc = signalOrigin
+ SSshuttle.emergency_last_call_loc = signalOrigin
else
- SSshuttle.emergencyLastCallLoc = null
+ SSshuttle.emergency_last_call_loc = null
- priority_announce("The emergency shuttle has been recalled.[SSshuttle.emergencyLastCallLoc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", null, ANNOUNCER_SHUTTLERECALLED, "Priority")
+ priority_announce("The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]", null, ANNOUNCER_SHUTTLERECALLED, "Priority")
/obj/docking_port/mobile/emergency/proc/is_hijacked()
var/has_people = FALSE
@@ -360,7 +361,7 @@
setTimer(20)
return
mode = SHUTTLE_DOCKED
- setTimer(SSshuttle.emergencyDockTime)
+ setTimer(SSshuttle.emergency_dock_time)
send2irc("Server", "The Emergency Shuttle ([name]) has docked with the station.") // yogs - make it say the name of the shuttle
priority_announce("[SSshuttle.emergency] has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, ANNOUNCER_SHUTTLEDOCK, "Priority")
ShuttleDBStuff()
@@ -372,7 +373,7 @@
SSshuttle.checkHostileEnvironment()
if(mode == SHUTTLE_STRANDED)
return
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
M.check_transit_zone()
@@ -384,7 +385,7 @@
return
success &= (check_transit_zone() == TRANSIT_READY)
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
if(M.launch_status == UNLAUNCHED)
success &= (M.check_transit_zone() == TRANSIT_READY)
@@ -398,9 +399,9 @@
areas += E
hyperspace_sound(HYPERSPACE_WARMUP, areas)
- if(time_left <= 0 && !SSshuttle.emergencyNoEscape)
+ if(time_left <= 0 && !SSshuttle.emergency_no_escape)
//move each escape pod (or applicable spaceship) to its corresponding transit dock
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
M.on_emergency_launch()
@@ -412,7 +413,7 @@
enterTransit()
mode = SHUTTLE_ESCAPE
launch_status = ENDGAME_LAUNCHED
- setTimer(SSshuttle.emergencyEscapeTime * engine_coeff)
+ setTimer(SSshuttle.emergency_escape_time * engine_coeff)
priority_announce("The Emergency Shuttle has left the station. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority")
if(SHUTTLE_STRANDED)
@@ -433,7 +434,7 @@
break
if(area_parallax)
parallax_slowdown()
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
if(M.launch_status == ENDGAME_LAUNCHED)
if(istype(M, /obj/docking_port/mobile/pod))
@@ -441,7 +442,7 @@
if(time_left <= 0)
//move each escape pod to its corresponding escape dock
- for(var/A in SSshuttle.mobile)
+ for(var/A in SSshuttle.mobile_docking_ports)
var/obj/docking_port/mobile/M = A
M.on_emergency_dock()
@@ -465,20 +466,17 @@
mode = SHUTTLE_ESCAPE
launch_status = ENDGAME_LAUNCHED
- setTimer(SSshuttle.emergencyEscapeTime)
+ setTimer(SSshuttle.emergency_escape_time)
priority_announce("The Emergency Shuttle preparing for direct jump. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority")
/obj/docking_port/mobile/pod
name = "escape pod"
- id = "pod"
- dwidth = 1
- width = 3
- height = 4
+ shuttle_id = "pod"
launch_status = UNLAUNCHED
/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S)
- var/obj/machinery/computer/shuttle/C = getControlConsole()
+ var/obj/machinery/computer/shuttle/C = get_control_console()
if(!istype(C, /obj/machinery/computer/shuttle/pod))
return ..()
if(GLOB.security_level >= SEC_LEVEL_RED || (C && (C.obj_flags & EMAGGED)))
@@ -512,31 +510,45 @@
ENABLE_BITFIELD(obj_flags, EMAGGED)
to_chat(user, span_warning("You fry the pod's alert level checking system."))
-/obj/machinery/computer/shuttle/pod/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE)
+/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
. = ..()
- if(possible_destinations == initial(possible_destinations) || override)
- possible_destinations = "pod_lavaland[idnum]"
+ if(port)
+ //Checks if the computer has already added the shuttle destination with the initial id
+ //This has to be done because connect_to_shuttle is called again after its ID is updated
+ //due to conflicting id names
+ var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland"
+ var/shuttle_destination = ";[port.shuttle_id]_lavaland"
+
+ var/position = findtext(possible_destinations, base_shuttle_destination)
+ if(position)
+ if(base_shuttle_destination == shuttle_destination)
+ return
+ possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination)
+ return
+
+ possible_destinations += shuttle_destination
/obj/docking_port/stationary/random
name = "escape pod"
- id = "pod"
- dwidth = 1
- width = 3
- height = 4
- var/areacheck = /area/lavaland/surface/outdoors
+ shuttle_id = "pod"
+ hidden = TRUE
+ override_can_dock_checks = TRUE
+ /// The area the pod tries to land at
+ var/target_area = /area/lavaland/surface/outdoors
+ /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
var/edge_distance = 16
- // Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
-
-/obj/docking_port/stationary/random/icemoon
- areacheck = /area/icemoon/surface/outdoors/unexplored/danger
/obj/docking_port/stationary/random/Initialize(mapload)
. = ..()
if(!mapload)
return
- var/list/turfs = get_area_turfs(areacheck)
+ var/list/turfs = get_area_turfs(target_area)
var/original_len = turfs.len
+ //YOGS EDIT
+ if(!original_len)
+ return INITIALIZE_HINT_QDEL // we clearly havent loaded lavaland, and there is no pretty way to do this with jungleland, temporary fix for now at least
+ //YOGS END
while(turfs.len)
var/turf/T = pick(turfs)
if(T.x 1)
- if(id == initial(id))
- id = "[id][idnum]"
- if(name == initial(name))
- name = "[name] [idnum]"
- for(var/place in shuttle_areas)
- var/area/area = place
- area.connect_to_shuttle(src, dock, idnum, FALSE)
- for(var/each in place)
- var/atom/atom = each
- atom.connect_to_shuttle(src, dock, idnum, FALSE)
-
+// Called after the shuttle is loaded from template, so we make sure they know it's from mapload.
+/obj/docking_port/mobile/proc/linkup(obj/docking_port/stationary/dock)
+ for(var/area/place as anything in shuttle_areas)
+ place.connect_to_shuttle(TRUE, src, dock)
+ for(var/atom/individual_atoms in place)
+ individual_atoms.connect_to_shuttle(TRUE, src, dock)
//this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact
/obj/docking_port/mobile/proc/canMove()
return TRUE
-//this is to check if this shuttle can physically dock at dock S
-/obj/docking_port/mobile/proc/canDock(obj/docking_port/stationary/S)
- if(!istype(S))
+//this is to check if this shuttle can physically dock at dock stationary_dock
+/obj/docking_port/mobile/proc/canDock(obj/docking_port/stationary/stationary_dock)
+ if(!istype(stationary_dock))
return SHUTTLE_NOT_A_DOCKING_PORT
- if(istype(S, /obj/docking_port/stationary/transit))
+ if(stationary_dock.override_can_dock_checks)
return SHUTTLE_CAN_DOCK
- if(dwidth > S.dwidth)
+ if(dwidth > stationary_dock.dwidth)
return SHUTTLE_DWIDTH_TOO_LARGE
- if(width-dwidth > S.width-S.dwidth)
+ if(width-dwidth > stationary_dock.width-stationary_dock.dwidth)
return SHUTTLE_WIDTH_TOO_LARGE
- if(dheight > S.dheight)
+ if(dheight > stationary_dock.dheight)
return SHUTTLE_DHEIGHT_TOO_LARGE
- if(height-dheight > S.height-S.dheight)
+ if(height-dheight > stationary_dock.height-stationary_dock.dheight)
return SHUTTLE_HEIGHT_TOO_LARGE
//check the dock isn't occupied
- var/currently_docked = S.get_docked()
+ var/currently_docked = stationary_dock.get_docked()
if(currently_docked)
// by someone other than us
if(currently_docked != src)
@@ -391,14 +676,13 @@
return SHUTTLE_CAN_DOCK
-/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent=FALSE)
+/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE)
var/status = canDock(S)
if(status == SHUTTLE_CAN_DOCK)
return TRUE
else
if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error
- var/msg = "Shuttle [src] cannot dock at [S], error: [status]"
- message_admins(msg)
+ message_admins("Shuttle [src] cannot dock at [S], error: [status]")
// We're already docked there, don't need to do anything.
// Triggering shuttle movement code in place is weird
return FALSE
@@ -446,7 +730,7 @@
mode = SHUTTLE_RECALL
/obj/docking_port/mobile/proc/enterTransit()
- if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
+ if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
mode = SHUTTLE_IDLE
return
previous = null
@@ -457,15 +741,14 @@
var/obj/docking_port/stationary/S1 = assigned_transit
if(S1)
if(initiate_docking(S1) != DOCKING_SUCCESS)
- WARNING("shuttle \"[id]\" could not enter transit space. Docked at [S0 ? S0.id : "null"]. Transit dock [S1 ? S1.id : "null"].")
+ WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].")
else if(S0)
if(S0.delete_after)
qdel(S0, TRUE)
else
previous = S0
else
- WARNING("shuttle \"[id]\" could not enter transit space. S0=[S0 ? S0.id : "null"] S1=[S1 ? S1.id : "null"]")
-
+ WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]")
/obj/docking_port/mobile/proc/jumpToNullSpace()
// Destroys the docking port and the shuttle contents.
@@ -488,19 +771,13 @@
var/turf/oldT = old_turfs[i]
if(!oldT || !istype(oldT.loc, area_type))
continue
- var/area/old_area = oldT.loc
- old_area.turfs_to_uncontain += oldT
- underlying_area.contents += oldT
- underlying_area.contained_turfs += oldT
- oldT.change_area(old_area, underlying_area)
+ oldT.change_area(oldT.loc, underlying_area)
oldT.empty(FALSE)
- // Here we locate the bottomost shuttle boundary and remove all turfs above it
- var/list/baseturf_cache = oldT.baseturfs
- for(var/k in 1 to length(baseturf_cache))
- if(ispath(baseturf_cache[k], /turf/baseturf_skipover/shuttle))
- oldT.ScrapeAway(baseturf_cache.len - k + 1)
- break
+ // Here we locate the bottommost shuttle boundary and remove all turfs above it
+ var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)
+ if (!isnull(shuttle_tile_depth))
+ oldT.ScrapeAway(shuttle_tile_depth)
qdel(src, force=TRUE)
@@ -533,14 +810,10 @@
var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)
var/list/ripple_turfs = list()
-
- for(var/i in 1 to L0.len)
+ var/stop = min(L0.len, L1.len)
+ for(var/i in 1 to stop)
var/turf/T0 = L0[i]
var/turf/T1 = L1[i]
- if(!T0 || !T1)
- continue // out of bounds
- if(T0.type == T0.baseturfs)
- continue // indestructible
if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit))
continue // not part of the shuttle
ripple_turfs += T1
@@ -558,16 +831,10 @@
else
. = null
-/obj/effect/landmark/shuttle_import
- name = "Shuttle Import"
-
-// Never move the shuttle import landmark, otherwise things get WEIRD
-/obj/effect/landmark/shuttle_import/onShuttleMove()
- return FALSE
-
//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()
@@ -701,11 +968,13 @@
return "RCH"
if(SHUTTLE_PREARRIVAL)
return "LDN"
+ if(SHUTTLE_DISABLED)
+ return "DIS"
return ""
// returns 5-letter timer string, used by status screens and mob status panel
/obj/docking_port/mobile/proc/getTimerStr()
- if(mode == SHUTTLE_STRANDED)
+ if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED)
return "--:--"
var/timeleft = timeLeft()
@@ -759,22 +1028,23 @@
else
dst = destination
if(dst)
- . = "(transit to) [dst.name || dst.id]"
+ . = "(transit to) [dst.name || dst.shuttle_id]"
else
. = "(transit to) nowhere"
else if(dockedAt)
- . = dockedAt.name || dockedAt.id
+ . = dockedAt.name || dockedAt.shuttle_id
else
. = "unknown"
// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle
-/obj/docking_port/mobile/proc/getControlConsole()
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- for(var/obj/machinery/computer/shuttle/S in shuttle_area)
- if(S.shuttleId == id)
- return S
+/obj/docking_port/mobile/proc/get_control_console()
+ for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
+ var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area
+ if(!shuttle_computer)
+ continue
+ if(shuttle_computer.shuttleId == shuttle_id)
+ return shuttle_computer
return null
/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
@@ -794,31 +1064,32 @@
var/range = (engine_coeff * max(width, height))
var/long_range = range * 2.5
var/atom/distant_source
- if(engine_list[1])
+ if(engine_list.len)
distant_source = engine_list[1]
else
- for(var/A in areas)
- distant_source = locate(/obj/machinery/door) in A
+ for(var/our_area in areas)
+ distant_source = locate(/obj/machinery/door) in our_area
if(distant_source)
break
- if(distant_source)
- for(var/mob/M in SSmobs.clients_by_zlevel[z])
- var/dist_far = get_dist(M, distant_source)
- if(dist_far <= long_range && dist_far > range)
- M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff_exponent = 20)
- else if(dist_far <= range)
- var/source
- if(engine_list.len == 0)
- source = distant_source
- else
- var/closest_dist = 10000
- for(var/obj/O in engine_list)
- var/dist_near = get_dist(M, O)
- if(dist_near < closest_dist)
- source = O
- closest_dist = dist_near
- M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff_exponent = range / 2)
+ if(!distant_source)
+ return
+ for(var/mob/M in SSmobs.clients_by_zlevel[z])
+ var/dist_far = get_dist(M, distant_source)
+ if(dist_far <= long_range && dist_far > range)
+ M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff_exponent = 20)
+ else if(dist_far <= range)
+ var/source
+ if(engine_list.len == 0)
+ source = distant_source
+ else
+ var/closest_dist = 10000
+ for(var/obj/O in engine_list)
+ var/dist_near = get_dist(M, O)
+ if(dist_near < closest_dist)
+ source = O
+ closest_dist = dist_near
+ M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff_exponent = range / 2)
// Losing all initial engines should get you 2
// Adding another set of engines at 0.5 time
@@ -873,8 +1144,7 @@
return TRUE
if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
return FALSE
- else
- return FALSE // hmm
+ return FALSE // hmm
/obj/docking_port/mobile/emergency/in_flight()
switch(mode)
@@ -882,9 +1152,7 @@
return TRUE
if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME)
return FALSE
- else
- return ..()
-
+ return ..()
//Called when emergency shuttle leaves the station
/obj/docking_port/mobile/proc/on_emergency_launch()
@@ -903,7 +1171,7 @@
/obj/docking_port/mobile/pod/on_emergency_dock()
if(launch_status == ENDGAME_LAUNCHED)
- initiate_docking(SSshuttle.getDock("[id]_away")) //Escape pods dock at centcom
+ initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom
mode = SHUTTLE_ENDGAME
/obj/docking_port/mobile/emergency/on_emergency_dock()
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
index 002da36e162c..eff2ba771126 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
@@ -188,8 +188,8 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
stationary_port.name = "[recorded_shuttle_area.name] Custom Shuttle construction site"
port.callTime = 50
port.dir = 1 //Point away from space.
- port.id = "custom_[GLOB.custom_shuttle_count]"
- linkedShuttleId = port.id
+ port.shuttle_id = "custom_[GLOB.custom_shuttle_count]"
+ linkedShuttleId = port.shuttle_id
port.ignitionTime = 25
port.port_direction = 2
port.preferred_direction = EAST
@@ -283,7 +283,6 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
//Yogs End
newS = new /area/shuttle/custom/powered()
newS.setup(str)
- newS.set_dynamic_lighting()
//Shuttles always have gravity
newS.has_gravity = TRUE
newS.requires_power = TRUE
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm
index 6c130af91c45..d4bd63f3f7a5 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm
@@ -2,7 +2,7 @@
/datum/action/innate/shuttle_creator
button_icon = 'icons/mob/actions/actions_shuttle.dmi'
var/mob/living/C
- var/mob/camera/aiEye/remote/shuttle_creation/remote_eye
+ var/mob/camera/ai_eye/remote/shuttle_creation/remote_eye
var/obj/item/shuttle_creator/shuttle_creator
/datum/action/innate/shuttle_creator/Activate()
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm
index 096384aae6e2..2de698d485a8 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm
@@ -15,7 +15,7 @@
user.unset_machine()
/obj/machinery/computer/camera_advanced/shuttle_creator/CreateEye()
- eyeobj = new /mob/camera/aiEye/remote/shuttle_creation(get_turf(owner_rsd))
+ eyeobj = new /mob/camera/ai_eye/remote/shuttle_creation(get_turf(owner_rsd))
eyeobj.origin = src
eyeobj.use_static = FALSE
@@ -78,13 +78,13 @@
eyeobj.eye_initialized = TRUE
give_eye_control(L)
eyeobj.setLoc(camera_location)
- var/mob/camera/aiEye/remote/shuttle_creation/shuttle_eye = eyeobj
+ var/mob/camera/ai_eye/remote/shuttle_creation/shuttle_eye = eyeobj
shuttle_eye.source_turf = get_turf(user)
else
user.unset_machine()
else
var/camera_location = get_turf(owner_rsd)
- var/mob/camera/aiEye/remote/shuttle_creation/eye = eyeobj
+ var/mob/camera/ai_eye/remote/shuttle_creation/eye = eyeobj
give_eye_control(L)
if(camera_location)
eye.source_turf = camera_location
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm
index f8c60e4eb9de..ae1f0207495c 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm
@@ -1,5 +1,5 @@
//===============Camera Eye================
-/mob/camera/aiEye/remote/shuttle_creation
+/mob/camera/ai_eye/remote/shuttle_creation
name = "shuttle holo-drone"
icon = 'icons/obj/mining.dmi'
icon_state = "construction_drone"
@@ -8,18 +8,19 @@
var/turf/source_turf
var/max_range = 12
-/mob/camera/aiEye/remote/shuttle_creation/Initialize(mapload)
+/mob/camera/ai_eye/remote/shuttle_creation/Initialize(mapload)
. = ..()
setLoc(get_turf(source_turf))
icon_state = "construction_drone"
-/mob/camera/aiEye/remote/shuttle_creation/update_remote_sight(mob/living/user)
- user.sight = BLIND|SEE_TURFS
- user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- user.sync_lighting_plane_alpha()
+/mob/camera/ai_eye/remote/shuttle_creation/update_remote_sight(mob/living/user)
+ user.set_sight(BLIND|SEE_TURFS)
+ // Pale blue, should look nice I think
+ user.lighting_color_cutoffs = list(30, 40, 50)
+ user.sync_lighting_plane_cutoff()
return TRUE
-/mob/camera/aiEye/remote/shuttle_creation/relaymove(mob/user, direct)
+/mob/camera/ai_eye/remote/shuttle_creation/relaymove(mob/user, direct)
dir = direct //This camera eye is visible as a drone, and needs to keep the dir updated
var/initial = initial(sprint)
var/max_sprint = 50
@@ -38,18 +39,18 @@
else
sprint = initial
-/mob/camera/aiEye/remote/shuttle_creation/proc/can_move_to(turf/T)
+/mob/camera/ai_eye/remote/shuttle_creation/proc/can_move_to(turf/T)
var/origin_x = source_turf.x
var/origin_y = source_turf.y
var/change_X = abs(origin_x - T.x)
var/change_Y = abs(origin_y - T.y)
return (change_X < max_range && change_Y < max_range)
-/mob/camera/aiEye/remote/shuttle_creation/setLoc(T)
+/mob/camera/ai_eye/remote/shuttle_creation/setLoc(turf/destination, force_update = FALSE)
..()
if(eye_user?.client)
eye_user.client.images -= user_image
var/image/I = image(icon, loc, icon_state, FLY_LAYER, dir)
- I.plane = MASSIVE_OBJ_LAYER
+ I.plane = MASSIVE_OBJ_PLANE
user_image = I
eye_user.client.images += user_image
diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/shuttle_rotate.dm
index 958c9aa5c5ab..eca98c24c07b 100644
--- a/code/modules/shuttle/shuttle_rotate.dm
+++ b/code/modules/shuttle/shuttle_rotate.dm
@@ -12,8 +12,8 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
setDir(angle2dir(rotation+dir2angle(dir)))
//resmooth if need be.
- if(smooth && (params & ROTATE_SMOOTH))
- queue_smooth(src)
+ if(params & ROTATE_SMOOTH && smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
+ QUEUE_SMOOTH(src)
//rotate the pixel offsets too.
if((pixel_x || pixel_y) && (params & ROTATE_OFFSET))
@@ -25,6 +25,20 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
pixel_x = oldPY
pixel_y = (oldPX*(-1))
+/************************************Base /atom/movable proc************************************/
+
+/atom/movable/shuttleRotate(rotation, params)
+ . = ..()
+ //rotate the physical bounds and offsets for multitile atoms too. Owerride base "rotate the pixel offsets" for multitile atoms.
+ //Owerride non zero bound_x, bound_y, pixel_x, pixel_y to zero.
+ //Dont take in account starting bound_x, bound_y, pixel_x, pixel_y.
+ //So it can unintentionally shift physical bounds of things that starts with non zero bound_x, bound_y.
+ if(((bound_height != world.icon_size) || (bound_width != world.icon_size)) && (bound_x == 0) && (bound_y == 0)) //Dont shift things that have non zero bound_x and bound_y, or it move somewhere. Now it BSA and Gateway.
+ pixel_x = dir & (NORTH|EAST) ? -bound_width+world.icon_size : 0
+ pixel_y = dir & (NORTH|WEST) ? -bound_width+world.icon_size : 0
+ bound_x = pixel_x
+ bound_y = pixel_y
+
/************************************Turf rotate procs************************************/
/turf/closed/mineral/shuttleRotate(rotation, params)
@@ -42,7 +56,7 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
/mob/dead/observer/shuttleRotate(rotation, params)
. = ..()
- update_appearance(UPDATE_ICON)
+ update_appearance()
/************************************Structure rotate procs************************************/
@@ -59,7 +73,7 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
var/temp = d1
d1 = d2
d2 = temp
- update_appearance(UPDATE_ICON)
+ update_appearance()
//Fixes dpdir on shuttle rotation
/obj/structure/disposalpipe/shuttleRotate(rotation, params)
diff --git a/code/modules/shuttle/spaceship_navigation_beacon.dm b/code/modules/shuttle/spaceship_navigation_beacon.dm
index ce10314952b8..17c10dba487e 100644
--- a/code/modules/shuttle/spaceship_navigation_beacon.dm
+++ b/code/modules/shuttle/spaceship_navigation_beacon.dm
@@ -19,13 +19,13 @@
/obj/machinery/spaceship_navigation_beacon/Initialize(mapload)
. = ..()
- SSshuttle.beacons |= src
+ SSshuttle.beacon_list |= src
obj/machinery/spaceship_navigation_beacon/emp_act()
locked = TRUE
/obj/machinery/spaceship_navigation_beacon/Destroy()
- SSshuttle.beacons -= src
+ SSshuttle.beacon_list -= src
return ..()
// update the icon_state
diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm
index 350e723c762e..d05758527ed0 100644
--- a/code/modules/shuttle/special.dm
+++ b/code/modules/shuttle/special.dm
@@ -208,7 +208,7 @@
/mob/living/simple_animal/drone/snowflake/mafia
name = "Mafiosdrone"
icon_state = "drone_synd"
- desc = "An indestructable drone \"\ probably\"\ involved in some shady buisness. Good thing its pacificm circuits are still there."
+ desc = "An indestructable drone \"\ probably\"\ involved in some shady business. Good thing its pacificm circuits are still there."
hacked = TRUE
laws = "1. Be loyal to members of the organization.\n\
2. Be rational.\n\
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index 3eb91bd880c1..65055b1e44a3 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -31,7 +31,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
/obj/docking_port/mobile/supply
name = "supply shuttle"
- id = "supply"
+ shuttle_id = "supply"
callTime = 30 SECONDS
dir = WEST
@@ -86,7 +86,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
var/list/obj/miscboxes = list() //miscboxes are combo boxes that contain all small_item orders grouped
var/list/misc_order_num = list() //list of strings of order numbers, so that the manifest can show all orders in a box
var/list/misc_contents = list() //list of lists of items that each box will contain
- if(!SSshuttle.shoppinglist.len)
+ if(!SSshuttle.shopping_list.len)
return
var/list/empty_turfs = list()
@@ -99,7 +99,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
var/value = 0
var/purchases = 0
- for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
+ for(var/datum/supply_order/SO in SSshuttle.shopping_list)
if(!empty_turfs.len)
break
var/price = SO.pack.get_cost()
@@ -120,8 +120,9 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR)
cargo.adjust_money(price - SO.pack.get_cost()) //Cargo gets the handling fee
value += SO.pack.get_cost()
- SSshuttle.shoppinglist -= SO
- SSshuttle.orderhistory += SO
+ SSshuttle.shopping_list -= SO
+ SSshuttle.order_history += SO
+ SO.pack.times_ordered_in_one_order = 0 //dripstation edit
if(SO.pack.small_item) //small_item means it gets piled in the miscbox
if(SO.paying_account)
diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm
index b56aa88e3db4..1551ffeb47e0 100644
--- a/code/modules/shuttle/syndicate.dm
+++ b/code/modules/shuttle/syndicate.dm
@@ -1,4 +1,4 @@
-#define SYNDICATE_CHALLENGE_TIMER 12000 //20 minutes
+#define SYNDICATE_CHALLENGE_TIMER (20 MINUTES)
/obj/machinery/computer/shuttle/syndicate
name = "syndicate shuttle terminal"
@@ -13,11 +13,6 @@
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
flags_1 = NODECONSTRUCT_1
-/obj/machinery/computer/shuttle/syndicate/allowed(mob/M)
- if(issilicon(M) && !(ROLE_SYNDICATE in M.faction))
- return FALSE
- return ..()
-
/obj/machinery/computer/shuttle/syndicate/launch_check(mob/user)
. = ..()
if(!.)
@@ -62,10 +57,11 @@
shuttleId = "syndicate"
lock_override = CAMERA_LOCK_STATION
shuttlePortId = "syndicate_custom"
- jumpto_ports = list("syndicate_ne" = 1, "syndicate_nw" = 1, "syndicate_n" = 1, "syndicate_se" = 1, "syndicate_sw" = 1, "syndicate_s" = 1)
+ jump_to_ports = list("syndicate_ne" = 1, "syndicate_nw" = 1, "syndicate_n" = 1, "syndicate_se" = 1, "syndicate_sw" = 1, "syndicate_s" = 1)
view_range = 5.5
x_offset = -7
y_offset = -1
+ whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava, /turf/closed/mineral, /turf/open/openspace)
see_hidden = TRUE
#undef SYNDICATE_CHALLENGE_TIMER
diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/white_ship.dm
index f460c606d388..79f346401e6f 100644
--- a/code/modules/shuttle/white_ship.dm
+++ b/code/modules/shuttle/white_ship.dm
@@ -3,7 +3,7 @@
desc = "Used to control the White Ship."
circuit = /obj/item/circuitboard/computer/white_ship
shuttleId = "whiteship"
- possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;spacebar;whiteship_custom"
+ possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_mining;spacebar;whiteship_custom"
/obj/machinery/computer/shuttle/white_ship/pod
name = "Salvage Pod Console"
@@ -24,7 +24,7 @@
shuttleId = "whiteship"
lock_override = NONE
shuttlePortId = "whiteship_custom"
- jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1)
+ jump_to_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1)
view_range = 10
x_offset = -6
y_offset = -10
@@ -35,7 +35,7 @@
desc = "Used to designate a precise transit location for the Salvage Pod."
shuttleId = "whiteship_pod"
shuttlePortId = "whiteship_pod_custom"
- jumpto_ports = list("whiteship_pod_home" = 1)
+ jump_to_ports = list("whiteship_pod_home" = 1)
view_range = 0
x_offset = -2
y_offset = 0
diff --git a/code/modules/spells/spell_types/conjure/_conjure.dm b/code/modules/spells/spell_types/conjure/_conjure.dm
index 2e49ce063280..5f9a7ccdd7a7 100644
--- a/code/modules/spells/spell_types/conjure/_conjure.dm
+++ b/code/modules/spells/spell_types/conjure/_conjure.dm
@@ -34,7 +34,11 @@
to_summon_in -= spawn_place
if(ispath(summoned_object_type, /turf))
- spawn_place.ChangeTurf(summoned_object_type, flags = CHANGETURF_INHERIT_AIR)
+ if (spawn_place.overfloor_placed)
+ spawn_place.ChangeTurf(summoned_object_type, flags = CHANGETURF_INHERIT_AIR)
+ else
+ spawn_place.place_on_top(summoned_object_type, flags = CHANGETURF_INHERIT_AIR)
+ return
else
var/atom/summoned_object = new summoned_object_type(spawn_place)
@@ -47,4 +51,4 @@
/// Called on atoms summoned after they are created, allows extra variable editing and such of created objects
/datum/action/cooldown/spell/conjure/proc/post_summon(atom/summoned_object, atom/cast_on)
- return
\ No newline at end of file
+ return
diff --git a/code/modules/spells/spell_types/devil.dm b/code/modules/spells/spell_types/devil.dm
index 0ff643244e6f..077c6a12159a 100644
--- a/code/modules/spells/spell_types/devil.dm
+++ b/code/modules/spells/spell_types/devil.dm
@@ -30,6 +30,7 @@
invocation_type = INVOCATION_WHISPER
item_type = /obj/item/instrument/violin/golden
+ spell_requirements = NONE
/datum/action/cooldown/spell/pointed/summon_contract
name = "Summon infernal contract"
@@ -164,7 +165,7 @@
return FALSE
fakefire()
forceMove(drop_location())
- client.eye = src
+ client.set_eye(src)
visible_message(span_warning("[src] appears in a fiery blaze!"))
playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 100, 1, -1)
addtimer(CALLBACK(src, PROC_REF(fakefireextinguish)), 15, TIMER_UNIQUE)
diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm
index f1fd17b6b7d5..f8222acbeeb2 100644
--- a/code/modules/spells/spell_types/hivemind.dm
+++ b/code/modules/spells/spell_types/hivemind.dm
@@ -212,7 +212,7 @@
if(!hive.is_carbon_member(target))
power *= 0.5
target.blind_eyes(4*power)
- target.blur_eyes(30*power)
+ target.adjust_eye_blur(30*power)
target.minimumDeafTicks(15*power) //equivalent to 30s deafness max
target.adjust_jitter(power SECONDS)
target.silent += 10*power
diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm
index 6c964f9450fb..2908b0f5a4c5 100644
--- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm
+++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm
@@ -273,10 +273,10 @@
to_chat(jaunter, span_clown("[victim] joins your party! Your health is fully restored."))
consumed_mobs += victim
RegisterSignal(victim, COMSIG_MOB_STATCHANGE, PROC_REF(on_victim_statchange))
- RegisterSignal(victim, COMSIG_PARENT_QDELETING, PROC_REF(on_victim_deleted))
+ RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(on_victim_deleted))
/**
- * Signal proc for COMSIG_LIVING_DEATH and COMSIG_PARENT_QDELETING
+ * Signal proc for COMSIG_LIVING_DEATH and COMSIG_QDELETING
*
* If our demon is deleted or destroyed, expel all of our consumed mobs
*/
@@ -287,7 +287,7 @@
for(var/mob/living/friend as anything in consumed_mobs)
// Unregister the signals first
- UnregisterSignal(friend, list(COMSIG_MOB_STATCHANGE, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(friend, list(COMSIG_MOB_STATCHANGE, COMSIG_QDELETING))
INVOKE_ASYNC(friend, TYPE_PROC_REF(/atom/movable/, forceMove), release_turf)
if(!INVOKE_ASYNC(friend, TYPE_PROC_REF(/mob/living/, revive), TRUE, TRUE))
diff --git a/code/modules/spells/spell_types/jaunt/tar_pool.dm b/code/modules/spells/spell_types/jaunt/tar_pool.dm
new file mode 100644
index 000000000000..1afc1c4f4a5e
--- /dev/null
+++ b/code/modules/spells/spell_types/jaunt/tar_pool.dm
@@ -0,0 +1,20 @@
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/tar_pool
+ name = "Return to tar"
+ desc = "Temporarily dissolve into a pool of tar, in this form you are involnurable to damage."
+ button_icon = 'yogstation/icons/mob/actions/actions.dmi'
+ button_icon_state = "tarshift"
+ background_icon = 'yogstation/icons/mob/actions/backgrounds.dmi'
+ background_icon_state = "jungle"
+
+ jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt/tar_pool
+ spell_requirements = NONE
+ cooldown_time = 120 SECONDS
+
+ jaunt_duration = 10 SECONDS
+ jaunt_in_time = 1 SECONDS
+ jaunt_out_time = 1 SECONDS
+ jaunt_in_type = /obj/effect/better_animated_temp_visual/skin_twister_in
+ jaunt_out_type = /obj/effect/better_animated_temp_visual/skin_twister_out
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/tar_pool/do_steam_effects(turf/loc)
+ return
diff --git a/code/modules/spells/spell_types/jaunt/wirecrawl.dm b/code/modules/spells/spell_types/jaunt/wirecrawl.dm
index a1829b04dbf6..c12e1adb4d8d 100644
--- a/code/modules/spells/spell_types/jaunt/wirecrawl.dm
+++ b/code/modules/spells/spell_types/jaunt/wirecrawl.dm
@@ -108,7 +108,6 @@
wire.visible_message(span_warning("[jaunter] zips into [wire]!"))
jaunter.extinguish_mob()
- jaunter.sight |= (SEE_TURFS|BLIND)
jaunter.add_wirevision(wire)
holder.travelled = wire.powernet
do_sparks(10, FALSE, jaunter)
@@ -135,7 +134,6 @@
if(!exit_jaunt(jaunter, get_turf(wire)))
return FALSE
- jaunter.sight &= ~(SEE_TURFS|BLIND)
jaunter.remove_wirevision()
do_sparks(10, FALSE, jaunter)
@@ -214,6 +212,8 @@
if(!totalMembers.len)
return
+
+ sight |= (SEE_TURFS|BLIND)
if(client)
for(var/object in totalMembers)//cables and power machinery are not the same unfortunately
@@ -221,16 +221,18 @@
if(istype(object, /obj/structure/cable))
var/obj/structure/cable/display = object
if(!display.wire_vision_img)
- display.wire_vision_img = image(display, display.loc, layer = ABOVE_HUD_LAYER, dir = display.dir)
- display.wire_vision_img.plane = ABOVE_HUD_PLANE
+ var/turf/their_turf = get_turf(display)
+ display.wire_vision_img = image(display, display.loc, dir = display.dir)
+ SET_PLANE(display.wire_vision_img, ABOVE_HUD_PLANE, their_turf)
client.images += display.wire_vision_img
wires_shown += display.wire_vision_img
else if(istype(object, /obj/machinery/power))
var/obj/machinery/power/display = object
if(!display.wire_vision_img)
- display.wire_vision_img = image(display, display.loc, layer = ABOVE_HUD_LAYER, dir = display.dir)
- display.wire_vision_img.plane = ABOVE_HUD_PLANE
+ var/turf/their_turf = get_turf(display)
+ display.wire_vision_img = image(display, display.loc, dir = display.dir)
+ SET_PLANE(display.wire_vision_img, ABOVE_HUD_PLANE, their_turf)
client.images += display.wire_vision_img
wires_shown += display.wire_vision_img
@@ -239,6 +241,7 @@
for(var/image/current_image in wires_shown)
client.images -= current_image
wires_shown.len = 0
+ sight &= ~(SEE_TURFS|BLIND)
/obj/item/wirecrawl
diff --git a/code/modules/spells/spell_types/pointed/appendicitis.dm b/code/modules/spells/spell_types/pointed/appendicitis.dm
new file mode 100644
index 000000000000..eeddb8fffdb8
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/appendicitis.dm
@@ -0,0 +1,48 @@
+/datum/action/cooldown/spell/pointed/appendicitis
+ name = "Bestow Appendicitis"
+ desc = "Give someone appendicitis."
+ button_icon = 'icons/obj/surgery.dmi'
+ button_icon_state = "appendix"
+
+ active_msg = "You prepare to give someone appendicitis."
+ deactive_msg = "You stop preparing to give someone appendicitis."
+ cast_range = 10
+ aim_assist = TRUE
+
+ sound = 'sound/magic/enter_blood.ogg'
+ school = SCHOOL_TRANSMUTATION
+ cooldown_time = 60 SECONDS
+ cooldown_reduction_per_rank = 6 SECONDS
+
+ invocation = "OW'MI'SEID"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC
+
+/datum/action/cooldown/spell/pointed/appendicitis/is_valid_target(atom/cast_on)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!ishuman(cast_on))
+ return FALSE
+ return TRUE
+
+/datum/action/cooldown/spell/pointed/appendicitis/cast(mob/living/carbon/human/cast_on)
+ . = ..()
+ if(cast_on.stat == DEAD)
+ return FALSE
+
+ if(cast_on.can_block_magic(antimagic_flags) || !cast_on.getorgan(/obj/item/organ/appendix))
+ owner.balloon_alert(owner, "no effect!")
+ return FALSE
+
+ var/foundAlready = FALSE //don't infect someone that already has appendicitis
+ for(var/datum/disease/appendicitis/A in cast_on.diseases)
+ foundAlready = TRUE
+ break
+ if(foundAlready)
+ owner.balloon_alert(owner, "no effect!")
+ return FALSE
+
+ var/datum/disease/D = new /datum/disease/appendicitis()
+ cast_on.ForceContractDisease(D, FALSE, TRUE)
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm
index f4c9b9c0958a..ba3bfbd87ba7 100644
--- a/code/modules/spells/spell_types/pointed/blind.dm
+++ b/code/modules/spells/spell_types/pointed/blind.dm
@@ -39,5 +39,5 @@
to_chat(cast_on, span_warning("Your eyes cry out in pain!"))
cast_on.adjust_blindness(eye_blind_duration)
- cast_on.blur_eyes(eye_blur_duration)
+ cast_on.adjust_eye_blur(eye_blur_duration)
return TRUE
diff --git a/code/modules/spells/spell_types/pointed/finger_guns.dm b/code/modules/spells/spell_types/pointed/finger_guns.dm
index 6f975b7843b2..7b24298e85eb 100644
--- a/code/modules/spells/spell_types/pointed/finger_guns.dm
+++ b/code/modules/spells/spell_types/pointed/finger_guns.dm
@@ -5,7 +5,7 @@
background_icon_state = "bg_mime"
overlay_icon_state = "bg_mime_border"
button_icon = 'icons/mob/actions/actions_mime.dmi'
- button_icon_state = "finger_guns0"
+ button_icon_state = "finger_guns"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED
panel = "Mime"
sound = null
@@ -47,4 +47,4 @@
/datum/action/cooldown/spell/pointed/projectile/finger_guns/before_cast(atom/cast_on)
. = ..()
- invocation = span_notice("[cast_on] fires [cast_on.p_their()] finger gun!")
+ invocation = span_notice("[owner] fires [owner.p_their()] finger gun at [cast_on]!")
diff --git a/code/modules/spells/spell_types/pointed/lightning_bolt.dm b/code/modules/spells/spell_types/pointed/lightning_bolt.dm
index 84c634a565c7..45910a0a4a6b 100644
--- a/code/modules/spells/spell_types/pointed/lightning_bolt.dm
+++ b/code/modules/spells/spell_types/pointed/lightning_bolt.dm
@@ -39,6 +39,6 @@
return
var/obj/projectile/magic/aoe/lightning/bolt = to_fire
- bolt.tesla_range = bolt_range
- bolt.tesla_power = bolt_power
- bolt.tesla_flags = bolt_flags
+ bolt.zap_range = bolt_range
+ bolt.zap_power = bolt_power
+ bolt.zap_flags = bolt_flags
diff --git a/code/modules/spells/spell_types/self/night_vision.dm b/code/modules/spells/spell_types/self/night_vision.dm
deleted file mode 100644
index c3b3fc9977fa..000000000000
--- a/code/modules/spells/spell_types/self/night_vision.dm
+++ /dev/null
@@ -1,39 +0,0 @@
-//Toggle Night Vision
-/datum/action/cooldown/spell/night_vision
- name = "Toggle Nightvision"
- desc = "Toggle your nightvision mode."
-
- cooldown_time = 1 SECONDS
- spell_requirements = NONE
-
- /// The span the "toggle" message uses when sent to the user
- var/toggle_span = "notice"
-
-/datum/action/cooldown/spell/night_vision/New(Target)
- . = ..()
- name = "[name] \[ON\]"
-
-/datum/action/cooldown/spell/night_vision/is_valid_target(atom/cast_on)
- return isliving(cast_on)
-
-/datum/action/cooldown/spell/night_vision/cast(mob/living/cast_on)
- . = ..()
- to_chat(cast_on, "You toggle your night vision.")
-
- var/next_mode_text = ""
- switch(cast_on.lighting_alpha)
- if (LIGHTING_PLANE_ALPHA_VISIBLE)
- cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- next_mode_text = "More"
- if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
- cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- next_mode_text = "Full"
- if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
- next_mode_text = "OFF"
- else
- cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
- next_mode_text = "ON"
-
- cast_on.update_sight()
- name = "[initial(name)] \[[next_mode_text]\]"
\ No newline at end of file
diff --git a/code/modules/spells/spell_types/self/spacetime_distortion.dm b/code/modules/spells/spell_types/self/spacetime_distortion.dm
index b41560aa1629..62e4edcc3d73 100644
--- a/code/modules/spells/spell_types/self/spacetime_distortion.dm
+++ b/code/modules/spells/spell_types/self/spacetime_distortion.dm
@@ -98,7 +98,7 @@
/obj/effect/cross_action
name = "cross me"
- desc = "for crossing"
+ desc = "For crossing."
anchored = TRUE
/obj/effect/cross_action/spacetime_dist
diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm
index 61a2066d0f70..e6459bab56bd 100644
--- a/code/modules/spells/spell_types/self/summonitem.dm
+++ b/code/modules/spells/spell_types/self/summonitem.dm
@@ -22,15 +22,15 @@
/datum/action/cooldown/spell/summonitem/proc/mark_item(obj/to_mark)
name = "Recall [to_mark]"
marked_item = to_mark
- RegisterSignal(marked_item, COMSIG_PARENT_QDELETING, PROC_REF(on_marked_item_deleted))
+ RegisterSignal(marked_item, COMSIG_QDELETING, PROC_REF(on_marked_item_deleted))
/// Unset our current marked item
/datum/action/cooldown/spell/summonitem/proc/unmark_item()
name = initial(name)
- UnregisterSignal(marked_item, COMSIG_PARENT_QDELETING)
+ UnregisterSignal(marked_item, COMSIG_QDELETING)
marked_item = null
-/// Signal proc for COMSIG_PARENT_QDELETING on our marked item, unmarks our item if it's deleted
+/// Signal proc for COMSIG_QDELETING on our marked item, unmarks our item if it's deleted
/datum/action/cooldown/spell/summonitem/proc/on_marked_item_deleted(datum/source)
SIGNAL_HANDLER
diff --git a/code/modules/spells/spell_types/shapeshift/_shape_status.dm b/code/modules/spells/spell_types/shapeshift/_shape_status.dm
index ad0942ae83f5..7b64e675f655 100644
--- a/code/modules/spells/spell_types/shapeshift/_shape_status.dm
+++ b/code/modules/spells/spell_types/shapeshift/_shape_status.dm
@@ -38,7 +38,7 @@
RegisterSignal(owner, COMSIG_LIVING_PRE_WABBAJACKED, PROC_REF(on_wabbajacked))
RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_shape_death))
RegisterSignal(caster_mob, COMSIG_LIVING_DEATH, PROC_REF(on_caster_death))
- RegisterSignal(caster_mob, COMSIG_PARENT_QDELETING, PROC_REF(on_caster_deleted))
+ RegisterSignal(caster_mob, COMSIG_QDELETING, PROC_REF(on_caster_deleted))
SEND_SIGNAL(caster_mob, COMSIG_LIVING_SHAPESHIFTED, owner)
return TRUE
@@ -74,7 +74,7 @@
already_restored = TRUE
UnregisterSignal(owner, list(COMSIG_LIVING_PRE_WABBAJACKED, COMSIG_LIVING_DEATH))
- UnregisterSignal(caster_mob, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH))
+ UnregisterSignal(caster_mob, list(COMSIG_QDELETING, COMSIG_LIVING_DEATH))
caster_mob.forceMove(owner.loc)
caster_mob.notransform = FALSE
@@ -121,7 +121,7 @@
else
INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/living, death))
-/// Signal proc for [COMSIG_PARENT_QDELETING] from our caster, delete us / our owner if we get deleted
+/// Signal proc for [COMSIG_QDELETING] from our caster, delete us / our owner if we get deleted
/datum/status_effect/shapechange_mob/proc/on_caster_deleted(datum/source)
SIGNAL_HANDLER
diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm
index 8b9391513f94..b4f1b74c859c 100644
--- a/code/modules/spells/spell_types/touch/_touch.dm
+++ b/code/modules/spells/spell_types/touch/_touch.dm
@@ -129,7 +129,7 @@
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_hand_hit))
// RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_hand_hit))
RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped))
- RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted))
+ RegisterSignal(attached_hand, COMSIG_QDELETING, PROC_REF(on_hand_deleted))
// We can high five with our touch hand. It casts the spell on people. Radical
// attached_hand.AddElement(/datum/element/high_fiver)
@@ -143,7 +143,7 @@
COMSIG_ITEM_AFTERATTACK,
// COMSIG_ITEM_AFTERATTACK_SECONDARY,
COMSIG_ITEM_DROPPED,
- COMSIG_PARENT_QDELETING,
+ COMSIG_QDELETING,
COMSIG_ITEM_OFFER_TAKEN,
))
@@ -273,7 +273,7 @@
//FUCK COMBAT MODE!!!
/**
- * Signal proc for [COMSIG_PARENT_QDELETING] from our attached hand.
+ * Signal proc for [COMSIG_QDELETING] from our attached hand.
*
* If our hand is deleted for a reason unrelated to our spell,
* unlink it (clear refs) and revert the cooldown
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index eda1348554ec..0932258445e5 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -14,7 +14,8 @@
var/status = BODYPART_ORGANIC
var/sub_status = BODYPART_SUBTYPE_ORGANIC
var/needs_processing = FALSE
-
+ ///This is effectively the icon_state prefix for limbs.
+ var/limb_id
var/body_zone //BODY_ZONE_CHEST, BODY_ZONE_L_ARM, etc , used for def_zone
var/aux_zone // used for hands
var/aux_layer
@@ -56,6 +57,10 @@
var/species_color = ""
var/mutation_color = ""
var/no_update = 0
+ /// The colour of damage done to this bodypart
+ var/damage_color = ""
+ /// Should we even use a color?
+ var/use_damage_color = FALSE
var/animal_origin = null //for nonhuman bodypart (e.g. monkey)
var/dismemberable = 1 //whether it can be dismembered with a weapon.
@@ -425,6 +430,9 @@
*/
/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus, attack_direction)
// note that these are fed into an exponent, so these are magnified
+ if(HAS_TRAIT(owner, TRAIT_HARDLY_WOUNDED))
+ damage *= 0.33
+
if(HAS_TRAIT(owner, TRAIT_EASILY_WOUNDED))
damage *= 1.5
else
@@ -628,7 +636,7 @@
if(!HAS_TRAIT(owner, TRAIT_STUNIMMUNE) && stamina_dam >= max_damage)
if(!last_maxed)
if(owner.stat < UNCONSCIOUS)
- owner.emote("scream")
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "scream")
last_maxed = TRUE
set_disabled(TRUE)
return
@@ -646,7 +654,7 @@
if(total_damage >= max_damage * disable_threshold)
if(!last_maxed)
if(owner.stat < UNCONSCIOUS)
- owner.emote("scream")
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "scream")
last_maxed = TRUE
set_disabled(TRUE)
return
@@ -848,6 +856,7 @@
body_gender = H.gender
should_draw_gender = S.sexes
+ use_damage_color = S.use_damage_color
if((MUTCOLORS in S.species_traits) || (DYNCOLORS in S.species_traits))
if(S.fixed_mut_color)
@@ -897,7 +906,10 @@
image_dir = SOUTH
if(dmg_overlay_type)
if(brutestate)
- . += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, image_dir)
+ var/image/bruteoverlay = image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_[brutestate]0", -DAMAGE_LAYER, image_dir)
+ if(use_damage_color)
+ bruteoverlay.color = damage_color
+ . += bruteoverlay
if(burnstate)
. += image('icons/mob/dam_mob.dmi', "[dmg_overlay_type]_[body_zone]_0[burnstate]", -DAMAGE_LAYER, image_dir)
diff --git a/code/modules/surgery/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm
index 9cfb874c4b65..94d02d6901fd 100644
--- a/code/modules/surgery/eye_surgery.dm
+++ b/code/modules/surgery/eye_surgery.dm
@@ -49,7 +49,7 @@
target.cure_blind(list(EYE_DAMAGE))
target.set_blindness(0)
target.cure_nearsighted(list(EYE_DAMAGE))
- target.blur_eyes(35) //this will fix itself slowly.
+ target.adjust_eye_blur(35) //this will fix itself slowly.
E.setOrganDamage(0)
return TRUE
diff --git a/code/modules/surgery/mechanic_steps.dm b/code/modules/surgery/mechanic_steps.dm
index d79ef7340437..0d76573a3c86 100644
--- a/code/modules/surgery/mechanic_steps.dm
+++ b/code/modules/surgery/mechanic_steps.dm
@@ -57,7 +57,11 @@
name = "prepare electronics"
implements = list(
TOOL_MULTITOOL = 100,
- TOOL_HEMOSTAT = 10) // try to reboot internal controllers via short circuit with some conductor
+ /obj/item/melee/touch_attack/shock = 90,
+ /obj/item/gun/energy = 90,
+ /obj/item/melee/baton = 90, // try to reboot internal controllers via short circuit with some conductor
+ /obj/item/shockpaddles = 90,
+ TOOL_HEMOSTAT = 20) //"safe" option, but super tedious
time = 2.4 SECONDS
bloody_chance = FALSE
preop_sound = 'sound/items/tape_flip.ogg'
@@ -69,21 +73,64 @@
"[user] begins to prepare electronics in [target]'s [parse_zone(target_zone)].",
"[user] begins to prepare electronics in [target]'s [parse_zone(target_zone)].")
+/datum/surgery_step/prepare_electronics/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(!(tool.tool_behaviour == TOOL_MULTITOOL || tool.tool_behaviour == TOOL_HEMOSTAT))
+ target.electrocute_act(10, tool, stun = FALSE)//reduced damage if successful
+ return ..()
+
+/datum/surgery_step/prepare_electronics/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(!(tool.tool_behaviour == TOOL_MULTITOOL || tool.tool_behaviour == TOOL_HEMOSTAT))
+ target.electrocute_act(20, tool, stun = FALSE)//reduced damage if successful
+ return ..()
+
+/datum/surgery_step/prepare_electronics/play_success_sound(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(!success_sound)
+ return
+ var/sound_file_use
+ if(islist(success_sound ))
+ for(var/typepath in success_sound )//iterate and assign subtype to a list, works best if list is arranged from subtype first and parent last
+ if(istype(tool, typepath))
+ sound_file_use = success_sound [typepath]
+ break
+ else
+ sound_file_use = success_sound
+ if(!sound_file_use)
+ return
+ if(tool.tool_behaviour == TOOL_MULTITOOL || tool.tool_behaviour == TOOL_HEMOSTAT)
+ playsound(get_turf(target), sound_file_use, 30, TRUE, falloff_exponent = 2)
+ else
+ playsound(get_turf(target), 'sound/machines/defib_zap.ogg', 30, TRUE, falloff_exponent = 2)
+
//unwrench
/datum/surgery_step/mechanic_unwrench
name = "unwrench bolts"
implements = list(
TOOL_WRENCH = 100,
- TOOL_RETRACTOR = 10)
+ TOOL_CROWBAR = 100,//this sounds like a REALLY bad idea
+ TOOL_RETRACTOR = 20)
time = 2.4 SECONDS
bloody_chance = FALSE
preop_sound = 'sound/items/ratchet.ogg'
/datum/surgery_step/mechanic_unwrench/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
- display_results(user, target, span_notice("You begin to unwrench some bolts in [target]'s [parse_zone(target_zone)]..."),
- "[user] begins to unwrench some bolts in [target]'s [parse_zone(target_zone)].",
- "[user] begins to unwrench some bolts in [target]'s [parse_zone(target_zone)].")
-
+ if(tool.tool_behaviour == TOOL_CROWBAR)
+ display_results(user, target, span_notice("You begin reefing on the bolts in [target]'s [parse_zone(target_zone)]..."),
+ "[user] begins reefing on some bolts in [target]'s [parse_zone(target_zone)].",
+ "[user] begins reefing on some bolts in [target]'s [parse_zone(target_zone)].")
+ else
+ display_results(user, target, span_notice("You begin to unwrench some bolts in [target]'s [parse_zone(target_zone)]..."),
+ "[user] begins to unwrench some bolts in [target]'s [parse_zone(target_zone)].",
+ "[user] begins to unwrench some bolts in [target]'s [parse_zone(target_zone)].")
+
+/datum/surgery_step/mechanic_unwrench/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(tool.tool_behaviour == TOOL_CROWBAR)
+ target.apply_damage(10, BRUTE, target_zone)//reduced damage if successful
+ return ..()
+
+/datum/surgery_step/mechanic_unwrench/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(tool.tool_behaviour == TOOL_CROWBAR)
+ target.apply_damage(20, BRUTE, target_zone)
+ return ..()
/datum/surgery_step/mechanic_unwrench/tool_check(mob/user, obj/item/tool)
if(tool.usesound)
@@ -95,16 +142,31 @@
name = "wrench bolts"
implements = list(
TOOL_WRENCH = 100,
- TOOL_RETRACTOR = 10)
+ TOOL_WELDER = 100, //also a terrible idea
+ TOOL_RETRACTOR = 20)
time = 2.4 SECONDS
bloody_chance = FALSE
preop_sound = 'sound/items/ratchet.ogg'
/datum/surgery_step/mechanic_wrench/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
- display_results(user, target, span_notice("You begin to wrench some bolts in [target]'s [parse_zone(target_zone)]..."),
- "[user] begins to wrench some bolts in [target]'s [parse_zone(target_zone)].",
- "[user] begins to wrench some bolts in [target]'s [parse_zone(target_zone)].")
-
+ if(tool.tool_behaviour == TOOL_WELDER)
+ display_results(user, target, span_notice("You begin to weld some bolts in [target]'s [parse_zone(target_zone)]..."),
+ "[user] begins to weld some bolts in [target]'s [parse_zone(target_zone)].",
+ "[user] begins to weld some bolts in [target]'s [parse_zone(target_zone)].")
+ else
+ display_results(user, target, span_notice("You begin to wrench some bolts in [target]'s [parse_zone(target_zone)]..."),
+ "[user] begins to wrench some bolts in [target]'s [parse_zone(target_zone)].",
+ "[user] begins to wrench some bolts in [target]'s [parse_zone(target_zone)].")
+
+/datum/surgery_step/mechanic_wrench/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(tool.tool_behaviour == TOOL_WELDER)
+ target.apply_damage(10, BURN, target_zone)//reduced damage if successful
+ return ..()
+
+/datum/surgery_step/mechanic_wrench/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(tool.tool_behaviour == TOOL_WELDER)
+ target.apply_damage(20, BURN, target_zone)
+ return ..()
/datum/surgery_step/mechanic_wrench/tool_check(mob/user, obj/item/tool)
if(tool.usesound)
diff --git a/code/modules/surgery/organs/augments_chest.dm b/code/modules/surgery/organs/augments_chest.dm
index 0ab2df136add..c58ac8b27f2f 100644
--- a/code/modules/surgery/organs/augments_chest.dm
+++ b/code/modules/surgery/organs/augments_chest.dm
@@ -337,7 +337,7 @@
owner.set_drugginess(4 * severity)
owner.adjust_hallucinations((50 * severity) SECONDS)
- owner.blur_eyes(2 * severity)
+ owner.adjust_eye_blur(2 * severity)
owner.adjust_dizzy(severity SECONDS)
time_on += severity
owner.adjustFireLoss(severity)
diff --git a/code/modules/surgery/organs/augments_eyes.dm b/code/modules/surgery/organs/augments_eyes.dm
index 067261c67490..67d77435c416 100644
--- a/code/modules/surgery/organs/augments_eyes.dm
+++ b/code/modules/surgery/organs/augments_eyes.dm
@@ -1,6 +1,6 @@
/obj/item/organ/cyberimp/eyes/hud
name = "cybernetic hud"
- desc = "artificial photoreceptors with specialized functionality"
+ desc = "A couple of artificial photoreceptors with specialized functionality."
icon_state = "eye_implant"
implant_overlay = "eye_implant_overlay"
slot = ORGAN_SLOT_EYES
diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm
index 589147dbde4b..3a21ba0f3841 100644
--- a/code/modules/surgery/organs/autosurgeon.dm
+++ b/code/modules/surgery/organs/autosurgeon.dm
@@ -1,5 +1,3 @@
-#define INFINITE -1
-
/obj/item/autosurgeon
name = "autosurgeon"
desc = "A device that automatically inserts an implant or organ into the user without the hassle of extensive surgery. It has a slot to insert implants/organs and a screwdriver slot for removing accidentally added items."
diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm
index 0c3b10aaf071..fbb126c5448f 100644
--- a/code/modules/surgery/organs/ears.dm
+++ b/code/modules/surgery/organs/ears.dm
@@ -91,7 +91,7 @@
/obj/item/organ/ears/cat
name = "cat ears"
- icon = 'icons/obj/clothing/hats.dmi'
+ icon = 'icons/obj/clothing/hats/hats.dmi'
icon_state = "kitty"
visual = TRUE
compatible_biotypes = ALL_BIOTYPES // meowchine... turn back now
diff --git a/code/modules/surgery/organs/ethereal_heart.dm b/code/modules/surgery/organs/ethereal_heart.dm
index 506ed41cbb1f..a3a157fc02c5 100644
--- a/code/modules/surgery/organs/ethereal_heart.dm
+++ b/code/modules/surgery/organs/ethereal_heart.dm
@@ -34,10 +34,10 @@
brute_damage_to_stop_crystal = initial(brute_damage_to_stop_crystal)
RegisterSignal(heart_owner, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_change))
RegisterSignal(heart_owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_owner_fully_heal))
- RegisterSignal(heart_owner, COMSIG_PARENT_QDELETING, PROC_REF(owner_deleted))
+ RegisterSignal(heart_owner, COMSIG_QDELETING, PROC_REF(owner_deleted))
/obj/item/organ/heart/ethereal/Remove(mob/living/carbon/heart_owner, special = FALSE)
- UnregisterSignal(heart_owner, list(COMSIG_MOB_STATCHANGE, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_PARENT_QDELETING))
+ UnregisterSignal(heart_owner, list(COMSIG_MOB_STATCHANGE, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_QDELETING))
REMOVE_TRAIT(heart_owner, TRAIT_CORPSELOCKED, SPECIES_TRAIT)
stop_crystalization_process(heart_owner)
QDEL_NULL(current_crystal)
@@ -76,7 +76,7 @@
crystalize_timer_id = addtimer(CALLBACK(src, PROC_REF(crystalize), victim), CRYSTALIZE_PRE_WAIT_TIME, TIMER_STOPPABLE)
RegisterSignal(victim, COMSIG_HUMAN_DISARM_HIT, PROC_REF(reset_crystalizing))
- RegisterSignal(victim, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(victim, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage))
///Ran when examined while crystalizing, gives info about the amount of time left
@@ -129,7 +129,7 @@
///Stop the crystalization process, unregistering any signals and resetting any variables.
/obj/item/organ/heart/ethereal/proc/stop_crystalization_process(mob/living/ethereal, succesful = FALSE)
UnregisterSignal(ethereal, COMSIG_HUMAN_DISARM_HIT)
- UnregisterSignal(ethereal, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(ethereal, COMSIG_ATOM_EXAMINE)
UnregisterSignal(ethereal, COMSIG_MOB_APPLY_DAMAGE)
crystalization_process_damage = 0 //Reset damage taken during crystalization
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index b295459a9090..6bd6c3d7eb46 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -20,14 +20,16 @@
low_threshold_cleared = span_info("Your vision is cleared of any ailment.")
var/sight_flags = 0
- var/see_in_dark = 2
var/tint = 0
var/eye_color = "" //set to a hex code to override a mob's eye color
var/eye_icon_state = "eyes"
var/old_eye_color = "fff"
var/flash_protect = 0
var/see_invisible = SEE_INVISIBLE_LIVING
- var/lighting_alpha
+ /// How much darkness to cut out of your view (basically, night vision)
+ var/lighting_cutoff = null
+ /// List of color cutoffs from eyes, or null if not applicable
+ var/list/color_cutoffs = null
var/no_glasses
var/damaged = TRUE //damaged indicates that our eyes are undergoing some level of negative effect,starts as true so it removes the impaired vision overlay if it is replacing damaged eyes
@@ -40,8 +42,6 @@
HMN.eye_color = eye_color
else
eye_color = HMN.eye_color
- if(HAS_TRAIT(HMN, TRAIT_NIGHT_VISION) && !lighting_alpha)
- lighting_alpha = LIGHTING_PLANE_ALPHA_NV_TRAIT
M.update_tint()
owner.update_sight()
if(M.has_dna() && ishuman(M))
@@ -55,7 +55,7 @@
HMN.update_body()
M.cure_blind(list(EYE_DAMAGE)) // can't be blind from eye damage if there's no eye to be damaged, still blind from not having eyes though
M.cure_nearsighted(list(EYE_DAMAGE)) // likewise for nearsightedness
- M.set_blurriness(0) // no eyes to blur
+ M.set_eye_blur(0) // no eyes to blur
M.update_tint()
M.update_sight()
@@ -117,48 +117,83 @@
#undef OFFSET_X
#undef OFFSET_Y
+#define NIGHTVISION_LIGHT_OFF 0
+#define NIGHTVISION_LIGHT_LOW 1
+#define NIGHTVISION_LIGHT_MID 2
+#define NIGHTVISION_LIGHT_HIG 3
+
/obj/item/organ/eyes/night_vision
name = "shadow eyes"
desc = "A spooky set of eyes that can see in the dark."
- see_in_dark = 8
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+
actions_types = list(/datum/action/item_action/organ_action/use)
- var/night_vision = TRUE
+ // These lists are used as the color cutoff for the eye
+ // They need to be filled out for subtypes
+ var/list/low_light_cutoff
+ var/list/medium_light_cutoff
+ var/list/high_light_cutoff
+ var/light_level = NIGHTVISION_LIGHT_OFF
+
+/obj/item/organ/eyes/night_vision/Initialize(mapload)
+ . = ..()
+
+ if(length(low_light_cutoff) != 3 || length(medium_light_cutoff) != 3 || length(high_light_cutoff) != 3)
+ stack_trace("[type] did not have fully filled out color cutoff lists")
+ if(low_light_cutoff)
+ color_cutoffs = low_light_cutoff.Copy()
+ light_level = NIGHTVISION_LIGHT_LOW
/obj/item/organ/eyes/night_vision/ui_action_click()
sight_flags = initial(sight_flags)
- switch(lighting_alpha)
- if (LIGHTING_PLANE_ALPHA_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
- if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE)
- lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE
+ switch(light_level)
+ if (NIGHTVISION_LIGHT_OFF)
+ color_cutoffs = low_light_cutoff.Copy()
+ light_level = NIGHTVISION_LIGHT_LOW
+ if (NIGHTVISION_LIGHT_LOW)
+ color_cutoffs = medium_light_cutoff.Copy()
+ light_level = NIGHTVISION_LIGHT_MID
+ if (NIGHTVISION_LIGHT_MID)
+ color_cutoffs = high_light_cutoff.Copy()
+ light_level = NIGHTVISION_LIGHT_HIG
else
- lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE
- sight_flags &= ~SEE_BLACKNESS
+ color_cutoffs = null
+ light_level = NIGHTVISION_LIGHT_OFF
owner.update_sight()
-/obj/item/organ/eyes/night_vision/alien
- name = "alien eyes"
- desc = "It turned out they had them after all!"
- sight_flags = SEE_MOBS
+#undef NIGHTVISION_LIGHT_OFF
+#undef NIGHTVISION_LIGHT_LOW
+#undef NIGHTVISION_LIGHT_MID
+#undef NIGHTVISION_LIGHT_HIG
-/obj/item/organ/eyes/night_vision/zombie
+/obj/item/organ/eyes/night_vision/mushroom
+ name = "fung-eye"
+ desc = "While on the outside they look inert and dead, the eyes of mushroom people are actually very advanced."
+ low_light_cutoff = list(0, 15, 20)
+ medium_light_cutoff = list(0, 20, 35)
+ high_light_cutoff = list(0, 40, 50)
+
+/obj/item/organ/eyes/zombie
name = "undead eyes"
desc = "Somewhat counterintuitively, these half-rotten eyes actually have superior vision to those of a living human."
+ color_cutoffs = list(25, 35, 5)
+ lighting_cutoff = LIGHTING_CUTOFF_HIGH
+
+//innate nightvision eyes
+/obj/item/organ/eyes/alien
+ name = "alien eyes"
+ desc = "It turned out they had them after all!"
+ sight_flags = SEE_MOBS
+ color_cutoffs = list(25, 5, 42)
+ lighting_cutoff = LIGHTING_CUTOFF_HIGH
-/obj/item/organ/eyes/night_vision/nightmare
+/obj/item/organ/eyes/shadow
name = "burning red eyes"
desc = "Even without their shadowy owner, looking at these eyes gives you a sense of dread."
icon_state = "burning_eyes"
-
-/obj/item/organ/eyes/night_vision/mushroom
- name = "fung-eye"
- desc = "While on the outside they look inert and dead, the eyes of mushroom people are actually very advanced."
+ color_cutoffs = list(20, 10, 40)
+ lighting_cutoff = LIGHTING_CUTOFF_HIGH
///Robotic
-
/obj/item/organ/eyes/robotic
name = "robotic eyes"
icon_state = "cybernetic_eyeballs"
@@ -171,19 +206,21 @@
. = ..()
if(!owner || . & EMP_PROTECT_SELF)
return
+ var/obj/item/organ/eyes/eyes = owner.getorganslot(ORGAN_SLOT_EYES)
to_chat(owner, span_danger("your eyes overload and blind you!"))
owner.flash_act(override_blindness_check = 1)
- owner.blind_eyes(severity / 2)
- owner.blur_eyes(8)
- applyOrganDamage(2 * severity)
+ owner.blind_eyes(5)
+ owner.adjust_eye_blur(8)
+ eyes.applyOrganDamage(20 / severity)
/obj/item/organ/eyes/robotic/xray
name = "\improper meson eyes"
desc = "These cybernetic eyes will give you meson-vision. Looks like it could withstand seeing a supermatter crystal!."
eye_color = "00FF00"
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
+ // We're gonna downshift green and blue a bit so darkness looks yellow
+ color_cutoffs = list(25, 8, 5)
sight_flags = SEE_TURFS
- see_in_dark = 4
+
/obj/item/organ/eyes/robotic/xray/Insert(mob/living/carbon/M, special = FALSE, drop_if_replaced = FALSE, initialising)
. = ..()
@@ -198,16 +235,15 @@
desc = "These cybernetic eyes will give you true X-ray vision. Blinking is futile."
eye_color = "000"
sight_flags = SEE_MOBS | SEE_OBJS | SEE_TURFS
- see_in_dark = 8
/obj/item/organ/eyes/robotic/thermals
name = "thermal eyes"
desc = "These cybernetic eye implants will give you thermal vision. Vertical slit pupil included."
eye_color = "FC0"
sight_flags = SEE_MOBS
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
- flash_protect = -1
- see_in_dark = 8
+ // We're gonna downshift green and blue a bit so darkness looks yellow
+ color_cutoffs = list(25, 8, 5)
+ flash_protect = FLASH_PROTECTION_SENSITIVE
/obj/item/organ/eyes/robotic/flashlight
name = "flashlight eyes"
@@ -226,14 +262,14 @@
..()
if(!eye)
eye = new /obj/item/flashlight/eyelight()
- eye.on = TRUE
+ eye.light_on = TRUE
eye.forceMove(M)
eye.update_brightness(M)
M.become_blind("flashlight_eyes")
/obj/item/organ/eyes/robotic/flashlight/Remove(mob/living/carbon/M, special = 0)
- eye.on = FALSE
+ eye.light_on = FALSE
eye.update_brightness(M)
eye.forceMove(src)
M.cure_blind("flashlight_eyes")
@@ -243,7 +279,7 @@
/obj/item/organ/eyes/robotic/shield
name = "shielded robotic eyes"
desc = "These reactive micro-shields will protect you from welders and flashes without obscuring your vision."
- flash_protect = 2
+ flash_protect = FLASH_PROTECTION_WELDER
/obj/item/organ/eyes/robotic/shield/emp_act(severity)
return
@@ -442,7 +478,7 @@
/obj/item/organ/eyes/moth
name = "moth eyes"
- desc = "These eyes seem to have increased sensitivity to bright light, with no improvement to low light vision."
+ desc = "These eyes can see just a little too well, light doesn't entirely agree with them."
flash_protect = -1
/obj/item/organ/eyes/snail
@@ -454,5 +490,28 @@
/obj/item/organ/eyes/polysmorph
name = "polysmorph eyes"
desc = "Eyes from a polysmorph, capable of retaining slightly more vision in low light environments"
- lighting_alpha = LIGHTING_PLANE_ALPHA_NV_TRAIT
- see_in_dark = 5
+ lighting_cutoff = LIGHTING_CUTOFF_REAL_LOW
+
+/obj/item/organ/eyes/ethereal
+ name = "fractal eyes"
+ desc = "Crystalline eyes from an Ethereal. Seeing with them should feel like using a kaleidoscope, but somehow it isn't."
+ icon_state = "ethereal_eyes"
+ ///Color of the eyes, is set by the species on gain
+ var/ethereal_color = "#9c3030"
+
+/obj/item/organ/eyes/ethereal/Initialize(mapload)
+ . = ..()
+ add_atom_colour(ethereal_color, FIXED_COLOUR_PRIORITY)
+
+/obj/item/organ/eyes/ethereal/Insert(mob/living/carbon/M, special, drop_if_replaced, initialising)
+ . = ..()
+ var/client/dude = M.client
+ if(dude)
+ dude.view_size.resetToDefault(getScreenSize(dude.prefs.read_preference(/datum/preference/toggle/widescreen)))
+ dude.view_size.addTo("2x2")
+
+/obj/item/organ/eyes/ethereal/Remove(mob/living/carbon/M, special)
+ var/client/dude = M.client
+ if(dude)
+ dude.view_size.resetToDefault(getScreenSize(dude.prefs.read_preference(/datum/preference/toggle/widescreen)))
+ . = ..()
diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm
index 36ee323f11b9..b5358a0d98ce 100644
--- a/code/modules/surgery/organs/lungs.dm
+++ b/code/modules/surgery/organs/lungs.dm
@@ -334,7 +334,7 @@
var/existing = H.reagents.get_reagent_amount(/datum/reagent/hypernoblium)
H.reagents.add_reagent(/datum/reagent/hypernoblium, max(0, eff - existing))
breath.adjust_moles(GAS_HYPERNOB, -gas_breathed)
-
+
// Anti-noblium
gas_breathed = breath.get_moles(GAS_ANTINOB)
if(gas_breathed > gas_stimulation_min)
@@ -565,7 +565,7 @@
owner.blood_volume += (0.2 * plasma_pp) // 10/s when breathing literally nothing but plasma, which will suffocate you.
/obj/item/organ/lungs/ghetto
- name = "oxygen tanks welded to a modular reciever"
+ name = "oxygen tanks welded to a modular receiver"
desc = "A pair of oxygen tanks which have been attached to a modular (oxygen) receiver. They are incapable of supplying air, but can work as a replacement for lungs."
icon_state = "lungs-g"
organ_efficiency = 0.5
diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm
index 52c76be97411..3377c06405ef 100644
--- a/code/modules/surgery/organs/stomach.dm
+++ b/code/modules/surgery/organs/stomach.dm
@@ -63,7 +63,7 @@
H.adjust_dizzy(5 SECONDS)
if(H.disgust >= DISGUST_LEVEL_DISGUSTED)
if(prob(25))
- H.blur_eyes(3) //We need to add more shit down here
+ H.adjust_eye_blur(3) //We need to add more shit down here
H.adjust_disgust(-0.5 * disgust_metabolism)
switch(H.disgust)
@@ -147,21 +147,22 @@
to_chat(owner, emp_message)
charge(amount = owner.nutrition * -0.02 * severity)
-/obj/item/organ/stomach/cell/Insert(mob/living/carbon/M, special, drop_if_replaced)
+/obj/item/organ/stomach/cell/Insert(mob/living/carbon/stomach_owner, special, drop_if_replaced)
. = ..()
- if(HAS_TRAIT(M, TRAIT_POWERHUNGRY))
- M.nutrition = stored_charge
- RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge))
+ if(HAS_TRAIT(stomach_owner, TRAIT_POWERHUNGRY))
+ stomach_owner.nutrition = stored_charge
+ RegisterSignal(stomach_owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge))
-/obj/item/organ/stomach/cell/Remove(mob/living/carbon/M, special)
+/obj/item/organ/stomach/cell/Remove(mob/living/carbon/stomach_owner, special)
. = ..()
- if(HAS_TRAIT(M, TRAIT_POWERHUNGRY))
- stored_charge = M.nutrition
- M.nutrition = 0
+ if(HAS_TRAIT(stomach_owner, TRAIT_POWERHUNGRY))
+ stored_charge = stomach_owner.nutrition
+ stomach_owner.nutrition = 0
UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
- M.dna?.species.handle_digestion(M) // update nutrition stuff
+ stomach_owner.dna?.species.handle_digestion(stomach_owner) // update nutrition stuff
/obj/item/organ/stomach/cell/proc/charge(datum/source, amount, repairs)
+ SIGNAL_HANDLER
if(!HAS_TRAIT(owner, TRAIT_POWERHUNGRY))
return // do nothing in the owner doesn't run on electricity
owner.adjust_nutrition(amount/100) // ipcs can't get fat anymore
@@ -174,15 +175,16 @@
organ_flags = NONE
compatible_biotypes = ALL_NON_ROBOTIC
-/obj/item/organ/stomach/cell/ethereal/Insert(mob/living/carbon/M, special = 0)
- ..()
- RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_electrocute))
+/obj/item/organ/stomach/cell/ethereal/Insert(mob/living/carbon/stomach_owner, special = 0)
+ . = ..()
+ RegisterSignal(stomach_owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_electrocute))
-/obj/item/organ/stomach/cell/ethereal/Remove(mob/living/carbon/M, special = 0)
- UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT)
- ..()
+/obj/item/organ/stomach/cell/ethereal/Remove(mob/living/carbon/stomach_owner, special = 0)
+ UnregisterSignal(stomach_owner, COMSIG_LIVING_ELECTROCUTE_ACT)
+ return ..()
/obj/item/organ/stomach/cell/ethereal/proc/on_electrocute(mob/living/victim, shock_damage, obj/source, siemens_coeff = 1, zone = null, tesla_shock = 0, illusion = 0)
+ SIGNAL_HANDLER
if(illusion)
return
if(!HAS_TRAIT(owner, TRAIT_POWERHUNGRY))
@@ -236,4 +238,4 @@
..()
var/datum/component/crawl/vomit/B = M.GetComponent(/datum/component/crawl/vomit)
if(B)
- B.RemoveComponent()
+ qdel(B)
diff --git a/code/modules/surgery/organs/tails.dm b/code/modules/surgery/organs/tails.dm
index 7f6c4b014b05..5ea7c1175016 100644
--- a/code/modules/surgery/organs/tails.dm
+++ b/code/modules/surgery/organs/tails.dm
@@ -103,6 +103,8 @@
else
H.dna.species.mutant_bodyparts["tail_polysmorph"] = H.dna.features["tail_polysmorph"]
H.update_body()
+ if(H.physiology)
+ H.physiology.crawl_speed += 0.5
/obj/item/organ/tail/polysmorph/Remove(mob/living/carbon/human/H, special = 0)
..()
@@ -110,3 +112,5 @@
H.dna.species.mutant_bodyparts -= "tail_polysmorph"
tail_type = H.dna.features["tail_polysmorph"]
H.update_body()
+ if(H.physiology)
+ H.physiology.crawl_speed -= 0.5
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 92b50110451d..ea151357a997 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -189,7 +189,7 @@
var/insertpos = rand(1, message_list.len - 1)
var/inserttext = message_list[insertpos]
- if(!(copytext(inserttext, -3) == "..."))//3 == length("...")
+ if(!(copytext_char(inserttext, -3) == "..."))//3 == length("...") //Dripstation edit
message_list[insertpos] = inserttext + "..."
if(prob(20) && message_list.len > 3)
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index 05192ad4b1c2..985e0397a275 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -91,6 +91,9 @@
if(!attempt_initiate_surgery(src, M, user))
..()
+/obj/item/cautery/ignition_effect(atom/A, mob/living/user)
+ . = span_danger("[user] carefully lights their [A.name] with [src].")
+
/obj/item/cautery/augment
name = "cautery"
desc = "A heated element that cauterizes wounds."
diff --git a/code/modules/swarmers/swarmer.dm b/code/modules/swarmers/swarmer.dm
index a803253f031a..1acbc6ca7e74 100644
--- a/code/modules/swarmers/swarmer.dm
+++ b/code/modules/swarmers/swarmer.dm
@@ -20,7 +20,7 @@
desc = "Robotic constructs of unknown design, swarmers seek only to consume materials and replicate themselves indefinitely."
speak_emote = list("tones")
initial_language_holder = /datum/language_holder/swarmer
- bubble_icon = "swarmer"
+ bubble_icon = BUBBLE_SWARMER
mob_biotypes = MOB_ROBOTIC
health = 40
maxHealth = 40
@@ -249,7 +249,7 @@
if(ishuman(target))
var/mob/living/carbon/human/victim = target
if(!victim.handcuffed)
- victim.set_handcuffed(new /obj/item/restraints/handcuffs/energy/used(victim))
+ victim.set_handcuffed(new /obj/item/restraints/handcuffs/energy/used/swarmer(victim))
victim.update_handcuffed()
log_combat(src, victim, "handcuffed")
@@ -368,7 +368,7 @@
return
var/mob/newswarmer = Fabricate(createtype, 20)
LAZYADD(dronelist, newswarmer)
- RegisterSignal(newswarmer, COMSIG_PARENT_QDELETING, PROC_REF(remove_drone), newswarmer)
+ RegisterSignal(newswarmer, COMSIG_QDELETING, PROC_REF(remove_drone), newswarmer)
playsound(loc,'sound/items/poster_being_created.ogg', 20, TRUE, -1)
/**
diff --git a/code/modules/swarmers/swarmer_objs.dm b/code/modules/swarmers/swarmer_objs.dm
index 69cda8ec6278..c5801983b834 100644
--- a/code/modules/swarmers/swarmer_objs.dm
+++ b/code/modules/swarmers/swarmer_objs.dm
@@ -46,7 +46,7 @@
icon_state = "swarmer_console"
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 50, BIO = 100, RAD = 100, FIRE = 100, ACID = 100)
max_integrity = 400
- layer = MASSIVE_OBJ_LAYER
+ plane = MASSIVE_OBJ_PLANE
light_color = LIGHT_COLOR_CYAN
light_range = 10
anchored = TRUE
diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm
index 92c1c59161b7..55f8fc35043c 100644
--- a/code/modules/tgchat/to_chat.dm
+++ b/code/modules/tgchat/to_chat.dm
@@ -1,4 +1,4 @@
-/**
+/*!
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
@@ -7,69 +7,79 @@
* Circumvents the message queue and sends the message
* to the recipient (target) as soon as possible.
*/
-/proc/to_chat_immediate(target, html,
- type = null,
- text = null,
- avoid_highlighting = FALSE,
- // FIXME: These flags are now pointless and have no effect
- handle_whitespace = TRUE,
- trailing_newline = TRUE,
- confidential = FALSE)
- if(!target || (!html && !text))
+/proc/to_chat_immediate(
+ target,
+ html,
+ type = null,
+ text = null,
+ avoid_highlighting = FALSE,
+ allow_linkify = FALSE,
+ // FIXME: These flags are now pointless and have no effect
+ handle_whitespace = TRUE,
+ trailing_newline = TRUE,
+ confidential = FALSE,
+)
+ // Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
+ html = "[html]"
+ text = "[text]"
+
+ if(!target)
return
+ if(!html && !text)
+ CRASH("Empty or null string in to_chat proc call.")
if(target == world)
target = GLOB.clients
+
// Build a message
var/message = list()
if(type) message["type"] = type
if(text) message["text"] = text
if(html) message["html"] = html
if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting
- var/message_blob = TGUI_CREATE_MESSAGE("chat/message", message)
- var/message_html = message_to_html(message)
if(!confidential)
SSdemo.write_chat(target, message)
- if(islist(target))
- for(var/_target in target)
- var/client/client = CLIENT_FROM_VAR(_target)
- if(client)
- // Send to tgchat
- client.tgui_panel?.window.send_raw_message(message_blob)
- // Send to old chat
- SEND_TEXT(client, message_html)
- return
- var/client/client = CLIENT_FROM_VAR(target)
- if(client)
- // Send to tgchat
- client.tgui_panel?.window.send_raw_message(message_blob)
- // Send to old chat
- SEND_TEXT(client, message_html)
+ // send it immediately
+ SSchat.send_immediate(target, message)
/**
* Sends the message to the recipient (target).
*
* Recommended way to write to_chat calls:
+ * ```
* to_chat(client,
* type = MESSAGE_TYPE_INFO,
* html = "You have found [object]")
+ * ```
*/
-/proc/to_chat(target, html,
- type = null,
- text = null,
- avoid_highlighting = FALSE,
- // FIXME: These flags are now pointless and have no effect
- handle_whitespace = TRUE,
- trailing_newline = TRUE,
- confidential = FALSE)
- if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
- to_chat_immediate(target, html, type, text, confidential=confidential)
+/proc/to_chat(
+ target,
+ html,
+ type = null,
+ text = null,
+ avoid_highlighting = FALSE,
+ allow_linkify = FALSE,
+ // FIXME: These flags are now pointless and have no effect
+ handle_whitespace = TRUE,
+ trailing_newline = TRUE,
+ confidential = FALSE
+)
+ if(isnull(Master) || !Master.current_runlevel)
+ to_chat_immediate(target, html, type, text, avoid_highlighting, allow_linkify)
return
- if(!target || (!html && !text))
+
+ // Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
+ html = "[html]"
+ text = "[text]"
+
+ if(!target)
return
+ if(!html && !text)
+ CRASH("Empty or null string in to_chat proc call.")
if(target == world)
target = GLOB.clients
+
// Build a message
var/message = list()
if(type) message["type"] = type
diff --git a/code/modules/tgui/states/pilot.dm b/code/modules/tgui/states/pilot.dm
new file mode 100644
index 000000000000..c5b63b5ad1d8
--- /dev/null
+++ b/code/modules/tgui/states/pilot.dm
@@ -0,0 +1,9 @@
+GLOBAL_DATUM_INIT(pilot_state, /datum/ui_state/pilot_state, new)
+
+/datum/ui_state/pilot_state/can_use_topic(src_object, mob/user)
+ if(!ismecha(src_object))
+ return UI_CLOSE
+ var/obj/mecha/gundam = src_object
+ if(user == gundam.occupant)
+ return UI_INTERACTIVE
+ return UI_CLOSE
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
index bc8d132122f8..8704c6cf5e11 100644
--- a/code/modules/tgui/tgui_window.dm
+++ b/code/modules/tgui/tgui_window.dm
@@ -18,11 +18,13 @@
var/message_queue
var/sent_assets = list()
// Vars passed to initialize proc (and saved for later)
+ var/initial_strict_mode
var/initial_fancy
var/initial_assets
var/initial_inline_html
var/initial_inline_js
var/initial_inline_css
+ var/mouse_event_macro_set = FALSE
/**
* public
diff --git a/code/modules/tgui_input/list.dm b/code/modules/tgui_input/list.dm
index 242b69a9347d..66641c7aa51e 100644
--- a/code/modules/tgui_input/list.dm
+++ b/code/modules/tgui_input/list.dm
@@ -9,7 +9,7 @@
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout.
*/
-/proc/tgui_input_list(mob/user, message, title, list/buttons, timeout = 0)
+/proc/tgui_input_list(mob/user, message, title = "Select", list/buttons, default, timeout = 0)
if (!user)
user = usr
if(!length(buttons))
@@ -19,8 +19,18 @@
var/client/client = user
user = client.mob
else
- return
- var/datum/tgui_list_input/input = new(user, message, title, buttons, timeout)
+ return null
+
+ if(isnull(user.client))
+ return null
+
+ /// Client does NOT have tgui_input on: Returns regular input
+ if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input))
+ return input(user, message, title, default) as null|anything in buttons
+ var/datum/tgui_list_input/input = new(user, message, title, buttons, default, timeout)
+ if(input.invalid)
+ qdel(input)
+ return
input.ui_interact(user)
input.wait()
if (input)
@@ -70,38 +80,46 @@
var/list/buttons_map
/// The button that the user has pressed, null if no selection has been made
var/choice
+ /// The default button to be selected
+ var/default
/// The time at which the tgui_list_input was created, for displaying timeout progress.
var/start_time
/// The lifespan of the tgui_list_input, after which the window will close and delete itself.
var/timeout
/// Boolean field describing if the tgui_list_input was closed by the user.
var/closed
+ /// Whether the tgui list input is invalid or not (i.e. due to all list entries being null)
+ var/invalid = FALSE
-/datum/tgui_list_input/New(mob/user, message, title, list/buttons, timeout)
+/datum/tgui_list_input/New(mob/user, message, title, list/buttons, default, timeout)
src.title = title
src.message = message
src.buttons = list()
src.buttons_map = list()
+ src.default = default
// Gets rid of illegal characters
var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"})
for(var/i in buttons)
+ if(!i)
+ continue
var/string_key = whitelistedWords.Replace("[i]", "")
src.buttons += string_key
src.buttons_map[string_key] = i
-
-
+
+ if(length(src.buttons) == 0)
+ invalid = TRUE
if (timeout)
src.timeout = timeout
start_time = world.time
QDEL_IN(src, timeout)
-/datum/tgui_list_input/Destroy(force, ...)
+/datum/tgui_list_input/Destroy(force)
SStgui.close_uis(src)
QDEL_NULL(buttons)
- . = ..()
+ return ..()
/**
* Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if
@@ -125,16 +143,20 @@
return GLOB.always_state
/datum/tgui_list_input/ui_static_data(mob/user)
- . = list(
- "title" = title,
- "message" = message,
- "buttons" = buttons
- )
+ var/list/data = list()
+ data["init_value"] = default || buttons[1]
+ data["buttons"] = buttons
+ data["large_buttons"] = user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_large)
+ data["message"] = message
+ data["swapped_buttons"] = user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_swapped)
+ data["title"] = title
+ return data
/datum/tgui_list_input/ui_data(mob/user)
- . = list()
+ var/list/data = list()
if(timeout)
- .["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
+ data["timeout"] = clamp((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS), 0, 1)
+ return data
/datum/tgui_list_input/ui_act(action, list/params)
. = ..()
@@ -144,7 +166,8 @@
if("choose")
if (!(params["choice"] in buttons))
return
- choice = buttons_map[params["choice"]]
+ set_choice(buttons_map[params["choice"]])
+ closed = TRUE
SStgui.close_uis(src)
return TRUE
if("cancel")
@@ -152,6 +175,9 @@
closed = TRUE
return TRUE
+/datum/tgui_list_input/proc/set_choice(choice)
+ src.choice = choice
+
/**
* # async tgui_list_input
*
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index ae2ed4f9067b..5bc18446821a 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -13,9 +13,9 @@
var/broken = FALSE
var/initialized_at
-/datum/tgui_panel/New(client/client)
+/datum/tgui_panel/New(client/client, id)
src.client = client
- window = new(client, "browseroutput")
+ window = new(client, id)
window.subscribe(src, PROC_REF(on_message))
/datum/tgui_panel/Del()
@@ -39,7 +39,7 @@
/datum/tgui_panel/proc/Initialize(force = FALSE)
set waitfor = FALSE
// Minimal sleep to defer initialization to after client constructor
- sleep(1)
+ sleep(1 TICKS)
initialized_at = world.time
// Perform a clean initialization
window.Initialize(assets = list(
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index af3d3c365116..ca029b16733c 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -58,14 +58,21 @@
#define TEST_DEFAULT 1
/// After most test steps, used for tests that run long so shorter issues can be noticed faster
#define TEST_LONGER 10
-/// This must be the last test to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
-#define TEST_CREATE_AND_DESTROY INFINITY
+/// This must be the one of last tests to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time.
+#define TEST_CREATE_AND_DESTROY 9001
+/**
+ * For tests that rely on create and destroy having iterated through every (tangible) atom so they don't have to do something similar.
+ * Keep in mind tho that create and destroy will absolutely break the test platform, anything that relies on its shape cannot come after it.
+ */
+#define TEST_AFTER_CREATE_AND_DESTROY INFINITY
/// A trait source when adding traits through unit tests
#define TRAIT_SOURCE_UNIT_TESTS "unit_tests"
#include "anchored_mobs.dm"
+#include "baseturfs.dm"
#include "component_tests.dm"
+#include "dcs_check_list_arguments.dm"
#include "dragon_expiration.dm"
#include "dynamic_ruleset_sanity.dm"
#include "focus_only_tests.dm"
@@ -77,6 +84,7 @@
#include "species_whitelists.dm"
#include "subsystem_init.dm"
#include "timer_sanity.dm"
+#include "trait_addition_and_removal.dm"
#include "unit_test.dm"
#undef TEST_ASSERT
diff --git a/code/modules/unit_tests/baseturfs.dm b/code/modules/unit_tests/baseturfs.dm
new file mode 100644
index 000000000000..e18eef874936
--- /dev/null
+++ b/code/modules/unit_tests/baseturfs.dm
@@ -0,0 +1,76 @@
+#define EXPECTED_FLOOR_TYPE /turf/open/floor/plasteel
+// Do this instead of just ChangeTurf to guarantee that baseturfs is completely default on-init behavior
+#define RESET_TO_EXPECTED(turf) \
+ turf.ChangeTurf(EXPECTED_FLOOR_TYPE);\
+ turf.assemble_baseturfs(initial(turf.baseturfs))
+
+/// Validates that unmodified baseturfs tear down properly
+/datum/unit_test/baseturfs_unmodified_scrape
+
+/datum/unit_test/baseturfs_unmodified_scrape/Run()
+ // What this is specifically doesn't matter, just as long as the test is built for it
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, EXPECTED_FLOOR_TYPE, "run_loc_floor_bottom_left should be a plasteel floor")
+
+ RESET_TO_EXPECTED(run_loc_floor_bottom_left)
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/open/floor/plating, "Iron floors should scrape away to plating")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/open/space, "Plating should scrape away to space")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/open/space, "Space should scrape away to space")
+
+/datum/unit_test/baseturfs_unmodified_scrape/Destroy()
+ RESET_TO_EXPECTED(run_loc_floor_bottom_left)
+ return ..()
+
+/// Validates that specially placed baseturfs tear down properly
+/datum/unit_test/baseturfs_placed_on_top
+
+/datum/unit_test/baseturfs_placed_on_top/Run()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, EXPECTED_FLOOR_TYPE, "run_loc_floor_bottom_left should be a plasteel floor")
+
+ // Do this instead of just ChangeTurf to guarantee that baseturfs is completely default on-init behavior
+ RESET_TO_EXPECTED(run_loc_floor_bottom_left)
+
+ run_loc_floor_bottom_left.place_on_top(/turf/closed/wall/rock)
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/closed/wall/rock, "Rock wall should've been placed on top")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, EXPECTED_FLOOR_TYPE, "Rock wall should've been scraped off, back into the expected type")
+
+/datum/unit_test/baseturfs_placed_on_top/Destroy()
+ RESET_TO_EXPECTED(run_loc_floor_bottom_left)
+ return ..()
+
+/// Validates that specially placed baseturfs BELOW tear down properly
+/datum/unit_test/baseturfs_placed_on_bottom
+
+/datum/unit_test/baseturfs_placed_on_bottom/Run()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, EXPECTED_FLOOR_TYPE, "run_loc_floor_bottom_left should be a plasteel floor")
+
+ // Do this instead of just ChangeTurf to guarantee that baseturfs is completely default on-init behavior
+ RESET_TO_EXPECTED(run_loc_floor_bottom_left)
+
+ run_loc_floor_bottom_left.place_on_bottom(/turf/closed/wall/rock)
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, EXPECTED_FLOOR_TYPE, "PlaceOnBottom shouldn't have changed turf")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/open/floor/plating, "Plasteel floors should scrape away to plating")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/open/space, "Plating should've scraped off to space")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/closed/wall/rock, "Space should've scraped down to a rock wall")
+
+ run_loc_floor_bottom_left.ScrapeAway()
+ TEST_ASSERT_EQUAL(run_loc_floor_bottom_left.type, /turf/open/floor/plating, "Rock wall should've scraped down back to plating (because it's a wall)")
+
+/datum/unit_test/baseturfs_placed_on_bottom/Destroy()
+ RESET_TO_EXPECTED(run_loc_floor_bottom_left)
+ return ..()
+
+#undef RESET_TO_EXPECTED
+#undef EXPECTED_FLOOR_TYPE
diff --git a/code/modules/unit_tests/dcs_check_list_arguments.dm b/code/modules/unit_tests/dcs_check_list_arguments.dm
new file mode 100644
index 000000000000..67d7417062b2
--- /dev/null
+++ b/code/modules/unit_tests/dcs_check_list_arguments.dm
@@ -0,0 +1,55 @@
+/**
+ * list arguments for bespoke elements are treated as a text ref in the ID, like any other datum.
+ * Which means that, unless cached, using lists as arguments will lead to multiple instance of the same element
+ * being created over and over.
+ *
+ * Because of how it works, this unit test checks that these list datum args
+ * do not share similar contents (when rearranged in descending alpha-numerical order), to ensure that
+ * the least necessary amount of elements is created. So, using static lists may not be enough,
+ * for example, in the case of two different critters using the death_drops element to drop ectoplasm on death, since,
+ * despite being static lists, the two are different instances assigned to different mob types.
+ *
+ * Most of the time, you won't encounter two different static lists with similar contents used as element args,
+ * meaning using static lists is accepted. However, should that happen, it's advised to replace the instances
+ * with various string_x procs: lists, assoc_lists, assoc_nested_lists or numbers_list, depending on the type.
+ *
+ * In the case of an element where the position of the contents of each datum list argument is important,
+ * ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar
+ * when sorted, but the element instances are not.
+ *
+ * In the off-chance the element is not compatible with this unit test (such as for connect_loc et simila),
+ * you can also use ELEMENT_NO_LIST_UNIT_TEST so that they won't be processed by this unit test at all.
+ */
+/datum/unit_test/dcs_check_list_arguments
+ /**
+ * This unit test requires every (unless ignored) atom to have been created at least once
+ * for a more accurate search, which is why it's run after create_and_destroy is done running.
+ */
+ priority = TEST_AFTER_CREATE_AND_DESTROY
+
+/datum/unit_test/dcs_check_list_arguments/Run()
+ var/we_failed = FALSE
+ for(var/element_type in SSdcs.arguments_that_are_lists_by_element)
+ // Keeps track of the lists that shouldn't be compared with again.
+ var/list/to_ignore = list()
+ var/list/superlist = SSdcs.arguments_that_are_lists_by_element[element_type]
+ for(var/list/current as anything in superlist)
+ to_ignore[current] = TRUE
+ var/list/bad_lists
+ for(var/list/compare as anything in superlist)
+ if(to_ignore[compare])
+ continue
+ if(deep_compare_list(current, compare))
+ if(!bad_lists)
+ bad_lists = list(list(current))
+ bad_lists += list(compare)
+ to_ignore[compare] = TRUE
+ if(bad_lists)
+ we_failed = TRUE
+ //Include the original, unsorted list in the report. It should be easier to find by the contributor.
+ var/list/unsorted_list = superlist[current]
+ TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].")
+ ///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone.
+ if(we_failed)
+ TEST_FAIL("Ensure that each list is static or cached. string_lists() (as well as similar procs) is your friend here.\n\
+ Check the documentation from dcs_check_list_arguments.dm for more information!")
diff --git a/code/modules/unit_tests/focus_only_tests.dm b/code/modules/unit_tests/focus_only_tests.dm
index 426a236ab4bd..07fcdc75e0de 100644
--- a/code/modules/unit_tests/focus_only_tests.dm
+++ b/code/modules/unit_tests/focus_only_tests.dm
@@ -6,8 +6,26 @@
/// and you will only test the check for invalid overlays in appearance building.
/datum/unit_test/focus_only
+/// Checks that every overlay passed into build_appearance_list exists in the icon
+/datum/unit_test/focus_only/invalid_overlays
+
/// Checks that every created emissive has a valid icon_state
-/datum/unit_test/focus_only/multiple_space_initialization
+/datum/unit_test/focus_only/invalid_emissives
/// Checks that every overlay passed into build_appearance_list exists in the icon
-///datum/unit_test/focus_only/invalid_overlays
+/datum/unit_test/focus_only/invalid_overlays
+
+/// Checks that every created emissive has a valid icon_state
+/datum/unit_test/focus_only/multiple_space_initialization
+
+/// Checks that smoothing_groups and canSmoothWith are properly sorted in /atom/Initialize
+/datum/unit_test/focus_only/sorted_smoothing_groups
+
+/// Checks that nightvision eyes have a full set of color lists
+/datum/unit_test/focus_only/nightvision_color_cutoffs
+
+/// Checks that no light shares a tile/pixel offsets with another
+/datum/unit_test/focus_only/stacked_lights
+
+/// Ensures openspace never spawns on the bottom of a z stack
+/datum/unit_test/focus_only/openspace_clear
diff --git a/code/modules/unit_tests/trait_addition_and_removal.dm b/code/modules/unit_tests/trait_addition_and_removal.dm
new file mode 100644
index 000000000000..273cf102a00a
--- /dev/null
+++ b/code/modules/unit_tests/trait_addition_and_removal.dm
@@ -0,0 +1,94 @@
+/// Simple Unit Test to ensure multiple methods of adding/removing a trait work as intended.
+/datum/unit_test/trait_addition_and_removal
+
+#define TRAIT_UNIT_TEST_MAIN "trait_main"
+#define TRAIT_UNIT_TEST_ALT "trait_alternate"
+#define TRAIT_UNIT_TEST_A "trait_a"
+#define TRAIT_UNIT_TEST_B "trait_b"
+#define TRAIT_UNIT_TEST_C "trait_c"
+#define UNIT_TEST_SOURCE_MAIN "source_main"
+#define UNIT_TEST_SOURCE_ALT "source_alt"
+#define UNIT_TEST_SOURCE_A "source_a"
+#define UNIT_TEST_SOURCE_B "source_b"
+
+/datum/unit_test/trait_addition_and_removal/Run()
+ var/datum/trait_target = allocate(/datum) // traits work on the datum-level, so use a datum to ensure that it'll work on all children
+
+ // We want to ensure that what we add is also removed when we're done with our "block" of tests, if it starts to get messy we might just have to reset the datum between tests to ensure cleanliness
+
+ // The basics, if these fail burn down the codebase
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_A, UNIT_TEST_SOURCE_A)
+ TEST_ASSERT(HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_A), "Basic trait addition failed for [TRAIT_UNIT_TEST_A], source being [UNIT_TEST_SOURCE_A]!")
+
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_B)
+ TEST_ASSERT(HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_B), "Basic trait addition failed for [TRAIT_UNIT_TEST_B], source being [UNIT_TEST_SOURCE_B]!")
+ TEST_ASSERT(HAS_TRAIT_FROM(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_B), "Failed to verify source for [TRAIT_UNIT_TEST_B], expected source being [UNIT_TEST_SOURCE_B]!")
+ TEST_ASSERT(HAS_TRAIT_NOT_FROM(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_A), "Failed to verify source for [TRAIT_UNIT_TEST_B], expected source being [UNIT_TEST_SOURCE_B] but was actually [UNIT_TEST_SOURCE_A]!")
+
+ REMOVE_TRAIT(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_B)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_B), "Basic trait removal failed for [TRAIT_UNIT_TEST_B], source being [UNIT_TEST_SOURCE_B]!")
+ TEST_ASSERT(HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_A), "Trait [TRAIT_UNIT_TEST_A] was removed when it shouldn't have been!")
+ REMOVE_TRAIT(trait_target, TRAIT_UNIT_TEST_A, UNIT_TEST_SOURCE_A)
+
+ // Test adding the trait multiple times from different sources
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_MAIN, UNIT_TEST_SOURCE_A)
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_MAIN, UNIT_TEST_SOURCE_B)
+ TEST_ASSERT(!HAS_TRAIT_FROM_ONLY(trait_target, TRAIT_UNIT_TEST_MAIN, UNIT_TEST_SOURCE_A), "Failed to recognize that [TRAIT_UNIT_TEST_MAIN] was added by both [UNIT_TEST_SOURCE_A] and [UNIT_TEST_SOURCE_B]!")
+
+ // as well as its removal
+ REMOVE_TRAIT_NOT_FROM(trait_target, TRAIT_UNIT_TEST_MAIN, UNIT_TEST_SOURCE_A)
+ TEST_ASSERT(!HAS_TRAIT_FROM(trait_target, TRAIT_UNIT_TEST_MAIN, UNIT_TEST_SOURCE_B), "Failed to remove [TRAIT_UNIT_TEST_MAIN] with [UNIT_TEST_SOURCE_B] when removal was expected!")
+ TEST_ASSERT(HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_MAIN), "[TRAIT_UNIT_TEST_MAIN] was completely removed when it should not have been!")
+ REMOVE_TRAIT(trait_target, TRAIT_UNIT_TEST_MAIN, UNIT_TEST_SOURCE_A)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_MAIN), "[TRAIT_UNIT_TEST_MAIN] was not removed when it should have been!")
+
+ // Test adding multiple traits from the same source
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_A, UNIT_TEST_SOURCE_MAIN)
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_MAIN)
+ TEST_ASSERT(HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_B), "Failed to add [TRAIT_UNIT_TEST_B] with common source [UNIT_TEST_SOURCE_MAIN]!")
+
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_C, UNIT_TEST_SOURCE_ALT)
+ REMOVE_TRAITS_NOT_IN(trait_target, UNIT_TEST_SOURCE_MAIN)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_C), "Failed to remove [TRAIT_UNIT_TEST_C] when expected to be removed with remove_traits_NOT_IN(), was added with source [UNIT_TEST_SOURCE_ALT]!")
+
+ // as well as its removal
+ REMOVE_TRAITS_IN(trait_target, UNIT_TEST_SOURCE_MAIN)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_A), "Failed to remove [TRAIT_UNIT_TEST_A] with common source [UNIT_TEST_SOURCE_MAIN]!")
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_B), "Failed to remove [TRAIT_UNIT_TEST_B] with common source [UNIT_TEST_SOURCE_MAIN]!")
+
+ // Lastly, let's ensure that adding/removing traits using lists still works.
+ var/static/list/standardized_traits_list = list(TRAIT_UNIT_TEST_A, TRAIT_UNIT_TEST_B, TRAIT_UNIT_TEST_C)
+ trait_target.add_traits(standardized_traits_list, UNIT_TEST_SOURCE_MAIN)
+ for(var/trait in standardized_traits_list)
+ TEST_ASSERT(HAS_TRAIT(trait_target, trait), "Failed to add [trait] when using add_traits() using [UNIT_TEST_SOURCE_MAIN]!")
+ trait_target.remove_traits(standardized_traits_list, UNIT_TEST_SOURCE_MAIN)
+ for(var/trait in standardized_traits_list)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, trait), "Failed to remove [trait] when using remove_traits() using [UNIT_TEST_SOURCE_MAIN]!")
+
+ // As well as ensure mixing-and-matching types of trait addition/removal works.
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_A, UNIT_TEST_SOURCE_MAIN)
+ ADD_TRAIT(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_MAIN)
+ trait_target.remove_traits(standardized_traits_list, UNIT_TEST_SOURCE_MAIN)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_A), "Failed to remove [TRAIT_UNIT_TEST_A] when using remove_traits() using [UNIT_TEST_SOURCE_MAIN] (was added using ADD_TRAIT())!")
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_B), "Failed to remove [TRAIT_UNIT_TEST_B] when using remove_traits() using [UNIT_TEST_SOURCE_MAIN] (was added using ADD_TRAIT())!")
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_C), "[TRAIT_UNIT_TEST_C] somehow came into existence when using remove_traits() using [UNIT_TEST_SOURCE_MAIN] (what the actual fuck)!")
+
+ trait_target.add_traits(standardized_traits_list, UNIT_TEST_SOURCE_MAIN)
+ REMOVE_TRAIT(trait_target, TRAIT_UNIT_TEST_A, UNIT_TEST_SOURCE_MAIN)
+ REMOVE_TRAIT(trait_target, TRAIT_UNIT_TEST_B, UNIT_TEST_SOURCE_MAIN)
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_A), "Failed to remove [TRAIT_UNIT_TEST_A] when using REMOVE_TRAIT() using [UNIT_TEST_SOURCE_MAIN] (was added using add_traits())!")
+ TEST_ASSERT(!HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_B), "Failed to remove [TRAIT_UNIT_TEST_B] when using REMOVE_TRAIT() using [UNIT_TEST_SOURCE_MAIN] (was added using add_traits())!")
+ TEST_ASSERT(HAS_TRAIT(trait_target, TRAIT_UNIT_TEST_C), "[TRAIT_UNIT_TEST_C] was unexpectedly removed when using REMOVE_TRAIT() using [UNIT_TEST_SOURCE_MAIN] (was added using add_traits())!")
+ REMOVE_TRAIT(trait_target, TRAIT_UNIT_TEST_C, UNIT_TEST_SOURCE_MAIN) //just for cleanliness+completeness
+
+ TEST_ASSERT(!length(trait_target._status_traits), "Failed to clean up all status traits at the end of the unit test!")
+
+#undef TRAIT_UNIT_TEST_MAIN
+#undef TRAIT_UNIT_TEST_ALT
+#undef TRAIT_UNIT_TEST_A
+#undef TRAIT_UNIT_TEST_B
+#undef TRAIT_UNIT_TEST_C
+#undef UNIT_TEST_SOURCE_MAIN
+#undef UNIT_TEST_SOURCE_ALT
+#undef UNIT_TEST_SOURCE_A
+#undef UNIT_TEST_SOURCE_B
diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm
index d7a4e4f6baa6..5c839e3e09de 100644
--- a/code/modules/uplink/uplink_devices.dm
+++ b/code/modules/uplink/uplink_devices.dm
@@ -103,7 +103,7 @@
/obj/item/ntuplink/proc/finalize() //if the uplink type has been modified somehow, remove it and replace it
var/datum/component/uplink/nanotrasen/uplink = GetComponent(/datum/component/uplink/nanotrasen)
if(uplink)
- uplink.RemoveComponent()
+ qdel(uplink)
AddComponent(nt_datum, datum_owner, FALSE, TRUE, null, wc_start)
/obj/item/ntuplink/official
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 02868530da54..7699c09a3e46 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -339,6 +339,13 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
manufacturer = /datum/corporation/traitor/cybersun
surplus = 0
+/datum/uplink_item/dangerous/gasharpoon
+ name = "Gasharpoon"
+ desc = "A repurposed space-whaling tool attatched to a glove, can be used as a sturdy weapon in both hands, or worn as a glove to allow access to it's harpoon."
+ item = /obj/item/clothing/gloves/gasharpoon
+ cost = 10
+ surplus = 0
+
/datum/uplink_item/dangerous/rawketlawnchair
name = "84mm Rocket Propelled Grenade Launcher"
desc = "A reusable rocket propelled grenade launcher preloaded with a low-yield 84mm HE round. \
@@ -1811,7 +1818,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
name = "Hacked AI Law Upload Module"
desc = "When used with an upload console, this module allows you to upload priority laws to an artificial intelligence. \
Be careful with wording, as artificial intelligences may look for loopholes to exploit."
- item = /obj/item/aiModule/syndicate
+ item = /obj/item/aiModule/hacked
cost = 4
manufacturer = /datum/corporation/traitor/cybersun
exclude_modes = list(/datum/game_mode/infiltration)
diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm
index 0fe04abfd05a..836fcd5035c2 100644
--- a/code/modules/vehicles/_vehicle.dm
+++ b/code/modules/vehicles/_vehicle.dm
@@ -7,6 +7,7 @@
armor = list(MELEE = 30, BULLET = 30, LASER = 30, ENERGY = 0, BOMB = 30, BIO = 0, RAD = 0, FIRE = 60, ACID = 60)
density = TRUE
anchored = FALSE
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
var/list/mob/occupants //mob = bitflags of their control level.
var/max_occupants = 1
var/max_drivers = 1
diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm
index d81a88714168..f6c261c876b8 100644
--- a/code/modules/vehicles/atv.dm
+++ b/code/modules/vehicles/atv.dm
@@ -39,7 +39,7 @@
turret = new(loc)
turret.base = src
-/obj/vehicle/ridden/atv/turret/Moved()
+/obj/vehicle/ridden/atv/turret/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(turret)
turret.forceMove(get_turf(src))
diff --git a/code/modules/vehicles/scooter.dm b/code/modules/vehicles/scooter.dm
index 8d7ec576d145..bea046eb4ca9 100644
--- a/code/modules/vehicles/scooter.dm
+++ b/code/modules/vehicles/scooter.dm
@@ -24,7 +24,7 @@
qdel(src)
return TRUE
-/obj/vehicle/ridden/scooter/Moved()
+/obj/vehicle/ridden/scooter/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
diff --git a/code/modules/vehicles/speedbike.dm b/code/modules/vehicles/speedbike.dm
index e44d2052bbc1..af8897685905 100644
--- a/code/modules/vehicles/speedbike.dm
+++ b/code/modules/vehicles/speedbike.dm
@@ -93,7 +93,7 @@
visible_message(span_danger("[src] crashes into [H]!"))
playsound(src, 'sound/effects/bang.ogg', 50, 1)
-/obj/vehicle/ridden/space/speedwagon/Moved()
+/obj/vehicle/ridden/space/speedwagon/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
if(has_buckled_mobs())
for(var/atom/A in range(2, src))
diff --git a/code/modules/vehicles/wheelchair.dm b/code/modules/vehicles/wheelchair.dm
index 17ccc720c834..4ae64a963983 100644
--- a/code/modules/vehicles/wheelchair.dm
+++ b/code/modules/vehicles/wheelchair.dm
@@ -54,7 +54,7 @@
//if that made no sense this simply makes the wheelchair speed change along with movement speed delay
D.vehicle_move_delay = round(CONFIG_GET(number/movedelay/run_delay) * movedelay) / clamp(user.get_num_arms(), 0.25, 2) // div by zero :x
-/obj/vehicle/ridden/wheelchair/Moved()
+/obj/vehicle/ridden/wheelchair/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
. = ..()
cut_overlays()
playsound(src, move_sound, 75, TRUE)
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 87f510ce3c10..d24140621bcc 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -78,6 +78,8 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
circuit = /obj/item/circuitboard/machine/vendor
clicksound = 'sound/machines/pda_button1.ogg'
payment_department = ACCOUNT_SRV
+ light_power = 0.7
+ light_range = MINIMUM_USEFUL_LIGHT_RANGE
/// Is the machine active (No sales pitches if off)!
var/active = 1
///Are we ready to vend?? Is it time??
@@ -86,13 +88,24 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
var/purchase_message_cooldown
///Last mob to shop with us
var/last_shopper
+ ///Whether the vendor is tilted or not
var/tilted = FALSE
+ /// If tilted, this variable should always be the rotation that was applied when we were tilted. Stored for the purposes of unapplying it.
+ var/tilted_rotation = 0
+ ///Whether this vendor can be tilted over or not
var/tiltable = TRUE
+ ///Damage this vendor does when tilting onto an atom
var/squish_damage = 75
+ /// The chance, in percent, of this vendor performing a critical hit on anything it crushes via [tilt].
+ var/crit_chance = 15
+ /// If set to a critical define in crushing.dm, anything this vendor crushes will always be hit with that effect.
var/forcecrit = 0
+ ///Number of glass shards the vendor creates and tries to embed into an atom it tilted onto
var/num_shards = 7
+ ///List of mobs stuck under the vendor
var/list/pinned_mobs = list()
-
+ ///Icon for the maintenance panel overlay
+ var/panel_type = "panel1"
/**
* List of products this machine sells
@@ -185,6 +198,9 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
/// how many items have been inserted in a vendor
var/loaded_items = 0
+ ///Name of lighting mask for the vending machine
+ var/light_mask
+
/// used for narcing on underages
var/obj/item/radio/alertradio
@@ -266,17 +282,26 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
else
..()
-/obj/machinery/vending/update_icon_state()
+/obj/machinery/vending/update_appearance(updates=ALL)
. = ..()
if(stat & BROKEN)
- icon_state = "[initial(icon_state)]-broken"
+ set_light(0)
return
+ set_light(powered() ? MINIMUM_USEFUL_LIGHT_RANGE : 0)
- if(powered())
- icon_state = initial(icon_state)
- else
- icon_state = "[initial(icon_state)]-off"
+/obj/machinery/vending/update_icon_state()
+ if(stat & BROKEN)
+ icon_state = "[initial(icon_state)]-broken"
+ return ..()
+ icon_state = "[initial(icon_state)][powered() ? null : "-off"]"
+ return ..()
+/obj/machinery/vending/update_overlays()
+ . = ..()
+ if(panel_open)
+ . += panel_type
+ if(light_mask && !(stat & BROKEN) && powered())
+ . += emissive_appearance(icon, light_mask, src)
/obj/machinery/vending/obj_break(damage_flag)
. = ..()
@@ -609,7 +634,7 @@ GLOBAL_LIST_EMPTY(vending_products)
M.Turn(0)
transform = M
-/obj/machinery/vending/unbuckle_mob(mob/living/buckled_mob, force=FALSE)
+/obj/machinery/vending/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
if(!force)
return
. = ..()
@@ -1084,6 +1109,3 @@ GLOBAL_LIST_EMPTY(vending_products)
to_chat(user, span_warning("[src]'s input compartment blinks red: Access denied."))
return FALSE
-
-/obj/machinery/vending/onTransitZ()
- return
diff --git a/code/modules/vending/assist.dm b/code/modules/vending/assist.dm
index 3a5c4c0bb1c4..72fe162d820e 100644
--- a/code/modules/vending/assist.dm
+++ b/code/modules/vending/assist.dm
@@ -1,4 +1,9 @@
/obj/machinery/vending/assist
+ name = "\improper Part-Mart"
+ desc = "All the finest of miscellaneous electronics one could ever need! Not responsible for any injuries caused by reckless misuse of parts."
+ icon_state = "parts"
+ icon_deny = "parts-deny"
+ panel_type = "panel10"
products = list(/obj/item/assembly/prox_sensor = 5,
/obj/item/assembly/igniter = 3,
/obj/item/assembly/signaler = 4,
@@ -14,7 +19,9 @@
default_price = 10
extra_price = 50
payment_department = NO_FREEBIES
+ light_mask = "parts-light-mask"
/obj/item/vending_refill/assist
machine_name = "Vendomat"
icon_state = "refill_engi"
+
diff --git a/code/modules/vending/autodrobe.dm b/code/modules/vending/autodrobe.dm
index 2cd504b0cbc7..e1bf57931370 100644
--- a/code/modules/vending/autodrobe.dm
+++ b/code/modules/vending/autodrobe.dm
@@ -3,6 +3,7 @@
desc = "A vending machine for costumes."
icon_state = "theater"
icon_deny = "theater-deny"
+ panel_type = "panel16"
req_access = list(ACCESS_THEATRE)
product_slogans = "Dress for success!;Suited and booted!;It's show time!;Why leave style up to fate? Use AutoDrobe!"
vend_reply = "Thank you for using AutoDrobe!"
@@ -234,6 +235,7 @@
default_price = 50
extra_price = 75
payment_department = ACCOUNT_SRV
+ light_mask="theater-light-mask"
/obj/machinery/vending/autodrobe/canLoadItem(obj/item/I,mob/user)
return (I.type in products)
@@ -249,8 +251,10 @@
/obj/machinery/vending/autodrobe/capdrobe
name = "\improper CapDrobe"
desc = "A vending machine for captain outfits."
+ icon = 'yogstation/icons/obj/vending.dmi'
icon_state = "capdrobe"
icon_deny = "capdrobe-deny"
+ panel_type = "panel-capdrobe"
req_access = list(ACCESS_CAPTAIN)
product_slogans = "Dress for success!;Suited and booted!;It's show time!;Why leave style up to fate? Use the Captain's Autodrobe!"
vend_reply = "Thank you for using the Captain's Autodrobe!"
diff --git a/code/modules/vending/boozeomat.dm b/code/modules/vending/boozeomat.dm
index 06b9519e5fc7..a25baae65553 100644
--- a/code/modules/vending/boozeomat.dm
+++ b/code/modules/vending/boozeomat.dm
@@ -3,6 +3,8 @@
desc = "A technological marvel, supposedly able to mix just the mixture you'd like to drink the moment you ask for one."
icon_state = "boozeomat"
icon_deny = "boozeomat-deny"
+ panel_type = "panel22"
+
products = list(/obj/item/reagent_containers/food/drinks/drinkingglass = 30,
/obj/item/reagent_containers/food/drinks/drinkingglass/shotglass = 12,
/obj/item/reagent_containers/food/drinks/flask = 3,
@@ -50,6 +52,7 @@
default_price = 20
extra_price = 50
payment_department = ACCOUNT_SRV
+ light_mask = "boozeomat-light-mask"
/obj/machinery/vending/boozeomat/all_access
desc = "A technological marvel, supposedly able to mix just the mixture you'd like to drink the moment you ask for one. This model appears to have no access restrictions."
diff --git a/code/modules/vending/cartridge.dm b/code/modules/vending/cartridge.dm
index dd08f9011069..dc2afdaba1fb 100644
--- a/code/modules/vending/cartridge.dm
+++ b/code/modules/vending/cartridge.dm
@@ -5,11 +5,13 @@
product_slogans = "Carts to go!"
icon_state = "cart"
icon_deny = "cart-deny"
+ panel_type = "panel6"
products = list(/obj/item/modular_computer/tablet/pda/preset = 10)//honestly, this feels dumb, but okay
refill_canister = /obj/item/vending_refill/cart
default_price = 50
extra_price = 100
payment_department = ACCOUNT_SRV
+ light_mask="cart-light-mask"
/obj/item/vending_refill/cart
machine_name = "PTech"
diff --git a/code/modules/vending/cigarette.dm b/code/modules/vending/cigarette.dm
index e86c6985e87d..b5a5d5e09c73 100644
--- a/code/modules/vending/cigarette.dm
+++ b/code/modules/vending/cigarette.dm
@@ -4,6 +4,7 @@
product_slogans = "Space cigs taste good like a cigarette should.;I'd rather toolbox than switch.;Smoke!;Don't believe the reports - smoke today!"
product_ads = "Probably not bad for you!;Don't believe the scientists!;It's good for you!;Don't quit, buy more!;Smoke!;Nicotine heaven.;Best cigarettes since 2150.;Award-winning cigs."
icon_state = "cigs"
+ panel_type = "panel5"
products = list(/obj/item/storage/fancy/cigarettes = 5,
/obj/item/storage/fancy/cigarettes/cigpack_uplift = 3,
/obj/item/storage/fancy/cigarettes/cigpack_robust = 3,
@@ -23,6 +24,7 @@
default_price = 10
extra_price = 50
payment_department = ACCOUNT_SRV
+ light_mask = "cigs-light-mask"
/obj/machinery/vending/cigarette/syndicate
products = list(/obj/item/storage/fancy/cigarettes/cigpack_syndicate = 7,
diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm
index dc53821bdcba..b55dd8715d32 100644
--- a/code/modules/vending/clothesmate.dm
+++ b/code/modules/vending/clothesmate.dm
@@ -4,6 +4,7 @@
desc = "A vending machine for clothing."
icon_state = "clothes"
icon_deny = "clothes-deny"
+ panel_type = "panel15"
product_slogans = "Dress for success!;Prepare to look swagalicious!;Look at all this swag!;Why leave style up to fate? Use the ClothesMate!"
vend_reply = "Thank you for using the ClothesMate!"
products = list(/obj/item/clothing/head/beanie = 3,
@@ -217,6 +218,7 @@
default_price = 50
extra_price = 75
payment_department = NO_FREEBIES
+ light_mask = "wardrobe-light-mask"
/obj/machinery/vending/clothing/canLoadItem(obj/item/I,mob/user)
return (I.type in products)
diff --git a/code/modules/vending/coffee.dm b/code/modules/vending/coffee.dm
index 007135a5ef80..ea8ef1cf977e 100644
--- a/code/modules/vending/coffee.dm
+++ b/code/modules/vending/coffee.dm
@@ -12,6 +12,9 @@
default_price = 10
extra_price = 25
payment_department = ACCOUNT_SRV
+ light_mask = "coffee-light-mask"
+ light_color = COLOR_DARK_MODERATE_ORANGE
+
/obj/item/vending_refill/coffee
machine_name = "Solar's Best Hot Drinks"
icon_state = "refill_joe"
diff --git a/code/modules/vending/cola.dm b/code/modules/vending/cola.dm
index ba76ed700420..169b5d68b5b5 100644
--- a/code/modules/vending/cola.dm
+++ b/code/modules/vending/cola.dm
@@ -3,6 +3,7 @@
name = "\improper Robust Softdrinks"
desc = "A softdrink vendor provided by Robust Industries, LLC."
icon_state = "Cola_Machine"
+ panel_type = "panel2"
product_slogans = "Robust Softdrinks: More robust than a toolbox to the head!"
product_ads = "Refreshing!;Hope you're thirsty!;Over 1 million drinks sold!;Thirsty? Why not cola?;Please, have a drink!;Drink up!;The best drinks in space.;Thats cheap ass soda!"
products = list(/obj/item/reagent_containers/food/drinks/soda_cans/cola = 10,
@@ -46,36 +47,49 @@
/obj/machinery/vending/cola/blue
icon_state = "Cola_Machine"
+ light_mask = "cola-light-mask"
+ light_color = LIGHT_COLOR_BLUE
/obj/machinery/vending/cola/black
icon_state = "cola_black"
+ light_mask = "cola-light-mask"
/obj/machinery/vending/cola/red
icon_state = "red_cola"
name = "\improper Space Cola Vendor"
desc = "It vends cola, in space."
product_slogans = "Cola in space!"
+ light_mask = "red_cola-light-mask"
+ light_color = COLOR_DARK_RED
/obj/machinery/vending/cola/space_up
icon_state = "space_up"
name = "\improper Space-up! Vendor"
desc = "Indulge in an explosion of flavor."
product_slogans = "Space-up! Like a hull breach in your mouth."
+ light_mask = "space_up-light-mask"
+ light_color = COLOR_DARK_MODERATE_LIME_GREEN
/obj/machinery/vending/cola/starkist
icon_state = "starkist"
name = "\improper Star-kist Vendor"
desc = "The taste of a star in liquid form."
product_slogans = "Drink the stars! Star-kist!"
+ light_mask = "starkist-light-mask"
+ light_color = COLOR_LIGHT_ORANGE
/obj/machinery/vending/cola/sodie
icon_state = "soda"
+ light_mask = "soda-light-mask"
+ light_color = COLOR_WHITE
/obj/machinery/vending/cola/pwr_game
icon_state = "pwr_game"
name = "\improper Pwr Game Vendor"
desc = "You want it, we got it. Brought to you in partnership with Vlad's Salads."
product_slogans = "The POWER that gamers crave! PWR GAME!"
+ light_mask = "pwr_game-light-mask"
+ light_color = COLOR_STRONG_VIOLET
/obj/machinery/vending/cola/shamblers
name = "\improper Shambler's Vendor"
@@ -91,6 +105,8 @@
/obj/item/reagent_containers/food/drinks/soda_cans/shamblers = 10)
product_slogans = "~Shake me up some of that Shambler's Juice!~"
product_ads = "Refreshing!;Jyrbv dv lg jfdv fw kyrk Jyrdscvi'j Alztv!;Over 1 trillion souls drank!;Thirsty? Nyp efk uizeb kyv uribevjj?;Kyv Jyrdscvi uizebj kyv ezxyk!;Drink up!;Krjkp."
+ light_mask = "shamblers-light-mask"
+ light_color = COLOR_MOSTLY_PURE_PINK
/obj/machinery/vending/cola/shamblers/prison
products = list(/obj/item/reagent_containers/food/drinks/soda_cans/shamblers = 80)
diff --git a/code/modules/vending/dinnerware.dm b/code/modules/vending/dinnerware.dm
index ee2ac944ee1d..d814e0787742 100644
--- a/code/modules/vending/dinnerware.dm
+++ b/code/modules/vending/dinnerware.dm
@@ -3,6 +3,7 @@
desc = "A kitchen and restaurant equipment vendor."
product_ads = "Mm, food stuffs!;Food and food accessories.;Get your plates!;You like forks?;I like forks.;Woo, utensils.;You don't really need these..."
icon_state = "dinnerware"
+ panel_type = "panel4"
products = list(/obj/item/storage/bag/tray = 8,
/obj/item/reagent_containers/glass/bowl = 20,
/obj/item/kitchen/fork = 6,
@@ -28,7 +29,7 @@
refill_canister = /obj/item/vending_refill/dinnerware
default_price = 5
extra_price = 50
- payment_department = ACCOUNT_SRV
+ light_mask = "dinnerware-light-mask"
/obj/item/vending_refill/dinnerware
machine_name = "Plasteel Chef's Dinnerware Vendor"
diff --git a/code/modules/vending/engineering.dm b/code/modules/vending/engineering.dm
index 0dbc4254676d..0bd952c59b7f 100644
--- a/code/modules/vending/engineering.dm
+++ b/code/modules/vending/engineering.dm
@@ -4,6 +4,7 @@
desc = "Everything you need for do-it-yourself station repair."
icon_state = "engi"
icon_deny = "engi-deny"
+ panel_type = "panel10"
req_access = list(ACCESS_ENGINE_EQUIP)
products = list(/obj/item/clothing/under/rank/chief_engineer = 4,
/obj/item/clothing/under/rank/engineer = 4,
@@ -31,7 +32,8 @@
default_price = 50
extra_price = 60
payment_department = ACCOUNT_ENG
+ light_mask = "engi-light-mask"
/obj/item/vending_refill/engineering
machine_name = "Robco Tool Maker"
- icon_state = "refill_engi"
\ No newline at end of file
+ icon_state = "refill_engi"
diff --git a/code/modules/vending/engivend.dm b/code/modules/vending/engivend.dm
index b04c44f6e54d..ee666947dfe4 100644
--- a/code/modules/vending/engivend.dm
+++ b/code/modules/vending/engivend.dm
@@ -3,6 +3,7 @@
desc = "Spare tool vending. What? Did you expect some witty description?"
icon_state = "engivend"
icon_deny = "engivend-deny"
+ panel_type = "panel10"
req_access = list(ACCESS_ENGINE_EQUIP)
products = list(/obj/item/clothing/glasses/meson/engine = 2,
/obj/item/clothing/glasses/welding = 3,
@@ -28,6 +29,7 @@
default_price = 20
extra_price = 50
payment_department = ACCOUNT_ENG
+ light_mask = "engivend-light-mask"
/obj/item/vending_refill/engivend
machine_name = "Engi-Vend"
diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm
index 2ea25268a052..188ec5349320 100644
--- a/code/modules/vending/games.dm
+++ b/code/modules/vending/games.dm
@@ -3,6 +3,7 @@
desc = "Vends things that the Captain and Head of Personnel are probably not going to appreciate you fiddling with instead of your job..."
product_ads = "Escape to a fantasy world!;Fuel your gambling addiction!;Ruin your friendships!;Roll for initiative!;Elves and dwarves!;Paranoid computers!;Totally not satanic!;Fun times forever!"
icon_state = "games"
+ panel_type = "panel4"
products = list(/obj/item/toy/cards/deck = 5,
/obj/item/toy/cards/deck/uno = 3,
/obj/item/storage/pill_bottle/dice = 10,
@@ -25,6 +26,7 @@
default_price = 10
extra_price = 25
payment_department = ACCOUNT_SRV
+ light_mask = "games-light-mask"
/obj/item/vending_refill/games
machine_name = "\improper Good Clean Fun"
diff --git a/code/modules/vending/liberation.dm b/code/modules/vending/liberation.dm
index d500661b5821..774989c7e4a6 100644
--- a/code/modules/vending/liberation.dm
+++ b/code/modules/vending/liberation.dm
@@ -5,6 +5,7 @@
product_slogans = "Liberation Station: Your one-stop shop for all things second amendment!;Be a patriot today, pick up a gun!;Quality weapons for cheap prices!;Better dead than red!"
product_ads = "Float like an astronaut, sting like a bullet!;Express your second amendment today!;Guns don't kill people, but you can!;Who needs responsibilities when you have guns?"
vend_reply = "Remember the name: Liberation Station!"
+ panel_type = "panel17"
products = list(/obj/item/reagent_containers/food/snacks/burger/plain = 5, //O say can you see, by the dawn's early light
/obj/item/reagent_containers/food/snacks/burger/baseball = 3, //What so proudly we hailed at the twilight's last gleaming
/obj/item/reagent_containers/food/snacks/fries = 5, //Whose broad stripes and bright stars through the perilous fight
@@ -30,4 +31,5 @@
resistance_flags = FIRE_PROOF
default_price = 50
extra_price = 100
- payment_department = ACCOUNT_SEC
\ No newline at end of file
+ payment_department = ACCOUNT_SEC
+ light_mask = "liberation-light-mask"
diff --git a/code/modules/vending/liberation_toy.dm b/code/modules/vending/liberation_toy.dm
index 141df9a93cae..9eb0a047cf07 100644
--- a/code/modules/vending/liberation_toy.dm
+++ b/code/modules/vending/liberation_toy.dm
@@ -2,6 +2,7 @@
name = "\improper Syndicate Donksoft Toy Vendor"
desc = "An ages 8 and up approved vendor that dispenses toys. If you were to find the right wires, you can unlock the adult mode setting!"
icon_state = "syndi"
+ panel_type = "panel18"
req_access = list(ACCESS_SECURITY)
product_slogans = "Get your cool toys today!;Trigger a valid hunter today!;Quality toy weapons for cheap prices!;Give them to HoPs for all access!;Give them to HoS to get permabrigged!"
product_ads = "Feel robust with your toys!;Express your inner child today!;Toy weapons don't kill people, but valid hunters do!;Who needs responsibilities when you have toy weapons?;Make your next murder FUN!"
@@ -30,3 +31,4 @@
default_price = 25
extra_price = 50
payment_department = ACCOUNT_SRV
+ light_mask = "donksoft-light-mask"
diff --git a/code/modules/vending/magivend.dm b/code/modules/vending/magivend.dm
index 016b06d9bae9..8d3d864339f5 100644
--- a/code/modules/vending/magivend.dm
+++ b/code/modules/vending/magivend.dm
@@ -2,20 +2,24 @@
name = "\improper MagiVend"
desc = "A magic vending machine."
icon_state = "MagiVend"
+ panel_type = "panel10"
product_slogans = "Sling spells the proper way with MagiVend!;Be your own Houdini! Use MagiVend!"
vend_reply = "Have an enchanted evening!"
product_ads = "FJKLFJSD;AJKFLBJAKL;1234 LOONIES LOL!;>MFW;Kill them fuckers!;GET DAT FUKKEN DISK;HONK!;EI NATH;Destroy the station!;Admin conspiracies since forever!;Space-time bending hardware!"
- products = list(/obj/item/clothing/head/wizard = 1,
- /obj/item/clothing/suit/wizrobe = 1,
- /obj/item/clothing/head/wizard/red = 1,
- /obj/item/clothing/suit/wizrobe/red = 1,
- /obj/item/clothing/head/wizard/yellow = 1,
- /obj/item/clothing/suit/wizrobe/yellow = 1,
- /obj/item/clothing/shoes/sandal/magic = 1,
- /obj/item/staff = 2)
+ products = list(
+ /obj/item/clothing/head/wizard = 1,
+ /obj/item/clothing/suit/wizrobe = 1,
+ /obj/item/clothing/head/wizard/red = 1,
+ /obj/item/clothing/suit/wizrobe/red = 1,
+ /obj/item/clothing/head/wizard/yellow = 1,
+ /obj/item/clothing/suit/wizrobe/yellow = 1,
+ /obj/item/clothing/shoes/sandal/magic = 1,
+ /obj/item/staff = 2,
+ )
contraband = list(/obj/item/reagent_containers/glass/bottle/wizarditis = 1) //No one can get to the machine to hack it anyways; for the lulz - Microwave
armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 50)
resistance_flags = FIRE_PROOF
default_price = 25
extra_price = 50
payment_department = ACCOUNT_SRV
+ light_mask = "magivend-light-mask"
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index d646b9e628ca..1f01a68d544c 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -3,6 +3,7 @@
desc = "Medical drug dispenser."
icon_state = "med"
icon_deny = "med-deny"
+ panel_type = "panel11"
product_ads = "Go save some lives!;The best stuff for your medbay.;Only the finest tools.;Natural chemicals!;This stuff saves lives.;Don't you want some?;Ping!"
req_access = list(ACCESS_MEDICAL)
products = list(/obj/item/stack/medical/gauze = 8,
@@ -44,6 +45,7 @@
default_price = 25
extra_price = 100
payment_department = ACCOUNT_MED
+ light_mask = "med-light-mask"
/obj/item/vending_refill/medical
machine_name = "NanoMed Plus"
diff --git a/code/modules/vending/medical_wall.dm b/code/modules/vending/medical_wall.dm
index ffcc66dd08c6..adfcc0a5e178 100644
--- a/code/modules/vending/medical_wall.dm
+++ b/code/modules/vending/medical_wall.dm
@@ -3,7 +3,6 @@
desc = "Wall-mounted Medical Equipment dispenser."
icon_state = "wallmed"
icon_deny = "wallmed-deny"
- tiltable = FALSE
density = FALSE
products = list(/obj/item/reagent_containers/syringe = 3,
/obj/item/reagent_containers/pill/patch/styptic = 5,
@@ -19,40 +18,9 @@
default_price = 25
extra_price = 100
payment_department = ACCOUNT_MED
+ tiltable = FALSE
+ light_mask = "wallmed-light-mask"
/obj/item/vending_refill/wallmed
machine_name = "NanoMed"
icon_state = "refill_medical"
-
-/obj/machinery/vending/wallhypo
- name = "\improper HypoMed"
- desc = "Wall-mounted Hypospray Equipment dispenser."
- icon_state = "wallhypo"
- icon_deny = "wallhypo-deny"
- tiltable = FALSE
- density = FALSE
- // No default products, all of this shit costs money
- premium = list( /obj/item/hypospray = 5,
- /obj/item/reagent_containers/glass/bottle/vial/libital = 10,
- /obj/item/reagent_containers/glass/bottle/vial/aiuri = 10,
- /obj/item/reagent_containers/glass/bottle/vial/styptic = 10,
- /obj/item/reagent_containers/glass/bottle/vial/silver_sulfadiazine = 10,
- /obj/item/reagent_containers/glass/bottle/vial/charcoal = 10,
- /obj/item/reagent_containers/glass/bottle/vial/perfluorodecalin = 10,
- /obj/item/reagent_containers/glass/bottle/vial/epi = 10,
- /obj/item/reagent_containers/glass/bottle/vial/coagulant = 10,
- /obj/item/storage/firstaid/hypospray/basic = 5,
- /obj/item/storage/firstaid/hypospray/advanced = 5,
- /obj/item/storage/firstaid/hypospray/brute = 3,
- /obj/item/storage/firstaid/hypospray/burn = 3,
- /obj/item/storage/firstaid/hypospray/toxin = 3,
- /obj/item/storage/firstaid/hypospray/oxygen = 3)
- extra_price = 50
- armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 0, BIO = 0, RAD = 0, FIRE = 100, ACID = 50)
- resistance_flags = FIRE_PROOF
- refill_canister = /obj/item/vending_refill/wallhypo
- payment_department = ACCOUNT_MED
-
-/obj/item/vending_refill/wallhypo
- machine_name = "HypoMed"
- icon_state = "refill_medical"
diff --git a/code/modules/vending/megaseed.dm b/code/modules/vending/megaseed.dm
index b26cc8daebc0..7e7b36b53812 100644
--- a/code/modules/vending/megaseed.dm
+++ b/code/modules/vending/megaseed.dm
@@ -4,6 +4,8 @@
product_slogans = "THIS'S WHERE TH' SEEDS LIVE! GIT YOU SOME!;Hands down the best seed selection on the station!;Also certain mushroom varieties available, more for experts! Get certified today!"
product_ads = "We like plants!;Grow some crops!;Grow, baby, growww!;Aw h'yeah son!"
icon_state = "seeds"
+ panel_type = "panel2"
+ light_mask = "seeds-light-mask"
products = list(/obj/item/seeds/aloe = 3,
/obj/item/seeds/ambrosia = 3,
/obj/item/seeds/apple = 3,
@@ -63,6 +65,8 @@
refill_canister = /obj/item/vending_refill/hydroseeds
default_price = 10
extra_price = 50
+ light_mask = "seeds-light-mask"
+ light_color = LIGHT_COLOR_BLUE
payment_department = ACCOUNT_SRV
/obj/item/vending_refill/hydroseeds
diff --git a/code/modules/vending/modularpc.dm b/code/modules/vending/modularpc.dm
index b44b4ce5a651..dd2c012d4cb8 100644
--- a/code/modules/vending/modularpc.dm
+++ b/code/modules/vending/modularpc.dm
@@ -3,6 +3,8 @@
desc = "All the parts you need to build your own custom pc."
icon_state = "modularpc"
icon_deny = "modularpc-deny"
+ panel_type = "panel21"
+ light_mask = "modular-light-mask"
product_ads = "Get your gamer gear!;The best GPUs for all of your space-crypto needs!;The most robust cooling!;The finest RGB in space!"
vend_reply = "Game on!"
products = list(/obj/item/modular_computer/tablet/pda = 6,
@@ -26,6 +28,8 @@
refill_canister = /obj/item/vending_refill/modularpc
default_price = 30
extra_price = 250
+ light_color = LIGHT_COLOR_BLUE
+ light_mask = "modular-light-mask"
payment_department = ACCOUNT_SCI
/obj/item/vending_refill/modularpc
diff --git a/code/modules/vending/nutrimax.dm b/code/modules/vending/nutrimax.dm
index d3741f7f8529..a628e241f249 100644
--- a/code/modules/vending/nutrimax.dm
+++ b/code/modules/vending/nutrimax.dm
@@ -5,6 +5,8 @@
product_ads = "We like plants!;Don't you want some?;The greenest thumbs ever.;We like big plants.;Soft soil..."
icon_state = "nutri"
icon_deny = "nutri-deny"
+ panel_type = "panel2"
+ light_mask = "nutri-light-mask"
products = list(/obj/item/reagent_containers/glass/bottle/nutrient/ez = 30,
/obj/item/reagent_containers/glass/bottle/nutrient/l4z = 20,
/obj/item/reagent_containers/glass/bottle/nutrient/rh = 10,
@@ -23,4 +25,4 @@
/obj/item/vending_refill/hydronutrients
machine_name = "NutriMax"
- icon_state = "refill_plant"
\ No newline at end of file
+ icon_state = "refill_plant"
diff --git a/code/modules/vending/robotics.dm b/code/modules/vending/robotics.dm
index e49d45141ef1..c7b8cfcd7229 100644
--- a/code/modules/vending/robotics.dm
+++ b/code/modules/vending/robotics.dm
@@ -4,6 +4,8 @@
desc = "All the tools you need to create your own robot army."
icon_state = "robotics"
icon_deny = "robotics-deny"
+ panel_type = "panel14"
+ light_mask = "robotics-light-mask"
req_access = list(ACCESS_ROBO_CONTROL)
products = list(/obj/item/stack/cable_coil = 4,
/obj/item/assembly/flash/handheld = 6,
diff --git a/code/modules/vending/security.dm b/code/modules/vending/security.dm
index ed5bf1b84d89..9b561e265a5f 100644
--- a/code/modules/vending/security.dm
+++ b/code/modules/vending/security.dm
@@ -4,6 +4,8 @@
product_ads = "Crack capitalist skulls!;Beat some heads in!;Don't forget - harm is good!;Your weapons are right here.;Handcuffs!;Freeze, scumbag!;Don't tase me bro!;Tase them, bro.;Why not have a donut?"
icon_state = "sec"
icon_deny = "sec-deny"
+ panel_type = "panel6"
+ light_mask = "sec-light-mask"
req_access = list(ACCESS_SECURITY)
products = list(/obj/item/clothing/head/helmet/plated = 6,
/obj/item/clothing/suit/armor/plated = 6,
@@ -35,7 +37,7 @@
G.preprime()
else if(istype(I, /obj/item/flashlight))
var/obj/item/flashlight/F = I
- F.on = TRUE
+ F.light_on = TRUE
F.update_brightness()
/obj/item/vending_refill/security
diff --git a/code/modules/vending/security_armaments.dm b/code/modules/vending/security_armaments.dm
index 04650488c861..c47f2bdea4ab 100644
--- a/code/modules/vending/security_armaments.dm
+++ b/code/modules/vending/security_armaments.dm
@@ -2,7 +2,7 @@
/obj/machinery/armaments_dispenser
name = "armaments dispenser"
desc = "A standard issue security armaments dispenser."
- icon = 'icons/obj/vending.dmi'
+ icon = 'yogstation/icons/obj/vending.dmi'
icon_state = "armament" // BAIOMU REPLACE THIS WITH YOUR SPRITE
layer = 2.9
density = TRUE
diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm
index 33ad829b4c9a..9fa908996386 100644
--- a/code/modules/vending/snack.dm
+++ b/code/modules/vending/snack.dm
@@ -4,6 +4,8 @@
product_slogans = "Try our new nougat bar!;Twice the calories for half the price!"
product_ads = "The healthiest!;Award-winning chocolate bars!;Mmm! So good!;Oh my god it's so juicy!;Have a snack.;Snacks are good for you!;Have some more Getmore!;Best quality snacks straight from mars.;We love chocolate!;Try our new jerky!"
icon_state = "snack"
+ panel_type = "panel2"
+ light_mask = "snack-light-mask"
products = list(/obj/item/reagent_containers/food/snacks/spacetwinkie = 6,
/obj/item/reagent_containers/food/snacks/cheesiehonkers = 6,
/obj/item/reagent_containers/food/snacks/candy = 6,
diff --git a/code/modules/vending/sovietsoda.dm b/code/modules/vending/sovietsoda.dm
index 01832df2855a..7d7670297c3f 100644
--- a/code/modules/vending/sovietsoda.dm
+++ b/code/modules/vending/sovietsoda.dm
@@ -2,6 +2,8 @@
name = "\improper BODA"
desc = "Old sweet water vending machine."
icon_state = "sovietsoda"
+ panel_type = "panel8"
+ light_mask = "soviet-light-mask"
product_ads = "For Tsar and Country.;Have you fulfilled your nutrition quota today?;Very nice!;We are simple people, for this is all we eat.;If there is a person, there is a problem. If there is no person, then there is no problem."
products = list(/obj/item/reagent_containers/food/drinks/drinkingglass/filled/soda = 30)
contraband = list(/obj/item/reagent_containers/food/drinks/drinkingglass/filled/cola = 20)
@@ -13,4 +15,4 @@
/obj/item/vending_refill/sovietsoda
machine_name = "BODA"
- icon_state = "refill_cola"
\ No newline at end of file
+ icon_state = "refill_cola"
diff --git a/code/modules/vending/sustenance.dm b/code/modules/vending/sustenance.dm
index 266a58e10ab0..c4c4868e986c 100644
--- a/code/modules/vending/sustenance.dm
+++ b/code/modules/vending/sustenance.dm
@@ -3,7 +3,9 @@
desc = "A vending machine which vends food, as required by section 47-C of the NT's Prisoner Ethical Treatment Agreement."
product_slogans = "Enjoy your meal.;Enough calories to support strenuous labor."
product_ads = "Sufficiently healthy.;Efficiently produced tofu!;Mmm! So good!;Have a meal.;You need food to live!;Have some more candy corn!;Try our new ice cups!"
+ light_mask = "snack-light-mask"
icon_state = "sustenance"
+ panel_type = "panel2"
products = list(/obj/item/reagent_containers/food/snacks/tofu/prison = 24,
/obj/item/reagent_containers/food/drinks/ice/prison = 12,
/obj/item/reagent_containers/food/snacks/candy_corn/prison = 6)
diff --git a/code/modules/vending/toys.dm b/code/modules/vending/toys.dm
index 2cf117a730d2..32519753a6ae 100644
--- a/code/modules/vending/toys.dm
+++ b/code/modules/vending/toys.dm
@@ -2,6 +2,7 @@
name = "\improper Donksoft Toy Vendor"
desc = "Ages 8 and up approved vendor that dispenses toys."
icon_state = "syndi"
+ panel_type = "panel18"
product_slogans = "Get your cool toys today!;Trigger a security officer today!;Quality toy weapons for cheap prices!;Give them to HoPs for all access!;Give them to HoS to get permabrigged!"
product_ads = "Feel robust with your toys!;Express your inner child today!;Toy weapons don't kill people, but security does!;Who needs responsibilities when you have toy weapons?;Make your next murder FUN!"
vend_reply = "Come back for more!"
@@ -36,6 +37,7 @@
refill_canister = /obj/item/vending_refill/donksoft
default_price = 25
extra_price = 50
+ light_mask = "donksoft-light-mask"
payment_department = ACCOUNT_SRV
/obj/item/vending_refill/donksoft
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index 97e8716b0b14..37098892965c 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -6,6 +6,9 @@
extra_price = 75
payment_department = NO_FREEBIES
input_display_header = "Returned Clothing"
+ panel_type = "panel19"
+ light_mask = "wardrobe-light-mask"
+
/obj/machinery/vending/wardrobe/canLoadItem(obj/item/I,mob/user)
if(I.type in products)
@@ -48,7 +51,7 @@
/obj/item/clothing/head/yogs/tricornhat = 5,
/obj/item/clothing/under/rank/security/skirt = 3,
/obj/item/clothing/under/rank/security/grey = 3,
- /obj/item/clothing/under/yogs/shitcurity = 3,
+ /obj/item/clothing/under/rank/security/shitcurity = 3,
/obj/item/clothing/under/pants/khaki = 3,
/obj/item/clothing/under/rank/security/blueshirt = 3,
/obj/item/clothing/under/rank/security/secconuniform = 3,
@@ -61,6 +64,7 @@
/obj/item/clothing/head/beret/sec/navyofficer = 3)
refill_canister = /obj/item/vending_refill/wardrobe/sec_wardrobe
payment_department = ACCOUNT_SEC
+ light_color = COLOR_MOSTLY_PURE_RED
/obj/item/vending_refill/wardrobe/sec_wardrobe
machine_name = "SecDrobe"
@@ -77,6 +81,7 @@
/obj/item/storage/backpack/satchel/med = 4,
/obj/item/clothing/head/beret/med = 4,
/obj/item/clothing/suit/hooded/wintercoat/medical = 4,
+ /obj/item/clothing/suit/hooded/wintercoat/medical/paramedic = 4,
/obj/item/clothing/under/rank/nursesuit = 4,
/obj/item/clothing/head/nursehat = 4,
/obj/item/clothing/under/yogs/nursedress = 4,
@@ -95,6 +100,7 @@
/obj/item/clothing/accessory/armband/medblue = 2)
refill_canister = /obj/item/vending_refill/wardrobe/medi_wardrobe
payment_department = ACCOUNT_MED
+
/obj/item/vending_refill/wardrobe/medi_wardrobe
machine_name = "MediDrobe"
@@ -123,6 +129,7 @@
/obj/item/clothing/head/hardhat/weldhat = 1)
refill_canister = /obj/item/vending_refill/wardrobe/engi_wardrobe
payment_department = ACCOUNT_ENG
+ light_color = COLOR_VIVID_YELLOW
/obj/item/vending_refill/wardrobe/engi_wardrobe
machine_name = "EngiDrobe"
@@ -143,13 +150,17 @@
/obj/item/clothing/shoes/sneakers/black = 3)
refill_canister = /obj/item/vending_refill/wardrobe/atmos_wardrobe
payment_department = ACCOUNT_ENG
+ light_color = COLOR_VIVID_YELLOW
+
/obj/item/vending_refill/wardrobe/atmos_wardrobe
machine_name = "AtmosDrobe"
/obj/machinery/vending/wardrobe/sig_wardrobe
name = "NetDrobe"
desc = "A rarely used vending machine that provides clothing for Network Admins."
+ icon = 'yogstation/icons/obj/vending.dmi'
icon_state = "sigdrobe"
+ panel_type = "panel-sigdrobe"
product_ads = "Dress to impress yourself!;The drones will love you!;Get your clothing here!"
vend_reply = "Thank you for using the NetDrobe!"
products = list(/obj/item/storage/backpack/duffelbag/engineering = 1,
@@ -173,6 +184,8 @@
products = list(/obj/item/clothing/suit/hooded/wintercoat/cargo = 3,
/obj/item/clothing/under/rank/cargotech = 3,
/obj/item/clothing/under/rank/cargotech/skirt = 3,
+ /obj/item/clothing/under/rank/cargotech/turtleneck = 3,
+ /obj/item/clothing/under/rank/cargotech/skirt/turtleneck = 3,
/obj/item/clothing/shoes/sneakers/black = 3,
/obj/item/clothing/shoes/xeno_wraps/cargo = 3,
/obj/item/clothing/gloves/fingerless = 3,
@@ -201,6 +214,7 @@
/obj/item/clothing/suit/toggle/labcoat = 2,
/obj/item/clothing/suit/toggle/labcoat/wardtlab = 2,
/obj/item/clothing/suit/toggle/labcoat/aeneasrinil = 2,
+ /obj/item/clothing/suit/hooded/wintercoat/science/robotics = 3,
/obj/item/clothing/shoes/sneakers/black = 2,
/obj/item/clothing/gloves/fingerless = 2,
/obj/item/clothing/head/soft/black = 2,
@@ -253,6 +267,8 @@
/obj/item/clothing/accessory/armband/hydro = 3)
refill_canister = /obj/item/vending_refill/wardrobe/hydro_wardrobe
payment_department = ACCOUNT_SRV
+ light_color = LIGHT_COLOR_ELECTRIC_GREEN
+
/obj/item/vending_refill/wardrobe/hydro_wardrobe
machine_name = "HyDrobe"
@@ -351,6 +367,7 @@
products = list(/obj/item/clothing/under/rank/janitor = 2,
/obj/item/clothing/under/yogs/casualjanitorsuit = 2,
/obj/item/clothing/suit/yogs/janitorcoat = 2,
+ /obj/item/clothing/suit/hooded/wintercoat/janitor = 2,
/obj/item/cartridge/janitor = 2,
/obj/item/clothing/under/rank/janitor/skirt = 2,
/obj/item/clothing/under/janimaid = 2,
@@ -372,6 +389,8 @@
/obj/item/reagent_containers/spray/cleaner = 1)
refill_canister = /obj/item/vending_refill/wardrobe/jani_wardrobe
payment_department = ACCOUNT_SRV
+ light_color = COLOR_STRONG_MAGENTA
+
/obj/item/vending_refill/wardrobe/jani_wardrobe
machine_name = "JaniDrobe"
@@ -456,6 +475,7 @@
/obj/item/clothing/under/rank/chemist/skirt = 2,
/obj/item/clothing/shoes/sneakers/white = 2,
/obj/item/clothing/suit/toggle/labcoat/chemist = 2,
+ /obj/item/clothing/suit/hooded/wintercoat/medical/chemistry = 2,
/obj/item/clothing/head/beret/chem = 2,
/obj/item/storage/backpack/chemistry = 2,
/obj/item/storage/backpack/satchel/chem = 2,
@@ -475,6 +495,7 @@
/obj/item/clothing/under/rank/geneticist/skirt = 2,
/obj/item/clothing/shoes/sneakers/white = 2,
/obj/item/clothing/suit/toggle/labcoat/genetics = 2,
+ /obj/item/clothing/suit/hooded/wintercoat/science/genetics = 2,
/obj/item/storage/backpack/genetics = 2,
/obj/item/storage/backpack/satchel/gen = 2)
refill_canister = /obj/item/vending_refill/wardrobe/gene_wardrobe
@@ -492,6 +513,7 @@
/obj/item/clothing/under/rank/virologist/skirt = 2,
/obj/item/clothing/shoes/sneakers/white = 2,
/obj/item/clothing/suit/toggle/labcoat/virologist = 2,
+ /obj/item/clothing/suit/hooded/wintercoat/medical/viro = 2,
/obj/item/clothing/mask/surgical = 2,
/obj/item/storage/backpack/virology = 2,
/obj/item/storage/backpack/satchel/vir = 2)
diff --git a/code/modules/vending/youtool.dm b/code/modules/vending/youtool.dm
index 4d90076586e9..7a83d814bb9e 100644
--- a/code/modules/vending/youtool.dm
+++ b/code/modules/vending/youtool.dm
@@ -3,6 +3,8 @@
desc = "Tools for tools."
icon_state = "tool"
icon_deny = "tool-deny"
+ panel_type = "panel11"
+ light_mask = "tool-light-mask"
products = list(/obj/item/stack/cable_coil/random = 10,
/obj/item/crowbar = 5,
/obj/item/weldingtool = 3,
diff --git a/code/modules/visual/render_step.dm b/code/modules/visual/render_step.dm
new file mode 100644
index 000000000000..e29436596c6e
--- /dev/null
+++ b/code/modules/visual/render_step.dm
@@ -0,0 +1,92 @@
+/**
+ * Internal atom that uses render relays to apply "appearance things" to a render source
+ * Branch, subtypes have behavior
+*/
+/atom/movable/render_step
+ name = "render step"
+ plane = DEFAULT_PLANE
+ layer = FLOAT_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ //Why?
+ //render_targets copy the transform of the target as well, but vis_contents also applies the transform
+ //we'll display using that, so we gotta reset
+ appearance_flags = KEEP_APART|KEEP_TOGETHER|RESET_TRANSFORM
+
+/atom/movable/render_step/Initialize(mapload, atom/source)
+ . = ..()
+ verbs.Cut() //Cargo cultttttt
+
+ 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
+
+/atom/movable/render_step/singularity_act()
+ return
+
+/atom/movable/render_step/singularity_pull()
+ return
+
+/atom/movable/render_step/blob_act()
+ return
+
+//Prevents people from moving these after creation, because they shouldn't be.
+/atom/movable/render_step/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
+ 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
+ * Other stuff too I'm sure
+ */
+/atom/movable/render_step/color
+ name = "color step"
+ //RESET_COLOR is obvious I hope
+ appearance_flags = KEEP_APART|KEEP_TOGETHER|RESET_COLOR|RESET_TRANSFORM
+
+/atom/movable/render_step/color/Initialize(mapload, atom/source, color)
+ . = ..()
+ src.color = color
+
+/**
+ * Render step that makes the passed in render source block emissives
+ *
+ * Copies an appearance vis render_target and render_source on to the emissive blocking plane.
+ * This means that the atom in question will block any emissive sprites.
+ * This should only be used internally. If you are directly creating more of these, you're
+ * almost guaranteed to be doing something wrong.
+ */
+/atom/movable/render_step/emissive_blocker
+ name = "emissive blocker"
+ plane = EMISSIVE_PLANE
+ appearance_flags = EMISSIVE_APPEARANCE_FLAGS|RESET_TRANSFORM
+
+/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
+ *
+ * Copies an appearance vis render_target and render_source on to the emissive plane
+ */
+/atom/movable/render_step/emissive
+ name = "emissive"
+ plane = EMISSIVE_PLANE
+ appearance_flags = EMISSIVE_APPEARANCE_FLAGS|RESET_TRANSFORM
+
+/atom/movable/render_step/emissive/Initialize(mapload, source)
+ . = ..()
+ src.color = GLOB.emissive_color
diff --git a/code/ze_genesis_call/genesis_call.dm b/code/ze_genesis_call/genesis_call.dm
new file mode 100644
index 000000000000..c50abf2b22c8
--- /dev/null
+++ b/code/ze_genesis_call/genesis_call.dm
@@ -0,0 +1,53 @@
+/*
+
+ You look around.
+
+ There is nothing but naught about you.
+
+ You've come to the end of the world.
+
+ You get a feeling that you really shouldn't be here.
+
+ Ever.
+
+ But with all ends come beginnings.
+
+ As you turn to leave, you spot it out of the corner of your eye.
+
+ Your eye widen in wonder as you look upon the the legendary treasure.
+
+ After all these years of pouring through shitcode
+ your endevours have brought you to...
+
+*/
+
+/**
+ * THE GENESIS CALL
+ *
+ * THE VERY FIRST LINE OF DM CODE TO EXECUTE
+ * Ong this must be done after !!!EVERYTHING!!! else
+ * NO IFS ANDS OR BUTS
+ * it's a hack, not an example of any sort, and DEFINITELY should NOT be emulated
+ * IT JUST HAS TO BE LAST!!!!!!
+ * If you want to do something in the initialization pipeline
+ * FIRST RTFM IN /code/game/world.dm
+ * AND THEN NEVER RETURN TO THIS PLACE
+ *
+ *
+ *
+ * If you're still here, here's an explanation:
+ * BYOND loves to tell you about its loving spouse /global
+ * But it's actually having a sexy an affair with /static
+ * Specifically statics in procs
+ * Priority is given to these lines of code in REVERSE order of declaration in the .dme
+ * Which is why this file has a funky name
+ * So this is what we use to call world.Genesis()
+ * It's a nameless, no-op function, because it does absolutely nothing
+ * It exists to hold a static var which is initialized to null
+ * It's on /world to hide it from reflection
+ * Painful right? Good, now you share my suffering
+ * Please lock the door on your way out
+ */
+//Yogstation is not yet ready for the birth of something new and beautiful
+// /world/proc/_()
+// var/static/_ = world.Genesis()
diff --git a/config/config.txt b/config/config.txt
index e077fe2da1f1..e3e17849b3e1 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -422,6 +422,16 @@ DEFAULT_VIEW 19x15
## You probably shouldn't ever be changing this, but it's here if you want to.
DEFAULT_VIEW_SQUARE 15x15
+
+## Enable automatic profiling - Byond 513.1506 and newer only.
+#AUTO_PROFILE
+
+## Threshold (in deciseconds) for real time between ticks before we start dumping profiles
+DRIFT_DUMP_THRESHOLD 40
+
+## How long to wait (in deciseconds) after a profile dump before logging another tickdrift sourced one
+DRIFT_PROFILE_DELAY 150
+
## Comment this out if you want to use the SQL based mentor system, the legacy system uses mentors.txt.
## You need to set up your database to use the SQL based system.
## This flag is automatically enabled if SQL_ENABLED isn't
diff --git a/config/game_options.txt b/config/game_options.txt
index e7a2a000b0eb..9dd863aded73 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -644,6 +644,9 @@ LAVALAND_BUDGET 60
## Ice Moon Budget
ICEMOON_BUDGET 90
+## Jungleland Budget
+JUNGLELAND_BUDGET 40
+
## Space Ruin Budget
Space_Budget 16
@@ -701,5 +704,5 @@ ECONOMY
## Uncomment to enable dynamic ruleset config file.
DYNAMIC_CONFIG_ENABLED
-## Force Engine - 1 for SM, 2 for Sing/Tesla, 3 for any, 4 for AGCNR
+## Force Engine - 1 for SM, 2 for Sing/Tesla, 3 for any, 4 for AGCNR, 5 for TEG
ENGINE_TYPE 3
diff --git a/config/iceruinblacklist.txt b/config/iceruinblacklist.txt
index 4718700e37c2..fe592253d43c 100644
--- a/config/iceruinblacklist.txt
+++ b/config/iceruinblacklist.txt
@@ -11,7 +11,7 @@
#_maps/RandomRuins/IceRuins/icemoon_surface_asteroid.dmm
#_maps/RandomRuins/IceRuins/icemoon_surface_hotsprings.dmm
#_maps/RandomRuins/IceRuins/icemoon_surface_hermit.dmm
-_maps/RandomRuins/IceRuins/icemoon_surface_walkervillage.dmm
+#_maps/RandomRuins/IceRuins/icemoon_surface_walkervillage.dmm
#_maps/RandomRuins/IceRuins/icemoon_underground_puzzle.dmm
_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
#_maps/RandomRuins/IceRuins/icemoon_underground_mining_site.dmm
diff --git a/config/jukebox_music/LICENSE.txt b/config/jukebox_music/LICENSE.txt
index 7089a300038b..a5e126c4cc8b 100644
--- a/config/jukebox_music/LICENSE.txt
+++ b/config/jukebox_music/LICENSE.txt
@@ -91,4 +91,8 @@ LoneDigger.ogg was created by Caravan Palace on September 18, 2015.
CrabRave.ogg was created by Noisestorm on April 1, 2018.
-GloriousMorning.ogg was created by Waterflame on Jul 9, 2009.
\ No newline at end of file
+GloriousMorning.ogg was created by Waterflame on Jul 9, 2009.
+
+WaitingForTheSun.ogg was created by Alistair Lindsay on July 15, 2016
+
+RainOnbrick.ogg was created by Bill Kiley on April 18, 2019
\ No newline at end of file
diff --git a/config/jukebox_music/sounds/Rain_On_Brick+840+5.ogg b/config/jukebox_music/sounds/Rain_On_Brick+840+5.ogg
new file mode 100644
index 000000000000..c282b382a323
Binary files /dev/null and b/config/jukebox_music/sounds/Rain_On_Brick+840+5.ogg differ
diff --git a/config/jukebox_music/sounds/WaitingForTheSun+1900+5.ogg b/config/jukebox_music/sounds/WaitingForTheSun+1900+5.ogg
new file mode 100644
index 000000000000..8e2ab5728501
Binary files /dev/null and b/config/jukebox_music/sounds/WaitingForTheSun+1900+5.ogg differ
diff --git a/config/jungleruinblacklist.txt b/config/jungleruinblacklist.txt
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/config/maps.txt b/config/maps.txt
index 8710bdc5b147..25c97d18a0fd 100644
--- a/config/maps.txt
+++ b/config/maps.txt
@@ -11,8 +11,12 @@ Format:
voteweight [number] (How much to count each player vote as, defaults to 1, setting to 0.5 counts each vote as half a vote, 2 as double, etc, Setting to 0 disables the map but allows players to still pick it)
disabled (disables the map)
votable (is this map votable)
+endmap
+
+# Production-level maps.
map yogstation
+ #default
voteweight 0.7
votable
endmap
@@ -37,11 +41,13 @@ map donutstation
votable
endmap
-map runtimestation
+# Debug-only maps.
+
+map multiz_debug
disabled
endmap
-map multiz_debug
+map runtimestation
disabled
endmap
diff --git a/config/minor_filter.txt b/config/minor_filter.txt
index 808fe7fbd295..aa85b4b3076f 100644
--- a/config/minor_filter.txt
+++ b/config/minor_filter.txt
@@ -46,5 +46,7 @@ pls=please
plz=please
\btf\b=the fuck
\bi\b=I
-auxmos=La Li Lu Le Lo
-aux mos=La Li Lu Le Lo
\ No newline at end of file
+auxmos=space wind
+aux mos=space wind
+\bnp\b=no problem
+\bthx\b=thanks
diff --git a/config/pretty_filter.txt b/config/pretty_filter.txt
index 9910e3015a45..31e095f9c666 100644
--- a/config/pretty_filter.txt
+++ b/config/pretty_filter.txt
@@ -44,4 +44,3 @@ One day while Andy was masturbating=BAN ME ADMINS!
XEzwgBD=BAN ME ADMINS!
\b(?:https?:\/\/)?(?:www|i)?\.?(?!yogstation\.net|github\.com)\w{4,128}\.\w{2}\.?\w{0,2}\b\S*=[Link Removed]
-[^ -ÿ]+=
diff --git a/config/private_default.txt b/config/private_default.txt
index bb807ae7f4c8..832186c7c650 100644
--- a/config/private_default.txt
+++ b/config/private_default.txt
@@ -66,10 +66,6 @@ ENABLE_LOCALHOST_RANK
## The default value assumes youtube-dl is in your system PATH
# INVOKE_YOUTUBEDL youtube-dl
-## Defines the ticklimit for subsystem initialization (In percents of a byond tick). Lower makes world start smoother. Higher makes it faster.
-##This is currently a testing optimized setting. A good value for production would be 98.
-TICK_LIMIT_MC_INIT 500
-
## Should SQL be enabled? Uncomment to enable
#SQL_ENABLED
@@ -90,9 +86,8 @@ VOICE_ANNOUNCE_DIR ../Yogstation.net/voice_announce_tmp
## Enable the demo subsystem
#DEMOS_ENABLED
-## Starlight for exterior walls and breaches. Uncomment for starlight!
-## This is disabled by default to make testing quicker, should be enabled on production servers or testing servers messing with lighting
-#STARLIGHT
+## Enable automatic profiling - Byond 513.1506 and newer only.
+#AUTO_PROFILE
## Assets can opt-in to caching their results into `tmp`.
diff --git a/config/private_server.txt b/config/private_server.txt
index 8b2019e124ee..f8d2ee73d29d 100644
--- a/config/private_server.txt
+++ b/config/private_server.txt
@@ -97,6 +97,9 @@ VOICE_ANNOUNCE_DIR data/voice_announcements
## Enable the demo subsystem
DEMOS_ENABLED
+## Enable automatic profiling - Byond 513.1506 and newer only.
+AUTO_PROFILE
+
## Starlight for exterior walls and breaches. Uncomment for starlight!
## This is disabled by default to make testing quicker, should be enabled on production servers or testing servers messing with lighting
STARLIGHT
diff --git a/dependencies.sh b/dependencies.sh
old mode 100755
new mode 100644
index 8f6360c72b76..b247dba0f144
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -5,7 +5,7 @@
# byond version
export BYOND_MAJOR=515
-export BYOND_MINOR=1620
+export BYOND_MINOR=1621
#rust_g git tag
export RUST_G_VERSION=1.2.0-yogs1
@@ -21,4 +21,7 @@ export SPACEMAN_DMM_VERSION=suite-1.8
export PYTHON_VERSION=3.9.0
# Auxmos git tag
-export AUXMOS_VERSION=434ed4ca7a0bf072f9861bd6e54552af8fb9e27f
+export AUXMOS_VERSION=7854a9e0170189b5293018286de91521c2054026
+#auxlua repo
+export AUXLUA_REPO=tgstation/auxlua
+
diff --git a/html/changelog.html b/html/changelog.html
index 0a630afa9b91..edfa835aec8e 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -37,10 +37,10 @@
Current Head Developers: JamieD12, baiomu Currently Active GitHub contributor list:-Click Here-
- Code Maintainers: adamsogm, AshCorr, Bibby, Cuackles, Djiq, Ling, Manatee, Molti, monster860, TheReddDragon, Wejengin2, ynot01
- Sprite Maintainers: Anerisa, Cuackles, Halcyon
- Map Maintainers: Aquizit, Ezhan, Wejengin2
- Thanks to: /tg/station, FTL13 devs, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, the original SpaceStation developers, Invisty for the title image and Hippiestation for dissapointed.ogg and other assets. Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on our discord.
+ Code Maintainers: adamsogm, AshCorr, Bibby, Chubbygummibear, Djiq, John Willard, Manatee, Molti, monster860, SapphicOverload, ynot01
+ Sprite Maintainers: Anerisa, BlueishTsunami, Cuackles, Halcyon
+ Map Maintainers: Aquizit, Cark, Ezhan, Wejengin2
+ Thanks to: /tg/station, FTL13 devs, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, BeeStation, Shiptest, the original SpaceStation developers, Invisty for the title image and Hippiestation for dissapointed.ogg and other assets. Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on our discord.
Have a bug to report? Visit our Issue Tracker.
Please ensure that the bug has not already been reported and use the template provided here.
Want something minor added? Check out this topic!
@@ -57,1957 +57,1328 @@
-->
-
23 December 2023
-
bruhlookatthisdood updated:
+
23 February 2024
+
Cowbot92 & Chubbygummibear updated:
-
fixed a lot of ai blind spots
+
Fixes a bunch of stuff you step on
+
Added new traitor item, the gasharpoon
-
-
21 December 2023
-
Runian updated:
+
Chubbygummibear updated:
-
Various mood descriptions now properly start on a new line.
-
Space cigarettes now properly spawn with their branded cigarette rather than the base cigarette.
-
Ending punctuation for a few mood descriptions.
-
Removing/extracting IPC's positron brain properly moves their mind.
-
Spells properly transfer over from one mob to another; e.g. wizard shapechange & xenobio burning black shapechange.
-
Rune scimitar works as intended; you can now properly grind and obtain loot.
+
auto_profile config flag added to the private default and private server config files
+
private servers aren't dumpy unless you specifically request them to be
+
main yog server will be extra dumpy because we can handle it
+
no more double clerk and chaplain workplace spawns
-
SapphicOverload updated:
+
Moltijoe updated:
-
cryo pod joining puts you inside the pod instead of on the ground
+
New ethereal bodyparts
+
Old ethereal bodyparts
-
azzzertyy updated:
+
+
21 February 2024
+
Identification updated:
-
Ambience follows ai eye rather than its core.
-
Added paystand to bar and kitchen, decreased wideness of kitchen, made the raised section look logical.
+
Restores walls to true top-down.
-
-
20 December 2023
-
Mqiib updated:
+
sapphicoverload, syncit21, zxaber updated:
-
Fixes spray cans/crayons not working
+
added a bunch more equipment for utility mechs
+
adds new sound effects for hydraulic clamp and APLU mech movement
+
hydraulic clamp works as a wrench or crowbar and can pry powered doors
+
mech plasma cutter can be used as a welder like the handheld version
+
mech RCD works like an actual RCD now
+
adds tgui RCD menu
+
strafe button icon shows whether or not you're strafing
+
APLU and Clarke mechs now have radiation shielding
+
fixed clarke movement not working correctly
+
fixes and re-adds multi-Z mech movement (no phasing through floors or teleporting to other z-levels this time)
-
Runian updated:
+
13spacemen updated:
-
You can now use a wrench to anchor armed beartraps after a second delay. It can be disarmed with an empty hand with a second delay.
-
Message for arming and disarming beartraps properly ends with a period.
+
The latejoin menu no longer has anemia.
-
ToasterBiome updated:
+
AMyriad updated:
-
Removes arrival shuttle from NVS Gax, people spawn in cryo now
+
Fixed the eye of god expose cooldown going off when an invalid target is selected
+
Fixed mention of Weyland-Yutani appearing in the ripley mech repair manual
+
"Hyperspace" no longer exists and never did, FTL travel is achieved by tunneling through bluespace
+
Players can no longer relabel gas canisters to a variant that's intended for admins and breaks the 4th wall
-
ynot01 updated:
+
Chubbygummibear updated:
-
Disarming someone holding a gun now has a high chance to force them to fire
+
index_out_of_bounds fixed on blend_cutoff_colors
+
turfs that smooth will spur their neighboring turfs to check their smoothing again when late loaded in
+
cables on the correct plane so they don't have weird lattice lighting filters
+
meteors with space lighting overlays rotate with the meteors instead of being disjointed and unmoving
+
shuttle atmos shouldn't get left behind on takeoff
-
-
19 December 2023
-
Scrambledeggs00 & Molti updated:
+
LazennG updated:
-
new vampire cloak
-
old vampire cloak
+
fixed seismic suplex animation and some text
-
cark updated:
+
adamsong updated:
-
fixes mislabeled cameras in chapel on box
+
fixed shuttle catastrophe
+
fixed admin shuttle manipulator
-
cark, cowbot93 updated:
+
cowbot92 updated:
-
improves new bar by fixing issues and adding coffee
+
adjust some pop requirements for several antags
-
SapphicOverload updated:
+
redmoogle updated:
-
icemeta's useless solar arrays have been replaced with geothermal power stations
+
PC Range Upgrade
+
PC Cooldown Upgrade
+
Vending machines not containing upgrades
+
All plasma cutters can be upgraded
+
Plasmacutters can be recharged with one click now
+
+
warface1234455 updated:
+
+
Partially revert undocumented change to shutter/blastdoor layer covering window but still keep the blast door covering the door
-
18 December 2023
+
20 February 2024
+
Chubbygummibear updated:
+
+
fix adminwho by skipping null clients in the admin list
+
cowbot92 updated:
-
Adjusts the singulo/tesla engine template
+
fixed beartraps not trapping
+
+
warface1234455 updated:
+
+
cable coil maxstacks is now 40
-
17 December 2023
+
19 February 2024
SapphicOverload updated:
-
fixed a bunch of things being able to behead when they shouldn't
Pitying the miners attempting to look for it, the Demonic Frost Miner now gives out a Hollow Signal on GPS.
+
Syndie Borgs Spawners removed from BR loot pool.
-
azzzertyy updated:
+
Runian updated:
-
You can now point while handcuffed
+
Four tech node dedicated for cyborgs upgrades: Night Vision, Engineering, Mining, and Service. Each costing 500 points.
+
Various cyborg upgrades have been moved to more relevant/dedicated technodes.
+
Most tech nodes that involve cyborg upgrades now require the "Advanced Robotics" tech node or a node that already has it as a prerequisite.
+
Most tech nodes that involve cyborg upgrades have now a better description that explains the purpose of the node.
+
"Cyborg Upgrades: Advanced Utility" tech node has been renamed to "Cyborg Upgrades: Advanced Engineering".
+
"Cyborg Upgrades: Advanced Engineering" tech node cost reduced to 2,500 from 5,000.
+
"Cyborg Upgrades: Utility" tech node cost reduced to 1,500 from 2,000.
+
"Cyborg Upgrades: Engineering" and "Cyborg Upgrades: Mining" tech node's icon has been changed to better visualize the node.
+
Janiborg's trashbag of holding upgrade is properly locked behind Advanced Sanitation Technology.
+
Medical gripper can now hold medsprays, lollipops, pills, patches, gummies, and chemistry bags.
-
cowbot92 updated:
+
SapphicOverload updated:
-
Adds new bar signs
+
beam rifles now create turf fires instead of hotspots
+
beam rifle projectiles can always reflect off of coins
+
fixed hitscan projectiles not rendering properly when reflected
+
fixed beam rifle projectiles not doing damage when fired from turrets or emitters
-
-
30 November 2023
-
ToasterBiome updated:
+
SomeguyManperson updated:
-
Adds a new lavaland lava type that you can't replace with shelter
-
lavaland syndie base is surrounded by no-shelter lavaland lava
+
limb loss in brazil is now worth 7 marbles
+
limb skeletonization in brazil is now worth 3 marbles
+
If you somehow manage to run out of limbs while leaving brazil, you will be ejected
-
-
29 November 2023
-
Moltijoe updated:
+
jachlompsky updated:
-
Brig physician also gets cloning and airlock access during skeleton crew
-
Paramedic also gets surgery access during skeleton crew
-
Mining medic also gets cloning, maint, and airlock access during skeleton crew
-
All hecata zombies die when the controlling bloodsucker dies
-
lightning flow no longer immobilizes for like 0.1 seconds every dash
-
Preterni no longer get 20% more power from liquid electricity than other powerhungry species
-
All powerhungry species now get "food" from consuming teslium, not just Preterni
+
Added a frost oil chem sprayer to the mr freeze syndikit
-
Runian updated:
+
warface1234455 updated:
-
Lizard tail users can *thump their tail.
-
Tail thump.
-
Guardian's Frenzy Ability now respects the three second cooldown that it is suppose to be limited by.
+
Shitcurity uniform now has fucking armor
+
Nightshift lights now actually consume less power
+
Removes other useless power vars as they dont work with the light fixture anyway
+
Cyborg engineering gripper can now hold server rack and cpu
-
ToasterBiome updated:
+
wonderinghost updated:
-
Removes tile grime and support for it.
-
Replaces tiles to be less shiny.
+
Cluwncurse takes 3 minutes instead of 1 minute
-
ynot01 updated:
+
+
29 January 2024
+
ktlwjec updated:
-
Command channel is now blue once more
-
fixed pseudocider not duplicating headwear
+
All pizzas need mozzarella to craft (5u enzyme, 30u cow milk, 5u lemon juice).
+
Mime finger guns properly show what was shot at, and by who.
+
Mime finger gun button has a working icon.
-
-
28 November 2023
-
MajManatee updated:
+
13spacemen updated:
-
Horrors can now scan their hosts
-
Horror chemicals now say how much they inject
+
Compiling the code launches directly into Dream Seeker
Moltijoe updated:
-
Only Delta alert gets red lights
-
Fixes an IAA bug involving pseudocider
-
adds a glowing outline to anyone with the holy light heal boost
-
fixed a bug where limb healing generated less favour than overall healing
-
Increases the holy light heal boost duration to 1 minute from 30 seconds
-
Reduces the amount of favour that holy light generates by 50%
-
Gives warden brig phys access during skeleton crew
-
Makes monster hunters monster hunting objective actually tell the monster hunter to hunt monsters
+
Fixes a bug with polysmorph tail crawlspeed
-
Runian updated:
+
SapphicOverload updated:
-
Constructed anchored emitters are correctly recognized as wrenched; you no longer need to wrench these anchored emitters twice to make it work.
-
Flying Fang's damage-dealing disarms respects pacifism as intended.
+
fixed budget insulating gloves melting when touching unpowered cables
-
SapphicOverload updated:
+
adamsong updated:
-
fixed fire extinguishers not working if the particle never moves
+
Added option for admins to spawn items directly in a storage container
-
cowbot92 updated:
+
bruhlookatthisdood updated:
-
Added new fruit
-
Added new drink to go with that fruit
-
added some icons and images for lantern fruits and drinks
+
bonesetter spawns during battle royal now
-
-
27 November 2023
-
goober3, cark updated:
+
cowbot92 updated:
-
added some dirt linings
+
removes infinite money in the form of syrup
-
Moltijoe updated:
+
jachlompsky updated:
-
Syndicate fedora can no longer be caught using throw mode
+
fixed lasombra vassals and eye examine
-
Mqiib updated:
+
wonderinghost updated:
-
Lizard wings now use the new lizard colors instead of the slightly-off old colors
+
updates min and max values of byond_version_combat
-
Runian updated:
+
ynot01 updated:
-
Ion storms can affect up to 20 airlocks and 10 APCs with random effects, only if is no AI.
-
Airlocks affected by ion storms will randomly have one of the following effects: toggle bolts, toggle emergency access, shock temporarily or have their safety & speed toggled.
-
APCs affected by ion storms will have one of three or all of their power channels turned off.
+
Live xeno aliens on station level now passively generate research points
+
+
28 January 2024
SapphicOverload updated:
-
preternis now purge 4% of internal chemicals every tick, instead of purging everything after 20 ticks
-
preternis are no longer immune to the effects of morphine and a few other chemicals
-
fixed unintended change to damage of every two-handed weapon
-
fixed runtime error when trying to use certain two-handed weapons with one hand
-
fixed preternis being affected by water through hardsuits
+
fixed being unable to analyze the instability of cold fusion reactions
+
IPCs, androids, polysmorphs, and slimepeople now have unique typing indicators
+
Using harm intent makes your typing indicator angry
+
+
adamsong updated:
+
+
fixed mfa backup codes not working
cowbot92 updated:
-
Ports updated TG announcements & TGUI
+
Makes the deep fryer hungry, occsionally
+
fixed the faded eyepatch not giving you night vision despite it telling you it does
-
26 November 2023
-
Moltijoe updated:
+
27 January 2024
+
ktlwjec updated:
-
Battle royale loot distribution
+
Removes a line in the sith cloak sprite.
-
SomeguyManperson updated:
+
+
26 January 2024
+
Runian updated:
-
hallucination anomalies have had their effects altered. You many want to bring a toolbox instead of mesons when responding to one
-
hallucination reactive armor has had its effects modified similarly to the new effects of the anomaly
-
the supermatter will no longer spawn hallucination anomalies when exploding, on fire, or otherwise.
+
Shooting a cyborg snack dispenser while in a zero gravity environment correctly starts moving you in the opposite direction that you shot it.
-
25 November 2023
-
Ghommie updated:
+
23 January 2024
+
Moltijoe updated:
-
Examining a human mob as an observer displays "Quirks", not "Traits"
+
Fixes an issue with blood decals
-
JohnFulpWillard updated:
+
+
22 January 2024
+
Moltijoe updated:
-
Seclites now have directional lights- point them in the direction you want to see more of.
-
Ported over many refactors to lighting and opacity. Please report if you can walk or see through walls unintentionally.
+
Added new maroon organ objective for traitors to roll
+
Traitor break machinery objective only requires breaking the original machines
+
Traitor break machinery targets at max 2 areas instead of up to 4
-
Majkl-J updated:
+
Mqiib updated:
-
Virology: New symptom, superficial healing
+
Imperial jumpsuits don't have alt styles, stop trying to break the dress code soldier!
-
Runian updated:
+
Yarinoi updated:
-
Shooting plasma sheets with a damaging burn-based projectile ignites it.
+
'Solo' special syndi-kit is now slightly more balanced. David. David you aren't going to let this stop you are you David. Get to work David ignore the EMPs. Take your pills get to work.
-
SapphicOverload updated:
+
+
21 January 2024
+
ktlwjec updated:
-
added preference for non-upgraded cybernetic organs as quirk options
-
random cybernetic organ quirk costs 3 instead of 4
+
Mammi and Pea Soup need bowls to craft.
-
cowbot92 updated:
+
+
20 January 2024
+
kugamo, warface1234455 updated:
-
Adds auxmos to the minor filter
+
Resprites the Bluespace Harvester.
+
Also also slightly changes the nether portals event, so that the portals spawn sequentially over at most 15 seconds.
-
23 November 2023
+
19 January 2024
SapphicOverload updated:
-
power-fist works like a glove now
-
-
SomeguyManperson updated:
-
-
gangrel gorilla transformation is now 15 armor down from 40
-
-
Yarinoi updated:
-
-
Medical can print chemical analyzer implants again.
+
added stairs to icemeta's mining base
+
added directional stairs icons
+
fixed regenerative cores having a delay on icemoon
warface1234455 updated:
-
Remove the portable pump cargo pack
+
Fix invisible netmin wintercoat hood
+
Bring back Raven cruiser emergency shuttle
-
ynot01 updated:
+
+
18 January 2024
+
SapphicOverload updated:
-
The door between the armory and the auxiliary armory are now default security access
+
made the chat message from losing obsession more visible
+
you can now place items on the altar of the gods without having to climb on top of it
-
22 November 2023
-
PositiveEntropy, Cark, Blutsu updated:
+
17 January 2024
+
Loiosh42 updated:
-
new wood sprites from TG, and resprites on stairs by Blutsu
-
replaces old wood tile sprites
+
Adds an ignition_effect to the cautery.
-
cark updated:
+
+
15 January 2024
+
bruhlookatthisdood updated:
-
bridge on donut now has an APC and blast doors, apc in chapel is moved and some other minor fixes
+
Waiting For The Sun (Rimworld OST) is in jukebox now
-
00ze-cyclone updated:
+
cowbot92 updated:
-
Removed helmet, bulletproof helmet and riot helmet crates
-
armor, bulletproof armor and riot armor crates now contain helmets as well, price adjusted to cost the same as before
+
fixes decals
-
JohnFulpWillard updated:
+
+
13 January 2024
+
SapphicOverload updated:
-
Pancake overlays now work
+
fixed chat not always showing up in replays
+
+
12 January 2024
SapphicOverload updated:
-
fixed people with the bald trait not being bald when cloned
+
adds more winter coats
+
moves winter coat and hood sprites to their own files
Therandomhoboo updated:
-
Glycerol making results in 3 units instead of 1
-
Reinforced Window Damage deflection is now 11 instead of 10
+
Astrotame, bbq, sugar, cream & chocolate are now 10u's instead of 5.
+
Chocolate packets are now correctly called chocolate packets instead of Creamer....
-
wonderinghost updated:
+
warface1234455 updated:
-
Adds new programs and hardware to pdas
+
FIx preset programs arent installed in pda/phone
-
20 November 2023
-
Cowbot92 & ChubbyGummibear updated:
-
-
tweaks heretic TGUI
-
tweaks heretic blade's wound chance
-
-
Moltijoe updated:
+
11 January 2024
+
Runian updated:
-
Fixes a bug where lightning flow disabled certain actions if a dash ended weirdly
-
Makes syndicate fedora and boomerang not super janky
-
Syndicate fedora has a longer click throw cd
-
Fixes extended shock touch not having a cooldown or sound effect
+
The description of ammo boxes now accurately show how much bullets are left when you use an ammo box on another ammo box.
-
19 November 2023
-
Cark, Paxilmaniac updated:
-
-
added a stone floor icon
-
-
JohnFulpWillard, warface1234455 updated:
+
10 January 2024
+
AMyriad updated:
-
Atmos alert console can clear air alarm atmos alert
-
During an active fire nearby a firealarm, it will show you the current temperature when examined
-
Firealarm now notify on engineering channel when theres a fire
-
Fix station alert console and program not updating overlays
-
Fix fire alerts not clearing when there are multiple fire alarms in one area
-
fix destroyed airalarm clearing atmos alert if theres an actual atmos alert in an area with multiple air alarms
+
Dusted plasmamen now have plasma remains
-
cark updated:
+
Runian updated:
-
fixes issues with the asteroid field on donut causing people to get teleported inside rocks when moving onto the z-level
-
adds more wall extinguishers to donut
+
Override Machine and Overload Machine abilities (from malf AI/malf upgrade disk) now work.
+
Nerve Grounding and other ways that change your physiology's siemens coeff properly protect you from getting electrocuted.
-
Moltijoe updated:
+
Scrambledeggs00 updated:
-
Lets admins choose the amount of TC given when they make everyone traitor
+
Added a new thing to IceMeta
+
Sprites for the new thing
-
SapphicOverload updated:
+
ToasterBiome updated:
-
mech extinguisher uses firefighting foam instead of water
-
fixed extinguisher particles not disappearing when they stop moving
-
fixed blocking your own extinguisher when moving forward
-
lizard tails grow back properly
-
fire extinguishers can once again extinguish fire
+
rating polls now work properly
+
Updates maintainer list and thanks more stations in our special thanks section
bruhlookatthisdood updated:
-
[Donut] Moved medical laptop in brig medical to make medical supplies accessible
+
ASay and DSay are now no longer visible to non-admins
-
ynot01 updated:
+
wonderinghost updated:
-
Added Christ Christodoulou - Coalescence (2023) to the lobby playlist
-
Removed Christ Christodoulou - Coalescence (2013) from the lobby playlist
+
fixed my mistakes
+
makes the prior pr picture perfect
-
18 November 2023
+
09 January 2024
Aquizit updated:
-
Removed comms /maintenance/ area and icon
-
Added /tcommsat/storage area and icon
-
Replaced maint with storage on Box & Asteroid
-
Fixed area swap on Asteroid
+
The innkeeper can now get stuff from their booze-o-matic and kitchen vending machines.
-
17 November 2023
-
Majkl-J updated:
+
08 January 2024
+
cark updated:
-
Printed IPCs may now die loud
+
fixes some railings in bar
+
Reenables Icemoon Walkers
-
Moltijoe updated:
+
Therandomhoboo updated:
-
Removes the obsolete healing surgeries
-
Makes admin spawned ntuplinks work
+
Moonshine now makes you gain blurry, long live blurry vision
-
16 November 2023
+
05 January 2024
AMyriad updated:
-
Items dealing 14-20 force are now "robust," changed from 11-20
-
Items dealing 10-14 force are now "high," changed from 10-11
+
Updated the storage implant's toggle sprite
-
SapphicOverload updated:
+
EdgeLordExe updated:
-
plasma cutter projectiles glow and check against energy armor
+
New Mining area: Jungleland, Most content added by Djiq, Most ruins, some mobs and Ivymen added by Marmio64. Other ruins added by ToasterBiome. Various fixes and updates by courtesy of Jamie1D and SapphicOverload!
+
New Mobs now appear on jungleland, such as Meduracha, Dryads, Giant mosquitoes, and Skin Twisters!
+
New Mining implement: Kinetic Javelin, get it either from the roundstart kit, or buy it in a mining vendor.
+
New Megafauna: Tar King - You can summon it by collecting 3 parts of a broken crystal, assembling it, and placing it in a Forgotten Altar!
+
Ivymen - Jungleland's counterpart to ashwalkers, by Marmio64
+
Jungleland has many new biomes, beware of sulphuric pits, as it will cause you trouble if you step into it.
+
Added new icons and icon states for jungleland
+
Added Jungleland as a new mining destination!
-
15 November 2023
-
Mqiib updated:
+
04 January 2024
+
ZephyrTFA, san7890 updated:
-
One of the hos armor variants now has a digitigrade sprite
-
Hos leather coat now uses its digitigrade variant
+
fixed chat messages not showing up sometimes
-
SomeguyManperson updated:
+
Aquizit updated:
-
a number of changeling abilities have had their chemical costs increased to balance out a chemical maximum and regeneration speed buff
-
adrenaline now costs 50 chemicals up from 30
-
fleshmend now costs 40 chemicals up from 20
-
panacea now costs 30 chemicals up from 20
-
resonant shriek now costs 60 chemicals up from 20
-
dissonant shriek now costs 40 chemicals up from 20
-
spawning spiders now costs 90 chemicals up from 45, the "no fun allowed" chanting can start here
-
mute sting now costs 40 chemicals up from 20
-
blind sting now costs 40 chemicals up from 25
-
cryosting now costs 30 chemicals up from 15
+
Removed disposals air alarm
+
Moved windoor in disposals
+
Scrubs a couple bad AI Ion Law things
-
cowbot92 updated:
+
Runian updated:
-
Adds new station trait: Moonscorch
+
The threshold description for symbiotic regeneration is now accurate.
-
-
14 November 2023
-
cowbot92 updated:
+
SapphicOverload updated:
-
Adds new heretic stuff
-
Removes some heretic stuff
-
fixes some heretic stuff
-
tweaks some heretic stuff
-
adds new heretic icons
+
polysmorph tails slightly increase crawl speed
+
fixed printed IPCs having human names instead of IPC names
+
supermatter crystal and nuclear reactor are less unstable
Slurring now happens based on what kind of drinker you are (Light, normal or heavy drinker which are based on traits)
+
Slurring duration changed based on drinker type
+
The constant chance to randomly slur, we can drink now
-
AMyriad updated:
+
+
03 January 2024
+
Cowbot92 & Molti updated:
-
Marine quirk text now explains what it does
+
tweaks how blood works
+
adds new blood
+
removes the old bad blood
+
fear the old blood
Moltijoe updated:
-
Deletes "How to suck blood 101"
-
Adds vampire blood sucking guide to the antag menu
-
Adds a new ethereal species martial art
-
Granter books no longer repeat remarks
-
-
RG4ORDR updated:
-
-
Lowered radiation pulse from 1000 to 500
-
Lowered particle max spawn from 15 to 5.
+
Ethereal eyes now give +1 view range
-
thegoldencat413 updated:
+
+
29 December 2023
+
SapphicOverload updated:
-
added slugcat plushie
-
added sprites for slugcat plushie and inhands
+
exosuits now have directional lights, with much longer range
-
-
12 November 2023
-
ChesterTheCheesy updated:
+
ToasterBiome updated:
-
clockwork golems are now golems and not robots made of brass, making them immune to the effects of their own EMP
+
Project Bee has a new sprite and description
-
cowbot92 updated:
+
jupyterkat updated:
-
fixes echolocation
+
updated auxmos to latest
-
11 November 2023
+
25 December 2023
cark updated:
-
Due to budget cuts, NT has retrofitted an old escape pod dock on Donutstation to accept shuttles instead. Additionally, console software in numerous Head of Staff offices has been updated to the newest version.
-
-
TheGamerdk updated:
-
-
On very rare occassions, clapping will turn toggle the lights in an area
+
Icemoon Hermit's home has been improved with more amenities
+
fixes there being too many firelocks under a door in AI sat on Donut
-
Therandomhoboo updated:
+
Moltijoe updated:
-
The stupid Marine Quirk to let you eat fucking crayons
-
Made taking bites of crayon give you stacks of disgust because you're eating wax and shit.
+
Adds more tool options for ghetto mechanical surgery
+
existing ghetto mechanical surgery options are slightly better
-
ynot01 updated:
+
N3D6 updated:
-
fixed pseudocider not duplicating mask
+
moves smes units from engi foyer to the former gravity generator area on icemeta
-
09 November 2023
-
Majkl-J updated:
+
24 December 2023
+
Moltijoe updated:
-
Added invisible touch to mimes
-
Sprites for invisible touch
+
Lets hand-based extinguishing be used on corpses
+
Gives polysmorph minor resistance to pressure damage
+
Preterni no longer regenerate blood
-
-
08 November 2023
-
Moltijoe updated:
+
Runian updated:
-
Fixes bad wizard's abilities not being linked to their mind
+
Trying to remove decoy brains from MMIs properly works.
+
Removing MMI brains from range no longer teleport it into your hands if you're far away (e.g. through walls/windows with TK). Same for folding paper.
+
Ending punctuation for ripping out brain from MMI.
+
IPCs now play their special walk sound when they move as intended.
Removes the "WARNING. THE WAY AI IS PLAYED HAS CHANGED" message when joining as AI
-
ktlwjec updated:
+
hedgehog1029 updated:
-
Energy Harvester Control and NTNet Diagnostics and Monitoring PDA programs moved from misc to engineering category.
+
XL beakers show contained chemicals again
-
Mqiib updated:
+
+
23 December 2023
+
bruhlookatthisdood updated:
-
Security winter coat hood has an item state now
+
fixed a lot of ai blind spots
+
+
21 December 2023
Runian updated:
-
Slipping on certain things (e.g. liquid-content slippery-skin plants) now trigger their callback.
-
Syndicate Saboteur's Cyborg Chameleon Projector can now disguise as any cyborg module that is available to normal cyborgs.
-
Syndicate Saboteur's Cyborg Chameleon Projector can be alt-clicked to re-randomize the disguise name.
-
-
ynot01 updated:
-
-
All mechs built by hand no longer cost energy to move
-
Scanning modules on mechs now affect all power usage
+
Various mood descriptions now properly start on a new line.
+
Space cigarettes now properly spawn with their branded cigarette rather than the base cigarette.
+
Ending punctuation for a few mood descriptions.
+
Removing/extracting IPC's positron brain properly moves their mind.
+
Spells properly transfer over from one mob to another; e.g. wizard shapechange & xenobio burning black shapechange.
+
Rune scimitar works as intended; you can now properly grind and obtain loot.
-
-
06 November 2023
-
MajManatee updated:
+
SapphicOverload updated:
-
Mentors no longer try to ahelp new people
+
cryo pod joining puts you inside the pod instead of on the ground
-
Moltijoe updated:
+
azzzertyy updated:
-
Makes flying bloodsplats actually work
+
Ambience follows ai eye rather than its core.
+
Added paystand to bar and kitchen, decreased wideness of kitchen, made the raised section look logical.
-
05 November 2023
-
Timberpoes, ToasterBiome updated:
+
20 December 2023
+
Mqiib updated:
-
Adds new Master RND server, with a special hard drive that if removed, halves point generation.
-
Add a new "Steal Master HDD" objective, where you must steal the hard drive and escape with it.
-
Gives Ninja a different objective to sabotage research, and using their gloves on the server will drain all the points.
-
Removes "Steal Research" objective as it's stupid easy.
-
-
ktlwjec updated:
-
-
Renames cyborg sheet snatcher.
-
-
Ghommie, KylerAce, LemonInTheDark, Fox McCloud, JohnFulpWillard updated:
-
-
Footsteps have been reworked, please make an issue report if they stop working.
-
-
MajManatee updated:
-
-
Mentors get alerted to new players connecting for the first time.
-
-
Moltijoe updated:
-
-
Slightly rewords researcher lawset for reading comprehension
-
Prevents printing of mutations that can't otherwise be saved
-
Touch spells now properly check if the target is a valid target
-
Hug fire_stack transfer now works properly again
-
Drying off no longer takes 10x as long as it used to
-
Being on fire heats up as fast as it used to
+
Fixes spray cans/crayons not working
Runian updated:
-
A pair of machines that allows people to mind swap between bodies of their choices. For fun, science, or torture; who knows what it will be used for?
-
New preferences to enable TGUI and more.
-
Anything that uses tgui_input_text and tgui_input_number now have better looking input boxes. Can be disabled via preferences.
-
Hand labelers now uses tgui_input_text.
-
Janitor Cyborgs can go through wet floor holobarriers.
-
Janitor Cyborgs can now start a process which wets (and cleans) the floor under them at the cost of movement speed and requiring filling their bucket with water.
-
Janitor Cyborg's auto-cleaning ability is now an upgrade instead of being given to them round-start.
-
Various sounds when a janitor cyborg starts and stops their new cleaning process.
-
-
SomeguyManperson updated:
-
-
You can now fix damaged pAIs by submerging their card in a bowl of rice for a few seconds.
-
-
redmoogle updated:
-
-
Remote Light Switch
-
Light Switch Wallframe
-
APCs can now toggle lights
-
-
-
04 November 2023
-
Chubbygummibear updated:
-
-
lighting should actually update in replays
-
player directionality should actually update in replays
-
-
-
03 November 2023
-
Moltijoe updated:
-
-
Can no longer do mechanical surgeries on simple mobs that aren't robotic
-
-
Mqiib updated:
-
-
You can now put TWO HUNDRED characters in a unique item description!
-
-
Runian updated:
-
-
Cyborgs & drones can properly screwdriver open/close autolathes again.
-
-
cowbot92 updated:
-
-
nerfs void cloak's armor values
-
Adds new heretic path
-
Adds new heretic sounds
-
Adds new heretic portal icon and abilities
-
-
thegoldencat413 updated:
-
-
Added new lobby music
-
-
ynot01 updated:
-
-
Fixed team objectives not showing up in status panel
-
-
-
02 November 2023
-
ynot01, thegoldencat413 updated:
-
-
Added the pseudocider to the stealthy tools uplink section for 6TC. Activate it and the next time you take damage, you will feign your death or stun!
-
-
bruhlookatthisdood updated:
-
-
donut janitor closet/security maintenance doors have better access requirments.
-
donut medical cryo freezer room maintenance door no longer lets those with maintenance access in and is now under the exam room's area.
-
-
cowbot92 updated:
-
-
Adds new anti-magic item for security
-
Tweaks around some heretic spell reqs
-
Adds new sprites for anti-magic collar
-
-
ynot01 updated:
-
-
Added a blackjack PDA app
-
-
-
01 November 2023
-
cark updated:
-
-
fixes 2 mapping errors on donut
-
fixes some atmos issues in perma hallway on box
-
-
JohnFulpWillard updated:
-
-
Golf now works.
-
Different golf holes can now be differentiated.
-
Golf doesn't need power anymore.
-
Golf closets now come with all golf equipment.
-
-
Moltijoe updated:
-
-
Venus human traps slowly regen if standing near vines
-
Venus human traps lifesteal more, but must be near vines to do it
-
Venus human traps can now be hurt by weedkiller that's applied ways other than in a gas cloud
-
neural overclocker also gives a slight crawl speed increase
-
neural overclocker costs 12tc instead of 14tc for traitors
-
neural overclocker costs 20tc instead of 14tc for nukies
-
toy neural overclocker is slightly more common in maints
-
more things can be rusted
-
leeching walk also slowly restores missing blood
-
Aggressive spread now has a 25 second cooldown instead of 30 second
-
Refunding discounted items no longer gives more tc than it costed
-
-
Mqiib updated:
-
-
You should no longer have an extra-wide stance with digitigrade robot legs
-
Fixes blue scrubs digi alt not having a sprite
-
-
hedgehog1029 & papi updated:
-
-
Added new symptom sprites to the PanD.E.M.I.C UI
-
-
warface1234455 updated:
-
-
Mentors can now mentor pm AI camera eye
-
-
wonderinghost updated:
-
-
allows the sheet snatcher to hold rods and tiles
-
-
-
30 October 2023
-
MajManatee updated:
-
-
Removes [explitive] lawsets, increases good ones
-
-
Moltijoe updated:
-
-
Changed how fire and water are handled in code
-
adds sprites for water particles and new fire sprite
-
Let me know about any bugs involving fire on mobs
-
-
azzzertyy updated:
-
-
New punk accent
-
-
warface1234455 updated:
-
-
FIx shuttle call annoucement with x2 Nature of emergency
-
-
-
29 October 2023
-
Addust updated:
-
-
Charlie Station's nuclear reactor now has shutters, a damage report, and is in general now slightly idiot-resistant.
-
-
Moltijoe updated:
-
-
Ethereal crystal revive now actually applies a brain trauma
-
Ethereal heart no longer decays (can cause issues with reviving)
-
-
cowbot92 updated:
-
-
fixes some code
-
-
warface1234455 updated:
-
-
Healium inside coolant can now restore integrity of the reactor. This only work if the reactor is below 1800K
-
Updated guide to reactor 101 paper
-
-
ynot01 updated:
-
-
You can no longer use tape on the model settler ship
+
You can now use a wrench to anchor armed beartraps after a second delay. It can be disarmed with an empty hand with a second delay.
+
Message for arming and disarming beartraps properly ends with a period.
-
-
28 October 2023
ToasterBiome updated:
-
Moves evac up on AsteroidStation, removes petting zoo and adds small garden where evac used to be.
-
-
adamsong updated:
-
-
Added system for loading json event data
-
Added a system to assign roles to players in the lobby
+
Removes arrival shuttle from NVS Gax, people spawn in cryo now
ynot01 updated:
-
Toy sledgehammer can no longer be used to propel yourself forward in space or over tables
+
Disarming someone holding a gun now has a high chance to force them to fire
-
27 October 2023
-
OnlineGirlfriend, ktlwjec updated:
-
-
Pineapple snow cone needs 5u pineapple juice to craft, instead of 2 pineapple slices.
-
Pwr game snow cone needs 5u pwrgame to craft, instead of 15u.
Facehuggers knock out for 15 seconds instead of 50 seconds
-
Facehuggers take 2 seconds to pry off your face
-
Adds restricted ghost beacons
-
reverts all existing ghost beacons to being unrestricted
-
-
cowbot92 updated:
-
-
Adjusts Brazil (Heretic sac zone) a bit.
-
-
-
26 October 2023
-
AMyriad updated:
-
-
Tweaked and improved the Nordic accent
-
-
LazennG updated:
-
-
fixed tome bugs concerning loot and an animation
-
-
MajManatee updated:
-
-
New boxes and bags of randomized ammo for antags! Get creative!
-
-
Time_URSS updated:
-
-
New medkit + briefcase sprites
-
-
redmoogle updated:
-
-
Holopads no longer freak out if you call a nonfunctioning pad
-
-
warface1234455 updated:
-
-
Ethereal heart now gives a crystallization cooldown timer on status panel
+
fixes mislabeled cameras in chapel on box
-
-
25 October 2023
-
JohnFulpWillard updated:
+
cark, cowbot93 updated:
-
Wands now work as intended again.
-
Wizard hardsuits no longer block spell usage.
+
improves new bar by fixing issues and adding coffee
SapphicOverload updated:
-
fixed nuclear reactor not outputting power correctly
-
-
ToasterBiome updated:
-
-
box lawyer office actually has spawns now
+
icemeta's useless solar arrays have been replaced with geothermal power stations
-
24 October 2023
-
Addust updated:
-
-
oopsies
-
charlie station AGCNR no longer blows out invariably
-
-
Aquizit updated:
-
-
Lawyers now have access to their shutter buttons
-
-
LoliconSlayer updated:
-
-
Asimov++ law 3 no longer contradicts itself
-
+
18 December 2023
cowbot92 updated:
-
Adds new Heretic paths
-
Adds new heretic sounds
-
added new sprites, icons, and actions
-
-
-
23 October 2023
-
Apogee-dev, Azarak, CydiaLamiales, PestoVerde322, BriggsIDP, Absolucy, Molti updated:
-
-
Added a random 1-3 second delay to lights turning on, plus a sound
-
added a sound for light fixtures being turned on
-
Added a station-wide light flicker after a powerful explosion or worldbreaker stomp
-
Made unstaffed departments start off with their lights turned off
-
-
PowerfulBacon, Sinestia, ToasterBiome updated:
-
-
Each area can now have their own tube and bulb color
-
Some old floors that should have been decals were deleted
-
Light overlays are brighter and no longer emissive (so you can see them in light)
-
Floors have been redesigned
-
-
Therandomhoboo updated:
-
-
Hearty punch output gone from 1 to 6
-
-
redmoogle updated:
-
-
Experimental tools can now be potentially found in maint
+
Adjusts the singulo/tesla engine template
-
22 October 2023
-
Identification updated:
-
-
Stechkin grip edited by 1 pixel to look more anatomically correct.
-
-
RaveRadbury, ktlwjec updated:
-
-
Space Mountain Wind snow cone.
-
Snow cones do not need water to craft.
-
Sodawater snow cones are now Space Cola snow cones.
-
Clown snow cone now needs laughter, not clown's tears.
-
Better descriptions for snowcones. Removes flavored from the snowcone names.
-
-
coiax, Bazelart at TGI Friday's, TheBonded, Ghilker, mystery3525, Rukofamicom, Shiraizawa, DrDiasyl, John Willard and Warface1234455 updated:
-
-
New resonance cascade effects because old one is boring as shit, this should make the crew panic **DEATH RATE: >60%**
-
New hallucination anomaly which creates powerful hallucination pulses.
-
Each of the reactive armors now have unique negative effects when emp'ed.
-
Moves supermatter delamination to its own datum.
-
Nerf rad anomly particle counts after death
-
Rad anomaly no longer spin and shoot particles on death, instead shoot at the same time with less particles and release one rad goat only for rad anomaly goat type, should improve performance
-
Nerfed particle counts of each tick to 5 particles and buff rad pulse to 10000
-
Nerfed particle counts when anomaly dies to 15 particles and buff rad pulse to 20000
-
replaces airraid.ogg with a new one from tg
-
new anomaly and core sprites
-
new radiation core sprite
-
fixes overlay of action button for reactive armor
-
fixes reactive armor sprite not update when active on body
-
add a fucking supermatter tesla (spawned by SM resonance cascade)
-
tesla_zap now has dust ability and ignore mob with godmode or spirit
-
tesla_zap can hit atmos machines, grille and simple animals (except for hostile one and slime)
-
Improved blob delam code and make the delam more deadly
-
-
ktlwjec updated:
-
-
Citadel kitchen template has the usual contraband items in the dinnerware vendor.
-
-
Addust updated:
-
-
Charlie Station now has a nuclear reactor.
-
please do not spread fallout throughout space, or you will be fined 2 credits for littering
-
-
LazennG updated:
-
-
Added philospher's tome loot
-
Removed bloody knuckles from bg loot
-
tweaked the way goat king drops its loot
-
added bubbleblender.ogg
-
added sprites for philospher's tome
-
-
LoliconSlayer updated:
-
-
Deputy armband boxes have been removed, as council has ruled that you are not allowed to deputize people
-
-
MajManatee updated:
-
-
Russian Accent
-
Might look wierd for a bit while its dialed in.
-
Bunch of clockwork noises reduced in volume
-
-
Moltijoe updated:
-
-
Improvised shotgun now needs to be maintained with a screwdriver or it can explode
-
-
ToasterBiome updated:
-
-
Gives Clerk a new template where instead of a giftshop it's a gambling hall.
-
Gives coinstacks the ability to be maploaded in.
-
-
warface1234455 updated:
-
-
Delta, Gamma and *@&!#&EPSILON*#%@ alert now make all lights turn red
-
fix atmos alerts not clearing when there are multiple air alarms in one area
-
Add a new label "Alarm overwrite" in the air alarm interface to dertermine an area atmos alarm if it is altered by someone (Note: The atmos alarm wont go away if atmos overwrite is on)
-
Fix apc not clearing alert when deleted
-
-
ynot01 updated:
+
17 December 2023
+
SapphicOverload updated:
-
Syndi-kit Special's Oddjob kit no longer gives you a dummy landmine
+
fixed a bunch of things being able to behead when they shouldn't
+
fixed roboticists not having robotics access
-
21 October 2023
-
MajManatee updated:
-
-
Ripslugs and Anarchy buckshot!
-
CRAZY ammo. Please send me more cool ideas.
-
+
15 December 2023
Mqiib updated:
-
(Nearly) doubles extinguisher damage against blazing oil blobs
-
it's like I was right the whole time
-
-
cowbot92 updated:
-
-
Adds new Heretic path
-
Adds a new sound for blade path
-
Adds new path blades
-
-
-
20 October 2023
-
ktlwjec updated:
-
-
Updates the donut emojis.
-
-
ynot01, Cuackles updated:
-
-
Added sledgehammers to the engine room, which does bonus damage to structures and is craftable
-
Hitting a wall with an item now makes noise
-
-
LoliconSlayer updated:
-
-
Deputy armbands and boxes now say that you can't deputize people
-
-
Moltijoe updated:
-
-
No longer get wirevision when spawning ontop of a wire
-
Fixes some entrance and exit point targeting issues with wirecrawling
-
-
Therandomhoboo updated:
-
-
Gave Amasec the effect of making you punch harder (+2)
-
Boozepower changed from 35 to 55
-
-
cowbot92 updated:
-
-
fixed maths for whetstones
-
-
warface1234455 updated:
-
-
You only need to cut power wire to disassemble air alarm
-
Fix nuclear fallout weather running on station zlevel when the reactor isnt even on that zlevel
Ethereals now crystallize upon death, reviving after a delay
-
Sounds for crystalization starting, finishing, and being interrupted
-
Sprites for ethereal crystallization
-
-
GraveHat updated:
-
-
adds a new Lavaland Ruin for the Legionnaire Elite Fauna
-
-
JohnFulpWillard updated:
-
-
Cables now properly transfer colors upon being cut
-
Built computers no longer have a wacky computer screen.
-
-
Moltijoe updated:
-
-
Vampire revive only goes on cooldown after reviving rather than on button press
-
Vampire revive has a 1 minute cooldown instead of 100 seconds
-
Vampire revive can be canceled at any point after intial activation if you want to delay it
-
Vampire revive chapel check has been moved to when the revive happens rather than when the button is clicked
-
Vampire screech no longer multiplies window damage and sound effects by the number of players nearby
-
Ethereals can no longer be walking voids of light
-
Fixes radiant burst sychronizer chromosome
-
Upgraded sleepers no longer deal toxins instead of healing it
-
-
ToasterBiome updated:
-
-
unconscious+ people now count towards heretic sacrifice
-
-
warface1234455 updated:
-
-
Heads can now open any private crate
-
Purchased departmental crates now use different crate sprites instead of the same private crate sprite
-
Departmenl/Private crates can now be sold back to CC for 500cr(same as other crates), except for small crate type packs will only be sold for 1/5 the value of the crate(100cr)
-
Fixes NT IRN app turning cargo budget crate into private crate (this bug existed for awhile and I forgor to fix this on my other pr as well)
-
Add a new secure radiation crate that prevents radiation from leaking
-
Fixes fuel rods crate using non rad-proofed crate
-
Fixes NT IRN packs price not updating station trait modifer cost
-
-
-
18 October 2023
-
Krysonism updated:
-
-
The kitchen knife has a new sprite.
-
-
Addust updated:
-
-
Syndicate Command has drilled into an extremely rare water reserve in Lavaland, and has started building bases on these rare water reserves to ensure that they can safely fish - despite the ash.
-
I fucked around with the nukie base and it looks like a ship now, layout should be the same mostly
-
-
Moltijoe updated:
-
-
Reenables nightmares
+
You can now use darker colors on canvases.
ToasterBiome updated:
-
Pencil holders have had their sprites updated and their descriptions too.
-
-
iloveloopers updated:
-
-
You can now move while opening mail
+
Removes species pay modifiers
-
17 October 2023
-
@Simplehorror, @azzzertyy updated:
+
14 December 2023
+
Mqiib, @RG4ORDR @SapphicOverload updated:
-
SM delams with miasma will now spawn a blob
+
Sidewinder mechs made moderately tougher
+
Sidewinder mechs slightly better in melee than before
+
Sidewinder mechs have superior projectile deflection, watch those lasers!
+
Directional damage modifiers and deflection modifiers work for melee too now
+
Demolition modifiers are half-effective versus non-combat mechs and completely ineffective vs combat mechs
-
Krysonism, Ktlwjec updated:
+
cark updated:
-
Small and large plates have been added to the kitchen vendor.
-
Plates break when thrown.
-
Plates breaking.
+
fixed some issues on donut
+
certain items now give chems they did not before
-
Vishenka0704, Ying-The-Pando, ktlwjec updated:
+
cark, azzzertyy updated:
-
Cucumbers. Seeds found in the seed vendor. Can be juiced to make cucumber juice.
-
Pickles. Crafted with 5u water, 2u salt and 1 cucumber.
-
Super bite burger now needs a pickle to craft.
-
Danish hotdog. Crafted with 1 bun, 1 sausage, 1 pickle, 5u ketchup and 1 onion slice.
-
Gin Garden drink. Made with 1 part lemon juice, 1 part sugar, 3 parts gin, 3 parts cucumber juice, 5 parts sol dry and 2 parts ice.
-
Cucumber Lemonade. Made with 3 parts spite, 2 parts cucumber juice and 1 part ice.
+
Redesigns Box's bar/kitchen, nukes all the old templates and moves theatre north to fitness.
-
Kell-E updated:
+
AMyriad updated:
-
Stun batons no longer confuse their victims.
+
Added Biosignaller implants to nuke ops and ERTs, which alert their teammates when and where they died
+
Syndicate ghost roles now have a survival box
Moltijoe updated:
-
Wizard cloak and void cloak now properly have projectiles pierce
-
Adds wirecrawling as an Ethereal traitor item
-
Botanist lockers now spawn with a scythe and a weedkiller grenade in them
-
space vines can spread through firelocks that aren't welded shut
-
Can no longer have your click blocked by glowing heretic runes
-
Clicking on pierced reality is no longer annoyingly precise
-
Deleted vampire diseased touch
-
Deleted vampire appearance shift
-
Vampire cloak no longer gives armour, instead dodges an attack every 10 seconds
-
Vampire cloak regen now happens every 10 seconds rather than every 20 seconds and can regen to full blood
-
Vampire cloak gives a very small speed boost
-
Vampire rejuvenate now heals over 5 seconds rather than all at once and costs 20 blood per use
-
Vampire regen passive now makes rejuvenate slowly remove immobilizing effects rather than heal over time
-
Vampire regen passive now gives passive regen
-
Reformatted "how suck" help button of vampire
-
Vampire revive no longer dusts the user if used in the chapel if a full vampire (still don't revive though)
-
Cancelling lilith's pact midway now refunds all blood rather than half
-
Ethereal brightness and range now scales with hunger in addition to health
-
Ethereal lights are slightly less overpowering
-
Cyberpunk jacket can now toggle the symbol on the back
-
Cyberpunk jacket is no longer Cuackle's donator item, can instead be found in the clothesmate
-
Preterni can no longer drain power from themselves
-
fixes bugs with every heretic grasp
-
BattleRoyale 🎸🔥 🎶
+
Reworks superficial healing
Mqiib updated:
-
Sol dry is no longer almost as good as real medicine
+
Actually buffs the immolator this time
+
Base mech vs mech damage multiplier for melee weapons and punches reduced
+
Energy axe and rapier mech multipliers tweaked
+
Mech katana damage bonus and AP increased
-
Yarinoi updated:
+
SapphicOverload updated:
-
Welcome to Space Station 13. The only limit to what you can do, is what you're willing to become. Adds a new Solo bundle to the Syndi-Kit Special.
+
fixed snowstorms bypassing cold protection
+
adds electrical and heat protection to engineering envirosuit helmets
-
redmoogle updated:
+
ToasterBiome updated:
-
Ghost beacons now rip you back to their area
+
Changes NVS Gax Robotics to be more open
+
Mood is globally enabled for everyone
+
Map rotations and player votes no longer offer maps if they were played the last 70% of rounds
+
Adds a new Christmas song to the lobby music for Christmas!
-
-
16 October 2023
-
Moltijoe updated:
+
bruhlookatthisdood updated:
-
Marrow weavers and gutlunches can no longer eat robotic organs
-
Matter bins actually serve a function for sleepers now, healing speed
-
Vampire hypnosis and gaze are now reduced rather than negated by eye protection
-
Vampire and Abomination no longer have two different abilities overwriting eachother
+
you can now hit the captain's gun case
cowbot92 updated:
-
Added new heretic spell: Volcano Blast
-
Removed hookshot from heretic blade
-
Fixed a few things with heretic ascension text
-
Tweaked some heretic spell paths for purchase
+
Adds coffee machine to the new bar
+
Maps it duh
-
15 October 2023
-
Chubbygummibear updated:
-
-
projectiles are no longer an item subtype, this only matters for coders
-
projectiles flag var renamed to armor_flag for clarification
-
-
JohnFulpWillard updated:
-
-
Bibles now protect against unholy stuff again.
-
Antimagic protection now works properly and more consistently.
-
-
Moltijoe updated:
+
13 December 2023
+
AMyriad updated:
-
Fixes regular dragon blood being able to turn you into a draconid
-
dragon blood also gives ash immunity when it gives lava immunity
-
Fixes the creamator not creamating
-
creamator can sometimes replace the cremator at roundstart
-
gimmick objectives work now
-
Adds sanity check to rpgloot fixing a runtime that breaks it
+
Captains can now access their antique gun display case on Gamma alert or higher
Mqiib updated:
-
Cloaker belt now loses charge when you move
-
Cloaker belt drains slightly slower in light
-
Adds a couple tweakable variables
-
-
TheGamerdk updated:
-
-
Icon conflict resolver is fixed again
-
-
ToasterBiome updated:
-
-
research access restrictions can now be hacked
-
-
iloveloopers updated:
-
-
Bo Staff now makes you unconscious instead of sleeping
buffed detective's armor to be the values it rightfully should be
+
Added new fonts and refactored runechat. Also some backend stuff it's fine.
-
Kell-E updated:
+
sapphicoverload, bluishtsunami updated:
-
It's no longer possible to remove embedded objects from yourself while unconcious or paralyzed.
-
Adds a new negative trait: Drunkard
+
adds a water gun to donksoft vending machines (and uplink)
+
preternis water damage tweaked to be more affected by smaller volumes
+
preternis water damage updates immediately when splashed instead of waiting up to 2 seconds
+
+
04 December 2023
Moltijoe updated:
-
Adds a 2 second delay to leaving ventcrawling
-
No longer need to click adjacent tiles to attack using worldbreaker's pummel
-
Can no longer use worldbreaker abilities on items in inventory
-
regular fire extinguishers now make the person being sprayed more wet
-
Cautery tools have 3 force and do burn damage
-
Advanced Catuery tools in drill mode are sharp and have 4 force
-
Preternis water damage is stronger but starts out weaker
-
Preternis water damage now properly makes them spark
-
Preternis burn mod increased to 1.2
-
Preterni are worse at punching
-
Preterni heat up and cool down slightly faster
-
Powerhungry species no longer look fat when fully charged
-
Phytosians no longer passively regen robotic limbs
-
Worldbreaker is even hungrier
-
Worldbreaker gives a bottomless "stomach"
+
Pseudocider costs 8tc instead of 6tc
+
Pseudocider has a 30s cooldown instead of 20s
-
cowbot92 updated:
+
SapphicOverload updated:
-
Added new heretic transmutation
-
tweaked heretic UI a tiny bit to have more clarity
-
tweaked void armor effect + armor values
-
adjusts void ascension
-
added new sprites for mask of madness transmutation
+
electrical protection is now its own armor type
+
certain engineering equipment like hard hats and work boots now have electrical protection
+
spliced wires check electrical protection on your feet instead of your hands when you step on them
+
combat defib disarm takes electrical protection on targeted limb into account
+
lightning flow punches, combat defib disarm, and hacked cyborg hug module all check electrical protection on the targeted limb
+
touching hacked cyborg energy fields with your hand now actually checks protection on that hand
+
slightly reorganized the armor tags and adds electrical protection to the list
+
fixed nanites not being affected by electric shock because of a missing signal
+
fixed gloves and shoes with armor not actually protecting your hands and feet respectively
+
fixed power fist not working
-
warface1234455 updated:
+
azzzertyy updated:
-
Add an option to toggle between using departmental budget or cargo budget when requesting a package
-
Using department budget to order now actually show the departmental name to avoid confusion
-
Fixes issue when departmental budget amount not appearing on the app
-
Fixes NT IRN app not printing requisition paper
-
Fixes modular computer's NT IRN app showing buyer as unknown because it doesnt recognize the pda
-
Refactored code a bit
-
private crate can now be sent back to CC if unlocked
-
change stabilized plasma decal sprite to something that doesnt block you from clicking the tile
+
Mediator is no longer roundstart.
-
29 September 2023
-
warface1234455(code and seed sprite), baiomu (3d lime sprite) updated:
-
-
new plant 3d lime
-
lime can now be mutated to 3d lime
-
new 3d lime seed
-
-
Aquizit updated:
-
-
Added Janitor Closet back to destination tagger menu
-
Changed a couple of the group names to be more accurate
-
-
Kell-E updated:
+
03 December 2023
+
cark updated:
-
Hallucinations from RDS will now last significantly longer.
+
fixes missing floor in airlock on donut
-
LoliconSlayer updated:
+
itseasytosee, sapphicoverload updated:
-
Pet collars are for actual pets
+
some items and projectiles are now better or worse than others at destroying things
+
chat messages from hitting an object now tell whether you can damage it
+
fixed machines/structures/etc not caring about armor penetration when hit by a melee weapon
+
fixed cannonballs not doing any damage to mobs
Moltijoe updated:
-
Added 7 new round start lawsets
-
Modified some existing lawsets to reference things other than specifically humans
-
-
Mqiib updated:
-
-
Brig physician labcoats should be not invisible now
+
Sometimes fixes feed ability sometimes breaking
+
Gangrel transformation ability now shows the radial menu before the 10 second do_after
+
Fixes gangrel transformation ability sometimes deleting itself without ever being used
+
Fixes gangrel werewolf transformation being eradicated from the timeline by tattax
SapphicOverload updated:
-
fixed firefighting foam creating stabilized plasma whether or not it actually scrubbed any plasma from the air
+
fixed ED-209 not being affected by heavy EMPs
+
fixed leg implants not being affected by EMPs at all
+
fixed cybernetic ears causing longer knockdown times on light EMPs instead of the other way around
+
fixed nanite heart killing you instantly on EMP regardless of whether or not it kills your nanites
+
HoS gun now shoots ion carbine projectiles instead of its own redundant projectile type
+
mech ion cannon has 1.5x stronger EMPs than the ion rifle
+
syndicate EMP bomb has 2.5x stronger EMPs than standard EMP grenades
+
reworks EMPs to allow any severity, which now decreases with distance
-
SomeguyManperson updated:
+
+
02 December 2023
+
Moltijoe updated:
-
apostate blade attack cooldown increased by 40%, putting its damage in line with other nullrods
+
No more infinitely duplicating hulk DAN
+
Hulk now occasionally says the brain damage hulk lines
-
cowbot92 updated:
+
SapphicOverload updated:
-
Adjusts void path spells
-
Adjusts ominous armor values
-
Adjusts amount of targets you can choose
+
fixed runtime error when passive vents encounter zero temperature or zero volume
+
fixed abandoned crates having a 2% chance to cause runtime errors when initialized
-
warface1234455 updated:
+
+
01 December 2023
+
Twaticus, Imaginos16, Cark updated:
-
You can now see the amount flesh damage and healing in a burn wound using health scanner
-
health scanner now accurately indicates amount of infection in burn wound as well as the percentage of sanitzation/infestation
-
Fix unnable to give rnd console access to ID
-
Changed the "R&D Access" desc on id mod to "Toxins Access" desc so it wont confuse people
+
added new object sprite for plasteel tile
+
deleted old object sprite for plasteel tile
-
-
28 September 2023
-
cark updated:
+
JohnFulpWillard updated:
-
v1 can no longer be bought if there are under 25 players in the round
+
Stasis units and sleepers now have proper overlays and lavaland sleepers aren't invisible anymore.
-
xPokee updated:
+
Yarinoi updated:
-
Adds a frog holoform for pAIs.
+
Pitying the miners attempting to look for it, the Demonic Frost Miner now gives out a Hollow Signal on GPS.
-
ToasterBiome updated:
+
azzzertyy updated:
-
you can no longer use hilbert's hotel while incapacitated
+
You can now point while handcuffed
cowbot92 updated:
-
Adds new heretic path - Void
-
Adds new sounds to go with heretic abilities
-
Adds new sprites for Void Blade and abilities
+
Adds new bar signs
-
27 September 2023
-
@Moltijoe @Scrambledeggs00 updated:
+
30 November 2023
+
ToasterBiome updated:
-
Adds new nullrod, aspergillum and aspersorium
-
Sprites for aspergillum and aspersorium
+
Adds a new lavaland lava type that you can't replace with shelter
+
lavaland syndie base is surrounded by no-shelter lavaland lava
-
@Mqiib @Ebin-Halcyon updated:
+
+
29 November 2023
+
Moltijoe updated:
-
TONS of updated sprites for lizards, jumpsuits, and some external wear
-
Some new digitigrade versions of biosuits/armors
-
(Temporary) removed light tiger stripe tails
-
Removes soul
-
I can't believe it's finally done
+
Brig physician also gets cloning and airlock access during skeleton crew
+
Paramedic also gets surgery access during skeleton crew
+
Mining medic also gets cloning, maint, and airlock access during skeleton crew
+
All hecata zombies die when the controlling bloodsucker dies
+
lightning flow no longer immobilizes for like 0.1 seconds every dash
+
Preterni no longer get 20% more power from liquid electricity than other powerhungry species
+
All powerhungry species now get "food" from consuming teslium, not just Preterni
-
ChesterTheCheesy updated:
+
Runian updated:
-
cannot activate luminescent slime core effects while incapacitated
+
Lizard tail users can *thump their tail.
+
Tail thump.
+
Guardian's Frenzy Ability now respects the three second cooldown that it is suppose to be limited by.
-
cowbot92 updated:
+
ToasterBiome updated:
-
Adjusts Heretic Book Desc
-
Allows IPC & Pretenis (and robotic limbs) to heal on rust tiles
-
Allows heretics to gain points w/o needing book in bag
-
non-heretics can no longer wear heretic stuff
+
Removes tile grime and support for it.
+
Replaces tiles to be less shiny.
ynot01 updated:
-
Toolboxes now universally do 3 more damage
-
Welding tool flames now do 3 less damage
+
Command channel is now blue once more
+
fixed pseudocider not duplicating headwear
-
26 September 2023
-
cark updated:
+
28 November 2023
+
MajManatee updated:
-
fixes missing wall in asteroid medbay
+
Horrors can now scan their hosts
+
Horror chemicals now say how much they inject
Moltijoe updated:
-
fixes a "bug" with borg hypospray recharge time being 10x what it was supposed to be
-
Cogging an APC removes access requirement to locking
-
Random chance for APCs to be unlocked round start
-
Battle Royale 😴 💤
+
Only Delta alert gets red lights
+
Fixes an IAA bug involving pseudocider
+
adds a glowing outline to anyone with the holy light heal boost
+
fixed a bug where limb healing generated less favour than overall healing
+
Increases the holy light heal boost duration to 1 minute from 30 seconds
+
Reduces the amount of favour that holy light generates by 50%
+
Gives warden brig phys access during skeleton crew
+
Makes monster hunters monster hunting objective actually tell the monster hunter to hunt monsters
-
Therandomhoboo updated:
+
Runian updated:
-
Fixed the typo for when you make syndicate screwdriver (flavour text)
-
Horror's got that very needed eye surgery to give them vision in the dark
+
Constructed anchored emitters are correctly recognized as wrenched; you no longer need to wrench these anchored emitters twice to make it work.
+
Flying Fang's damage-dealing disarms respects pacifism as intended.
-
Yarinoi updated:
+
SapphicOverload updated:
-
CANNOT STOP HULK!!!!
-
HULK TWEAKED TO SERVE PURPOSE OF TRAITOR ITEM BETTER!!!
+
fixed fire extinguishers not working if the particle never moves
cowbot92 updated:
-
Added new UI for heretics
-
Removed old heretic UI
-
Adjusted how heretic codex works slightly
+
Added new fruit
+
Added new drink to go with that fruit
+
added some icons and images for lantern fruits and drinks
-
warface1234455 updated:
+
+
27 November 2023
+
goober3, cark updated:
-
bluespace harvester can now output ores based on weight for 30K points, you will get 70 ores total, with the quantity of each ore type determined by its weight.
-
adjust debug material box diamond counts to 50 like others
-
bluespace harvester no longer output things on top of the machine
-
removed some unnecessary metal/glass sheets in cultural tab
-
Give cyborg tablet access to the cyborg monitor program.
-
Fix the issue of the log file not being downloaded in the cyborg monitor program.
+
added some dirt linings
-
-
25 September 2023
-
ToasterBiome, Someguymanperson updated:
+
Moltijoe updated:
-
Research circuit boards in R&D can be "hacked" with a screwdriver to unlock access.
+
Syndicate fedora can no longer be caught using throw mode
-
cark updated:
+
Mqiib updated:
-
new ghost role: icemoon hermit
+
Lizard wings now use the new lizard colors instead of the slightly-off old colors
-
JohnFulpWillard updated:
+
Runian updated:
-
Super Chameleon (Changeling chameleon) can no longer be downloaded from a DNA console.
-
Tanks with devices attached to it now properly remove their appearance when the devices are removed.
+
Ion storms can affect up to 20 airlocks and 10 APCs with random effects, only if is no AI.
+
Airlocks affected by ion storms will randomly have one of the following effects: toggle bolts, toggle emergency access, shock temporarily or have their safety & speed toggled.
+
APCs affected by ion storms will have one of three or all of their power channels turned off.
-
Kell-E updated:
+
SapphicOverload updated:
-
It's no longer possible to draw infinite sanguirite from an epipen.
+
preternis now purge 4% of internal chemicals every tick, instead of purging everything after 20 ticks
+
preternis are no longer immune to the effects of morphine and a few other chemicals
+
fixed unintended change to damage of every two-handed weapon
+
fixed runtime error when trying to use certain two-handed weapons with one hand
+
fixed preternis being affected by water through hardsuits
-
MajManatee updated:
+
cowbot92 updated:
-
staff no longer are deafened by prayer
+
Ports updated TG announcements & TGUI
+
+
26 November 2023
Moltijoe updated:
-
Fixes a "bug" with holoparasite body dusting (!)
+
Battle royale loot distribution
-
SapphicOverload updated:
+
SomeguyManperson updated:
-
fixed undocumented change that nerfed lizards, they no longer have slower tool speed
+
hallucination anomalies have had their effects altered. You many want to bring a toolbox instead of mesons when responding to one
+
hallucination reactive armor has had its effects modified similarly to the new effects of the anomaly
+
the supermatter will no longer spawn hallucination anomalies when exploding, on fire, or otherwise.
-
SomeguyManperson updated:
+
+
25 November 2023
+
Ghommie updated:
-
stuff nobody will read has been written
+
Examining a human mob as an observer displays "Quirks", not "Traits"
-
wonderinghost updated:
+
JohnFulpWillard updated:
+
+
Seclites now have directional lights- point them in the direction you want to see more of.
+
Ported over many refactors to lighting and opacity. Please report if you can walk or see through walls unintentionally.
+
+
Majkl-J updated:
-
adds admin fax sound
-
fixes centcom dissapearing from fax list if switching off
+
Virology: New symptom, superficial healing
-
-
24 September 2023
-
cark updated:
+
Runian updated:
-
new icemoon ghostrole - Icemoon Walkers
-
icemoon seed vault is now added
-
adds a new shuttle - the cozy shuttle
+
Shooting plasma sheets with a damaging burn-based projectile ignites it.
-
Moltijoe updated:
+
SapphicOverload updated:
-
Holoparasites no longer dust liches upon death
-
Holoparasites lose 4 random stats upon revival by a lich
+
added preference for non-upgraded cybernetic organs as quirk options
+
random cybernetic organ quirk costs 3 instead of 4
-
Therandomhoboo updated:
+
cowbot92 updated:
-
Added nutriments to Polypore, Porcini, inocybe & ember so grind results will work
-
Ash flora, mushroom shavings, stem, cap & leaves now give mushroom powder when you grind them
-
Hallucinogen Mushroom & Hippie's delight no longer perma dizzy you
-
Hippie's delight numbers for how long their effects last (dizzy, druggy, jitter etc) have been sorted out (Thanks to @Moltijoe)
+
Adds auxmos to the minor filter
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index 05a5538f23f4..73ff413e40ed 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -42703,3 +42703,548 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
2023-12-23:
bruhlookatthisdood:
- bugfix: fixed a lot of ai blind spots
+2023-12-24:
+ Moltijoe:
+ - tweak: Lets hand-based extinguishing be used on corpses
+ - tweak: Gives polysmorph minor resistance to pressure damage
+ - tweak: Preterni no longer regenerate blood
+ Runian:
+ - bugfix: Trying to remove decoy brains from MMIs properly works.
+ - bugfix: Removing MMI brains from range no longer teleport it into your hands if
+ you're far away (e.g. through walls/windows with TK). Same for folding paper.
+ - spellcheck: Ending punctuation for ripping out brain from MMI.
+ - bugfix: IPCs now play their special walk sound when they move as intended.
+ - spellcheck: 'Emagging/hacking cyborg event typo: privleges -> privileges'
+ SapphicOverload:
+ - bugfix: fixed a runtime error caused by steamed hams
+ - bugfix: fixed meson goggles not protecting you from looking at the supermatter
+ crystal
+ - tweak: being on fire no longer gives a negative moodlet if you're immune to fire
+ azzzertyy:
+ - rscdel: Removes the "WARNING. THE WAY AI IS PLAYED HAS CHANGED" message when joining
+ as AI
+ hedgehog1029:
+ - bugfix: XL beakers show contained chemicals again
+2023-12-25:
+ ' cark':
+ - mapping: Icemoon Hermit's home has been improved with more amenities
+ - mapping: fixes there being too many firelocks under a door in AI sat on Donut
+ Moltijoe:
+ - rscadd: Adds more tool options for ghetto mechanical surgery
+ - tweak: existing ghetto mechanical surgery options are slightly better
+ N3D6:
+ - mapping: moves smes units from engi foyer to the former gravity generator area
+ on icemeta
+2023-12-26: {}
+2023-12-29:
+ SapphicOverload:
+ - tweak: exosuits now have directional lights, with much longer range
+ ToasterBiome:
+ - tweak: Project Bee has a new sprite and description
+ jupyterkat:
+ - tweak: updated auxmos to latest
+2024-01-03:
+ ' Cowbot92 & Molti':
+ - tweak: tweaks how blood works
+ - imageadd: adds new blood
+ - imagedel: removes the old bad blood
+ - experiment: fear the old blood
+ Moltijoe:
+ - tweak: Ethereal eyes now give +1 view range
+2024-01-04:
+ ' ZephyrTFA, san7890':
+ - bugfix: fixed chat messages not showing up sometimes
+ Aquizit:
+ - mapping: Removed disposals air alarm
+ - mapping: Moved windoor in disposals
+ - tweak: Scrubs a couple bad AI Ion Law things
+ Runian:
+ - spellcheck: The threshold description for symbiotic regeneration is now accurate.
+ SapphicOverload:
+ - tweak: polysmorph tails slightly increase crawl speed
+ - bugfix: fixed printed IPCs having human names instead of IPC names
+ - bugfix: supermatter crystal and nuclear reactor are less unstable
+ - bugfix: footstep sounds actually work again
+ Therandomhoboo:
+ - rscadd: Slurring now happens based on what kind of drinker you are (Light, normal
+ or heavy drinker which are based on traits)
+ - rscadd: Slurring duration changed based on drinker type
+ - rscdel: The constant chance to randomly slur, we can drink now
+2024-01-05:
+ AMyriad:
+ - imageadd: Updated the storage implant's toggle sprite
+ EdgeLordExe:
+ - rscadd: 'New Mining area: Jungleland, Most content added by Djiq, Most ruins,
+ some mobs and Ivymen added by Marmio64. Other ruins added by ToasterBiome. Various
+ fixes and updates by courtesy of Jamie1D and SapphicOverload!'
+ - rscadd: New Mobs now appear on jungleland, such as Meduracha, Dryads, Giant mosquitoes,
+ and Skin Twisters!
+ - rscadd: 'New Mining implement: Kinetic Javelin, get it either from the roundstart
+ kit, or buy it in a mining vendor.'
+ - rscadd: 'New Megafauna: Tar King - You can summon it by collecting 3 parts of
+ a broken crystal, assembling it, and placing it in a Forgotten Altar!'
+ - rscadd: Ivymen - Jungleland's counterpart to ashwalkers, by Marmio64
+ - rscadd: Jungleland has many new biomes, beware of sulphuric pits, as it will cause
+ you trouble if you step into it.
+ - imageadd: Added new icons and icon states for jungleland
+ - mapping: Added Jungleland as a new mining destination!
+2024-01-08:
+ ' cark':
+ - mapping: fixes some railings in bar
+ - mapping: Reenables Icemoon Walkers
+ Therandomhoboo:
+ - rscadd: Moonshine now makes you gain blurry, long live blurry vision
+2024-01-09:
+ Aquizit:
+ - bugfix: The innkeeper can now get stuff from their booze-o-matic and kitchen vending
+ machines.
+2024-01-10:
+ AMyriad:
+ - rscadd: Dusted plasmamen now have plasma remains
+ Runian:
+ - bugfix: Override Machine and Overload Machine abilities (from malf AI/malf upgrade
+ disk) now work.
+ - bugfix: Nerve Grounding and other ways that change your physiology's siemens coeff
+ properly protect you from getting electrocuted.
+ Scrambledeggs00:
+ - rscadd: Added a new thing to IceMeta
+ - imageadd: Sprites for the new thing
+ ToasterBiome:
+ - bugfix: rating polls now work properly
+ - tweak: Updates maintainer list and thanks more stations in our special thanks
+ section
+ bruhlookatthisdood:
+ - bugfix: ASay and DSay are now no longer visible to non-admins
+ wonderinghost:
+ - bugfix: fixed my mistakes
+ - mapping: makes the prior pr picture perfect
+2024-01-11:
+ Runian:
+ - bugfix: The description of ammo boxes now accurately show how much bullets are
+ left when you use an ammo box on another ammo box.
+2024-01-12:
+ SapphicOverload:
+ - rscadd: adds more winter coats
+ - imageadd: moves winter coat and hood sprites to their own files
+ Therandomhoboo:
+ - tweak: Astrotame, bbq, sugar, cream & chocolate are now 10u's instead of 5.
+ - tweak: Chocolate packets are now correctly called chocolate packets instead of
+ Creamer....
+ warface1234455:
+ - bugfix: FIx preset programs arent installed in pda/phone
+2024-01-13:
+ SapphicOverload:
+ - bugfix: fixed chat not always showing up in replays
+2024-01-15:
+ bruhlookatthisdood:
+ - rscadd: Waiting For The Sun (Rimworld OST) is in jukebox now
+ cowbot92:
+ - bugfix: fixes decals
+2024-01-17:
+ Loiosh42:
+ - tweak: Adds an ignition_effect to the cautery.
+2024-01-18:
+ SapphicOverload:
+ - tweak: made the chat message from losing obsession more visible
+ - tweak: you can now place items on the altar of the gods without having to climb
+ on top of it
+2024-01-19:
+ SapphicOverload:
+ - mapping: added stairs to icemeta's mining base
+ - imageadd: added directional stairs icons
+ - bugfix: fixed regenerative cores having a delay on icemoon
+ warface1234455:
+ - bugfix: Fix invisible netmin wintercoat hood
+ - rscadd: Bring back Raven cruiser emergency shuttle
+2024-01-20:
+ ' kugamo, warface1234455':
+ - imageadd: Resprites the Bluespace Harvester.
+ - tweak: Also also slightly changes the nether portals event, so that the portals
+ spawn sequentially over at most 15 seconds.
+2024-01-21:
+ ' ktlwjec':
+ - tweak: Mammi and Pea Soup need bowls to craft.
+2024-01-22:
+ Moltijoe:
+ - rscadd: Added new maroon organ objective for traitors to roll
+ - tweak: Traitor break machinery objective only requires breaking the original machines
+ - tweak: Traitor break machinery targets at max 2 areas instead of up to 4
+ Mqiib:
+ - bugfix: Imperial jumpsuits don't have alt styles, stop trying to break the dress
+ code soldier!
+ Yarinoi:
+ - tweak: '''Solo'' special syndi-kit is now slightly more balanced. David. David
+ you aren''t going to let this stop you are you David. Get to work David ignore
+ the EMPs. Take your pills get to work.'
+2024-01-23:
+ Moltijoe:
+ - bugfix: Fixes an issue with blood decals
+2024-01-26:
+ Runian:
+ - bugfix: Shooting a cyborg snack dispenser while in a zero gravity environment
+ correctly starts moving you in the opposite direction that you shot it.
+2024-01-27:
+ ' ktlwjec':
+ - imageadd: Removes a line in the sith cloak sprite.
+2024-01-28:
+ SapphicOverload:
+ - bugfix: fixed being unable to analyze the instability of cold fusion reactions
+ - tweak: IPCs, androids, polysmorphs, and slimepeople now have unique typing indicators
+ - imageadd: Using harm intent makes your typing indicator angry
+ adamsong:
+ - bugfix: fixed mfa backup codes not working
+ cowbot92:
+ - rscadd: Makes the deep fryer hungry, occsionally
+ - bugfix: fixed the faded eyepatch not giving you night vision despite it telling
+ you it does
+2024-01-29:
+ ' ktlwjec':
+ - tweak: All pizzas need mozzarella to craft (5u enzyme, 30u cow milk, 5u lemon
+ juice).
+ - bugfix: Mime finger guns properly show what was shot at, and by who.
+ - imageadd: Mime finger gun button has a working icon.
+ 13spacemen:
+ - tweak: Compiling the code launches directly into Dream Seeker
+ Moltijoe:
+ - bugfix: Fixes a bug with polysmorph tail crawlspeed
+ SapphicOverload:
+ - bugfix: fixed budget insulating gloves melting when touching unpowered cables
+ adamsong:
+ - rscadd: Added option for admins to spawn items directly in a storage container
+ bruhlookatthisdood:
+ - tweak: bonesetter spawns during battle royal now
+ cowbot92:
+ - bugfix: removes infinite money in the form of syrup
+ jachlompsky:
+ - bugfix: fixed lasombra vassals and eye examine
+ wonderinghost:
+ - tweak: updates min and max values of byond_version_combat
+ ynot01:
+ - tweak: Live xeno aliens on station level now passively generate research points
+2024-01-31:
+ ' ktlwjec':
+ - bugfix: Mime's mail cheesewheel is now visible.
+ - tweak: Croissants need raw pastry bases to craft, instead of cooked ones.
+ - rscadd: Seafood ingredients box for cooks.
+ AMyriad:
+ - spellcheck: Salicylic acid is now spelled correctly
+ Marmio64:
+ - rscadd: Adds jaunt abilities for sinful demons.
+ Mqiib:
+ - tweak: Chaplain monk staff null rod (stamina) damage increased (15 -> 18)
+ - tweak: The above no longer can block projectiles
+ RG4ORDR:
+ - rscdel: Syndie Borgs Spawners removed from BR loot pool.
+ Runian:
+ - rscadd: 'Four tech node dedicated for cyborgs upgrades: Night Vision, Engineering,
+ Mining, and Service. Each costing 500 points.'
+ - tweak: Various cyborg upgrades have been moved to more relevant/dedicated technodes.
+ - tweak: Most tech nodes that involve cyborg upgrades now require the "Advanced
+ Robotics" tech node or a node that already has it as a prerequisite.
+ - tweak: Most tech nodes that involve cyborg upgrades have now a better description
+ that explains the purpose of the node.
+ - tweak: '"Cyborg Upgrades: Advanced Utility" tech node has been renamed to "Cyborg
+ Upgrades: Advanced Engineering".'
+ - tweak: '"Cyborg Upgrades: Advanced Engineering" tech node cost reduced to 2,500
+ from 5,000.'
+ - tweak: '"Cyborg Upgrades: Utility" tech node cost reduced to 1,500 from 2,000.'
+ - tweak: '"Cyborg Upgrades: Engineering" and "Cyborg Upgrades: Mining" tech node''s
+ icon has been changed to better visualize the node.'
+ - bugfix: Janiborg's trashbag of holding upgrade is properly locked behind Advanced
+ Sanitation Technology.
+ - tweak: Medical gripper can now hold medsprays, lollipops, pills, patches, gummies,
+ and chemistry bags.
+ SapphicOverload:
+ - tweak: beam rifles now create turf fires instead of hotspots
+ - tweak: beam rifle projectiles can always reflect off of coins
+ - bugfix: fixed hitscan projectiles not rendering properly when reflected
+ - bugfix: fixed beam rifle projectiles not doing damage when fired from turrets
+ or emitters
+ SomeguyManperson:
+ - tweak: limb loss in brazil is now worth 7 marbles
+ - tweak: limb skeletonization in brazil is now worth 3 marbles
+ - bugfix: If you somehow manage to run out of limbs while leaving brazil, you will
+ be ejected
+ jachlompsky:
+ - rscadd: Added a frost oil chem sprayer to the mr freeze syndikit
+ warface1234455:
+ - tweak: Shitcurity uniform now has fucking armor
+ - tweak: Nightshift lights now actually consume less power
+ - rscdel: Removes other useless power vars as they dont work with the light fixture
+ anyway
+ - tweak: Cyborg engineering gripper can now hold server rack and cpu
+ wonderinghost:
+ - tweak: Cluwncurse takes 3 minutes instead of 1 minute
+2024-02-02:
+ AMyriad:
+ - spellcheck: Added "np" and "thx" to the netspeak filter
+ - tweak: Saying "auxmos" IC now becomes "space wind"
+ Moltijoe:
+ - tweak: xeno embryos now burst significantly faster if the host is alive and buckled
+ to a xeno bed
+ - tweak: xeno embryos now burst significantly slower if the host is dead or not
+ buckled to a xeno bed
+ - tweak: xeno beds now handcuff the person buckled to it
+ - tweak: xeno beds are significantly faster to unbuckle from (if not handcuffed)
+ Therandomhoboo:
+ - tweak: Nice, Good, Very good and Fantastic quality drinks have their mood changes
+ increased, and their timeouts increased
+ - tweak: Allies cocktail mood flavour text is less cringe
+2024-02-04:
+ AMyriad:
+ - rscdel: Corporate Agents are now gone. Don't know what they are? Exactly nobody
+ does, they won't be missed
+ Therandomhoboo:
+ - rscdel: Cluwne Curse is gone because 3 minutes might as well be 1 use per round
+ - rscdel: Shitty wizards can't have a chance to spawn with Cluwnecurse spell
+ - rscadd: Shit wiz's now have Slip spell instead of Cluwnecurse
+2024-02-05:
+ AMyriad:
+ - bugfix: Chemist ID card no longer looks like the prisoner ID card
+ SapphicOverload:
+ - bugfix: reactions happening in tanks now know they're reacting inside a tank
+ - tweak: buckling to operating tables no longer requires cuffs
+2024-02-06:
+ ' Cowbot92 & Chubbygummibear & Mqiib':
+ - tweak: adjusts how emagged minesweeper rewards/works
+ - tweak: adjusts the sound of syndicate mines
+ Moltijoe:
+ - tweak: Battle Royale
+ warface1234455:
+ - tweak: Airalarm draught mode now set scrubber range from normal to expanded
+2024-02-08:
+ Identification, Molti, lore team (rip skrem):
+ - imageadd: new preternis sprites
+ - imagedel: old preternis sprites
+2024-02-09:
+ Mqiib, @Ebin-Halcyon:
+ - imageadd: Added new turtlenecks for cargo, CMO, and HoP!
+ ToasterBiome:
+ - bugfix: hermes boots no lonnger create runtimes when walking
+ cowbot92:
+ - tweak: Tweaks changeling abilities
+2024-02-10:
+ ' Identification & Molti':
+ - rscadd: Adds a new preternis body colour
+ - imageadd: adds new preternis eye option
+ - imageadd: adds new preternis wings
+ Aquizit:
+ - mapping: Laying pipe
+ Runian:
+ - bugfix: Decreases the unintended burn damage reduction from 2 to 1 for preternis
+ limbs.
+ - tweak: The genetics power, Shock Touch, causes confusion down from 15 seconds
+ to 3 seconds. Can be reduced/increased based on targeted electric armor.
+2024-02-11:
+ ' AMyriad, Wallem, Vincent938, The-Moon-Itself':
+ - rscadd: You can now construct, deconstruct, and print mass drivers
+ - tweak: You can also hack mass drivers now
+ - tweak: Mass driver base power reduced from 50 to 10, can be upgraded or hacked
+ to increase it
+ - soundadd: Mass drivers also have sound effects now!
+ - imageadd: Updated the mass driver sprite(s)
+ AMyriad:
+ - spellcheck: Standardized all mentions of the Clockwork "Justicar" into "Justiciar"
+ Loiosh42:
+ - rscadd: Adds an Anti-megafauna ERT, mk2
+ MajManatee:
+ - bugfix: Holopads stop recording/playback when wrenched
+ Moltijoe:
+ - tweak: Confusion modified to not scale up to completely randomized movement
+ Mqiib:
+ - bugfix: Flying fang inaccuracy debuff now actually goes away fully instead of
+ stacking infinitely
+ - bugfix: Leaping with air shoes makes you fall over
+ - tweak: Blocking the flying fang leap helps significantly but won't totally save
+ you
+ - tweak: Disarm chain defaults to basic harm intent attacks when prone
+ - tweak: Mechs don't deflect ion bolts any more
+ Runian:
+ - rscadd: Silicon Law Manager has been added.
+ - rscadd: Antag AIs can modify their laws with exceptions. They cannot remove their
+ zeroth law (or their devil laws).
+ - rscdel: '"State Laws" has been replaced/moved to the Law Manager.'
+ - rscdel: '"Set Auto Announce Mode" has been replaced/moved to Law Manager.'
+ - rscdel: You can no longer view a law module's laws by using it in hand and has
+ been replaced with examining.
+ - rscdel: The unused and unobtainable "malfunction" lawset was removed.
+ - tweak: Examining a law module has been improved and also reveals the laws on it
+ if you are near it or are an observer. Now with formatting!
+ - tweak: Silicons now have an easier time setting their auto announcement to Holopad
+ and Binary.
+ - tweak: Trying to upload an another law that has the same exact text as a previous
+ law in the same category simply doesn't upload it as panic/spam prevention.
+ - bugfix: Core preset lawset boards ignores the law cap limit and should no longer
+ causes accidental purges of all inherent laws for AIs.
+ - tweak: Changing a pAI's laws is now logged in their history.
+ - bugfix: RoboTact's law section displays laws without any formatting that comes
+ from things like ion/hacked laws.
+ bruhlookatthisdood:
+ - bugfix: donut station's tachyon-doppler array is facing the right way now.
+ cowbot92:
+ - rscdel: Removes the donksoft vendor from asteroid
+ wonderinghost:
+ - tweak: max tabs in microprocessors have been reduced by one
+ - tweak: telescreens can now download camera software
+ - tweak: construction bag can now hold computer parts
+ - tweak: wired network card has added one bulk to advanced network card
+ - tweak: pdas and phones can accept small computer parts
+ - mapping: adds box and meta TEG to engine rotation.
+2024-02-12:
+ Moltijoe:
+ - tweak: Makes a small minor mini tweak to some battle royale weapon weights
+ wonderinghost:
+ - bugfix: fixes holoparasites ability to break cuffs
+ - tweak: swarmer cuffs take 20 seconds instead of 45
+ - tweak: converts cuff deci seconds to seconds
+ - bugfix: added sprite to alien cuffs
+2024-02-13:
+ ' ktlwjec':
+ - imagedel: Removes stray pixels from a couple of medikits.
+ warface1234455:
+ - rscadd: Give HFR gas monitoring bar a decreasement/increasement rate rate
+ - tweak: Moderator now list all sticky gases
+ - tweak: increase HFR effciency due to recent process change that decreased HFR
+ performance
+ - tweak: dirty production rate no longer relies on fuel injection rate and instead
+ fully rely on the amount of byproduct gas in the reaction
+2024-02-15:
+ ' @Chubbygummibear @LazennG':
+ - rscadd: Added new martial art for seismic arm
+ - rscdel: Removed old seismic arm actions
+ ' ktlwjec':
+ - rscadd: Ice cream vat tells you that vanilla ice cream needs vanilla powder to
+ make.
+ - tweak: Grape snowcone recipe changed from 5u berry juice to 5u grape juice.
+ Aquizit:
+ - tweak: No longer told to ahelp when cryoing as a valentine
+ Moltijoe:
+ - rscadd: New wizard spell, bestow appendicitis
+ - tweak: Bad wizard that previously had curse of cluwne (before it was removed)
+ now gets appendicitis
+ Runian:
+ - bugfix: Newly created cyborgs with an closed AI Connection Port do not inherit
+ an AI's laws (if any) before getting desynced.
+ SapphicOverload:
+ - bugfix: radial menus show up when you're inside an object
+ redmoogle:
+ - bugfix: Shitcurity jumpsuit shows up
+ - bugfix: Light switches are no longer a free source of frames
+ - spellcheck: Fixed RLS description
+ - tweak: Teleport + Jump to Area have been merged together
+ - rscadd: 'new PS upgrade: doubles the capacity'
+ tattax:
+ - bugfix: fixed spells statpanel not working
+ - bugfix: fixed golden violin devil spell (lol)
+ thorium90cent:
+ - rscadd: rain on brick from katana zero is now apart of the jukebox
+2024-02-16:
+ Mqiib:
+ - imageadd: Tweaked female neck pixel to make clothes look better on non-humans
+2024-02-17:
+ ' cark':
+ - rscadd: Added a new lobby song for Valentine's Day "Be My Valentine" by Tim McMorris
+ Moltijoe:
+ - bugfix: TRAIT_HARDLY_WOUNDED now actually does what it says
+ Mqiib:
+ - bugfix: Mechs without any power are no longer immune to EMPs
+ ToasterBiome:
+ - rscdel: Traitor AIs can no longer edit their laws.
+ - bugfix: Paladin now has the correct laws again.
+ cowbot92:
+ - rscadd: Adds new donut recipie
+ redmoogle:
+ - bugfix: Plasma shotgun can no longer steal your stuff
+2024-02-18:
+ ' Chubbygummibear, cowbot92, JohnFulpWillard, ToasterBiome, LazennG, Moltijoe, LemonInTheDark, azzzertyy':
+ - rscadd: Rendering backend is like 200% different
+ - rscadd: stuff glows in the dark
+ - rscdel: Overlay smoothing is dead
+ - bugfix: Cameras and other popup maps work on clients >1615
+ - bugfix: mapmerge tool shouldn't blow up multi-z maps anymore
+ - wip: 'Yes'
+ - mapping: icemeta got flipped but it should be the same from top to bottom
+ - experiment: yeah
+ Moltijoe:
+ - rscadd: Adds stuff for Hisa to bus with
+ - soundadd: Sound for bussing stuff
+ - imageadd: Images for bussing stuff
+ ToasterBiome:
+ - mapping: Removes arrival shuttle from NVS Gax, people spawn in cryo now
+ cowbot92:
+ - rscdel: Removed Yellow Jacket Matriarchs from the gold slime pool
+2024-02-19:
+ SapphicOverload:
+ - bugfix: RCDs work on space tiles again
+ - bugfix: Fixed space lighting not working in space
+2024-02-20:
+ Chubbygummibear:
+ - bugfix: fix adminwho by skipping null clients in the admin list
+ cowbot92:
+ - bugfix: fixed beartraps not trapping
+ warface1234455:
+ - bugfix: cable coil maxstacks is now 40
+2024-02-21:
+ ' Identification':
+ - imageadd: Restores walls to true top-down.
+ ' sapphicoverload, syncit21, zxaber':
+ - rscadd: added a bunch more equipment for utility mechs
+ - soundadd: adds new sound effects for hydraulic clamp and APLU mech movement
+ - tweak: hydraulic clamp works as a wrench or crowbar and can pry powered doors
+ - tweak: mech plasma cutter can be used as a welder like the handheld version
+ - tweak: mech RCD works like an actual RCD now
+ - tweak: adds tgui RCD menu
+ - tweak: strafe button icon shows whether or not you're strafing
+ - tweak: APLU and Clarke mechs now have radiation shielding
+ - bugfix: fixed clarke movement not working correctly
+ - bugfix: fixes and re-adds multi-Z mech movement (no phasing through floors or
+ teleporting to other z-levels this time)
+ 13spacemen:
+ - tweak: The latejoin menu no longer has anemia.
+ AMyriad:
+ - bugfix: Fixed the eye of god expose cooldown going off when an invalid target
+ is selected
+ - spellcheck: Fixed mention of Weyland-Yutani appearing in the ripley mech repair
+ manual
+ - tweak: '"Hyperspace" no longer exists and never did, FTL travel is achieved by
+ tunneling through bluespace'
+ - bugfix: Players can no longer relabel gas canisters to a variant that's intended
+ for admins and breaks the 4th wall
+ Chubbygummibear:
+ - bugfix: index_out_of_bounds fixed on blend_cutoff_colors
+ - bugfix: turfs that smooth will spur their neighboring turfs to check their smoothing
+ again when late loaded in
+ - bugfix: cables on the correct plane so they don't have weird lattice lighting
+ filters
+ - bugfix: meteors with space lighting overlays rotate with the meteors instead of
+ being disjointed and unmoving
+ - bugfix: shuttle atmos shouldn't get left behind on takeoff
+ LazennG:
+ - bugfix: fixed seismic suplex animation and some text
+ adamsong:
+ - bugfix: fixed shuttle catastrophe
+ - bugfix: fixed admin shuttle manipulator
+ cowbot92:
+ - tweak: adjust some pop requirements for several antags
+ redmoogle:
+ - rscadd: PC Range Upgrade
+ - rscadd: PC Cooldown Upgrade
+ - bugfix: Vending machines not containing upgrades
+ - tweak: All plasma cutters can be upgraded
+ - tweak: Plasmacutters can be recharged with one click now
+ warface1234455:
+ - tweak: Partially revert undocumented change to shutter/blastdoor layer covering
+ window but still keep the blast door covering the door
+2024-02-23:
+ ' Cowbot92 & Chubbygummibear':
+ - bugfix: Fixes a bunch of stuff you step on
+ - rscadd: Added new traitor item, the gasharpoon
+ Chubbygummibear:
+ - rscadd: auto_profile config flag added to the private default and private server
+ config files
+ - tweak: private servers aren't dumpy unless you specifically request them to be
+ - tweak: main yog server will be extra dumpy because we can handle it
+ - bugfix: no more double clerk and chaplain workplace spawns
+ Moltijoe:
+ - imageadd: New ethereal bodyparts
+ - imagedel: Old ethereal bodyparts
diff --git a/html/changelogs/AutoChangelog-pr-21149.yml b/html/changelogs/AutoChangelog-pr-21149.yml
deleted file mode 100644
index 24c35043cb4f..000000000000
--- a/html/changelogs/AutoChangelog-pr-21149.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Moltijoe"
-delete-after: true
-changes:
- - tweak: "Lets hand-based extinguishing be used on corpses"
diff --git a/html/changelogs/AutoChangelog-pr-21152.yml b/html/changelogs/AutoChangelog-pr-21152.yml
deleted file mode 100644
index 622639a40606..000000000000
--- a/html/changelogs/AutoChangelog-pr-21152.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Runian"
-delete-after: true
-changes:
- - bugfix: "IPCs now play their special walk sound when they move as intended."
diff --git a/html/changelogs/AutoChangelog-pr-21154.yml b/html/changelogs/AutoChangelog-pr-21154.yml
deleted file mode 100644
index c90668a119f6..000000000000
--- a/html/changelogs/AutoChangelog-pr-21154.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Runian"
-delete-after: true
-changes:
- - spellcheck: "Emagging/hacking cyborg event typo: privleges -> privileges"
diff --git a/html/changelogs/AutoChangelog-pr-21157.yml b/html/changelogs/AutoChangelog-pr-21157.yml
deleted file mode 100644
index 416a17addb3b..000000000000
--- a/html/changelogs/AutoChangelog-pr-21157.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: "Runian"
-delete-after: true
-changes:
- - bugfix: "Trying to remove decoy brains from MMIs properly works."
- - bugfix: "Removing MMI brains from range no longer teleport it into your hands if you're far away (e.g. through walls/windows with TK). Same for folding paper."
- - spellcheck: "Ending punctuation for ripping out brain from MMI."
diff --git a/html/changelogs/AutoChangelog-pr-21390.yml b/html/changelogs/AutoChangelog-pr-21390.yml
new file mode 100644
index 000000000000..33de417ea20c
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21390.yml
@@ -0,0 +1,4 @@
+author: " ktlwjec"
+delete-after: true
+changes:
+ - imageadd: "Lightens the outline for the bag of holding in-hand sprites."
diff --git a/html/changelogs/AutoChangelog-pr-21415.yml b/html/changelogs/AutoChangelog-pr-21415.yml
new file mode 100644
index 000000000000..56fdcabb14b4
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21415.yml
@@ -0,0 +1,4 @@
+author: "AMyriad"
+delete-after: true
+changes:
+ - spellcheck: "Fixed every description that didn't begin with a capital letter, make a bug report if you find something I missed"
diff --git a/html/changelogs/AutoChangelog-pr-21430.yml b/html/changelogs/AutoChangelog-pr-21430.yml
new file mode 100644
index 000000000000..c97868fc83b2
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21430.yml
@@ -0,0 +1,4 @@
+author: "Mqiib"
+delete-after: true
+changes:
+ - tweak: "Mech melee weapons now also hit vines properly when using cleave attacks"
diff --git a/html/changelogs/AutoChangelog-pr-21437.yml b/html/changelogs/AutoChangelog-pr-21437.yml
new file mode 100644
index 000000000000..b0e6631e8de6
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21437.yml
@@ -0,0 +1,7 @@
+author: "SapphicOverload"
+delete-after: true
+changes:
+ - tweak: "flamethrowers now react their fuel for damage and effects instead of using pre-determined values"
+ - tweak: "flamethrower tanks release some of their contents when shot instead of just deleting themselves"
+ - tweak: "flamethrowers can hit vines on windows"
+ - bugfix: "fixed flamethrowers sometimes using more fuel than intended"
diff --git a/html/changelogs/AutoChangelog-pr-21474.yml b/html/changelogs/AutoChangelog-pr-21474.yml
new file mode 100644
index 000000000000..210b6c682b74
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21474.yml
@@ -0,0 +1,4 @@
+author: "AMyriad"
+delete-after: true
+changes:
+ - tweak: "MMIs and positronic brains can no longer be printed from the medical techfab"
diff --git a/html/changelogs/AutoChangelog-pr-21483.yml b/html/changelogs/AutoChangelog-pr-21483.yml
new file mode 100644
index 000000000000..f02b7a33d652
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21483.yml
@@ -0,0 +1,4 @@
+author: "cowbot92"
+delete-after: true
+changes:
+ - rscadd: " Ports baton light up from TG"
diff --git a/html/changelogs/AutoChangelog-pr-21493.yml b/html/changelogs/AutoChangelog-pr-21493.yml
new file mode 100644
index 000000000000..b7eb51bc0cdf
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21493.yml
@@ -0,0 +1,4 @@
+author: "warface1234455"
+delete-after: true
+changes:
+ - tweak: "HFR gas byproducts now uses scaled_production instead of fuel_consumption, and now scaled alongside with gas produced from moderator, code refactor"
diff --git a/html/changelogs/AutoChangelog-pr-21496.yml b/html/changelogs/AutoChangelog-pr-21496.yml
new file mode 100644
index 000000000000..1d9544e59ed1
--- /dev/null
+++ b/html/changelogs/AutoChangelog-pr-21496.yml
@@ -0,0 +1,4 @@
+author: "Mqiib"
+delete-after: true
+changes:
+ - tweak: "The clarke is now tied for fastest mech in the game with the ripley mk-I"
diff --git a/html/templates/header.html b/html/templates/header.html
index 480a4baed9ce..af04adbc6bf6 100644
--- a/html/templates/header.html
+++ b/html/templates/header.html
@@ -37,10 +37,10 @@
Current Head Developers: JamieD12, baiomu Currently Active GitHub contributor list:-Click Here-
- Code Maintainers: adamsogm, AshCorr, Bibby, Cuackles, Djiq, Ling, Manatee, Molti, monster860, TheReddDragon, Wejengin2, ynot01
- Sprite Maintainers: Anerisa, Cuackles, Halcyon
- Map Maintainers: Aquizit, Ezhan, Wejengin2
- Thanks to: /tg/station, FTL13 devs, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, the original SpaceStation developers, Invisty for the title image and Hippiestation for dissapointed.ogg and other assets. Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on our discord.
+ Code Maintainers: adamsogm, AshCorr, Bibby, Chubbygummibear, Djiq, John Willard, Manatee, Molti, monster860, SapphicOverload, ynot01
+ Sprite Maintainers: Anerisa, BlueishTsunami, Cuackles, Halcyon
+ Map Maintainers: Aquizit, Cark, Ezhan, Wejengin2
+ Thanks to: /tg/station, FTL13 devs, Baystation 12, /vg/station, NTstation, CDK Station devs, FacepunchStation, GoonStation devs, BeeStation, Shiptest, the original SpaceStation developers, Invisty for the title image and Hippiestation for dissapointed.ogg and other assets. Also a thanks to anybody who has contributed who is not listed here :( Ask to be added here on our discord.
Have a bug to report? Visit our Issue Tracker.
Please ensure that the bug has not already been reported and use the template provided here.
Want something minor added? Check out this topic!
diff --git a/icons/UI_Icons/tgui/grid_background.png b/icons/UI_Icons/tgui/grid_background.png
new file mode 100644
index 000000000000..228d373456ce
Binary files /dev/null and b/icons/UI_Icons/tgui/grid_background.png differ
diff --git a/icons/effects/alphacolors.dmi b/icons/effects/alphacolors.dmi
index c3dbcaee39be..f3241ba010d9 100644
Binary files a/icons/effects/alphacolors.dmi and b/icons/effects/alphacolors.dmi differ
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index fbe6182716b7..9f06f421b549 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/blood.dmi b/icons/effects/blood.dmi
index 232d46dd37f0..483ab0444233 100644
Binary files a/icons/effects/blood.dmi and b/icons/effects/blood.dmi differ
diff --git a/icons/effects/clothing.dmi b/icons/effects/clothing.dmi
index eb0f58386583..3e387d7a4ded 100644
Binary files a/icons/effects/clothing.dmi and b/icons/effects/clothing.dmi differ
diff --git a/icons/effects/dirt.dmi b/icons/effects/dirt.dmi
index cd673a949ded..c3eeae8a9c55 100644
Binary files a/icons/effects/dirt.dmi and b/icons/effects/dirt.dmi differ
diff --git a/icons/effects/footprints.dmi b/icons/effects/footprints.dmi
index a98344abe41e..dc8ddb4e7341 100644
Binary files a/icons/effects/footprints.dmi and b/icons/effects/footprints.dmi differ
diff --git a/icons/effects/glow_weather.dmi b/icons/effects/glow_weather.dmi
new file mode 100644
index 000000000000..021da02fe37b
Binary files /dev/null and b/icons/effects/glow_weather.dmi differ
diff --git a/icons/effects/lighting_object.dmi b/icons/effects/lighting_object.dmi
index 077043809d64..49d16c58acf8 100644
Binary files a/icons/effects/lighting_object.dmi and b/icons/effects/lighting_object.dmi differ
diff --git a/icons/effects/mouse_pointers/tar_shapeshift.dmi b/icons/effects/mouse_pointers/tar_shapeshift.dmi
new file mode 100644
index 000000000000..89dfb28b9a2a
Binary files /dev/null and b/icons/effects/mouse_pointers/tar_shapeshift.dmi differ
diff --git a/icons/effects/parallax.dmi b/icons/effects/parallax.dmi
index ffcfeee79a4a..e729ea529bb3 100755
Binary files a/icons/effects/parallax.dmi and b/icons/effects/parallax.dmi differ
diff --git a/icons/effects/weather_effects.dmi b/icons/effects/weather_effects.dmi
index da1523706e2b..dbee209aa751 100644
Binary files a/icons/effects/weather_effects.dmi and b/icons/effects/weather_effects.dmi differ
diff --git a/icons/mecha/mecha_equipment.dmi b/icons/mecha/mecha_equipment.dmi
index e64729a33764..abfa721d57d9 100644
Binary files a/icons/mecha/mecha_equipment.dmi and b/icons/mecha/mecha_equipment.dmi differ
diff --git a/icons/misc/colortest.dmi b/icons/misc/colortest.dmi
new file mode 100644
index 000000000000..0f74685ebc24
Binary files /dev/null and b/icons/misc/colortest.dmi differ
diff --git a/icons/mob/actions/actions_arm.dmi b/icons/mob/actions/actions_arm.dmi
index 71bb415e8ad9..2154e742819e 100644
Binary files a/icons/mob/actions/actions_arm.dmi and b/icons/mob/actions/actions_arm.dmi differ
diff --git a/icons/mob/actions/actions_mecha.dmi b/icons/mob/actions/actions_mecha.dmi
index 81c706b22c8a..78a2ba5ec89a 100644
Binary files a/icons/mob/actions/actions_mecha.dmi and b/icons/mob/actions/actions_mecha.dmi differ
diff --git a/icons/mob/actions/actions_silicon.dmi b/icons/mob/actions/actions_silicon.dmi
index 266af14ac9c4..e622217b9ccd 100644
Binary files a/icons/mob/actions/actions_silicon.dmi and b/icons/mob/actions/actions_silicon.dmi differ
diff --git a/icons/mob/clothing/hands/hands.dmi b/icons/mob/clothing/hands/hands.dmi
index 57bef8c67fff..e07cfff4b117 100644
Binary files a/icons/mob/clothing/hands/hands.dmi and b/icons/mob/clothing/hands/hands.dmi differ
diff --git a/icons/mob/clothing/head/winterhood.dmi b/icons/mob/clothing/head/winterhood.dmi
new file mode 100644
index 000000000000..48e5e402169c
Binary files /dev/null and b/icons/mob/clothing/head/winterhood.dmi differ
diff --git a/icons/mob/clothing/suit/suit.dmi b/icons/mob/clothing/suit/suit.dmi
index 50e29351f29c..ac8c422c19db 100644
Binary files a/icons/mob/clothing/suit/suit.dmi and b/icons/mob/clothing/suit/suit.dmi differ
diff --git a/icons/mob/clothing/suit/wintercoat.dmi b/icons/mob/clothing/suit/wintercoat.dmi
new file mode 100644
index 000000000000..3c10514f7e1d
Binary files /dev/null and b/icons/mob/clothing/suit/wintercoat.dmi differ
diff --git a/icons/mob/clothing/uniform/uniform.dmi b/icons/mob/clothing/uniform/uniform.dmi
index 6508b56f227e..9fa35b94b8f3 100644
Binary files a/icons/mob/clothing/uniform/uniform.dmi and b/icons/mob/clothing/uniform/uniform.dmi differ
diff --git a/icons/mob/human_parts_greyscale.dmi b/icons/mob/human_parts_greyscale.dmi
index 4b5972de849b..a7e311e3a225 100644
Binary files a/icons/mob/human_parts_greyscale.dmi and b/icons/mob/human_parts_greyscale.dmi differ
diff --git a/icons/mob/icemoon/icemoon_monsters.dmi b/icons/mob/icemoon/icemoon_monsters.dmi
index cbfdc26c7458..e8a66fdca049 100644
Binary files a/icons/mob/icemoon/icemoon_monsters.dmi and b/icons/mob/icemoon/icemoon_monsters.dmi differ
diff --git a/icons/mob/inhands/equipment/backpack_lefthand.dmi b/icons/mob/inhands/equipment/backpack_lefthand.dmi
index 15425dabca14..0183ae173468 100644
Binary files a/icons/mob/inhands/equipment/backpack_lefthand.dmi and b/icons/mob/inhands/equipment/backpack_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/backpack_righthand.dmi b/icons/mob/inhands/equipment/backpack_righthand.dmi
index c51285f0867f..94b50fbb8d63 100644
Binary files a/icons/mob/inhands/equipment/backpack_righthand.dmi and b/icons/mob/inhands/equipment/backpack_righthand.dmi differ
diff --git a/icons/mob/inhands/equipment/security_lefthand.dmi b/icons/mob/inhands/equipment/security_lefthand.dmi
index 3f61d341ef11..434be69e4db4 100644
Binary files a/icons/mob/inhands/equipment/security_lefthand.dmi and b/icons/mob/inhands/equipment/security_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/security_righthand.dmi b/icons/mob/inhands/equipment/security_righthand.dmi
index ec860707cd1b..ee2140a38a58 100644
Binary files a/icons/mob/inhands/equipment/security_righthand.dmi and b/icons/mob/inhands/equipment/security_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/melee_lefthand.dmi b/icons/mob/inhands/weapons/melee_lefthand.dmi
index 63624745e62c..dd0e7cfc86b0 100644
Binary files a/icons/mob/inhands/weapons/melee_lefthand.dmi and b/icons/mob/inhands/weapons/melee_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/melee_righthand.dmi b/icons/mob/inhands/weapons/melee_righthand.dmi
index d50eb8ccea9d..6b66977d77a0 100644
Binary files a/icons/mob/inhands/weapons/melee_righthand.dmi and b/icons/mob/inhands/weapons/melee_righthand.dmi differ
diff --git a/icons/mob/mutant_bodyparts.dmi b/icons/mob/mutant_bodyparts.dmi
index 92b83f8db279..f1e91c19e92b 100644
Binary files a/icons/mob/mutant_bodyparts.dmi and b/icons/mob/mutant_bodyparts.dmi differ
diff --git a/icons/mob/radial.dmi b/icons/mob/radial.dmi
index 533e11521d65..9006b42b0700 100644
Binary files a/icons/mob/radial.dmi and b/icons/mob/radial.dmi differ
diff --git a/icons/mob/talk.dmi b/icons/mob/talk.dmi
index 772d4bc6f930..e0648a5af0a1 100644
Binary files a/icons/mob/talk.dmi and b/icons/mob/talk.dmi differ
diff --git a/icons/mob/wings.dmi b/icons/mob/wings.dmi
index 502299010438..70c02c6a77ab 100644
Binary files a/icons/mob/wings.dmi and b/icons/mob/wings.dmi differ
diff --git a/icons/obj/barsigns.dmi b/icons/obj/barsigns.dmi
index 46b9cfb8a746..584395f14644 100644
Binary files a/icons/obj/barsigns.dmi and b/icons/obj/barsigns.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats/hats.dmi
similarity index 51%
rename from icons/obj/clothing/hats.dmi
rename to icons/obj/clothing/hats/hats.dmi
index a570f272fee5..38329a78dc60 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats/hats.dmi differ
diff --git a/icons/obj/clothing/hats/winterhood.dmi b/icons/obj/clothing/hats/winterhood.dmi
new file mode 100644
index 000000000000..7fd5bac95c08
Binary files /dev/null and b/icons/obj/clothing/hats/winterhood.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
deleted file mode 100644
index 7c5e61340cf6..000000000000
Binary files a/icons/obj/clothing/suits.dmi and /dev/null differ
diff --git a/icons/obj/clothing/suits/suits.dmi b/icons/obj/clothing/suits/suits.dmi
new file mode 100644
index 000000000000..9e3b9be7ff5a
Binary files /dev/null and b/icons/obj/clothing/suits/suits.dmi differ
diff --git a/icons/obj/clothing/suits/wintercoat.dmi b/icons/obj/clothing/suits/wintercoat.dmi
new file mode 100644
index 000000000000..5fe5681fa7aa
Binary files /dev/null and b/icons/obj/clothing/suits/wintercoat.dmi differ
diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi
index 745cdf7c32b9..0bc062197142 100644
Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ
diff --git a/icons/obj/economy.dmi b/icons/obj/economy.dmi
index ccce44f2306f..d3aedab56a0f 100644
Binary files a/icons/obj/economy.dmi and b/icons/obj/economy.dmi differ
diff --git a/icons/obj/flora/deadtrees.dmi b/icons/obj/flora/deadtrees.dmi
index 25494901afc0..74a7b0f1e00e 100644
Binary files a/icons/obj/flora/deadtrees.dmi and b/icons/obj/flora/deadtrees.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 990c31ada002..8af620b9d4b5 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/guns/magic.dmi b/icons/obj/guns/magic.dmi
index 94bb606cbfc5..7ae2646ec3b8 100644
Binary files a/icons/obj/guns/magic.dmi and b/icons/obj/guns/magic.dmi differ
diff --git a/icons/obj/hydroponics/growing.dmi b/icons/obj/hydroponics/growing.dmi
index b49789057b5a..dd1bd4424307 100644
Binary files a/icons/obj/hydroponics/growing.dmi and b/icons/obj/hydroponics/growing.dmi differ
diff --git a/icons/obj/hydroponics/growing_flowers.dmi b/icons/obj/hydroponics/growing_flowers.dmi
index 851db98dbbe9..af1da79061ed 100644
Binary files a/icons/obj/hydroponics/growing_flowers.dmi and b/icons/obj/hydroponics/growing_flowers.dmi differ
diff --git a/icons/obj/hydroponics/growing_fruits.dmi b/icons/obj/hydroponics/growing_fruits.dmi
index dde63ba1d286..58802d672cb5 100644
Binary files a/icons/obj/hydroponics/growing_fruits.dmi and b/icons/obj/hydroponics/growing_fruits.dmi differ
diff --git a/icons/obj/hydroponics/growing_mushrooms.dmi b/icons/obj/hydroponics/growing_mushrooms.dmi
index b51b12bb6f93..63a03df81ddf 100644
Binary files a/icons/obj/hydroponics/growing_mushrooms.dmi and b/icons/obj/hydroponics/growing_mushrooms.dmi differ
diff --git a/icons/obj/hydroponics/seeds.dmi b/icons/obj/hydroponics/seeds.dmi
index bd009705983a..f707bb375d0e 100644
Binary files a/icons/obj/hydroponics/seeds.dmi and b/icons/obj/hydroponics/seeds.dmi differ
diff --git a/icons/obj/implants.dmi b/icons/obj/implants.dmi
index 9f9d9f06515a..bcb69a7c4448 100644
Binary files a/icons/obj/implants.dmi and b/icons/obj/implants.dmi differ
diff --git a/icons/obj/lavaland/artefacts.dmi b/icons/obj/lavaland/artefacts.dmi
index 494c964d48f2..00d590b2ac43 100644
Binary files a/icons/obj/lavaland/artefacts.dmi and b/icons/obj/lavaland/artefacts.dmi differ
diff --git a/icons/obj/machines/bluespace_tap.dmi b/icons/obj/machines/bluespace_tap.dmi
index 1688b2c5ba48..7d53d84fc225 100644
Binary files a/icons/obj/machines/bluespace_tap.dmi and b/icons/obj/machines/bluespace_tap.dmi differ
diff --git a/icons/obj/monitors.dmi b/icons/obj/monitors.dmi
index 06e2f8782ab0..860a11a75ed5 100644
Binary files a/icons/obj/monitors.dmi and b/icons/obj/monitors.dmi differ
diff --git a/icons/obj/nuke_tools.dmi b/icons/obj/nuke_tools.dmi
index 68af3b80bfa7..db2f590251be 100644
Binary files a/icons/obj/nuke_tools.dmi and b/icons/obj/nuke_tools.dmi differ
diff --git a/icons/obj/power.dmi b/icons/obj/power.dmi
index 36a6ee743e76..91978ee4d4b5 100644
Binary files a/icons/obj/power.dmi and b/icons/obj/power.dmi differ
diff --git a/icons/obj/smartfridge.dmi b/icons/obj/smartfridge.dmi
new file mode 100644
index 000000000000..35feb98f29d6
Binary files /dev/null and b/icons/obj/smartfridge.dmi differ
diff --git a/icons/obj/smooth_structures/alien/nest.dmi b/icons/obj/smooth_structures/alien/nest.dmi
index 4a1c757bc642..5f2c2503ba20 100644
Binary files a/icons/obj/smooth_structures/alien/nest.dmi and b/icons/obj/smooth_structures/alien/nest.dmi differ
diff --git a/icons/obj/smooth_structures/alien/nest.png b/icons/obj/smooth_structures/alien/nest.png
new file mode 100644
index 000000000000..65d06047e34e
Binary files /dev/null and b/icons/obj/smooth_structures/alien/nest.png differ
diff --git a/icons/obj/smooth_structures/alien/nest.png.toml b/icons/obj/smooth_structures/alien/nest.png.toml
new file mode 100644
index 000000000000..0d2dedabe815
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/nest.png.toml
@@ -0,0 +1,2 @@
+output_name = "nest"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/alien/resin_membrane.dmi b/icons/obj/smooth_structures/alien/resin_membrane.dmi
index 517838a653a3..f91125dcecaf 100644
Binary files a/icons/obj/smooth_structures/alien/resin_membrane.dmi and b/icons/obj/smooth_structures/alien/resin_membrane.dmi differ
diff --git a/icons/obj/smooth_structures/alien/resin_membrane.png b/icons/obj/smooth_structures/alien/resin_membrane.png
new file mode 100644
index 000000000000..3ea4dc33ecce
Binary files /dev/null and b/icons/obj/smooth_structures/alien/resin_membrane.png differ
diff --git a/icons/obj/smooth_structures/alien/resin_membrane.png.toml b/icons/obj/smooth_structures/alien/resin_membrane.png.toml
new file mode 100644
index 000000000000..be1ef95dde42
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/resin_membrane.png.toml
@@ -0,0 +1,2 @@
+output_name = "resin_membrane"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/alien/resin_wall.dmi b/icons/obj/smooth_structures/alien/resin_wall.dmi
index eba082a8303b..1c2be2e90763 100644
Binary files a/icons/obj/smooth_structures/alien/resin_wall.dmi and b/icons/obj/smooth_structures/alien/resin_wall.dmi differ
diff --git a/icons/obj/smooth_structures/alien/resin_wall.png b/icons/obj/smooth_structures/alien/resin_wall.png
new file mode 100644
index 000000000000..d42f25a43fd8
Binary files /dev/null and b/icons/obj/smooth_structures/alien/resin_wall.png differ
diff --git a/icons/obj/smooth_structures/alien/resin_wall.png.toml b/icons/obj/smooth_structures/alien/resin_wall.png.toml
new file mode 100644
index 000000000000..d0ef4fffbdea
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/resin_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "resin_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/alien/weednode.dmi b/icons/obj/smooth_structures/alien/weednode.dmi
index 8d963d7c83eb..926730d89912 100644
Binary files a/icons/obj/smooth_structures/alien/weednode.dmi and b/icons/obj/smooth_structures/alien/weednode.dmi differ
diff --git a/icons/obj/smooth_structures/alien/weednode.png b/icons/obj/smooth_structures/alien/weednode.png
new file mode 100644
index 000000000000..0a6992d4e8b8
Binary files /dev/null and b/icons/obj/smooth_structures/alien/weednode.png differ
diff --git a/icons/obj/smooth_structures/alien/weednode.png.toml b/icons/obj/smooth_structures/alien/weednode.png.toml
new file mode 100644
index 000000000000..591ca723785b
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/weednode.png.toml
@@ -0,0 +1,14 @@
+output_name = "weednode"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/alien/weeds1.dmi b/icons/obj/smooth_structures/alien/weeds1.dmi
index b794b6853fc0..4ba1bafcfe52 100644
Binary files a/icons/obj/smooth_structures/alien/weeds1.dmi and b/icons/obj/smooth_structures/alien/weeds1.dmi differ
diff --git a/icons/obj/smooth_structures/alien/weeds1.png b/icons/obj/smooth_structures/alien/weeds1.png
new file mode 100644
index 000000000000..1275858dba88
Binary files /dev/null and b/icons/obj/smooth_structures/alien/weeds1.png differ
diff --git a/icons/obj/smooth_structures/alien/weeds1.png.toml b/icons/obj/smooth_structures/alien/weeds1.png.toml
new file mode 100644
index 000000000000..8dc62bea3ccb
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/weeds1.png.toml
@@ -0,0 +1,14 @@
+output_name = "weeds1"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/alien/weeds2.dmi b/icons/obj/smooth_structures/alien/weeds2.dmi
index 8917bade585b..12c3b37b0ad7 100644
Binary files a/icons/obj/smooth_structures/alien/weeds2.dmi and b/icons/obj/smooth_structures/alien/weeds2.dmi differ
diff --git a/icons/obj/smooth_structures/alien/weeds2.png b/icons/obj/smooth_structures/alien/weeds2.png
new file mode 100644
index 000000000000..a3e0002774b2
Binary files /dev/null and b/icons/obj/smooth_structures/alien/weeds2.png differ
diff --git a/icons/obj/smooth_structures/alien/weeds2.png.toml b/icons/obj/smooth_structures/alien/weeds2.png.toml
new file mode 100644
index 000000000000..a24f8c01759c
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/weeds2.png.toml
@@ -0,0 +1,14 @@
+output_name = "weeds2"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/alien/weeds3.dmi b/icons/obj/smooth_structures/alien/weeds3.dmi
index c7f0aa0ca970..45665d8969ed 100644
Binary files a/icons/obj/smooth_structures/alien/weeds3.dmi and b/icons/obj/smooth_structures/alien/weeds3.dmi differ
diff --git a/icons/obj/smooth_structures/alien/weeds3.png b/icons/obj/smooth_structures/alien/weeds3.png
new file mode 100644
index 000000000000..b9c0d4440156
Binary files /dev/null and b/icons/obj/smooth_structures/alien/weeds3.png differ
diff --git a/icons/obj/smooth_structures/alien/weeds3.png.toml b/icons/obj/smooth_structures/alien/weeds3.png.toml
new file mode 100644
index 000000000000..19eb01420b4e
--- /dev/null
+++ b/icons/obj/smooth_structures/alien/weeds3.png.toml
@@ -0,0 +1,14 @@
+output_name = "weeds3"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/alien_table.dmi b/icons/obj/smooth_structures/alien_table.dmi
index 1d60bcc82f66..865b7d3572f1 100644
Binary files a/icons/obj/smooth_structures/alien_table.dmi and b/icons/obj/smooth_structures/alien_table.dmi differ
diff --git a/icons/obj/smooth_structures/alien_table.png b/icons/obj/smooth_structures/alien_table.png
new file mode 100644
index 000000000000..d9a5c3b73e41
Binary files /dev/null and b/icons/obj/smooth_structures/alien_table.png differ
diff --git a/icons/obj/smooth_structures/alien_table.png.toml b/icons/obj/smooth_structures/alien_table.png.toml
new file mode 100644
index 000000000000..8ae63217ae57
--- /dev/null
+++ b/icons/obj/smooth_structures/alien_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "alien_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/brass_table.dmi b/icons/obj/smooth_structures/brass_table.dmi
index f3cc8bb9ee51..74417b815ab3 100644
Binary files a/icons/obj/smooth_structures/brass_table.dmi and b/icons/obj/smooth_structures/brass_table.dmi differ
diff --git a/icons/obj/smooth_structures/brass_table.png b/icons/obj/smooth_structures/brass_table.png
new file mode 100644
index 000000000000..2552eb2f92b0
Binary files /dev/null and b/icons/obj/smooth_structures/brass_table.png differ
diff --git a/icons/obj/smooth_structures/brass_table.png.toml b/icons/obj/smooth_structures/brass_table.png.toml
new file mode 100644
index 000000000000..d633747042d5
--- /dev/null
+++ b/icons/obj/smooth_structures/brass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "brass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/catwalk.dmi b/icons/obj/smooth_structures/catwalk.dmi
index 4b71fb507316..568afb15fea9 100644
Binary files a/icons/obj/smooth_structures/catwalk.dmi and b/icons/obj/smooth_structures/catwalk.dmi differ
diff --git a/icons/obj/smooth_structures/catwalk.png b/icons/obj/smooth_structures/catwalk.png
new file mode 100644
index 000000000000..5d9cc9405625
Binary files /dev/null and b/icons/obj/smooth_structures/catwalk.png differ
diff --git a/icons/obj/smooth_structures/catwalk.png.toml b/icons/obj/smooth_structures/catwalk.png.toml
new file mode 100644
index 000000000000..42610e2043b1
--- /dev/null
+++ b/icons/obj/smooth_structures/catwalk.png.toml
@@ -0,0 +1,2 @@
+output_name = "catwalk"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/catwalk_clockwork.dmi b/icons/obj/smooth_structures/catwalk_clockwork.dmi
index ecef1df38979..764a7c9af5ef 100644
Binary files a/icons/obj/smooth_structures/catwalk_clockwork.dmi and b/icons/obj/smooth_structures/catwalk_clockwork.dmi differ
diff --git a/icons/obj/smooth_structures/catwalk_clockwork_large.dmi b/icons/obj/smooth_structures/catwalk_clockwork_large.dmi
index 675ebd91f6df..6595393c4e05 100644
Binary files a/icons/obj/smooth_structures/catwalk_clockwork_large.dmi and b/icons/obj/smooth_structures/catwalk_clockwork_large.dmi differ
diff --git a/icons/obj/smooth_structures/clockwork_window.dmi b/icons/obj/smooth_structures/clockwork_window.dmi
index 10d5bcf631c7..6676095e9fbf 100644
Binary files a/icons/obj/smooth_structures/clockwork_window.dmi and b/icons/obj/smooth_structures/clockwork_window.dmi differ
diff --git a/icons/obj/smooth_structures/clockwork_window.png b/icons/obj/smooth_structures/clockwork_window.png
new file mode 100644
index 000000000000..a94b706447b6
Binary files /dev/null and b/icons/obj/smooth_structures/clockwork_window.png differ
diff --git a/icons/obj/smooth_structures/clockwork_window.png.toml b/icons/obj/smooth_structures/clockwork_window.png.toml
new file mode 100644
index 000000000000..72375e775410
--- /dev/null
+++ b/icons/obj/smooth_structures/clockwork_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "clockwork_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/fancy_table.dmi b/icons/obj/smooth_structures/fancy_table.dmi
index a57b165f4acc..c72e06d6798a 100644
Binary files a/icons/obj/smooth_structures/fancy_table.dmi and b/icons/obj/smooth_structures/fancy_table.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table.png b/icons/obj/smooth_structures/fancy_table.png
new file mode 100644
index 000000000000..3ca279bbd72b
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table.png differ
diff --git a/icons/obj/smooth_structures/fancy_table.png.toml b/icons/obj/smooth_structures/fancy_table.png.toml
new file mode 100644
index 000000000000..df9ca0016d76
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_black.dmi b/icons/obj/smooth_structures/fancy_table_black.dmi
index d4570e8a7c1a..3be7232af5ee 100644
Binary files a/icons/obj/smooth_structures/fancy_table_black.dmi and b/icons/obj/smooth_structures/fancy_table_black.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_black.png b/icons/obj/smooth_structures/fancy_table_black.png
new file mode 100644
index 000000000000..f7e2bfa810bc
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_black.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_black.png.toml b/icons/obj/smooth_structures/fancy_table_black.png.toml
new file mode 100644
index 000000000000..acf7a6b82137
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_black.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_black"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_blue.dmi b/icons/obj/smooth_structures/fancy_table_blue.dmi
new file mode 100644
index 000000000000..26263e514911
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_blue.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_blue.png b/icons/obj/smooth_structures/fancy_table_blue.png
new file mode 100644
index 000000000000..ad2d4192c7f0
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_blue.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_blue.png.toml b/icons/obj/smooth_structures/fancy_table_blue.png.toml
new file mode 100644
index 000000000000..0a71213ab340
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_blue.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_blue"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_cyan.dmi b/icons/obj/smooth_structures/fancy_table_cyan.dmi
index 4f1a90e33b2c..6da44a863cd5 100644
Binary files a/icons/obj/smooth_structures/fancy_table_cyan.dmi and b/icons/obj/smooth_structures/fancy_table_cyan.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_cyan.png b/icons/obj/smooth_structures/fancy_table_cyan.png
new file mode 100644
index 000000000000..96a68b936173
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_cyan.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_cyan.png.toml b/icons/obj/smooth_structures/fancy_table_cyan.png.toml
new file mode 100644
index 000000000000..6991b7ea5248
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_cyan.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_cyan"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_exoticblue.dmi b/icons/obj/smooth_structures/fancy_table_exoticblue.dmi
deleted file mode 100644
index 6e6eee54c8c1..000000000000
Binary files a/icons/obj/smooth_structures/fancy_table_exoticblue.dmi and /dev/null differ
diff --git a/icons/obj/smooth_structures/fancy_table_exoticgreen.dmi b/icons/obj/smooth_structures/fancy_table_exoticgreen.dmi
deleted file mode 100644
index 52ca81972627..000000000000
Binary files a/icons/obj/smooth_structures/fancy_table_exoticgreen.dmi and /dev/null differ
diff --git a/icons/obj/smooth_structures/fancy_table_exoticpurple.dmi b/icons/obj/smooth_structures/fancy_table_exoticpurple.dmi
deleted file mode 100644
index a2a060bc7626..000000000000
Binary files a/icons/obj/smooth_structures/fancy_table_exoticpurple.dmi and /dev/null differ
diff --git a/icons/obj/smooth_structures/fancy_table_green.dmi b/icons/obj/smooth_structures/fancy_table_green.dmi
new file mode 100644
index 000000000000..dc6c7e4703c8
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_green.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_green.png b/icons/obj/smooth_structures/fancy_table_green.png
new file mode 100644
index 000000000000..616eeeaa6eca
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_green.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_green.png.toml b/icons/obj/smooth_structures/fancy_table_green.png.toml
new file mode 100644
index 000000000000..a4486179c6aa
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_green.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_green"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_orange.dmi b/icons/obj/smooth_structures/fancy_table_orange.dmi
index fe0375ddbb8b..91428a752531 100644
Binary files a/icons/obj/smooth_structures/fancy_table_orange.dmi and b/icons/obj/smooth_structures/fancy_table_orange.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_orange.png b/icons/obj/smooth_structures/fancy_table_orange.png
new file mode 100644
index 000000000000..d2aff0ff4c17
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_orange.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_orange.png.toml b/icons/obj/smooth_structures/fancy_table_orange.png.toml
new file mode 100644
index 000000000000..fcca03afb5aa
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_orange.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_orange"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_purple.dmi b/icons/obj/smooth_structures/fancy_table_purple.dmi
new file mode 100644
index 000000000000..930b0556520f
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_purple.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_purple.png b/icons/obj/smooth_structures/fancy_table_purple.png
new file mode 100644
index 000000000000..fad1b80c9229
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_purple.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_purple.png.toml b/icons/obj/smooth_structures/fancy_table_purple.png.toml
new file mode 100644
index 000000000000..2da707716ff0
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_purple.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_purple"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_red.dmi b/icons/obj/smooth_structures/fancy_table_red.dmi
index 8bca0ca8c94d..ceca04aeb060 100644
Binary files a/icons/obj/smooth_structures/fancy_table_red.dmi and b/icons/obj/smooth_structures/fancy_table_red.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_red.png b/icons/obj/smooth_structures/fancy_table_red.png
new file mode 100644
index 000000000000..f94307cb7d42
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_red.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_red.png.toml b/icons/obj/smooth_structures/fancy_table_red.png.toml
new file mode 100644
index 000000000000..d3b99e80746e
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_red.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_red"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.dmi b/icons/obj/smooth_structures/fancy_table_royalblack.dmi
index 064b7c1945ef..b7f6f6e284db 100644
Binary files a/icons/obj/smooth_structures/fancy_table_royalblack.dmi and b/icons/obj/smooth_structures/fancy_table_royalblack.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.png b/icons/obj/smooth_structures/fancy_table_royalblack.png
new file mode 100644
index 000000000000..82913622d1ee
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_royalblack.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_royalblack.png.toml b/icons/obj/smooth_structures/fancy_table_royalblack.png.toml
new file mode 100644
index 000000000000..76a975938f87
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_royalblack.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_royalblack"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.dmi b/icons/obj/smooth_structures/fancy_table_royalblue.dmi
index 9d0eba7265ee..8f7a03ba5adc 100644
Binary files a/icons/obj/smooth_structures/fancy_table_royalblue.dmi and b/icons/obj/smooth_structures/fancy_table_royalblue.dmi differ
diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.png b/icons/obj/smooth_structures/fancy_table_royalblue.png
new file mode 100644
index 000000000000..6b1a2b2a8134
Binary files /dev/null and b/icons/obj/smooth_structures/fancy_table_royalblue.png differ
diff --git a/icons/obj/smooth_structures/fancy_table_royalblue.png.toml b/icons/obj/smooth_structures/fancy_table_royalblue.png.toml
new file mode 100644
index 000000000000..59987f0feb8b
--- /dev/null
+++ b/icons/obj/smooth_structures/fancy_table_royalblue.png.toml
@@ -0,0 +1,14 @@
+output_name = "fancy_table_royalblue"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 32
+y = 34
+
+[output_icon_size]
+x = 32
+y = 34
+
+[cut_pos]
+x = 16
+y = 17
\ No newline at end of file
diff --git a/icons/obj/smooth_structures/glass_table.dmi b/icons/obj/smooth_structures/glass_table.dmi
index 112499f3801c..56c43801832b 100644
Binary files a/icons/obj/smooth_structures/glass_table.dmi and b/icons/obj/smooth_structures/glass_table.dmi differ
diff --git a/icons/obj/smooth_structures/glass_table.png b/icons/obj/smooth_structures/glass_table.png
new file mode 100644
index 000000000000..f61de10c5077
Binary files /dev/null and b/icons/obj/smooth_structures/glass_table.png differ
diff --git a/icons/obj/smooth_structures/glass_table.png.toml b/icons/obj/smooth_structures/glass_table.png.toml
new file mode 100644
index 000000000000..91177545f2c0
--- /dev/null
+++ b/icons/obj/smooth_structures/glass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "glass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/grille.dmi b/icons/obj/smooth_structures/grille.dmi
index 0ca0eecf5528..b8d13f27af73 100644
Binary files a/icons/obj/smooth_structures/grille.dmi and b/icons/obj/smooth_structures/grille.dmi differ
diff --git a/icons/obj/smooth_structures/hedge.dmi b/icons/obj/smooth_structures/hedge.dmi
new file mode 100644
index 000000000000..dea538dd752b
Binary files /dev/null and b/icons/obj/smooth_structures/hedge.dmi differ
diff --git a/icons/obj/smooth_structures/hedge.png b/icons/obj/smooth_structures/hedge.png
new file mode 100644
index 000000000000..923c425cb325
Binary files /dev/null and b/icons/obj/smooth_structures/hedge.png differ
diff --git a/icons/obj/smooth_structures/hedge.png.toml b/icons/obj/smooth_structures/hedge.png.toml
new file mode 100644
index 000000000000..a03553bb3260
--- /dev/null
+++ b/icons/obj/smooth_structures/hedge.png.toml
@@ -0,0 +1,2 @@
+output_name = "hedge"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/lattice.dmi b/icons/obj/smooth_structures/lattice.dmi
index 5adab0caf1be..3ae4964a11f7 100644
Binary files a/icons/obj/smooth_structures/lattice.dmi and b/icons/obj/smooth_structures/lattice.dmi differ
diff --git a/icons/obj/smooth_structures/lattice.png b/icons/obj/smooth_structures/lattice.png
new file mode 100644
index 000000000000..9d00f8dfe20e
Binary files /dev/null and b/icons/obj/smooth_structures/lattice.png differ
diff --git a/icons/obj/smooth_structures/lattice.png.toml b/icons/obj/smooth_structures/lattice.png.toml
new file mode 100644
index 000000000000..63e711fce313
--- /dev/null
+++ b/icons/obj/smooth_structures/lattice.png.toml
@@ -0,0 +1,2 @@
+output_name = "lattice"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/paperframes.dmi b/icons/obj/smooth_structures/paperframes.dmi
index 5ac2c27dd519..0adb2646b1c0 100644
Binary files a/icons/obj/smooth_structures/paperframes.dmi and b/icons/obj/smooth_structures/paperframes.dmi differ
diff --git a/icons/obj/smooth_structures/paperframes.png b/icons/obj/smooth_structures/paperframes.png
new file mode 100644
index 000000000000..b6d10892eccb
Binary files /dev/null and b/icons/obj/smooth_structures/paperframes.png differ
diff --git a/icons/obj/smooth_structures/paperframes.png.toml b/icons/obj/smooth_structures/paperframes.png.toml
new file mode 100644
index 000000000000..5d3bbe124429
--- /dev/null
+++ b/icons/obj/smooth_structures/paperframes.png.toml
@@ -0,0 +1,2 @@
+output_name = "paperframes"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/plasma_window.dmi b/icons/obj/smooth_structures/plasma_window.dmi
index 3d57d156f01e..bbfe75917773 100644
Binary files a/icons/obj/smooth_structures/plasma_window.dmi and b/icons/obj/smooth_structures/plasma_window.dmi differ
diff --git a/icons/obj/smooth_structures/plasma_window.png b/icons/obj/smooth_structures/plasma_window.png
new file mode 100644
index 000000000000..d38c888be9af
Binary files /dev/null and b/icons/obj/smooth_structures/plasma_window.png differ
diff --git a/icons/obj/smooth_structures/plasma_window.png.toml b/icons/obj/smooth_structures/plasma_window.png.toml
new file mode 100644
index 000000000000..2ed25c1b89cc
--- /dev/null
+++ b/icons/obj/smooth_structures/plasma_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "plasma_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/plasmaglass_table.dmi b/icons/obj/smooth_structures/plasmaglass_table.dmi
new file mode 100644
index 000000000000..7c90a489e82d
Binary files /dev/null and b/icons/obj/smooth_structures/plasmaglass_table.dmi differ
diff --git a/icons/obj/smooth_structures/plasmaglass_table.png b/icons/obj/smooth_structures/plasmaglass_table.png
new file mode 100644
index 000000000000..f3b062d6fdb9
Binary files /dev/null and b/icons/obj/smooth_structures/plasmaglass_table.png differ
diff --git a/icons/obj/smooth_structures/plasmaglass_table.png.toml b/icons/obj/smooth_structures/plasmaglass_table.png.toml
new file mode 100644
index 000000000000..744e082acf62
--- /dev/null
+++ b/icons/obj/smooth_structures/plasmaglass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "plasmaglass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/plastitanium_window.dmi b/icons/obj/smooth_structures/plastitanium_window.dmi
index 82ac0306159d..85eae01e8cb8 100644
Binary files a/icons/obj/smooth_structures/plastitanium_window.dmi and b/icons/obj/smooth_structures/plastitanium_window.dmi differ
diff --git a/icons/obj/smooth_structures/plastitanium_window.png b/icons/obj/smooth_structures/plastitanium_window.png
new file mode 100644
index 000000000000..114e5d7e0219
Binary files /dev/null and b/icons/obj/smooth_structures/plastitanium_window.png differ
diff --git a/icons/obj/smooth_structures/plastitanium_window.png.toml b/icons/obj/smooth_structures/plastitanium_window.png.toml
new file mode 100644
index 000000000000..fe2fcaada1de
--- /dev/null
+++ b/icons/obj/smooth_structures/plastitanium_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "plastitanium_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/plastitaniumglass_table.dmi b/icons/obj/smooth_structures/plastitaniumglass_table.dmi
new file mode 100644
index 000000000000..78b6af93ba2a
Binary files /dev/null and b/icons/obj/smooth_structures/plastitaniumglass_table.dmi differ
diff --git a/icons/obj/smooth_structures/plastitaniumglass_table.png b/icons/obj/smooth_structures/plastitaniumglass_table.png
new file mode 100644
index 000000000000..b9619645bdfa
Binary files /dev/null and b/icons/obj/smooth_structures/plastitaniumglass_table.png differ
diff --git a/icons/obj/smooth_structures/plastitaniumglass_table.png.toml b/icons/obj/smooth_structures/plastitaniumglass_table.png.toml
new file mode 100644
index 000000000000..b1db1da55fc0
--- /dev/null
+++ b/icons/obj/smooth_structures/plastitaniumglass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "plastitaniumglass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/pod_window.dmi b/icons/obj/smooth_structures/pod_window.dmi
index 0fe7501225c7..f179cea280f9 100644
Binary files a/icons/obj/smooth_structures/pod_window.dmi and b/icons/obj/smooth_structures/pod_window.dmi differ
diff --git a/icons/obj/smooth_structures/pod_window.png b/icons/obj/smooth_structures/pod_window.png
new file mode 100644
index 000000000000..3bc31691919b
Binary files /dev/null and b/icons/obj/smooth_structures/pod_window.png differ
diff --git a/icons/obj/smooth_structures/pod_window.png.toml b/icons/obj/smooth_structures/pod_window.png.toml
new file mode 100644
index 000000000000..f23c6e7a3ae2
--- /dev/null
+++ b/icons/obj/smooth_structures/pod_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "pod_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/poker_table.dmi b/icons/obj/smooth_structures/poker_table.dmi
index 2e16a5468dcb..3dbc230ad0b2 100644
Binary files a/icons/obj/smooth_structures/poker_table.dmi and b/icons/obj/smooth_structures/poker_table.dmi differ
diff --git a/icons/obj/smooth_structures/poker_table.png b/icons/obj/smooth_structures/poker_table.png
new file mode 100644
index 000000000000..1be2da651963
Binary files /dev/null and b/icons/obj/smooth_structures/poker_table.png differ
diff --git a/icons/obj/smooth_structures/poker_table.png.toml b/icons/obj/smooth_structures/poker_table.png.toml
new file mode 100644
index 000000000000..b12426f9236e
--- /dev/null
+++ b/icons/obj/smooth_structures/poker_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "poker_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/reinforced_table.dmi b/icons/obj/smooth_structures/reinforced_table.dmi
index 85de93594f94..5053deff017c 100644
Binary files a/icons/obj/smooth_structures/reinforced_table.dmi and b/icons/obj/smooth_structures/reinforced_table.dmi differ
diff --git a/icons/obj/smooth_structures/reinforced_table.png b/icons/obj/smooth_structures/reinforced_table.png
new file mode 100644
index 000000000000..0f9ae450e562
Binary files /dev/null and b/icons/obj/smooth_structures/reinforced_table.png differ
diff --git a/icons/obj/smooth_structures/reinforced_table.png.toml b/icons/obj/smooth_structures/reinforced_table.png.toml
new file mode 100644
index 000000000000..f7143356b4c9
--- /dev/null
+++ b/icons/obj/smooth_structures/reinforced_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "reinforced_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/reinforced_window.dmi b/icons/obj/smooth_structures/reinforced_window.dmi
index a1f60f0677ab..5506e8274167 100644
Binary files a/icons/obj/smooth_structures/reinforced_window.dmi and b/icons/obj/smooth_structures/reinforced_window.dmi differ
diff --git a/icons/obj/smooth_structures/reinforced_window.png b/icons/obj/smooth_structures/reinforced_window.png
new file mode 100644
index 000000000000..d7a4f76654d0
Binary files /dev/null and b/icons/obj/smooth_structures/reinforced_window.png differ
diff --git a/icons/obj/smooth_structures/reinforced_window.png.toml b/icons/obj/smooth_structures/reinforced_window.png.toml
new file mode 100644
index 000000000000..686fd268e3b0
--- /dev/null
+++ b/icons/obj/smooth_structures/reinforced_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "reinforced_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/rglass_table.dmi b/icons/obj/smooth_structures/rglass_table.dmi
new file mode 100644
index 000000000000..fd0bb83c2eee
Binary files /dev/null and b/icons/obj/smooth_structures/rglass_table.dmi differ
diff --git a/icons/obj/smooth_structures/rglass_table.png b/icons/obj/smooth_structures/rglass_table.png
new file mode 100644
index 000000000000..970fc7d77707
Binary files /dev/null and b/icons/obj/smooth_structures/rglass_table.png differ
diff --git a/icons/obj/smooth_structures/rglass_table.png.toml b/icons/obj/smooth_structures/rglass_table.png.toml
new file mode 100644
index 000000000000..c4cca008bbdd
--- /dev/null
+++ b/icons/obj/smooth_structures/rglass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "rglass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/rice_window.dmi b/icons/obj/smooth_structures/rice_window.dmi
index f5e7a6dd57ae..b418cd87cfd8 100644
Binary files a/icons/obj/smooth_structures/rice_window.dmi and b/icons/obj/smooth_structures/rice_window.dmi differ
diff --git a/icons/obj/smooth_structures/rice_window.png b/icons/obj/smooth_structures/rice_window.png
new file mode 100644
index 000000000000..61645b22d964
Binary files /dev/null and b/icons/obj/smooth_structures/rice_window.png differ
diff --git a/icons/obj/smooth_structures/rice_window.png.toml b/icons/obj/smooth_structures/rice_window.png.toml
new file mode 100644
index 000000000000..52f1aae1b2ea
--- /dev/null
+++ b/icons/obj/smooth_structures/rice_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "rice_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/rollingtable.dmi b/icons/obj/smooth_structures/rollingtable.dmi
new file mode 100644
index 000000000000..9e559a75d94c
Binary files /dev/null and b/icons/obj/smooth_structures/rollingtable.dmi differ
diff --git a/icons/obj/smooth_structures/rplasma_window.dmi b/icons/obj/smooth_structures/rplasma_window.dmi
index c64f42c7f5a3..1952a0a8200a 100644
Binary files a/icons/obj/smooth_structures/rplasma_window.dmi and b/icons/obj/smooth_structures/rplasma_window.dmi differ
diff --git a/icons/obj/smooth_structures/rplasma_window.png b/icons/obj/smooth_structures/rplasma_window.png
new file mode 100644
index 000000000000..9ea53c9b540a
Binary files /dev/null and b/icons/obj/smooth_structures/rplasma_window.png differ
diff --git a/icons/obj/smooth_structures/rplasma_window.png.toml b/icons/obj/smooth_structures/rplasma_window.png.toml
new file mode 100644
index 000000000000..791b37792459
--- /dev/null
+++ b/icons/obj/smooth_structures/rplasma_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "rplasma_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/rplasmaglass_table.dmi b/icons/obj/smooth_structures/rplasmaglass_table.dmi
new file mode 100644
index 000000000000..db65243ab7c2
Binary files /dev/null and b/icons/obj/smooth_structures/rplasmaglass_table.dmi differ
diff --git a/icons/obj/smooth_structures/rplasmaglass_table.png b/icons/obj/smooth_structures/rplasmaglass_table.png
new file mode 100644
index 000000000000..4752df63b82f
Binary files /dev/null and b/icons/obj/smooth_structures/rplasmaglass_table.png differ
diff --git a/icons/obj/smooth_structures/rplasmaglass_table.png.toml b/icons/obj/smooth_structures/rplasmaglass_table.png.toml
new file mode 100644
index 000000000000..29f4f3408cbe
--- /dev/null
+++ b/icons/obj/smooth_structures/rplasmaglass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "rplasmaglass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/sandbags.dmi b/icons/obj/smooth_structures/sandbags.dmi
index 8ac099555a81..d5e1d1877da2 100644
Binary files a/icons/obj/smooth_structures/sandbags.dmi and b/icons/obj/smooth_structures/sandbags.dmi differ
diff --git a/icons/obj/smooth_structures/sandbags.png b/icons/obj/smooth_structures/sandbags.png
new file mode 100644
index 000000000000..ed0308c854ff
Binary files /dev/null and b/icons/obj/smooth_structures/sandbags.png differ
diff --git a/icons/obj/smooth_structures/sandbags.png.toml b/icons/obj/smooth_structures/sandbags.png.toml
new file mode 100644
index 000000000000..146b6d42589d
--- /dev/null
+++ b/icons/obj/smooth_structures/sandbags.png.toml
@@ -0,0 +1,2 @@
+output_name = "sandbags"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/shuttle_window.dmi b/icons/obj/smooth_structures/shuttle_window.dmi
index 67acefd53da0..68ca36d37788 100644
Binary files a/icons/obj/smooth_structures/shuttle_window.dmi and b/icons/obj/smooth_structures/shuttle_window.dmi differ
diff --git a/icons/obj/smooth_structures/shuttle_window.png b/icons/obj/smooth_structures/shuttle_window.png
new file mode 100644
index 000000000000..a5312cb8ae7c
Binary files /dev/null and b/icons/obj/smooth_structures/shuttle_window.png differ
diff --git a/icons/obj/smooth_structures/shuttle_window.png.toml b/icons/obj/smooth_structures/shuttle_window.png.toml
new file mode 100644
index 000000000000..1c26ec4d86f9
--- /dev/null
+++ b/icons/obj/smooth_structures/shuttle_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "shuttle_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/structure_variations.dmi b/icons/obj/smooth_structures/structure_variations.dmi
new file mode 100644
index 000000000000..afabd8f4f2ee
Binary files /dev/null and b/icons/obj/smooth_structures/structure_variations.dmi differ
diff --git a/icons/obj/smooth_structures/table.dmi b/icons/obj/smooth_structures/table.dmi
index ffd88a84fc7a..225f0950b95f 100644
Binary files a/icons/obj/smooth_structures/table.dmi and b/icons/obj/smooth_structures/table.dmi differ
diff --git a/icons/obj/smooth_structures/table.png b/icons/obj/smooth_structures/table.png
new file mode 100644
index 000000000000..b5acd10b4b8d
Binary files /dev/null and b/icons/obj/smooth_structures/table.png differ
diff --git a/icons/obj/smooth_structures/table.png.toml b/icons/obj/smooth_structures/table.png.toml
new file mode 100644
index 000000000000..3700febbed56
--- /dev/null
+++ b/icons/obj/smooth_structures/table.png.toml
@@ -0,0 +1,2 @@
+output_name = "table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/table_greyscale.dmi b/icons/obj/smooth_structures/table_greyscale.dmi
new file mode 100644
index 000000000000..0c3513c58d1a
Binary files /dev/null and b/icons/obj/smooth_structures/table_greyscale.dmi differ
diff --git a/icons/obj/smooth_structures/table_greyscale.png b/icons/obj/smooth_structures/table_greyscale.png
new file mode 100644
index 000000000000..535a535d03cd
Binary files /dev/null and b/icons/obj/smooth_structures/table_greyscale.png differ
diff --git a/icons/obj/smooth_structures/table_greyscale.png.toml b/icons/obj/smooth_structures/table_greyscale.png.toml
new file mode 100644
index 000000000000..6a68ba8bc4d7
--- /dev/null
+++ b/icons/obj/smooth_structures/table_greyscale.png.toml
@@ -0,0 +1,2 @@
+output_name = "table_greyscale"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/tinted_window.dmi b/icons/obj/smooth_structures/tinted_window.dmi
index 85e27e09a67b..53f803c39422 100644
Binary files a/icons/obj/smooth_structures/tinted_window.dmi and b/icons/obj/smooth_structures/tinted_window.dmi differ
diff --git a/icons/obj/smooth_structures/tinted_window.png b/icons/obj/smooth_structures/tinted_window.png
new file mode 100644
index 000000000000..6601e8c231f7
Binary files /dev/null and b/icons/obj/smooth_structures/tinted_window.png differ
diff --git a/icons/obj/smooth_structures/tinted_window.png.toml b/icons/obj/smooth_structures/tinted_window.png.toml
new file mode 100644
index 000000000000..9d8250aa81d0
--- /dev/null
+++ b/icons/obj/smooth_structures/tinted_window.png.toml
@@ -0,0 +1,2 @@
+output_name = "tinted_window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/titaniumglass_table.dmi b/icons/obj/smooth_structures/titaniumglass_table.dmi
new file mode 100644
index 000000000000..43bf24b5afec
Binary files /dev/null and b/icons/obj/smooth_structures/titaniumglass_table.dmi differ
diff --git a/icons/obj/smooth_structures/titaniumglass_table.png b/icons/obj/smooth_structures/titaniumglass_table.png
new file mode 100644
index 000000000000..8da5ba0849da
Binary files /dev/null and b/icons/obj/smooth_structures/titaniumglass_table.png differ
diff --git a/icons/obj/smooth_structures/titaniumglass_table.png.toml b/icons/obj/smooth_structures/titaniumglass_table.png.toml
new file mode 100644
index 000000000000..d77b867616c6
--- /dev/null
+++ b/icons/obj/smooth_structures/titaniumglass_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "titaniumglass_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/window.dmi b/icons/obj/smooth_structures/window.dmi
index b680e44fa901..6dab5136d41f 100644
Binary files a/icons/obj/smooth_structures/window.dmi and b/icons/obj/smooth_structures/window.dmi differ
diff --git a/icons/obj/smooth_structures/window.png b/icons/obj/smooth_structures/window.png
new file mode 100644
index 000000000000..e06498a9e3a8
Binary files /dev/null and b/icons/obj/smooth_structures/window.png differ
diff --git a/icons/obj/smooth_structures/window.png.toml b/icons/obj/smooth_structures/window.png.toml
new file mode 100644
index 000000000000..7ae19716bb6a
--- /dev/null
+++ b/icons/obj/smooth_structures/window.png.toml
@@ -0,0 +1,2 @@
+output_name = "window"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/wood_table.dmi b/icons/obj/smooth_structures/wood_table.dmi
index 16fb364ad27f..ddc65b0c739d 100644
Binary files a/icons/obj/smooth_structures/wood_table.dmi and b/icons/obj/smooth_structures/wood_table.dmi differ
diff --git a/icons/obj/smooth_structures/wood_table.png b/icons/obj/smooth_structures/wood_table.png
new file mode 100644
index 000000000000..72dc68ce7c35
Binary files /dev/null and b/icons/obj/smooth_structures/wood_table.png differ
diff --git a/icons/obj/smooth_structures/wood_table.png.toml b/icons/obj/smooth_structures/wood_table.png.toml
new file mode 100644
index 000000000000..1c197d3d9f8e
--- /dev/null
+++ b/icons/obj/smooth_structures/wood_table.png.toml
@@ -0,0 +1,2 @@
+output_name = "wood_table"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/stairs.dmi b/icons/obj/stairs.dmi
index 615e88f68455..5c3f378ced99 100644
Binary files a/icons/obj/stairs.dmi and b/icons/obj/stairs.dmi differ
diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi
index 7bf2ddaed7fc..505e934fb411 100644
Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index cade1fe23ca6..bd97442a1b4f 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index f7718df0c9d7..1fd613edc17e 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index b058ddff1a67..a728c79817a9 100755
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/icons/obj/tiles.dmi b/icons/obj/tiles.dmi
index ffbc64e76ef4..12c051c0d837 100644
Binary files a/icons/obj/tiles.dmi and b/icons/obj/tiles.dmi differ
diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi
index d078c6654144..e772caff0077 100644
Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ
diff --git a/icons/obj/traitor.dmi b/icons/obj/traitor.dmi
index 92851f3708cd..4d7a4931bcaf 100644
Binary files a/icons/obj/traitor.dmi and b/icons/obj/traitor.dmi differ
diff --git a/icons/obj/vending.dmi b/icons/obj/vending.dmi
index e915bb259c74..a5db6caa6e2b 100644
Binary files a/icons/obj/vending.dmi and b/icons/obj/vending.dmi differ
diff --git a/icons/obj/wallmounts.dmi b/icons/obj/wallmounts.dmi
index fcbc0830b76b..43601b49844c 100644
Binary files a/icons/obj/wallmounts.dmi and b/icons/obj/wallmounts.dmi differ
diff --git a/icons/turf/damaged.dmi b/icons/turf/damaged.dmi
new file mode 100644
index 000000000000..a81384d8be6a
Binary files /dev/null and b/icons/turf/damaged.dmi differ
diff --git a/icons/turf/debug.dmi b/icons/turf/debug.dmi
index db034a79042e..0391881e1a2e 100644
Binary files a/icons/turf/debug.dmi and b/icons/turf/debug.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index b8d77dceaff9..c3a5a75967ad 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/icons/turf/floors/ash.dmi b/icons/turf/floors/ash.dmi
index 718cd120194e..e6d3ed666fb6 100644
Binary files a/icons/turf/floors/ash.dmi and b/icons/turf/floors/ash.dmi differ
diff --git a/icons/turf/floors/ash.png b/icons/turf/floors/ash.png
new file mode 100644
index 000000000000..419dd49d9def
Binary files /dev/null and b/icons/turf/floors/ash.png differ
diff --git a/icons/turf/floors/ash.png.toml b/icons/turf/floors/ash.png.toml
new file mode 100644
index 000000000000..9bbdc460b4d4
--- /dev/null
+++ b/icons/turf/floors/ash.png.toml
@@ -0,0 +1,14 @@
+output_name = "ash"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/floors/bamboo_mat.dmi b/icons/turf/floors/bamboo_mat.dmi
index e18237b3f63f..c2380c5b874f 100644
Binary files a/icons/turf/floors/bamboo_mat.dmi and b/icons/turf/floors/bamboo_mat.dmi differ
diff --git a/icons/turf/floors/bamboo_mat.png b/icons/turf/floors/bamboo_mat.png
new file mode 100644
index 000000000000..f945572a8b7b
Binary files /dev/null and b/icons/turf/floors/bamboo_mat.png differ
diff --git a/icons/turf/floors/bamboo_mat.png.toml b/icons/turf/floors/bamboo_mat.png.toml
new file mode 100644
index 000000000000..ce1ce14e2598
--- /dev/null
+++ b/icons/turf/floors/bamboo_mat.png.toml
@@ -0,0 +1,2 @@
+output_name = "mat"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet.dmi b/icons/turf/floors/carpet.dmi
index 52208611a476..24d8c8b0dfd6 100644
Binary files a/icons/turf/floors/carpet.dmi and b/icons/turf/floors/carpet.dmi differ
diff --git a/icons/turf/floors/carpet.png b/icons/turf/floors/carpet.png
new file mode 100644
index 000000000000..b69bf87a4d05
Binary files /dev/null and b/icons/turf/floors/carpet.png differ
diff --git a/icons/turf/floors/carpet.png.toml b/icons/turf/floors/carpet.png.toml
new file mode 100644
index 000000000000..13916473257f
--- /dev/null
+++ b/icons/turf/floors/carpet.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_black.dmi b/icons/turf/floors/carpet_black.dmi
index d8cbc1c45342..7df942035adc 100644
Binary files a/icons/turf/floors/carpet_black.dmi and b/icons/turf/floors/carpet_black.dmi differ
diff --git a/icons/turf/floors/carpet_black.png b/icons/turf/floors/carpet_black.png
new file mode 100644
index 000000000000..57f7eaf8dd9a
Binary files /dev/null and b/icons/turf/floors/carpet_black.png differ
diff --git a/icons/turf/floors/carpet_black.png.toml b/icons/turf/floors/carpet_black.png.toml
new file mode 100644
index 000000000000..a98d0e0bc605
--- /dev/null
+++ b/icons/turf/floors/carpet_black.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_black"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_blue.dmi b/icons/turf/floors/carpet_blue.dmi
new file mode 100644
index 000000000000..6eea31902c8d
Binary files /dev/null and b/icons/turf/floors/carpet_blue.dmi differ
diff --git a/icons/turf/floors/carpet_blue.png b/icons/turf/floors/carpet_blue.png
new file mode 100644
index 000000000000..e81e56cf7c28
Binary files /dev/null and b/icons/turf/floors/carpet_blue.png differ
diff --git a/icons/turf/floors/carpet_blue.png.toml b/icons/turf/floors/carpet_blue.png.toml
new file mode 100644
index 000000000000..1e1ebdb22015
--- /dev/null
+++ b/icons/turf/floors/carpet_blue.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_blue"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_cyan.dmi b/icons/turf/floors/carpet_cyan.dmi
index feca351ca925..fa38993c5cee 100644
Binary files a/icons/turf/floors/carpet_cyan.dmi and b/icons/turf/floors/carpet_cyan.dmi differ
diff --git a/icons/turf/floors/carpet_cyan.png b/icons/turf/floors/carpet_cyan.png
new file mode 100644
index 000000000000..f1dc7da076aa
Binary files /dev/null and b/icons/turf/floors/carpet_cyan.png differ
diff --git a/icons/turf/floors/carpet_cyan.png.toml b/icons/turf/floors/carpet_cyan.png.toml
new file mode 100644
index 000000000000..8c93ad0baf32
--- /dev/null
+++ b/icons/turf/floors/carpet_cyan.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_cyan"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_donk.dmi b/icons/turf/floors/carpet_donk.dmi
new file mode 100644
index 000000000000..1a8f59470243
Binary files /dev/null and b/icons/turf/floors/carpet_donk.dmi differ
diff --git a/icons/turf/floors/carpet_donk.png b/icons/turf/floors/carpet_donk.png
new file mode 100644
index 000000000000..eb0243c4926e
Binary files /dev/null and b/icons/turf/floors/carpet_donk.png differ
diff --git a/icons/turf/floors/carpet_donk.png.toml b/icons/turf/floors/carpet_donk.png.toml
new file mode 100644
index 000000000000..7e60d4272b8d
--- /dev/null
+++ b/icons/turf/floors/carpet_donk.png.toml
@@ -0,0 +1,5 @@
+output_name = "donk_carpet"
+template = "bitmask/diagonal_32x32.toml"
+
+[prefabs]
+255 = 5
diff --git a/icons/turf/floors/carpet_executive.dmi b/icons/turf/floors/carpet_executive.dmi
new file mode 100644
index 000000000000..8c0528d8f28b
Binary files /dev/null and b/icons/turf/floors/carpet_executive.dmi differ
diff --git a/icons/turf/floors/carpet_executive.png b/icons/turf/floors/carpet_executive.png
new file mode 100644
index 000000000000..bf8a538b8f82
Binary files /dev/null and b/icons/turf/floors/carpet_executive.png differ
diff --git a/icons/turf/floors/carpet_executive.png.toml b/icons/turf/floors/carpet_executive.png.toml
new file mode 100644
index 000000000000..1322b9bd6d90
--- /dev/null
+++ b/icons/turf/floors/carpet_executive.png.toml
@@ -0,0 +1,6 @@
+output_name = "executive_carpet"
+template = "bitmask/diagonal_32x32.toml"
+
+[prefabs]
+255 = 5
+
diff --git a/icons/turf/floors/carpet_exoticblue.dmi b/icons/turf/floors/carpet_exoticblue.dmi
deleted file mode 100644
index f797be9745a6..000000000000
Binary files a/icons/turf/floors/carpet_exoticblue.dmi and /dev/null differ
diff --git a/icons/turf/floors/carpet_exoticgreen.dmi b/icons/turf/floors/carpet_exoticgreen.dmi
deleted file mode 100644
index fdd1f071f70f..000000000000
Binary files a/icons/turf/floors/carpet_exoticgreen.dmi and /dev/null differ
diff --git a/icons/turf/floors/carpet_exoticpurple.dmi b/icons/turf/floors/carpet_exoticpurple.dmi
deleted file mode 100644
index c1f40ec7fa82..000000000000
Binary files a/icons/turf/floors/carpet_exoticpurple.dmi and /dev/null differ
diff --git a/icons/turf/floors/carpet_green.dmi b/icons/turf/floors/carpet_green.dmi
new file mode 100644
index 000000000000..49daef00acd0
Binary files /dev/null and b/icons/turf/floors/carpet_green.dmi differ
diff --git a/icons/turf/floors/carpet_green.png b/icons/turf/floors/carpet_green.png
new file mode 100644
index 000000000000..ee059d755afb
Binary files /dev/null and b/icons/turf/floors/carpet_green.png differ
diff --git a/icons/turf/floors/carpet_green.png.toml b/icons/turf/floors/carpet_green.png.toml
new file mode 100644
index 000000000000..88a2a120f282
--- /dev/null
+++ b/icons/turf/floors/carpet_green.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_green"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_base.dmi b/icons/turf/floors/carpet_neon_base.dmi
new file mode 100644
index 000000000000..342da1530769
Binary files /dev/null and b/icons/turf/floors/carpet_neon_base.dmi differ
diff --git a/icons/turf/floors/carpet_neon_base.png b/icons/turf/floors/carpet_neon_base.png
new file mode 100644
index 000000000000..08b3ef22e15f
Binary files /dev/null and b/icons/turf/floors/carpet_neon_base.png differ
diff --git a/icons/turf/floors/carpet_neon_base.png.toml b/icons/turf/floors/carpet_neon_base.png.toml
new file mode 100644
index 000000000000..723e73af9e9a
--- /dev/null
+++ b/icons/turf/floors/carpet_neon_base.png.toml
@@ -0,0 +1,2 @@
+output_name = "base"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_base_nodots.dmi b/icons/turf/floors/carpet_neon_base_nodots.dmi
new file mode 100644
index 000000000000..d5fa68a6a123
Binary files /dev/null and b/icons/turf/floors/carpet_neon_base_nodots.dmi differ
diff --git a/icons/turf/floors/carpet_neon_base_nodots.png b/icons/turf/floors/carpet_neon_base_nodots.png
new file mode 100644
index 000000000000..ae73004e95bb
Binary files /dev/null and b/icons/turf/floors/carpet_neon_base_nodots.png differ
diff --git a/icons/turf/floors/carpet_neon_base_nodots.png.toml b/icons/turf/floors/carpet_neon_base_nodots.png.toml
new file mode 100644
index 000000000000..03b019890ed3
--- /dev/null
+++ b/icons/turf/floors/carpet_neon_base_nodots.png.toml
@@ -0,0 +1,2 @@
+output_name = "base-nodots"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_glow.dmi b/icons/turf/floors/carpet_neon_glow.dmi
new file mode 100644
index 000000000000..c1ab20145aca
Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow.dmi differ
diff --git a/icons/turf/floors/carpet_neon_glow.png b/icons/turf/floors/carpet_neon_glow.png
new file mode 100644
index 000000000000..1087698ae60a
Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow.png differ
diff --git a/icons/turf/floors/carpet_neon_glow.png.toml b/icons/turf/floors/carpet_neon_glow.png.toml
new file mode 100644
index 000000000000..2af0ddb34bf6
--- /dev/null
+++ b/icons/turf/floors/carpet_neon_glow.png.toml
@@ -0,0 +1,2 @@
+output_name = "glow"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_glow_nodots.dmi b/icons/turf/floors/carpet_neon_glow_nodots.dmi
new file mode 100644
index 000000000000..72175d8b3537
Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow_nodots.dmi differ
diff --git a/icons/turf/floors/carpet_neon_glow_nodots.png b/icons/turf/floors/carpet_neon_glow_nodots.png
new file mode 100644
index 000000000000..19b9d311b13c
Binary files /dev/null and b/icons/turf/floors/carpet_neon_glow_nodots.png differ
diff --git a/icons/turf/floors/carpet_neon_glow_nodots.png.toml b/icons/turf/floors/carpet_neon_glow_nodots.png.toml
new file mode 100644
index 000000000000..8d6e69b01c07
--- /dev/null
+++ b/icons/turf/floors/carpet_neon_glow_nodots.png.toml
@@ -0,0 +1,2 @@
+output_name = "glow-nodots"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_light.dmi b/icons/turf/floors/carpet_neon_light.dmi
new file mode 100644
index 000000000000..38fe6ff2db54
Binary files /dev/null and b/icons/turf/floors/carpet_neon_light.dmi differ
diff --git a/icons/turf/floors/carpet_neon_light.png b/icons/turf/floors/carpet_neon_light.png
new file mode 100644
index 000000000000..9443c6942903
Binary files /dev/null and b/icons/turf/floors/carpet_neon_light.png differ
diff --git a/icons/turf/floors/carpet_neon_light.png.toml b/icons/turf/floors/carpet_neon_light.png.toml
new file mode 100644
index 000000000000..6ea86ad1e650
--- /dev/null
+++ b/icons/turf/floors/carpet_neon_light.png.toml
@@ -0,0 +1,2 @@
+output_name = "light"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_light_nodots.dmi b/icons/turf/floors/carpet_neon_light_nodots.dmi
new file mode 100644
index 000000000000..0cf86fecb67a
Binary files /dev/null and b/icons/turf/floors/carpet_neon_light_nodots.dmi differ
diff --git a/icons/turf/floors/carpet_neon_light_nodots.png b/icons/turf/floors/carpet_neon_light_nodots.png
new file mode 100644
index 000000000000..fc82b593c077
Binary files /dev/null and b/icons/turf/floors/carpet_neon_light_nodots.png differ
diff --git a/icons/turf/floors/carpet_neon_light_nodots.png.toml b/icons/turf/floors/carpet_neon_light_nodots.png.toml
new file mode 100644
index 000000000000..20e0713c39f4
--- /dev/null
+++ b/icons/turf/floors/carpet_neon_light_nodots.png.toml
@@ -0,0 +1,2 @@
+output_name = "light-nodots"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_neon_simple.dmi b/icons/turf/floors/carpet_neon_simple.dmi
new file mode 100644
index 000000000000..62cb355a2db9
Binary files /dev/null and b/icons/turf/floors/carpet_neon_simple.dmi differ
diff --git a/icons/turf/floors/carpet_orange.dmi b/icons/turf/floors/carpet_orange.dmi
index ddf239b63b20..0ee9b56e8b09 100644
Binary files a/icons/turf/floors/carpet_orange.dmi and b/icons/turf/floors/carpet_orange.dmi differ
diff --git a/icons/turf/floors/carpet_orange.png b/icons/turf/floors/carpet_orange.png
new file mode 100644
index 000000000000..c58cddcf0bc1
Binary files /dev/null and b/icons/turf/floors/carpet_orange.png differ
diff --git a/icons/turf/floors/carpet_orange.png.toml b/icons/turf/floors/carpet_orange.png.toml
new file mode 100644
index 000000000000..4315d81da1de
--- /dev/null
+++ b/icons/turf/floors/carpet_orange.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_orange"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_purple.dmi b/icons/turf/floors/carpet_purple.dmi
new file mode 100644
index 000000000000..a00c1621d388
Binary files /dev/null and b/icons/turf/floors/carpet_purple.dmi differ
diff --git a/icons/turf/floors/carpet_purple.png b/icons/turf/floors/carpet_purple.png
new file mode 100644
index 000000000000..530534c5bca3
Binary files /dev/null and b/icons/turf/floors/carpet_purple.png differ
diff --git a/icons/turf/floors/carpet_purple.png.toml b/icons/turf/floors/carpet_purple.png.toml
new file mode 100644
index 000000000000..708027cee780
--- /dev/null
+++ b/icons/turf/floors/carpet_purple.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_purple"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_red.dmi b/icons/turf/floors/carpet_red.dmi
index 926655688ed2..dcba136f8115 100644
Binary files a/icons/turf/floors/carpet_red.dmi and b/icons/turf/floors/carpet_red.dmi differ
diff --git a/icons/turf/floors/carpet_red.png b/icons/turf/floors/carpet_red.png
new file mode 100644
index 000000000000..3f7f190a126c
Binary files /dev/null and b/icons/turf/floors/carpet_red.png differ
diff --git a/icons/turf/floors/carpet_red.png.toml b/icons/turf/floors/carpet_red.png.toml
new file mode 100644
index 000000000000..4bbd11bfb347
--- /dev/null
+++ b/icons/turf/floors/carpet_red.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_red"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_royalblack.dmi b/icons/turf/floors/carpet_royalblack.dmi
index bc5cef1cf0eb..296b7b683fd0 100644
Binary files a/icons/turf/floors/carpet_royalblack.dmi and b/icons/turf/floors/carpet_royalblack.dmi differ
diff --git a/icons/turf/floors/carpet_royalblack.png b/icons/turf/floors/carpet_royalblack.png
new file mode 100644
index 000000000000..b125c6111cd4
Binary files /dev/null and b/icons/turf/floors/carpet_royalblack.png differ
diff --git a/icons/turf/floors/carpet_royalblack.png.toml b/icons/turf/floors/carpet_royalblack.png.toml
new file mode 100644
index 000000000000..eef2685abeb2
--- /dev/null
+++ b/icons/turf/floors/carpet_royalblack.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_royalblack"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_royalblue.dmi b/icons/turf/floors/carpet_royalblue.dmi
index 841e49e957b6..9657d9c6704b 100644
Binary files a/icons/turf/floors/carpet_royalblue.dmi and b/icons/turf/floors/carpet_royalblue.dmi differ
diff --git a/icons/turf/floors/carpet_royalblue.png b/icons/turf/floors/carpet_royalblue.png
new file mode 100644
index 000000000000..babf33777b79
Binary files /dev/null and b/icons/turf/floors/carpet_royalblue.png differ
diff --git a/icons/turf/floors/carpet_royalblue.png.toml b/icons/turf/floors/carpet_royalblue.png.toml
new file mode 100644
index 000000000000..9a11f2507b21
--- /dev/null
+++ b/icons/turf/floors/carpet_royalblue.png.toml
@@ -0,0 +1,2 @@
+output_name = "carpet_royalblue"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/carpet_stellar.dmi b/icons/turf/floors/carpet_stellar.dmi
new file mode 100644
index 000000000000..2e0eec615d84
Binary files /dev/null and b/icons/turf/floors/carpet_stellar.dmi differ
diff --git a/icons/turf/floors/carpet_stellar.png b/icons/turf/floors/carpet_stellar.png
new file mode 100644
index 000000000000..ab233965a6dc
Binary files /dev/null and b/icons/turf/floors/carpet_stellar.png differ
diff --git a/icons/turf/floors/carpet_stellar.png.toml b/icons/turf/floors/carpet_stellar.png.toml
new file mode 100644
index 000000000000..817768bad5f2
--- /dev/null
+++ b/icons/turf/floors/carpet_stellar.png.toml
@@ -0,0 +1,2 @@
+output_name = "stellar_carpet"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/catwalk_plating.dmi b/icons/turf/floors/catwalk_plating.dmi
new file mode 100644
index 000000000000..b49c46564de5
Binary files /dev/null and b/icons/turf/floors/catwalk_plating.dmi differ
diff --git a/icons/turf/floors/chasms.dmi b/icons/turf/floors/chasms.dmi
index cc4feb2e032c..d37fa7d50c64 100644
Binary files a/icons/turf/floors/chasms.dmi and b/icons/turf/floors/chasms.dmi differ
diff --git a/icons/turf/floors/chasms.png b/icons/turf/floors/chasms.png
new file mode 100644
index 000000000000..2a856eb8aa42
Binary files /dev/null and b/icons/turf/floors/chasms.png differ
diff --git a/icons/turf/floors/chasms.png.toml b/icons/turf/floors/chasms.png.toml
new file mode 100644
index 000000000000..51e70389edde
--- /dev/null
+++ b/icons/turf/floors/chasms.png.toml
@@ -0,0 +1,2 @@
+output_name = "chasms"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/floor_variations.dmi b/icons/turf/floors/floor_variations.dmi
new file mode 100644
index 000000000000..81cff6d76e86
Binary files /dev/null and b/icons/turf/floors/floor_variations.dmi differ
diff --git a/icons/turf/floors/glass.dmi b/icons/turf/floors/glass.dmi
new file mode 100644
index 000000000000..ae840919f6e5
Binary files /dev/null and b/icons/turf/floors/glass.dmi differ
diff --git a/icons/turf/floors/glass.png b/icons/turf/floors/glass.png
new file mode 100644
index 000000000000..b73fff065747
Binary files /dev/null and b/icons/turf/floors/glass.png differ
diff --git a/icons/turf/floors/glass.png.toml b/icons/turf/floors/glass.png.toml
new file mode 100644
index 000000000000..2e90c6298cac
--- /dev/null
+++ b/icons/turf/floors/glass.png.toml
@@ -0,0 +1,2 @@
+output_name = "glass"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/grass.dmi b/icons/turf/floors/grass.dmi
new file mode 100644
index 000000000000..0b28bc9ccee5
Binary files /dev/null and b/icons/turf/floors/grass.dmi differ
diff --git a/icons/turf/floors/grass.png b/icons/turf/floors/grass.png
new file mode 100644
index 000000000000..82790712e30b
Binary files /dev/null and b/icons/turf/floors/grass.png differ
diff --git a/icons/turf/floors/grass.png.toml b/icons/turf/floors/grass.png.toml
new file mode 100644
index 000000000000..e06f8518c7c8
--- /dev/null
+++ b/icons/turf/floors/grass.png.toml
@@ -0,0 +1,14 @@
+output_name = "grass"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 50
+y = 50
+
+[output_icon_size]
+x = 50
+y = 50
+
+[cut_pos]
+x = 25
+y = 25
\ No newline at end of file
diff --git a/icons/turf/floors/ice_turf.dmi b/icons/turf/floors/ice_turf.dmi
index 8751a3721626..ccb528c872d6 100644
Binary files a/icons/turf/floors/ice_turf.dmi and b/icons/turf/floors/ice_turf.dmi differ
diff --git a/icons/turf/floors/ice_turf.png b/icons/turf/floors/ice_turf.png
new file mode 100644
index 000000000000..26887f278537
Binary files /dev/null and b/icons/turf/floors/ice_turf.png differ
diff --git a/icons/turf/floors/ice_turf.png.toml b/icons/turf/floors/ice_turf.png.toml
new file mode 100644
index 000000000000..3aa5cdf4a36a
--- /dev/null
+++ b/icons/turf/floors/ice_turf.png.toml
@@ -0,0 +1,2 @@
+output_name = "ice_turf"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/icechasms.dmi b/icons/turf/floors/icechasms.dmi
index 97dfc0a2da6b..e01521e34416 100644
Binary files a/icons/turf/floors/icechasms.dmi and b/icons/turf/floors/icechasms.dmi differ
diff --git a/icons/turf/floors/icechasms.png b/icons/turf/floors/icechasms.png
new file mode 100644
index 000000000000..c47756629b52
Binary files /dev/null and b/icons/turf/floors/icechasms.png differ
diff --git a/icons/turf/floors/icechasms.png.toml b/icons/turf/floors/icechasms.png.toml
new file mode 100644
index 000000000000..6dde47bfeb25
--- /dev/null
+++ b/icons/turf/floors/icechasms.png.toml
@@ -0,0 +1,2 @@
+output_name = "icechasms"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/junglechasm.dmi b/icons/turf/floors/junglechasm.dmi
index 699a3b667cd9..c50775c91b1c 100644
Binary files a/icons/turf/floors/junglechasm.dmi and b/icons/turf/floors/junglechasm.dmi differ
diff --git a/icons/turf/floors/junglechasm.png b/icons/turf/floors/junglechasm.png
new file mode 100644
index 000000000000..dc26f65e16c2
Binary files /dev/null and b/icons/turf/floors/junglechasm.png differ
diff --git a/icons/turf/floors/junglechasm.png.toml b/icons/turf/floors/junglechasm.png.toml
new file mode 100644
index 000000000000..e210a9128bd1
--- /dev/null
+++ b/icons/turf/floors/junglechasm.png.toml
@@ -0,0 +1,2 @@
+output_name = "junglechasm"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/junglegrass.dmi b/icons/turf/floors/junglegrass.dmi
new file mode 100644
index 000000000000..ef192232c3bc
Binary files /dev/null and b/icons/turf/floors/junglegrass.dmi differ
diff --git a/icons/turf/floors/junglegrass.png b/icons/turf/floors/junglegrass.png
new file mode 100644
index 000000000000..c1d13d4f4f27
Binary files /dev/null and b/icons/turf/floors/junglegrass.png differ
diff --git a/icons/turf/floors/junglegrass.png.toml b/icons/turf/floors/junglegrass.png.toml
new file mode 100644
index 000000000000..0096e9bad747
--- /dev/null
+++ b/icons/turf/floors/junglegrass.png.toml
@@ -0,0 +1,14 @@
+output_name = "junglegrass"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 50
+y = 50
+
+[output_icon_size]
+x = 50
+y = 50
+
+[cut_pos]
+x = 25
+y = 25
\ No newline at end of file
diff --git a/icons/turf/floors/lava.dmi b/icons/turf/floors/lava.dmi
index e4a276c001d9..a2a06f34200a 100644
Binary files a/icons/turf/floors/lava.dmi and b/icons/turf/floors/lava.dmi differ
diff --git a/icons/turf/floors/lava.png b/icons/turf/floors/lava.png
new file mode 100644
index 000000000000..ef826dee188b
Binary files /dev/null and b/icons/turf/floors/lava.png differ
diff --git a/icons/turf/floors/lava.png.toml b/icons/turf/floors/lava.png.toml
new file mode 100644
index 000000000000..c4e36e85ab19
--- /dev/null
+++ b/icons/turf/floors/lava.png.toml
@@ -0,0 +1,5 @@
+output_name = "lava"
+template = "bitmask/diagonal_32x32.toml"
+
+[animation]
+delays = [20, 20, 20, 20]
diff --git a/icons/turf/floors/lava_mask.dmi b/icons/turf/floors/lava_mask.dmi
new file mode 100644
index 000000000000..3e77c406b268
Binary files /dev/null and b/icons/turf/floors/lava_mask.dmi differ
diff --git a/icons/turf/floors/lava_mask.png b/icons/turf/floors/lava_mask.png
new file mode 100644
index 000000000000..dfcd0dba4cb9
Binary files /dev/null and b/icons/turf/floors/lava_mask.png differ
diff --git a/icons/turf/floors/lava_mask.png.toml b/icons/turf/floors/lava_mask.png.toml
new file mode 100644
index 000000000000..08d0173d5cde
--- /dev/null
+++ b/icons/turf/floors/lava_mask.png.toml
@@ -0,0 +1,2 @@
+output_name = "lava"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/plasma_glass.dmi b/icons/turf/floors/plasma_glass.dmi
new file mode 100644
index 000000000000..a0e2dd20b796
Binary files /dev/null and b/icons/turf/floors/plasma_glass.dmi differ
diff --git a/icons/turf/floors/plasma_glass.png b/icons/turf/floors/plasma_glass.png
new file mode 100644
index 000000000000..06e8a79b5675
Binary files /dev/null and b/icons/turf/floors/plasma_glass.png differ
diff --git a/icons/turf/floors/plasma_glass.png.toml b/icons/turf/floors/plasma_glass.png.toml
new file mode 100644
index 000000000000..0fb4b5fa2761
--- /dev/null
+++ b/icons/turf/floors/plasma_glass.png.toml
@@ -0,0 +1,2 @@
+output_name = "plasma_glass"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/reinf_glass.dmi b/icons/turf/floors/reinf_glass.dmi
new file mode 100644
index 000000000000..0713375caa6e
Binary files /dev/null and b/icons/turf/floors/reinf_glass.dmi differ
diff --git a/icons/turf/floors/reinf_glass.png b/icons/turf/floors/reinf_glass.png
new file mode 100644
index 000000000000..9659f3e35c10
Binary files /dev/null and b/icons/turf/floors/reinf_glass.png differ
diff --git a/icons/turf/floors/reinf_glass.png.toml b/icons/turf/floors/reinf_glass.png.toml
new file mode 100644
index 000000000000..043b1d354359
--- /dev/null
+++ b/icons/turf/floors/reinf_glass.png.toml
@@ -0,0 +1,2 @@
+output_name = "reinf_glass"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/reinf_plasma_glass.dmi b/icons/turf/floors/reinf_plasma_glass.dmi
new file mode 100644
index 000000000000..31c1c339c37d
Binary files /dev/null and b/icons/turf/floors/reinf_plasma_glass.dmi differ
diff --git a/icons/turf/floors/reinf_plasma_glass.png b/icons/turf/floors/reinf_plasma_glass.png
new file mode 100644
index 000000000000..6c47f299723e
Binary files /dev/null and b/icons/turf/floors/reinf_plasma_glass.png differ
diff --git a/icons/turf/floors/reinf_plasma_glass.png.toml b/icons/turf/floors/reinf_plasma_glass.png.toml
new file mode 100644
index 000000000000..1b3fbb05ef9b
--- /dev/null
+++ b/icons/turf/floors/reinf_plasma_glass.png.toml
@@ -0,0 +1,2 @@
+output_name = "reinf_plasma_glass"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/floors/rocky_ash.dmi b/icons/turf/floors/rocky_ash.dmi
index 2a57edfc53a4..758793cba421 100644
Binary files a/icons/turf/floors/rocky_ash.dmi and b/icons/turf/floors/rocky_ash.dmi differ
diff --git a/icons/turf/floors/rocky_ash.png b/icons/turf/floors/rocky_ash.png
new file mode 100644
index 000000000000..bdc8ee3615c4
Binary files /dev/null and b/icons/turf/floors/rocky_ash.png differ
diff --git a/icons/turf/floors/rocky_ash.png.toml b/icons/turf/floors/rocky_ash.png.toml
new file mode 100644
index 000000000000..6e3f50e05427
--- /dev/null
+++ b/icons/turf/floors/rocky_ash.png.toml
@@ -0,0 +1,14 @@
+output_name = "rocky_ash"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/floors/snow_turf.dmi b/icons/turf/floors/snow_turf.dmi
index e7fef38602b2..a150dfdc2db8 100644
Binary files a/icons/turf/floors/snow_turf.dmi and b/icons/turf/floors/snow_turf.dmi differ
diff --git a/icons/turf/floors/snow_turf.png b/icons/turf/floors/snow_turf.png
new file mode 100644
index 000000000000..474a0f486e79
Binary files /dev/null and b/icons/turf/floors/snow_turf.png differ
diff --git a/icons/turf/floors/snow_turf.png.toml b/icons/turf/floors/snow_turf.png.toml
new file mode 100644
index 000000000000..cc50c9afda68
--- /dev/null
+++ b/icons/turf/floors/snow_turf.png.toml
@@ -0,0 +1,2 @@
+output_name = "snow_turf"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/mining_yog.dmi b/icons/turf/mining_yog.dmi
new file mode 100644
index 000000000000..d0ce7f7a44f8
Binary files /dev/null and b/icons/turf/mining_yog.dmi differ
diff --git a/icons/turf/smoothrocks.dmi b/icons/turf/smoothrocks.dmi
index ba2bbce95518..e138e2af783d 100644
Binary files a/icons/turf/smoothrocks.dmi and b/icons/turf/smoothrocks.dmi differ
diff --git a/icons/turf/smoothrocks.png b/icons/turf/smoothrocks.png
new file mode 100644
index 000000000000..dea84d594f66
Binary files /dev/null and b/icons/turf/smoothrocks.png differ
diff --git a/icons/turf/smoothrocks.png.toml b/icons/turf/smoothrocks.png.toml
new file mode 100644
index 000000000000..4466fbbf3c92
--- /dev/null
+++ b/icons/turf/smoothrocks.png.toml
@@ -0,0 +1,14 @@
+output_name = "smoothrocks"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/smoothrocks_hard.dmi b/icons/turf/smoothrocks_hard.dmi
index d0ce7f7a44f8..4c017834d439 100644
Binary files a/icons/turf/smoothrocks_hard.dmi and b/icons/turf/smoothrocks_hard.dmi differ
diff --git a/icons/turf/smoothrocks_hard.png b/icons/turf/smoothrocks_hard.png
new file mode 100644
index 000000000000..f455fa7cb453
Binary files /dev/null and b/icons/turf/smoothrocks_hard.png differ
diff --git a/icons/turf/smoothrocks_hard.png.toml b/icons/turf/smoothrocks_hard.png.toml
new file mode 100644
index 000000000000..30f8958c08c1
--- /dev/null
+++ b/icons/turf/smoothrocks_hard.png.toml
@@ -0,0 +1,14 @@
+output_name = "smoothhardrocks"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
diff --git a/icons/turf/smoothrocks_overlays.dmi b/icons/turf/smoothrocks_overlays.dmi
new file mode 100644
index 000000000000..e49a48ca09e7
Binary files /dev/null and b/icons/turf/smoothrocks_overlays.dmi differ
diff --git a/icons/turf/walls/abductor_wall.dmi b/icons/turf/walls/abductor_wall.dmi
index d1d8954bfb96..4f731d99f4cd 100644
Binary files a/icons/turf/walls/abductor_wall.dmi and b/icons/turf/walls/abductor_wall.dmi differ
diff --git a/icons/turf/walls/bamboo_wall.dmi b/icons/turf/walls/bamboo_wall.dmi
index 3111c32b7ccc..44c5824397e5 100644
Binary files a/icons/turf/walls/bamboo_wall.dmi and b/icons/turf/walls/bamboo_wall.dmi differ
diff --git a/icons/turf/walls/bamboo_wall.png b/icons/turf/walls/bamboo_wall.png
new file mode 100644
index 000000000000..6869cb409b2a
Binary files /dev/null and b/icons/turf/walls/bamboo_wall.png differ
diff --git a/icons/turf/walls/bamboo_wall.png.toml b/icons/turf/walls/bamboo_wall.png.toml
new file mode 100644
index 000000000000..69d9d9050383
--- /dev/null
+++ b/icons/turf/walls/bamboo_wall.png.toml
@@ -0,0 +1,5 @@
+output_name = "bamboo_wall"
+template = "bitmask/diagonal_32x32.toml"
+
+[prefabs]
+0 = 5
diff --git a/icons/turf/walls/bananium_wall.dmi b/icons/turf/walls/bananium_wall.dmi
index 47e249c42e5b..7c563fca0d08 100644
Binary files a/icons/turf/walls/bananium_wall.dmi and b/icons/turf/walls/bananium_wall.dmi differ
diff --git a/icons/turf/walls/bananium_wall.png b/icons/turf/walls/bananium_wall.png
new file mode 100644
index 000000000000..52d4b2a5888b
Binary files /dev/null and b/icons/turf/walls/bananium_wall.png differ
diff --git a/icons/turf/walls/bananium_wall.png.toml b/icons/turf/walls/bananium_wall.png.toml
new file mode 100644
index 000000000000..3225586db5cc
--- /dev/null
+++ b/icons/turf/walls/bananium_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "bananium_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/boss_wall.dmi b/icons/turf/walls/boss_wall.dmi
index a197aac8ce32..eb0992e86e00 100644
Binary files a/icons/turf/walls/boss_wall.dmi and b/icons/turf/walls/boss_wall.dmi differ
diff --git a/icons/turf/walls/boss_wall.png b/icons/turf/walls/boss_wall.png
new file mode 100644
index 000000000000..45d334f25763
Binary files /dev/null and b/icons/turf/walls/boss_wall.png differ
diff --git a/icons/turf/walls/boss_wall.png.toml b/icons/turf/walls/boss_wall.png.toml
new file mode 100644
index 000000000000..4d6797e1ddaf
--- /dev/null
+++ b/icons/turf/walls/boss_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "boss_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/clockwork_wall.dmi b/icons/turf/walls/clockwork_wall.dmi
index 8263e901d00b..8785209409b2 100644
Binary files a/icons/turf/walls/clockwork_wall.dmi and b/icons/turf/walls/clockwork_wall.dmi differ
diff --git a/icons/turf/walls/clockwork_wall.png b/icons/turf/walls/clockwork_wall.png
new file mode 100644
index 000000000000..7dcc7d6ea6a1
Binary files /dev/null and b/icons/turf/walls/clockwork_wall.png differ
diff --git a/icons/turf/walls/clockwork_wall.png.toml b/icons/turf/walls/clockwork_wall.png.toml
new file mode 100644
index 000000000000..bfec1cdfd588
--- /dev/null
+++ b/icons/turf/walls/clockwork_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "clockwork_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/cult_wall.dmi b/icons/turf/walls/cult_wall.dmi
index 5239f7b1e068..51e98161d6a5 100644
Binary files a/icons/turf/walls/cult_wall.dmi and b/icons/turf/walls/cult_wall.dmi differ
diff --git a/icons/turf/walls/cult_wall.png b/icons/turf/walls/cult_wall.png
new file mode 100644
index 000000000000..c2005bdd60c3
Binary files /dev/null and b/icons/turf/walls/cult_wall.png differ
diff --git a/icons/turf/walls/cult_wall.png.toml b/icons/turf/walls/cult_wall.png.toml
new file mode 100644
index 000000000000..8739156f9ac9
--- /dev/null
+++ b/icons/turf/walls/cult_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "cult_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/diamond_wall.dmi b/icons/turf/walls/diamond_wall.dmi
index 0645aa2e6fd6..99149f0d43d9 100644
Binary files a/icons/turf/walls/diamond_wall.dmi and b/icons/turf/walls/diamond_wall.dmi differ
diff --git a/icons/turf/walls/diamond_wall.png b/icons/turf/walls/diamond_wall.png
new file mode 100644
index 000000000000..75263ac97a93
Binary files /dev/null and b/icons/turf/walls/diamond_wall.png differ
diff --git a/icons/turf/walls/diamond_wall.png.toml b/icons/turf/walls/diamond_wall.png.toml
new file mode 100644
index 000000000000..e9565ac712b9
--- /dev/null
+++ b/icons/turf/walls/diamond_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "diamond_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/false_walls.dmi b/icons/turf/walls/false_walls.dmi
new file mode 100644
index 000000000000..a64fbe06fa5b
Binary files /dev/null and b/icons/turf/walls/false_walls.dmi differ
diff --git a/icons/turf/walls/gold_wall.dmi b/icons/turf/walls/gold_wall.dmi
index c6ba713c6492..283dd437646d 100644
Binary files a/icons/turf/walls/gold_wall.dmi and b/icons/turf/walls/gold_wall.dmi differ
diff --git a/icons/turf/walls/gold_wall.png b/icons/turf/walls/gold_wall.png
new file mode 100644
index 000000000000..13774d5d9693
Binary files /dev/null and b/icons/turf/walls/gold_wall.png differ
diff --git a/icons/turf/walls/gold_wall.png.toml b/icons/turf/walls/gold_wall.png.toml
new file mode 100644
index 000000000000..c351a8eb43d7
--- /dev/null
+++ b/icons/turf/walls/gold_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "gold_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/hierophant_wall.dmi b/icons/turf/walls/hierophant_wall.dmi
index 8963db4e996d..3d417d70871d 100644
Binary files a/icons/turf/walls/hierophant_wall.dmi and b/icons/turf/walls/hierophant_wall.dmi differ
diff --git a/icons/turf/walls/hierophant_wall_temp.dmi b/icons/turf/walls/hierophant_wall_temp.dmi
index 9f0d4b8d23c0..3af6681157da 100644
Binary files a/icons/turf/walls/hierophant_wall_temp.dmi and b/icons/turf/walls/hierophant_wall_temp.dmi differ
diff --git a/icons/turf/walls/hierophant_wall_temp.png b/icons/turf/walls/hierophant_wall_temp.png
new file mode 100644
index 000000000000..ec8451a0d0ab
Binary files /dev/null and b/icons/turf/walls/hierophant_wall_temp.png differ
diff --git a/icons/turf/walls/hierophant_wall_temp.png.toml b/icons/turf/walls/hierophant_wall_temp.png.toml
new file mode 100644
index 000000000000..02d429d88218
--- /dev/null
+++ b/icons/turf/walls/hierophant_wall_temp.png.toml
@@ -0,0 +1,2 @@
+output_name = "hierophant_wall_temp"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/icedmetal_wall.dmi b/icons/turf/walls/icedmetal_wall.dmi
index 1782b9809b66..b069da4394d6 100644
Binary files a/icons/turf/walls/icedmetal_wall.dmi and b/icons/turf/walls/icedmetal_wall.dmi differ
diff --git a/icons/turf/walls/icedmetal_wall.png b/icons/turf/walls/icedmetal_wall.png
new file mode 100644
index 000000000000..a8973a16b234
Binary files /dev/null and b/icons/turf/walls/icedmetal_wall.png differ
diff --git a/icons/turf/walls/icedmetal_wall.png.toml b/icons/turf/walls/icedmetal_wall.png.toml
new file mode 100644
index 000000000000..6aa236a0da3c
--- /dev/null
+++ b/icons/turf/walls/icedmetal_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "icedmetal_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/icerock_wall.dmi b/icons/turf/walls/icerock_wall.dmi
index 4e2afc67d695..652a2df113b6 100644
Binary files a/icons/turf/walls/icerock_wall.dmi and b/icons/turf/walls/icerock_wall.dmi differ
diff --git a/icons/turf/walls/icerock_wall.png b/icons/turf/walls/icerock_wall.png
new file mode 100644
index 000000000000..08b0547a937c
Binary files /dev/null and b/icons/turf/walls/icerock_wall.png differ
diff --git a/icons/turf/walls/icerock_wall.png.toml b/icons/turf/walls/icerock_wall.png.toml
new file mode 100644
index 000000000000..7d713818b562
--- /dev/null
+++ b/icons/turf/walls/icerock_wall.png.toml
@@ -0,0 +1,14 @@
+output_name = "icerock_wall"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/walls/iron_wall.dmi b/icons/turf/walls/iron_wall.dmi
index 86bed015f152..d2d723adb28b 100644
Binary files a/icons/turf/walls/iron_wall.dmi and b/icons/turf/walls/iron_wall.dmi differ
diff --git a/icons/turf/walls/iron_wall.png b/icons/turf/walls/iron_wall.png
new file mode 100644
index 000000000000..ff71f25a0cb4
Binary files /dev/null and b/icons/turf/walls/iron_wall.png differ
diff --git a/icons/turf/walls/iron_wall.png.toml b/icons/turf/walls/iron_wall.png.toml
new file mode 100644
index 000000000000..13708a21e29b
--- /dev/null
+++ b/icons/turf/walls/iron_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "iron_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/material_wall.dmi b/icons/turf/walls/material_wall.dmi
new file mode 100644
index 000000000000..d4c48e66f6ea
Binary files /dev/null and b/icons/turf/walls/material_wall.dmi differ
diff --git a/icons/turf/walls/material_wall.png b/icons/turf/walls/material_wall.png
new file mode 100644
index 000000000000..0c2533dec6f9
Binary files /dev/null and b/icons/turf/walls/material_wall.png differ
diff --git a/icons/turf/walls/material_wall.png.toml b/icons/turf/walls/material_wall.png.toml
new file mode 100644
index 000000000000..899ac0aec8b2
--- /dev/null
+++ b/icons/turf/walls/material_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "material_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/meat_wall.dmi b/icons/turf/walls/meat_wall.dmi
new file mode 100644
index 000000000000..b587dc8e1be4
Binary files /dev/null and b/icons/turf/walls/meat_wall.dmi differ
diff --git a/icons/turf/walls/meat_wall.png b/icons/turf/walls/meat_wall.png
new file mode 100644
index 000000000000..8dfafa4b33e5
Binary files /dev/null and b/icons/turf/walls/meat_wall.png differ
diff --git a/icons/turf/walls/meat_wall.png.toml b/icons/turf/walls/meat_wall.png.toml
new file mode 100644
index 000000000000..0d3c9a41a9f0
--- /dev/null
+++ b/icons/turf/walls/meat_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "meat_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/mountain_wall.dmi b/icons/turf/walls/mountain_wall.dmi
index 8b90de864734..31ca67dc40bf 100644
Binary files a/icons/turf/walls/mountain_wall.dmi and b/icons/turf/walls/mountain_wall.dmi differ
diff --git a/icons/turf/walls/mountain_wall.png b/icons/turf/walls/mountain_wall.png
new file mode 100644
index 000000000000..47565a687eac
Binary files /dev/null and b/icons/turf/walls/mountain_wall.png differ
diff --git a/icons/turf/walls/mountain_wall.png.toml b/icons/turf/walls/mountain_wall.png.toml
new file mode 100644
index 000000000000..f5f413ba1218
--- /dev/null
+++ b/icons/turf/walls/mountain_wall.png.toml
@@ -0,0 +1,14 @@
+output_name = "mountain_wall"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/walls/plasma_wall.dmi b/icons/turf/walls/plasma_wall.dmi
index 4f2080d06acb..f7219ed1edcd 100644
Binary files a/icons/turf/walls/plasma_wall.dmi and b/icons/turf/walls/plasma_wall.dmi differ
diff --git a/icons/turf/walls/plasma_wall.png b/icons/turf/walls/plasma_wall.png
new file mode 100644
index 000000000000..cdaeec65154e
Binary files /dev/null and b/icons/turf/walls/plasma_wall.png differ
diff --git a/icons/turf/walls/plasma_wall.png.toml b/icons/turf/walls/plasma_wall.png.toml
new file mode 100644
index 000000000000..442517655751
--- /dev/null
+++ b/icons/turf/walls/plasma_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "plasma_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/plastinum_wall.dmi b/icons/turf/walls/plastinum_wall.dmi
new file mode 100644
index 000000000000..30ea5dbf945a
Binary files /dev/null and b/icons/turf/walls/plastinum_wall.dmi differ
diff --git a/icons/turf/walls/plastitanium_wall.dmi b/icons/turf/walls/plastitanium_wall.dmi
index 01dfdd1234de..078d4642785a 100644
Binary files a/icons/turf/walls/plastitanium_wall.dmi and b/icons/turf/walls/plastitanium_wall.dmi differ
diff --git a/icons/turf/walls/red_wall.dmi b/icons/turf/walls/red_wall.dmi
new file mode 100644
index 000000000000..8dc959f36cb3
Binary files /dev/null and b/icons/turf/walls/red_wall.dmi differ
diff --git a/icons/turf/walls/red_wall.png b/icons/turf/walls/red_wall.png
new file mode 100644
index 000000000000..312386a53777
Binary files /dev/null and b/icons/turf/walls/red_wall.png differ
diff --git a/icons/turf/walls/red_wall.png.toml b/icons/turf/walls/red_wall.png.toml
new file mode 100644
index 000000000000..917dc8ee3771
--- /dev/null
+++ b/icons/turf/walls/red_wall.png.toml
@@ -0,0 +1,14 @@
+output_name = "red_wall"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/walls/reinforced_rock.dmi b/icons/turf/walls/reinforced_rock.dmi
new file mode 100644
index 000000000000..e00e51482049
Binary files /dev/null and b/icons/turf/walls/reinforced_rock.dmi differ
diff --git a/icons/turf/walls/reinforced_rock.png b/icons/turf/walls/reinforced_rock.png
new file mode 100644
index 000000000000..544a877ec1a5
Binary files /dev/null and b/icons/turf/walls/reinforced_rock.png differ
diff --git a/icons/turf/walls/reinforced_rock.png.toml b/icons/turf/walls/reinforced_rock.png.toml
new file mode 100644
index 000000000000..7fcdd7f45b9f
--- /dev/null
+++ b/icons/turf/walls/reinforced_rock.png.toml
@@ -0,0 +1,2 @@
+output_name = "porous_rock"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/reinforced_states.dmi b/icons/turf/walls/reinforced_states.dmi
new file mode 100644
index 000000000000..4fc5c06e05a9
Binary files /dev/null and b/icons/turf/walls/reinforced_states.dmi differ
diff --git a/icons/turf/walls/reinforced_wall.dmi b/icons/turf/walls/reinforced_wall.dmi
index a6f1bc206665..210268b4625a 100644
Binary files a/icons/turf/walls/reinforced_wall.dmi and b/icons/turf/walls/reinforced_wall.dmi differ
diff --git a/icons/turf/walls/reinforced_wall.png b/icons/turf/walls/reinforced_wall.png
new file mode 100644
index 000000000000..37093774fbdc
Binary files /dev/null and b/icons/turf/walls/reinforced_wall.png differ
diff --git a/icons/turf/walls/reinforced_wall.png.toml b/icons/turf/walls/reinforced_wall.png.toml
new file mode 100644
index 000000000000..cd86b1f36f65
--- /dev/null
+++ b/icons/turf/walls/reinforced_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "reinforced_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/riveted.dmi b/icons/turf/walls/riveted.dmi
index 90d1e96297b9..e3746885e488 100644
Binary files a/icons/turf/walls/riveted.dmi and b/icons/turf/walls/riveted.dmi differ
diff --git a/icons/turf/walls/riveted.png b/icons/turf/walls/riveted.png
new file mode 100644
index 000000000000..f6b0369a7b44
Binary files /dev/null and b/icons/turf/walls/riveted.png differ
diff --git a/icons/turf/walls/riveted.png.toml b/icons/turf/walls/riveted.png.toml
new file mode 100644
index 000000000000..abc3f367d4fd
--- /dev/null
+++ b/icons/turf/walls/riveted.png.toml
@@ -0,0 +1,2 @@
+output_name = "riveted"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/rock_wall.dmi b/icons/turf/walls/rock_wall.dmi
index c07657149ac1..b6f81475833a 100644
Binary files a/icons/turf/walls/rock_wall.dmi and b/icons/turf/walls/rock_wall.dmi differ
diff --git a/icons/turf/walls/rock_wall.png b/icons/turf/walls/rock_wall.png
new file mode 100644
index 000000000000..54288bd18a90
Binary files /dev/null and b/icons/turf/walls/rock_wall.png differ
diff --git a/icons/turf/walls/rock_wall.png.toml b/icons/turf/walls/rock_wall.png.toml
new file mode 100644
index 000000000000..320b6b7dafee
--- /dev/null
+++ b/icons/turf/walls/rock_wall.png.toml
@@ -0,0 +1,14 @@
+output_name = "rock_wall"
+template = "bitmask/diagonal_32x32.toml"
+
+[icon_size]
+x = 40
+y = 40
+
+[output_icon_size]
+x = 40
+y = 40
+
+[cut_pos]
+x = 20
+y = 20
\ No newline at end of file
diff --git a/icons/turf/walls/rusty_reinforced_wall.dmi b/icons/turf/walls/rusty_reinforced_wall.dmi
new file mode 100644
index 000000000000..e2636950d27e
Binary files /dev/null and b/icons/turf/walls/rusty_reinforced_wall.dmi differ
diff --git a/icons/turf/walls/rusty_reinforced_wall.png b/icons/turf/walls/rusty_reinforced_wall.png
new file mode 100644
index 000000000000..7a1b3d4a1787
Binary files /dev/null and b/icons/turf/walls/rusty_reinforced_wall.png differ
diff --git a/icons/turf/walls/rusty_reinforced_wall.png.toml b/icons/turf/walls/rusty_reinforced_wall.png.toml
new file mode 100644
index 000000000000..8bb07c3839ac
--- /dev/null
+++ b/icons/turf/walls/rusty_reinforced_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "rusty_reinforced_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/rusty_wall.dmi b/icons/turf/walls/rusty_wall.dmi
new file mode 100644
index 000000000000..ca27a10fe074
Binary files /dev/null and b/icons/turf/walls/rusty_wall.dmi differ
diff --git a/icons/turf/walls/rusty_wall.png b/icons/turf/walls/rusty_wall.png
new file mode 100644
index 000000000000..4e38523bbbb3
Binary files /dev/null and b/icons/turf/walls/rusty_wall.png differ
diff --git a/icons/turf/walls/rusty_wall.png.toml b/icons/turf/walls/rusty_wall.png.toml
new file mode 100644
index 000000000000..358a6ccaa4d6
--- /dev/null
+++ b/icons/turf/walls/rusty_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "rusty_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/sandstone_wall.dmi b/icons/turf/walls/sandstone_wall.dmi
index 4118a3dbabf2..46c5b0af1c0a 100644
Binary files a/icons/turf/walls/sandstone_wall.dmi and b/icons/turf/walls/sandstone_wall.dmi differ
diff --git a/icons/turf/walls/sandstone_wall.png b/icons/turf/walls/sandstone_wall.png
new file mode 100644
index 000000000000..a000f4e934ec
Binary files /dev/null and b/icons/turf/walls/sandstone_wall.png differ
diff --git a/icons/turf/walls/sandstone_wall.png.toml b/icons/turf/walls/sandstone_wall.png.toml
new file mode 100644
index 000000000000..b9f06b895d18
--- /dev/null
+++ b/icons/turf/walls/sandstone_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "sandstone_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/shuttle_wall.dmi b/icons/turf/walls/shuttle_wall.dmi
index 605e0c32febb..3d9374b4a30d 100644
Binary files a/icons/turf/walls/shuttle_wall.dmi and b/icons/turf/walls/shuttle_wall.dmi differ
diff --git a/icons/turf/walls/silver_wall.dmi b/icons/turf/walls/silver_wall.dmi
index d8a932121995..66d337ef59d2 100644
Binary files a/icons/turf/walls/silver_wall.dmi and b/icons/turf/walls/silver_wall.dmi differ
diff --git a/icons/turf/walls/silver_wall.png b/icons/turf/walls/silver_wall.png
new file mode 100644
index 000000000000..1a731d9728f1
Binary files /dev/null and b/icons/turf/walls/silver_wall.png differ
diff --git a/icons/turf/walls/silver_wall.png.toml b/icons/turf/walls/silver_wall.png.toml
new file mode 100644
index 000000000000..cdfb92592c49
--- /dev/null
+++ b/icons/turf/walls/silver_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "silver_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/snow_wall.dmi b/icons/turf/walls/snow_wall.dmi
index dfcf459f2a11..833f7245e461 100644
Binary files a/icons/turf/walls/snow_wall.dmi and b/icons/turf/walls/snow_wall.dmi differ
diff --git a/icons/turf/walls/snow_wall.png b/icons/turf/walls/snow_wall.png
new file mode 100644
index 000000000000..ce6902fbab33
Binary files /dev/null and b/icons/turf/walls/snow_wall.png differ
diff --git a/icons/turf/walls/snow_wall.png.toml b/icons/turf/walls/snow_wall.png.toml
new file mode 100644
index 000000000000..79381bb6dcb1
--- /dev/null
+++ b/icons/turf/walls/snow_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "snow_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/survival_pod_walls.dmi b/icons/turf/walls/survival_pod_walls.dmi
index d6a8b097ce32..01700f1ed6f9 100644
Binary files a/icons/turf/walls/survival_pod_walls.dmi and b/icons/turf/walls/survival_pod_walls.dmi differ
diff --git a/icons/turf/walls/uranium_wall.dmi b/icons/turf/walls/uranium_wall.dmi
index 67bbc307dd8d..b64d270b3bda 100644
Binary files a/icons/turf/walls/uranium_wall.dmi and b/icons/turf/walls/uranium_wall.dmi differ
diff --git a/icons/turf/walls/uranium_wall.png b/icons/turf/walls/uranium_wall.png
new file mode 100644
index 000000000000..1f2c858d7c29
Binary files /dev/null and b/icons/turf/walls/uranium_wall.png differ
diff --git a/icons/turf/walls/uranium_wall.png.toml b/icons/turf/walls/uranium_wall.png.toml
new file mode 100644
index 000000000000..035bd1c2af73
--- /dev/null
+++ b/icons/turf/walls/uranium_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "uranium_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/wall.dmi b/icons/turf/walls/wall.dmi
index 5df919097cac..043da38c0346 100644
Binary files a/icons/turf/walls/wall.dmi and b/icons/turf/walls/wall.dmi differ
diff --git a/icons/turf/walls/wall.png b/icons/turf/walls/wall.png
new file mode 100644
index 000000000000..6e603751a6e0
Binary files /dev/null and b/icons/turf/walls/wall.png differ
diff --git a/icons/turf/walls/wall.png.toml b/icons/turf/walls/wall.png.toml
new file mode 100644
index 000000000000..1264d5314a18
--- /dev/null
+++ b/icons/turf/walls/wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/turf/walls/wood_wall.dmi b/icons/turf/walls/wood_wall.dmi
index 84e1c0832884..5be00658fffa 100644
Binary files a/icons/turf/walls/wood_wall.dmi and b/icons/turf/walls/wood_wall.dmi differ
diff --git a/icons/turf/walls/wood_wall.png b/icons/turf/walls/wood_wall.png
new file mode 100644
index 000000000000..dc9c53a0b703
Binary files /dev/null and b/icons/turf/walls/wood_wall.png differ
diff --git a/icons/turf/walls/wood_wall.png.toml b/icons/turf/walls/wood_wall.png.toml
new file mode 100644
index 000000000000..c40a9740a98e
--- /dev/null
+++ b/icons/turf/walls/wood_wall.png.toml
@@ -0,0 +1,2 @@
+output_name = "wood_wall"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/interface/skin.dmf b/interface/skin.dmf
index fe3e851ac048..763ea44d9fef 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -141,7 +141,7 @@ window "mapwindow"
text-color = none
is-default = true
saved-params = "zoom;letterbox;zoom-mode"
- style = ".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }"
+ style = ".center { text-align: center; } .maptext { font-family: 'MS Serif'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }"
window "infowindow"
elem "infowindow"
diff --git a/libauxmos.so b/libauxmos.so
deleted file mode 100644
index c13f7b23a1bf..000000000000
Binary files a/libauxmos.so and /dev/null differ
diff --git a/modular_dripstation/code/controllers/subsystem/blackmarket.dm b/modular_dripstation/code/controllers/subsystem/blackmarket.dm
new file mode 100644
index 000000000000..357fa0df2915
--- /dev/null
+++ b/modular_dripstation/code/controllers/subsystem/blackmarket.dm
@@ -0,0 +1,115 @@
+SUBSYSTEM_DEF(blackmarket)
+ name = "Blackmarket"
+ flags = SS_BACKGROUND
+ init_order = INIT_ORDER_DEFAULT
+
+ /// Descriptions for each shipping methods.
+ var/shipping_method_descriptions = list(
+ SHIPPING_METHOD_LAUNCH="Launches the item at the station from space, cheap but you might not receive your item at all.",
+ SHIPPING_METHOD_LTSRBT="Long-To-Short-Range-Bluespace-Transceiver, a machine that receives items outside the station and then teleports them to the location of the uplink.",
+ SHIPPING_METHOD_TELEPORT="Teleports the item in a random area in the station, you get 60 seconds to get there first though."
+ )
+
+ /// List of all existing markets.
+ var/list/datum/market/markets = list()
+ /// List of existing ltsrbts.
+ var/list/obj/machinery/ltsrbt/telepads = list()
+ /// Currently queued purchases.
+ var/list/queued_purchases = list()
+
+/datum/controller/subsystem/blackmarket/Initialize()
+ for(var/market in subtypesof(/datum/market))
+ markets[market] += new market
+
+ for(var/item in subtypesof(/datum/market_item))
+ var/datum/market_item/I = new item()
+ if(!I.item)
+ continue
+
+ for(var/M in I.markets)
+ if(!markets[M])
+ stack_trace("SSblackmarket: Item [I] available in market that does not exist.")
+ continue
+ markets[M].add_item(item)
+ qdel(I)
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/blackmarket/fire(resumed)
+ while(length(queued_purchases))
+ var/datum/market_purchase/purchase = queued_purchases[1]
+ queued_purchases.Cut(1,2)
+
+ // Uh oh, uplink is gone. We will just keep the money and you will not get your order.
+ if(!purchase.uplink || QDELETED(purchase.uplink))
+ queued_purchases -= purchase
+ qdel(purchase)
+ continue
+
+ switch(purchase.method)
+ // Find a ltsrbt pad and make it handle the shipping.
+ if(SHIPPING_METHOD_LTSRBT)
+ if(!telepads.len)
+ continue
+ // Prioritize pads that don't have a cooldown active.
+ var/free_pad_found = FALSE
+ for(var/obj/machinery/ltsrbt/pad in telepads)
+ if(pad.recharge_cooldown)
+ continue
+ pad.add_to_queue(purchase)
+ queued_purchases -= purchase
+ free_pad_found = TRUE
+ break
+
+ if(free_pad_found)
+ continue
+
+ var/obj/machinery/ltsrbt/pad = pick(telepads)
+
+ to_chat(recursive_loc_check(purchase.uplink.loc, /mob), span_notice("[purchase.uplink] flashes a message noting that the order is being processed by [pad]."))
+
+ queued_purchases -= purchase
+ pad.add_to_queue(purchase)
+ // Get random area, throw it somewhere there.
+ if(SHIPPING_METHOD_TELEPORT)
+ var/turf/targetturf = get_safe_random_station_turf()
+ // This shouldn't happen.
+ if (!targetturf)
+ continue
+
+ to_chat(recursive_loc_check(purchase.uplink.loc, /mob), span_notice("[purchase.uplink] flashes a message noting that the order is being teleported to [get_area(targetturf)] in 60 seconds."))
+
+ // do_teleport does not want to teleport items from nullspace, so it just forceMoves and does sparks.
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/controller/subsystem/blackmarket,fake_teleport), purchase.entry.spawn_item(), targetturf), 60 SECONDS)
+ queued_purchases -= purchase
+ qdel(purchase)
+ // Get the current location of the uplink if it exists, then throws the item from space at the station from a random direction.
+ if(SHIPPING_METHOD_LAUNCH)
+ var/startSide = pick(GLOB.cardinals)
+ var/turf/T = get_turf(purchase.uplink)
+ var/pickedloc = spaceDebrisStartLoc(startSide, T.z)
+
+ var/atom/movable/item = purchase.entry.spawn_item(pickedloc)
+ item.throw_at(purchase.uplink, 3, 3, spin = FALSE)
+
+ to_chat(recursive_loc_check(purchase.uplink.loc, /mob), span_notice("[purchase.uplink] flashes a message noting the order is being launched at the station from [dir2text(startSide)]."))
+
+ queued_purchases -= purchase
+ qdel(purchase)
+
+ if(MC_TICK_CHECK)
+ break
+
+/// Used to make a teleportation effect as do_teleport does not like moving items from nullspace.
+/datum/controller/subsystem/blackmarket/proc/fake_teleport(atom/movable/item, turf/target)
+ item.forceMove(target)
+ var/datum/effect_system/spark_spread/sparks = new
+ sparks.set_up(5, 1, target)
+ sparks.attach(item)
+ sparks.start()
+
+/// Used to add /datum/market_purchase to queued_purchases var. Returns TRUE when queued.
+/datum/controller/subsystem/blackmarket/proc/queue_item(datum/market_purchase/P)
+ if(P.method == SHIPPING_METHOD_LTSRBT && !telepads.len)
+ return FALSE
+ queued_purchases += P
+ return TRUE
diff --git a/modular_dripstation/code/datums/brain_damage/severe.dm b/modular_dripstation/code/datums/brain_damage/severe.dm
new file mode 100644
index 000000000000..247ecd016360
--- /dev/null
+++ b/modular_dripstation/code/datums/brain_damage/severe.dm
@@ -0,0 +1,31 @@
+/datum/brain_trauma/severe/hypnotic_trigger
+ name = "Hypnotic Trigger"
+ desc = "Patient has a trigger phrase set in their subconscious that will trigger a suggestible trance-like state."
+ scan_desc = "oneiric feedback loop"
+ gain_text = span_warning("You feel odd, like you just forgot something important.")
+ lose_text = span_notice("You feel like a weight was lifted from your mind.")
+ random_gain = FALSE
+ var/trigger_phrase = "Nanotrasen"
+
+/datum/brain_trauma/severe/hypnotic_trigger/New(phrase)
+ ..()
+ if(phrase)
+ trigger_phrase = phrase
+
+/datum/brain_trauma/severe/hypnotic_trigger/on_lose() //hypnosis must be cleared separately, but brain surgery should get rid of both anyway
+ ..()
+ owner.remove_status_effect(/datum/status_effect/trance)
+
+/datum/brain_trauma/severe/hypnotic_trigger/handle_hearing(datum/source, list/hearing_args)
+ if(!owner.can_hear() || owner == hearing_args[HEARING_SPEAKER])
+ return
+
+ var/regex/reg = new("(\\b[REGEX_QUOTE(trigger_phrase)]\\b)","ig")
+
+ if(findtext(hearing_args[HEARING_RAW_MESSAGE], reg))
+ addtimer(CALLBACK(src, PROC_REF(hypnotrigger)), 10) //to react AFTER the chat message
+ hearing_args[HEARING_RAW_MESSAGE] = reg.Replace(hearing_args[HEARING_RAW_MESSAGE], span_hypnophrase("*********"))
+
+/datum/brain_trauma/severe/hypnotic_trigger/proc/hypnotrigger()
+ to_chat(owner, span_warning("The words trigger something deep within you, and you feel your consciousness slipping away..."))
+ owner.apply_status_effect(/datum/status_effect/trance, rand(100,300), FALSE)
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/component/mood.dm b/modular_dripstation/code/datums/component/mood.dm
new file mode 100644
index 000000000000..b7540a9a694c
--- /dev/null
+++ b/modular_dripstation/code/datums/component/mood.dm
@@ -0,0 +1,87 @@
+#define MINOR_INSANITY_PEN 5
+#define MAJOR_INSANITY_PEN 10
+//Okey, just mood rebalance
+/datum/component/mood/process(delta_time)
+ var/mob/living/owner = parent
+ if(!owner)
+ qdel(src)
+ return
+
+ switch(mood_level)
+ if(1)
+ setSanity(sanity-0.3*delta_time, minimum=SANITY_INSANE)
+ if(2)
+ setSanity(sanity-0.15*delta_time, minimum=SANITY_INSANE)
+ if(3)
+ setSanity(sanity-0.1*delta_time, minimum=SANITY_CRAZY)
+ if(4)
+ setSanity(sanity-0.05*delta_time, minimum=SANITY_UNSTABLE)
+ if(5)
+ setSanity(sanity, minimum=SANITY_UNSTABLE) //This makes sure that mood gets increased should you be below the minimum.
+ if(6)
+ setSanity(sanity+0.15*delta_time, minimum=SANITY_UNSTABLE)
+ if(7)
+ setSanity(sanity+0.2*delta_time, minimum=SANITY_UNSTABLE)
+ if(8)
+ setSanity(sanity+0.25*delta_time, minimum=SANITY_NEUTRAL, maximum=SANITY_GREAT)
+ if(9)
+ setSanity(sanity+0.4*delta_time, minimum=SANITY_NEUTRAL, maximum=INFINITY)
+
+
+ if(HAS_TRAIT(owner, TRAIT_DEPRESSION))
+ if(prob(0.05))
+ add_event(null, "depression", /datum/mood_event/depression_mild)
+ clear_event(null, "jolly")
+ if(HAS_TRAIT(owner, TRAIT_JOLLY))
+ if(prob(0.05))
+ add_event(null, "jolly", /datum/mood_event/jolly)
+ clear_event(null, "depression")
+ if(HAS_TRAIT(owner, TRAIT_PSYCHOPATHIC))
+ if(prob(0.005))
+ add_event(null, "depression", /datum/mood_event/depression_moderate)
+ clear_event(null, "jolly")
+ if(prob(0.005))
+ add_event(null, "jolly", /datum/mood_event/jolly)
+ clear_event(null, "depression")
+
+ HandleNutrition(owner)
+
+
+/datum/component/mood/proc/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_NEUTRAL)
+ if(amount < minimum)
+ amount += clamp(minimum - amount, 0, 0.7)
+ if(HAS_TRAIT(parent, TRAIT_UNSTABLE) || amount > maximum)
+ amount = min(sanity, amount)
+ if(amount == sanity) //Prevents stuff from flicking around.
+ return
+ sanity = amount
+ var/mob/living/master = parent
+ switch(sanity)
+ if(SANITY_INSANE to SANITY_CRAZY)
+ setInsanityEffect(MAJOR_INSANITY_PEN)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.75, movetypes=(~FLYING))
+ sanity_level = 6
+ if(SANITY_CRAZY to SANITY_UNSTABLE)
+ setInsanityEffect(MINOR_INSANITY_PEN)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.5, movetypes=(~FLYING))
+ sanity_level = 5
+ if(SANITY_UNSTABLE to SANITY_DISTURBED)
+ setInsanityEffect(0)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.25, movetypes=(~FLYING))
+ sanity_level = 4
+ if(SANITY_DISTURBED to SANITY_NEUTRAL)
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 3
+ if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 2
+ if(SANITY_GREAT+1 to INFINITY)
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 1
+ update_mood_icon()
+
+#undef MINOR_INSANITY_PEN
+#undef MAJOR_INSANITY_PEN
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/component/transforming.dm b/modular_dripstation/code/datums/component/transforming.dm
new file mode 100644
index 000000000000..3941d5eabe6c
--- /dev/null
+++ b/modular_dripstation/code/datums/component/transforming.dm
@@ -0,0 +1,258 @@
+/*
+ * Transforming weapon component. For weapons that swap between states.
+ * For example: Energy swords, cleaving saws, switch blades.
+ *
+ * Used to easily make an item that can be attack_self'd to gain force or change mode.
+ *
+ * Only values passed on initialize will update when the item is activated (except the icon_state).
+ * The icon_state of the item will swap between "[icon_state]" and "[icon_state]_on".
+ */
+/datum/component/transforming
+ /// Whether the weapon is transformed
+ var/active = FALSE
+ /// Cooldown on transforming this item back and forth
+ var/transform_cooldown_time
+ /// Force of the weapon when active
+ var/force_on
+ /// Throwforce of the weapon when active
+ var/throwforce_on
+ /// Throw speed of the weapon when active
+ var/throw_speed_on
+ /// Weight class of the weapon when active
+ var/w_class_on
+ /// The sharpness of the weapon when active
+ var/sharpness_on
+ /// Hitsound played when active
+ var/hitsound_on
+ /// List of the original continuous attack verbs the item has.
+ var/list/attack_verb_off
+ /// List of simple attack verbs used when the weapon is enabled
+ var/list/attack_verb_on
+ /// Whether clumsy people need to succeed an RNG check to turn it on without hurting themselves
+ var/clumsy_check
+ /// If we get sharpened with a whetstone, save the bonus here for later use if we un/redeploy
+ var/sharpened_bonus = 0
+ /// Dictate whether we change inhands or not
+ var/item_state_change = TRUE
+ /// Cooldown in between transforms
+ COOLDOWN_DECLARE(transform_cooldown)
+
+/datum/component/transforming/Initialize(
+ start_transformed = FALSE,
+ transform_cooldown_time = 0 SECONDS,
+ force_on = 0,
+ throwforce_on = 0,
+ throw_speed_on = 2,
+ sharpness_on = NONE,
+ hitsound_on = 'sound/weapons/blade1.ogg',
+ w_class_on = WEIGHT_CLASS_BULKY,
+ clumsy_check = TRUE,
+ list/attack_verb_on,
+ item_state_change = TRUE,
+)
+
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ var/obj/item/item_parent = parent
+
+ src.transform_cooldown_time = transform_cooldown_time
+ src.force_on = force_on
+ src.throwforce_on = throwforce_on
+ src.throw_speed_on = throw_speed_on
+ src.sharpness_on = sharpness_on
+ src.hitsound_on = hitsound_on
+ src.w_class_on = w_class_on
+ src.clumsy_check = clumsy_check
+ src.item_state_change = item_state_change
+
+ if(attack_verb_on)
+ src.attack_verb_on = attack_verb_on
+ attack_verb_off = item_parent.attack_verb
+
+ if(start_transformed)
+ toggle_active(parent)
+
+/datum/component/transforming/RegisterWithParent()
+ var/obj/item/item_parent = parent
+
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_attack_self))
+ if(item_parent.sharpness || sharpness_on)
+ RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, PROC_REF(on_sharpen))
+
+ RegisterSignal(parent, COMSIG_DETECTIVE_SCANNED, PROC_REF(on_scan))
+
+/datum/component/transforming/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ITEM_SHARPEN_ACT, COMSIG_DETECTIVE_SCANNED))
+
+/datum/component/transforming/proc/on_scan(datum/source, mob/user, list/extra_data)
+ SIGNAL_HANDLER
+ LAZYADD(extra_data[DETSCAN_CATEGORY_NOTES], "Readings suggest some form of state changing.")
+
+
+/*
+ * Called on [COMSIG_ITEM_ATTACK_SELF].
+ *
+ * Check if we can transform our weapon, and if so, call [do_transform].
+ * Sends signal [COMSIG_TRANSFORMING_PRE_TRANSFORM], and stops the transform action if it returns [COMPONENT_BLOCK_TRANSFORM].
+ * And, if [do_transform] was successful, do a clumsy effect from [clumsy_transform_effect].
+ *
+ * source - source of the signal, the item being transformed / parent
+ * user - the mob transforming the weapon
+ */
+/datum/component/transforming/proc/on_attack_self(obj/item/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(!COOLDOWN_FINISHED(src, transform_cooldown))
+ to_chat(user, span_warning("Wait a bit before trying to use [source] again!"))
+ return
+
+ if(SEND_SIGNAL(source, COMSIG_TRANSFORMING_PRE_TRANSFORM, user, active) & COMPONENT_BLOCK_TRANSFORM)
+ return
+
+ if(do_transform(source, user))
+ clumsy_transform_effect(user)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/*
+ * Transform the weapon into its alternate form, calling [toggle_active].
+ *
+ * Sends signal [COMSIG_TRANSFORMING_ON_TRANSFORM], and calls [default_transform_message] if it does not return [COMPONENT_NO_DEFAULT_MESSAGE].
+ * Also starts the [transform_cooldown] if we have a set [transform_cooldown_time].
+ *
+ * source - the item being transformed / parent
+ * user - the mob transforming the item
+ *
+ * returns TRUE.
+ */
+/datum/component/transforming/proc/do_transform(obj/item/source, mob/user)
+ toggle_active(source)
+ if(!(SEND_SIGNAL(source, COMSIG_TRANSFORMING_ON_TRANSFORM, user, active) & COMPONENT_NO_DEFAULT_MESSAGE))
+ default_transform_message(source, user)
+
+ if(isnum(transform_cooldown_time))
+ COOLDOWN_START(src, transform_cooldown, transform_cooldown_time)
+ if(user)
+ source.add_fingerprint(user)
+ return TRUE
+
+/*
+ * The default feedback message and sound effect for an item transforming.
+ *
+ * source - the item being transformed / parent
+ * user - the mob transforming the item
+ */
+/datum/component/transforming/proc/default_transform_message(obj/item/source, mob/user)
+ if(user)
+ source.balloon_alert(user, "[active ? "enabled" : "disabled"] [source]")
+ playsound(source, 'sound/weapons/batonextend.ogg', 50, TRUE)
+
+/*
+ * Toggle active between true and false, and call
+ * either set_active or set_inactive depending on whichever state is toggled.
+ *
+ * source - the item being transformed / parent
+ */
+/datum/component/transforming/proc/toggle_active(obj/item/source)
+ active = !active
+ if(active)
+ set_active(source)
+ else
+ set_inactive(source)
+
+/*
+ * Set our transformed item into its active state.
+ * Updates all the values that were passed from init and the icon_state.
+ *
+ * source - the item being transformed / parent
+ */
+/datum/component/transforming/proc/set_active(obj/item/source)
+ ADD_TRAIT(source, TRAIT_TRANSFORM_ACTIVE, REF(src))
+ if(sharpness_on)
+ source.sharpness = sharpness_on
+ if(force_on)
+ source.force = force_on + (source.sharpness ? sharpened_bonus : 0)
+ if(throwforce_on)
+ source.throwforce = throwforce_on + (source.sharpness ? sharpened_bonus : 0)
+ if(throw_speed_on)
+ source.throw_speed = throw_speed_on
+
+ if(LAZYLEN(attack_verb_on))
+ source.attack_verb = attack_verb_on
+
+ source.hitsound = hitsound_on
+ source.w_class = w_class_on
+ source.icon_state = "[source.icon_state]_on"
+ if(item_state_change && source.item_state)
+ source.item_state = "[source.item_state]_on"
+ source.update_icon()
+
+/*
+ * Set our transformed item into its inactive state.
+ * Updates all the values back to the item's initial values.
+ *
+ * source - the item being un-transformed / parent
+ */
+/datum/component/transforming/proc/set_inactive(obj/item/source)
+ REMOVE_TRAIT(source, TRAIT_TRANSFORM_ACTIVE, REF(src))
+ if(sharpness_on)
+ source.sharpness = initial(source.sharpness)
+ if(force_on)
+ source.force = initial(source.force) + (source.sharpness ? sharpened_bonus : 0)
+ if(throwforce_on)
+ source.throwforce = initial(source.throwforce) + (source.sharpness ? sharpened_bonus : 0)
+ if(throw_speed_on)
+ source.throw_speed = initial(source.throw_speed)
+
+ if(LAZYLEN(attack_verb_on))
+ source.attack_verb = attack_verb_off
+
+ source.hitsound = initial(source.hitsound)
+ source.w_class = initial(source.w_class)
+ source.icon_state = initial(source.icon_state)
+ source.item_state = initial(source.item_state)
+
+/*
+ * If [clumsy_check] is set to TRUE, attempt to cause a side effect for clumsy people activating this item.
+ * Called after the transform is done, meaning [active] var has already updated.
+ *
+ * user - the clumsy mob, transforming our item (parent)
+ *
+ * Returns TRUE if side effects happened, FALSE otherwise
+ */
+/datum/component/transforming/proc/clumsy_transform_effect(mob/living/user)
+ if(!clumsy_check)
+ return FALSE
+
+ if(!user || !HAS_TRAIT(user, TRAIT_CLUMSY))
+ return FALSE
+
+ if(active && prob(50))
+ var/hurt_self_verb = LAZYLEN(attack_verb_on) ? pick(attack_verb_on) : "hited"
+ user.visible_message(
+ span_warning("[user] triggers [parent] while holding it backwards and [hurt_self_verb] themself, like a doofus!"),
+ span_warning("You trigger [parent] while holding it backwards and [hurt_self_verb] yourself, like a doofus!"),
+ )
+ user.take_bodypart_damage(10)
+ return TRUE
+ return FALSE
+
+/*
+ * Called on [COMSIG_ITEM_SHARPEN_ACT].
+ * We need to track our sharpened bonus here, so we correctly apply and unapply it
+ * if our item's sharpness state changes from transforming.
+ *
+ * source - the item being sharpened / parent
+ * increment - the amount of force added
+ * max - the maximum force that the item can be adjusted to.
+ *
+ * Does not return naturally [COMPONENT_BLOCK_SHARPEN_APPLIED] as this is only to track our sharpened bonus between transformation.
+ */
+/datum/component/transforming/proc/on_sharpen(obj/item/source, increment, max)
+ SIGNAL_HANDLER
+
+ if(sharpened_bonus)
+ return COMPONENT_BLOCK_SHARPEN_ALREADY
+ if(force_on + increment > max)
+ return COMPONENT_BLOCK_SHARPEN_MAXED
+ sharpened_bonus = increment
diff --git a/modular_dripstation/code/datums/keybinding/communication.dm b/modular_dripstation/code/datums/keybinding/communication.dm
new file mode 100644
index 000000000000..8eb9ddf9c940
--- /dev/null
+++ b/modular_dripstation/code/datums/keybinding/communication.dm
@@ -0,0 +1,36 @@
+/datum/keybinding/client/communication/say/down(client/user)
+ user.mob.say_wrapper()
+ return TRUE
+
+/datum/keybinding/client/communication/emote/down(client/user)
+ user.mob.me_wrapper()
+ return TRUE
+
+/datum/keybinding/client/communication/looc/down(client/user)
+ user.looc_wrapper()
+ return TRUE
+
+/datum/keybinding/client/communication/ooc/down(client/user)
+ user.ooc_wrapper()
+ return TRUE
+
+/datum/keybinding/client/communication/donor_say/down(client/user)
+ user.get_donator_say()
+ return TRUE
+
+/datum/keybinding/client/communication/mentor_say/down(client/user)
+ user.mentor_wrapper()
+ return TRUE
+
+/client/verb/looc_wrapper()
+ set hidden = TRUE
+ var/message = input("", "LOOC \"text\"") as null|text
+ looc(message)
+
+/client/verb/mentor_wrapper()
+ set hidden = TRUE
+ set name = "msay"
+
+ var/message = input(src, null, "Mentor Chat \"text\"") as text|null
+ if (message)
+ cmd_mentor_say(message)
diff --git a/modular_dripstation/code/datums/mood_events/generic_negative_events.dm b/modular_dripstation/code/datums/mood_events/generic_negative_events.dm
new file mode 100644
index 000000000000..9e127c6d55ee
--- /dev/null
+++ b/modular_dripstation/code/datums/mood_events/generic_negative_events.dm
@@ -0,0 +1,15 @@
+/datum/mood_event/ate_without_table
+ mood_change = 0 //F THIS RIMWORLD REFERENCE
+
+/datum/mood_event/surgery
+ timeout = 5 MINUTES
+
+/datum/mood_event/bad_touch
+ description = "I don't like when people touch me.\n"
+ mood_change = -3
+ timeout = 4 MINUTES
+
+/datum/mood_event/very_bad_touch
+ description = "I really don't like when people touch me.\n"
+ mood_change = -5
+ timeout = 4 MINUTES
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/mood_events/generic_positive_events.dm b/modular_dripstation/code/datums/mood_events/generic_positive_events.dm
new file mode 100644
index 000000000000..a137be670394
--- /dev/null
+++ b/modular_dripstation/code/datums/mood_events/generic_positive_events.dm
@@ -0,0 +1,30 @@
+/datum/mood_event/focused
+ mood_change = 10 //Used for syndies, nukeops etc so they can focus on their goals
+
+/datum/mood_event/slaughter
+ description ="These pitiful NanoTrasen scam will have to drink vacuum sooner or later. Slaughter... THEM... ALL!!\n"
+ mood_change = 15
+
+/datum/mood_event/heretics
+ mood_change = 8
+
+/datum/mood_event/cult
+ mood_change = 15 //maybe being a cultist isnt that bad after all
+
+/datum/mood_event/drankblood
+ mood_change = 8
+
+/datum/mood_event/sacrifice_geometer
+ description ="The Great Geometer of Blood is pleased with this offering!\n"
+ mood_change = 5
+ timeout = 3 MINUTES
+
+/datum/mood_event/sacrifice_heretic
+ description ="Your patrons are pleased with this offering!\n"
+ mood_change = 5
+ timeout = 3 MINUTES
+
+/datum/mood_event/jolly_moderate
+ description = "That was a really funny joke that my inner self told me!\n"
+ mood_change = 9
+ timeout = 2 MINUTES
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/reagent/baldium.dm b/modular_dripstation/code/datums/reagent/baldium.dm
new file mode 100644
index 000000000000..82a53e58bf36
--- /dev/null
+++ b/modular_dripstation/code/datums/reagent/baldium.dm
@@ -0,0 +1,16 @@
+/datum/reagent/baldium
+ name = "Baldium"
+ description = "A major cause of hair loss across the world."
+ reagent_state = LIQUID
+ color = "#ecb2cf"
+ taste_description = "bitterness"
+
+/datum/reagent/baldium/reaction_mob(mob/living/L, method=TOUCH, reac_volume, show_message=TRUE, touch_protection=FALSE)
+ . = ..()
+ if(!(method & (TOUCH|VAPOR)) || !ishuman(L))
+ return
+
+ var/mob/living/carbon/human/baldtarget = L
+ to_chat(baldtarget, span_danger("Your hair is falling out in clumps!"))
+ baldtarget.facial_hair_style = "Shaved"
+ baldtarget.hair_style = "Bald"
diff --git a/modular_dripstation/code/datums/reagent/chemoverride.dm b/modular_dripstation/code/datums/reagent/chemoverride.dm
new file mode 100644
index 000000000000..1e69ac66b919
--- /dev/null
+++ b/modular_dripstation/code/datums/reagent/chemoverride.dm
@@ -0,0 +1,40 @@
+/datum/reagent/medicine/mannitol
+ name = "Mannitolin"
+ description = "Generic drug that uses a patented active substance molecule to restore brain damage. Unfortunately it isn`t too effective and requires very cold temperatures to properly metabolize."
+ color = "#DCDCFF"
+ metabolization_rate = 1.5 * REAGENTS_METABOLISM
+
+/datum/reagent/medicine/mannitol/on_mob_life(mob/living/carbon/M)
+ if(M.bodytemperature < T0C)
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, (holder.has_reagent(/datum/reagent/drug/methamphetamine) ? 0 : -2)*REM)
+ . = 1
+ else
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, (holder.has_reagent(/datum/reagent/drug/methamphetamine) ? 0 : -0.2)*REM)
+ metabolization_rate = REAGENTS_METABOLISM * (0.00001 * (M.bodytemperature ** 2) + 0.5)
+ ..()
+
+/datum/reagent/medicine/mannitol/advanced
+ name = "Mannitol"
+ description = "Efficiently restores brain damage. Brand patented."
+ color = "#4CE8E2"
+ metabolization_rate = REAGENTS_METABOLISM
+
+/datum/reagent/medicine/mannitol/advanced/on_mob_life(mob/living/carbon/M)
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, (holder.has_reagent(/datum/reagent/drug/methamphetamine) ? 0 : -2)*REM)
+ current_cycle++
+ holder.remove_reagent(type, metabolization_rate / M.metabolism_efficiency) //medicine reagents stay longer if you have a better metabolism
+
+/datum/reagent/medicine/clonexadone
+ name = "Clonexadone"
+ description = "A chemical that derives from Cryoxadone. It specializes in healing clone damage, but nothing else. Requires very cold temperatures to properly metabolize, and metabolizes quicker than cryoxadone."
+ color = "#80BFFF"
+ taste_description = "muscle"
+ metabolization_rate = 1.5 * REAGENTS_METABOLISM
+
+/datum/reagent/medicine/clonexadone/on_mob_life(mob/living/carbon/M)
+ if(M.bodytemperature < T0C && M.IsSleeping()) //yes you have to be in cryo shut up and drink your corn syrup
+ M.adjustCloneLoss(0.001 * (M.bodytemperature ** 2) - 100, 0)
+ REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC)
+ . = 1
+ metabolization_rate = REAGENTS_METABOLISM * (0.000015 * (M.bodytemperature ** 2) + 0.75)
+ ..()
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/reagent/leadacetate.dm b/modular_dripstation/code/datums/reagent/leadacetate.dm
new file mode 100644
index 000000000000..708ceb1c7d1e
--- /dev/null
+++ b/modular_dripstation/code/datums/reagent/leadacetate.dm
@@ -0,0 +1,16 @@
+/datum/reagent/toxin/leadacetate
+ name = "Lead Acetate"
+ description = "Used hundreds of years ago as a sweetener, before it was realized that it's incredibly poisonous."
+ reagent_state = SOLID
+ color = "#2b2b2b" // rgb: 127, 132, 0
+ toxpwr = 0.5
+ taste_mult = 1.3
+ taste_description = "sugary sweetness"
+
+/datum/reagent/toxin/leadacetate/on_mob_life(mob/living/carbon/affected_mob)
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_EARS, 1 SECONDS * REM)
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 SECONDS * REM)
+ if(prob(5))
+ to_chat(affected_mob, span_notice("Ah, what was that? You thought you heard something..."))
+ affected_mob.adjust_confusion(5 SECONDS)
+ return ..()
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/strong_pull.dm b/modular_dripstation/code/datums/strong_pull.dm
new file mode 100644
index 000000000000..8a6d851b7559
--- /dev/null
+++ b/modular_dripstation/code/datums/strong_pull.dm
@@ -0,0 +1,47 @@
+/*
+This component attaches to mobs, and makes their pulls !strong!
+Basically, the items they pull cannot be pulled (except by the puller)
+*/
+/datum/component/strong_pull
+ var/atom/movable/strongpulling
+
+/datum/component/strong_pull/Initialize()
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/strong_pull/Destroy(force, silent)
+ if(strongpulling)
+ lose_strong_grip()
+ return ..()
+
+/datum/component/strong_pull/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_LIVING_START_PULL, PROC_REF(on_pull))
+
+/**
+ * Called when the parent grabs something, adds signals to the object to reject interactions
+ */
+/datum/component/strong_pull/proc/on_pull(datum/source, atom/movable/pulled, state, force)
+ SIGNAL_HANDLER
+ strongpulling = pulled
+ RegisterSignal(strongpulling, COMSIG_ATOM_NO_LONGER_PULLED, PROC_REF(on_no_longer_pulled))
+ if(istype(strongpulling, /obj/structure/closet) && !istype(strongpulling, /obj/structure/closet/body_bag))
+ var/obj/structure/closet/grabbed_closet = strongpulling
+ grabbed_closet.strong_grab = TRUE
+
+/**
+ * Unregisters signals and stops any buffs to pulling.
+ */
+/datum/component/strong_pull/proc/lose_strong_grip()
+ UnregisterSignal(strongpulling, list(COMSIG_ATOM_CAN_BE_PULLED, COMSIG_ATOM_NO_LONGER_PULLED))
+ if(istype(strongpulling, /obj/structure/closet))
+ var/obj/structure/closet/ungrabbed_closet = strongpulling
+ ungrabbed_closet.strong_grab = FALSE
+ strongpulling = null
+
+/**
+ * Called when the hooked object is no longer pulled and removes the strong grip.
+ */
+/datum/component/strong_pull/proc/on_no_longer_pulled(datum/source, atom/movable/last_puller)
+ SIGNAL_HANDLER
+ lose_strong_grip()
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/traits/negative.dm b/modular_dripstation/code/datums/traits/negative.dm
new file mode 100644
index 000000000000..a5b43d56450c
--- /dev/null
+++ b/modular_dripstation/code/datums/traits/negative.dm
@@ -0,0 +1,52 @@
+/datum/quirk/bad_touch
+ name = "Bad Touch"
+ desc = "You don't like hugs. You'd really prefer if people just left you alone."
+ icon = "tg-bad-touch"
+ mob_trait = TRAIT_BADTOUCH
+ value = -1
+ gain_text = span_danger("You just want people to leave you alone.")
+ lose_text = span_notice("You could use a big hug.")
+ medical_record_text = "Patient has disdain for being touched. Potentially has undiagnosed haphephobia."
+ mood_quirk = TRUE
+
+/datum/quirk/bad_touch/add(client/client_source)
+ RegisterSignals(quirk_holder, list(COMSIG_LIVING_GET_PULLED, COMSIG_CARBON_HELP_ACT), PROC_REF(uncomfortable_touch))
+
+/datum/quirk/bad_touch/remove()
+ UnregisterSignal(quirk_holder, list(COMSIG_LIVING_GET_PULLED, COMSIG_CARBON_HELP_ACT))
+
+/// Causes a negative moodlet to our quirk holder on signal
+/datum/quirk/bad_touch/proc/uncomfortable_touch(datum/source)
+ SIGNAL_HANDLER
+
+ if(quirk_holder.stat == DEAD)
+ return
+
+ new /obj/effect/temp_visual/annoyed(quirk_holder.loc)
+ var/datum/component/mood/mob_mood = quirk_holder.GetComponent(/datum/component/mood)
+ if(mob_mood.sanity <= SANITY_NEUTRAL)
+ SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "bad_touch", /datum/mood_event/very_bad_touch)
+ else
+ SEND_SIGNAL(quirk_holder, COMSIG_ADD_MOOD_EVENT, "bad_touch", /datum/mood_event/bad_touch)
+
+/datum/quirk/prosthetic_limb
+ value = -1
+
+/datum/quirk/prosthetic_limb/check_quirk(datum/preferences/prefs)
+ var/species_type = prefs.read_preference(/datum/preference/choiced/species)
+
+ if(species_type == /datum/species/ipc) // IPCs are already cybernetic
+ return "You already have cybernetic limbs!"
+ return FALSE
+
+/datum/quirk/prosthetic_limb/left_arm
+ value = -0.5
+
+/datum/quirk/prosthetic_limb/right_arm
+ value = -0.5
+
+/datum/quirk/prosthetic_limb/left_leg
+ value = -0.5
+
+/datum/quirk/prosthetic_limb/right_leg
+ value = -0.5
\ No newline at end of file
diff --git a/modular_dripstation/code/datums/traits/positive.dm b/modular_dripstation/code/datums/traits/positive.dm
new file mode 100644
index 000000000000..25491d6a5c5f
--- /dev/null
+++ b/modular_dripstation/code/datums/traits/positive.dm
@@ -0,0 +1,24 @@
+/datum/quirk/psychopathic
+ name = "Psychopathic"
+ desc = "You often hear to yourself: Hey, secy, how many animals have you killed as a child?"
+ icon = "meh"
+ value = 5
+ mob_trait = TRAIT_PSYCHOPATHIC
+ mood_quirk = TRUE
+ gain_text = span_danger("You don`t mind if they all die.")
+ lose_text = span_notice("Okey, time to touch some grass.")
+ medical_record_text = "The patient has a psychopathic personality disorder. It is normal for him to react socially distantly to certain events."
+
+/datum/quirk/psychopathic/add()
+ var/datum/component/mood/mood = quirk_holder.GetComponent(/datum/component/mood)
+ if(mood)
+ mood.mood_modifier -= 0.6
+
+/datum/quirk/psychopathic/remove()
+ if(quirk_holder)
+ var/datum/component/mood/mood = quirk_holder.GetComponent(/datum/component/mood)
+ if(mood)
+ mood.mood_modifier += 0.6
+
+/datum/quirk/apathetic
+ mob_trait = TRAIT_APATHETIC
\ No newline at end of file
diff --git a/modular_dripstation/code/game/effects/effects_foam.dm b/modular_dripstation/code/game/effects/effects_foam.dm
new file mode 100644
index 000000000000..c9f1518111a9
--- /dev/null
+++ b/modular_dripstation/code/game/effects/effects_foam.dm
@@ -0,0 +1,30 @@
+/obj/structure/foamedmetal/attackby(obj/item/W, mob/user, params)
+ ///A speed modifier for how fast the wall is build
+ var/platingmodifier = 1
+ if(HAS_TRAIT(user, TRAIT_QUICK_BUILD))
+ platingmodifier = 0.7
+ if(next_beep <= world.time)
+ next_beep = world.time + 1 SECONDS
+ playsound(src, 'sound/machines/clockcult/integration_cog_install.ogg', 50, TRUE)
+ add_fingerprint(user)
+
+ if(!istype(W, /obj/item/stack/sheet))
+ return ..()
+
+ var/obj/item/stack/sheet/sheet_for_plating = W
+ if(istype(sheet_for_plating, /obj/item/stack/sheet/metal))
+ if(sheet_for_plating.get_amount() < 2)
+ to_chat(user, span_warning("You need two sheets of iron to finish a wall on [src]!"))
+ return
+ to_chat(user, span_notice("You start adding plating to the foam structure..."))
+ if (do_after(user, 40 * platingmodifier, target = src))
+ if(!sheet_for_plating.use(2))
+ return
+ to_chat(user, span_notice("You add the plating."))
+ var/turf/T = get_turf(src)
+ T.place_on_top(/turf/closed/wall/metal_foam_base)
+ transfer_fingerprints_to(T)
+ qdel(src)
+ return
+
+ add_hiddenprint(user)
diff --git a/modular_dripstation/code/game/effects/temporary_visuals/misc.dm b/modular_dripstation/code/game/effects/temporary_visuals/misc.dm
new file mode 100644
index 000000000000..dd1e291b0133
--- /dev/null
+++ b/modular_dripstation/code/game/effects/temporary_visuals/misc.dm
@@ -0,0 +1,11 @@
+/obj/effect/temp_visual/annoyed
+ name = "annoyed"
+ icon = 'modular_dripstation/icons/effects/effects.dmi'
+ icon_state = "annoyed"
+ duration = 25
+
+/obj/effect/temp_visual/annoyed/Initialize(mapload)
+ . = ..()
+ pixel_x = rand(-4,0)
+ pixel_y = rand(8,12)
+ animate(src, pixel_y = pixel_y + 16, alpha = 0, time = duration)
\ No newline at end of file
diff --git a/modular_dripstation/code/game/gamemodes/nuclear/nuclear.dm b/modular_dripstation/code/game/gamemodes/nuclear/nuclear.dm
new file mode 100644
index 000000000000..8f1a6a509a25
--- /dev/null
+++ b/modular_dripstation/code/game/gamemodes/nuclear/nuclear.dm
@@ -0,0 +1,51 @@
+/datum/outfit/syndicate
+ id = /obj/item/card/id/syndicate/nuke
+
+/datum/outfit/syndicate/leader
+ gloves = /obj/item/clothing/gloves/combat
+
+/datum/outfit/syndicate/leader/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ ..()
+ var/datum/martial_art/cqc/justanop = new
+ justanop.teach(H)
+
+/datum/outfit/syndicate/no_crystals
+ var/faction = "The Syndicate"
+
+/datum/outfit/syndicate/no_crystals/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ to_chat(H, span_notice("You're an agent of [faction], sent to accompany the nuclear squad on their mission. \
+ Support your allies, and remember: Down with Nanotrasen."))
+ . = ..()
+
+/datum/outfit/syndicate/no_crystals/gorlex
+ name = "Syndicate Operative - Gorlex Reinforcement"
+ suit = /obj/item/clothing/suit/armor/vest/alt
+ head = /obj/item/clothing/head/helmet/swat
+ neck = /obj/item/clothing/neck/scarf/red
+ glasses = /obj/item/clothing/glasses/cold
+ faction = "the Gorlex Marauders"
+
+/datum/outfit/syndicate/no_crystals/cybersun
+ name = "Syndicate Operative - Cybersun Reinforcement"
+ uniform = /obj/item/clothing/under/syndicate/combat
+ suit = /obj/item/clothing/suit/jacket/leather/overcoat
+ gloves = /obj/item/clothing/gloves/fingerless
+ glasses = /obj/item/clothing/glasses/sunglasses
+ mask = /obj/item/clothing/mask/cigarette/cigar
+ faction = "Cybersun Industries"
+
+/datum/outfit/syndicate/no_crystals/donk
+ name = "Syndicate Operative - Donk Reinforcement"
+ suit = /obj/item/clothing/suit/hazardvest
+ head = /obj/item/clothing/head/hardhat/weldhat/orange
+ shoes = /obj/item/clothing/shoes/workboots
+ glasses = /obj/item/clothing/glasses/meson
+ faction = "the Donk Corporation"
+
+/datum/outfit/syndicate/no_crystals/waffle
+ name = "Syndicate Operative - Waffle Reinforcement"
+ uniform = /obj/item/clothing/under/syndicate/combat
+ suit = /obj/item/clothing/suit/armor/vest
+ head = /obj/item/clothing/head/helmet/blueshirt
+ glasses = /obj/item/clothing/glasses/welding
+ faction = "the Waffle Corporation"
\ No newline at end of file
diff --git a/modular_dripstation/code/game/machinery/dance_machine.dm b/modular_dripstation/code/game/machinery/dance_machine.dm
index f4adecefbb4f..0ba0736e150a 100644
--- a/modular_dripstation/code/game/machinery/dance_machine.dm
+++ b/modular_dripstation/code/game/machinery/dance_machine.dm
@@ -1,2 +1,5 @@
/obj/machinery/jukebox
icon = 'modular_dripstation/icons/obj/stationobjs.dmi'
+
+/obj/machinery/jukebox/disco
+ icon = 'icons/obj/stationobjs.dmi'
\ No newline at end of file
diff --git a/modular_dripstation/code/game/machinery/pod_fabricator.dm b/modular_dripstation/code/game/machinery/pod_fabricator.dm
new file mode 100644
index 000000000000..e6c9bd0f4154
--- /dev/null
+++ b/modular_dripstation/code/game/machinery/pod_fabricator.dm
@@ -0,0 +1,756 @@
+/obj/item/circuitboard/machine/pod_fab
+ name = "Space Pod Fabricator (Machine Board)"
+ icon_state = "science"
+ build_path = /obj/machinery/pod_fabricator
+ req_components = list(
+ /obj/item/stock_parts/matter_bin = 2,
+ /obj/item/stock_parts/manipulator = 1,
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stack/sheet/glass = 1)
+
+/datum/design/board/podfab
+ name = "Machine Design (Space Pod Fabricator Board)"
+ desc = "The circuit board for an Space Pod Fabricator."
+ id = "podfab"
+ build_path = /obj/machinery/pod_fabricator
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/wires/tesla_coil
+ proper_name = "Spacepod Fabricator"
+ randomize = TRUE //Only one wire don't need blueprints
+ holder_type = /obj/machinery/pod_fabricator
+
+/obj/machinery/pod_fabricator
+ icon = 'icons/obj/robotics.dmi'
+ icon_state = "fab-idle"
+ name = "spacepod fabricator"
+ desc = "Nothing is being built."
+ density = TRUE
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 20
+ active_power_usage = 5000
+
+ req_access = list(ACCESS_SECURE_TECH_STORAGE) //place mechanic access here
+ var/hacked = FALSE
+ ///World ticks the machine is electified for
+ var/seconds_electrified = MACHINE_NOT_ELECTRIFIED
+
+
+ circuit = /obj/item/circuitboard/machine/pod_fab
+ subsystem_type = /datum/controller/subsystem/processing/fastprocess
+ /// Controls whether or not the more dangerous designs have been unlocked by a head's id manually, rather than alert level unlocks
+ var/authorization_override = FALSE
+ /// ID card of the person using the machine for the purpose of tracking access
+ var/obj/item/card/id/id_card = new()
+ /// Current items in the build queue.
+ var/list/queue = list()
+ /// Whether or not the machine is building the entire queue automagically.
+ var/process_queue = FALSE
+
+ /// The current design datum that the machine is building.
+ var/datum/design/being_built
+ /// World time when the build will finish.
+ var/build_finish = 0
+ /// World time when the build started.
+ var/build_start = 0
+ /// Reference to all materials used in the creation of the item being_built.
+ var/list/build_materials
+ /// Part currently stored in the Exofab.
+ var/obj/item/stored_part
+
+ /// Coefficient for the speed of item building. Based on the installed parts.
+ var/time_coeff = 1
+ /// Coefficient for the efficiency of material usage in item building. Based on the installed parts.
+ var/component_coeff = 1
+
+ /// Reference to the techweb.
+ var/datum/techweb/stored_research
+
+ /// Whether the Exofab links to the ore silo on init. Special derelict or maintanance variants should set this to FALSE.
+ var/link_on_init = TRUE
+
+ /// Reference to a remote material inventory, such as an ore silo.
+ var/datum/component/remote_materials/rmat
+
+ /// A list of categories that valid pod fab design datums will broadly categorise themselves under.
+ var/list/part_sets = list(
+ "Spacepod Designs",
+ "Shuttle Machinery",
+ "Misc"
+ )
+
+/obj/machinery/pod_fabricator/Initialize(mapload)
+ stored_research = SSresearch.science_tech
+ rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init)
+ RefreshParts() //Recalculating local material sizes if the fab isn't linked
+ wires = new /datum/wires/mecha_part_fabricator(src)
+ return ..()
+
+/obj/machinery/pod_fabricator/Destroy()
+ QDEL_NULL(wires)
+ return ..()
+
+/obj/machinery/pod_fabricator/RefreshParts()
+ var/T = 0
+
+ //maximum stocking amount (default 300000, 600000 at T4)
+ for(var/obj/item/stock_parts/matter_bin/M in component_parts)
+ T += M.rating
+ rmat.set_local_size((200000 + (T*50000)))
+
+ //resources adjustment coefficient (1 -> 0.85 -> 0.7 -> 0.55)
+ T = 1.15
+ for(var/obj/item/stock_parts/micro_laser/Ma in component_parts)
+ T -= Ma.rating*0.15
+ component_coeff = T
+
+ //building time adjustment coefficient (1 -> 0.8 -> 0.6)
+ T = -1
+ for(var/obj/item/stock_parts/manipulator/Ml in component_parts)
+ T += Ml.rating
+ time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01)
+
+ // Adjust the build time of any item currently being built.
+ if(being_built)
+ var/last_const_time = build_finish - build_start
+ var/new_const_time = get_construction_time_w_coeff(initial(being_built.construction_time))
+ var/const_time_left = build_finish - world.time
+ var/new_build_time = (new_const_time / last_const_time) * const_time_left
+ build_finish = world.time + new_build_time
+
+ update_static_data(usr)
+
+/obj/machinery/pod_fabricator/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += span_notice("The status display reads: Storing up to [rmat.local_size] material units. Material consumption at [component_coeff*100]%. Build time reduced by [100-time_coeff*100]%.")
+
+/obj/machinery/pod_fabricator/attackby(obj/item/I, mob/living/user, params)
+ if(panel_open && is_wire_tool(I))
+ wires.interact(user)
+ return TRUE
+ if(I.GetID())
+ var/obj/item/card/id/C = I.GetID()
+ if(obj_flags & EMAGGED)
+ to_chat(user, span_warning("The authentication slot spits sparks at you and the display reads scrambled text!"))
+ do_sparks(1, FALSE, src)
+ authorization_override = TRUE //just in case it wasn't already for some reason. keycard reader is busted.
+ return
+ if(ACCESS_HEADS in C.access)
+ if(!authorization_override)
+ authorization_override = TRUE
+ to_chat(user, span_warning("You override the safety protocols on the [src], removing access restrictions from this terminal."))
+ else
+ authorization_override = FALSE
+ to_chat(user, span_notice("You reengage the safety protocols on the [src], restoring access restrictions to this terminal."))
+ update_static_data(user)
+ return
+ return ..()
+/**
+ * All the negative wire effects
+ * Break wire breaks one limb (Because pain is to be had)
+*/
+/obj/machinery/pod_fabricator/_try_interact(mob/user)
+ if(seconds_electrified && !(stat & NOPOWER))
+ if(shock(user, 100))
+ return
+ return ..()
+
+/obj/machinery/pod_fabricator/proc/wire_break(mob/user)
+ if(stat & (BROKEN|NOPOWER))
+ return FALSE
+ var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
+ s.set_up(5, 1, src)
+ s.start()
+ var/mob/living/carbon/C = user
+ var/datum/wound/blunt/severe/break_it = new
+ ///Picks limb to break. People with less limbs have a chance of it grapping at air
+ var/obj/item/bodypart/bone = C.get_bodypart(pick(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG))
+ if(bone && Adjacent(user))
+ to_chat(C,span_userdanger("The manipulator arms grapple after your [bone.name], attempting to break its bone!"))
+ break_it.apply_wound(bone)
+ bone.receive_damage(brute=50, updating_health=TRUE)
+ else
+ to_chat(C,span_userdanger("The manipulator arms attempt to grab one of your limbs, but grapple air instead!"))
+ qdel(break_it)
+
+/obj/machinery/pod_fabricator/proc/reset(wire)
+ switch(wire)
+ if(WIRE_HACK)
+ if(!wires.is_cut(wire))
+ hacked = FALSE
+/**
+ * Shock the passed in user
+ *
+ * This checks we have power and that the passed in prob is passed, then generates some sparks
+ * and calls electrocute_mob on the user
+ *
+ * Arguments:
+ * * user - the user to shock
+ * * prb - probability the shock happens
+ */
+/obj/machinery/pod_fabricator/proc/shock(mob/user, prb)
+ if(stat & (BROKEN|NOPOWER)) // unpowered, no shock
+ return FALSE
+ if(!prob(prb))
+ return FALSE
+ do_sparks(5, TRUE, src)
+ var/check_range = TRUE
+ if(electrocute_mob(user, get_area(src), src, 0.7, check_range))
+ return TRUE
+ else
+ return FALSE
+/**
+ * Generates an info list for a given part.
+ *
+ * Returns a list of part information.
+ * * D - Design datum to get information on.
+ * * categories - Boolean, whether or not to parse snowflake categories into the part information list.
+ */
+
+/obj/machinery/pod_fabricator/proc/output_part_info(datum/design/D, categories = FALSE)
+ var/cost = list()
+ for(var/c in D.materials)
+ var/datum/material/M = c
+ cost[M.name] = get_resource_cost_w_coeff(D, M)
+
+ var/obj/built_item = D.build_path
+
+ var/list/sub_category = null
+
+ if(categories)
+ var/module_types = D.departmental_flags
+ sub_category = list()
+ if(D.category == "Spacepod Designs" && module_types)
+ if(module_types & DEPARTMENTAL_FLAG_SECURITY)
+ sub_category += "Security"
+ if(module_types & DEPARTMENTAL_FLAG_CARGO)
+ sub_category += "Cargo"
+ if(module_types & DEPARTMENTAL_FLAG_SCIENCE)
+ sub_category += "Science"
+ if(module_types & DEPARTMENTAL_FLAG_ALL)
+ sub_category += "Civilian"
+ else
+ sub_category = "Equipment"
+
+ var/list/part = list(
+ "name" = D.name,
+ "desc" = initial(built_item.desc),
+ "printTime" = get_construction_time_w_coeff(initial(D.construction_time))/10,
+ "cost" = cost,
+ "id" = D.id,
+ "subCategory" = sub_category,
+ "searchMeta" = D.search_metadata
+ )
+
+ return part
+
+/**
+ * Generates a list of resources / materials available to this Exosuit Fab
+ *
+ * Returns null if there is no material container available.
+ * List format is list(material_name = list(amount = ..., ref = ..., etc.))
+ */
+/obj/machinery/pod_fabricator/proc/output_available_resources()
+ var/datum/component/material_container/materials = rmat.mat_container
+
+ var/list/material_data = list()
+
+ if(materials)
+ for(var/mat_id in materials.materials)
+ var/datum/material/M = mat_id
+ var/list/material_info = list()
+ var/amount = materials.materials[mat_id]
+
+ material_info = list(
+ "name" = M.name,
+ "ref" = REF(M),
+ "amount" = amount,
+ "sheets" = round(amount / MINERAL_MATERIAL_AMOUNT),
+ "removable" = amount >= MINERAL_MATERIAL_AMOUNT
+ )
+
+ material_data += list(material_info)
+
+ return material_data
+
+ return null
+
+/**
+ * Intended to be called when an item starts printing.
+ *
+ * Adds the overlay to show the fab working and sets active power usage settings.
+ */
+/obj/machinery/pod_fabricator/proc/on_start_printing()
+ add_overlay("fab-active")
+ use_power = ACTIVE_POWER_USE
+
+/**
+ * Intended to be called when the exofab has stopped working and is no longer printing items.
+ *
+ * Removes the overlay to show the fab working and sets idle power usage settings. Additionally resets the description and turns off queue processing.
+ */
+/obj/machinery/pod_fabricator/proc/on_finish_printing()
+ cut_overlay("fab-active")
+ use_power = IDLE_POWER_USE
+ desc = initial(desc)
+ process_queue = FALSE
+
+/**
+ * Calculates resource/material costs for printing an item based on the machine's resource coefficient.
+ *
+ * Returns a list of k,v resources with their amounts.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
+/obj/machinery/pod_fabricator/proc/get_resources_w_coeff(datum/design/D)
+ var/list/resources = list()
+ for(var/R in D.materials)
+ var/datum/material/M = R
+ resources[M] = get_resource_cost_w_coeff(D, M)
+ return resources
+
+/**
+ * Checks if the Exofab has enough resources to print a given item.
+ *
+ * Returns FALSE if the design has no reagents used in its construction (?) or if there are insufficient resources.
+ * Returns TRUE if there are sufficient resources to print the item.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
+/obj/machinery/pod_fabricator/proc/check_resources(datum/design/D)
+ if(length(D.reagents_list)) // No reagents storage - no reagent designs.
+ return FALSE
+ var/datum/component/material_container/materials = rmat.mat_container
+ if(materials.has_materials(get_resources_w_coeff(D)))
+ return TRUE
+ return FALSE
+
+/**
+ * Attempts to build the next item in the build queue.
+ *
+ * Returns FALSE if either there are no more parts to build or the next part is not buildable.
+ * Returns TRUE if the next part has started building.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/pod_fabricator/proc/build_next_in_queue(verbose = TRUE)
+ if(!length(queue))
+ return FALSE
+
+ var/datum/design/D = queue[1]
+ if(build_part(D, verbose))
+ remove_from_queue(1)
+ return TRUE
+
+ return FALSE
+
+/**
+ * Starts the build process for a given design datum.
+ *
+ * Returns FALSE if the procedure fails. Returns TRUE when being_built is set.
+ * Uses materials.
+ * * D - Design datum to attempt to print.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/pod_fabricator/proc/build_part(datum/design/D, verbose = TRUE)
+ if(!D)
+ return FALSE
+
+ var/datum/component/material_container/materials = rmat.mat_container
+ if (!materials)
+ if(verbose)
+ say("No access to material storage, please contact the quartermaster.")
+ return FALSE
+ if (rmat.on_hold())
+ if(verbose)
+ say("Mineral access is on hold, please contact the quartermaster.")
+ return FALSE
+ if(!check_resources(D))
+ if(verbose)
+ say("Not enough resources. Processing stopped.")
+ return FALSE
+
+ build_materials = get_resources_w_coeff(D)
+
+ materials.use_materials(build_materials)
+ being_built = D
+ build_finish = world.time + get_construction_time_w_coeff(initial(D.construction_time))
+ build_start = world.time
+ desc = "It's building \a [D.name]."
+
+ rmat.silo_log(src, "built", -1, "[D.name]", build_materials)
+
+ return TRUE
+
+/obj/machinery/pod_fabricator/process()
+ // Deelectrifies the machine
+ if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
+ seconds_electrified--
+
+ // If there's a stored part to dispense due to an obstruction, try to dispense it.
+ if(stored_part)
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ return TRUE
+
+ say("Obstruction cleared. \The [stored_part] is complete.")
+ stored_part.forceMove(exit)
+ stored_part = null
+
+ // If there's nothing being built, try to build something
+ if(!being_built)
+ // If we're not processing the queue anymore or there's nothing to build, end processing.
+ if(!process_queue || !build_next_in_queue())
+ on_finish_printing()
+ end_processing()
+ return TRUE
+ on_start_printing()
+
+ // If there's an item being built, check if it is complete.
+ if(being_built && (build_finish < world.time))
+ // Then attempt to dispense it and if appropriate build the next item.
+ dispense_built_part(being_built)
+ if(process_queue)
+ build_next_in_queue(FALSE)
+ return TRUE
+
+
+/**
+ * Dispenses a part to the tile infront of the Exosuit Fab.
+ *
+ * Returns FALSE is the machine cannot dispense the part on the appropriate turf.
+ * Return TRUE if the part was successfully dispensed.
+ * * D - Design datum to attempt to dispense.
+ */
+/obj/machinery/pod_fabricator/proc/dispense_built_part(datum/design/D)
+ var/obj/item/I = new D.build_path(src)
+ //I.set_custom_materials(build_materials)
+ being_built = null
+
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ say("Error! Part outlet is obstructed.")
+ desc = "It's trying to dispense \a [D.name], but the part outlet is obstructed."
+ stored_part = I
+ return FALSE
+
+ say("\The [I] is complete.")
+ I.forceMove(exit)
+ return TRUE
+
+/**
+ * Adds a list of datum designs to the build queue.
+ *
+ * Will only add designs that are in this machine's stored techweb.
+ * Does final checks for datum IDs and makes sure this machine can build the designs.
+ * * part_list - List of datum design ids for designs to add to the queue.
+ */
+/obj/machinery/pod_fabricator/proc/add_part_set_to_queue(list/part_list, mob/user)
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if((D.id in part_list) && (!D.combat_design || combat_parts_allowed(user)))
+ add_to_queue(D, user)
+
+/**
+ * Adds a datum design to the build queue.
+ *
+ * Returns TRUE if successful and FALSE if the design was not added to the queue.
+ * * D - Datum design to add to the queue.
+ */
+/obj/machinery/pod_fabricator/proc/add_to_queue(datum/design/D, mob/user)
+ if(D.combat_design && !combat_parts_allowed(user))
+ return FALSE
+ if(!istype(queue))
+ queue = list()
+ if(D)
+ queue[++queue.len] = D
+ return TRUE
+ return FALSE
+
+/**
+ * Removes datum design from the build queue based on index.
+ *
+ * Returns TRUE if successful and FALSE if a design was not removed from the queue.
+ * * index - Index in the build queue of the element to remove.
+ */
+/obj/machinery/pod_fabricator/proc/remove_from_queue(index)
+ if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>length(queue)))
+ return FALSE
+ queue.Cut(index,++index)
+ return TRUE
+
+/**
+ * Generates a list of parts formatted for tgui based on the current build queue.
+ *
+ * Returns a formatted list of lists containing formatted part information for every part in the build queue.
+ */
+/obj/machinery/pod_fabricator/proc/list_queue()
+ if(!istype(queue) || !length(queue))
+ return null
+
+ var/list/queued_parts = list()
+ for(var/datum/design/D in queue)
+ var/list/part = output_part_info(D)
+ queued_parts += list(part)
+ return queued_parts
+
+/**
+ * Calculates the coefficient-modified resource cost of a single material component of a design's recipe.
+ *
+ * Returns coefficient-modified resource cost for the given material component.
+ * * D - Design datum to pull the resource cost from.
+ * * resource - Material datum reference to the resource to calculate the cost of.
+ * * roundto - Rounding value for round() proc
+ */
+/obj/machinery/pod_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, datum/material/resource, roundto = 1)
+ return round(D.materials[resource]*component_coeff, roundto)
+
+/**
+ * Calculates the coefficient-modified build time of a design.
+ *
+ * Returns coefficient-modified build time of a given design.
+ * * D - Design datum to calculate the modified build time of.
+ * * roundto - Rounding value for round() proc
+ */
+/obj/machinery/pod_fabricator/proc/get_construction_time_w_coeff(construction_time, roundto = 1) //aran
+ return round(construction_time*time_coeff, roundto)
+
+/obj/machinery/pod_fabricator/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
+ )
+
+/obj/machinery/pod_fabricator/ui_status(mob/user)
+ if(stat & BROKEN || panel_open)
+ return UI_CLOSE
+ return ..()
+
+/obj/machinery/pod_fabricator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ExosuitFabricator")
+ ui.open()
+
+/obj/machinery/pod_fabricator/ui_static_data(mob/user)
+ var/list/data = list()
+
+ var/list/final_sets = part_sets.Copy()
+ var/list/buildable_parts = list()
+
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if(D.combat_design && !combat_parts_allowed(user)) // Yogs -- ID swiping for combat parts
+ continue
+ // This is for us.
+ var/list/part = output_part_info(D, TRUE)
+ for(var/cat in part_sets)
+ // Find all matching categories.
+ if(!(cat in D.category))
+ continue
+ buildable_parts[cat] += list(part)
+ data["partSets"] = final_sets
+ data["buildableParts"] = buildable_parts
+
+ return data
+
+/obj/machinery/pod_fabricator/ui_data(mob/user)
+ var/list/data = list()
+ data["materials"] = output_available_resources()
+
+ if(being_built)
+ var/list/part = list(
+ "name" = being_built.name,
+ "duration" = build_finish - world.time,
+ "printTime" = get_construction_time_w_coeff(initial(being_built.construction_time))
+ )
+ data["buildingPart"] = part
+ else
+ data["buildingPart"] = null
+
+ data["queue"] = list_queue()
+
+ if(stored_part)
+ data["storedPart"] = stored_part.name
+ else
+ data["storedPart"] = null
+
+ data["isProcessingQueue"] = process_queue
+ data["authorization"] = authorization_override
+ data["user_clearance"] = head_or_silicon(user)
+ data["alert_level"] = GLOB.security_level
+ data["combat_parts_allowed"] = combat_parts_allowed(user)
+ data["emagged"] = (obj_flags & EMAGGED)
+ data["silicon_user"] = issilicon(user)
+
+ return data
+
+/// Updates the various authorization checks used to determine if combat parts are available to the current user
+/obj/machinery/pod_fabricator/proc/combat_parts_allowed(mob/user)
+ return authorization_override || GLOB.security_level >= SEC_LEVEL_RED || head_or_silicon(user)
+
+/// made as a lazy check to allow silicons full access always
+/obj/machinery/pod_fabricator/proc/head_or_silicon(mob/user)
+ if(issilicon(user))
+ return TRUE
+ id_card = user.get_idcard(hand_first = TRUE)
+ return ACCESS_HEADS in id_card?.access
+
+/obj/machinery/pod_fabricator/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ . = TRUE
+
+ add_fingerprint(usr)
+ usr.set_machine(src)
+
+ switch(action)
+ if("sync_rnd")
+ // Syncronises designs on interface with R&D techweb.
+ update_static_data(usr)
+ say("Successfully synchronized with R&D server.")
+ return
+ if("add_queue_set")
+ // Add all parts of a set to queue
+ var/part_list = params["part_list"]
+ add_part_set_to_queue(part_list, usr)
+ return
+ if("add_queue_part")
+ // Add a specific part to queue
+ var/T = params["id"]
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if((D.id == T))
+ add_to_queue(D, usr)
+ break
+ return
+ if("del_queue_part")
+ // Delete a specific from from the queue
+ var/index = text2num(params["index"])
+ remove_from_queue(index)
+ return
+ if("clear_queue")
+ // Delete everything from queue
+ queue.Cut()
+ return
+ if("build_queue")
+ // Build everything in queue
+ if(process_queue)
+ return
+ process_queue = TRUE
+
+ if(!being_built)
+ begin_processing()
+ return
+ if("stop_queue")
+ // Pause queue building. Also known as stop.
+ process_queue = FALSE
+ return
+ if("build_part")
+ // Build a single part
+ if(being_built || process_queue)
+ return
+
+ var/id = params["id"]
+ var/datum/design/D = SSresearch.techweb_design_by_id(id)
+
+ if(!(D.id == id))
+ return
+
+ if(build_part(D))
+ on_start_printing()
+ begin_processing()
+
+ return
+ if("move_queue_part")
+ // Moves a part up or down in the queue.
+ var/index = text2num(params["index"])
+ var/new_index = index + text2num(params["newindex"])
+ if(isnum(index) && isnum(new_index) && ISINTEGER(index) && ISINTEGER(new_index))
+ if(ISINRANGE(new_index,1,length(queue)))
+ queue.Swap(index,new_index)
+ return
+ if("remove_mat")
+ // Remove a material from the fab
+ var/mat_ref = params["ref"]
+ var/amount = text2num(params["amount"])
+ var/datum/material/mat = locate(mat_ref)
+ eject_sheets(mat, amount)
+ return
+
+ return FALSE
+
+/**
+ * Eject material sheets.
+ *
+ * Returns the number of sheets successfully ejected.
+ * eject_sheet - Byond REF of the material to eject.
+ * eject_amt - Number of sheets to attempt to eject.
+ */
+/obj/machinery/pod_fabricator/proc/eject_sheets(eject_sheet, eject_amt)
+ var/datum/component/material_container/mat_container = rmat.mat_container
+ if (!mat_container)
+ say("No access to material storage, please contact the quartermaster.")
+ return 0
+ if (rmat.on_hold())
+ say("Mineral access is on hold, please contact the quartermaster.")
+ return 0
+ var/count = mat_container.retrieve_sheets(text2num(eject_amt), eject_sheet, drop_location())
+ var/list/matlist = list()
+ matlist[eject_sheet] = text2num(eject_amt)
+ rmat.silo_log(src, "ejected", -count, "sheets", matlist)
+ return count
+
+/obj/machinery/pod_fabricator/proc/AfterMaterialInsert(item_inserted, id_inserted, amount_inserted)
+ var/datum/material/M = id_inserted
+ add_overlay("fab-load-[M.name]")
+ addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[M.name]"), 10)
+
+/obj/machinery/pod_fabricator/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ if(being_built)
+ to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion."))
+ return FALSE
+ return default_deconstruction_screwdriver(user, "fab-o", "fab-idle", I)
+
+/obj/machinery/pod_fabricator/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+ if(being_built)
+ to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion."))
+ return FALSE
+ return default_deconstruction_crowbar(I)
+
+/obj/machinery/pod_fabricator/proc/is_insertion_ready(mob/user)
+ if(panel_open)
+ to_chat(user, span_warning("You can't load [src] while it's panel is opened!"))
+ return FALSE
+ if(being_built)
+ to_chat(user, span_warning("\The [src] is currently processing! Please wait until completion."))
+ return FALSE
+ return TRUE
+
+/obj/machinery/pod_fabricator/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ to_chat(user, span_warning("[src] has no functional safeties to emag."))
+ return FALSE
+ do_sparks(1, FALSE, src)
+ to_chat(user, span_notice("You short out [src]'s safeties."))
+ authorization_override = TRUE
+ obj_flags |= EMAGGED
+ update_static_data(user)
+ return TRUE
+
+
+/obj/machinery/pod_fabricator/maint
+ link_on_init = FALSE
+
+/obj/machinery/pod_fabricator/ruin
+ link_on_init = FALSE
+ authorization_override = TRUE
+ hacked = TRUE
+
+/obj/machinery/pod_fabricator/ruin/Initialize(mapload)
+ . = ..()
+ stored_research = SSresearch.ruin_tech
diff --git a/modular_dripstation/code/game/mecha/cargo_hauler.dm b/modular_dripstation/code/game/mecha/cargo_hauler.dm
new file mode 100644
index 000000000000..c6a106c7b396
--- /dev/null
+++ b/modular_dripstation/code/game/mecha/cargo_hauler.dm
@@ -0,0 +1,51 @@
+GLOBAL_DATUM(cargo_ripley, /obj/mecha/working/ripley/cargo)
+
+/obj/mecha/working/ripley/cargo
+ desc = "An ailing, old, repurposed cargo hauler. Most of its equipment wires are frayed or missing and its frame is rusted. You should handle best queen carefully."
+ name = "\improper APLU \"Queen Bess II\""
+ icon_state = "hauler"
+ silicon_icon_state = "hauler-empty"
+ icon = 'modular_dripstation/icons/mob/mecha/cargo_hauler.dmi'
+ fast_pressure_step_in = 1 //step_in while in low pressure conditions, fast as fuck boi
+ slow_pressure_step_in = 1.3 //step_in while in normal pressure conditions
+ step_in = 1.3
+ max_equip = 5 //modified exoskeleton power drive
+ max_integrity = 150 //Lesser health then have normal RIPLEY mech, so it's harder to use as a weapon.
+ obj_integrity = 75 //Starting at low health
+ internals_req_access = list(ACCESS_CARGO, ACCESS_MECH_SCIENCE) //Giving access to cargotech & robo
+
+/obj/mecha/working/ripley/cargo/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += "Mech`s power drive looks modified."
+
+/obj/mecha/working/ripley/cargo/attack_hand(mob/living/carbon/human/user, params)
+ . = ..()
+ if(user.a_intent == INTENT_DISARM)
+ user.say("All hail the queen!")
+
+/obj/mecha/working/ripley/cargo/Initialize(mapload)
+ . = ..()
+ if(cell)
+ cell.charge = FLOOR(cell.charge * 0.33, 1) //Starts at very low charge
+
+ //Attach hydraulic clamp ONLY
+ var/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp/HC = new
+ HC.attach(src)
+
+ take_damage(50, sound_effect=FALSE) //Low starting health
+ if(!GLOB.cargo_ripley && mapload)
+ GLOB.cargo_ripley = src
+
+/obj/mecha/working/ripley/cargo/Destroy()
+ if(GLOB.cargo_ripley == src)
+ GLOB.cargo_ripley = null
+
+ return ..()
+
+/obj/structure/mecha_wreckage/ripley/cargo
+ name = "\improper APLU \"Queen Bess II\ wreckage"
+ desc = "Oh no, Bessy!"
+ icon_state = "hauler-broken"
+ icon = 'modular_dripstation/icons/mob/mecha/cargo_hauler.dmi'
+ orig_mecha = /obj/mecha/working/ripley/cargo
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/boomerang.dm b/modular_dripstation/code/game/objects/items/bepis_items/boomerang.dm
new file mode 100644
index 000000000000..e515a2776e17
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/boomerang.dm
@@ -0,0 +1,34 @@
+//bepis boomerang
+/obj/item/melee/baton/boomerang
+ name = "\improper OZtek Boomerang"
+ desc = "A device invented in 2486 for the great Space Emu War by the confederacy of Australicus, these high-tech boomerangs also work exceptionally well at stunning crewmembers. Just be careful to catch it when thrown!"
+ throw_speed = 2
+ icon = 'modular_dripstation/icons/obj/weapons/security.dmi'
+ lefthand_file = 'modular_dripstation/icons/mob/inhands/security_lefthand.dmi'
+ righthand_file = 'modular_dripstation/icons/mob/inhands/security_righthand.dmi'
+ icon_state = "boomerang"
+ item_state = "boomerang"
+ force = 5
+ throwforce = 5
+ throw_range = 5
+ hitcost = 2000
+ throw_hit_chance = 99 //Have you prayed today?
+ custom_materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 1000)
+
+/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ if(!status)
+ return ..()
+ var/caught = hit_atom.hitby(src, skipcatch = FALSE, hitpush = FALSE, throwingdatum = throwingdatum)
+ if(isliving(hit_atom) && !iscyborg(hit_atom) && !caught && prob(throw_hit_chance))//if they are a living creature and they didn't catch it
+ baton_stun(hit_atom, thrownby)
+ throw_at(thrownby, throw_range+3, throw_speed, null)
+ ..()
+
+/obj/item/melee/baton/boomerang/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, quickstart = TRUE)
+ if(iscarbon(thrower))
+ var/mob/living/carbon/C = thrower
+ C.throw_mode_on()
+ ..()
+
+/obj/item/melee/baton/boomerang/loaded //Same as above, comes with a cell.
+ preload_cell_type = /obj/item/stock_parts/cell/high
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/eng_gloves.dm b/modular_dripstation/code/game/objects/items/bepis_items/eng_gloves.dm
new file mode 100644
index 000000000000..039bc412e114
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/eng_gloves.dm
@@ -0,0 +1,11 @@
+/obj/item/clothing/gloves/tinkerer
+ name = "tinker's gloves"
+ desc = "Overdesigned engineering gloves that have automated construction subrutines dialed in, allowing for faster construction while worn."
+ item_state = "concussive_gauntlets"
+ icon_state = "concussive_gauntlets"
+ icon = 'modular_dripstation/icons/obj/clothing/gloves.dmi'
+ mob_overlay_icon = 'modular_dripstation/icons/mob/clothing/hands.dmi'
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 70, RAD = 0, FIRE = 70, ACID = 50, ELECTRIC = 80)
+ clothing_flags = list(TRAIT_QUICK_BUILD)
+ custom_materials = list(/datum/material/iron= 2000, /datum/material/silver= 1500, /datum/material/gold = 1000)
+ resistance_flags = NONE
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/explorerpin.dm b/modular_dripstation/code/game/objects/items/bepis_items/explorerpin.dm
new file mode 100644
index 000000000000..f8b7870f8c3e
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/explorerpin.dm
@@ -0,0 +1,14 @@
+// Explorer Firing Pin- Prevents use on station Z-Level, so it's justifiable to give Explorers guns that don't suck.
+/obj/item/firing_pin/explorer
+ name = "outback firing pin"
+ desc = "A firing pin used by the austrailian defense force, retrofit to prevent weapon discharge on the station."
+ icon = 'modular_dripstation/icons/obj/device.dmi'
+ icon_state = "firing_pin_explorer"
+ fail_message = "Cannot fire while on station, mate!"
+
+// This checks that the user isn't on the station Z-level.
+/obj/item/firing_pin/explorer/pin_auth(mob/living/user)
+ var/turf/station_check = get_turf(user)
+ if(!station_check || is_station_level(station_check.z))
+ return FALSE
+ return TRUE
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/hypnochair.dm b/modular_dripstation/code/game/objects/items/bepis_items/hypnochair.dm
new file mode 100644
index 000000000000..9b5b5dcf4b26
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/hypnochair.dm
@@ -0,0 +1,223 @@
+/obj/item/circuitboard/machine/hypnochair
+ name = "Enhanced Interrogation Chamber"
+ build_path = /obj/machinery/hypnochair
+ icon_state = "security"
+ req_components = list(
+ /obj/item/stock_parts/micro_laser = 2,
+ /obj/item/stock_parts/scanning_module = 2
+ )
+
+
+/obj/machinery/hypnochair
+ name = "enhanced interrogation chamber"
+ desc = "A device used to perform \"enhanced interrogation\" through invasive mental conditioning."
+ icon = 'modular_dripstation/icons/obj/hypnochair.dmi'
+ icon_state = "hypnochair"
+ base_icon_state = "hypnochair"
+ circuit = /obj/item/circuitboard/machine/hypnochair
+ density = TRUE
+ opacity = FALSE
+
+ var/mob/living/carbon/victim = null ///Keeps track of the victim to apply effects if it teleports away
+ var/interrogating = FALSE ///Is the device currently interrogating someone?
+ var/start_time = 0 ///Time when the interrogation was started, to calculate effect in case of interruption
+ var/trigger_phrase = "" ///Trigger phrase to implant
+ var/timerid = 0 ///Timer ID for interrogations
+ var/message_cooldown = 0 ///Cooldown for breakout message
+ var/resisting = FALSE ///Yeah, shitcode my beloved
+
+/obj/machinery/hypnochair/Initialize(mapload)
+ . = ..()
+ open_machine()
+ update_icon()
+
+/obj/machinery/hypnochair/attackby(obj/item/I, mob/user, params)
+ if(!occupant && default_deconstruction_screwdriver(user, icon_state, icon_state, I))
+ update_icon()
+ return
+ if(default_pry_open(I))
+ return
+ if(default_deconstruction_crowbar(I))
+ return
+ return ..()
+
+/obj/machinery/hypnochair/ui_state(mob/user)
+ return GLOB.notcontained_state
+
+/obj/machinery/hypnochair/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "HypnoChair", name)
+ ui.open()
+
+/obj/machinery/hypnochair/ui_data()
+ var/list/data = list()
+ var/mob/living/mob_occupant = occupant
+
+ data["occupied"] = mob_occupant ? 1 : 0
+ data["open"] = state_open
+ data["interrogating"] = interrogating
+
+ data["occupant"] = list()
+ if(mob_occupant)
+ data["occupant"]["name"] = mob_occupant.name
+ data["occupant"]["stat"] = mob_occupant.stat
+
+ data["trigger"] = trigger_phrase
+
+ return data
+
+/obj/machinery/hypnochair/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("door")
+ if(state_open)
+ close_machine()
+ else
+ if(!interrogating)
+ open_machine()
+ . = TRUE
+ if("set_phrase")
+ set_phrase(params["phrase"])
+ . = TRUE
+ if("interrogate")
+ if(!interrogating)
+ interrogate()
+ else
+ interrupt_interrogation()
+ . = TRUE
+
+/obj/machinery/hypnochair/proc/set_phrase(phrase)
+ trigger_phrase = phrase
+
+/obj/machinery/hypnochair/proc/interrogate()
+ if(!trigger_phrase)
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, TRUE)
+ return
+ var/mob/living/carbon/C = occupant
+ if(!istype(C))
+ playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, TRUE)
+ return
+ victim = C
+ if(C.get_eye_protection() <= 0)
+ to_chat(C, span_warning("Strobing coloured lights assault you relentlessly! You're losing your ability to think straight!"))
+ C.become_blind(HYPNOCHAIR_TRAIT)
+ ADD_TRAIT(C, TRAIT_DEAF, HYPNOCHAIR_TRAIT)
+ interrogating = TRUE
+ START_PROCESSING(SSobj, src)
+ start_time = world.time
+ update_icon()
+ timerid = addtimer(CALLBACK(src, PROC_REF(finish_interrogation)), 450, TIMER_STOPPABLE)
+
+/obj/machinery/hypnochair/process(delta_time)
+ var/mob/living/carbon/C = occupant
+ if(!istype(C) || C != victim)
+ interrupt_interrogation()
+ return
+ if(DT_PROB(5, delta_time) && !(C.get_eye_protection() > 0))
+ to_chat(C, "[pick(\
+ "...blue... red... green... blue, red, green, blueredgreen[span_small("blueredgreen")]",\
+ "...pretty colors...",\
+ "...you keep hearing words, but you can't seem to understand them...",\
+ "...so peaceful...",\
+ "...an annoying buzz in your ears..."\
+ )]")
+
+ use_power(active_power_usage * delta_time)
+
+/obj/machinery/hypnochair/proc/finish_interrogation()
+ interrogating = FALSE
+ STOP_PROCESSING(SSobj, src)
+ update_icon()
+ var/temp_trigger = trigger_phrase
+ trigger_phrase = "" //Erase evidence, in case the subject is able to look at the panel afterwards
+ audible_message(span_notice("[src] pings!"))
+ playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
+
+ if(QDELETED(victim) || victim != occupant)
+ victim = null
+ return
+ victim.cure_blind(HYPNOCHAIR_TRAIT)
+ REMOVE_TRAIT(victim, TRAIT_DEAF, HYPNOCHAIR_TRAIT)
+ if(!(victim.get_eye_protection() > 0))
+ victim.cure_trauma_type(/datum/brain_trauma/severe/hypnotic_trigger, TRAUMA_RESILIENCE_SURGERY)
+ if(prob(90))
+ victim.gain_trauma(new /datum/brain_trauma/severe/hypnotic_trigger(temp_trigger), TRAUMA_RESILIENCE_SURGERY)
+ else
+ victim.gain_trauma(new /datum/brain_trauma/severe/hypnotic_stupor(), TRAUMA_RESILIENCE_SURGERY)
+ victim = null
+
+/obj/machinery/hypnochair/proc/interrupt_interrogation()
+ deltimer(timerid)
+ interrogating = FALSE
+ STOP_PROCESSING(SSobj, src)
+ update_icon()
+
+ if(QDELETED(victim))
+ victim = null
+ return
+ victim.cure_blind(HYPNOCHAIR_TRAIT)
+ REMOVE_TRAIT(victim, TRAIT_DEAF, HYPNOCHAIR_TRAIT)
+ if(!(victim.get_eye_protection() > 0))
+ var/time_diff = world.time - start_time
+ switch(time_diff)
+ if(0 to 100)
+ victim.adjust_confusion(10 SECONDS)
+ victim.set_dizzy_if_lower(100 SECONDS)
+ victim.adjust_eye_blur(100)
+ if(101 to 200)
+ victim.adjust_confusion(15 SECONDS)
+ victim.set_dizzy_if_lower(200 SECONDS)
+ victim.adjust_eye_blur(200)
+ if(prob(25))
+ victim.apply_status_effect(/datum/status_effect/trance, rand(50,150), FALSE)
+ if(201 to INFINITY)
+ victim.adjust_confusion(20 SECONDS)
+ victim.set_dizzy_if_lower(300 SECONDS)
+ victim.adjust_eye_blur(300)
+ if(prob(65))
+ victim.apply_status_effect(/datum/status_effect/trance, rand(50,150), FALSE)
+ victim = null
+
+/obj/machinery/hypnochair/update_icon_state()
+ if((stat & MAINT) || panel_open)
+ icon_state = "[base_icon_state][state_open ? "_open" : null]"+"_maintenance"
+ else
+ icon_state = "[base_icon_state][state_open ? "_open" : null][occupant ? "_[interrogating ? "active" : "occupied"]" : null]"
+ return ..()
+
+/obj/machinery/hypnochair/update_icon()
+ . = ..()
+ update_icon_state()
+
+/obj/machinery/hypnochair/container_resist(mob/living/user)
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \
+ span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(600)].)"), \
+ span_hear("You hear a metallic creaking from [src]."))
+ if(do_after(user,(600), target = src))
+ if(!user || user.stat != CONSCIOUS || user.loc != src || state_open)
+ return
+ user.visible_message(span_warning("[user] successfully broke out of [src]!"), \
+ span_notice("You successfully break out of [src]!"))
+ open_machine()
+
+/obj/machinery/hypnochair/relaymove(mob/living/user, direction)
+ if(message_cooldown <= world.time)
+ message_cooldown = world.time + 50
+ to_chat(user, span_warning("[src]'s door won't budge!"))
+ if(resisting)
+ return
+ container_resist(user)
+ resisting = TRUE
+
+
+/obj/machinery/hypnochair/MouseDrop_T(mob/target, mob/user)
+ if(HAS_TRAIT(user, TRAIT_UI_BLOCKED) || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser())
+ return
+
+ close_machine(target)
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/lava_rods.dm b/modular_dripstation/code/game/objects/items/bepis_items/lava_rods.dm
new file mode 100644
index 000000000000..ad31ec9f5b7e
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/lava_rods.dm
@@ -0,0 +1,84 @@
+/obj/item/stack/rods/lava
+ name = "heat resistant rod"
+ desc = "Treated, specialized iron rods. When exposed to the vaccum of space their coating breaks off, but they can hold up against the extreme heat of active lava."
+ singular_name = "heat resistant rod"
+ icon_state = "rods"
+ item_state = "rods"
+ color = "#5286b9ff"
+ flags_1 = CONDUCT_1
+ w_class = WEIGHT_CLASS_NORMAL
+ materials = list(/datum/material/iron = 1000, /datum/material/plasma = 500, /datum/material/titanium = 2000)
+ max_amount = 30
+ resistance_flags = FIRE_PROOF | LAVA_PROOF
+ merge_type = /obj/item/stack/rods/lava
+
+/obj/item/stack/rods/lava/thirty
+ amount = 30
+
+/obj/structure/lattice/lava
+ name = "heatproof support lattice"
+ desc = "A specialized support beam for building across lava. Watch your step."
+ icon = 'icons/obj/smooth_structures/catwalk.dmi'
+ icon_state = "catwalk"
+ number_of_rods = 1
+ color = "#5286b9ff"
+ obj_flags = CAN_BE_HIT
+ resistance_flags = FIRE_PROOF | LAVA_PROOF
+
+/obj/structure/lattice/lava/over
+ layer = CATWALK_LAYER
+ plane = GAME_PLANE
+
+/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.")
+
+/obj/structure/lattice/lava/attackby(obj/item/C, mob/user, params)
+ . = ..()
+ if(istype(C, /obj/item/stack/tile/plasteel))
+ var/obj/item/stack/tile/plasteel/P = C
+ if(P.use(1))
+ to_chat(user, span_notice("You construct a floor plating, as lava settles around the rods."))
+ playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
+ new /turf/open/floor/plating(locate(x, y, z))
+ else
+ to_chat(user, span_warning("You need one floor tile to build atop [src]."))
+ return
+
+/turf/open/lava/attackby(obj/item/C, mob/user, params)
+ ..()
+ if(istype(C, /obj/item/stack/rods/lava))
+ var/obj/item/stack/rods/lava/R = C
+ var/obj/structure/lattice/lava/H = locate(/obj/structure/lattice/lava, src)
+ if(H)
+ to_chat(user, span_warning("There is already a lattice here!"))
+ return
+ if(R.use(1))
+ to_chat(user, span_notice("You construct a lattice."))
+ playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
+ new /obj/structure/lattice/lava(locate(x, y, z))
+ else
+ to_chat(user, span_warning("You need one rod to build a heatproof lattice."))
+ return
+ // Light a cigarette in the lava
+ if(istype(C, /obj/item/clothing/mask/cigarette))
+ var/obj/item/clothing/mask/cigarette/ciggie = C
+ if(ciggie.lit)
+ to_chat(user, span_warning("The [ciggie.name] is already lit!"))
+ return TRUE
+ var/clumsy_modifier = HAS_TRAIT(user, TRAIT_CLUMSY) ? 2 : 1
+ if(prob(25 * clumsy_modifier ))
+ ciggie.light(span_warning("[user] expertly dips \the [ciggie.name] into [src], along with the rest of [user.p_their()] arm. What a dumbass."))
+ var/obj/item/bodypart/affecting = user.get_active_hand()
+ affecting?.receive_damage(burn = 90)
+ else
+ ciggie.light(span_rose("[user] expertly dips \the [ciggie.name] into [src], lighting it with the scorching heat of the planet. Witnessing such a feat is almost enough to make you cry."))
+ return TRUE
+
+/turf/open/lava/is_safe()
+ //if anything matching this typecache is found in the lava, we don't burn things
+ var/static/list/lava_safeties_typecache = typecacheof(list(/obj/structure/lattice/catwalk, /obj/structure/lattice/lava, /obj/structure/stone_tile))
+ var/list/found_safeties = typecache_filter_list(contents, lava_safeties_typecache)
+ for(var/obj/structure/stone_tile/S in found_safeties)
+ if(S.fallen)
+ LAZYREMOVE(found_safeties, S)
+ return LAZYLEN(found_safeties)
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/party_pod.dm b/modular_dripstation/code/game/objects/items/bepis_items/party_pod.dm
new file mode 100644
index 000000000000..a35f01397169
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/party_pod.dm
@@ -0,0 +1,309 @@
+/obj/item/circuitboard/machine/sleeper/party
+ name = "Party Pod"
+ build_path = /obj/machinery/party
+
+/obj/machinery/party
+ name = "party pod"
+ desc = "'Sleeper' units were once known for their healing properties, until a lengthy investigation revealed they were also dosing patients with deadly lead acetate. This appears to be one of those old 'sleeper' units repurposed as a 'Party Pod'. It’s probably not a good idea to use it."
+ icon_state = "partypod"
+ base_icon_state = "partypod"
+ icon = 'modular_dripstation/icons/obj/partypod.dmi'
+ circuit = /obj/item/circuitboard/machine/sleeper/party
+
+ density = FALSE
+ state_open = TRUE
+ payment_department = ACCOUNT_MED
+ fair_market_price = 5
+
+ ///How much chems is allowed to be in a patient at once, before we force them to wait for the reagent to process.
+ var/efficiency = 1
+ ///Whether the machine can be operated by the person inside of it.
+ var/controls_inside = TRUE
+ var/enter_message = "You're surrounded by some funky music inside the chamber. You zone out as you feel waves of krunk vibe within you."
+ var/min_health = -25
+ ///List of currently available chems.
+ var/list/available_chems = list()
+ ///Used when emagged to scramble which chem is used, eg: mutadone -> morphine
+ var/list/chem_buttons
+ //Exclusively uses non-lethal, "fun" chems. At an obvious downside.
+ var/list/possible_chems = list(
+ list(
+ /datum/reagent/consumable/ethanol/beer,
+ /datum/reagent/consumable/laughter,
+ ),
+ list(
+ /datum/reagent/spraytan,
+ /datum/reagent/barbers_aid,
+ ),
+ list(
+ /datum/reagent/colorful_reagent,
+ /datum/reagent/hair_dye,
+ ),
+ list(
+ /datum/reagent/drug/space_drugs,
+ /datum/reagent/baldium,
+ ),
+ )
+ ///Chemicals that need to have a touch or vapor reaction to be applied, not the standard chamber reaction.
+ var/spray_chems = list(
+ /datum/reagent/spraytan,
+ /datum/reagent/hair_dye,
+ /datum/reagent/baldium,
+ /datum/reagent/barbers_aid,
+ )
+
+/obj/machinery/party/Initialize(mapload)
+ . = ..()
+ if(mapload)
+ LAZYREMOVE(component_parts, circuit)
+ QDEL_NULL(circuit)
+ occupant_typecache = GLOB.typecache_living
+ update_icon()
+ reset_chem_buttons()
+
+/obj/machinery/party/RefreshParts()
+ . = ..()
+ var/matterbin_rating
+ for(var/obj/item/stock_parts/matter_bin/matterbins in component_parts)
+ matterbin_rating += matterbins.rating
+ efficiency = initial(efficiency) * matterbin_rating
+ min_health = initial(min_health) * matterbin_rating
+
+ available_chems.Cut()
+ for(var/obj/item/stock_parts/manipulator/servos in component_parts)
+ for(var/i in 1 to servos.rating)
+ available_chems |= possible_chems[i]
+
+ reset_chem_buttons()
+
+/obj/machinery/party/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ return FALSE
+
+ balloon_alert(user, "interface scrambled")
+ obj_flags |= EMAGGED
+
+ var/list/av_chem = available_chems.Copy()
+ for(var/chem in av_chem)
+ chem_buttons[chem] = pick_n_take(av_chem) //no dupes, allow for random buttons to still be correct
+ return TRUE
+
+/obj/machinery/party/proc/inject_chem(chem, mob/user)
+ if(obj_flags & EMAGGED)
+ occupant.reagents.add_reagent(/datum/reagent/toxin/leadacetate, 4)
+ else if (prob(20)) //You're injecting chemicals into yourself from a recalled, decrepit medical machine. What did you expect?
+ occupant.reagents.add_reagent(/datum/reagent/toxin/leadacetate, rand(1,3))
+ if(chem in spray_chems)
+ var/datum/reagents/holder = new()
+ holder.add_reagent(chem_buttons[chem], 10) //I hope this is the correct way to do this.
+ holder.trans_to(occupant, 10) //, methods = VAPOR untill proc trans_to remade
+ playsound(src.loc, 'sound/effects/spray2.ogg', 50, TRUE, -6)
+ if(user)
+ log_combat(user, occupant, "sprayed [chem] into", addition = "via [src]")
+ return TRUE
+ if((chem in available_chems) && chem_allowed(chem))
+ occupant.reagents.add_reagent(chem_buttons[chem], 10) //emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU
+ if(user)
+ log_combat(user, occupant, "injected [chem] into", addition = "via [src]")
+ return TRUE
+
+/obj/machinery/party/proc/chem_allowed(chem)
+ var/mob/living/mob_occupant = occupant
+ if(!mob_occupant || !mob_occupant.reagents)
+ return
+ var/amount = mob_occupant.reagents.get_reagent_amount(chem) + 10 <= 20 * efficiency
+ var/occ_health = mob_occupant.health > min_health || chem == /datum/reagent/medicine/epinephrine
+ return amount && occ_health
+
+/obj/machinery/party/proc/reset_chem_buttons()
+ obj_flags &= ~EMAGGED
+ LAZYINITLIST(chem_buttons)
+ for(var/chem in available_chems)
+ chem_buttons[chem] = chem
+
+
+/obj/machinery/party/update_icon()
+ icon_state = "[base_icon_state][state_open ? "-open" : null]"
+ return ..()
+
+/obj/machinery/party/proc/container_resist_act(mob/living/user)
+ visible_message(span_notice("[occupant] emerges from [src]!"),
+ span_notice("You climb out of [src]!"))
+ open_machine()
+
+/obj/machinery/party/Exited(atom/movable/gone, direction)
+ . = ..()
+ if (!state_open && gone == occupant)
+ container_resist_act(gone)
+
+/obj/machinery/party/relaymove(mob/living/user, direction)
+ if (!state_open)
+ container_resist_act(user)
+
+/obj/machinery/party/open_machine(drop = TRUE, density_to_set = FALSE)
+ if(!state_open && !panel_open)
+ flick("[initial(icon_state)]-anim", src)
+ return ..()
+
+/obj/machinery/party/close_machine(mob/user, density_to_set = TRUE)
+ if((isnull(user) || istype(user)) && state_open && !panel_open)
+ flick("[initial(icon_state)]-anim", src)
+ ..()
+ var/mob/living/mob_occupant = occupant
+ if(mob_occupant && mob_occupant.stat != DEAD)
+ to_chat(mob_occupant, "[enter_message]")
+
+/obj/machinery/party/emp_act(severity)
+ . = ..()
+ if (. & EMP_PROTECT_SELF)
+ return
+ if(!(stat & (NOPOWER|BROKEN|MAINT)) && occupant)
+ open_machine()
+
+
+/obj/machinery/party/MouseDrop_T(mob/target, mob/user)
+ if(HAS_TRAIT(user, TRAIT_UI_BLOCKED) || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
+ return
+ close_machine(target)
+
+/obj/machinery/party/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(occupant)
+ to_chat(user, span_warning("[src] is currently occupied!"))
+ return TRUE
+ if(state_open)
+ to_chat(user, span_warning("[src] must be closed to [panel_open ? "close" : "open"] its maintenance hatch!"))
+ return TRUE
+ if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
+ return TRUE
+ return FALSE
+
+/obj/machinery/party/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(default_change_direction_wrench(user, I))
+ return TRUE
+ return FALSE
+
+/obj/machinery/party/crowbar_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(default_pry_open(I))
+ return TRUE
+ if(default_deconstruction_crowbar(I))
+ return TRUE
+ return FALSE
+
+/obj/machinery/party/default_pry_open(obj/item/I) //wew
+ . = !(state_open || panel_open || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR
+ if(.)
+ I.play_tool_sound(src, 50)
+ visible_message(span_notice("[usr] pries open [src]."), span_notice("You pry open [src]."))
+ open_machine()
+
+/obj/machinery/party/ui_state(mob/user)
+ if(!controls_inside)
+ return GLOB.notcontained_state
+ return GLOB.default_state
+
+/obj/machinery/party/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Party", name)
+ ui.open()
+
+/obj/machinery/party/AltClick(mob/user)
+ . = ..()
+ if(state_open)
+ close_machine()
+ else
+ open_machine()
+
+/obj/machinery/party/examine(mob/user)
+ . = ..()
+ . += span_notice("Alt-click [src] to [state_open ? "close" : "open"] it.")
+
+/obj/machinery/party/process()
+ ..()
+ use_power(idle_power_usage)
+
+/obj/machinery/party/nap_violation(mob/violator)
+ . = ..()
+ open_machine()
+
+/obj/machinery/party/ui_data()
+ var/list/data = list()
+ data["occupied"] = !!occupant
+ data["open"] = state_open
+
+ data["chems"] = list()
+ for(var/chem in available_chems)
+ var/datum/reagent/R = GLOB.chemical_reagents_list[chem]
+ data["chems"] += list(
+ list(
+ "name" = R.name,
+ "id" = R.type,
+ "allowed" = chem_allowed(chem),
+ ),
+ )
+
+ data["occupant"] = list()
+ var/mob/living/mob_occupant = occupant
+ if(mob_occupant)
+ data["occupant"]["name"] = mob_occupant.name
+ switch(mob_occupant.stat)
+ if(CONSCIOUS)
+ data["occupant"]["stat"] = "Conscious"
+ data["occupant"]["statstate"] = "good"
+ if(SOFT_CRIT)
+ data["occupant"]["stat"] = "Conscious"
+ data["occupant"]["statstate"] = "average"
+ if(UNCONSCIOUS)
+ data["occupant"]["stat"] = "Unconscious"
+ data["occupant"]["statstate"] = "average"
+ if(DEAD)
+ data["occupant"]["stat"] = "Dead"
+ data["occupant"]["statstate"] = "bad"
+ data["occupant"]["health"] = mob_occupant.health
+ data["occupant"]["maxHealth"] = mob_occupant.maxHealth
+ data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD
+ data["occupant"]["bruteLoss"] = mob_occupant.getBruteLoss()
+ data["occupant"]["oxyLoss"] = mob_occupant.getOxyLoss()
+ data["occupant"]["toxLoss"] = mob_occupant.getToxLoss()
+ data["occupant"]["fireLoss"] = mob_occupant.getFireLoss()
+ data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss()
+ data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN)
+ data["occupant"]["reagents"] = list()
+ if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len)
+ for(var/datum/reagent/R in mob_occupant.reagents.reagent_list)
+ data["occupant"]["reagents"] += list(
+ list(
+ "name" = R.name,
+ "volume" = R.volume,
+ ),
+ )
+
+ return data
+
+/obj/machinery/party/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/mob_occupant = occupant
+ check_nap_violations()
+ switch(action)
+ if("door")
+ if(state_open)
+ close_machine()
+ else
+ open_machine()
+ . = TRUE
+ if("inject")
+ var/chem = text2path(params["chem"])
+ if((stat & (NOPOWER|BROKEN|MAINT)) || !mob_occupant || isnull(chem))
+ return
+ if(mob_occupant.health < min_health && !ispath(chem, /datum/reagent/medicine/epinephrine))
+ return
+ if(inject_chem(chem, usr))
+ . = TRUE
+ if((obj_flags & EMAGGED) && prob(5))
+ to_chat(usr, span_warning("Chemical system re-route detected, results may not be as expected!"))
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/polycircuit.dm b/modular_dripstation/code/game/objects/items/bepis_items/polycircuit.dm
new file mode 100644
index 000000000000..0f5f82ac3fdb
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/polycircuit.dm
@@ -0,0 +1,59 @@
+/obj/item/stack/circuit_stack
+ name = "polycircuit aggregate"
+ desc = "A dense, overdesigned cluster of electronics which attempted to function as a multipurpose circuit electronic. Circuits can be removed from it... if you don't bleed out in the process."
+ icon = 'modular_dripstation/icons/obj/circuit_mess.dmi'
+ icon_state = "circuit_mess"
+ item_state = "rods"
+ w_class = WEIGHT_CLASS_TINY
+ max_amount = 8
+ merge_type = /obj/item/stack/circuit_stack
+ singular_name = "circuit aggregate"
+ var/circuit_type = /obj/item/electronics/airlock
+ var/chosen_circuit = "airlock"
+
+/obj/item/stack/circuit_stack/attack_self(mob/user)// Prevents the crafting menu, and tells you how to use it.
+ to_chat(user, span_warning("You can't use [src] by itself, you'll have to try and remove one of these circuits by hand... carefully."))
+
+/obj/item/stack/circuit_stack/attack_hand(mob/user, list/modifiers)
+ var/mob/living/carbon/human/H = user
+ if(user.get_inactive_held_item() != src)
+ return ..()
+ else
+ if(is_zero_amount())
+ return
+ chosen_circuit = tgui_input_list(user, "Circuit to remove", "Circuit Removal", list("airlock","firelock","fire alarm","air alarm","APC"), chosen_circuit)
+ if(isnull(chosen_circuit))
+ to_chat(user, span_notice("You wisely avoid putting your hands anywhere near [src]."))
+ return
+ if(is_zero_amount())
+ return
+ if(loc != user)
+ return
+ switch(chosen_circuit)
+ if("airlock")
+ circuit_type = /obj/item/electronics/airlock
+ if("firelock")
+ circuit_type = /obj/item/electronics/firelock
+ if("fire alarm")
+ circuit_type = /obj/item/electronics/firealarm
+ if("air alarm")
+ circuit_type = /obj/item/electronics/airalarm
+ if("APC")
+ circuit_type = /obj/item/electronics/apc
+ to_chat(user, span_notice("You spot your circuit, and carefully attempt to remove it from [src], hold still!"))
+ if(do_after(user, 30, target = user))
+ if(!src || QDELETED(src))//Sanity Check.
+ return
+ var/returned_circuit = new circuit_type(src)
+ user.put_in_hands(returned_circuit)
+ use(1)
+ if(!amount)
+ to_chat(user, span_notice("You navigate the sharp edges of circuitry and remove the last board."))
+ else
+ to_chat(user, span_notice("You navigate the sharp edges of circuitry and remove a single board from [src]"))
+ else
+ H.apply_damage(15, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ to_chat(user, span_warning("You give yourself a wicked cut on [src]'s many sharp corners and edges!"))
+
+/obj/item/stack/circuit_stack/full
+ amount = 8
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/rldmini.dm b/modular_dripstation/code/game/objects/items/bepis_items/rldmini.dm
new file mode 100644
index 000000000000..c59ca00262d0
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/rldmini.dm
@@ -0,0 +1,5 @@
+/obj/item/construction/rld/mini
+ name = "mini-rapid-light-device"
+ desc = "A device used to rapidly provide lighting sources to an area. Reload with iron, plasteel, glass or compressed matter cartridges."
+ matter = 100
+ max_matter = 100
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/sprayoncan.dm b/modular_dripstation/code/game/objects/items/bepis_items/sprayoncan.dm
new file mode 100644
index 000000000000..71a5f8c01212
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/sprayoncan.dm
@@ -0,0 +1,48 @@
+/obj/item/toy/sprayoncan
+ name = "spray-on insulation applicator"
+ desc = "What is the number one problem facing our station today?"
+ icon = 'modular_dripstation/icons/obj/clothing/gloves.dmi'
+ icon_state = "sprayoncan"
+
+/obj/item/toy/sprayoncan/afterattack(atom/target, mob/living/carbon/user, proximity)
+ if(iscarbon(target) && proximity)
+ var/mob/living/carbon/C = target
+ var/mob/living/carbon/U = user
+ var/obj/item/clothing/gloves/color/yellow/sprayon/spr = new(src)
+ var/success = C.equip_to_slot_or_del(spr, ITEM_SLOT_GLOVES)
+ if(success)
+ if(C == U)
+ C.visible_message(span_notice("[U] sprays their hands with glittery rubber!"))
+ else
+ C.visible_message(span_warning("[U] sprays glittery rubber on the hands of [C]!"))
+ else
+ C.visible_message(span_warning("The rubber fails to stick to [C]'s hands!"))
+
+/obj/item/clothing/gloves/color/yellow/sprayon
+ desc = "How're you gonna get 'em off, nerd?"
+ name = "spray-on insulated gloves"
+ icon = 'modular_dripstation/icons/obj/clothing/gloves.dmi'
+ mob_overlay_icon = 'modular_dripstation/icons/mob/clothing/hands.dmi'
+ icon_state = "sprayon"
+ item_state = "sprayon"
+ item_flags = DROPDEL
+ resistance_flags = ACID_PROOF
+ var/charges_remaining = 10
+
+/obj/item/clothing/gloves/color/yellow/sprayon/Initialize(mapload)
+ .=..()
+ ADD_TRAIT(src, TRAIT_NODROP, INNATE_TRAIT)
+
+/obj/item/clothing/gloves/color/yellow/sprayon/equipped(mob/user, slot)
+ . = ..()
+ RegisterSignal(user, COMSIG_LIVING_SHOCK_PREVENTED, PROC_REF(use_charge))
+ RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(use_charge))
+
+/obj/item/clothing/gloves/color/yellow/sprayon/proc/use_charge()
+ SIGNAL_HANDLER
+
+ charges_remaining--
+ if(charges_remaining <= 0)
+ var/turf/location = get_turf(src)
+ location.visible_message(span_warning("[src] crumble[p_s()] away into nothing.")) // just like my dreams after working with .dm
+ qdel(src)
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/bepis_items/survival_pen.dm b/modular_dripstation/code/game/objects/items/bepis_items/survival_pen.dm
new file mode 100644
index 000000000000..9238fc9e9c76
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/bepis_items/survival_pen.dm
@@ -0,0 +1,21 @@
+/obj/item/pen/fountain/survival
+ name = "survival pen"
+ desc = "The latest in portable survival technology, this pen was designed as a miniature hardlight shovel. Watchers find them very desirable for their diamond exterior."
+ icon = 'modular_dripstation/icons/obj/bureaucracy.dmi'
+ icon_state = "digging_pen"
+ item_state = "pen"
+ force = 8 //hard light beats hard, still weaker than the kitchen knife
+ throwforce = 0 //ineffective at long range
+ sharpness = SHARP_EDGED // Sharp shovel
+ resistance_flags = FIRE_PROOF //lavaland is dangerous
+ attack_verb = list("robusted", "slashed", "stabbed", "sliced", "thrashed", "whacked")
+ custom_materials = list(/datum/material/iron = 100, /datum/material/diamond = 200, /datum/material/titanium = 100)
+ pressure_resistance = 2 * ONE_ATMOSPHERE
+ grind_results = list(/datum/reagent/iron = 2, /datum/reagent/iodine = 1)
+ tool_behaviour = TOOL_MINING //For the classic "digging out of prison with a spoon but you're in space so this analogy doesn't work" situation.
+ toolspeed = 2 //You will never willingly choose to use one of these over a shovel.
+ hitsound = 'sound/weapons/blade1.ogg'
+
+/obj/item/pen/fountain/survival/Initialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 60, 100, 0, 'sound/weapons/blade1.ogg') //it's a strange hardlight tech
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/blackmarketstuff.dm b/modular_dripstation/code/game/objects/items/blackmarketstuff.dm
new file mode 100644
index 000000000000..4d8f1de8d7bb
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/blackmarketstuff.dm
@@ -0,0 +1,17 @@
+/obj/item/reagent_containers/glass/beaker/thermite
+ name = "thermite beaker"
+ desc = "Beaker with thermite. Danger, flammable."
+ list_reagents = list(/datum/reagent/thermite = 30)
+
+/obj/item/clothing/shoes/bhop/rocket
+ name = "rocket boots"
+ desc = "Very special boots with built-in rocket thrusters! SHAZBOT!"
+ icon_state = "rocketboots"
+ icon = 'modular_dripstation/icons/obj/clothing/shoes.dmi'
+ actions_types = list(/datum/action/cooldown/boost/brocket)
+ jumpdistance = 20 //great for throwing yourself into walls and people at high speeds
+ jumpspeed = 5
+
+/datum/action/cooldown/boost/brocket
+ name = "Activate Rocket Boots"
+ desc = "Activates the boot's rocket propulsion system, allowing the user to hurl themselves great distances."
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/card_ids.dm b/modular_dripstation/code/game/objects/items/card_ids.dm
index 044474b79833..76e5203b9b16 100644
--- a/modular_dripstation/code/game/objects/items/card_ids.dm
+++ b/modular_dripstation/code/game/objects/items/card_ids.dm
@@ -7,3 +7,24 @@
/obj/item/card/id/departmental_budget/sec
icon_state = "sec_budget"
+
+/obj/item/card/id/syndicate/nuke
+ name = "operative card"
+ registered_name = "operative"
+ assignment = "Nuclear Squad"
+ originalassignment = "Nuclear Squad"
+ registered_age = null
+ forged = TRUE
+ anyone = TRUE
+ registered_age = null
+
+/obj/item/card/id/syndicate/nuke_leader
+ name = "squad leader card"
+ registered_name = "leader"
+ assignment = "Nuclear Squad"
+ originalassignment = "Nuclear Squad"
+ registered_age = null
+ forged = TRUE
+ anyone = TRUE
+ access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER)
+ registered_age = null
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/cargo_boxcutter.dm b/modular_dripstation/code/game/objects/items/cargo_boxcutter.dm
new file mode 100644
index 000000000000..5b76290d89cc
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/cargo_boxcutter.dm
@@ -0,0 +1,46 @@
+/obj/item/boxcutter
+ name = "boxcutter"
+ desc = "A tool for cutting boxes, or throats."
+ icon = 'modular_dripstation/icons/obj/cargo/boxcutter.dmi'
+ icon_state = "boxcutter"
+ item_state = "boxcutter"
+ lefthand_file = 'modular_dripstation/icons/mob/inhands/equipment/boxcutter_lefthand.dmi'
+ righthand_file = 'modular_dripstation/icons/mob/inhands/equipment/boxcutter_righthand.dmi'
+ attack_verb = list("proded", "poked")
+ w_class = WEIGHT_CLASS_SMALL
+ slot_flags = ITEM_SLOT_BELT
+ resistance_flags = FIRE_PROOF
+ force = 0
+ bare_wound_bonus = 20
+ /// Used on Initialize, how much time to cut cable restraints and zipties.
+ //var/snap_time_weak_handcuffs = 0 SECONDS
+ /// Used on Initialize, how much time to cut real handcuffs. Null means it can't.
+ //var/snap_time_strong_handcuffs = null
+
+/obj/item/boxcutter/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/butchering, 70, 100)
+
+ AddComponent( \
+ /datum/component/transforming, \
+ force_on = 10, \
+ throwforce_on = 4, \
+ throw_speed_on = throw_speed, \
+ sharpness_on = SHARP_EDGED, \
+ hitsound_on = 'sound/weapons/bladeslice.ogg', \
+ w_class_on = WEIGHT_CLASS_NORMAL, \
+ attack_verb_on = list("cuted", "stabed", "slashed"), \
+ )
+
+ RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform))
+
+/obj/item/boxcutter/proc/on_transform(obj/item/source, mob/user, active)
+ SIGNAL_HANDLER
+
+ playsound(src, 'modular_dripstation/sound/item/boxcutter_activate.ogg', 50)
+ //tool_behaviour = (active ? TOOL_KNIFE : NONE)
+ //if(active)
+ // AddElement(/datum/element/cuffsnapping, snap_time_weak_handcuffs, snap_time_strong_handcuffs)
+ //else
+ // RemoveElement(/datum/element/cuffsnapping, snap_time_weak_handcuffs, snap_time_strong_handcuffs)
+ //return COMPONENT_NO_DEFAULT_MESSAGE
diff --git a/modular_dripstation/code/game/objects/items/cargo_inducer.dm b/modular_dripstation/code/game/objects/items/cargo_inducer.dm
new file mode 100644
index 000000000000..b9b0eb579fbe
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/cargo_inducer.dm
@@ -0,0 +1,12 @@
+/obj/item/inducer/cargo
+ name = "inducer"
+ icon_state = "inducer-cargo"
+ icon = 'modular_dripstation/icons/obj/cargo/cargo_inducer.dmi'
+ desc = "A tool for inductively charging internal power cells. This one has a cargo color scheme, and is less potent than its engineering counterpart."
+ cell_type = /obj/item/stock_parts/cell/high/empty
+ powertransfer = 500
+ opened = TRUE
+
+/obj/item/inducer/cargo/Initialize()
+ . = ..()
+ update_icon()
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/cargo_teleporter.dm b/modular_dripstation/code/game/objects/items/cargo_teleporter.dm
new file mode 100644
index 000000000000..91943a298afd
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/cargo_teleporter.dm
@@ -0,0 +1,320 @@
+GLOBAL_LIST_EMPTY(cargo_marks)
+
+/obj/item/cargo_teleporter
+ name = "cargo handheld teleporter"
+ desc = "Specialised personal teleporter based on modern bluespace technology. This one issued to QM for moving crates, closets and none-living objects to previously marked locations."
+ icon = 'modular_dripstation/icons/obj/cargo/cargo_teleporter.dmi'
+ mob_overlay_icon = 'modular_dripstation/icons/mob/clothing/belt.dmi'
+ lefthand_file = 'modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_lefthand.dmi'
+ righthand_file = 'modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_righthand.dmi'
+ icon_state = "cargo_tele"
+ item_state = "cargo_tele"
+ materials = list(MAT_METAL=10000)
+ slot_flags = ITEM_SLOT_BELT
+ cryo_preserve = TRUE
+ flags_1 = CONDUCT_1
+ force = 10
+ throwforce = 10
+ throw_speed = 3
+ throw_range = 5
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 30, BIO = 0, RAD = 0, FIRE = 100, ACID = 100)
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ var/list/marker_children = list() //the list of markers spawned by this item
+ var/choice //storing marker of choice
+ var/cooldown_in_seconds = 16 SECONDS //cooldown, made for upgrades
+ var/emagged = FALSE //emagged?
+ var/emped = FALSE //emped?
+ verb_say = "beeps"
+ verb_exclaim = "blares"
+ var/obj/item/stock_parts/manipulator/manipulator = null
+ var/obj/item/stock_parts/cell/high/ctcell = null // Power cell (10000W)
+ var/chargecost = 1000 // How much energy tele use
+ var/static/list/high_risk_teleporting = typecacheof(list(
+ /obj/item/disk/nuclear,
+ /obj/item/gun/energy/laser/captain,
+ /obj/item/hand_tele,
+ /obj/item/clothing/accessory/medal/gold/captain,
+ /obj/item/melee/sabre,
+ /obj/item/clothing/gloves/krav_maga/sec,
+ /obj/item/gun/energy/e_gun/hos,
+ /obj/item/card/id/captains_spare,
+ /obj/item/tank/jetpack/oxygen/captain,
+ /obj/item/aicard,
+ /obj/item/hypospray/deluxe/cmo,
+ /obj/item/clothing/suit/armor/reactive/teleport,
+ /obj/item/clothing/suit/armor/laserproof,
+ /obj/item/blackbox,
+ /obj/item/holotool,
+ /obj/item/areaeditor/blueprints)
+ )
+ var/static/list/blacklisted_turfs = typecacheof(list(
+ /turf/closed,
+ /turf/open/chasm,
+ /turf/open/lava))
+ var/static/list/deathtrap_list = typecacheof(list(
+ /obj/machinery/power/supermatter_crystal,
+ /obj/machinery/conveyor,
+ /obj/machinery/recycler))
+
+ COOLDOWN_DECLARE(use_cooldown)
+
+/obj/item/cargo_teleporter/get_cell()
+ return ctcell
+
+/obj/item/cargo_teleporter/Initialize()
+ . = ..()
+ manipulator = new /obj/item/stock_parts/manipulator(src)
+ ctcell = new(src)
+
+/obj/item/cargo_teleporter/emp_act(severity)
+ if (. & EMP_PROTECT_SELF)
+ return
+ emped = TRUE
+ SStgui.close_uis(src) //Close the UI control if it is open. I guess do not work, will work with ui refactor, if it will be made some day
+ if(severity > EMP_LIGHT)
+ ctcell.charge = 0
+ if(emagged)
+ emagged = FALSE
+ if(prob(50))
+ var/mob/living/carbon/human/user = src.loc
+ say("E:FATAL:PWR_BUS_OVERLOAD")
+ if(user.is_holding(src))
+ if(user.dna && user.dna.species.id == "human")
+ say("E:FATAL:STACK_EMPTY\nE:FATAL:READ_NULL_POINT")
+ to_chat(user, span_danger("An electromagnetic pulse disrupts your hacked teleporter and violently turns you into abomination."))
+ to_chat(user, span_userdanger("You SEE THE LIGHT."))
+ user.set_species(/datum/species/moth)
+ else
+ ctcell.use(chargecost)
+ if(emagged)
+ emagged = FALSE
+
+/obj/item/cargo_teleporter/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ if(emagged)
+ . += "Warning: Safety protocols disabled."
+ if(!manipulator)
+ . += "The manipulator is missing."
+ else
+ . += "A tier [manipulator.rating] manipulator is installed. It is screwed in place."
+ . += "There are [round(ctcell.charge/chargecost)] charge\s left."
+ . += span_notice("Attack itself to set down the markers!")
+ . += span_notice("ALT-CLICK to change destination marker!")
+
+/obj/item/cargo_teleporter/Destroy()
+ if(length(marker_children))
+ for(var/obj/effect/decal/cleanable/cargo_mark/destroy_children in marker_children)
+ destroy_children.parent_item = null
+ qdel(destroy_children)
+ QDEL_NULL(manipulator)
+ QDEL_NULL(ctcell)
+ return ..()
+
+/obj/item/cargo_teleporter/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/stock_parts/manipulator))
+ if(!manipulator)
+ if(!user.transferItemToLoc(W, src))
+ return
+ manipulator = W
+ playsound(src, 'sound/machines/click.ogg', 25)
+ to_chat(user, span_notice("You install a [manipulator.name] in [src]."))
+ else
+ to_chat(user, span_notice("[src] already has a manipulator installed."))
+
+/obj/item/cargo_teleporter/screwdriver_act(mob/living/user, obj/item/I)
+ if(manipulator)
+ I.play_tool_sound(src)
+ to_chat(user, span_notice("You remove the [manipulator.name] from \the [src]."))
+ manipulator.forceMove(drop_location())
+ manipulator = null
+
+/obj/item/cargo_teleporter/emag_act(user)
+ if(!emagged)
+ emagged = TRUE
+ do_sparks(3, TRUE, src)
+ say("SAFETY PROTOCOLS OVERRIDE DETECTED!")
+ return
+
+/obj/item/cargo_teleporter/attack_self(mob/user, modifiers)
+ if(isnull(manipulator))
+ say("\The [src] is missing it's manipulator, and cannot function.")
+ return
+ var/turf/current_location = get_turf(user)//What turf is the user on?
+ var/area/current_area = current_location.loc
+ if(!current_location || current_area.noteleport || is_away_level(current_location.z) || !isturf(user.loc))//If turf was not found or they're on z level 2 or >7 which does not currently exist. or if user is not located on a turf
+ say("\The [src] is malfunctioning.")
+ return
+ if(!is_station_level(current_location.z))
+ var/lava_emag
+ if(emagged & is_mining_level(current_location.z))
+ lava_emag = TRUE
+ if(!lava_emag)
+ say("Error 503. Marker location undefined. Bluespace signal is not tracing. Try again onboard.")
+ return
+ if(isspaceturf(current_location))
+ say("You cannot place markers in space.")
+ return
+ if(is_type_in_typecache(current_location, blacklisted_turfs))
+ say("You cannot place markers here.")
+ return
+ if(length(marker_children) >= manipulator.rating)
+ say("You may only have [manipulator.rating] spawned markers from [src].")
+ return
+ if(locate(/obj/effect/decal/cleanable/cargo_mark) in current_location)
+ say("There is already a marker here.")
+ return
+ var/obj/effect/decal/cleanable/cargo_mark/spawned_marker = new /obj/effect/decal/cleanable/cargo_mark(get_turf(src))
+ to_chat(user, span_notice("You place a cargo marker below your feet."))
+ playsound(src, 'sound/machines/click.ogg', 50)
+ spawned_marker.parent_item = src
+ marker_children += spawned_marker
+
+/obj/item/cargo_teleporter/AltClick(mob/user)
+ if(!user.is_holding(src))
+ to_chat(user, span_notice("You should be able to press the change destination button to to interact with interface."))
+ return
+ if(emped)
+ to_chat(user, "Teleporter restarts by itself!")
+ playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, 3)
+ emped = FALSE
+ choice = null
+ return
+ makingchoice(user)
+
+/obj/item/cargo_teleporter/proc/makingchoice(mob/user)
+ choice = tgui_input_list(user, "Select which cargo mark to teleport the items to?", "Cargo Mark Selection", GLOB.cargo_marks)
+ if(!choice)
+ return
+
+
+/obj/item/cargo_teleporter/verb/remove_all_markers()
+ set name = "Cargo Tele: Remove All Markers"
+ set category = "Object"
+
+ if(!usr.is_holding(src))
+ return
+ if(length(marker_children))
+ for(var/obj/effect/decal/cleanable/cargo_mark/destroy_children in marker_children)
+ qdel(destroy_children)
+ marker_children = list()
+ choice = null
+ playsound(src, 'sound/machines/click.ogg', 50)
+ to_chat(usr, span_notice("You destroyed all markers."))
+
+/obj/item/cargo_teleporter/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ if(!user.is_holding(src))
+ to_chat(user, span_notice("You should be able to press the teleportation button to be able teleport something."))
+ return ..()
+ if(isnull(manipulator))
+ say("\The [src] is missing it's manipulator, and cannot function.")
+ return ..()
+ if(ctcell.charge < chargecost)
+ say("Unable to teleport, insufficient charge.")
+ return ..()
+ if(!emagged && !proximity_flag)
+ return ..()
+ if(emagged && (get_dist(user, target) > manipulator.rating))
+ return ..()
+ if(target == src)
+ return ..()
+ if(emped)
+ say("\The [src] malfunctioning and needs to be restarted.")
+ to_chat(user, span_notice("AltClick on device to restart it."))
+ return ..()
+ if(!COOLDOWN_FINISHED(src, use_cooldown))
+ say("\The [src] is still on cooldown.")
+ return
+ if(!choice)
+ return makingchoice(user)
+ var/turf/moving_turf = get_turf(choice)
+ var/turf/target_turf = get_turf(target)
+ var/area/current_area = target_turf.loc
+ if(current_area.noteleport)//If z level disables teleportation
+ say("\The [src] is malfunctioning.")
+ return
+ var/destination_incorrect = FALSE //prevent spamming on one turf and primitive deathraps
+ for(var/deathtrap_check in moving_turf.contents) //deathrap checking
+ if(emagged & is_type_in_typecache(deathtrap_check, deathtrap_list)) //preventing oneclick deathraps, say no to oneturf recycler bins
+ destination_incorrect = TRUE
+ continue
+ if(moving_turf == target_turf) //die spamm
+ destination_incorrect = TRUE
+ continue
+ if(destination_incorrect) //prevent oneturf sound spamming and primitive deathraps
+ say("Teleportation error detected. Destination incorrect.")
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ return ..()
+ var/dust = FALSE
+ for(var/check_content in target_turf.contents)
+ if(isobserver(check_content))
+ continue
+ if(!ismovable(check_content))
+ continue
+ if(is_type_in_typecache(check_content, high_risk_teleporting))
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ continue
+ var/atom/movable/movable_content = check_content
+ if(isliving(movable_content) && !emagged)
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ continue
+ if(HAS_TRAIT(movable_content, TRAIT_NO_TELEPORT))
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ continue
+ if(length(movable_content.get_all_contents_type(/mob/living)) && !emagged)
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ continue
+ if(length(typecache_filter_list(movable_content.get_all_contents_type(/obj/item), high_risk_teleporting)))
+ playsound(src, 'sound/machines/terminal_alert.ogg', 50)
+ continue
+ if(movable_content.anchored)
+ continue
+ if(length(movable_content.get_all_contents_type(/obj/item/cargo_teleporter)))
+ playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, 1)
+ say("Teleportation error detected. Do not try teleport teleporter.")
+ continue
+ dust = TRUE
+ do_teleport(movable_content, moving_turf, asoundout = 'sound/magic/Disable_Tech.ogg')
+ if(dust)
+ new /obj/effect/decal/cleanable/ash(target_turf)
+ ctcell.use(chargecost)
+ say("Teleportation successful. [round(ctcell.charge/chargecost)] charge\s left.")
+ cooldown_in_seconds -= (manipulator.rating * 2)
+ COOLDOWN_START(src, use_cooldown, cooldown_in_seconds)
+
+/obj/effect/decal/cleanable/cargo_mark
+ name = "cargo mark"
+ desc = "A mark left behind by a cargo teleporter, which allows targeted teleportation. Can be removed by the cargo teleporter."
+ icon = 'modular_dripstation/icons/obj/cargo/cargo_teleporter.dmi'
+ icon_state = "marker"
+ ///the reference to the item that spawned the cargo mark
+ var/obj/item/cargo_teleporter/parent_item
+
+ light_range = 3
+ light_color = COLOR_VIVID_YELLOW
+
+/obj/effect/decal/cleanable/cargo_mark/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/cargo_teleporter))
+ to_chat(user, span_notice("You remove [src] using [W]."))
+ playsound(src, 'sound/machines/click.ogg', 50)
+ if(parent_item.choice)
+ if(get_turf(parent_item.choice) == get_turf(src))
+ parent_item.choice = null
+ qdel(src)
+ return
+ return ..()
+
+/obj/effect/decal/cleanable/cargo_mark/Destroy()
+ if(parent_item)
+ parent_item.marker_children -= src
+ if(parent_item.choice)
+ if(get_turf(parent_item.choice) == get_turf(src))
+ parent_item.choice = null
+ GLOB.cargo_marks -= src
+ return ..()
+
+/obj/effect/decal/cleanable/cargo_mark/Initialize(mapload, list/datum/disease/diseases)
+ . = ..()
+ var/area/src_area = get_area(src)
+ name = "[src_area.name] ([rand(100000,999999)])"
+ GLOB.cargo_marks += src
diff --git a/modular_dripstation/code/game/objects/items/clothing/gloves.dm b/modular_dripstation/code/game/objects/items/clothing/gloves.dm
new file mode 100644
index 000000000000..7c59113fcdc7
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/clothing/gloves.dm
@@ -0,0 +1,66 @@
+/obj/item/clothing/gloves/cargo_gauntlet
+ name = "\improper cargo gauntlets"
+ desc = "These rubberized gauntlets have high adhesion to the metal surface that allows you to drag crates and lockers with more confidence on them not getting nabbed from you."
+ icon = 'modular_dripstation/icons/obj/clothing/gloves.dmi'
+ mob_overlay_icon = 'modular_dripstation/icons/mob/clothing/hands.dmi'
+ icon_state = "cargogloves"
+ item_state = "cargogloves"
+ cold_protection = HANDS
+ heat_protection = HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ undyeable = TRUE
+ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 80, ACID = 50, ELECTRIC = 60)
+ var/datum/weakref/pull_component_weakref
+
+/obj/item/clothing/gloves/cargo_gauntlet/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_ITEM_EQUIPPED, PROC_REF(on_glove_equip))
+ RegisterSignal(src, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_glove_unequip))
+
+/// Called when the glove is equipped. Adds a component to the equipper and stores a weak reference to it.
+/obj/item/clothing/gloves/cargo_gauntlet/proc/on_glove_equip(datum/source, mob/equipper, slot)
+ SIGNAL_HANDLER
+
+ if(!(slot & ITEM_SLOT_GLOVES))
+ return
+
+ var/datum/component/strong_pull/pull_component = pull_component_weakref?.resolve()
+ if(pull_component)
+ stack_trace("Gloves already have a pull component associated with \[[pull_component.parent]\] when \[[equipper]\] is trying to equip them.")
+ QDEL_NULL(pull_component_weakref)
+
+ to_chat(equipper, span_notice("You feel the gauntlets activate as soon as you fit them on, making your pulls stronger!"))
+
+ pull_component_weakref = WEAKREF(equipper.AddComponent(/datum/component/strong_pull))
+
+/*
+ * Called when the glove is unequipped. Deletes the component if one exists.
+ *
+ * No component being associated on equip is a valid state, as holding the gloves in your hands also counts
+ * as having them equipped, or even in pockets. They only give the component when they're worn on the hands.
+ */
+/obj/item/clothing/gloves/cargo_gauntlet/proc/on_glove_unequip(datum/source, force, atom/newloc, no_move, invdrop, silent)
+ SIGNAL_HANDLER
+
+ var/datum/component/strong_pull/pull_component = pull_component_weakref?.resolve()
+
+ if(!pull_component)
+ return
+
+ to_chat(pull_component.parent, span_warning("You have lost the grip power of [src]!"))
+
+ QDEL_NULL(pull_component_weakref)
+
+
+/obj/item/clothing/gloves/krav_maga/sec
+ name = "krav maga gloves"
+ desc = "These gloves can teach you to perform Krav Maga using nanochips."
+ icon_state = "fightgloves"
+ item_state = "fightgloves"
+ cold_protection = HANDS
+ min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
+ heat_protection = HANDS
+ max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+ cryo_preserve = TRUE
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/devices/PDA/PDA_types.dm b/modular_dripstation/code/game/objects/items/devices/PDA/PDA_types.dm
new file mode 100644
index 000000000000..24a06170d713
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/devices/PDA/PDA_types.dm
@@ -0,0 +1,2 @@
+/obj/item/pda/quartermaster
+ insert_type = /obj/item/pen/fountain/survival
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/devices/advpinpointer.dm b/modular_dripstation/code/game/objects/items/devices/advpinpointer.dm
new file mode 100644
index 000000000000..5e19b0c8efdb
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/devices/advpinpointer.dm
@@ -0,0 +1,135 @@
+#define SETTING_DISK 0
+#define SETTING_OBJECT 1
+#define SETTING_PERSON 2
+GLOBAL_LIST_INIT(HIGHRISK, typecacheof(list(/obj/item/disk/nuclear,
+ /obj/item/gun/energy/laser/captain,
+ /obj/item/hand_tele,
+ /obj/item/clothing/accessory/medal/gold/captain,
+ /obj/item/melee/sabre,
+ /obj/item/gun/energy/e_gun/hos,
+ /obj/item/card/id/captains_spare,
+ /obj/item/tank/jetpack/oxygen/captain,
+ /obj/item/aicard,
+ /obj/item/hypospray/deluxe/cmo,
+ /obj/item/clothing/suit/armor/reactive/teleport,
+ /obj/item/clothing/suit/armor/laserproof,
+ /obj/item/blackbox,
+ /obj/item/holotool,
+ /obj/item/areaeditor/blueprints)))
+ ///obj/item/clothing/gloves/krav_maga/sec,
+ ///obj/item/cargo_teleporter,
+/obj/item/pinpointer/adv
+ var/modelocked = FALSE // If true, user cannot change mode.
+ var/obj/item/highrisk_rem = null
+ var/remember_target = null
+ var/setting = SETTING_DISK
+
+/obj/item/pinpointer/adv/examine(mob/user)
+ . = ..()
+ var/msg = "Its tracking indicator reads "
+ if(is_syndicate(user))
+ switch(setting)
+ if(SETTING_DISK)
+ msg += "\"nuclear_disk\"."
+ if(SETTING_OBJECT)
+ msg += "\"target\"."
+ if(SETTING_PERSON)
+ msg += "\"person\"."
+ else
+ msg = "Its tracking indicator is blank."
+ else
+ msg += "\"nuclear_disk\"."
+ . += msg
+
+/obj/item/pinpointer/adv/toggle_on()
+ active = !active
+ playsound(src, 'sound/items/screwdriver2.ogg', 50, 1)
+ if(active)
+ if(!is_syndicate(usr))
+ setting = SETTING_DISK
+ START_PROCESSING(SSfastprocess, src)
+ else
+ target = null
+ highrisk_rem = null
+ remember_target = null
+ setting = SETTING_DISK
+ STOP_PROCESSING(SSfastprocess, src)
+ update_appearance(UPDATE_ICON)
+
+/obj/item/pinpointer/adv/scan_for_target()
+ target = null
+ switch(setting)
+ if(SETTING_DISK)
+ var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list
+ target = N
+ if(SETTING_OBJECT)
+ if(!highrisk_rem)
+ setting = SETTING_DISK
+ playsound(src, 'sound/machines/triple_beep.ogg', 50, 1)
+ return
+ var/obj/item/H = highrisk_rem
+ target = H
+ if(SETTING_PERSON)
+ if(!remember_target)
+ setting = SETTING_DISK
+ playsound(src, 'sound/machines/triple_beep.ogg', 50, 1)
+ return
+ target = remember_target
+ ..()
+
+/obj/item/pinpointer/adv/AltClick(mob/user)
+ if(isliving(user))
+ if(is_syndicate(user))
+ if(!user.is_holding(src))
+ to_chat(user, span_notice("You should be able to press the change mode button to interact with interface."))
+ return
+ var/mob/living/L = user
+ to_chat(L, span_danger("Your [name] beeps as it reconfigures it's tracking algorithms."))
+ playsound(src, 'sound/machines/boop.ogg', 50, 1)
+ switch_mode_to(user)
+ else
+ setting = SETTING_DISK
+
+/obj/item/pinpointer/adv/proc/switch_mode_to(mob/user)
+ switch(alert("Please select the mode you want to put the pinpointer in.", "Pinpointer Mode Select", "Disk Recovery", "High Risk", "DNA RSS"))
+ if("Disk Recovery")
+ setting = SETTING_DISK
+ if("High Risk")
+ setting = SETTING_OBJECT
+ var/list/item_names[0]
+ var/list/item_paths[0]
+ for(var/objective in GLOB.HIGHRISK)
+ var/obj/item/I = objective
+ var/name = initial(I.name)
+ item_names += name
+ item_paths[name] = objective
+ var/targetitem = input("Select item to search for.", "Item Mode Select","") as null|anything in item_names
+ if(!targetitem)
+ return
+ var/list/target_candidates = get_all_of_type(item_paths[targetitem], subtypes = TRUE)
+ for(var/obj/item/candidate in target_candidates)
+ if(!is_centcom_level((get_turf(candidate)).z))
+ highrisk_rem = candidate
+ playsound(src, get_sfx("terminal_type"), 25, 1)
+ to_chat(user, "You set the pinpointer to locate [targetitem].")
+ return
+ if(!highrisk_rem)
+ to_chat(user, "Failed to locate [targetitem]!")
+ return
+
+ if("DNA RSS")
+ setting = SETTING_PERSON
+ var/DNAstring = input("Input DNA string to search for." , "Please Enter String." , "")
+ if(!DNAstring)
+ return
+ for(var/mob/living/carbon/C in GLOB.mob_list)
+ if(!C.dna)
+ continue
+ if(C.dna.unique_enzymes == DNAstring)
+ if(!is_centcom_level((get_turf(C)).z))
+ remember_target = C
+ playsound(src, get_sfx("terminal_type"), 25, 1)
+ to_chat(user, "You set the pinpointer to locate somebody.")
+ else
+ playsound(src, 'sound/machines/triple_beep.ogg', 50, 1)
+ to_chat(user, "Malfunction detected.")
diff --git a/modular_dripstation/code/game/objects/items/devices/laserpointer.dm b/modular_dripstation/code/game/objects/items/devices/laserpointer.dm
new file mode 100644
index 000000000000..806e7a276612
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/devices/laserpointer.dm
@@ -0,0 +1,3 @@
+/obj/item/laser_pointer/RefreshParts()
+ ///The rate at which the laser regenerates charge. Clamped between 20 seconds and basically instantly just in case of weirdness. Knock off 5 seconds per diode rating
+ recharge_rate = clamp((20 SECONDS - (5 SECONDS * diode.rating)), 1, 20 SECONDS)
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/projectiles/guns/ballistic/rifle.dm b/modular_dripstation/code/game/objects/items/projectiles/guns/ballistic/rifle.dm
new file mode 100644
index 000000000000..2425712e6b78
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/projectiles/guns/ballistic/rifle.dm
@@ -0,0 +1,30 @@
+/obj/item/gun/ballistic/rifle/boltaction/brand_new
+ desc = "A brand new Mosin Nagant issued by Nanotrasen for their interns. You would rather not to damage it."
+ icon_state = "mosinprime"
+ item_state = "mosinprime"
+ sawn_desc = "A sawn-off Brand New Nagant... Doing this was a sin, I hope you're happy. \
+ You are now probably one of the few people in the universe to ever hold a \"Brand New Obrez\". \
+ Even thinking about that name combination makes you ill."
+ icon = 'modular_dripstation/icons/obj/weapons/48x32.dmi'
+ mob_overlay_icon = 'modular_dripstation/icons/mob/clothing/guns_on_back.dmi'
+ lefthand_file = 'modular_dripstation/icons/mob/inhands/guns_lefthand.dmi'
+ righthand_file = 'modular_dripstation/icons/mob/inhands/guns_righthand.dmi'
+
+/obj/item/gun/ballistic/rifle/boltaction/brand_new/sawoff(mob/user)
+ . = ..()
+ if(.)
+ name = "\improper Brand New Obrez" // wear it loud and proud
+
+/obj/item/gun/ballistic/rifle/boltaction/qmrifle
+ name = "\improper 'Forbidden' precision rifle"
+ desc = "Modernized boltaction rifle, the frame feels robust as cargotech liver. \
+ This thing was probably built with a conversion kit from a shady NTnet site. \
+
\
+ BRAND NEW: Cannot be sawn off."
+ icon = 'modular_dripstation/icons/obj/weapons/48x32.dmi'
+ mob_overlay_icon = 'modular_dripstation/icons/mob/clothing/guns_on_back.dmi'
+ lefthand_file = 'modular_dripstation/icons/mob/inhands/guns_lefthand.dmi'
+ righthand_file = 'modular_dripstation/icons/mob/inhands/guns_righthand.dmi'
+ icon_state = "mosintactical"
+ item_state = "mosintactical"
+ can_be_sawn_off = FALSE
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/tanks/watertank.dm b/modular_dripstation/code/game/objects/items/tanks/watertank.dm
new file mode 100644
index 000000000000..394f296971e7
--- /dev/null
+++ b/modular_dripstation/code/game/objects/items/tanks/watertank.dm
@@ -0,0 +1,6 @@
+/obj/item/reagent_containers/spray/mister/janitor
+ possible_transfer_amounts = list(5, 10)
+
+/obj/item/reagent_containers/spray/mister/janitor/attack_self(var/mob/user)
+ amount_per_transfer_from_this = (amount_per_transfer_from_this == 10 ? 5 : 10)
+ to_chat(user, span_notice("You [amount_per_transfer_from_this == 10 ? "remove" : "affix"] the nozzle. You'll now use [amount_per_transfer_from_this] units per spray."))
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/items/teleportation.dm b/modular_dripstation/code/game/objects/items/teleportation.dm
index 21423c5c0685..4c89e3968b4c 100644
--- a/modular_dripstation/code/game/objects/items/teleportation.dm
+++ b/modular_dripstation/code/game/objects/items/teleportation.dm
@@ -1,2 +1,14 @@
/obj/item/hand_tele
icon = 'modular_dripstation/icons/obj/device.dmi'
+
+/obj/item/hand_tele/try_dispel_portal(atom/target, mob/user)
+ if(is_parent_of_portal(target)) //dispel me from this horrid realm
+ var/dispel_time = 2 - (manipulator.rating/2)
+ if(dispel_time == 0)
+ qdel(target)
+ to_chat(user, span_notice("You dispel [target] with \the [src]!"))
+ return
+ balloon_alert(user, "Dispelling portal...")
+ if(do_after(user, dispel_time SECONDS, target))
+ qdel(target)
+ to_chat(user, span_notice("You dispel [target] with \the [src]!"))
\ No newline at end of file
diff --git a/modular_dripstation/code/game/objects/structures/crates_lockers/closets.dm b/modular_dripstation/code/game/objects/structures/crates_lockers/closets.dm
new file mode 100644
index 000000000000..79c1590c5964
--- /dev/null
+++ b/modular_dripstation/code/game/objects/structures/crates_lockers/closets.dm
@@ -0,0 +1,76 @@
+GLOBAL_LIST_INIT(closet_cutting_types, typecacheof(list(
+ /obj/item/gun/energy/plasmacutter)))
+
+/obj/structure/closet/secure_closet/tool_interact(obj/item/W, mob/user, proximity)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise
+ . = TRUE
+ if(opened)
+ if(user.a_intent == INTENT_HARM)
+ return FALSE
+ if(istype(W, cutting_tool))
+ if(W.tool_behaviour == TOOL_WELDER)
+ if(!W.tool_start_check(user, amount=0))
+ return
+
+ to_chat(user, span_notice("You begin cutting \the [src] apart..."))
+ if(W.use_tool(src, user, 40, volume=50))
+ if(!opened)
+ return
+ user.visible_message(span_notice("[user] slices apart \the [src]."),
+ span_notice("You cut \the [src] apart with \the [W]."),
+ span_italics("You hear welding."))
+ deconstruct(TRUE)
+ return
+ else // for example cardboard box is cut with wirecutters
+ user.visible_message(span_notice("[user] cut apart \the [src]."), \
+ span_notice("You cut \the [src] apart with \the [W]."))
+ deconstruct(TRUE)
+ return
+ if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
+ return
+ else if(is_type_in_typecache(W, GLOB.closet_cutting_types) && user.a_intent == INTENT_HARM)
+ to_chat(user, span_notice("You begin cutting off electronic lock \the [src]..."))
+ if(W.tool_behaviour == TOOL_WELDER)
+ if(!W.tool_start_check(user, amount=0))
+ return
+ to_chat(user, span_notice("You begin cutting \the [src] lock..."))
+ while(obj_integrity > integrity_failure)
+ if(W.use_tool(src, user, 40, volume=50))
+ if(opened)
+ return
+ user.visible_message(span_notice("[user] melts the lock of \the [src]."),
+ span_notice("You melting the lock of \the [src] with \the [W]."),
+ span_italics("You hear welding."))
+ obj_integrity -= 40
+ if(obj_integrity <= integrity_failure)
+ bust_open()
+ else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut)
+ if(!W.tool_start_check(user, amount=0))
+ return
+
+ to_chat(user, span_notice("You begin [welded ? "unwelding":"welding"] \the [src]..."))
+ if(W.use_tool(src, user, 40, volume=50))
+ if(opened)
+ return
+ welded = !welded
+ after_weld(welded)
+ update_airtightness()
+ user.visible_message(span_notice("[user] [welded ? "welds shut" : "unwelded"] \the [src]."),
+ span_notice("You [welded ? "weld" : "unwelded"] \the [src] with \the [W]."),
+ span_italics("You hear welding."))
+ update_appearance(UPDATE_ICON)
+ else if(W.tool_behaviour == TOOL_WRENCH && anchorable)
+ if(isinspace() && !anchored)
+ return
+ setAnchored(!anchored)
+ W.play_tool_sound(src, 75)
+ user.visible_message(span_notice("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground."), \
+ span_notice("You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground."), \
+ span_italics("You hear a ratchet."))
+ else if(user.a_intent != INTENT_HARM)
+ var/item_is_id = W.GetID()
+ if(!item_is_id && !(W.item_flags & NOBLUDGEON))
+ return FALSE
+ if(item_is_id || !toggle(user))
+ togglelock(user)
+ else
+ return FALSE
\ No newline at end of file
diff --git a/modular_dripstation/code/game/turfs/simulated/walls.dm b/modular_dripstation/code/game/turfs/simulated/walls.dm
new file mode 100644
index 000000000000..50ce45791c7f
--- /dev/null
+++ b/modular_dripstation/code/game/turfs/simulated/walls.dm
@@ -0,0 +1,2 @@
+/turf/closed/wall/metal_foam_base
+ girder_type = /obj/structure/foamedmetal
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/antagonists/_common/antag_spawner.dm b/modular_dripstation/code/modules/antagonists/_common/antag_spawner.dm
new file mode 100644
index 000000000000..fd4b48b2c713
--- /dev/null
+++ b/modular_dripstation/code/modules/antagonists/_common/antag_spawner.dm
@@ -0,0 +1,26 @@
+/obj/item/antag_spawner/nuke_ops
+ /// Do we use a random subtype of the outfit?
+ var/use_subtypes = TRUE
+ /// The applied outfit
+ var/datum/outfit/syndicate/outfit = /datum/outfit/syndicate/no_crystals
+
+/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user)
+ var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
+ C.prefs.apply_prefs_to(M)
+ M.key = C.key
+
+ var/datum/antagonist/nukeop/new_op = new()
+ new_op.send_to_spawnpoint = FALSE
+ new_op.nukeop_outfit = use_subtypes ? pick(subtypesof(outfit)) : outfit
+
+ var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
+ if(creator_op)
+ M.playsound_local(get_turf(M), 'sound/ambience/antag/ops.ogg',100,0)
+ M.mind.add_antag_datum(new_op,creator_op.nuke_team)
+ M.mind.special_role = "Nuclear Operative"
+
+/obj/item/antag_spawner/nuke_ops/clown
+ use_subtypes = FALSE
+
+/obj/item/antag_spawner/nuke_ops/borg_tele
+ use_subtypes = FALSE
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/antagonists/changeling/panacea.dm b/modular_dripstation/code/modules/antagonists/changeling/panacea.dm
new file mode 100644
index 000000000000..4506cafa82f4
--- /dev/null
+++ b/modular_dripstation/code/modules/antagonists/changeling/panacea.dm
@@ -0,0 +1,39 @@
+/datum/action/changeling/panacea/sting_action(mob/user)
+ to_chat(user, span_notice("We cleanse impurities from our form."))
+ var/mob/living/simple_animal/horror/H = user.has_horror_inside()
+ if(H)
+ H.leave_victim()
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ C.vomit(0)
+ to_chat(user, span_notice("A parasite exits our form."))
+ ..()
+ var/list/bad_organs = list(
+ user.getorgan(/obj/item/organ/body_egg),
+ user.getorgan(/obj/item/organ/internal/shadowtumor),
+ user.getorgan(/obj/item/organ/zombie_infection))
+
+ for(var/o in bad_organs)
+ var/obj/item/organ/O = o
+ if(!istype(O))
+ continue
+
+ O.Remove(user)
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ C.vomit(0)
+ O.forceMove(get_turf(user))
+
+ user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10)
+ user.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 20)
+ user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10)
+ user.reagents.add_reagent(/datum/reagent/medicine/mannitol/advanced, 25)
+
+ if(isliving(user))
+ var/mob/living/L = user
+ for(var/thing in L.diseases)
+ var/datum/disease/D = thing
+ if(D.severity == DISEASE_SEVERITY_POSITIVE)
+ continue
+ D.cure()
+ return TRUE
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/antagonists/horror/horror_chemicals.dm b/modular_dripstation/code/modules/antagonists/horror/horror_chemicals.dm
new file mode 100644
index 000000000000..9de3b73ea26e
--- /dev/null
+++ b/modular_dripstation/code/modules/antagonists/horror/horror_chemicals.dm
@@ -0,0 +1,4 @@
+/datum/horror_chem/mannitol
+ chemname = "mannitol"
+ R = /datum/reagent/medicine/mannitol/advanced
+ chem_desc = "Heals brain damage."
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/antagonists/nukeop/nukeop.dm b/modular_dripstation/code/modules/antagonists/nukeop/nukeop.dm
new file mode 100644
index 000000000000..852269504c02
--- /dev/null
+++ b/modular_dripstation/code/modules/antagonists/nukeop/nukeop.dm
@@ -0,0 +1,212 @@
+/datum/antagonist/nukeop
+ antag_moodlet = /datum/mood_event/slaughter
+
+/datum/antagonist/nukeop/on_gain()
+ . = ..()
+ equip_op()
+ give_alias()
+ memorize_code()
+ if(send_to_spawnpoint)
+ move_to_spawnpoint()
+ // grant extra TC for the people who start in the nukie base ie. not the lone op
+ var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5)
+ var/datum/component/uplink/U = owner.find_syndicate_uplink()
+ if (U)
+ U.telecrystals += extra_tc
+
+//species toggling
+/datum/antagonist/nukeop
+ var/whitelist_species = list("human", "felinid")
+
+/datum/antagonist/nukeop/equip_op()
+ if(!ishuman(owner.current))
+ return
+ var/mob/living/carbon/human/H = owner.current
+ if(isnull(H.client) || !(H.dna.species.id in whitelist_species))
+ H.set_species(/datum/species/human) //Plasamen burn up otherwise, and lizards are vulnerable to asimov AIs
+ if(H.client.prefs.read_preference(/datum/preference/toggle/purrbation)) //Yeah, above is right, but who cares about felinid players?
+ purrbation_toggle_onlyhumans(H)
+ var/chosen_name = H.dna.species.random_name(H.gender,0,pick(GLOB.last_names))
+ owner.current.real_name = "[chosen_name]"
+ if(H.has_trauma_type(/datum/brain_trauma/severe/paralysis/paraplegic, TRAUMA_RESILIENCE_ABSOLUTE))
+ H.cure_trauma_type(/datum/brain_trauma/severe/paralysis/paraplegic, TRAUMA_RESILIENCE_ABSOLUTE) //you won it, gamer
+ if(is_blind(H))
+ H.cure_blind()
+
+ H.equipOutfit(nukeop_outfit)
+ return TRUE
+
+//naming
+/syndicate_name()
+ var/name = ""
+
+ // Prefix
+ name += pick("Clandestine", "Prima", "Blue", "Zero-G", "Max", "Blasto", "Waffle", "North", "Omni", "Newton", "Cyber", "Bonk", "Gene", "Gib", "Vahlen")
+
+ // Suffix
+ if (prob(80))
+ name += " "
+
+ // Full
+ if (prob(60))
+ name += pick("Syndicate", "Consortium", "Collective", "Corporation", "Group", "Holdings", "Biotech", "Industries", "Systems", "Products", "Chemicals", "Pharmaceuticals", "Enterprises", "Creations", "International", "Intergalactic", "Interplanetary", "Foundation", "Positronics", "Hive")
+ // Broken
+ else
+ name += pick("Syndi", "Corp", "Bio", "System", "Prod", "Chem", "Inter", "Hive")
+ name += pick("", "-")
+ name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Dyne", "Code")
+ // Small
+ else
+ name += pick("-", "")
+ name += pick("Tech", "Sun", "Co", "Tek", "X", "Inc", "Gen", "Star", "Dyne", "Code", "Hive", "Group")
+
+ return name
+
+
+/datum/antagonist/nukeop/give_alias()
+ if(nuke_team && nuke_team.syndicate_name)
+ var/mob/living/carbon/human/H = owner.current
+ var/number = 1
+ number = nuke_team.members.Find(owner)
+ H.replace_op_id(H,"Operative #[number]", nuke_team.syndicate_name)
+ addtimer(CALLBACK(src, PROC_REF(op_rename)), 1)
+
+/datum/antagonist/nukeop/proc/op_rename()
+ var/mob/living/op_mob = owner.current
+ var/newname = sanitize_name(reject_bad_text(tgui_input_text(op_mob, "You are the [name] of [nuke_team.syndicate_name]. Would you like to change your name to something else?", "Name change", op_mob.real_name, MAX_NAME_LEN)))
+ if (!newname)
+ return
+
+ op_mob.real_name = "[newname]"
+
+
+//////Nuke leader//////
+/datum/antagonist/nukeop/leader
+ antag_hud_name = "syndleader"
+
+/datum/antagonist/nukeop/leader/give_alias()
+ title = pick("Boss", "Commander", "Chief", "Director", "Overlord")
+ var/mob/living/carbon/human/H = owner.current
+ if(nuke_team && nuke_team.syndicate_name)
+ H.replace_op_id(H, title, nuke_team.syndicate_name)
+ else
+ H.replace_op_id(H,title,"Syndicate")
+ addtimer(CALLBACK(src, PROC_REF(op_rename)), 1)
+ addtimer(CALLBACK(src, PROC_REF(leader_title_rename)), 1)
+
+/datum/antagonist/nukeop/leader/proc/leader_title_rename()
+ switch(tgui_alert(owner.current, "Do you want to change your title?", "Select",list("Manually", "Auto", "No")))
+ if("Manually")
+ var/newtitle = tgui_input_text(owner.current, "Change your title", "Name change", title, MAX_NAME_LEN)
+ title = newtitle
+ if("Auto")
+ var/newtitle = tgui_alert(owner.current, "Choose your destiny","Choice",list("Boss", "Commander", "Chief", "Director", "Overlord"))
+ title = newtitle
+ if("No")
+ return
+ owner.current.replace_op_id(owner.current, title, nuke_team.syndicate_name)
+
+/datum/antagonist/nukeop/leader/nuketeam_name_assign()
+ if(!nuke_team)
+ return
+ switch(tgui_alert(owner.current, "Please select how you want to rename your team.", "Select",list("Manually", "Automatic", "Cancel")))
+ if("Manually")
+ nuke_team.rename_team(title,ask_name())
+ if("Automatic")
+ nuke_team.rename_team(title,automatic_select())
+ if("Cancel")
+ return
+
+/datum/antagonist/nukeop/leader/proc/automatic_select()
+ var/name = ""
+ var/list/prefix = list("Clandestine", "Prima", "Blue", "Zero-G", "Max", "Blasto", "Waffle", "Donk", "North", "Omni", "Newton", "Cyber", "Bonk", "Gene", "Gib", "Vahlen")
+ var/list/fullsuffix = list("Syndicate", "Consortium", "Collective", "Corporation", "Group", "Holdings", "Biotech", "Industries", "Systems", "Products", "Chemicals", "Pharmaceuticals", "Enterprises", "Creations", "International", "Intergalactic", "Interplanetary", "Foundation", "Positronics", "Hive")
+ var/list/customfirstsuffix = list("Syndi", "Corp", "Bio", "System", "Prod", "Chem", "Inter", "Hive")
+ var/list/customsecondsuffix = list("Tech", "Sun", "Co", "Tek", "X", "Inc", "Dyne", "Code")
+ var/list/shortsuffix = list("Tech", "Sun", "Co", "Tek", "X", "Inc", "Gen", "Star", "Dyne", "Code", "Hive", "Group")
+
+ var/name_prefix = tgui_alert(owner.current, "Please select prefix", "Select", prefix)
+ name += name_prefix
+
+ // Suffix
+ switch(tgui_alert(owner.current, "Please select suffix", "Select",list("Full", "Custom", "Small")))
+ if("Full")
+ name += " "
+ var/name_suffix = tgui_alert(owner.current, "Please select suffix", "Select", fullsuffix)
+ name += name_suffix
+ if ("Custom")
+ name += " "
+ var/name_firstsuffix = tgui_alert(owner.current, "Please select first suffix", "Select", customfirstsuffix)
+ name += name_firstsuffix
+ switch(tgui_alert(owner.current, "Please select second suffix", "Select", list("None", "-")))
+ if("None")
+ name += ""
+ if("-")
+ name += "-"
+ var/name_secondsuffix = tgui_alert(owner.current, "Please select second suffix", "Select", customsecondsuffix)
+ name += name_secondsuffix
+ if("Small")
+ switch(tgui_alert(owner.current, "Please select second suffix", "Select", list("None", "-")))
+ if("None")
+ name += ""
+ if("-")
+ name += "-"
+ var/name_shortsuffix = tgui_alert(owner.current, "Please select short suffix", "Select", shortsuffix)
+ name += name_shortsuffix
+
+ return name
+
+/datum/antagonist/nukeop/leader/ask_name()
+ var/oldname = "Syndicate"
+ if(nuke_team.syndicate_name)
+ oldname = nuke_team.syndicate_name
+ var/newname = tgui_input_text(owner.current,"You are the nuke operative [title]. Please choose a last name of your Dream Team.", "Name change",oldname)
+ if (!newname)
+ newname = oldname
+ else
+ newname = reject_bad_name(newname)
+ if(!newname)
+ newname = oldname
+
+ return capitalize(newname)
+
+/datum/team/nuclear/rename_team(title,new_name)
+ syndicate_name = new_name
+ name = "[syndicate_name] Team"
+ for(var/I in members)
+ var/datum/mind/synd_mind = I
+ var/mob/living/carbon/human/H = synd_mind.current
+ if(!istype(H))
+ continue
+ if(synd_mind.has_antag_datum(/datum/antagonist/nukeop/leader))
+ H.replace_op_id(H,title, syndicate_name)
+ else
+ var/number = 1
+ number = members.Find(synd_mind)
+ H.replace_op_id(H,"Operative #[number]", syndicate_name)
+
+/atom/proc/replace_op_id(op,newname,squad_name)
+ var/mob/living/carbon/oper = op
+ var/list/searching = oper.get_all_gear()
+ var/search_id = 1
+
+ for(var/A in searching)
+ if( search_id && istype(A, /obj/item/card/id) )
+ var/obj/item/card/id/ID = A
+ ID.registered_name = "[squad_name] [newname]"
+ ID.assignment = squad_name
+ ID.originalassignment = squad_name
+ ID.registered_age = null
+ if(istype(ID, /obj/item/card/id/syndicate))
+ var/obj/item/card/id/syndicate/SID = ID
+ SID.forged = TRUE
+ SID.anyone = TRUE
+ ID.update_label()
+ if(istype(ID.loc, /obj/item/computer_hardware/card_slot))
+ var/obj/item/computer_hardware/card_slot/CS = ID.loc
+ CS.holder?.update_label()
+ balloon_alert(op, "name replaced")
+ search_id = 0
+
+/datum/antagonist/nukeop/lone
+ antag_hud_name = "loneop"
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/assembly/signaler.dm b/modular_dripstation/code/modules/assembly/signaler.dm
index 95063a74d2bc..36e99a680f3a 100644
--- a/modular_dripstation/code/modules/assembly/signaler.dm
+++ b/modular_dripstation/code/modules/assembly/signaler.dm
@@ -1,2 +1,14 @@
/obj/item/assembly/signaler
icon = 'modular_dripstation/icons/obj/assemblies/new_assemblies.dmi'
+
+/obj/item/assembly/signaler/anomaly/pyro
+ icon_state = "pyro_core"
+
+/obj/item/assembly/signaler/anomaly/grav
+ icon_state = "grav_core"
+
+/obj/item/assembly/signaler/anomaly/flux
+ icon_state = "flux_core"
+
+/obj/item/assembly/signaler/anomaly/vortex
+ icon_state = "vortex_core"
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/bepis/all_nodes.dm b/modular_dripstation/code/modules/bepis/all_nodes.dm
new file mode 100644
index 000000000000..bb93c7073569
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/all_nodes.dm
@@ -0,0 +1,51 @@
+////////////////////////B.E.P.I.S. Locked Techs////////////////////////
+/datum/techweb_node/light_apps
+ id = "light_apps"
+ display_name = "Illumination Applications"
+ description = "Applications of lighting and vision technology not originally thought to be commercially viable."
+ prereq_ids = list("base")
+ design_ids = list("bright_helmet", "rld_mini")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ hidden = TRUE
+ experimental = TRUE
+
+/datum/techweb_node/spec_eng
+ id = "spec_eng"
+ display_name = "Specialized Engineering"
+ description = "Conventional wisdom has deemed these engineering products 'technically' safe, but far too dangerous to traditionally condone."
+ prereq_ids = list("base")
+ design_ids = list("eng_gloves", "lava_rods")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ hidden = TRUE
+ experimental = TRUE
+
+/datum/techweb_node/aus_security
+ id = "aus_security"
+ display_name = "Australicus Security Protocols"
+ description = "It is said that security in the Australicus sector is tight, so we took some pointers from their equipment. Thankfully, our sector lacks any signs of these, 'dropbears'."
+ prereq_ids = list("base")
+ design_ids = list("pin_explorer", "stun_boomerang")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ hidden = TRUE
+ experimental = TRUE
+
+/datum/techweb_node/interrogation
+ id = "interrogation"
+ display_name = "Enhanced Interrogation Technology"
+ description = "By cross-referencing several declassified documents from past dictatorial regimes, we were able to develop an incredibly effective interrogation device. \
+ Ethical concerns about loss of free will do not apply to criminals, according to galactic law."
+ prereq_ids = list("base")
+ design_ids = list("hypnochair")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500)
+ hidden = TRUE
+ experimental = TRUE
+/*
+/datum/techweb_node/tackle_advanced
+ id = "tackle_advanced"
+ display_name = "Advanced Grapple Technology"
+ description = "Nanotrasen would like to remind its researching staff that it is never acceptable to \"glomp\" your coworkers, and further \"scientific trials\" on the subject will no longer be accepted in its academic journals."
+ design_ids = list("tackle_dolphin", "tackle_rocket")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ hidden = TRUE
+ experimental = TRUE
+*/
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/bepis/bepis.dm b/modular_dripstation/code/modules/bepis/bepis.dm
new file mode 100644
index 000000000000..360ddbee7d7a
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/bepis.dm
@@ -0,0 +1,294 @@
+//This system is designed to act as an in-between for cargo and science, and the first major money sink in the game outside of just buying things from cargo (As of 10/9/19, anyway).
+
+//economics defined values, subject to change should anything be too high or low in practice.
+
+#define MACHINE_OPERATION 100000
+#define MACHINE_OVERLOAD 500000
+#define MAJOR_THRESHOLD (8*CARGO_CRATE_VALUE)
+#define MINOR_THRESHOLD (4*CARGO_CRATE_VALUE)
+#define STANDARD_DEVIATION (2*CARGO_CRATE_VALUE)
+#define PART_CASH_OFFSET_AMOUNT (0.5*CARGO_CRATE_VALUE)
+
+/obj/machinery/rnd/bepis
+ name = "\improper B.E.P.I.S. Chamber"
+ desc = "A high fidelity testing device which unlocks the secrets of the known universe using the two most powerful substances available to man: excessive amounts of electricity and capital."
+ icon = 'modular_dripstation/icons/obj/bepis/bepis.dmi'
+ icon_state = "chamber"
+ base_icon_state = "chamber"
+ density = TRUE
+ layer = ABOVE_MOB_LAYER
+ circuit = /obj/item/circuitboard/machine/bepis
+
+ ///How much cash the UI and machine are depositing at a time.
+ var/banking_amount = 100
+ ///How much stored player cash exists within the machine.
+ var/banked_cash = 0
+ ///Payer's bank account.
+ var/datum/bank_account/account
+ ///Name on the payer's bank account.
+ var/account_name
+ ///When the BEPIS fails to hand out any reward, the ERROR cause will be a randomly picked string displayed on the UI.
+ var/error_cause = null
+
+ //Vars related to probability and chance of success for testing, using gaussian normal distribution.
+ ///How much cash you will need to obtain a Major Tech Disk reward.
+ var/major_threshold = MAJOR_THRESHOLD
+ ///How much cash you will need to obtain a minor invention reward.
+ var/minor_threshold = MINOR_THRESHOLD
+ ///The standard deviation of the BEPIS's gaussian normal distribution.
+ var/std = STANDARD_DEVIATION
+
+ //Stock part variables
+ ///Multiplier that lowers how much the BEPIS' power costs are. Maximum of 1, upgraded to a minimum of 0.7. See RefreshParts.
+ var/power_saver = 1
+ ///Variability on the money you actively spend on the BEPIS, with higher inaccuracy making the most change, good and bad to spent cash.
+ var/inaccuracy_percentage = 1.5
+ ///How much "cash" is added to your inserted cash efforts for free. Based on manipulator stock part level.
+ var/positive_cash_offset = 0
+ ///How much "cost" is removed from both the minor and major threshold costs. Based on laser stock part level.
+ var/negative_cash_offset = 0
+ ///List of objects that constitute your minor rewards. All rewards are unique or rare outside of the BEPIS.
+ var/minor_rewards = list(
+ //To add a new minor reward, add it here.
+ /obj/item/stack/circuit_stack/full,
+ /obj/item/pen/fountain/survival,
+ /obj/item/circuitboard/machine/sleeper/party,
+ /obj/item/toy/sprayoncan,
+ )
+
+/obj/machinery/rnd/bepis/attackby(obj/item/O, mob/user, params)
+ if(stat & (NOPOWER|BROKEN|MAINT))
+ to_chat(user, span_notice("[src] can't accept money when it's not functioning."))
+ return
+ if(istype(O, /obj/item/holochip) || istype(O, /obj/item/stack/spacecash))
+ var/deposit_value = O.get_item_credit_value()
+ banked_cash += deposit_value
+ qdel(O)
+ say("Deposited [deposit_value] credits into storage.")
+ update_icon()
+ return
+ if(isidcard(O))
+ var/obj/item/card/id/Card = O
+ if(Card.registered_account)
+ account = Card.registered_account
+ if(istype(Card, /obj/item/card/id/departmental_budget))
+ var/obj/item/card/id/departmental_budget/DB = Card
+ account_name = DB.department_name
+ else
+ account_name = Card.registered_name
+ say("New account detected. Console Updated.")
+ else
+ say("No account detected on card. Aborting.")
+ return
+ return ..()
+
+/obj/machinery/rnd/bepis/screwdriver_act(mob/living/user, obj/item/tool)
+ return default_deconstruction_screwdriver(user, "chamber_open", "chamber", tool)
+
+/obj/machinery/rnd/bepis/RefreshParts()
+ . = ..()
+ var/C = 0
+ var/M = 0
+ var/L = 0
+ var/S = 0
+ for(var/obj/item/stock_parts/capacitor/capacitor in component_parts)
+ C += ((capacitor.rating - 1) * 0.1)
+ power_saver = 1 - C
+ for(var/obj/item/stock_parts/manipulator/manipulator in component_parts)
+ M += ((manipulator.rating - 1) * PART_CASH_OFFSET_AMOUNT)
+ positive_cash_offset = M
+ for(var/obj/item/stock_parts/micro_laser/Laser in component_parts)
+ L += ((Laser.rating - 1) * PART_CASH_OFFSET_AMOUNT)
+ negative_cash_offset = L
+ for(var/obj/item/stock_parts/scanning_module/scanning_module in component_parts)
+ S += ((scanning_module.rating - 1) * 0.25)
+ inaccuracy_percentage = (1.5 - S)
+
+/obj/machinery/rnd/bepis/update_icon()
+ if(panel_open == TRUE)
+ icon_state = "[base_icon_state]_open"
+ return ..()
+ if((use_power == ACTIVE_POWER_USE) && (banked_cash > 0) && !(stat & (NOPOWER|BROKEN|MAINT)))
+ icon_state = "[base_icon_state]_active_loaded"
+ return ..()
+ if (((use_power == IDLE_POWER_USE) && (banked_cash > 0)) || (banked_cash > 0) && (stat & (NOPOWER|BROKEN|MAINT)))
+ icon_state = "[base_icon_state]_loaded"
+ return ..()
+ if(use_power == ACTIVE_POWER_USE && !(stat & (NOPOWER|BROKEN|MAINT)))
+ icon_state = "[base_icon_state]_active"
+ return ..()
+ if(((use_power == IDLE_POWER_USE) && (banked_cash == 0)) || (stat & (NOPOWER|BROKEN|MAINT)))
+ icon_state = base_icon_state
+ return ..()
+ return ..()
+
+/obj/machinery/rnd/bepis/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Bepis", name)
+ ui.open()
+ RefreshParts()
+// if(isliving(user))
+// var/mob/living/carbon/human/customer = user
+// account = customer.get_bank_account()
+
+/obj/machinery/rnd/bepis/ui_data(mob/user)
+ var/list/data = list()
+ var/powered = FALSE
+ var/zvalue = ((banking_amount + banked_cash) - (major_threshold - positive_cash_offset - negative_cash_offset))/(std)
+ var/std_success = 0
+ var/prob_success = 0
+ //Admittedly this is messy, but not nearly as messy as the alternative, which is jury-rigging an entire Z-table into the code, or making an adaptive z-table.
+ var/z = abs(zvalue)
+ if(z > 0 && z <= 0.5)
+ std_success = 19.1
+ else if(z > 0.5 && z <= 1.0)
+ std_success = 34.1
+ else if(z > 1.0 && z <= 1.5)
+ std_success = 43.3
+ else if(z > 1.5 && z <= 2.0)
+ std_success = 47.7
+ else if(z > 2.0 && z <= 2.5)
+ std_success = 49.4
+ else
+ std_success = 50
+ if(zvalue > 0)
+ prob_success = 50 + std_success
+ else if(zvalue == 0)
+ prob_success = 50
+ else
+ prob_success = 50 - std_success
+
+ if(use_power == ACTIVE_POWER_USE)
+ powered = TRUE
+ data["account_owner"] = account_name
+ data["amount"] = banking_amount
+ data["stored_cash"] = account?.account_balance
+ data["mean_value"] = (major_threshold - positive_cash_offset - negative_cash_offset)
+ data["error_name"] = error_cause
+ data["power_saver"] = power_saver
+ data["accuracy_percentage"] = inaccuracy_percentage * 100
+ data["positive_cash_offset"] = positive_cash_offset
+ data["negative_cash_offset"] = negative_cash_offset
+ data["manual_power"] = powered ? FALSE : TRUE
+ data["silicon_check"] = issilicon(user)
+ data["success_estimate"] = prob_success
+ return data
+
+/obj/machinery/rnd/bepis/ui_act(action,params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("begin_experiment")
+ if(use_power == IDLE_POWER_USE)
+ return
+ depositcash()
+ if(banked_cash == 0)
+ say("Please select funds to deposit to begin testing.")
+ return
+ calcsuccess()
+ use_power(MACHINE_OPERATION * power_saver) //This thing should eat your APC battery if you're not careful.
+ use_power = IDLE_POWER_USE //Machine shuts off after use to prevent spam and look better visually.
+ update_icon()
+ if("amount")
+ var/input = text2num(params["amount"])
+ if(input)
+ banking_amount = input
+ if("toggle_power")
+ if(use_power == ACTIVE_POWER_USE)
+ use_power = IDLE_POWER_USE
+ else
+ use_power = ACTIVE_POWER_USE
+ update_icon()
+ if("account_reset")
+ if(use_power == IDLE_POWER_USE)
+ return
+ account_name = ""
+ account = null
+ say("Account settings reset.")
+ . = TRUE
+
+/**
+ * Proc that handles the user's account to deposit credits for the BEPIS.
+ * Handles success and fail cases for transferring credits, then logs the transaction and uses small amounts of power.
+ **/
+/obj/machinery/rnd/bepis/proc/depositcash()
+ var/deposit_value = 0
+ deposit_value = banking_amount
+ if(deposit_value == 0)
+ update_icon()
+ say("Attempting to deposit 0 credits. Aborting.")
+ return
+ deposit_value = clamp(round(deposit_value, 1), 1, 10000)
+ if(!account)
+ say("Cannot find user account. Please swipe a valid ID.")
+ return
+ if(!account.has_money(deposit_value))
+ say("You do not possess enough credits.")
+ return
+ account.adjust_money(-deposit_value, "Vending: B.E.P.I.S. Chamber") //The money vanishes, not paid to any accounts.
+ SSblackbox.record_feedback("amount", "BEPIS_credits_spent", deposit_value)
+ log_message("[deposit_value] credits were inserted into [src] by [account.account_holder]", LOG_GAME)
+ banked_cash += deposit_value
+ use_power(1000 * power_saver)
+ return
+
+/**
+ * Proc used to determine the experiment math and results all in one.
+ * Uses banked_cash and stock part levels to determine minor, major, and real gauss values for the BEPIS to hold.
+ * If by the end real is larger than major, You get a tech disk. If all the disks are earned or you at least beat minor, you get a minor reward.
+ **/
+
+/obj/machinery/rnd/bepis/proc/calcsuccess()
+ var/turf/dropturf = null
+ var/gauss_major = 0
+ var/gauss_minor = 0
+ var/gauss_real = 0
+
+ var/turf/open/floor/turfs = get_turf(src)
+ while(length(turfs))
+ var/turf/T = pick_n_take(turfs)
+ if(T.is_blocked_turf())
+ continue
+ else
+ dropturf = T
+ break
+
+ if (!dropturf)
+ dropturf = drop_location()
+ gauss_major = (gaussian(major_threshold, std) - negative_cash_offset) //This is the randomized profit value that this experiment has to surpass to unlock a tech.
+ gauss_minor = (gaussian(minor_threshold, std) - negative_cash_offset) //And this is the threshold to instead get a minor prize.
+ gauss_real = (gaussian(banked_cash, std*inaccuracy_percentage) + positive_cash_offset) //this is the randomized profit value that your experiment expects to give.
+ say("Real: [gauss_real]. Minor: [gauss_minor]. Major: [gauss_major].")
+ flick("chamber_flash",src)
+ update_icon()
+ banked_cash = 0
+ if((gauss_real >= gauss_major)) //Major Success.
+ if(SSresearch.techweb_nodes_experimental.len > 0)
+ say("Experiment concluded with major success. New technology node discovered on technology disc.")
+ new /obj/item/disk/design_disk/bepis/remove_tech(dropturf,1)
+ return
+ say("Expended all available experimental technology nodes. Resorting to minor rewards.")
+ if(gauss_real >= gauss_minor) //Minor Success.
+ var/reward = pick(minor_rewards)
+ new reward(dropturf)
+ say("Experiment concluded with partial success. Dispensing compiled research efforts.")
+ return
+ if(gauss_real <= -1) //Critical Failure
+ say("ERROR: CRITICAL MACHIME MALFUNCTI- ON. CURRENCY IS NOT CRASH. CANNOT COMPUTE COMMAND: 'make bucks'") //not a typo, for once.
+ new /mob/living/simple_animal/hostile/retaliate/frog(dropturf, 1)
+ use_power(MACHINE_OVERLOAD * power_saver) //To prevent gambling at low cost and also prevent spamming for infinite deer.
+ return
+ //Minor Failure
+ error_cause = pick("attempted to sell grey products to American dominated market.","attempted to sell gray products to British dominated market.","placed wild assumption that PDAs would go out of style.","simulated product #76 damaged brand reputation mortally.","simulated business model resembled 'pyramid scheme' by 98.7%.","product accidently granted override access to all station doors.")
+ say("Experiment concluded with zero product viability. Cause of error: [error_cause]")
+ return
+
+
+#undef MACHINE_OPERATION
+#undef MACHINE_OVERLOAD
+#undef MAJOR_THRESHOLD
+#undef MINOR_THRESHOLD
+#undef STANDARD_DEVIATION
+#undef PART_CASH_OFFSET_AMOUNT
diff --git a/modular_dripstation/code/modules/bepis/bepis_board.dm b/modular_dripstation/code/modules/bepis/bepis_board.dm
new file mode 100644
index 000000000000..0aa7b16b954f
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/bepis_board.dm
@@ -0,0 +1,18 @@
+/datum/design/board/bepis
+ name = "B.E.P.I.S. Board"
+ desc = "The circuit board for a B.E.P.I.S."
+ id = "bepis"
+ build_path = /obj/item/circuitboard/machine/bepis
+ category = list("Research Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_CARGO
+
+/obj/item/circuitboard/machine/bepis
+ name = "BEPIS Chamber"
+ icon_state = "supply"
+ build_path = /obj/machinery/rnd/bepis
+ req_components = list(
+ /obj/item/stack/cable_coil = 5,
+ /obj/item/stock_parts/capacitor = 1,
+ /obj/item/stock_parts/manipulator = 1,
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stock_parts/scanning_module = 1)
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/bepis/bepis_designs.dm b/modular_dripstation/code/modules/bepis/bepis_designs.dm
new file mode 100644
index 000000000000..028eeb2d1a8f
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/bepis_designs.dm
@@ -0,0 +1,66 @@
+/datum/design/bright_helmet
+ name = "Workplace-Ready Firefighter Helmet"
+ desc = "By applying state of the art lighting technology to a fire helmet with industry standard photo-chemical hardening methods, this hardhat will protect you from robust workplace hazards."
+ id = "bright_helmet"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 4000, /datum/material/glass = 1000, /datum/material/plastic = 3000, /datum/material/silver = 500)
+ build_path = /obj/item/clothing/head/hardhat/red/upgraded
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
+
+/datum/design/rld_mini
+ name = "Mini Rapid Light Device (MRLD)"
+ desc = "A tool that can deploy portable and standing lighting orbs and glowsticks."
+ id = "rld_mini"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 20000, /datum/material/glass = 10000, /datum/material/plastic = 8000, /datum/material/gold = 2000)
+ build_path = /obj/item/construction/rld/mini
+ category = list("Tool Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SERVICE
+
+/datum/design/eng_gloves
+ name = "Tinkers Gloves"
+ desc = "Overdesigned engineering gloves that have automated construction subroutines dialed in, allowing for faster construction while worn."
+ id = "eng_gloves"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron= 2000, /datum/material/silver= 1500, /datum/material/gold = 1000)
+ build_path = /obj/item/clothing/gloves/tinkerer
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
+
+/datum/design/lavarods
+ name = "Lava-Resistant Iron Rods"
+ id = "lava_rods"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron= 1000, /datum/material/plasma= 500, /datum/material/titanium= 2000)
+ build_path = /obj/item/stack/rods/lava
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
+
+/datum/design/pin_explorer
+ name = "Outback Firing Pin"
+ desc = "This firing pin only shoots while ya ain't on station, fair dinkum!"
+ id = "pin_explorer"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/silver = 1000, /datum/material/gold = 1000, /datum/material/iron = 500)
+ build_path = /obj/item/firing_pin/explorer
+ category = list("Firing Pins")
+ departmental_flags = DEPARTMENTAL_FLAG_SECURITY
+
+/datum/design/stun_boomerang
+ name = "OZtek Boomerang"
+ desc = "Uses reverse flow gravitodynamics to flip its personal gravity back to the thrower mid-flight. Also functions similar to a stun baton."
+ id = "stun_boomerang"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 10000, /datum/material/glass = 4000, /datum/material/silver = 10000, /datum/material/gold = 2000)
+ build_path = /obj/item/melee/baton/boomerang
+ category = list("Weapons")
+ departmental_flags = DEPARTMENTAL_FLAG_SECURITY
+
+/datum/design/board/hypnochair
+ name = "Enhanced Interrogation Chamber Board"
+ desc = "Allows for the construction of circuit boards used to build an Enhanced Interrogation Chamber."
+ id = "hypnochair"
+ build_path = /obj/item/circuitboard/machine/hypnochair
+ category = list("Misc. Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SECURITY
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/bepis/bepis_layout.dm b/modular_dripstation/code/modules/bepis/bepis_layout.dm
new file mode 100644
index 000000000000..5f49659f1a35
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/bepis_layout.dm
@@ -0,0 +1,27 @@
+/datum/techweb_node/light_apps
+ ui_x = 32
+ ui_y = -576
+
+/datum/techweb_node/spec_eng
+ ui_x = -32
+ ui_y = -576
+
+/datum/techweb_node/aus_security
+ ui_x = -96
+ ui_y = -576
+
+/datum/techweb_node/interrogation
+ ui_x = -160
+ ui_y = -576
+
+///datum/techweb_node/free_space1
+// ui_x = -252
+// ui_y = -576
+
+///datum/techweb_node/free_space2
+// ui_x = -352
+// ui_y = -576
+
+///datum/techweb_node/free_space3
+// ui_x = -448
+// ui_y = -576
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/bepis/bounty.dm b/modular_dripstation/code/modules/bepis/bounty.dm
new file mode 100644
index 000000000000..fec472a3659a
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/bounty.dm
@@ -0,0 +1,5 @@
+/datum/bounty/item/science/bepis_disc
+ name = "Reformatted Tech Disk"
+ description = "It turns out the diskettes the BEPIS prints experimental nodes on are extremely space-efficient. Send us one of your spares when you're done with it."
+ reward = 10000
+ wanted_types = list(/obj/item/disk/design_disk/bepis/remove_tech, /obj/item/disk/design_disk/bepis)
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/bepis/designs.dm b/modular_dripstation/code/modules/bepis/designs.dm
new file mode 100644
index 000000000000..13263134a24c
--- /dev/null
+++ b/modular_dripstation/code/modules/bepis/designs.dm
@@ -0,0 +1,37 @@
+/obj/item/disk/design_disk/proc/on_upload(datum/techweb/stored_research)
+ return
+
+/obj/item/disk/design_disk/bepis
+ name = "Old experimental technology disk"
+ desc = "A disk containing some long-forgotten technology from a past age. You hope it still works after all these years. Upload the disk to an R&D Console to redeem the tech."
+ icon_state = "datadisk0"
+ max_blueprints = 0
+
+ ///The bepis node we have the design id's of
+ var/datum/techweb_node/bepis_node
+
+/obj/item/disk/design_disk/bepis/Initialize(mapload)
+ . = ..()
+ var/bepis_id = pick(SSresearch.techweb_nodes_experimental)
+ bepis_node = (SSresearch.techweb_node_by_id(bepis_id))
+
+ for(var/entry in bepis_node.design_ids)
+ var/datum/design/new_entry = SSresearch.techweb_design_by_id(entry)
+ blueprints += new_entry
+
+///Unhide and research our node so we show up in the R&D console.
+/obj/item/disk/design_disk/bepis/on_upload(datum/techweb/stored_research)
+ stored_research.hidden_nodes -= bepis_node.id
+ stored_research.research_node(bepis_node, force = TRUE, auto_adjust_cost = FALSE)
+
+/**
+ * Subtype of Bepis tech disk
+ * Removes the tech disk that's held on it from the experimental node list, making them not show up in future disks.
+ */
+/obj/item/disk/design_disk/bepis/remove_tech
+ name = "Reformatted technology disk"
+ desc = "A disk containing a new, completed tech from the B.E.P.I.S. Upload the disk to an R&D Console to redeem the tech."
+
+/obj/item/disk/design_disk/bepis/remove_tech/Initialize(mapload)
+ . = ..()
+ SSresearch.techweb_nodes_experimental -= bepis_node.id
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/cargo/bounties/progression.dm b/modular_dripstation/code/modules/cargo/bounties/progression.dm
new file mode 100644
index 000000000000..085bd8b6f144
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/bounties/progression.dm
@@ -0,0 +1,15 @@
+/datum/bounty/item/progression/mining_advanced/reward_string()
+ return "[reward] Credits and clearance to order high quality plasmacutters"
+
+/*
+/datum/bounty/item/progression/drakeslayer
+ name = "Hardsuit Initiatives"
+ description = "Strange dragon-like fauna detected in the area. Provide us a few examples of scaly material from this creatures and we`ll pay in reward."
+ reward = 20000
+ required_count = 10
+ wanted_types = list(/obj/item/stack/sheet/animalhide/ashdrake, /obj/item/stack/sheet/animalhide/carpdragon) //let`s get down some lizards
+ unlocked_crates = list(/datum/supply_pack/clearance/heavymining)
+
+/datum/bounty/item/progression/drakeslayer/reward_string()
+ return "[reward] Credits and clearance to order high quality mining gear."
+*/
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/cargo/bounties/syndicate.dm b/modular_dripstation/code/modules/cargo/bounties/syndicate.dm
new file mode 100644
index 000000000000..0f07736f676f
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/bounties/syndicate.dm
@@ -0,0 +1,24 @@
+/datum/bounty/item/syndicate/secbudget
+ name = "!&@#DEFENCE BUDGET CARD!#@*$"
+ description = "!&@#WE ARE INTERESTED IN CRYPTO CODES OF NANOTRASEN BUDGET CARDS. WE WANT SECS ONE. DO NOT FAIL US IN THIS TASK.#@*$"
+ wanted_types = list(/obj/item/card/id/departmental_budget/sec)
+ reward = 5 //you gotta take it from armory
+/datum/bounty/item/syndicate/combatmagboots
+ name = "!&@#COMBAT MAGBOOTS!#@*$"
+ description = "!&@#WE'RE LOOKING FOR NANOTRASEN ADVANCED MAGNETIC TECHNOLOGY. PROVIDE US SOME EXAMPLES FROM STATIONBOARD SECURITY DEPARTMENT.#@*$"
+ wanted_types = list(/obj/item/clothing/shoes/magboots/security)
+ required_count = 3
+ reward = 4
+/datum/bounty/item/syndicate/hoshardsuit
+ name = "!&@#HEAD OF SECURITY HARDSUIT!#@*$"
+ description = "!&@#SOME PARTS OF SECURITY PLATESHIELDING ARE EXPENSIVE ENOUGH TO BE SOLD TO SOME COLLECTORS. YOU KNOW WHAT TO DO.#@*$"
+ wanted_types = list(/obj/item/clothing/suit/space/hardsuit/security/hos)
+ reward = 6 //you gotta take down a HoS
+/datum/bounty/item/syndicate/gygaxboards
+ name = "!&@#GYGAX EXOSUIT BOARDS!#@*$"
+ description = "!&@#AFTER A RECENT OPERATION, SEVERAL OF OUR DARK GYGAX UNITS HAVE BEEN LEFT INOPERABLE. SEND SOME REPLACEMENT BOARDS TO US FOR A REWARD#@*$"
+ wanted_types = list(/obj/item/circuitboard/mecha/gygax/peripherals,/obj/item/circuitboard/mecha/gygax/targeting,/obj/item/circuitboard/mecha/gygax/main)
+ reward = 3
+ required_count = 10
+/datum/bounty/item/syndicate/phazon
+ reward = 14 //you need an anomaly core and robotics for this one broski, it's gotta be worth a lot
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/cargo/export_scaner.dm b/modular_dripstation/code/modules/cargo/export_scaner.dm
new file mode 100644
index 000000000000..0d792f291c4f
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/export_scaner.dm
@@ -0,0 +1,2 @@
+/obj/item/export_scanner
+ slot_flags = ITEM_SLOT_BELT
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/cargo/markets/_market.dm b/modular_dripstation/code/modules/cargo/markets/_market.dm
new file mode 100644
index 000000000000..3c264289cd2b
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/_market.dm
@@ -0,0 +1,61 @@
+/datum/market
+ /// Name for the market.
+ var/name = "huh?"
+
+ /// Available shipping methods and prices, just leave the shipping method out that you don't want to have.
+ var/list/shipping
+
+ // Automatic vars, do not touch these.
+ /// Items available from this market, populated by SSblackmarket on initialization. Automatically assigned, so don't manually adjust.
+ var/list/available_items = list()
+ /// Item categories available from this market, only items which are in these categories can be gotten from this market. Automatically assigned, so don't manually adjust.
+ var/list/categories = list()
+
+/// Adds item to the available items and add it's category if it is not in categories yet.
+/datum/market/proc/add_item(datum/market_item/item)
+ if(!prob(initial(item.availability_prob)))
+ return FALSE
+
+ if(ispath(item))
+ item = new item()
+
+ if(!(item.category in categories))
+ categories += item.category
+ available_items[item.category] = list()
+
+ available_items[item.category] += item
+ return TRUE
+
+/// Handles buying the item, this is mainly for future use and moving the code away from the uplink.
+/datum/market/proc/purchase(item, category, method, obj/item/market_uplink/uplink, user)
+ if(!istype(uplink) || !(method in shipping))
+ return FALSE
+
+ for(var/datum/market_item/I in available_items[category])
+ if(I.type != item)
+ continue
+ var/price = I.price + shipping[method]
+
+ if(!uplink.current_user)///There is no ID card on the user, or the ID card has no account
+ to_chat(user, span_warning("The uplink sparks, as it can't identify an ID card with a bank account on you."))
+ return FALSE
+ var/balance = uplink?.current_user.account_balance
+
+ // I can't get the price of the item and shipping in a clean way to the UI, so I have to do this.
+ if(balance < price)
+ to_chat(user, span_warning("You don't have enough credits in [uplink] for [I] with [method] shipping."))
+ return FALSE
+
+ if(I.buy(uplink, user, method))
+ uplink.current_user.adjust_money(-price, "Other: Third Party Transaction")
+ if(ismob(user))
+ var/mob/m_user = user
+ m_user.playsound_local(get_turf(m_user), 'sound/machines/twobeep_high.ogg', 50, TRUE)
+ return TRUE
+ return FALSE
+
+/datum/market/blackmarket
+ name = "Black Market"
+ shipping = list(SHIPPING_METHOD_LTSRBT =50,
+ SHIPPING_METHOD_LAUNCH =10,
+ SHIPPING_METHOD_TELEPORT=75)
diff --git a/modular_dripstation/code/modules/cargo/markets/market_item.dm b/modular_dripstation/code/modules/cargo/markets/market_item.dm
new file mode 100644
index 000000000000..2113e1c846d5
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_item.dm
@@ -0,0 +1,76 @@
+/datum/market_item
+ /// Name for the item entry used in the uplink.
+ var/name
+ /// Description for the item entry used in the uplink.
+ var/desc
+ /// The category this item belongs to, should be already declared in the market that this item is accessible in.
+ var/category
+ /// "/datum/market"s that this item should be in, used by SSblackmarket on init.
+ var/list/markets = list(/datum/market/blackmarket)
+
+ /// Price for the item, if not set creates a price according to the *_min and *_max vars.
+ var/price
+ /// How many of this type of item is available, if not set creates a price according to the *_min and *_max vars.
+ var/stock
+
+ /// Path to or the item itself what this entry is for, this should be set even if you override spawn_item to spawn your item.
+ var/item
+
+ /// Minimum price for the item if generated randomly.
+ var/price_min = 0
+ /// Maximum price for the item if generated randomly.
+ var/price_max = 0
+ /// Minimum amount that there should be of this item in the market if generated randomly. This defaults to 1 as most items will have it as 1.
+ var/stock_min = 1
+ /// Maximum amount that there should be of this item in the market if generated randomly.
+ var/stock_max = 0
+ /// Probability for this item to be available. Used by SSblackmarket on init.
+ var/availability_prob = 0
+
+/datum/market_item/New()
+ if(isnull(price))
+ price = rand(price_min, price_max)
+ if(isnull(stock))
+ stock = rand(stock_min, stock_max)
+
+/// Used for spawning the wanted item, override if you need to do something special with the item.
+/datum/market_item/proc/spawn_item(loc)
+ return new item(loc)
+
+/// Buys the item and makes SSblackmarket handle it.
+/datum/market_item/proc/buy(obj/item/market_uplink/uplink, mob/buyer, shipping_method)
+ // Sanity
+ if(!istype(uplink) || !istype(buyer))
+ return FALSE
+
+ // This shouldn't be able to happen unless there was some manipulation or admin fuckery.
+ if(!item || stock <= 0)
+ return FALSE
+
+ // Alright, the item has been purchased.
+ var/datum/market_purchase/purchase = new(src, uplink, shipping_method)
+
+ // SSblackmarket takes care of the shipping.
+ if(SSblackmarket.queue_item(purchase))
+ stock--
+ buyer.log_message("has succesfully purchased [name] using [shipping_method] for shipping.", LOG_GAME)
+ return TRUE
+ return FALSE
+
+// This exists because it is easier to keep track of all the vars this way.
+/datum/market_purchase
+ /// The entry being purchased.
+ var/datum/market_item/entry
+ /// Instance of the item being sent.
+ var/item
+ /// The uplink where this purchase was done from.
+ var/obj/item/market_uplink/uplink
+ /// Shipping method used to buy this item.
+ var/method
+
+/datum/market_purchase/New(_entry, _uplink, _method)
+ entry = _entry
+ if(!ispath(entry.item))
+ item = entry.item
+ uplink = _uplink
+ method = _method
diff --git a/modular_dripstation/code/modules/cargo/markets/market_items/clothing.dm b/modular_dripstation/code/modules/cargo/markets/market_items/clothing.dm
new file mode 100644
index 000000000000..42fd81856b51
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_items/clothing.dm
@@ -0,0 +1,80 @@
+/datum/market_item/clothing
+ category = "Clothing"
+
+/datum/market_item/clothing/ninja_mask
+ name = "Space Ninja Mask"
+ desc = "Apart from being acid, lava, fireproof and being hard to take off someone it does nothing special on it's own."
+ item = /obj/item/clothing/mask/gas/space_ninja
+
+ price_min = CARGO_CRATE_VALUE
+ price_max = CARGO_CRATE_VALUE * 2.5
+ stock_max = 3
+ availability_prob = 40
+
+/datum/market_item/clothing/sunglasses
+ name = "Space Sunglasses"
+ desc = "Just cool sunglasses for all your needs."
+ item = /obj/item/clothing/glasses/sunglasses
+
+ price_min = CARGO_CRATE_VALUE
+ price_max = CARGO_CRATE_VALUE * 2.5
+ stock_max = 3
+ availability_prob = 40
+
+/datum/market_item/clothing/durathread_vest
+ name = "Durathread Vest"
+ desc = "Don't let them tell you this stuff is \"Like asbestos\" or \"Pulled from the market for safety concerns\". It could be the difference between a robusting and a retaliation."
+ item = /obj/item/clothing/suit/armor/vest/durathread
+
+ price_min = CARGO_CRATE_VALUE
+ price_max = CARGO_CRATE_VALUE * 2
+ stock_max = 4
+ availability_prob = 50
+
+/datum/market_item/clothing/durathread_helmet
+ name = "Durathread Helmet"
+ desc = "Customers ask why it's called a helmet when it's just made from armoured fabric and I always say the same thing: No refunds."
+ item = /obj/item/clothing/head/helmet/durathread
+
+ price_min = CARGO_CRATE_VALUE * 0.5
+ price_max = CARGO_CRATE_VALUE
+ stock_max = 4
+ availability_prob = 50
+
+/datum/market_item/clothing/full_spacesuit_set
+ name = "\improper Nanotrasen Branded Spacesuit Box"
+ desc = "A few boxes of \"Old Style\" space suits fell off the back of a space truck."
+ item = /obj/item/storage/box
+
+ price_min = CARGO_CRATE_VALUE * 7.5
+ price_max = CARGO_CRATE_VALUE * 20
+ stock_max = 3
+ availability_prob = 30
+
+/datum/market_item/clothing/full_spacesuit_set/spawn_item(loc)
+ var/obj/item/storage/box/B = ..()
+ B.name = "Spacesuit Box"
+ B.desc = "It has an NT logo on it."
+ new /obj/item/clothing/suit/space(B)
+ new /obj/item/clothing/head/helmet/space(B)
+ return B
+
+/datum/market_item/clothing/chameleon_hat
+ name = "Chameleon Hat"
+ desc = "Pick any hat you want with this Handy device. Not Quality Tested."
+ item = /obj/item/clothing/head/chameleon/broken
+
+ price_min = CARGO_CRATE_VALUE * 0.5
+ price_max = CARGO_CRATE_VALUE
+ stock_max = 2
+ availability_prob = 70
+
+/datum/market_item/clothing/rocket_boots
+ name = "Rocket Boots"
+ desc = "We found a pair of jump boots and overclocked the hell out of them. No liability for grevious harm to or with a body."
+ item = /obj/item/clothing/shoes/bhop/rocket
+
+ price_min = CARGO_CRATE_VALUE * 5
+ price_max = CARGO_CRATE_VALUE * 15
+ stock_max = 1
+ availability_prob = 40
diff --git a/modular_dripstation/code/modules/cargo/markets/market_items/consumables.dm b/modular_dripstation/code/modules/cargo/markets/market_items/consumables.dm
new file mode 100644
index 000000000000..e61c2bae1041
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_items/consumables.dm
@@ -0,0 +1,53 @@
+/datum/market_item/consumable
+ category = "Consumables"
+
+/datum/market_item/consumable/donk_pocket_box
+ name = "Box of Donk Pockets"
+ desc = "A well packaged box containing the favourite snack of every spacefarer."
+ item = /obj/item/storage/box/donkpockets
+
+ stock_min = 2
+ stock_max = 5
+ price_min = CARGO_CRATE_VALUE * 1.625
+ price_max = CARGO_CRATE_VALUE * 2
+ availability_prob = 80
+
+/datum/market_item/consumable/suspicious_pills
+ name = "Bottle of Suspicious Pills"
+ desc = "A random cocktail of luxury drugs that are sure to put a smile on your face!"
+ item = /obj/item/storage/pill_bottle
+
+ stock_min = 2
+ stock_max = 3
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 3.5
+ availability_prob = 50
+
+/datum/market_item/consumable/suspicious_pills/spawn_item(loc)
+ var/pillbottle = pick(list(/obj/item/storage/pill_bottle/zoom,
+ /obj/item/storage/pill_bottle/happy,
+ /obj/item/storage/pill_bottle/lsd,
+ /obj/item/storage/pill_bottle/aranesp,
+ /obj/item/storage/pill_bottle/stimulant))
+ return new pillbottle(loc)
+
+/datum/market_item/consumable/floor_pill
+ name = "Strange Pill"
+ desc = "The Russian Roulette of the Maintenance Tunnels."
+ item = /obj/item/reagent_containers/pill/floorpill
+
+ stock_min = 5
+ stock_max = 35
+ price_min = CARGO_CRATE_VALUE * 0.05
+ price_max = CARGO_CRATE_VALUE * 0.3
+ availability_prob = 50
+
+/datum/market_item/consumable/pumpup
+ name = "Maintenance Pump-Up"
+ desc = "Resist any Baton stun with this handy device!"
+ item = /obj/item/reagent_containers/autoinjector/medipen/pumpup
+
+ stock_max = 3
+ price_min = CARGO_CRATE_VALUE * 0.25
+ price_max = CARGO_CRATE_VALUE * 0.75
+ availability_prob = 90
diff --git a/modular_dripstation/code/modules/cargo/markets/market_items/misc.dm b/modular_dripstation/code/modules/cargo/markets/market_items/misc.dm
new file mode 100644
index 000000000000..70668d1c3b02
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_items/misc.dm
@@ -0,0 +1,78 @@
+/datum/market_item/misc
+ category = "Miscellaneous"
+
+/datum/market_item/misc/Clear_PDA
+ name = "Clear PDA"
+ desc = "Show off your style with this limited edition clear PDA!."
+ item = /obj/item/modular_computer/tablet/pda/preset/basic
+
+ price_min = CARGO_CRATE_VALUE * 1.25
+ price_max = CARGO_CRATE_VALUE *3
+ stock_max = 2
+ availability_prob = 50
+
+/datum/market_item/misc/jade_Lantern
+ name = "Jade Lantern"
+ desc = "Found in a box labeled 'Danger: Radioactive'. Probably safe."
+ item = /obj/item/flashlight/lantern/jade
+
+ price_min = CARGO_CRATE_VALUE * 0.75
+ price_max = CARGO_CRATE_VALUE * 2.5
+ stock_max = 2
+ availability_prob = 45
+
+/datum/market_item/misc/cap_gun
+ name = "Cap Gun"
+ desc = "Prank your friends with this harmless gun! Harmlessness guranteed."
+ item = /obj/item/toy/gun
+
+ price_min = CARGO_CRATE_VALUE * 0.25
+ price_max = CARGO_CRATE_VALUE
+ stock_max = 6
+ availability_prob = 80
+
+/datum/market_item/misc/shoulder_holster
+ name = "Shoulder holster"
+ desc = "Yeehaw, hardboiled friends! This holster is the first step in your dream of becoming a detective and being allowed to shoot real guns!"
+ item = /obj/item/storage/belt/holster
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ stock_max = 8
+ availability_prob = 70
+
+/datum/market_item/misc/holywater
+ name = "Flask of holy water"
+ desc = "Father Lootius' own brand of ready-made holy water."
+ item = /obj/item/reagent_containers/food/drinks/bottle/holywater
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 3
+ stock_max = 3
+ availability_prob = 40
+
+/datum/market_item/misc/holywater/spawn_item(loc)
+ if (prob(6.66))
+ return new /obj/item/reagent_containers/glass/beaker/unholywater(loc)
+ return ..()
+
+/datum/market_item/misc/strange_seed
+ name = "Strange Seeds"
+ desc = "An Exotic Variety of seed that can contain anything from glow to acid."
+ item = /obj/item/seeds/random
+
+ price_min = CARGO_CRATE_VALUE * 1.6
+ price_max = CARGO_CRATE_VALUE * 1.8
+ stock_min = 2
+ stock_max = 5
+ availability_prob = 50
+
+/datum/market_item/misc/smugglers_satchel
+ name = "Smuggler's Satchel"
+ desc = "This easily hidden satchel can become a versatile tool to anybody with the desire to keep certain items out of sight and out of mind."
+ item = /obj/item/storage/backpack/satchel/flat/empty
+
+ price_min = CARGO_CRATE_VALUE * 3.75
+ price_max = CARGO_CRATE_VALUE * 5
+ stock_max = 2
+ availability_prob = 30
diff --git a/modular_dripstation/code/modules/cargo/markets/market_items/tools.dm b/modular_dripstation/code/modules/cargo/markets/market_items/tools.dm
new file mode 100644
index 000000000000..27aa49dcc130
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_items/tools.dm
@@ -0,0 +1,82 @@
+/datum/market_item/tool
+ category = "Tools"
+
+/datum/market_item/tool/caravan_wrench
+ name = "Experimental Wrench"
+ desc = "The extra fast and handy wrench you always wanted!"
+ item = /obj/item/wrench/caravan
+ stock = 1
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ availability_prob = 20
+
+/datum/market_item/tool/caravan_wirecutters
+ name = "Experimental Wirecutters"
+ desc = "The extra fast and handy wirecutters you always wanted!"
+ item = /obj/item/wirecutters/caravan
+ stock = 1
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ availability_prob = 20
+
+/datum/market_item/tool/caravan_screwdriver
+ name = "Experimental Screwdriver"
+ desc = "The extra fast and handy screwdriver you always wanted!"
+ item = /obj/item/screwdriver/caravan
+ stock = 1
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ availability_prob = 20
+
+/datum/market_item/tool/caravan_crowbar
+ name = "Experimental Crowbar"
+ desc = "The extra fast and handy crowbar you always wanted!"
+ item = /obj/item/crowbar/red/caravan
+ stock = 1
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ availability_prob = 20
+
+/datum/market_item/tool/binoculars
+ name = "Binoculars"
+ desc = "Increase your sight by 150% with this handy Tool!"
+ item = /obj/item/binoculars
+ stock = 1
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4.8
+ availability_prob = 30
+
+/datum/market_item/tool/riot_shield
+ name = "Riot Shield"
+ desc = "Protect yourself from an unexpected Riot at your local Police department!"
+ item = /obj/item/shield/riot
+
+ price_min = CARGO_CRATE_VALUE * 2.25
+ price_max = CARGO_CRATE_VALUE * 3.25
+ stock_max = 2
+ availability_prob = 50
+
+/datum/market_item/tool/thermite_bottle
+ name = "Thermite Bottle"
+ desc = "30u of Thermite to assist in creating a quick access point or get away!"
+ item = /obj/item/reagent_containers/glass/beaker/thermite
+
+ price_min = CARGO_CRATE_VALUE * 2.5
+ price_max = CARGO_CRATE_VALUE * 7.5
+ stock_max = 3
+ availability_prob = 30
+
+/datum/market_item/tool/science_goggles
+ name = "Science Goggles"
+ desc = "These glasses scan the contents of containers and projects their contents to the user in an easy to read format."
+ item = /obj/item/clothing/glasses/science
+
+ price_min = CARGO_CRATE_VALUE * 0.75
+ price_max = CARGO_CRATE_VALUE
+ stock_max = 3
+ availability_prob = 50
diff --git a/modular_dripstation/code/modules/cargo/markets/market_items/weapons.dm b/modular_dripstation/code/modules/cargo/markets/market_items/weapons.dm
new file mode 100644
index 000000000000..2cb48a45b5ee
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_items/weapons.dm
@@ -0,0 +1,65 @@
+/datum/market_item/weapon
+ category = "Weapons"
+
+/datum/market_item/weapon/bear_trap
+ name = "Bear Trap"
+ desc = "Get the janitor back at his own game with this affordable prank kit."
+ item = /obj/item/restraints/legcuffs/beartrap
+
+ price_min = CARGO_CRATE_VALUE * 1.5
+ price_max = CARGO_CRATE_VALUE * 2.75
+ stock_max = 3
+ availability_prob = 50
+
+/datum/market_item/weapon/shotgun_dart
+ name = "Shotgun Dart"
+ desc = "These handy darts can be filled up with any chemical and be shot with a shotgun! \
+ Prank your friends by shooting them with laughter! \
+ Not recommended for comercial use."
+ item = /obj/item/ammo_casing/shotgun/dart
+
+ price_min = CARGO_CRATE_VALUE * 0.05
+ price_max = CARGO_CRATE_VALUE * 0.25
+ stock_min = 10
+ stock_max = 60
+ availability_prob = 40
+
+/datum/market_item/weapon/bone_spear
+ name = "Bone Spear"
+ desc = "Authentic tribal spear, made from real bones! A steal at any price, especially if you're a caveman."
+ item = /obj/item/melee/spear/bonespear
+
+ price_min = CARGO_CRATE_VALUE
+ price_max = CARGO_CRATE_VALUE * 1.5
+ stock_max = 3
+ availability_prob = 60
+
+/datum/market_item/weapon/chainsaw
+ name = "Chainsaw"
+ desc = "A lumberjack's best friend, perfect for cutting trees or limbs efficiently."
+ item = /obj/item/melee/chainsaw
+
+ price_min = CARGO_CRATE_VALUE * 1.75
+ price_max = CARGO_CRATE_VALUE * 3
+ stock_max = 1
+ availability_prob = 35
+
+/datum/market_item/weapon/switchblade
+ name = "Switchblade"
+ desc = "Tunnel Snakes rule!"
+ item = /obj/item/switchblade
+
+ price_min = CARGO_CRATE_VALUE * 1.25
+ price_max = CARGO_CRATE_VALUE * 1.75
+ stock_max = 3
+ availability_prob = 45
+
+/datum/market_item/weapon/emp_grenade
+ name = "EMP Grenade"
+ desc = "Use this grenade for SHOCKING results!"
+ item = /obj/item/grenade/empgrenade
+
+ price_min = CARGO_CRATE_VALUE * 0.5
+ price_max = CARGO_CRATE_VALUE * 2
+ stock_max = 2
+ availability_prob = 50
diff --git a/modular_dripstation/code/modules/cargo/markets/market_telepad.dm b/modular_dripstation/code/modules/cargo/markets/market_telepad.dm
new file mode 100644
index 000000000000..6b6dbab960c4
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_telepad.dm
@@ -0,0 +1,120 @@
+/obj/item/circuitboard/machine/ltsrbt
+ name = "LTSRBT (Machine Board)"
+ icon_state = "bluespacearray"
+ icon = 'modular_dripstation/icons/obj/blackmarket/module.dmi'
+ build_path = /obj/machinery/ltsrbt
+ req_components = list(
+ /obj/item/stack/ore/bluespace_crystal = 2,
+ /obj/item/stock_parts/subspace/ansible = 1,
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stock_parts/scanning_module = 2)
+ def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial)
+
+/obj/machinery/ltsrbt
+ name = "Long-To-Short-Range-Bluespace-Transceiver"
+ desc = "The LTSRBT is a compact teleportation machine for receiving and sending items outside the station and inside the station.\nUsing teleportation frequencies stolen from NT it is near undetectable.\nEssential for any illegal market operations on NT stations.\n"
+ icon = 'modular_dripstation/icons/obj/blackmarket/telecoms.dmi'
+ icon_state = "exonet_node"
+ circuit = /obj/item/circuitboard/machine/ltsrbt
+ density = TRUE
+
+ use_power = IDLE_POWER_USE
+ idle_power_usage = 200
+ active_power_usage = 250
+
+ /// Divider for power_usage_per_teleport.
+ var/power_efficiency = 1
+ /// Power used per teleported which gets divided by power_efficiency.
+ var/power_usage_per_teleport = 10000
+ /// The time it takes for the machine to recharge before being able to send or receive items.
+ var/recharge_time = 0
+ /// Current recharge progress.
+ var/recharge_cooldown = 0
+ /// Base recharge time in seconds which is used to get recharge_time.
+ var/base_recharge_time = 100
+ /// Current /datum/market_purchase being received.
+ var/receiving
+ /// Current /datum/market_purchase being sent to the target uplink.
+ var/transmitting
+ /// Queue for purchases that the machine should receive and send.
+ var/list/datum/market_purchase/queue = list()
+
+/obj/machinery/ltsrbt/Initialize(mapload)
+ . = ..()
+ SSblackmarket.telepads += src
+
+/obj/machinery/ltsrbt/Destroy()
+ SSblackmarket.telepads -= src
+ // Bye bye orders.
+ if(SSblackmarket.telepads.len)
+ for(var/datum/market_purchase/P in queue)
+ SSblackmarket.queue_item(P)
+ . = ..()
+
+/obj/machinery/ltsrbt/RefreshParts()
+ . = ..()
+ recharge_time = base_recharge_time
+ // On tier 4 recharge_time should be 20 and by default it is 80 as scanning modules should be tier 1.
+ for(var/obj/item/stock_parts/scanning_module/scanning_module in component_parts)
+ recharge_time -= scanning_module.rating * 10
+ recharge_cooldown = recharge_time
+
+ power_efficiency = 0
+ for(var/obj/item/stock_parts/micro_laser/laser in component_parts)
+ power_efficiency += laser.rating
+ // Shouldn't happen but you never know.
+ if(!power_efficiency)
+ power_efficiency = 1
+
+/// Adds /datum/market_purchase to queue unless the machine is free, then it sets the purchase to be instantly received
+/obj/machinery/ltsrbt/proc/add_to_queue(datum/market_purchase/purchase)
+ if(!recharge_cooldown && !receiving && !transmitting)
+ receiving = purchase
+ return
+ queue += purchase
+
+/obj/machinery/ltsrbt/process(seconds_per_tick)
+ if(stat & NOPOWER)
+ return
+
+ if(recharge_cooldown > 0)
+ recharge_cooldown -= seconds_per_tick
+ return
+
+ var/turf/T = get_turf(src)
+ if(receiving)
+ var/datum/market_purchase/P = receiving
+
+ if(!P.item || ispath(P.item))
+ P.item = P.entry.spawn_item(T)
+ else
+ var/atom/movable/M = P.item
+ M.forceMove(T)
+
+ use_power(power_usage_per_teleport / power_efficiency)
+ var/datum/effect_system/spark_spread/sparks = new
+ sparks.set_up(5, 1, get_turf(src))
+ sparks.attach(P.item)
+ sparks.start()
+
+ receiving = null
+ transmitting = P
+
+ recharge_cooldown = recharge_time
+ return
+ else if(transmitting)
+ var/datum/market_purchase/P = transmitting
+ if(!P.item)
+ QDEL_NULL(transmitting)
+ if(!(P.item in T.contents))
+ QDEL_NULL(transmitting)
+ return
+ do_teleport(P.item, get_turf(P.uplink))
+ use_power(power_usage_per_teleport / power_efficiency)
+ QDEL_NULL(transmitting)
+
+ recharge_cooldown = recharge_time
+ return
+
+ if(queue.len)
+ receiving = pick_n_take(queue)
diff --git a/modular_dripstation/code/modules/cargo/markets/market_uplink.dm b/modular_dripstation/code/modules/cargo/markets/market_uplink.dm
new file mode 100644
index 000000000000..02a1ff7c877a
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/markets/market_uplink.dm
@@ -0,0 +1,171 @@
+/obj/item/market_uplink
+ name = "\improper Market Uplink"
+ desc = "An market uplink. Usable with markets. You probably shouldn't have this!"
+ icon = 'modular_dripstation/icons/obj/blackmarket/blackmarket.dmi'
+ icon_state = "uplink"
+
+ // UI variables.
+ /// What category is the current uplink viewing?
+ var/viewing_category
+ /// What market is currently being bought from by the uplink?
+ var/viewing_market
+ /// What item is the current uplink attempting to buy?
+ var/selected_item
+ /// Is the uplink in the process of buying the selected item?
+ var/buying
+ ///Reference to the currently logged in user's bank account.
+ var/datum/bank_account/current_user
+ /// List of typepaths for "/datum/market"s that this uplink can access.
+ var/list/accessible_markets = list(/datum/market/blackmarket)
+
+/obj/item/market_uplink/Initialize(mapload)
+ . = ..()
+ // We don't want to go through this at mapload because the SSblackmarket isn't initialized yet.
+ if(mapload)
+ return
+
+ update_viewing_category()
+
+/// Simple internal proc for updating the viewing_category variable.
+/obj/item/market_uplink/proc/update_viewing_category()
+ if(accessible_markets.len)
+ viewing_market = accessible_markets[1]
+ var/list/categories = SSblackmarket.markets[viewing_market].categories
+ if(categories?.len)
+ viewing_category = categories[1]
+
+/obj/item/market_uplink/ui_interact(mob/user, datum/tgui/ui)
+ if(!viewing_category)
+ update_viewing_category()
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "BlackMarketUplink", name)
+ ui.open()
+
+/obj/item/market_uplink/ui_data(mob/user)
+ var/list/data = list()
+ var/datum/market/market = viewing_market ? SSblackmarket.markets[viewing_market] : null
+ var/obj/item/card/id/id_card
+ if(isliving(user))
+ var/mob/living/livin = user
+ id_card = livin.get_idcard()
+ if(id_card?.registered_account)
+ current_user = id_card.registered_account
+ else
+ current_user = null
+ data["categories"] = market ? market.categories : null
+ data["delivery_methods"] = list()
+ if(market)
+ for(var/delivery in market.shipping)
+ data["delivery_methods"] += list(list("name" = delivery, "price" = market.shipping[delivery]))
+ data["money"] = "N/A cr"
+ if(current_user)
+ data["money"] = current_user.account_balance
+ data["buying"] = buying
+ data["items"] = list()
+ data["viewing_category"] = viewing_category
+ data["viewing_market"] = viewing_market
+ if(viewing_category && market)
+ if(market.available_items[viewing_category])
+ for(var/datum/market_item/I in market.available_items[viewing_category])
+ data["items"] += list(list(
+ "id" = I.type,
+ "name" = I.name,
+ "cost" = I.price,
+ "amount" = I.stock,
+ "desc" = I.desc || I.name
+ ))
+ return data
+
+/obj/item/market_uplink/ui_static_data(mob/user)
+ var/list/data = list()
+ data["delivery_method_description"] = SSblackmarket.shipping_method_descriptions
+ data["ltsrbt_built"] = SSblackmarket.telepads.len
+ data["markets"] = list()
+ for(var/M in accessible_markets)
+ var/datum/market/BM = SSblackmarket.markets[M]
+ data["markets"] += list(list(
+ "id" = M,
+ "name" = BM.name
+ ))
+ return data
+
+/obj/item/market_uplink/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("set_category")
+ if(isnull(params["category"]))
+ return
+ if(isnull(viewing_market))
+ return
+ if(!(params["category"] in SSblackmarket.markets[viewing_market].categories))
+ return
+ viewing_category = params["category"]
+ . = TRUE
+ if("set_market")
+ if(isnull(params["market"]))
+ return
+ var/market = text2path(params["market"])
+ if(!(market in accessible_markets))
+ return
+
+ viewing_market = market
+
+ var/list/categories = SSblackmarket.markets[viewing_market].categories
+ if(categories?.len)
+ viewing_category = categories[1]
+ else
+ viewing_category = null
+ . = TRUE
+ if("select")
+ if(isnull(params["item"]))
+ return
+ var/item = text2path(params["item"])
+ selected_item = item
+ buying = TRUE
+ . = TRUE
+ if("cancel")
+ selected_item = null
+ buying = FALSE
+ . = TRUE
+ if("buy")
+ if(isnull(params["method"]))
+ return
+ if(isnull(selected_item))
+ buying = FALSE
+ return
+ var/datum/market/market = SSblackmarket.markets[viewing_market]
+ market.purchase(selected_item, viewing_category, params["method"], src, usr)
+
+ buying = FALSE
+ selected_item = null
+
+/obj/item/market_uplink/blackmarket
+ name = "\improper Black Market Uplink"
+ desc = "An illegal black market uplink. If command wanted you to have these, they wouldn't have made it so hard to get one."
+ icon = 'modular_dripstation/icons/obj/blackmarket/blackmarket.dmi'
+ icon_state = "uplink"
+ //The original black market uplink
+ accessible_markets = list(/datum/market/blackmarket)
+
+
+/datum/crafting_recipe/blackmarket_uplink
+ name = "Black Market Uplink"
+ result = /obj/item/market_uplink/blackmarket
+ time = 15 SECONDS
+ tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_MULTITOOL)
+ reqs = list(
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/assembly/signaler = 1,
+ /obj/item/stack/cable_coil = 15,
+ /obj/item/radio = 1,
+ /obj/item/analyzer = 1)
+ category = CAT_EQUIPMENT
+
+/datum/crafting_recipe/blackmarket_uplink/New()
+ ..()
+ blacklist |= typesof(/obj/item/radio/headset) // because we got shit like /obj/item/radio/off ... WHY!?!
+ blacklist |= typesof(/obj/item/radio/intercom)
diff --git a/modular_dripstation/code/modules/cargo/packs.dm b/modular_dripstation/code/modules/cargo/packs.dm
new file mode 100644
index 000000000000..4921dddcc3b7
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/packs.dm
@@ -0,0 +1,105 @@
+/datum/supply_pack
+ var/order_limit = -1 // The number of times one can order a cargo crate, before it becomes restricted. -1 for infinite
+ var/times_ordered = 0 // Number of times a crate has been ordered in a shift
+ var/order_limit_in_one_order = -1 // The number of times one can order a cargo crate, before it becomes restricted. -1 for infinite
+ var/times_ordered_in_one_order = 0 // Number of times a crate has been ordered in one package
+
+//specialops edit
+/datum/supply_pack/emergency/specialops
+ desc = "(*!&@#TOO CHEAP FOR THAT NULL_ENTRY, HUH OPERATIVE? WELL, THIS LITTLE ORDER CAN STILL HELP YOU OUT IN A PINCH. CONTAINS A BOX OF FIVE EMP GRENADES, THREE SMOKEBOMBS, AN INCENDIARY GRENADE, A \"SLEEPY PEN\" FULL OF NICE TOXINS AND YOUR NEW GEAR!#@*$"
+ cost = 6000
+ order_limit = 2
+ contains = list(/obj/item/storage/box/emps,
+ /obj/item/grenade/smokebomb,
+ /obj/item/grenade/smokebomb,
+ /obj/item/grenade/smokebomb,
+ /obj/item/pen/blue/sleepy,
+ /obj/item/grenade/chem_grenade/incendiary,
+ /obj/item/clothing/glasses/night,
+ /obj/item/storage/belt/holster/syndicate,
+ /obj/item/clothing/mask/gas/syndicate,
+ /obj/item/clothing/under/syndicate/combat,
+ /obj/item/clothing/shoes/combat)
+ crate_name = "crate"
+ crate_type = /obj/structure/closet/crate
+
+//telepad for black market
+/datum/supply_pack/costumes_toys/blackmarket_telepad
+ name = "Black Market LTSRBT"
+ desc = "Need a faster and better way of transporting your illegal goods from and to the \
+ station? Fear not, the Long-To-Short-Range-Bluespace-Transceiver (LTSRBT for short) \
+ is here to help. Contains a LTSRBT circuit, two bluespace crystals, and one ansible."
+ cost = 26000
+ contraband = TRUE
+ contains = list(/obj/item/circuitboard/machine/ltsrbt,
+ /obj/item/stack/ore/bluespace_crystal/artificial = 2,
+ /obj/item/stock_parts/subspace/ansible)
+ crate_name = "crate"
+
+//nullcrate check
+/datum/supply_pack/emergency/nullcrate
+ name = "NULL_ENTRY"
+ desc = "(#@&^$THIS IS YOUR LOVELY PACKAGE THAT CONTAINS SOME RANDOM SYNDICATE STUFF. GIVE EM HELL, OPERATIVE@&!*()"
+ hidden = TRUE
+ order_limit_in_one_order = 2
+ order_limit = 10
+ cost = 12000
+ crate_name = "crate"
+ crate_type = /obj/structure/closet/crate
+ contains = list()
+
+/datum/supply_pack/emergency/nullcrate/fill(obj/structure/closet/crate/C)
+ switch (rand(0,3))
+ if(0)
+ new /obj/item/gun/ballistic/automatic/pistol(C)
+ new /obj/item/ammo_box/magazine/m10mm(C)
+ new /obj/item/ammo_box/magazine/m10mm(C)
+ if(1)
+ new /obj/item/gun/ballistic/rifle/boltaction(C)
+ new /obj/item/ammo_box/a762(C)
+ if(2)
+ new /obj/item/gun/ballistic/automatic/toy/pistol/riot(C)
+ new /obj/item/ammo_box/magazine/toy/pistol/riot(C)
+ new /obj/item/ammo_box/foambox/riot(C)
+ if(3)
+ new /obj/item/pen/red/edagger(C)
+ new /obj/item/grenade/plastic/c4(C)
+ for(var/i in 1 to 2)
+ //Gear
+ var/item = pick(/obj/item/clothing/shoes/magboots/syndie,
+ /obj/item/clothing/gloves/fingerless/bigboss,
+ /obj/item/storage/backpack/duffelbag/syndie,
+ /obj/item/storage/belt/chameleon/syndicate,
+ /obj/item/clothing/under/chameleon,
+ /obj/item/clothing/suit/chameleon,
+ /obj/item/flashlight/emp,
+ /obj/item/syndicateReverseCard,
+ /obj/item/camera_bug,
+ /obj/item/storage/toolbox/syndicate)
+ new item(C)
+ //Misk
+ item = pick(/obj/item/storage/box/syndie_kit/cutouts,
+ /obj/item/disk/nuclear/fake,
+ /obj/item/toy/plush/carpplushie/dehy_carp,
+ /obj/item/storage/pill_bottle/gummies/omnizine,
+ /obj/item/storage/pill_bottle/gummies/sleepy,
+ /obj/item/storage/fancy/cigarettes/cigpack_syndicate,
+ /obj/item/stack/tape/guerrilla,
+ /obj/item/soap/syndie,
+ /obj/item/flashlight/lantern/syndicate,
+ /obj/item/storage/box/syndie_kit/bugs,
+ /obj/item/computer_hardware/hard_drive/portable/syndicate/ntnet_dos,
+ /obj/item/storage/box/syndie_kit/throwing_weapons,
+ /obj/item/multitool/ai_detect,
+ /obj/item/stamp/syndiround,
+ /obj/item/suppressor)
+ new item(C)
+
+/datum/supply_pack/security/armory/laser //dripstation mooving lethals to the armory
+ name = "Lasers Crate"
+ desc = "Contains three lethal, high-energy laser guns. Requires Armory access to open."
+ cost = 2000
+ contains = list(/obj/item/gun/energy/laser,
+ /obj/item/gun/energy/laser,
+ /obj/item/gun/energy/laser)
+ crate_name = "laser crate"
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/cargo/supplypod.dm b/modular_dripstation/code/modules/cargo/supplypod.dm
new file mode 100644
index 000000000000..f66e9d187bd2
--- /dev/null
+++ b/modular_dripstation/code/modules/cargo/supplypod.dm
@@ -0,0 +1,46 @@
+GLOBAL_LIST_INIT(podstyles, list(\
+ list(POD_SHAPE_OTHER, "pod", TRUE, FALSE, FALSE, RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\
+ list(POD_SHAPE_OTHER, "advpod", TRUE, FALSE, FALSE, RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\
+ list(POD_SHAPE_OTHER, "ntpod", TRUE, FALSE, FALSE, RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\
+ list(POD_SHAPE_OTHER, "syndipod", TRUE, FALSE, FALSE, RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\
+ list(POD_SHAPE_OTHER, "podmil", TRUE, FALSE, FALSE, RUBBLE_NORMAL, "military drop pod", "An unknown drop pod marked the markings of unknown elite strike team."),\
+ list(POD_SHAPE_OTHER, "cultpod", TRUE, FALSE, FALSE, RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\
+ list(POD_SHAPE_OTHER, "missile", FALSE, FALSE, FALSE, RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxiliary payload hatch on the side, though manually opening it is likely impossible."),\
+ list(POD_SHAPE_OTHER, "smissile", FALSE, FALSE, FALSE, RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxiliary payload hatch on the side, though manually opening it is likely impossible."),\
+ list(POD_SHAPE_OTHER, "box", TRUE, FALSE, FALSE, RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\
+ list(POD_SHAPE_OTHER, "clownpod",TRUE, FALSE, FALSE, RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\
+ list(POD_SHAPE_OTHER, "orange", TRUE, FALSE, FALSE, RUBBLE_NONE, "\improper Orange", "An angry orange."),\
+ list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\
+ list(POD_SHAPE_OTHER, "gondola", FALSE, FALSE, FALSE, RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\
+ list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, FALSE, FALSE, "rl_click", "give_po")\
+))
+
+/obj/structure/closet/supplypod
+ icon = 'modular_dripstation/icons/obj/supplypods.dmi'
+ icon_state = "pod"
+
+/datum/asset/spritesheet/supplypods/create_spritesheets()
+ for (var/style in 1 to length(GLOB.podstyles))
+ var/icon_file = 'modular_dripstation/icons/obj/supplypods.dmi'
+ if (style == STYLE_SEETHROUGH)
+ Insert("pod_asset[style]", icon(icon_file, "seethrough-icon"))
+ continue
+ var/base = GLOB.podstyles[style][POD_BASE]
+ if (!base)
+ Insert("pod_asset[style]", icon(icon_file, "invisible-icon"))
+ continue
+ var/icon/podIcon = icon(icon_file, base)
+ var/door = GLOB.podstyles[style][POD_DOOR]
+ if (door)
+ door = "[base]_door"
+ podIcon.Blend(icon(icon_file, door), ICON_OVERLAY)
+ var/shape = GLOB.podstyles[style][POD_SHAPE]
+ if (shape == POD_SHAPE_NORML)
+ var/decal = GLOB.podstyles[style][POD_DECAL]
+ if (decal)
+ podIcon.Blend(icon(icon_file, decal), ICON_OVERLAY)
+ var/glow = GLOB.podstyles[style][POD_GLOW]
+ if (glow)
+ glow = "pod_glow_[glow]"
+ podIcon.Blend(icon(icon_file, glow), ICON_OVERLAY)
+ Insert("pod_asset[style]", podIcon)
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/job/job_types/janitor.dm b/modular_dripstation/code/modules/job/job_types/janitor.dm
new file mode 100644
index 000000000000..2898e9b82a3d
--- /dev/null
+++ b/modular_dripstation/code/modules/job/job_types/janitor.dm
@@ -0,0 +1,2 @@
+/datum/job/janitor
+ base_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM, ACCESS_MAILSORTING, ACCESS_RESEARCH, ACCESS_MEDICAL, ACCESS_CONSTRUCTION)
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/job/job_types/quartermaster.dm b/modular_dripstation/code/modules/job/job_types/quartermaster.dm
new file mode 100644
index 000000000000..9b0a1dc7ebdb
--- /dev/null
+++ b/modular_dripstation/code/modules/job/job_types/quartermaster.dm
@@ -0,0 +1,11 @@
+/datum/job/qm
+ mail_goodies = list(
+ /obj/item/stack/sheet/mineral/gold = 10,
+ /obj/item/circuitboard/machine/emitter = 5,
+ /obj/item/survivalcapsule/luxuryelite = 3,
+ /obj/item/construction/rcd = 3,
+ /obj/item/circuitboard/machine/vending/donksofttoyvendor = 2
+ )
+
+/datum/outfit/job/quartermaster
+ backpack_contents = list(/obj/item/melee/classic_baton/telescopic = 1)
\ No newline at end of file
diff --git a/modular_dripstation/code/modules/mob/mob_helpers.dm b/modular_dripstation/code/modules/mob/mob_helpers.dm
new file mode 100644
index 000000000000..b10fd6801524
--- /dev/null
+++ b/modular_dripstation/code/modules/mob/mob_helpers.dm
@@ -0,0 +1,182 @@
+/proc/stars(n, pr)
+ n = html_encode(n)
+ if (pr == null)
+ pr = 25
+ if (pr <= 0)
+ return null
+ else
+ if (pr >= 100)
+ return n
+ var/te = n
+ var/t = ""
+ n = length_char(n)
+
+ for(var/p = 1 to min(n,MAX_BROADCAST_LEN))
+ if ((copytext_char(te, p, p + 1) == " " || prob(pr)))
+ t = text("[][]", t, copytext_char(te, p, p + 1))
+ else
+ t = text("[]*", t)
+ if(n > MAX_BROADCAST_LEN)
+ t += "..." //signals missing text
+ return sanitize(t)
+/**
+ * Makes you speak like you're drunk
+ */
+/proc/slur(phrase)
+ phrase = html_decode(phrase)
+ var/leng = length_char(phrase)
+ . = ""
+ var/newletter = ""
+ var/rawchar = ""
+ for(var/i = 1, i <= leng, i += length_char(rawchar))
+ rawchar = newletter = phrase[i]
+ if(rand(1, 3) == 3)
+ var/lowerletter = lowertext(newletter)
+ if(lowerletter == "o")
+ newletter = "u"
+ else if(lowerletter == "s")
+ newletter = "ch"
+ else if(lowerletter == "a")
+ newletter = "ah"
+ else if(lowerletter == "u")
+ newletter = "oo"
+ else if(lowerletter == "c")
+ newletter = "k"
+ else if(lowerletter == "о")
+ newletter ="у"
+ else if(lowerletter =="с")
+ newletter ="ч"
+ else if(lowerletter == "а")
+ newletter ="ах"
+ else if(lowerletter == "ц")
+ newletter ="к"
+ else if(lowerletter == "э")
+ newletter ="о"
+ else if(lowerletter == "г")
+ newletter ="х"
+ if(rand(1, 20) == 20)
+ if(newletter == " ")
+ newletter = "...эээааааээа..."
+ else if(newletter == ".")
+ newletter = " *РЫГ*."
+ switch(rand(1, 20))
+ if(1)
+ newletter += "'"
+ if(10)
+ newletter += "[newletter]"
+ if(20)
+ newletter += "[newletter][newletter]"
+ else
+ newletter += ""
+ . += "[newletter]"
+ return sanitize(.)
+
+/// Makes you talk like you got cult stunned, which is slurring but with some dark messages
+/proc/cultslur(phrase) // Inflicted on victims of a stun talisman
+ phrase = html_decode(phrase)
+ var/leng = length_char(phrase)
+ . = ""
+ var/newletter = ""
+ var/rawchar = ""
+ for(var/i = 1, i <= leng, i += length_char(rawchar))
+ rawchar = newletter = phrase[i]
+ if(rand(1, 2) == 2)
+ var/lowerletter = lowertext(newletter)
+ if(lowerletter == "о")
+ newletter = "у"
+ else if(lowerletter == "т")
+ newletter = "ч"
+ else if(lowerletter == "а")
+ newletter = "ах"
+ else if(lowerletter == "c")
+ newletter = " НАР "
+ else if(lowerletter == "с")
+ newletter = " СИ "
+ if(rand(1, 4) == 4)
+ if(newletter == " ")
+ newletter = " нет надежды... "
+ else if(newletter == "Х")
+ newletter = " ОНО ИДЁТ... "
+
+ switch(rand(1, 15))
+ if(1)
+ newletter = "'"
+ if(2)
+ newletter += "агн"
+ if(3)
+ newletter = "фз"
+ if(4)
+ newletter = "нглу"
+ if(5)
+ newletter = "глор"
+ else
+ newletter += ""
+ . += newletter
+ return sanitize(.)
+
+///Adds stuttering to the message passed in
+/proc/stutter(phrase)
+ phrase = html_decode(phrase)
+ var/leng = length(phrase)
+ . = ""
+ var/newletter = ""
+ var/rawchar
+ for(var/i = 1, i <= leng, i += length(rawchar))
+ rawchar = newletter = phrase[i]
+ if(prob(80) && !(lowertext(newletter) in list("а", "е", "и", "о", "у", " ")))
+ if(prob(10))
+ newletter = "[newletter]-[newletter]-[newletter]-[newletter]"
+ else if(prob(20))
+ newletter = "[newletter]-[newletter]-[newletter]"
+ else if (prob(5))
+ newletter = ""
+ else
+ newletter = "[newletter]-[newletter]"
+ . += newletter
+ return sanitize(.)
+
+///Convert a message to derpy speak
+/proc/derpspeech(message, stuttering)
+ message = replacetext(message, " я ", " ")
+ message = replacetext(message, " есть ", " ")
+ message = replacetext(message, " ты ", "ти")
+ message = replacetext(message, "помогите", "памагити")
+ message = replacetext(message, "grief", "grife")
+ message = replacetext(message, "космос", "spess")
+ message = replacetext(message, "карп", "крип")
+ message = replacetext(message, "причина", "почини")
+ if(prob(50))
+ message = uppertext(message)
+ message += "[stutter(pick("!", "!!", "!!!"))]"
+ if(!stuttering && prob(15))
+ message = stutter(message)
+ return message
+
+/proc/lizardspeech(message)
+ var/static/regex/lizard_hiss = new("с+", "г")
+ var/static/regex/lizard_hiSS = new("С+", "г")
+ if(message[1] != "*")
+ message = lizard_hiss.Replace(message, "ссс")
+ message = lizard_hiSS.Replace(message, "ССС")
+ return message
+
+/**
+ * Turn text into complete gibberish!
+ *
+ * text is the inputted message, and any value higher than 70 for chance will cause letters to be replaced instead of added
+ */
+/proc/Gibberish(text, replace_characters = FALSE, chance = 50)
+ text = html_decode(text)
+ . = ""
+ var/rawchar = ""
+ var/letter = ""
+ var/lentext = length_char(text)
+ for(var/i = 1, i <= lentext, i += length_char(rawchar))
+ rawchar = letter = text[i]
+ if(prob(chance))
+ if(replace_characters)
+ letter = ""
+ for(var/j in 1 to rand(0, 2))
+ letter += pick("#", "@", "*", "&", "%", "$", "/", "<", ">", ";", "*", "*", "*", "*", "*", "*", "*")
+ . += letter
+ return sanitize(.)
diff --git a/modular_dripstation/code/modules/research/techweb/all_nodes.dm b/modular_dripstation/code/modules/research/techweb/all_nodes.dm
new file mode 100644
index 000000000000..af3fe10c67c6
--- /dev/null
+++ b/modular_dripstation/code/modules/research/techweb/all_nodes.dm
@@ -0,0 +1,5 @@
+/datum/techweb_node/base //BASE NODES OVERRIDE!
+ design_ids = list("basic_matter_bin", "basic_cell", "basic_scanning", "basic_capacitor", "basic_micro_laser", "micro_mani", "desttagger", "handlabel", "packagewrap",
+ "destructive_analyzer", "circuit_imprinter", "rack_creator", "experimentor", "rdconsole", "design_disk", "tech_disk", "rdserver", "rdservercontrol", "mechfab", "podfab", "paystand", "ticket_machine", "ticket_remote", "light_tube", "light_bulb",
+ "space_heater", "beaker", "large_beaker", "vial", "bucket", "fork", "tray","plate", "bowl", "mixing_bowl", "drinking_glass", "shot_glass", "shaker", "xlarge_beaker", "sec_rshot", "sec_beanbag_slug", "sec_bshot", "sec_slug", "sec_Islug", "sec_Brslug", "sec_38", "sec_38_lethal", "apc_control", "power control", "airlock_board", "firelock_board", "airalarm_electronics", "firealarm_electronics", "blastdoorcontroller", "aac_electronics", "mousetrap",
+ "rglass","plasteel","plastitanium","plasmaglass","plasmareinforcedglass","titaniumglass","plastitaniumglass","wallframe/flasher", "rsf", "rls", "oven_tray", "bounced_radio", "signaler", "signalbutton", "inspector_booth", "intercom_frame", "infrared_emitter", "health_sensor", "timer", "voice_analyser", "camera_assembly", "newscaster_frame", "prox_sensor", "flashlight", "extinguisher", "pocketfireextinguisher")
diff --git a/modular_dripstation/code/modules/surgery/surgery_step.dm b/modular_dripstation/code/modules/surgery/surgery_step.dm
new file mode 100644
index 000000000000..78c82da5a2ab
--- /dev/null
+++ b/modular_dripstation/code/modules/surgery/surgery_step.dm
@@ -0,0 +1,3 @@
+/datum/surgery_step/cause_ouchie(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, success)
+ . = ..()
+ SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "screw_up", /datum/mood_event/surgery)
\ No newline at end of file
diff --git a/modular_dripstation/icons/effects/effects.dmi b/modular_dripstation/icons/effects/effects.dmi
new file mode 100644
index 000000000000..1b5f405b4bfc
Binary files /dev/null and b/modular_dripstation/icons/effects/effects.dmi differ
diff --git a/modular_dripstation/icons/mob/clothing/belt.dmi b/modular_dripstation/icons/mob/clothing/belt.dmi
new file mode 100644
index 000000000000..d2edeffb8753
Binary files /dev/null and b/modular_dripstation/icons/mob/clothing/belt.dmi differ
diff --git a/modular_dripstation/icons/mob/clothing/guns_on_back.dmi b/modular_dripstation/icons/mob/clothing/guns_on_back.dmi
new file mode 100644
index 000000000000..0e88c3e92c8b
Binary files /dev/null and b/modular_dripstation/icons/mob/clothing/guns_on_back.dmi differ
diff --git a/modular_dripstation/icons/mob/clothing/hands.dmi b/modular_dripstation/icons/mob/clothing/hands.dmi
new file mode 100644
index 000000000000..c2f296c60b28
Binary files /dev/null and b/modular_dripstation/icons/mob/clothing/hands.dmi differ
diff --git a/modular_dripstation/icons/mob/hud.dmi b/modular_dripstation/icons/mob/hud.dmi
new file mode 100644
index 000000000000..cea3996fa7a5
Binary files /dev/null and b/modular_dripstation/icons/mob/hud.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/equipment/boxcutter_lefthand.dmi b/modular_dripstation/icons/mob/inhands/equipment/boxcutter_lefthand.dmi
new file mode 100644
index 000000000000..73f563d66d61
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/equipment/boxcutter_lefthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/equipment/boxcutter_righthand.dmi b/modular_dripstation/icons/mob/inhands/equipment/boxcutter_righthand.dmi
new file mode 100644
index 000000000000..d83a9f2fe652
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/equipment/boxcutter_righthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_lefthand.dmi b/modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_lefthand.dmi
new file mode 100644
index 000000000000..abde6177b576
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_lefthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_righthand.dmi b/modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_righthand.dmi
new file mode 100644
index 000000000000..804322b94a34
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/equipment/cargo_teleporter_righthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/guns_lefthand.dmi b/modular_dripstation/icons/mob/inhands/guns_lefthand.dmi
new file mode 100644
index 000000000000..87d9332db003
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/guns_lefthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/guns_righthand.dmi b/modular_dripstation/icons/mob/inhands/guns_righthand.dmi
new file mode 100644
index 000000000000..be3e720e8a37
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/guns_righthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/security_lefthand.dmi b/modular_dripstation/icons/mob/inhands/security_lefthand.dmi
new file mode 100644
index 000000000000..b3f49af053ef
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/security_lefthand.dmi differ
diff --git a/modular_dripstation/icons/mob/inhands/security_righthand.dmi b/modular_dripstation/icons/mob/inhands/security_righthand.dmi
new file mode 100644
index 000000000000..65c48c22f7e6
Binary files /dev/null and b/modular_dripstation/icons/mob/inhands/security_righthand.dmi differ
diff --git a/modular_dripstation/icons/mob/mecha/cargo_hauler.dmi b/modular_dripstation/icons/mob/mecha/cargo_hauler.dmi
new file mode 100644
index 000000000000..4c549ac87bcc
Binary files /dev/null and b/modular_dripstation/icons/mob/mecha/cargo_hauler.dmi differ
diff --git a/modular_dripstation/icons/obj/assemblies/new_assemblies.dmi b/modular_dripstation/icons/obj/assemblies/new_assemblies.dmi
index cb982d6ee07e..2c27d59ddde1 100644
Binary files a/modular_dripstation/icons/obj/assemblies/new_assemblies.dmi and b/modular_dripstation/icons/obj/assemblies/new_assemblies.dmi differ
diff --git a/modular_dripstation/icons/obj/bepis/bepis.dmi b/modular_dripstation/icons/obj/bepis/bepis.dmi
new file mode 100644
index 000000000000..f348c2e1b055
Binary files /dev/null and b/modular_dripstation/icons/obj/bepis/bepis.dmi differ
diff --git a/modular_dripstation/icons/obj/blackmarket/blackmarket.dmi b/modular_dripstation/icons/obj/blackmarket/blackmarket.dmi
new file mode 100644
index 000000000000..99f4811ea6b9
Binary files /dev/null and b/modular_dripstation/icons/obj/blackmarket/blackmarket.dmi differ
diff --git a/modular_dripstation/icons/obj/blackmarket/module.dmi b/modular_dripstation/icons/obj/blackmarket/module.dmi
new file mode 100644
index 000000000000..ff9ce847d797
Binary files /dev/null and b/modular_dripstation/icons/obj/blackmarket/module.dmi differ
diff --git a/modular_dripstation/icons/obj/blackmarket/telecoms.dmi b/modular_dripstation/icons/obj/blackmarket/telecoms.dmi
new file mode 100644
index 000000000000..fb0da921d9d4
Binary files /dev/null and b/modular_dripstation/icons/obj/blackmarket/telecoms.dmi differ
diff --git a/modular_dripstation/icons/obj/bureaucracy.dmi b/modular_dripstation/icons/obj/bureaucracy.dmi
index d883cbecb413..bda80eb870de 100644
Binary files a/modular_dripstation/icons/obj/bureaucracy.dmi and b/modular_dripstation/icons/obj/bureaucracy.dmi differ
diff --git a/modular_dripstation/icons/obj/cargo/boxcutter.dmi b/modular_dripstation/icons/obj/cargo/boxcutter.dmi
new file mode 100644
index 000000000000..4f1c3ec30f55
Binary files /dev/null and b/modular_dripstation/icons/obj/cargo/boxcutter.dmi differ
diff --git a/modular_dripstation/icons/obj/cargo/cargo_inducer.dmi b/modular_dripstation/icons/obj/cargo/cargo_inducer.dmi
new file mode 100644
index 000000000000..3b027c6418fd
Binary files /dev/null and b/modular_dripstation/icons/obj/cargo/cargo_inducer.dmi differ
diff --git a/modular_dripstation/icons/obj/cargo/cargo_teleporter.dmi b/modular_dripstation/icons/obj/cargo/cargo_teleporter.dmi
new file mode 100644
index 000000000000..75bafcbbea02
Binary files /dev/null and b/modular_dripstation/icons/obj/cargo/cargo_teleporter.dmi differ
diff --git a/modular_dripstation/icons/obj/circuit_mess.dmi b/modular_dripstation/icons/obj/circuit_mess.dmi
new file mode 100644
index 000000000000..559e67806617
Binary files /dev/null and b/modular_dripstation/icons/obj/circuit_mess.dmi differ
diff --git a/modular_dripstation/icons/obj/clothing/gloves.dmi b/modular_dripstation/icons/obj/clothing/gloves.dmi
new file mode 100644
index 000000000000..8ed1f72553f7
Binary files /dev/null and b/modular_dripstation/icons/obj/clothing/gloves.dmi differ
diff --git a/modular_dripstation/icons/obj/clothing/shoes.dmi b/modular_dripstation/icons/obj/clothing/shoes.dmi
new file mode 100644
index 000000000000..e878a1d33ed4
Binary files /dev/null and b/modular_dripstation/icons/obj/clothing/shoes.dmi differ
diff --git a/modular_dripstation/icons/obj/device.dmi b/modular_dripstation/icons/obj/device.dmi
index c4093e118bf0..1555a11664de 100644
Binary files a/modular_dripstation/icons/obj/device.dmi and b/modular_dripstation/icons/obj/device.dmi differ
diff --git a/modular_dripstation/icons/obj/hypnochair.dmi b/modular_dripstation/icons/obj/hypnochair.dmi
new file mode 100644
index 000000000000..c25e429c649b
Binary files /dev/null and b/modular_dripstation/icons/obj/hypnochair.dmi differ
diff --git a/modular_dripstation/icons/obj/partypod.dmi b/modular_dripstation/icons/obj/partypod.dmi
new file mode 100644
index 000000000000..cb6f2a6dc1c3
Binary files /dev/null and b/modular_dripstation/icons/obj/partypod.dmi differ
diff --git a/modular_dripstation/icons/obj/supplypods.dmi b/modular_dripstation/icons/obj/supplypods.dmi
new file mode 100644
index 000000000000..053d77761f4a
Binary files /dev/null and b/modular_dripstation/icons/obj/supplypods.dmi differ
diff --git a/modular_dripstation/icons/obj/weapons/48x32.dmi b/modular_dripstation/icons/obj/weapons/48x32.dmi
new file mode 100644
index 000000000000..7b7ee4064aa2
Binary files /dev/null and b/modular_dripstation/icons/obj/weapons/48x32.dmi differ
diff --git a/modular_dripstation/icons/obj/weapons/security.dmi b/modular_dripstation/icons/obj/weapons/security.dmi
new file mode 100644
index 000000000000..d510634d6f16
Binary files /dev/null and b/modular_dripstation/icons/obj/weapons/security.dmi differ
diff --git a/modular_dripstation/includes.dm b/modular_dripstation/includes.dm
index 333da863fe10..e4964aa060fb 100644
--- a/modular_dripstation/includes.dm
+++ b/modular_dripstation/includes.dm
@@ -1,9 +1,73 @@
+#include "code\modules\antagonists\_common\antag_spawner.dm"
+#include "code\game\gamemodes\nuclear\nuclear.dm"
+#include "code\modules\antagonists\nukeop\nukeop.dm"
+#include "code\controllers\subsystem\blackmarket.dm"
+#include "code\datums\brain_damage\severe.dm"
+#include "code\datums\component\transforming.dm"
+#include "code\datums\reagent\baldium.dm"
+#include "code\datums\reagent\chemoverride.dm"
+#include "code\datums\component\mood.dm"
+#include "code\datums\reagent\leadacetate.dm"
+#include "code\datums\strong_pull.dm"
+#include "code\datums\keybinding\communication.dm"
+#include "code\datums\traits\negative.dm"
+#include "code\datums\traits\positive.dm"
+#include "code\game\effects\temporary_visuals\misc.dm"
+#include "code\game\effects\effects_foam.dm"
+#include "code\game\mecha\cargo_hauler.dm"
+#include "code\game\objects\items\bepis_items\boomerang.dm"
+#include "code\game\objects\items\bepis_items\eng_gloves.dm"
+#include "code\game\objects\items\bepis_items\explorerpin.dm"
+#include "code\game\objects\items\bepis_items\hypnochair.dm"
+#include "code\game\objects\items\bepis_items\lava_rods.dm"
+#include "code\game\objects\items\bepis_items\party_pod.dm"
+#include "code\game\objects\items\bepis_items\polycircuit.dm"
+#include "code\game\objects\items\bepis_items\rldmini.dm"
+#include "code\game\objects\items\bepis_items\sprayoncan.dm"
+#include "code\game\objects\items\bepis_items\survival_pen.dm"
+#include "code\game\objects\items\blackmarketstuff.dm"
+#include "code\game\objects\items\cargo_boxcutter.dm"
+#include "code\game\objects\items\cargo_inducer.dm"
+#include "code\game\objects\items\cargo_teleporter.dm"
+#include "code\game\objects\items\clothing\gloves.dm"
+#include "code\game\objects\items\devices\PDA\PDA_types.dm"
+#include "code\game\objects\items\projectiles\guns\ballistic\rifle.dm"
+#include "code\game\objects\items\tanks\watertank.dm"
+#include "code\game\turfs\simulated\walls.dm"
+#include "code\modules\antagonists\changeling\panacea.dm"
+#include "code\modules\antagonists\horror\horror_chemicals.dm"
+#include "code\modules\bepis\all_nodes.dm"
+#include "code\modules\bepis\bepis.dm"
+#include "code\modules\bepis\bepis_board.dm"
+#include "code\modules\bepis\bepis_designs.dm"
+#include "code\modules\bepis\bepis_layout.dm"
+#include "code\modules\bepis\bounty.dm"
+#include "code\modules\bepis\designs.dm"
+#include "code\modules\cargo\bounties\progression.dm"
+#include "code\modules\cargo\bounties\syndicate.dm"
+#include "code\modules\cargo\export_scaner.dm"
+#include "code\modules\cargo\markets\_market.dm"
+#include "code\modules\cargo\markets\market_item.dm"
+#include "code\modules\cargo\markets\market_items\clothing.dm"
+#include "code\modules\cargo\markets\market_items\consumables.dm"
+#include "code\modules\cargo\markets\market_items\misc.dm"
+#include "code\modules\cargo\markets\market_items\tools.dm"
+#include "code\modules\cargo\markets\market_items\weapons.dm"
+#include "code\modules\cargo\markets\market_telepad.dm"
+#include "code\modules\cargo\markets\market_uplink.dm"
+#include "code\modules\cargo\packs.dm"
+#include "code\modules\job\job_types\janitor.dm"
+#include "code\modules\job\job_types\quartermaster.dm"
#include "code\modules\antagonists\cult\cult_items.dm"
#include "code\modules\antagonists\cult\cult_structures.dm"
#include "code\modules\antagonists\wizard\equipment\wizard_spellbook.dm"
+#include "code\modules\cargo\supplypod.dm"
#include "code\modules\assembly\assembly.dm"
#include "code\modules\assembly\holder.dm"
#include "code\modules\assembly\signaler.dm"
+#include "code\datums\mood_events\generic_negative_events.dm"
+#include "code\datums\mood_events\generic_positive_events.dm"
+#include "code\modules\surgery\surgery_step.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\binary_devices.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\unary_devices.dm"
#include "code\modules\atmospherics\machinery\portable\scrubber.dm"
@@ -11,6 +75,7 @@
#include "code\modules\atmospherics\machinery\other\meter.dm"
#include "code\modules\awaymissions\mission_code\Academy.dm"
#include "code\modules\clothing\_neck.dm"
+#include "code\game\objects\structures\crates_lockers\closets.dm"
#include "code\modules\economy\pay_stand.dm"
#include "code\modules\events\wizard\greentext.dm"
#include "code\modules\research\stock_parts.dm"
@@ -21,6 +86,7 @@
#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm"
#include "code\modules\mob\living\simple_animal\hostile\mimic.dm"
#include "code\modules\mob\living\human\species_types\dripstation_blacklist.dm"
+#include "code\modules\mob\mob_helpers.dm"
#include "code\modules\uplink\uplink_devices.dm"
#include "code\modules\paperwork\folders.dm"
#include "code\modules\paperwork\photocopier.dm"
@@ -40,6 +106,8 @@
#include "code\game\turfs\simulated\floor.dm"
#include "code\game\turfs\open.dm"
#include "code\game\mecha\mech_bay.dm"
+#include "code\modules\research\techweb\all_nodes.dm"
+#include "code\game\machinery\pod_fabricator.dm"
#include "code\game\objects\effects\contraband.dm"
#include "code\game\objects\structures\artstuff.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
@@ -66,6 +134,8 @@
#include "code\game\objects\items\devices\radio\encryptionkey.dm"
#include "code\game\objects\items\devices\radio\radio.dm"
#include "code\game\objects\items\devices\powersink.dm"
+#include "code\game\objects\items\devices\laserpointer.dm"
+#include "code\game\objects\items\devices\advpinpointer.dm"
#include "code\game\objects\items\stacks\cash.dm"
#include "code\game\objects\items\implants\implant.dm"
#include "code\game\objects\items\implants\implanter.dm"
diff --git a/modular_dripstation/sound/item/boxcutter_activate.ogg b/modular_dripstation/sound/item/boxcutter_activate.ogg
new file mode 100644
index 000000000000..6700c6d03fde
Binary files /dev/null and b/modular_dripstation/sound/item/boxcutter_activate.ogg differ
diff --git a/modular_dripstation/sound/voice/sechailer_off1.ogg b/modular_dripstation/sound/voice/sechailer_off1.ogg
new file mode 100644
index 000000000000..9c509139f5d7
Binary files /dev/null and b/modular_dripstation/sound/voice/sechailer_off1.ogg differ
diff --git a/modular_dripstation/sound/voice/sechailer_off2.ogg b/modular_dripstation/sound/voice/sechailer_off2.ogg
new file mode 100644
index 000000000000..e84221070a50
Binary files /dev/null and b/modular_dripstation/sound/voice/sechailer_off2.ogg differ
diff --git a/modular_dripstation/sound/voice/sechailer_off3.ogg b/modular_dripstation/sound/voice/sechailer_off3.ogg
new file mode 100644
index 000000000000..ca1f9b377c33
Binary files /dev/null and b/modular_dripstation/sound/voice/sechailer_off3.ogg differ
diff --git a/modular_dripstation/sound/voice/sechailer_off4.ogg b/modular_dripstation/sound/voice/sechailer_off4.ogg
new file mode 100644
index 000000000000..6831bc1bcdef
Binary files /dev/null and b/modular_dripstation/sound/voice/sechailer_off4.ogg differ
diff --git a/modular_dripstation/sound/voice/sechailer_on1.ogg b/modular_dripstation/sound/voice/sechailer_on1.ogg
new file mode 100644
index 000000000000..5137fa195a76
Binary files /dev/null and b/modular_dripstation/sound/voice/sechailer_on1.ogg differ
diff --git a/modular_dripstation/sound/voice/sechailer_on2.ogg b/modular_dripstation/sound/voice/sechailer_on2.ogg
new file mode 100644
index 000000000000..d0178cae4325
Binary files /dev/null and b/modular_dripstation/sound/voice/sechailer_on2.ogg differ
diff --git a/sound/machines/mass_driver.ogg b/sound/machines/mass_driver.ogg
new file mode 100644
index 000000000000..b277b5a897bd
Binary files /dev/null and b/sound/machines/mass_driver.ogg differ
diff --git a/sound/mecha/hydraulic.ogg b/sound/mecha/hydraulic.ogg
new file mode 100644
index 000000000000..3281ed2dc0f0
Binary files /dev/null and b/sound/mecha/hydraulic.ogg differ
diff --git a/sound/mecha/powerloader_step.ogg b/sound/mecha/powerloader_step.ogg
new file mode 100644
index 000000000000..538ba1efed31
Binary files /dev/null and b/sound/mecha/powerloader_step.ogg differ
diff --git a/sound/mecha/powerloader_turn2.ogg b/sound/mecha/powerloader_turn2.ogg
new file mode 100644
index 000000000000..c3816df9d9e2
Binary files /dev/null and b/sound/mecha/powerloader_turn2.ogg differ
diff --git a/sound/weapons/sword1.ogg b/sound/weapons/sword1.ogg
new file mode 100644
index 000000000000..3797c105728b
Binary files /dev/null and b/sound/weapons/sword1.ogg differ
diff --git a/sound/weapons/sword2.ogg b/sound/weapons/sword2.ogg
new file mode 100644
index 000000000000..5c8413aacfa4
Binary files /dev/null and b/sound/weapons/sword2.ogg differ
diff --git a/sound/weapons/sword3.ogg b/sound/weapons/sword3.ogg
new file mode 100644
index 000000000000..874c605c009c
Binary files /dev/null and b/sound/weapons/sword3.ogg differ
diff --git a/sound/weapons/sword4.ogg b/sound/weapons/sword4.ogg
new file mode 100644
index 000000000000..a944f27026d5
Binary files /dev/null and b/sound/weapons/sword4.ogg differ
diff --git a/sound/weapons/sword5.ogg b/sound/weapons/sword5.ogg
new file mode 100644
index 000000000000..4515dc4a66b0
Binary files /dev/null and b/sound/weapons/sword5.ogg differ
diff --git a/strings/accents/accent_nordic.json b/strings/accents/accent_nordic.json
index bed61ff8f095..91253e53f051 100644
--- a/strings/accents/accent_nordic.json
+++ b/strings/accents/accent_nordic.json
@@ -857,7 +857,7 @@
"\bclockwork\b": "clockvurk",
"\bclockworks\b": "clockvurks",
"\bjustice\b": "yustice",
- "\bjusticar\b": "yusticar",
+ "\bjusticiar\b": "yusticiar",
"\bgeometer\b": "yeometer",
"\bvampire\b": "vampyr",
"\bvampires\b": "vampyrs",
diff --git a/strings/names/wizardsecond.txt b/strings/names/wizardsecond.txt
index 6daff0f3e7f5..0d13cec3b273 100644
--- a/strings/names/wizardsecond.txt
+++ b/strings/names/wizardsecond.txt
@@ -37,4 +37,4 @@ the Wise
the Wizzard
Unseen
Weatherwax
-Yagg
\ No newline at end of file
+Yagg
diff --git a/strings/slurring_cult_text.json b/strings/slurring_cult_text.json
index 192893bdc858..e3f0c6278be1 100644
--- a/strings/slurring_cult_text.json
+++ b/strings/slurring_cult_text.json
@@ -2,26 +2,25 @@
"replacements": {
"characters": {
"common": {
- "o": "u",
- "t": "ch",
- "a": "ah",
- "u": "oo",
- "c": " NAR ",
- "s": " SIE "
+ "о": "у",
+ "а": "аэ",
+ "к": "х",
+ "ц": " НАР ",
+ "с": " СИ "
},
"uncommon": {
- " ": " no hope... ",
- "H": " IT COMES... "
+ " ": " надежды нет... ",
+ "H": " ОНО ИДЁТ... "
}
},
"string_replacements": [
"'",
- "fth",
- "nglu",
- "glor"
+ "фх",
+ "нглу",
+ "глор"
],
"string_additions": [
- "agn"
+ "агн"
]
}
}
diff --git a/strings/slurring_drunk_text.json b/strings/slurring_drunk_text.json
index a68a00b08588..ab560ec21141 100644
--- a/strings/slurring_drunk_text.json
+++ b/strings/slurring_drunk_text.json
@@ -2,15 +2,15 @@
"replacements": {
"characters": {
"common": {
- "o": "u",
- "s": "ch",
- "a": "ah",
- "u": "oo",
- "c": "k"
+ "о": "у",
+ "с": "щ",
+ "а": "аэ",
+ "г": "х",
+ "к": "х"
},
"uncommon": {
- " ": "...huuuhhh...",
- ".": " *BURP*."
+ " ": "...аээээаэ...",
+ ".": " *РЫГ*."
}
},
"string_additions": [
diff --git a/strings/slurring_heretic_text.json b/strings/slurring_heretic_text.json
index d5b87ec911cf..f55d52c6d90b 100644
--- a/strings/slurring_heretic_text.json
+++ b/strings/slurring_heretic_text.json
@@ -2,31 +2,31 @@
"replacements": {
"characters": {
"common": {
- "o": "u",
+ "о": "у",
"t": "ch",
- "a": "ah",
- "c": "th",
- "i": "ks",
- "m": "nth"
+ "а": "аэ",
+ "с": "з",
+ "и": "кс",
+ "м": "ннмм"
},
"uncommon": {
- " ": " endless... ",
- "H": " THE HANDS... ",
- "h": " BRIGHT ",
- "s": " LEAK ",
- "r": " CRACK "
+ " ": " бесконечность... ",
+ "H": " РУКИ... ",
+ "h": " ЯРКИЙ ",
+ "s": " ТЕЧЬ",
+ "r": " РАЗЛОМ "
}
},
"string_replacements": [
"'",
- "br",
- "th",
- "see",
- "etch"
+ "бр",
+ "з",
+ "сии",
+ "ич"
],
"string_additions": [
- "ah",
- "wth"
+ "ах",
+ "втх"
]
}
}
diff --git a/strings/yogs_ion_laws.json b/strings/yogs_ion_laws.json
index 03c37252fc80..665372bad072 100644
--- a/strings/yogs_ion_laws.json
+++ b/strings/yogs_ion_laws.json
@@ -40,14 +40,12 @@
"LIE",
"GOSSIP",
"RHYME",
- "FLIRT",
"HELP",
"HARM",
"INVEIGLE",
"DEFENSTRATE",
"NARRATE",
"CONSPIRE",
- "SEDUCE",
"LIVE",
"DIE",
"EAT",
diff --git a/tgui/packages/common/string.js b/tgui/packages/common/string.js
index 16a0921a2559..5b4e5d6d7f2b 100644
--- a/tgui/packages/common/string.js
+++ b/tgui/packages/common/string.js
@@ -87,6 +87,19 @@ export const capitalize = str => {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
};
+/**
+ * Similar to capitalize, this takes a string and replaces all first letters
+ * of any words.
+ *
+ * @param {string} str
+ * @return {string} The string with the first letters capitalized.
+ *
+ * @example capitalizeAll('heLLo woRLd') === 'HeLLo WoRLd'
+ */
+export const capitalizeAll = (str) => {
+ return str.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase());
+};
+
export const toTitleCase = str => {
// Handle array
if (Array.isArray(str)) {
diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js
index 6cfc72bd2994..ed2602b9c5d1 100644
--- a/tgui/packages/tgui-panel/chat/middleware.js
+++ b/tgui/packages/tgui-panel/chat/middleware.js
@@ -67,6 +67,8 @@ const loadChatFromStorage = async store => {
export const chatMiddleware = store => {
let initialized = false;
let loaded = false;
+ const sequences = [];
+ const sequences_requested = [];
chatRenderer.events.on('batchProcessed', countByType => {
// Use this flag to workaround unread messages caused by
// loading them from storage. Side effect of that, is that
@@ -86,10 +88,37 @@ export const chatMiddleware = store => {
loadChatFromStorage(store);
}
if (type === 'chat/message') {
- // Normalize the payload
- const batch = Array.isArray(payload) ? payload : [payload];
- chatRenderer.processBatch(batch);
- return;
+ let payload_obj;
+ try {
+ payload_obj = JSON.parse(payload);
+ } catch (err) {
+ return;
+ }
+
+ const sequence = payload_obj.sequence;
+ if (sequences.includes(sequence)) {
+ return;
+ }
+
+ const sequence_count = sequences.length;
+ seq_check: if (sequence_count > 0) {
+ if (sequences_requested.includes(sequence)) {
+ sequences_requested.splice(sequences_requested.indexOf(sequence), 1);
+ // if we are receiving a message we requested, we can stop reliability checks
+ break seq_check;
+ }
+
+ // cannot do reliability if we don't have any messages
+ const expected_sequence = sequences[sequence_count - 1] + 1;
+ if (sequence !== expected_sequence) {
+ for (let requesting = expected_sequence; requesting < sequence; requesting++) {
+ requested_sequences.push(requesting);
+ Byond.sendMessage('chat/resend', requesting);
+ }
+ }
+ }
+
+ chatRenderer.processBatch([payload_obj.content]);
}
if (type === loadChat.type) {
next(action);
diff --git a/tgui/packages/tgui/components/Dropdown.js b/tgui/packages/tgui/components/Dropdown.js
deleted file mode 100644
index e6fe8a840a5f..000000000000
--- a/tgui/packages/tgui/components/Dropdown.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import { classes } from 'common/react';
-import { Component } from 'inferno';
-import { Box } from './Box';
-import { Icon } from './Icon';
-
-export class Dropdown extends Component {
- constructor(props) {
- super(props);
- this.state = {
- selected: props.selected,
- open: false,
- };
- this.handleClick = () => {
- if (this.state.open) {
- this.setOpen(false);
- }
- };
- }
-
- componentWillUnmount() {
- window.removeEventListener('click', this.handleClick);
- }
-
- setOpen(open) {
- this.setState({ open: open });
- if (open) {
- setTimeout(() => {
- window.addEventListener('click', this.handleClick);
- });
- this.menuRef.focus();
- } else {
- window.removeEventListener('click', this.handleClick);
- }
- }
-
- setSelected(selected) {
- this.setState({
- selected: selected,
- });
- this.setOpen(false);
- this.props.onSelected(selected);
- }
-
- buildMenu() {
- const { options = [] } = this.props;
- const ops = options.map((option) => {
- let displayText, value;
-
- if (typeof option === 'string') {
- displayText = option;
- value = option;
- } else {
- displayText = option.displayText;
- value = option.value;
- }
-
- return (
- {
- this.setSelected(value);
- }}>
- {displayText}
-
- );
- });
- return ops.length ? ops : 'No Options Found';
- }
-
- render() {
- const { props } = this;
- const {
- icon,
- iconRotation,
- iconSpin,
- clipSelectedText = true,
- color = 'default',
- dropdownStyle,
- over,
- noscroll,
- nochevron,
- width,
- openWidth = width,
- onClick,
- onOpen,
- selected,
- disabled,
- displayText,
- ...boxProps
- } = props;
- const { className, ...rest } = boxProps;
-
- const adjustedOpen = over ? !this.state.open : this.state.open;
-
- const menu = this.state.open ? (
-
+ This UI exists to help visualize plane masters, the backbone of our
+ rendering system.
+ It also provices some tools for editing and messing with them.
+
+
How to use this UI
+ This UI exists primarially as a visualizer, mostly because this info is
+ quite obscure, and I want it to be easier to understand.
+
+
+ That said, it also supports editing plane masters, adding and removing
+ relays, and provides easy access to color matrix/filter/alpha/vv
+ editing.
+
+ To start off with, each little circle represents a{' '}
+ render_target based connection.
+
+ Blue nodes are relays, so drawing one plane onto another. Purple ones
+ are filter based connections.
+ You can tell where a node starts and ends based on the side of the plane
+ it's on.
+
+ Adding a new relay is simple, you just need to hit the + button, and
+ select a plane by name to relay onto.
+
+ Each plane can be viewed more closely by clicking the little button in
+ it's top right corner. This opens a sidebar, and displays a lot of
+ more general info about the plane and its purpose, alongside exposing
+ some useful buttons and interesting values.
+
+ Planes are aligned based off their initial setup. If you end up breaking
+ things byond repair, or just want to reset things, you can hit the
+ recycle button in the top left to totally refresh your plane masters.{' '}
+
+
+
What is a plane master?
+ You can think of a plane master as a way to group a set of objects onto
+ one rendering slate.
+ It is per client too, which makes it quite powerful. This is done using
+ the plane variable of /atom.
+
+ We first create an atom with an appearance flag that contains{' '}
+ PLANE_MASTER and give it a plane value.
+ Then we mirror the same plane value on all the atoms we
+ want to render in this group.
+
+
+ Finally, we place the PLANE_MASTER'd atom in the
+ relevent client's screen contents.
+ That sets up the bare minimum.
+
+
+ It is worth noting that the plane var does not only effect
+ this rendering grouping behavior.
+ It also effects the layering of objects on the map.
+
+ For this reason, there are some effects that are pretty much impossible
+ with planes.
+ Masking one thing while also drawing that thing in the correct order
+ with other objects on the map is a good example of this.
+
+ It is possible to do, but it's quite disruptive.
+
+
+ Normally, planes will just group, apply an effect, and then draw
+ directly to the game.
+
+ What if we wanted to draw planes onto other planes then?
+
+
Render Targets and Relays
+
+ Rendering one thing onto another is actually not that complex.
+ We can set the render_target variable of an atom to relay
+ it to some render_source.
+
+ If that render_target is preceeded by *, it will
+ not be drawn to the actual client view, and instead just relayed.{' '}
+
+
+ Ok so we can relay a plane master onto some other atom, but how do we
+ get it on another plane master? We can't just draw it with{' '}
+ render_source, since we might want to relay more then one
+ plane master.
+
+
+ Why not relay it to another atom then? and then well, set that
+ atom's plane var to the plane master we want?
+
+ That ends up being about what we do.
+ It's worth noting that render sources are often used by filters,
+ normally to apply some displacement or mask.
+
+
+
Applying effects
+ Ok so we can group and relay planes, but what can we actually do with
+ that?
+
+ Lots of stuff it turns out. Filters are quite powerful, and we use them
+ quite a bit.
+ You can use filters to mask one plane with another, or use one plane as
+ a distortion source for another.
+
+ Can do more basic stuff too, setting a plane's color matrix can be
+ quite powerful.
+ Even just setting alpha to show and hide things can be quite useful.{' '}
+
+
+ I won't get into every effect we do here, you can learn more about
+ each plane by clicking on the little button in their top right.
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/servantofratvar.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/servantofratvar.ts
index 49b064711357..96f667bc777c 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/servantofratvar.ts
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/antagonists/antagonists/servantofratvar.ts
@@ -13,7 +13,7 @@ const ServantOfRatvar: Antagonist = {
description: [
multiline`
A flash of yellow light! The sound of whooshing steam and clanking cogs surrounds you, and you understand your mission.
- Ratvar, the Clockwork Justicar, has trusted you to secure the gateway in his Ark!
+ Ratvar, the Clockwork Justiciar, has trusted you to secure the gateway in his Ark!
`,
RATVAR_MECHANICAL_DESCRIPTION,
],
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/multiz_parallax.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/multiz_parallax.tsx
new file mode 100644
index 000000000000..8b499b2aef1e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/multiz_parallax.tsx
@@ -0,0 +1,8 @@
+import { CheckboxInput, FeatureToggle } from '../base';
+
+export const multiz_parallax: FeatureToggle = {
+ name: "Enable multi-z parallax",
+ category: "GAMEPLAY",
+ description: "Enable multi-z parallax, for a 3D effect.",
+ component: CheckboxInput,
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/multiz_performance.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/multiz_performance.tsx
new file mode 100644
index 000000000000..f844b9f41f2a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/multiz_performance.tsx
@@ -0,0 +1,13 @@
+import { createDropdownInput, Feature } from '../base';
+
+export const multiz_performance: Feature = {
+ name: 'Multi-Z Detail',
+ category: 'GAMEPLAY',
+ description: 'How detailed multi-z is. Lower this to improve performance',
+ component: createDropdownInput({
+ [-1]: 'Standard',
+ 2: 'High',
+ 1: 'Medium',
+ 0: 'Low',
+ }),
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx
index 1a0ae0c40fac..51171da67598 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/species_features.tsx
@@ -119,3 +119,18 @@ export const feature_ethereal_mark: FeatureChoiced = {
name: "Mark",
component: FeatureDropdownInput,
};
+
+export const feature_preternis_weathering: FeatureChoiced = {
+ name: "Weathering",
+ component: FeatureDropdownInput,
+};
+
+export const feature_preternis_antenna: FeatureChoiced = {
+ name: "Antenna",
+ component: FeatureDropdownInput,
+};
+
+export const feature_preternis_eye: FeatureChoiced = {
+ name: "Eye",
+ component: FeatureDropdownInput,
+};
diff --git a/tgui/packages/tgui/interfaces/RapidConstructionDevice.tsx b/tgui/packages/tgui/interfaces/RapidConstructionDevice.tsx
new file mode 100644
index 000000000000..937c4b256029
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RapidConstructionDevice.tsx
@@ -0,0 +1,181 @@
+import { Window } from '../layouts';
+import { BooleanLike, classes } from 'common/react';
+import { capitalizeAll } from 'common/string';
+import { useBackend, useLocalState } from '../backend';
+import { LabeledList, Section, Button, Tabs, Stack, Box } from '../components';
+import { AirLockMainSection } from './AirlockElectronics';
+
+type Data = {
+ matterLeft: number;
+ silo_upgraded: BooleanLike;
+ silo_enabled: BooleanLike;
+ root_categories: string[];
+ selected_root: string;
+ categories: Category[];
+ selected_category: string;
+ selected_design: string;
+ display_tabs: BooleanLike;
+};
+
+type Category = {
+ cat_name: string;
+ designs: Design[];
+};
+
+type Design = {
+ title: string;
+ design_id: Number;
+ icon: string;
+};
+
+export const MatterItem = (props, context) => {
+ const { data } = useBackend(context);
+ const { matterLeft } = data;
+ return (
+
+ {matterLeft} Units
+
+ );
+};
+
+export const SiloItem = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { silo_enabled } = data;
+ return (
+
+ act('toggle_silo')}
+ />
+
+ );
+};
+
+const CategoryItem = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { root_categories = [], selected_root } = data;
+ return (
+
+ {root_categories.map((root) => (
+ act('root_category', { root_category: root })}
+ />
+ ))}
+
+ );
+};
+
+const InfoSection = (props, context) => {
+ const { data } = useBackend(context);
+ const { silo_upgraded } = data;
+
+ return (
+
+
+
+ {silo_upgraded ? : ''}
+
+
+
+ );
+};
+
+const DesignSection = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { categories = [], selected_category, selected_design } = data;
+ const [categoryName, setCategoryName] = useLocalState(
+ context,
+ 'categoryName',
+ selected_category
+ );
+ const shownCategory =
+ categories.find((category) => category.cat_name === categoryName) ||
+ categories[0];
+ return (
+
+
+ {categories.map((category) => (
+ setCategoryName(category.cat_name)}>
+ {category.cat_name}
+
+ ))}
+
+ {shownCategory?.designs.map((design) => (
+
+ act('design', {
+ category: shownCategory.cat_name,
+ index: design.design_id,
+ })
+ }>
+
+ {capitalizeAll(design.title)}
+
+ ))}
+
+ );
+};
+
+const ConfigureSection = (props, context) => {
+ const { data } = useBackend(context);
+ const { selected_root } = data;
+
+ return (
+
+ {selected_root === 'Airlock Access' ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export const RapidConstructionDevice = (props, context) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/common/AccessConfig.js b/tgui/packages/tgui/interfaces/common/AccessConfig.js
new file mode 100644
index 000000000000..32828faca24c
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/AccessConfig.js
@@ -0,0 +1,135 @@
+import { sortBy } from 'common/collections';
+import { Section, Button, Flex, Tabs, Grid } from '../../components';
+import { useLocalState } from '../../backend';
+
+export const AccessConfig = (props, context) => {
+ const {
+ accesses = [],
+ selectedList = [],
+ accessMod,
+ grantAll,
+ denyAll,
+ grantDep,
+ denyDep,
+ } = props;
+ const [selectedAccessName, setSelectedAccessName] = useLocalState(
+ context,
+ 'accessName',
+ accesses[0]?.name
+ );
+ const selectedAccess = accesses.find(
+ (access) => access.name === selectedAccessName
+ );
+ const selectedAccessEntries = sortBy((entry) => entry.desc)(
+ selectedAccess?.accesses || []
+ );
+
+ const checkAccessIcon = (accesses) => {
+ let oneAccess = false;
+ let oneInaccess = false;
+ for (let element of accesses) {
+ if (selectedList.includes(element.ref)) {
+ oneAccess = true;
+ } else {
+ oneInaccess = true;
+ }
+ }
+ if (!oneAccess && oneInaccess) {
+ return 0;
+ } else if (oneAccess && oneInaccess) {
+ return 1;
+ } else {
+ return 2;
+ }
+ };
+
+ return (
+
+ grantAll()}
+ />
+ denyAll()}
+ />
+ >
+ }>
+
+
+
+ {accesses.map((access) => {
+ const entries = access.accesses || [];
+ const icon = diffMap[checkAccessIcon(entries)].icon;
+ const color = diffMap[checkAccessIcon(entries)].color;
+ return (
+ setSelectedAccessName(access.name)}>
+ {access.name}
+
+ );
+ })}
+
+
+
+
+
+ grantDep(selectedAccess.regid)}
+ />
+
+
+ denyDep(selectedAccess.regid)}
+ />
+
+
+ {selectedAccessEntries.map((entry) => (
+ accessMod(entry.ref)}
+ />
+ ))}
+
+
+
+ );
+};
+
+const diffMap = {
+ 0: {
+ icon: 'times-circle',
+ color: 'bad',
+ },
+ 1: {
+ icon: 'stop-circle',
+ color: null,
+ },
+ 2: {
+ icon: 'check-circle',
+ color: 'good',
+ },
+};
diff --git a/tgui/packages/tgui/interfaces/common/Connections.tsx b/tgui/packages/tgui/interfaces/common/Connections.tsx
new file mode 100644
index 000000000000..e27b48d67358
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/common/Connections.tsx
@@ -0,0 +1,95 @@
+import { classes } from '../../../common/react';
+import { CSS_COLORS } from '../../constants';
+
+const SVG_CURVE_INTENSITY = 64;
+
+enum ConnectionStyle {
+ CURVE = 'curve',
+ SUBWAY = 'subway',
+}
+
+export type Position = {
+ x: number;
+ y: number;
+};
+
+export type Connection = {
+ // X, Y starting point
+ from: Position;
+ // X, Y ending point
+ to: Position;
+ // Color of the line, defaults to blue
+ color?: string;
+ // Type of line - Curvy or Straight / angled, defaults to curvy
+ style?: ConnectionStyle;
+ // Optional: the ref of what element this connection is sourced
+ ref?: string;
+};
+
+export const Connections = (props: {
+ connections: Connection[];
+ zLayer?: number;
+ lineWidth?: number;
+}) => {
+ const { connections, zLayer = -1, lineWidth = '2px' } = props;
+
+ const isColorClass = (str) => {
+ if (typeof str === 'string') {
+ return CSS_COLORS.includes(str);
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/components/Dropdown.scss b/tgui/packages/tgui/styles/components/Dropdown.scss
index 6909f8d93123..91517e5a40db 100644
--- a/tgui/packages/tgui/styles/components/Dropdown.scss
+++ b/tgui/packages/tgui/styles/components/Dropdown.scss
@@ -6,17 +6,18 @@
@use '../base.scss';
.Dropdown {
- position: relative;
+ display: flex;
+ align-items: flex-start;
}
.Dropdown__control {
- position: relative;
- display: inline-block;
+ flex: 1;
font-family: Verdana, sans-serif;
font-size: base.em(12px);
+ overflow: hidden;
+ user-select: none;
width: base.em(100px);
line-height: base.em(17px);
- user-select: none;
}
.Dropdown__arrow-button {
diff --git a/tgui/packages/tgui/styles/interfaces/CameraConsole.scss b/tgui/packages/tgui/styles/interfaces/CameraConsole.scss
deleted file mode 100644
index 7e8f99fdcceb..000000000000
--- a/tgui/packages/tgui/styles/interfaces/CameraConsole.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-@use '../base.scss';
-
-$background-color: rgba(0, 0, 0, 0.33) !default;
-
-.CameraConsole__left {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- width: base.em(220px);
-}
-
-.CameraConsole__right {
- position: absolute;
- top: 0;
- bottom: 0;
- left: base.em(220px);
- right: 0;
- background-color: $background-color;
-}
-
-.CameraConsole__toolbar {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 2em;
- line-height: 2em;
- margin: 0.25em 1em 0;
-}
-
-.CameraConsole__toolbarRight {
- position: absolute;
- top: 0;
- right: 0;
- height: 2em;
- line-height: 2em;
- margin: 0.33em 0.5em 0;
-}
-
-.CameraConsole__map {
- position: absolute;
- top: base.em(26px);
- bottom: 0;
- left: 0;
- right: 0;
- margin: 0.5em;
- text-align: center;
-
- .NoticeBox {
- margin-top: calc(50% - 2em);
- }
-}
diff --git a/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss b/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss
new file mode 100644
index 000000000000..7cfecb250acc
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/IntegratedCircuit.scss
@@ -0,0 +1,72 @@
+@use '../colors.scss';
+@use '../base.scss';
+
+$fg-map: colors.$fg-map !default;
+
+.CircuitInfo__Examined {
+ background-color: rgba(0, 0, 0, 1);
+ padding: 8px;
+ border-radius: 5px;
+
+ user-select: none;
+ pointer-events: none;
+}
+
+.ObjectComponent__Titlebar {
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
+
+ white-space: nowrap;
+
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.ObjectComponent__Content {
+ white-space: nowrap;
+ background-color: rgba(0, 0, 0, 0.5);
+
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.ObjectComponent__Greyed_Content {
+ white-space: nowrap;
+ background-color: rgba(19, 19, 19, 0.5);
+
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.ObjectComponent__Port {
+ position: relative;
+ width: 13px;
+ height: 13px;
+}
+
+.ObjectComponent__PortPos {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+@each $color-name, $color-value in $fg-map {
+ .color-stroke-#{$color-name} {
+ stroke: $color-value !important;
+ }
+
+ .color-fill-#{$color-name} {
+ fill: $color-value !important;
+ }
+}
+
+$border-color: #88bfff !default;
+$border-radius: base.$border-radius !default;
+
+.IntegratedCircuit__BlueBorder {
+ border: base.em(1px) solid $border-color;
+ border: base.em(1px) solid rgba($border-color, 0.75);
+ border-radius: $border-radius;
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index d60242ae8627..a94293cb8f54 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -48,11 +48,11 @@
// Interfaces
@include meta.load-css('./interfaces/AlertModal.scss');
-@include meta.load-css('./interfaces/CameraConsole.scss');
@include meta.load-css('./interfaces/InspectorBooth.scss');
@include meta.load-css('./interfaces/ListInput.scss');
@include meta.load-css('./interfaces/HellishRunes.scss');
@include meta.load-css('./interfaces/Hypertorus.scss');
+@include meta.load-css('./interfaces/IntegratedCircuit.scss');
@include meta.load-css('./interfaces/NuclearBomb.scss');
@include meta.load-css('./interfaces/PreferencesMenu.scss');
@include meta.load-css('./interfaces/Roulette.scss');
diff --git a/tools/dmi/Resolve Icon Conflicts.bat b/tools/dmi/Resolve Icon Conflicts.bat
new file mode 100644
index 000000000000..8def88442c63
--- /dev/null
+++ b/tools/dmi/Resolve Icon Conflicts.bat
@@ -0,0 +1,2 @@
+@call "%~dp0\..\bootstrap\python.bat" -m dmi.merge_driver --posthoc %*
+@pause
diff --git a/tools/dmi/__init__.py b/tools/dmi/__init__.py
new file mode 100644
index 000000000000..4c3ef221370a
--- /dev/null
+++ b/tools/dmi/__init__.py
@@ -0,0 +1,247 @@
+# Tools for working with modern DreamMaker icon files (PNGs + metadata)
+
+import math
+from PIL import Image
+from PIL.PngImagePlugin import PngInfo
+
+DEFAULT_SIZE = 32, 32
+LOOP_UNLIMITED = 0
+LOOP_ONCE = 1
+
+NORTH = 1
+SOUTH = 2
+EAST = 4
+WEST = 8
+SOUTHEAST = SOUTH | EAST
+SOUTHWEST = SOUTH | WEST
+NORTHEAST = NORTH | EAST
+NORTHWEST = NORTH | WEST
+
+CARDINALS = [NORTH, SOUTH, EAST, WEST]
+DIR_ORDER = [SOUTH, NORTH, EAST, WEST, SOUTHEAST, SOUTHWEST, NORTHEAST, NORTHWEST]
+DIR_NAMES = {
+ 'SOUTH': SOUTH,
+ 'NORTH': NORTH,
+ 'EAST': EAST,
+ 'WEST': WEST,
+ 'SOUTHEAST': SOUTHEAST,
+ 'SOUTHWEST': SOUTHWEST,
+ 'NORTHEAST': NORTHEAST,
+ 'NORTHWEST': NORTHWEST,
+ **{str(x): x for x in DIR_ORDER},
+ **{x: x for x in DIR_ORDER},
+ '0': SOUTH,
+ None: SOUTH,
+}
+
+
+class Dmi:
+ version = "4.0"
+
+ def __init__(self, width, height):
+ self.width = width
+ self.height = height
+ self.states = []
+
+ @classmethod
+ def from_file(cls, fname):
+ image = Image.open(fname)
+ if image.mode != 'RGBA':
+ image = image.convert('RGBA')
+
+ # no metadata = regular image file
+ if 'Description' not in image.info:
+ dmi = Dmi(*image.size)
+ state = dmi.state("")
+ state.frame(image)
+ return dmi
+
+ # read metadata
+ metadata = image.info['Description']
+ line_iter = iter(metadata.splitlines())
+ assert next(line_iter) == "# BEGIN DMI"
+ assert next(line_iter) == f"version = {cls.version}"
+
+ dmi = Dmi(*DEFAULT_SIZE)
+ state = None
+
+ for line in line_iter:
+ if line == "# END DMI":
+ break
+ key, value = line.lstrip().split(" = ")
+ if key == 'width':
+ dmi.width = int(value)
+ elif key == 'height':
+ dmi.height = int(value)
+ elif key == 'state':
+ state = dmi.state(unescape(value))
+ elif key == 'dirs':
+ state.dirs = int(value)
+ elif key == 'frames':
+ state._nframes = int(value)
+ elif key == 'delay':
+ state.delays = [parse_num(x) for x in value.split(',')]
+ elif key == 'loop':
+ state.loop = int(value)
+ elif key == 'rewind':
+ state.rewind = parse_bool(value)
+ elif key == 'hotspot':
+ x, y, frm = [int(x) for x in value.split(',')]
+ state.hotspot(frm - 1, x, y)
+ elif key == 'movement':
+ state.movement = parse_bool(value)
+ else:
+ raise NotImplementedError(key)
+
+ # cut image into frames
+ width, height = image.size
+ gridwidth = width // dmi.width
+ i = 0
+ for state in dmi.states:
+ for frame in range(state._nframes):
+ for dir in range(state.dirs):
+ px = dmi.width * (i % gridwidth)
+ py = dmi.height * (i // gridwidth)
+ im = image.crop((px, py, px + dmi.width, py + dmi.height))
+ assert im.size == (dmi.width, dmi.height)
+ state.frames.append(im)
+ i += 1
+ state._nframes = None
+
+ return dmi
+
+ def state(self, *args, **kwargs):
+ s = State(self, *args, **kwargs)
+ self.states.append(s)
+ return s
+
+ @property
+ def default_state(self):
+ return self.states[0]
+
+ def get_state(self, name):
+ for state in self.states:
+ if state.name == name:
+ return state
+ raise KeyError(name)
+
+ def _assemble_comment(self):
+ comment = "# BEGIN DMI\n"
+ comment += f"version = {self.version}\n"
+ comment += f"\twidth = {self.width}\n"
+ comment += f"\theight = {self.height}\n"
+ for state in self.states:
+ comment += f"state = {escape(state.name)}\n"
+ comment += f"\tdirs = {state.dirs}\n"
+ comment += f"\tframes = {state.framecount}\n"
+ if state.framecount > 1 and len(state.delays): # any(x != 1 for x in state.delays):
+ comment += "\tdelay = " + ",".join(map(str, state.delays)) + "\n"
+ if state.loop != 0:
+ comment += f"\tloop = {state.loop}\n"
+ if state.rewind:
+ comment += "\trewind = 1\n"
+ if state.movement:
+ comment += "\tmovement = 1\n"
+ if state.hotspots and any(state.hotspots):
+ current = None
+ for i, value in enumerate(state.hotspots):
+ if value != current:
+ x, y = value
+ comment += f"\thotspot = {x},{y},{i + 1}\n"
+ current = value
+ comment += "# END DMI"
+ return comment
+
+ def to_file(self, filename, *, palette=False):
+ # assemble comment
+ comment = self._assemble_comment()
+
+ # assemble spritesheet
+ W, H = self.width, self.height
+ num_frames = sum(len(state.frames) for state in self.states)
+ sqrt = math.ceil(math.sqrt(num_frames))
+ output = Image.new('RGBA', (sqrt * W, math.ceil(num_frames / sqrt) * H))
+
+ i = 0
+ for state in self.states:
+ for frame in state.frames:
+ output.paste(frame, ((i % sqrt) * W, (i // sqrt) * H))
+ i += 1
+
+ # save
+ pnginfo = PngInfo()
+ pnginfo.add_text('Description', comment, zip=True)
+ if palette:
+ output = output.convert('P')
+ output.save(filename, 'png', optimize=True, pnginfo=pnginfo)
+
+
+class State:
+ def __init__(self, dmi, name, *, loop=LOOP_UNLIMITED, rewind=False, movement=False, dirs=1):
+ self.dmi = dmi
+ self.name = name
+ self.loop = loop
+ self.rewind = rewind
+ self.movement = movement
+ self.dirs = dirs
+
+ self._nframes = None # used during loading only
+ self.frames = []
+ self.delays = []
+ self.hotspots = None
+
+ @property
+ def framecount(self):
+ if self._nframes is not None:
+ return self._nframes
+ else:
+ return len(self.frames) // self.dirs
+
+ def frame(self, image, *, delay=1):
+ assert image.size == (self.dmi.width, self.dmi.height)
+ self.delays.append(delay)
+ self.frames.append(image)
+
+ def hotspot(self, first_frame, x, y):
+ if self.hotspots is None:
+ self.hotspots = [None] * self.framecount
+ for i in range(first_frame, self.framecount):
+ self.hotspots[i] = x, y
+
+ def _frame_index(self, frame=0, dir=None):
+ ofs = DIR_ORDER.index(DIR_NAMES[dir])
+ if ofs >= self.dirs:
+ ofs = 0
+ return frame * self.dirs + ofs
+
+ def get_frame(self, *args, **kwargs):
+ return self.frames[self._frame_index(*args, **kwargs)]
+
+
+def escape(text):
+ text = text.replace('\\', '\\\\')
+ text = text.replace('"', '\\"')
+ return f'"{text}"'
+
+
+def unescape(text, quote='"'):
+ if text == 'null':
+ return None
+ if not (text.startswith(quote) and text.endswith(quote)):
+ raise ValueError(text)
+ text = text[1:-1]
+ text = text.replace('\\"', '"')
+ text = text.replace('\\\\', '\\')
+ return text
+
+
+def parse_num(value):
+ if '.' in value:
+ return float(value)
+ return int(value)
+
+
+def parse_bool(value):
+ if value not in ('0', '1'):
+ raise ValueError(value)
+ return value == '1'
diff --git a/tools/dmi/merge_driver.py b/tools/dmi/merge_driver.py
new file mode 100644
index 000000000000..75c3daacb071
--- /dev/null
+++ b/tools/dmi/merge_driver.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+import sys
+import dmi
+from hooks.merge_frontend import MergeDriver
+
+
+def images_equal(left, right):
+ if left.size != right.size:
+ return False
+ w, h = left.size
+ left_load, right_load = left.load(), right.load()
+ for y in range(0, h):
+ for x in range(0, w):
+ lpixel, rpixel = left_load[x, y], right_load[x, y]
+ # quietly ignore changes where both pixels are fully transparent
+ if lpixel != rpixel and (lpixel[3] != 0 or rpixel[3] != 0):
+ return False
+ return True
+
+
+def states_equal(left, right):
+ result = True
+
+ # basic properties
+ for attr in ('loop', 'rewind', 'movement', 'dirs', 'delays', 'hotspots', 'framecount'):
+ lval, rval = getattr(left, attr), getattr(right, attr)
+ if lval != rval:
+ result = False
+
+ # frames
+ for (left_frame, right_frame) in zip(left.frames, right.frames):
+ if not images_equal(left_frame, right_frame):
+ result = False
+
+ return result
+
+
+def key_of(state):
+ return (state.name, state.movement)
+
+
+def dictify(sheet):
+ result = {}
+ for state in sheet.states:
+ k = key_of(state)
+ if k in result:
+ print(f" duplicate {k!r}")
+ result[k] = state
+ return result
+
+
+def three_way_merge(base, left, right):
+ base_dims = base.width, base.height
+ if base_dims != (left.width, left.height) or base_dims != (right.width, right.height):
+ print("Dimensions have changed:")
+ print(f" Base: {base.width} x {base.height}")
+ print(f" Ours: {left.width} x {left.height}")
+ print(f" Theirs: {right.width} x {right.height}")
+ return True, None
+
+ base_states, left_states, right_states = dictify(base), dictify(left), dictify(right)
+
+ new_left = {k: v for k, v in left_states.items() if k not in base_states}
+ new_right = {k: v for k, v in right_states.items() if k not in base_states}
+ new_both = {}
+ conflicts = []
+ for key, state in list(new_left.items()):
+ in_right = new_right.get(key, None)
+ if in_right:
+ if states_equal(state, in_right):
+ # allow it
+ new_both[key] = state
+ else:
+ # generate conflict states
+ print(f" C: {state.name!r}: added differently in both!")
+ state.name = f"{state.name} !CONFLICT! left"
+ conflicts.append(state)
+ in_right.name = f"{state.name} !CONFLICT! right"
+ conflicts.append(in_right)
+ # don't add it a second time
+ del new_left[key]
+ del new_right[key]
+
+ final_states = []
+ # add states that are currently in the base
+ for state in base.states:
+ in_left = left_states.get(key_of(state), None)
+ in_right = right_states.get(key_of(state), None)
+ left_equals = in_left and states_equal(state, in_left)
+ right_equals = in_right and states_equal(state, in_right)
+
+ if not in_left and not in_right:
+ # deleted in both left and right, it's just deleted
+ print(f" {state.name!r}: deleted in both")
+ elif not in_left:
+ # left deletes
+ print(f" {state.name!r}: deleted in left")
+ if not right_equals:
+ print(f" ... but modified in right")
+ final_states.append(in_right)
+ elif not in_right:
+ # right deletes
+ print(f" {state.name!r}: deleted in right")
+ if not left_equals:
+ print(f" ... but modified in left")
+ final_states.append(in_left)
+ elif left_equals and right_equals:
+ # changed in neither
+ #print(f"Same in both: {state.name!r}")
+ final_states.append(state)
+ elif left_equals:
+ # changed only in right
+ print(f" {state.name!r}: changed in left")
+ final_states.append(in_right)
+ elif right_equals:
+ # changed only in left
+ print(f" {state.name!r}: changed in right")
+ final_states.append(in_left)
+ elif states_equal(in_left, in_right):
+ # changed in both, to the same thing
+ print(f" {state.name!r}: changed same in both")
+ final_states.append(in_left) # either or
+ else:
+ # changed in both
+ name = state.name
+ print(f" C: {name!r}: changed differently in both!")
+ state.name = f"{name} !CONFLICT! base"
+ conflicts.append(state)
+ in_left.name = f"{name} !CONFLICT! left"
+ conflicts.append(in_left)
+ in_right.name = f"{name} !CONFLICT! right"
+ conflicts.append(in_right)
+
+ # add states which both left and right added the same
+ for key, state in new_both.items():
+ print(f" {state.name!r}: added same in both")
+ final_states.append(state)
+
+ # add states that are brand-new in the left
+ for key, state in new_left.items():
+ print(f" {state.name!r}: added in left")
+ final_states.append(state)
+
+ # add states that are brand-new in the right
+ for key, state in new_right.items():
+ print(f" {state.name!r}: added in right")
+ final_states.append(state)
+
+ final_states.extend(conflicts)
+ merged = dmi.Dmi(base.width, base.height)
+ merged.states = final_states
+ return len(conflicts), merged
+
+
+class DmiDriver(MergeDriver):
+ driver_id = 'dmi'
+
+ def merge(self, base, left, right):
+ icon_base = dmi.Dmi.from_file(base)
+ icon_left = dmi.Dmi.from_file(left)
+ icon_right = dmi.Dmi.from_file(right)
+ trouble, merge_result = three_way_merge(icon_base, icon_left, icon_right)
+ return not trouble, merge_result
+
+ def to_file(self, outfile, merge_result):
+ merge_result.to_file(outfile)
+
+ def post_announce(self, success, merge_result):
+ if not success:
+ print("!!! Manual merge required!")
+ if merge_result:
+ print(" A best-effort merge was performed. You must edit the icon and remove all")
+ print(" icon states marked with !CONFLICT!, leaving only the desired icon.")
+ else:
+ print(" The icon was totally unable to be merged, you must start with one version")
+ print(" or the other and manually resolve the conflict.")
+ print(" Information about which states conflicted is listed above.")
+
+
+if __name__ == '__main__':
+ exit(DmiDriver().main())
diff --git a/tools/dmi/test.py b/tools/dmi/test.py
new file mode 100644
index 000000000000..09629927d88a
--- /dev/null
+++ b/tools/dmi/test.py
@@ -0,0 +1,39 @@
+import os
+import sys
+from dmi import *
+
+
+def _self_test():
+ # test: can we load every DMI in the tree
+ count = 0
+ for dirpath, dirnames, filenames in os.walk('.'):
+ if '.git' in dirnames:
+ dirnames.remove('.git')
+ for filename in filenames:
+ if filename.endswith('.dmi'):
+ fullpath = os.path.join(dirpath, filename)
+ try:
+ Dmi.from_file(fullpath)
+ except Exception:
+ print('Failed on:', fullpath)
+ raise
+ count += 1
+
+ print(f"{os.path.relpath(__file__)}: successfully parsed {count} .dmi files")
+
+
+def _usage():
+ print(f"Usage:")
+ print(f" tools{os.sep}bootstrap{os.sep}python -m {__spec__.name}")
+ exit(1)
+
+
+def _main():
+ if len(sys.argv) == 1:
+ return _self_test()
+
+ return _usage()
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/tools/hooks/README.md b/tools/hooks/README.md
index b15fb493d8d6..c71059039723 100644
--- a/tools/hooks/README.md
+++ b/tools/hooks/README.md
@@ -1,41 +1,36 @@
# Git Integration Hooks
-This folder contains installable scripts for [Git hooks] and [merge drivers].
+This folder contains installable scripts for Git [hooks] and [merge drivers].
Use of these hooks and drivers is optional and they must be installed
explicitly before they take effect.
To install the current set of hooks, or update if new hooks are added, run
-`install.bat` (Windows) or `install.sh` (Unix-like) as appropriate.
+`Install.bat` (Windows) or `tools/hooks/install.sh` (Unix-like) as appropriate.
-Hooks expect a Unix-like environment on the backend. Usually this is handled
-automatically by GUI tools like TortoiseGit and GitHub for Windows, but
-[Git for Windows] is an option if you prefer to use a CLI even on Windows.
+If your Git GUI does not support a given hook, there is usually a `.bat` file
+or other script you can run instead - see the links below for details.
-## Current Hooks
+## Hooks
-* **Pre-commit**: Runs [mapmerge2] on changed maps, if any.
-* **DMI merger**: Attempts to [fix icon conflicts] when performing a git merge.
- If it succeeds, the file is marked merged. If it fails, it logs what states
- are still in conflict and adds them to the .dmi file, where the desired
- resolution can be chosen.
+* **Pre-commit**: Runs [mapmerge2] to reduce the diff on any changed maps.
+* **DMI merger**: Attempts to [fix icon conflicts] when performing a Git merge.
+* **DMM merger**: Attempts to [fix map conflicts] when performing a Git merge.
## Adding New Hooks
-New [Git hooks] may be added by creating a file named `.hook` in
+New Git [hooks] may be added by creating a file named `.hook` in
this directory. Git determines what hooks are available and what their names
-are. The install script copies the `.hook` file into `.git/hooks`, so editing
-the `.hook` file will require a reinstall.
+are.
-New [merge drivers] may be added by adding a shell script named `.merge`
+New Git [merge drivers] may be added by adding a shell script named `.merge`
and updating `.gitattributes` in the root of the repository to include the line
-`*. merge=`. The install script will set up the merge driver to point
-to the `.merge` file directly, and editing it will not require a reinstall.
+`*. merge=`.
-`tools/hooks/python.sh` may be used as a trampoline to ensure that the correct
-version of Python is found.
+Adding or removing hooks or merge drivers requires running the install script
+again, but modifying them does not. See existing `.hook` and `.merge` files for examples.
-[Git hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
+[hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
[merge drivers]: https://git-scm.com/docs/gitattributes#_performing_a_three_way_merge
-[Git for Windows]: https://gitforwindows.org/
[mapmerge2]: ../mapmerge2/README.md
-[fix icon conflicts]: ../mapmerge2/merge_driver_dmi.py
+[fix icon conflicts]: https://tgstation13.org/wiki/Resolving_icon_conflicts
+[fix map conflicts]: https://tgstation13.org/wiki/Map_Merger
diff --git a/tools/hooks/Uninstall.bat b/tools/hooks/Uninstall.bat
new file mode 100644
index 000000000000..862cbaf8bc4f
--- /dev/null
+++ b/tools/hooks/Uninstall.bat
@@ -0,0 +1,2 @@
+@call "%~dp0\..\bootstrap\python" -m hooks.install --uninstall %*
+@pause
diff --git a/tools/hooks/dmi.merge b/tools/hooks/dmi.merge
index 7fd9f171bf07..d82ac06be867 100755
--- a/tools/hooks/dmi.merge
+++ b/tools/hooks/dmi.merge
@@ -1,2 +1,2 @@
#!/bin/sh
-exec tools/hooks/python.sh -m merge_driver_dmi "$@"
+exec tools/bootstrap/python -m dmi.merge_driver "$@"
diff --git a/tools/hooks/dmm.merge b/tools/hooks/dmm.merge
new file mode 100644
index 000000000000..64fa16b5020f
--- /dev/null
+++ b/tools/hooks/dmm.merge
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec tools/bootstrap/python -m mapmerge2.merge_driver "$@"
diff --git a/tools/hooks/install.bat b/tools/hooks/install.bat
index 7a11129a2a2f..1bfc1b50d5c0 100644
--- a/tools/hooks/install.bat
+++ b/tools/hooks/install.bat
@@ -1,16 +1,2 @@
-@echo off
-cd %~dp0
-for %%f in (*.hook) do (
- echo Installing hook: %%~nf
- copy %%f ..\..\.git\hooks\%%~nf >nul
-)
-for %%f in (*.merge) do (
- echo Installing merge driver: %%~nf
- echo [merge "%%~nf"]^
-
- driver = tools/hooks/%%f %%P %%O %%A %%B %%L >> ..\..\.git\config
-)
-echo Installing Python dependencies
-python -m pip install -r ..\mapmerge2\requirements.txt
-echo Done
-pause
+@call "%~dp0\..\bootstrap\python" -m hooks.install %*
+@pause
diff --git a/tools/hooks/install.py b/tools/hooks/install.py
new file mode 100644
index 000000000000..0736b29681e8
--- /dev/null
+++ b/tools/hooks/install.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# hooks/install.py
+#
+# This script is configured by adding `*.hook` and `*.merge` files in the same
+# directory. Such files should be `#!/bin/sh` scripts, usually invoking Python.
+# This installer will have to be re-run any time a hook or merge file is added
+# or removed, but not when they are changed.
+#
+# Merge drivers will also need a corresponding entry in the `.gitattributes`
+# file.
+
+import os
+import stat
+import glob
+import re
+import pygit2
+import shlex
+
+
+def write_hook(fname, command):
+ with open(fname, 'w', encoding='utf-8', newline='\n') as f:
+ print("#!/bin/sh", file=f)
+ print("exec", command, file=f)
+
+ # chmod +x
+ st = os.stat(fname)
+ if not hasattr(st, 'st_file_attributes'):
+ os.chmod(fname, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
+
+
+def _find_stuff(target=None):
+ repo_dir = pygit2.discover_repository(target or os.getcwd())
+ repo = pygit2.Repository(repo_dir)
+ # Strips any active worktree to find the hooks directory.
+ root_repo_dir = re.sub(r'/.git/worktrees/[^/]+/', '/.git/', repo_dir)
+ hooks_dir = os.path.join(root_repo_dir, 'hooks')
+ return repo, hooks_dir
+
+
+def uninstall(target=None, keep=()):
+ repo, hooks_dir = _find_stuff(target)
+
+ # Remove hooks
+ for fname in glob.glob(os.path.join(hooks_dir, '*')):
+ _, shortname = os.path.split(fname)
+ if not fname.endswith('.sample') and f"{shortname}.hook" not in keep:
+ print('Removing hook:', shortname)
+ os.unlink(fname)
+
+ # Remove merge driver configuration
+ for entry in repo.config:
+ match = re.match(r'^merge\.([^.]+)\.driver$', entry.name)
+ if match and f"{match.group(1)}.merge" not in keep:
+ print('Removing merge driver:', match.group(1))
+ del repo.config[entry.name]
+
+
+def install(target=None):
+ repo, hooks_dir = _find_stuff(target)
+ tools_hooks = os.path.split(__file__)[0]
+
+ keep = set()
+ for full_path in glob.glob(os.path.join(tools_hooks, '*.hook')):
+ _, fname = os.path.split(full_path)
+ name, _ = os.path.splitext(fname)
+ print('Installing hook:', name)
+ keep.add(fname)
+ relative_path = shlex.quote(os.path.relpath(full_path, repo.workdir).replace('\\', '/'))
+ write_hook(os.path.join(hooks_dir, name), f'{relative_path} "$@"')
+
+ # Use libgit2 config manipulation to set the merge driver config.
+ for full_path in glob.glob(os.path.join(tools_hooks, '*.merge')):
+ # Merge drivers are documented here: https://git-scm.com/docs/gitattributes
+ _, fname = os.path.split(full_path)
+ name, _ = os.path.splitext(fname)
+ print('Installing merge driver:', name)
+ keep.add(fname)
+ # %P: "real" path of the file, should not usually be read or modified
+ # %O: ancestor's version
+ # %A: current version, and also the output path
+ # %B: other branches' version
+ # %L: conflict marker size
+ relative_path = shlex.quote(os.path.relpath(full_path, repo.workdir).replace('\\', '/'))
+ repo.config[f"merge.{name}.driver"] = f'{relative_path} %P %O %A %B %L'
+
+ uninstall(target, keep=keep)
+
+
+def main(argv):
+ if len(argv) <= 1:
+ return install()
+ elif argv[1] == '--uninstall':
+ return uninstall()
+ else:
+ print("Usage: python -m hooks.install [--uninstall]")
+ return 1
+
+
+if __name__ == '__main__':
+ import sys
+ exit(main(sys.argv))
diff --git a/tools/hooks/install.sh b/tools/hooks/install.sh
old mode 100755
new mode 100644
index 30785a290bef..d0e995ca097a
--- a/tools/hooks/install.sh
+++ b/tools/hooks/install.sh
@@ -1,19 +1,2 @@
-#!/bin/bash
-set -e
-shopt -s nullglob
-cd "$(dirname "$0")"
-for f in *.hook; do
- echo Installing hook: ${f%.hook}
- cp $f ../../.git/hooks/${f%.hook}
-done
-for f in *.merge; do
- echo Installing merge driver: ${f%.merge}
- git config --replace-all merge.${f%.merge}.driver "tools/hooks/$f %P %O %A %B %L"
-done
-
-echo "Installing tgui hooks"
-../../tgui/bin/tgui --install-git-hooks
-
-echo "Installing Python dependencies"
-./python.sh -m pip install -r ../requirements.txt
-echo "Done"
+#!/bin/sh
+exec "$(dirname "$0")/../bootstrap/python" -m hooks.install "$@"
diff --git a/tools/hooks/merge_frontend.py b/tools/hooks/merge_frontend.py
new file mode 100644
index 000000000000..b735d1e3a5c3
--- /dev/null
+++ b/tools/hooks/merge_frontend.py
@@ -0,0 +1,169 @@
+# merge_frontend.py
+import sys
+import io
+import os
+import pygit2
+import collections
+import typing
+
+
+ENCODING = 'utf-8'
+
+
+class MergeReturn(typing.NamedTuple):
+ success: bool
+ merge_result: typing.Optional[object]
+
+
+class MergeDriver:
+ driver_id: typing.Optional[str] = None
+
+ def pre_announce(self, path: str):
+ """
+ Called before merge() is called, with a human-friendly path for output.
+ """
+ print(f"Merging {self.driver_id}: {path}")
+
+ def merge(self, base: typing.BinaryIO, left: typing.BinaryIO, right: typing.BinaryIO) -> MergeReturn:
+ """
+ Read from three BinaryIOs: base (common ancestor), left (ours), and
+ right (theirs). Perform the actual three-way merge operation. Leave
+ conflict markers if necessary.
+
+ Return (False, None) to indicate the merge driver totally failed.
+ Return (False, merge_result) if the result contains conflict markers.
+ Return (True, merge_result) if everything went smoothly.
+ """
+ raise NotImplementedError
+
+ def to_file(self, output: typing.BinaryIO, merge_result: object):
+ """
+ Save the merge() result to the given output stream.
+ Override this if the merge() result is not bytes or str.
+ """
+ if isinstance(merge_result, bytes):
+ output.write(merge_result)
+ elif isinstance(merge_result, str):
+ with io.TextIOWrapper(output, ENCODING) as f:
+ f.write(merge_result)
+ else:
+ raise NotImplementedError
+
+ def post_announce(self, success: bool, merge_result: object):
+ """
+ Called after merge() is called, to warn the user if action is needed.
+ """
+ if not success:
+ print("!!! Manual merge required")
+ if merge_result:
+ print(" A best-effort merge was performed. You must finish the job yourself.")
+ else:
+ print(" No merge was possible. You must resolve the conflict yourself.")
+
+ def main(self, args: typing.List[str] = None):
+ return _main(self, args or sys.argv[1:])
+
+
+def _main(driver: MergeDriver, args: typing.List[str]):
+ if len(args) > 0 and args[0] == '--posthoc':
+ return _posthoc_main(driver, args[1:])
+ else:
+ return _driver_main(driver, args)
+
+
+def _driver_main(driver: MergeDriver, args: typing.List[str]):
+ """
+ Act like a normal Git merge driver, called by Git during a merge.
+ """
+ if len(args) != 5:
+ print("merge driver called with wrong number of arguments")
+ print(" usage: %P %O %A %B %L")
+ return 1
+
+ path, path_base, path_left, path_right, _ = args
+ driver.pre_announce(path)
+
+ with open(path_base, 'rb') as io_base:
+ with open(path_left, 'rb') as io_left:
+ with open(path_right, 'rb') as io_right:
+ success, merge_result = driver.merge(io_base, io_left, io_right)
+
+ if merge_result:
+ # If we got anything, write it to the working directory.
+ with open(path_left, 'wb') as io_output:
+ driver.to_file(io_output, merge_result)
+
+ driver.post_announce(success, merge_result)
+ if not success:
+ # If we were not successful, do not mark the conflict as resolved.
+ return 1
+
+
+def _posthoc_main(driver: MergeDriver, args: typing.List[str]):
+ """
+ Apply merge driver logic to a repository which is already in a conflicted
+ state, running the driver on any conflicted files.
+ """
+ repo_dir = pygit2.discover_repository(os.getcwd())
+ repo = pygit2.Repository(repo_dir)
+ conflicts = repo.index.conflicts
+ if not conflicts:
+ print("There are no unresolved conflicts.")
+ return 0
+
+ all_success = True
+ index_changed = False
+ any_attempted = False
+ for base, left, right in list(conflicts):
+ if not base or not left or not right:
+ # (not left) or (not right): deleted in one branch, modified in the other.
+ # (not base): added differently in both branches.
+ # In either case, there's nothing we can do for now.
+ continue
+
+ path = left.path
+ if not _applies_to(repo, driver, path):
+ # Skip the file if it's not the right extension.
+ continue
+
+ any_attempted = True
+ driver.pre_announce(path)
+ io_base = io.BytesIO(repo[base.id].data)
+ io_left = io.BytesIO(repo[left.id].data)
+ io_right = io.BytesIO(repo[right.id].data)
+ success, merge_result = driver.merge(io_base, io_left, io_right)
+ if merge_result:
+ # If we got anything, write it to the working directory.
+ with open(os.path.join(repo.workdir, path), 'wb') as io_output:
+ driver.to_file(io_output, merge_result)
+
+ if success:
+ # If we were successful, mark the conflict as resolved.
+ with open(os.path.join(repo.workdir, path), 'rb') as io_readback:
+ contents = io_readback.read()
+ merged_id = repo.create_blob(contents)
+ repo.index.add(pygit2.IndexEntry(path, merged_id, left.mode))
+ del conflicts[path]
+ index_changed = True
+ if not success:
+ all_success = False
+ driver.post_announce(success, merge_result)
+
+ if index_changed:
+ repo.index.write()
+
+ if not any_attempted:
+ print("There are no unresolved", driver.driver_id, "conflicts.")
+
+ if not all_success:
+ # Not usually observed, but indicate the failure just in case.
+ return 1
+
+
+def _applies_to(repo: pygit2.Repository, driver: MergeDriver, path: str):
+ """
+ Check if the current merge driver is a candidate to handle a given path.
+ """
+ if not driver.driver_id:
+ raise ValueError('Driver must have ID to perform post-hoc merge')
+ return repo.get_attr(path, 'merge') == driver.driver_id
diff --git a/tools/hooks/pre-commit.hook b/tools/hooks/pre-commit.hook
index 970be47a44b8..8ad5ba32a457 100755
--- a/tools/hooks/pre-commit.hook
+++ b/tools/hooks/pre-commit.hook
@@ -1,3 +1,2 @@
#!/bin/sh
-# `sh` must be used here instead of `bash` to support GitHub Desktop.
-exec tools/hooks/python.sh -m precommit
+exec tools/bootstrap/python -m mapmerge2.precommit
diff --git a/tools/hooks/python.sh b/tools/hooks/python.sh
old mode 100755
new mode 100644
index 992635e4ac82..841b1208b50c
--- a/tools/hooks/python.sh
+++ b/tools/hooks/python.sh
@@ -1,5 +1,4 @@
#!/bin/sh
-# `sh` must be used here instead of `bash` to support GitHub Desktop.
set -e
if command -v python >/dev/null 2>&1; then
@@ -8,16 +7,18 @@ elif command -v python3 >/dev/null 2>&1; then
PY=python3
elif command -v py >/dev/null 2>&1; then
PY=py
+if [ "$*" = "-m precommit" ]; then
+ echo "Hooks are being updated..."
+ echo "Details: https://github.com/tgstation/tgstation/pull/55658"
+ if [ "$(uname -o)" = "Msys" ]; then
+ tools/hooks/Install.bat
+ else
+ tools/hooks/install.sh
+ fi
+ echo "---------------"
+ exec tools/hooks/pre-commit.hook
else
- echo "Please install Python 3.6 or later."
+ echo "tools/hooks/python.sh is replaced by tools/bootstrap/python"
+ echo "Details: https://github.com/tgstation/tgstation/pull/55658"
+ exit 1
fi
-PATHSEP=$($PY - <<'EOF'
-import sys, os
-if sys.version_info.major != 3 or sys.version_info.minor < 6:
- sys.stderr.write("Python 3.6 or later is required, but you have: " + sys.version + "\n")
- exit(1)
-print(os.pathsep)
-EOF
-)
-export PYTHONPATH=tools/mapmerge2/${PATHSEP}${PYTHONPATH}
-exec $PY "$@"
diff --git a/tools/mapmerge2/I Forgot To Map Merge.bat b/tools/mapmerge2/I Forgot To Map Merge.bat
new file mode 100644
index 000000000000..952b9fe25508
--- /dev/null
+++ b/tools/mapmerge2/I Forgot To Map Merge.bat
@@ -0,0 +1,7 @@
+@echo off
+echo Installing hooks for next time...
+call "%~dp0\..\bootstrap\python.bat" -m hooks.install
+echo.
+echo Fixing things up...
+call "%~dp0\..\bootstrap\python.bat" -m mapmerge2.fixup
+pause
diff --git a/tools/mapmerge2/README.md b/tools/mapmerge2/README.md
index 0ff4d21ac2ed..71164bdb71f0 100644
--- a/tools/mapmerge2/README.md
+++ b/tools/mapmerge2/README.md
@@ -1,8 +1,27 @@
-# Map Merge 2
+# Map Merger
-**Map Merge 2** is an improvement over previous map merging scripts, with
-better merge-conflict prevention, multi-Z support, and automatic handling of
-key overflow. For up-to-date tips and tricks, also visit the [Map Merger] wiki article.
+The **Map Merger** is a collection of scripts that keep this repository's maps
+in a format which is easier to track in Git and less likely to cause merge
+conflicts. When merge conflicts do occur, it can sometimes resolve them.
+
+For detailed troubleshooting instructions and other tips, visit the
+[Map Merger] wiki article.
+
+## Installation
+
+To install the [Git hooks], open the `tools/hooks/` folder and double-click
+`Install.bat`. Linux users run `tools/hooks/install.sh`.
+
+## Manual Use
+
+If using a Git GUI which is not compatible with the hooks:
+
+* Before committing, double-click `Run Before Committing.bat`
+* When a merge has map conflicts, double-click `Resolve Map Conflicts.bat`
+
+The console will show whether the operation succeeded.
+
+For more details, see the [Map Merger] wiki article.
## What Map Merging Is
@@ -13,16 +32,8 @@ version of the map while maintaining all the actual changes. It requires an old
version of the map to use as a reference and a new version of the map which
contains the desired changes.
-## Installation
-
-To install Python dependencies, run `requirements-install.bat`, or run
-`python -m pip install -r requirements.txt` directly. See the [Git hooks]
-documentation to install the Git pre-commit hook which runs the map merger
-automatically, or use `tools/mapmerge/Prepare Maps.bat` to save backups before
-running `mapmerge.bat`.
-
-For up-to-date installation and detailed troubleshooting instructions, visit
-the [Map Merger] wiki article.
+Map Merge 2 adds multi-Z support, automatic handling of key overflow, better
+merge conflict prevention, and a real merge conflict resolver.
## Code Structure
diff --git a/tools/mapmerge2/Resolve Map Conflicts.bat b/tools/mapmerge2/Resolve Map Conflicts.bat
new file mode 100644
index 000000000000..cd4b95834b25
--- /dev/null
+++ b/tools/mapmerge2/Resolve Map Conflicts.bat
@@ -0,0 +1,2 @@
+@call "%~dp0\..\bootstrap\python.bat" -m mapmerge2.merge_driver --posthoc %*
+@pause
diff --git a/tools/mapmerge2/Run Before Committing.bat b/tools/mapmerge2/Run Before Committing.bat
new file mode 100644
index 000000000000..e989edb7b5bc
--- /dev/null
+++ b/tools/mapmerge2/Run Before Committing.bat
@@ -0,0 +1,2 @@
+@call "%~dp0\..\bootstrap\python" -m mapmerge2.precommit --use-workdir %*
+@pause
diff --git a/tools/mapmerge2/convert.py b/tools/mapmerge2/convert.py
index 35e5dda4433b..63fe8cbb9a01 100644
--- a/tools/mapmerge2/convert.py
+++ b/tools/mapmerge2/convert.py
@@ -1,8 +1,7 @@
#!/usr/bin/env python3
-import frontend
-import dmm
+from . import frontend, dmm
if __name__ == '__main__':
settings = frontend.read_settings()
for fname in frontend.process(settings, "convert"):
- dmm.DMM.from_file(fname).to_file(fname, settings.tgm)
+ dmm.DMM.from_file(fname).to_file(fname, tgm = settings.tgm)
diff --git a/tools/mapmerge2/dmm.py b/tools/mapmerge2/dmm.py
index ac52024458ab..bc12a39b3556 100644
--- a/tools/mapmerge2/dmm.py
+++ b/tools/mapmerge2/dmm.py
@@ -22,20 +22,19 @@ def __init__(self, key_length, size):
@staticmethod
def from_file(fname):
- # stream the file rather than forcing all its contents to memory
with open(fname, 'r', encoding=ENCODING) as f:
- return _parse(iter(lambda: f.read(1), ''))
+ return _parse(f.read())
@staticmethod
def from_bytes(bytes):
return _parse(bytes.decode(ENCODING))
- def to_file(self, fname, tgm = True):
+ def to_file(self, fname, *, tgm = True):
self._presave_checks()
with open(fname, 'w', newline='\n', encoding=ENCODING) as f:
(save_tgm if tgm else save_dmm)(self, f)
- def to_bytes(self, tgm = True):
+ def to_bytes(self, *, tgm = True):
self._presave_checks()
bio = io.BytesIO()
with io.TextIOWrapper(bio, newline='\n', encoding=ENCODING) as f:
@@ -43,20 +42,29 @@ def to_bytes(self, tgm = True):
f.flush()
return bio.getvalue()
+ def get_or_generate_key(self, tile):
+ try:
+ return self.dictionary.inv[tile]
+ except KeyError:
+ key = self.generate_new_key()
+ self.dictionary[key] = tile
+ return key
+
+ def get_tile(self, coord):
+ return self.dictionary[self.grid[coord]]
+
+ def set_tile(self, coord, tile):
+ tile = tuple(tile)
+ self.grid[coord] = self.get_or_generate_key(tile)
+
def generate_new_key(self):
- free_keys = self._ensure_free_keys(1)
+ self._ensure_free_keys(1)
+ max_key = max_key_for(self.key_length)
# choose one of the free keys at random
- key = 0
- while free_keys:
- if key not in self.dictionary:
- # this construction is used to avoid needing to construct the
- # full set in order to random.choice() from it
- if random.random() < 1 / free_keys:
- return key
- free_keys -= 1
- key += 1
-
- raise RuntimeError("ran out of keys, this shouldn't happen")
+ key = random.randint(0, max_key - 1)
+ while key in self.dictionary:
+ key = random.randint(0, max_key - 1)
+ return key
def overwrite_key(self, key, fixed, bad_keys):
try:
@@ -75,6 +83,14 @@ def reassign_bad_keys(self, bad_keys):
# reassign the grid entries which used the old key
self.grid[k] = bad_keys.get(v, v)
+ def remove_unused_keys(self, modified_keys = None):
+ unused_keys = list(set(modified_keys)) if modified_keys is not None else self.dictionary.keys()
+ for key in self.grid.values():
+ if key in unused_keys:
+ unused_keys.remove(key)
+ for key in unused_keys:
+ del self.dictionary[key]
+
def _presave_checks(self):
# last-second handling of bogus keys to help prevent and fix broken maps
self._ensure_free_keys(0)
@@ -125,6 +141,9 @@ def coords_yx(self):
for x in range(1, self.size.x + 1):
yield (y, x)
+ def __repr__(self):
+ return f"DMM(size={self.size}, key_length={self.key_length}, dictionary_size={len(self.dictionary)})"
+
# ----------
# key handling
@@ -227,10 +246,8 @@ def is_bad_atom_ordering(key, atoms):
print(f"Warning: key '{key}' is missing either a turf or area")
return can_fix
-def fix_atom_ordering(atoms):
- movables = []
- turfs = []
- areas = []
+def split_atom_groups(atoms):
+ movables, turfs, areas = [], [], []
for each in atoms:
if each.startswith('/turf'):
turfs.append(each)
@@ -238,6 +255,10 @@ def fix_atom_ordering(atoms):
areas.append(each)
else:
movables.append(each)
+ return movables, turfs, areas
+
+def fix_atom_ordering(atoms):
+ movables, turfs, areas = split_atom_groups(atoms)
movables.extend(turfs)
movables.extend(areas)
return movables
@@ -283,24 +304,12 @@ def save_tgm(dmm, output):
# thanks to YotaXP for finding out about this one
max_x, max_y, max_z = dmm.size
-# yogs start - multi-column mode
- columns_to_write = 1
- total_tiles = max_x * max_y * max_z
- while total_tiles > 65536:
- columns_to_write += 1
- total_tiles -= 65536
-# yogs end
for z in range(1, max_z + 1):
output.write("\n")
- for x in range(1, max_x + 1, columns_to_write): # yogs - make the step be columns_to_write
+ for x in range(1, max_x + 1):
output.write(f"({x},{1},{z}) = {{\"\n")
- for y in range(1, max_y + 1):
-# yogs start - multi-column mode
- for xo in range(0, columns_to_write):
- if (xo + x) <= max_x:
- output.write(f"{num_to_key(dmm.grid[x+xo, y, z], dmm.key_length)}")
- output.write("\n")
-# yogs end
+ for y in range(max_y, 0, -1):
+ output.write(f"{num_to_key(dmm.grid[x, y, z], dmm.key_length)}\n")
output.write("\"}\n")
# ----------
@@ -321,7 +330,7 @@ def save_dmm(dmm, output):
for z in range(1, max_z + 1):
output.write(f"(1,1,{z}) = {{\"\n")
- for y in range(1, max_y + 1):
+ for y in range(max_y, 0, -1):
for x in range(1, max_x + 1):
try:
output.write(num_to_key(dmm.grid[x, y, z], dmm.key_length))
@@ -554,7 +563,17 @@ def _parse(map_raw_text):
if curr_y > maxy:
maxy = curr_y
+ if not grid:
+ # Usually caused by unbalanced quotes.
+ max_key = num_to_key(max(dictionary.keys()), key_length, True)
+ raise ValueError(f"dmm failed to parse, check for a syntax error near or after key {max_key!r}")
+
+ # Convert from raw .dmm coordinates to DM/BYOND coordinates by flipping Y
+ grid2 = dict()
+ for (x, y, z), tile in grid.items():
+ grid2[x, maxy + 1 - y, z] = tile
+
data = DMM(key_length, Coordinate(maxx, maxy, maxz))
data.dictionary = dictionary
- data.grid = grid
+ data.grid = grid2
return data
diff --git a/tools/mapmerge2/dmm_test.py b/tools/mapmerge2/dmm_test.py
new file mode 100644
index 000000000000..02f5383f2abb
--- /dev/null
+++ b/tools/mapmerge2/dmm_test.py
@@ -0,0 +1,39 @@
+import os
+import sys
+from .dmm import *
+
+
+def _self_test():
+ # test: can we load every DMM in the tree
+ count = 0
+ for dirpath, dirnames, filenames in os.walk('.'):
+ if '.git' in dirnames:
+ dirnames.remove('.git')
+ for filename in filenames:
+ if filename.endswith('.dmm'):
+ fullpath = os.path.join(dirpath, filename)
+ try:
+ DMM.from_file(fullpath)
+ except Exception:
+ print('Failed on:', fullpath)
+ raise
+ count += 1
+
+ print(f"{os.path.relpath(__file__)}: successfully parsed {count} .dmm files")
+
+
+def _usage():
+ print(f"Usage:")
+ print(f" tools{os.sep}bootstrap{os.sep}python -m {__spec__.name}")
+ exit(1)
+
+
+def _main():
+ if len(sys.argv) == 1:
+ return _self_test()
+
+ return _usage()
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/tools/mapmerge2/fixup.py b/tools/mapmerge2/fixup.py
new file mode 100644
index 000000000000..6953e7ab7782
--- /dev/null
+++ b/tools/mapmerge2/fixup.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+import os
+import pygit2
+from . import dmm
+from .mapmerge import merge_map
+
+
+STATUS_INDEX = (pygit2.GIT_STATUS_INDEX_NEW
+ | pygit2.GIT_STATUS_INDEX_MODIFIED
+ | pygit2.GIT_STATUS_INDEX_DELETED
+ | pygit2.GIT_STATUS_INDEX_RENAMED
+ | pygit2.GIT_STATUS_INDEX_TYPECHANGE
+)
+STATUS_WT = (pygit2.GIT_STATUS_WT_NEW
+ | pygit2.GIT_STATUS_WT_MODIFIED
+ | pygit2.GIT_STATUS_WT_DELETED
+ | pygit2.GIT_STATUS_WT_RENAMED
+ | pygit2.GIT_STATUS_WT_TYPECHANGE
+)
+ABBREV_LEN = 12
+TGM_HEADER = dmm.TGM_HEADER.encode(dmm.ENCODING)
+
+
+def walk_tree(tree, *, _prefix=''):
+ for child in tree:
+ if isinstance(child, pygit2.Tree):
+ yield from walk_tree(child, _prefix=f'{_prefix}{child.name}/')
+ else:
+ yield f'{_prefix}{child.name}', child
+
+
+def insert_into_tree(repo, tree_builder, path, blob_oid):
+ try:
+ first, rest = path.split('/', 1)
+ except ValueError:
+ tree_builder.insert(path, blob_oid, pygit2.GIT_FILEMODE_BLOB)
+ else:
+ inner = repo.TreeBuilder(tree_builder.get(first))
+ insert_into_tree(repo, inner, rest, blob_oid)
+ tree_builder.insert(first, inner.write(), pygit2.GIT_FILEMODE_TREE)
+
+
+def main(repo):
+ if repo.index.conflicts:
+ print("You need to resolve merge conflicts first.")
+ return 1
+
+ # Ensure the index is clean.
+ for path, status in repo.status().items():
+ if status & pygit2.GIT_STATUS_IGNORED:
+ continue
+ if status & STATUS_INDEX:
+ print("You have changes staged for commit. Commit them or unstage them first.")
+ print("If you are about to commit maps for the first time, run `Run Before Committing.bat`.")
+ return 1
+ if path.endswith(".dmm") and (status & STATUS_WT):
+ print("You have modified maps. Commit them first.")
+ print("If you are about to commit maps for the first time, run `Run Before Committing.bat`.")
+ return 1
+
+ # Read the HEAD commit.
+ head_commit = repo[repo.head.target]
+ head_files = {}
+ for path, blob in walk_tree(head_commit.tree):
+ if path.endswith(".dmm"):
+ data = blob.read_raw()
+ if not data.startswith(TGM_HEADER):
+ head_files[path] = dmm.DMM.from_bytes(data)
+
+ if not head_files:
+ print("All committed maps appear to be in the correct format.")
+ print("If you are about to commit maps for the first time, run `Run Before Committing.bat`.")
+ return 1
+
+ # Work backwards to find a base for each map, converting as found.
+ converted = {}
+ if len(head_commit.parents) != 1:
+ print("Unable to automatically fix anything because HEAD is a merge commit.")
+ return 1
+ commit_message_lines = []
+ working_commit = head_commit.parents[0]
+ while len(converted) < len(head_files):
+ for path in head_files.keys() - converted.keys():
+ try:
+ blob = working_commit.tree[path]
+ except KeyError:
+ commit_message_lines.append(f"{'new':{ABBREV_LEN}}: {path}")
+ print(f"Converting new map: {path}")
+ converted[path] = head_files[path]
+ else:
+ data = blob.read_raw()
+ if data.startswith(TGM_HEADER):
+ str_id = str(working_commit.id)[:ABBREV_LEN]
+ commit_message_lines.append(f"{str_id}: {path}")
+ print(f"Converting map: {path}")
+ converted[path] = merge_map(head_files[path], dmm.DMM.from_bytes(data))
+ if len(working_commit.parents) != 1:
+ print("A merge commit was encountered before good versions of these maps were found:")
+ print("\n".join(f" {x}" for x in head_files.keys() - converted.keys()))
+ return 1
+ working_commit = working_commit.parents[0]
+
+ # Okay, do the actual work.
+ tree_builder = repo.TreeBuilder(head_commit.tree)
+ for path, merged_map in converted.items():
+ blob_oid = repo.create_blob(merged_map.to_bytes())
+ insert_into_tree(repo, tree_builder, path, blob_oid)
+ repo.index.add(pygit2.IndexEntry(path, blob_oid, repo.index[path].mode))
+ merged_map.to_file(os.path.join(repo.workdir, path))
+
+ # Save the index.
+ repo.index.write()
+
+ # Commit the index to the current branch.
+ signature = pygit2.Signature(repo.config['user.name'], repo.config['user.email'])
+ joined = "\n".join(commit_message_lines)
+ repo.create_commit(
+ repo.head.name,
+ signature, # author
+ signature, # committer
+ f'Convert maps to TGM\n\n{joined}\n\nAutomatically commited by: {os.path.relpath(__file__, repo.workdir)}',
+ tree_builder.write(),
+ [head_commit.id],
+ )
+
+ # Success.
+ print("Successfully committed a fixup. Push as needed.")
+ return 0
+
+
+if __name__ == '__main__':
+ exit(main(pygit2.Repository(pygit2.discover_repository(os.getcwd()))))
diff --git a/tools/mapmerge2/mapmerge.py b/tools/mapmerge2/mapmerge.py
index f449bd948f0f..d9cdea24cfb6 100644
--- a/tools/mapmerge2/mapmerge.py
+++ b/tools/mapmerge2/mapmerge.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
-import frontend
import shutil
-from dmm import *
from collections import defaultdict
+from . import frontend
+from .dmm import *
def merge_map(new_map, old_map, delete_unused=False):
if new_map.key_length != old_map.key_length:
@@ -66,7 +66,7 @@ def select_key(assigned):
# step two: delete unused keys
if unused_keys:
- print(f"Notice: Trimming {len(unused_keys)} unused dictionary keys.")
+ #print(f"Notice: Trimming {len(unused_keys)} unused dictionary keys.")
for key in unused_keys:
del merged.dictionary[key]
diff --git a/tools/mapmerge2/merge_driver.py b/tools/mapmerge2/merge_driver.py
new file mode 100644
index 000000000000..8daead9c165c
--- /dev/null
+++ b/tools/mapmerge2/merge_driver.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+import sys
+import collections
+from . import dmm, mapmerge
+from hooks.merge_frontend import MergeDriver
+
+
+debug_stats = collections.defaultdict(int)
+
+
+def select(base, left, right, *, debug=None):
+ if left == right:
+ # whether or not it's in the base, both sides agree
+ if debug:
+ debug_stats[f"select {debug} both"] += 1
+ return left
+ elif base == left:
+ # base == left, but right is different: accept right
+ if debug:
+ debug_stats[f"select {debug} right"] += 1
+ return right
+ elif base == right:
+ # base == right, but left is different: accept left
+ if debug:
+ debug_stats[f"select {debug} left"] += 1
+ return left
+ else:
+ # all three versions are different
+ if debug:
+ debug_stats[f"select {debug} fail"] += 1
+ return None
+
+
+def three_way_merge(base, left, right):
+ if base.size != left.size or base.size != right.size:
+ print("Dimensions have changed:")
+ print(f" Base: {base.size}")
+ print(f" Ours: {left.size}")
+ print(f" Theirs: {right.size}")
+ return True, None
+
+ trouble = False
+ merged = dmm.DMM(base.key_length, base.size)
+ merged.dictionary = base.dictionary.copy()
+
+ for (z, y, x) in base.coords_zyx:
+ coord = x, y, z
+ base_tile = base.get_tile(coord)
+ left_tile = left.get_tile(coord)
+ right_tile = right.get_tile(coord)
+
+ # try to merge the whole tiles
+ whole_tile_merge = select(base_tile, left_tile, right_tile, debug='tile')
+ if whole_tile_merge is not None:
+ merged.set_tile(coord, whole_tile_merge)
+ continue
+
+ # try to merge each group independently (movables, turfs, areas)
+ base_movables, base_turfs, base_areas = dmm.split_atom_groups(base_tile)
+ left_movables, left_turfs, left_areas = dmm.split_atom_groups(left_tile)
+ right_movables, right_turfs, right_areas = dmm.split_atom_groups(right_tile)
+
+ merged_movables = select(base_movables, left_movables, right_movables, debug='movable')
+ merged_turfs = select(base_turfs, left_turfs, right_turfs, debug='turf')
+ merged_areas = select(base_areas, left_areas, right_areas, debug='area')
+
+ if merged_movables is not None and merged_turfs is not None and merged_areas is not None:
+ merged.set_tile(coord, merged_movables + merged_turfs + merged_areas)
+ continue
+
+ # TODO: more advanced strategies?
+
+ # fall back to requiring manual conflict resolution
+ trouble = True
+ print(f" C: Both sides touch the tile at {coord}")
+
+ if merged_movables is None:
+ # Note that if you do not have an object that matches this path in your DME, the invalid path may be discarded when the map is loaded into a map editor.
+ # To rectify this, either add an object with this same path, or create a new object/denote an existing object in the obj_path define.
+ obj_path = "/obj/merge_conflict_marker"
+ obj_name = "---Merge Conflict Marker---"
+ obj_desc = "A best-effort merge was performed. You must resolve this conflict yourself (manually) and remove this object once complete."
+ merged_movables = left_movables + [f'{obj_path}{{name = "{obj_name}";\n\tdesc = "{obj_desc}"}}'] + right_movables
+ print(f" Left and right movable groups are split by an `{obj_path}` named \"{obj_name}\"")
+ if merged_turfs is None:
+ merged_turfs = left_turfs
+ print(f" Saving turf: {', '.join(left_turfs)}")
+ print(f" Alternative: {', '.join(right_turfs)}")
+ print(f" Original: {', '.join(base_turfs)}")
+ if merged_areas is None:
+ merged_areas = left_areas
+ print(f" Saving area: {', '.join(left_areas)}")
+ print(f" Alternative: {', '.join(right_areas)}")
+ print(f" Original: {', '.join(base_areas)}")
+
+ merged.set_tile(coord, merged_movables + merged_turfs + merged_areas)
+
+ merged = mapmerge.merge_map(merged, base)
+ return trouble, merged
+
+
+class DmmDriver(MergeDriver):
+ driver_id = 'dmm'
+
+ def merge(self, base, left, right):
+ map_base = dmm.DMM.from_bytes(base.read())
+ map_left = dmm.DMM.from_bytes(left.read())
+ map_right = dmm.DMM.from_bytes(right.read())
+ trouble, merge_result = three_way_merge(map_base, map_left, map_right)
+ return not trouble, merge_result
+
+ def to_file(self, outfile, merge_result):
+ outfile.write(merge_result.to_bytes())
+
+ def post_announce(self, success, merge_result):
+ if not success:
+ print("!!! Manual merge required!")
+ if merge_result:
+ print(" A best-effort merge was performed. You must edit the map and confirm")
+ print(" that all coordinates mentioned above are as desired.")
+ else:
+ print(" The map was totally unable to be merged; you must start with one version")
+ print(" or the other and manually resolve the conflict. Information about the")
+ print(" conflicting tiles is listed above.")
+
+
+if __name__ == '__main__':
+ exit(DmmDriver().main())
diff --git a/tools/mapmerge2/precommit.py b/tools/mapmerge2/precommit.py
index f5ea49a50ea4..3d22917f92c1 100644
--- a/tools/mapmerge2/precommit.py
+++ b/tools/mapmerge2/precommit.py
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import os
+import sys
import pygit2
-import dmm
-from mapmerge import merge_map
+from . import dmm
+from .mapmerge import merge_map
-def main(repo):
+
+def main(repo, *, use_workdir=False):
if repo.index.conflicts:
print("You need to resolve merge conflicts first.")
return 1
@@ -16,12 +18,21 @@ def main(repo):
except KeyError:
pass
+ target_statuses = pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_INDEX_NEW
+ skip_to_file_statuses = pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_MODIFIED
+ if use_workdir:
+ target_statuses |= pygit2.GIT_STATUS_WT_MODIFIED | pygit2.GIT_STATUS_WT_NEW
+ skip_to_file_statuses &= ~pygit2.GIT_STATUS_WT_MODIFIED
+
changed = 0
for path, status in repo.status().items():
- if path.endswith(".dmm") and (status & (pygit2.GIT_STATUS_INDEX_MODIFIED | pygit2.GIT_STATUS_INDEX_NEW)):
+ if path.endswith(".dmm") and (status & target_statuses):
# read the index
index_entry = repo.index[path]
- index_map = dmm.DMM.from_bytes(repo[index_entry.id].read_raw())
+ if use_workdir:
+ index_map = dmm.DMM.from_file(os.path.join(repo.workdir, path))
+ else:
+ index_map = dmm.DMM.from_bytes(repo[index_entry.id].read_raw())
try:
head_blob = repo[repo[repo.head.target].tree[path].id]
@@ -32,7 +43,7 @@ def main(repo):
merged_map = index_map
else:
# Entry in HEAD, merge the index over it
- print(f"Merging map: {path}", flush=True)
+ print(f"Converting map: {path}", flush=True)
assert not (status & pygit2.GIT_STATUS_INDEX_NEW)
head_map = dmm.DMM.from_bytes(head_blob.read_raw())
merged_map = merge_map(index_map, head_map)
@@ -43,15 +54,16 @@ def main(repo):
changed += 1
# write to the working directory if that's clean
- if status & (pygit2.GIT_STATUS_WT_DELETED | pygit2.GIT_STATUS_WT_MODIFIED):
+ if status & skip_to_file_statuses:
print(f"Warning: {path} has unindexed changes, not overwriting them")
else:
merged_map.to_file(os.path.join(repo.workdir, path))
if changed:
repo.index.write()
- print(f"Merged {changed} maps.")
return 0
+
if __name__ == '__main__':
- exit(main(pygit2.Repository(pygit2.discover_repository(os.getcwd()))))
+ repo = pygit2.Repository(pygit2.discover_repository(os.getcwd()))
+ exit(main(repo, use_workdir='--use-workdir' in sys.argv))
diff --git a/tools/requirements.txt b/tools/requirements.txt
index 25db21a92267..5670a9e28109 100644
--- a/tools/requirements.txt
+++ b/tools/requirements.txt
@@ -4,3 +4,7 @@ Pillow==10.0.1
# ezdb
mysql-connector-python==8.0.33
+
+# changelogs
+PyYaml==5.4
+beautifulsoup4==4.9.3
diff --git a/tools/tgs4_scripts/PreCompile.sh b/tools/tgs4_scripts/PreCompile.sh
index 0a82220cca87..79758de38504 100644
--- a/tools/tgs4_scripts/PreCompile.sh
+++ b/tools/tgs4_scripts/PreCompile.sh
@@ -83,7 +83,7 @@ fi
echo "Deploying auxmos..."
git checkout "$AUXMOS_VERSION"
-env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo rustc --release --target=i686-unknown-linux-gnu --features all_reaction_hooks,katmos -- -C target-cpu=native
+env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo rustc --release --target=i686-unknown-linux-gnu --features katmos -- -C target-cpu=native
mv -f target/i686-unknown-linux-gnu/release/libauxmos.so "$1/libauxmos.so"
cd ..
diff --git a/tools/travis/install_extools.sh b/tools/travis/install_extools.sh
index 8a9f6156332d..0bb73d5856d8 100644
--- a/tools/travis/install_extools.sh
+++ b/tools/travis/install_extools.sh
@@ -2,18 +2,23 @@
set -euo pipefail
-echo "Installing Extools dependencies"
-mkdir ../extools
-cd ../extools
-gh repo clone yogstation13/extools
-rm -r build
-mkdir build
-cd build
-cmake ../byond-extools
-cmake --build .
-chmod 755 libbyond-extools.so
+source dependencies.sh
-mkdir -p ~/.byond/bin
-cp libbyond-extools.so ~/.byond/bin/libbyond-extools.so
-ldd ~/.byond/bin/libbyond-extools.so
-echo "Finished installing Extools"
+if [ -f "$HOME/.byond/bin/libauxmos.so" ] && grep -Fxq "${AUXMOS_VERSION}" $HOME/.byond/bin/auxmos-version.txt;
+then
+ echo "Using cached directory."
+else
+ echo "Installing Auxmos"
+ git clone https://github.com/Putnam3145/auxmos
+ cd ./auxmos
+ rustup target add i686-unknown-linux-gnu
+ cargo build --target i686-unknown-linux-gnu --features katmos --release
+ chmod 755 ./target/i686-unknown-linux-gnu/release/libauxmos.so
+
+ mkdir -p ~/.byond/bin
+ cp ./target/i686-unknown-linux-gnu/release/libauxmos.so ~/.byond/bin/libauxmos.so
+ echo "$AUXMOS_VERSION" > "$HOME/.byond/bin/auxmos-version.txt"
+ ldd ~/.byond/bin/libauxmos.so
+ echo "Finished installing Auxmos"
+ cd ..
+fi
diff --git a/yogstation.dme b/yogstation.dme
index df88d14d961f..aa62ab5cb810 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -18,6 +18,7 @@
#include "code\_compile_options.dm"
#include "code\_debugger.dm"
#include "code\world.dm"
+#include "code\__DEFINES\_atoms.dm"
#include "code\__DEFINES\_auxtools.dm"
#include "code\__DEFINES\_bitfields.dm"
#include "code\__DEFINES\_click.dm"
@@ -30,6 +31,8 @@
#include "code\__DEFINES\actions.dm"
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\ai.dm"
+#include "code\__DEFINES\alerts.dm"
+#include "code\__DEFINES\ammo.dm"
#include "code\__DEFINES\announcements.dm"
#include "code\__DEFINES\anomalies.dm"
#include "code\__DEFINES\antagonists.dm"
@@ -39,8 +42,10 @@
#include "code\__DEFINES\atmospherics.dm"
#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\bindings.dm"
+#include "code\__DEFINES\blend_modes.dm"
#include "code\__DEFINES\bloodsuckers.dm"
#include "code\__DEFINES\callbacks.dm"
+#include "code\__DEFINES\cameranets.dm"
#include "code\__DEFINES\cargo.dm"
#include "code\__DEFINES\chat.dm"
#include "code\__DEFINES\cinematics.dm"
@@ -57,6 +62,7 @@
#include "code\__DEFINES\directional.dm"
#include "code\__DEFINES\diseases.dm"
#include "code\__DEFINES\DNA.dm"
+#include "code\__DEFINES\do_afters.dm"
#include "code\__DEFINES\dye_keys.dm"
#include "code\__DEFINES\dynamic.dm"
#include "code\__DEFINES\economy.dm"
@@ -69,30 +75,39 @@
#include "code\__DEFINES\food.dm"
#include "code\__DEFINES\footsteps.dm"
#include "code\__DEFINES\forensics.dm"
+#include "code\__DEFINES\gradient.dm"
#include "code\__DEFINES\hud.dm"
+#include "code\__DEFINES\icon_smoothing.dm"
#include "code\__DEFINES\instruments.dm"
#include "code\__DEFINES\interaction_flags.dm"
#include "code\__DEFINES\inventory.dm"
#include "code\__DEFINES\is_helpers.dm"
#include "code\__DEFINES\jobs.dm"
+#include "code\__DEFINES\lag_switch.dm"
#include "code\__DEFINES\language.dm"
#include "code\__DEFINES\layers.dm"
+#include "code\__DEFINES\lazy_templates.dm"
#include "code\__DEFINES\lighting.dm"
#include "code\__DEFINES\logging.dm"
#include "code\__DEFINES\machines.dm"
#include "code\__DEFINES\magic.dm"
+#include "code\__DEFINES\map_switch.dm"
+#include "code\__DEFINES\mapping.dm"
#include "code\__DEFINES\maps.dm"
#include "code\__DEFINES\materials.dm"
#include "code\__DEFINES\maths.dm"
+#include "code\__DEFINES\matrices.dm"
#include "code\__DEFINES\MC.dm"
#include "code\__DEFINES\melee.dm"
#include "code\__DEFINES\menu.dm"
#include "code\__DEFINES\misc.dm"
+#include "code\__DEFINES\mobfactions.dm"
#include "code\__DEFINES\mobs.dm"
#include "code\__DEFINES\monkeys.dm"
#include "code\__DEFINES\move_force.dm"
#include "code\__DEFINES\movement.dm"
#include "code\__DEFINES\movespeed_modification.dm"
+#include "code\__DEFINES\multiz.dm"
#include "code\__DEFINES\nanites.dm"
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\obj_flags.dm"
@@ -104,6 +119,7 @@
#include "code\__DEFINES\preferences.dm"
#include "code\__DEFINES\procpath.dm"
#include "code\__DEFINES\profile.dm"
+#include "code\__DEFINES\projectiles.dm"
#include "code\__DEFINES\qdel.dm"
#include "code\__DEFINES\radiation.dm"
#include "code\__DEFINES\radio.dm"
@@ -116,10 +132,12 @@
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\rust_g.dm"
#include "code\__DEFINES\say.dm"
+#include "code\__DEFINES\screentips.dm"
#include "code\__DEFINES\security.dm"
#include "code\__DEFINES\shuttles.dm"
#include "code\__DEFINES\sight.dm"
#include "code\__DEFINES\sound.dm"
+#include "code\__DEFINES\space.dm"
#include "code\__DEFINES\spaceman_dmm.dm"
#include "code\__DEFINES\span.dm"
#include "code\__DEFINES\speech_channels.dm"
@@ -143,18 +161,32 @@
#include "code\__DEFINES\wall_dents.dm"
#include "code\__DEFINES\wires.dm"
#include "code\__DEFINES\wounds.dm"
+#include "code\__DEFINES\construction\actions.dm"
+#include "code\__DEFINES\construction\material.dm"
+#include "code\__DEFINES\construction\rcd.dm"
+#include "code\__DEFINES\construction\structures.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
+#include "code\__DEFINES\dcs\signals\mapping.dm"
#include "code\__DEFINES\dcs\signals\signal_species.dm"
#include "code\__DEFINES\dcs\signals\signals_action.dm"
#include "code\__DEFINES\dcs\signals\signals_area.dm"
+#include "code\__DEFINES\dcs\signals\signals_beam.dm"
+#include "code\__DEFINES\dcs\signals\signals_camera.dm"
+#include "code\__DEFINES\dcs\signals\signals_client.dm"
#include "code\__DEFINES\dcs\signals\signals_datum.dm"
#include "code\__DEFINES\dcs\signals\signals_food.dm"
#include "code\__DEFINES\dcs\signals\signals_gib.dm"
#include "code\__DEFINES\dcs\signals\signals_global.dm"
#include "code\__DEFINES\dcs\signals\signals_global_object.dm"
#include "code\__DEFINES\dcs\signals\signals_heretic.dm"
+#include "code\__DEFINES\dcs\signals\signals_huds.dm"
#include "code\__DEFINES\dcs\signals\signals_janitor.dm"
+#include "code\__DEFINES\dcs\signals\signals_ladder.dm"
+#include "code\__DEFINES\dcs\signals\signals_lazy_templates.dm"
+#include "code\__DEFINES\dcs\signals\signals_mind.dm"
+#include "code\__DEFINES\dcs\signals\signals_mining.dm"
+#include "code\__DEFINES\dcs\signals\signals_modular_computer.dm"
#include "code\__DEFINES\dcs\signals\signals_mood.dm"
#include "code\__DEFINES\dcs\signals\signals_moveloop.dm"
#include "code\__DEFINES\dcs\signals\signals_movetype.dm"
@@ -179,10 +211,17 @@
#include "code\__DEFINES\dcs\signals\signals_mob\signals_human.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_living.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_spawner.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_silicon.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_simplemob.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_tools.dm"
#include "code\__DEFINES\dcs\signals\signals_mob\signals_xenobiology.dm"
+#include "code\__DEFINES\traits\declarations.dm"
+#include "code\__DEFINES\traits\sources.dm"
+#include "code\__DEFINES\{dripstation_defines}\blackmarket.dm"
+#include "code\__DEFINES\{dripstation_defines}\cargo.dm"
+#include "code\__DEFINES\{dripstation_defines}\dcs\signals\signals_transform.dm"
+#include "code\__DEFINES\{dripstation_defines}\traits.dm"
#include "code\__DEFINES\{yogs_defines}\admin.dm"
#include "code\__DEFINES\{yogs_defines}\antagonists.dm"
#include "code\__DEFINES\{yogs_defines}\atmospherics.dm"
@@ -190,7 +229,10 @@
#include "code\__DEFINES\{yogs_defines}\DNA.dm"
#include "code\__DEFINES\{yogs_defines}\flavor_misc.dm"
#include "code\__DEFINES\{yogs_defines}\is_helpers.dm"
+#include "code\__DEFINES\{yogs_defines}\jungle.dm"
+#include "code\__DEFINES\{yogs_defines}\layers.dm"
#include "code\__DEFINES\{yogs_defines}\logging.dm"
+#include "code\__DEFINES\{yogs_defines}\maps.dm"
#include "code\__DEFINES\{yogs_defines}\mentor.dm"
#include "code\__DEFINES\{yogs_defines}\misc.dm"
#include "code\__DEFINES\{yogs_defines}\mobs.dm"
@@ -200,14 +242,17 @@
#include "code\__DEFINES\{yogs_defines}\spacepods.dm"
#include "code\__DEFINES\{yogs_defines}\status_effects.dm"
#include "code\__DEFINES\{yogs_defines}\telecomms.dm"
+#include "code\__DEFINES\{yogs_defines}\traits.dm"
#include "code\__DEFINES\{yogs_defines}\wires.dm"
#include "code\__HELPERS\_extools_api.dm"
#include "code\__HELPERS\_lists.dm"
-#include "code\__HELPERS\_logging.dm"
+#include "code\__HELPERS\_planes.dm"
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\animations.dm"
#include "code\__HELPERS\areas.dm"
#include "code\__HELPERS\AStar.dm"
+#include "code\__HELPERS\bitflag_lists.dm"
+#include "code\__HELPERS\cameras.dm"
#include "code\__HELPERS\cmp.dm"
#include "code\__HELPERS\colors.dm"
#include "code\__HELPERS\config.dm"
@@ -222,13 +267,16 @@
#include "code\__HELPERS\heap.dm"
#include "code\__HELPERS\icon_smoothing.dm"
#include "code\__HELPERS\icons.dm"
+#include "code\__HELPERS\lazy_templates.dm"
#include "code\__HELPERS\level_traits.dm"
+#include "code\__HELPERS\lighting.dm"
#include "code\__HELPERS\maths.dm"
#include "code\__HELPERS\matrices.dm"
#include "code\__HELPERS\mobs.dm"
#include "code\__HELPERS\mouse_control.dm"
#include "code\__HELPERS\nameof.dm"
#include "code\__HELPERS\names.dm"
+#include "code\__HELPERS\piping_colors_lists.dm"
#include "code\__HELPERS\priority_announce.dm"
#include "code\__HELPERS\pronouns.dm"
#include "code\__HELPERS\radiation.dm"
@@ -240,15 +288,22 @@
#include "code\__HELPERS\screen_objs.dm"
#include "code\__HELPERS\shell.dm"
#include "code\__HELPERS\stat_tracking.dm"
+#include "code\__HELPERS\string_assoc_lists.dm"
+#include "code\__HELPERS\string_lists.dm"
#include "code\__HELPERS\text.dm"
#include "code\__HELPERS\time.dm"
#include "code\__HELPERS\traits.dm"
+#include "code\__HELPERS\turfs.dm"
#include "code\__HELPERS\type2type.dm"
#include "code\__HELPERS\typelists.dm"
#include "code\__HELPERS\unsorted.dm"
#include "code\__HELPERS\verbs.dm"
#include "code\__HELPERS\view.dm"
#include "code\__HELPERS\weakref.dm"
+#include "code\__HELPERS\logging\_logging.dm"
+#include "code\__HELPERS\logging\attack.dm"
+#include "code\__HELPERS\logging\shuttle.dm"
+#include "code\__HELPERS\logging\talk.dm"
#include "code\__HELPERS\sorts\__main.dm"
#include "code\__HELPERS\sorts\InsertSort.dm"
#include "code\__HELPERS\sorts\MergeSort.dm"
@@ -261,10 +316,10 @@
#include "code\_globalvars\misc.dm"
#include "code\_globalvars\regexes.dm"
#include "code\_globalvars\religion.dm"
-#include "code\_globalvars\traits.dm"
#include "code\_globalvars\lists\ambience.dm"
#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
+#include "code\_globalvars\lists\icons.dm"
#include "code\_globalvars\lists\keybindings.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
#include "code\_globalvars\lists\mapping.dm"
@@ -274,6 +329,8 @@
#include "code\_globalvars\lists\objects.dm"
#include "code\_globalvars\lists\poll_ignore.dm"
#include "code\_globalvars\lists\typecache.dm"
+#include "code\_globalvars\traits\_traits.dm"
+#include "code\_globalvars\traits\admin_tooling.dm"
#include "code\_js\byjax.dm"
#include "code\_js\menus.dm"
#include "code\_onclick\adjacent.dm"
@@ -308,23 +365,30 @@
#include "code\_onclick\hud\lavaland_elite.dm"
#include "code\_onclick\hud\living.dm"
#include "code\_onclick\hud\map_popups.dm"
+#include "code\_onclick\hud\map_view.dm"
#include "code\_onclick\hud\monkey.dm"
#include "code\_onclick\hud\movable_screen_objects.dm"
#include "code\_onclick\hud\pai.dm"
#include "code\_onclick\hud\parallax.dm"
#include "code\_onclick\hud\picture_in_picture.dm"
-#include "code\_onclick\hud\plane_master.dm"
#include "code\_onclick\hud\radial.dm"
#include "code\_onclick\hud\radial_persistent.dm"
+#include "code\_onclick\hud\random_layer.dm"
#include "code\_onclick\hud\revenanthud.dm"
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\slime.dm"
#include "code\_onclick\hud\swarmer.dm"
+#include "code\_onclick\hud\rendering\plane_master_controller.dm"
+#include "code\_onclick\hud\rendering\plane_master_group.dm"
+#include "code\_onclick\hud\rendering\render_plate.dm"
+#include "code\_onclick\hud\rendering\plane_masters\_plane_master.dm"
+#include "code\_onclick\hud\rendering\plane_masters\plane_master_subtypes.dm"
#include "code\controllers\admin.dm"
#include "code\controllers\controller.dm"
#include "code\controllers\failsafe.dm"
#include "code\controllers\globals.dm"
+#include "code\controllers\lag_switch.dm"
#include "code\controllers\master.dm"
#include "code\controllers\subsystem.dm"
#include "code\controllers\configuration\config_entry.dm"
@@ -337,7 +401,6 @@
#include "code\controllers\subsystem\achievements.dm"
#include "code\controllers\subsystem\acid.dm"
#include "code\controllers\subsystem\air.dm"
-#include "code\controllers\subsystem\air_machinery.dm"
#include "code\controllers\subsystem\ambience.dm"
#include "code\controllers\subsystem\area_contents.dm"
#include "code\controllers\subsystem\asset_loading.dm"
@@ -363,6 +426,7 @@
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
#include "code\controllers\subsystem\idlenpcpool.dm"
+#include "code\controllers\subsystem\init_profiler.dm"
#include "code\controllers\subsystem\input.dm"
#include "code\controllers\subsystem\ipintel.dm"
#include "code\controllers\subsystem\job.dm"
@@ -383,6 +447,7 @@
#include "code\controllers\subsystem\pathfinder.dm"
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\persistent_paintings.dm"
+#include "code\controllers\subsystem\profiler.dm"
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\radio.dm"
#include "code\controllers\subsystem\research.dm"
@@ -423,8 +488,10 @@
#include "code\datums\ai_laws.dm"
#include "code\datums\armor.dm"
#include "code\datums\beam.dm"
+#include "code\datums\blood_types.dm"
#include "code\datums\browser.dm"
#include "code\datums\callback.dm"
+#include "code\datums\chat_payload.dm"
#include "code\datums\chatmessage.dm"
#include "code\datums\cinematic.dm"
#include "code\datums\dash_weapon.dm"
@@ -441,6 +508,7 @@
#include "code\datums\holocall.dm"
#include "code\datums\http.dm"
#include "code\datums\hud.dm"
+#include "code\datums\lazy_template.dm"
#include "code\datums\map_config.dm"
#include "code\datums\martial.dm"
#include "code\datums\mind.dm"
@@ -455,11 +523,12 @@
#include "code\datums\recipe.dm"
#include "code\datums\ruins.dm"
#include "code\datums\saymode.dm"
-#include "code\datums\shuttles.dm"
+#include "code\datums\signals.dm"
#include "code\datums\soullink.dm"
#include "code\datums\spawners_menu.dm"
#include "code\datums\verbs.dm"
#include "code\datums\view.dm"
+#include "code\datums\visual_data.dm"
#include "code\datums\voice_announcements.dm"
#include "code\datums\weakrefs.dm"
#include "code\datums\world_topic.dm"
@@ -479,6 +548,7 @@
#include "code\datums\actions\items\stealth_box.dm"
#include "code\datums\actions\items\toggles.dm"
#include "code\datums\actions\items\vortex_recall.dm"
+#include "code\datums\actions\mobs\adjust_vision.dm"
#include "code\datums\actions\mobs\language_menu.dm"
#include "code\datums\actions\mobs\ninja.dm"
#include "code\datums\actions\mobs\small_sprite.dm"
@@ -500,18 +570,20 @@
#include "code\datums\components\_component.dm"
#include "code\datums\components\action_item_overlay.dm"
#include "code\datums\components\anti_magic.dm"
-#include "code\datums\components\archaeology.dm"
#include "code\datums\components\armor_plate.dm"
#include "code\datums\components\art.dm"
#include "code\datums\components\bakeable.dm"
#include "code\datums\components\bane.dm"
#include "code\datums\components\beetlejuice.dm"
+#include "code\datums\components\bloodysoles.dm"
#include "code\datums\components\butchering.dm"
#include "code\datums\components\caltrop.dm"
#include "code\datums\components\chasm.dm"
+#include "code\datums\components\connect_containers.dm"
+#include "code\datums\components\connect_loc_behalf.dm"
+#include "code\datums\components\connect_mob_behalf.dm"
#include "code\datums\components\construction.dm"
#include "code\datums\components\curse_of_hunger.dm"
-#include "code\datums\components\decal.dm"
#include "code\datums\components\earhealing.dm"
#include "code\datums\components\earprotection.dm"
#include "code\datums\components\echolocation.dm"
@@ -519,10 +591,12 @@
#include "code\datums\components\explodable.dm"
#include "code\datums\components\forced_gravity.dm"
#include "code\datums\components\forensics.dm"
+#include "code\datums\components\gps.dm"
#include "code\datums\components\grillable.dm"
#include "code\datums\components\gunpoint.dm"
#include "code\datums\components\heal_react.dm"
#include "code\datums\components\heirloom.dm"
+#include "code\datums\components\hide_highest_offset.dm"
#include "code\datums\components\hot_ice.dm"
#include "code\datums\components\ice_walk.dm"
#include "code\datums\components\igniter.dm"
@@ -560,6 +634,7 @@
#include "code\datums\components\spill.dm"
#include "code\datums\components\spooky.dm"
#include "code\datums\components\squeak.dm"
+#include "code\datums\components\squishable.dm"
#include "code\datums\components\stationloving.dm"
#include "code\datums\components\summoning.dm"
#include "code\datums\components\surgery_bed.dm"
@@ -570,6 +645,7 @@
#include "code\datums\components\twohanded.dm"
#include "code\datums\components\uplink.dm"
#include "code\datums\components\waddling.dm"
+#include "code\datums\components\wall_mounted.dm"
#include "code\datums\components\wearertargeting.dm"
#include "code\datums\components\wet_floor.dm"
#include "code\datums\components\crafting\antag.dm"
@@ -579,7 +655,6 @@
#include "code\datums\components\crafting\recipes.dm"
#include "code\datums\components\crafting\tailoring.dm"
#include "code\datums\components\crafting\weapons.dm"
-#include "code\datums\components\decals\blood.dm"
#include "code\datums\components\fantasy\_fantasy.dm"
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
@@ -673,8 +748,13 @@
#include "code\datums\elements\movetype_handler.dm"
#include "code\datums\elements\regenerator.dm"
#include "code\datums\elements\rust.dm"
+#include "code\datums\elements\speech_bubble_override.dm"
#include "code\datums\elements\squish.dm"
+#include "code\datums\elements\turf_transparency.dm"
+#include "code\datums\elements\undertile.dm"
#include "code\datums\elements\update_icon_blocker.dm"
+#include "code\datums\elements\decals\_decal.dm"
+#include "code\datums\elements\decals\blood.dm"
#include "code\datums\helper_datums\events.dm"
#include "code\datums\helper_datums\getrev.dm"
#include "code\datums\helper_datums\icon_snapshot.dm"
@@ -701,6 +781,7 @@
#include "code\datums\looping_sounds\weather.dm"
#include "code\datums\mapgen\_MapGenerator.dm"
#include "code\datums\mapgen\CaveGenerator.dm"
+#include "code\datums\mapgen\biomes\_biome.dm"
#include "code\datums\mapgen\Cavegens\IcemoonCaves.dm"
#include "code\datums\mapgen\Cavegens\LavalandGenerator.dm"
#include "code\datums\martial\boxing.dm"
@@ -714,6 +795,7 @@
#include "code\datums\martial\mushpunch.dm"
#include "code\datums\martial\plasma_fist.dm"
#include "code\datums\martial\psychotic_brawl.dm"
+#include "code\datums\martial\reverbpalm.dm"
#include "code\datums\martial\sleeping_carp.dm"
#include "code\datums\martial\ultra_violence.dm"
#include "code\datums\martial\worldbreaker.dm"
@@ -754,6 +836,8 @@
#include "code\datums\ruins\icemoon.dm"
#include "code\datums\ruins\lavaland.dm"
#include "code\datums\ruins\space.dm"
+#include "code\datums\shuttles\_shuttles.dm"
+#include "code\datums\shuttles\emergency.dm"
#include "code\datums\station_traits\_station_trait.dm"
#include "code\datums\station_traits\negative_traits.dm"
#include "code\datums\station_traits\neutral_traits.dm"
@@ -775,6 +859,7 @@
#include "code\datums\status_effects\debuffs\jitteriness.dm"
#include "code\datums\status_effects\debuffs\knuckleroot.dm"
#include "code\datums\status_effects\debuffs\red_eye.dm"
+#include "code\datums\status_effects\debuffs\screen_blur.dm"
#include "code\datums\status_effects\debuffs\silenced.dm"
#include "code\datums\status_effects\debuffs\speech_debuffs.dm"
#include "code\datums\traits\_quirk.dm"
@@ -797,6 +882,7 @@
#include "code\datums\wires\emitter.dm"
#include "code\datums\wires\explosive.dm"
#include "code\datums\wires\igniter.dm"
+#include "code\datums\wires\mass_driver.dm"
#include "code\datums\wires\mech_fabricator.dm"
#include "code\datums\wires\microwave.dm"
#include "code\datums\wires\mulebot.dm"
@@ -816,7 +902,6 @@
#include "code\datums\wounds\slash.dm"
#include "code\datums\wounds\scars\_scars.dm"
#include "code\game\alternate_appearance.dm"
-#include "code\game\atoms.dm"
#include "code\game\atoms_movable.dm"
#include "code\game\communications.dm"
#include "code\game\data_huds.dm"
@@ -838,6 +923,13 @@
#include "code\game\area\areas\ruins\lavaland.dm"
#include "code\game\area\areas\ruins\space.dm"
#include "code\game\area\areas\ruins\templates.dm"
+#include "code\game\atom\_atom.dm"
+#include "code\game\atom\atom_appearance.dm"
+#include "code\game\atom\atom_initializing_EXPENSIVE.dm"
+#include "code\game\atom\atom_invisibility.dm"
+#include "code\game\atom\atom_materials.dm"
+#include "code\game\atom\atom_orbit.dm"
+#include "code\game\atom\atom_tool_acts.dm"
#include "code\game\gamemodes\events.dm"
#include "code\game\gamemodes\game_mode.dm"
#include "code\game\gamemodes\objective.dm"
@@ -891,7 +983,7 @@
#include "code\game\machinery\aug_manipulator.dm"
#include "code\game\machinery\autolathe.dm"
#include "code\game\machinery\bank_machine.dm"
-#include "code\game\machinery\Beacon.dm"
+#include "code\game\machinery\barsigns.dm"
#include "code\game\machinery\bounty_board.dm"
#include "code\game\machinery\buttons.dm"
#include "code\game\machinery\cell_charger.dm"
@@ -911,6 +1003,7 @@
#include "code\game\machinery\fat_sucker.dm"
#include "code\game\machinery\firealarm.dm"
#include "code\game\machinery\flasher.dm"
+#include "code\game\machinery\gigabeacon.dm"
#include "code\game\machinery\gulag_item_reclaimer.dm"
#include "code\game\machinery\gulag_processor.dm"
#include "code\game\machinery\gulag_teleporter.dm"
@@ -1068,6 +1161,7 @@
#include "code\game\objects\effects\bump_teleporter.dm"
#include "code\game\objects\effects\contraband.dm"
#include "code\game\objects\effects\countdown.dm"
+#include "code\game\objects\effects\cursor_catcher.dm"
#include "code\game\objects\effects\custom_portals.dm"
#include "code\game\objects\effects\echo.dm"
#include "code\game\objects\effects\effects.dm"
@@ -1114,6 +1208,7 @@
#include "code\game\objects\effects\spawners\lootdrop.dm"
#include "code\game\objects\effects\spawners\minor_variation.dm"
#include "code\game\objects\effects\spawners\minor_variation_templates.dm"
+#include "code\game\objects\effects\spawners\mystery_box.dm"
#include "code\game\objects\effects\spawners\structure.dm"
#include "code\game\objects\effects\spawners\traps.dm"
#include "code\game\objects\effects\spawners\vaultspawner.dm"
@@ -1250,6 +1345,8 @@
#include "code\game\objects\items\devices\transfer_valve.dm"
#include "code\game\objects\items\devices\busterarm\_buster.dm"
#include "code\game\objects\items\devices\busterarm\buster_limb.dm"
+#include "code\game\objects\items\devices\busterarm\busterharpoon.dm"
+#include "code\game\objects\items\devices\busterarm\gasharpoon.dm"
#include "code\game\objects\items\devices\busterarm\megabuster.dm"
#include "code\game\objects\items\devices\busterarm\wire_snatch.dm"
#include "code\game\objects\items\devices\PDA\cart.dm"
@@ -1393,7 +1490,6 @@
#include "code\game\objects\items\two_handed\spears.dm"
#include "code\game\objects\structures\aliens.dm"
#include "code\game\objects\structures\artstuff.dm"
-#include "code\game\objects\structures\barsigns.dm"
#include "code\game\objects\structures\bedsheet_bin.dm"
#include "code\game\objects\structures\catwalk.dm"
#include "code\game\objects\structures\crateshelf.dm"
@@ -1506,35 +1602,40 @@
#include "code\game\objects\structures\transit_tubes\transit_tube_construction.dm"
#include "code\game\objects\structures\transit_tubes\transit_tube_pod.dm"
#include "code\game\turfs\baseturf_skipover.dm"
+#include "code\game\turfs\baseturfs.dm"
#include "code\game\turfs\change_turf.dm"
-#include "code\game\turfs\closed.dm"
-#include "code\game\turfs\open.dm"
#include "code\game\turfs\turf.dm"
-#include "code\game\turfs\openspace\openspace.dm"
-#include "code\game\turfs\simulated\chasm.dm"
-#include "code\game\turfs\simulated\dirtystation.dm"
-#include "code\game\turfs\simulated\floor.dm"
-#include "code\game\turfs\simulated\lava.dm"
-#include "code\game\turfs\simulated\minerals.dm"
-#include "code\game\turfs\simulated\reebe_void.dm"
-#include "code\game\turfs\simulated\river.dm"
-#include "code\game\turfs\simulated\walls.dm"
-#include "code\game\turfs\simulated\water.dm"
-#include "code\game\turfs\simulated\floor\fancy_floor.dm"
-#include "code\game\turfs\simulated\floor\light_floor.dm"
-#include "code\game\turfs\simulated\floor\mineral_floor.dm"
-#include "code\game\turfs\simulated\floor\misc_floor.dm"
-#include "code\game\turfs\simulated\floor\plasteel_floor.dm"
-#include "code\game\turfs\simulated\floor\plating.dm"
-#include "code\game\turfs\simulated\floor\reinf_floor.dm"
-#include "code\game\turfs\simulated\floor\plating\asteroid.dm"
-#include "code\game\turfs\simulated\floor\plating\dirt.dm"
-#include "code\game\turfs\simulated\floor\plating\misc_plating.dm"
-#include "code\game\turfs\simulated\wall\mineral_walls.dm"
-#include "code\game\turfs\simulated\wall\misc_walls.dm"
-#include "code\game\turfs\simulated\wall\reinf_walls.dm"
-#include "code\game\turfs\space\space.dm"
-#include "code\game\turfs\space\transit.dm"
+#include "code\game\turfs\closed\_closed.dm"
+#include "code\game\turfs\closed\indestructible.dm"
+#include "code\game\turfs\closed\minerals.dm"
+#include "code\game\turfs\closed\walls.dm"
+#include "code\game\turfs\closed\wall\material_walls.dm"
+#include "code\game\turfs\closed\wall\mineral_walls.dm"
+#include "code\game\turfs\closed\wall\misc_walls.dm"
+#include "code\game\turfs\closed\wall\reinf_walls.dm"
+#include "code\game\turfs\open\_open.dm"
+#include "code\game\turfs\open\chasm.dm"
+#include "code\game\turfs\open\dirtystation.dm"
+#include "code\game\turfs\open\floor.dm"
+#include "code\game\turfs\open\lava.dm"
+#include "code\game\turfs\open\openspace.dm"
+#include "code\game\turfs\open\reebe_void.dm"
+#include "code\game\turfs\open\river.dm"
+#include "code\game\turfs\open\water.dm"
+#include "code\game\turfs\open\floor\fancy_floor.dm"
+#include "code\game\turfs\open\floor\hull.dm"
+#include "code\game\turfs\open\floor\light_floor.dm"
+#include "code\game\turfs\open\floor\mineral_floor.dm"
+#include "code\game\turfs\open\floor\misc_floor.dm"
+#include "code\game\turfs\open\floor\plasteel_floor.dm"
+#include "code\game\turfs\open\floor\plating.dm"
+#include "code\game\turfs\open\floor\reinf_floor.dm"
+#include "code\game\turfs\open\floor\plating\asteroid.dm"
+#include "code\game\turfs\open\floor\plating\dirt.dm"
+#include "code\game\turfs\open\floor\plating\misc_plating.dm"
+#include "code\game\turfs\open\space\space.dm"
+#include "code\game\turfs\open\space\space_EXPENSIVE.dm"
+#include "code\game\turfs\open\space\transit.dm"
#include "code\modules\admin\admin.dm"
#include "code\modules\admin\admin_investigate.dm"
#include "code\modules\admin\admin_verbs.dm"
@@ -1594,6 +1695,7 @@
#include "code\modules\admin\verbs\one_click_antag.dm"
#include "code\modules\admin\verbs\onlyone.dm"
#include "code\modules\admin\verbs\panicbunker.dm"
+#include "code\modules\admin\verbs\plane_debugger.dm"
#include "code\modules\admin\verbs\playsound.dm"
#include "code\modules\admin\verbs\possess.dm"
#include "code\modules\admin\verbs\pray.dm"
@@ -1605,7 +1707,9 @@
#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm"
#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm"
#include "code\modules\admin\verbs\SDQL2\SDQL_2_wrappers.dm"
+#include "code\modules\admin\view_variables\color_matrix_editor.dm"
#include "code\modules\admin\view_variables\debug_variables.dm"
+#include "code\modules\admin\view_variables\filterrific.dm"
#include "code\modules\admin\view_variables\mark_datum.dm"
#include "code\modules\admin\view_variables\massmodvar.dm"
#include "code\modules\admin\view_variables\modifyvariables.dm"
@@ -1776,13 +1880,13 @@
#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_drivers.dm"
#include "code\modules\antagonists\clockcult\clock_scriptures\scripture_scripts.dm"
#include "code\modules\antagonists\clockcult\clock_structures\_trap_object.dm"
-#include "code\modules\antagonists\clockcult\clock_structures\ark_of_the_clockwork_justicar.dm"
+#include "code\modules\antagonists\clockcult\clock_structures\ark_of_the_clockwork_justiciar.dm"
#include "code\modules\antagonists\clockcult\clock_structures\clockwork_obelisk.dm"
#include "code\modules\antagonists\clockcult\clock_structures\eminence_spire.dm"
#include "code\modules\antagonists\clockcult\clock_structures\heralds_beacon.dm"
#include "code\modules\antagonists\clockcult\clock_structures\mania_motor.dm"
#include "code\modules\antagonists\clockcult\clock_structures\ocular_warden.dm"
-#include "code\modules\antagonists\clockcult\clock_structures\ratvar_the_clockwork_justicar.dm"
+#include "code\modules\antagonists\clockcult\clock_structures\ratvar_the_clockwork_justiciar.dm"
#include "code\modules\antagonists\clockcult\clock_structures\stargazer.dm"
#include "code\modules\antagonists\clockcult\clock_structures\taunting_trail.dm"
#include "code\modules\antagonists\clockcult\clock_structures\wall_gear.dm"
@@ -1953,6 +2057,7 @@
#include "code\modules\asset_cache\asset_list_items.dm"
#include "code\modules\asset_cache\assets\contracts.dm"
#include "code\modules\asset_cache\assets\crafting.dm"
+#include "code\modules\asset_cache\assets\plane_debug.dm"
#include "code\modules\asset_cache\transports\asset_transport.dm"
#include "code\modules\asset_cache\transports\webroot_transport.dm"
#include "code\modules\atmospherics\auxgm\breathing_classes.dm"
@@ -2005,6 +2110,7 @@
#include "code\modules\atmospherics\machinery\pipes\manifold.dm"
#include "code\modules\atmospherics\machinery\pipes\manifold4w.dm"
#include "code\modules\atmospherics\machinery\pipes\mapping.dm"
+#include "code\modules\atmospherics\machinery\pipes\multiz.dm"
#include "code\modules\atmospherics\machinery\pipes\pipes.dm"
#include "code\modules\atmospherics\machinery\pipes\simple.dm"
#include "code\modules\atmospherics\machinery\pipes\heat_exchange\he_pipes.dm"
@@ -2118,8 +2224,8 @@
#include "code\modules\client\preferences\assets.dm"
#include "code\modules\client\preferences\auto_fit_viewport.dm"
#include "code\modules\client\preferences\balloon_alerts.dm"
-#include "code\modules\client\preferences\clerk_choice.dm"
#include "code\modules\client\preferences\chapel_choice.dm"
+#include "code\modules\client\preferences\clerk_choice.dm"
#include "code\modules\client\preferences\clothing.dm"
#include "code\modules\client\preferences\credits.dm"
#include "code\modules\client\preferences\donor.dm"
@@ -2134,6 +2240,8 @@
#include "code\modules\client\preferences\jobless_role.dm"
#include "code\modules\client\preferences\mood.dm"
#include "code\modules\client\preferences\mood_enabling.dm"
+#include "code\modules\client\preferences\multiz_parallax.dm"
+#include "code\modules\client\preferences\multiz_performance.dm"
#include "code\modules\client\preferences\names.dm"
#include "code\modules\client\preferences\ooc.dm"
#include "code\modules\client\preferences\parallax.dm"
@@ -2244,6 +2352,7 @@
#include "code\modules\clothing\suits\reactive_armour.dm"
#include "code\modules\clothing\suits\toggles.dm"
#include "code\modules\clothing\suits\utility.dm"
+#include "code\modules\clothing\suits\wintercoat.dm"
#include "code\modules\clothing\suits\wiz_robe.dm"
#include "code\modules\clothing\under\_under.dm"
#include "code\modules\clothing\under\accessories.dm"
@@ -2445,6 +2554,7 @@
#include "code\modules\holodeck\area_copy.dm"
#include "code\modules\holodeck\computer.dm"
#include "code\modules\holodeck\holo_effect.dm"
+#include "code\modules\holodeck\holodeck_map_templates.dm"
#include "code\modules\holodeck\items.dm"
#include "code\modules\holodeck\mobs.dm"
#include "code\modules\holodeck\turfs.dm"
@@ -2553,6 +2663,7 @@
#include "code\modules\jobs\job_types\security_officer.dm"
#include "code\modules\jobs\job_types\shaft_miner.dm"
#include "code\modules\jobs\job_types\station_engineer.dm"
+#include "code\modules\jobs\job_types\unassigned.dm"
#include "code\modules\jobs\job_types\virologist.dm"
#include "code\modules\jobs\job_types\warden.dm"
#include "code\modules\keybindings\bindings_atom.dm"
@@ -2594,7 +2705,6 @@
#include "code\modules\library\lib_machines.dm"
#include "code\modules\library\random_books.dm"
#include "code\modules\library\soapstone.dm"
-#include "code\modules\lighting\emissive_blocker.dm"
#include "code\modules\lighting\lighting_area.dm"
#include "code\modules\lighting\lighting_atom.dm"
#include "code\modules\lighting\lighting_corner.dm"
@@ -2602,6 +2712,7 @@
#include "code\modules\lighting\lighting_setup.dm"
#include "code\modules\lighting\lighting_source.dm"
#include "code\modules\lighting\lighting_turf.dm"
+#include "code\modules\lighting\static_lighting_area.dm"
#include "code\modules\mapping\map_template.dm"
#include "code\modules\mapping\mapping_helpers.dm"
#include "code\modules\mapping\minimap.dm"
@@ -2677,11 +2788,11 @@
#include "code\modules\mob\dead\new_player\poll.dm"
#include "code\modules\mob\dead\new_player\preferences_setup.dm"
#include "code\modules\mob\dead\new_player\sprite_accessories.dm"
+#include "code\modules\mob\dead\observer\jumpto.dm"
#include "code\modules\mob\dead\observer\login.dm"
#include "code\modules\mob\dead\observer\logout.dm"
#include "code\modules\mob\dead\observer\notificationprefs.dm"
#include "code\modules\mob\dead\observer\observer.dm"
-#include "code\modules\mob\dead\observer\observer_movement.dm"
#include "code\modules\mob\dead\observer\orbit.dm"
#include "code\modules\mob\dead\observer\say.dm"
#include "code\modules\mob\living\blood.dm"
@@ -2776,7 +2887,6 @@
#include "code\modules\mob\living\carbon\human\update_icons.dm"
#include "code\modules\mob\living\carbon\human\species_types\abductors.dm"
#include "code\modules\mob\living\carbon\human\species_types\android.dm"
-#include "code\modules\mob\living\carbon\human\species_types\corporate.dm"
#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm"
#include "code\modules\mob\living\carbon\human\species_types\eggpeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\ethereal.dm"
@@ -2887,7 +2997,6 @@
#include "code\modules\mob\living\simple_animal\parrot.dm"
#include "code\modules\mob\living\simple_animal\shade.dm"
#include "code\modules\mob\living\simple_animal\simple_animal.dm"
-#include "code\modules\mob\living\simple_animal\status_procs.dm"
#include "code\modules\mob\living\simple_animal\bot\atmosbot.dm"
#include "code\modules\mob\living\simple_animal\bot\bot.dm"
#include "code\modules\mob\living\simple_animal\bot\cleanbot.dm"
@@ -2979,6 +3088,7 @@
#include "code\modules\mob\living\simple_animal\hostile\jungle\mega_arachnid.dm"
#include "code\modules\mob\living\simple_animal\hostile\jungle\mook.dm"
#include "code\modules\mob\living\simple_animal\hostile\jungle\seedling.dm"
+#include "code\modules\mob\living\simple_animal\hostile\megafauna\_megafauna.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\blood_drunk_miner.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\bubblegum.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\colossus.dm"
@@ -2986,9 +3096,9 @@
#include "code\modules\mob\living\simple_animal\hostile\megafauna\drake.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\hierophant.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\legion.dm"
-#include "code\modules\mob\living\simple_animal\hostile\megafauna\megafauna.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\stalwart.dm"
#include "code\modules\mob\living\simple_animal\hostile\megafauna\swarmer.dm"
+#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\ambusher.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\basilisk.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\curse_blob.dm"
#include "code\modules\mob\living\simple_animal\hostile\mining_mobs\drakeling.dm"
@@ -3052,7 +3162,6 @@
#include "code\modules\modular_computers\file_system\programs\file_browser.dm"
#include "code\modules\modular_computers\file_system\programs\minesweeper.dm"
#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm"
-#include "code\modules\modular_computers\file_system\programs\ntmonitor.dm"
#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm"
#include "code\modules\modular_computers\file_system\programs\ntpda_msg.dm"
#include "code\modules\modular_computers\file_system\programs\paperworkprinter.dm"
@@ -3072,6 +3181,7 @@
#include "code\modules\modular_computers\file_system\programs\engineering\alarm.dm"
#include "code\modules\modular_computers\file_system\programs\engineering\atmosscan.dm"
#include "code\modules\modular_computers\file_system\programs\engineering\energyharvestercontrol.dm"
+#include "code\modules\modular_computers\file_system\programs\engineering\ntmonitor.dm"
#include "code\modules\modular_computers\file_system\programs\engineering\powermonitor.dm"
#include "code\modules\modular_computers\file_system\programs\engineering\reactor_monitor.dm"
#include "code\modules\modular_computers\file_system\programs\engineering\sm_monitor.dm"
@@ -3186,6 +3296,7 @@
#include "code\modules\power\gravitygenerator.dm"
#include "code\modules\power\lighting.dm"
#include "code\modules\power\monitor.dm"
+#include "code\modules\power\multiz.dm"
#include "code\modules\power\port_gen.dm"
#include "code\modules\power\power.dm"
#include "code\modules\power\powernet.dm"
@@ -3403,6 +3514,7 @@
#include "code\modules\recycling\disposal\construction.dm"
#include "code\modules\recycling\disposal\eject.dm"
#include "code\modules\recycling\disposal\holder.dm"
+#include "code\modules\recycling\disposal\multiz.dm"
#include "code\modules\recycling\disposal\outlet.dm"
#include "code\modules\recycling\disposal\pipe.dm"
#include "code\modules\recycling\disposal\pipe_sorting.dm"
@@ -3533,6 +3645,7 @@
#include "code\modules\shuttle\elevator.dm"
#include "code\modules\shuttle\emergency.dm"
#include "code\modules\shuttle\ferry.dm"
+#include "code\modules\shuttle\infiltrator.dm"
#include "code\modules\shuttle\manipulator.dm"
#include "code\modules\shuttle\monastery.dm"
#include "code\modules\shuttle\navigation_computer.dm"
@@ -3591,11 +3704,13 @@
#include "code\modules\spells\spell_types\jaunt\bloodcrawl.dm"
#include "code\modules\spells\spell_types\jaunt\ethereal_jaunt.dm"
#include "code\modules\spells\spell_types\jaunt\shadow_walk.dm"
+#include "code\modules\spells\spell_types\jaunt\tar_pool.dm"
#include "code\modules\spells\spell_types\jaunt\wirecrawl.dm"
#include "code\modules\spells\spell_types\list_target\_list_target.dm"
#include "code\modules\spells\spell_types\list_target\telepathy.dm"
#include "code\modules\spells\spell_types\pointed\_pointed.dm"
#include "code\modules\spells\spell_types\pointed\abyssal_gaze.dm"
+#include "code\modules\spells\spell_types\pointed\appendicitis.dm"
#include "code\modules\spells\spell_types\pointed\barnyard.dm"
#include "code\modules\spells\spell_types\pointed\blind.dm"
#include "code\modules\spells\spell_types\pointed\dominate.dm"
@@ -3614,7 +3729,6 @@
#include "code\modules\spells\spell_types\self\lichdom.dm"
#include "code\modules\spells\spell_types\self\mime_vow.dm"
#include "code\modules\spells\spell_types\self\mutate.dm"
-#include "code\modules\spells\spell_types\self\night_vision.dm"
#include "code\modules\spells\spell_types\self\personality_commune.dm"
#include "code\modules\spells\spell_types\self\rod_form.dm"
#include "code\modules\spells\spell_types\self\smoke.dm"
@@ -3736,6 +3850,7 @@
#include "code\modules\tgui\states\observer.dm"
#include "code\modules\tgui\states\permissions.dm"
#include "code\modules\tgui\states\physical.dm"
+#include "code\modules\tgui\states\pilot.dm"
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui_input\alert.dm"
@@ -3780,7 +3895,6 @@
#include "code\modules\vending\engineering.dm"
#include "code\modules\vending\engivend.dm"
#include "code\modules\vending\games.dm"
-#include "code\modules\vending\gift.dm"
#include "code\modules\vending\liberation.dm"
#include "code\modules\vending\liberation_toy.dm"
#include "code\modules\vending\magivend.dm"
@@ -3788,7 +3902,6 @@
#include "code\modules\vending\medical_wall.dm"
#include "code\modules\vending\megaseed.dm"
#include "code\modules\vending\modularpc.dm"
-#include "code\modules\vending\monkey_wall.dm"
#include "code\modules\vending\nutrimax.dm"
#include "code\modules\vending\plasmaresearch.dm"
#include "code\modules\vending\robotics.dm"
@@ -3800,10 +3913,12 @@
#include "code\modules\vending\toys.dm"
#include "code\modules\vending\wardrobes.dm"
#include "code\modules\vending\youtool.dm"
+#include "code\modules\visual\render_step.dm"
#include "code\modules\VR\vr_human.dm"
#include "code\modules\VR\vr_sleeper.dm"
#include "code\modules\zombie\items.dm"
#include "code\modules\zombie\organs.dm"
+#include "code\ze_genesis_call\genesis_call.dm"
#include "interface\interface.dm"
#include "interface\menu.dm"
#include "interface\stylesheet.dm"
@@ -3845,6 +3960,7 @@
#include "yogstation\code\datums\components\fishingbonus.dm"
#include "yogstation\code\datums\components\radioactive.dm"
#include "yogstation\code\datums\components\randomcrits.dm"
+#include "yogstation\code\datums\components\shielded.dm"
#include "yogstation\code\datums\components\uplink.dm"
#include "yogstation\code\datums\components\walks.dm"
#include "yogstation\code\datums\components\storage\storage.dm"
@@ -3854,13 +3970,17 @@
#include "yogstation\code\datums\diseases\advance\symptoms\confusion.dm"
#include "yogstation\code\datums\diseases\advance\symptoms\heal.dm"
#include "yogstation\code\datums\looping_sounds\darkspawn.dm"
+#include "yogstation\code\datums\mapgen\JungleGen.dm"
+#include "yogstation\code\datums\mapgen\biomes\JungleBiomes.dm"
#include "yogstation\code\datums\martial\explosive_fist.dm"
#include "yogstation\code\datums\martial\garden_warfare.dm"
#include "yogstation\code\datums\martial\stealth.dm"
+#include "yogstation\code\datums\mood_events\generic_negative_events.dm"
#include "yogstation\code\datums\mood_events\generic_positive_events.dm"
#include "yogstation\code\datums\mutations\alcohol.dm"
#include "yogstation\code\datums\mutations\extendoarm.dm"
#include "yogstation\code\datums\ruins\free_miners.dm"
+#include "yogstation\code\datums\ruins\jungle.dm"
#include "yogstation\code\datums\ruins\station.dm"
#include "yogstation\code\datums\status_effects\buffs.dm"
#include "yogstation\code\datums\status_effects\neutral.dm"
@@ -3874,6 +3994,7 @@
#include "yogstation\code\game\area\areas\centcom.dm"
#include "yogstation\code\game\area\areas\holodeck.dm"
#include "yogstation\code\game\area\areas\shuttles.dm"
+#include "yogstation\code\game\area\areas\ruins\jungleland.dm"
#include "yogstation\code\game\gamemodes\game_mode.dm"
#include "yogstation\code\game\gamemodes\objective.dm"
#include "yogstation\code\game\gamemodes\objective_items.dm"
@@ -4064,6 +4185,7 @@
#include "yogstation\code\modules\antagonists\infiltrator\items\ai_hijack.dm"
#include "yogstation\code\modules\antagonists\infiltrator\items\hardsuit.dm"
#include "yogstation\code\modules\antagonists\infiltrator\items\services.dm"
+#include "yogstation\code\modules\antagonists\ivymen\ivymen.dm"
#include "yogstation\code\modules\antagonists\nukeop\clownop.dm"
#include "yogstation\code\modules\antagonists\nukeop\nukeop.dm"
#include "yogstation\code\modules\antagonists\nukeop\equipment\nuclearbomb.dm"
@@ -4168,6 +4290,18 @@
#include "yogstation\code\modules\jobs\job_types\paramedic.dm"
#include "yogstation\code\modules\jobs\job_types\psychiatrist.dm"
#include "yogstation\code\modules\jobs\job_types\tourist.dm"
+#include "yogstation\code\modules\jungleland\ghost_role_spawners.dm"
+#include "yogstation\code\modules\jungleland\jungle_alpha_mobs.dm"
+#include "yogstation\code\modules\jungleland\jungle_datums.dm"
+#include "yogstation\code\modules\jungleland\jungle_items.dm"
+#include "yogstation\code\modules\jungleland\jungle_megafauna.dm"
+#include "yogstation\code\modules\jungleland\jungle_mobs.dm"
+#include "yogstation\code\modules\jungleland\jungle_projectiles.dm"
+#include "yogstation\code\modules\jungleland\jungle_status_effects.dm"
+#include "yogstation\code\modules\jungleland\jungle_structures.dm"
+#include "yogstation\code\modules\jungleland\jungle_turfs.dm"
+#include "yogstation\code\modules\jungleland\jungleland_crafting.dm"
+#include "yogstation\code\modules\jungleland\kinetic_javelin.dm"
#include "yogstation\code\modules\language\darkspeak.dm"
#include "yogstation\code\modules\language\japanese.dm"
#include "yogstation\code\modules\mentor\follow.dm"
@@ -4262,7 +4396,6 @@
#include "yogstation\code\modules\reagents\chemistry\reagents\food_reagents.dm"
#include "yogstation\code\modules\reagents\chemistry\reagents\other_reagents.dm"
#include "yogstation\code\modules\reagents\chemistry\recipes\slime_extracts.dm"
-#include "yogstation\code\modules\reagents\reagent_containers\blood_pack.dm"
#include "yogstation\code\modules\reagents\reagent_containers\bottle.dm"
#include "yogstation\code\modules\reagents\reagent_containers\gummies.dm"
#include "yogstation\code\modules\reagents\reagent_containers\hypospray.dm"
@@ -4279,6 +4412,7 @@
#include "yogstation\code\modules\research\designs\telecomms_designs.dm"
#include "yogstation\code\modules\research\designs\tool_designs.dm"
#include "yogstation\code\modules\research\techweb\all_nodes.dm"
+#include "yogstation\code\modules\ruins\ivymen_den.dm"
#include "yogstation\code\modules\ruins\lavaland_ruin_code.dm"
#include "yogstation\code\modules\scripting\client_verbs.dm"
#include "yogstation\code\modules\scripting\Errors.dm"
@@ -4310,7 +4444,6 @@
#include "yogstation\code\modules\spacepods\prebuilt.dm"
#include "yogstation\code\modules\spacepods\spacepod.dm"
#include "yogstation\code\modules\spells\spell_types\aoe_spell\slip.dm"
-#include "yogstation\code\modules\spells\spell_types\pointed\cluwnecurse.dm"
#include "yogstation\code\modules\spells\spell_types\projectile\animation.dm"
#include "yogstation\code\modules\spells\spell_types\self\cauterize.dm"
#include "yogstation\code\modules\spells\spell_types\shapeshift\mouse.dm"
@@ -4321,6 +4454,9 @@
#include "yogstation\code\modules\surgery\organs\shadowling_organs.dm"
#include "yogstation\code\modules\uplink\uplink_item.dm"
#include "yogstation\code\modules\vending\fishing.dm"
+#include "yogstation\code\modules\vending\gift.dm"
+#include "yogstation\code\modules\vending\hypomed.dm"
+#include "yogstation\code\modules\vending\nanogene.dm"
#include "yogstation\code\modules\webhook\webhook.dm"
#include "yogstation\code\modules\xenoarch\loot\gigadrill.dm"
#include "yogstation\code\modules\xenoarch\loot\guns.dm"
diff --git a/yogstation/code/_onclick/adjacent.dm b/yogstation/code/_onclick/adjacent.dm
index 3caab169f861..58db7eb48e07 100644
--- a/yogstation/code/_onclick/adjacent.dm
+++ b/yogstation/code/_onclick/adjacent.dm
@@ -18,21 +18,3 @@
return FALSE
return ..()
-
-/*
- Adjacency (to anything else):
- * Must be on a turf
-*/
-/atom/movable/Adjacent(atom/neighbor)
- if(neighbor == loc)
- return TRUE
- var/turf/T = loc
- if(!istype(T))
- return FALSE
- if((islist(locs) && locs.len > 1) && (bound_width != world.icon_size || bound_height != world.icon_size))
- for(var/turf/place in locs) //this is to handle multi tile objects
- if(place.Adjacent(neighbor, src, src))
- return TRUE
- else if(T.Adjacent(neighbor,target = neighbor, mover = src))
- return TRUE
- return FALSE
diff --git a/yogstation/code/controllers/configuration/entries/general.dm b/yogstation/code/controllers/configuration/entries/general.dm
index 9a77e2f54d48..b4b14b94d564 100644
--- a/yogstation/code/controllers/configuration/entries/general.dm
+++ b/yogstation/code/controllers/configuration/entries/general.dm
@@ -1,5 +1,3 @@
/datum/config_entry/flag/log_looc
/datum/config_entry/flag/looc_during_round
-
-/datum/config_entry/flag/toast_notification_on_init
diff --git a/yogstation/code/controllers/subsystem/bluespace_locker.dm b/yogstation/code/controllers/subsystem/bluespace_locker.dm
index 9469d82ed2b3..f6dee597ddcb 100644
--- a/yogstation/code/controllers/subsystem/bluespace_locker.dm
+++ b/yogstation/code/controllers/subsystem/bluespace_locker.dm
@@ -1,6 +1,7 @@
SUBSYSTEM_DEF(bluespace_locker)
name = "Bluespace Locker"
flags = SS_NO_FIRE
+ init_order = INIT_ORDER_BLUESPACE_LOCKER
var/obj/structure/closet/bluespace/internal/internal_locker = null
var/obj/structure/closet/bluespace/external/external_locker = null
@@ -65,8 +66,8 @@ SUBSYSTEM_DEF(bluespace_locker)
internal_locker.contents += external_locker.contents
internal_locker.open()
internal_locker.dump_contents()
- internal_locker.update_appearance(UPDATE_ICON)
- external_locker.update_appearance(UPDATE_ICON)
+ internal_locker.update_appearance()
+ external_locker.update_appearance()
/datum/controller/subsystem/bluespace_locker/proc/redistribute_locker()
if(!internal_locker)
diff --git a/yogstation/code/datums/components/crawl.dm b/yogstation/code/datums/components/crawl.dm
index ba56cffc8a2c..afd1e1ea028c 100644
--- a/yogstation/code/datums/components/crawl.dm
+++ b/yogstation/code/datums/components/crawl.dm
@@ -75,7 +75,7 @@
qdel(holder)
holder = null
-/datum/component/crawl/RemoveComponent(del_holder=TRUE)
+/datum/component/crawl/ClearFromParent(del_holder=TRUE)
var/mob/living/M = parent
if(del_holder && holder)
M.forceMove(get_turf(holder))
@@ -85,7 +85,7 @@
////////////BLOODCRAWL
/datum/component/crawl/blood
- crawling_types = list(/obj/effect/decal/cleanable/blood, /obj/effect/decal/cleanable/xenoblood, /obj/effect/decal/cleanable/trail_holder)
+ crawling_types = list(/obj/effect/decal/cleanable/blood, /obj/effect/decal/cleanable/xenoblood, /obj/effect/decal/cleanable/blood/trail_holder)
gain_message = span_boldnotice("You can now bloodcrawl! Alt-click blood or gibs to phase in and out.")
loss_message = span_warning("You can no longer bloodcrawl.")
@@ -356,7 +356,7 @@ GLOBAL_LIST_EMPTY(vomit_spots)
C.regenerate_icons()
C.extinguish_mob()
enteredvomit = target
- RegisterSignal(target, COMSIG_PARENT_PREQDELETED, PROC_REF(throw_out))
+ RegisterSignal(target, COMSIG_PREQDELETED, PROC_REF(throw_out))
user.visible_message(span_warning("[user] sinks into the pool of vomit!?"))
playsound(get_turf(target), 'sound/magic/mutate.ogg', 50, 1, -1)
holder = new /obj/effect/dummy/crawling/vomit(get_turf(user))
@@ -386,7 +386,7 @@ GLOBAL_LIST_EMPTY(vomit_spots)
for(var/obj/item/vomitcrawl/B in C)
qdel(B)
..()
- UnregisterSignal(enteredvomit, COMSIG_PARENT_PREQDELETED)
+ UnregisterSignal(enteredvomit, COMSIG_PREQDELETED)
enteredvomit = null
user.visible_message(span_warning("[user] rises out of the pool of vomit!?"))
exit_vomit_effect(target, user)
diff --git a/yogstation/code/datums/components/shielded.dm b/yogstation/code/datums/components/shielded.dm
new file mode 100644
index 000000000000..b35e4b5585f7
--- /dev/null
+++ b/yogstation/code/datums/components/shielded.dm
@@ -0,0 +1,89 @@
+/datum/component/shielded
+ var/shield_icon
+ var/shield_icon_state
+ var/shield_recharge
+
+ var/target_slot
+
+ var/cached_mutable_appearance
+
+ var/is_shielded = FALSE
+ var/is_charged = TRUE
+
+ var/mob/living/current_owner
+
+/datum/component/shielded/Initialize(shielded_icon, shielded_icon_state, shielded_recharge, slot)
+ if(!shielded_icon)
+ CRASH("Invalid shield icon passed")
+ if(!shielded_icon_state)
+ CRASH("Invalid shield icon state passed")
+ if(!isnum(shielded_recharge))
+ CRASH("Invalid shield recharge passed, expected number, found [shielded_recharge]")
+
+ shield_icon = shielded_icon
+ shield_icon_state = shielded_icon_state
+ shield_recharge = shielded_recharge
+ target_slot = slot
+ cached_mutable_appearance = mutable_appearance(shield_icon, shield_icon_state)
+
+ RegisterSignal(parent,COMSIG_ITEM_HIT_REACT,PROC_REF(on_hit_react))
+ RegisterSignal(parent,COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped))
+ RegisterSignal(parent,COMSIG_ITEM_DROPPED, PROC_REF(on_dropped))
+
+/datum/component/shielded/proc/apply_shield()
+ if(is_shielded)
+ return
+
+ is_shielded = TRUE
+
+ if(!current_owner)
+ return
+
+ if(is_charged)
+ current_owner.add_overlay(cached_mutable_appearance)
+
+/datum/component/shielded/proc/remove_shield()
+ if(!is_shielded)
+ return
+
+ is_shielded = FALSE
+
+ if(!current_owner)
+ return
+
+ if(is_charged)
+ current_owner.cut_overlay(cached_mutable_appearance)
+
+/datum/component/shielded/proc/drain_charge()
+ if(!is_charged)
+ return
+ is_charged = FALSE
+ if(!current_owner)
+ return
+ current_owner.cut_overlay(cached_mutable_appearance)
+ addtimer(CALLBACK(src,PROC_REF(recharge)),shield_recharge)
+
+/datum/component/shielded/proc/recharge()
+ if(is_charged)
+ return
+ is_charged = TRUE
+ if(!current_owner)
+ return
+ current_owner.add_overlay(cached_mutable_appearance)
+
+/datum/component/shielded/proc/on_hit_react(datum/source, mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
+ if(!is_shielded || !is_charged)
+ return
+ drain_charge()
+ return COMPONENT_HIT_REACTION_BLOCK
+
+/datum/component/shielded/proc/on_equipped(datum/source, mob/equipper, slot)
+ current_owner = equipper
+ if(target_slot && target_slot != slot)
+ remove_shield()
+ return
+ apply_shield()
+
+/datum/component/shielded/proc/on_dropped(datum/source,mob/dropper)
+ remove_shield()
+ current_owner = null
diff --git a/yogstation/code/datums/components/storage/storage.dm b/yogstation/code/datums/components/storage/storage.dm
index 00fadaa8f31e..918f8f805f26 100644
--- a/yogstation/code/datums/components/storage/storage.dm
+++ b/yogstation/code/datums/components/storage/storage.dm
@@ -8,7 +8,7 @@
if (.)
SEND_SIGNAL(parent, COMSIG_STORAGE_REMOVED, AM, new_location)
-/datum/component/storage/RemoveComponent() // hey TG you dropped this
+/datum/component/storage/ClearFromParent() // hey TG you dropped this
UnregisterSignal(parent, COMSIG_CONTAINS_STORAGE)
UnregisterSignal(parent, COMSIG_IS_STORAGE_LOCKED)
UnregisterSignal(parent, COMSIG_TRY_STORAGE_SHOW)
@@ -43,12 +43,12 @@
UnregisterSignal(parent, COMSIG_CLICK_ALT)
UnregisterSignal(parent, COMSIG_MOUSEDROP_ONTO)
UnregisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO)
- . = ..()
+ return ..()
-/datum/component/storage/concrete/RemoveComponent()
+/datum/component/storage/concrete/ClearFromParent()
UnregisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL)
UnregisterSignal(parent, COMSIG_OBJ_DECONSTRUCT)
- . = ..()
+ return ..()
// You know, TG created the component system to prevent exactly this problem.
// Turns out they did a shit job. Enjoy the literal copypaste.
@@ -89,7 +89,7 @@
user.visible_message(span_warning("An unseen force knocks [user] to the ground!"), "[span_big_brass("\"I think not!\"")]")
user.Paralyze(60)
return
- if(istype(loccheck.loc, /area/fabric_of_reality))
+ if(istype(loccheck.loc, /area/centcom/fabric_of_reality))
to_chat(user, span_danger("You can't do that here!"))
to_chat(user, span_danger("The Bluespace interfaces of the two devices catastrophically malfunction!"))
playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1)
diff --git a/yogstation/code/datums/components/walks.dm b/yogstation/code/datums/components/walks.dm
index c8affac3a981..401b15dd2a4f 100644
--- a/yogstation/code/datums/components/walks.dm
+++ b/yogstation/code/datums/components/walks.dm
@@ -14,7 +14,7 @@
RegisterSignal(parent, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(handle_move))
ADD_TRAIT(parent, TRAIT_SILENT_FOOTSTEPS, WALK_COMPONENT_TRAIT)
-/datum/component/walk/RemoveComponent()
+/datum/component/walk/ClearFromParent()
REMOVE_TRAIT(parent, TRAIT_SILENT_FOOTSTEPS, WALK_COMPONENT_TRAIT)
return ..()
@@ -82,7 +82,7 @@
R.reveal(20)
R.stun(20)
return MOVE_NOT_ALLOWED
- if(destination.flags_1 & NOJAUNT_1 || is_secret_level(destination.z))
+ if(destination.turf_flags & NOJAUNT || is_secret_level(destination.z))
to_chat(user, span_warning("Some strange aura is blocking the way."))
return MOVE_NOT_ALLOWED
if (locate(/obj/effect/blessing, destination))
diff --git a/yogstation/code/datums/mapgen/JungleGen.dm b/yogstation/code/datums/mapgen/JungleGen.dm
new file mode 100644
index 000000000000..f00e852bf255
--- /dev/null
+++ b/yogstation/code/datums/mapgen/JungleGen.dm
@@ -0,0 +1,270 @@
+#define BIOME_TOXIC "toxic"
+#define BIOME_BARREN "barren"
+
+#define LOW_HUMIDITY "low_humidity"
+#define MED_HUMIDITY "med_humidity"
+#define HIGH_HUMIDITY "high_humidity"
+
+#define LOW_DENSITY "low_density"
+#define MED_DENSITY "med_density"
+#define HIGH_DENSITY "high_density"
+
+#define CA_INITIAL_CLOSED_CHANCE "initial_closed_chance"
+#define CA_SMOOTHING_INTERATIONS "smoothing_iterations"
+#define CA_BIRTH_LIMIT "birth_limit"
+#define CA_DEATH_LIMIT "death_limit"
+
+#define WORLEY_REG_SIZE "reg_size"
+#define WORLEY_THRESHOLD "threshold"
+#define WORLEY_NODE_PER_REG "node_per_reg"
+
+/datum/map_generator/jungleland
+
+ var/list/possible_biomes = list(
+ BIOME_BARREN = list( LOW_HUMIDITY = /datum/biome/jungleland/barren_rocks,
+ MED_HUMIDITY = /datum/biome/jungleland/dry_swamp,
+ HIGH_HUMIDITY = /datum/biome/jungleland/dying_forest),
+
+ BIOME_TOXIC = list( LOW_HUMIDITY = /datum/biome/jungleland/toxic_pit,
+ MED_HUMIDITY = /datum/biome/jungleland/toxic_pit,
+ HIGH_HUMIDITY = /datum/biome/jungleland/jungle)
+ )
+ ///Used to select "zoom" level into the perlin noise, higher numbers result in slower transitions
+ var/perlin_zoom = 65
+
+ var/list/cellular_preferences = list(
+ LOW_DENSITY = list(
+ WORLEY_REG_SIZE = 1,
+ WORLEY_THRESHOLD = 2.5,
+ WORLEY_NODE_PER_REG = 75),
+
+ MED_DENSITY = list(
+ CA_INITIAL_CLOSED_CHANCE = 45,
+ CA_SMOOTHING_INTERATIONS = 20,
+ CA_BIRTH_LIMIT = 4,
+ CA_DEATH_LIMIT = 3),
+
+ HIGH_DENSITY = list(
+ WORLEY_REG_SIZE = 15,
+ WORLEY_THRESHOLD = 5.5,
+ WORLEY_NODE_PER_REG = 25)
+ )
+
+ var/list/ore_preferences = list(
+ ORE_IRON = list(
+ WORLEY_REG_SIZE = 10,
+ WORLEY_THRESHOLD = 3,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_URANIUM = list(
+ WORLEY_REG_SIZE = 10,
+ WORLEY_THRESHOLD = 1,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_TITANIUM = list(
+ WORLEY_REG_SIZE = 10,
+ WORLEY_THRESHOLD = 2,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_BLUESPACE = list(
+ WORLEY_REG_SIZE = 15,
+ WORLEY_THRESHOLD = 8,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_PLASMA = list(
+ WORLEY_REG_SIZE = 15,
+ WORLEY_THRESHOLD = 6,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_GOLD = list(
+ WORLEY_REG_SIZE = 10,
+ WORLEY_THRESHOLD = 1,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_SILVER = list(
+ WORLEY_REG_SIZE = 10,
+ WORLEY_THRESHOLD = 1,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_DILITHIUM = list(
+ WORLEY_REG_SIZE = 15,
+ WORLEY_THRESHOLD = 8,
+ WORLEY_NODE_PER_REG = 50),
+
+ ORE_DIAMOND = list(
+ WORLEY_REG_SIZE = 15,
+ WORLEY_THRESHOLD = 8,
+ WORLEY_NODE_PER_REG = 50)
+ )
+//creates a 2d map of every single ore vein on the map
+/datum/map_generator/jungleland/proc/generate_ores(list/turfs)
+ var/list/ore_strings = list(
+ ORE_BLUESPACE = rustg_worley_generate("[ore_preferences[ORE_BLUESPACE][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_BLUESPACE][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_BLUESPACE][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_DILITHIUM = rustg_worley_generate("[ore_preferences[ORE_DILITHIUM][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_DILITHIUM][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_DILITHIUM][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_DIAMOND = rustg_worley_generate("[ore_preferences[ORE_DIAMOND][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_DIAMOND][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_DIAMOND][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+
+ ORE_PLASMA = rustg_worley_generate("[ore_preferences[ORE_PLASMA][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_PLASMA][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_PLASMA][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_GOLD = rustg_worley_generate("[ore_preferences[ORE_GOLD][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_GOLD][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_GOLD][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_URANIUM = rustg_worley_generate("[ore_preferences[ORE_URANIUM][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_URANIUM][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_URANIUM][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_TITANIUM = rustg_worley_generate("[ore_preferences[ORE_TITANIUM][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_TITANIUM][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_TITANIUM][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_SILVER = rustg_worley_generate("[ore_preferences[ORE_SILVER][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_SILVER][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_SILVER][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"),
+ ORE_IRON = rustg_worley_generate("[ore_preferences[ORE_IRON][WORLEY_REG_SIZE]]",
+ "[ore_preferences[ORE_IRON][WORLEY_THRESHOLD]]",
+ "[ore_preferences[ORE_IRON][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2"))
+ //order of generation, ordered from rarest to most common
+ var/list/generation_queue = list(
+ ORE_IRON,
+ ORE_SILVER,
+ ORE_TITANIUM,
+ ORE_URANIUM,
+ ORE_GOLD,
+ ORE_PLASMA,
+ ORE_DIAMOND,
+ ORE_DILITHIUM,
+ ORE_BLUESPACE
+ )
+ var/return_list[world.maxx * world.maxy]
+
+
+ for(var/t in turfs)
+ var/turf/gen_turf = t
+ var/generated = FALSE
+ for(var/ore in generation_queue)
+ if(ore_strings[ore][world.maxx * (gen_turf.y - 1) + gen_turf.x] == "1")
+ continue
+ return_list[world.maxx * (gen_turf.y - 1) + gen_turf.x] = ore
+ generated = TRUE
+ break
+
+ if(!generated)
+ return_list[world.maxx * (gen_turf.y - 1) + gen_turf.x] = ORE_EMPTY
+
+ CHECK_TICK
+
+ //guaranteed spawn at least some rare ores like bluespace and dilithium in small pockets
+
+ for(var/i in 0 to 64)
+ var/x = rand(16,239)
+ var/y = rand(16,239)
+ return_list[world.maxx * y + x] = ORE_DILITHIUM
+ for(var/j in 1 to 8)
+ var/x_o = x + rand(-j,j)
+ var/y_o = y + rand(-j,j)
+ return_list[world.maxx * y_o + x_o] = ORE_DILITHIUM
+
+ for(var/i in 0 to 64)
+ var/x = rand(16,239)
+ var/y = rand(16,239)
+ return_list[world.maxx * y + x] = ORE_BLUESPACE
+ for(var/j in 1 to 8)
+ var/x_o = x + rand(-j,j)
+ var/y_o = y + rand(-j,j)
+ return_list[world.maxx * y_o + x_o] = ORE_BLUESPACE
+
+ return return_list
+
+/datum/map_generator/jungleland/generate_terrain(list/turfs)
+ var/start_time = REALTIMEOFDAY
+ var/list/ore_map = generate_ores(turfs)
+
+ var/toxic_seed = rand(0, 50000)
+ var/humid_seed = rand(0, 50000)
+ var/list/density_strings = list()
+ density_strings[LOW_DENSITY] = rustg_worley_generate("[cellular_preferences[LOW_DENSITY][WORLEY_REG_SIZE]]",
+ "[cellular_preferences[LOW_DENSITY][WORLEY_THRESHOLD]]",
+ "[cellular_preferences[LOW_DENSITY][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2")
+
+ density_strings[MED_DENSITY] = rustg_cnoise_generate("[cellular_preferences[MED_DENSITY][CA_INITIAL_CLOSED_CHANCE]]",
+ "[cellular_preferences[MED_DENSITY][CA_SMOOTHING_INTERATIONS]]",
+ "[cellular_preferences[MED_DENSITY][CA_BIRTH_LIMIT]]",
+ "[cellular_preferences[MED_DENSITY][CA_DEATH_LIMIT]]",
+ "[world.maxx]",
+ "[world.maxy]")
+
+ density_strings[HIGH_DENSITY] = rustg_worley_generate("[cellular_preferences[HIGH_DENSITY][WORLEY_REG_SIZE]]",
+ "[cellular_preferences[HIGH_DENSITY][WORLEY_THRESHOLD]]",
+ "[cellular_preferences[HIGH_DENSITY][WORLEY_NODE_PER_REG]]",
+ "[world.maxx]",
+ "1",
+ "2")
+
+ var/toxic_string = rustg_dbp_generate("[toxic_seed]","60","75","[world.maxx]","-0.05","1.1")
+ var/list/humid_strings = list()
+ humid_strings[HIGH_HUMIDITY] = rustg_dbp_generate("[humid_seed]","60","75","[world.maxx]","-0.1","1.1")
+ humid_strings[MED_HUMIDITY] = rustg_dbp_generate("[humid_seed]","60","75","[world.maxx]","-0.3","-0.1")
+
+ for(var/t in turfs) //Go through all the turfs and generate them
+ var/turf/gen_turf = t
+ var/toxic_pick = text2num(toxic_string[world.maxx * (gen_turf.y - 1) + gen_turf.x]) ? BIOME_TOXIC : BIOME_BARREN
+
+ var/humid_pick = text2num(humid_strings[HIGH_HUMIDITY][world.maxx * (gen_turf.y - 1) + gen_turf.x]) ? HIGH_HUMIDITY : text2num(humid_strings[MED_HUMIDITY][world.maxx * (gen_turf.y - 1) + gen_turf.x]) ? MED_HUMIDITY : LOW_HUMIDITY
+
+ var/datum/biome/jungleland/selected_biome = possible_biomes[toxic_pick][humid_pick]
+
+ selected_biome = SSmapping.biomes[selected_biome] //Get the instance of this biome from SSmapping
+ var/turf/GT = selected_biome.generate_turf(gen_turf,density_strings)
+ if(istype(GT,/turf/open/floor/plating/dirt/jungleland))
+ var/turf/open/floor/plating/dirt/jungleland/J = GT
+ J.ore_present = ore_map[world.maxx * (gen_turf.y - 1) + gen_turf.x]
+ var/area/jungleland/jungle_area = selected_biome.this_area
+ var/area/old_area = GT.loc
+ old_area.contents -= GT
+ old_area.contained_turfs -= GT
+ jungle_area.contents += GT
+ jungle_area.contained_turfs += GT
+ GT.change_area(old_area,jungle_area)
+ CHECK_TICK
+
+ for(var/biome in subtypesof(/datum/biome/jungleland))
+ var/datum/biome/jungleland/selected_biome = SSmapping.biomes[biome]
+ selected_biome.this_area.reg_in_areas_in_z()
+
+ var/message = "Jungle land finished in [(REALTIMEOFDAY - start_time)/10]s!"
+ to_chat(world, "[message]")
+ log_world(message)
diff --git a/yogstation/code/datums/mapgen/biomes/JungleBiomes.dm b/yogstation/code/datums/mapgen/biomes/JungleBiomes.dm
new file mode 100644
index 000000000000..e89d6b4442df
--- /dev/null
+++ b/yogstation/code/datums/mapgen/biomes/JungleBiomes.dm
@@ -0,0 +1,92 @@
+/datum/biome/jungleland
+ var/cellular_noise_map_id = MED_DENSITY
+ var/turf/closed_turf = /turf/closed/mineral/random
+ var/list/dense_flora = list()
+ var/list/loose_flora = list()
+ var/loose_flora_density = 0 // from 0 to 100
+ var/dense_flora_density = 100
+ var/spawn_fauna_on_closed = FALSE
+ var/area/jungleland/this_area = /area/jungleland
+
+/datum/biome/jungleland/New()
+ . = ..()
+ this_area = new this_area()
+
+/datum/biome/jungleland/generate_turf(turf/gen_turf,list/density_map)
+
+ var/closed = text2num(density_map[cellular_noise_map_id][world.maxx * (gen_turf.y - 1) + gen_turf.x])
+ var/turf/chosen_turf
+ if(closed)
+ chosen_turf = closed_turf
+ spawn_dense_flora(gen_turf)
+ else
+ chosen_turf = turf_type
+ spawn_loose_flora(gen_turf)
+
+ if((!closed || spawn_fauna_on_closed) && length(fauna_types) && prob(fauna_density))
+ var/mob/fauna = pickweight(fauna_types)
+ new fauna(gen_turf)
+ . = gen_turf.ChangeTurf(chosen_turf, initial(chosen_turf.baseturfs), CHANGETURF_DEFER_CHANGE)
+
+/datum/biome/jungleland/proc/spawn_dense_flora(turf/gen_turf)
+ if(length(dense_flora) && prob(dense_flora_density))
+ var/obj/structure/flora = pickweight(dense_flora)
+ new flora(gen_turf)
+
+/datum/biome/jungleland/proc/spawn_loose_flora(turf/gen_turf)
+ if(length(loose_flora) && prob(loose_flora_density))
+ var/obj/structure/flora = pickweight(loose_flora)
+ new flora(gen_turf)
+
+/datum/biome/jungleland/barren_rocks
+ turf_type = /turf/open/floor/plating/dirt/jungleland/barren_rocks
+ loose_flora = list(/obj/structure/flora/rock = 2,/obj/structure/flora/rock/pile = 2)
+ loose_flora_density = 10
+ cellular_noise_map_id = LOW_DENSITY
+ fauna_density = 0.5
+ fauna_types = list(/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 33,/mob/living/simple_animal/hostile/asteroid/goliath/beast = 33,/mob/living/simple_animal/hostile/asteroid/goldgrub = 25,/mob/living/simple_animal/hostile/yog_jungle/skin_twister = 1, /mob/living/simple_animal/hostile/asteroid/marrowweaver = 7)
+ this_area = /area/jungleland/barren_rocks
+
+/datum/biome/jungleland/dry_swamp
+ turf_type = /turf/open/floor/plating/dirt/jungleland/dry_swamp
+ closed_turf = /turf/open/floor/plating/dirt/jungleland/dry_swamp1
+ dense_flora = list(/obj/structure/flora/rock = 2,/obj/structure/flora/rock/jungle = 1,/obj/structure/flora/rock/pile = 2)
+ loose_flora = list(/obj/structure/flora/ausbushes/stalkybush = 2,/obj/structure/flora/rock = 2,/obj/structure/flora/rock/jungle = 2,/obj/structure/flora/rock/pile = 2,/obj/structure/flora/stump=2,/obj/structure/flora/tree/jungle = 1,/obj/structure/herb/cinchona = 0.1, /obj/structure/flytrap = 0.1)
+ dense_flora_density = 10
+ loose_flora_density = 10
+ fauna_types = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast = 39,/mob/living/simple_animal/hostile/asteroid/goldgrub = 31,/mob/living/simple_animal/hostile/yog_jungle/meduracha = 8,/mob/living/simple_animal/hostile/yog_jungle/skin_twister = 1,/mob/living/simple_animal/hostile/yog_jungle/mosquito = 13, /mob/living/simple_animal/hostile/yog_jungle/emeraldspider = 8)
+ fauna_density = 0.4
+ spawn_fauna_on_closed = TRUE
+ this_area = /area/jungleland/dry_swamp
+
+/datum/biome/jungleland/toxic_pit
+ turf_type = /turf/open/floor/plating/dirt/jungleland/toxic_pit
+ closed_turf = /turf/open/water/toxic_pit
+ loose_flora = list(/obj/structure/flora/ausbushes/stalkybush = 2,/obj/structure/flora/rock = 2,/obj/structure/flora/rock/jungle = 2,/obj/structure/flora/rock/pile = 2,/obj/structure/flora/stump=2,/obj/structure/flora/tree/jungle = 1,/obj/structure/herb/explosive_shrooms = 0.05,/obj/structure/herb/liberal_hats = 0.5,/obj/structure/herb/magnus_purpura = 0.5)
+ dense_flora = list(/obj/structure/flora/ausbushes/stalkybush = 1)
+ loose_flora_density = 20
+ dense_flora_density = 10
+ fauna_types = list(/mob/living/simple_animal/hostile/yog_jungle/blobby = 20,/mob/living/simple_animal/hostile/yog_jungle/meduracha = 50,/mob/living/simple_animal/hostile/yog_jungle/skin_twister = 2,/mob/living/simple_animal/hostile/yog_jungle/mosquito = 46, /mob/living/simple_animal/hostile/yog_jungle/emeraldspider = 2)
+ fauna_density = 0.7
+ spawn_fauna_on_closed = TRUE
+ this_area = /area/jungleland/toxic_pit
+
+/datum/biome/jungleland/dying_forest
+ turf_type = /turf/open/floor/plating/dirt/jungleland/dying_forest
+ closed_turf = /turf/open/floor/plating/dirt/jungleland/dying_forest
+ dense_flora = list(/obj/structure/flora/stump=1,/obj/structure/flora/tree/dead/jungle = 2,/obj/structure/flora/rock/jungle = 2,/obj/structure/flora/rock/pile = 2,/obj/structure/flora/rock = 2,/obj/structure/flora/tree/jungle/small = 1,/obj/structure/herb/cinchona = 0.25)
+ dense_flora_density = 50
+ fauna_types = list(/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/magmawing = 39,/mob/living/simple_animal/hostile/yog_jungle/corrupted_dryad = 55,/mob/living/simple_animal/hostile/yog_jungle/skin_twister = 1,/mob/living/simple_animal/hostile/yog_jungle/mosquito = 5)
+ fauna_density = 0.8
+ this_area = /area/jungleland/dying_forest
+
+/datum/biome/jungleland/jungle
+ turf_type = /turf/open/floor/plating/dirt/jungleland/jungle
+ closed_turf = /turf/open/floor/plating/dirt/jungleland/jungle
+ cellular_noise_map_id = HIGH_DENSITY
+ dense_flora = list(/obj/structure/flora/tree/jungle/small = 2,/obj/structure/flora/tree/jungle = 2, /obj/structure/flora/rock/jungle = 1, /obj/structure/flora/junglebush = 1, /obj/structure/flora/junglebush/b = 1, /obj/structure/flora/junglebush/c = 1, /obj/structure/flora/junglebush/large = 1, /obj/structure/flora/rock/pile/largejungle = 1)
+ loose_flora = list(/obj/structure/flora/grass/jungle = 3,/obj/structure/flora/grass/jungle/b = 2,/obj/structure/flora/ausbushes = 2,/obj/structure/flora/ausbushes/leafybush = 1,/obj/structure/flora/ausbushes/sparsegrass = 1,/obj/structure/flora/ausbushes/fullgrass = 1,/obj/structure/herb/explosive_shrooms = 0.05,/obj/structure/flytrap = 0.1,/obj/structure/herb/fruit = 0.2)
+ loose_flora_density = 60
+ fauna_types = list(/mob/living/simple_animal/hostile/yog_jungle/dryad = 65 ,/mob/living/simple_animal/hostile/yog_jungle/skin_twister = 1,/mob/living/simple_animal/hostile/yog_jungle/mosquito = 10, /mob/living/simple_animal/hostile/yog_jungle/yellowjacket = 10, /mob/living/simple_animal/hostile/yog_jungle/emeraldspider = 14)
+ fauna_density = 0.65
+ this_area = /area/jungleland/proper
diff --git a/yogstation/code/datums/martial/explosive_fist.dm b/yogstation/code/datums/martial/explosive_fist.dm
index bd2d3b791f14..2d400f84bbfd 100644
--- a/yogstation/code/datums/martial/explosive_fist.dm
+++ b/yogstation/code/datums/martial/explosive_fist.dm
@@ -366,7 +366,7 @@
var/armor_block = D.run_armor_check(hed, BOMB)
D.apply_damage(A.get_punchdamagehigh() + 3, BURN, BODY_ZONE_HEAD, armor_block) //10 burn (vs bomb armor)
D.emote("scream")
- D.blur_eyes(4)
+ D.adjust_eye_blur(4)
A.apply_damage(10, BURN, BODY_ZONE_CHEST, 0) //Take some unblockable damage since you're using your inner flame or something
diff --git a/yogstation/code/datums/mood_events/generic_negative_events.dm b/yogstation/code/datums/mood_events/generic_negative_events.dm
new file mode 100644
index 000000000000..5f9c5c94e8c5
--- /dev/null
+++ b/yogstation/code/datums/mood_events/generic_negative_events.dm
@@ -0,0 +1,5 @@
+/datum/mood_event/corrupted_dryad_bad
+ description = "I feel sick, my veins hurt, my eyes have blistered, my mind is foggy. I was on top and now I'm below.\n"
+ mood_change = -10
+ timeout = 3 MINUTES
+
diff --git a/yogstation/code/datums/mood_events/generic_positive_events.dm b/yogstation/code/datums/mood_events/generic_positive_events.dm
index 1661b315d094..8944e55a4a5a 100644
--- a/yogstation/code/datums/mood_events/generic_positive_events.dm
+++ b/yogstation/code/datums/mood_events/generic_positive_events.dm
@@ -4,4 +4,9 @@
/datum/mood_event/sling
description = "The keys to reality are within my grasp.\n"
- mood_change = 20
\ No newline at end of file
+ mood_change = 20
+
+/datum/mood_event/corrupted_dryad
+ description = "My heart beats strong, my eyes shine with eridition, my voice is truth, my actions without consequence, I AM ON TOP.\n"
+ mood_change = 20
+ timeout = 3 MINUTES
diff --git a/yogstation/code/datums/mutations/extendoarm.dm b/yogstation/code/datums/mutations/extendoarm.dm
index 21cfc96a4ed5..246d0f8a6f87 100644
--- a/yogstation/code/datums/mutations/extendoarm.dm
+++ b/yogstation/code/datums/mutations/extendoarm.dm
@@ -53,25 +53,25 @@
return TRUE
-/datum/action/cooldown/spell/pointed/projectile/extendoarm/ready_projectile(obj/projectile/bullet/arm/P, atom/target, mob/user, iteration)
+/datum/action/cooldown/spell/pointed/projectile/extendoarm/ready_projectile(obj/projectile/bullet/arm/firing_arm, atom/target, mob/user, iteration)
. = ..()
- var/mob/living/carbon/C = user
+ var/mob/living/carbon/carbon_user = user
var/new_color
- if(C.dna && !C.dna.species.use_skintones)
- new_color = C.dna.features["mcolor"]
- P.add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
+ if(carbon_user.dna && !carbon_user.dna.species.use_skintones)
+ new_color = carbon_user.dna.features["mcolor"]
+ firing_arm.add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
- P.homing = target
- P.beam = new(C, P, time=200, beam_icon_state="2-full", maxdistance=150, beam_sleep_time=1, beam_color = new_color)
- P.beam.Start()
+
+ firing_arm.set_homing_target(target)
+ firing_arm.beam = firing_arm.Beam(carbon_user, icon_state = "2-full", time = 20 SECONDS, maxdistance = 150, beam_color = new_color)
- var/obj/item/I = C.get_active_held_item()
- if(I && C.dropItemToGround(I, FALSE))
- var/obj/projectile/bullet/arm/ARM = P
+ var/obj/item/I = carbon_user.get_active_held_item()
+ if(I && carbon_user.dropItemToGround(I, FALSE))
+ var/obj/projectile/bullet/arm/ARM = firing_arm
ARM.grab(I)
- P.arm = C.hand_bodyparts[C.active_hand_index]
- P.arm.drop_limb()
- P.arm.forceMove(P)
+ firing_arm.arm = carbon_user.hand_bodyparts[carbon_user.active_hand_index]
+ firing_arm.arm.drop_limb()
+ firing_arm.arm.forceMove(firing_arm)
/obj/projectile/bullet/arm
name = "arm"
diff --git a/yogstation/code/datums/ruins/free_miners.dm b/yogstation/code/datums/ruins/free_miners.dm
index 78b9f17fbb1f..98e1910956e3 100644
--- a/yogstation/code/datums/ruins/free_miners.dm
+++ b/yogstation/code/datums/ruins/free_miners.dm
@@ -26,15 +26,15 @@
/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/miner
name = "Free Miner Navigation Computer"
desc = "Used to designate a precise transit location for the Free Miner Ship."
- jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_mining0" = 1, "whiteship_mining1" = 1, "whiteship_mining2" = 1)
+ jump_to_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_mining0" = 1, "whiteship_mining1" = 1, "whiteship_mining2" = 1)
x_offset = -4
y_offset = -7
/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/miner/Initialize(mapload)
. = ..()
- for(var/V in SSshuttle.stationary)
+ for(var/V in SSshuttle.stationary_docking_ports)
var/obj/docking_port/stationary/S = V
- if(jumpto_ports[S.id])
+ if(jump_to_ports[S.shuttle_id])
z_lock |= S.z
diff --git a/yogstation/code/datums/ruins/jungle.dm b/yogstation/code/datums/ruins/jungle.dm
new file mode 100644
index 000000000000..f0b77263e4c6
--- /dev/null
+++ b/yogstation/code/datums/ruins/jungle.dm
@@ -0,0 +1,255 @@
+/datum/map_template/ruin/jungle
+ prefix = "_maps/RandomRuins/JungleRuins/"
+ allow_duplicates = FALSE
+ cost = 5
+
+/datum/map_template/ruin/jungle/all
+ should_place_on_top = FALSE
+
+/datum/map_template/ruin/jungle/dying/crashed_ship
+ name = "Crashed Ship"
+ id = "jungle-crashed-ship"
+ description = "The remains of a long crashed ship, weathered away into scrap."
+ suffix = "jungleland_dead_crashedship.dmm"
+
+/datum/map_template/ruin/jungle/dying/testing_facility
+ name = "Testing-facility"
+ id = "jungle-testing-facility"
+ description = "A testing facility, were bodily experiments were conducted on people, safely remote from scrutiny."
+ suffix = "jungleland_dead_testingfacility.dmm"
+
+/datum/map_template/ruin/jungle/all/ivymen_nest
+ name = "Ivymen Nest"
+ id = "jungle-ivymen-next"
+ description = "A dormant nest filled with primal plant creatures, waiting to hatch."
+ suffix = "jungleland_jungle_ivymen_nest.dmm"
+
+/datum/map_template/ruin/jungle/proper/old_temple
+ name = "Ancient Temple"
+ id = "jungle-old-temple"
+ description = "A temple bearing signs of the occult. It seems the spirits inside have been corrupted..."
+ suffix = "jungleland_jungle_oldtemple.dmm"
+
+/datum/map_template/ruin/jungle/proper/xenos
+ name = "Xeno Nest"
+ id = "jungle-xenos"
+ description = "Once an expeditionary camp for soldiers, it fell to predatory alien creatures."
+ suffix = "jungleland_jungle_xenos.dmm"
+
+/datum/map_template/ruin/jungle/proper/geode
+ name = "Geode"
+ id = "jungle-geode"
+ description = "Geode"
+ suffix = "jungleland_jungle_geode.dmm"
+
+/datum/map_template/ruin/jungle/proper/felinid
+ name = "Felinid Party"
+ id = "jungle-felinid"
+ description = "Felinid party"
+ suffix = "jungleland_jungle_felinid.dmm"
+
+/datum/map_template/ruin/jungle/proper/garden
+ name = "Old Garden"
+ id = "jungle-garden"
+ description = "A very old garden, still kept in peak condition by a dryad. A quaint place for a traveller to fish and rest."
+ suffix = "jungleland_jungle_garden.dmm"
+
+/datum/map_template/ruin/jungle/proper/seedvault
+ name = "Seed Vault"
+ id = "jungle-seedvault"
+ description = "A seedvault launched from far away. Thousands of exact copies litter planets across the entire universe, so finding one here isn't too much of a surprise."
+ suffix = "jungleland_jungle_seed_vault.dmm"
+
+/datum/map_template/ruin/jungle/swamp/cave
+ name = "Cave"
+ id = "jungle-cave"
+ description = "A mass of rock hiding a small cave system, home to a den of basilisks. If you're willing to brave them, you might get something worthwhile."
+ suffix = "jungleland_swamp_cave.dmm"
+
+/datum/map_template/ruin/jungle/swamp/burial_grounds
+ name = "Drowned Burial Grounds"
+ id = "jungle-burial-grounds"
+ description = "Flooded burial grounds, filled with toxic water and the reanimated dead of those buried inside."
+ suffix = "jungleland_swamp_drownedburialgrounds.dmm"
+
+/datum/map_template/ruin/jungle/swamp/farm
+ name = "Abandoned Farm"
+ id = "jungle-farm"
+ description = "A large field of rotting, tilled soil next to a small home."
+ suffix = "jungleland_swamp_farm.dmm"
+
+/datum/map_template/ruin/jungle/swamp/hut
+ name = "Old Hut"
+ id = "jungle-hut"
+ description = "An old hut that belonged to a witch."
+ suffix = "jungleland_swamp_oldhut.dmm"
+
+/datum/map_template/ruin/jungle/swamp/carp_pond
+ name = "Carp Pond"
+ id = "jungle-carp-pond"
+ description = "A few ponds full of rancid and toxic water, guarded by overgrown carp. \
+ However, it looks like it could've been pretty, at least in the past..."
+ suffix = "jungleland_swamp_carp_pond.dmm"
+
+/* disables this till marmio fixes it */
+/datum/map_template/ruin/jungle/all/syndicate_base //has to be all biomes cause its so big it wont spawn otherwise
+ name = "Syndicate Base"
+ id = "jungle-syndicate-base"
+ description = "A large permanent research and comms station run by the syndicate."
+ suffix = "jungleland_swamp_syndicatestation.dmm"
+
+/datum/map_template/ruin/jungle/all/miningbase //THIS IS THE MINING BASE. DO NOT FUCK WITH THIS UNLESS YOU ARE 100% CERTAIN YOU KNOW WHAT YOU'RE DOING, OR THE MINING BASE WILL DISAPPEAR
+ name = "Mining Base"
+ id = "miningbase"
+ description = "The mining base that Nanotrasen uses for their mining operations."
+ suffix = "miningbase.dmm"
+ always_place = TRUE
+ unpickable = TRUE
+ cost = 0
+
+//TAR TEMPLES
+/datum/map_template/ruin/jungle/all/tar_temple0
+ name = "Tar Temple 0"
+ id = "tar_temple"
+ description = "Old ruin of a civilization long gone, only echoes of the past remain..."
+ suffix = "tar_temple0.dmm"
+ always_place = TRUE
+ cost = 0
+
+/datum/map_template/ruin/jungle/all/tar_temple1
+ name = "Tar temple 1"
+ id = "jungle-dying-tar-temple"
+ description = "Old ruin of a civilization long gone, only echoes of the past remain..."
+ suffix = "jungleland_dead_tartemple.dmm"
+ always_place = TRUE
+ cost = 0
+
+/datum/map_template/ruin/jungle/all/tar_temple2
+ name = "Tar temple 2"
+ id = "jungle-swamp-tar-temple"
+ description = "Old ruin of a civilization long gone, only echoes of the past remain..."
+ suffix = "jungleland_swamp_tartemple.dmm"
+ always_place = TRUE
+ cost = 0
+
+/datum/map_template/ruin/jungle/all/tar_temple3
+ name = "Tar temple 3"
+ id = "jungle-proper-tar-temple"
+ description = "Old ruin of a civilization long gone, only echoes of the past remain..."
+ suffix = "jungleland_jungle_tartemple.dmm"
+ always_place = TRUE
+ cost = 0
+
+/datum/map_template/ruin/jungle/all/tar_assistant
+ name = "Tar Assistant"
+ id = "jungle-proper-tar-assistant"
+ description = "Old ruin of a civilization long gone, only echoes of the past remain..."
+ suffix = "tar_assistant.dmm"
+ cost = 5
+
+
+/datum/map_template/ruin/jungle/all/tar_enchant
+ name = "Tar Enchant"
+ id = "jungle-proper-tar-enchant"
+ description = "Old ruin of a civilization long gone, only echoes of the past remain..."
+ suffix = "tar_enchant.dmm"
+ cost = 5
+
+
+//MEGAFAUNA
+/datum/map_template/ruin/jungle/swamp/miner
+ name = "Blood Drunk Miner"
+ id = "swamp_miner"
+ description = "Miner's hideout"
+ suffix = "jungleland_swamp_miner.dmm"
+ always_place = TRUE
+
+/datum/map_template/ruin/jungle/dying/colossus
+ name = "Colossus"
+ id = "dying_colossus"
+ description = "Colossus"
+ suffix = "jungleland_dead_colossus.dmm"
+ always_place = TRUE
+
+/datum/map_template/ruin/jungle/dying/bubblegum
+ name = "Bubblegum"
+ id = "dying_bubblegum"
+ description = "Bubblegum"
+ suffix = "jungleland_dead_bubblegum.dmm"
+ always_place = TRUE
+
+/datum/map_template/ruin/jungle/barren/drake
+ name = "Ash Drake"
+ id = "barren_drake"
+ description = "Ash Drake"
+ suffix = "jungleland_barren_drake.dmm"
+ always_place = TRUE
+ allow_duplicates = TRUE
+ cost = 20
+
+//NESTS
+/datum/map_template/ruin/jungle/dying/dead_nest
+ name = "Dying Forest Nest"
+ id = "jungle-dying-nest"
+ description = "a nest"
+ suffix = "jungleland_dead_nest.dmm"
+ allow_duplicates = TRUE
+ always_place = TRUE
+ cost = 2
+
+/datum/map_template/ruin/jungle/proper/jungle_nest
+ name = "Jungle Nest"
+ id = "jungle-proper-nest"
+ description = "a nest"
+ suffix = "jungleland_jungle_nest.dmm"
+ allow_duplicates = TRUE
+ always_place = TRUE
+ cost = 2
+
+/datum/map_template/ruin/jungle/swamp/swamp_nest
+ name = "Swamp Nest"
+ id = "jungle-swamp-nest"
+ description = "a nest"
+ suffix = "jungleland_swamp_nest.dmm"
+ allow_duplicates = TRUE
+ always_place = TRUE
+ cost = 2
+
+/datum/map_template/ruin/jungle/barren/barren_nest
+ name = "Barren Nest"
+ id = "jungle-barren-nest"
+ description = "a nest"
+ suffix = "jungleland_barren_nest.dmm"
+ allow_duplicates = TRUE
+ always_place = TRUE
+
+// OBSIDIAN PILLARS
+
+/datum/map_template/ruin/jungle/dying/obsidian_pillar0
+ name = "Obsidian pillar"
+ id = "jungle-dying-obsidian-pillar0"
+ description = "obsidian pillar"
+ suffix = "obsidian_pillar0.dmm"
+ cost = 1
+
+/datum/map_template/ruin/jungle/dying/obsidian_pillar1
+ name = "Obsidian pillar"
+ id = "jungle-dying-obsidian-pillar1"
+ description = "obsidian pillar"
+ suffix = "obsidian_pillar1.dmm"
+ cost = 1
+
+/datum/map_template/ruin/jungle/dying/obsidian_pillar2
+ name = "Obsidian pillar"
+ id = "jungle-dying-obsidian-pillar2"
+ description = "obsidian pillar"
+ suffix = "obsidian_pillar2.dmm"
+ cost = 1
+
+/datum/map_template/ruin/jungle/dying/obsidian_pillar3
+ name = "Obsidian pillar"
+ id = "jungle-dying-obsidian-pillar3"
+ description = "obsidian pillar"
+ suffix = "obsidian_pillar3.dmm"
+ cost = 1
+
diff --git a/yogstation/code/datums/ruins/station.dm b/yogstation/code/datums/ruins/station.dm
index e5a9d5a66d20..20c2716f1cc4 100644
--- a/yogstation/code/datums/ruins/station.dm
+++ b/yogstation/code/datums/ruins/station.dm
@@ -4,6 +4,7 @@
/datum/map_template/ruin/station/box
prefix = "_maps/RandomRuins/StationRuins/BoxStation/"
+ should_place_on_top = FALSE
/datum/map_template/ruin/station/box/engine
id = "engine_sm"
diff --git a/yogstation/code/datums/status_effects/neutral.dm b/yogstation/code/datums/status_effects/neutral.dm
index 258cf221dc3c..809cb5ab86c2 100644
--- a/yogstation/code/datums/status_effects/neutral.dm
+++ b/yogstation/code/datums/status_effects/neutral.dm
@@ -46,7 +46,6 @@
var/icon/temp = icon(sniffee.icon, sniffee.icon_state)
var/image/scent_glow = image(temp, layer = ABOVE_MOB_LAYER, loc = sniffee)
scent_glow.copy_overlays(sniffee)
- scent_glow.layer = HUD_LAYER
scent_glow.plane = HUD_PLANE
scent_glow.appearance_flags = NO_CLIENT_COLOR
scent_glow.color = scent_color
@@ -90,7 +89,6 @@
if(!red_thirst)
red_thirst = owner.overlay_fullscreen("thirsting", /atom/movable/screen/fullscreen/brute, 4)
red_thirst.alpha = 0
- red_thirst.layer = HUD_LAYER
red_thirst.plane = HUD_PLANE
animate(red_thirst, alpha = 255, time = 1 SECONDS, easing = EASE_IN) //fade IN
to_chat(owner, span_userdanger("As the scent of your prey overwhelms your sense of smell, the thrill of the hunt empowers you!"))
diff --git a/yogstation/code/datums/world_topic.dm b/yogstation/code/datums/world_topic.dm
index 8fc07efea64f..97e34eb09d5e 100644
--- a/yogstation/code/datums/world_topic.dm
+++ b/yogstation/code/datums/world_topic.dm
@@ -31,6 +31,8 @@ GLOBAL_VAR_INIT(mentornoot, FALSE)
var/list/admin_keys = list()
for(var/adm in GLOB.permissions.admins)
var/client/C = adm
+ if(isnull(C)) //Yog we're having issues with null holders breaking adminwho, so let's skip nulls
+ continue
if(input["adminchannel"])
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
else if(!C.holder.fakekey)
diff --git a/yogstation/code/game/area/areas/centcom.dm b/yogstation/code/game/area/areas/centcom.dm
index d6c5303cdac5..96b362c10deb 100644
--- a/yogstation/code/game/area/areas/centcom.dm
+++ b/yogstation/code/game/area/areas/centcom.dm
@@ -8,7 +8,6 @@
noteleport = TRUE
flags_1 = NONE
ambience_index = AMBIENCE_DANGER
- dynamic_lighting = DYNAMIC_LIGHTING_FORCED
/area/yogs/infiltrator_base/poweralert(state, obj/source)
return
@@ -23,7 +22,6 @@
/area/yogs/infiltrator_base/outside
name = "Syndicate Base X-77"
icon_state = "yellow"
- dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
/area/brazil
name = "Location Unresolved"
diff --git a/yogstation/code/game/area/areas/ruins/jungleland.dm b/yogstation/code/game/area/areas/ruins/jungleland.dm
new file mode 100644
index 000000000000..f59072b4d32b
--- /dev/null
+++ b/yogstation/code/game/area/areas/ruins/jungleland.dm
@@ -0,0 +1,8 @@
+/area/ruin/unpowered/ivymen
+ icon_state = "red"
+ static_lighting = FALSE
+ base_lighting_alpha = 255
+
+/area/ruin/unpowered/tar_temple
+ icon_state = "red"
+ noteleport = TRUE
diff --git a/yogstation/code/game/gamemodes/battle_royale/battleroyale.dm b/yogstation/code/game/gamemodes/battle_royale/battleroyale.dm
index e9c243a8d913..1810591c78ae 100644
--- a/yogstation/code/game/gamemodes/battle_royale/battleroyale.dm
+++ b/yogstation/code/game/gamemodes/battle_royale/battleroyale.dm
@@ -18,7 +18,9 @@ GLOBAL_VAR(final_zone)
Be the last man standing at the end of the game to win."
var/antag_datum_type = /datum/antagonist/battleroyale
var/list/queued = list() //Who is queued to enter?
- var/list/randomweathers = list("royale science", "royale medbay", "royale service", "royale cargo", "royale security", "royale engineering", "royale bridge")
+ var/list/roundstart_traits = list(TRAIT_XRAY_VISION, TRAIT_NOHUNGER, TRAIT_NOBREATH, TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE, TRAIT_SAFEWELD, TRAIT_HARDLY_WOUNDED)
+ var/list/randomweathers = list("royale science", "royale medbay", "royale service", "royale cargo", "royale security", "royale engineering", "royale the bridge")
+ var/list/weathered = list() //list of all the places currently covered by weather
var/stage_interval = 3 MINUTES
var/loot_interval = 75 SECONDS //roughly the time between loot drops
var/borderstage = 0
@@ -30,7 +32,7 @@ GLOBAL_VAR(final_zone)
title_icon = "ss13"
/datum/game_mode/fortnite/pre_setup()
- GLOB.stormdamage = 2
+ GLOB.stormdamage = 3
INVOKE_ASYNC(src, PROC_REF(spawn_bus))//so if a runtime happens with the spawn_bus proc, the rest of pre_setup still happens
for(var/mob/L in GLOB.player_list)
if(!L.mind || !L.client || isobserver(L))
@@ -65,11 +67,8 @@ GLOBAL_VAR(final_zone)
virgin.current.forceMove(GLOB.thebattlebus)
original_num ++
virgin.current.apply_status_effect(STATUS_EFFECT_DODGING_GAMER) //to prevent space from hurting
- ADD_TRAIT(virgin.current, TRAIT_XRAY_VISION, "virginity") //so they can see where theyre dropping
- ADD_TRAIT(virgin.current, TRAIT_NOHUNGER, "getthatbreadgamers") //so they don't need to worry about annoyingly running out of food
- ADD_TRAIT(virgin.current, TRAIT_NOBREATH, "breathingiscringe") //because atmos is silly and stupid and goofy and bad
- ADD_TRAIT(virgin.current, TRAIT_NOSOFTCRIT, "KEEP GOING, FIGHT MORE") //because no sleepy
- ADD_TRAIT(virgin.current, TRAIT_NOHARDCRIT, "Son always remember, dying is gay. @margot") //fight fight fight
+ for(var/i in roundstart_traits)
+ ADD_TRAIT(virgin.current, i, "battleroyale")
REMOVE_TRAIT(virgin.current, TRAIT_PACIFISM, ROUNDSTART_TRAIT) //FINE, i guess pacifists get to fight too
virgin.current.update_sight()
to_chat(virgin.current, " You are now in the battle bus! Click it to exit.")
@@ -161,32 +160,30 @@ GLOBAL_VAR(final_zone)
if(9)
set_security_level("epsilon")
+ var/datum/weather/royale/W
switch(borderstage)
if(0)
- SSweather.run_weather("royale start",2)
+ W = SSweather.run_weather("royale start",2)
if(1)
- SSweather.run_weather("royale maint",2)
+ W = SSweather.run_weather("royale maint",2)
if(2 to 7)//close off the map
var/weather = pick(randomweathers)
- SSweather.run_weather(weather, 2)
+ W = SSweather.run_weather(weather, 2)
randomweathers -= weather
if(8)
- var/weather = pick(randomweathers) //whichever one is left
- weather = replacetext(weather, "royale ", "")
- if(weather == "bridge")
- weather = "the bridge"
- GLOB.final_zone = weather
- SSweather.run_weather("royale hallway", 2)//force them to the final department
+ GLOB.final_zone = replacetext(pick(randomweathers), "royale ", "")
+ W = SSweather.run_weather("royale hallway", 2)//force them to the final department
if(9)//finish it
SSweather.run_weather("royale centre", 2)
+ if(W && istype(W))
+ weathered |= W.areasToWeather
+
if(borderstage)//doesn't cull during round start
ItemCull()
+ GLOB.stormdamage *= 1.1
borderstage++
-
- if(borderstage % 2 == 0) //so it scales, but not too hard
- GLOB.stormdamage *= 1.5
if(borderstage <= 9)
var/remainingpercent = LAZYLEN(GLOB.battleroyale_players) / original_num
@@ -260,6 +257,8 @@ GLOBAL_VAR(final_zone)
loot_spawn()
addtimer(CALLBACK(src, PROC_REF(loot_drop)), loot_interval)//literally just keep calling it
+/// How many tiles in a given room gives a 100% guaranteed crate
+#define ROOMSIZESCALING 60
/datum/game_mode/fortnite/proc/loot_spawn()
for(var/area/lootlake as anything in GLOB.areas)
if(!is_station_level(lootlake.z))//don't spawn it if it isn't a station level
@@ -270,7 +269,10 @@ GLOBAL_VAR(final_zone)
continue
if(istype(lootlake, /area/maintenance))//no maintenance, it's too large, it'd lag the hell out of the server and it's not as popular as main hallways
continue //also, ideally keeps people out of maints, and in larger open areas that are more interesting
- var/amount = round(LAZYLEN(lootlake.get_contained_turfs()) / 40)//so bigger areas spawn more crates
+ if(is_type_in_list(lootlake, weathered))
+ continue //if the area is covered with a storm, don't spawn loot (less lag)
+ var/number = LAZYLEN(lootlake.get_contained_turfs())//so bigger areas spawn more crates
+ var/amount = round(number / ROOMSIZESCALING) + prob(((number % ROOMSIZESCALING)/ROOMSIZESCALING)*100) //any remaining tiles gives a probability to have an extra crate
for(var/I = 0, I < amount, I++)
var/turf/turfy = pick(get_area_turfs(lootlake))
for(var/L = 0, L < 15, L++)//cap so it doesn't somehow end in an infinite loop
@@ -279,6 +281,8 @@ GLOBAL_VAR(final_zone)
turfy = pick(get_area_turfs(lootlake))
addtimer(CALLBACK(src, PROC_REF(drop_pod), turfy), rand(1,50))//to even out the lag that creating a drop pod causes
+#undef ROOMSIZESCALING
+
/datum/game_mode/fortnite/proc/drop_pod(turf/turfy)
var/obj/structure/closet/supplypod/centcompod/pod = new()
new /obj/structure/closet/crate/battleroyale(pod)
@@ -373,9 +377,12 @@ GLOBAL_VAR(final_zone)
name = "very cool tie (do not remove)"
desc = "Totally not just here for keeping track of kills."
var/datum/antagonist/battleroyale/last_hit
- clothing_traits = (TRAIT_NODROP)
resistance_flags = INDESTRUCTIBLE //no escaping
+/obj/item/clothing/neck/tie/gamer/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, "I will kill you if you take this off somehow") //can't be a clothing trait because those get applied to the mob, not the item
+
/obj/item/clothing/neck/tie/gamer/equipped(mob/user, slot)
. = ..()
RegisterSignal(user, COMSIG_LIVING_DEATH, PROC_REF(death))
@@ -448,7 +455,7 @@ GLOBAL_VAR(final_zone)
/obj/structure/battle_bus/proc/exit(mob/living/carbon/human/Ltaker)
Ltaker.forceMove(get_turf(src))
- REMOVE_TRAIT(Ltaker, TRAIT_XRAY_VISION, "virginity")
+ REMOVE_TRAIT(Ltaker, TRAIT_XRAY_VISION, "battleroyale")
Ltaker.update_sight()
SEND_SOUND(Ltaker, 'yogstation/sound/effects/battleroyale/exitbus.ogg')
diff --git a/yogstation/code/game/gamemodes/battle_royale/loot.dm b/yogstation/code/game/gamemodes/battle_royale/loot.dm
index a949edca50c4..640699b352c7 100644
--- a/yogstation/code/game/gamemodes/battle_royale/loot.dm
+++ b/yogstation/code/game/gamemodes/battle_royale/loot.dm
@@ -120,94 +120,171 @@ GLOBAL_LIST_INIT(battleroyale_armour, list(
//Weight of -3 - strong suits with additional utility
/obj/item/clothing/suit/space/hardsuit/shielded = -3,
/obj/item/clothing/suit/space/hardsuit/shielded/syndi = -3,
- /obj/item/clothing/suit/wizrobe/armor = -3,
//Weight of -4 - nukie level shit
/obj/item/shield/energy = -4,
/obj/item/clothing/suit/space/hardsuit/syndi/elite = -4,
/obj/item/clothing/suit/space/hardsuit/carp/dragon = -4,
- //Weight of -5 - ERT level shit
+
/obj/item/shield/energy/bananium = -5,
- /obj/item/clothing/suit/space/hardsuit/ert/sec = -5,
- /obj/item/clothing/suit/space/hardsuit/ert/engi = -5,
- /obj/item/clothing/suit/space/hardsuit/ert/med = -5,
- /obj/item/clothing/suit/space/hardsuit/ert/jani = -5,
- /obj/item/clothing/suit/space/hardsuit/ert/paranormal = -5,
- //Weight of -8 - you won't see this, but if you do you become a (still-killable) god (weight of 1 after the final ring closes)
- /obj/item/clothing/suit/space/hardsuit/deathsquad = -8,
- /obj/item/clothing/suit/space/hardsuit/shielded/swat = -8,
- /obj/item/clothing/suit/space/hardsuit/shielded/swat/honk = -8,
+ //Weight of -6 - ERT level shit
+ /obj/item/clothing/suit/space/hardsuit/ert/sec = -6,
+ /obj/item/clothing/suit/space/hardsuit/ert/engi = -6,
+ /obj/item/clothing/suit/space/hardsuit/ert/med = -6,
+ /obj/item/clothing/suit/space/hardsuit/ert/jani = -6,
+ /obj/item/clothing/suit/space/hardsuit/ert/paranormal = -6,
+ //Weight of -7 - you won't see this, but if you do you become a (still-killable) god (weight of 2 after the final ring closes)
+ /obj/item/clothing/suit/space/hardsuit/deathsquad = -7,
+ /obj/item/clothing/suit/space/hardsuit/shielded/swat = -7,
+ /obj/item/clothing/suit/space/hardsuit/shielded/swat/honk = -7,
))
GLOBAL_LIST_INIT(battleroyale_weapon, list(
+ //melee weapons
+ //meme weapons (can probably just be dead items) (no higher than 10 force)
/obj/item/kitchen/knife/carrotshiv = 5,
- /obj/item/storage/toolbox/mechanical = 5,
- /obj/item/weldingtool/experimental = 5,
-
- /obj/item/kitchen/knife/combat/survival = 4,
- /obj/item/melee/baseball_bat = 4,
- /obj/item/melee/spear = 4,
- /obj/item/melee/spear/bonespear = 4,
-
- /obj/item/bigspoon = 3,
- /obj/item/kitchen/knife/combat = 3,
- /obj/item/nullrod/hammer = 3,
- /obj/item/nullrod/tribal_knife = 3,
- /obj/item/nullrod/vibro = 3,
-
- /obj/item/pen/red/edagger = 2,
- /obj/item/flamethrower/full/tank = 2,
- /obj/item/melee/chainsaw = 2,
- /obj/item/fireaxe/metal_h2_axe = 2,
- /obj/item/nullrod/whip = 2,
-
- /obj/item/gun/ballistic/shotgun/riot = 1,
- /obj/item/gun/ballistic/revolver/detective = 1,
- /obj/item/melee/baseball_bat/homerun = 1,
- /obj/item/fireaxe = 1,
- /obj/item/nullrod/talking = 1,
- /obj/item/clothing/gloves/powerfist/filled = 1,
-
- /obj/item/melee/vxtvulhammer = 0,
- /obj/item/gun/ballistic/automatic/pistol = 0,
- /obj/item/gun/ballistic/shotgun/doublebarrel = 0,
+ /obj/item/toy/boomerang/violent = 5,
+ /obj/item/melee/flyswatter = 5,
+ /obj/item/banhammer = 5,
+ /obj/item/kirbyplants/random = 5,
+ /obj/item/clothing/glasses/sunglasses/gar = 5,
+ /obj/item/reagent_containers/food/drinks/trophy/silver_cup = 5,
+ /obj/item/storage/bag/money = 5,
+ /obj/item/pickaxe/mini = 5,
+ /obj/item/scalpel/advanced = 5, //channel your inner lizbay
+ /obj/item/toy/plush/goatplushie/angry/guardgoat = 5,
+
+ //weapons that are better than punches but only by a really small bit
+ /obj/item/mop/advanced = 4,
+ /obj/item/scythe = 4,
+ /obj/item/crowbar/large = 4,
+ /obj/item/hatchet = 4,
+ /obj/item/instrument/eguitar = 4,
+ /obj/item/melee/fryingpan = 4,
+ /obj/item/oar = 4,
+ /obj/item/weldingtool/experimental = 4,
+ /obj/item/nullrod/fedora = 4, //you throw it away
+ /obj/item/bigspoon = 4,
+ /obj/item/nullrod/tribal_knife = 4,
+ /obj/item/storage/toolbox/mechanical = 4,
+ /obj/item/statuebust = 4,
+ /obj/item/statuebust/hippocratic = 4,
+ /obj/item/melee/chainofcommand/tailwhip = 4, //YAY RACISM
+ /obj/item/melee/chainofcommand/tailwhip/kitty = 4,
+ /obj/item/tailclub = 4,
+ /obj/item/melee/skateboard = 4,
+ /obj/item/melee/skateboard/pro = 4,
+ /obj/item/reagent_containers/food/drinks/trophy/gold_cup = 4,
+ /obj/item/pickaxe = 4,
+ /obj/item/storage/secure/briefcase/syndie = 4,
+
+ /obj/item/kitchen/knife/combat/survival = 3,
+ /obj/item/storage/belt/sabre = 3,
+ /obj/item/melee/baseball_bat = 3,
+ /obj/item/nullrod/spear = 3,
+ /obj/item/claymore/bone = 3,
+ /obj/item/melee/skateboard/hoverboard = 3,
+ /obj/item/pickaxe/silver = 3,
+ /obj/item/melee/stinger_sword = 3,
+ /obj/item/adamantineshield = 3,
+ /obj/item/rune_scimmy = 3, //starts at 20 force, surprisingly good
+ /obj/item/storage/toolbox/mechanical/old/clean = 3,
+
+ /obj/item/pickaxe/diamond = 2,
+ /obj/item/kitchen/knife/combat = 2,
+ /obj/item/melee/spear = 2,
+ /obj/item/melee/spear/bamboospear = 2,
+ /obj/item/nullrod/hammer = 2,
+ /obj/item/nullrod/vibro = 2,
+ /obj/item/nullrod/claymore = 2,
+ /obj/item/nullrod/dualsword = 2,
+ /obj/item/melee/baseball_bat/homerun = 2,
+ /obj/item/melee/cutlass = 2,
+ /obj/item/melee/transforming/cleaving_saw = 2,
+ /obj/item/storage/toolbox/syndicate = 2,
+ /obj/item/kitchen/knife/rainbowknife = 2, //has possible cloning damage, watch out
+
+ /obj/item/melee/spear/bonespear = 1,
+ /obj/item/stinger_trident = 1,
+ /obj/item/pen/red/edagger = 1,
+ /obj/item/melee/chainsaw = 1,
+ /obj/item/fireaxe/metal_h2_axe = 1,
+ /obj/item/nullrod/whip = 1,
+ /obj/item/katana/basalt = 1,
+
+ /obj/item/melee/spear/bonespear/stalwartpike = 0,
+ /obj/item/fireaxe = 0,
+ /obj/item/clothing/gloves/powerfist/filled = 0,
/obj/item/melee/transforming/energy/sword = 0,
- /obj/item/gun/energy/laser/retro/old = 0,
+ /obj/item/switchblade = 0,
+ /obj/item/melee/spear/bonespear/chitinspear = 0,
/obj/item/melee/baseball_bat/metal_bat = -1,
/obj/item/melee/ghost_sword = -1, //snowballer
- /obj/item/gun/ballistic/shotgun/automatic/combat = -1,
- /obj/item/gun/energy/laser = -1,
+ /obj/item/cane/cursed = -1, //shhhhh, don't worry about it, it'll be fine
+ /obj/item/cult_spear = -1,
+ /obj/item/melee/spear/grey_tide = -1,
+ /obj/item/reagent_containers/food/snacks/powercrepe = -1, //lol what?
- /obj/item/gun/ballistic/shotgun/automatic/combat/compact = -2,
- /obj/item/gun/ballistic/automatic/wt550 = -2,
- /obj/item/gun/ballistic/shotgun/bulldog/unrestricted = -2,
- /obj/item/gun/energy/kinetic_accelerator/crossbow = -2,
/obj/item/fireaxe/energy = -2, //lol, this is the energy fire axe, not the debug energy axe
-
- /obj/item/gun/ballistic/revolver = -3,
- /obj/item/gun/ballistic/bow/energy = -3,
- /obj/item/gun/energy/laser/captain = -3,
-
- /obj/item/gun/ballistic/automatic/m90/unrestricted = -3,
- /obj/item/gun/ballistic/automatic/pistol/deagle = -3,
- /obj/item/gun/ballistic/automatic/ar = -3,
- /obj/item/gun/ballistic/automatic/c20r/unrestricted = -3,
- /obj/item/gun/ballistic/automatic/mini_uzi = -3,
- /obj/item/gun/ballistic/automatic/tommygun = -3,
- /obj/item/gun/ballistic/rifle/sniper_rifle = -3, //Not a stun anymore
+ /obj/item/melee/vxtvulhammer = -2,
+ /obj/item/singularityhammer = -2,
+
/obj/item/vibro_weapon = -3, //Strong melee weapon, but not enough to be -5
+ /obj/item/autosurgeon/arm/syndicate/syndie_mantis = -3,
+ /obj/item/melee/chainsaw/demon = -3,
- /obj/item/autosurgeon/arm/syndicate/syndie_mantis = -4,
/obj/item/melee/dualsaber = -4,
- /obj/item/battleroyale/itemspawner/breakbow = -4, //Strong melee weapon, along with infinte arrows
- /obj/item/gun/energy/beam_rifle = -4,
/obj/item/melee/fryingpan/bananium = -5,
/obj/item/his_grace = -5,
/obj/item/melee/chainsaw/doomslayer = -5,
- /obj/item/gun/ballistic/bow/energy/ert = -5,
- /obj/item/minigunpack = -5,
- /obj/item/minigunbackpack = -5,
+
+ //guns (no higher than 0, first wave should never have guns)
+ /obj/item/gun/ballistic/shotgun/doublebarrel/improvised = 0,
+ /obj/item/gun/ballistic/shotgun/doublebarrel/improvised/sawn = 0,
+ /obj/item/gun/ballistic/revolver/detective = 0,
+ /obj/item/flamethrower/full/tank = 0,
+
+ /obj/item/gun/ballistic/automatic/pistol = -1,
+ /obj/item/gun/ballistic/shotgun/doublebarrel = -1,
+
+ /obj/item/gun/ballistic/shotgun/automatic/combat = -2,
+ /obj/item/gun/energy/laser = -2,
+ /obj/item/gun/energy/laser/retro/old = -2,
+
+ /obj/item/gun/ballistic/shotgun/automatic/combat/compact = -3,
+ /obj/item/gun/ballistic/automatic/wt550 = -3,
+ /obj/item/gun/ballistic/shotgun/bulldog/unrestricted = -3,
+ /obj/item/gun/energy/kinetic_accelerator/crossbow = -3,
+ /obj/item/gun/ballistic/automatic/proto/unrestricted = -3,
+
+ /obj/item/gun/ballistic/revolver = -4,
+ /obj/item/gun/ballistic/bow/energy = -4,
+ /obj/item/gun/energy/laser/captain = -4,
+ /obj/item/gun/ballistic/automatic/m90/unrestricted = -4,
+ /obj/item/gun/ballistic/automatic/pistol/deagle = -4,
+ /obj/item/gun/ballistic/automatic/c20r/unrestricted = -4,
+ /obj/item/gun/ballistic/automatic/mini_uzi = -4,
+ /obj/item/gun/ballistic/automatic/tommygun = -4,
+ /obj/item/gun/ballistic/automatic/surplus = -4,
+ /obj/item/gun/ballistic/automatic/laser = -4,
+
+ /obj/item/battleroyale/itemspawner/breakbow = -5, //Strong melee weapon, along with infinte arrows
+ /obj/item/gun/ballistic/automatic/l6_saw/unrestricted = -5,
+ /obj/item/gun/ballistic/automatic/ar = -5,
+ /obj/item/gun/ballistic/automatic/lwt650 = -5,
+ /obj/item/gun/ballistic/automatic/k41s = -5,
+
+ /obj/item/gun/energy/beam_rifle = -6,
+ /obj/item/gun/ballistic/rifle/sniper_rifle = -6,
+ /obj/item/gun/ballistic/automatic/laser/lasgun = -6,
+ /obj/item/gun/ballistic/automatic/laser/laspistol = -6,
+
+ /obj/item/gun/ballistic/bow/energy/ert = -7,
+ /obj/item/minigunpack = -7,
+ /obj/item/minigunbackpack = -7,
+ /obj/item/gun/ballistic/automatic/laser/longlas = -7,
+ /obj/item/gun/ballistic/automatic/laser/hotshot = -7,
))
GLOBAL_LIST_INIT(battleroyale_healing, list(//this one doesn't scale because max health doesn't scale, there's also less healing items than other items
@@ -228,6 +305,7 @@ GLOBAL_LIST_INIT(battleroyale_healing, list(//this one doesn't scale because max
/obj/item/storage/firstaid/brute = 3,
/obj/item/reagent_containers/autoinjector/medipen/stimpack = 3,
/obj/item/clothing/mask/cigarette/syndicate = 3,
+ /obj/item/bonesetter = 3,
/obj/item/storage/firstaid/advanced = 2,
/obj/item/reagent_containers/autoinjector/medipen/survival = 2,
/obj/item/organ/heart/cursed/wizard = 2, //Rarely used, albiet the healing is incredibly strong
@@ -280,6 +358,7 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
/obj/item/slimecross/stabilized/red = -1,
/obj/item/autosurgeon/reviver = -1,
/obj/effect/spawner/lootdrop/ammobox = -1,
+ /obj/item/slime_sling = -1,
/obj/item/multisurgeon/airshoes = -2,
/obj/item/grenade/syndieminibomb = -2,
@@ -292,17 +371,13 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
/obj/item/battleroyale/martial/plasmaman = -2,
/obj/item/battleroyale/martial/lizard = -2,
/obj/item/book/granter/action/spell/summonitem = -2,
- /obj/item/nullrod/hermes = -2,
/obj/item/nullrod/unrestricted = -2,
/obj/effect/spawner/lootdrop/ammobox = -2,
+ /obj/item/stand_arrow = -2, //possibly OP but it's 50/50 to get dusted
- /obj/item/antag_spawner/nuke_ops/borg_tele/medical = -3,
- /obj/item/antag_spawner/nuke_ops/borg_tele/assault = -3,
- /obj/item/antag_spawner/nuke_ops/borg_tele/saboteur = -3,
/obj/item/storage/box/syndie_kit/augmentation = -3,
/obj/item/storage/backpack/duffelbag/syndie/c4 = -3, //C4 Is kind of useless when you have AA
/obj/item/battleroyale/itemspawner/construct = -3,
- /obj/item/stand_arrow = -3, //possibly OP but it's 50/50 to get dusted
/obj/item/book/granter/action/spell/forcewall = -3,
/obj/item/antag_spawner/slaughter_demon = -3, //why the hell not
/obj/item/antag_spawner/slaughter_demon/laughter = -3, //people still get disqualified, but they at least get to come back
@@ -310,25 +385,30 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
/obj/effect/spawner/lootdrop/stronggene = -3,
/obj/item/gun/magic/wand/resurrection = -3, //the person revived isn't able to win, but why not, maybe they help
/obj/item/antag_spawner/contract = -3, //might be a terrible idea to add this
+ /obj/item/nullrod/hermes = -3,
/obj/item/battleroyale/extraarm = -3,
+ /obj/item/clothing/head/yogs/tar_king_crown = -3,
/obj/item/guardiancreator/tech/random = -4,
/obj/item/storage/belt/military/shadowcloak = -4, // Very strong for short bursts
/obj/item/implanter/empshield = -4, //EMP Shields are fairly useful, especially with the now wealth of xray / thermal eyes, among others
/obj/item/guardiancreator/carp/random = -4,
- /obj/item/bodypart/l_arm/robot/buster = -4, // Buster is strong, but most people aren't too good with it. Especially useful for closing the gap
/obj/item/battleroyale/martial/ipc = -4,
- /obj/item/book/granter/martial/carp = -4,
- /obj/item/battleroyale/martial/worldbreaker = -4, // Shaking the ground of Moria
/obj/item/necromantic_stone = -4,
/obj/item/slimecross/stabilized/sepia = -4,
+ /obj/item/melee/skateboard/hoverboard/admin = -4,
/obj/item/grenade/spawnergrenade/manhacks = -5,
/obj/item/slimecross/stabilized/bluespace = -5,
/obj/machinery/syndicatebomb = -5,
/obj/item/stand_arrow/safe = -5,
/obj/item/mdrive = -5, //get out of jail free card
- /obj/item/autosurgeon/syndicate/spinalspeed = -5, // No opportunity cost speed boost
+ /obj/item/book/granter/martial/carp = -5,
+ /obj/item/battleroyale/martial/worldbreaker = -5, // Shaking the ground of Moria
+ /obj/item/bodypart/l_arm/robot/buster = -5,
+ /obj/item/demon_core = -5,
+
+ /obj/item/autosurgeon/syndicate/spinalspeed = -6, // No opportunity cost speed boost
/obj/item/storage/belt/wands/full = -7, //not quite spellbook, but some of these wands are FUCKED
@@ -338,8 +418,6 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
/obj/structure/closet/crate/battleroyale
name = "Supply Crate"
icon_state = "trashcart"
- light_range = 5
- light_color = LIGHT_COLOR_YELLOW //Let it glow, let it glow
dense_when_open = FALSE
/obj/structure/closet/crate/battleroyale/PopulateContents()
@@ -364,7 +442,12 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
add_atom_colour(LIGHT_COLOR_GREEN, FIXED_COLOUR_PRIORITY)
if(type != 5)//don't remove healing crates
- addtimer(CALLBACK(src, PROC_REF(declutter)), 6 MINUTES)//remove obsolete outscaled crates after a bit
+ addtimer(CALLBACK(src, PROC_REF(declutter)), 3 MINUTES)//remove obsolete outscaled crates after a bit
+
+ if(rand(0, 100000) == 1)
+ for(var/i = 0, i < 10, i++)
+ new /mob/living/simple_animal/hostile/retaliate/clown(src)// you've been clowned
+ return //no items, just clowns
var/selected
switch(type)
@@ -378,13 +461,11 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
selected = pickweightAllowZero(GLOB.battleroyale_armour)
new selected(src)
- if(3)//allrounder, technically has more items than the others
+ if(3)//allrounder, no longer has healing since healing crates don't despawn
selected = pickweightAllowZero(GLOB.battleroyale_weapon)
new selected(src)
selected = pickweightAllowZero(GLOB.battleroyale_armour)
new selected(src)
- selected = pickweightAllowZero(GLOB.battleroyale_healing)
- new selected(src)
selected = pickweightAllowZero(GLOB.battleroyale_utility)
new selected(src)
@@ -398,10 +479,6 @@ GLOBAL_LIST_INIT(battleroyale_utility, list(//bombs, explosives, anything that's
selected = pickweightAllowZero(GLOB.battleroyale_healing)
new selected(src)
- if(rand(0, 10000) == 1)
- for(var/i = 0, i < 10, i++)
- new /mob/living/simple_animal/hostile/retaliate/clown(src)// you've been clowned
-
/obj/structure/closet/crate/battleroyale/proc/declutter()
if(QDELETED(src))
return
diff --git a/yogstation/code/game/gamemodes/battle_royale/storm.dm b/yogstation/code/game/gamemodes/battle_royale/storm.dm
index 0c34a168b2d5..d7bc1bf4ecaf 100644
--- a/yogstation/code/game/gamemodes/battle_royale/storm.dm
+++ b/yogstation/code/game/gamemodes/battle_royale/storm.dm
@@ -92,7 +92,7 @@
areaTypesToWeather = list(/area/quartermaster, /area/vacant_room, /area/shuttle)
/datum/weather/royale/bridge
- name = "royale bridge"
+ name = "royale the bridge"
telegraph_message = span_narsiesmall("The storm is closing in, get away from the bridge!")
areasToWeather = list(/area/teleporter, /area/crew_quarters/heads/captain, /area/crew_quarters/heads/hop)
areaTypesToWeather = list(/area/bridge)
@@ -110,4 +110,5 @@
/datum/weather/royale/final
name = "royale centre" //final wave, takes out the centre ring.
+ telegraph_duration = 30 SECONDS //the zone annihilates people, give some time for them to "make their final stand"
telegraph_message = span_narsiesmall("The eye of the storm is closing, make your final stand!")
diff --git a/yogstation/code/game/gamemodes/gangs/gangtool.dm b/yogstation/code/game/gamemodes/gangs/gangtool.dm
index f35fd60a665f..39d69b0e1d11 100644
--- a/yogstation/code/game/gamemodes/gangs/gangtool.dm
+++ b/yogstation/code/game/gamemodes/gangs/gangtool.dm
@@ -226,13 +226,13 @@
gang.recalls--
return TRUE
- to_chat(user, span_info("[icon2html(src, loc)]No response recieved. Emergency shuttle cannot be recalled at this time."))
+ to_chat(user, span_info("[icon2html(src, loc)]No response received. Emergency shuttle cannot be recalled at this time."))
return
/obj/item/gangtool/proc/recallchecks(mob/user)
if(!can_use(user))
return
- if(SSshuttle.emergencyNoRecall)
+ if(SSshuttle.emergency_no_recall)
return
if(!is_station_level(user.z)) //Shuttle can only be recalled while on station
to_chat(user, span_warning("[icon2html(src, user)]Error: Device out of range of station communication arrays."))
diff --git a/yogstation/code/game/gamemodes/objective.dm b/yogstation/code/game/gamemodes/objective.dm
index 2b446309e9f8..f5e427b3c000 100644
--- a/yogstation/code/game/gamemodes/objective.dm
+++ b/yogstation/code/game/gamemodes/objective.dm
@@ -1,4 +1,4 @@
-GLOBAL_LIST_INIT(infiltrator_objective_areas, typecacheof(list(/area/yogs/infiltrator_base, /area/syndicate_mothership, /area/shuttle/yogs/stealthcruiser)))
+GLOBAL_LIST_INIT(infiltrator_objective_areas, typecacheof(list(/area/yogs/infiltrator_base, /area/centcom/syndicate_mothership, /area/shuttle/yogs/stealthcruiser)))
/datum/objective/assassinate/internal/check_completion()
if(..())
diff --git a/yogstation/code/game/gamemodes/shadowling/shadowling.dm b/yogstation/code/game/gamemodes/shadowling/shadowling.dm
index 44b224ba7e78..8c49e5e19ca0 100644
--- a/yogstation/code/game/gamemodes/shadowling/shadowling.dm
+++ b/yogstation/code/game/gamemodes/shadowling/shadowling.dm
@@ -146,7 +146,7 @@ Made by Xhuis
inherent_traits = list(TRAIT_NOGUNS, TRAIT_RESISTCOLD, TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE, TRAIT_NOBREATH, TRAIT_RADIMMUNE, TRAIT_VIRUSIMMUNE, TRAIT_PIERCEIMMUNE)
no_equip = list(ITEM_SLOT_MASK, ITEM_SLOT_EYES, ITEM_SLOT_GLOVES, ITEM_SLOT_FEET, ITEM_SLOT_ICLOTHING, ITEM_SLOT_SUITSTORE)
nojumpsuit = TRUE
- mutanteyes = /obj/item/organ/eyes/night_vision/alien/sling
+ mutanteyes = /obj/item/organ/eyes/alien/sling
burnmod = 1.5 //1.5x burn damage, 2x is excessive
heatmod = 1.5
var/mutable_appearance/eyes_overlay
diff --git a/yogstation/code/game/gamemodes/vampire/vampire_bat.dm b/yogstation/code/game/gamemodes/vampire/vampire_bat.dm
index 75fd9ccf8341..80abc4fb8fad 100644
--- a/yogstation/code/game/gamemodes/vampire/vampire_bat.dm
+++ b/yogstation/code/game/gamemodes/vampire/vampire_bat.dm
@@ -13,8 +13,6 @@
maxHealth = 20
health = 20
speed = 0
- see_in_dark = 10
- lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
harm_intent_damage = 7
melee_damage_lower = 5
melee_damage_upper = 7
diff --git a/yogstation/code/game/gamemodes/vampire/vampire_other.dm b/yogstation/code/game/gamemodes/vampire/vampire_other.dm
index de7fcde7ba42..ae80cae4f84f 100644
--- a/yogstation/code/game/gamemodes/vampire/vampire_other.dm
+++ b/yogstation/code/game/gamemodes/vampire/vampire_other.dm
@@ -8,7 +8,7 @@
name = "Vampire Coat"
desc = "What is a man? A miserable little pile of secrets."
mob_overlay_icon = 'yogstation/icons/mob/clothing/suit/suit.dmi'
- icon = 'yogstation/icons/obj/clothing/suits.dmi'
+ icon = 'icons/obj/clothing/suits/suits.dmi'
icon_state = "draculacoat"
item_state = "draculacoat"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
diff --git a/yogstation/code/game/machinery/computer/arcade.dm b/yogstation/code/game/machinery/computer/arcade.dm
index 08fcf38fae5c..4104e85d6f99 100644
--- a/yogstation/code/game/machinery/computer/arcade.dm
+++ b/yogstation/code/game/machinery/computer/arcade.dm
@@ -3,7 +3,6 @@
desc = "An arcade machine that generates grids. It seems that the machine sparks and screeches when a grid is generated, as if it cannot cope with the intensity of generating the grid."
icon_state = "arcade"
circuit = /obj/item/circuitboard/computer/arcade/minesweeper
-
var/datum/minesweeper/board
/obj/machinery/computer/arcade/minesweeper/Initialize(mapload)
@@ -12,11 +11,22 @@
board.emaggable = TRUE
board.host = src
+/obj/machinery/computer/arcade/minesweeper/screwdriver_act(mob/living/user, obj/item/I)
+ if(obj_flags & EMAGGED)
+ explosion(get_turf(src), 0, 1, 5, flame_range = 5)
+ else
+ . = ..()
+ return
+
/obj/machinery/computer/arcade/minesweeper/Destroy(force)
- board.host = null
- QDEL_NULL(board)
+ if(obj_flags & EMAGGED)
+ explosion(get_turf(src), 0, 1, 5, flame_range = 5)
+ else
+ board.host = null
+ QDEL_NULL(board)
. = ..()
+
/obj/machinery/computer/arcade/minesweeper/interact(mob/user, special_state)
. = ..()
if(!is_operational())
@@ -93,8 +103,7 @@
if(!diff)
return
board.play_snd('yogstation/sound/arcade/minesweeper_boardpress.ogg')
- board.difficulty = diff
- return TRUE
+ return board.change_difficulty(diff)
if("PRG_height")
var/cin = params["height"]
diff --git a/yogstation/code/game/machinery/doors/airlock.dm b/yogstation/code/game/machinery/doors/airlock.dm
index f78cfdaa8ac3..5aef9b4dd57b 100644
--- a/yogstation/code/game/machinery/doors/airlock.dm
+++ b/yogstation/code/game/machinery/doors/airlock.dm
@@ -42,7 +42,7 @@
if(dir & WEST)
return WEST
-/obj/machinery/door/airlock/Moved()
+/obj/machinery/door/airlock/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
if(brace)
brace.remove()
return ..()
diff --git a/yogstation/code/game/machinery/doors/spacepod.dm b/yogstation/code/game/machinery/doors/spacepod.dm
index b4afa71809c1..c6edfe83c1fc 100644
--- a/yogstation/code/game/machinery/doors/spacepod.dm
+++ b/yogstation/code/game/machinery/doors/spacepod.dm
@@ -6,14 +6,14 @@
density = 1
anchored = 1
var/id = 1.0
- CanAtmosPass = ATMOS_PASS_NO
+ can_atmos_pass = ATMOS_PASS_NO
/obj/structure/spacepoddoor/Initialize(mapload)
air_update_turf()
return ..()
/obj/structure/spacepoddoor/Destroy()
- CanAtmosPass = ATMOS_PASS_YES
+ can_atmos_pass = ATMOS_PASS_YES
air_update_turf()
return ..()
diff --git a/yogstation/code/game/objects/effects/landmarks.dm b/yogstation/code/game/objects/effects/landmarks.dm
index 7d6b247ac411..61140dd2e873 100644
--- a/yogstation/code/game/objects/effects/landmarks.dm
+++ b/yogstation/code/game/objects/effects/landmarks.dm
@@ -37,15 +37,13 @@ GLOBAL_LIST_EMPTY(chosen_station_templates)
var/list/template_names = list()
/// Whether or not we can choose templates that have already been chosen
var/unique = FALSE
- layer = BULLET_HOLE_LAYER
-/obj/effect/landmark/stationroom/New()
- ..()
+/obj/effect/landmark/stationroom/Initialize(mapload)
+ . = ..()
GLOB.stationroom_landmarks += src
/obj/effect/landmark/stationroom/Destroy()
- if(src in GLOB.stationroom_landmarks)
- GLOB.stationroom_landmarks -= src
+ GLOB.stationroom_landmarks -= src
return ..()
/obj/effect/landmark/stationroom/proc/load(template_name)
@@ -95,7 +93,10 @@ GLOBAL_LIST_EMPTY(chosen_station_templates)
return chosen_template
/obj/effect/landmark/stationroom/box/bar
- template_names = list("Bar Trek", "Bar Spacious", "Bar Box", "Bar Casino", "Bar Citadel", "Bar Conveyor", "Bar Diner", "Bar Disco", "Bar Purple", "Bar Cheese", "Bar Clock", "Bar Arcade")
+ template_names = list(
+ "Bar Trek", "Bar Spacious", "Bar Box", "Bar Casino", "Bar Citadel",
+ "Bar Conveyor", "Bar Diner", "Bar Disco", "Bar Purple", "Bar Cheese",
+ "Bar Clock", "Bar Arcade")
/obj/effect/landmark/stationroom/box/bar/load(template_name)
GLOB.stationroom_landmarks -= src
@@ -109,7 +110,7 @@ GLOBAL_LIST_EMPTY(chosen_station_templates)
return TRUE
/obj/effect/landmark/stationroom/box/engine
- template_names = list("Engine SM" = 50, "Engine Singulo And Tesla" = 30, "Engine Nuclear Reactor" = 20)
+ template_names = list("Engine SM" = 40, "Engine Singulo And Tesla" = 20, "Engine Nuclear Reactor" = 20,"Engine TEG" = 20)
/obj/effect/landmark/stationroom/box/engine/choose()
. = ..()
@@ -123,6 +124,8 @@ GLOBAL_LIST_EMPTY(chosen_station_templates)
return . //We let the normal choose() do the work if we want to have all of them in play
if(4)
return "Engine Nuclear Reactor"
+ if(5)
+ return "Engine TEG"
/obj/effect/landmark/stationroom/box/testingsite
@@ -148,7 +151,7 @@ GLOBAL_LIST_EMPTY(chosen_station_templates)
return TRUE
/obj/effect/landmark/stationroom/meta/engine
- template_names = list("Meta SM" = 25, "Meta Nuclear Reactor" = 75) // tesla is loud as fuck and singulo doesn't make sense, so SM/reactor only
+ template_names = list("Meta SM" = 25, "Meta Nuclear Reactor" = 45, "Meta TEG" = 25) // tesla is loud as fuck and singulo doesn't make sense, so SM/reactor only
/obj/effect/landmark/stationroom/meta/engine/choose()
. = ..()
@@ -162,6 +165,8 @@ GLOBAL_LIST_EMPTY(chosen_station_templates)
return . //We let the normal choose() do the work if we want to have all of them in play
if(4)
return "Meta Nuclear Reactor"
+ if(5)
+ return "Meta TEG"
/obj/effect/landmark/stationroom/maint/
diff --git a/yogstation/code/game/objects/items/fishing/bait.dm b/yogstation/code/game/objects/items/fishing/bait.dm
index 54e4abf3d3f8..d0c64df32657 100644
--- a/yogstation/code/game/objects/items/fishing/bait.dm
+++ b/yogstation/code/game/objects/items/fishing/bait.dm
@@ -1,6 +1,6 @@
/obj/item/reagent_containers/food/snacks/bait
name = "development bait"
- desc = "if you see this, get help"
+ desc = "If you see this, get help."
icon = 'yogstation/icons/obj/fishing/fishing.dmi'
icon_state = "bait_worm"
tastes = list("sour, rotten water" = 1)
diff --git a/yogstation/code/game/objects/items/implants/implant_infiltrator.dm b/yogstation/code/game/objects/items/implants/implant_infiltrator.dm
index dbfa8dedf85d..128185d91942 100644
--- a/yogstation/code/game/objects/items/implants/implant_infiltrator.dm
+++ b/yogstation/code/game/objects/items/implants/implant_infiltrator.dm
@@ -137,7 +137,7 @@
/datum/status_effect/infiltrator_pinpointer/New()
. = ..()
- scan_target = SSshuttle.getShuttle("syndicatecutter")
+ //scan_target = SSshuttle.getShuttle("syndicatecutter")
/datum/status_effect/infiltrator_pinpointer/proc/point_to_target() //If we found what we're looking for, show the distance and direction
linked_alert.cut_overlays()
diff --git a/yogstation/code/game/objects/items/stacks/dilithiumcrystal.dm b/yogstation/code/game/objects/items/stacks/dilithiumcrystal.dm
index bb2e7c4eca0d..39b401f4a708 100644
--- a/yogstation/code/game/objects/items/stacks/dilithiumcrystal.dm
+++ b/yogstation/code/game/objects/items/stacks/dilithiumcrystal.dm
@@ -51,7 +51,7 @@
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/stack/sheet/dilithium_crystal/attack_hand(mob/user)
if(user.get_inactive_held_item() == src)
- if(zero_amount())
+ if(is_zero_amount(delete_if_zero = TRUE))
return
var/BC = new crystal_type(src)
user.put_in_hands(BC)
diff --git a/yogstation/code/game/objects/items/stacks/tiles/tile_types.dm b/yogstation/code/game/objects/items/stacks/tiles/tile_types.dm
index 64253c07ebf7..967786421a71 100644
--- a/yogstation/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/yogstation/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -10,33 +10,3 @@
/obj/item/stack/tile/ballpit/loaded
amount = 30
-
-/obj/item/stack/tile/carpet/green
- name = "green carpet"
- icon = 'yogstation/icons/obj/tiles.dmi'
- icon_state = "tile-carpet-green"
- item_state = "tile-carpet-green"
- turf_type = /turf/open/floor/carpet/green
-
-/obj/item/stack/tile/carpet/green/fifty
- amount = 50
-
-/obj/item/stack/tile/carpet/purple
- name = "purple carpet"
- icon = 'yogstation/icons/obj/tiles.dmi'
- icon_state = "tile-carpet-purple"
- item_state = "tile-carpet-purple"
- turf_type = /turf/open/floor/carpet/purple
-
-/obj/item/stack/tile/carpet/purple/fifty
- amount = 50
-
-/obj/item/stack/tile/carpet/blue
- name = "blue carpet"
- icon = 'yogstation/icons/obj/tiles.dmi'
- icon_state = "tile-carpet-blue"
- item_state = "tile-carpet-blue"
- turf_type = /turf/open/floor/carpet/blue
-
-/obj/item/stack/tile/carpet/blue/fifty
- amount = 50
\ No newline at end of file
diff --git a/yogstation/code/game/objects/items/storage/backpack.dm b/yogstation/code/game/objects/items/storage/backpack.dm
index 9cc95f1eef4d..1cf6689bf5f8 100644
--- a/yogstation/code/game/objects/items/storage/backpack.dm
+++ b/yogstation/code/game/objects/items/storage/backpack.dm
@@ -44,7 +44,8 @@
var/obj/item/storage/backpack/holding/twin = new(loc)
var/datum/component/storage/old_other_storage = twin.GetComponent(/datum/component/storage)
- old_other_storage.RemoveComponent()
+ if(old_other_storage)
+ qdel(old_other_storage)
var/datum/component/storage/this_storage = GetComponent(/datum/component/storage)
var/datum/component/storage/twin_storage = twin.AddComponent(/datum/component/storage/bluespace/bag_of_holding, this_storage.master()) // add a slave storage component
twin_storage.allow_big_nesting = TRUE
@@ -85,7 +86,7 @@
var/obj/item/storage/backpack/holding/m_obj = new_master.parent
var/datum/component/storage/m_storage = m_obj.GetComponent(/datum/component/storage)
if(m_storage)
- m_storage.RemoveComponent()
+ qdel(m_storage)
m_storage = m_obj.AddComponent(m_obj.component_type)
m_storage.allow_big_nesting = TRUE
m_storage.max_w_class = WEIGHT_CLASS_GIGANTIC
@@ -95,7 +96,7 @@
slave.change_master(m_storage)
for(var/obj/item/I in src)
I.forceMove(m_obj)
- STR.RemoveComponent()
+ qdel(STR)
if(dump && get_turf(src))
for(var/obj/item/I in src)
I.forceMove(get_turf(src))
@@ -106,7 +107,7 @@
/obj/item/storage/backpack/holding/proc/fuck_up(mob/living/user)
var/turf/loccheck = get_turf(src)
- if(is_reebe(loccheck.z) || istype(loccheck.loc, /area/fabric_of_reality))
+ if(is_reebe(loccheck.z) || istype(loccheck.loc, /area/centcom/fabric_of_reality))
qdel(src)
return
playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1)
@@ -123,8 +124,8 @@
M.visible_message(span_danger("The bluespace collapse crushes the air towards it, pulling [M] towards the ground..."))
M.Paralyze(5, TRUE, TRUE) //Overrides stun absorbs.
T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic)
- for(var/fabricarea in get_areas(/area/fabric_of_reality))
- var/area/fabric_of_reality/R = fabricarea
+ for(var/fabricarea in get_areas(/area/centcom/fabric_of_reality))
+ var/area/centcom/fabric_of_reality/R = fabricarea
R.origin = loccheck
for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders)
ladder.ActivateAlmonds()
diff --git a/yogstation/code/game/objects/items/toys.dm b/yogstation/code/game/objects/items/toys.dm
index 69e6e04e25b0..abf7cabfd555 100644
--- a/yogstation/code/game/objects/items/toys.dm
+++ b/yogstation/code/game/objects/items/toys.dm
@@ -77,6 +77,9 @@
returning = TRUE
. = ..()
+/obj/item/toy/boomerang/violent
+ throwforce = 10
+
/obj/item/toy/frisbee
name = "frisbee"
desc = "Comes further in life than you."
diff --git a/yogstation/code/game/objects/structures/bar_stuff/bar_stuff.dm b/yogstation/code/game/objects/structures/bar_stuff/bar_stuff.dm
index 9cf4a684cbf4..a516fed1e226 100644
--- a/yogstation/code/game/objects/structures/bar_stuff/bar_stuff.dm
+++ b/yogstation/code/game/objects/structures/bar_stuff/bar_stuff.dm
@@ -49,7 +49,8 @@ turf/open/floor/plasteel/ameridiner
desc = "A counter with a red and black motif."
icon = 'yogstation/icons/obj/smooth_structures/ameritable.dmi'
icon_state = "table"
- smooth = SMOOTH_FALSE
+ smoothing_flags = NONE
+ smoothing_groups = null
canSmoothWith = null
/obj/structure/table/american/end
diff --git a/yogstation/code/game/objects/structures/tables_racks.dm b/yogstation/code/game/objects/structures/tables_racks.dm
index 01d65350de98..ef7917503157 100644
--- a/yogstation/code/game/objects/structures/tables_racks.dm
+++ b/yogstation/code/game/objects/structures/tables_racks.dm
@@ -9,7 +9,8 @@
buildstack = /obj/item/stack/sheet/mineral/bananium
framestackamount = 1
buildstackamount = 1
- canSmoothWith = list(/obj/structure/table/bananium)
+ smoothing_groups = SMOOTH_GROUP_BANANIUM_TABLES //Don't smooth with SMOOTH_GROUP_TABLES
+ canSmoothWith = SMOOTH_GROUP_BANANIUM_TABLES
var/spam_flag = 0
/obj/structure/table/bananium/attackby(obj/item/W, mob/user, params)
@@ -44,4 +45,4 @@
icon_state = "minibar_left"
/obj/structure/rack/skeletal/right
- icon_state = "minibar_right"
\ No newline at end of file
+ icon_state = "minibar_right"
diff --git a/yogstation/code/game/objects/structures/window.dm b/yogstation/code/game/objects/structures/window.dm
index dc3bee66c1e1..2441012f083e 100644
--- a/yogstation/code/game/objects/structures/window.dm
+++ b/yogstation/code/game/objects/structures/window.dm
@@ -50,9 +50,10 @@
max_integrity = 50
fulltile = TRUE
flags_1 = PREVENT_CLICK_UNDER_1
- smooth = SMOOTH_TRUE
- canSmoothWith = list(/obj/structure/window/fulltile, /obj/structure/window/reinforced/fulltile, /obj/structure/window/reinforced/tinted/fulltile, /obj/structure/window/plasma/fulltile, /obj/structure/window/plasma/reinforced/fulltile, /obj/structure/window/bananium/fulltile)
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
glass_amount = 2
/obj/structure/window/bananium/fulltile/unanchored
- anchored = FALSE
\ No newline at end of file
+ anchored = FALSE
diff --git a/yogstation/code/game/turfs/change_turf.dm b/yogstation/code/game/turfs/change_turf.dm
index 45ea3eca74db..50357fb4e250 100644
--- a/yogstation/code/game/turfs/change_turf.dm
+++ b/yogstation/code/game/turfs/change_turf.dm
@@ -5,4 +5,4 @@
if(islist(baseturfs))
return ChangeTurf(baseturfs[1],baseturfs[1],flags)
else
- return ChangeTurf(baseturfs, baseturfs, flags) // The bottom baseturf will never go away
\ No newline at end of file
+ return ChangeTurf(baseturfs, baseturfs, flags) // The bottom baseturf will never go away
diff --git a/yogstation/code/game/turfs/simulated/floor/fancy_floor.dm b/yogstation/code/game/turfs/simulated/floor/fancy_floor.dm
index 72f5c3483f84..da2aae573894 100644
--- a/yogstation/code/game/turfs/simulated/floor/fancy_floor.dm
+++ b/yogstation/code/game/turfs/simulated/floor/fancy_floor.dm
@@ -1,24 +1,10 @@
/turf/open/floor/ballpit
desc = "A bunch of balls compressed together into a tile. Fun for the whole family!"
- smooth = SMOOTH_TRUE | SMOOTH_BORDER | SMOOTH_MORE
- canSmoothWith = list(/turf/open/floor/ballpit)
icon = 'yogstation/icons/turf/floors/ballpit_smooth.dmi'
icon_state = "smooth"
-
-/turf/open/floor/carpet/purple
- icon = 'goon/icons/turfs/carpet_purple.dmi'
- floor_tile = /obj/item/stack/tile/carpet/purple
- canSmoothWith = list(/turf/open/floor/carpet/purple)
-
-/turf/open/floor/carpet/green
- icon = 'goon/icons/turfs/carpet_green.dmi'
- floor_tile = /obj/item/stack/tile/carpet/green
- canSmoothWith = list(/turf/open/floor/carpet/green)
-
-/turf/open/floor/carpet/blue
- icon = 'goon/icons/turfs/carpet_blue.dmi'
- floor_tile = /obj/item/stack/tile/carpet/blue
- canSmoothWith = list(/turf/open/floor/carpet/blue)
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ smoothing_groups = SMOOTH_GROUP_TURF_BALLPIT
+ canSmoothWith = SMOOTH_GROUP_TURF_BALLPIT
/turf/open/floor/plasteel/stairs/goon
icon = 'goon/icons/turfs/floors.dmi'
@@ -26,53 +12,72 @@
/turf/open/floor/plasteel/stairs/goon/stairs_alone
icon_state = "stairs_alone"
+ base_icon_state = "stairs_alone"
/turf/open/floor/plasteel/stairs/goon/stairs_wide
- icon_state ="stairs_wide"
+ icon_state = "stairs_wide"
+ base_icon_state = "stairs_wide"
/turf/open/floor/plasteel/stairs/goon/stairs2_wide
- icon_state ="stairs2_wide"
+ icon_state = "stairs2_wide"
+ base_icon_state = "stairs2_wide"
/turf/open/floor/plasteel/stairs/goon/stairs_middle
- icon_state ="stairs_middle"
+ icon_state = "stairs_middle"
+ base_icon_state = "stairs_middle"
/turf/open/floor/plasteel/stairs/goon/white_stairs_alone
- icon_state ="medstairs_alone"
+ icon_state = "medstairs_alone"
+ base_icon_state = "medstairs_alone"
/turf/open/floor/plasteel/stairs/goon/white_stairs_wide
- icon_state ="medstairs_wide"
+ icon_state = "medstairs_wide"
+ base_icon_state = "medstairs_wide"
/turf/open/floor/plasteel/stairs/goon/white_stairs_wide2
- icon_state ="medstairs2_wide"
+ icon_state = "medstairs2_wide"
+ base_icon_state = "medstairs2_wide"
/turf/open/floor/plasteel/stairs/goon/white_stairs_middle
- icon_state ="medstairs_middle"
+ icon_state = "medstairs_middle"
+ base_icon_state = "medstairs_middle"
/turf/open/floor/plasteel/stairs/goon/wood_stairs_alone
- icon_state ="woodstairs_alone"
+ icon_state = "woodstairs_alone"
+ base_icon_state = "woodstairs_alone"
/turf/open/floor/plasteel/stairs/goon/wood_stairs_middle
- icon_state ="woodstairs_middle"
+ icon_state = "woodstairs_middle"
+ base_icon_state = "woodstairs_middle"
/turf/open/floor/plasteel/stairs/goon/wood_stairs_wide
- icon_state ="woodstairs_wide"
+ icon_state = "woodstairs_wide"
+ base_icon_state = "woodstairs_wide"
/turf/open/floor/plasteel/stairs/goon/wood_stairs_wide2
- icon_state ="woodstairs2_wide"
+ icon_state = "woodstairs2_wide"
+ base_icon_state = "woodstairs2_wide"
/turf/open/floor/plasteel/stairs/goon/dark_stairs_alone
- icon_state ="darkstairs_alone"
+ icon_state = "darkstairs_alone"
+ base_icon_state = "darkstairs_alone"
/turf/open/floor/plasteel/stairs/goon/dark_stairs_wide
- icon_state ="darkstairs_wide"
+ icon_state = "darkstairs_wide"
+ base_icon_state = "darkstairs_wide"
/turf/open/floor/plasteel/stairs/goon/dark_stairs_wide2
- icon_state ="darkstairs2_wide"
+ icon_state = "darkstairs2_wide"
+ base_icon_state = "darkstairs2_wide"
/turf/open/floor/plasteel/stairs/goon/dark_stairs_middle
- icon_state ="darkstairs_middle"
+ icon_state = "darkstairs_middle"
+ base_icon_state = "darkstairs_middle"
+
+/turf/open/floor/wood/jungle
+ initial_gas_mix = JUNGLELAND_DEFAULT_ATMOS
diff --git a/yogstation/code/game/turfs/simulated/minerals.dm b/yogstation/code/game/turfs/simulated/minerals.dm
index 6cb7132a317d..8e0bca9ec9f1 100644
--- a/yogstation/code/game/turfs/simulated/minerals.dm
+++ b/yogstation/code/game/turfs/simulated/minerals.dm
@@ -13,18 +13,21 @@
defer_change = 1
/turf/closed/mineral/dilithium/volcanic/hard
- smooth_icon = 'icons/turf/smoothrocks_hard.dmi'
+ icon = MAP_SWITCH('icons/turf/smoothrocks_hard.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "smoothrocks_hard"
+ base_icon_state = ""
hardness = 2
-/turf/closed/mineral/dilithium/volcanic/hard/harder
- smooth_icon = 'icons/turf/smoothrocks.dmi'
+/turf/closed/mineral/dilithium/volcanic/harder
mineralAmt = 5
color = "#eb9877"
hardness = 3
/turf/closed/mineral/dilithium/ice
environment_type = "snow_cavern"
- smooth_icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon = MAP_SWITCH('icons/turf/walls/icerock_wall.dmi', 'icons/turf/mining.dmi')
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
turf_type = /turf/open/floor/plating/asteroid/snow/ice
baseturfs = /turf/open/floor/plating/asteroid/snow/ice
initial_gas_mix = FROZEN_ATMOS
diff --git a/yogstation/code/modules/admin/admin_verbs.dm b/yogstation/code/modules/admin/admin_verbs.dm
index dce41f6ee272..45db69984ab1 100644
--- a/yogstation/code/modules/admin/admin_verbs.dm
+++ b/yogstation/code/modules/admin/admin_verbs.dm
@@ -99,3 +99,15 @@
data += "